# -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function, unicode_literals ################################################################################################## from six.moves.urllib.parse import urlencode from kodi_six.utils import py2_encode import downloader as server from database import jellyfin_db, queries as QUEM from helper import api, stop, validate, validate_bluray_dir, validate_dvd_dir, jellyfin_item, values, Local from helper import LazyLogger from helper.utils import find_library from helper.exceptions import PathValidationException from .obj import Objects from .kodi import Movies as KodiDb, queries as QU ################################################################################################## LOG = LazyLogger(__name__) ################################################################################################## class Movies(KodiDb): def __init__(self, server, jellyfindb, videodb, direct_path, library=None): self.server = server self.jellyfin = jellyfindb self.video = videodb self.direct_path = direct_path self.jellyfin_db = jellyfin_db.JellyfinDatabase(jellyfindb.cursor) self.objects = Objects() self.item_ids = [] self.library = library KodiDb.__init__(self, videodb.cursor) @stop @jellyfin_item def movie(self, item, e_item): ''' If item does not exist, entry will be added. If item exists, entry will be updated. ''' server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address'] API = api.API(item, server_address) obj = self.objects.map(item, 'Movie') update = True try: obj['MovieId'] = e_item[0] obj['FileId'] = e_item[1] obj['PathId'] = e_item[2] obj['LibraryId'] = e_item[6] obj['LibraryName'] = self.jellyfin_db.get_view_name(obj['LibraryId']) except TypeError: update = False LOG.debug("MovieId %s not found", obj['Id']) library = self.library or find_library(self.server, item) if not library: # This item doesn't belong to a whitelisted library return obj['MovieId'] = self.create_entry() obj['LibraryId'] = library['Id'] obj['LibraryName'] = library['Name'] else: if self.get(*values(obj, QU.get_movie_obj)) is None: update = False LOG.info("MovieId %s missing from kodi. repairing the entry.", obj['MovieId']) obj['Path'] = API.get_file_path(obj['Path']) obj['Genres'] = obj['Genres'] or [] obj['Studios'] = [API.validate_studio(studio) for studio in (obj['Studios'] or [])] obj['People'] = obj['People'] or [] obj['Genre'] = " / ".join(obj['Genres']) obj['Writers'] = " / ".join(obj['Writers'] or []) obj['Directors'] = " / ".join(obj['Directors'] or []) obj['Plot'] = API.get_overview(obj['Plot']) obj['Mpaa'] = API.get_mpaa(obj['Mpaa']) obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0) obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6) obj['People'] = API.get_people_artwork(obj['People']) obj['DateAdded'] = Local(obj['DateAdded']).split('.')[0].replace('T', " ") obj['DatePlayed'] = None if not obj['DatePlayed'] else Local(obj['DatePlayed']).split('.')[0].replace('T', " ") obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork')) obj['Video'] = API.video_streams(obj['Video'] or [], obj['Container']) obj['Audio'] = API.audio_streams(obj['Audio'] or []) obj['Streams'] = API.media_streams(obj['Video'], obj['Audio'], obj['Subtitles']) self.get_path_filename(obj) self.trailer(obj) if obj['Countries']: self.add_countries(*values(obj, QU.update_country_obj)) tags = [] tags.extend(obj['Tags'] or []) tags.append(obj['LibraryName']) if obj['Favorite']: tags.append('Favorite movies') obj['Tags'] = tags if update: self.movie_update(obj) else: self.movie_add(obj) self.update_path(*values(obj, QU.update_path_movie_obj)) self.update_file(*values(obj, QU.update_file_obj)) self.add_tags(*values(obj, QU.add_tags_movie_obj)) self.add_genres(*values(obj, QU.add_genres_movie_obj)) self.add_studios(*values(obj, QU.add_studios_movie_obj)) self.add_playstate(*values(obj, QU.add_bookmark_obj)) self.add_people(*values(obj, QU.add_people_movie_obj)) self.add_streams(*values(obj, QU.add_streams_obj)) self.artwork.add(obj['Artwork'], obj['MovieId'], "movie") self.item_ids.append(obj['Id']) return not update def movie_add(self, obj): ''' Add object to kodi. ''' obj['RatingId'] = self.create_entry_rating() self.add_ratings(*values(obj, QU.add_rating_movie_obj)) obj['Unique'] = self.create_entry_unique_id() self.add_unique_id(*values(obj, QU.add_unique_id_movie_obj)) obj['PathId'] = self.add_path(*values(obj, QU.add_path_obj)) obj['FileId'] = self.add_file(*values(obj, QU.add_file_obj)) self.add(*values(obj, QU.add_movie_obj)) self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_movie_obj)) LOG.debug("ADD movie [%s/%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['MovieId'], obj['Id'], obj['Title']) def movie_update(self, obj): ''' Update object to kodi. ''' obj['RatingId'] = self.get_rating_id(*values(obj, QU.get_rating_movie_obj)) self.update_ratings(*values(obj, QU.update_rating_movie_obj)) obj['Unique'] = self.get_unique_id(*values(obj, QU.get_unique_id_movie_obj)) self.update_unique_id(*values(obj, QU.update_unique_id_movie_obj)) self.update(*values(obj, QU.update_movie_obj)) self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj)) LOG.debug("UPDATE movie [%s/%s/%s] %s: %s", obj['PathId'], obj['FileId'], obj['MovieId'], obj['Id'], obj['Title']) def trailer(self, obj): try: if obj['LocalTrailer']: trailer = self.server.jellyfin.get_local_trailers(obj['Id']) obj['Trailer'] = "plugin://plugin.video.jellyfin/trailer?id=%s&mode=play" % trailer[0]['Id'] elif obj['Trailer']: obj['Trailer'] = "plugin://plugin.video.youtube/play/?video_id=%s" % obj['Trailer'].rsplit('=', 1)[1] except Exception as error: LOG.exception("Failed to get trailer: %s", error) obj['Trailer'] = None def get_path_filename(self, obj): ''' Get the path and filename and build it into protocol://path ''' obj['Filename'] = obj['Path'].rsplit('\\', 1)[1] if '\\' in obj['Path'] else obj['Path'].rsplit('/', 1)[1] if self.direct_path: if not validate(obj['Path']): raise PathValidationException("Failed to validate path. User stopped.") obj['Path'] = obj['Path'].replace(obj['Filename'], "") '''check dvd directries and point it to ./VIDEO_TS/VIDEO_TS.IFO''' if validate_dvd_dir(obj['Path'] + obj['Filename']): obj['Path'] = obj['Path'] + obj['Filename'] + '/VIDEO_TS/' obj['Filename'] = 'VIDEO_TS.IFO' LOG.debug("DVD directry %s", obj['Path']) '''check bluray directries and point it to ./BDMV/index.bdmv''' if validate_bluray_dir(obj['Path'] + obj['Filename']): obj['Path'] = obj['Path'] + obj['Filename'] + '/BDMV/' obj['Filename'] = 'index.bdmv' LOG.debug("Bluray directry %s", obj['Path']) else: obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId'] params = { 'filename': py2_encode(obj['Filename'], 'utf-8'), 'id': obj['Id'], 'dbid': obj['MovieId'], 'mode': "play" } obj['Filename'] = "%s?%s" % (obj['Path'], urlencode(params)) @stop @jellyfin_item def boxset(self, item, e_item): ''' If item does not exist, entry will be added. If item exists, entry will be updated. Process movies inside boxset. Process removals from boxset. ''' server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address'] API = api.API(item, server_address) obj = self.objects.map(item, 'Boxset') obj['Overview'] = API.get_overview(obj['Overview']) try: obj['SetId'] = e_item[0] self.update_boxset(*values(obj, QU.update_set_obj)) except TypeError: LOG.debug("SetId %s not found", obj['Id']) obj['SetId'] = self.add_boxset(*values(obj, QU.add_set_obj)) self.boxset_current(obj) obj['Artwork'] = API.get_all_artwork(self.objects.map(item, 'Artwork')) for movie in obj['Current']: temp_obj = dict(obj) temp_obj['Movie'] = movie temp_obj['MovieId'] = obj['Current'][temp_obj['Movie']] self.remove_from_boxset(*values(temp_obj, QU.delete_movie_set_obj)) self.jellyfin_db.update_parent_id(*values(temp_obj, QUEM.delete_parent_boxset_obj)) LOG.debug("DELETE from boxset [%s] %s: %s", temp_obj['SetId'], temp_obj['Title'], temp_obj['MovieId']) self.artwork.add(obj['Artwork'], obj['SetId'], "set") self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_boxset_obj)) LOG.debug("UPDATE boxset [%s] %s", obj['SetId'], obj['Title']) def boxset_current(self, obj): ''' Add or removes movies based on the current movies found in the boxset. ''' try: current = self.jellyfin_db.get_item_id_by_parent_id(*values(obj, QUEM.get_item_id_by_parent_boxset_obj)) movies = dict(current) except ValueError: movies = {} obj['Current'] = movies for all_movies in server.get_movies_by_boxset(obj['Id']): for movie in all_movies['Items']: temp_obj = dict(obj) temp_obj['Title'] = movie['Name'] temp_obj['Id'] = movie['Id'] try: temp_obj['MovieId'] = self.jellyfin_db.get_item_by_id(*values(temp_obj, QUEM.get_item_obj))[0] except TypeError: LOG.info("Failed to process %s to boxset.", temp_obj['Title']) continue if temp_obj['Id'] not in obj['Current']: self.set_boxset(*values(temp_obj, QU.update_movie_set_obj)) self.jellyfin_db.update_parent_id(*values(temp_obj, QUEM.update_parent_movie_obj)) LOG.debug("ADD to boxset [%s/%s] %s: %s to boxset", temp_obj['SetId'], temp_obj['MovieId'], temp_obj['Title'], temp_obj['Id']) else: obj['Current'].pop(temp_obj['Id']) def boxsets_reset(self): ''' Special function to remove all existing boxsets. ''' boxsets = self.jellyfin_db.get_items_by_media('set') for boxset in boxsets: self.remove(boxset[0]) @stop @jellyfin_item def userdata(self, item, e_item): ''' This updates: Favorite, LastPlayedDate, Playcount, PlaybackPositionTicks Poster with progress bar ''' server_address = self.server.auth.get_server_info(self.server.auth.server_id)['address'] API = api.API(item, server_address) obj = self.objects.map(item, 'MovieUserData') try: obj['MovieId'] = e_item[0] obj['FileId'] = e_item[1] except TypeError: return obj['Resume'] = API.adjust_resume((obj['Resume'] or 0) / 10000000.0) obj['Runtime'] = round(float((obj['Runtime'] or 0) / 10000000.0), 6) obj['PlayCount'] = API.get_playcount(obj['Played'], obj['PlayCount']) if obj['DatePlayed']: obj['DatePlayed'] = Local(obj['DatePlayed']).split('.')[0].replace('T', " ") if obj['Favorite']: self.get_tag(*values(obj, QU.get_tag_movie_obj)) else: self.remove_tag(*values(obj, QU.delete_tag_movie_obj)) LOG.debug("New resume point %s: %s", obj['Id'], obj['Resume']) self.add_playstate(*values(obj, QU.add_bookmark_obj)) self.jellyfin_db.update_reference(*values(obj, QUEM.update_reference_obj)) LOG.debug("USERDATA movie [%s/%s] %s: %s", obj['FileId'], obj['MovieId'], obj['Id'], obj['Title']) @stop @jellyfin_item def remove(self, item_id, e_item): ''' Remove movieid, fileid, jellyfin reference. Remove artwork, boxset ''' obj = {'Id': item_id} try: obj['KodiId'] = e_item[0] obj['FileId'] = e_item[1] obj['Media'] = e_item[4] except TypeError: return self.artwork.delete(obj['KodiId'], obj['Media']) if obj['Media'] == 'movie': self.delete(*values(obj, QU.delete_movie_obj)) elif obj['Media'] == 'set': for movie in self.jellyfin_db.get_item_by_parent_id(*values(obj, QUEM.get_item_by_parent_movie_obj)): temp_obj = dict(obj) temp_obj['MovieId'] = movie[1] temp_obj['Movie'] = movie[0] self.remove_from_boxset(*values(temp_obj, QU.delete_movie_set_obj)) self.jellyfin_db.update_parent_id(*values(temp_obj, QUEM.delete_parent_boxset_obj)) self.delete_boxset(*values(obj, QU.delete_set_obj)) self.jellyfin_db.remove_item(*values(obj, QUEM.delete_item_obj)) LOG.debug("DELETE %s [%s/%s] %s", obj['Media'], obj['FileId'], obj['KodiId'], obj['Id'])