diff --git a/jellyfin_kodi/database/__init__.py b/jellyfin_kodi/database/__init__.py index d8c30650..2e51357c 100644 --- a/jellyfin_kodi/database/__init__.py +++ b/jellyfin_kodi/database/__init__.py @@ -263,6 +263,10 @@ def reset_kodi(): if name not in ["version", "videoversiontype"]: videodb.cursor.execute("DELETE FROM " + name) + # Delete the custom videoversiontype entries + if name == "videoversiontype": + videodb.cursor.execute("DELETE from videoversiontype WHERE id > 40800 AND owner = 1") + if settings("enableMusic.bool") or dialog("yesno", "{jellyfin}", translate(33162)): with Database("music") as musicdb: @@ -427,3 +431,17 @@ def get_item(kodi_id, media): return return item + +def get_item_by_file_id(file_id, media): + """Get jellyfin item based on kodi file id and media.""" + with Database("jellyfin") as jellyfindb: + item = jellyfin_db.JellyfinDatabase(jellyfindb.cursor).get_item_by_file_id( + file_id, media + ) + + if not item: + LOG.debug("Not an jellyfin item") + + return + + return item diff --git a/jellyfin_kodi/database/jellyfin_db.py b/jellyfin_kodi/database/jellyfin_db.py index 5201e932..9708aa89 100644 --- a/jellyfin_kodi/database/jellyfin_db.py +++ b/jellyfin_kodi/database/jellyfin_db.py @@ -61,6 +61,15 @@ class JellyfinDatabase: return self.cursor.fetchall() + def get_item_by_file_id(self, *args): + + try: + self.cursor.execute(QU.get_item_by_file_id, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + def get_item_by_kodi_id(self, *args): try: diff --git a/jellyfin_kodi/database/queries.py b/jellyfin_kodi/database/queries.py index 41a59cda..715ba7db 100644 --- a/jellyfin_kodi/database/queries.py +++ b/jellyfin_kodi/database/queries.py @@ -23,7 +23,7 @@ WHERE parent_id = ? AND media_type = ? """ get_item_by_media_folder = """ -SELECT jellyfin_id, jellyfin_type +SELECT jellyfin_id, jellyfin_type, jellyfin_parent_id FROM jellyfin WHERE media_folder = ? """ @@ -39,6 +39,12 @@ FROM jellyfin WHERE jellyfin_id LIKE ? """ get_item_by_wild_obj = ["{Id}"] +get_item_by_file_id = """ +SELECT jellyfin_id, parent_id, media_folder, jellyfin_type, checksum +FROM jellyfin +WHERE kodi_fileid = ? +AND media_type = ? +""" get_item_by_kodi = """ SELECT jellyfin_id, parent_id, media_folder, jellyfin_type, checksum FROM jellyfin diff --git a/jellyfin_kodi/downloader.py b/jellyfin_kodi/downloader.py index 147efece..47fb909d 100644 --- a/jellyfin_kodi/downloader.py +++ b/jellyfin_kodi/downloader.py @@ -328,7 +328,9 @@ class GetItemWorker(threading.Thread): result = self.server.http.request(request, s) for item in result["Items"]: - + # Force Jellyfin to treat version as a movie to get the right metadata + if settings("useVersions") == "true" and item["Type"] == "Video": + item["Type"] = "Movie" if item["Type"] in self.output: self.output[item["Type"]].put(item) except HTTPException as error: diff --git a/jellyfin_kodi/entrypoint/service.py b/jellyfin_kodi/entrypoint/service.py index 8702be88..e1b6da21 100644 --- a/jellyfin_kodi/entrypoint/service.py +++ b/jellyfin_kodi/entrypoint/service.py @@ -80,6 +80,7 @@ class Service(xbmc.Monitor): LOG.info("Platform: %s", settings("platformDetected")) LOG.info("Python Version: %s", sys.version) LOG.info("Using dynamic paths: %s", settings("useDirectPaths") == "0") + LOG.info("Syncing video versions: %s", settings("useVersions") == "true") LOG.info("Log Level: %s", self.settings["log_level"]) verify_kodi_defaults() diff --git a/jellyfin_kodi/full_sync.py b/jellyfin_kodi/full_sync.py index 1448028a..47a72794 100644 --- a/jellyfin_kodi/full_sync.py +++ b/jellyfin_kodi/full_sync.py @@ -359,7 +359,9 @@ class FullSync(object): for x in items: if x[0] not in current and x[1] == "Movie": - obj.remove(x[0]) + # Parent ID tied to main version so check it before removing + if x[2] not in current: + obj.remove(x[0]) @progress() def tvshows(self, library, dialog): diff --git a/jellyfin_kodi/helper/api.py b/jellyfin_kodi/helper/api.py index dfa05a7e..a0689cb2 100644 --- a/jellyfin_kodi/helper/api.py +++ b/jellyfin_kodi/helper/api.py @@ -236,6 +236,11 @@ class API(object): elif self.item["Container"] == "bluray": path = "%s/BDMV/index.bdmv" % path + # Loop through configured path replacements searching for a match prior to slash correction + for local_path in self.path_data.keys(): + if local_path in path: + path = path.replace(local_path, self.path_data[local_path]) + path = path.replace("\\\\", "\\") if "\\" in path: @@ -245,11 +250,6 @@ class API(object): protocol = path.split("://")[0] path = path.replace(protocol, protocol.lower()) - # Loop through configured path replacements searching for a match - for local_path in self.path_data.keys(): - if local_path in path: - path = path.replace(local_path, self.path_data[local_path]) - return path def get_user_artwork(self, user_id): diff --git a/jellyfin_kodi/helper/playutils.py b/jellyfin_kodi/helper/playutils.py index d881101e..d86d6180 100644 --- a/jellyfin_kodi/helper/playutils.py +++ b/jellyfin_kodi/helper/playutils.py @@ -95,7 +95,7 @@ class PlayUtils(object): break - elif not self.is_selection(info) or len(info["MediaSources"]) == 1: + elif not self.is_selection(info) or len(info["MediaSources"]) == 1 or settings("useVersions") == "true": LOG.info("Skip source selection.") sources.append(info["MediaSources"][0]) diff --git a/jellyfin_kodi/jellyfin/api.py b/jellyfin_kodi/jellyfin/api.py index 88087f7a..d40fad5b 100644 --- a/jellyfin_kodi/jellyfin/api.py +++ b/jellyfin_kodi/jellyfin/api.py @@ -142,6 +142,9 @@ class API(object): def get_media_folders(self): return self.users("/Items") + def get_extras(self, item_id): + return self.users("/Items/%s/SpecialFeatures" % item_id) + def get_item(self, item_id): return self.users("/Items/%s" % item_id) diff --git a/jellyfin_kodi/objects/actions.py b/jellyfin_kodi/objects/actions.py index ced4751e..72ce3103 100644 --- a/jellyfin_kodi/objects/actions.py +++ b/jellyfin_kodi/objects/actions.py @@ -3,6 +3,7 @@ from __future__ import division, absolute_import, print_function, unicode_litera ################################################################################################# +import os import threading import sys import json @@ -935,6 +936,29 @@ def on_play(data, server): return item = server.jellyfin.get_item(item[0]) + + # If using Video Versions, need to match the actual file as the kodi_id is always the primary + if settings("useVersions") == "true": + # Addon Mode matches the jellyfin_id's properly automatically + # This may need to be done elsewhere as well until Kodi exposes versions properly + + # Check the playing file against the primary file; if no match get the right one + playing_file = os.path.basename(file) + primary_file = os.path.basename(item["Path"]) + if playing_file != primary_file: + from .kodi import Movies + + # If not, search kodi database for the filename and get kodi fileid + with database.Database("video") as videodb: + path_id = Movies(videodb.cursor).get_path(file.replace(playing_file, "")) + file_id = Movies(videodb.cursor).get_file(path_id, playing_file) + + # Get the proper jellyfin_id for this file + newitem = database.get_item_by_file_id(file_id, media) + + # Get the correct item now with the new id + item = server.jellyfin.get_item(newitem) + item["PlaybackInfo"] = {"Path": file} playutils.set_properties( item, diff --git a/jellyfin_kodi/objects/kodi/kodi.py b/jellyfin_kodi/objects/kodi/kodi.py index 94698267..68776fac 100644 --- a/jellyfin_kodi/objects/kodi/kodi.py +++ b/jellyfin_kodi/objects/kodi/kodi.py @@ -86,21 +86,13 @@ class Kodi(object): def remove_path(self, *args): self.cursor.execute(QU.delete_path, args) - def add_file(self, filename, path_id): + def add_file(self, *args): + file_id = self.get_file(*args) - try: - self.cursor.execute( - QU.get_file, - ( - filename, - path_id, - ), - ) - file_id = self.cursor.fetchone()[0] - except TypeError: + if file_id is None: file_id = self.create_entry_file() - self.cursor.execute(QU.add_file, (file_id, path_id, filename)) + self.cursor.execute(QU.add_file, (file_id,) + args) return file_id @@ -113,6 +105,15 @@ class Kodi(object): if path_id is not None: self.cursor.execute(QU.delete_file_by_path, (path_id,) + args) + def get_file(self, *args): + + try: + self.cursor.execute(QU.get_file, args) + + return self.cursor.fetchone()[0] + except TypeError: + return + def get_filename(self, *args): try: diff --git a/jellyfin_kodi/objects/kodi/movies.py b/jellyfin_kodi/objects/kodi/movies.py index da959806..808093bb 100644 --- a/jellyfin_kodi/objects/kodi/movies.py +++ b/jellyfin_kodi/objects/kodi/movies.py @@ -1,11 +1,13 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function, unicode_literals +import os +import re from sqlite3 import DatabaseError ################################################################################################## -from ...helper import LazyLogger +from ...helper import LazyLogger, settings from .kodi import Kodi from . import queries as QU @@ -63,6 +65,58 @@ class Movies(Kodi): if self.cursor.fetchone()[0] == 1: self.cursor.execute(QU.add_video_version, args) + def update_videoversion(self, *args): + self.cursor.execute(QU.check_video_version) + if self.cursor.fetchone()[0] == 1: + self.cursor.execute(QU.update_video_version, args) + + def check_videoversion(self, *args): + self.cursor.execute(QU.count_video_version, args) + return self.cursor.fetchone()[0] + + def get_or_create_videoversiontype(self, name, filepath, extra=False): + """Retrieve or create a video version type based on the Jellyfin version name or filename.""" + # If versions are disabled, always return the standard edition + if settings("useVersions") != "true": + return 40400 + + # Change itemtype for extras. If other types added in the future, need to adjust. + itemtype = self.itemtype + 1 if extra else self.itemtype + + # Get the filename without extension + filename = os.path.splitext(os.path.basename(filepath))[0] + + # Remove Jellyfin-added suffixes--may need to add others + test_name = re.sub(r"/(3D|DVD|Bluray)$", "", name) + + # If the Jellyfin version name matches the filename completely, a good version name + # wasn't created automatically, so extract it, or set to Standard Edition + if not extra and test_name == filename: + # Check for ' - XXXX' at end of the name to use for version name + match = re.search(r" - (.+)$", name) + if match: + name = match.group(1).strip() + else: + name = None + + # Set Standard Edition for empty names or DVD/Bluray folders + if not name or filename.lower() in ("index", "video_ts"): + return 40400 + + # Remove */3D suffixes that Jellyfin adds (ie '.mvc/3D') as long as 3D in the name already + if '3D' in name[:-2]: + name = re.sub(r'\.(\w{3,4})/3D$', lambda m: ' ' + m.group(1).upper(), name) + + # Check if this version type already exists and return it + self.cursor.execute(QU.get_video_version_type, (name, itemtype,)) + row = self.cursor.fetchone() + if row: + return row[0] + + # Create a new version type and return the id + self.cursor.execute(QU.add_video_version_type, (name, 1, itemtype)) + return self.cursor.lastrowid + def update(self, *args): self.cursor.execute(QU.update_movie, args) @@ -72,7 +126,39 @@ class Movies(Kodi): self.cursor.execute(QU.delete_file, (file_id,)) self.cursor.execute(QU.check_video_version) if self.cursor.fetchone()[0] == 1: + # Cleanup version types + versions = self.get_videoversions(kodi_id) + type_id = next((row[0] for row in versions if row[1] == file_id), None) self.cursor.execute(QU.delete_video_version, (file_id,)) + self.delete_unused_version_type(type_id) + + # Remove all other versions; Jellyfin creates a new base entry if other versions are left + for row in versions: + self.delete_video_version(row[1], row[0]) + + def delete_video_version(self, file_id, type_id): + """Remove video version file and cleanup version type if unused.""" + self.cursor.execute(QU.delete_file, (file_id,)) + self.cursor.execute(QU.delete_video_version, (file_id,)) + self.delete_unused_version_type(type_id) + + def delete_unused_version_type(self, type_id): + """Delete video version type if no references exist, and its not a builtin type.""" + if type_id and type_id > 40800: + self.cursor.execute(QU.count_video_version_type, (type_id,)) + if self.cursor.fetchone()[0] == 0: + self.cursor.execute(QU.delete_video_version_type, (type_id,)) + + def get_videoversions(self, kodi_id): + self.cursor.execute(QU.get_video_versions, (kodi_id,)) + return self.cursor.fetchall() + + def check_movie_file_primary(self, kodi_id, file_id): + """Return True if the movie row with idMovie matches the provided idFile.""" + self.cursor.execute(QU.check_movie_file_primary, (kodi_id, file_id)) + row = self.cursor.fetchone() + + return row is not None def get_rating_id(self, *args): diff --git a/jellyfin_kodi/objects/kodi/queries.py b/jellyfin_kodi/objects/kodi/queries.py index 86b0b8ba..0ebb84a8 100644 --- a/jellyfin_kodi/objects/kodi/queries.py +++ b/jellyfin_kodi/objects/kodi/queries.py @@ -70,12 +70,13 @@ FROM files WHERE idPath = ? AND strFilename = ? """ -get_file_obj = ["{FileId}"] +get_file_obj = ["{PathId}", "{Filename}"] get_filename = """ SELECT strFilename FROM files WHERE idFile = ? """ +get_filename_obj = ["{FileId}"] get_all_people = """ SELECT name, actor_id FROM actor @@ -416,8 +417,30 @@ add_video_version_obj = [ "{MovieId}", "movie", "{VideoVersionItemType}", - 40400, + "{VideoVersionTypeId}", ] +update_video_version = """ +UPDATE videoversion +SET idMedia = ?, media_type = ?, itemType = ?, idType = ? +WHERE idFile = ? +""" +update_video_version_obj = [ + "{MovieId}", + "movie", + "{VideoVersionItemType}", + "{VideoVersionTypeId}", + "{FileId}", +] +count_video_version = """ +SELECT COUNT(*) FROM videoversion WHERE idFile = ? AND idMedia = ? AND idType = ? +""" +count_video_version_obj = ["{FileId}", "{MovieId}", "{VideoVersionTypeId}"] +count_video_version_type = """ +SELECT COUNT(*) FROM videoversion WHERE idType = ? +""" +get_video_versions = """ +SELECT DISTINCT idType, idFile FROM videoversion WHERE idMedia = ? +""" get_videoversion_itemtype = """ SELECT itemType FROM videoversiontype WHERE id = ? """ @@ -425,6 +448,18 @@ get_videoversion_itemtype_obj = ["{VideoVersionId}"] check_video_version = """ SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name='videoversion' """ +get_video_version_type = """ +SELECT id FROM videoversiontype WHERE name = ? and itemType = ? +""" +add_video_version_type = """ +INSERT INTO videoversiontype(name, owner, itemType) VALUES (?, ?, ?) +""" +get_max_video_version_type = """ +SELECT MAX(id) FROM videoversiontype +""" +check_movie_file_primary = """ +SELECT 1 FROM movie WHERE idMovie = ? AND idFile = ? LIMIT 1 +""" add_musicvideo = """ INSERT INTO musicvideo(idMVideo, idFile, c00, c04, c05, c06, c07, c08, c09, c10, c11, c12, premiered) @@ -801,6 +836,11 @@ delete_video_version = """ DELETE FROM videoversion WHERE idFile = ? """ +delete_video_version_type = """ +DELETE FROM videoversiontype +WHERE id = ? +AND owner = 1 +""" delete_set = """ DELETE FROM sets WHERE idSet = ? diff --git a/jellyfin_kodi/objects/movies.py b/jellyfin_kodi/objects/movies.py index e155af9e..f989761a 100644 --- a/jellyfin_kodi/objects/movies.py +++ b/jellyfin_kodi/objects/movies.py @@ -16,6 +16,7 @@ from ..helper import ( jellyfin_item, values, Local, + settings, ) from ..helper import LazyLogger from ..helper.utils import find_library @@ -66,6 +67,11 @@ class Movies(KodiDb): obj["PathId"] = e_item[2] obj["LibraryId"] = e_item[6] obj["LibraryName"] = self.jellyfin_db.get_view_name(obj["LibraryId"]) + + if settings("useVersions") == "true": + # Only process primary movie files, not versions and extras; those are processed in add_versions. + if not self.check_movie_file_primary(obj["MovieId"], obj["FileId"]): + return except TypeError: update = False LOG.debug("MovieId %s not found", obj["Id"]) @@ -127,6 +133,7 @@ class Movies(KodiDb): tags.append("Favorite movies") obj["Tags"] = tags + obj["media_sources"] = item.get("MediaSources") if update: self.movie_update(obj) @@ -142,10 +149,80 @@ class Movies(KodiDb): 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.artwork.add(obj["Artwork"], obj["FileId"], "videoversion") self.item_ids.append(obj["Id"]) + self.current_type_ids = set() + self.add_versions(API, obj) + self.add_versions(API, obj, True) # Add extras as versions + self.cleanup_versions(obj) return not update + def add_versions(self, API, obj, extra=False): + """Add all additional media sources as Kodi versions.""" + if settings("useVersions") != "true" or (extra and settings("useExtras") != "true"): + return + + sources = self.server.jellyfin.get_extras(obj["Id"]) if extra else obj["media_sources"] + for source in sources: + if obj["Id"] == source["Id"]: + # Found primary version, so skip + continue + + # Extras already in the right format from get_extras, but versions need to be pulled + jfitem = source if extra else self.server.jellyfin.get_item(source["Id"]) + version = self.objects.map(jfitem, "Movie") + version["MovieId"] = obj["MovieId"] + version["LibraryId"] = obj["LibraryId"] + version["JellyfinParentId"] = obj["Id"] + + # Version specific metadata + version["DateAdded"] = Local(version["DateAdded"]).split(".")[0].replace("T", " ") + version["Resume"] = API.adjust_resume((version["Resume"] or 0) / 10000000.0) + version["Runtime"] = round(float((version["Runtime"] or 0) / 10000000.0), 6) + version["PlayCount"] = API.get_playcount(version["Played"], version["PlayCount"]) + version["Video"] = API.video_streams(version["Video"] or [], version["Container"]) + version["Audio"] = API.audio_streams(version["Audio"] or []) + version["Streams"] = API.media_streams(version["Video"], version["Audio"], version["Subtitles"]) + version["Artwork"] = API.get_all_artwork(self.objects.map(jfitem, "Artwork")) + if version["DatePlayed"]: + version["DatePlayed"] = Local(version["DatePlayed"]).split(".")[0].replace("T", " ") + + version["Path"] = API.get_file_path(version["Path"]) + self.get_path_filename(version) + version["PathId"] = self.add_path(*values(version, QU.add_path_obj)) + + # Find the correct version name for this source + version_type_id = self.get_or_create_videoversiontype(source.get("Name"), version["SourceFilename"], extra) + version["VideoVersionTypeId"] = version_type_id + version["VideoVersionItemType"] = self.itemtype + 1 if extra else self.itemtype + self.current_type_ids.add(version_type_id) + + version["FileId"] = self.get_file(*values(version, QU.get_file_obj)) + if version["FileId"]: + # Version already exists + self.update_videoversion(*values(version, QU.update_video_version_obj)) + self.jellyfin_db.update_reference(*values(version, QUEM.update_reference_obj)) + else: + # Add the version file and version type + version["FileId"] = self.add_file(*values(version, QU.add_file_obj)) + self.add_videoversion(*values(version, QU.add_video_version_obj)) + self.jellyfin_db.add_reference(*values(version, QUEM.add_reference_movie_obj)) + + self.update_file(*values(version, QU.update_file_obj)) + self.add_playstate(*values(version, QU.add_bookmark_obj)) + self.add_streams(*values(version, QU.add_streams_obj)) + self.artwork.add(version["Artwork"], version["FileId"], "videoversion") + + def cleanup_versions(self, obj): + """Cleanup versions that are no longer in Jellyfin""" + versions = self.get_videoversions(obj["MovieId"]) + for row in versions: + type_id = row[0] + file_id = row[1] + if file_id != obj["FileId"] and type_id not in self.current_type_ids: + self.delete_video_version(file_id, type_id) + def movie_add(self, obj): """Add object to kodi.""" obj["RatingId"] = self.create_entry_rating() @@ -158,6 +235,15 @@ class Movies(KodiDb): obj["FileId"] = self.add_file(*values(obj, QU.add_file_obj)) obj["VideoVersionItemType"] = self.itemtype + version_name = None + for source in obj["media_sources"]: + # First media source isn't always the main version, so find the correct version name for the primary + if obj["Id"] == source["Id"]: + version_name = source.get("Name") + break + version_type_id = self.get_or_create_videoversiontype(version_name, obj["SourceFilename"]) + obj["VideoVersionTypeId"] = version_type_id + 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)) @@ -217,6 +303,7 @@ class Movies(KodiDb): if "\\" in obj["Path"] else obj["Path"].rsplit("/", 1)[1] ) + obj["SourceFilename"] = obj["Filename"] if self.direct_path: @@ -225,18 +312,20 @@ class Movies(KodiDb): obj["Path"] = obj["Path"].replace(obj["Filename"], "") + sl = "\\" if "\\" in obj["Path"] else "/" """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["Path"] = obj["Path"] + obj["Filename"] + sl + "VIDEO_TS" + sl 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["Path"] = obj["Path"] + obj["Filename"] + sl + "BDMV" + sl obj["Filename"] = "index.bdmv" LOG.debug("Bluray directory %s", obj["Path"]) + obj["SourceFilename"] = obj["Filename"] else: obj["Path"] = "plugin://plugin.video.jellyfin/%s/" % obj["LibraryId"] params = { diff --git a/jellyfin_kodi/objects/tvshows.py b/jellyfin_kodi/objects/tvshows.py index ee6dbd7c..d76f1cd8 100644 --- a/jellyfin_kodi/objects/tvshows.py +++ b/jellyfin_kodi/objects/tvshows.py @@ -616,7 +616,7 @@ class TVShows(KodiDb): temp_obj = dict(obj) temp_obj["Filename"] = self.get_filename( - *values(temp_obj, QU.get_file_obj) + *values(temp_obj, QU.get_filename_obj) ) temp_obj["Path"] = "plugin://plugin.video.jellyfin/" self.remove_file(*values(temp_obj, QU.delete_file_obj)) @@ -625,7 +625,7 @@ class TVShows(KodiDb): temp_obj = dict(obj) temp_obj["Filename"] = self.get_filename( - *values(temp_obj, QU.get_file_obj) + *values(temp_obj, QU.get_filename_obj) ) temp_obj["PathId"] = self.get_path("plugin://plugin.video.jellyfin/") temp_obj["FileId"] = self.add_file(*values(temp_obj, QU.add_file_obj)) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 62ddb511..ff121ea9 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -1257,3 +1257,10 @@ msgctxt "#33261" msgid "Off" msgstr "Off" +msgctxt "#30900" +msgid "Sync video versions" +msgstr "Sync video versions" + +msgctxt "#30901" +msgid "Sync video extras" +msgstr "Sync video extras" diff --git a/resources/language/resource.language.en_us/strings.po b/resources/language/resource.language.en_us/strings.po index 84f63066..5a4a742e 100644 --- a/resources/language/resource.language.en_us/strings.po +++ b/resources/language/resource.language.en_us/strings.po @@ -1180,3 +1180,11 @@ msgstr "16.0 Mbps" msgctxt "#33239" msgid "96" msgstr "96" + +msgctxt "#30900" +msgid "Sync video versions" +msgstr "Sync video versions" + +msgctxt "#30901" +msgid "Sync video extras" +msgstr "Sync video extras" diff --git a/resources/settings.xml b/resources/settings.xml index bd83d8e4..fe01420c 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -108,6 +108,19 @@ + + 0 + false + + + + 0 + false + + true + + +