diff --git a/plugin.audio.ampache/addon.xml b/plugin.audio.ampache/addon.xml index 10fe0b3ea..105aa6460 100644 --- a/plugin.audio.ampache/addon.xml +++ b/plugin.audio.ampache/addon.xml @@ -1,5 +1,5 @@ - + @@ -14,7 +14,9 @@ Streame Musik von der Ampache XML-API Streaming Musicale per l'XML-API di Ampache Retransmitir música a través de la XML-API de Ampache + Streama musik från Ampache XML-API A web based audio/video streaming application and file manager allowing you to access your music and videos from anywhere, using almost any internet enabled device. + En webbaserad applikation för strömning av ljud och video samt filhantering som gör att du kan komma åt din musik och dina videor var du än befinner dig, med nästan alla internetanslutna enheter. all https://forum.kodi.tv/showthread.php?tid=230736 https://ampache.org/ diff --git a/plugin.audio.ampache/resources/language/resource.language.sv_se/strings.po b/plugin.audio.ampache/resources/language/resource.language.sv_se/strings.po new file mode 100644 index 000000000..8a0ef68fa --- /dev/null +++ b/plugin.audio.ampache/resources/language/resource.language.sv_se/strings.po @@ -0,0 +1,500 @@ +# Kodi Media Center language file +# Addon Name: Ampache plugin Swedish translation +# Addon id: plugin.audio.ampache +# Addon Provider: lusum +msgid "" +msgstr "" +"Project-Id-Version: XBMC Addons Ampache Swedish\n" +"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n" +"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: 2025-07-06 14:02+0200\n" +"Last-Translator: Daniel Nylander \n" +"Language-Team: sv\n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.6\n" + +# settings +msgctxt "#30001" +msgid "General" +msgstr "Allmänt" + +msgctxt "#30002" +msgid "Servers" +msgstr "Servrar" + +msgctxt "#30010" +msgid "Random Items" +msgstr "Slumpmässiga objekt" + +msgctxt "#30011" +msgid "Disable Ssl certs" +msgstr "Inaktivera SSL-certifikat" + +msgctxt "#30012" +msgid "Api Version" +msgstr "API-version" + +msgctxt "#30013" +msgid "Old search gui (web controller friendly)" +msgstr "Gamla sökgränssnittet (webbkontrollvänligt)" + +msgctxt "#30014" +msgid "Enable images on long lists" +msgstr "Aktivera bilder på långa listor" + +msgctxt "#30015" +msgid "Auto fullscreen" +msgstr "Automatisk helskärm" + +msgctxt "#30016" +msgid "Clean image cache" +msgstr "Töm bildcachen" + +msgctxt "#30020" +msgid "Add Server" +msgstr "Lägg till server" + +msgctxt "#30021" +msgid "Remove Server" +msgstr "Ta bort server" + +msgctxt "#30022" +msgid "Modify Server" +msgstr "Ändra server" + +msgctxt "#30023" +msgid "Switch Server" +msgstr "Byt server" + +# code +msgctxt "#30101" +msgid "Search..." +msgstr "Sök…" + +msgctxt "#30102" +msgid "Quick access..." +msgstr "Snabbåtkomst..." + +msgctxt "#30103" +msgid "Explore..." +msgstr "Utforska..." + +msgctxt "#30104" +msgid "Library..." +msgstr "Bibliotek..." + +msgctxt "#30105" +msgid "Settings" +msgstr "Inställningar" + +msgctxt "#30106" +msgid "Artist" +msgstr "Artist" + +msgctxt "#30107" +msgid "Album" +msgstr "Album" + +msgctxt "#30108" +msgid "Song" +msgstr "Låt" + +msgctxt "#30109" +msgid "Playlist" +msgstr "Spellista" + +msgctxt "#30110" +msgid "All" +msgstr "Alla" + +msgctxt "#30111" +msgid "Tag" +msgstr "Tagg" + +msgctxt "#30112" +msgid "Artist tag" +msgstr "Artisttagg" + +msgctxt "#30113" +msgid "Album tag" +msgstr "Albumtagg" + +msgctxt "#30114" +msgid "Song tag" +msgstr "Låttagg" + +msgctxt "#30115" +msgid "Artists" +msgstr "Artister" + +msgctxt "#30116" +msgid "Albums" +msgstr "Album" + +msgctxt "#30117" +msgid "Songs" +msgstr "Låtar" + +msgctxt "#30118" +msgid "Playlists" +msgstr "Spellistor" + +msgctxt "#30119" +msgid "Tags" +msgstr "Taggar" + +msgctxt "#30120" +msgid "Search Artists..." +msgstr "Sök efter artister..." + +msgctxt "#30121" +msgid "Search Albums..." +msgstr "Sök efter album..." + +msgctxt "#30122" +msgid "Search Songs..." +msgstr "Sök efter låtar..." + +msgctxt "#30123" +msgid "Search Playlists..." +msgstr "Sök i spellistor..." + +msgctxt "#30124" +msgid "Search All..." +msgstr "Sök alla..." + +msgctxt "#30125" +msgid "Search Tags..." +msgstr "Sök i taggar..." + +msgctxt "#30126" +msgid "Recently Added Artists..." +msgstr "Nyligen tillagda artister..." + +msgctxt "#30127" +msgid "Recently Added Albums..." +msgstr "Nyligen tillagda album..." + +msgctxt "#30128" +msgid "Recently Added Songs..." +msgstr "Nyligen tillagda låtar..." + +msgctxt "#30129" +msgid "Recently Added Playlists..." +msgstr "Nyligen tillagda spellistor..." + +msgctxt "#30130" +msgid "Last Update" +msgstr "Senast uppdaterad" + +msgctxt "#30131" +msgid "1 Week" +msgstr "1 vecka" + +msgctxt "#30132" +msgid "1 Month" +msgstr "1 månad" + +msgctxt "#30133" +msgid "3 Months" +msgstr "3 månander" + +msgctxt "#30134" +msgid "Random Artists..." +msgstr "Slumpmässiga artister..." + +msgctxt "#30135" +msgid "Random Albums..." +msgstr "Slumpmässiga album..." + +msgctxt "#30136" +msgid "Random Songs..." +msgstr "Slumpmässiga låtar..." + +msgctxt "#30137" +msgid "Random Playlists..." +msgstr "Slumpmässiga spellistor..." + +msgctxt "#30138" +msgid "Show artist from this song" +msgstr "Visa artisten från denna låt" + +msgctxt "#30139" +msgid "Show album from this song" +msgstr "Visa album från denna låt" + +msgctxt "#30140" +msgid "Search all songs with this title" +msgstr "Sök efter alla låtar med denna titel" + +msgctxt "#30141" +msgid "Show all albums from artist" +msgstr "Visa alla album från artisten" + +msgctxt "#30142" +msgid "Artist tags..." +msgstr "Artisttaggar..." + +msgctxt "#30143" +msgid "Album tags..." +msgstr "Albumtaggar..." + +msgctxt "#30144" +msgid "Song tags..." +msgstr "Låttaggar..." + +msgctxt "#30145" +msgid "Recently Added..." +msgstr "Nyligen tillagda..." + +msgctxt "#30146" +msgid "Random..." +msgstr "Slumpmässiga..." + +msgctxt "#30147" +msgid "Server playlist..." +msgstr "Serverns spellista..." + +msgctxt "#30148" +msgid "Highest Rated..." +msgstr "Högst rankade..." + +msgctxt "#30149" +msgid "Highest Rated Artists..." +msgstr "Högst rankade artister..." + +msgctxt "#30150" +msgid "Highest Rated Albums..." +msgstr "Högst rankade album..." + +msgctxt "#30151" +msgid "Highest Rated Songs..." +msgstr "Högst rankade låtar..." + +msgctxt "#30152" +msgid "Frequent Artists..." +msgstr "Frekventa artister..." + +msgctxt "#30153" +msgid "Frequent Albums..." +msgstr "Frekventa album..." + +msgctxt "#30154" +msgid "Frequent Songs..." +msgstr "Frekventa låtar..." + +msgctxt "#30155" +msgid "Flagged Artists..." +msgstr "Flaggade artister..." + +msgctxt "#30156" +msgid "Flagged Albums..." +msgstr "Flaggade album..." + +msgctxt "#30157" +msgid "Flagged Songs..." +msgstr "Flaggade låtar..." + +msgctxt "#30158" +msgid "Forgotten Artists..." +msgstr "Bortglömda artister..." + +msgctxt "#30159" +msgid "Forgotten Albums..." +msgstr "Bortglömda album..." + +msgctxt "#30160" +msgid "Forgotten Songs..." +msgstr "Bortglömda låtar..." + +msgctxt "#30161" +msgid "Newest Artists..." +msgstr "Senaste artisterna..." + +msgctxt "#30162" +msgid "Newest Albums..." +msgstr "Senaste albumen..." + +msgctxt "#30163" +msgid "Newest Songs..." +msgstr "Senaste låtarna..." + +msgctxt "#30164" +msgid "Frequent..." +msgstr "Frekventa..." + +msgctxt "#30165" +msgid "Flagged..." +msgstr "Flaggade..." + +msgctxt "#30166" +msgid "Forgotten..." +msgstr "Bortglömda..." + +msgctxt "#30167" +msgid "Newest..." +msgstr "Senaste..." + +msgctxt "#30168" +msgid "Modify the data, cancel to exit" +msgstr "Ändra data, avbryt för att avsluta" + +msgctxt "#30169" +msgid "Choose a default server" +msgstr "Välj en standardserver" + +msgctxt "#30170" +msgid "Enter the Server name" +msgstr "Ange servernamnet" + +msgctxt "#30171" +msgid "Enter the url of the server" +msgstr "Ange URLen till servern" + +msgctxt "#30173" +msgid "Do you want to use an api-key?" +msgstr "Vill du använda en api-nyckel?" + +msgctxt "#30174" +msgid "Enter the Api key" +msgstr "Ange API-nyckeln" + +msgctxt "#30175" +msgid "Enter the username" +msgstr "Ange användarnamn" + +msgctxt "#30177" +msgid "The server needs a password?" +msgstr "Behöver servern ett lösenord?" + +msgctxt "#30178" +msgid "Enter the password" +msgstr "Ange lösenordet" + +msgctxt "#30179" +msgid "Choose a server to remove" +msgstr "Välj en server att ta bort" + +msgctxt "#30180" +msgid "Modify a server" +msgstr "Ändra en server" + +msgctxt "#30181" +msgid "Server name" +msgstr "Servernamn" + +msgctxt "#30182" +msgid "Server url" +msgstr "Serverns url" + +msgctxt "#30183" +msgid "Username" +msgstr "Användarnamn" + +msgctxt "#30184" +msgid "Enable password" +msgstr "Aktivera lösenord" + +msgctxt "#30185" +msgid "Password" +msgstr "Lösenord" + +msgctxt "#30186" +msgid "Use api key" +msgstr "Använd api-nyckel" + +msgctxt "#30187" +msgid "Api key" +msgstr "API-nyckel" + +msgctxt "#30188" +msgid "Are you sure?" +msgstr "Är du säker?" + +msgctxt "#30189" +msgid "Ampache plugin" +msgstr "Ampache-tillägg" + +msgctxt "#30190" +msgid "Recently Played Artists..." +msgstr "Nyligen spelade artister..." + +msgctxt "#30191" +msgid "Recently Played Albums..." +msgstr "Nyligen spelade album..." + +msgctxt "#30192" +msgid "Recently Played Songs..." +msgstr "Nyligen spelade låtar..." + +msgctxt "#30193" +msgid "Recently Played..." +msgstr "Nyligen spelade..." + +msgctxt "#30194" +msgid "Next items..." +msgstr "Nästa objekt..." + +msgctxt "#30195" +msgid "Disk" +msgstr "Disk" + +msgctxt "#30197" +msgid "Information" +msgstr "Information" + +msgctxt "#30198" +msgid "Error" +msgstr "Fel" + +msgctxt "#30202" +msgid "Connection Error" +msgstr "Anslutningsfel" + +msgctxt "#30203" +msgid "Connection OK" +msgstr "Anslutning OK" + +msgctxt "#30204" +msgid "Permission error. If you are using Nextcloud don't check api_key box" +msgstr "Behörighetsfel. Om du använder Nextcloud ska du inte markera rutan api_key" + +msgctxt "#30220" +msgid "Video" +msgstr "Video" + +msgctxt "#30221" +msgid "Videos" +msgstr "Videor" + +msgctxt "#30222" +msgid "Search Videos..." +msgstr "Sök efter videor..." + +msgctxt "#30225" +msgid "Podcast" +msgstr "Poddsändning" + +msgctxt "#30226" +msgid "Podcasts" +msgstr "Poddsändningar" + +msgctxt "#30227" +msgid "Search Podcasts..." +msgstr "Sök efter poddsändningar..." + +msgctxt "#30228" +msgid "Live stream" +msgstr "Direktsändning" + +msgctxt "#30229" +msgid "Live streams" +msgstr "Direktsändningar" + +msgctxt "#30230" +msgid "Search Live Streams..." +msgstr "Sök efter direktströmmar..." diff --git a/plugin.audio.ampache/resources/lib/ampache_connect.py b/plugin.audio.ampache/resources/lib/ampache_connect.py index 493847bfc..2d157fc89 100644 --- a/plugin.audio.ampache/resources/lib/ampache_connect.py +++ b/plugin.audio.ampache/resources/lib/ampache_connect.py @@ -17,16 +17,23 @@ from resources.lib import utils as ut from resources.lib.art_clean import clean_settings +# Connection timeout constants +REQUEST_TIMEOUT = 400 +TOKEN_EXPIRE_DELTA = 2400 + class AmpacheConnect(object): class ConnectionError(Exception): pass - + def __init__(self): self._ampache = xbmcaddon.Addon("plugin.audio.ampache") jsStorServer = json_storage.JsonStorage("servers.json") serverStorage = jsStorServer.getData() - self._connectionData = serverStorage["servers"][serverStorage["current_server"]] + try: + self._connectionData = serverStorage["servers"][serverStorage["current_server"]] + except KeyError: + self._connectionData = None #self._connectionData = None self.filter=None self.add=None @@ -38,7 +45,7 @@ def __init__(self): self.id=None self.rating=None #force the latest version on the server - self.version="600001" + self.version="680001" def getBaseUrl(self): return '/server/xml.server.php' @@ -48,8 +55,10 @@ def fillConnectionSettings(self,tree,nTime): token = tree.findtext('auth') version = tree.findtext('api') if not version: - #old api + #old api version = tree.findtext('version') + if not version: + raise self.ConnectionError #setSettings only string or unicode self._ampache.setSetting("api-version",version) self._ampache.setSetting("artists", tree.findtext("artists")) @@ -67,7 +76,7 @@ def fillConnectionSettings(self,tree,nTime): self._ampache.setSetting("add", tree.findtext("add")) self._ampache.setSetting("token", token) #not 24000 seconds ( 6 hours ) , but 2400 ( 40 minutes ) expiration time - self._ampache.setSetting("token-exp", str(nTime+2400)) + self._ampache.setSetting("token-exp", str(nTime+TOKEN_EXPIRE_DELTA)) def getCodeMessError(self,tree): errormess = None @@ -91,6 +100,8 @@ def getCodeMessError(self,tree): return errormess def getHashedPassword(self,timeStamp): + if self._connectionData is None: + raise self.ConnectionError enablePass = self._connectionData["enable_password"] if enablePass: sdf = self._connectionData["password"] @@ -108,6 +119,8 @@ def getHashedPassword(self,timeStamp): return passwordHash def get_user_pwd_login_url(self,nTime): + if self._connectionData is None: + raise self.ConnectionError myTimeStamp = str(nTime) myPassphrase = self.getHashedPassword(myTimeStamp) myURL = self._connectionData["url"] + self.getBaseUrl() + '?action=handshake&auth=' @@ -116,6 +129,8 @@ def get_user_pwd_login_url(self,nTime): return myURL def get_auth_key_login_url(self): + if self._connectionData is None: + raise self.ConnectionError myURL = self._connectionData["url"] + self.getBaseUrl() + '?action=handshake&auth=' myURL += self._connectionData["api_key"] myURL += '&version=' + self.version @@ -128,19 +143,19 @@ def handle_request(self,url): req = urllib.request.Request(url) if ut.strBool_to_bool(ssl_certs_str): if PY2: - response = urllib.request.urlopen(req, timeout=400) + response = urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) else: gcontext = ssl.create_default_context() gcontext.check_hostname = False gcontext.verify_mode = ssl.CERT_NONE - response = urllib.request.urlopen(req, context=gcontext, timeout=400) + response = urllib.request.urlopen(req, context=gcontext, timeout=REQUEST_TIMEOUT) xbmc.log("AmpachePlugin::handle_request: disable ssl certificates",xbmc.LOGDEBUG) else: if PY2: - gcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1) - response = urllib.request.urlopen(req, context=gcontext, timeout=400) + gcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) + response = urllib.request.urlopen(req, context=gcontext, timeout=REQUEST_TIMEOUT) else: - response = urllib.request.urlopen(req, timeout=400) + response = urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) xbmc.log("AmpachePlugin::handle_request: ssl certificates",xbmc.LOGDEBUG) except urllib.error.HTTPError as e: xbmc.log("AmpachePlugin::handle_request: HTTPError " +\ @@ -160,6 +175,8 @@ def handle_request(self,url): return headers,contents def AMPACHECONNECT(self,showok=False): + if self._connectionData is None: + raise self.ConnectionError socket.setdefaulttimeout(3600) nTime = int(time.time()) use_api_key = self._connectionData["use_api_key"] @@ -203,6 +220,7 @@ def AMPACHECONNECT(self,showok=False): self.fillConnectionSettings(tree,nTime) return + #handle request to the xml api that return binary files def ampache_binary_request(self,action): thisURL = self.build_ampache_url(action) @@ -234,6 +252,8 @@ def ampache_http_request(self,action): return tree def build_ampache_url(self,action): + if self._connectionData is None: + raise self.ConnectionError token = self._ampache.getSetting("token") thisURL = self._connectionData["url"] + self.getBaseUrl() + '?action=' + action thisURL += '&auth=' + token diff --git a/plugin.audio.ampache/resources/lib/ampache_monitor.py b/plugin.audio.ampache/resources/lib/ampache_monitor.py index 20cf71e78..7ac2c0547 100644 --- a/plugin.audio.ampache/resources/lib/ampache_monitor.py +++ b/plugin.audio.ampache/resources/lib/ampache_monitor.py @@ -1,17 +1,19 @@ import xbmc import xbmcaddon -#service class +# service class ampache = xbmcaddon.Addon("plugin.audio.ampache") from resources.lib.utils import get_objectId_from_fileURL -class AmpacheMonitor( xbmc.Monitor ): + +class AmpacheMonitor(xbmc.Monitor): + # Monitor for Ampache service events onPlay = False def __init__(self): - xbmc.log( 'AmpacheMonitor::ServiceMonitor called', xbmc.LOGDEBUG) + xbmc.log("AmpacheMonitor::ServiceMonitor called", xbmc.LOGDEBUG) # start mainloop def run(self): @@ -24,27 +26,51 @@ def close(self): pass def onNotification(self, sender, method, data): - #i don't know why i have called monitor.onNotification, but now it - #seems useless - #xbmc.Monitor.onNotification(self, sender, method, data) - xbmc.log('AmpacheMonitor:Notification %s from %s, params: %s' % (method, sender, str(data))) + xbmc.log( + "AmpacheMonitor::onNotification called - method: %s, sender: %s" + % (method, sender), + xbmc.LOGDEBUG, + ) + + if not sender or not method or not data: + xbmc.log("AmpacheMonitor::Invalid notification data", xbmc.LOGWARNING) + return - #a little hack to avoid calling rate every time a song start - if method == 'Player.OnStop': + # a little hack to avoid calling rate every time a song start + if method == "Player.OnStop": self.onPlay = False - if method == 'Player.OnPlay': + xbmc.log("AmpacheMonitor::onPlay status changed to False", xbmc.LOGDEBUG) + elif method == "Player.OnPlay": self.onPlay = True - #called on infoChanged ( rating ) - if method == 'Info.OnChanged' and self.onPlay: - #call setRating + xbmc.log("AmpacheMonitor::onPlay status changed to True", xbmc.LOGDEBUG) + elif method == "Info.OnChanged" and self.onPlay: if xbmc.Player().isPlaying(): try: file_url = xbmc.Player().getPlayingFile() - #it is not our file - if not (get_objectId_from_fileURL( file_url )): + if not file_url: + xbmc.log( + "AmpacheMonitor::No playing file, skipping", xbmc.LOGDEBUG + ) + return + + if not get_objectId_from_fileURL(file_url): + xbmc.log( + "AmpacheMonitor::No object ID found in URL, skipping", + xbmc.LOGDEBUG, + ) return - except: - xbmc.log("AmpacheMonitor::no playing file " , xbmc.LOGDEBUG) - return - xbmc.executebuiltin('RunPlugin(plugin://plugin.audio.ampache/?mode=205)') + xbmc.log( + "AmpacheMonitor::Starting setRating for URL: %s" % file_url, + xbmc.LOGDEBUG, + ) + xbmc.executebuiltin( + "RunPlugin(plugin://plugin.audio.ampache/?mode=205)" + ) + except ValueError as e: + xbmc.log("AmpacheMonitor::Value error: %s" % str(e), xbmc.LOGDEBUG) + except Exception as e: + xbmc.log( + "AmpacheMonitor::Error in onNotification: %s" % repr(e), + xbmc.LOGERROR, + ) diff --git a/plugin.audio.ampache/resources/lib/ampache_plugin.py b/plugin.audio.ampache/resources/lib/ampache_plugin.py index 038532825..04451cc37 100644 --- a/plugin.audio.ampache/resources/lib/ampache_plugin.py +++ b/plugin.audio.ampache/resources/lib/ampache_plugin.py @@ -169,7 +169,8 @@ def fill_tags(elem_type , node, info_tag): duration_str = node.findtext("time") info_tag.setDuration(int(duration_str) if duration_str else 0) year_str = node.findtext("year") - info_tag.setYear(int(year_str) if year_str else None) + if year_str: + info_tag.setYear(int(year_str)) track_num = node.findtext("track") info_tag.setTrack(int(track_num) if track_num else 0) info_tag.setUserRating(rating) @@ -182,7 +183,8 @@ def fill_tags(elem_type , node, info_tag): #xbmc.log("AmpachePlugin::disc_num " + str(disc_num), xbmc.LOGDEBUG) info_tag.setDisc(int(disc_num) if disc_num else 0) year_str = node.findtext("year") - info_tag.setYear(int(year_str) if year_str else None) + if year_str: + info_tag.setYear(int(year_str)) info_tag.setUserRating(rating) elif elem_type == 'artist': @@ -202,20 +204,22 @@ def fill_tags(elem_type , node, info_tag): def getNestedTypeText(node, elem_tag ,elem_type): try: obj_elem = node.find(elem_type) - if obj_elem is not None or obj_elem != '': + if obj_elem is not None and obj_elem != '': obj_tag = obj_elem.findtext(elem_tag) return obj_tag - except: + except Exception as e: + xbmc.log("Error in getNestedTypeText: %s" % str(e), xbmc.LOGERROR) return None return None def getNestedTypeId(node,elem_type): try: obj_elem = node.find(elem_type) - if obj_elem is not None or obj_elem != '': + if obj_elem is not None and obj_elem != '': obj_id = obj_elem.attrib["id"] return obj_id - except: + except Exception as e: + xbmc.log("Error in getNestedTypeId: %s" % str(e), xbmc.LOGERROR) return None return None @@ -229,6 +233,7 @@ def precacheArt(elem,elem_type): return threadList = [] + max_threads = 10 for node in elem.iter(elem_type): if elem_type == "song": art_type = "album" @@ -246,10 +251,15 @@ def precacheArt(elem,elem_type): continue x = threading.Thread(target=art.get_art,args=(object_id,art_type,image_url,)) threadList.append(x) - #start threads - for x in threadList: + #start threads with limit + for i, x in enumerate(threadList): x.start() - #join threads + #join every max_threads threads to avoid blocking UI + if i > 0 and i % max_threads == 0: + for j in range(i - max_threads, i): + if j < len(threadList): + threadList[j].join() + #join remaining threads for x in threadList: x.join() @@ -389,7 +399,7 @@ def addPlayLinks(elem, elem_type): "Container.Update(%s?title=%s&mode=%s&submode=%s)" % ( sys.argv[0],urllib.parse.quote_plus(object_title), str(AmpMode.SONGS), str(AmpSubmode.DO_SEARCH_ITEM) ) ) ) - if cm != []: + if cm: liz.addContextMenuItems(cm) elif elem_type == "podcast_episode": info_tag = liz.getMusicInfoTag() @@ -408,7 +418,7 @@ def addPlayLinks(elem, elem_type): #The function that actually plays an Ampache URL by using setResolvedUrl def play_track(url): - if url == None: + if url is None: xbmc.log("AmpachePlugin::play_track url null", xbmc.LOGINFO ) return @@ -474,7 +484,7 @@ def addItems( object_type, elem, object_subtype=None,precache=True): return def get_all(object_type, mode ,offset=None): - if offset == None: + if offset is None: offset=0 try: limit = int(ampache.getSetting(object_type)) @@ -515,7 +525,7 @@ def get_items(object_type, object_id=None, add=None,\ if object_id: xbmc.log("AmpachePlugin::get_items: object_id " + object_id, xbmc.LOGDEBUG) - if limit == None: + if limit is None: limit = int(ampache.getSetting(object_type)) #default: object_type is the action,otherwise see the if list below @@ -571,41 +581,54 @@ def get_items(object_type, object_id=None, add=None,\ elem = ampConn.ampache_http_request(action) addItems( object_type, elem, object_subtype) - except: + except Exception as e: + xbmc.log("AmpachePlugin::get_items: Generic Error " +\ + repr(e),xbmc.LOGDEBUG) return def setRating(): try: file_url = xbmc.Player().getPlayingFile() - xbmc.log("AmpachePlugin::setRating url " + file_url , xbmc.LOGDEBUG) - except: - xbmc.log("AmpachePlugin::no playing file " , xbmc.LOGDEBUG) + except Exception: + xbmc.log("AmpachePlugin::setRating: Error getting playing file", xbmc.LOGERROR) return - - object_id = ut.get_objectId_from_fileURL( file_url ) - if not object_id: + + if not file_url: + xbmc.log("AmpachePlugin::setRating: No playing file", xbmc.LOGDEBUG) return - rating = xbmc.getInfoLabel('MusicPlayer.UserRating') - if rating == "": - rating = "0" - - xbmc.log("AmpachePlugin::setRating, user Rating " + rating , xbmc.LOGDEBUG) - #converts from five stats ampache rating to ten stars kodi rating - amp_rating = math.ceil(int(rating)/2.0) - + + xbmc.log("AmpachePlugin::setRating url " + file_url , xbmc.LOGDEBUG) + try: + object_id = ut.get_objectId_from_fileURL(file_url) + if not object_id: + raise ValueError("No object ID found in URL") + + rating = xbmc.getInfoLabel('MusicPlayer.UserRating') + #used to remove a rating from a song, it is not an error + if not rating: + rating="0" + + xbmc.log("AmpachePlugin::setRating, user Rating " + rating , xbmc.LOGDEBUG) + #converts from five stats ampache rating to ten stars kodi rating + amp_rating = math.ceil(int(rating)/2.0) + ampConn = ampache_connect.AmpacheConnect() - action = "rate" ampConn.id = object_id ampConn.type = "song" ampConn.rating = str(amp_rating) - ampConn.ampache_http_request(action) - except: - #do nothing - return + + except (ampache_connect.AmpacheConnect.ConnectionError) as e: + xbmc.log("AmpachePlugin::setRating - Connection error: " + repr(e), xbmc.LOGWARNING) + except ValueError as e: + xbmc.log("AmpachePlugin::setRating - Validation error: " + str(e), xbmc.LOGDEBUG) + except Exception as e: + xbmc.log("AmpachePlugin::setRating - Generic error: " + repr(e), xbmc.LOGERROR) + + return def do_search(object_type,object_subtype=None,thisFilter=None): """ @@ -651,8 +674,11 @@ def get_recent(object_type,submode,object_subtype=None): if submode == AmpSubmode.LAST_UPDATE: update = ampache.getSetting("add") - xbmc.log(update[:10],xbmc.LOGINFO) - get_items(object_type=object_type,add=update[:10],object_subtype=object_subtype) + if update and len(update) >= 10: + xbmc.log(update[:10],xbmc.LOGINFO) + get_items(object_type=object_type,add=update[:10],object_subtype=object_subtype) + else: + xbmc.log("AmpachePlugin::get_recent - no update setting", xbmc.LOGDEBUG) elif submode == AmpSubmode.WEEK_UPDATE: get_items(object_type=object_type,add=ut.get_time(-7),object_subtype=object_subtype) elif submode == AmpSubmode.MONTH_UPDATE: @@ -683,8 +709,8 @@ def get_random(object_type, num_items): ampConn.limit = 1 elem = ampConn.ampache_http_request(action) addItems( object_type, elem,precache=False) - except: - pass + except Exception as e: + xbmc.log("AmpachePlugin::get_random - error getting item %s: %s" % (item_id, str(e)), xbmc.LOGDEBUG) def switchFromMusicPlaylist(addon_url, mode, submode, object_id=None, title=None): """ @@ -817,8 +843,8 @@ def Main(): servers_manager.initializeServer() ampacheConnect = ampache_connect.AmpacheConnect() ampacheConnect.AMPACHECONNECT() - except: - pass + except Exception as e: + xbmc.log("AmpachePlugin::Main - error connecting to server: %s" % str(e), xbmc.LOGDEBUG) apiVersion = int(ampache.getSetting("api-version")) diff --git a/plugin.audio.ampache/resources/lib/art.py b/plugin.audio.ampache/resources/lib/art.py index 178ff9923..20e4a9170 100644 --- a/plugin.audio.ampache/resources/lib/art.py +++ b/plugin.audio.ampache/resources/lib/art.py @@ -118,6 +118,8 @@ def clean_cache_art(isDialog=False): for c_type in cacheTypes: cacheDirType = os.path.join( cacheDir , c_type ) + if not os.path.isdir(cacheDirType): + continue for currentFile in os.listdir(cacheDirType): #xbmc.log("Clear Cache Art " + str(currentFile),xbmc.LOGDEBUG) pathDel = os.path.join( cacheDirType, currentFile) diff --git a/plugin.audio.ampache/resources/lib/art_clean.py b/plugin.audio.ampache/resources/lib/art_clean.py index 85c7a4e84..f983ef26e 100644 --- a/plugin.audio.ampache/resources/lib/art_clean.py +++ b/plugin.audio.ampache/resources/lib/art_clean.py @@ -1,24 +1,29 @@ from future.utils import PY2 import os -import xbmc,xbmcaddon +import xbmc, xbmcaddon import xbmcvfs from datetime import datetime, timedelta -#split the art library to not have import problems, as the function is used by -#service and the main plugin -#library used both by service and main plugin, DO NOT INCLUDE OTHER LOCAL -#LIBRARIES +# split the art library to not have import problems, as the function is used by +# service and the main plugin +# library used both by service and main plugin, DO NOT INCLUDE OTHER LOCAL +# LIBRARIES ampache = xbmcaddon.Addon("plugin.audio.ampache") -#different functions in kodi 19 (python3) and kodi 18 (python2) +# Cache configuration constants +ART_CACHE_EXPIRY_DAYS = 30 +ART_CACHE_TYPES = ["album", "artist", "song", "podcast", "playlist"] + +# different functions in kodi 19 (python3) and kodi 18 (python2) if PY2: - user_dir = xbmc.translatePath( ampache.getAddonInfo('profile')) - user_dir = user_dir.decode('utf-8') + user_dir = xbmc.translatePath(ampache.getAddonInfo("profile")) + user_dir = user_dir.decode("utf-8") else: - user_dir = xbmcvfs.translatePath( ampache.getAddonInfo('profile')) -user_mediaDir = os.path.join( user_dir , 'media' ) -cacheDir = os.path.join( user_mediaDir , 'cache' ) + user_dir = xbmcvfs.translatePath(ampache.getAddonInfo("profile")) +user_mediaDir = os.path.join(user_dir, "media") +cacheDir = os.path.join(user_mediaDir, "cache") + def clean_settings(): ampache.setSetting("session_expire", "") @@ -33,9 +38,10 @@ def clean_settings(): ampache.setSetting("podcasts", "") ampache.setSetting("live_streams", "") - #hack to force the creation of profile directory if don't exists + # hack to force the creation of profile directory if don't exists if not os.path.isdir(user_dir): - ampache.setSetting("api-version","350001") + ampache.setSetting("api-version", "350001") + def is_expired(cache_file_path: str) -> bool: """Check if the cache file has expired (older than one month).""" @@ -48,42 +54,55 @@ def is_expired(cache_file_path: str) -> bool: last_modified = datetime.fromtimestamp(mod_time) # Calculate if more than a month has passed since modification - expiration_duration = timedelta(days=30) # One month + expiration_duration = timedelta( + days=ART_CACHE_EXPIRY_DAYS + ) # ART_CACHE_EXPIRY_DAYS return (now - last_modified) > expiration_duration except FileNotFoundError: return True # Treat missing files as expired -def delete_expired_files(): - cacheTypes = ["album", "artist" , "song", "podcast","playlist"] +def delete_expired_files(): - for c_type in cacheTypes: - cacheDirType = os.path.join( cacheDir , c_type ) + for c_type in ART_CACHE_TYPES: + cacheDirType = os.path.join(cacheDir, c_type) + if not os.path.isdir(cacheDirType): + continue for currentFile in os.listdir(cacheDirType): - #xbmc.log("Clear Cache Art " + str(currentFile),xbmc.LOGDEBUG) - pathDel = os.path.join( cacheDirType, currentFile) + # xbmc.log("Clear Cache Art " + str(currentFile),xbmc.LOGDEBUG) + pathDel = os.path.join(cacheDirType, currentFile) if is_expired(pathDel): try: os.remove(pathDel) - except PermissionError as e: - pass + except OSError as e: + xbmc.log( + "AmpachePlugin::art_clean: Failed to delete file %s: %s" + % (pathDel, repr(e)), + xbmc.LOGDEBUG, + ) + continue + def remove_expired(): - print("Starting cache cleanup...") - delete_expired_files() - print("Cache cleanup completed.") + try: + print("Starting cache cleanup...") + delete_expired_files() + print("Cache cleanup completed.") + except Exception as e: + xbmc.log( + "AmpachePlugin::Service failed to cleanup cache: %s" % repr(e), + xbmc.LOGERROR, + ) + def init_cache(): - cacheTypes = ["album", "artist" , "song", "podcast","playlist"] - #if cacheDir doesn't exist, create it + # if cacheDir doesn't exist, create it if not os.path.isdir(user_mediaDir): os.mkdir(user_mediaDir) if not os.path.isdir(cacheDir): os.mkdir(cacheDir) - for c_type in cacheTypes: - cacheDirType = os.path.join( cacheDir , c_type ) + for c_type in ART_CACHE_TYPES: + cacheDirType = os.path.join(cacheDir, c_type) if not os.path.isdir(cacheDirType): - os.mkdir( cacheDirType ) - - + os.mkdir(cacheDirType) diff --git a/plugin.audio.ampache/resources/lib/servers_manager.py b/plugin.audio.ampache/resources/lib/servers_manager.py index a461b253b..d1ae11b58 100644 --- a/plugin.audio.ampache/resources/lib/servers_manager.py +++ b/plugin.audio.ampache/resources/lib/servers_manager.py @@ -13,10 +13,9 @@ def initializeServer(): jsStorServer = json_storage.JsonStorage("servers.json") serverData = jsStorServer.getData() - if serverData: - pass - else: + if not serverData: xbmc.log( "AmpachePlugin::initializeServer: no servers file",xbmc.LOGDEBUG) + serverData = {} serverData["servers"] = {} tempd = {} tempd["0"] = {} @@ -93,8 +92,8 @@ def switchServer(): try: ampacheConnect = ampache_connect.AmpacheConnect() ampacheConnect.AMPACHECONNECT(showok=True) - except: - pass + except Exception as e: + xbmc.log("AmpachePlugin::switchServer error: %s" % repr(e), xbmc.LOGERROR) def addServer(): xbmc.log("AmpachePlugin::addServer" , xbmc.LOGDEBUG ) @@ -196,7 +195,7 @@ def modifyServer(): value = gui.getFilterFromUser(ut.tString(30187)) else: pass - if value != False: + if value is not False: serverData["servers"][i][key] = value xbmc.executebuiltin("PlayerControl(Stop)") jsStorServer.save(serverData) @@ -204,5 +203,5 @@ def modifyServer(): try: ampacheConnect = ampache_connect.AmpacheConnect() ampacheConnect.AMPACHECONNECT() - except: - pass + except Exception as e: + xbmc.log("AmpachePlugin::modifyServer error: %s" % repr(e), xbmc.LOGERROR) diff --git a/plugin.audio.ampache/resources/lib/utils.py b/plugin.audio.ampache/resources/lib/utils.py index 4d9a60123..68dd71f85 100644 --- a/plugin.audio.ampache/resources/lib/utils.py +++ b/plugin.audio.ampache/resources/lib/utils.py @@ -1,13 +1,15 @@ import time import datetime -import xbmcaddon,xbmcplugin +import xbmcaddon,xbmcplugin,xbmc import sys #main plugin/service library ampache = xbmcaddon.Addon("plugin.audio.ampache") +CONTENT_TYPES = frozenset(['artists', 'albums', 'songs', 'videos']) + def setContent(handle,object_type): - if object_type == 'artists' or object_type == 'albums' or object_type == 'songs' or object_type == 'videos': + if object_type in CONTENT_TYPES: xbmcplugin.setContent(handle, object_type) def otype_to_mode(object_type, object_subtype=None): @@ -148,20 +150,13 @@ def get_params(plugin_url): def get_objectId_from_fileURL( file_url ): params = get_params(file_url) - object_id = None #i use two kind of object_id, i don't know, but sometime i have different #url, btw, no problem, i handle both and i solve the problem in this way - try: - object_id=params["object_id"] - xbmc.log("AmpachePlugin::object_id " + object_id, xbmc.LOGDEBUG) - except: - pass - try: - object_id=params["oid"] - xbmc.log("AmpachePlugin::object_id " + object_id, xbmc.LOGDEBUG) - except: - pass - return object_id + for key in ("object_id", "oid"): + if key in params: + xbmc.log("AmpachePlugin::object_id " + params[key], xbmc.LOGDEBUG) + return params[key] + return None def getRating(rating): if rating: