This commit is contained in:
midfords 2026-04-27 15:35:39 +08:00 committed by GitHub
commit 792d04813f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 338 additions and 5 deletions

5
.gitignore vendored
View file

@ -75,4 +75,9 @@ pyinstrument/
# Now managed by templates
addon.xml
*.zip
*.db
*.db-shm
*.db-wal
*.log

View file

@ -28,6 +28,7 @@ FROM jellyfin
WHERE media_folder = ?
"""
get_item_by_parent_movie_obj = ["{KodiId}", "movie"]
get_item_by_parent_extra_obj = ["{KodiId}", "extra"]
get_item_by_parent_tvshow_obj = ["{ParentId}", "tvshow"]
get_item_by_parent_season_obj = ["{ParentId}", "season"]
get_item_by_parent_episode_obj = ["{ParentId}", "episode"]
@ -106,6 +107,18 @@ add_reference_movie_obj = [
"{LibraryId}",
"{JellyfinParentId}",
]
add_reference_extra_obj = [
"{Id}",
"{ExtraId}",
"{FileId}",
"{PathId}",
"Extra",
"extra",
"{MovieId}",
"{Checksum}",
"{LibraryId}",
"{JellyfinParentId}",
]
add_reference_boxset_obj = [
"{Id}",
"{SetId}",

View file

@ -95,6 +95,21 @@ def get_movies_by_boxset(boxset_id):
yield items
def get_extra_by_movie(movie_id):
query = {
"url": "Items/%s/SpecialFeatures" % movie_id,
"params": {
"EnableUserData": True,
"EnableImages": True,
"UserId": "{UserId}",
"Fields": api.info(),
},
}
for items in _get_items(query):
yield items
def get_episode_by_show(show_id):
query = {
@ -213,9 +228,16 @@ def _get_items(query, server_id=None):
test_params["Limit"] = 1
test_params["EnableTotalRecordCount"] = True
items["TotalRecordCount"] = _get(url, test_params, server_id=server_id)[
"TotalRecordCount"
]
response = _get(url, test_params, server_id=server_id)
if "TotalRecordCount" in response:
items["TotalRecordCount"] = response["TotalRecordCount"]
elif isinstance(response, list):
yield {
"Items": response,
"TotalRecordCount": len(response),
"StartIndex": 0,
}
except Exception as error:
LOG.exception(

View file

@ -341,6 +341,8 @@ class FullSync(object):
message=movie["Name"],
)
obj.movie(movie)
obj.add_extras(movie)
processed_ids.append(movie["Id"])
with self.video_database_locks() as (videodb, jellyfindb):

View file

@ -740,6 +740,8 @@ class UpdateWorker(threading.Thread):
LOG.debug("{} - {}".format(item["Type"], item["Name"]))
if item["Type"] == "Movie":
movies.movie(item)
movies.remove_extras(item)
movies.add_extras(item)
elif item["Type"] == "BoxSet":
movies.boxset(item)
elif item["Type"] == "Series":

View file

@ -80,6 +80,22 @@ class Artwork(object):
elif artwork.get(art):
self.update(*(artwork[art],) + args + (KODI[art],))
def add_extra(self, artwork, *args):
"""Add all artworks."""
KODI = {
"Thumb": ["landscape", "thumb", "poster"],
"Primary": ["landscape", "thumb", "poster"],
}
for art in KODI:
if art == "Primary":
for kodi_image in KODI["Primary"]:
self.update(*(artwork["Primary"],) + args + (kodi_image,))
elif art == "Thumb":
for kodi_image in KODI["Thumb"]:
self.update(*(artwork["Thumb"],) + args + (kodi_image,))
def delete(self, *args):
"""Delete artwork from kodi database"""
self.cursor.execute(QU.delete_art, args)

View file

@ -47,14 +47,25 @@ class Movies(Kodi):
return self.cursor.fetchone()[0] + 1
def get(self, *args):
def create_entry_extra(self):
self.cursor.execute(QU.create_extra)
return self.cursor.fetchone()[0] + 1
def get(self, *args):
try:
self.cursor.execute(QU.get_movie, args)
return self.cursor.fetchone()[0]
except TypeError:
return
def get_extra(self, *args):
try:
self.cursor.execute(QU.get_extra, args)
return self.cursor.fetchone()[0]
except TypeError:
return
def add(self, *args):
self.cursor.execute(QU.add_movie, args)
@ -66,6 +77,18 @@ class Movies(Kodi):
def update(self, *args):
self.cursor.execute(QU.update_movie, args)
def update_extra(self, *args):
self.cursor.execute(QU.update_video_version_extra, args)
def update_video_version_type_extra(self, *args):
self.cursor.execute(QU.update_video_version_type_extra, args)
def delete_extra(self, kodi_id, file_id):
self.cursor.execute(QU.delete_video_version_extra, (kodi_id,))
self.cursor.execute(QU.delete_video_version_type_extra, (kodi_id,))
self.cursor.execute(QU.delete_file, (file_id,))
self.cursor.execute(QU.delete_streams, (file_id,))
def delete(self, kodi_id, file_id):
self.cursor.execute(QU.delete_movie, (kodi_id,))

View file

@ -40,6 +40,11 @@ create_movie = """
SELECT coalesce(max(idMovie), 0)
FROM movie
"""
# Kodi extras start at id 40801
create_extra = """
SELECT coalesce(max(id), 40800)
FROM videoversiontype
"""
create_musicvideo = """
SELECT coalesce(max(idMVideo), 0)
FROM musicvideo
@ -120,6 +125,12 @@ FROM movie
WHERE idMovie = ?
"""
get_movie_obj = ["{MovieId}"]
get_extra = """
SELECT *
FROM videoversiontype
WHERE id = ?
"""
get_extra_obj = ["{ExtraId}"]
get_rating = """
SELECT rating_id
FROM rating
@ -418,6 +429,17 @@ add_video_version_obj = [
"{VideoVersionItemType}",
40400,
]
update_video_version_extra = """
INSERT OR REPLACE INTO videoversion(idFile, idMedia, media_type, itemType, idType)
VALUES (?, ?, ?, ?, ?)
"""
update_video_version_extra_obj = [
"{FileId}",
"{MovieId}",
"movie",
"1",
"{VideoVersionIdType}",
]
get_videoversion_itemtype = """
SELECT itemType FROM videoversiontype WHERE id = ?
"""
@ -653,6 +675,16 @@ update_unique_id_episode_obj = [
"{ProviderName}",
"{Unique}",
]
update_video_version_type_extra = """
INSERT OR REPLACE INTO videoversiontype(id, name, owner, itemType)
VALUES (?, ?, ?, ?)
"""
update_video_version_type_extra_obj = [
"{VideoVersionIdType}",
"{Title}",
"1",
"1",
]
update_country = """
INSERT OR REPLACE INTO country_link(country_id, media_id, media_type)
VALUES (?, ?, ?)
@ -797,6 +829,7 @@ DELETE FROM movie
WHERE idMovie = ?
"""
delete_movie_obj = ["{KodiId}", "{FileId}"]
delete_extra_obj = ["{KodiId}", "{FileId}"]
delete_video_version = """
DELETE FROM videoversion
WHERE idFile = ?
@ -840,6 +873,16 @@ WHERE media_id = ?
AND media_type = ?
AND type LIKE ?
"""
delete_video_version_extra = """
DELETE FROM videoversion
WHERE idType = ?
AND itemType = 1
"""
delete_video_version_type_extra = """
DELETE FROM videoversiontype
WHERE id = ?
AND itemType = 1
"""
get_missing_versions = """
SELECT idFile,idMovie
FROM movie

View file

@ -189,6 +189,172 @@ class Movies(KodiDb):
obj["Title"],
)
def add_extras(self, item):
for extras in server.get_extra_by_movie(item["Id"]):
for extra in extras["Items"]:
LOG.debug("Extras: {}".format(extra))
extra["MovieId"] = item["Id"]
if extra.get("Path"):
self.extra(extra)
@stop
@jellyfin_item
def extra(self, item, e_item):
"""If item does not exist, entry will be added.
If item exists, entry will be updated.
Create additional entry for widgets.
"""
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, "Extra")
update = True
if "Location" in obj and obj["Location"] == "Virtual":
LOG.info("Skipping virtual item %s: %s", obj["Title"], obj["Id"])
return
try:
obj["ExtraId"] = 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("ExtraId %s not found", obj["Id"])
temp_obj = dict(obj)
temp_obj["Id"] = obj["MovieId"]
library = self.library or find_library(self.server, temp_obj)
if not library:
# This item doesn't belong to a whitelisted library
return
obj["ExtraId"] = self.create_entry_extra()
obj["LibraryId"] = library["Id"]
obj["LibraryName"] = library["Name"]
else:
if self.get_extra(*values(obj, QU.get_extra_obj)) is None:
update = False
LOG.info(
"ExtraId %s missing from kodi. repairing the entry.",
obj["ExtraId"],
)
try:
temp_obj = dict(obj)
temp_obj["Id"] = obj["MovieId"]
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 extra %s to movie.", temp_obj["Title"])
return update
obj["Path"] = API.get_file_path(obj["Path"])
obj["Index"] = obj["Index"] or -1
obj["Resume"] = API.adjust_resume((obj["Resume"] or 0) / 10000000.0)
obj["Runtime"] = round(float((obj["Runtime"] or 0) / 10000000.0), 6)
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, "ArtworkParent"), True
)
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)
obj["MovieId"] = temp_obj["MovieId"]
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 extras")
obj["Tags"] = tags
self.extra_add(obj)
self.update_file(*values(obj, QU.update_file_obj))
self.add_playstate(*values(obj, QU.add_bookmark_obj))
self.add_streams(*values(obj, QU.add_streams_obj))
self.artwork.add_extra(obj["Artwork"], obj["FileId"], "videoversion")
self.item_ids.append(obj["Id"])
return not update
def extra_add(self, obj):
"""Add object to kodi."""
obj["PathId"] = self.add_path(*values(obj, QU.add_path_obj))
obj["FileId"] = self.add_file(*values(obj, QU.add_file_obj))
obj["VideoVersionIdType"] = obj["ExtraId"]
self.update_extra(*values(obj, QU.update_video_version_extra_obj))
self.update_video_version_type_extra(
*values(obj, QU.update_video_version_type_extra_obj)
)
self.jellyfin_db.add_reference(*values(obj, QUEM.add_reference_extra_obj))
LOG.debug(
"ADD extra [%s/%s/%s] %s: %s",
obj["PathId"],
obj["FileId"],
obj["ExtraId"],
obj["Id"],
obj["Title"],
)
@stop
@jellyfin_item
def remove_extras(self, item, e_item):
"""Remove movieid, fileid, jellyfin reference.
Remove artwork, boxset
"""
try:
item["KodiId"] = e_item[0]
item["FileId"] = e_item[1]
item["Media"] = e_item[4]
except TypeError:
return
for extra in self.jellyfin_db.get_item_by_parent_id(
*values(item, QUEM.get_item_by_parent_extra_obj)
):
temp_obj = dict()
temp_obj["Id"] = extra[0]
temp_obj["KodiId"] = extra[1]
temp_obj["FileId"] = extra[2]
self.delete_extra(*values(temp_obj, QU.delete_extra_obj))
self.jellyfin_db.remove_item(*values(temp_obj, QUEM.delete_item_obj))
LOG.debug(
"DELETE Extra [%s/%s] %s",
temp_obj["FileId"],
temp_obj["KodiId"],
temp_obj["Id"],
)
def trailer(self, obj):
try:

View file

@ -54,6 +54,47 @@
"DatePlayed": "UserData/LastPlayedDate",
"Played": "UserData/Played"
},
"Extra": {
"Id": "Id",
"MovieId": "MovieId",
"Index": "IndexNumber",
"Title": "MediaSources/0/Name",
"SortTitle": "SortName",
"Path": "Path",
"Location": "LocationType",
"Genres": "Genres",
"UniqueId": "ProviderIds/Imdb",
"Rating": "CommunityRating",
"Year": "ProductionYear",
"Premiere": "PremiereDate,ProductionYear",
"Plot": "Overview",
"People": "People",
"Writers": "People:?Type=Writer$Name",
"Directors": "People:?Type=Director$Name",
"Cast": "People:?Type=Actor$Name",
"Tagline": "Taglines/0",
"Mpaa": "OfficialRating",
"Country": "ProductionLocations/0",
"Countries": "ProductionLocations",
"Studios": "Studios:?$Name",
"Studio": "Studios/0/Name",
"Runtime": "RunTimeTicks,CumulativeRunTimeTicks",
"LocalTrailer": "LocalTrailerCount",
"Trailer": "RemoteTrailers/0/Url",
"DateAdded": "DateCreated",
"Played": "UserData/Played",
"PlayCount": "UserData/PlayCount",
"DatePlayed": "UserData/LastPlayedDate",
"Favorite": "UserData/IsFavorite",
"Resume": "UserData/PlaybackPositionTicks",
"Tags": "Tags",
"Subtitles": "MediaSources/0/MediaStreams:?Type=Subtitle$Language",
"Audio": "MediaSources/0/MediaStreams:?Type=Audio",
"Video": "MediaSources/0/MediaStreams:?Type=Video",
"Container": "MediaSources/0/Container",
"JellyfinParentId": "ParentId",
"CriticRating": "CriticRating"
},
"Boxset": {
"Id": "Id",
"Title": "Name",
@ -360,4 +401,4 @@
"rating": "CommunityRating",
"firstaired": "ProductionYear"
}
}
}