From 3f6fe0a9e742f76a77b09cb65eadf09bf3ba97b0 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Thu, 24 Dec 2015 13:51:47 -0600 Subject: [PATCH] Version 1.1.63 alpha ready for beta testing --- addon.xml | 11 +- default.py | 140 +- resources/language/English/strings.xml | 2 +- resources/lib/API.py | 801 +++----- resources/lib/ClientInformation.py | 105 - resources/lib/ConnectionManager.py | 161 -- resources/lib/Lock.py | 40 - resources/lib/ReadEmbyDB.py | 386 ---- resources/lib/ReadKodiDB.py | 132 -- resources/lib/TextureCache.py | 236 --- resources/lib/UserPreferences.py | 78 - resources/lib/WebSocketClient.py | 305 --- resources/lib/WriteKodiMusicDB.py | 518 ----- resources/lib/WriteKodiVideoDB.py | 1338 ------------- resources/lib/artwork.py | 524 +++++ resources/lib/clientinfo.py | 96 + resources/lib/embydb_functions.py | 294 +++ resources/lib/initialsetup.py | 192 ++ resources/lib/itemtypes.py | 2528 ++++++++++++++++++++++++ resources/lib/kodidb_functions.py | 1143 +++++++++++ resources/lib/playlist.py | 201 ++ resources/lib/read_embyserver.py | 426 ++++ resources/lib/websocket_client.py | 319 +++ resources/settings.xml | 98 +- service.py | 273 +-- 25 files changed, 6278 insertions(+), 4069 deletions(-) delete mode 100644 resources/lib/ClientInformation.py delete mode 100644 resources/lib/ConnectionManager.py delete mode 100644 resources/lib/Lock.py delete mode 100644 resources/lib/ReadEmbyDB.py delete mode 100644 resources/lib/ReadKodiDB.py delete mode 100644 resources/lib/TextureCache.py delete mode 100644 resources/lib/UserPreferences.py delete mode 100644 resources/lib/WebSocketClient.py delete mode 100644 resources/lib/WriteKodiMusicDB.py delete mode 100644 resources/lib/WriteKodiVideoDB.py create mode 100644 resources/lib/artwork.py create mode 100644 resources/lib/clientinfo.py create mode 100644 resources/lib/embydb_functions.py create mode 100644 resources/lib/initialsetup.py create mode 100644 resources/lib/itemtypes.py create mode 100644 resources/lib/kodidb_functions.py create mode 100644 resources/lib/playlist.py create mode 100644 resources/lib/read_embyserver.py create mode 100644 resources/lib/websocket_client.py diff --git a/addon.xml b/addon.xml index 6c29ecd0..e6c37213 100644 --- a/addon.xml +++ b/addon.xml @@ -1,11 +1,14 @@ - - + + + + @@ -23,4 +26,4 @@ Welcome to Emby for Kodi A whole new way to manage and view your media library. The Emby addon for Kodi combines the best of Kodi - ultra smooth navigation, beautiful UIs and playback of any file under the sun, and Emby - the most powerful fully open source multi-client media metadata indexer and server. Emby for Kodi is the absolute best way to enjoy the incredible Kodi playback engine combined with the power of Emby's centralized database. Features: Direct integration with the Kodi library for native Kodi speed Instant synchronization with the Emby server Full support for Movie, TV and Music collections Emby Server direct stream and transcoding support - use Kodi when you are away from home! - + \ No newline at end of file diff --git a/default.py b/default.py index 2ab68e6f..b4adddee 100644 --- a/default.py +++ b/default.py @@ -1,18 +1,33 @@ # -*- coding: utf-8 -*- -import xbmcaddon, xbmc -import os, sys + +################################################################################################# + +import os +import sys import urlparse +import xbmc +import xbmcaddon + +################################################################################################# + addon_ = xbmcaddon.Addon(id='plugin.video.emby') addon_path = addon_.getAddonInfo('path').decode('utf-8') -base_resource_path = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8') -sys.path.append(base_resource_path) -import Entrypoint as entrypoint +base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8') +sys.path.append(base_resource) + +################################################################################################# + +import entrypoint +import utils + +################################################################################################# enableProfiling = False class Main: + # MAIN ENTRY POINT def __init__(self): @@ -24,83 +39,66 @@ class Main: try: mode = params['mode'][0] - id = params.get('id', None) - if id: - id = id[0] + itemid = params.get('id') + if itemid: + itemid = itemid[0] except: params = {} mode = "" - ##### PLAY ITEM VIA plugin://plugin.video.emby/ ##### - if "play" in mode or "playnow" in mode: - entrypoint.doPlayback(id) - #### DO RESET AUTH ##### - elif "resetauth" in mode: - entrypoint.resetAuth() - - ##### DO DATABASE RESET ##### - elif "reset" in mode: - import Utils as utils - utils.reset() + modes = { - ##### ADD/REMOVE USER FROM SESSION ##### - elif "adduser" in mode: - entrypoint.addUser() + 'reset': utils.reset, + 'resetauth': entrypoint.resetAuth, + 'play': entrypoint.doPlayback, + 'passwords': utils.passwordsXML, + 'adduser': entrypoint.addUser, + 'thememedia': entrypoint.getThemeMedia, + 'channels': entrypoint.BrowseChannels, + 'channelsfolder': entrypoint.BrowseChannels, + 'nextup': entrypoint.getNextUpEpisodes, + 'inprogressepisodes': entrypoint.getInProgressEpisodes, + 'recentepisodes': entrypoint.getRecentEpisodes + } - ##### SYNC THEME MEDIA ##### - elif "thememedia" in mode: - entrypoint.getThemeMedia() + if modes.get(mode): + # Simple functions + if mode == "play": + dbid = params.get('dbid') + modes[mode](itemid, dbid) - ##### LAUNCH EMBY USER PREFS ##### - elif "userprefs" in mode: - entrypoint.userPreferences() - - ##### OPEN ADDON SETTINGS ##### - elif "settings" in mode: - xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') - - ##### MANUALLY SYNC LIBRARY ##### - elif "manualsync" in mode: - from LibrarySync import LibrarySync - LibrarySync().FullLibrarySync(True) - - ##### CACHE ARTWORK ##### - elif "texturecache" in mode: - from TextureCache import TextureCache - TextureCache().FullTextureCacheSync() - - ##### BROWSE EMBY CHANNELS FOLDER ##### - elif "channelsfolder" in mode: - folderid = params['folderid'][0] - entrypoint.BrowseChannels(id,folderid) + elif mode in ("nextup", "inprogressepisodes", "recentepisodes"): + limit = int(params['limit'][0]) + modes[mode](itemid, limit) - ##### BROWSE EMBY CHANNELS ROOT ##### - elif "channels" in mode: - entrypoint.BrowseChannels(id) - - ##### GET NEXTUP EPISODES FOR TAGNAME ##### - elif "nextup" in mode: - limit = int(params['limit'][0]) - entrypoint.getNextUpEpisodes(id, limit) + elif mode == "channels": + modes[mode](itemid) - ##### GET INPROGRESS EPISODES FOR TAGNAME ##### - elif "inprogressepisodes" in mode: - limit = int(params['limit'][0]) - entrypoint.getInProgressEpisodes(id, limit) + elif mode == "channelsfolder": + folderid = params['folderid'][0] + modes[mode](itemid, folderid) + + else: + modes[mode]() + else: + # Other functions + if mode == "settings": + xbmc.executebuiltin('Addon.OpenSettings(plugin.video.emby)') + elif mode in ("manualsync", "repair"): + import librarysync + if mode == "manualsync": + librarysync.LibrarySync().fullSync(manualrun=True) + else: + librarysync.LibrarySync().fullSync(repair=True) + elif mode == "texturecache": + import artwork + artwork.Artwork().FullTextureCacheSync() + elif "extrafanart" in sys.argv[0]: + entrypoint.getExtraFanArt() + else: + entrypoint.doMainListing() - ##### GET RECENT EPISODES FOR TAGNAME ##### - elif "recentepisodes" in mode: - limit = int(params['limit'][0]) - entrypoint.getRecentEpisodes(id, limit) - - ##### GET EXTRAFANART FOR LISTITEM ##### - elif "extrafanart" in sys.argv[0]: - entrypoint.getExtraFanArt() - - ##### SHOW ADDON NODES LISTING ##### - if not mode: - entrypoint.doMainListing() if ( __name__ == "__main__" ): xbmc.log('plugin.video.emby started') diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index fda8a302..a622180f 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -239,7 +239,7 @@ Enable Netflix style next up notification - The number of seconds before the end to show the notification Show Emby Info dialog on play/select action - Suppress server connection message on start-up + Enable server connection message on start-up Use local paths instead of addon redirect for playback diff --git a/resources/lib/API.py b/resources/lib/API.py index 52212311..d15822bb 100644 --- a/resources/lib/API.py +++ b/resources/lib/API.py @@ -1,94 +1,245 @@ -# -- coding: utf-8 -- -# API.py -# This class helps translate more complex cases from the MediaBrowser API to the XBMC API +# -*- coding: utf-8 -*- -from datetime import datetime -from random import randrange -import xbmc -import xbmcgui +################################################################################################## + +import clientinfo +import utils + +################################################################################################## -import Utils as utils class API(): - - def getPeople(self, item): + + def __init__(self, item): + + self.item = item + self.clientinfo = clientinfo.ClientInfo() + self.addonName = self.clientinfo.getAddonName() + + def logMsg(self, msg, lvl=1): + + className = self.__class__.__name__ + utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) + + + def getUserData(self): + # Default + favorite = False + playcount = None + played = False + lastPlayedDate = None + resume = 0 + rating = 0 + + try: + userdata = self.item['UserData'] + + except KeyError: # No userdata found. + pass + + else: + favorite = userdata['IsFavorite'] + likes = userdata.get('Likes') + # Rating for album and songs + if favorite: + rating = 5 + elif likes: + rating = 3 + elif likes == False: + rating = 1 + else: + rating = 0 + + lastPlayedDate = userdata.get('LastPlayedDate') + if lastPlayedDate: + lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ") + + if userdata['Played']: + # Playcount is tied to the watch status + played = True + playcount = userdata['PlayCount'] + if playcount == 0: + playcount = 1 + + if lastPlayedDate is None: + lastPlayedDate = self.getDateCreated() + + playbackPosition = userdata.get('PlaybackPositionTicks') + if playbackPosition: + resume = playbackPosition / 10000000.0 + + return { + + 'Favorite': favorite, + 'PlayCount': playcount, + 'Played': played, + 'LastPlayedDate': lastPlayedDate, + 'Resume': resume, + 'Rating': rating + } + + def getPeople(self): # Process People director = [] writer = [] cast = [] try: - people = item['People'] - except: pass + people = self.item['People'] + + except KeyError: + pass + else: - for person in people: type = person['Type'] - Name = person['Name'] + name = person['Name'] if "Director" in type: - director.append(Name) - elif "Writing" in type: - writer.append(Name) - elif "Writer" in type: - writer.append(Name) + director.append(name) elif "Actor" in type: - cast.append(Name) + cast.append(name) + elif type in ("Writing", "Writer"): + writer.append(name) return { 'Director': director, - 'Writer': writer, - 'Cast': cast + 'Writer': writer, + 'Cast': cast } - def getTimeInfo(self, item): - # Runtime and Resume point - tempRuntime = 0 - runtime = 0 - resume = 0 + def getMediaStreams(self): + item = self.item + videotracks = [] + audiotracks = [] + subtitlelanguages = [] - try: # Get resume point - userdata = item['UserData'] - playbackPosition = userdata['PlaybackPositionTicks'] - resume = playbackPosition / 10000000.0 - except: pass - - try: # Get total runtime - tempRuntime = item['RunTimeTicks'] - - except: - try: tempRuntime = item['CumulativeRunTimeTicks'] - except: pass + try: + media_streams = item['MediaSources'][0]['MediaStreams'] - finally: - runtime = tempRuntime / 10000000.0 + except KeyError: + media_streams = item['MediaStreams'] + for media_stream in media_streams: + # Sort through Video, Audio, Subtitle + stream_type = media_stream['Type'] + codec = media_stream.get('Codec', "").lower() + profile = media_stream.get('Profile', "").lower() + + if stream_type == "Video": + # Height, Width, Codec, AspectRatio, AspectFloat, 3D + track = { + + 'videocodec': codec, + 'height': media_stream.get('Height'), + 'width': media_stream.get('Width'), + 'video3DFormat': item.get('Video3DFormat'), + 'aspectratio': 1.85 + } + + try: + container = item['MediaSources'][0]['Container'].lower() + except: + container = "" + + # Sort codec vs container/profile + if "msmpeg4" in codec: + track['videocodec'] = "divx" + elif "mpeg4" in codec: + if "simple profile" in profile or not profile: + track['videocodec'] = "xvid" + elif "h264" in codec: + if container in ("mp4", "mov", "m4v"): + track['videocodec'] = "avc1" + + # Aspect ratio + if item.get('AspectRatio'): + # Metadata AR + aspectratio = item['AspectRatio'] + else: # File AR + aspectratio = media_stream.get('AspectRatio', "0") + + try: + aspectwidth, aspectheight = aspectratio.split(':') + track['aspectratio'] = round(float(aspectwidth) / float(aspectheight), 6) + + except ValueError: + width = track['width'] + height = track['height'] + + if width and height: + track['aspectratio'] = round(float(width / height), 6) + + videotracks.append(track) + + elif stream_type == "Audio": + # Codec, Channels, language + track = { + + 'audiocodec': codec, + 'channels': media_stream.get('Channels'), + 'audiolanguage': media_stream.get('Language') + } + + if "dca" in codec and "dts-hd ma" in profile: + track['audiocodec'] = "dtshd_ma" + + audiotracks.append(track) + + elif stream_type == "Subtitle": + # Language + subtitlelanguages.append(media_stream.get('Language', "Unknown")) return { - 'ResumeTime': resume, - 'TotalTime': runtime + 'video': videotracks, + 'audio': audiotracks, + 'subtitle': subtitlelanguages } - def getStudios(self, item): - # Process Studio + def getRuntime(self): + item = self.item + try: + runtime = item['RunTimeTicks'] / 10000000.0 + + except KeyError: + runtime = item.get('CumulativeRunTimeTicks', 0) / 10000000.0 + + return runtime + + def adjustResume(self, resume_seconds): + + resume = 0 + if resume_seconds: + resume = round(float(resume_seconds), 6) + jumpback = int(utils.settings('resumeJumpBack')) + if resume > jumpback: + # To avoid negative bookmark + resume = resume - jumpback + + return resume + + def getStudios(self): + # Process Studios + item = self.item studios = [] try: studio = item['SeriesStudio'] - studios.append(self.getStudio(studio)) - except: - try: - studioArray = item['Studios'] - for studio in studioArray: - studios.append(self.getStudio(studio['Name'])) - except: pass + studios.append(self.verifyStudio(studio)) + + except KeyError: + studioList = item['Studios'] + for studio in studioList: + name = studio['Name'] + studios.append(self.verifyStudio(name)) + return studios - def getStudio(self, studioName): + def verifyStudio(self, studioName): # Convert studio for Kodi to properly detect them studios = { @@ -101,503 +252,127 @@ class API(): return studios.get(studioName.lower(), studioName) - def getGenre(self,item): - genre = "" - genres = item.get("Genres") - if genres != None and genres != []: - for genre_string in genres: - if genre == "": #Just take the first genre - genre = genre_string - else: - genre = genre + " / " + genre_string - elif item.get("SeriesGenres") != None and item.get("SeriesGenres") != '': - genres = item.get("SeriesGenres") - if genres != None and genres != []: - for genre_string in genres: - if genre == "": #Just take the first genre - genre = genre_string - else: - genre = genre + " / " + genre_string - return genre + def getChecksum(self): + # Use the etags checksum and userdata + item = self.item + userdata = item['UserData'] - def getMediaStreams(self, item, mediaSources = False): - - videotracks = [] # Height, Width, Codec, AspectRatio, AspectFloat, 3D - audiotracks = [] # Codec, Channels, language - subtitlelanguages = [] # Language - - if mediaSources: - try: - MediaStreams = item['MediaSources'][0]['MediaStreams'] - except: - MediaStreams = None - else: - MediaStreams = item.get('MediaStreams') - - if MediaStreams: - # Sort through the Video, Audio, Subtitle tracks - for mediaStream in MediaStreams: - - type = mediaStream.get('Type', "") - profile = mediaStream.get('Profile', "").lower() - codec = mediaStream.get('Codec', "").lower() - - if "Video" in type: - videotrack = {} - videotrack['videocodec'] = codec - container = item['MediaSources'][0].get('Container', "").lower() - if "msmpeg4" in videotrack['videocodec']: - videotrack['videocodec'] = "divx" - elif "mpeg4" in videotrack['videocodec']: - if "simple profile" in profile or profile == "": - videotrack['videocodec'] = "xvid" - elif "h264" in videotrack['videocodec']: - if container in ("mp4", "mov", "m4v"): - videotrack['videocodec'] = "avc1" - videotrack['height'] = mediaStream.get('Height') - videotrack['width'] = mediaStream.get('Width') - videotrack['Video3DFormat'] = item.get('Video3DFormat') - if item.get('AspectRatio'): - # Metadata aspect ratio - videotrack['aspectratio'] = item['AspectRatio'] - else: # File aspect ratio - videotrack['aspectratio'] = mediaStream.get('AspectRatio', "0") - if len(videotrack['aspectratio']) >= 3: - try: - aspectwidth, aspectheight = videotrack['aspectratio'].split(':') - videotrack['aspectratio'] = round(float(aspectwidth) / float(aspectheight), 6) - except: - videotrack['aspectratio'] = 1.85 - else: - try: - videotrack['aspectratio'] = round(float(videotrack['width'] / videotrack['height']), 6) - except: # In the event the aspect ratio is missing and the width and height are missing as well. - videotrack['aspectratio'] = 1.85 - videotracks.append(videotrack) - - elif "Audio" in type: - audiotrack = {} - audiotrack['audiocodec'] = codec - if "dca" in audiotrack['audiocodec'] and "dts-hd ma" in profile: - audiotrack['audiocodec'] = "dtshd_ma" - audiotrack['channels'] = mediaStream.get('Channels') - audiotrack['audiolanguage'] = mediaStream.get('Language') - audiotracks.append(audiotrack) - - elif "Subtitle" in type: - try: - subtitlelanguages.append(mediaStream['Language']) - except: - subtitlelanguages.append("Unknown") - - return { - - 'videocodec' : videotracks, - 'audiocodec' : audiotracks, - 'subtitlelanguage' : subtitlelanguages - } - - - def getChecksum(self, item): - # use the etags checksum for this if available - # AND the userdata - checksum = "" - - if item.get("Etag") != None: - checksum = item.get("Etag") - userData = item.get("UserData") - if(userData != None): - checksum += str(userData.get("Played")) - checksum += str(userData.get("IsFavorite")) - if userData.get('UnplayedItemCount') != None: - checksum += str(userData.get("UnplayedItemCount")) - if userData.get('LastPlayedDate') != None: - checksum += str(userData.get("LastPlayedDate")) - if userData.get('PlaybackPositionTicks') != None: - checksum += str(userData.get("PlaybackPositionTicks")) + checksum = "%s%s%s%s%s%s" % ( + item['Etag'], + userdata['Played'], + userdata['IsFavorite'], + userdata['PlaybackPositionTicks'], + userdata.get('UnplayedItemCount', ""), + userdata.get('LastPlayedDate', "") + ) + return checksum - - def getUserData(self, item): - # Default - favorite = False - playcount = None - lastPlayedDate = None - userKey = "" + + def getGenres(self): + item = self.item + all_genres = "" + genres = item.get('Genres', item.get('SeriesGenres')) + + if genres: + all_genres = " / ".join(genres) + + return all_genres + + def getDateCreated(self): try: - userdata = item['UserData'] - - except: # No userdata found. - pass - - else: - favorite = userdata['IsFavorite'] - userKey = userdata.get('Key', "") - - watched = userdata['Played'] - if watched: - # Playcount is tied to the watch status - playcount = userdata['PlayCount'] - if playcount == 0: - playcount = 1 - else: - playcount = None - - lastPlayedDate = userdata.get('LastPlayedDate', None) - if lastPlayedDate: - lastPlayedDate = lastPlayedDate.split('.')[0].replace('T', " ") - - return { - - 'Favorite': favorite, - 'PlayCount': playcount, - 'LastPlayedDate': lastPlayedDate, - 'Key': userKey - } - - - def getRecursiveItemCount(self, item): - if item.get("RecursiveItemCount") != None: - return str(item.get("RecursiveItemCount")) - else: - return "0" - - def getOverview(self, item): - - overview = "" - - try: - overview = item['Overview'] - overview = overview.replace("\"", "\'") - overview = overview.replace("\n", " ") - overview = overview.replace("\r", " ") - except: pass - - return overview - - def getTVInfo(self, item, userData): - TotalSeasons = 0 if item.get("ChildCount")==None else item.get("ChildCount") - TotalEpisodes = 0 if item.get("RecursiveItemCount")==None else item.get("RecursiveItemCount") - WatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else TotalEpisodes-int(userData.get("UnplayedItemCount")) - UnWatchedEpisodes = 0 if userData.get("UnplayedItemCount")==None else int(userData.get("UnplayedItemCount")) - NumEpisodes = TotalEpisodes - tempEpisode = "" - if (item.get("IndexNumber") != None): - episodeNum = item.get("IndexNumber") - if episodeNum < 10: - tempEpisode = "0" + str(episodeNum) - else: - tempEpisode = str(episodeNum) - - tempSeason = "" - if (str(item.get("ParentIndexNumber")) != None): - tempSeason = str(item.get("ParentIndexNumber")) - if item.get("ParentIndexNumber") < 10: - tempSeason = "0" + tempSeason - if item.get("SeriesName") != None: - temp=item.get("SeriesName") - SeriesName=temp.encode('utf-8') - else: - SeriesName='' - return {'TotalSeasons' : str(TotalSeasons), - 'TotalEpisodes' : str(TotalEpisodes), - 'WatchedEpisodes' : str(WatchedEpisodes), - 'UnWatchedEpisodes': str(UnWatchedEpisodes), - 'NumEpisodes' : str(NumEpisodes), - 'Season' : tempSeason, - 'Episode' : tempEpisode, - 'SeriesName' : SeriesName - } - - def getDateCreated(self, item): - - try: - dateadded = item['DateCreated'] + dateadded = self.item['DateCreated'] dateadded = dateadded.split('.')[0].replace('T', " ") - except: + except KeyError: dateadded = None return dateadded - def getPremiereDate(self, item): + def getPremiereDate(self): try: - premiere = item['PremiereDate'] + premiere = self.item['PremiereDate'] premiere = premiere.split('.')[0].replace('T', " ") - except: + except KeyError: premiere = None return premiere - def getTagline(self, item): + def getOverview(self): try: - tagline = item['Taglines'][0] - except: + overview = self.item['Overview'] + overview = overview.replace("\"", "\'") + overview = overview.replace("\n", " ") + overview = overview.replace("\r", " ") + except KeyError: + overview = "" + + return overview + + def getTagline(self): + + try: + tagline = self.item['Taglines'][0] + except IndexError: tagline = None return tagline - def getProvider(self, item, providername): - # Provider Name: imdb or tvdb + def getProvider(self, providername): + try: - if "imdb" in providername: - provider = item['ProviderIds']['Imdb'] - elif "tvdb" in providername: - provider = item['ProviderIds']['Tvdb'] - elif "musicBrainzArtist" in providername: - provider = item['ProviderIds']['MusicBrainzArtist'] - elif "musicBrainzAlbum" in providername: - provider = item['ProviderIds']['MusicBrainzAlbum'] - elif "musicBrainzTrackId" in providername: - provider = item['ProviderIds']['MusicBrainzTrackId'] - except: + provider = self.item['ProviderIds'][providername] + except KeyError: provider = None return provider - def getCountry(self, item): + def getMpaa(self): + # Convert more complex cases + mpaa = self.item.get('OfficialRating', "") + + if mpaa in ("NR", "UR"): + # Kodi seems to not like NR, but will accept Not Rated + mpaa = "Not Rated" + + return mpaa + + def getCountry(self): try: - country = item['ProductionLocations'][0] - except: + country = self.item['ProductionLocations'][0] + except IndexError: country = None return country - def getMpaa(self, item): - # Convert more complex cases - mpaa = item.get('OfficialRating', "") - if mpaa in ("NR", "UR"): - # Kodi seems to not like NR, but will accept Rated Not Rated - mpaa = "Rated Not Rated" + def getFilePath(self): - return mpaa + item = self.item + try: + filepath = item['Path'] - def getAllArtwork(self, item, parentInfo = False): + except KeyError: + filepath = "" - """ - Get all artwork, it will return an empty string - for the artwork type not found. + else: + if "\\\\" in filepath: + # append smb protocol + filepath = filepath.replace("\\\\", "smb://") + filepath = filepath.replace("\\", "/") - Artwork type: Primary, Art, Banner, Logo, Thumb, - Disc, Backdrop - """ - - username = utils.window('currUser') - server = utils.window('server%s' % username) - - id = item['Id'] - artworks = item['ImageTags'] - backdrops = item['BackdropImageTags'] - - maxHeight = 10000 - maxWidth = 10000 - customquery = "" - - if utils.settings('compressArt') == "true": - customquery = "&Quality=90" - - if utils.settings('disableCoverArt') == "true": - customquery += "&EnableImageEnhancers=false" - - allartworks = { - - 'Primary': "", - 'Art': "", - 'Banner': "", - 'Logo': "", - 'Thumb': "", - 'Disc': "", - 'Backdrop': [] - } - - # Process backdrops - backdropIndex = 0 - for backdroptag in backdrops: - artwork = "%s/mediabrowser/Items/%s/Images/Backdrop/%s?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, backdropIndex, maxWidth, maxHeight, backdroptag, customquery) - allartworks['Backdrop'].append(artwork) - backdropIndex += 1 - - # Process the rest of the artwork - for art in artworks: - tag = artworks[art] - artwork = "%s/mediabrowser/Items/%s/Images/%s/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, id, art, maxWidth, maxHeight, tag, customquery) - allartworks[art] = artwork - - # Process parent items if the main item is missing artwork - if parentInfo: + if item.get('VideoType'): + videotype = item['VideoType'] + # Specific format modification + if 'Dvd'in videotype: + filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath + elif 'Bluray' in videotype: + filepath = "%s/BDMV/index.bdmv" % filepath - # Process backdrops - if not allartworks['Backdrop']: - - parentId = item.get('ParentBackdropItemId') - if parentId: - # If there is a parentId, go through the parent backdrop list - parentbackdrops = item['ParentBackdropImageTags'] + if "\\" in filepath: + # Local path scenario, with special videotype + filepath = filepath.replace("/", "\\") - backdropIndex = 0 - for parentbackdroptag in parentbackdrops: - artwork = "%s/mediabrowser/Items/%s/Images/Backdrop/%s?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, backdropIndex, maxWidth, maxHeight, parentbackdroptag, customquery) - allartworks['Backdrop'].append(artwork) - backdropIndex += 1 - - # Process the rest of the artwork - parentartwork = ['Logo', 'Art', 'Thumb'] - for parentart in parentartwork: - - if not allartworks[parentart]: - - parentId = item.get('Parent%sItemId' % parentart) - if parentId: - - parentTag = item['Parent%sImageTag' % parentart] - artwork = "%s/mediabrowser/Items/%s/Images/%s/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, parentart, maxWidth, maxHeight, parentTag, customquery) - allartworks[parentart] = artwork - - # Parent album works a bit differently - if not allartworks['Primary']: - - parentId = item.get('AlbumId') - if parentId and item.get('AlbumPrimaryImageTag'): - - parentTag = item['AlbumPrimaryImageTag'] - artwork = "%s/mediabrowser/Items/%s/Images/Primary/0?MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" % (server, parentId, maxWidth, maxHeight, parentTag, customquery) - allartworks['Primary'] = artwork - - - return allartworks - - def getArtwork(self, data, type, mediaType = "", index = "0", userParentInfo = False): - - id = data.get("Id") - getSeriesData = False - userData = data.get("UserData") - - if type == "tvshow.poster": # Change the Id to the series to get the overall series poster - if data.get("Type") == "Season" or data.get("Type")== "Episode": - id = data.get("SeriesId") - getSeriesData = True - elif type == "poster" and data.get("Type") == "Episode" and utils.settings('useSeasonPoster')=='true': # Change the Id to the Season to get the season poster - id = data.get("SeasonId") - if type == "poster" or type == "tvshow.poster": # Now that the Ids are right, change type to MB3 name - type="Primary" - if data.get("Type") == "Season": # For seasons: primary (poster), thumb and banner get season art, rest series art - if type != "Primary" and type != "Primary2" and type != "Primary3" and type != "Primary4" and type != "Thumb" and type != "Banner" and type!="Thumb3" and type!="Backdrop": - id = data.get("SeriesId") - getSeriesData = True - if data.get("Type") == "Episode": # For episodes: primary (episode thumb) gets episode art, rest series art. - if type != "Primary" and type != "Primary2" and type != "Primary3" and type != "Primary4": - id = data.get("SeriesId") - getSeriesData = True - if type =="Primary2" or type=="Primary3" or type=="Primary4": - id = data.get("SeasonId") - getSeriesData = True - if data.get("SeasonUserData") != None: - userData = data.get("SeasonUserData") - if id == None: - id=data.get("Id") - - imageTag = "e3ab56fe27d389446754d0fb04910a34" # a place holder tag, needs to be in this format - originalType = type - if type == "Primary2" or type == "Primary3" or type == "Primary4" or type=="SeriesPrimary": - type = "Primary" - if type == "Backdrop2" or type=="Backdrop3" or type=="BackdropNoIndicators": - type = "Backdrop" - if type == "Thumb2" or type=="Thumb3": - type = "Thumb" - if(data.get("ImageTags") != None and data.get("ImageTags").get(type) != None): - imageTag = data.get("ImageTags").get(type) - - if (data.get("Type") == "Episode" or data.get("Type") == "Season") and type=="Logo": - imageTag = data.get("ParentLogoImageTag") - if (data.get("Type") == "Episode" or data.get("Type") == "Season") and type=="Art": - imageTag = data.get("ParentArtImageTag") - if (data.get("Type") == "Episode" or data.get("Type") == "Season") and type=="Backdrop": - if data.get("BackdropImageTags"): - imageTag = data['BackdropImageTags'][0] - if (data.get("Type") == "Episode" and originalType=="Thumb3"): - imageTag = data.get("SeriesThumbImageTag") - if (data.get("Type") == "Season" and originalType=="Thumb3" and imageTag=="e3ab56fe27d389446754d0fb04910a34"): - imageTag = data.get("ParentThumbImageTag") - id = data.get("SeriesId") - - # for music we return the parent art if no image exists - if (data.get("Type") == "MusicAlbum" or data.get("Type") == "Audio") and type=="Backdrop" and not data.get("BackdropImageTags"): - data["BackdropImageTags"] = data.get("ParentBackdropImageTags") - id = data.get("ParentBackdropItemId") - if (data.get("Type") == "MusicAlbum" or data.get("Type") == "Audio") and type=="Logo" and (not imageTag or imageTag == "e3ab56fe27d389446754d0fb04910a34"): - imageTag = data.get("ParentLogoImageTag") - id = data.get("ParentLogoItemId") - if (data.get("Type") == "MusicAlbum" or data.get("Type") == "Audio") and type=="Art" and (not imageTag or imageTag == "e3ab56fe27d389446754d0fb04910a34"): - imageTag = data.get("ParentArtImageTag") - id = data.get("ParentArtItemId") - - query = "" - maxHeight = "10000" - maxWidth = "10000" - height = "" - width = "" - played = "0" - totalbackdrops = 0 - - if utils.settings('coverArtratio') == "true": - if mediaType in ("movie","boxset","tvshow"): - if "Primary" in type: - # Only force ratio for cover art for main covers - aspectratio = data.get("PrimaryImageAspectRatio") - width = "&Width=1000" - height = "&Height=1480" - - if originalType =="BackdropNoIndicators" and index == "0" and data.get("BackdropImageTags") != None: - totalbackdrops = len(data.get("BackdropImageTags")) - if totalbackdrops != 0: - index = str(randrange(0,totalbackdrops)) - # use the local image proxy server that is made available by this addons service - # Load user information set by UserClient - WINDOW = xbmcgui.Window(10000) - username = WINDOW.getProperty('currUser') - server = WINDOW.getProperty('server%s' % username) - - if utils.settings('compressArt')=='true': - query = query + "&Quality=90" - - if imageTag == None: - imageTag = "e3ab56fe27d389446754d0fb04910a34" - - artwork = "%s/mediabrowser/Items/%s/Images/%s/%s?MaxWidth=%s&MaxHeight=%s%s%s&Format=original&Tag=%s%s" % (server, id, type, index, maxWidth, maxHeight, height, width, imageTag, query) - #artwork = "%s/mediabrowser/Items/%s/Images/%s/%s/%s/original/%s/%s/%s?%s" % (server, id, type, index, imageTag, width, height, played, query) <- broken - if utils.settings('disableCoverArt')=='true': - artwork = artwork + "&EnableImageEnhancers=false" - - # do not return non-existing images - if ( (type!="Backdrop" and imageTag=="e3ab56fe27d389446754d0fb04910a34") | #Remember, this is the placeholder tag, meaning we didn't find a valid tag - (type=="Backdrop" and data.get("BackdropImageTags") != None and len(data.get("BackdropImageTags")) == 0) | - (type=="Backdrop" and data.get("BackdropImageTag") != None and len(data.get("BackdropImageTag")) == 0) - ): - if type != "Backdrop" or (type=="Backdrop" and getSeriesData==True and data.get("ParentBackdropImageTags") == None) or (type=="Backdrop" and getSeriesData!=True): - artwork='' - - return artwork - - def imageUrl(self, id, type, index, width, height): - - WINDOW = xbmcgui.Window(10000) - username = WINDOW.getProperty('currUser') - server = WINDOW.getProperty('server%s' % username) - # For people image - actors, directors, writers - return "%s/mediabrowser/Items/%s/Images/%s?MaxWidth=%s&MaxHeight=%s&Index=%s" % (server, id, type, width, height, index) - - def getUserArtwork(self, data, type, index = "0"): - - # Load user information set by UserClient - WINDOW = xbmcgui.Window(10000) - username = WINDOW.getProperty('currUser') - server = WINDOW.getProperty('server%s' % username) - id = data.get("Id") - - artwork = "%s/mediabrowser/Users/%s/Images/%s?Format=original" % (server, id, type) - - return artwork - \ No newline at end of file + return filepath \ No newline at end of file diff --git a/resources/lib/ClientInformation.py b/resources/lib/ClientInformation.py deleted file mode 100644 index f014b165..00000000 --- a/resources/lib/ClientInformation.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- - -import os -from uuid import uuid4 as uuid4 -from Lock import Lock - -import xbmc -import xbmcaddon -import xbmcgui - -import Utils as utils - - -class ClientInformation(): - - def __init__(self): - - self.addon = xbmcaddon.Addon() - self.addonName = self.getAddonName() - - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) - - def getAddonId(self): - # To use when declaring xbmcaddon.Addon(id=addonId) - return "plugin.video.emby" - - def getAddonName(self): - # Useful for logging - return self.addon.getAddonInfo('name').upper() - - def getVersion(self): - - return self.addon.getAddonInfo('version') - - def getDeviceName(self): - - if utils.settings('deviceNameOpt') == "false": - # Use Kodi's deviceName - deviceName = xbmc.getInfoLabel('System.FriendlyName') - else: - deviceName = utils.settings('deviceName') - deviceName = deviceName.replace("\"", "_") - deviceName = deviceName.replace("/", "_") - - return deviceName - - def getMachineId(self): - - WINDOW = xbmcgui.Window(10000) - - clientId = WINDOW.getProperty("client_id") - if clientId: - return clientId - - # we need to load and or generate a client machine id - addon = self.addon - addondir = addon.getAddonInfo('path').decode('utf-8') - machine_guid_lock_path = xbmc.translatePath(os.path.join(addondir, "machine_guid.lock")).decode('utf-8') - machine_guid_path = xbmc.translatePath(os.path.join(addondir, "machine_guid")).decode('utf-8') - clientId = "" - - try: - lock = Lock(machine_guid_lock_path) - locked = lock.acquire() - - if locked: - - fd = os.open(machine_guid_path, os.O_CREAT|os.O_RDWR) - clientId = os.read(fd, 256) - - if len(clientId) == 0: - uuid = uuid4() - clientId = str("%012X" % uuid) - self.logMsg("ClientId saved to FILE: %s" % clientId, 2) - os.write(fd, clientId) - os.fsync(fd) - - os.close(fd) - - self.logMsg("ClientId saved to WINDOW: %s" % clientId, 1) - WINDOW.setProperty("client_id", clientId) - finally: - lock.release() - - return clientId - - def getPlatform(self): - - if xbmc.getCondVisibility('system.platform.osx'): - return "OSX" - elif xbmc.getCondVisibility('system.platform.atv2'): - return "ATV2" - elif xbmc.getCondVisibility('system.platform.ios'): - return "iOS" - elif xbmc.getCondVisibility('system.platform.windows'): - return "Windows" - elif xbmc.getCondVisibility('system.platform.linux'): - return "Linux/RPi" - elif xbmc.getCondVisibility('system.platform.android'): - return "Linux/Android" - - return "Unknown" \ No newline at end of file diff --git a/resources/lib/ConnectionManager.py b/resources/lib/ConnectionManager.py deleted file mode 100644 index 016bf2dd..00000000 --- a/resources/lib/ConnectionManager.py +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# -# connection manager class -################################################################################################# - -import json -import socket - -import xbmc -import xbmcgui -import xbmcaddon - -import Utils as utils -from ClientInformation import ClientInformation -from DownloadUtils import DownloadUtils -from UserClient import UserClient - - -class ConnectionManager(): - - clientInfo = ClientInformation() - user = UserClient() - doUtils = DownloadUtils() - - addonName = clientInfo.getAddonName() - addonId = clientInfo.getAddonId() - addon = xbmcaddon.Addon() - WINDOW = xbmcgui.Window(10000) - - def __init__(self): - - self.__language__ = self.addon.getLocalizedString - - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) - - def checkServer(self): - - self.WINDOW.setProperty("Server_Checked", "True") - self.logMsg("Connection Manager Called", 2) - - server = self.user.getServer() - - if server != "": - self.logMsg("Server already set", 2) - return - - serverInfo = self.getServerDetails() - - try: - prefix,ip,port = serverInfo.split(":") - setServer = xbmcgui.Dialog().yesno(self.__language__(30167), "Proceed with the following server?", self.__language__(30169) + serverInfo) - except: # serverInfo is None - self.logMsg("getServerDetails failed", 1) - xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) - return - - if setServer == 1: - self.logMsg("Server selected. Saving information.", 1) - utils.settings("ipaddress", ip.replace("/", "")) - utils.settings("port", port) - # If https, enable the setting - if (prefix == 'https'): - utils.settings('https', "true") - else: - self.logMsg("No server selected.", 1) - xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) - return - - # Get List of public users - self.logMsg("Getting user list", 1) - server = "%s:%s" % (ip.replace("/", ""), port) - url = "%s/mediabrowser/Users/Public?format=json" % serverInfo - - result = self.doUtils.downloadUrl(url, authenticate=False) - if result == "": - self.logMsg("Unable to connect to %s." % server, 1) - return - - self.logMsg("Result: %s" % result, 2) - - # Process the list returned - names = [] - userList = [] - for user in result: - name = user['Name'] - userList.append(name) - - if user['HasPassword']: - name = "%s (Secure)" % name - names.append(name) - - self.logMsg("User list: %s" % names, 1) - resp = xbmcgui.Dialog().select(self.__language__(30200), names) - if resp > -1: - selected_user = userList[resp] - self.logMsg("Selected User: %s" % selected_user, 1) - utils.settings("username", selected_user) - else: - self.logMsg("No user selected.", 1) - xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) - return - - musicDisabled = xbmcgui.Dialog().yesno("Music Setting", "Disable music library?") - if musicDisabled: - self.logMsg("User opted to disable music library.", 1) - utils.settings('enableMusicSync', "false") - else: - # Music is enabled, prompt for direct stream - musicPath = xbmcgui.Dialog().yesno("Music Setting", "Direct stream the music library?", "Select this option only if you plan on listening to music outside your network.") - if musicPath: - self.logMsg("User option to direct stream music library.", 1) - utils.settings('directstreammusic', "true") - - directPaths = xbmcgui.Dialog().yesno( - heading="Direct paths", - line1=( - "Use direct paths? Caution! If you choose yes, you " - "will lose access to certain Emby features such as: " - "Emby cinema mode, direct stream/transcode options, " - "parental access schedule.")) - if directPaths: - self.logMsg("User opted to use direct paths.", 1) - utils.settings('useDirectPaths', "true") - - return - - def getServerDetails(self): - - self.logMsg("Getting Server Details from Network") - - MULTI_GROUP = ("", 7359) - MESSAGE = "who is EmbyServer?" - - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - sock.settimeout(6.0) - - sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20) - - sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) - sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1) - - self.logMsg("MutliGroup : %s" % str(MULTI_GROUP), 2); - self.logMsg("Sending UDP Data: %s" % MESSAGE, 2); - sock.sendto(MESSAGE, MULTI_GROUP) - - try: - data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes - self.logMsg("Received Response: %s" % data) - # Get the address - data = json.loads(data) - return data['Address'] - except: - self.logMsg("No UDP Response") - pass - - return None \ No newline at end of file diff --git a/resources/lib/Lock.py b/resources/lib/Lock.py deleted file mode 100644 index 59103d9c..00000000 --- a/resources/lib/Lock.py +++ /dev/null @@ -1,40 +0,0 @@ -import os -import time -import errno -import xbmc - -class Lock: - - def __init__(self, filename): - self.filename = filename - self.delay = 0.5 - self.timeout = 10 - self.is_locked = False - self.fd = None - - def acquire(self): - start_time = time.time() - while True: - try: - self.fd = os.open(self.filename, os.O_CREAT|os.O_RDWR|os.O_EXCL) - break; - except OSError as e: - if (time.time() - start_time) >= self.timeout: - xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " timed out") - return False - #xbmc.log("File_Lock_On " + self.filename + " error " + str(e)) - time.sleep(self.delay) - self.is_locked = True - xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " obtained") - return True - - def release(self): - if self.is_locked: - os.close(self.fd) - os.unlink(self.filename) - self.is_locked = False - xbmc.log("File_Lock_On " + self.filename.encode('utf-8') + " released") - - def __del__(self): - self.release() - \ No newline at end of file diff --git a/resources/lib/ReadEmbyDB.py b/resources/lib/ReadEmbyDB.py deleted file mode 100644 index 3ab12bfd..00000000 --- a/resources/lib/ReadEmbyDB.py +++ /dev/null @@ -1,386 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# -# ReadEmbyDB -################################################################################################# - -from DownloadUtils import DownloadUtils - - -class ReadEmbyDB(): - - doUtils = DownloadUtils() - urllimit = 50 - - def filterbyId(self, result, itemList = []): - - newResult = [] - for item in result: - if item['Id'] in itemList: - newResult.append(item) - - return newResult - - def getMovies(self, parentId, itemList = []): - - result = [] - lenlist = len(itemList) < self.urllimit - # Only get basic info for our sync-compares - url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&SortBy=SortName&Fields=CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Movie&CollapseBoxSetItems=false&ImageTypeLimit=1&format=json" % parentId - if itemList and lenlist: - url = "%s&Ids=%s" % (url, ",".join(itemList)) - - jsondata = self.doUtils.downloadUrl(url) - try: - result = jsondata['Items'] - except: pass - else: # If list was longer than 49 items, we pulled the entire list so we need to sort - if not lenlist: - result = self.filterbyId(result, itemList) - - return result - - def getMusicVideos(self, itemList = []): - - result = [] - lenlist = len(itemList) < self.urllimit - # Only get basic info for our sync-compares - url = "{server}/mediabrowser/Users/{UserId}/items?&SortBy=SortName&Fields=CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=MusicVideo&CollapseBoxSetItems=false&ImageTypeLimit=1&format=json" - if itemList and lenlist: - url = "%s&Ids=%s" % (url, ",".join(itemList)) - - jsondata = self.doUtils.downloadUrl(url) - try: - result = jsondata['Items'] - except: pass - else: # If list was longer than 49 items, we pulled the entire list so we need to sort - if not lenlist: - result = self.filterbyId(result, itemList) - - return result - - def getMusicArtists(self, itemList = []): - - result = [] - lenlist = len(itemList) < self.urllimit - # Only get basic info for our sync-compares - url = "{server}/Artists?Recursive=true&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&UserId={UserId}&format=json" - if itemList and lenlist: - url = "%s&Ids=%s" % (url, ",".join(itemList)) - - jsondata = self.doUtils.downloadUrl(url) - try: - result = jsondata['Items'] - except: pass - else: # If list was longer than 49 items, we pulled the entire list so we need to sort - if not lenlist: - result = self.filterbyId(result, itemList) - - return result - - def getMusicArtistsTotal(self): - - result = [] - - url = "{server}/Artists?Limit=1&Recursive=true&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&UserId={UserId}&format=json" - jsondata = self.doUtils.downloadUrl(url) - - total = jsondata['TotalRecordCount'] - index = 1 - jump = 200 - - while index < total: - url = "{server}/Artists?StartIndex=%s&Limit=%s&Recursive=true&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&UserId={UserId}&format=json" % (index, jump) - jsondata = self.doUtils.downloadUrl(url) - result.extend(jsondata['Items']) - index += jump - - return result - - def getMusicSongs(self, itemList = []): - - result = [] - lenlist = len(itemList) < self.urllimit - # Only get basic info for our sync-compares - url = "{server}/mediabrowser/Users/{UserId}/Items?Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=Audio&format=json" - if itemList and lenlist: - url = "%s&Ids=%s" % (url, ",".join(itemList)) - - jsondata = self.doUtils.downloadUrl(url) - try: - result = jsondata['Items'] - except: pass - else: # If list was longer than 49 items, we pulled the entire list so we need to sort - if not lenlist: - result = self.filterbyId(result, itemList) - - return result - - def getMusicSongsTotal(self): - - result = [] - - url = "{server}/mediabrowser/Users/{UserId}/Items?Index=1&Limit=1&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=Audio&format=json" - jsondata = self.doUtils.downloadUrl(url) - - total = jsondata['TotalRecordCount'] - index = 1 - jump = 200 - - while index < total: - url = "{server}/mediabrowser/Users/{UserId}/Items?StartIndex=%s&Limit=%s&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=Audio&format=json" % (index, jump) - jsondata = self.doUtils.downloadUrl(url) - result.extend(jsondata['Items']) - index += jump - - return result - - def getMusicAlbums(self, itemList = []): - - result = [] - lenlist = len(itemList) < self.urllimit - # Only get basic info for our sync-compares - url = "{server}/mediabrowser/Users/{UserId}/Items?Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=MusicAlbum&format=json" - if itemList and lenlist: - url = "%s&Ids=%s" % (url, ",".join(itemList)) - - jsondata = self.doUtils.downloadUrl(url) - try: - result = jsondata['Items'] - except: pass - else: - tempresult = [] - # Only return valid albums - which have artists - for item in result: - if item['AlbumArtists']: - tempresult.append(item) - result = tempresult - # If list was longer than 49 items, we pulled the entire list so we need to sort - if not lenlist: - result = self.filterbyId(result, itemList) - - return result - - def getMusicAlbumsTotal(self): - - result = [] - - url = "{server}/mediabrowser/Users/{UserId}/Items?Limit=1&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&Recursive=true&IncludeItemTypes=MusicAlbum&format=json" - jsondata = self.doUtils.downloadUrl(url) - - total = jsondata['TotalRecordCount'] - index = 1 - jump = 200 - - while index < total: - url = "{server}/mediabrowser/Users/{UserId}/Items?StartIndex=%s&Limit=%s&Fields=Etag,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview&SortBy=DateCreated&Recursive=true&IncludeItemTypes=MusicAlbum&format=json" % (index, jump) - jsondata = self.doUtils.downloadUrl(url) - - #tempresult = [] - # Only return valid albums - which have artists - '''for item in jsondata['Items']: - if item['AlbumArtists']: - tempresult.append(item) - - result.extend(tempresult)''' - result.extend(jsondata['Items']) - index += jump - - return result - - def getTvShows(self, parentId, itemList = []): - - result = [] - lenlist = len(itemList) < self.urllimit - # Only get basic info for our sync-compares - url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&SortBy=SortName&Fields=CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Series&format=json&ImageTypeLimit=1" % parentId - if itemList and lenlist: - url = "%s&Ids=%s" % (url, ",".join(itemList)) - - jsondata = self.doUtils.downloadUrl(url) - try: - result = jsondata['Items'] - except: pass - else: # If list was longer than 49 items, we pulled the entire list so we need to sort - if not lenlist: - result = self.filterbyId(result, itemList) - - return result - - def getTVShowSeasons(self, tvShowId): - - result = [] - url = "{server}/Shows/%s/Seasons?UserId={UserId}&format=json&ImageTypeLimit=1" % tvShowId - - jsondata = self.doUtils.downloadUrl(url) - if jsondata: - result = jsondata['Items'] - - return result - - def getEpisodes(self, showId, itemList = []): - - result = [] - lenlist = len(itemList) < self.urllimit - - url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&IsVirtualUnaired=false&IsMissing=False&SortBy=SortName&Fields=Name,SortName,CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Ascending&IncludeItemTypes=Episode&format=json&ImageTypeLimit=1" % showId - if itemList and lenlist: - url = "%s&Ids=%s" % (url, ",".join(itemList)) - - jsondata = self.doUtils.downloadUrl(url) - try: - result = jsondata['Items'] - except: pass - else: # If list was longer than 49 items, we pulled the entire list so we need to sort - if not lenlist: - result = self.filterbyId(result, itemList) - - return result - - def getLatestEpisodes(self, fullinfo = False, itemList = []): - - result = [] - - limitString = "Limit=20&SortBy=DateCreated&" - if itemList: # if we want a certain list specify it - limitString = "Ids=%s&" % ",".join(itemList) - - if fullinfo: - url = "{server}/mediabrowser/Users/{UserId}/Items?%sIsVirtualUnaired=false&IsMissing=False&Fields=ParentId,Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Episode&format=json&ImageTypeLimit=1" % limitString - else: - url = "{server}/mediabrowser/Users/{UserId}/Items?%sIsVirtualUnaired=false&IsMissing=False&Fields=ParentId,Name,SortName,CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Descending&IncludeItemTypes=Episode&format=json&ImageTypeLimit=1" % limitString - - jsondata = self.doUtils.downloadUrl(url) - if jsondata: - result = jsondata['Items'] - - return result - - def getItem(self, id): - - result = {} - url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&ImageTypeLimit=1&Fields=Etag" % id - - jsondata = self.doUtils.downloadUrl(url) - if jsondata: - result = jsondata - - return result - - def getFullItem(self, id): - - result = {} - url = "{server}/mediabrowser/Users/{UserId}/Items/%s?format=json&Fields=Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines,CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore,AirTime,DateCreated,MediaStreams,People,Overview,CriticRating,CriticRatingSummary" % id - - jsondata = self.doUtils.downloadUrl(url) - if jsondata: - result = jsondata - - return result - - def getCollections(self, type): - # Build a list of the user views - collections = [] - - url = "{server}/mediabrowser/Users/{UserId}/Views?format=json" #Items?Sortby=SortName&format=json" - jsondata = self.doUtils.downloadUrl(url) - - try: - result = jsondata['Items'] - except: pass - else: - for item in result: - - name = item['Name'] - contentType = item['Type'] - itemtype = item.get('CollectionType') - content = itemtype - - if contentType == "Channel": - # Ignore channel type otherwise, they get processed as mixed content - continue - - if itemtype is None and type in ("movies", "tvshows"): - # Mixed content or rich presentation is disabled - itemtype = type - content = "mixed" - - if itemtype == type and name not in ("Collections", "Trailers"): - collections.append({ - - 'title': name, - 'type': itemtype, - 'id': item['Id'], - 'content': content - }) - - return collections - - def getBoxSets(self): - - result = [] - url = "{server}/mediabrowser/Users/{UserId}/Items?SortBy=SortName&IsVirtualUnaired=false&IsMissing=False&Fields=Name,SortName,CumulativeRunTimeTicks,Etag&Recursive=true&SortOrder=Ascending&IncludeItemTypes=BoxSet&format=json&ImageTypeLimit=1" - - jsondata = self.doUtils.downloadUrl(url) - if jsondata: - result = jsondata['Items'] - - return result - - def getMoviesInBoxSet(self, boxsetId): - - result = [] - url = "{server}/mediabrowser/Users/{UserId}/Items?ParentId=%s&Fields=ItemCounts,Etag&format=json&ImageTypeLimit=1" % boxsetId - - jsondata = self.doUtils.downloadUrl(url) - if jsondata: - result = jsondata['Items'] - - return result - - - # This is not being used. - # To be removed? - - def getViewCollections(self, type): - #Build a list of the user views - doUtils = DownloadUtils() - - viewsUrl = "{server}/mediabrowser/Users/{UserId}/Views?format=json&ImageTypeLimit=1" - result = doUtils.downloadUrl(viewsUrl) - collections=[] - - if (result == ""): - return [] - - result = result[u'Items'] - - for view in result: - if (view[u'Type'] == 'UserView'): # Need to grab the real main node - newViewsUrl = "{server}/mediabrowser/Users/{UserId}/items?ParentId=%s&SortBy=SortName&SortOrder=Ascending&format=json&ImageTypeLimit=1" % view[u'Id'] - newViews = doUtils.downloadUrl(newViewsUrl) - if (result == ""): - return [] - newViews = newViews[u'Items'] - for newView in newViews: - # There are multiple nodes in here like 'Latest', 'NextUp' - below we grab the full node. - if newView[u'CollectionType'] != None: - if newView[u'CollectionType'] == "MovieMovies" or newView[u'CollectionType'] == "TvShowSeries": - view=newView - if (view[u'ChildCount'] != 0): - Name = view[u'Name'] - - total = str(view[u'ChildCount']) - try: - itemtype = view[u'CollectionType'] - except: - itemtype = "movies" - if itemtype == "MovieMovies": - itemtype = "movies" - if itemtype == "TvShowSeries": - itemtype = "tvshows" - if itemtype == type: - collections.append( {'title' : Name, - 'type' : type, - 'id' : view[u'Id']}) - return collections \ No newline at end of file diff --git a/resources/lib/ReadKodiDB.py b/resources/lib/ReadKodiDB.py deleted file mode 100644 index 29655b94..00000000 --- a/resources/lib/ReadKodiDB.py +++ /dev/null @@ -1,132 +0,0 @@ -################################################################################################# -# ReadKodiDB -################################################################################################# - - -import xbmc -import xbmcgui -import xbmcaddon -import json -import os - -import Utils as utils - -class ReadKodiDB(): - - - def getKodiMovies(self, connection, cursor): - #returns all movies in Kodi db - cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='movie'") - allmovies = cursor.fetchall() - #this will return a list with tuples of all items returned from the database - return allmovies - - def getKodiMusicVideos(self, connection, cursor): - #returns all musicvideos in Kodi db - cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='musicvideo'") - allvideos = cursor.fetchall() - #this will return a list with tuples of all items returned from the database - return allvideos - - def getKodiTvShows(self, connection, cursor): - cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='tvshow'") - allshows = cursor.fetchall() - #this will return a list with tuples of all items returned from the database - return allshows - - def getKodiEpisodes(self, connection, cursor, showid=None): - - if showid == None: - cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type=?",("episode",)) - else: - cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type=? AND parent_id=?",("episode", showid)) - - allepisodes = cursor.fetchall() - #this will return a list with tuples of all items returned from the database - return allepisodes - - def getEmbyIdByKodiId(self, id, type, connection=None, cursor=None): - if not connection: - connection = utils.KodiSQL() - cursor = connection.cursor() - closeCon = True - else: - closeCon = False - cursor.execute("SELECT emby_id FROM emby WHERE media_type=? AND kodi_id=?",(type,id)) - result = cursor.fetchone() - if closeCon: - connection.close() - if result: - return result[0] - else: - return None - - def getShowIdByEmbyId(self, id, connection=None, cursor=None): - if not connection: - connection = utils.KodiSQL() - cursor = connection.cursor() - closeCon = True - else: - closeCon = False - cursor.execute("SELECT parent_id FROM emby WHERE emby_id=?",(id,)) - result = cursor.fetchone() - if closeCon: - connection.close() - if result: - return result[0] - else: - return None - - def getTypeByEmbyId(self, id, connection=None, cursor=None): - if not connection: - connection = utils.KodiSQL() - cursor = connection.cursor() - closeCon = True - else: - closeCon = False - cursor.execute("SELECT media_type FROM emby WHERE emby_id=?",(id,)) - result = cursor.fetchone() - if closeCon: - connection.close() - if result: - return result[0] - else: - return None - - def getShowTotalCount(self, id, connection=None, cursor=None): - if not connection: - connection = utils.KodiSQL() - cursor = connection.cursor() - closeCon = True - else: - closeCon = False - command = "SELECT totalCount FROM tvshowcounts WHERE idShow=%s" % str(id) - cursor.execute(command) - result = cursor.fetchone() - if closeCon: - connection.close() - if result: - return result[0] - else: - return None - - def getKodiMusicArtists(self, connection, cursor): - #returns all artists in Kodi db - cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='artist'") - allartists = cursor.fetchall() - #this will return a list with tuples of all items returned from the database - return allartists - - def getKodiMusicAlbums(self, connection, cursor): - #returns all artists in Kodi db - cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='album'") - allalbums = cursor.fetchall() - #this will return a list with tuples of all items returned from the database - return allalbums - - def getKodiMusicSongs(self, connection, cursor): - #returns all songs in Kodi db - cursor.execute("SELECT kodi_id, emby_id, checksum FROM emby WHERE media_type='song'") - allsongs = cursor.fetchall() - #this will return a list with tuples of all items returned from the database - return allsongs \ No newline at end of file diff --git a/resources/lib/TextureCache.py b/resources/lib/TextureCache.py deleted file mode 100644 index 0e1e93db..00000000 --- a/resources/lib/TextureCache.py +++ /dev/null @@ -1,236 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# - -import json -import requests -import urllib -import os - -import xbmc -import xbmcvfs - -import Utils as utils -from ClientInformation import ClientInformation - -################################################################################################# - -class TextureCache(): - - addonName = ClientInformation().getAddonName() - - xbmc_host = 'localhost' - xbmc_port = None - xbmc_username = None - xbmc_password = None - enableTextureCache = utils.settings('enableTextureCache') == "true" - - def __init__(self): - - if not self.xbmc_port and self.enableTextureCache: - self.setKodiWebServerDetails() - - def logMsg(self, msg, lvl=1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) - - def double_urlencode(self, text): - text = self.single_urlencode(text) - text = self.single_urlencode(text) - - return text - - def single_urlencode(self, text): - blah = urllib.urlencode({'blahblahblah':text}) - blah = blah[13:] - - return blah - - def setKodiWebServerDetails(self): - # Get the Kodi webserver details - used to set the texture cache - json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserver"}, "id":1}') - jsonobject = json.loads(json_response.decode('utf-8','replace')) - if(jsonobject.has_key('result')): - xbmc_webserver_enabled = jsonobject["result"]["value"] - - if not xbmc_webserver_enabled: - #enable the webserver if not enabled - xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"services.webserverport","value":8080}, "id":1}') - self.xbmc_port = 8080 - xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.SetSettingValue","params":{"setting":"services.webserver","value":true}, "id":1}') - self.xbmc_port = "kodi" - - json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverport"}, "id":1}') - jsonobject = json.loads(json_response.decode('utf-8','replace')) - if(jsonobject.has_key('result')): - self.xbmc_port = jsonobject["result"]["value"] - - json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverusername"}, "id":1}') - jsonobject = json.loads(json_response.decode('utf-8','replace')) - if(jsonobject.has_key('result')): - self.xbmc_username = jsonobject["result"]["value"] - - json_response = xbmc.executeJSONRPC('{"jsonrpc":"2.0", "id":1, "method":"Settings.GetSettingValue","params":{"setting":"services.webserverpassword"}, "id":1}') - jsonobject = json.loads(json_response.decode('utf-8','replace')) - if(jsonobject.has_key('result')): - self.xbmc_password = jsonobject["result"]["value"] - - def FullTextureCacheSync(self): - #this method can be called from the plugin to sync all Kodi textures to the texture cache. - #Warning: this means that every image will be cached locally, this takes diskspace! - - # Remove all existing textures first - path = "special://thumbnails/" - if xbmcvfs.exists(path): - allDirs, allFiles = xbmcvfs.listdir(path) - for dir in allDirs: - allDirs, allFiles = xbmcvfs.listdir(path+dir) - for file in allFiles: - xbmcvfs.delete(os.path.join(path+dir,file)) - - textureconnection = utils.KodiSQL('texture') - texturecursor = textureconnection.cursor() - texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') - rows = texturecursor.fetchall() - for row in rows: - tableName = row[0] - if(tableName != "version"): - texturecursor.execute("DELETE FROM " + tableName) - textureconnection.commit() - texturecursor.close() - - - # Cache all entries in video DB - connection = utils.KodiSQL('video') - cursor = connection.cursor() - cursor.execute("SELECT url FROM art") - result = cursor.fetchall() - for url in result: - self.CacheTexture(url[0]) - cursor.close() - - # Cache all entries in music DB - connection = utils.KodiSQL('music') - cursor = connection.cursor() - cursor.execute("SELECT url FROM art") - result = cursor.fetchall() - for url in result: - self.CacheTexture(url[0]) - cursor.close() - - - def addArtwork(self, artwork, kodiId, mediaType, cursor): - # Kodi conversion table - kodiart = { - - 'Primary': ["thumb", "poster"], - 'Banner': "banner", - 'Logo': "clearlogo", - 'Art': "clearart", - 'Thumb': "landscape", - 'Disc': "discart", - 'Backdrop': "fanart", - 'BoxRear': "poster" - } - - # Artwork is a dictionary - for art in artwork: - - if art == "Backdrop": - # Backdrop entry is a list, process extra fanart for artwork downloader (fanart, fanart1, fanart2, etc.) - backdrops = artwork[art] - backdropsNumber = len(backdrops) - - cursor.execute("SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type LIKE ?", (kodiId, mediaType, "fanart%",)) - rows = cursor.fetchall() - - if len(rows) > backdropsNumber: - # More backdrops in database than what we are going to process. Delete extra fanart. - cursor.execute("DELETE FROM art WHERE media_id = ? AND media_type = ? AND type LIKE ?", (kodiId, mediaType, "fanart_",)) - - index = "" - for backdrop in backdrops: - self.addOrUpdateArt(backdrop, kodiId, mediaType, "%s%s" % ("fanart", index), cursor) - if backdropsNumber > 1: - try: # Will only fail on the first try, str to int. - index += 1 - except TypeError: - index = 1 - - elif art == "Primary": - # Primary art is processed as thumb and poster for Kodi. - for artType in kodiart[art]: - self.addOrUpdateArt(artwork[art], kodiId, mediaType, artType, cursor) - - elif kodiart.get(art): # For banner, logo, art, thumb, disc - # Only process artwork type that Kodi can use - self.addOrUpdateArt(artwork[art], kodiId, mediaType, kodiart[art], cursor) - - def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): - # Possible that the imageurl is an empty string - if imageUrl: - cacheimage = False - - cursor.execute("SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type = ?", (kodiId, mediaType, imageType,)) - try: # Update the artwork - url = cursor.fetchone()[0] - - except: # Add the artwork - cacheimage = True - self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2) - query = "INSERT INTO art(media_id, media_type, type, url) values(?, ?, ?, ?)" - cursor.execute(query, (kodiId, mediaType, imageType, imageUrl)) - - else: # Only cache artwork if it changed - if url != imageUrl: - cacheimage = True - - # Only for the main backdrop, poster - if imageType in ("fanart", "poster"): - # Delete current entry before updating with the new one - self.deleteCachedArtwork(url) - - self.logMsg("Updating Art Link for kodiId: %s (%s) -> (%s)" % (kodiId, url, imageUrl), 1) - query = "UPDATE art set url = ? WHERE media_id = ? AND media_type = ? AND type = ?" - cursor.execute(query, (imageUrl, kodiId, mediaType, imageType)) - - # Cache fanart and poster in Kodi texture cache - if cacheimage and imageType in ("fanart", "poster"): - self.CacheTexture(imageUrl) - - def CacheTexture(self, url): - # Cache a single image url to the texture cache - if url and self.enableTextureCache: - self.logMsg("Processing: %s" % url, 2) - - # Add image to texture cache by simply calling it at the http endpoint - url = self.double_urlencode(url) - try: # Extreme short timeouts so we will have a exception, but we don't need the result so pass - response = requests.head('http://%s:%s/image/image://%s' % (self.xbmc_host, self.xbmc_port, url), auth=(self.xbmc_username, self.xbmc_password), timeout=(0.01, 0.01)) - except: pass - - def deleteCachedArtwork(self, url): - # Only necessary to remove and apply a new backdrop or poster - connection = utils.KodiSQL('texture') - cursor = connection.cursor() - - cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,)) - try: - cachedurl = cursor.fetchone()[0] - - except: - self.logMsg("Could not find cached url.", 1) - - else: # Delete thumbnail as well as the entry - thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl) - self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1) - xbmcvfs.delete(thumbnails) - try: - cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) - connection.commit() - except: - self.logMsg("Issue deleting url from cache. Skipping.", 2) - - finally: - cursor.close() \ No newline at end of file diff --git a/resources/lib/UserPreferences.py b/resources/lib/UserPreferences.py deleted file mode 100644 index ea203133..00000000 --- a/resources/lib/UserPreferences.py +++ /dev/null @@ -1,78 +0,0 @@ - -import sys -import xbmc -import xbmcgui -import xbmcaddon -import json as json -import urllib - -ACTION_BACK = 92 - -class UserPreferences(xbmcgui.WindowXMLDialog): - - configuration = None - save = False - name = None - image = None - - def __init__(self, *args, **kwargs): - xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) - - def onInit(self): - self.action_exitkeys_id = [10, 13] - # set the dialog data - self.save = False - cinemaMode = self.configuration.get(u'EnableCinemaMode') - self.getControl(8011).setSelected(cinemaMode) - self.getControl(8001).setLabel(self.name) - self.getControl(8002).setImage(self.image) - def save(self): - self.save = True - - def isSave(self): - return self.save - - def setConfiguration(self, configuration): - self.configuration = configuration - - def getConfiguration(self): - return self.configuration - - def setName(self, name): - self.name = name - - def setImage(self, image): - self.image = image - - def onFocus(self, controlId): - pass - - def doAction(self): - pass - - def closeDialog(self): - self.close() - - def onClick(self, controlID): - - if(controlID == 8012): - # save now - self.save = True - self.close() - - elif(controlID == 8013): - #cancel - self.close() - - if(controlID == 8011): - # cinema mode - cinemamode = self.getControl(8011).isSelected() - self.configuration['EnableCinemaMode'] = cinemamode - - pass - - def onAction(self, action): - if action == ACTION_BACK: - self.close() - - diff --git a/resources/lib/WebSocketClient.py b/resources/lib/WebSocketClient.py deleted file mode 100644 index 39cfd6b2..00000000 --- a/resources/lib/WebSocketClient.py +++ /dev/null @@ -1,305 +0,0 @@ -# -*- coding: utf-8 -*- - -import json -import threading -import websocket -import logging - -################################################################################################# -# WebSocket Client thread -################################################################################################# - -import xbmc -import xbmcgui -import xbmcaddon - -import KodiMonitor -import Utils as utils -from ClientInformation import ClientInformation -from DownloadUtils import DownloadUtils -from PlaybackUtils import PlaybackUtils -from LibrarySync import LibrarySync -from WriteKodiVideoDB import WriteKodiVideoDB -from WriteKodiMusicDB import WriteKodiMusicDB - -logging.basicConfig() - - -class WebSocketThread(threading.Thread): - - _shared_state = {} - - doUtils = DownloadUtils() - clientInfo = ClientInformation() - KodiMonitor = KodiMonitor.Kodi_Monitor() - - addonName = clientInfo.getAddonName() - - client = None - keepRunning = True - - def __init__(self, *args): - - self.__dict__ = self._shared_state - threading.Thread.__init__(self, *args) - - def logMsg(self, msg, lvl=1): - - self.className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, self.className), msg, int(lvl)) - - def sendProgressUpdate(self, data): - self.logMsg("sendProgressUpdate", 1) - if self.client: - try: - # Send progress update - messageData = { - 'MessageType': "ReportPlaybackProgress", - 'Data': data - } - messageString = json.dumps(messageData) - self.client.send(messageString) - self.logMsg("Message data: %s" % messageString, 2) - except Exception as e: - self.logMsg("Exception: %s" % e, 1) - - def stopClient(self): - # stopping the client is tricky, first set keep_running to false and then trigger one - # more message by requesting one SessionsStart message, this causes the - # client to receive the message and then exit - if self.client: - self.logMsg("Stopping Client") - self.keepRunning = False - self.client.keep_running = False - self.client.close() - self.logMsg("Stopping Client : KeepRunning set to False") - else: - self.logMsg("Stopping Client NO Object ERROR") - - def on_message(self, ws, message): - - WINDOW = xbmcgui.Window(10000) - self.logMsg("Message: %s" % message, 1) - - result = json.loads(message) - messageType = result['MessageType'] - data = result.get("Data") - - if messageType == "Play": - # A remote control play command has been sent from the server. - itemIds = data['ItemIds'] - playCommand = data['PlayCommand'] - - if "PlayNow" in playCommand: - startPositionTicks = data.get('StartPositionTicks', 0) - xbmc.executebuiltin("Dialog.Close(all,true)") - xbmc.executebuiltin("XBMC.Notification(Playlist: Added %s items to Playlist,)" % len(itemIds)) - PlaybackUtils().PLAYAllItems(itemIds, startPositionTicks) - - elif "PlayNext" in playCommand: - xbmc.executebuiltin("XBMC.Notification(Playlist: Added %s items to Playlist,)" % len(itemIds)) - playlist = PlaybackUtils().AddToPlaylist(itemIds) - if not xbmc.Player().isPlaying(): - xbmc.Player().play(playlist) - - elif messageType == "Playstate": - # A remote control update playstate command has been sent from the server. - command = data['Command'] - - if "Stop" in command: - self.logMsg("Playback Stopped.", 1) - xbmc.Player().stop() - elif "Unpause" in command: - self.logMsg("Playback unpaused.", 1) - xbmc.Player().pause() - elif "Pause" in command: - self.logMsg("Playback paused.", 1) - xbmc.Player().pause() - elif "NextTrack" in command: - self.logMsg("Playback next track.", 1) - xbmc.Player().playnext() - elif "PreviousTrack" in command: - self.logMsg("Playback previous track.", 1) - xbmc.Player().playprevious() - elif "Seek" in command: - seekPositionTicks = data['SeekPositionTicks'] - seekTime = seekPositionTicks / 10000000.0 - self.logMsg("Seek to %s" % seekTime, 1) - xbmc.Player().seekTime(seekTime) - # Report playback - WINDOW.setProperty('commandUpdate', "true") - - elif messageType == "UserDataChanged": - # A user changed their personal rating for an item, or their playstate was updated - userdataList = data['UserDataList'] - self.logMsg("Message: Doing UserDataChanged: UserDataList: %s" % userdataList, 1) - LibrarySync().user_data_update(userdataList) - - elif messageType == "LibraryChanged": - # Library items - itemsRemoved = data.get("ItemsRemoved") - itemsAdded = data.get("ItemsAdded") - itemsUpdated = data.get("ItemsUpdated") - itemsToUpdate = itemsAdded + itemsUpdated - - self.logMsg("Message: WebSocket LibraryChanged: Items Added: %s" % itemsAdded, 1) - self.logMsg("Message: WebSocket LibraryChanged: Items Updated: %s" % itemsUpdated, 1) - self.logMsg("Message: WebSocket LibraryChanged: Items Removed: %s" % itemsRemoved, 1) - - LibrarySync().remove_items(itemsRemoved) - LibrarySync().update_items(itemsToUpdate) - - elif messageType == "GeneralCommand": - - command = data['Name'] - arguments = data.get("Arguments") - - if command in ('Mute', 'Unmute', 'SetVolume', 'SetSubtitleStreamIndex', 'SetAudioStreamIndex'): - # These commands need to be reported back - if command == "Mute": - xbmc.executebuiltin('Mute') - elif command == "Unmute": - xbmc.executebuiltin('Mute') - elif command == "SetVolume": - volume = arguments['Volume'] - xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume) - elif command == "SetSubtitleStreamIndex": - # Emby merges audio and subtitle index together - xbmcplayer = xbmc.Player() - currentFile = xbmcplayer.getPlayingFile() - embyIndex = int(arguments['Index']) - - mapping = WINDOW.getProperty("%sIndexMapping" % currentFile) - externalIndex = json.loads(mapping) - - if externalIndex: - # If there's external subtitles added via PlaybackUtils - for index in externalIndex: - if externalIndex[index] == embyIndex: - xbmcplayer.setSubtitleStream(int(index)) - else: - # User selected internal subtitles - external = len(externalIndex) - audioTracks = len(xbmcplayer.getAvailableAudioStreams()) - xbmcplayer.setSubtitleStream(external + embyIndex - audioTracks - 1) - else: - # Emby merges audio and subtitle index together - audioTracks = len(xbmcplayer.getAvailableAudioStreams()) - xbmcplayer.setSubtitleStream(index - audioTracks - 1) - - elif command == "SetAudioStreamIndex": - index = int(arguments['Index']) - xbmc.Player().setAudioStream(index - 1) - # Report playback - WINDOW.setProperty('commandUpdate', "true") - - else: - # GUI commands - if command == "ToggleFullscreen": - xbmc.executebuiltin('Action(FullScreen)') - elif command == "ToggleOsdMenu": - xbmc.executebuiltin('Action(OSD)') - elif command == "MoveUp": - xbmc.executebuiltin('Action(Up)') - elif command == "MoveDown": - xbmc.executebuiltin('Action(Down)') - elif command == "MoveLeft": - xbmc.executebuiltin('Action(Left)') - elif command == "MoveRight": - xbmc.executebuiltin('Action(Right)') - elif command == "Select": - xbmc.executebuiltin('Action(Select)') - elif command == "Back": - xbmc.executebuiltin('Action(back)') - elif command == "ToggleContextMenu": - xbmc.executebuiltin('Action(ContextMenu)') - elif command == "GoHome": - xbmc.executebuiltin('ActivateWindow(Home)') - elif command == "PageUp": - xbmc.executebuiltin('Action(PageUp)') - elif command == "NextLetter": - xbmc.executebuiltin('Action(NextLetter)') - elif command == "GoToSearch": - xbmc.executebuiltin('VideoLibrary.Search') - elif command == "GoToSettings": - xbmc.executebuiltin('ActivateWindow(Settings)') - elif command == "PageDown": - xbmc.executebuiltin('Action(PageDown)') - elif command == "PreviousLetter": - xbmc.executebuiltin('Action(PrevLetter)') - elif command == "TakeScreenshot": - xbmc.executebuiltin('TakeScreenshot') - elif command == "ToggleMute": - xbmc.executebuiltin('Mute') - elif command == "VolumeUp": - xbmc.executebuiltin('Action(VolumeUp)') - elif command == "VolumeDown": - xbmc.executebuiltin('Action(VolumeDown)') - elif command == "DisplayMessage": - header = arguments['Header'] - text = arguments['Text'] - xbmcgui.Dialog().notification(header, text, icon="special://home/addons/plugin.video.emby/icon.png", time=4000) - elif command == "SendString": - string = arguments['String'] - text = '{"jsonrpc": "2.0", "method": "Input.SendText", "params": { "text": "%s", "done": false }, "id": 0}' % string - result = xbmc.executeJSONRPC(text) - else: - self.logMsg("Unknown command.", 1) - - elif messageType == "ServerRestarting": - if utils.settings('supressRestartMsg') == "true": - xbmcgui.Dialog().notification("Emby server", "Server is restarting.", icon="special://home/addons/plugin.video.emby/icon.png") - - def on_error(self, ws, error): - if "10061" in str(error): - # Server is offline - pass - else: - self.logMsg("Error: %s" % error, 1) - #raise - - def on_close(self, ws): - self.logMsg("Closed", 2) - - def on_open(self, ws): - deviceId = ClientInformation().getMachineId() - self.doUtils.postCapabilities(deviceId) - - def run(self): - - WINDOW = xbmcgui.Window(10000) - logLevel = int(WINDOW.getProperty('getLogLevel')) - username = WINDOW.getProperty('currUser') - server = WINDOW.getProperty('server%s' % username) - token = WINDOW.getProperty('accessToken%s' % username) - deviceId = ClientInformation().getMachineId() - - '''if (logLevel == 2): - websocket.enableTrace(True)''' - - # Get the appropriate prefix for websocket - if "https" in server: - server = server.replace('https', 'wss') - else: - server = server.replace('http', 'ws') - - websocketUrl = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId) - self.logMsg("websocket URL: %s" % websocketUrl) - - self.client = websocket.WebSocketApp(websocketUrl, - on_message = self.on_message, - on_error = self.on_error, - on_close = self.on_close) - - self.client.on_open = self.on_open - - while self.keepRunning: - - self.client.run_forever() - - if self.keepRunning: - self.logMsg("Client Needs To Restart", 2) - if self.KodiMonitor.waitForAbort(5): - break - - self.logMsg("Thread Exited", 1) \ No newline at end of file diff --git a/resources/lib/WriteKodiMusicDB.py b/resources/lib/WriteKodiMusicDB.py deleted file mode 100644 index efb22e96..00000000 --- a/resources/lib/WriteKodiMusicDB.py +++ /dev/null @@ -1,518 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# -# WriteKodiMusicDB -################################################################################################# - -import sqlite3 -from datetime import datetime -from ntpath import split as ntsplit - -import xbmc -import xbmcgui -import xbmcaddon - -from ClientInformation import ClientInformation -import Utils as utils -from API import API -from PlayUtils import PlayUtils -from ReadKodiDB import ReadKodiDB -from ReadEmbyDB import ReadEmbyDB -from TextureCache import TextureCache - - -class WriteKodiMusicDB(): - - textureCache = TextureCache() - kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) - - addonName = ClientInformation().getAddonName() - - def __init__(self): - - username = utils.window('currUser') - self.userid = utils.window('userId%s' % username) - self.server = utils.window('server%s' % username) - - def logMsg(self, msg, lvl = 1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) - - def addOrUpdateArtistToKodiLibrary(self, MBitem, connection, cursor): - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - kodiVersion = self.kodiversion - embyId = MBitem["Id"] - - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,)) - try: - artistid = cursor.fetchone()[0] - except: - artistid = None - - ##### The artist details ##### - lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - dateadded = API().getDateCreated(MBitem) - checksum = API().getChecksum(MBitem) - - name = MBitem['Name'] - musicBrainzId = API().getProvider(MBitem, "musicBrainzArtist") - genres = " / ".join(MBitem.get('Genres')) - bio = API().getOverview(MBitem) - - # Associate artwork - artworks = API().getAllArtwork(MBitem, parentInfo=True) - thumb = artworks['Primary'] - backdrops = artworks['Backdrop'] # List - - if thumb: - thumb = "%s" % thumb - if backdrops: - fanart = "%s" % backdrops[0] - else: - fanart = "" - - ##### UPDATE THE ARTIST ##### - if artistid: - self.logMsg("UPDATE artist to Kodi library, Id: %s - Artist: %s" % (embyId, name), 1) - - if kodiVersion == 16: - query = "UPDATE artist SET strArtist = ?, strMusicBrainzArtistID = ?, strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?, lastScraped = ? WHERE idArtist = ?" - cursor.execute(query, (name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, artistid)) - else: - query = "UPDATE artist SET strArtist = ?, strMusicBrainzArtistID = ?, strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?, lastScraped = ?, dateadded = ? WHERE idArtist = ?" - cursor.execute(query, (name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, dateadded, artistid)) - - # Update the checksum in emby table - query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" - cursor.execute(query, (checksum, embyId)) - - ##### OR ADD THE ARTIST ##### - else: - self.logMsg("ADD artist to Kodi library, Id: %s - Artist: %s" % (embyId, name), 1) - - #safety checks: It looks like Emby supports the same artist multiple times in the database while Kodi doesn't allow that. In case that happens we just merge the artist in the Kodi database. - - # Safety check 1: does the artist already exist? - cursor.execute("SELECT idArtist FROM artist WHERE strArtist = ? COLLATE NOCASE", (name,)) - try: - artistid = cursor.fetchone()[0] - self.logMsg("Artist already exists in Kodi library - appending to existing object, Id: %s - Artist: %s - MusicBrainzId: %s - existing Kodi Id: %s" % (embyId, name, musicBrainzId, str(artistid)), 1) - except: pass - - # Safety check 2: does the MusicBrainzArtistId already exist? - cursor.execute("SELECT idArtist FROM artist WHERE strMusicBrainzArtistID = ?", (musicBrainzId,)) - try: - artistid = cursor.fetchone()[0] - self.logMsg("Artist already exists in Kodi library - appending to existing object, Id: %s - Artist: %s - MusicBrainzId: %s - existing Kodi Id: %s" % (embyId, name, musicBrainzId, str(artistid)), 1) - except: pass - - if not artistid: - # Create the artist - cursor.execute("select coalesce(max(idArtist),0) as artistid from artist") - artistid = cursor.fetchone()[0] + 1 - if kodiVersion == 16: - query = "INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID, strGenres, strBiography, strImage, strFanart, lastScraped) values(?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (artistid, name, musicBrainzId, genres, bio, thumb, fanart, lastScraped)) - else: - query = "INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID, strGenres, strBiography, strImage, strFanart, lastScraped, dateAdded) values(?, ?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (artistid, name, musicBrainzId, genres, bio, thumb, fanart, lastScraped, dateadded)) - - # Create the reference in emby table - query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)" - cursor.execute(query, (embyId, artistid, "artist", checksum)) - - # Update artwork - self.textureCache.addArtwork(artworks, artistid, "artist", cursor) - - def addOrUpdateAlbumToKodiLibrary(self, MBitem, connection, cursor): - - kodiVersion = self.kodiversion - - embyId = MBitem["Id"] - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,)) - try: - albumid = cursor.fetchone()[0] - except: - albumid = None - - genres = MBitem.get('Genres') - - ##### The album details ##### - lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - dateadded = API().getDateCreated(MBitem) - checksum = API().getChecksum(MBitem) - - name = MBitem['Name'] - musicBrainzId = API().getProvider(MBitem, "musicBrainzAlbum") - year = MBitem.get('ProductionYear') - genre = " / ".join(genres) - bio = API().getOverview(MBitem) - - MBartists = [] - for item in MBitem['AlbumArtists']: - MBartists.append(item['Name']) - artists = " / ".join(MBartists) - - # Associate the artwork - artworks = API().getAllArtwork(MBitem, parentInfo=True) - thumb = artworks['Primary'] - if thumb: - thumb = "%s" % thumb - - - ##### UPDATE THE ALBUM ##### - if albumid: - self.logMsg("UPDATE album to Kodi library, Id: %s - Title: %s" % (embyId, name), 1) - - if kodiVersion == 15: - # Kodi Isengard - query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, dateAdded = ?, strReleaseType = ? WHERE idAlbum = ?" - cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, "album", albumid)) - elif kodiVersion == 16: - query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, strReleaseType = ? WHERE idAlbum = ?" - cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, "album", albumid)) - else: - # Kodi Gotham and Helix - query = "UPDATE album SET strAlbum = ?, strMusicBrainzAlbumID = ?, strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?, lastScraped = ?, dateAdded = ? WHERE idAlbum = ?" - cursor.execute(query, (name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, albumid)) - - # Update the checksum in emby table - query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" - cursor.execute(query, (checksum, embyId)) - - ##### OR ADD THE ALBUM ##### - else: - self.logMsg("ADD album to Kodi library, Id: %s - Title: %s" % (embyId, name), 1) - - # Safety check: does the strMusicBrainzAlbumID already exist? - cursor.execute("SELECT idAlbum FROM album WHERE strMusicBrainzAlbumID = ?", (musicBrainzId,)) - try: - albumid = cursor.fetchone()[0] - except: - # Create the album - cursor.execute("select coalesce(max(idAlbum),0) as albumid from album") - albumid = cursor.fetchone()[0] + 1 - if kodiVersion == 15: - # Kodi Isengard - query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, dateAdded, strReleaseType) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded, "album")) - elif kodiVersion == 16: - query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, strReleaseType) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, "album")) - else: - # Kodi Gotham and Helix - query = "INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID, strArtists, iYear, strGenres, strReview, strImage, lastScraped, dateAdded) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (albumid, name, musicBrainzId, artists, year, genre, bio, thumb, lastScraped, dateadded)) - - # Create the reference in emby table - query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)" - cursor.execute(query, (embyId, albumid, "album", checksum)) - - - # Add genres - self.AddGenresToMedia(albumid, genres, "album", cursor) - - # Update artwork - if artworks['Primary']: - self.textureCache.addOrUpdateArt(artworks['Primary'], albumid, "album", "thumb", cursor) - artworks['Primary'] = "" - - if artworks.get('BoxRear'): - self.textureCache.addOrUpdateArt(artworks['BoxRear'], albumid, "album", "poster", cursor) - artworks['BoxRear'] = "" - - self.textureCache.addArtwork(artworks, albumid, "album", cursor) - - # Link album to artists - if MBartists: - album_artists = MBitem['AlbumArtists'] - else: - album_artists = MBitem.get('ArtistItems', []) - - for artist in album_artists: - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],)) - try: - artistid = cursor.fetchone()[0] - except: pass - else: - query = "INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) values(?, ?, ?)" - cursor.execute(query, (artistid, albumid, artist['Name'])) - # Update discography - query = "INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) values(?, ?, ?)" - cursor.execute(query, (artistid, name, str(year))) - - def addOrUpdateSongToKodiLibrary(self, MBitem, connection, cursor): - - kodiVersion = self.kodiversion - - embyId = MBitem["Id"] - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,)) - try: - songid = cursor.fetchone()[0] - except: - songid = None - - timeInfo = API().getTimeInfo(MBitem) - userData = API().getUserData(MBitem) - genres = MBitem.get('Genres') - - ##### The song details ##### - playcount = userData.get('PlayCount') - lastplayed = userData.get('LastPlayedDate') - dateadded = API().getDateCreated(MBitem) - checksum = API().getChecksum(MBitem) - - name = MBitem['Name'] - musicBrainzId = API().getProvider(MBitem, "musicBrainzTrackId") - genre = " / ".join(genres) - artists = " / ".join(MBitem.get('Artists')) - tracknumber = MBitem.get('IndexNumber', 0) - disc = MBitem.get('ParentIndexNumber', 1) - track = disc*2**16 + tracknumber - year = MBitem.get('ProductionYear') - bio = API().getOverview(MBitem) - duration = timeInfo.get('TotalTime') - - - if utils.settings('directstreammusic') == "true": - WINDOW = xbmcgui.Window(10000) - username = WINDOW.getProperty('currUser') - server = WINDOW.getProperty('server%s' % username) - - playurl = PlayUtils().directStream(MBitem, server, embyId, "Audio") - filename = "stream.mp3" - path = playurl.replace(filename, "") - else: - # Get the path and filename - playurl = PlayUtils().directPlay(MBitem) - path, filename = ntsplit(playurl) - if "/" in playurl: - path = "%s/" % path - elif "\\" in playurl: - path = "%s\\" % path - - - # Validate the path in database - cursor.execute("SELECT idPath as pathid FROM path WHERE strPath = ?", (path,)) - try: - pathid = cursor.fetchone()[0] - except: - cursor.execute("select coalesce(max(idPath),0) as pathid from path") - pathid = cursor.fetchone()[0] + 1 - query = "INSERT INTO path(idPath, strPath) values(?, ?)" - cursor.execute(query, (pathid, path)) - - # Get the album - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (MBitem.get("AlbumId"),)) - try: - albumid = cursor.fetchone()[0] - except: - # No album found, create a single's album - cursor.execute("select coalesce(max(idAlbum),0) as albumid from album") - albumid = cursor.fetchone()[0] + 1 - if kodiVersion == 15: - # Kodi Isengard - query = "INSERT INTO album(idAlbum, strGenres, iYear, dateAdded, strReleaseType) values(?, ?, ?, ?, ?)" - cursor.execute(query, (albumid, genre, year, dateadded, "single")) - elif kodiVersion == 16: - query = "INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType) values(?, ?, ?, ?)" - cursor.execute(query, (albumid, genre, year, "single")) - else: - # Kodi Gotham and Helix - query = "INSERT INTO album(idAlbum, strGenres, iYear, dateAdded) values(?, ?, ?, ?)" - cursor.execute(query, (albumid, genre, year, dateadded)) - finally: - cursor.execute("SELECT strArtists FROM album WHERE idAlbum = ?", (albumid,)) - result = cursor.fetchone() - if result and result[0] == "": - # Link album to artists - if MBitem['AlbumArtists']: - album_artists = MBitem['AlbumArtists'] - else: - album_artists = MBitem['ArtistItems'] - - MBartists = [] - for artist in album_artists: - MBartists.append(artist['Name']) - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],)) - try: - artistid = cursor.fetchone()[0] - except: pass - else: - query = "INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) values(?, ?, ?)" - cursor.execute(query, (artistid, albumid, artist['Name'])) - - artists_onalbum = " / ".join(MBartists) - if kodiVersion == 15: - # Kodi Isengard - query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" - cursor.execute(query, (artists_onalbum, albumid)) - elif kodiVersion == 16: - query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" - cursor.execute(query, (artists_onalbum, albumid)) - else: - # Kodi Gotham and Helix - query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" - cursor.execute(query, (artists_onalbum, albumid)) - - - ##### UPDATE THE SONG ##### - if songid: - self.logMsg("UPDATE song to Kodi library, Id: %s - Title: %s" % (embyId, name), 1) - - query = "UPDATE song SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?, iDuration = ?, iYear = ?, strFilename = ?, strMusicBrainzTrackID = ?, iTimesPlayed = ?, lastplayed = ? WHERE idSong = ?" - cursor.execute(query, (albumid, artists, genre, name, track, duration, year, filename, musicBrainzId, playcount, lastplayed, songid)) - - # Update the checksum in emby table - query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" - cursor.execute(query, (checksum, embyId)) - - ##### OR ADD THE SONG ##### - else: - self.logMsg("ADD song to Kodi library, Id: %s - Title: %s" % (embyId, name), 1) - - # Create the song - cursor.execute("select coalesce(max(idSong),0) as songid from song") - songid = cursor.fetchone()[0] + 1 - query = "INSERT INTO song(idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack, iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (songid, albumid, pathid, artists, genre, name, track, duration, year, filename, musicBrainzId, playcount, lastplayed)) - - # Create the reference in emby table - query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)" - cursor.execute(query, (embyId, songid, "song", checksum)) - - - # Add genres - self.AddGenresToMedia(songid, genres, "song", cursor) - - # Link song to album - if albumid: - query = "INSERT OR REPLACE INTO albuminfosong(idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration) values(?, ?, ?, ?, ?)" - cursor.execute(query, (songid, albumid, track, name, duration)) - - # Link song to artist - for artist in MBitem.get('ArtistItems'): - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (artist['Id'],)) - try: - artistid = cursor.fetchone()[0] - except: pass - else: - query = "INSERT OR REPLACE INTO song_artist(idArtist, idSong, strArtist) values(?, ?, ?)" - cursor.execute(query, (artistid, songid, artist['Name'])) - - # Update artwork - self.textureCache.addArtwork(API().getAllArtwork(MBitem, parentInfo=True), songid, "song", cursor) - - def deleteItemFromKodiLibrary(self, id, connection, cursor): - - cursor.execute("SELECT kodi_id, media_type FROM emby WHERE emby_id=?", (id,)) - try: - result = cursor.fetchone() - kodi_id = result[0] - media_type = result[1] - except: pass - else: - if "artist" in media_type: - self.logMsg("Deleting artist from Kodi library, Id: %s" % id, 1) - cursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodi_id,)) - elif "song" in media_type: - self.logMsg("Deleting song from Kodi library, Id: %s" % id, 1) - cursor.execute("DELETE FROM song WHERE idSong = ?", (kodi_id,)) - elif "album" in media_type: - self.logMsg("Deleting album from Kodi library, Id: %s" % id, 1) - cursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodi_id,)) - - # Delete the record in emby table - cursor.execute("DELETE FROM emby WHERE emby_id = ?", (id,)) - - def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): - - if imageUrl: - - cacheimage = False - - cursor.execute("SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type = ?", (kodiId, mediaType, imageType,)) - try: - url = cursor.fetchone()[0] - except: # Image does not exists - cacheimage = True - self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 1) - query = "INSERT INTO art(media_id, media_type, type, url) values(?, ?, ?, ?)" - cursor.execute(query, (kodiId, mediaType, imageType, imageUrl)) - else: - if url != imageUrl: - cacheimage = True - self.logMsg("Updating Art Link for kodiId: %s (%s) -> (%s)" % (kodiId, url, imageUrl), 1) - query = "UPDATE art SET url = ? WHERE media_id = ? and media_type = ? and type = ?" - cursor.execute(query, (imageUrl, kodiId, mediaType, imageType)) - - # Cache fanart textures in Kodi texture cache - if cacheimage and "fanart" in imageType: - self.textureCache.CacheTexture(imageUrl) - - def AddGenresToMedia(self, id, genres, mediatype, cursor): - - if genres: - - for genre in genres: - - cursor.execute("SELECT idGenre as idGenre FROM genre WHERE strGenre = ? COLLATE NOCASE", (genre,)) - try: - idGenre = cursor.fetchone()[0] - except: # Create the genre - cursor.execute("select coalesce(max(idGenre),0) as idGenre from genre") - idGenre = cursor.fetchone()[0] + 1 - query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" - cursor.execute(query, (idGenre, genre)) - finally: # Assign the genre to item - if "album" in mediatype: - query = "INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) values(?, ?)" - cursor.execute(query, (idGenre, id)) - elif "song" in mediatype: - query = "INSERT OR REPLACE INTO song_genre(idGenre, idSong) values(?, ?)" - cursor.execute(query, (idGenre, id)) - - def updateUserdata(self, userdata, connection, cursor): - # This updates: LastPlayedDate, Playcount - embyId = userdata['ItemId'] - MBitem = ReadEmbyDB().getItem(embyId) - - if not MBitem: - self.logMsg("UPDATE userdata to Kodi library FAILED, Item %s not found on server!" % embyId, 1) - return - - # Get details - checksum = API().getChecksum(MBitem) - userdata = API().getUserData(MBitem) - - # Find the Kodi Id - cursor.execute("SELECT kodi_id, media_type FROM emby WHERE emby_id = ?", (embyId,)) - try: - result = cursor.fetchone() - kodiid = result[0] - mediatype = result[1] - self.logMsg("Found embyId: %s in database - kodiId: %s type: %s" % (embyId, kodiid, mediatype), 1) - except: - self.logMsg("Id: %s not found in the emby database table." % embyId, 1) - else: - if mediatype in ("song"): - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - - query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ? WHERE idSong = ?" - cursor.execute(query, (playcount, dateplayed, kodiid)) - - #update the checksum in emby table - query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" - cursor.execute(query, (checksum, embyId)) \ No newline at end of file diff --git a/resources/lib/WriteKodiVideoDB.py b/resources/lib/WriteKodiVideoDB.py deleted file mode 100644 index bc11bcf8..00000000 --- a/resources/lib/WriteKodiVideoDB.py +++ /dev/null @@ -1,1338 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################################# -# WriteKodiVideoDB -################################################################################################# - -import sqlite3 -from ntpath import dirname, split as ntsplit - -import xbmc -import xbmcgui -import xbmcaddon - -from ClientInformation import ClientInformation -import Utils as utils -from API import API -from DownloadUtils import DownloadUtils -from PlayUtils import PlayUtils -from ReadKodiDB import ReadKodiDB -from ReadEmbyDB import ReadEmbyDB -from TextureCache import TextureCache - -class WriteKodiVideoDB(): - - textureCache = TextureCache() - doUtils = DownloadUtils() - kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) - - addonName = ClientInformation().getAddonName() - - def __init__(self): - - username = utils.window('currUser') - self.userid = utils.window('userId%s' % username) - self.server = utils.window('server%s' % username) - - self.directpath = utils.settings('useDirectPaths') == "true" - - def logMsg(self, msg, lvl = 1): - - className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) - - def updatePlayCountFromKodi(self, id, type, playcount = 0): - - # When user marks item watched from kodi interface update this in Emby - # Erase resume point when user marks watched/unwatched to follow Emby behavior - doUtils = self.doUtils - self.logMsg("updatePlayCountFromKodi Called", 2) - - connection = utils.KodiSQL() - cursor = connection.cursor() - cursor.execute("SELECT emby_id FROM emby WHERE media_type = ? AND kodi_id = ?", (type, id,)) - - try: # Find associated Kodi Id to Emby Id - emby_id = cursor.fetchone()[0] - except: - # Could not find the Emby Id - self.logMsg("Emby Id not found.", 2) - else: - # Stop from manually marking as watched unwatched, with actual playback. - # Window property is set in Player.py - if utils.window('SkipWatched%s' % emby_id) == "true": - utils.window('SkipWatched%s' % emby_id, clear=True) - else: - # Found the Emby Id, let Emby server know of new playcount - watchedurl = "{server}/mediabrowser/Users/{UserId}/PlayedItems/%s" % emby_id - if playcount != 0: - doUtils.downloadUrl(watchedurl, type = "POST") - self.logMsg("Mark as watched for Id: %s, playcount: %s." % (emby_id, playcount), 1) - else: - doUtils.downloadUrl(watchedurl, type = "DELETE") - self.logMsg("Mark as unwatched for Id: %s, playcount: %s." % (emby_id, playcount), 1) - # Erase any resume point associated - self.setKodiResumePoint(id, 0, 0, cursor, playcount) - finally: - cursor.close() - - def addOrUpdateMovieToKodiLibrary(self, embyId, connection, cursor, viewTag): - - MBitem = ReadEmbyDB().getFullItem(embyId) - - if not MBitem: - self.logMsg("ADD movie to Kodi library FAILED, Item %s not found on server!" % embyId, 1) - return - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,)) - try: - movieid = cursor.fetchone()[0] - except: - movieid = None - self.logMsg("Movie Id: %s not found." % embyId, 1) - - - timeInfo = API().getTimeInfo(MBitem) - userData = API().getUserData(MBitem) - people = API().getPeople(MBitem) - genres = MBitem.get('Genres') - studios = API().getStudios(MBitem) - - #### The movie details #### - playcount = userData.get('PlayCount') - dateplayed = userData.get("LastPlayedDate") - dateadded = API().getDateCreated(MBitem) - checksum = API().getChecksum(MBitem) - - title = MBitem['Name'] - plot = API().getOverview(MBitem) - shortplot = MBitem.get('ShortOverview') - tagline = API().getTagline(MBitem) - votecount = MBitem.get('VoteCount') - rating = MBitem.get('CommunityRating') - writer = " / ".join(people.get('Writer')) - year = MBitem.get('ProductionYear') - imdb = API().getProvider(MBitem, "imdb") - sorttitle = MBitem['SortName'] - runtime = timeInfo.get('TotalTime') - mpaa = API().getMpaa(MBitem) - genre = " / ".join(genres) - director = " / ".join(people.get('Director')) - try: - studio = studios[0] - except IndexError: - studio = None - country = API().getCountry(MBitem) - - try: # Verify if there's a local trailer - if MBitem.get('LocalTrailerCount'): - itemTrailerUrl = "{server}/mediabrowser/Users/{UserId}/Items/%s/LocalTrailers?format=json" % embyId - result = self.doUtils.downloadUrl(itemTrailerUrl) - trailerUrl = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id'] - # Or get youtube trailer - else: - trailerUrl = MBitem['RemoteTrailers'][0]['Url'] - trailerId = trailerUrl.split('=')[1] - trailerUrl = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId - except: - trailerUrl = None - - ##### ADD OR UPDATE THE FILE AND PATH ##### - ##### NOTE THAT LASTPLAYED AND PLAYCOUNT ARE STORED AT THE FILE ENTRY ##### - - playurl = PlayUtils().directPlay(MBitem) - realfile = "" - realpath = "" - - if self.directpath: - if playurl == False: - return - elif "\\" in playurl: - filename = playurl.rsplit("\\",1)[-1] - path = playurl.replace(filename, "") - elif "/" in playurl: - filename = playurl.rsplit("/",1)[-1] - path = playurl.replace(filename, "") - else: - self.logMsg("Invalid path: %s" % playurl, 1) - return - else: # Set plugin path and media flags using real filename - try: - if not "plugin://" in playurl: - realpath, realfile = ntsplit(playurl) - if "/" in playurl: - realpath = realpath + "/" - else: - realpath = realpath + "\\" - except: - pass - - filename = "plugin://plugin.video.emby/movies/%s/?filename=%s&id=%s&mode=play" % (embyId, realfile, embyId) - path = "plugin://plugin.video.emby/movies/%s/" % embyId - - - ##### UPDATE THE MOVIE ##### - if movieid: - self.logMsg("UPDATE movie to Kodi Library, Id: %s - Title: %s" % (embyId, title), 1) - - #get the file ID - cursor.execute("SELECT idFile as fileid FROM movie WHERE idMovie = ?", (movieid,)) - fileid = cursor.fetchone()[0] - - #always update the filepath (fix for path change) - query = "UPDATE files SET strFilename = ?, dateAdded = ? WHERE idFile = ?" - cursor.execute(query, (filename, dateadded, fileid)) - - query = "UPDATE movie SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?, c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?, c16 = ?, c18 = ?, c19 = ?, c21 = ? WHERE idMovie = ?" - cursor.execute(query, (title, plot, shortplot, tagline, votecount, rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, trailerUrl, country, movieid)) - - # Update the checksum in emby table and critic ratings - query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" - cursor.execute(query, (checksum, embyId)) - - ##### OR ADD THE MOVIE ##### - else: - self.logMsg("ADD movie to Kodi Library, Id: %s - Title: %s" % (embyId, title), 1) - - # Validate the path in database - cursor.execute("SELECT idPath as pathid FROM path WHERE strPath = ?", (path,)) - try: - pathid = cursor.fetchone()[0] - except: - # Path does not exist yet - cursor.execute("select coalesce(max(idPath),0) as pathid from path") - pathid = cursor.fetchone()[0] + 1 - query = "INSERT into path(idPath, strPath, strContent, strScraper, noUpdate) values(?, ?, ?, ?, ?)" - cursor.execute(query, (pathid, path, "movies", "metadata.local", 1)) - - # Validate the file in database - if self.directpath: - cursor.execute("SELECT idFile as fileid FROM files WHERE strFilename = ? and idPath = ?", (filename, pathid,)) - else: - cursor.execute("SELECT idFile as fileid FROM files WHERE strFilename LIKE ? and idPath = ?", (filename, embyId,)) - try: - fileid = cursor.fetchone()[0] - except: - # File does not exist yet - cursor.execute("select coalesce(max(idFile),0) as fileid from files") - fileid = cursor.fetchone()[0] + 1 - query = "INSERT INTO files(idFile, idPath, strFilename, playCount, lastPlayed, dateAdded) values(?, ?, ?, ?, ?, ?)" - cursor.execute(query, (fileid, pathid, filename, playcount, dateplayed, dateadded)) - - # Create the movie - cursor.execute("select coalesce(max(idMovie),0) as movieid from movie") - movieid = cursor.fetchone()[0] + 1 - query = "INSERT INTO movie(idMovie, idFile, c00, c01, c02, c03, c04, c05, c06, c07, c09, c10, c11, c12, c14, c15, c16, c18, c19, c21) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (movieid, fileid, title, plot, shortplot, tagline, votecount, rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, trailerUrl, country)) - - # Create the reference in emby table - query = "INSERT INTO emby(emby_id, kodi_id, kodi_file_id, media_type, checksum) values(?, ?, ?, ?, ?)" - cursor.execute(query, (embyId, movieid, fileid, "movie", checksum)) - - - # Add tags to item, view tag and emby tags - tags = [viewTag] - tags.extend(MBitem['Tags']) - if userData['Favorite']: - tags.append("Favorite movies") - - self.AddTagsToMedia(movieid, tags, "movie", cursor) - - # Update artwork - self.textureCache.addArtwork(API().getAllArtwork(MBitem), movieid, "movie", cursor) - - # Update or insert actors - self.AddPeopleToMedia(movieid, MBitem.get('People'), "movie", connection, cursor) - - # Update genres - self.AddGenresToMedia(movieid, genres, "movie", cursor) - - # Update countries - self.AddCountriesToMedia(movieid, MBitem.get('ProductionLocations'), "movie", cursor) - - # Update studios - self.AddStudiosToMedia(movieid, studios, "movie", cursor) - - # Add streamdetails - self.AddStreamDetailsToMedia(API().getMediaStreams(MBitem), runtime ,fileid, cursor) - - # Set resume point and round to 6th decimal - resume = round(float(timeInfo.get('ResumeTime')), 6) - total = round(float(timeInfo.get('TotalTime')), 6) - jumpback = int(utils.settings('resumeJumpBack')) - if resume > jumpback: - # To avoid negative bookmark - resume = resume - jumpback - self.setKodiResumePoint(fileid, resume, total, cursor, playcount, dateplayed, realpath, realfile) - - def addOrUpdateMusicVideoToKodiLibrary( self, embyId ,connection, cursor): - - WINDOW = xbmcgui.Window(10000) - username = WINDOW.getProperty('currUser') - userid = WINDOW.getProperty('userId%s' % username) - server = WINDOW.getProperty('server%s' % username) - downloadUtils = DownloadUtils() - - MBitem = ReadEmbyDB().getFullItem(embyId) - - if not MBitem: - utils.logMsg("ADD musicvideo to Kodi library FAILED", "Item %s not found on server!" %embyId) - return - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?",(MBitem["Id"],)) - result = cursor.fetchone() - if result != None: - idMVideo = result[0] - else: - idMVideo = None - - timeInfo = API().getTimeInfo(MBitem) - userData=API().getUserData(MBitem) - people = API().getPeople(MBitem) - - #### The video details ######### - runtime = timeInfo.get('TotalTime') - plot = utils.convertEncoding(API().getOverview(MBitem)) - title = utils.convertEncoding(MBitem["Name"]) - year = MBitem.get("ProductionYear") - genres = MBitem.get("Genres") - genre = " / ".join(genres) - studios = API().getStudios(MBitem) - studio = " / ".join(studios) - director = " / ".join(people.get("Director")) - artist = " / ".join(MBitem.get("Artists")) - album = MBitem.get("Album") - track = MBitem.get("Track") - dateplayed = userData.get("LastPlayedDate") - playcount = userData.get('PlayCount') - dateadded = API().getDateCreated(MBitem) - - ##### ADD OR UPDATE THE FILE AND PATH ##### - ##### NOTE THAT LASTPLAYED AND PLAYCOUNT ARE STORED AT THE FILE ENTRY ##### - - playurl = PlayUtils().directPlay(MBitem) - realfile = "" - realpath = "" - - if self.directpath: - if playurl == False: - return - elif "\\" in playurl: - filename = playurl.rsplit("\\",1)[-1] - path = playurl.replace(filename, "") - elif "/" in playurl: - filename = playurl.rsplit("/",1)[-1] - path = playurl.replace(filename, "") - else: - self.logMsg("Invalid path: %s" % playurl, 1) - return - else: # Set plugin path and media flags using real filename - try: - if not "plugin://" in playurl: - realpath, realfile = ntsplit(playurl) - if "/" in playurl: - realpath = realpath + "/" - else: - realpath = realpath + "\\" - except: - pass - - filename = "plugin://plugin.video.emby/musicvideos/%s/?filename=%s&id=%s&mode=play" % (MBitem["Id"], realfile, MBitem["Id"]) - path = "plugin://plugin.video.emby/movies/%s/" % embyId - - - ##### ADD THE VIDEO ############ - if idMVideo == None: - - utils.logMsg("ADD musicvideo to Kodi library","Id: %s - Title: %s" % (embyId, title)) - - #create the path - cursor.execute("SELECT idPath as pathid FROM path WHERE strPath = ?",(path,)) - result = cursor.fetchone() - if result != None: - pathid = result[0] - else: - cursor.execute("select coalesce(max(idPath),0) as pathid from path") - pathid = cursor.fetchone()[0] - pathid = pathid + 1 - pathsql = "insert into path(idPath, strPath, strContent, strScraper, noUpdate) values(?, ?, ?, ?, ?)" - cursor.execute(pathsql, (pathid,path,"musicvideos","metadata.local",1)) - - #create the file if not exists - if self.directpath: - cursor.execute("SELECT idFile as fileid FROM files WHERE strFilename = ? and idPath = ?", (filename, pathid,)) - else: - cursor.execute("SELECT idFile as fileid FROM files WHERE strFilename LIKE ? and idPath = ?", (filename, embyId,)) - result = cursor.fetchone() - if result != None: - fileid = result[0] - if result == None: - cursor.execute("select coalesce(max(idFile),0) as fileid from files") - fileid = cursor.fetchone()[0] - fileid = fileid + 1 - pathsql="insert into files(idFile, idPath, strFilename, playCount, lastPlayed, dateAdded) values(?, ?, ?, ?, ?, ?)" - cursor.execute(pathsql, (fileid,pathid,filename,playcount,userData.get("LastPlayedDate"),dateadded)) - - #create the video - cursor.execute("select coalesce(max(idMVideo),0) as idMVideo from musicvideo") - idMVideo = cursor.fetchone()[0] - idMVideo = idMVideo + 1 - pathsql="insert into musicvideo(idMVideo, idFile, c00, c04, c05, c06, c07, c08, c09, c10, c11, c12) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(pathsql, (idMVideo, fileid, title, runtime, director, studio, year, plot, album, artist, genre, track)) - - #create the reference in emby table - pathsql = "INSERT into emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)" - cursor.execute(pathsql, (MBitem["Id"], idMVideo, "musicvideo", API().getChecksum(MBitem))) - - #### UPDATE THE VIDEO ##### - else: - utils.logMsg("UPDATE musicvideo to Kodi library","Id: %s - Title: %s" % (embyId, title)) - - #get the file ID - cursor.execute("SELECT idFile as fileid FROM musicvideo WHERE idMVideo = ?", (idMVideo,)) - fileid = cursor.fetchone()[0] - - #always update the filepath (fix for path change) - query = "UPDATE files SET strFilename = ?, dateAdded = ? WHERE idFile = ?" - cursor.execute(query, (filename, dateadded, fileid)) - - pathsql="update musicvideo SET c00 = ?, c04 = ?, c05 = ?, c06 = ?, c07 = ?, c08 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ? WHERE idMVideo = ?" - cursor.execute(pathsql, (title, runtime, director, studio, year, plot, album, artist, genre, track, idMVideo)) - - #update the checksum in emby table - cursor.execute("UPDATE emby SET checksum = ? WHERE emby_id = ?", (API().getChecksum(MBitem),MBitem["Id"])) - - # Add tags to item, view tag and emby tags - tags = MBitem['Tags'] - self.AddTagsToMedia(idMVideo, tags, "musicvideo", cursor) - - #update or insert actors - people = MBitem['People'] - artists = MBitem['ArtistItems'] - for artist in artists: - artist['Type'] = "Artist" - people.extend(artists) - self.AddPeopleToMedia(idMVideo,people,"musicvideo", connection, cursor) - - # Update artwork - self.textureCache.addArtwork(API().getAllArtwork(MBitem), idMVideo, "musicvideo", cursor) - - #update genres - self.AddGenresToMedia(idMVideo, genres, "musicvideo", cursor) - - #update studios - self.AddStudiosToMedia(idMVideo, studios, "musicvideo", cursor) - - #add streamdetails - self.AddStreamDetailsToMedia(API().getMediaStreams(MBitem), runtime ,fileid, cursor) - - #set resume point - resume = int(round(float(timeInfo.get("ResumeTime"))))*60 - total = int(round(float(timeInfo.get("TotalTime"))))*60 - self.setKodiResumePoint(fileid, resume, total, cursor, playcount, dateplayed, realpath, realfile) - - def addOrUpdateTvShowToKodiLibrary(self, embyId, connection, cursor, viewTag ): - - MBitem = ReadEmbyDB().getFullItem(embyId) - - if not MBitem: - self.logMsg("ADD tvshow to Kodi library FAILED, Item %s not found on server!" % embyId) - return - - # If the item already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (embyId,)) - try: - showid = cursor.fetchone()[0] - except: - showid = None - self.logMsg("TV Show Id: %s not found." % embyId, 1) - - - timeInfo = API().getTimeInfo(MBitem) - userData = API().getUserData(MBitem) - people = API().getPeople(MBitem) - genres = MBitem.get('Genres') - studios = API().getStudios(MBitem) - - #### The tv show details #### - playcount = userData.get('PlayCount') - dateplayed = userData.get("LastPlayedDate") - dateadded = API().getDateCreated(MBitem) - checksum = API().getChecksum(MBitem) - - title = MBitem['Name'] - plot = API().getOverview(MBitem) - rating = MBitem.get('CommunityRating') - premieredate = API().getPremiereDate(MBitem) - genre = " / ".join(genres) - tvdb = API().getProvider(MBitem, "tvdb") - mpaa = API().getMpaa(MBitem) - studio = " / ".join(studios) - sorttitle = MBitem['SortName'] - - - #create toplevel path as monitored source - needed for things like actors and stuff to work (no clue why) - if self.directpath: - # Network share - playurl = PlayUtils().directPlay(MBitem) - if "/" in playurl: - # Network path - path = "%s/" % playurl - toplevelpath = "%s/" % dirname(dirname(path)) - else: - # Local path - path = "%s\\" % playurl - toplevelpath = "%s\\" % dirname(dirname(path)) - else:# Set plugin path - path = "plugin://plugin.video.emby/tvshows/%s/" % embyId - toplevelpath = "plugin://plugin.video.emby/" - - - ##### UPDATE THE TV SHOW ##### - if showid: - self.logMsg("UPDATE tvshow to Kodi library, Id: %s - Title: %s" % (embyId, title)) - - query = "UPDATE tvshow SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?, c12 = ?, c13 = ?, c14 = ?, c15 = ? WHERE idShow = ?" - cursor.execute(query, (title, plot, rating, premieredate, genre, title, tvdb, mpaa, studio, sorttitle, showid)) - - # Update the checksum in emby table - query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" - cursor.execute(query, (checksum, embyId)) - - ##### OR ADD THE TV SHOW ##### - else: - self.logMsg("ADD tvshow to Kodi library, Id: %s - Title: %s" % (embyId, title)) - - # Create the TV show path - cursor.execute("select coalesce(max(idPath),0) as pathid from path") - pathid = cursor.fetchone()[0] + 1 - query = "INSERT INTO path(idPath, strPath, strContent, strScraper, noUpdate) values(?, ?, ?, ?, ?)" - cursor.execute(query, (pathid, path, None, None, 1)) - - # Validate the top level path in database - cursor.execute("SELECT idPath as tlpathid FROM path WHERE strPath = ?", (toplevelpath,)) - try: - cursor.fetchone()[0] - except: - # Top level path does not exist yet - cursor.execute("select coalesce(max(idPath),0) as tlpathid from path") - tlpathid = cursor.fetchone()[0] + 1 - query = "INSERT INTO path(idPath, strPath, strContent, strScraper, noUpdate) values(?, ?, ?, ?, ?)" - cursor.execute(query, (tlpathid, toplevelpath, "tvshows", "metadata.local", 1)) - - # Create the TV show - cursor.execute("select coalesce(max(idShow),0) as showid from tvshow") - showid = cursor.fetchone()[0] + 1 - query = "INSERT INTO tvshow(idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, c15) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (showid, title, plot, rating, premieredate, genre, title, tvdb, mpaa, studio, sorttitle)) - - # Create the reference in emby table - query = "INSERT INTO emby(emby_id, kodi_id, media_type, checksum) values(?, ?, ?, ?)" - cursor.execute(query, (embyId, showid, "tvshow", checksum)) - - # Link the path - query = "INSERT INTO tvshowlinkpath(idShow, idPath) values(?, ?)" - cursor.execute(query, (showid, pathid)) - - - # Add tags to item, view tag, emby tags and favourite - tags = [viewTag] - tags.extend(MBitem['Tags']) - if userData['Favorite']: - tags.append("Favorite tvshows") - - self.AddTagsToMedia(showid, tags, "tvshow", cursor) - - # Update artwork - self.textureCache.addArtwork(API().getAllArtwork(MBitem), showid, "tvshow", cursor) - - # Update or insert people - self.AddPeopleToMedia(showid, MBitem.get('People'),"tvshow", connection, cursor) - - # Update genres - self.AddGenresToMedia(showid, genres, "tvshow", cursor) - - # Update studios - self.AddStudiosToMedia(showid, studios, "tvshow", cursor) - - # Update season details - self.updateSeasons(embyId, showid, connection, cursor) - - def addOrUpdateEpisodeToKodiLibrary(self, embyId, showid, connection, cursor): - kodiVersion = self.kodiversion - MBitem = ReadEmbyDB().getFullItem(embyId) - - if not MBitem: - self.logMsg("ADD episode to Kodi library FAILED, Item %s not found on server!" % embyId, 1) - return - - # If the episode already exist in the local Kodi DB we'll perform a full item update - # If the item doesn't exist, we'll add it to the database - - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (MBitem['Id'],)) - try: - episodeid = cursor.fetchone()[0] - except: - episodeid = None - self.logMsg("Episode Id: %s not found." % embyId, 1) - - - timeInfo = API().getTimeInfo(MBitem) - userData = API().getUserData(MBitem) - people = API().getPeople(MBitem) - - ##### The episode details ##### - seriesId = MBitem['SeriesId'] - seriesName = MBitem['SeriesName'] - season = MBitem.get('ParentIndexNumber') - episode = MBitem.get('IndexNumber', 0) - - if utils.settings('syncSpecialsOrder') == "true": - airsBeforeSeason = MBitem.get('AirsBeforeSeasonNumber', "-1") - airsBeforeEpisode = MBitem.get('AirsBeforeEpisodeNumber', "-1") - else: - airsBeforeSeason = "-1" - airsBeforeEpisode = "-1" - - playcount = userData.get('PlayCount') - dateplayed = userData.get("LastPlayedDate") - dateadded = API().getDateCreated(MBitem) - checksum = API().getChecksum(MBitem) - - title = MBitem['Name'] - plot = API().getOverview(MBitem) - rating = MBitem.get('CommunityRating') - writer = " / ".join(people.get('Writer')) - premieredate = API().getPremiereDate(MBitem) - runtime = timeInfo.get('TotalTime') - director = " / ".join(people.get('Director')) - - playurl = PlayUtils().directPlay(MBitem) - realfile = "" - realpath = "" - - if self.directpath: - if playurl == False: - return - elif "\\" in playurl: - filename = playurl.rsplit("\\",1)[-1] - path = playurl.replace(filename, "") - elif "/" in playurl: - filename = playurl.rsplit("/",1)[-1] - path = playurl.replace(filename, "") - else: - self.logMsg("Invalid path: %s" % playurl, 1) - return - else: # Set plugin path and media flags - real filename with extension - realfile = "" - realpath = "" - try: - if not "plugin://" in playurl: - realpath, realfile = ntsplit(playurl) - if "/" in playurl: - realpath = realpath + "/" - else: - realpath = realpath + "\\" - except: - pass - - filename = "plugin://plugin.video.emby/tvshows/%s/?filename=%s&id=%s&mode=play" % (seriesId, realfile, embyId) - path = "plugin://plugin.video.emby/tvshows/%s/" % seriesId - - # Validate the season exists in Emby and in database - if season is None: - self.logMsg("SKIP adding episode to Kodi Library, no season assigned - ID: %s - %s" % (embyId, title)) - return False - - idSeason = None - count = 0 - while idSeason is None: - cursor.execute("SELECT idSeason FROM seasons WHERE idShow = ? and season = ?", (showid, season,)) - try: - idSeason = cursor.fetchone()[0] - except: # Season does not exist, update seasons - if not count: - self.updateSeasons(seriesId, showid, connection, cursor) - count += 1 - else: - # Season is still not found, skip episode. - self.logMsg("Skipping episode: %s. Season number is missing at season level in the metadata manager." % title, 1) - return False - - ##### UPDATE THE EPISODE ##### - if episodeid: - self.logMsg("UPDATE episode from // %s - S%s // to Kodi library, Id: %s - E%s: %s" % (seriesName, season, embyId, episode, title)) - - #get the file ID - cursor.execute("SELECT idFile as fileid FROM episode WHERE idEpisode = ?", (episodeid,)) - fileid = cursor.fetchone()[0] - - #always update the filepath (fix for path change) - query = "UPDATE files SET strFilename = ?, dateAdded = ? WHERE idFile = ?" - cursor.execute(query, (filename, dateadded, fileid)) - - if kodiVersion == 16: - query = "UPDATE episode SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?, c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, idSeason = ? WHERE idEpisode = ?" - cursor.execute(query, (title, plot, rating, writer, premieredate, runtime, director, season, episode, title, airsBeforeSeason, airsBeforeEpisode, idSeason ,episodeid)) - else: - query = "UPDATE episode SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?, c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ? WHERE idEpisode = ?" - cursor.execute(query, (title, plot, rating, writer, premieredate, runtime, director, season, episode, title, airsBeforeSeason, airsBeforeEpisode, episodeid)) - - #update the checksum in emby table - query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" - cursor.execute(query, (checksum, embyId)) - - ##### OR ADD THE EPISODE ##### - else: - self.logMsg("ADD episode from // %s - S%s // to Kodi library, Id: %s - E%s: %s" % (seriesName, season, embyId, episode, title)) - - # Validate the path in database - if self.directpath: - cursor.execute("SELECT idPath as pathid FROM path WHERE strPath = ?", (path,)) - else: - cursor.execute("SELECT idPath as pathid FROM path WHERE strPath LIKE ?", (seriesId,)) - try: - pathid = cursor.fetchone()[0] - except: - # Path does not exist yet - cursor.execute("select coalesce(max(idPath),0) as pathid from path") - pathid = cursor.fetchone()[0] + 1 - query = "INSERT INTO path(idPath, strPath, strContent, strScraper, noUpdate) values(?, ?, ?, ?, ?)" - cursor.execute(query, (pathid, path, None, None, 1)) - - # Validate the file in database - cursor.execute("SELECT idFile as fileid FROM files WHERE strFilename = ? and idPath = ?", (filename, pathid,)) - try: - fileid = cursor.fetchone()[0] - except: - # File does not exist yet - cursor.execute("select coalesce(max(idFile),0) as fileid from files") - fileid = cursor.fetchone()[0] + 1 - query = "INSERT INTO files(idFile, idPath, strFilename, playCount, lastPlayed, dateAdded) values(?, ?, ?, ?, ?, ?)" - cursor.execute(query, (fileid, pathid, filename, playcount, dateplayed, dateadded)) - - # Create the episode - cursor.execute("select coalesce(max(idEpisode),0) as episodeid from episode") - episodeid = cursor.fetchone()[0] + 1 - if kodiVersion == 16: - query = "INSERT INTO episode(idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14, idShow, c15, c16, idSeason) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (episodeid, fileid, title, plot, rating, writer, premieredate, runtime, director, season, episode, title, showid, airsBeforeSeason, airsBeforeEpisode, idSeason)) - else: - query = "INSERT INTO episode(idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14, idShow, c15, c16) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (episodeid, fileid, title, plot, rating, writer, premieredate, runtime, director, season, episode, title, showid, airsBeforeSeason, airsBeforeEpisode)) - - # Create the reference in emby table - query = "INSERT INTO emby(emby_id, kodi_id, kodi_file_id, media_type, checksum, parent_id) values(?, ?, ?, ?, ?, ?)" - cursor.execute(query, (embyId, episodeid, fileid, "episode", checksum, showid)) - - # Update or insert actors - self.AddPeopleToMedia(episodeid, MBitem.get('People'), "episode", connection, cursor) - - # Add streamdetails - self.AddStreamDetailsToMedia(API().getMediaStreams(MBitem), runtime, fileid, cursor) - - # Update artwork - artworks = API().getAllArtwork(MBitem) - self.textureCache.addOrUpdateArt(artworks['Primary'], episodeid, "episode", "thumb", cursor) - - # Set resume point and round to 6th decimal - resume = round(float(timeInfo.get('ResumeTime')), 6) - total = round(float(timeInfo.get('TotalTime')), 6) - jumpback = int(utils.settings('resumeJumpBack')) - if resume > jumpback: - # To avoid negative bookmark - resume = resume - jumpback - self.setKodiResumePoint(fileid, resume, total, cursor, playcount, dateplayed, realpath, realfile) - - def deleteItemFromKodiLibrary(self, id, connection, cursor): - - cursor.execute("SELECT kodi_id, media_type FROM emby WHERE emby_id = ?", (id,)) - try: - result = cursor.fetchone() - kodi_id = result[0] - media_type = result[1] - except: pass - else: # Delete entry from database - if "movie" in media_type: - self.logMsg("Deleting movie from Kodi library, Id: %s" % id, 1) - cursor.execute("DELETE FROM movie WHERE idMovie = ?", (kodi_id,)) - - elif "episode" in media_type: - cursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodi_id,)) - self.logMsg("Deleting episode from Kodi library, Id: %s" % id, 1) - - elif "tvshow" in media_type: - cursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodi_id,)) - self.logMsg("Deleting tvshow from Kodi library, Id: %s" % id, 1) - - elif "musicvideo" in media_type: - cursor.execute("DELETE FROM musicvideo WHERE idMVideo = ?", (kodi_id,)) - self.logMsg("Deleting musicvideo from Kodi library, Id: %s" % id, 1) - - # Delete the record in emby table - cursor.execute("DELETE FROM emby WHERE emby_id = ?", (id,)) - - def updateSeasons(self, embyTvShowId, kodiTvShowId, connection, cursor): - - textureCache = self.textureCache - seasonData = ReadEmbyDB().getTVShowSeasons(embyTvShowId) - - for season in seasonData: - - seasonNum = season.get('IndexNumber') - - cursor.execute("SELECT idSeason as seasonid FROM seasons WHERE idShow = ? and season = ?", (kodiTvShowId, seasonNum,)) - try: - seasonid = cursor.fetchone()[0] - except: # Create the season - cursor.execute("select coalesce(max(idSeason),0) as seasonid from seasons") - seasonid = cursor.fetchone()[0] + 1 - query = "INSERT INTO seasons(idSeason, idShow, season) values(?, ?, ?)" - cursor.execute(query, (seasonid, kodiTvShowId, seasonNum)) - finally: # Update artwork - textureCache.addArtwork(API().getAllArtwork(season), seasonid, "season", cursor) - - # All season entry - MBitem = ReadEmbyDB().getFullItem(embyTvShowId) - seasonNum = -1 - - cursor.execute("SELECT idSeason as seasonid FROM seasons WHERE idShow = ? and season = ?", (kodiTvShowId, seasonNum,)) - try: - seasonid = cursor.fetchone()[0] - except: # Create all season entry - cursor.execute("select coalesce(max(idSeason),0) as seasonid from seasons") - seasonid = cursor.fetchone()[0] + 1 - query = "INSERT INTO seasons(idSeason, idShow, season) values(?, ?, ?)" - cursor.execute(query, (seasonid, kodiTvShowId, seasonNum)) - finally: # Update the artwork - textureCache.addArtwork(API().getAllArtwork(MBitem), seasonid, "season", cursor) - - def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): - - if imageUrl: - - cacheimage = False - - cursor.execute("SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type = ?", (kodiId, mediaType, imageType,)) - try: # Update the artwork - url = cursor.fetchone()[0] - except: # Add the artwork - cacheimage = True - self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 1) - query = "INSERT INTO art(media_id, media_type, type, url) values(?, ?, ?, ?)" - cursor.execute(query, (kodiId, mediaType, imageType, imageUrl)) - else: - if url != imageUrl: - cacheimage = True - self.logMsg("Updating Art Link for kodiId: %s (%s) -> (%s)" % (kodiId, url, imageUrl), 1) - query = "UPDATE art set url = ? WHERE media_id = ? AND media_type = ? AND type = ?" - cursor.execute(query, (imageUrl, kodiId, mediaType, imageType)) - - # Cache fanart and poster in Kodi texture cache - if cacheimage and imageType in ("fanart", "poster"): - self.textureCache.CacheTexture(imageUrl) - - def setKodiResumePoint(self, fileid, resume_seconds, total_seconds, cursor, playcount, dateplayed=None, realpath=None, realfile=None): - - if realpath: - #delete any existing resume point for the real filepath - cursor.execute("SELECT idPath as pathid FROM path WHERE strPath = ?", (realpath,)) - result = cursor.fetchone() - if result: - pathid = result[0] - cursor.execute("SELECT idFile as fileid FROM files WHERE strFilename = ? and idPath = ?", (realfile, pathid,)) - result = cursor.fetchone() - if result: - cursor.execute("DELETE FROM bookmark WHERE idFile = ?", (result[0],)) - - #delete existing resume point for the actual filepath - cursor.execute("DELETE FROM bookmark WHERE idFile = ?", (fileid,)) - - #set watched count - query = "UPDATE files SET playCount = ?, lastPlayed = ? WHERE idFile = ?" - cursor.execute(query, (playcount, dateplayed, fileid)) - - #set the resume bookmark - if resume_seconds: - cursor.execute("select coalesce(max(idBookmark),0) as bookmarkId from bookmark") - bookmarkId = cursor.fetchone()[0] + 1 - query = "INSERT INTO bookmark(idBookmark, idFile, timeInSeconds, totalTimeInSeconds, thumbNailImage, player, playerState, type) values(?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (bookmarkId, fileid, resume_seconds, total_seconds, None, "DVDPlayer", None, 1)) - - - def AddPeopleToMedia(self, id, people, mediatype, connection, cursor): - - kodiVersion = self.kodiversion - - if people: - castorder = 1 - - for person in people: - - name = person['Name'] - type = person['Type'] - - if kodiVersion == 15 or kodiVersion == 16: - # Kodi Isengard/jarvis - cursor.execute("SELECT actor_id as actorid FROM actor WHERE name = ? COLLATE NOCASE", (name,)) - else: - # Kodi Gotham or Helix - cursor.execute("SELECT idActor as actorid FROM actors WHERE strActor = ? COLLATE NOCASE", (name,)) - - try: # Update person in database - actorid = cursor.fetchone()[0] - except: - # Person entry does not exist yet. - if kodiVersion == 15 or kodiVersion == 16: - # Kodi Isengard - cursor.execute("select coalesce(max(actor_id),0) as actorid from actor") - query = "INSERT INTO actor(actor_id, name) values(?, ?)" - else: - # Kodi Gotham or Helix - cursor.execute("select coalesce(max(idActor),0) as actorid from actors") - query = "INSERT INTO actors(idActor, strActor) values(?, ?)" - - actorid = cursor.fetchone()[0] + 1 - self.logMsg("Adding people to Media, processing: %s" % name, 2) - - cursor.execute(query, (actorid, name)) - finally: - query = "" - # Add person image to art table - thumb = API().imageUrl(person['Id'], "Primary", 0, 400, 400) - if thumb: - arttype = type.lower() - - if "writing" in arttype: - arttype = "writer" - - self.textureCache.addOrUpdateArt(thumb, actorid, arttype, "thumb", cursor) - - # Link person to content in database - if kodiVersion == 15 or kodiVersion == 16: - # Kodi Isengard - if "Actor" in type: - Role = person.get('Role') - query = "INSERT OR REPLACE INTO actor_link(actor_id, media_id, media_type, role, cast_order) values(?, ?, ?, ?, ?)" - cursor.execute(query, (actorid, id, mediatype, Role, castorder)) - castorder += 1 - - elif "Director" in type: - query = "INSERT OR REPLACE INTO director_link(actor_id, media_id, media_type) values(?, ?, ?)" - cursor.execute(query, (actorid, id, mediatype)) - - elif type in ("Writing", "Writer"): - query = "INSERT OR REPLACE INTO writer_link(actor_id, media_id, media_type) values(?, ?, ?)" - cursor.execute(query, (actorid, id, mediatype)) - - elif "Artist" in type: - query = "INSERT OR REPLACE INTO actor_link(actor_id, media_id, media_type) values(?, ?, ?)" - cursor.execute(query, (actorid, id, mediatype)) - - else: - # Kodi Gotham or Helix - if "Actor" in type: - Role = person.get('Role') - query = None - if "movie" in mediatype: - query = "INSERT OR REPLACE INTO actorlinkmovie(idActor, idMovie, strRole, iOrder) values(?, ?, ?, ?)" - elif "tvshow" in mediatype: - query = "INSERT OR REPLACE INTO actorlinktvshow(idActor, idShow, strRole, iOrder) values(?, ?, ?, ?)" - elif "episode" in mediatype: - query = "INSERT OR REPLACE INTO actorlinkepisode(idActor, idEpisode, strRole, iOrder) values(?, ?, ?, ?)" - - if query: - cursor.execute(query, (actorid, id, Role, castorder)) - castorder += 1 - - elif "Director" in type: - - if "movie" in mediatype: - query = "INSERT OR REPLACE INTO directorlinkmovie(idDirector, idMovie) values(?, ?)" - elif "tvshow" in mediatype: - query = "INSERT OR REPLACE INTO directorlinktvshow(idDirector, idShow) values(?, ?)" - elif "musicvideo" in mediatype: - query = "INSERT OR REPLACE INTO directorlinkmusicvideo(idDirector, idMVideo) values(?, ?)" - elif "episode" in mediatype: - query = "INSERT OR REPLACE INTO directorlinkepisode(idDirector, idEpisode) values(?, ?)" - - if query: - cursor.execute(query, (actorid, id)) - - elif type in ("Writing", "Writer"): - - if "movie" in mediatype: - query = "INSERT OR REPLACE INTO writerlinkmovie(idWriter, idMovie) values(?, ?)" - elif "episode" in mediatype: - query = "INSERT OR REPLACE INTO writerlinkepisode(idWriter, idEpisode) values(?, ?)" - - if query: - cursor. execute(query, (actorid, id)) - - elif "Artist" in type: - query = "INSERT OR REPLACE INTO artistlinkmusicvideo(idArtist, idMVideo) values(?, ?)" - cursor.execute(query, (actorid, id)) - - def AddGenresToMedia(self, id, genres, mediatype, cursor): - - kodiVersion = self.kodiversion - - if genres: - - # Delete current genres for clean slate - if kodiVersion == 15 or kodiVersion == 16: - cursor.execute("DELETE FROM genre_link WHERE media_id = ? AND media_type = ?", (id, mediatype,)) - else: - if "movie" in mediatype: - cursor.execute("DELETE FROM genrelinkmovie WHERE idMovie = ?", (id,)) - elif "tvshow" in mediatype: - cursor.execute("DELETE FROM genrelinktvshow WHERE idShow = ?", (id,)) - elif "musicvideo" in mediatype: - cursor.execute("DELETE FROM genrelinkmusicvideo WHERE idMVideo = ?", (id,)) - - # Add Genres - for genre in genres: - - if kodiVersion == 15 or kodiVersion == 16: - # Kodi Isengard - cursor.execute("SELECT genre_id as genre_id FROM genre WHERE name = ? COLLATE NOCASE", (genre,)) - try: - genre_id = cursor.fetchone()[0] - except: - # Create genre in database - cursor.execute("select coalesce(max(genre_id),0) as genre_id from genre") - genre_id = cursor.fetchone()[0] + 1 - - query = "INSERT INTO genre(genre_id, name) values(?, ?)" - cursor.execute(query, (genre_id, genre)) - self.logMsg("Add Genres to media, processing: %s" % genre, 1) - finally: - # Assign genre to item - query = "INSERT OR REPLACE INTO genre_link(genre_id, media_id, media_type) values(?, ?, ?)" - cursor.execute(query, (genre_id, id, mediatype)) - else: - # Kodi Gotham or Helix - cursor.execute("SELECT idGenre as idGenre FROM genre WHERE strGenre = ? COLLATE NOCASE", (genre,)) - try: - idGenre = cursor.fetchone()[0] - except: - # Create genre in database - cursor.execute("select coalesce(max(idGenre),0) as idGenre from genre") - idGenre = cursor.fetchone()[0] + 1 - - query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" - cursor.execute(query, (idGenre, genre)) - self.logMsg("Add Genres to media, processing: %s" % genre, 1) - finally: - # Assign genre to item - if "movie" in mediatype: - query = "INSERT OR REPLACE into genrelinkmovie(idGenre, idMovie) values(?, ?)" - elif "tvshow" in mediatype: - query = "INSERT OR REPLACE into genrelinktvshow(idGenre, idShow) values(?, ?)" - elif "musicvideo" in mediatype: - query = "INSERT OR REPLACE into genrelinkmusicvideo(idGenre, idMVideo) values(?, ?)" - else: # Item is invalid - return - cursor.execute(query, (idGenre, id)) - - def AddCountriesToMedia(self, id, countries, mediatype, cursor): - - kodiVersion = self.kodiversion - - if countries: - for country in countries: - - if kodiVersion == 15 or kodiVersion == 16: - # Kodi Isengard - cursor.execute("SELECT country_id as country_id FROM country WHERE name = ? COLLATE NOCASE", (country,)) - try: - country_id = cursor.fetchone()[0] - except: - # Country entry does not exists - cursor.execute("select coalesce(max(country_id),0) as country_id from country") - country_id = cursor.fetchone()[0] + 1 - - query = "INSERT INTO country(country_id, name) values(?, ?)" - cursor.execute(query, (country_id, country)) - self.logMsg("Add Countries to Media, processing: %s" % country) - finally: - # Assign country to content - query = "INSERT OR REPLACE INTO country_link(country_id, media_id, media_type) values(?, ?, ?)" - cursor.execute(query, (country_id, id, mediatype)) - else: - # Kodi Gotham or Helix - cursor.execute("SELECT idCountry as idCountry FROM country WHERE strCountry = ? COLLATE NOCASE", (country,)) - try: - idCountry = cursor.fetchone()[0] - except: - # Country entry does not exists - cursor.execute("select coalesce(max(idCountry),0) as idCountry from country") - idCountry = cursor.fetchone()[0] + 1 - - query = "INSERT INTO country(idCountry, strCountry) values(?, ?)" - cursor.execute(query, (idCountry, country)) - finally: - # Only movies have a country field - if "movie" in mediatype: - query = "INSERT OR REPLACE INTO countrylinkmovie(idCountry, idMovie) values(?, ?)" - cursor.execute(query, (idCountry, id)) - - def AddStudiosToMedia(self, id, studios, mediatype, cursor): - - kodiVersion = self.kodiversion - - if studios: - for studio in studios: - - if kodiVersion == 15 or kodiVersion == 16: - # Kodi Isengard - cursor.execute("SELECT studio_id as studio_id FROM studio WHERE name = ? COLLATE NOCASE", (studio,)) - try: - studio_id = cursor.fetchone()[0] - except: # Studio does not exists. - cursor.execute("select coalesce(max(studio_id),0) as studio_id from studio") - studio_id = cursor.fetchone()[0] + 1 - - query = "INSERT INTO studio(studio_id, name) values(?, ?)" - cursor.execute(query, (studio_id,studio)) - self.logMsg("Add Studios to media, processing: %s" % studio, 1) - finally: # Assign studio to item - query = "INSERT OR REPLACE INTO studio_link(studio_id, media_id, media_type) values(?, ?, ?)" - cursor.execute(query, (studio_id, id, mediatype)) - else: - # Kodi Gotham or Helix - cursor.execute("SELECT idstudio as idstudio FROM studio WHERE strstudio = ? COLLATE NOCASE",(studio,)) - try: - idstudio = cursor.fetchone()[0] - except: # Studio does not exists. - cursor.execute("select coalesce(max(idstudio),0) as idstudio from studio") - idstudio = cursor.fetchone()[0] + 1 - - query = "INSERT INTO studio(idstudio, strstudio) values(?, ?)" - cursor.execute(query, (idstudio,studio)) - self.logMsg("Add Studios to media, processing: %s" % studio, 1) - finally: # Assign studio to item - - if "movie" in mediatype: - query = "INSERT OR REPLACE INTO studiolinkmovie(idstudio, idMovie) values(?, ?)" - elif "musicvideo" in mediatype: - query = "INSERT OR REPLACE INTO studiolinkmusicvideo(idstudio, idMVideo) values(?, ?)" - elif "tvshow" in mediatype: - query = "INSERT OR REPLACE INTO studiolinktvshow(idstudio, idShow) values(?, ?)" - elif "episode" in mediatype: - query = "INSERT OR REPLACE INTO studiolinkepisode(idstudio, idEpisode) values(?, ?)" - cursor.execute(query, (idstudio, id)) - - - def AddTagsToMedia(self, id, tags, mediatype, cursor): - - # First, delete any existing tags associated to the id - if self.kodiversion in (15, 16): - # Kodi Isengard, Jarvis - query = "DELETE FROM tag_link WHERE media_id = ? AND media_type = ?" - cursor.execute(query, (id, mediatype)) - - else: # Kodi Helix - query = "DELETE FROM taglinks WHERE idMedia = ? AND media_type = ?" - cursor.execute(query, (id, mediatype)) - - # Add tags - self.logMsg("Adding Tags: %s" % tags, 1) - for tag in tags: - self.AddTagToMedia(id, tag, mediatype, cursor) - - def AddTagToMedia(self, id, tag, mediatype, cursor, doRemove=False): - - if self.kodiversion in (15, 16): - # Kodi Isengard, Jarvis - cursor.execute("SELECT tag_id FROM tag WHERE name = ? COLLATE NOCASE", (tag,)) - try: - tag_id = cursor.fetchone()[0] - except: - # Create the tag, because it does not exist - cursor.execute("select coalesce(max(tag_id),0) as tag_id from tag") - tag_id = cursor.fetchone()[0] + 1 - - query = "INSERT INTO tag(tag_id, name) values(?, ?)" - cursor.execute(query, (tag_id, tag)) - self.logMsg("Add Tag to media, adding tag: %s" % tag, 2) - finally: - # Assign tag to item - if not doRemove: - query = "INSERT OR REPLACE INTO tag_link(tag_id, media_id, media_type) values(?, ?, ?)" - cursor.execute(query, (tag_id, id, mediatype)) - else: - query = "DELETE FROM tag_link WHERE media_id = ? AND media_type = ? AND tag_id = ?" - cursor.execute(query, (id, mediatype, tag_id)) - - else: - # Kodi Helix - cursor.execute("SELECT idTag FROM tag WHERE strTag = ? COLLATE NOCASE", (tag,)) - try: - idTag = cursor.fetchone()[0] - except: - # Create the tag - cursor.execute("select coalesce(max(idTag),0) as idTag from tag") - idTag = cursor.fetchone()[0] + 1 - - query = "INSERT INTO tag(idTag, strTag) values(?, ?)" - cursor.execute(query, (idTag, tag)) - self.logMsg("Add Tag to media, adding tag: %s" % tag, 2) - finally: - # Assign tag to item - if not doRemove: - query = "INSERT OR REPLACE INTO taglinks(idTag, idMedia, media_type) values(?, ?, ?)" - cursor.execute(query, (idTag, id, mediatype)) - else: - query = "DELETE FROM taglinks WHERE idMedia = ? AND media_type = ? AND idTag = ?" - cursor.execute(query, (id, mediatype, idTag)) - - - def AddStreamDetailsToMedia(self, streamdetails, runtime , fileid, cursor): - - # First remove any existing entries - cursor.execute("DELETE FROM streamdetails WHERE idFile = ?", (fileid,)) - if streamdetails: - # Video details - for videotrack in streamdetails['videocodec']: - query = "INSERT INTO streamdetails(idFile, iStreamType, strVideoCodec, fVideoAspect, iVideoWidth, iVideoHeight, iVideoDuration ,strStereoMode) values(?, ?, ?, ?, ?, ?, ?, ?)" - cursor.execute(query, (fileid, 0, videotrack.get('videocodec'), videotrack.get('aspectratio'), videotrack.get('width'), videotrack.get('height'), runtime ,videotrack.get('Video3DFormat'))) - - # Audio details - for audiotrack in streamdetails['audiocodec']: - query = "INSERT INTO streamdetails(idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) values(?, ?, ?, ?, ?)" - cursor.execute(query, (fileid, 1, audiotrack.get('audiocodec'), audiotrack.get('channels'), audiotrack.get('audiolanguage'))) - - # Subtitles details - for subtitletrack in streamdetails['subtitlelanguage']: - query = "INSERT INTO streamdetails(idFile, iStreamType, strSubtitleLanguage) values(?, ?, ?)" - cursor.execute(query, (fileid, 2, subtitletrack)) - - def addBoxsetToKodiLibrary(self, boxset, connection, cursor): - - strSet = boxset['Name'] - cursor.execute("SELECT idSet FROM sets WHERE strSet = ?", (strSet,)) - try: - setid = cursor.fetchone()[0] - except: - # Boxset does not exists - query = "INSERT INTO sets(idSet, strSet) values(?, ?)" - cursor.execute(query, (None, strSet)) - # Get the setid of the new boxset - cursor.execute("SELECT idSet FROM sets WHERE strSet = ?", (strSet,)) - setid = cursor.fetchone()[0] - finally: - # Assign artwork - cursor.execute('SELECT type, url FROM art WHERE media_type = ? AND media_id = ? and url != ""', ("set", setid,)) - - existing_type_map = {} - rows = cursor.fetchall() - for row in rows: - existing_type_map[row[0] ] = row[1] - - artwork = {} - artwork['poster'] = API().getArtwork(boxset, "Primary", mediaType = "boxset") - artwork['banner'] = API().getArtwork(boxset, "Banner", mediaType = "boxset") - artwork['clearlogo'] = API().getArtwork(boxset, "Logo", mediaType = "boxset") - artwork['clearart'] = API().getArtwork(boxset, "Art", mediaType = "boxset") - artwork['landscape'] = API().getArtwork(boxset, "Thumb", mediaType = "boxset") - artwork['discart'] = API().getArtwork(boxset, "Disc", mediaType = "boxset") - artwork['fanart'] = API().getArtwork(boxset, "Backdrop", mediaType = "boxset") - - art_types = ['poster','fanart','landscape','clearlogo','clearart','banner','discart'] - for update_type in art_types: - if ( update_type in existing_type_map ): - if ( existing_type_map[update_type] != artwork[update_type] ) and artwork[update_type] != '': - setupdateartsql = "UPDATE art SET url = ? where media_type = ? and media_id = ? and type = ?" - cursor.execute(setupdateartsql,(artwork[update_type],"set",setid,update_type)) - elif artwork[update_type] != '': - setartsql = "INSERT INTO art(media_id, media_type, type, url) VALUES(?, ?, ?, ?)" - cursor.execute(setartsql,(setid,"set",update_type,artwork[update_type])) - - return True - - def updateBoxsetToKodiLibrary(self, boxsetmovie, boxset, connection, cursor): - - strSet = boxset['Name'] - boxsetmovieid = boxsetmovie['Id'] - cursor.execute("SELECT kodi_id FROM emby WHERE emby_id = ?", (boxsetmovieid,)) - try: - movieid = cursor.fetchone()[0] - cursor.execute("SELECT idSet FROM sets WHERE strSet = ? COLLATE NOCASE", (strSet,)) - setid = cursor.fetchone()[0] - except: pass - else: - query = "UPDATE movie SET idSet = ? WHERE idMovie = ?" - cursor.execute(query, (setid, movieid)) - - # Update the checksum in emby table - query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" - cursor.execute(query, (API().getChecksum(boxsetmovie), boxsetmovieid)) - - def removeMoviesFromBoxset(self, boxset, connection, cursor): - - strSet = boxset['Name'] - try: - cursor.execute("SELECT idSet FROM sets WHERE strSet = ? COLLATE NOCASE", (strSet,)) - setid = cursor.fetchone()[0] - except: pass - else: - query = "UPDATE movie SET idSet = null WHERE idSet = ?" - cursor.execute(query, (setid,)) - - def updateUserdata(self, userdata, connection, cursor): - # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks - embyId = userdata['ItemId'] - MBitem = ReadEmbyDB().getItem(embyId) - - if not MBitem: - self.logMsg("UPDATE userdata to Kodi library FAILED, Item %s not found on server!" % embyId, 1) - return - - # Get details - checksum = API().getChecksum(MBitem) - userdata = API().getUserData(MBitem) - timeInfo = API().getTimeInfo(MBitem) - - # Find the Kodi Id - cursor.execute("SELECT kodi_id, kodi_file_id, media_type FROM emby WHERE emby_id = ?", (embyId,)) - try: - result = cursor.fetchone() - kodiid = result[0] - fileid = result[1] - mediatype = result[2] - self.logMsg("Found embyId: %s in database - kodiId: %s fileId: %s type: %s" % (embyId, kodiid, fileid, mediatype), 1) - except: - self.logMsg("Id: %s not found in the emby database table." % embyId, 1) - else: - if mediatype in ("movie", "episode"): - playcount = userdata['PlayCount'] - dateplayed = userdata['LastPlayedDate'] - - # Set resume point and round to 6th decimal - resume = round(float(timeInfo.get('ResumeTime')), 6) - total = round(float(timeInfo.get('TotalTime')), 6) - jumpback = int(utils.settings('resumeJumpBack')) - if resume > jumpback: - # To avoid negative bookmark - resume = resume - jumpback - self.setKodiResumePoint(fileid, resume, total, cursor, playcount, dateplayed) - - #update the checksum in emby table - query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" - cursor.execute(query, (checksum, embyId)) - - if mediatype in ("movie", "tvshow"): - # Add to or remove from favorites tag - if userdata['Favorite']: - self.AddTagToMedia(kodiid, "Favorite %ss" % mediatype, mediatype, cursor) - else: - self.AddTagToMedia(kodiid, "Favorite %ss" % mediatype, mediatype, cursor, True) \ No newline at end of file diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py new file mode 100644 index 00000000..ded11a7e --- /dev/null +++ b/resources/lib/artwork.py @@ -0,0 +1,524 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import requests +import os +import urllib + +import xbmc +import xbmcvfs + +import utils +import clientinfo + +################################################################################################# + + +class Artwork(): + + xbmc_host = 'localhost' + xbmc_port = None + xbmc_username = None + xbmc_password = None + + + def __init__(self): + + self.clientinfo = clientinfo.ClientInfo() + self.addonName = self.clientinfo.getAddonName() + + self.enableTextureCache = utils.settings('enableTextureCache') == "true" + if not self.xbmc_port and self.enableTextureCache: + self.setKodiWebServerDetails() + + self.userId = utils.window('emby_currUser') + self.server = utils.window('emby_server%s' % self.userId) + + def logMsg(self, msg, lvl=1): + + className = self.__class__.__name__ + utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) + + + def double_urlencode(self, text): + text = self.single_urlencode(text) + text = self.single_urlencode(text) + + return text + + def single_urlencode(self, text): + text = urllib.urlencode({'blahblahblah':text}) + text = text[13:] + + return text + + def setKodiWebServerDetails(self): + # Get the Kodi webserver details - used to set the texture cache + web_query = { + + "jsonrpc": "2.0", + "id": 1, + "method": "Settings.GetSettingValue", + "params": { + + "setting": "services.webserver" + } + } + result = xbmc.executeJSONRPC(json.dumps(web_query)) + result = json.loads(result) + try: + xbmc_webserver_enabled = result['result']['value'] + except TypeError: + xbmc_webserver_enabled = False + + if not xbmc_webserver_enabled: + # Enable the webserver, it is disabled + web_port = { + + "jsonrpc": "2.0", + "id": 1, + "method": "Settings.SetSettingValue", + "params": { + + "setting": "services.webserverport", + "value": 8080 + } + } + result = xbmc.executeJSONRPC(json.dumps(web_port)) + self.xbmc_port = 8080 + + web_user = { + + "jsonrpc": "2.0", + "id": 1, + "method": "Settings.SetSettingValue", + "params": { + + "setting": "services.webserver", + "value": True + } + } + result = xbmc.executeJSONRPC(json.dumps(web_user)) + self.xbmc_username = "kodi" + + else: + # Webserver already enabled + web_port = { + + "jsonrpc": "2.0", + "id": 1, + "method": "Settings.GetSettingValue", + "params": { + + "setting": "services.webserverport" + } + } + result = xbmc.executeJSONRPC(json.dumps(web_port)) + try: + self.xbmc_port = result['result']['value'] + except TypeError: + pass + + web_user = { + + "jsonrpc": "2.0", + "id": 1, + "method": "Settings.GetSettingValue", + "params": { + + "setting": "services.webserverusername" + } + } + result = xbmc.executeJSONRPC(json.dumps(web_user)) + try: + self.xbmc_username = result['result']['value'] + except TypeError: + pass + + web_pass = { + + "jsonrpc": "2.0", + "id": 1, + "method": "Settings.GetSettingValue", + "params": { + + "setting": "services.webserverpassword" + } + } + result = xbmc.executeJSONRPC(json.dumps(web_pass)) + try: + self.xbmc_password = result['result']['value'] + except TypeError: + pass + + def FullTextureCacheSync(self): + # This method will sync all Kodi artwork to textures13.db + # and cache them locally. This takes diskspace! + + # Remove all existing textures first + path = xbmc.translatePath("special://thumbnails/").decode('utf-8') + if xbmcvfs.exists(path): + allDirs, allFiles = xbmcvfs.listdir(path) + for dir in allDirs: + allDirs, allFiles = xbmcvfs.listdir(path+dir) + for file in allFiles: + xbmcvfs.delete(os.path.join(path+dir,file)) + + textureconnection = utils.KodiSQL('texture') + texturecursor = textureconnection.cursor() + texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') + rows = texturecursor.fetchall() + for row in rows: + tableName = row[0] + if(tableName != "version"): + texturecursor.execute("DELETE FROM " + tableName) + textureconnection.commit() + texturecursor.close() + + + # Cache all entries in video DB + connection = utils.KodiSQL('video') + cursor = connection.cursor() + cursor.execute("SELECT url FROM art") + result = cursor.fetchall() + for url in result: + self.CacheTexture(url[0]) + cursor.close() + + # Cache all entries in music DB + connection = utils.KodiSQL('music') + cursor = connection.cursor() + cursor.execute("SELECT url FROM art") + result = cursor.fetchall() + for url in result: + self.CacheTexture(url[0]) + cursor.close() + + def CacheTexture(self, url): + # Cache a single image url to the texture cache + if url and self.enableTextureCache: + self.logMsg("Processing: %s" % url, 2) + + # Add image to texture cache by simply calling it at the http endpoint + url = self.double_urlencode(url) + try: # Extreme short timeouts so we will have a exception. + response = requests.head( + url=( + "http://%s:%s/image/image://%s" + % (self.xbmc_host, self.xbmc_port, url)), + auth=(self.xbmc_username, self.xbmc_password), + timeout=(0.01, 0.01)) + # We don't need the result + except: pass + + + def addArtwork(self, artwork, kodiId, mediaType, cursor): + # Kodi conversion table + kodiart = { + + 'Primary': ["thumb", "poster"], + 'Banner': "banner", + 'Logo': "clearlogo", + 'Art': "clearart", + 'Thumb': "landscape", + 'Disc': "discart", + 'Backdrop': "fanart", + 'BoxRear': "poster" + } + + # Artwork is a dictionary + for art in artwork: + + if art == "Backdrop": + # Backdrop entry is a list + # Process extra fanart for artwork downloader (fanart, fanart1, fanart2...) + backdrops = artwork[art] + backdropsNumber = len(backdrops) + + query = ' '.join(( + + "SELECT url", + "FROM art", + "WHERE media_id = ?", + "AND media_type = ?", + "AND type LIKE ?" + )) + cursor.execute(query, (kodiId, mediaType, "fanart%",)) + rows = cursor.fetchall() + + if len(rows) > backdropsNumber: + # More backdrops in database. Delete extra fanart. + query = ' '.join(( + + "DELETE FROM art", + "WHERE media_id = ?", + "AND media_type = ?", + "AND type LIKE ?" + )) + cursor.execute(query, (kodiId, mediaType, "fanart_",)) + + # Process backdrops and extra fanart + index = "" + for backdrop in backdrops: + self.addOrUpdateArt( + imageUrl=backdrop, + kodiId=kodiId, + mediaType=mediaType, + imageType="%s%s" % ("fanart", index), + cursor=cursor) + + if backdropsNumber > 1: + try: # Will only fail on the first try, str to int. + index += 1 + except TypeError: + index = 1 + + elif art == "Primary": + # Primary art is processed as thumb and poster for Kodi. + for artType in kodiart[art]: + self.addOrUpdateArt( + imageUrl=artwork[art], + kodiId=kodiId, + mediaType=mediaType, + imageType=artType, + cursor=cursor) + + elif kodiart.get(art): + # Process the rest artwork type that Kodi can use + self.addOrUpdateArt( + imageUrl=artwork[art], + kodiId=kodiId, + mediaType=mediaType, + imageType=kodiart[art], + cursor=cursor) + + def addOrUpdateArt(self, imageUrl, kodiId, mediaType, imageType, cursor): + # Possible that the imageurl is an empty string + if imageUrl: + cacheimage = False + + query = ' '.join(( + + "SELECT url", + "FROM art", + "WHERE media_id = ?", + "AND media_type = ?", + "AND type = ?" + )) + cursor.execute(query, (kodiId, mediaType, imageType,)) + try: # Update the artwork + url = cursor.fetchone()[0] + + except TypeError: # Add the artwork + cacheimage = True + self.logMsg("Adding Art Link for kodiId: %s (%s)" % (kodiId, imageUrl), 2) + + query = ' '.join(( + + "INSERT INTO art(", + "media_id, media_type, type, url)", + + "VALUES (?, ?, ?, ?)" + )) + cursor.execute(query, (kodiId, mediaType, imageType, imageUrl)) + + else: # Only cache artwork if it changed + if url != imageUrl: + cacheimage = True + + # Only for the main backdrop, poster + if imageType in ("fanart", "poster"): + # Delete current entry before updating with the new one + self.deleteCachedArtwork(url) + + self.logMsg( + "Updating Art url for %s kodiId: %s (%s) -> (%s)" + % (imageType, kodiId, url, imageUrl), 1) + + query = ' '.join(( + + "UPDATE art", + "SET url = ?", + "WHERE media_id = ?", + "AND media_type = ?", + "AND type = ?" + )) + cursor.execute(query, (imageUrl, kodiId, mediaType, imageType)) + + # Cache fanart and poster in Kodi texture cache + if cacheimage and imageType in ("fanart", "poster"): + self.CacheTexture(imageUrl) + + def deleteArtwork(self, kodiid, mediatype, cursor): + + query = ' '.join(( + + "SELECT url, type", + "FROM art", + "WHERE media_id = ?", + "AND media_type = ?" + )) + cursor.execute(query, (kodiid, mediatype,)) + rows = cursor.fetchall() + for row in rows: + + url = row[0] + imagetype = row[1] + if imagetype in ("poster", "fanart"): + self.deleteCachedArtwork(url) + + def deleteCachedArtwork(self, url): + # Only necessary to remove and apply a new backdrop or poster + connection = utils.kodiSQL('texture') + cursor = connection.cursor() + + cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,)) + try: + cachedurl = cursor.fetchone()[0] + + except TypeError: + self.logMsg("Could not find cached url.", 1) + + else: # Delete thumbnail as well as the entry + thumbnails = xbmc.translatePath("special://thumbnails/%s" % cachedurl).decode('utf-8') + self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1) + xbmcvfs.delete(thumbnails) + + try: + cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) + connection.commit() + except: + self.logMsg("Issue deleting url from cache. Skipping.", 2) + + finally: + cursor.close() + + def getPeopleArtwork(self, people): + # append imageurl if existing + for person in people: + + personId = person['Id'] + tag = person.get('PrimaryImageTag') + + image = "" + if tag: + image = ( + "%s/emby/Items/%s/Images/Primary?" + "MaxWidth=400&MaxHeight=400&Index=0&Tag=%s" + % (self.server, personId, tag)) + + person['imageurl'] = image + + return people + + def getUserArtwork(self, itemid, itemtype): + # Load user information set by UserClient + image = ("%s/emby/Users/%s/Images/%s?Format=original" + % (self.server, itemid, itemtype)) + return image + + def getAllArtwork(self, item, parentInfo=False): + + server = self.server + + id = item['Id'] + artworks = item['ImageTags'] + backdrops = item['BackdropImageTags'] + + maxHeight = 10000 + maxWidth = 10000 + customquery = "" + + if utils.settings('compressArt') == "true": + customquery = "&Quality=90" + + if utils.settings('enableCoverArt') == "false": + customquery += "&EnableImageEnhancers=false" + + allartworks = { + + 'Primary': "", + 'Art': "", + 'Banner': "", + 'Logo': "", + 'Thumb': "", + 'Disc': "", + 'Backdrop': [] + } + + # Process backdrops + backdropIndex = 0 + for backdroptag in backdrops: + artwork = ( + "%s/emby/Items/%s/Images/Backdrop/%s?" + "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" + % (server, id, backdropIndex, + maxWidth, maxHeight, backdroptag, customquery)) + allartworks['Backdrop'].append(artwork) + backdropIndex += 1 + + # Process the rest of the artwork + for art in artworks: + # Filter backcover + if art != "BoxRear": + tag = artworks[art] + artwork = ( + "%s/emby/Items/%s/Images/%s/0?" + "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" + % (server, id, art, maxWidth, maxHeight, tag, customquery)) + allartworks[art] = artwork + + # Process parent items if the main item is missing artwork + if parentInfo: + + # Process parent backdrops + if not allartworks['Backdrop']: + + parentId = item.get('ParentBackdropItemId') + if parentId: + # If there is a parentId, go through the parent backdrop list + parentbackdrops = item['ParentBackdropImageTags'] + + backdropIndex = 0 + for parentbackdroptag in parentbackdrops: + artwork = ( + "%s/emby/Items/%s/Images/Backdrop/%s?" + "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" + % (server, parentId, backdropIndex, + maxWidth, maxHeight, parentbackdroptag, customquery)) + allartworks['Backdrop'].append(artwork) + backdropIndex += 1 + + # Process the rest of the artwork + parentartwork = ['Logo', 'Art', 'Thumb'] + for parentart in parentartwork: + + if not allartworks[parentart]: + + parentId = item.get('Parent%sItemId' % parentart) + if parentId: + + parentTag = item['Parent%sImageTag' % parentart] + artwork = ( + "%s/emby/Items/%s/Images/%s/0?" + "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" + % (server, parentId, parentart, + maxWidth, maxHeight, parentTag, customquery)) + allartworks[parentart] = artwork + + # Parent album works a bit differently + if not allartworks['Primary']: + + parentId = item.get('AlbumId') + if parentId and item.get('AlbumPrimaryImageTag'): + + parentTag = item['AlbumPrimaryImageTag'] + artwork = ( + "%s/emby/Items/%s/Images/Primary/0?" + "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" + % (server, parentId, maxWidth, maxHeight, parentTag, customquery)) + allartworks['Primary'] = artwork + + return allartworks \ No newline at end of file diff --git a/resources/lib/clientinfo.py b/resources/lib/clientinfo.py new file mode 100644 index 00000000..22884c5d --- /dev/null +++ b/resources/lib/clientinfo.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +from uuid import uuid4 + +import xbmc +import xbmcaddon + +import utils + +################################################################################################# + + +class ClientInfo(): + + + def __init__(self): + + self.addon = xbmcaddon.Addon() + self.addonName = self.getAddonName() + + def logMsg(self, msg, lvl=1): + + className = self.__class__.__name__ + utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) + + + def getAddonName(self): + # Used for logging + return self.addon.getAddonInfo('name').upper() + + def getAddonId(self): + + return "plugin.video.emby" + + def getVersion(self): + + return self.addon.getAddonInfo('version') + + def getDeviceName(self): + + if utils.settings('deviceNameOpt') == "false": + # Use Kodi's deviceName + deviceName = xbmc.getInfoLabel('System.FriendlyName') + else: + deviceName = utils.settings('deviceName') + deviceName = deviceName.replace("\"", "_") + deviceName = deviceName.replace("/", "_") + + return deviceName + + def getPlatform(self): + + if xbmc.getCondVisibility('system.platform.osx'): + return "OSX" + elif xbmc.getCondVisibility('system.platform.atv2'): + return "ATV2" + elif xbmc.getCondVisibility('system.platform.ios'): + return "iOS" + elif xbmc.getCondVisibility('system.platform.windows'): + return "Windows" + elif xbmc.getCondVisibility('system.platform.linux'): + return "Linux/RPi" + elif xbmc.getCondVisibility('system.platform.android'): + return "Linux/Android" + else: + return "Unknown" + + def getDeviceId(self): + + clientId = utils.window('emby_deviceId') + if clientId: + return clientId + + addon_path = self.addon.getAddonInfo('path').decode('utf-8') + GUID_file = xbmc.translatePath("%s\machine_guid" % addon_path).decode('utf-8') + + try: + GUID = open(GUID_file) + + except IOError: # machine_guid does not exists. + clientId = str("%012X" % uuid4()) + GUID = open(GUID_file, 'w') + GUID.write(clientId) + + else: # machine_guid already exists. Get guid. + clientId = GUID.read() + + finally: + GUID.close() + + self.logMsg("DeviceId loaded: %s" % clientId, 1) + utils.window('emby_deviceId', value=clientId) + + return clientId \ No newline at end of file diff --git a/resources/lib/embydb_functions.py b/resources/lib/embydb_functions.py new file mode 100644 index 00000000..1afa0d84 --- /dev/null +++ b/resources/lib/embydb_functions.py @@ -0,0 +1,294 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import utils +import clientinfo + +################################################################################################# + + +class Embydb_Functions(): + + + def __init__(self, embycursor): + + self.embycursor = embycursor + + self.clientInfo = clientinfo.ClientInfo() + self.addonName = self.clientInfo.getAddonName() + + def logMsg(self, msg, lvl=1): + + className = self.__class__.__name__ + utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) + + def getViews(self): + + embycursor = self.embycursor + views = [] + + query = ' '.join(( + + "SELECT view_id", + "FROM view" + )) + embycursor.execute(query) + rows = embycursor.fetchall() + for row in rows: + views.append(row[0]) + + return views + + def getView_byId(self, viewid): + + embycursor = self.embycursor + + query = ' '.join(( + + "SELECT view_name, media_type, kodi_tagid", + "FROM view", + "WHERE view_id = ?" + )) + embycursor.execute(query, (viewid,)) + view = embycursor.fetchone() + + return view + + def getView_byType(self, mediatype): + + embycursor = self.embycursor + views = [] + + query = ' '.join(( + + "SELECT view_id, view_name", + "FROM view", + "WHERE media_type = ?" + )) + embycursor.execute(query, (mediatype,)) + rows = embycursor.fetchall() + for row in rows: + views.append({ + + 'id': row[0], + 'name': row[1] + }) + + return views + + def getView_byName(self, tagname): + + embycursor = self.embycursor + + query = ' '.join(( + + "SELECT view_id", + "FROM view", + "WHERE view_name = ?" + )) + embycursor.execute(query, (tagname,)) + try: + view = embycursor.fetchone()[0] + + except TypeError: + view = None + + return view + + def addView(self, embyid, name, mediatype, tagid): + + query = ( + ''' + INSERT INTO view( + view_id, view_name, media_type, kodi_tagid) + + VALUES (?, ?, ?, ?) + ''' + ) + self.embycursor.execute(query, (embyid, name, mediatype, tagid)) + + def updateView(self, name, tagid, mediafolderid): + + query = ' '.join(( + + "UPDATE view", + "SET view_name = ?, kodi_tagid = ?", + "WHERE view_id = ?" + )) + self.embycursor.execute(query, (name, tagid, mediafolderid)) + + def getItem_byId(self, embyid): + + embycursor = self.embycursor + + query = ' '.join(( + + "SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, media_type, emby_type", + "FROM emby", + "WHERE emby_id = ?" + )) + embycursor.execute(query, (embyid,)) + item = embycursor.fetchone() + + return item + + def getItem_byView(self, mediafolderid): + + embycursor = self.embycursor + + query = ' '.join(( + + "SELECT kodi_id", + "FROM emby", + "WHERE media_folder = ?" + )) + embycursor.execute(query, (mediafolderid,)) + items = embycursor.fetchall() + + return items + + def getItem_byKodiId(self, kodiid, mediatype): + + embycursor = self.embycursor + + query = ' '.join(( + + "SELECT emby_id, parent_id", + "FROM emby", + "WHERE kodi_id = ?", + "AND media_type = ?" + )) + embycursor.execute(query, (kodiid, mediatype,)) + item = embycursor.fetchone() + + return item + + def getItem_byParentId(self, parentid, mediatype): + + embycursor = self.embycursor + + query = ' '.join(( + + "SELECT emby_id, kodi_id, kodi_fileid", + "FROM emby", + "WHERE parent_id = ?", + "AND media_type = ?" + )) + embycursor.execute(query, (parentid, mediatype,)) + items = embycursor.fetchall() + + return items + + def getItemId_byParentId(self, parentid, mediatype): + + embycursor = self.embycursor + + query = ' '.join(( + + "SELECT emby_id, kodi_id", + "FROM emby", + "WHERE parent_id = ?", + "AND media_type = ?" + )) + embycursor.execute(query, (parentid, mediatype,)) + items = embycursor.fetchall() + + return items + + def getChecksum(self, mediatype): + + embycursor = self.embycursor + + query = ' '.join(( + + "SELECT emby_id, checksum", + "FROM emby", + "WHERE emby_type = ?" + )) + embycursor.execute(query, (mediatype,)) + items = embycursor.fetchall() + + return items + + def getMediaType_byId(self, embyid): + + embycursor = self.embycursor + + query = ' '.join(( + + "SELECT emby_type", + "FROM emby", + "WHERE emby_id = ?" + )) + embycursor.execute(query, (embyid,)) + try: + itemtype = embycursor.fetchone()[0] + + except TypeError: + itemtype = None + + return itemtype + + def sortby_mediaType(self, itemids, unsorted=True): + + sorted_items = {} + + for itemid in itemids: + + mediatype = self.getMediaType_byId(itemid) + if mediatype: + sorted_items.setdefault(mediatype, []).append(itemid) + elif unsorted: + sorted_items.setdefault('Unsorted', []).append(itemid) + + return sorted_items + + def addReference(self, embyid, kodiid, embytype, mediatype, fileid=None, pathid=None, + parentid=None, checksum=None, mediafolderid=None): + query = ( + ''' + INSERT OR REPLACE INTO emby( + emby_id, kodi_id, kodi_fileid, kodi_pathid, emby_type, media_type, parent_id, + checksum, media_folder) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + self.embycursor.execute(query, (embyid, kodiid, fileid, pathid, embytype, mediatype, + parentid, checksum, mediafolderid)) + + def updateReference(self, embyid, checksum): + + query = "UPDATE emby SET checksum = ? WHERE emby_id = ?" + self.embycursor.execute(query, (checksum, embyid)) + + def updateParentId(self, embyid, parent_kodiid): + + query = "UPDATE emby SET parent_id = ? WHERE emby_id = ?" + self.embycursor.execute(query, (parent_kodiid, embyid)) + + def removeItems_byParentId(self, parent_kodiid, mediatype): + + query = ' '.join(( + + "DELETE FROM emby", + "WHERE parent_id = ?", + "AND media_type = ?" + )) + self.embycursor.execute(query, (parent_kodiid, mediatype,)) + + def removeItem_byKodiId(self, kodiid, mediatype): + + query = ' '.join(( + + "DELETE FROM emby", + "WHERE kodi_id = ?", + "AND media_type = ?" + )) + self.embycursor.execute(query, (kodiid, mediatype,)) + + def removeItem(self, embyid): + + query = "DELETE FROM emby WHERE emby_id = ?" + self.embycursor.execute(query, (embyid,)) \ No newline at end of file diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py new file mode 100644 index 00000000..e23c9001 --- /dev/null +++ b/resources/lib/initialsetup.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import socket + +import xbmc +import xbmcgui +import xbmcaddon + +import utils +import clientinfo +import downloadutils +import userclient + +################################################################################################# + + +class InitialSetup(): + + + def __init__(self): + + self.addon = xbmcaddon.Addon() + self.__language__ = self.addon.getLocalizedString + + self.clientInfo = clientinfo.ClientInfo() + self.addonName = self.clientInfo.getAddonName() + self.addonId = self.clientInfo.getAddonId() + self.doUtils = downloadutils.DownloadUtils() + self.userClient = userclient.UserClient() + + def logMsg(self, msg, lvl=1): + + className = self.__class__.__name__ + utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) + + + def setup(self): + # Check server, user, direct paths, music, direct stream if not direct path. + string = self.__language__ + addonId = self.addonId + + ##### SERVER INFO ##### + + self.logMsg("Initial setup called.", 2) + server = self.userClient.getServer() + + if server: + self.logMsg("Server is already set.", 2) + return + + self.logMsg("Looking for server...", 2) + server = self.getServerDetails() + self.logMsg("Found: %s" % server, 2) + try: + prefix, ip, port = server.replace("/", "").split(":") + except: # Failed to retrieve server information + self.logMsg("getServerDetails failed.", 1) + xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) + return + else: + server_confirm = xbmcgui.Dialog().yesno( + heading="Emby for Kodi", + line1="Proceed with the following server?", + line2="%s %s" % (string(30169), server)) + if server_confirm: + # Correct server found + self.logMsg("Server is selected. Saving the information.", 1) + utils.settings('ipaddress', value=ip) + utils.settings('port', value=port) + + if prefix == "https": + utils.settings('https', value="true") + else: + # User selected no or cancelled the dialog + self.logMsg("No server selected.", 1) + xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) + return + + ##### USER INFO ##### + + self.logMsg("Getting user list.", 1) + + url = "%s/emby/Users/Public?format=json" % server + result = self.doUtils.downloadUrl(url, authenticate=False) + if result == "": + self.logMsg("Unable to connect to %s" % server, 1) + return + + self.logMsg("Response: %s" % result, 2) + # Process the list of users + usernames = [] + users_hasPassword = [] + + for user in result: + # Username + name = user['Name'] + usernames.append(name) + # Password + if user['HasPassword']: + name = "%s (secure)" % name + users_hasPassword.append(name) + + self.logMsg("Presenting user list: %s" % users_hasPassword, 1) + user_select = xbmcgui.Dialog().select(string(30200), users_hasPassword) + if user_select > -1: + selected_user = usernames[user_select] + self.logMsg("Selected user: %s" % selected_user, 1) + utils.settings('username', value=selected_user) + else: + self.logMsg("No user selected.", 1) + xbmc.executebuiltin('Addon.OpenSettings(%s)' % addonId) + + ##### ADDITIONAL PROMPTS ##### + dialog = xbmcgui.Dialog() + + directPaths = dialog.yesno( + heading="Playback Mode", + line1=( + "Caution! If you choose Native mode, you " + "will lose access to certain Emby features such as: " + "Emby cinema mode, direct stream/transcode options, " + "parental access schedule."), + nolabel="Addon (Default)", + yeslabel="Native (Direct Paths)") + if directPaths: + self.logMsg("User opted to use direct paths.", 1) + utils.settings('useDirectPaths', value="1") + + # ask for credentials + credentials = dialog.yesno( + heading="Network credentials", + line1= ( + "Add network credentials to allow Kodi access to your " + "content? Note: Skipping this step may generate a message " + "during the initial scan of your content if Kodi can't " + "locate your content.")) + if credentials: + self.logMsg("Presenting network credentials dialog.", 1) + utils.passwordsXML() + + musicDisabled = dialog.yesno( + heading="Music Library", + line1="Disable Emby music library?") + if musicDisabled: + self.logMsg("User opted to disable Emby music library.", 1) + utils.settings('enableMusic', value="false") + else: + # Only prompt if the user didn't select direct paths for videos + if not directPaths: + musicAccess = dialog.yesno( + heading="Music Library", + line1=( + "Direct stream the music library? Select " + "this option only if you plan on listening " + "to music outside of your network.")) + if musicAccess: + self.logMsg("User opted to direct stream music.", 1) + utils.settings('streamMusic', value="true") + + def getServerDetails(self): + + self.logMsg("Getting Server Details from Network", 1) + + MULTI_GROUP = ("", 7359) + MESSAGE = "who is EmbyServer?" + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(6.0) + + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20) + + sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1) + sock.setsockopt(socket.IPPROTO_IP, socket.SO_REUSEADDR, 1) + + self.logMsg("MultiGroup : %s" % str(MULTI_GROUP), 2); + self.logMsg("Sending UDP Data: %s" % MESSAGE, 2); + sock.sendto(MESSAGE, MULTI_GROUP) + + try: + data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes + self.logMsg("Received Response: %s" % data) + except: + self.logMsg("No UDP Response") + return None + else: + # Get the address + data = json.loads(data) + return data['Address'] \ No newline at end of file diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py new file mode 100644 index 00000000..9c87fb73 --- /dev/null +++ b/resources/lib/itemtypes.py @@ -0,0 +1,2528 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import urllib +from ntpath import dirname +from datetime import datetime + +import xbmc +import xbmcgui +import xbmcvfs + +import api +import artwork +import clientinfo +import downloadutils +import utils +import embydb_functions as embydb +import kodidb_functions as kodidb +import read_embyserver as embyserver + +################################################################################################## + + +class Items(object): + + + def __init__(self, embycursor, kodicursor): + + self.embycursor = embycursor + self.kodicursor = kodicursor + + self.clientInfo = clientinfo.ClientInfo() + self.addonName = self.clientInfo.getAddonName() + self.doUtils = downloadutils.DownloadUtils() + + self.kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) + self.directpath = utils.settings('useDirectPaths') == "1" + self.music_enabled = utils.settings('enableMusic') == "true" + self.contentmsg = utils.settings('newContent') == "true" + + self.artwork = artwork.Artwork() + self.emby = embyserver.Read_EmbyServer() + self.emby_db = embydb.Embydb_Functions(embycursor) + self.kodi_db = kodidb.Kodidb_Functions(kodicursor) + + def logMsg(self, msg, lvl=1): + + className = self.__class__.__name__ + utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) + + + def itemsbyId(self, items, process, pdialog=None): + # Process items by itemid. Process can be added, update, userdata, remove + emby = self.emby + embycursor = self.embycursor + kodicursor = self.kodicursor + music_enabled = self.music_enabled + + itemtypes = { + + 'Movie': Movies, + 'BoxSet': Movies, + 'MusicVideo': MusicVideos, + 'Series': TVShows, + 'Season': TVShows, + 'Episode': TVShows, + 'MusicAlbum': Music, + 'MusicArtist': Music, + 'AlbumArtist': Music, + 'Audio': Music, + 'Video': HomeVideos + } + + total = 0 + for item in items: + total += len(items[item]) + + if total == 0: + return False + + self.logMsg("Processing %s: %s" % (process, items), 1) + if pdialog: + pdialog.update(heading="Processing %s: %s items" % (process, total)) + + count = 0 + for itemtype in items: + + # Safety check + if not itemtypes.get(itemtype): + # We don't process this type of item + continue + + itemlist = items[itemtype] + if not itemlist: + # The list to process is empty + continue + + musicconn = None + + if itemtype in ('MusicAlbum', 'MusicArtist', 'Audio'): + if music_enabled: + musicconn = utils.kodiSQL('music') + musiccursor = musicconn.cursor() + items_process = itemtypes[itemtype](embycursor, musiccursor) + else: + # Music is not enabled, do not proceed with itemtype + continue + else: + items_process = itemtypes[itemtype](embycursor, kodicursor) + + if itemtype == "Movie": + actions = { + 'added': items_process.added, + 'update': items_process.add_update, + 'userdata': items_process.updateUserdata, + 'remove': items_process.remove + } + elif itemtype == "BoxSet": + actions = { + 'added': items_process.added_boxset, + 'update': items_process.add_updateBoxset, + 'remove': items_process.remove + } + elif itemtype == "MusicVideo": + actions = { + 'added': items_process.added, + 'update': items_process.add_update, + 'userdata': items_process.updateUserdata, + 'remove': items_process.remove + } + elif itemtype == "Series": + actions = { + 'added': items_process.added, + 'update': items_process.add_update, + 'userdata': items_process.updateUserdata, + 'remove': items_process.remove + } + elif itemtype == "Season": + actions = { + 'added': items_process.added_season, + 'update': items_process.add_updateSeason, + 'remove': items_process.remove + } + elif itemtype == "Episode": + actions = { + 'added': items_process.added_episode, + 'update': items_process.add_updateEpisode, + 'userdata': items_process.updateUserdata, + 'remove': items_process.remove + } + elif itemtype == "MusicAlbum": + actions = { + 'added': items_process.added_album, + 'update': items_process.add_updateAlbum, + 'userdata': items_process.updateUserdata, + 'remove': items_process.remove + } + elif itemtype in ("MusicArtist", "AlbumArtist"): + actions = { + 'added': items_process.added, + 'update': items_process.add_updateArtist, + 'remove': items_process.remove + } + elif itemtype == "Audio": + actions = { + 'added': items_process.added_song, + 'update': items_process.add_updateSong, + 'userdata': items_process.updateUserdata, + 'remove': items_process.remove + } + elif itemtype == "Video": + actions = { + 'added': items_process.added, + 'update': items_process.add_update, + 'userdata': items_process.updateUserdata, + 'remove': items_process.remove + } + else: + self.logMsg("Unsupported itemtype: %s." % itemtype, 1) + actions = {} + + if actions.get(process): + + if process == "remove": + for item in itemlist: + actions[process](item) + + elif process == "added": + actions[process](itemlist, pdialog) + + else: + processItems = emby.getFullItems(itemlist) + for item in processItems: + + title = item['Name'] + + if itemtype == "Episode": + title = "%s - %s" % (item['SeriesName'], title) + + if pdialog: + percentage = int((float(count) / float(total))*100) + pdialog.update(percentage, message=title) + count += 1 + + actions[process](item) + else: + if itemtype == "Movie" and process == "update": + # Refresh boxsets + boxsets = self.emby.getBoxset() + items_process.added_boxset(boxsets['Items'], pdialog) + + + if musicconn is not None: + # close connection for special types + musicconn.commit() + musiccursor.close() + + return True + + def contentPop(self, name): + xbmcgui.Dialog().notification( + heading="Emby for Kodi", + message="Added: %s" % name, + icon="special://home/addons/plugin.video.emby/icon.png", + sound=False) + + +class Movies(Items): + + + def __init__(self, embycursor, kodicursor): + Items.__init__(self, embycursor, kodicursor) + + def added(self, items, pdialog): + + total = len(items) + count = 0 + for movie in items: + + title = movie['Name'] + if pdialog: + percentage = int((float(count) / float(total))*100) + pdialog.update(percentage, message=title) + count += 1 + self.add_update(movie) + if not pdialog and self.contentmsg: + self.contentPop(title) + # Refresh boxsets + boxsets = self.emby.getBoxset() + self.added_boxset(boxsets['Items'], pdialog) + + def added_boxset(self, items, pdialog): + + total = len(items) + count = 0 + for boxset in items: + + title = boxset['Name'] + if pdialog: + percentage = int((float(count) / float(total))*100) + pdialog.update(percentage, message=title) + count += 1 + self.add_updateBoxset(boxset) + + + def add_update(self, item, viewtag=None, viewid=None): + # Process single movie + kodicursor = self.kodicursor + emby_db = self.emby_db + kodi_db = self.kodi_db + artwork = self.artwork + API = api.API(item) + + # If the item already exist in the local Kodi DB we'll perform a full item update + # If the item doesn't exist, we'll add it to the database + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + movieid = emby_dbitem[0] + fileid = emby_dbitem[1] + pathid = emby_dbitem[2] + self.logMsg("movieid: %s fileid: %s pathid: %s" % (movieid, fileid, pathid), 1) + + except TypeError: + update_item = False + self.logMsg("movieid: %s not found." % itemid, 2) + # movieid + kodicursor.execute("select coalesce(max(idMovie),0) from movie") + movieid = kodicursor.fetchone()[0] + 1 + + if not viewtag or not viewid: + # Get view tag from emby + viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) + self.logMsg("View tag found: %s" % viewtag, 2) + + # fileId information + checksum = API.getChecksum() + dateadded = API.getDateCreated() + userdata = API.getUserData() + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + # item details + people = API.getPeople() + writer = " / ".join(people['Writer']) + director = " / ".join(people['Director']) + genres = item['Genres'] + title = item['Name'] + plot = API.getOverview() + shortplot = item.get('ShortOverview') + tagline = API.getTagline() + votecount = item.get('VoteCount') + rating = item.get('CommunityRating') + year = item.get('ProductionYear') + imdb = API.getProvider('Imdb') + sorttitle = item['SortName'] + runtime = API.getRuntime() + mpaa = API.getMpaa() + genre = " / ".join(genres) + country = API.getCountry() + studios = API.getStudios() + try: + studio = studios[0] + except IndexError: + studio = None + + if item.get('LocalTrailerCount'): + # There's a local trailer + url = ( + "{server}/emby/Users/{UserId}/Items/%s/LocalTrailers?format=json" + % itemid + ) + result = self.doUtils.downloadUrl(url) + trailer = "plugin://plugin.video.emby/trailer/?id=%s&mode=play" % result[0]['Id'] + else: + # Try to get the youtube trailer + try: + trailer = item['RemoteTrailers'][0]['Url'] + except (KeyError, IndexError): + trailer = None + else: + trailerId = trailer.rsplit('=', 1)[1] + trailer = "plugin://plugin.video.youtube/play/?video_id=%s" % trailerId + + + ##### GET THE FILE AND PATH ##### + playurl = API.getFilePath() + + if "\\" in playurl: + # Local path + filename = playurl.rsplit("\\", 1)[1] + else: # Network share + filename = playurl.rsplit("/", 1)[1] + + if self.directpath: + # Direct paths is set the Kodi way + if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl): + # Validate the path is correct with user intervention + resp = xbmcgui.Dialog().yesno( + heading="Can't validate path", + line1=( + "Kodi can't locate file: %s. Verify the path. " + "You may to verify your network credentials in the " + "add-on settings or use the emby path substitution " + "to format your path correctly. Stop syncing?" + % playurl)) + if resp: + utils.window('emby_shouldStop', value="true") + return False + + path = playurl.replace(filename, "") + utils.window('emby_pathverified', value="true") + else: + # Set plugin path and media flags using real filename + path = "plugin://plugin.video.emby.movies/" + params = { + + 'filename': filename.encode('utf-8'), + 'id': itemid, + 'dbid': movieid, + 'mode': "play" + } + filename = "%s?%s" % (path, urllib.urlencode(params)) + + + ##### UPDATE THE MOVIE ##### + if update_item: + self.logMsg("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1) + + # Update the movie entry + query = ' '.join(( + + "UPDATE movie", + "SET c00 = ?, c01 = ?, c02 = ?, c03 = ?, c04 = ?, c05 = ?, c06 = ?,", + "c07 = ?, c09 = ?, c10 = ?, c11 = ?, c12 = ?, c14 = ?, c15 = ?,", + "c16 = ?, c18 = ?, c19 = ?, c21 = ?", + "WHERE idMovie = ?" + )) + kodicursor.execute(query, (title, plot, shortplot, tagline, votecount, rating, writer, + year, imdb, sorttitle, runtime, mpaa, genre, director, title, studio, trailer, + country, movieid)) + + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE MOVIE ##### + else: + self.logMsg("ADD movie itemid: %s - Title: %s" % (itemid, title), 1) + + # Add path + pathid = kodi_db.addPath(path) + # Add the file + fileid = kodi_db.addFile(filename, pathid) + + # Create the movie entry + query = ( + ''' + INSERT INTO movie( + idMovie, idFile, c00, c01, c02, c03, c04, c05, c06, c07, + c09, c10, c11, c12, c14, c15, c16, c18, c19, c21) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (movieid, fileid, title, plot, shortplot, tagline, votecount, + rating, writer, year, imdb, sorttitle, runtime, mpaa, genre, director, title, + studio, trailer, country)) + + # Create the reference in emby table + emby_db.addReference(itemid, movieid, "Movie", "movie", fileid, pathid, None, checksum, viewid) + + # Update the path + query = ' '.join(( + + "UPDATE path", + "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", + "WHERE idPath = ?" + )) + kodicursor.execute(query, (path, "movies", "metadata.local", 1, pathid)) + + # Update the file + query = ' '.join(( + + "UPDATE files", + "SET idPath = ?, strFilename = ?, dateAdded = ?", + "WHERE idFile = ?" + )) + kodicursor.execute(query, (pathid, filename, dateadded, fileid)) + + # Process countries + kodi_db.addCountries(movieid, item['ProductionLocations'], "movie") + # Process cast + people = artwork.getPeopleArtwork(item['People']) + kodi_db.addPeople(movieid, people, "movie") + # Process genres + kodi_db.addGenres(movieid, genres, "movie") + # Process artwork + artwork.addArtwork(artwork.getAllArtwork(item), movieid, "movie", kodicursor) + # Process stream details + streams = API.getMediaStreams() + kodi_db.addStreams(fileid, streams, runtime) + # Process studios + kodi_db.addStudios(movieid, studios, "movie") + # Process tags: view, emby tags + tags = [viewtag] + tags.extend(item['Tags']) + if userdata['Favorite']: + tags.append("Favorite movies") + kodi_db.addTags(movieid, tags, "movie") + # Process playstates + resume = API.adjustResume(userdata['Resume']) + total = round(float(runtime), 6) + kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + + def add_updateBoxset(self, boxset): + + emby = self.emby + emby_db = self.emby_db + kodi_db = self.kodi_db + artwork = self.artwork + + boxsetid = boxset['Id'] + title = boxset['Name'] + checksum = boxset['Etag'] + emby_dbitem = emby_db.getItem_byId(boxsetid) + try: + setid = emby_dbitem[0] + + except TypeError: + setid = kodi_db.createBoxset(title) + + # Process artwork + artwork.addArtwork(artwork.getAllArtwork(boxset), setid, "set", self.kodicursor) + + # Process movies inside boxset + current_movies = emby_db.getItemId_byParentId(setid, "movie") + process = [] + try: + # Try to convert tuple to dictionary + current = dict(current_movies) + except ValueError: + current = {} + + # Sort current titles + for current_movie in current: + process.append(current_movie) + + # New list to compare + boxsetMovies = emby.getMovies_byBoxset(boxsetid) + for movie in boxsetMovies['Items']: + + itemid = movie['Id'] + + if not current.get(itemid): + # Assign boxset to movie + emby_dbitem = emby_db.getItem_byId(itemid) + movieid = emby_dbitem[0] + self.logMsg("New addition to boxset %s: %s" % (title, movie['Name'])) + kodi_db.assignBoxset(setid, movieid) + # Update emby reference + emby_db.updateParentId(itemid, setid) + else: + # Remove from process, because the item still belongs + process.remove(itemid) + + # Process removals from boxset + for movie in process: + movieid = current[movie] + self.logMsg("Remove from boxset %s: %s" % (title, movieid)) + kodi_db.removefromBoxset(movieid) + # Update emby reference + emby_db.updateParentId(movie, None) + + # Update the reference in the emby table + emby_db.addReference(boxsetid, setid, "BoxSet", mediatype="set", checksum=checksum) + + def updateUserdata(self, item): + # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + # Poster with progress bar + emby_db = self.emby_db + kodi_db = self.kodi_db + API = api.API(item) + + # Get emby information + itemid = item['Id'] + checksum = API.getChecksum() + userdata = API.getUserData() + runtime = API.getRuntime() + + # Get Kodi information + emby_dbitem = emby_db.getItem_byId(itemid) + try: + movieid = emby_dbitem[0] + fileid = emby_dbitem[1] + self.logMsg( + "Update playstate for movie: %s fileid: %s" + % (item['Name'], fileid), 1) + except TypeError: + return + + # Process favorite tags + if userdata['Favorite']: + kodi_db.addTag(movieid, "Favorite movies", "movie") + else: + kodi_db.removeTag(movieid, "Favorite movies", "movie") + + # Process playstates + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + resume = API.adjustResume(userdata['Resume']) + total = round(float(runtime), 6) + + kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + emby_db.updateReference(itemid, checksum) + + def remove(self, itemid): + # Remove movieid, fileid, emby reference + emby_db = self.emby_db + kodicursor = self.kodicursor + artwork = self.artwork + + emby_dbitem = emby_db.getItem_byId(itemid) + try: + kodiid = emby_dbitem[0] + fileid = emby_dbitem[1] + mediatype = emby_dbitem[4] + self.logMsg("Removing %sid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) + except TypeError: + return + + # Remove the emby reference + emby_db.removeItem(itemid) + # Remove artwork + artwork.deleteArtwork(kodiid, mediatype, kodicursor) + + if mediatype == "movie": + # Delete kodi movie and file + kodicursor.execute("DELETE FROM movie WHERE idMovie = ?", (kodiid,)) + kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) + + elif mediatype == "set": + # Delete kodi boxset + boxset_movies = emby_db.getItem_byParentId(kodiid, "movie") + for movie in boxset_movies: + embyid = movie[0] + movieid = movie[1] + self.kodi_db.removefromBoxset(movieid) + # Update emby reference + emby_db.updateParentId(embyid, None) + + kodicursor.execute("DELETE FROM sets WHERE idSet = ?", (kodiid,)) + + self.logMsg("Deleted %s %s from kodi database" % (mediatype, itemid), 1) + +class HomeVideos(Items): + + + def __init__(self, embycursor, kodicursor): + Items.__init__(self, embycursor, kodicursor) + + def added(self, items, pdialog): + + total = len(items) + count = 0 + for hvideo in items: + + title = hvideo['Name'] + if pdialog: + percentage = int((float(count) / float(total))*100) + pdialog.update(percentage, message=title) + count += 1 + self.add_update(hvideo) + if not pdialog and self.contentmsg: + self.contentPop(title) + + + def add_update(self, item, viewtag=None, viewid=None): + # Process single movie + kodicursor = self.kodicursor + emby_db = self.emby_db + kodi_db = self.kodi_db + artwork = self.artwork + API = api.API(item) + + # If the item already exist in the local Kodi DB we'll perform a full item update + # If the item doesn't exist, we'll add it to the database + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + hmovieid = emby_dbitem[0] + fileid = emby_dbitem[1] + pathid = emby_dbitem[2] + self.logMsg("hmovieid: %s fileid: %s pathid: %s" % (hmovieid, fileid, pathid), 1) + + except TypeError: + update_item = False + self.logMsg("hmovieid: %s not found." % itemid, 2) + # movieid + kodicursor.execute("select coalesce(max(idMovie),0) from movie") + hmovieid = kodicursor.fetchone()[0] + 1 + + if not viewtag or not viewid: + # Get view tag from emby + viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) + self.logMsg("View tag found: %s" % viewtag, 2) + + # fileId information + checksum = API.getChecksum() + dateadded = API.getDateCreated() + userdata = API.getUserData() + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + # item details + people = API.getPeople() + title = item['Name'] + year = item.get('ProductionYear') + sorttitle = item['SortName'] + runtime = API.getRuntime() + genre = "HomeVideos" + + + ##### GET THE FILE AND PATH ##### + playurl = API.getFilePath() + + if "\\" in playurl: + # Local path + filename = playurl.rsplit("\\", 1)[1] + else: # Network share + filename = playurl.rsplit("/", 1)[1] + + if self.directpath: + # Direct paths is set the Kodi way + if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl): + # Validate the path is correct with user intervention + utils.window('emby_directPath', clear=True) + resp = xbmcgui.Dialog().yesno( + heading="Can't validate path", + line1=( + "Kodi can't locate file: %s. Verify the path. " + "You may to verify your network credentials in the " + "add-on settings or use the emby path substitution " + "to format your path correctly. Stop syncing?" + % playurl)) + if resp: + utils.window('emby_shouldStop', value="true") + return False + + path = playurl.replace(filename, "") + utils.window('emby_pathverified', value="true") + else: + # Set plugin path and media flags using real filename + path = "plugin://plugin.video.emby.movies/" + params = { + + 'filename': filename.encode('utf-8'), + 'id': itemid, + 'dbid': hmovieid, + 'mode': "play" + } + filename = "%s?%s" % (path, urllib.urlencode(params)) + + + ##### UPDATE THE MOVIE ##### + if update_item: + self.logMsg("UPDATE homemovie itemid: %s - Title: %s" % (itemid, title), 1) + + # Update the movie entry + query = ' '.join(( + + "UPDATE movie", + "SET c00 = ?, c07 = ?, c10 = ?, c11 = ?, c14 = ?, c16 = ?", + "WHERE idMovie = ?" + )) + kodicursor.execute(query, (title, year, sorttitle, runtime, genre, title, hmovieid)) + + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE MOVIE ##### + else: + self.logMsg("ADD homemovie itemid: %s - Title: %s" % (itemid, title), 1) + + # Add path + pathid = kodi_db.addPath(path) + # Add the file + fileid = kodi_db.addFile(filename, pathid) + + # Create the movie entry + query = ( + ''' + INSERT INTO movie( + idMovie, idFile, c00, c07, c10, c11, c14, c16) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (hmovieid, fileid, title, year, sorttitle, runtime, + genre, title)) + + # Create the reference in emby table + emby_db.addReference(itemid, hmovieid, "Video", "movie", fileid, pathid, + None, checksum, viewid) + + # Update the path + query = ' '.join(( + + "UPDATE path", + "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", + "WHERE idPath = ?" + )) + kodicursor.execute(query, (path, "movies", "metadata.local", 1, pathid)) + + # Update the file + query = ' '.join(( + + "UPDATE files", + "SET idPath = ?, strFilename = ?, dateAdded = ?", + "WHERE idFile = ?" + )) + kodicursor.execute(query, (pathid, filename, dateadded, fileid)) + + + # Process artwork + artwork.addArtwork(artwork.getAllArtwork(item), hmovieid, "movie", kodicursor) + # Process stream details + streams = API.getMediaStreams() + kodi_db.addStreams(fileid, streams, runtime) + # Process tags: view, emby tags + tags = [viewtag] + tags.extend(item['Tags']) + if userdata['Favorite']: + tags.append("Favorite homemovies") + kodi_db.addTags(hmovieid, tags, "movie") + # Process playstates + resume = API.adjustResume(userdata['Resume']) + total = round(float(runtime), 6) + kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + + def updateUserdata(self, item): + # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + # Poster with progress bar + emby_db = self.emby_db + kodi_db = self.kodi_db + API = api.API(item) + + # Get emby information + itemid = item['Id'] + checksum = API.getChecksum() + userdata = API.getUserData() + runtime = API.getRuntime() + + # Get Kodi information + emby_dbitem = emby_db.getItem_byId(itemid) + try: + movieid = emby_dbitem[0] + fileid = emby_dbitem[1] + self.logMsg( + "Update playstate for homemovie: %s fileid: %s" + % (item['Name'], fileid), 1) + except TypeError: + return + + # Process favorite tags + if userdata['Favorite']: + kodi_db.addTag(movieid, "Favorite homemovies", "movie") + else: + kodi_db.removeTag(movieid, "Favorite homemovies", "movie") + + # Process playstates + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + resume = API.adjustResume(userdata['Resume']) + total = round(float(runtime), 6) + + kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + emby_db.updateReference(itemid, checksum) + + def remove(self, itemid): + # Remove movieid, fileid, emby reference + emby_db = self.emby_db + kodicursor = self.kodicursor + artwork = self.artwork + + emby_dbitem = emby_db.getItem_byId(itemid) + try: + hmovieid = emby_dbitem[0] + fileid = emby_dbitem[1] + self.logMsg("Removing hmovieid: %s fileid: %s" % (hmovieid, fileid), 1) + except TypeError: + return + + # Remove artwork + artwork.deleteArtwork(hmovieid, "movie", kodicursor) + + # Delete kodi movie and file + kodicursor.execute("DELETE FROM movie WHERE idMovie = ?", (hmovieid,)) + kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) + + # Remove the emby reference + emby_db.removeItem(itemid) + + self.logMsg("Deleted homemovie %s from kodi database" % itemid, 1) + +class MusicVideos(Items): + + + def __init__(self, embycursor, kodicursor): + Items.__init__(self, embycursor, kodicursor) + + def added(self, items, pdialog): + + total = len(items) + count = 0 + for mvideo in items: + + title = mvideo['Name'] + if pdialog: + percentage = int((float(count) / float(total))*100) + pdialog.update(percentage, message=title) + count += 1 + self.add_update(mvideo) + if not pdialog and self.contentmsg: + self.contentPop(title) + + + def add_update(self, item, viewtag=None, viewid=None): + # Process single music video + kodicursor = self.kodicursor + emby_db = self.emby_db + kodi_db = self.kodi_db + artwork = self.artwork + API = api.API(item) + + # If the item already exist in the local Kodi DB we'll perform a full item update + # If the item doesn't exist, we'll add it to the database + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + mvideoid = emby_dbitem[0] + fileid = emby_dbitem[1] + pathid = emby_dbitem[2] + self.logMsg("mvideoid: %s fileid: %s pathid: %s" % (mvideoid, fileid, pathid), 1) + + except TypeError: + update_item = False + self.logMsg("mvideoid: %s not found." % itemid, 2) + # mvideoid + kodicursor.execute("select coalesce(max(idMVideo),0) from musicvideo") + mvideoid = kodicursor.fetchone()[0] + 1 + + if not viewtag or not viewid: + # Get view tag from emby + viewtag, viewid, mediatype = self.emby.getView_embyId(itemid) + self.logMsg("View tag found: %s" % viewtag, 2) + + # fileId information + checksum = API.getChecksum() + dateadded = API.getDateCreated() + userdata = API.getUserData() + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + # item details + runtime = API.getRuntime() + plot = API.getOverview() + title = item['Name'] + year = item.get('ProductionYear') + genres = item['Genres'] + genre = " / ".join(genres) + studios = API.getStudios() + studio = " / ".join(studios) + artist = " / ".join(item.get('Artists')) + album = item.get('Album') + track = item.get('Track') + people = API.getPeople() + director = " / ".join(people['Director']) + + + ##### GET THE FILE AND PATH ##### + playurl = API.getFilePath() + + if "\\" in playurl: + # Local path + filename = playurl.rsplit("\\", 1)[1] + else: # Network share + filename = playurl.rsplit("/", 1)[1] + + if self.directpath: + # Direct paths is set the Kodi way + if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl): + # Validate the path is correct with user intervention + resp = xbmcgui.Dialog().yesno( + heading="Can't validate path", + line1=( + "Kodi can't locate file: %s. Verify the path. " + "You may to verify your network credentials in the " + "add-on settings or use the emby path substitution " + "to format your path correctly. Stop syncing?" + % playurl)) + if resp: + utils.window('emby_shouldStop', value="true") + return False + + path = playurl.replace(filename, "") + utils.window('emby_pathverified', value="true") + else: + # Set plugin path and media flags using real filename + path = "plugin://plugin.video.emby.musicvideos/" + params = { + + 'filename': filename.encode('utf-8'), + 'id': itemid, + 'dbid': mvideoid, + 'mode': "play" + } + filename = "%s?%s" % (path, urllib.urlencode(params)) + + + ##### UPDATE THE MUSIC VIDEO ##### + if update_item: + self.logMsg("UPDATE mvideo itemid: %s - Title: %s" % (itemid, title), 1) + + # Update path + query = "UPDATE path SET strPath = ? WHERE idPath = ?" + kodicursor.execute(query, (path, pathid)) + + # Update the filename + query = "UPDATE files SET strFilename = ?, dateAdded = ? WHERE idFile = ?" + kodicursor.execute(query, (filename, dateadded, fileid)) + + # Update the music video entry + query = ' '.join(( + + "UPDATE musicvideo", + "SET c00 = ?, c04 = ?, c05 = ?, c06 = ?, c07 = ?, c08 = ?, c09 = ?, c10 = ?,", + "c11 = ?, c12 = ?" + "WHERE idMVideo = ?" + )) + kodicursor.execute(query, (title, runtime, director, studio, year, plot, album, + artist, genre, track, mvideoid)) + + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE MUSIC VIDEO ##### + else: + self.logMsg("ADD mvideo itemid: %s - Title: %s" % (itemid, title), 1) + + # Add path + query = ' '.join(( + + "SELECT idPath", + "FROM path", + "WHERE strPath = ?" + )) + kodicursor.execute(query, (path,)) + try: + pathid = kodicursor.fetchone()[0] + except TypeError: + kodicursor.execute("select coalesce(max(idPath),0) from path") + pathid = kodicursor.fetchone()[0] + 1 + query = ( + ''' + INSERT OR REPLACE INTO path( + idPath, strPath, strContent, strScraper, noUpdate) + + VALUES (?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (pathid, path, "musicvideos", "metadata.local", 1)) + + # Add the file + kodicursor.execute("select coalesce(max(idFile),0) from files") + fileid = kodicursor.fetchone()[0] + 1 + query = ( + ''' + INSERT INTO files( + idFile, idPath, strFilename, dateAdded) + + VALUES (?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (fileid, pathid, filename, dateadded)) + + # Create the musicvideo entry + query = ( + ''' + INSERT INTO musicvideo( + idMVideo, idFile, c00, c04, c05, c06, c07, c08, c09, c10, c11, c12) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (mvideoid, fileid, title, runtime, director, studio, + year, plot, album, artist, genre, track)) + + # Create the reference in emby table + emby_db.addReference(itemid, mvideoid, "MusicVideo", "musicvideo", fileid, pathid, + checksum=checksum, mediafolderid=viewid) + + + # Process cast + people = item['People'] + artists = item['ArtistItems'] + for artist in artists: + artist['Type'] = "Artist" + people.extend(artists) + people = artwork.getPeopleArtwork(people) + kodi_db.addPeople(mvideoid, people, "musicvideo") + # Process genres + kodi_db.addGenres(mvideoid, genres, "musicvideo") + # Process artwork + artwork.addArtwork(artwork.getAllArtwork(item), mvideoid, "musicvideo", kodicursor) + # Process stream details + streams = API.getMediaStreams() + kodi_db.addStreams(fileid, streams, runtime) + # Process studios + kodi_db.addStudios(mvideoid, studios, "musicvideo") + # Process tags: view, emby tags + tags = [viewtag] + tags.extend(item['Tags']) + if userdata['Favorite']: + tags.append("Favorite musicvideos") + kodi_db.addTags(mvideoid, tags, "musicvideo") + # Process playstates + resume = API.adjustResume(userdata['Resume']) + total = round(float(runtime), 6) + kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + + def updateUserdata(self, item): + # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + # Poster with progress bar + emby_db = self.emby_db + kodi_db = self.kodi_db + API = api.API(item) + + # Get emby information + itemid = item['Id'] + checksum = API.getChecksum() + userdata = API.getUserData() + runtime = API.getRuntime() + + # Get Kodi information + emby_dbitem = emby_db.getItem_byId(itemid) + try: + mvideoid = emby_dbitem[0] + fileid = emby_dbitem[1] + self.logMsg( + "Update playstate for musicvideo: %s fileid: %s" + % (item['Name'], fileid), 1) + except TypeError: + return + + # Process favorite tags + if userdata['Favorite']: + kodi_db.addTag(mvideoid, "Favorite musicvideos", "musicvideo") + else: + kodi_db.removeTag(mvideoid, "Favorite musicvideos", "musicvideo") + + # Process playstates + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + resume = API.adjustResume(userdata['Resume']) + total = round(float(runtime), 6) + + kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + emby_db.updateReference(itemid, checksum) + + def remove(self, itemid): + # Remove mvideoid, fileid, pathid, emby reference + emby_db = self.emby_db + kodicursor = self.kodicursor + artwork = self.artwork + + emby_dbitem = emby_db.getItem_byId(itemid) + try: + mvideoid = emby_dbitem[0] + fileid = emby_dbitem[1] + pathid = emby_dbitem[2] + self.logMsg("Removing mvideoid: %s fileid: %s" % (mvideoid, fileid, pathid), 1) + except TypeError: + return + + # Remove artwork + query = ' '.join(( + + "SELECT url, type", + "FROM art", + "WHERE media_id = ?", + "AND media_type = 'musicvideo'" + )) + kodicursor.execute(query, (mvideoid,)) + rows = kodicursor.fetchall() + for row in rows: + + url = row[0] + imagetype = row[1] + if imagetype in ("poster", "fanart"): + artwork.deleteCachedArtwork(url) + + kodicursor.execute("DELETE FROM musicvideo WHERE idMVideo = ?", (mvideoid,)) + kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) + if self.directpath: + kodicursor.execute("DELETE FROM path WHERE idPath = ?", (pathid,)) + self.embycursor.execute("DELETE FROM emby WHERE emby_id = ?", (itemid,)) + + self.logMsg("Deleted musicvideo %s from kodi database" % itemid, 1) + +class TVShows(Items): + + + def __init__(self, embycursor, kodicursor): + Items.__init__(self, embycursor, kodicursor) + + def added(self, items, pdialog): + + total = len(items) + count = 0 + for tvshow in items: + + title = tvshow['Name'] + if pdialog: + percentage = int((float(count) / float(total))*100) + pdialog.update(percentage, message=title) + count += 1 + self.add_update(tvshow) + # Add episodes + all_episodes = self.emby.getEpisodesbyShow(tvshow['Id']) + self.added_episode(all_episodes['Items'], pdialog) + + def added_season(self, items, pdialog): + + total = len(items) + count = 0 + for season in items: + + title = "%s - %s" % (season.get('SeriesName', "Unknown"), season['Name']) + if pdialog: + percentage = int((float(count) / float(total))*100) + pdialog.update(percentage, message=title) + count += 1 + self.add_updateSeason(season) + # Add episodes + all_episodes = self.emby.getEpisodesbySeason(season['Id']) + self.added_episode(all_episodes['Items'], pdialog) + + def added_episode(self, items, pdialog): + + total = len(items) + count = 0 + for episode in items: + title = "%s - %s" % (episode.get('SeriesName', "Unknown"), episode['Name']) + if pdialog: + percentage = int((float(count) / float(total))*100) + pdialog.update(percentage, message=title) + count += 1 + self.add_updateEpisode(episode) + if not pdialog and self.contentmsg: + self.contentPop(title) + + + def add_update(self, item, viewtag=None, viewid=None): + # Process single tvshow + kodicursor = self.kodicursor + emby = self.emby + emby_db = self.emby_db + kodi_db = self.kodi_db + artwork = self.artwork + API = api.API(item) + + if utils.settings('syncEmptyShows') == "false" and not item['RecursiveItemCount']: + self.logMsg("Skipping empty show: %s" % item['Name'], 1) + return + # If the item already exist in the local Kodi DB we'll perform a full item update + # If the item doesn't exist, we'll add it to the database + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + showid = emby_dbitem[0] + pathid = emby_dbitem[2] + self.logMsg("showid: %s pathid: %s" % (showid, pathid), 1) + + except TypeError: + update_item = False + self.logMsg("showid: %s not found." % itemid, 2) + + if viewtag is None or viewid is None: + # Get view tag from emby + viewtag, viewid, mediatype = emby.getView_embyId(itemid) + self.logMsg("View tag found: %s" % viewtag, 2) + + # fileId information + checksum = API.getChecksum() + dateadded = API.getDateCreated() + userdata = API.getUserData() + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + # item details + genres = item['Genres'] + title = item['Name'] + plot = API.getOverview() + rating = item.get('CommunityRating') + premieredate = API.getPremiereDate() + tvdb = API.getProvider('Tvdb') + sorttitle = item['SortName'] + mpaa = API.getMpaa() + genre = " / ".join(genres) + studios = API.getStudios() + studio = " / ".join(studios) + + + ##### GET THE FILE AND PATH ##### + playurl = API.getFilePath() + + if self.directpath: + # Direct paths is set the Kodi way + if "\\" in playurl: + # Local path + path = "%s\\" % playurl + toplevelpath = "%s\\" % dirname(dirname(path)) + else: + # Network path + path = "%s/" % playurl + toplevelpath = "%s/" % dirname(dirname(path)) + + if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(path): + # Validate the path is correct with user intervention + resp = xbmcgui.Dialog().yesno( + heading="Can't validate path", + line1=( + "Kodi can't locate file: %s. Verify the path. " + "You may to verify your network credentials in the " + "add-on settings or use the emby path substitution " + "to format your path correctly. Stop syncing?" + % playurl)) + if resp: + utils.window('emby_shouldStop', value="true") + return False + + utils.window('emby_pathverified', value="true") + else: + # Set plugin path + toplevelpath = "plugin://plugin.video.emby.tvshows/" + path = "%s%s/" % (toplevelpath, itemid) + + + ##### UPDATE THE TVSHOW ##### + if update_item: + self.logMsg("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1) + + # Update the tvshow entry + query = ' '.join(( + + "UPDATE tvshow", + "SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?,", + "c12 = ?, c13 = ?, c14 = ?, c15 = ?", + "WHERE idShow = ?" + )) + kodicursor.execute(query, (title, plot, rating, premieredate, genre, title, + tvdb, mpaa, studio, sorttitle, showid)) + + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE TVSHOW ##### + else: + self.logMsg("ADD tvshow itemid: %s - Title: %s" % (itemid, title), 1) + + # Add top path + toppathid = kodi_db.addPath(toplevelpath) + query = ' '.join(( + + "UPDATE path", + "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", + "WHERE idPath = ?" + )) + kodicursor.execute(query, (toplevelpath, "tvshows", "metadata.local", 1, toppathid)) + + # Add path + pathid = kodi_db.addPath(path) + + # Create the tvshow entry + kodicursor.execute("select coalesce(max(idShow),0) from tvshow") + showid = kodicursor.fetchone()[0] + 1 + query = ( + ''' + INSERT INTO tvshow( + idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, c15) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (showid, title, plot, rating, premieredate, genre, + title, tvdb, mpaa, studio, sorttitle)) + + # Link the path + query = "INSERT INTO tvshowlinkpath(idShow, idPath) values(?, ?)" + kodicursor.execute(query, (showid, pathid)) + + # Create the reference in emby table + emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid, + checksum=checksum, mediafolderid=viewid) + + # Update the path + query = ' '.join(( + + "UPDATE path", + "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", + "WHERE idPath = ?" + )) + kodicursor.execute(query, (path, None, None, 1, pathid)) + + # Process cast + people = artwork.getPeopleArtwork(item['People']) + kodi_db.addPeople(showid, people, "tvshow") + # Process genres + kodi_db.addGenres(showid, genres, "tvshow") + # Process artwork + artwork.addArtwork(artwork.getAllArtwork(item), showid, "tvshow", kodicursor) + # Process studios + kodi_db.addStudios(showid, studios, "tvshow") + # Process tags: view, emby tags + tags = [viewtag] + tags.extend(item['Tags']) + if userdata['Favorite']: + tags.append("Favorite tvshows") + kodi_db.addTags(showid, tags, "tvshow") + # Process seasons + all_seasons = emby.getSeasons(itemid) + for season in all_seasons['Items']: + self.add_updateSeason(season, showid=showid) + else: + # Finally, refresh the all season entry + seasonid = kodi_db.addSeason(showid, -1) + # Process artwork + artwork.addArtwork(artwork.getAllArtwork(item), seasonid, "season", kodicursor) + + def add_updateSeason(self, item, showid=None): + + kodicursor = self.kodicursor + emby_db = self.emby_db + kodi_db = self.kodi_db + artwork = self.artwork + + if item['LocationType'] == "Virtual": + self.logMsg("Skipping virtual season.") + return + + seasonnum = item.get('IndexNumber', 1) + itemid = item['Id'] + + if showid is None: + try: + seriesId = item['SeriesId'] + showid = emby_db.getItem_byId(seriesId)[0] + except KeyError: + return + except TypeError: + # Show is missing, update show instead. + show = self.emby.getItem(seriesId) + self.add_update(show) + return + + seasonid = kodi_db.addSeason(showid, seasonnum) + # Create the reference in emby table + emby_db.addReference(itemid, seasonid, "Season", "season", parentid=showid) + + # Process artwork + artwork.addArtwork(artwork.getAllArtwork(item), seasonid, "season", kodicursor) + + def add_updateEpisode(self, item): + # Process single episode + kodiversion = self.kodiversion + kodicursor = self.kodicursor + emby_db = self.emby_db + kodi_db = self.kodi_db + artwork = self.artwork + API = api.API(item) + + # If the item already exist in the local Kodi DB we'll perform a full item update + # If the item doesn't exist, we'll add it to the database + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + episodeid = emby_dbitem[0] + fileid = emby_dbitem[1] + pathid = emby_dbitem[2] + self.logMsg("episodeid: %s fileid: %s pathid: %s" % (episodeid, fileid, pathid), 1) + + except TypeError: + update_item = False + self.logMsg("episodeid: %s not found." % itemid, 2) + # episodeid + kodicursor.execute("select coalesce(max(idEpisode),0) from episode") + episodeid = kodicursor.fetchone()[0] + 1 + + # fileId information + checksum = API.getChecksum() + dateadded = API.getDateCreated() + userdata = API.getUserData() + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + # item details + people = API.getPeople() + writer = " / ".join(people['Writer']) + director = " / ".join(people['Director']) + title = item['Name'] + plot = API.getOverview() + rating = item.get('CommunityRating') + runtime = API.getRuntime() + premieredate = API.getPremiereDate() + + # episode details + seriesId = item['SeriesId'] + seriesName = item['SeriesName'] + season = item.get('ParentIndexNumber') + episode = item.get('IndexNumber', -1) + + if season is None: + if item.get('AbsoluteEpisodeNumber'): + # Anime scenario + season = 1 + episode = item['AbsoluteEpisodeNumber'] + else: + season = -1 + + # Specials ordering within season + airsBeforeSeason = item.get('AirsBeforeSeasonNumber', "-1") + airsBeforeEpisode = item.get('AirsBeforeEpisodeNumber', "-1") + + # Append multi episodes to title + if item.get('IndexNumberEnd'): + title = "| %02d | %s" % (item['IndexNumberEnd'], title) + + # Get season id + show = emby_db.getItem_byId(seriesId) + try: + showid = show[0] + except TypeError: + # Show is missing from database + show = self.emby.getItem(seriesId) + self.add_update(show) + show = emby_db.getItem_byId(seriesId) + try: + showid = show[0] + except TypeError: + self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId)) + return False + + seasonid = kodi_db.addSeason(showid, season) + + + ##### GET THE FILE AND PATH ##### + playurl = API.getFilePath() + + if "\\" in playurl: + # Local path + filename = playurl.rsplit("\\", 1)[1] + else: # Network share + filename = playurl.rsplit("/", 1)[1] + + if self.directpath: + # Direct paths is set the Kodi way + if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl): + # Validate the path is correct with user intervention + resp = xbmcgui.Dialog().yesno( + heading="Can't validate path", + line1=( + "Kodi can't locate file: %s. Verify the path. " + "You may to verify your network credentials in the " + "add-on settings or use the emby path substitution " + "to format your path correctly. Stop syncing?" + % playurl)) + if resp: + utils.window('emby_shouldStop', value="true") + return False + + path = playurl.replace(filename, "") + utils.window('emby_pathverified', value="true") + else: + # Set plugin path and media flags using real filename + path = "plugin://plugin.video.emby.tvshows/%s/" % seriesId + params = { + + 'filename': filename.encode('utf-8'), + 'id': itemid, + 'dbid': episodeid, + 'mode': "play" + } + filename = "%s?%s" % (path, urllib.urlencode(params)) + + + ##### UPDATE THE EPISODE ##### + if update_item: + self.logMsg("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1) + + # Update the movie entry + if kodiversion == 16: + # Kodi Jarvis + query = ' '.join(( + + "UPDATE episode", + "SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,", + "c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, idSeason = ?", + "WHERE idEpisode = ?" + )) + kodicursor.execute(query, (title, plot, rating, writer, premieredate, + runtime, director, season, episode, title, airsBeforeSeason, + airsBeforeEpisode, seasonid, episodeid)) + else: + query = ' '.join(( + + "UPDATE episode", + "SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, c10 = ?,", + "c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?", + "WHERE idEpisode = ?" + )) + kodicursor.execute(query, (title, plot, rating, writer, premieredate, + runtime, director, season, episode, title, airsBeforeSeason, + airsBeforeEpisode, episodeid)) + + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + # Update parentid reference + emby_db.updateParentId(itemid, seasonid) + + ##### OR ADD THE EPISODE ##### + else: + self.logMsg("ADD episode itemid: %s - Title: %s" % (itemid, title), 1) + + # Add path + pathid = kodi_db.addPath(path) + # Add the file + fileid = kodi_db.addFile(filename, pathid) + + # Create the episode entry + if kodiversion == 16: + # Kodi Jarvis + query = ( + ''' + INSERT INTO episode( + idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14, + idShow, c15, c16, idSeason) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (episodeid, fileid, title, plot, rating, writer, + premieredate, runtime, director, season, episode, title, showid, + airsBeforeSeason, airsBeforeEpisode, seasonid)) + else: + query = ( + ''' + INSERT INTO episode( + idEpisode, idFile, c00, c01, c03, c04, c05, c09, c10, c12, c13, c14, + idShow, c15, c16) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (episodeid, fileid, title, plot, rating, writer, + premieredate, runtime, director, season, episode, title, showid, + airsBeforeSeason, airsBeforeEpisode)) + + # Create the reference in emby table + emby_db.addReference(itemid, episodeid, "Episode", "episode", fileid, pathid, + seasonid, checksum) + + # Update the path + query = ' '.join(( + + "UPDATE path", + "SET strPath = ?, strContent = ?, strScraper = ?, noUpdate = ?", + "WHERE idPath = ?" + )) + kodicursor.execute(query, (path, None, None, 1, pathid)) + + # Update the file + query = ' '.join(( + + "UPDATE files", + "SET idPath = ?, strFilename = ?, dateAdded = ?", + "WHERE idFile = ?" + )) + kodicursor.execute(query, (pathid, filename, dateadded, fileid)) + + # Process cast + people = artwork.getPeopleArtwork(item['People']) + kodi_db.addPeople(episodeid, people, "episode") + # Process artwork + artworks = artwork.getAllArtwork(item) + artwork.addOrUpdateArt(artworks['Primary'], episodeid, "episode", "thumb", kodicursor) + # Process stream details + streams = API.getMediaStreams() + kodi_db.addStreams(fileid, streams, runtime) + # Process playstates + resume = API.adjustResume(userdata['Resume']) + total = round(float(runtime), 6) + kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + if not self.directpath and resume: + # Create additional entry for widgets. This is only required for plugin/episode. + temppathid = kodi_db.getPath("plugin://plugin.video.emby.tvshows/") + tempfileid = kodi_db.addFile(filename, temppathid) + query = ' '.join(( + + "UPDATE files", + "SET idPath = ?, strFilename = ?, dateAdded = ?", + "WHERE idFile = ?" + )) + kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid)) + kodi_db.addPlaystate(tempfileid, resume, total, playcount, dateplayed) + + def updateUserdata(self, item): + # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + # Poster with progress bar + emby_db = self.emby_db + kodi_db = self.kodi_db + API = api.API(item) + + # Get emby information + itemid = item['Id'] + checksum = API.getChecksum() + userdata = API.getUserData() + runtime = API.getRuntime() + + # Get Kodi information + emby_dbitem = emby_db.getItem_byId(itemid) + try: + kodiid = emby_dbitem[0] + fileid = emby_dbitem[1] + mediatype = emby_dbitem[4] + self.logMsg( + "Update playstate for %s: %s fileid: %s" + % (mediatype, item['Name'], fileid), 1) + except TypeError: + return + + # Process favorite tags + if mediatype == "tvshow": + if userdata['Favorite']: + kodi_db.addTag(kodiid, "Favorite tvshows", "tvshow") + else: + kodi_db.removeTag(kodiid, "Favorite tvshows", "tvshow") + + # Process playstates + if mediatype == "episode": + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + resume = API.adjustResume(userdata['Resume']) + total = round(float(runtime), 6) + + kodi_db.addPlaystate(fileid, resume, total, playcount, dateplayed) + if not self.directpath and not resume: + # Make sure there's no other bookmarks created by widget. + filename = kodi_db.getFile(fileid) + kodi_db.removeFile("plugin://plugin.video.emby.tvshows/", filename) + emby_db.updateReference(itemid, checksum) + + def remove(self, itemid): + # Remove showid, fileid, pathid, emby reference + emby_db = self.emby_db + embycursor = self.embycursor + kodicursor = self.kodicursor + artwork = self.artwork + + emby_dbitem = emby_db.getItem_byId(itemid) + try: + kodiid = emby_dbitem[0] + fileid = emby_dbitem[1] + pathid = emby_dbitem[2] + parentid = emby_dbitem[3] + mediatype = emby_dbitem[4] + self.logMsg("Removing %s kodiid: %s fileid: %s" % (mediatype, kodiid, fileid), 1) + except TypeError: + return + + ##### PROCESS ITEM ##### + + # Remove the emby reference + emby_db.removeItem(itemid) + + + ##### IF EPISODE ##### + + if mediatype == "episode": + # Delete kodi episode and file, verify season and tvshow + self.removeEpisode(kodiid, fileid) + + # Season verification + season = emby_db.getItem_byKodiId(parentid, "season") + try: + showid = season[1] + except TypeError: + return + + season_episodes = emby_db.getItem_byParentId(parentid, "episode") + if not season_episodes: + self.removeSeason(parentid) + emby_db.removeItem(season[0]) + + # Show verification + show = emby_db.getItem_byKodiId(showid, "tvshow") + query = ' '.join(( + + "SELECT totalCount", + "FROM tvshowcounts", + "WHERE idShow = ?" + )) + kodicursor.execute(query, (showid,)) + result = kodicursor.fetchone() + if result and result[0] is None: + # There's no episodes left, delete show and any possible remaining seasons + seasons = emby_db.getItem_byParentId(showid, "season") + for season in seasons: + self.removeSeason(season[1]) + else: + # Delete emby season entries + emby_db.removeItems_byParentId(showid, "season") + self.removeShow(showid) + emby_db.removeItem(show[0]) + + ##### IF TVSHOW ##### + + elif mediatype == "tvshow": + # Remove episodes, seasons, tvshow + seasons = emby_db.getItem_byParentId(kodiid, "season") + for season in seasons: + seasonid = season[1] + season_episodes = emby_db.getItem_byParentId(seasonid, "episode") + for episode in season_episodes: + self.removeEpisode(episode[1], episode[2]) + else: + # Remove emby episodes + emby_db.removeItems_byParentId(seasonid, "episode") + else: + # Remove emby seasons + emby_db.removeItems_byParentId(kodiid, "season") + + # Remove tvshow + self.removeShow(kodiid) + + ##### IF SEASON ##### + + elif mediatype == "season": + # Remove episodes, season, verify tvshow + season_episodes = emby_db.getItem_byParentId(kodiid, "episode") + for episode in season_episodes: + self.removeEpisode(episode[1], episode[2]) + else: + # Remove emby episodes + emby_db.removeItems_byParentId(kodiid, "episode") + + # Remove season + self.removeSeason(kodiid) + + # Show verification + seasons = emby_db.getItem_byParentId(parentid, "season") + if not seasons: + # There's no seasons, delete the show + self.removeShow(parentid) + emby_db.removeItem_byKodiId(parentid, "tvshow") + + self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) + + def removeShow(self, kodiid): + + kodicursor = self.kodicursor + artwork = self.artwork + + artwork.deleteArtwork(kodiid, "tvshow", kodicursor) + kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", (kodiid,)) + self.logMsg("Removed tvshow: %s." % kodiid, 2) + + def removeSeason(self, kodiid): + + kodicursor = self.kodicursor + artwork = self.artwork + + artwork.deleteArtwork(kodiid, "season", kodicursor) + kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", (kodiid,)) + self.logMsg("Removed season: %s." % kodiid, 2) + + def removeEpisode(self, kodiid, fileid): + + kodicursor = self.kodicursor + artwork = self.artwork + + artwork.deleteArtwork(kodiid, "episode", kodicursor) + kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", (kodiid,)) + kodicursor.execute("DELETE FROM files WHERE idFile = ?", (fileid,)) + self.logMsg("Removed episode: %s." % kodiid, 2) + +class Music(Items): + + + def __init__(self, embycursor, kodicursor): + + Items.__init__(self, embycursor, kodicursor) + + self.directstream = utils.settings('streamMusic') == "true" + self.userid = utils.window('emby_currUser') + self.server = utils.window('emby_server%s' % self.userid) + + def added(self, items, pdialog): + + for artist in items: + + title = artist['Name'] + if pdialog: + percentage = int((float(count) / float(total))*100) + pdialog.update(percentage, message=title) + count += 1 + self.add_updateArtist(artist) + # Add albums + all_albums = self.emby.getAlbumsbyArtist(artist['Id']) + self.added_album(all_albums['Items'], pdialog) + + def added_album(self, items, pdialog): + + for album in items: + + title = album['Name'] + if pdialog: + percentage = int((float(count) / float(total))*100) + pdialog.update(percentage, message=title) + count += 1 + self.add_updateAlbum(album) + # Add songs + all_songs = self.emby.getSongsbyAlbum(album['Id']) + self.added_song(all_songs['Items'], pdialog) + + def added_song(self, items, pdialog): + + for song in items: + + title = song['Name'] + if pdialog: + percentage = int((float(count) / float(total))*100) + pdialog.update(percentage, message=title) + count += 1 + self.add_updateSong(song) + if not pdialog and self.contentmsg: + self.contentPop(title) + + + def add_updateArtist(self, item, artisttype="MusicArtist"): + # Process a single artist + kodiversion = self.kodiversion + kodicursor = self.kodicursor + emby_db = self.emby_db + kodi_db = self.kodi_db + artwork = self.artwork + API = api.API(item) + + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + artistid = emby_dbitem[0] + except TypeError: + update_item = False + self.logMsg("artistid: %s not found." % itemid, 2) + + ##### The artist details ##### + lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + dateadded = API.getDateCreated() + checksum = API.getChecksum() + + name = item['Name'] + musicBrainzId = API.getProvider('MusicBrainzArtist') + genres = " / ".join(item.get('Genres')) + bio = API.getOverview() + + # Associate artwork + artworks = artwork.getAllArtwork(item, parentInfo=True) + thumb = artworks['Primary'] + backdrops = artworks['Backdrop'] # List + + if thumb: + thumb = "%s" % thumb + if backdrops: + fanart = "%s" % backdrops[0] + else: + fanart = "" + + + ##### UPDATE THE ARTIST ##### + if update_item: + self.logMsg("UPDATE artist itemid: %s - Name: %s" % (itemid, name), 1) + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE ARTIST ##### + else: + self.logMsg("ADD artist itemid: %s - Name: %s" % (itemid, name), 1) + # safety checks: It looks like Emby supports the same artist multiple times. + # Kodi doesn't allow that. In case that happens we just merge the artist entries. + artistid = kodi_db.addArtist(name, musicBrainzId) + # Create the reference in emby table + emby_db.addReference(itemid, artistid, artisttype, "artist", checksum=checksum) + + + # Process the artist + if kodiversion == 16: + query = ' '.join(( + + "UPDATE artist", + "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,", + "lastScraped = ?", + "WHERE idArtist = ?" + )) + kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped, artistid)) + else: + query = ' '.join(( + + "UPDATE artist", + "SET strGenres = ?, strBiography = ?, strImage = ?, strFanart = ?,", + "lastScraped = ?, dateAdded = ?", + "WHERE idArtist = ?" + )) + kodicursor.execute(query, (genres, bio, thumb, fanart, lastScraped, + dateadded, artistid)) + + + # Update artwork + artwork.addArtwork(artworks, artistid, "artist", kodicursor) + + def add_updateAlbum(self, item): + # Process a single artist + emby = self.emby + kodiversion = self.kodiversion + kodicursor = self.kodicursor + emby_db = self.emby_db + kodi_db = self.kodi_db + artwork = self.artwork + API = api.API(item) + + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + albumid = emby_dbitem[0] + except TypeError: + update_item = False + self.logMsg("albumid: %s not found." % itemid, 2) + + ##### The album details ##### + lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + dateadded = API.getDateCreated() + userdata = API.getUserData() + checksum = API.getChecksum() + + name = item['Name'] + musicBrainzId = API.getProvider('MusicBrainzAlbum') + year = item.get('ProductionYear') + genres = item.get('Genres') + genre = " / ".join(genres) + bio = API.getOverview() + rating = userdata['Rating'] + artists = item['AlbumArtists'] + if not artists: + artists = item['ArtistItems'] + artistname = [] + for artist in artists: + artistname.append(artist['Name']) + artistname = " / ".join(artistname) + + # Associate artwork + artworks = artwork.getAllArtwork(item, parentInfo=True) + thumb = artworks['Primary'] + if thumb: + thumb = "%s" % thumb + + ##### UPDATE THE ALBUM ##### + if update_item: + self.logMsg("UPDATE album itemid: %s - Name: %s" % (itemid, name), 1) + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE ALBUM ##### + else: + self.logMsg("ADD album itemid: %s - Name: %s" % (itemid, name), 1) + # safety checks: It looks like Emby supports the same artist multiple times. + # Kodi doesn't allow that. In case that happens we just merge the artist entries. + albumid = kodi_db.addAlbum(name, musicBrainzId) + # Create the reference in emby table + emby_db.addReference(itemid, albumid, "MusicAlbum", "album", checksum=checksum) + + + # Process the album info + if kodiversion == 16: + # Kodi Jarvis + query = ' '.join(( + + "UPDATE album", + "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", + "iRating = ?, lastScraped = ?, strReleaseType = ?", + "WHERE idAlbum = ?" + )) + kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped, + "album", albumid)) + elif kodiversion == 15: + # Kodi Isengard + query = ' '.join(( + + "UPDATE album", + "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", + "iRating = ?, lastScraped = ?, dateAdded = ?, strReleaseType = ?", + "WHERE idAlbum = ?" + )) + kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped, + dateadded, "album", albumid)) + else: + # Kodi Helix + query = ' '.join(( + + "UPDATE album", + "SET strArtists = ?, iYear = ?, strGenres = ?, strReview = ?, strImage = ?,", + "iRating = ?, lastScraped = ?, dateAdded = ?", + "WHERE idAlbum = ?" + )) + kodicursor.execute(query, (artistname, year, genre, bio, thumb, rating, lastScraped, + dateadded, albumid)) + + # Associate the parentid for emby reference + parentId = item.get('ParentId') + if parentId is not None: + emby_dbartist = emby_db.getItem_byId(parentId) + try: + artistid = emby_dbartist[0] + except TypeError: + # Artist does not exist in emby database. + artist = emby.getItem(parentId) + # Item may not be an artist, verification necessary. + if artist['Type'] == "MusicArtist": + # Update with the parentId, for remove reference + emby_db.addReference(parentId, parentId, "MusicArtist", "artist") + emby_db.updateParentId(itemid, parentId) + else: + # Update emby reference with the artistid + emby_db.updateParentId(itemid, artistid) + + # Assign main artists to album + for artist in artists: + artistname = artist['Name'] + artistId = artist['Id'] + emby_dbartist = emby_db.getItem_byId(artistId) + try: + artistid = emby_dbartist[0] + except TypeError: + # Artist does not exist in emby database, create the reference + artist = emby.getItem(artistId) + self.add_updateArtist(artist, artisttype="AlbumArtist") + emby_dbartist = emby_db.getItem_byId(artistId) + artistid = emby_dbartist[0] + else: + # Best take this name over anything else. + query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?" + kodicursor.execute(query, (artistname, artistid,)) + + # Add artist to album + query = ( + ''' + INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) + + VALUES (?, ?, ?) + ''' + ) + kodicursor.execute(query, (artistid, albumid, artistname)) + # Update discography + query = ( + ''' + INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) + + VALUES (?, ?, ?) + ''' + ) + kodicursor.execute(query, (artistid, name, year)) + # Update emby reference with parentid + emby_db.updateParentId(artistId, albumid) + + # Add genres + kodi_db.addMusicGenres(albumid, genres, "album") + # Update artwork + artwork.addArtwork(artworks, albumid, "album", kodicursor) + + def add_updateSong(self, item): + # Process single song + kodiversion = self.kodiversion + kodicursor = self.kodicursor + emby_db = self.emby_db + kodi_db = self.kodi_db + artwork = self.artwork + API = api.API(item) + + update_item = True + itemid = item['Id'] + emby_dbitem = emby_db.getItem_byId(itemid) + try: + songid = emby_dbitem[0] + pathid = emby_dbitem[2] + albumid = emby_dbitem[3] + except TypeError: + update_item = False + self.logMsg("songid: %s not found." % itemid, 2) + + ##### The song details ##### + checksum = API.getChecksum() + dateadded = API.getDateCreated() + userdata = API.getUserData() + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + # item details + title = item['Name'] + musicBrainzId = API.getProvider('MusicBrainzTrackId') + genres = item.get('Genres') + genre = " / ".join(genres) + artists = " / ".join(item['Artists']) + tracknumber = item.get('IndexNumber', 0) + disc = item.get('ParentIndexNumber', 1) + if disc == 1: + track = tracknumber + else: + track = disc*2**16 + tracknumber + year = item.get('ProductionYear') + bio = API.getOverview() + duration = API.getRuntime() + rating = userdata['Rating'] + + + ##### GET THE FILE AND PATH ##### + if self.directstream: + path = "%s/emby/Audio/%s/" % (self.server, itemid) + filename = "stream.mp3" + else: + playurl = API.getFilePath() + + if "\\" in playurl: + # Local path + filename = playurl.rsplit("\\", 1)[1] + else: # Network share + filename = playurl.rsplit("/", 1)[1] + + # Direct paths is set the Kodi way + if utils.window('emby_pathverified') != "true" and not xbmcvfs.exists(playurl): + # Validate the path is correct with user intervention + utils.window('emby_directPath', clear=True) + resp = xbmcgui.Dialog().yesno( + heading="Can't validate path", + line1=( + "Kodi can't locate file: %s. Verify the path. " + "You may to verify your network credentials in the " + "add-on settings or use the emby path substitution " + "to format your path correctly. Stop syncing?" + % playurl)) + if resp: + utils.window('emby_shouldStop', value="true") + return False + + path = playurl.replace(filename, "") + utils.window('emby_pathverified', value="true") + + ##### UPDATE THE SONG ##### + if update_item: + self.logMsg("UPDATE song itemid: %s - Title: %s" % (itemid, title), 1) + + # Update path + query = "UPDATE path SET strPath = ? WHERE idPath = ?" + kodicursor.execute(query, (path, pathid)) + + # Update the song entry + query = ' '.join(( + + "UPDATE song", + "SET idAlbum = ?, strArtists = ?, strGenres = ?, strTitle = ?, iTrack = ?,", + "iDuration = ?, iYear = ?, strFilename = ?, iTimesPlayed = ?, lastplayed = ?,", + "rating = ?", + "WHERE idSong = ?" + )) + kodicursor.execute(query, (albumid, artists, genre, title, track, duration, year, + filename, playcount, dateplayed, rating, songid)) + + # Update the checksum in emby table + emby_db.updateReference(itemid, checksum) + + ##### OR ADD THE SONG ##### + else: + self.logMsg("ADD song itemid: %s - Title: %s" % (itemid, title), 1) + + # Add path + pathid = kodi_db.addPath(path) + + # Get the album + emby_dbalbum = emby_db.getItem_byId(item['AlbumId']) + try: + albumid = emby_dbalbum[0] + except TypeError: + # No album found, create a single's album + kodicursor.execute("select coalesce(max(idAlbum),0) from album") + albumid = kodicursor.fetchone()[0] + 1 + if kodiversion == 16: + # Kodi Jarvis + query = ( + ''' + INSERT INTO album(idAlbum, strGenres, iYear, strReleaseType) + + VALUES (?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (albumid, genre, year, "single")) + elif kodiversion == 15: + # Kodi Isengard + query = ( + ''' + INSERT INTO album(idAlbum, strGenres, iYear, dateAdded, strReleaseType) + + VALUES (?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (albumid, genre, year, dateadded, "single")) + else: + # Kodi Helix + query = ( + ''' + INSERT INTO album(idAlbum, strGenres, iYear, dateAdded) + + VALUES (?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (albumid, genre, year, dateadded)) + + # Create the song entry + kodicursor.execute("select coalesce(max(idSong),0) from song") + songid = kodicursor.fetchone()[0] + 1 + query = ( + ''' + INSERT INTO song( + idSong, idAlbum, idPath, strArtists, strGenres, strTitle, iTrack, + iDuration, iYear, strFileName, strMusicBrainzTrackID, iTimesPlayed, lastplayed, + rating) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (songid, albumid, pathid, artists, genre, title, track, + duration, year, filename, musicBrainzId, playcount, dateplayed, rating)) + + # Create the reference in emby table + emby_db.addReference(itemid, songid, "Audio", "song", pathid=pathid, parentid=albumid, + checksum=checksum) + + + # Link song to album + query = ( + ''' + INSERT OR REPLACE INTO albuminfosong( + idAlbumInfoSong, idAlbumInfo, iTrack, strTitle, iDuration) + + VALUES (?, ?, ?, ?, ?) + ''' + ) + kodicursor.execute(query, (songid, albumid, track, title, duration)) + + # Verify if album has artists + addArtist = False + query = ' '.join(( + + "SELECT strArtists", + "FROM album", + "WHERE idAlbum = ?" + )) + kodicursor.execute(query, (albumid,)) + result = kodicursor.fetchone() + if result and result[0] == "": + addArtist = True + + if item['AlbumArtists']: + album_artists = item['AlbumArtists'] + else: + album_artists = item['ArtistItems'] + + # Link song to artist + artists_name = [] + for artist in album_artists: + artist_name = artist['Name'] + artists_name.append(artist_name) + emby_dbartist = emby_db.getItem_byId(artist['Id']) + try: + artistid = emby_dbartist[0] + except: pass + else: + query = ( + ''' + INSERT OR REPLACE INTO song_artist(idArtist, idSong, strArtist) + + VALUES (?, ?, ?) + ''' + ) + kodicursor.execute(query, (artistid, songid, artist_name)) + + if addArtist: + query = ( + ''' + INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) + + VALUES (?, ?, ?) + ''' + ) + kodicursor.execute(query, (artistid, albumid, artist_name)) + else: + if addArtist: + artists_onalbum = " / ".join(artists_name) + if kodiversion == 16: + # Kodi Jarvis + query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" + kodicursor.execute(query, (artists_onalbum, albumid)) + elif kodiversion == 15: + # Kodi Isengard + query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" + kodicursor.execute(query, (artists_onalbum, albumid)) + else: + # Kodi Helix + query = "UPDATE album SET strArtists = ? WHERE idAlbum = ?" + kodicursor.execute(query, (artists_onalbum, albumid)) + + # Add genres + kodi_db.addMusicGenres(songid, genres, "song") + # Update artwork + artwork.addArtwork(artwork.getAllArtwork(item, parentInfo=True), songid, "song", kodicursor) + + def updateUserdata(self, item): + # This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks + # Poster with progress bar + kodicursor = self.kodicursor + emby_db = self.emby_db + kodi_db = self.kodi_db + API = api.API(item) + + # Get emby information + itemid = item['Id'] + checksum = API.getChecksum() + userdata = API.getUserData() + runtime = API.getRuntime() + rating = userdata['Rating'] + + # Get Kodi information + emby_dbitem = emby_db.getItem_byId(itemid) + try: + kodiid = emby_dbitem[0] + mediatype = emby_dbitem[4] + self.logMsg("Update playstate for %s: %s" % (mediatype, item['Name']), 1) + except TypeError: + return + + if mediatype == "song": + # Process playstates + playcount = userdata['PlayCount'] + dateplayed = userdata['LastPlayedDate'] + + query = "UPDATE song SET iTimesPlayed = ?, lastplayed = ?, rating = ? WHERE idSong = ?" + kodicursor.execute(query, (playcount, dateplayed, rating, kodiid)) + + elif mediatype == "album": + # Process playstates + query = "UPDATE album SET iRating = ? WHERE idAlbum = ?" + kodicursor.execute(query, (rating, kodiid)) + + emby_db.updateReference(itemid, checksum) + + def remove(self, itemid): + # Remove kodiid, fileid, pathid, emby reference + emby_db = self.emby_db + kodicursor = self.kodicursor + artwork = self.artwork + + emby_dbitem = emby_db.getItem_byId(itemid) + try: + kodiid = emby_dbitem[0] + mediatype = emby_dbitem[4] + self.logMsg("Removing %s kodiid: %s" % (mediatype, kodiid), 1) + except TypeError: + return + + ##### PROCESS ITEM ##### + + # Remove the emby reference + emby_db.removeItem(itemid) + + + ##### IF SONG ##### + + if mediatype == "song": + # Delete song + self.removeSong(kodiid) + + ##### IF ALBUM ##### + + elif mediatype == "album": + # Delete songs, album + album_songs = emby_db.getItem_byParentId(kodiid, "song") + for song in album_songs: + self.removeSong(song[1]) + else: + # Remove emby songs + emby_db.removeItems_byParentId(kodiid, "song") + + # Remove the album + self.removeAlbum(kodiid) + + ##### IF ARTIST ##### + + elif mediatype == "artist": + # Delete songs, album, artist + albums = emby_db.getItem_byParentId(kodiid, "album") + for album in albums: + albumid = album[1] + album_songs = emby_db.getItem_byParentId(albumid, "song") + for song in album_songs: + self.removeSong(song[1]) + else: + # Remove emby song + emby_db.removeItems_byParentId(albumid, "song") + # Remove emby artist + emby_db.removeItems_byParentId(albumid, "artist") + # Remove kodi album + self.removeAlbum(albumid) + else: + # Remove emby albums + emby_db.removeItems_byParentId(kodiid, "album") + + # Remove artist + self.removeArtist(kodiid) + + self.logMsg("Deleted %s: %s from kodi database" % (mediatype, itemid), 1) + + def removeSong(self, kodiid): + + kodicursor = self.kodicursor + artwork = self.artwork + + artwork.deleteArtwork(kodiid, "song", kodicursor) + kodicursor.execute("DELETE FROM song WHERE idSong = ?", (kodiid,)) + + def removeAlbum(self, kodiid): + + kodicursor = self.kodicursor + artwork = self.artwork + + artwork.deleteArtwork(kodiid, "album", kodicursor) + kodicursor.execute("DELETE FROM album WHERE idAlbum = ?", (kodiid,)) + + def removeArtist(self, kodiid): + + kodicursor = self.kodicursor + artwork = self.artwork + + artwork.deleteArtwork(kodiid, "artist", kodicursor) + kodicursor.execute("DELETE FROM artist WHERE idArtist = ?", (kodiid,)) \ No newline at end of file diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py new file mode 100644 index 00000000..300da84d --- /dev/null +++ b/resources/lib/kodidb_functions.py @@ -0,0 +1,1143 @@ +# -*- coding: utf-8 -*- + +################################################################################################## + +import xbmc + +import api +import artwork +import clientinfo +import utils + +################################################################################################## + + +class Kodidb_Functions(): + + kodiversion = int(xbmc.getInfoLabel("System.BuildVersion")[:2]) + + + def __init__(self, cursor): + + self.cursor = cursor + + self.clientInfo = clientinfo.ClientInfo() + self.addonName = self.clientInfo.getAddonName() + self.artwork = artwork.Artwork() + + def logMsg(self, msg, lvl=1): + + className = self.__class__.__name__ + utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) + + + def addPath(self, path): + + cursor = self.cursor + + query = ' '.join(( + + "SELECT idPath", + "FROM path", + "WHERE strPath = ?" + )) + cursor.execute(query, (path,)) + try: + pathid = cursor.fetchone()[0] + except TypeError: + cursor.execute("select coalesce(max(idPath),0) from path") + pathid = cursor.fetchone()[0] + 1 + query = ( + ''' + INSERT INTO path( + idPath, strPath) + + VALUES (?, ?) + ''' + ) + cursor.execute(query, (pathid, path)) + + return pathid + + def getPath(self, path): + + cursor = self.cursor + + query = ' '.join(( + + "SELECT idPath", + "FROM path", + "WHERE strPath = ?" + )) + cursor.execute(query, (path,)) + try: + pathid = cursor.fetchone()[0] + except TypeError: + pathid = None + + return pathid + + def addFile(self, filename, pathid): + + cursor = self.cursor + + query = ' '.join(( + + "SELECT idFile", + "FROM files", + "WHERE strFilename = ?", + "AND idPath = ?" + )) + cursor.execute(query, (filename, pathid,)) + try: + fileid = cursor.fetchone()[0] + except TypeError: + cursor.execute("select coalesce(max(idFile),0) from files") + fileid = cursor.fetchone()[0] + 1 + query = ( + ''' + INSERT INTO files( + idFile, strFilename) + + VALUES (?, ?) + ''' + ) + cursor.execute(query, (fileid, filename)) + + return fileid + + def getFile(self, fileid): + + cursor = self.cursor + + query = ' '.join(( + + "SELECT strFilename", + "FROM files", + "WHERE idFile = ?" + )) + cursor.execute(query, (fileid,)) + try: + filename = cursor.fetchone()[0] + except TypeError: + filename = "" + + return filename + + def removeFile(self, path, filename): + + pathid = self.getPath(path) + + if pathid is not None: + query = ' '.join(( + + "DELETE FROM files", + "WHERE idPath = ?", + "AND strFilename = ?" + )) + self.cursor.execute(query, (pathid, filename,)) + + def addCountries(self, kodiid, countries, mediatype): + + cursor = self.cursor + + if self.kodiversion in (15, 16): + # Kodi Isengard, Jarvis + for country in countries: + query = ' '.join(( + + "SELECT country_id", + "FROM country", + "WHERE name = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (country,)) + + try: + country_id = cursor.fetchone()[0] + + except TypeError: + # Country entry does not exists + cursor.execute("select coalesce(max(country_id),0) from country") + country_id = cursor.fetchone()[0] + 1 + + query = "INSERT INTO country(country_id, name) values(?, ?)" + cursor.execute(query, (country_id, country)) + self.logMsg("Add country to media, processing: %s" % country, 2) + + finally: # Assign country to content + query = ( + ''' + INSERT OR REPLACE INTO country_link( + country_id, media_id, media_type) + + VALUES (?, ?, ?) + ''' + ) + cursor.execute(query, (country_id, kodiid, mediatype)) + else: + # Kodi Helix + for country in countries: + query = ' '.join(( + + "SELECT idCountry", + "FROM country", + "WHERE strCountry = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (country,)) + + try: + idCountry = cursor.fetchone()[0] + + except TypeError: + # Country entry does not exists + cursor.execute("select coalesce(max(idCountry),0) from country") + idCountry = cursor.fetchone()[0] + 1 + + query = "INSERT INTO country(idCountry, strCountry) values(?, ?)" + cursor.execute(query, (idCountry, country)) + self.logMsg("Add country to media, processing: %s" % country, 2) + + finally: + # Only movies have a country field + if "movie" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO countrylinkmovie( + idCountry, idMovie) + + VALUES (?, ?) + ''' + ) + cursor.execute(query, (idCountry, itemid)) + + def addPeople(self, kodiid, people, mediatype): + + cursor = self.cursor + artwork = self.artwork + kodiversion = self.kodiversion + + castorder = 1 + for person in people: + + name = person['Name'] + type = person['Type'] + thumb = person['imageurl'] + + # Kodi Isengard, Jarvis + if kodiversion in (15, 16): + query = ' '.join(( + + "SELECT actor_id", + "FROM actor", + "WHERE name = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (name,)) + + try: + actorid = cursor.fetchone()[0] + + except TypeError: + # Cast entry does not exists + cursor.execute("select coalesce(max(actor_id),0) from actor") + actorid = cursor.fetchone()[0] + 1 + + query = "INSERT INTO actor(actor_id, name) values(?, ?)" + cursor.execute(query, (actorid, name)) + self.logMsg("Add people to media, processing: %s" % name, 2) + + finally: + # Link person to content + if "Actor" in type: + role = person['Role'] + query = ( + ''' + INSERT OR REPLACE INTO actor_link( + actor_id, media_id, media_type, role, cast_order) + + VALUES (?, ?, ?, ?, ?) + ''' + ) + cursor.execute(query, (actorid, kodiid, mediatype, role, castorder)) + castorder += 1 + + elif "Director" in type: + query = ( + ''' + INSERT OR REPLACE INTO director_link( + actor_id, media_id, media_type) + + VALUES (?, ?, ?) + ''' + ) + cursor.execute(query, (actorid, kodiid, mediatype)) + + elif type in ("Writing", "Writer"): + query = ( + ''' + INSERT OR REPLACE INTO writer_link( + actor_id, media_id, media_type) + + VALUES (?, ?, ?) + ''' + ) + cursor.execute(query, (actorid, kodiid, mediatype)) + # Kodi Helix + else: + query = ' '.join(( + + "SELECT idActor", + "FROM actors", + "WHERE strActor = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (name,)) + + try: + actorid = cursor.fetchone()[0] + + except TypeError: + # Cast entry does not exists + cursor.execute("select coalesce(max(idActor),0) from actors") + actorid = cursor.fetchone()[0] + 1 + + query = "INSERT INTO actors(idActor, strActor) values(?, ?)" + cursor.execute(query, (actorid, name)) + self.logMsg("Add people to media, processing: %s" % name, 2) + + finally: + # Link person to content + if "Actor" in type: + role = person['Role'] + + if "movie" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO actorlinkmovie( + idActor, idMovie, strRole, iOrder) + + VALUES (?, ?, ?, ?) + ''' + ) + elif "tvshow" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO actorlinktvshow( + idActor, idShow, strRole, iOrder) + + VALUES (?, ?, ?, ?) + ''' + ) + elif "episode" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO actorlinkepisode( + idActor, idEpisode, strRole, iOrder) + + VALUES (?, ?, ?, ?) + ''' + ) + else: return # Item is invalid + + cursor.execute(query, (actorid, kodiid, role, castorder)) + castorder += 1 + + elif "Director" in type: + if "movie" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO directorlinkmovie( + idDirector, idMovie) + + VALUES (?, ?) + ''' + ) + elif "tvshow" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO directorlinktvshow( + idDirector, idShow) + + VALUES (?, ?) + ''' + ) + elif "musicvideo" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO directorlinkmusicvideo( + idDirector, idMVideo) + + VALUES (?, ?) + ''' + ) + + elif "episode" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO directorlinkepisode( + idDirector, idEpisode) + + VALUES (?, ?) + ''' + ) + else: return # Item is invalid + + cursor.execute(query, (actorid, kodiid)) + + elif type in ("Writing", "Writer"): + if "movie" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO writerlinkmovie( + idWriter, idMovie) + + VALUES (?, ?) + ''' + ) + elif "episode" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO writerlinkepisode( + idWriter, idEpisode) + + VALUES (?, ?) + ''' + ) + else: return # Item is invalid + + cursor.execute(query, (actorid, kodiid)) + + # Add person image to art table + if thumb: + arttype = type.lower() + + if "writing" in arttype: + arttype = "writer" + + artwork.addOrUpdateArt(thumb, actorid, arttype, "thumb", cursor) + + def addGenres(self, kodiid, genres, mediatype): + + cursor = self.cursor + + # Kodi Isengard, Jarvis + if self.kodiversion in (15, 16): + # Delete current genres for clean slate + query = ' '.join(( + + "DELETE FROM genre_link", + "WHERE media_id = ?", + "AND media_type = ?" + )) + cursor.execute(query, (kodiid, mediatype,)) + + # Add genres + for genre in genres: + + query = ' '.join(( + + "SELECT genre_id", + "FROM genre", + "WHERE name = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (genre,)) + + try: + genre_id = cursor.fetchone()[0] + + except TypeError: + # Create genre in database + cursor.execute("select coalesce(max(genre_id),0) from genre") + genre_id = cursor.fetchone()[0] + 1 + + query = "INSERT INTO genre(genre_id, name) values(?, ?)" + cursor.execute(query, (genre_id, genre)) + self.logMsg("Add Genres to media, processing: %s" % genre, 2) + + finally: + # Assign genre to item + query = ( + ''' + INSERT OR REPLACE INTO genre_link( + genre_id, media_id, media_type) + + VALUES (?, ?, ?) + ''' + ) + cursor.execute(query, (genre_id, kodiid, mediatype)) + else: + # Kodi Helix + # Delete current genres for clean slate + if "movie" in mediatype: + cursor.execute("DELETE FROM genrelinkmovie WHERE idMovie = ?", (kodiid,)) + elif "tvshow" in mediatype: + cursor.execute("DELETE FROM genrelinktvshow WHERE idShow = ?", (kodiid,)) + elif "musicvideo" in mediatype: + cursor.execute("DELETE FROM genrelinkmusicvideo WHERE idMVideo = ?", (kodiid,)) + + # Add genres + for genre in genres: + + query = ' '.join(( + + "SELECT idGenre", + "FROM genre", + "WHERE strGenre = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (genre,)) + + try: + idGenre = cursor.fetchone()[0] + + except TypeError: + # Create genre in database + cursor.execute("select coalesce(max(idGenre),0) from genre") + idGenre = cursor.fetchone()[0] + 1 + + query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" + cursor.execute(query, (idGenre, genre)) + self.logMsg("Add Genres to media, processing: %s" % genre, 2) + + finally: + # Assign genre to item + if "movie" in mediatype: + query = ( + ''' + INSERT OR REPLACE into genrelinkmovie( + idGenre, idMovie) + + VALUES (?, ?) + ''' + ) + elif "tvshow" in mediatype: + query = ( + ''' + INSERT OR REPLACE into genrelinktvshow( + idGenre, idShow) + + VALUES (?, ?) + ''' + ) + elif "musicvideo" in mediatype: + query = ( + ''' + INSERT OR REPLACE into genrelinkmusicvideo( + idGenre, idMVideo) + + VALUES (?, ?) + ''' + ) + else: return # Item is invalid + + cursor.execute(query, (idGenre, kodiid)) + + def addStudios(self, kodiid, studios, mediatype): + + cursor = self.cursor + kodiversion = self.kodiversion + + for studio in studios: + + if kodiversion in (15, 16): + # Kodi Isengard, Jarvis + query = ' '.join(( + + "SELECT studio_id", + "FROM studio", + "WHERE name = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (studio,)) + try: + studioid = cursor.fetchone()[0] + + except TypeError: + # Studio does not exists. + cursor.execute("select coalesce(max(studio_id),0) from studio") + studioid = cursor.fetchone()[0] + 1 + + query = "INSERT INTO studio(studio_id, name) values(?, ?)" + cursor.execute(query, (studioid, studio)) + self.logMsg("Add Studios to media, processing: %s" % studio, 2) + + finally: # Assign studio to item + query = ( + ''' + INSERT OR REPLACE INTO studio_link( + studio_id, media_id, media_type) + + VALUES (?, ?, ?) + ''') + cursor.execute(query, (studioid, kodiid, mediatype)) + else: + # Kodi Helix + query = ' '.join(( + + "SELECT idstudio", + "FROM studio", + "WHERE strstudio = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (studio,)) + try: + studioid = cursor.fetchone()[0] + + except TypeError: + # Studio does not exists. + cursor.execute("select coalesce(max(idstudio),0) from studio") + studioid = cursor.fetchone()[0] + 1 + + query = "INSERT INTO studio(idstudio, strstudio) values(?, ?)" + cursor.execute(query, (studioid, studio)) + self.logMsg("Add Studios to media, processing: %s" % studio, 2) + + finally: # Assign studio to item + if "movie" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO studiolinkmovie(idstudio, idMovie) + VALUES (?, ?) + ''') + elif "musicvideo" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO studiolinkmusicvideo(idstudio, idMVideo) + VALUES (?, ?) + ''') + elif "tvshow" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO studiolinktvshow(idstudio, idShow) + VALUES (?, ?) + ''') + elif "episode" in mediatype: + query = ( + ''' + INSERT OR REPLACE INTO studiolinkepisode(idstudio, idEpisode) + VALUES (?, ?) + ''') + cursor.execute(query, (studioid, kodiid)) + + def addStreams(self, fileid, streamdetails, runtime): + + cursor = self.cursor + + # First remove any existing entries + cursor.execute("DELETE FROM streamdetails WHERE idFile = ?", (fileid,)) + if streamdetails: + # Video details + for videotrack in streamdetails['video']: + query = ( + ''' + INSERT INTO streamdetails( + idFile, iStreamType, strVideoCodec, fVideoAspect, + iVideoWidth, iVideoHeight, iVideoDuration ,strStereoMode) + + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''' + ) + cursor.execute(query, (fileid, 0, videotrack['videocodec'], + videotrack['aspectratio'], videotrack['width'], videotrack['height'], + runtime ,videotrack['video3DFormat'])) + + # Audio details + for audiotrack in streamdetails['audio']: + query = ( + ''' + INSERT INTO streamdetails( + idFile, iStreamType, strAudioCodec, iAudioChannels, strAudioLanguage) + + VALUES (?, ?, ?, ?, ?) + ''' + ) + cursor.execute(query, (fileid, 1, audiotrack['audiocodec'], + audiotrack['channels'], audiotrack['audiolanguage'])) + + # Subtitles details + for subtitletrack in streamdetails['subtitle']: + query = ( + ''' + INSERT INTO streamdetails( + idFile, iStreamType, strSubtitleLanguage) + + VALUES (?, ?, ?) + ''' + ) + cursor.execute(query, (fileid, 2, subtitletrack)) + + def addPlaystate(self, fileid, resume_seconds, total_seconds, playcount, dateplayed): + + cursor = self.cursor + + # Delete existing resume point + query = ' '.join(( + + "DELETE FROM bookmark", + "WHERE idFile = ?" + )) + cursor.execute(query, (fileid,)) + + # Set watched count + query = ' '.join(( + + "UPDATE files", + "SET playCount = ?, lastPlayed = ?", + "WHERE idFile = ?" + )) + cursor.execute(query, (playcount, dateplayed, fileid)) + + # Set the resume bookmark + if resume_seconds: + cursor.execute("select coalesce(max(idBookmark),0) from bookmark") + bookmarkId = cursor.fetchone()[0] + 1 + query = ( + ''' + INSERT INTO bookmark( + idBookmark, idFile, timeInSeconds, totalTimeInSeconds, player, type) + + VALUES (?, ?, ?, ?, ?, ?) + ''' + ) + cursor.execute(query, (bookmarkId, fileid, resume_seconds, total_seconds, + "DVDPlayer", 1)) + + def addTags(self, kodiid, tags, mediatype): + + cursor = self.cursor + + # First, delete any existing tags associated to the id + if self.kodiversion in (15, 16): + # Kodi Isengard, Jarvis + query = ' '.join(( + + "DELETE FROM tag_link", + "WHERE media_id = ?", + "AND media_type = ?" + )) + cursor.execute(query, (kodiid, mediatype)) + else: + # Kodi Helix + query = ' '.join(( + + "DELETE FROM taglinks", + "WHERE idMedia = ?", + "AND media_type = ?" + )) + cursor.execute(query, (kodiid, mediatype)) + + # Add tags + self.logMsg("Adding Tags: %s" % tags, 2) + for tag in tags: + self.addTag(kodiid, tag, mediatype) + + def addTag(self, kodiid, tag, mediatype): + + cursor = self.cursor + + if self.kodiversion in (15, 16): + # Kodi Isengard, Jarvis + query = ' '.join(( + + "SELECT tag_id", + "FROM tag", + "WHERE name = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (tag,)) + try: + tag_id = cursor.fetchone()[0] + + except TypeError: + # Create the tag, because it does not exist + tag_id = self.createTag(tag) + self.logMsg("Adding tag: %s" % tag, 2) + + finally: + # Assign tag to item + query = ( + ''' + INSERT OR REPLACE INTO tag_link( + tag_id, media_id, media_type) + + VALUES (?, ?, ?) + ''' + ) + cursor.execute(query, (tag_id, kodiid, mediatype)) + else: + # Kodi Helix + query = ' '.join(( + + "SELECT idTag", + "FROM tag", + "WHERE strTag = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (tag,)) + try: + idTag = cursor.fetchone()[0] + + except TypeError: + # Create the tag + tag_id = self.createTag(tag) + self.logMsg("Adding tag: %s" % tag, 2) + + finally: + # Assign tag to item + query = ( + ''' + INSERT OR REPLACE INTO taglinks( + idTag, idMedia, media_type) + + VALUES (?, ?, ?) + ''' + ) + cursor.execute(query, (idTag, kodiid, mediatype)) + + def createTag(self, name): + + cursor = self.cursor + + # This will create and return the tag_id + if self.kodiversion in (15, 16): + # Kodi Isengard, Jarvis + query = ' '.join(( + + "SELECT tag_id", + "FROM tag", + "WHERE name = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (name,)) + try: + tag_id = cursor.fetchone()[0] + + except TypeError: + cursor.execute("select coalesce(max(tag_id),0) from tag") + tag_id = cursor.fetchone()[0] + 1 + + query = "INSERT INTO tag(tag_id, name) values(?, ?)" + cursor.execute(query, (tag_id, name)) + self.logMsg("Create tag_id: %s name: %s" % (tag_id, name), 2) + else: + # Kodi Helix + query = ' '.join(( + + "SELECT idTag", + "FROM tag", + "WHERE strTag = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (name,)) + try: + tag_id = cursor.fetchone()[0] + + except TypeError: + cursor.execute("select coalesce(max(idTag),0) from tag") + tag_id = cursor.fetchone()[0] + 1 + + query = "INSERT INTO tag(idTag, strTag) values(?, ?)" + cursor.execute(query, (tag_id, name)) + self.logMsg("Create idTag: %s name: %s" % (tag_id, name), 2) + + return tag_id + + def updateTag(self, oldtag, newtag, kodiid, mediatype): + + cursor = self.cursor + self.logMsg("Updating: %s with %s for %s: %s" % (oldtag, newtag, mediatype, kodiid), 2) + + if self.kodiversion in (15, 16): + # Kodi Isengard, Jarvis + query = ' '.join(( + + "UPDATE tag_link", + "SET tag_id = ?", + "WHERE media_id = ?", + "AND media_type = ?", + "AND tag_id = ?" + )) + cursor.execute(query, (newtag, kodiid, mediatype, oldtag,)) + else: + # Kodi Helix + query = ' '.join(( + + "UPDATE taglinks", + "SET idTag = ?", + "WHERE idMedia = ?", + "AND media_type = ?", + "AND idTag = ?" + )) + cursor.execute(query, (newtag, kodiid, mediatype, oldtag,)) + + def removeTag(self, kodiid, tagname, mediatype): + + cursor = self.cursor + + if self.kodiversion in (15, 16): + # Kodi Isengard, Jarvis + query = ' '.join(( + + "SELECT tag_id", + "FROM tag", + "WHERE name = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (tagname,)) + try: + tag_id = cursor.fetchone()[0] + except TypeError: + return + else: + query = ' '.join(( + + "DELETE FROM tag_link", + "WHERE media_id = ?", + "AND media_type = ?", + "AND tag_id = ?" + )) + cursor.execute(query, (kodiid, mediatype, tag_id)) + else: + # Kodi Helix + query = ' '.join(( + + "SELECT idTag", + "FROM tag", + "WHERE strTag = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (tagname,)) + try: + tag_id = cursor.fetchone()[0] + except TypeError: + return + else: + query = ' '.join(( + + "DELETE FROM taglinks", + "WHERE idMedia = ?", + "AND media_type ?", + "AND idTag = ?" + )) + cursor.execute(query, (kodiid, mediatype, tag_id)) + + def createBoxset(self, boxsetname): + + cursor = self.cursor + self.logMsg("Adding boxset: %s" % boxsetname, 2) + query = ' '.join(( + + "SELECT idSet", + "FROM sets", + "WHERE strSet = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (boxsetname,)) + try: + setid = cursor.fetchone()[0] + + except TypeError: + cursor.execute("select coalesce(max(idSet),0) from sets") + setid = cursor.fetchone()[0] + 1 + + query = "INSERT INTO sets(idSet, strSet) values(?, ?)" + cursor.execute(query, (setid, boxsetname)) + + return setid + + def assignBoxset(self, setid, movieid): + + query = ' '.join(( + + "UPDATE movie", + "SET idSet = ?", + "WHERE idMovie = ?" + )) + self.cursor.execute(query, (setid, movieid,)) + + def removefromBoxset(self, movieid): + + query = ' '.join(( + + "UPDATE movie", + "SET idSet = null", + "WHERE idMovie = ?" + )) + self.cursor.execute(query, (movieid,)) + + def addSeason(self, showid, seasonnumber): + + cursor = self.cursor + + query = ' '.join(( + + "SELECT idSeason", + "FROM seasons", + "WHERE idShow = ?", + "AND season = ?" + )) + cursor.execute(query, (showid, seasonnumber,)) + try: + seasonid = cursor.fetchone()[0] + except TypeError: + cursor.execute("select coalesce(max(idSeason),0) from seasons") + seasonid = cursor.fetchone()[0] + 1 + query = "INSERT INTO seasons(idSeason, idShow, season) values(?, ?, ?)" + cursor.execute(query, (seasonid, showid, seasonnumber)) + + return seasonid + + def addArtist(self, name, musicbrainz): + + cursor = self.cursor + + query = ' '.join(( + + "SELECT idArtist, strArtist", + "FROM artist", + "WHERE strMusicBrainzArtistID = ?" + )) + cursor.execute(query, (musicbrainz,)) + try: + result = cursor.fetchone() + artistid = result[0] + artistname = result[1] + + except TypeError: + + query = ' '.join(( + + "SELECT idArtist", + "FROM artist", + "WHERE strArtist = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (name,)) + try: + artistid = cursor.fetchone()[0] + except TypeError: + cursor.execute("select coalesce(max(idArtist),0) from artist") + artistid = cursor.fetchone()[0] + 1 + query = ( + ''' + INSERT INTO artist(idArtist, strArtist, strMusicBrainzArtistID) + + VALUES (?, ?, ?) + ''' + ) + cursor.execute(query, (artistid, name, musicbrainz)) + else: + if artistname != name: + query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?" + cursor.execute(query, (name, artistid,)) + + return artistid + + def addAlbum(self, name, musicbrainz): + + cursor = self.cursor + + query = ' '.join(( + + "SELECT idAlbum", + "FROM album", + "WHERE strMusicBrainzAlbumID = ?" + )) + cursor.execute(query, (musicbrainz,)) + try: + albumid = cursor.fetchone()[0] + except TypeError: + # Verify by name + query = ' '.join(( + + "SELECT idAlbum", + "FROM album", + "WHERE strAlbum = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (name,)) + try: + albumid = cursor.fetchone()[0] + except TypeError: + # Create the album + cursor.execute("select coalesce(max(idAlbum),0) from album") + albumid = cursor.fetchone()[0] + 1 + query = ( + ''' + INSERT INTO album(idAlbum, strAlbum, strMusicBrainzAlbumID) + + VALUES (?, ?, ?) + ''' + ) + cursor.execute(query, (albumid, name, musicbrainz)) + + return albumid + + def addMusicGenres(self, kodiid, genres, mediatype): + + cursor = self.cursor + + if mediatype == "album": + + # Delete current genres for clean slate + query = ' '.join(( + + "DELETE FROM album_genre", + "WHERE idAlbum = ?" + )) + cursor.execute(query, (kodiid,)) + + for genre in genres: + query = ' '.join(( + + "SELECT idGenre", + "FROM genre", + "WHERE strGenre = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (genre,)) + try: + genreid = cursor.fetchone()[0] + except TypeError: + # Create the genre + cursor.execute("select coalesce(max(idGenre),0) from genre") + genreid = cursor.fetchone()[0] + 1 + query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" + cursor.execute(query, (genreid, genre)) + + query = "INSERT OR REPLACE INTO album_genre(idGenre, idAlbum) values(?, ?)" + cursor.execute(query, (genreid, kodiid)) + + elif mediatype == "song": + + # Delete current genres for clean slate + query = ' '.join(( + + "DELETE FROM song_genre", + "WHERE idSong = ?" + )) + cursor.execute(query, (kodiid,)) + + for genre in genres: + query = ' '.join(( + + "SELECT idGenre", + "FROM genre", + "WHERE strGenre = ?", + "COLLATE NOCASE" + )) + cursor.execute(query, (genre,)) + try: + genreid = cursor.fetchone()[0] + except TypeError: + # Create the genre + cursor.execute("select coalesce(max(idGenre),0) from genre") + genreid = cursor.fetchone()[0] + 1 + query = "INSERT INTO genre(idGenre, strGenre) values(?, ?)" + cursor.execute(query, (genreid, genre)) + + query = "INSERT OR REPLACE INTO song_genre(idGenre, idSong) values(?, ?)" + cursor.execute(query, (genreid, kodiid)) \ No newline at end of file diff --git a/resources/lib/playlist.py b/resources/lib/playlist.py new file mode 100644 index 00000000..03d07d39 --- /dev/null +++ b/resources/lib/playlist.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json + +import xbmc +import xbmcgui +import xbmcplugin + +import clientinfo +import playutils +import playbackutils +import embydb_functions as embydb +import read_embyserver as embyserver +import utils + +################################################################################################# + + +class Playlist(): + + + def __init__(self): + + self.clientInfo = clientinfo.ClientInfo() + self.addonName = self.clientInfo.getAddonName() + + self.userid = utils.window('emby_currUser') + self.server = utils.window('emby_server%s' % self.userid) + + self.emby = embyserver.Read_EmbyServer() + + def logMsg(self, msg, lvl=1): + + self.className = self.__class__.__name__ + utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) + + + def playAll(self, itemids, startat): + + embyconn = utils.kodiSQL('emby') + embycursor = embyconn.cursor() + emby_db = embydb.Embydb_Functions(embycursor) + + self.logMsg("---*** PLAY ALL ***---", 1) + self.logMsg("Items: %s" % itemids) + + player = xbmc.Player() + playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + playlist.clear() + started = False + + utils.window('emby_customplaylist', value="true", windowid=10101) + + position = 0 + + for itemid in itemids: + embydb_item = emby_db.getItem_byId(itemid) + try: + dbid = embydb_item[0] + mediatype = embydb_item[4] + except TypeError: + # Item is not found in our database, add item manually + item = self.emby.getItem(itemid) + self.addtoPlaylist_xbmc(playlist, item) + else: + # Add to playlist + self.addtoPlaylist(dbid, mediatype) + + self.logMsg("Adding %s to playlist." % itemid, 1) + + if not started: + started = True + player.play(playlist) + + if startat: + # Seek to the starting position + seektime = startat / 10000000.0 + player.seekTime(seektime) + + self.verifyPlaylist() + embycursor.close() + + def modifyPlaylist(self, itemids): + + embyconn = utils.kodiSQL('emby') + embycursor = embyconn.cursor() + emby_db = embydb.Embydb_Functions(embycursor) + + self.logMsg("---*** ADD TO PLAYLIST ***---", 1) + self.logMsg("Items: %s" % itemids, 1) + + player = xbmc.Player() + playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + + for itemid in itemids: + embydb_item = emby_db.getItem_byId(itemid) + try: + dbid = embydb_item[0] + mediatype = embydb_item[4] + except TypeError: + # Item is not found in our database, add item manually + item = self.emby.getItem(itemid) + self.addtoPlaylist_xbmc(playlist, item) + else: + # Add to playlist + self.addtoPlaylist(dbid, mediatype) + + self.logMsg("Adding %s to playlist." % itemid, 1) + + self.verifyPlaylist() + embycursor.close() + return playlist + + def addtoPlaylist(self, dbid=None, mediatype=None, url=None): + + pl = { + + 'jsonrpc': "2.0", + 'id': 1, + 'method': "Playlist.Add", + 'params': { + + 'playlistid': 1 + } + } + if dbid is not None: + pl['params']['item'] = {'%sid' % mediatype: int(dbid)} + else: + pl['params']['item'] = {'file': url} + + result = xbmc.executeJSONRPC(json.dumps(pl)) + self.logMsg(result, 2) + + def addtoPlaylist_xbmc(self, playlist, item): + + itemid = item['Id'] + playurl = playutils.PlayUtils(item).getPlayUrl() + if not playurl: + # Playurl failed + self.logMsg("Failed to retrieve playurl.", 1) + return + + self.logMsg("Playurl: %s" % playurl) + listitem = xbmcgui.ListItem() + playbackutils.PlaybackUtils(item).setProperties(playurl, listitem) + + playlist.add(playurl, listitem) + + def insertintoPlaylist(self, position, dbid=None, mediatype=None, url=None): + + pl = { + + 'jsonrpc': "2.0", + 'id': 1, + 'method': "Playlist.Insert", + 'params': { + + 'playlistid': 1, + 'position': position + } + } + if dbid is not None: + pl['params']['item'] = {'%sid' % mediatype: int(dbid)} + else: + pl['params']['item'] = {'file': url} + + result = xbmc.executeJSONRPC(json.dumps(pl)) + self.logMsg(result, 2) + + def verifyPlaylist(self): + + pl = { + + 'jsonrpc': "2.0", + 'id': 1, + 'method': "Playlist.GetItems", + 'params': { + + 'playlistid': 1 + } + } + result = xbmc.executeJSONRPC(json.dumps(pl)) + self.logMsg(result, 2) + + def removefromPlaylist(self, position): + + pl = { + + 'jsonrpc': "2.0", + 'id': 1, + 'method': "Playlist.Remove", + 'params': { + + 'playlistid': 1, + 'position': position + } + } + result = xbmc.executeJSONRPC(json.dumps(pl)) + self.logMsg(result, 2) \ No newline at end of file diff --git a/resources/lib/read_embyserver.py b/resources/lib/read_embyserver.py new file mode 100644 index 00000000..a16882a0 --- /dev/null +++ b/resources/lib/read_embyserver.py @@ -0,0 +1,426 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import utils +import clientinfo +import downloadutils + +################################################################################################# + + +class Read_EmbyServer(): + + limitIndex = 200 + + + def __init__(self): + + self.clientInfo = clientinfo.ClientInfo() + self.addonName = self.clientInfo.getAddonName() + self.doUtils = downloadutils.DownloadUtils() + + self.userId = utils.window('emby_currUser') + self.server = utils.window('emby_server%s' % self.userId) + + def logMsg(self, msg, lvl=1): + + className = self.__class__.__name__ + utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) + + + def split_list(self, itemlist, size): + # Split up list in pieces of size. Will generate a list of lists + return [itemlist[i:i+size] for i in range(0, len(itemlist), size)] + + + def getItem(self, itemid): + # This will return the full item + item = {} + + url = "{server}/emby/Users/{UserId}/Items/%s?format=json" % itemid + result = self.doUtils.downloadUrl(url) + if result: + item = result + + return item + + def getItems(self, itemlist): + + items = [] + + itemlists = self.split_list(itemlist, 50) + for itemlist in itemlists: + # Will return basic information + url = "{server}/emby/Users/{UserId}/Items?&format=json" + params = { + + 'Ids': ",".join(itemlist), + 'Fields': "Etag" + } + result = self.doUtils.downloadUrl(url, parameters=params) + if result: + items.extend(result['Items']) + + return items + + def getFullItems(self, itemlist): + + items = [] + + itemlists = self.split_list(itemlist, 50) + for itemlist in itemlists: + + url = "{server}/emby/Users/{UserId}/Items?format=json" + params = { + + "Ids": ",".join(itemlist), + "Fields": ( + + "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," + "CommunityRating,OfficialRating,CumulativeRunTimeTicks," + "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," + "CriticRating,CriticRatingSummary,Etag,ProductionLocations," + "OfficialRating,Tags,ProviderIds,RemoteTrailers" + ) + } + result = self.doUtils.downloadUrl(url, parameters=params) + if result: + items.extend(result['Items']) + + return items + + def getView_embyId(self, itemid): + # Returns ancestors using embyId + viewId = None + url = "{server}/emby/Items/%s/Ancestors?UserId={UserId}&format=json" % itemid + result = self.doUtils.downloadUrl(url) + + for view in result: + + viewtype = view['Type'] + if viewtype == "CollectionFolder": + # Found view + viewId = view['Id'] + + # Compare to view table in emby database + emby = utils.kodiSQL('emby') + cursor_emby = emby.cursor() + query = ' '.join(( + + "SELECT view_name, media_type", + "FROM view", + "WHERE view_id = ?" + )) + cursor_emby.execute(query, (viewId,)) + result = cursor_emby.fetchone() + try: + viewName = result[0] + mediatype = result[1] + except TypeError: + viewName = None + mediatype = None + + cursor_emby.close() + + return [viewName, viewId, mediatype] + + def getSection(self, parentid, itemtype=None, sortby="SortName", basic=False): + + doUtils = self.doUtils + items = { + + 'Items': [], + 'TotalRecordCount': 0 + } + + # Get total number of items + url = "{server}/emby/Users/{UserId}/Items?format=json" + params = { + + 'ParentId': parentid, + 'IncludeItemTypes': itemtype, + 'CollapseBoxSetItems': False, + 'IsVirtualUnaired': False, + 'IsMissing': False, + 'Recursive': True, + 'Limit': 1 + } + result = doUtils.downloadUrl(url, parameters=params) + try: + total = result['TotalRecordCount'] + items['TotalRecordCount'] = total + + except TypeError: # Failed to retrieve + self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2) + + else: + index = 0 + jump = self.limitIndex + + while index < total: + # Get items by chunk to increase retrieval speed at scale + params = { + + 'ParentId': parentid, + 'IncludeItemTypes': itemtype, + 'CollapseBoxSetItems': False, + 'IsVirtualUnaired': False, + 'IsMissing': False, + 'Recursive': True, + 'StartIndex': index, + 'Limit': jump, + 'SortBy': sortby, + 'SortOrder': "Ascending", + } + if basic: + params['Fields'] = "Etag" + else: + params['Fields'] = ( + + "Path,Genres,SortName,Studios,Writer,ProductionYear,Taglines," + "CommunityRating,OfficialRating,CumulativeRunTimeTicks," + "Metascore,AirTime,DateCreated,MediaStreams,People,Overview," + "CriticRating,CriticRatingSummary,Etag,ShortOverview,ProductionLocations," + "OfficialRating,Tags,ProviderIds,ParentId,RemoteTrailers" + ) + result = doUtils.downloadUrl(url, parameters=params) + items['Items'].extend(result['Items']) + + index += jump + + return items + + def getViews(self, type, root=False): + # Build a list of user views + doUtils = self.doUtils + views = [] + type = type.lower() + + if not root: + url = "{server}/emby/Users/{UserId}/Views?format=json" + else: # Views ungrouped + url = "{server}/emby/Users/{UserId}/Items?Sortby=SortName&format=json" + + result = doUtils.downloadUrl(url) + try: + items = result['Items'] + + except TypeError: + self.logMsg("Error retrieving views for type: %s" % type, 2) + + else: + for item in items: + + name = item['Name'] + itemId = item['Id'] + viewtype = item['Type'] + + if viewtype == "Channel": + # Filter view types + continue + + # 11/10/2015 Review key, when it's added to server. Currently unavailable. + itemtype = item.get('OriginalCollectionType', item.get('CollectionType')) + + # 11/29/2015 Remove this once OriginalCollectionType is added to stable server. + # Assumed missing is mixed then. + if itemtype is None: + url = "{server}/emby/Library/MediaFolders?format=json" + result = doUtils.downloadUrl(url) + + for folder in result['Items']: + if itemId == folder['Id']: + itemtype = folder.get('CollectionType', "mixed") + + if (itemtype == type or + (itemtype == "mixed" and type in ("movies", "tvshows"))): + + views.append({ + + 'name': name, + 'type': itemtype, + 'id': itemId + }) + + return views + + def getMovies(self, parentId, basic=False): + + items = self.getSection(parentId, "Movie", basic=basic) + + return items + + def getBoxset(self): + + items = self.getSection(None, "BoxSet") + + return items + + def getMovies_byBoxset(self, boxsetid): + + items = self.getSection(boxsetid) + + return items + + def getMusicVideos(self, parentId, basic=False): + + items = self.getSection(parentId, "MusicVideo", basic=basic) + + return items + + def getHomeVideos(self, parentId): + + items = self.getSection(parentId, "Video") + + return items + + def getShows(self, parentId, basic=False): + + items = self.getSection(parentId, "Series", basic=basic) + + return items + + def getSeasons(self, showId): + + items = { + + 'Items': [], + 'TotalRecordCount': 0 + } + + url = "{server}/emby/Shows/%s/Seasons?UserId={UserId}&format=json" % showId + params = { + + 'IsVirtualUnaired': False, + 'Fields': "Etag" + } + result = self.doUtils.downloadUrl(url, parameters=params) + if result: + items = result + + return items + + def getEpisodes(self, parentId, basic=False): + + items = self.getSection(parentId, "Episode", basic=basic) + + return items + + def getEpisodesbyShow(self, showId): + + items = self.getSection(showId, "Episode") + + return items + + def getEpisodesbySeason(self, seasonId): + + items = self.getSection(seasonId, "Episode") + + return items + + def getArtists(self): + + doUtils = self.doUtils + items = { + + 'Items': [], + 'TotalRecordCount': 0 + } + + # Get total number of items + url = "{server}/emby/Artists?UserId={UserId}&format=json" + params = { + + 'Recursive': True, + 'Limit': 1 + } + result = doUtils.downloadUrl(url, parameters=params) + try: + total = result['TotalRecordCount'] + items['TotalRecordCount'] = total + + except TypeError: # Failed to retrieve + self.logMsg("%s:%s Failed to retrieve the server response." % (url, params), 2) + + else: + index = 1 + jump = self.limitIndex + + while index < total: + # Get items by chunk to increase retrieval speed at scale + params = { + + 'Recursive': True, + 'IsVirtualUnaired': False, + 'IsMissing': False, + 'StartIndex': index, + 'Limit': jump, + 'SortBy': "SortName", + 'SortOrder': "Ascending", + 'Fields': ( + + "Etag,Genres,SortName,Studios,Writer,ProductionYear," + "CommunityRating,OfficialRating,CumulativeRunTimeTicks,Metascore," + "AirTime,DateCreated,MediaStreams,People,ProviderIds,Overview" + ) + } + result = doUtils.downloadUrl(url, parameters=params) + items['Items'].extend(result['Items']) + + index += jump + + return items + + def getAlbums(self, basic=False): + + items = self.getSection(None, "MusicAlbum", sortby="DateCreated", basic=basic) + + return items + + def getAlbumsbyArtist(self, artistId): + + items = self.getSection(artistId, "MusicAlbum", sortby="DateCreated") + + return items + + def getSongs(self, basic=False): + + items = self.getSection(None, "Audio", basic=basic) + + return items + + def getSongsbyAlbum(self, albumId): + + items = self.getSection(albumId, "Audio") + + return items + + def getAdditionalParts(self, itemId): + + items = { + + 'Items': [], + 'TotalRecordCount': 0 + } + + url = "{server}/emby/Videos/%s/AdditionalParts?UserId={UserId}&format=json" % itemId + result = self.doUtils.downloadUrl(url) + if result: + items = result + + return items + + def sortby_mediatype(self, itemids): + + sorted_items = {} + + # Sort items + items = self.getFullItems(itemids) + for item in items: + + mediatype = item.get('Type') + if mediatype: + sorted_items.setdefault(mediatype, []).append(item) + + return sorted_items diff --git a/resources/lib/websocket_client.py b/resources/lib/websocket_client.py new file mode 100644 index 00000000..df5a7658 --- /dev/null +++ b/resources/lib/websocket_client.py @@ -0,0 +1,319 @@ +# -*- coding: utf-8 -*- + +################################################################################################# + +import json +import threading +import websocket + +import xbmc +import xbmcgui + +import clientinfo +import downloadutils +import librarysync +import playlist +import userclient +import utils + +import logging +logging.basicConfig() + +################################################################################################# + + +class WebSocket_Client(threading.Thread): + + _shared_state = {} + + client = None + stopClient = False + + + def __init__(self): + + self.__dict__ = self._shared_state + self.monitor = xbmc.Monitor() + + self.doUtils = downloadutils.DownloadUtils() + self.clientInfo = clientinfo.ClientInfo() + self.addonName = self.clientInfo.getAddonName() + self.deviceId = self.clientInfo.getDeviceId() + self.librarySync = librarysync.LibrarySync() + + threading.Thread.__init__(self) + + def logMsg(self, msg, lvl=1): + + self.className = self.__class__.__name__ + utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) + + + def sendProgressUpdate(self, data): + self.logMsg("sendProgressUpdate", 2) + try: + messageData = { + + 'MessageType': "ReportPlaybackProgress", + 'Data': data + } + messageString = json.dumps(messageData) + self.client.send(messageString) + self.logMsg("Message data: %s" % messageString, 2) + + except Exception as e: + self.logMsg("Exception: %s" % e, 1) + + def on_message(self, ws, message): + + result = json.loads(message) + messageType = result['MessageType'] + data = result['Data'] + + if messageType not in ('SessionEnded'): + # Mute certain events + self.logMsg("Message: %s" % message, 1) + + if messageType == "Play": + # A remote control play command has been sent from the server. + itemIds = data['ItemIds'] + command = data['PlayCommand'] + + pl = playlist.Playlist() + dialog = xbmcgui.Dialog() + dialog.notification("Emby for Kodi", "Adding %s items to playlist." % len(itemIds)) + + if command == "PlayNow": + dialog.notification( + heading="Emby for Kodi", + message="Adding %s items to playlist." % len(itemIds), + icon="special://home/addons/plugin.video.emby/icon.png", + sound=False) + startat = data.get('StartPositionTicks', 0) + pl.playAll(itemIds, startat) + + elif command == "PlayNext": + dialog.notification( + heading="Emby for Kodi", + message="Queueing %s items to playlist." % len(itemIds), + icon="special://home/addons/plugin.video.emby/icon.png", + sound=False) + newplaylist = pl.modifyPlaylist(itemIds) + player = xbmc.Player() + if not player.isPlaying(): + # Only start the playlist if nothing is playing + player.play(newplaylist) + + elif messageType == "Playstate": + # A remote control update playstate command has been sent from the server. + command = data['Command'] + player = xbmc.Player() + + actions = { + + 'Stop': player.stop, + 'Unpause': player.pause, + 'Pause': player.pause, + 'NextTrack': player.playnext, + 'PreviousTrack': player.playprevious, + 'Seek': player.seekTime + } + action = actions[command] + if command == "Seek": + seekto = data['SeekPositionTicks'] + seektime = seekto / 10000000.0 + action(seektime) + self.logMsg("Seek to %s." % seektime, 1) + else: + action() + self.logMsg("Command: %s completed." % command, 1) + + utils.window('emby_command', value="true") + + elif messageType == "UserDataChanged": + # A user changed their personal rating for an item, or their playstate was updated + userdata_list = data['UserDataList'] + self.librarySync.triage_items("userdata", userdata_list) + + elif messageType == "LibraryChanged": + + librarySync = self.librarySync + processlist = { + + 'added': data['ItemsAdded'], + 'update': data['ItemsUpdated'], + 'remove': data['ItemsRemoved'] + } + for action in processlist: + librarySync.triage_items(action, processlist[action]) + + elif messageType == "GeneralCommand": + + command = data['Name'] + arguments = data['Arguments'] + + if command in ('Mute', 'Unmute', 'SetVolume', + 'SetSubtitleStreamIndex', 'SetAudioStreamIndex'): + + player = xbmc.Player() + # These commands need to be reported back + if command == "Mute": + xbmc.executebuiltin('Mute') + elif command == "Unmute": + xbmc.executebuiltin('Mute') + elif command == "SetVolume": + volume = arguments['Volume'] + xbmc.executebuiltin('SetVolume(%s[,showvolumebar])' % volume) + elif command == "SetAudioStreamIndex": + index = int(arguments['Index']) + player.setAudioStream(index - 1) + elif command == "SetSubtitleStreamIndex": + embyindex = int(arguments['Index']) + currentFile = player.getPlayingFile() + + mapping = utils.window('emby_%s.indexMapping' % currentFile) + if mapping: + externalIndex = json.loads(mapping) + # If there's external subtitles added via playbackutils + for index in externalIndex: + if externalIndex[index] == embyindex: + player.setSubtitleStream(int(index)) + break + else: + # User selected internal subtitles + external = len(externalIndex) + audioTracks = len(player.getAvailableAudioStreams()) + player.setSubtitleStream(external + embyindex - audioTracks - 1) + else: + # Emby merges audio and subtitle index together + audioTracks = len(player.getAvailableAudioStreams()) + player.setSubtitleStream(index - audioTracks - 1) + + # Let service know + utils.window('emby_command', value="true") + + elif command == "DisplayMessage": + + header = arguments['Header'] + text = arguments['Text'] + xbmcgui.Dialog().notification( + heading=header, + message=text, + icon="special://home/addons/plugin.video.emby/icon.png", + time=4000) + + elif command == "SendString": + + string = arguments['String'] + text = { + + 'jsonrpc': "2.0", + 'id': 0, + 'method': "Input.SendText", + 'params': { + + 'text': "%s" % string, + 'done': False + } + } + result = xbmc.executeJSONRPC(json.dumps(text)) + + else: + builtin = { + + 'ToggleFullscreen': 'Action(FullScreen)', + 'ToggleOsdMenu': 'Action(OSD)', + 'ToggleContextMenu': 'Action(ContextMenu)', + 'MoveUp': 'Action(Up)', + 'MoveDown': 'Action(Down)', + 'MoveLeft': 'Action(Left)', + 'MoveRight': 'Action(Right)', + 'Select': 'Action(Select)', + 'Back': 'Action(back)', + 'GoHome': 'ActivateWindow(Home)', + 'PageUp': 'Action(PageUp)', + 'NextLetter': 'Action(NextLetter)', + 'GoToSearch': 'VideoLibrary.Search', + 'GoToSettings': 'ActivateWindow(Settings)', + 'PageDown': 'Action(PageDown)', + 'PreviousLetter': 'Action(PrevLetter)', + 'TakeScreenshot': 'TakeScreenshot', + 'ToggleMute': 'Mute', + 'VolumeUp': 'Action(VolumeUp)', + 'VolumeDown': 'Action(VolumeDown)', + } + action = builtin.get(command) + if action: + xbmc.executebuiltin(action) + + elif messageType == "ServerRestarting": + if utils.settings('supressRestartMsg') == "true": + xbmcgui.Dialog().notification( + heading="Emby server", + message="Server is restarting.", + icon="special://home/addons/plugin.video.emby/icon.png") + + elif messageType == "UserConfigurationUpdated": + # Update user data set in userclient + userclient.UserClient().userSettings = data + self.librarySync.refresh_views = True + + def on_close(self, ws): + self.logMsg("Closed.", 2) + + def on_open(self, ws): + self.doUtils.postCapabilities(self.deviceId) + + def on_error(self, ws, error): + if "10061" in str(error): + # Server is offline + pass + else: + self.logMsg("Error: %s" % error, 2) + + def run(self): + + monitor = self.monitor + loglevel = int(utils.window('emby_logLevel')) + # websocket.enableTrace(True) + + userId = utils.window('emby_currUser') + server = utils.window('emby_server%s' % userId) + token = utils.window('emby_accessToken%s' % userId) + deviceId = self.deviceId + + # Get the appropriate prefix for the websocket + if "https" in server: + server = server.replace('https', "wss") + else: + server = server.replace('http', "ws") + + websocket_url = "%s?api_key=%s&deviceId=%s" % (server, token, deviceId) + self.logMsg("websocket url: %s" % websocket_url, 1) + + self.client = websocket.WebSocketApp(websocket_url, + on_message=self.on_message, + on_error=self.on_error, + on_close=self.on_close) + + self.client.on_open = self.on_open + self.logMsg("----===## Starting WebSocketClient ##===----", 0) + + while not monitor.abortRequested(): + + self.client.run_forever() + + if self.stopClient: + break + + if monitor.waitForAbort(5): + # Abort was requested, exit + break + + self.logMsg("##===---- WebSocketClient Stopped ----===##", 0) + + def stopClient(self): + + self.stopClient = True + self.client.close() + self.logMsg("Stopping thread.") \ No newline at end of file diff --git a/resources/settings.xml b/resources/settings.xml index c4a73c4c..901396e7 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -1,63 +1,69 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - + + + - + + - - - - - - - - - + + + + + + + + - - + - - - - - - - - + + + + + + + + + - + + + - - - - - + + + + + - - - - + + + + + diff --git a/service.py b/service.py index fd331fdb..4b3baa5e 100644 --- a/service.py +++ b/service.py @@ -7,8 +7,8 @@ import sys import time from datetime import datetime -import xbmcaddon import xbmc +import xbmcaddon import xbmcgui import xbmcvfs @@ -16,152 +16,142 @@ import xbmcvfs _addon = xbmcaddon.Addon(id='plugin.video.emby') addon_path = _addon.getAddonInfo('path').decode('utf-8') -base_resource_path = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8') -sys.path.append(base_resource_path) +base_resource = xbmc.translatePath(os.path.join(addon_path, 'resources', 'lib')).decode('utf-8') +sys.path.append(base_resource) ################################################################################################# -import KodiMonitor -import Utils as utils -from ClientInformation import ClientInformation -from ConnectionManager import ConnectionManager -from UserClient import UserClient -from Player import Player -from WebSocketClient import WebSocketThread -from LibrarySync import LibrarySync +import userclient +import clientinfo +import initialsetup +import kodimonitor +import librarysync +import player +import utils +import videonodes +import websocket_client as wsc ################################################################################################# + class Service(): - KodiMonitor = KodiMonitor.Kodi_Monitor() - clientInfo = ClientInformation() - - addonName = clientInfo.getAddonName() - logLevel = UserClient().getLogLevel() - WINDOW = xbmcgui.Window(10000) - - newWebSocketThread = None - newUserClient = None - newLibraryThread = None - warn_auth = True welcome_msg = True server_online = True + warn_auth = True - def __init__(self, *args): + userclient_running = False + websocket_running = False + library_running = False + kodimonitor_running = False - addonName = self.addonName - clientInfo = self.clientInfo - logLevel = self.logLevel - utils.window('getLogLevel', value=str(logLevel)) - utils.window('kodiProfile_emby', value=xbmc.translatePath("special://profile")) + def __init__(self): + + self.clientInfo = clientinfo.ClientInfo() + self.addonName = self.clientInfo.getAddonName() + logLevel = userclient.UserClient().getLogLevel() + self.monitor = xbmc.Monitor() + + utils.window('emby_logLevel', value=str(logLevel)) + utils.window('emby_kodiProfile', value=xbmc.translatePath("special://profile")) # Initial logging - self.logMsg("Starting Monitor", 0) - self.logMsg("======== START %s ========" % addonName, 0) - self.logMsg("Platform: %s" % (clientInfo.getPlatform()), 0) + self.logMsg("======== START %s ========" % self.addonName, 0) + self.logMsg("Platform: %s" % (self.clientInfo.getPlatform()), 0) self.logMsg("KODI Version: %s" % xbmc.getInfoLabel('System.BuildVersion'), 0) - self.logMsg("%s Version: %s" % (addonName, clientInfo.getVersion()), 0) + self.logMsg("%s Version: %s" % (self.addonName, self.clientInfo.getVersion()), 0) self.logMsg("Using plugin paths: %s" % (utils.settings('useDirectPaths') != "true"), 0) self.logMsg("Log Level: %s" % logLevel, 0) # Reset window props for profile switch - utils.window('Server_online', clear=True) - utils.window('Server_status', clear=True) - utils.window('startup', clear=True) - utils.window('OnWakeSync', clear=True) - utils.window('kodiScan', clear=True) - utils.window('minDBVersionCheck', clear=True) - - # Set min DB version - utils.window('minDBVersion', value="1.1.52") + properties = [ - embyProperty = utils.window('Emby.nodes.total') - propNames = [ - - "index","path","title","content", - "inprogress.content","inprogress.title", - "inprogress.content","inprogress.path", - "nextepisodes.title","nextepisodes.content", - "nextepisodes.path","unwatched.title", - "unwatched.content","unwatched.path", - "recent.title","recent.content","recent.path", - "recentepisodes.title","recentepisodes.content", - "recentepisodes.path","inprogressepisodes.title", - "inprogressepisodes.content","inprogressepisodes.path" + "emby_online", "emby_serverStatus", "emby_onWake", + "emby_syncRunning", "emby_dbCheck", "emby_kodiScan", + "emby_shouldStop", "emby_currUser", "emby_dbScan", "emby_sessionId" ] + for prop in properties: + utils.window(prop, clear=True) - if embyProperty: - totalNodes = int(embyProperty) - for i in range(totalNodes): - for prop in propNames: - utils.window('Emby.nodes.%s.%s' % (str(i), prop), clear=True) + # Clear playlist properties + xbmcgui.Window(10101).clearProperties() + # Clear video nodes properties + videonodes.VideoNodes().clearProperties() + + # Set the minimum database version + utils.window('emby_minDBVersion', value="1.1.63") def logMsg(self, msg, lvl=1): className = self.__class__.__name__ - utils.logMsg("%s %s" % (self.addonName, className), msg, int(lvl)) + utils.logMsg("%s %s" % (self.addonName, className), msg, lvl) + def ServiceEntryPoint(self): - + # Important: Threads depending on abortRequest will not trigger + # if profile switch happens more than once. + monitor = self.monitor kodiProfile = xbmc.translatePath("special://profile") # Server auto-detect - ConnectionManager().checkServer() + initialsetup.InitialSetup().setup() # Initialize important threads - user = UserClient() - player = Player() - ws = WebSocketThread() - library = LibrarySync() + user = userclient.UserClient() + ws = wsc.WebSocket_Client() + library = librarysync.LibrarySync() + kplayer = player.Player() # Sync and progress report lastProgressUpdate = datetime.today() - while not self.KodiMonitor.abortRequested(): + while not monitor.abortRequested(): + if utils.window('emby_kodiProfile') != kodiProfile: + # Profile change happened, terminate this thread and others + self.logMsg( + "Kodi profile was: %s and changed to: %s. Terminating old Emby thread." + % (kodiProfile, utils.window('emby_kodiProfile')), 1) + + break + # Before proceeding, need to make sure: # 1. Server is online # 2. User is set # 3. User has access to the server - if utils.window("kodiProfile_emby") != kodiProfile: - # Profile change happened, terminate this thread - self.logMsg("Kodi profile was: %s and changed to: %s. Terminating old Emby thread." % (kodiProfile, utils.window("kodiProfile_emby")), 1) - break - - if utils.window('Server_online') == "true": + if utils.window('emby_online') == "true": # Emby server is online # Verify if user is set and has access to the server if (user.currUser is not None) and user.HasAccess: - # If an item is playing + # If an item is playing if xbmc.Player().isPlaying(): try: # Update and report progress - playTime = xbmc.Player().getTime() + playtime = xbmc.Player().getTime() totalTime = xbmc.Player().getTotalTime() - currentFile = player.currentFile + currentFile = kplayer.currentFile # Update positionticks - if player.played_information.get(currentFile) is not None: - player.played_information[currentFile]['currentPosition'] = playTime + if kplayer.played_info.get(currentFile) is not None: + kplayer.played_info[currentFile]['currentPosition'] = playtime td = datetime.today() - lastProgressUpdate secDiff = td.seconds # Report progress to Emby server if (secDiff > 3): - player.reportPlayback() + kplayer.reportPlayback() lastProgressUpdate = datetime.today() - elif utils.window('commandUpdate') == "true": + elif utils.window('emby_command') == "true": # Received a remote control command that # requires updating immediately - utils.window('commandUpdate', clear=True) - player.reportPlayback() - lastProgressUpdate = da4tetime.today() + utils.window('emby_command', clear=True) + kplayer.reportPlayback() + lastProgressUpdate = datetime.today() except Exception as e: self.logMsg("Exception in Playback Monitor Service: %s" % e, 1) @@ -169,27 +159,34 @@ class Service(): else: # Start up events self.warn_auth = True - if utils.settings('supressConnectMsg') == "false": - if self.welcome_msg: - # Reset authentication warnings - self.welcome_msg = False - # Get additional users - additionalUsers = user.AdditionalUser - if additionalUsers: - add = ", %s" % ", ".join(additionalUsers) - else: - add = "" - xbmcgui.Dialog().notification("Emby server", "Welcome %s%s!" % (user.currUser, add), icon="special://home/addons/plugin.video.emby/icon.png", time=2000, sound=False) + if utils.settings('connectMsg') == "true" and self.welcome_msg: + # Reset authentication warnings + self.welcome_msg = False + # Get additional users + additionalUsers = user.AdditionalUser + if additionalUsers: + add = ", %s" % ", ".join(additionalUsers) + else: + add = "" + xbmcgui.Dialog().notification( + heading="Emby server", + message="Welcome %s%s!" % (user.currUser, add), + icon="special://home/addons/plugin.video.emby/icon.png", + time=2000, + sound=False) + + # Start monitoring kodi events + if not self.kodimonitor_running: + self.kodimonitor_running = kodimonitor.KodiMonitor() # Start the Websocket Client - if (self.newWebSocketThread is None): - self.newWebSocketThread = "Started" + if not self.websocket_running: + self.websocket_running = True ws.start() - # Start the Library Sync Thread - if (self.newLibraryThread is None): - self.newLibraryThread = "Started" + # Start the syncing thread + if not self.library_running: + self.library_running = True library.start() - else: if (user.currUser is None) and self.warn_auth: @@ -204,20 +201,19 @@ class Service(): # Verify access with an API call user.hasAccess() - if utils.window('Server_online') != "true": + if utils.window('emby_online') != "true": # Server went offline break - if self.KodiMonitor.waitForAbort(5): + if monitor.waitForAbort(5): # Abort was requested while waiting. We should exit break - else: # Wait until Emby server is online # or Kodi is shut down. - while not self.KodiMonitor.abortRequested(): + while not monitor.abortRequested(): - if user.getServer() == "": + if user.getServer() == False: # No server info set in add-on settings pass @@ -226,8 +222,14 @@ class Service(): # Alert the user and suppress future warning if self.server_online: self.logMsg("Server is offline.", 1) - utils.window('Server_online', value="false") - xbmcgui.Dialog().notification("Error connecting", "%s Server is unreachable." % self.addonName, icon="special://home/addons/plugin.video.emby/icon.png", sound=False) + utils.window('emby_online', value="false") + + xbmcgui.Dialog().notification( + heading="Error connecting", + message="%s Server is unreachable." % self.addonName, + icon="special://home/addons/plugin.video.emby/icon.png", + sound=False) + self.server_online = False else: @@ -235,54 +237,55 @@ class Service(): if not self.server_online: # Server was offline when Kodi started. # Wait for server to be fully established. - if self.KodiMonitor.waitForAbort(5): + if monitor.waitForAbort(5): # Abort was requested while waiting. break # Alert the user that server is online. - xbmcgui.Dialog().notification("Emby server", "Welcome %s!" % user.currUser, icon="special://home/addons/plugin.video.emby/icon.png", time=2000, sound=False) + xbmcgui.Dialog().notification( + heading="Emby server", + message="Server is online.", + icon="special://home/addons/plugin.video.emby/icon.png", + time=2000, + sound=False) self.server_online = True self.logMsg("Server is online and ready.", 1) - utils.window('Server_online', value="true") + utils.window('emby_online', value="true") - # Start the User client - if self.newUserClient is None: - self.newUserClient = "Started" + # Start the userclient thread + if not self.userclient_running: + self.userclient_running = True user.start() + break - if self.KodiMonitor.waitForAbort(1): + if monitor.waitForAbort(1): # Abort was requested while waiting. break - if self.KodiMonitor.waitForAbort(1): + if monitor.waitForAbort(1): # Abort was requested while waiting. We should exit break ##### Emby thread is terminating. ##### - # If music is enabled and direct stream for music is enabled - # We use Kodi pathsubstitution to allow for music to play outside network - # The setting needs to be set before Kodi starts. - if utils.settings('enableMusicSync') == "true" and utils.settings('directstreammusic') == "true": - # We need to keep track of the settings - alternate = utils.settings('altip') == "true" - pathsub = utils.settings('pathsub') == "true" - - if pathsub and not alternate: - # Path sub in place, but primary address in use, remove it - utils.pathsubstitution(False) - elif not pathsub and alternate: - # Path sub not in place, but secondary address in use, add it - utils.pathsubstitution() - - if (self.newWebSocketThread is not None): - ws.stopClient() + if self.library_running: + library.stopThread() - if (self.newUserClient is not None): + if self.websocket_running: + ws.stopClient() + + if self.userclient_running: user.stopClient() self.logMsg("======== STOP %s ========" % self.addonName, 0) -# Start the service -Service().ServiceEntryPoint() \ No newline at end of file +# Delay option +delay = int(utils.settings('startupDelay')) + +xbmc.log("Delaying emby startup by: %s sec..." % delay) +if delay and xbmc.Monitor().waitForAbort(delay): + # Start the service + xbmc.log("Abort requested while waiting. Emby for kodi not started.") +else: + Service().ServiceEntryPoint() \ No newline at end of file