tmdb / filename / loop MWE
This commit is contained in:
47
bin/ffx.py
47
bin/ffx.py
@@ -6,9 +6,13 @@ from ffx.file_properties import FileProperties
|
|||||||
|
|
||||||
from ffx.ffx_app import FfxApp
|
from ffx.ffx_app import FfxApp
|
||||||
from ffx.ffx_controller import FfxController
|
from ffx.ffx_controller import FfxController
|
||||||
|
from ffx.show_controller import ShowController
|
||||||
|
from ffx.tmdb_controller import TmdbController
|
||||||
|
|
||||||
from ffx.database import databaseContext
|
from ffx.database import databaseContext
|
||||||
|
|
||||||
from ffx.track_type import TrackType
|
from ffx.track_type import TrackType
|
||||||
|
from ffx.video_encoder import VideoEncoder
|
||||||
|
|
||||||
|
|
||||||
VERSION='0.1.0'
|
VERSION='0.1.0'
|
||||||
@@ -194,7 +198,9 @@ def convert(ctx,
|
|||||||
|
|
||||||
context = ctx.obj
|
context = ctx.obj
|
||||||
|
|
||||||
context['dry_run'] = dry_run
|
context['dry_run'] = True # dry_run
|
||||||
|
|
||||||
|
context['video_encoder'] = VideoEncoder.fromLabel(video_encoder)
|
||||||
|
|
||||||
context['jellyfin'] = jellyfin
|
context['jellyfin'] = jellyfin
|
||||||
context['tmdb'] = tmdb
|
context['tmdb'] = tmdb
|
||||||
@@ -209,8 +215,8 @@ def convert(ctx,
|
|||||||
qualityTokens = quality.split(',')
|
qualityTokens = quality.split(',')
|
||||||
q_list = [q for q in qualityTokens if q.isnumeric()]
|
q_list = [q for q in qualityTokens if q.isnumeric()]
|
||||||
|
|
||||||
# click.echo(f"Qualities: {q_list}")
|
click.echo(f"Qualities: {q_list}")
|
||||||
#
|
|
||||||
context['bitrates'] = {}
|
context['bitrates'] = {}
|
||||||
context['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k"
|
context['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k"
|
||||||
context['bitrates']['ac3'] = str(ac3_bitrate) if str(ac3_bitrate).endswith('k') else f"{ac3_bitrate}k"
|
context['bitrates']['ac3'] = str(ac3_bitrate) if str(ac3_bitrate).endswith('k') else f"{ac3_bitrate}k"
|
||||||
@@ -236,7 +242,8 @@ def convert(ctx,
|
|||||||
# # Parse subtitle files
|
# # Parse subtitle files
|
||||||
#
|
#
|
||||||
# availableFileSubtitleDescriptors = searchSubtitleFiles(subtitle_directory, subtitle_prefix) if context['import_subtitles'] else []
|
# availableFileSubtitleDescriptors = searchSubtitleFiles(subtitle_directory, subtitle_prefix) if context['import_subtitles'] else []
|
||||||
|
# sc = ShowController(context)
|
||||||
|
tc = TmdbController()
|
||||||
|
|
||||||
existingSourcePaths = [p for p in paths if os.path.isfile(p) and p.split('.')[-1] in FfxController.INPUT_FILE_EXTENSIONS]
|
existingSourcePaths = [p for p in paths if os.path.isfile(p) and p.split('.')[-1] in FfxController.INPUT_FILE_EXTENSIONS]
|
||||||
click.echo(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs")
|
click.echo(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs")
|
||||||
@@ -316,12 +323,32 @@ def convert(ctx,
|
|||||||
|
|
||||||
audioTokens = fc.generateAudioEncodingTokens()
|
audioTokens = fc.generateAudioEncodingTokens()
|
||||||
click.echo(f"Audio Tokens: {audioTokens}")
|
click.echo(f"Audio Tokens: {audioTokens}")
|
||||||
|
|
||||||
|
tmdbFileBasename = ''
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
# Case pattern matching
|
# Case pattern matching
|
||||||
|
|
||||||
targetMediaDescriptor = currentPattern.getMediaDescriptor() if currentPattern is not None else None
|
targetMediaDescriptor = currentPattern.getMediaDescriptor()
|
||||||
|
currentShowDescriptor = currentPattern.getShowDescriptor()
|
||||||
|
|
||||||
|
label = currentShowDescriptor.getFilenamePrefix()
|
||||||
|
|
||||||
|
tmdbResult = tc.queryTmdb(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode())
|
||||||
|
|
||||||
|
# click.echo(f"{tmdbResult}")
|
||||||
|
|
||||||
|
tmdbFileBasename = tc.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(),
|
||||||
|
tmdbResult['name'],
|
||||||
|
mediaFileProperties.getSeason(),
|
||||||
|
mediaFileProperties.getEpisode(),
|
||||||
|
currentShowDescriptor.getIndexSeasonDigits(),
|
||||||
|
currentShowDescriptor.getIndexEpisodeDigits(),
|
||||||
|
currentShowDescriptor.getIndicatorSeasonDigits(),
|
||||||
|
currentShowDescriptor.getIndicatorEpisodeDigits())
|
||||||
|
|
||||||
|
click.echo(f"tmdbFileBasename={tmdbFileBasename}")
|
||||||
|
|
||||||
if context['import_subtitles']:
|
if context['import_subtitles']:
|
||||||
targetMediaDescriptor.importSubtitles(context['subtitle_directory'], context['subtitle_prefix'])
|
targetMediaDescriptor.importSubtitles(context['subtitle_directory'], context['subtitle_prefix'])
|
||||||
@@ -349,7 +376,15 @@ def convert(ctx,
|
|||||||
click.echo(f"\nRunning job {jobIndex} file={sourcePath} q={q}")
|
click.echo(f"\nRunning job {jobIndex} file={sourcePath} q={q}")
|
||||||
jobIndex += 1
|
jobIndex += 1
|
||||||
|
|
||||||
|
targetFilename = tmdbFileBasename if tmdbFileBasename else mediaFileProperties.assembleTargetFilename(label, q if len(q_list) > 1 else -1)
|
||||||
|
targetPath = os.path.join(sourceDirectory, targetFilename)
|
||||||
|
|
||||||
|
fc.runJob(sourcePath,
|
||||||
|
targetPath,
|
||||||
|
context['video_encoder'],
|
||||||
|
q)
|
||||||
|
|
||||||
|
|
||||||
# #click.confirm('Warning! This file is not compliant to the defined source schema! Do you want to continue?', abort=True)
|
# #click.confirm('Warning! This file is not compliant to the defined source schema! Do you want to continue?', abort=True)
|
||||||
|
|
||||||
endTime = time.perf_counter()
|
endTime = time.perf_counter()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class FfxController():
|
|||||||
|
|
||||||
TEMP_FILE_NAME = "ffmpeg2pass-0.log"
|
TEMP_FILE_NAME = "ffmpeg2pass-0.log"
|
||||||
|
|
||||||
DEFAULT_VIDEO_ENCODER = 'vp9'
|
DEFAULT_VIDEO_ENCODER = VideoEncoder.VP9.label()
|
||||||
|
|
||||||
DEFAULT_QUALITY = 23
|
DEFAULT_QUALITY = 23
|
||||||
DEFAULT_AV1_PRESET = 5
|
DEFAULT_AV1_PRESET = 5
|
||||||
@@ -409,7 +409,7 @@ class FfxController():
|
|||||||
+ self.generateDispositionTokens())
|
+ self.generateDispositionTokens())
|
||||||
|
|
||||||
if not self.__sourceMediaDescriptor is None:
|
if not self.__sourceMediaDescriptor is None:
|
||||||
commandSequence += self.generateMetadataTokens()
|
commandSequence2 += self.generateMetadataTokens()
|
||||||
|
|
||||||
if denoise:
|
if denoise:
|
||||||
commandSequence2 += self.generateDenoiseTokens()
|
commandSequence2 += self.generateDenoiseTokens()
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class FileProperties():
|
|||||||
|
|
||||||
matchResult = self.__pc.matchFilename(self.__sourceFilename)
|
matchResult = self.__pc.matchFilename(self.__sourceFilename)
|
||||||
|
|
||||||
self.__pattern = matchResult['pattern'] if matchResult else None
|
self.__pattern: Pattern = matchResult['pattern'] if matchResult else None
|
||||||
|
|
||||||
matchedGroups = matchResult['match'].groups() if matchResult else {}
|
matchedGroups = matchResult['match'].groups() if matchResult else {}
|
||||||
seIndicator = matchedGroups[0] if matchedGroups else self.__sourceFilename
|
seIndicator = matchedGroups[0] if matchedGroups else self.__sourceFilename
|
||||||
@@ -187,13 +187,13 @@ class FileProperties():
|
|||||||
|
|
||||||
|
|
||||||
def assembleTargetFilename(self,
|
def assembleTargetFilename(self,
|
||||||
label = None,
|
label: str = "",
|
||||||
quality : int = -1,
|
quality: int = -1,
|
||||||
fileIndex : int = -1,
|
fileIndex: int = -1,
|
||||||
indexDigits : int = DEFAULT_INDEX_DIGITS,
|
indexDigits: int = DEFAULT_INDEX_DIGITS,
|
||||||
extension : str = None):
|
extension: str = None):
|
||||||
|
|
||||||
if 'show_descriptor' in self.context['show_descriptor'].keys():
|
if 'show_descriptor' in self.context.keys():
|
||||||
season_digits = self.context['show_descriptor'][ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
|
season_digits = self.context['show_descriptor'][ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
|
||||||
episode_digits = self.context['show_descriptor'][ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]
|
episode_digits = self.context['show_descriptor'][ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]
|
||||||
else:
|
else:
|
||||||
@@ -204,29 +204,27 @@ class FileProperties():
|
|||||||
|
|
||||||
targetFilenameExtension = FfxController.DEFAULT_FILE_EXTENSION if extension is None else str(extension)
|
targetFilenameExtension = FfxController.DEFAULT_FILE_EXTENSION if extension is None else str(extension)
|
||||||
|
|
||||||
if label is None:
|
if not label:
|
||||||
targetFilenameTokens = [self.__sourceFileBasename]
|
targetFilenameTokens = [self.__sourceFileBasename]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
targetFilenameTokens = [label]
|
targetFilenameTokens = [label]
|
||||||
|
|
||||||
if fileIndex > -1:
|
if fileIndex > -1:
|
||||||
targetFilenameTokens += [f"{fileIndex:0{indexDigits}d}"]
|
targetFilenameTokens += [f"{fileIndex:0{indexDigits}d}"]
|
||||||
|
|
||||||
elif self.__season > -1 and self.__episode > -1:
|
elif self.__season > -1 and self.__episode > -1:
|
||||||
targetFilenameTokens += [f"S{self.__season:0{season_digits}d}E{self.__episode:0{episode_digits}d}"]
|
targetFilenameTokens += [f"S{self.__season:0{season_digits}d}E{self.__episode:0{episode_digits}d}"]
|
||||||
elif self.__episode > -1:
|
elif self.__episode > -1:
|
||||||
targetFilenameTokens += [f"E{self.__episode:0{episode_digits}d}"]
|
targetFilenameTokens += [f"E{self.__episode:0{episode_digits}d}"]
|
||||||
|
|
||||||
if len(quality) != 1:
|
if quality != -1:
|
||||||
targetFilenameTokens += [f"q{quality}"]
|
targetFilenameTokens += [f"q{quality}"]
|
||||||
|
|
||||||
# In case source and target filenames are the same add an extension to distinct output from input
|
# In case source and target filenames are the same add an extension to distinct output from input
|
||||||
if label is None and self.__sourceFilenameExtension == targetFilenameExtension:
|
if not label and self.__sourceFilenameExtension == targetFilenameExtension:
|
||||||
targetFilenameTokens += ['ffx']
|
targetFilenameTokens += ['ffx']
|
||||||
|
|
||||||
targetFilename = '_'.join(targetFilenameTokens)
|
targetFilename = '_'.join(targetFilenameTokens)
|
||||||
|
|
||||||
click.echo(f"Target filename: {targetFilename}")
|
click.echo(f"Target filename: {targetFilename}")
|
||||||
|
|
||||||
return targetFilename
|
return targetFilename
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
import click
|
||||||
|
|
||||||
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
|
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
|
||||||
from sqlalchemy.orm import relationship, sessionmaker, Mapped, backref
|
from sqlalchemy.orm import relationship, sessionmaker, Mapped, backref
|
||||||
|
|
||||||
from .show import Base
|
from .show import Base, Show
|
||||||
from .track import Track
|
from .track import Track
|
||||||
|
|
||||||
from ffx.media_descriptor import MediaDescriptor
|
from ffx.media_descriptor import MediaDescriptor
|
||||||
|
from ffx.show_descriptor import ShowDescriptor
|
||||||
|
|
||||||
class Pattern(Base):
|
class Pattern(Base):
|
||||||
|
|
||||||
@@ -20,7 +23,7 @@ class Pattern(Base):
|
|||||||
|
|
||||||
# v1.x
|
# v1.x
|
||||||
show_id = Column(Integer, ForeignKey('shows.id', ondelete="CASCADE"))
|
show_id = Column(Integer, ForeignKey('shows.id', ondelete="CASCADE"))
|
||||||
show = relationship('Show', back_populates='patterns', lazy='joined')
|
show = relationship(Show, back_populates='patterns', lazy='joined')
|
||||||
|
|
||||||
# v2.0
|
# v2.0
|
||||||
# show_id: Mapped[int] = mapped_column(ForeignKey("shows.id", ondelete="CASCADE"))
|
# show_id: Mapped[int] = mapped_column(ForeignKey("shows.id", ondelete="CASCADE"))
|
||||||
@@ -38,6 +41,10 @@ class Pattern(Base):
|
|||||||
def getShowId(self):
|
def getShowId(self):
|
||||||
return int(self.show_id)
|
return int(self.show_id)
|
||||||
|
|
||||||
|
def getShowDescriptor(self) -> ShowDescriptor:
|
||||||
|
click.echo(f"self.show {self.show} id={self.show_id}")
|
||||||
|
return self.show.getDescriptor()
|
||||||
|
|
||||||
def getId(self):
|
def getId(self):
|
||||||
return int(self.id)
|
return int(self.id)
|
||||||
|
|
||||||
|
|||||||
@@ -44,30 +44,16 @@ class Show(Base):
|
|||||||
indicator_episode_digits = Column(Integer, default=ShowDescriptor.DEFAULT_INDICATOR_EPISODE_DIGITS)
|
indicator_episode_digits = Column(Integer, default=ShowDescriptor.DEFAULT_INDICATOR_EPISODE_DIGITS)
|
||||||
|
|
||||||
|
|
||||||
def getDesciptor(self):
|
def getDescriptor(self):
|
||||||
|
|
||||||
descriptor = {}
|
kwargs = {}
|
||||||
|
|
||||||
descriptor['id'] = int(self.id)
|
kwargs[ShowDescriptor.ID_KEY] = int(self.id)
|
||||||
descriptor['name'] = str(self.name)
|
kwargs[ShowDescriptor.NAME_KEY] = str(self.name)
|
||||||
descriptor['year'] = int(self.year)
|
kwargs[ShowDescriptor.YEAR_KEY] = int(self.year)
|
||||||
|
kwargs[ShowDescriptor.INDEX_SEASON_DIGITS_KEY] = int(self.index_season_digits)
|
||||||
descriptor['index_season_digits'] = int(self.index_season_digits)
|
kwargs[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY] = int(self.index_season_digits)
|
||||||
descriptor['index_episode_digits'] = int(self.index_episode_digits)
|
kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY] = int(self.indicator_season_digits)
|
||||||
descriptor['indicator_season_digits'] = int(self.indicator_season_digits)
|
kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY] = int(self.indicator_episode_digits)
|
||||||
descriptor['indicator_episode_digits'] = int(self.indicator_episode_digits)
|
|
||||||
|
return ShowDescriptor(**kwargs)
|
||||||
return descriptor
|
|
||||||
|
|
||||||
# def getDescriptor(self):
|
|
||||||
#
|
|
||||||
# kwargs = {}
|
|
||||||
#
|
|
||||||
# kwargs[]
|
|
||||||
# kwargs[]
|
|
||||||
# kwargs[]
|
|
||||||
#
|
|
||||||
# kwargs[]
|
|
||||||
# kwargs[]
|
|
||||||
# kwargs[]
|
|
||||||
# kwargs[]
|
|
||||||
|
|||||||
@@ -26,6 +26,18 @@ class ShowController():
|
|||||||
finally:
|
finally:
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
|
def getShow(self, showId):
|
||||||
|
|
||||||
|
try:
|
||||||
|
s = self.Session()
|
||||||
|
q = s.query(Show).filter(Show.id == showId)
|
||||||
|
|
||||||
|
return q.first() if q.count() else None
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
raise click.ClickException(f"ShowController.getShow(): {repr(ex)}")
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
|
||||||
def getAllShows(self):
|
def getAllShows(self):
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from ffx.helper import dictDiff, DIFF_ADDED_KEY, DIFF_CHANGED_KEY, DIFF_REMOVED_
|
|||||||
class ShowDescriptor():
|
class ShowDescriptor():
|
||||||
"""This class represents the structural content of a media file including streams and metadata"""
|
"""This class represents the structural content of a media file including streams and metadata"""
|
||||||
|
|
||||||
CONTEXT_KEY = 'context'
|
# CONTEXT_KEY = 'context'
|
||||||
|
|
||||||
ID_KEY = 'id'
|
ID_KEY = 'id'
|
||||||
NAME_KEY = 'name'
|
NAME_KEY = 'name'
|
||||||
@@ -29,306 +29,74 @@ class ShowDescriptor():
|
|||||||
DEFAULT_INDICATOR_SEASON_DIGITS = 2
|
DEFAULT_INDICATOR_SEASON_DIGITS = 2
|
||||||
DEFAULT_INDICATOR_EPISODE_DIGITS = 2
|
DEFAULT_INDICATOR_EPISODE_DIGITS = 2
|
||||||
|
|
||||||
def getDesciptor(self):
|
|
||||||
|
|
||||||
descriptor = {}
|
|
||||||
|
|
||||||
descriptor['id'] = int(self.id)
|
|
||||||
descriptor['name'] = str(self.name)
|
|
||||||
descriptor['year'] = int(self.year)
|
|
||||||
|
|
||||||
descriptor['index_season_digits'] = int(self.index_season_digits)
|
|
||||||
descriptor['index_episode_digits'] = int(self.index_episode_digits)
|
|
||||||
descriptor['indicator_season_digits'] = int(self.indicator_season_digits)
|
|
||||||
descriptor['indicator_episode_digits'] = int(self.indicator_episode_digits)
|
|
||||||
|
|
||||||
return descriptor
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
|
||||||
if ShowDescriptor.ID_KEY in kwargs.keys():
|
if ShowDescriptor.ID_KEY in kwargs.keys():
|
||||||
if type(kwargs[ShowDescriptor.ID_KEY]) is not dict:
|
if type(kwargs[ShowDescriptor.ID_KEY]) is not int:
|
||||||
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.ID_KEY} is required to be of type dict")
|
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.ID_KEY} is required to be of type int")
|
||||||
self.__showId = kwargs[ShowDescriptor.ID_KEY]
|
self.__showId = kwargs[ShowDescriptor.ID_KEY]
|
||||||
else:
|
else:
|
||||||
self.__showId = {}
|
self.__showId = {}
|
||||||
|
|
||||||
if ShowDescriptor.NAME_KEY in kwargs.keys():
|
if ShowDescriptor.NAME_KEY in kwargs.keys():
|
||||||
if type(kwargs[ShowDescriptor.NAME_KEY]) is not dict:
|
if type(kwargs[ShowDescriptor.NAME_KEY]) is not str:
|
||||||
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.NAME_KEY} is required to be of type dict")
|
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.NAME_KEY} is required to be of type str")
|
||||||
self.__showName = kwargs[ShowDescriptor.NAME_KEY]
|
self.__showName = kwargs[ShowDescriptor.NAME_KEY]
|
||||||
else:
|
else:
|
||||||
self.__showName = {}
|
self.__showName = {}
|
||||||
|
|
||||||
if ShowDescriptor.YEAR_KEY in kwargs.keys():
|
if ShowDescriptor.YEAR_KEY in kwargs.keys():
|
||||||
if type(kwargs[ShowDescriptor.YEAR_KEY]) is not dict:
|
if type(kwargs[ShowDescriptor.YEAR_KEY]) is not int:
|
||||||
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.YEAR_KEY} is required to be of type dict")
|
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.YEAR_KEY} is required to be of type int")
|
||||||
self.__showYear = kwargs[ShowDescriptor.YEAR_KEY]
|
self.__showYear = kwargs[ShowDescriptor.YEAR_KEY]
|
||||||
else:
|
else:
|
||||||
self.__showYear = {}
|
self.__showYear = {}
|
||||||
|
|
||||||
if ShowDescriptor.INDEX_SEASON_DIGITS_KEY in kwargs.keys():
|
if ShowDescriptor.INDEX_SEASON_DIGITS_KEY in kwargs.keys():
|
||||||
if type(kwargs[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]) is not dict:
|
if type(kwargs[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]) is not int:
|
||||||
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDEX_SEASON_DIGITS_KEY} is required to be of type dict")
|
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDEX_SEASON_DIGITS_KEY} is required to be of type int")
|
||||||
self.__indexSeasonDigits = kwargs[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]
|
self.__indexSeasonDigits = kwargs[ShowDescriptor.INDEX_SEASON_DIGITS_KEY]
|
||||||
else:
|
else:
|
||||||
self.__indexSeasonDigits = {}
|
self.__indexSeasonDigits = {}
|
||||||
|
|
||||||
if ShowDescriptor.INDEX_EPISODE_DIGITS_KEY in kwargs.keys():
|
if ShowDescriptor.INDEX_EPISODE_DIGITS_KEY in kwargs.keys():
|
||||||
if type(kwargs[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]) is not dict:
|
if type(kwargs[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]) is not int:
|
||||||
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDEX_EPISODE_DIGITS_KEY} is required to be of type dict")
|
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDEX_EPISODE_DIGITS_KEY} is required to be of type int")
|
||||||
self.__indexEpisodeDigits = kwargs[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]
|
self.__indexEpisodeDigits = kwargs[ShowDescriptor.INDEX_EPISODE_DIGITS_KEY]
|
||||||
else:
|
else:
|
||||||
self.__indexEpisodeDigits = {}
|
self.__indexEpisodeDigits = {}
|
||||||
|
|
||||||
if ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY in kwargs.keys():
|
if ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY in kwargs.keys():
|
||||||
if type(kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]) is not dict:
|
if type(kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]) is not int:
|
||||||
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY} is required to be of type dict")
|
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY} is required to be of type int")
|
||||||
self.__indicatorSeasonDigits = kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
|
self.__indicatorSeasonDigits = kwargs[ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY]
|
||||||
else:
|
else:
|
||||||
self.__indicatorSeasonDigits = {}
|
self.__indicatorSeasonDigits = {}
|
||||||
|
|
||||||
if ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY in kwargs.keys():
|
if ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY in kwargs.keys():
|
||||||
if type(kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]) is not dict:
|
if type(kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]) is not int:
|
||||||
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY} is required to be of type dict")
|
raise TypeError(f"ShowDescriptor.__init__(): Argument {ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY} is required to be of type int")
|
||||||
self.__indicatorEpisodeDigits = kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]
|
self.__indicatorEpisodeDigits = kwargs[ShowDescriptor.INDICATOR_EPISODE_DIGITS_KEY]
|
||||||
else:
|
else:
|
||||||
self.__indicatorEpisodeDigits = {}
|
self.__indicatorEpisodeDigits = {}
|
||||||
|
|
||||||
|
|
||||||
|
def getId(self):
|
||||||
def getDefaultVideoTrack(self):
|
return self.__showId
|
||||||
videoDefaultTracks = [v for v in self.getVideoTracks() if TrackDisposition.DEFAULT in v.getDispositionSet()]
|
def getName(self):
|
||||||
if len(videoDefaultTracks) > 1:
|
return self.__showName
|
||||||
raise ValueError('ShowDescriptor.getDefaultVideoTrack(): More than one default video track is not supported')
|
def getYear(self):
|
||||||
return videoDefaultTracks[0] if videoDefaultTracks else None
|
return self.__showYear
|
||||||
|
|
||||||
def getForcedVideoTrack(self):
|
def getIndexSeasonDigits(self):
|
||||||
videoForcedTracks = [v for v in self.getVideoTracks() if TrackDisposition.FORCED in v.getDispositionSet()]
|
return self.__indexSeasonDigits
|
||||||
if len(videoForcedTracks) > 1:
|
def getIndexEpisodeDigits(self):
|
||||||
raise ValueError('ShowDescriptor.getForcedVideoTrack(): More than one forced video track is not supported')
|
return self.__indexEpisodeDigits
|
||||||
return videoForcedTracks[0] if videoForcedTracks else None
|
def getIndicatorSeasonDigits(self):
|
||||||
|
return self.__indicatorSeasonDigits
|
||||||
def getDefaultAudioTrack(self):
|
def getIndicatorEpisodeDigits(self):
|
||||||
audioDefaultTracks = [a for a in self.getAudioTracks() if TrackDisposition.DEFAULT in a.getDispositionSet()]
|
return self.__indicatorEpisodeDigits
|
||||||
if len(audioDefaultTracks) > 1:
|
|
||||||
raise ValueError('ShowDescriptor.getDefaultAudioTrack(): More than one default audio track is not supported')
|
def getFilenamePrefix(self):
|
||||||
return audioDefaultTracks[0] if audioDefaultTracks else None
|
return f"{self.__showName} ({str(self.__showYear)})"
|
||||||
|
|
||||||
def getForcedAudioTrack(self):
|
|
||||||
audioForcedTracks = [a for a in self.getAudioTracks() if TrackDisposition.FORCED in a.getDispositionSet()]
|
|
||||||
if len(audioForcedTracks) > 1:
|
|
||||||
raise ValueError('ShowDescriptor.getForcedAudioTrack(): More than one forced audio track is not supported')
|
|
||||||
return audioForcedTracks[0] if audioForcedTracks else None
|
|
||||||
|
|
||||||
def getDefaultSubtitleTrack(self):
|
|
||||||
subtitleDefaultTracks = [s for s in self.getSubtitleTracks() if TrackDisposition.DEFAULT in s.getDispositionSet()]
|
|
||||||
if len(subtitleDefaultTracks) > 1:
|
|
||||||
raise ValueError('ShowDescriptor.getDefaultSubtitleTrack(): More than one default subtitle track is not supported')
|
|
||||||
return subtitleDefaultTracks[0] if subtitleDefaultTracks else None
|
|
||||||
|
|
||||||
def getForcedSubtitleTrack(self):
|
|
||||||
subtitleForcedTracks = [s for s in self.getSubtitleTracks() if TrackDisposition.FORCED in s.getDispositionSet()]
|
|
||||||
if len(subtitleForcedTracks) > 1:
|
|
||||||
raise ValueError('ShowDescriptor.getForcedSubtitleTrack(): More than one forced subtitle track is not supported')
|
|
||||||
return subtitleForcedTracks[0] if subtitleForcedTracks else None
|
|
||||||
|
|
||||||
|
|
||||||
def setDefaultSubTrack(self, trackType : TrackType, subIndex : int):
|
|
||||||
for t in self.getAllTrackDescriptors():
|
|
||||||
if t.getType() == trackType:
|
|
||||||
t.setDispositionFlag(TrackDisposition.DEFAULT, t.getSubIndex() == int(subIndex))
|
|
||||||
|
|
||||||
def setForcedSubTrack(self, trackType : TrackType, subIndex : int):
|
|
||||||
for t in self.getAllTrackDescriptors():
|
|
||||||
if t.getType() == trackType:
|
|
||||||
t.setDispositionFlag(TrackDisposition.FORCED, t.getSubIndex() == int(subIndex))
|
|
||||||
|
|
||||||
|
|
||||||
def getReorderedTrackDescriptors(self):
|
|
||||||
|
|
||||||
videoTracks = self.sortSubIndices(self.getVideoTracks())
|
|
||||||
audioTracks = self.sortSubIndices(self.getAudioTracks())
|
|
||||||
subtitleTracks = self.sortSubIndices(self.getSubtitleTracks())
|
|
||||||
|
|
||||||
videoDefaultTrack = self.getDefaultVideoTrack()
|
|
||||||
self.getForcedVideoTrack()
|
|
||||||
audioDefaultTrack = self.getDefaultAudioTrack()
|
|
||||||
self.getForcedAudioTrack()
|
|
||||||
subtitleDefaultTrack = self.getDefaultSubtitleTrack()
|
|
||||||
self.getForcedSubtitleTrack()
|
|
||||||
|
|
||||||
if self.__jellyfinOrder:
|
|
||||||
if not videoDefaultTrack is None:
|
|
||||||
videoTracks.append(videoTracks.pop(videoTracks.index(videoDefaultTrack)))
|
|
||||||
if not audioDefaultTrack is None:
|
|
||||||
audioTracks.append(audioTracks.pop(audioTracks.index(audioDefaultTrack)))
|
|
||||||
if not subtitleDefaultTrack is None:
|
|
||||||
subtitleTracks.append(subtitleTracks.pop(subtitleTracks.index(subtitleDefaultTrack)))
|
|
||||||
|
|
||||||
reorderedTrackDescriptors = videoTracks + audioTracks + subtitleTracks
|
|
||||||
orderedSourceTrackSequence = [t.getSourceIndex() for t in reorderedTrackDescriptors]
|
|
||||||
|
|
||||||
if len(set(orderedSourceTrackSequence)) < len(orderedSourceTrackSequence):
|
|
||||||
raise ValueError(f"Multiple streams originating from the same source stream not supported")
|
|
||||||
|
|
||||||
return reorderedTrackDescriptors
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def fromFfprobe(cls, formatData, streamData):
|
|
||||||
|
|
||||||
kwargs = {}
|
|
||||||
|
|
||||||
if ShowDescriptor.FFPROBE_TAGS_KEY in formatData.keys():
|
|
||||||
kwargs[ShowDescriptor.TAGS_KEY] = formatData[ShowDescriptor.FFPROBE_TAGS_KEY]
|
|
||||||
|
|
||||||
kwargs[ShowDescriptor.TRACK_DESCRIPTOR_LIST_KEY] = []
|
|
||||||
|
|
||||||
#TODO: Evtl obsolet
|
|
||||||
subIndexCounters = {}
|
|
||||||
|
|
||||||
for streamObj in streamData:
|
|
||||||
|
|
||||||
ffprobeCodecType = streamObj[ShowDescriptor.FFPROBE_CODEC_TYPE_KEY]
|
|
||||||
trackType = TrackType.fromLabel(ffprobeCodecType)
|
|
||||||
|
|
||||||
if trackType != TrackType.UNKNOWN:
|
|
||||||
|
|
||||||
if trackType not in subIndexCounters.keys():
|
|
||||||
subIndexCounters[trackType] = 0
|
|
||||||
|
|
||||||
kwargs[ShowDescriptor.TRACK_DESCRIPTOR_LIST_KEY].append(TrackDescriptor.fromFfprobe(streamObj,
|
|
||||||
subIndex=subIndexCounters[trackType]))
|
|
||||||
subIndexCounters[trackType] += 1
|
|
||||||
|
|
||||||
|
|
||||||
return cls(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def getTags(self):
|
|
||||||
return self.__mediaTags
|
|
||||||
|
|
||||||
|
|
||||||
def sortSubIndices(self, descriptors : List[TrackDescriptor]) -> List[TrackDescriptor]:
|
|
||||||
subIndex = 0
|
|
||||||
for t in descriptors:
|
|
||||||
t.setSubIndex(subIndex)
|
|
||||||
subIndex += 1
|
|
||||||
return descriptors
|
|
||||||
|
|
||||||
|
|
||||||
def getAllTrackDescriptors(self) -> List[TrackDescriptor]:
|
|
||||||
return self.getVideoTracks() + self.getAudioTracks() + self.getSubtitleTracks()
|
|
||||||
|
|
||||||
def getVideoTracks(self) -> List[TrackDescriptor]:
|
|
||||||
return [v for v in self.__trackDescriptors.copy() if v.getType() == TrackType.VIDEO]
|
|
||||||
|
|
||||||
|
|
||||||
def getAudioTracks(self) -> List[TrackDescriptor]:
|
|
||||||
return [a for a in self.__trackDescriptors.copy() if a.getType() == TrackType.AUDIO]
|
|
||||||
|
|
||||||
def getSubtitleTracks(self) -> List[TrackDescriptor]:
|
|
||||||
return [s for s in self.__trackDescriptors.copy() if s.getType() == TrackType.SUBTITLE]
|
|
||||||
|
|
||||||
def getJellyfin(self):
|
|
||||||
return self.__jellyfinOrder
|
|
||||||
|
|
||||||
def setJellyfinOrder(self, state):
|
|
||||||
self.__jellyfinOrder = state
|
|
||||||
|
|
||||||
def getClearTags(self):
|
|
||||||
return self.__clearTags
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def compare(self, vsShowDescriptor : Self):
|
|
||||||
|
|
||||||
if not isinstance(vsShowDescriptor, self.__class__):
|
|
||||||
raise click.ClickException(f"ShowDescriptor.compare(): Argument is required to be of type {self.__class__}")
|
|
||||||
|
|
||||||
vsTags = vsShowDescriptor.getTags()
|
|
||||||
tags = self.getTags()
|
|
||||||
|
|
||||||
#HINT: Some tags differ per file, for example creation_time, so these are removed before diff
|
|
||||||
for emt in ShowDescriptor.EXCLUDED_MEDIA_TAGS:
|
|
||||||
if emt in tags.keys():
|
|
||||||
del tags[emt]
|
|
||||||
if emt in vsTags.keys():
|
|
||||||
del vsTags[emt]
|
|
||||||
|
|
||||||
tagsDiff = dictDiff(vsTags, tags)
|
|
||||||
|
|
||||||
compareResult = {}
|
|
||||||
|
|
||||||
if tagsDiff:
|
|
||||||
compareResult[ShowDescriptor.TAGS_KEY] = tagsDiff
|
|
||||||
|
|
||||||
|
|
||||||
# Target track configuration (from DB)
|
|
||||||
#tracks = self.getAllTrackDescriptors()
|
|
||||||
tracks = self.getReorderedTrackDescriptors()
|
|
||||||
numTracks = len(tracks)
|
|
||||||
|
|
||||||
# Current track configuration (of file)
|
|
||||||
vsTracks = vsShowDescriptor.getAllTrackDescriptors()
|
|
||||||
numVsTracks = len(vsTracks)
|
|
||||||
|
|
||||||
maxNumOfTracks = max(numVsTracks, numTracks)
|
|
||||||
|
|
||||||
|
|
||||||
trackCompareResult = {}
|
|
||||||
|
|
||||||
for tp in range(maxNumOfTracks):
|
|
||||||
|
|
||||||
# inspect/update funktionier nur so
|
|
||||||
if self.__jellyfinOrder:
|
|
||||||
vsTrackIndex = tracks[tp].getSourceIndex()
|
|
||||||
else:
|
|
||||||
vsTrackIndex = tp
|
|
||||||
# vsTrackIndex = tracks[tp].getSourceIndex()
|
|
||||||
|
|
||||||
# Will trigger if tracks are missing in file
|
|
||||||
if tp > (numVsTracks - 1):
|
|
||||||
if DIFF_ADDED_KEY not in trackCompareResult.keys():
|
|
||||||
trackCompareResult[DIFF_ADDED_KEY] = set()
|
|
||||||
trackCompareResult[DIFF_ADDED_KEY].add(tracks[tp].getIndex())
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Will trigger if tracks are missing in DB definition
|
|
||||||
# New tracks will be added per update via this way
|
|
||||||
if tp > (numTracks - 1):
|
|
||||||
if DIFF_REMOVED_KEY not in trackCompareResult.keys():
|
|
||||||
trackCompareResult[DIFF_REMOVED_KEY] = {}
|
|
||||||
trackCompareResult[DIFF_REMOVED_KEY][vsTracks[vsTrackIndex].getIndex()] = vsTracks[vsTrackIndex]
|
|
||||||
continue
|
|
||||||
|
|
||||||
# assumption is made here that the track order will not change for all files of a sequence
|
|
||||||
trackDiff = tracks[tp].compare(vsTracks[vsTrackIndex])
|
|
||||||
|
|
||||||
if trackDiff:
|
|
||||||
if DIFF_CHANGED_KEY not in trackCompareResult.keys():
|
|
||||||
trackCompareResult[DIFF_CHANGED_KEY] = {}
|
|
||||||
trackCompareResult[DIFF_CHANGED_KEY][vsTracks[vsTrackIndex].getIndex()] = trackDiff
|
|
||||||
|
|
||||||
if trackCompareResult:
|
|
||||||
compareResult[ShowDescriptor.TRACKS_KEY] = trackCompareResult
|
|
||||||
|
|
||||||
return compareResult
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getInputMappingTokens(self, use_sub_index : bool = True):
|
|
||||||
|
|
||||||
reorderedTrackDescriptors = self.getReorderedTrackDescriptors()
|
|
||||||
inputMappingTokens = []
|
|
||||||
|
|
||||||
for rtd in reorderedTrackDescriptors:
|
|
||||||
trackType = rtd.getType()
|
|
||||||
if use_sub_index:
|
|
||||||
inputMappingTokens += ['-map', f"0:{trackType.indicator()}:{rtd.getSubIndex()}"]
|
|
||||||
else:
|
|
||||||
inputMappingTokens += ['-map', f"0:{rtd.getIndex()}"]
|
|
||||||
|
|
||||||
return inputMappingTokens
|
|
||||||
|
|
||||||
|
|||||||
@@ -40,12 +40,11 @@ class TmdbController():
|
|||||||
return requests.get(tmdbUrl).json()
|
return requests.get(tmdbUrl).json()
|
||||||
|
|
||||||
|
|
||||||
def getEpisodeFilename(self,
|
def getEpisodeFileBasename(self,
|
||||||
showName,
|
showName,
|
||||||
episodeName,
|
episodeName,
|
||||||
season,
|
season,
|
||||||
episode,
|
episode,
|
||||||
extension,
|
|
||||||
indexSeasonDigits = 2,
|
indexSeasonDigits = 2,
|
||||||
indexEpisodeDigits = 2,
|
indexEpisodeDigits = 2,
|
||||||
indicatorSeasonDigits = 2,
|
indicatorSeasonDigits = 2,
|
||||||
@@ -92,7 +91,5 @@ class TmdbController():
|
|||||||
filenameTokens += ['S{num:{fill}{width}}'.format(num=season, fill='0', width=indicatorSeasonDigits)]
|
filenameTokens += ['S{num:{fill}{width}}'.format(num=season, fill='0', width=indicatorSeasonDigits)]
|
||||||
if indicatorEpisodeDigits:
|
if indicatorEpisodeDigits:
|
||||||
filenameTokens += ['E{num:{fill}{width}}'.format(num=episode, fill='0', width=indicatorEpisodeDigits)]
|
filenameTokens += ['E{num:{fill}{width}}'.format(num=episode, fill='0', width=indicatorEpisodeDigits)]
|
||||||
|
|
||||||
filenameTokens += ['.', extension]
|
|
||||||
|
|
||||||
return ''.join(filenameTokens)
|
return ''.join(filenameTokens)
|
||||||
|
|||||||
Reference in New Issue
Block a user