mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2025-10-09 10:22:05 +00:00
Retrieves video version item type through a query for setting default values, enhancing flexibility and reducing hardcoding. Replaces static item type in video version insertion with dynamic retrieval, improving maintainability.
430 lines
15 KiB
Python
430 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
from __future__ import division, absolute_import, print_function, unicode_literals
|
|
|
|
##################################################################################################
|
|
|
|
from urllib.parse import urlencode
|
|
|
|
from .. 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"])
|
|
if obj["Premiere"] is not None:
|
|
obj["Premiere"] = str(obj["Premiere"]).split("T")[0]
|
|
|
|
self.get_path_filename(obj)
|
|
self.trailer(obj)
|
|
|
|
if obj["Countries"]:
|
|
self.add_countries(*values(obj, QU.update_country_obj))
|
|
|
|
tags = list(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))
|
|
obj["VideoVersionItemType"] = self.itemtype
|
|
|
|
self.add(*values(obj, QU.add_movie_obj))
|
|
self.add_videoversion(*values(obj, QU.add_video_version_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 directories 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 directory %s", obj["Path"])
|
|
|
|
"""check bluray directories 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 directory %s", obj["Path"])
|
|
|
|
else:
|
|
obj["Path"] = "plugin://plugin.video.jellyfin/%s/" % obj["LibraryId"]
|
|
params = {
|
|
"filename": obj["Filename"],
|
|
"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"],
|
|
)
|