8 Commits

Author SHA1 Message Date
Maveno
24d0700db2 nightly subtitle file import 2024-10-20 19:14:25 +02:00
Maveno
3463c1e371 improving unmux 2024-10-20 18:29:49 +02:00
Maveno
5ca7d6d12c unmux mwe 2024-10-20 17:58:07 +02:00
Maveno
77dfb4b1d3 reworking target media decriptor track handling 2024-10-20 16:20:33 +02:00
1df2e74566 ff 2024-10-19 20:26:14 +02:00
8f9f77e891 fixes ffmpeg parameter processing 2024-10-19 13:30:30 +02:00
6a03d4d6e2 nightly 2024-10-19 00:05:59 +02:00
a263c735aa nightly 2024-10-19 00:05:18 +02:00
12 changed files with 569 additions and 463 deletions

View File

@@ -11,14 +11,23 @@ from ffx.tmdb_controller import TmdbController
from ffx.database import databaseContext from ffx.database import databaseContext
from ffx.track_descriptor import TrackDescriptor
from ffx.track_type import TrackType from ffx.track_type import TrackType
from ffx.video_encoder import VideoEncoder from ffx.video_encoder import VideoEncoder
from ffx.track_disposition import TrackDisposition
from ffx.process import executeProcess
VERSION='0.1.1' VERSION='0.1.3'
# 0.1.1 # 0.1.1
# Bugfixes, TMBD identify shows # Bugfixes, TMBD identify shows
# 0.1.2
# Bugfixes
# 0.1.3
# Subtitle file imports
@click.group() @click.group()
@click.pass_context @click.pass_context
@@ -56,52 +65,98 @@ def inspect(ctx, filename):
app = FfxApp(ctx.obj) app = FfxApp(ctx.obj)
app.run() app.run()
#TODO: TrackCodec Klasse
CODEC_LOOKUP_TABLE = {
'h264': {'format': 'h264', 'extension': 'h264'},
'aac': { 'extension': 'aac'},
'ac3': {'format': 'ac3', 'extension': 'ac3'},
'ass': {'format': 'ass', 'extension': 'ass'},
'hdmv_pgs_subtitle': {'format': 'sup', 'extension': 'sup'}
}
def getUnmuxSequence(trackDescriptor: TrackDescriptor, sourcePath, targetPrefix, targetDirectory = ''):
trackCodec = trackDescriptor.getCodec()
if not trackCodec in CODEC_LOOKUP_TABLE.keys():
return []
commandTokens = FfxController.COMMAND_TOKENS + ['-i', sourcePath]
trackType = trackDescriptor.getType()
targetPathBase = os.path.join(targetDirectory, targetPrefix) if targetDirectory else targetPrefix
commandTokens += ['-map',
f"0:{trackType.indicator()}:{trackDescriptor.getSubIndex()}",
'-c',
'copy']
if 'format' in CODEC_LOOKUP_TABLE[trackCodec].keys():
commandTokens += ['-f', CODEC_LOOKUP_TABLE[trackCodec]['format']]
commandTokens += [f"{targetPathBase}.{CODEC_LOOKUP_TABLE[trackCodec]['extension']}"]
return commandTokens
# @ffx.command() @ffx.command()
# @click.pass_context @click.pass_context
#
# @click.argument('paths', nargs=-1) @click.argument('paths', nargs=-1)
# @click.option('-l', '--label', type=str, default='', help='Label to be used as filename prefix') @click.option('-l', '--label', type=str, default='', help='Label to be used as filename prefix')
# @click.option("-o", "--output-directory", type=str, default='')
# @click.option('-sd', '--subtitle-directory', type=str, default='', help='Load subtitles from here') @click.option("-s", "--subtitles-only", is_flag=True, default=False)
# @click.option('-sp', '--subtitle-prefix', type=str, default='', help='Subtitle filename prefix') @click.option("--dry-run", is_flag=True, default=False)
# def unmux(ctx,
# @click.option("-o", "--output-directory", type=str, default='') paths,
# label,
# @click.option("--dry-run", is_flag=True, default=False) output_directory,
# subtitles_only,
# dry_run):
# def unmux(ctx,
# label, existingSourcePaths = [p for p in paths if os.path.isfile(p)]
# paths, click.echo(f"\nUnmuxing {len(existingSourcePaths)} files")
# subtitle_directory,
# subtitle_prefix, for sourcePath in existingSourcePaths:
# output_directory,
# dry_run): fp = FileProperties(ctx.obj, sourcePath)
#
# existingSourcePaths = [p for p in paths if os.path.isfile(p)]
# click.echo(f"\nUnmuxing {len(existingSourcePaths)} files")
#
# for sourcePath in existingSourcePaths:
#
# sd = getStreamDescriptor(sourcePath)
#
# print(f"\nFile {sourcePath}\n")
#
# for v in sd['video']:
#
# if v['codec_name'] == 'h264':
#
# commandSequence = ['ffmpeg', '-i', sourcePath, '-map', '0:v:0', '-c', 'copy', '-f', 'h264']
# executeProcess()
#
# for a in sd['audio']:
# print(f"A: {a}\n")
# for s in sd['subtitle']:
# print(f"S: {s}\n")
try:
sourceMediaDescriptor = fp.getMediaDescriptor()
season = fp.getSeason()
episode = fp.getEpisode()
#TODO: Recognition für alle Formate anpassen
targetLabel = label if label else fp.getFileBasename()
targetIndicator = f"_S{season}E{episode}" if label and season != -1 and episode != -1 else ''
if label and not targetIndicator:
click.echo(f"Skipping file {fp.getFilename()}: Label set but no indicator recognized")
continue
else:
click.echo(f"\nUnmuxing file {fp.getFilename()}\n")
for trackDescriptor in sourceMediaDescriptor.getAllTrackDescriptors():
if trackDescriptor.getType() == TrackType.SUBTITLE or not subtitles_only:
# SEASON_EPISODE_STREAM_LANGUAGE_MATCH = '[sS]([0-9]+)[eE]([0-9]+)_([0-9]+)_([a-z]{3})'
targetPrefix = f"{targetLabel}{targetIndicator}_{trackDescriptor.getIndex()}_{trackDescriptor.getLanguage().threeLetter()}"
unmuxSequence = getUnmuxSequence(trackDescriptor, sourcePath, targetPrefix, targetDirectory = output_directory)
if unmuxSequence:
if not dry_run:
click.echo(f"Executing unmuxing sequence: {' '.join(unmuxSequence)}")
out, err, rc = executeProcess(unmuxSequence)
if rc:
click.echo(f"Unmuxing of stream {trackDescriptor.getIndex()} failed with error ({rc}) {err}")
else:
click.echo(f"Skipping stream with unknown codec {trackDescriptor.getCodec()}")
except Exception as ex:
click.echo(f"Skipping File {sourcePath} ({ex})")
@ffx.command() @ffx.command()
@@ -157,6 +212,7 @@ def shows(ctx):
@click.option("-t", "--no-tmdb", is_flag=True, default=False) @click.option("-t", "--no-tmdb", is_flag=True, default=False)
@click.option("-j", "--no-jellyfin", is_flag=True, default=False) @click.option("-j", "--no-jellyfin", is_flag=True, default=False)
@click.option("-np", "--no-pattern", is_flag=True, default=False)
@click.option("--dry-run", is_flag=True, default=False) @click.option("--dry-run", is_flag=True, default=False)
@@ -170,6 +226,7 @@ def convert(ctx,
stereo_bitrate, stereo_bitrate,
ac3_bitrate, ac3_bitrate,
dts_bitrate, dts_bitrate,
subtitle_directory, subtitle_directory,
subtitle_prefix, subtitle_prefix,
@@ -188,6 +245,7 @@ def convert(ctx,
denoise, denoise,
no_tmdb, no_tmdb,
no_jellyfin, no_jellyfin,
no_pattern,
dry_run): dry_run):
"""Batch conversion of audiovideo files in format suitable for web playback, e.g. jellyfin """Batch conversion of audiovideo files in format suitable for web playback, e.g. jellyfin
@@ -200,12 +258,13 @@ def convert(ctx,
context = ctx.obj context = ctx.obj
context['dry_run'] = True # dry_run context['dry_run'] = dry_run
context['video_encoder'] = VideoEncoder.fromLabel(video_encoder) context['video_encoder'] = VideoEncoder.fromLabel(video_encoder)
context['jellyfin'] = not no_jellyfin context['use_jellyfin'] = not no_jellyfin
context['tmdb'] = not no_tmdb context['use_tmdb'] = not no_tmdb
context['use_pattern'] = not no_pattern
context['import_subtitles'] = (subtitle_directory and subtitle_prefix) context['import_subtitles'] = (subtitle_directory and subtitle_prefix)
if context['import_subtitles']: if context['import_subtitles']:
@@ -235,11 +294,11 @@ def convert(ctx,
cTokens = crop.split(',') cTokens = crop.split(',')
if cTokens and len(cTokens) == 2: if cTokens and len(cTokens) == 2:
context['crop_start'] = int(cTokens[0]) context['crop_start'] = int(cTokens[0])
context['crop_lenght'] = int(cTokens[1]) context['crop_length'] = int(cTokens[1])
click.echo(f"Crop start={context['crop_start']} length={context['crop_length']}") click.echo(f"Crop start={context['crop_start']} length={context['crop_length']}")
tc = TmdbController() tc = TmdbController() if context['use_tmdb'] else None
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")
@@ -262,7 +321,7 @@ def convert(ctx,
sourceMediaDescriptor = mediaFileProperties.getMediaDescriptor() sourceMediaDescriptor = mediaFileProperties.getMediaDescriptor()
#HINT: This is None if the filename did not match anything in database #HINT: This is None if the filename did not match anything in database
currentPattern = mediaFileProperties.getPattern() currentPattern = mediaFileProperties.getPattern() if context['use_pattern'] else None
click.echo(f"Pattern matching: {'No' if currentPattern is None else 'Yes'}") click.echo(f"Pattern matching: {'No' if currentPattern is None else 'Yes'}")
@@ -276,45 +335,39 @@ def convert(ctx,
# #
# Query user for the correct sub indices, then configure flags in track descriptors associated with media descriptor accordingly. # Query user for the correct sub indices, then configure flags in track descriptors associated with media descriptor accordingly.
# The correct tokens should then be created by # The correct tokens should then be created by
try: if len([v for v in sourceMediaDescriptor.getVideoTracks() if v.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
sourceMediaDescriptor.getDefaultVideoTrack()
except ValueError:
defaultVideoTrackSubIndex = click.prompt("More than one default video stream detected! Please select stream", type=int) defaultVideoTrackSubIndex = click.prompt("More than one default video stream detected! Please select stream", type=int)
sourceMediaDescriptor.setDefaultSubTrack(TrackType.VIDEO, defaultVideoTrackSubIndex) sourceMediaDescriptor.setDefaultSubTrack(TrackType.VIDEO, defaultVideoTrackSubIndex)
try:
sourceMediaDescriptor.getForcedVideoTrack() if len([v for v in sourceMediaDescriptor.getVideoTracks() if v.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
except ValueError:
forcedVideoTrackSubIndex = click.prompt("More than one forced video stream detected! Please select stream", type=int) forcedVideoTrackSubIndex = click.prompt("More than one forced video stream detected! Please select stream", type=int)
sourceMediaDescriptor.setForcedSubTrack(TrackType.VIDEO, forcedVideoTrackSubIndex) sourceMediaDescriptor.setForcedSubTrack(TrackType.VIDEO, forcedVideoTrackSubIndex)
try:
sourceMediaDescriptor.getDefaultAudioTrack() if len([a for a in sourceMediaDescriptor.getAudioTracks() if a.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
except ValueError:
defaultAudioTrackSubIndex = click.prompt("More than one default audio stream detected! Please select stream", type=int) defaultAudioTrackSubIndex = click.prompt("More than one default audio stream detected! Please select stream", type=int)
sourceMediaDescriptor.setDefaultSubTrack(TrackType.AUDIO, defaultAudioTrackSubIndex) sourceMediaDescriptor.setDefaultSubTrack(TrackType.AUDIO, defaultAudioTrackSubIndex)
try:
sourceMediaDescriptor.getForcedAudioTrack() if len([a for a in sourceMediaDescriptor.getAudioTracks() if a.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
except ValueError:
forcedAudioTrackSubIndex = click.prompt("More than one forced audio stream detected! Please select stream", type=int) forcedAudioTrackSubIndex = click.prompt("More than one forced audio stream detected! Please select stream", type=int)
sourceMediaDescriptor.setForcedSubTrack(TrackType.AUDIO, forcedAudioTrackSubIndex) sourceMediaDescriptor.setForcedSubTrack(TrackType.AUDIO, forcedAudioTrackSubIndex)
try:
sourceMediaDescriptor.getDefaultSubtitleTrack() if len([s for s in sourceMediaDescriptor.getSubtitleTracks() if s.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
except ValueError:
defaultSubtitleTrackSubIndex = click.prompt("More than one default subtitle stream detected! Please select stream", type=int) defaultSubtitleTrackSubIndex = click.prompt("More than one default subtitle stream detected! Please select stream", type=int)
sourceMediaDescriptor.setDefaultSubTrack(TrackType.SUBTITLE, defaultSubtitleTrackSubIndex) sourceMediaDescriptor.setDefaultSubTrack(TrackType.SUBTITLE, defaultSubtitleTrackSubIndex)
try:
sourceMediaDescriptor.getForcedSubtitleTrack() if len([s for s in sourceMediaDescriptor.getSubtitleTracks() if s.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
except ValueError:
forcedSubtitleTrackSubIndex = click.prompt("More than one forced subtitle stream detected! Please select stream", type=int) forcedSubtitleTrackSubIndex = click.prompt("More than one forced subtitle stream detected! Please select stream", type=int)
sourceMediaDescriptor.setForcedSubTrack(TrackType.SUBTITLE, forcedSubtitleTrackSubIndex) sourceMediaDescriptor.setForcedSubTrack(TrackType.SUBTITLE, forcedSubtitleTrackSubIndex)
if context['import_subtitles']: if context['import_subtitles']:
sourceMediaDescriptor.importSubtitles(context['subtitle_directory'], context['subtitle_prefix']) sourceMediaDescriptor.importSubtitles(context['subtitle_directory'],
context['subtitle_prefix'],
mediaFileProperties.getSeason(),
mediaFileProperties.getEpisode())
fc = FfxController(context, sourceMediaDescriptor) fc = FfxController(context, sourceMediaDescriptor)
# mappingTokens = fc.generateMetadataTokens()
# click.echo(f"Metadata Tokens: {mappingTokens}")
dispositionTokens = fc.generateDispositionTokens() dispositionTokens = fc.generateDispositionTokens()
click.echo(f"Disposition Tokens: {dispositionTokens}") click.echo(f"Disposition Tokens: {dispositionTokens}")
@@ -326,15 +379,14 @@ def convert(ctx,
# Case pattern matching # Case pattern matching
targetMediaDescriptor = currentPattern.getMediaDescriptor() targetMediaDescriptor = currentPattern.getMediaDescriptor()
currentShowDescriptor = currentPattern.getShowDescriptor() currentShowDescriptor = currentPattern.getShowDescriptor()
if context['tmdb']: if context['use_tmdb']:
tmdbEpisodeResult = tc.queryEpisode(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode()) tmdbEpisodeResult = tc.queryEpisode(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode())
# click.echo(f"{tmdbEpisodeResult}")
if tmdbEpisodeResult: if tmdbEpisodeResult:
fileBasename = tc.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(), fileBasename = tc.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(),
tmdbEpisodeResult['name'], tmdbEpisodeResult['name'],
@@ -344,19 +396,28 @@ def convert(ctx,
currentShowDescriptor.getIndexEpisodeDigits(), currentShowDescriptor.getIndexEpisodeDigits(),
currentShowDescriptor.getIndicatorSeasonDigits(), currentShowDescriptor.getIndicatorSeasonDigits(),
currentShowDescriptor.getIndicatorEpisodeDigits()) currentShowDescriptor.getIndicatorEpisodeDigits())
else: else:
fileBasename = currentShowDescriptor.getFilenamePrefix() fileBasename = currentShowDescriptor.getFilenamePrefix()
click.echo(f"fileBasename={fileBasename}") click.echo(f"fileBasename={fileBasename}")
if context['import_subtitles']: if context['import_subtitles']:
targetMediaDescriptor.importSubtitles(context['subtitle_directory'], context['subtitle_prefix']) targetMediaDescriptor.importSubtitles(context['subtitle_directory'],
context['subtitle_prefix'],
mediaFileProperties.getSeason(),
mediaFileProperties.getEpisode())
targetMediaDescriptor.setJellyfinOrder(context['jellyfin']) # raise click.ClickException(f"tmd subindices: {[t.getSubIndex() for t in targetMediaDescriptor.getAllTrackDescriptors()]}")
# click.echo(f"tmd subindices: {[t.getIndex() for t in targetMediaDescriptor.getAllTrackDescriptors()]} {[t.getSubIndex() for t in targetMediaDescriptor.getAllTrackDescriptors()]} {[t.getDispositionFlag(TrackDisposition.DEFAULT) for t in targetMediaDescriptor.getAllTrackDescriptors()]}")
click.echo(f"Input mapping tokens: {targetMediaDescriptor.getInputMappingTokens()}") if context['use_jellyfin']:
# Reorder subtracks in types with default the last, then make subindices flat again
targetMediaDescriptor.applyJellyfinOrder()
# click.echo(f"tmd subindices: {[t.getIndex() for t in targetMediaDescriptor.getAllTrackDescriptors()]} {[t.getSubIndex() for t in targetMediaDescriptor.getAllTrackDescriptors()]} {[t.getDispositionFlag(TrackDisposition.DEFAULT) for t in targetMediaDescriptor.getAllTrackDescriptors()]}")
# raise click.Abort
click.echo(f"Input mapping tokens (2nd pass): {targetMediaDescriptor.getInputMappingTokens()}")
fc = FfxController(context, targetMediaDescriptor, sourceMediaDescriptor) fc = FfxController(context, targetMediaDescriptor, sourceMediaDescriptor)
@@ -379,9 +440,10 @@ def convert(ctx,
extra = ['ffx'] if sourceFilenameExtension == FfxController.DEFAULT_FILE_EXTENSION else [] extra = ['ffx'] if sourceFilenameExtension == FfxController.DEFAULT_FILE_EXTENSION else []
targetFilename = fileBasename if context['tmdb'] else mediaFileProperties.assembleTargetFileBasename(label if label else fileBasename, targetFilename = (fileBasename if context['use_tmdb']
q if len(q_list) > 1 else -1, else mediaFileProperties.assembleTargetFileBasename(label if label else fileBasename,
extraTokens = extra) q if len(q_list) > 1 else -1,
extraTokens = extra))
targetPath = os.path.join(output_directory if output_directory else sourceDirectory, targetFilename) targetPath = os.path.join(output_directory if output_directory else sourceDirectory, targetFilename)
@@ -392,7 +454,7 @@ def convert(ctx,
preset, preset,
denoise) denoise)
# #click.confirm('Warning! This file is not compliant to the defined source schema! Do you want to continue?', abort=True) #TODO: 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()
click.echo(f"\nDONE\nTime elapsed {endTime - startTime}") click.echo(f"\nDONE\nTime elapsed {endTime - startTime}")

View File

@@ -107,7 +107,8 @@ class FfxController():
def generateOutputTokens(self, filepath, format, ext): def generateOutputTokens(self, filepath, format, ext):
return ['-f', format, f"{filepath}.{ext}"] outputFilePath = f"{filepath}.{ext}"
return ['-f', format, outputFilePath]
def generateAudioEncodingTokens(self): def generateAudioEncodingTokens(self):
@@ -116,7 +117,8 @@ class FfxController():
audioTokens = [] audioTokens = []
#sourceAudioTrackDescriptors = [smd for smd in self.__sourceMediaDescriptor.getAllTrackDescriptors() if smd.getType() == TrackType.AUDIO] #sourceAudioTrackDescriptors = [smd for smd in self.__sourceMediaDescriptor.getAllTrackDescriptors() if smd.getType() == TrackType.AUDIO]
targetAudioTrackDescriptors = [rtd for rtd in self.__targetMediaDescriptor.getReorderedTrackDescriptors() if rtd.getType() == TrackType.AUDIO] # targetAudioTrackDescriptors = [rtd for rtd in self.__targetMediaDescriptor.getReorderedTrackDescriptors() if rtd.getType() == TrackType.AUDIO]
targetAudioTrackDescriptors = [td for td in self.__targetMediaDescriptor.getAllTrackDescriptors() if td.getType() == TrackType.AUDIO]
trackSubIndex = 0 trackSubIndex = 0
for trackDescriptor in targetAudioTrackDescriptors: for trackDescriptor in targetAudioTrackDescriptors:
@@ -165,185 +167,206 @@ class FfxController():
return audioTokens return audioTokens
# -disposition:s:0 default -disposition:s:1 0
def generateDispositionTokens(self): def generateDispositionTokens(self):
"""Source media descriptor is optional"""
sourceTrackDescriptors = [] if self.__sourceMediaDescriptor is None else self.__sourceMediaDescriptor.getAllTrackDescriptors() # sourceTrackDescriptors = [] if self.__sourceMediaDescriptor is None else self.__sourceMediaDescriptor.getAllTrackDescriptors()
targetTrackDescriptors = self.__targetMediaDescriptor.getReorderedTrackDescriptors() targetTrackDescriptors = self.__targetMediaDescriptor.getAllTrackDescriptors()
dispositionTokens = [] dispositionTokens = []
# for subStreamIndex in range(len(subDescriptor)): # raise click.ClickException(f"ttd subindices: {[t.getSubIndex() for t in targetTrackDescriptors]}")
#TODO: Sorting here is for the sole purpose to let the tokens appear with ascending subindices. Why necessary? Jellyfin order?
# for trackDescriptor in sorted(targetTrackDescriptors.copy(), key=lambda d: d.getSubIndex()):
for trackDescriptor in targetTrackDescriptors: for trackDescriptor in targetTrackDescriptors:
# Calculate source sub index. This applies only if a source media descriptor is defined. #HINT: No dispositions for pgs subtitle tracks that have no external file source
if sourceTrackDescriptors: if (trackDescriptor.getExternalSourceFilePath()
changedTargetTrackDescriptor : TrackDescriptor = targetTrackDescriptors[trackDescriptor.getIndex()] or trackDescriptor.getCodec() != TrackDescriptor.CODEC_PGS):
changedTargetTrackSourceIndex = changedTargetTrackDescriptor.getSourceIndex()
subIndex = sourceTrackDescriptors[changedTargetTrackSourceIndex].getSubIndex()
else:
subIndex = trackDescriptor.getSubIndex() subIndex = trackDescriptor.getSubIndex()
streamIndicator = trackDescriptor.getType().indicator()
dispositionSet = trackDescriptor.getDispositionSet()
streamIndicator = trackDescriptor.getType().indicator() if dispositionSet:
dispositionSet = trackDescriptor.getDispositionSet() dispositionTokens += [f"-disposition:{streamIndicator}:{subIndex}", '+'.join([d.label() for d in dispositionSet])]
else:
if dispositionSet: dispositionTokens += [f"-disposition:{streamIndicator}:{subIndex}", '0']
dispositionTokens += [f"-disposition:{streamIndicator}:{subIndex}", '+'.join([d.label() for d in dispositionSet])]
else:
dispositionTokens += [f"-disposition:{streamIndicator}:{subIndex}", '0']
return dispositionTokens return dispositionTokens
# def generateMetadataTokens(self):
# """Source media descriptor is mandatory"""
#
# metadataTokens = []
#
# # click.echo(f"source media descriptor: track indices={[d.getIndex() for d in sourceMediaDescriptor.getAllTrackDescriptors()]}")
# # click.echo(f"target media descriptor: track indices={[d.getIndex() for d in targetMediaDescriptor.getAllTrackDescriptors()]}")
#
# # +jellyfin -jellyfin
# mediaDifferences = self.__targetMediaDescriptor.compare(self.__sourceMediaDescriptor)
#
# # media diff {'tracks': {'changed': {4: {'tags': {'added': {'Yolo'}}}}}}
#
# click.echo(f"media diff {mediaDifferences}")
#
# if MediaDescriptor.TAGS_KEY in mediaDifferences.keys():
#
# sourceTags = self.__sourceMediaDescriptor.getTags()
# targetTags = self.__targetMediaDescriptor.getTags()
#
# #TODO: Warum erscheint nur -1 im output?
# if DIFF_REMOVED_KEY in mediaDifferences[MediaDescriptor.TAGS_KEY].keys():
# # for removedTagKey in mediaDifferences[MediaDescriptor.TAGS_KEY][DIFF_REMOVED_KEY]:
# # row = (f"removed media tag: key='{removedTagKey}' value='{sourceTags[removedTagKey]}'",)
# # self.differencesTable.add_row(*map(str, row))
# pass
# #metadataTokens += [f"-map_metadata:g", "-1"]
#
# #for targetMediaTagKey in targetTags:
# #metadataTokens += [f"-metadata:g", f"{targetMediaTagKey}={targetTags[targetMediaTagKey]}"]
#
# else:
#
# if DIFF_ADDED_KEY in mediaDifferences[MediaDescriptor.TAGS_KEY].keys():
# for addedTagKey in mediaDifferences[MediaDescriptor.TAGS_KEY][DIFF_ADDED_KEY]:
# # row = (f"added media tag: key='{addedTagKey}' value='{targetTags[addedTagKey]}'",)
# click.echo(f"added metadata key='{addedTagKey}' value='{targetTags[addedTagKey]}'->'{targetTags[addedTagKey]}'")
# # self.differencesTable.add_row(*map(str, row))
# #pass
# metadataTokens += [f"-metadata:g", f"{addedTagKey}={targetTags[addedTagKey]}"]
#
#
#
# if DIFF_CHANGED_KEY in mediaDifferences[MediaDescriptor.TAGS_KEY].keys():
# for changedTagKey in mediaDifferences[MediaDescriptor.TAGS_KEY][DIFF_CHANGED_KEY]:
# #row = (f"changed media tag: key='{changedTagKey}' value='{sourceTags[changedTagKey]}'->'{targetTags[changedTagKey]}'",)
# click.echo(f"changed metadata key='{changedTagKey}' value='{sourceTags[changedTagKey]}'->'{targetTags[changedTagKey]}'")
# # self.differencesTable.add_row(*map(str, row))
# #pass
# metadataTokens += [f"-metadata:g", f"{changedTagKey}={targetTags[changedTagKey]}"]
#
# if MediaDescriptor.TRACKS_KEY in mediaDifferences.keys():
#
# sourceTrackDescriptors = self.__sourceMediaDescriptor.getAllTrackDescriptors()
# targetTrackDescriptors = self.__targetMediaDescriptor.getReorderedTrackDescriptors()
#
# if DIFF_ADDED_KEY in mediaDifferences[MediaDescriptor.TRACKS_KEY].keys():
# addedTracksIndices = mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_ADDED_KEY]
# raise click.ClickException(f"FfxController.generateMetadataTokens(): Adding tracks is not supported. Track indices {addedTracksIndices}")
#
# #raise click.ClickException(f"add track {mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_ADDED_KEY]}")
# #for addedTrackIndex in mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_ADDED_KEY]:
# #addedTrack : Track = targetTrackDescriptors[addedTrackIndex]
# # row = (f"added {addedTrack.getType().label()} track: index={addedTrackIndex} lang={addedTrack.getLanguage().threeLetter()}",)
# # self.differencesTable.add_row(*map(str, row))
#
# if DIFF_REMOVED_KEY in mediaDifferences[MediaDescriptor.TRACKS_KEY].keys():
# removedTracksIndices = mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_ADDED_KEY].keys()
# raise click.ClickException(f"FfxController.generateMetadataTokens(): Removing tracks is not supported. Track indices {removedTracksIndices}")
# #for removedTrackIndex in mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_REMOVED_KEY]:
# # row = (f"removed track: index={removedTrackIndex}",)
# # self.differencesTable.add_row(*map(str, row))
#
# # media diff {'tracks': {'changed': {4: {'tags': {'added': {'Yolo'}}}}}}
# if DIFF_CHANGED_KEY in mediaDifferences[MediaDescriptor.TRACKS_KEY].keys():
# for changedTrackIndex in mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_CHANGED_KEY].keys():
#
# changedTargetTrackDescriptor : TrackDescriptor = targetTrackDescriptors[changedTrackIndex]
# changedTargetTrackSourceIndex = changedTargetTrackDescriptor.getSourceIndex()
# changedTargetSourceSubIndex = sourceTrackDescriptors[changedTargetTrackSourceIndex].getSubIndex()
# # changedSourceTrackDescriptor : TrackDescriptor = sourceTrackDescriptors[changedTargetTrackSourceIndex]
# # changedSourceTrackSubIndex = changedSourceTrackDescriptor.getSubIndex()
#
# changedTrackDiff : dict = mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_CHANGED_KEY][changedTrackIndex]
#
# if MediaDescriptor.TAGS_KEY in changedTrackDiff.keys():
#
#
# if DIFF_REMOVED_KEY in changedTrackDiff[MediaDescriptor.TAGS_KEY]:
# #for removedTagKey in changedTrackDiff[MediaDescriptor.TAGS_KEY][DIFF_REMOVED_KEY]:
# # row = (f"changed {changedTargetTrackDescriptor.getType().label()} track index={changedTrackIndex} removed key={removedTagKey}",)
# # self.differencesTable.add_row(*map(str, row))
#
# #addedTagValue = targetTrackDescriptors[changedTargetTrackSourceIndex].getTags()[addedTagKey]
#
# metadataTokens += [f"-map_metadata:s:{changedTargetTrackDescriptor.getType().indicator()}:{changedTargetSourceSubIndex}", "-1"]
#
# for targetTrackTagKey, targetTrackTagValue in changedTargetTrackDescriptor.getTags():
# metadataTokens += [f"-metadata:s:{changedTargetTrackDescriptor.getType().indicator()}:{changedTargetSourceSubIndex}",
# f"{targetTrackTagKey}={targetTrackTagValue}"]
#
# else:
#
# # media diff {'tracks': {'changed': {4: {'tags': {'added': {'Yolo'}}}}}}
# if DIFF_ADDED_KEY in changedTrackDiff[MediaDescriptor.TAGS_KEY]:
# for addedTagKey in changedTrackDiff[MediaDescriptor.TAGS_KEY][DIFF_ADDED_KEY]:
#
# addedTagValue = targetTrackDescriptors[changedTargetTrackSourceIndex].getTags()[addedTagKey]
#
# # addedTagValue = changedTrackDiff[MediaDescriptor.TAGS_KEY][DIFF_ADDED_KEY][addedTagKey]
#
# # click.echo(f"addedTagValue={addedTagValue}")
# # click.echo(f"sourceTrackDescriptors: subindex={[s.getSubIndex() for s in sourceTrackDescriptors]} sourceindex={[s.getSourceIndex() for s in sourceTrackDescriptors]} tags={[s.getTags() for s in sourceTrackDescriptors]}")
# # click.echo(f"targetTrackDescriptors: subindex={[t.getSubIndex() for t in targetTrackDescriptors]} sourceindex={[t.getSourceIndex() for t in targetTrackDescriptors]} tags={[t.getTags() for t in targetTrackDescriptors]}")
# # click.echo(f"changed track_index={changedTrackIndex} indicator={changedTargetTrackDescriptor.getType().indicator()} key={addedTagKey} value={addedTagValue} source_index={changedSourceTrackIndex}")
#
# metadataTokens += [f"-metadata:s:{changedTargetTrackDescriptor.getType().indicator()}:{changedTargetSourceSubIndex}",
# f"{addedTagKey}={addedTagValue}"]
#
# # media diff {'tracks': {'changed': {4: {'tags': {'added': {'Yolo'}}}}}}
# if DIFF_CHANGED_KEY in changedTrackDiff[MediaDescriptor.TAGS_KEY]:
# for changedTagKey in changedTrackDiff[MediaDescriptor.TAGS_KEY][DIFF_CHANGED_KEY]:
#
# changedTagValue = targetTrackDescriptors[changedTargetTrackSourceIndex].getTags()[changedTagKey]
# # sourceSubIndex = sourceTrackDescriptors[changedTargetTrackSourceIndex].getSubIndex()
# # addedTagValue = changedTrackDiff[MediaDescriptor.TAGS_KEY][DIFF_ADDED_KEY][addedTagKey]
#
# # click.echo(f"addedTagValue={addedTagValue}")
# # click.echo(f"sourceTrackDescriptors: subindex={[s.getSubIndex() for s in sourceTrackDescriptors]} sourceindex={[s.getSourceIndex() for s in sourceTrackDescriptors]} tags={[s.getTags() for s in sourceTrackDescriptors]}")
# # click.echo(f"targetTrackDescriptors: subindex={[t.getSubIndex() for t in targetTrackDescriptors]} sourceindex={[t.getSourceIndex() for t in targetTrackDescriptors]} tags={[t.getTags() for t in targetTrackDescriptors]}")
# # click.echo(f"changed track_index={changedTrackIndex} indicator={changedTargetTrackDescriptor.getType().indicator()} key={addedTagKey} value={addedTagValue} source_index={changedSourceTrackIndex}")
#
# metadataTokens += [f"-metadata:s:{changedTargetTrackDescriptor.getType().indicator()}:{changedTargetSourceSubIndex}",
# f"{changedTagKey}={changedTagValue}"]
#
# # if TrackDescriptor.DISPOSITION_SET_KEY in changedTrackDiff.keys():
#
# # if DIFF_ADDED_KEY in changedTrackDiff[TrackDescriptor.DISPOSITION_SET_KEY]:
# # for addedDisposition in changedTrackDiff[TrackDescriptor.DISPOSITION_SET_KEY][DIFF_ADDED_KEY]:
# # # row = (f"changed {changedTargetTrackDescriptor.getType().label()} track index={changedTrackIndex} added disposition={addedDisposition.label()}",)
# # # self.differencesTable.add_row(*map(str, row))
# # pass
#
# # if DIFF_REMOVED_KEY in changedTrackDiff[TrackDescriptor.DISPOSITION_SET_KEY]:
# # for removedDisposition in changedTrackDiff[TrackDescriptor.DISPOSITION_SET_KEY][DIFF_REMOVED_KEY]:
# # # row = (f"changed {changedTargetTrackDescriptor.getType().label()} track index={changedTrackIndex} removed disposition={removedDisposition.label()}",)
# # # self.differencesTable.add_row(*map(str, row))
# # pass
# return metadataTokens
def generateMetadataTokens(self): def generateMetadataTokens(self):
"""Source media descriptor is mandatory"""
mappingTokens = [] metadataTokens = []
# click.echo(f"source media descriptor: track indices={[d.getIndex() for d in sourceMediaDescriptor.getAllTrackDescriptors()]}") for tagKey, tagValue in self.__targetMediaDescriptor.getTags().items():
# click.echo(f"target media descriptor: track indices={[d.getIndex() for d in targetMediaDescriptor.getAllTrackDescriptors()]}") metadataTokens += [f"-metadata:g",
f"{tagKey}={tagValue}"]
# +jellyfin -jellyfin #HINT: With current ffmpeg version track metadata tags are not passed to the outfile
mediaDifferences = self.__targetMediaDescriptor.compare(self.__sourceMediaDescriptor) for td in self.__targetMediaDescriptor.getAllTrackDescriptors():
# media diff {'tracks': {'changed': {4: {'tags': {'added': {'Yolo'}}}}}} for tagKey, tagValue in td.getTags().items():
click.echo(f"media diff {mediaDifferences}") metadataTokens += [f"-metadata:s:{td.getType().indicator()}:{td.getSubIndex()}",
f"{tagKey}={tagValue}"]
if MediaDescriptor.TAGS_KEY in mediaDifferences.keys(): return metadataTokens
sourceTags = self.__sourceMediaDescriptor.getTags()
targetTags = self.__targetMediaDescriptor.getTags()
if DIFF_REMOVED_KEY in mediaDifferences[MediaDescriptor.TAGS_KEY].keys():
# for removedTagKey in mediaDifferences[MediaDescriptor.TAGS_KEY][DIFF_REMOVED_KEY]:
# row = (f"removed media tag: key='{removedTagKey}' value='{sourceTags[removedTagKey]}'",)
# self.differencesTable.add_row(*map(str, row))
mappingTokens += [f"-map_metadata:g", "-1"]
for targetMediaTagKey in targetTags:
mappingTokens += [f"-metadata:g", f"{targetMediaTagKey}={targetTags[targetMediaTagKey]}"]
else:
if DIFF_ADDED_KEY in mediaDifferences[MediaDescriptor.TAGS_KEY].keys():
for addedTagKey in mediaDifferences[MediaDescriptor.TAGS_KEY][DIFF_ADDED_KEY]:
# row = (f"added media tag: key='{addedTagKey}' value='{targetTags[addedTagKey]}'",)
click.echo(f"added metadata key='{addedTagKey}' value='{targetTags[addedTagKey]}'->'{targetTags[addedTagKey]}'")
# self.differencesTable.add_row(*map(str, row))
#pass
mappingTokens += [f"-metadata:g", f"{addedTagKey}={targetTags[addedTagKey]}"]
if DIFF_CHANGED_KEY in mediaDifferences[MediaDescriptor.TAGS_KEY].keys():
for changedTagKey in mediaDifferences[MediaDescriptor.TAGS_KEY][DIFF_CHANGED_KEY]:
#row = (f"changed media tag: key='{changedTagKey}' value='{sourceTags[changedTagKey]}'->'{targetTags[changedTagKey]}'",)
click.echo(f"changed metadata key='{changedTagKey}' value='{sourceTags[changedTagKey]}'->'{targetTags[changedTagKey]}'")
# self.differencesTable.add_row(*map(str, row))
#pass
mappingTokens += [f"-metadata:g", f"{changedTagKey}={targetTags[changedTagKey]}"]
if MediaDescriptor.TRACKS_KEY in mediaDifferences.keys():
sourceTrackDescriptors = self.__sourceMediaDescriptor.getAllTrackDescriptors()
targetTrackDescriptors = self.__targetMediaDescriptor.getReorderedTrackDescriptors()
if DIFF_ADDED_KEY in mediaDifferences[MediaDescriptor.TRACKS_KEY].keys():
addedTracksIndices = mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_ADDED_KEY]
raise click.ClickException(f"FfxController.generateMetadataTokens(): Adding tracks is not supported. Track indices {addedTracksIndices}")
#raise click.ClickException(f"add track {mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_ADDED_KEY]}")
#for addedTrackIndex in mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_ADDED_KEY]:
#addedTrack : Track = targetTrackDescriptors[addedTrackIndex]
# row = (f"added {addedTrack.getType().label()} track: index={addedTrackIndex} lang={addedTrack.getLanguage().threeLetter()}",)
# self.differencesTable.add_row(*map(str, row))
if DIFF_REMOVED_KEY in mediaDifferences[MediaDescriptor.TRACKS_KEY].keys():
removedTracksIndices = mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_ADDED_KEY].keys()
raise click.ClickException(f"FfxController.generateMetadataTokens(): Removing tracks is not supported. Track indices {removedTracksIndices}")
#for removedTrackIndex in mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_REMOVED_KEY]:
# row = (f"removed track: index={removedTrackIndex}",)
# self.differencesTable.add_row(*map(str, row))
# media diff {'tracks': {'changed': {4: {'tags': {'added': {'Yolo'}}}}}}
if DIFF_CHANGED_KEY in mediaDifferences[MediaDescriptor.TRACKS_KEY].keys():
for changedTrackIndex in mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_CHANGED_KEY].keys():
changedTargetTrackDescriptor : TrackDescriptor = targetTrackDescriptors[changedTrackIndex]
changedTargetTrackSourceIndex = changedTargetTrackDescriptor.getSourceIndex()
changedTargetSourceSubIndex = sourceTrackDescriptors[changedTargetTrackSourceIndex].getSubIndex()
# changedSourceTrackDescriptor : TrackDescriptor = sourceTrackDescriptors[changedTargetTrackSourceIndex]
# changedSourceTrackSubIndex = changedSourceTrackDescriptor.getSubIndex()
changedTrackDiff : dict = mediaDifferences[MediaDescriptor.TRACKS_KEY][DIFF_CHANGED_KEY][changedTrackIndex]
if MediaDescriptor.TAGS_KEY in changedTrackDiff.keys():
if DIFF_REMOVED_KEY in changedTrackDiff[MediaDescriptor.TAGS_KEY]:
#for removedTagKey in changedTrackDiff[MediaDescriptor.TAGS_KEY][DIFF_REMOVED_KEY]:
# row = (f"changed {changedTargetTrackDescriptor.getType().label()} track index={changedTrackIndex} removed key={removedTagKey}",)
# self.differencesTable.add_row(*map(str, row))
#addedTagValue = targetTrackDescriptors[changedTargetTrackSourceIndex].getTags()[addedTagKey]
mappingTokens += [f"-map_metadata:s:{changedTargetTrackDescriptor.getType().indicator()}:{changedTargetSourceSubIndex}", "-1"]
for targetTrackTagKey, targetTrackTagValue in changedTargetTrackDescriptor.getTags():
mappingTokens += [f"-metadata:s:{changedTargetTrackDescriptor.getType().indicator()}:{changedTargetSourceSubIndex}",
f"{targetTrackTagKey}={targetTrackTagValue}"]
else:
# media diff {'tracks': {'changed': {4: {'tags': {'added': {'Yolo'}}}}}}
if DIFF_ADDED_KEY in changedTrackDiff[MediaDescriptor.TAGS_KEY]:
for addedTagKey in changedTrackDiff[MediaDescriptor.TAGS_KEY][DIFF_ADDED_KEY]:
addedTagValue = targetTrackDescriptors[changedTargetTrackSourceIndex].getTags()[addedTagKey]
# addedTagValue = changedTrackDiff[MediaDescriptor.TAGS_KEY][DIFF_ADDED_KEY][addedTagKey]
# click.echo(f"addedTagValue={addedTagValue}")
# click.echo(f"sourceTrackDescriptors: subindex={[s.getSubIndex() for s in sourceTrackDescriptors]} sourceindex={[s.getSourceIndex() for s in sourceTrackDescriptors]} tags={[s.getTags() for s in sourceTrackDescriptors]}")
# click.echo(f"targetTrackDescriptors: subindex={[t.getSubIndex() for t in targetTrackDescriptors]} sourceindex={[t.getSourceIndex() for t in targetTrackDescriptors]} tags={[t.getTags() for t in targetTrackDescriptors]}")
# click.echo(f"changed track_index={changedTrackIndex} indicator={changedTargetTrackDescriptor.getType().indicator()} key={addedTagKey} value={addedTagValue} source_index={changedSourceTrackIndex}")
mappingTokens += [f"-metadata:s:{changedTargetTrackDescriptor.getType().indicator()}:{changedTargetSourceSubIndex}",
f"{addedTagKey}={addedTagValue}"]
# media diff {'tracks': {'changed': {4: {'tags': {'added': {'Yolo'}}}}}}
if DIFF_CHANGED_KEY in changedTrackDiff[MediaDescriptor.TAGS_KEY]:
for changedTagKey in changedTrackDiff[MediaDescriptor.TAGS_KEY][DIFF_CHANGED_KEY]:
changedTagValue = targetTrackDescriptors[changedTargetTrackSourceIndex].getTags()[changedTagKey]
# sourceSubIndex = sourceTrackDescriptors[changedTargetTrackSourceIndex].getSubIndex()
# addedTagValue = changedTrackDiff[MediaDescriptor.TAGS_KEY][DIFF_ADDED_KEY][addedTagKey]
# click.echo(f"addedTagValue={addedTagValue}")
# click.echo(f"sourceTrackDescriptors: subindex={[s.getSubIndex() for s in sourceTrackDescriptors]} sourceindex={[s.getSourceIndex() for s in sourceTrackDescriptors]} tags={[s.getTags() for s in sourceTrackDescriptors]}")
# click.echo(f"targetTrackDescriptors: subindex={[t.getSubIndex() for t in targetTrackDescriptors]} sourceindex={[t.getSourceIndex() for t in targetTrackDescriptors]} tags={[t.getTags() for t in targetTrackDescriptors]}")
# click.echo(f"changed track_index={changedTrackIndex} indicator={changedTargetTrackDescriptor.getType().indicator()} key={addedTagKey} value={addedTagValue} source_index={changedSourceTrackIndex}")
mappingTokens += [f"-metadata:s:{changedTargetTrackDescriptor.getType().indicator()}:{changedTargetSourceSubIndex}",
f"{changedTagKey}={changedTagValue}"]
# if TrackDescriptor.DISPOSITION_SET_KEY in changedTrackDiff.keys():
# if DIFF_ADDED_KEY in changedTrackDiff[TrackDescriptor.DISPOSITION_SET_KEY]:
# for addedDisposition in changedTrackDiff[TrackDescriptor.DISPOSITION_SET_KEY][DIFF_ADDED_KEY]:
# # row = (f"changed {changedTargetTrackDescriptor.getType().label()} track index={changedTrackIndex} added disposition={addedDisposition.label()}",)
# # self.differencesTable.add_row(*map(str, row))
# pass
# if DIFF_REMOVED_KEY in changedTrackDiff[TrackDescriptor.DISPOSITION_SET_KEY]:
# for removedDisposition in changedTrackDiff[TrackDescriptor.DISPOSITION_SET_KEY][DIFF_REMOVED_KEY]:
# # row = (f"changed {changedTargetTrackDescriptor.getType().label()} track index={changedTrackIndex} removed disposition={removedDisposition.label()}",)
# # self.differencesTable.add_row(*map(str, row))
# pass
return mappingTokens
def runJob(self, def runJob(self,
@@ -390,11 +413,11 @@ class FfxController():
if videoEncoder == VideoEncoder.VP9: if videoEncoder == VideoEncoder.VP9:
commandSequence1 = (commandTokens commandSequence1 = (commandTokens
+ self.__targetMediaDescriptor.getInputMappingTokens() + self.__targetMediaDescriptor.getInputMappingTokens(only_video=True)
+ self.generateVP9Pass1Tokens(int(quality))) + self.generateVP9Pass1Tokens(int(quality)))
if self.__context['perform_crop']: if self.__context['perform_crop']:
commandSequence1 += FfxController.generateCropTokens() commandSequence1 += self.generateCropTokens()
commandSequence1 += FfxController.NULL_TOKENS commandSequence1 += FfxController.NULL_TOKENS
@@ -420,7 +443,7 @@ class FfxController():
commandSequence2 += self.generateVP9Pass2Tokens(int(quality)) + self.generateAudioEncodingTokens() commandSequence2 += self.generateVP9Pass2Tokens(int(quality)) + self.generateAudioEncodingTokens()
if self.__context['perform_crop']: if self.__context['perform_crop']:
commandSequence2 += FfxController.generateCropTokens() commandSequence2 += self.generateCropTokens()
commandSequence2 += self.generateOutputTokens(targetPath, commandSequence2 += self.generateOutputTokens(targetPath,
FfxController.DEFAULT_FILE_FORMAT, FfxController.DEFAULT_FILE_FORMAT,
@@ -429,4 +452,7 @@ class FfxController():
click.echo(f"Command 2: {' '.join(commandSequence2)}") click.echo(f"Command 2: {' '.join(commandSequence2)}")
if not self.__context['dry_run']: if not self.__context['dry_run']:
executeProcess(commandSequence2) out, err, rc = executeProcess(commandSequence2)
if rc:
raise click.ClickException(f"Command resulted in error: rc={rc} error={err}")

View File

@@ -186,6 +186,13 @@ class FileProperties():
return int(self.__episode) return int(self.__episode)
def getFilename(self):
return self.__sourceFilename
def getFileBasename(self):
return self.__sourceFileBasename
def assembleTargetFileBasename(self, def assembleTargetFileBasename(self,
label: str = "", label: str = "",
quality: int = -1, quality: int = -1,

View File

@@ -61,15 +61,6 @@ class MediaDescriptor:
else: else:
self.__trackDescriptors = [] self.__trackDescriptors = []
if MediaDescriptor.CLEAR_TAGS_FLAG_KEY in kwargs.keys():
if type(kwargs[MediaDescriptor.CLEAR_TAGS_FLAG_KEY]) is not bool:
raise TypeError(
f"MediaDescriptor.__init__(): Argument {MediaDescriptor.CLEAR_TAGS_FLAG_KEY} is required to be of type bool"
)
self.__clearTags = kwargs[MediaDescriptor.CLEAR_TAGS_FLAG_KEY]
else:
self.__clearTags = False
if MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY in kwargs.keys(): if MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY in kwargs.keys():
if type(kwargs[MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY]) is not bool: if type(kwargs[MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY]) is not bool:
raise TypeError( raise TypeError(
@@ -79,77 +70,6 @@ class MediaDescriptor:
else: else:
self.__jellyfinOrder = False self.__jellyfinOrder = False
def getDefaultVideoTrack(self):
videoDefaultTracks = [
v
for v in self.getVideoTracks()
if TrackDisposition.DEFAULT in v.getDispositionSet()
]
if len(videoDefaultTracks) > 1:
raise ValueError(
"MediaDescriptor.getDefaultVideoTrack(): More than one default video track is not supported"
)
return videoDefaultTracks[0] if videoDefaultTracks else None
def getForcedVideoTrack(self):
videoForcedTracks = [
v
for v in self.getVideoTracks()
if TrackDisposition.FORCED in v.getDispositionSet()
]
if len(videoForcedTracks) > 1:
raise ValueError(
"MediaDescriptor.getForcedVideoTrack(): More than one forced video track is not supported"
)
return videoForcedTracks[0] if videoForcedTracks else None
def getDefaultAudioTrack(self):
audioDefaultTracks = [
a
for a in self.getAudioTracks()
if TrackDisposition.DEFAULT in a.getDispositionSet()
]
if len(audioDefaultTracks) > 1:
raise ValueError(
"MediaDescriptor.getDefaultAudioTrack(): More than one default audio track is not supported"
)
return audioDefaultTracks[0] if audioDefaultTracks else None
def getForcedAudioTrack(self):
audioForcedTracks = [
a
for a in self.getAudioTracks()
if TrackDisposition.FORCED in a.getDispositionSet()
]
if len(audioForcedTracks) > 1:
raise ValueError(
"MediaDescriptor.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(
"MediaDescriptor.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(
"MediaDescriptor.getForcedSubtitleTrack(): More than one forced subtitle track is not supported"
)
return subtitleForcedTracks[0] if subtitleForcedTracks else None
def setDefaultSubTrack(self, trackType: TrackType, subIndex: int): def setDefaultSubTrack(self, trackType: TrackType, subIndex: int):
for t in self.getAllTrackDescriptors(): for t in self.getAllTrackDescriptors():
@@ -165,56 +85,66 @@ class MediaDescriptor:
TrackDisposition.FORCED, t.getSubIndex() == int(subIndex) TrackDisposition.FORCED, t.getSubIndex() == int(subIndex)
) )
def checkDefaultAndForcedDispositions(self):
try:
self.getDefaultVideoTrack()
self.getForcedVideoTrack()
self.getDefaultAudioTrack()
self.getForcedAudioTrack()
self.getDefaultSubtitleTrack()
self.getForcedSubtitleTrack()
return True
except ValueError:
return False
def getReorderedTrackDescriptors(self): def checkConfiguration(self):
videoTracks = self.sortSubIndices(self.getVideoTracks()) videoTracks = self.getVideoTracks()
audioTracks = self.sortSubIndices(self.getAudioTracks()) audioTracks = self.getAudioTracks()
subtitleTracks = self.sortSubIndices(self.getSubtitleTracks()) subtitleTracks = self.getSubtitleTracks()
videoDefaultTrack = self.getDefaultVideoTrack() if len([v for v in videoTracks if v.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
self.getForcedVideoTrack() raise ValueError('More than one default video track')
audioDefaultTrack = self.getDefaultAudioTrack() if len([a for a in audioTracks if a.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
self.getForcedAudioTrack() raise ValueError('More than one default audio track')
subtitleDefaultTrack = self.getDefaultSubtitleTrack() if len([s for s in subtitleTracks if s.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
self.getForcedSubtitleTrack() raise ValueError('More than one default subtitle track')
if self.__jellyfinOrder: if len([v for v in videoTracks if v.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
if not videoDefaultTrack is None: raise ValueError('More than one forced video track')
videoTracks.append( if len([a for a in audioTracks if a.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
videoTracks.pop(videoTracks.index(videoDefaultTrack)) raise ValueError('More than one forced audio track')
) if len([s for s in subtitleTracks if s.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
if not audioDefaultTrack is None: raise ValueError('More than one forced subtitle track')
audioTracks.append(
audioTracks.pop(audioTracks.index(audioDefaultTrack))
)
if not subtitleDefaultTrack is None:
subtitleTracks.append(
subtitleTracks.pop(subtitleTracks.index(subtitleDefaultTrack))
)
reorderedTrackDescriptors = videoTracks + audioTracks + subtitleTracks trackDescriptors = videoTracks + audioTracks + subtitleTracks
orderedSourceTrackSequence = [ sourceIndices = [
t.getSourceIndex() for t in reorderedTrackDescriptors t.getSourceIndex() for t in trackDescriptors
] ]
if len(set(sourceIndices)) < len(trackDescriptors):
raise ValueError('Multiple streams originating from the same source stream')
if len(set(orderedSourceTrackSequence)) < len(orderedSourceTrackSequence):
raise ValueError(
f"Multiple streams originating from the same source stream not supported"
)
return reorderedTrackDescriptors def applyJellyfinOrder(self):
"""Reorder subtracks in types with default the last, then make subindices flat again"""
# videoTracks = self.sortSubIndices(self.getVideoTracks())
# audioTracks = self.sortSubIndices(self.getAudioTracks())
# subtitleTracks = self.sortSubIndices(self.getSubtitleTracks())
self.checkConfiguration()
# from self.__trackDescriptors
videoTracks = self.getVideoTracks()
audioTracks = self.getAudioTracks()
subtitleTracks = self.getSubtitleTracks()
defaultVideoTracks = [v for v in videoTracks if v.getDispositionFlag(TrackDisposition.DEFAULT)]
defaultAudioTracks = [a for a in audioTracks if a.getDispositionFlag(TrackDisposition.DEFAULT)]
defaultSubtitleTracks = [s for s in subtitleTracks if s.getDispositionFlag(TrackDisposition.DEFAULT)]
if defaultVideoTracks:
videoTracks.append(videoTracks.pop(videoTracks.index(defaultVideoTracks[0])))
self.sortSubIndices(videoTracks)
if defaultAudioTracks:
audioTracks.append(audioTracks.pop(audioTracks.index(defaultAudioTracks[0])))
self.sortSubIndices(audioTracks)
if defaultSubtitleTracks:
subtitleTracks.append(subtitleTracks.pop(subtitleTracks.index(defaultSubtitleTracks[0])))
self.sortSubIndices(subtitleTracks)
self.__trackDescriptors = videoTracks + audioTracks + subtitleTracks
self.sortIndices(self.__trackDescriptors)
@classmethod @classmethod
def fromFfprobe(cls, formatData, streamData): def fromFfprobe(cls, formatData, streamData):
@@ -253,43 +183,46 @@ class MediaDescriptor:
def getTags(self): def getTags(self):
return self.__mediaTags return self.__mediaTags
def sortSubIndices( def sortSubIndices(
self, descriptors: List[TrackDescriptor] self, descriptors: List[TrackDescriptor]
) -> List[TrackDescriptor]: ) -> List[TrackDescriptor]:
subIndex = 0 subIndex = 0
for t in descriptors: for d in descriptors:
t.setSubIndex(subIndex) d.setSubIndex(subIndex)
subIndex += 1 subIndex += 1
return descriptors return descriptors
def sortIndices(
self, descriptors: List[TrackDescriptor]
) -> List[TrackDescriptor]:
index = 0
for d in descriptors:
d.setIndex(index)
index += 1
return descriptors
def getAllTrackDescriptors(self) -> List[TrackDescriptor]: def getAllTrackDescriptors(self) -> List[TrackDescriptor]:
return self.getVideoTracks() + self.getAudioTracks() + self.getSubtitleTracks() return self.getVideoTracks() + self.getAudioTracks() + self.getSubtitleTracks()
def getVideoTracks(self) -> List[TrackDescriptor]: def getVideoTracks(self) -> List[TrackDescriptor]:
return [ return [
v for v in self.__trackDescriptors.copy() if v.getType() == TrackType.VIDEO v for v in self.__trackDescriptors if v.getType() == TrackType.VIDEO
] ]
def getAudioTracks(self) -> List[TrackDescriptor]: def getAudioTracks(self) -> List[TrackDescriptor]:
return [ return [
a for a in self.__trackDescriptors.copy() if a.getType() == TrackType.AUDIO a for a in self.__trackDescriptors if a.getType() == TrackType.AUDIO
] ]
def getSubtitleTracks(self) -> List[TrackDescriptor]: def getSubtitleTracks(self) -> List[TrackDescriptor]:
return [ return [
s s
for s in self.__trackDescriptors.copy() for s in self.__trackDescriptors
if s.getType() == TrackType.SUBTITLE 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, vsMediaDescriptor: Self): def compare(self, vsMediaDescriptor: Self):
@@ -301,6 +234,10 @@ class MediaDescriptor:
vsTags = vsMediaDescriptor.getTags() vsTags = vsMediaDescriptor.getTags()
tags = self.getTags() tags = self.getTags()
# tags ist leer
# click.echo(f"tags={tags} vsTags={vsTags}")
# raise click.Abort
# HINT: Some tags differ per file, for example creation_time, so these are removed before diff # HINT: Some tags differ per file, for example creation_time, so these are removed before diff
for emt in MediaDescriptor.EXCLUDED_MEDIA_TAGS: for emt in MediaDescriptor.EXCLUDED_MEDIA_TAGS:
if emt in tags.keys(): if emt in tags.keys():
@@ -317,7 +254,7 @@ class MediaDescriptor:
# Target track configuration (from DB) # Target track configuration (from DB)
# tracks = self.getAllTrackDescriptors() # tracks = self.getAllTrackDescriptors()
tracks = self.getReorderedTrackDescriptors() tracks = self.getAllTrackDescriptors() # filtern
numTracks = len(tracks) numTracks = len(tracks)
# Current track configuration (of file) # Current track configuration (of file)
@@ -371,14 +308,15 @@ class MediaDescriptor:
def getImportFileTokens(self, use_sub_index: bool = True): def getImportFileTokens(self, use_sub_index: bool = True):
reorderedTrackDescriptors = self.getReorderedTrackDescriptors() # reorderedTrackDescriptors = self.getReorderedTrackDescriptors()
importFileTokens = [] importFileTokens = []
for rtd in reorderedTrackDescriptors: #for rtd in reorderedTrackDescriptors:
for td in self.__trackDescriptors:
importedFilePath = rtd.getExternalSourceFilePath() importedFilePath = td.getExternalSourceFilePath()
if not importedFilePath is None: if importedFilePath:
importFileTokens += [ importFileTokens += [
"-i", "-i",
importedFilePath, importedFilePath,
@@ -386,39 +324,51 @@ class MediaDescriptor:
return importFileTokens return importFileTokens
def getInputMappingTokens(self, use_sub_index: bool = True):
reorderedTrackDescriptors = self.getReorderedTrackDescriptors() def getInputMappingTokens(self, use_sub_index: bool = True, only_video: bool = False):
# reorderedTrackDescriptors = self.getReorderedTrackDescriptors()
inputMappingTokens = [] inputMappingTokens = []
filePointer = 1 filePointer = 1
for rtd in reorderedTrackDescriptors: #for rtd in reorderedTrackDescriptors:
for td in self.__trackDescriptors:
importedFilePath = rtd.getExternalSourceFilePath() trackType = td.getType()
if (trackType == TrackType.VIDEO or not only_video):
importedFilePath = td.getExternalSourceFilePath()
if use_sub_index:
if importedFilePath:
inputMappingTokens += [
"-map",
f"{filePointer}:{trackType.indicator()}:0",
]
filePointer += 1
else:
if td.getCodec() != TrackDescriptor.CODEC_PGS:
inputMappingTokens += [
"-map",
f"0:{trackType.indicator()}:{td.getSubIndex()}",
]
trackType = rtd.getType()
if use_sub_index:
if importedFilePath is None:
inputMappingTokens += [
"-map",
f"0:{trackType.indicator()}:{rtd.getSubIndex()}",
]
else: else:
inputMappingTokens += [ if td.getCodec() != TrackDescriptor.CODEC_PGS:
"-map", inputMappingTokens += ["-map", f"0:{td.getIndex()}"]
f"{filePointer}:{trackType.indicator()}:0",
]
filePointer += 1
else:
inputMappingTokens += ["-map", f"0:{rtd.getIndex()}"]
return inputMappingTokens return inputMappingTokens
def searchSubtitleFiles(searchDirectory, prefix): def searchSubtitleFiles(self, searchDirectory, prefix):
sesl_match = re.compile(MediaDescriptor.SEASON_EPISODE_STREAM_LANGUAGE_MATCH) sesl_match = re.compile(MediaDescriptor.SEASON_EPISODE_STREAM_LANGUAGE_MATCH)
availableFileSubtitleDescriptors = [] subtitleFileDescriptors = []
for subtitleFilename in os.listdir(searchDirectory): for subtitleFilename in os.listdir(searchDirectory):
if subtitleFilename.startswith(prefix) and subtitleFilename.endswith( if subtitleFilename.startswith(prefix) and subtitleFilename.endswith(
"." + MediaDescriptor.SUBTITLE_FILE_EXTENSION "." + MediaDescriptor.SUBTITLE_FILE_EXTENSION
@@ -432,48 +382,48 @@ class MediaDescriptor:
subtitleFileDescriptor["path"] = subtitleFilePath subtitleFileDescriptor["path"] = subtitleFilePath
subtitleFileDescriptor["season"] = int(sesl_result.group(1)) subtitleFileDescriptor["season"] = int(sesl_result.group(1))
subtitleFileDescriptor["episode"] = int(sesl_result.group(2)) subtitleFileDescriptor["episode"] = int(sesl_result.group(2))
subtitleFileDescriptor["stream"] = int(sesl_result.group(3)) subtitleFileDescriptor["index"] = int(sesl_result.group(3))
subtitleFileDescriptor["language"] = sesl_result.group(4) subtitleFileDescriptor["language"] = sesl_result.group(4)
availableFileSubtitleDescriptors.append(subtitleFileDescriptor) subtitleFileDescriptors.append(subtitleFileDescriptor)
click.echo( click.echo(f"Available subtitle files {subtitleFileDescriptors}\n")
f"Found {len(availableFileSubtitleDescriptors)} subtitles in files\n"
)
return availableFileSubtitleDescriptors return subtitleFileDescriptors
def importSubtitles(
self, searchDirectory, prefix, season: int = -1, episode: int = -1
):
availableFileSubtitleDescriptors = self.searchSubtitleFiles( def importSubtitles(self, searchDirectory, prefix, season: int = -1, episode: int = -1):
searchDirectory, prefix
) click.echo(f"Season: {season} Episode: {episode}")
availableFileSubtitleDescriptors = self.searchSubtitleFiles(searchDirectory, prefix)
click.echo(f"availableFileSubtitleDescriptors: {availableFileSubtitleDescriptors}")
subtitleTracks = self.getSubtitleTracks() subtitleTracks = self.getSubtitleTracks()
click.echo(f"subtitleTracks: {[s.getIndex() for s in subtitleTracks]}")
# if len(availableFileSubtitleDescriptors) != len(subtitleTracks): # if len(availableFileSubtitleDescriptors) != len(subtitleTracks):
# raise click.ClickException(f"MediaDescriptor.importSubtitles(): Number if subtitle files not matching number of subtitle tracks") # raise click.ClickException(f"MediaDescriptor.importSubtitles(): Number if subtitle files not matching number of subtitle tracks")
matchingFileSubtitleDescriptors = ( matchingSubtitleFileDescriptors = (
sorted( sorted(
[ [
d d
for d in availableFileSubtitleDescriptors for d in availableFileSubtitleDescriptors
if d["season"] == int(season) and d["episode"] == int(episode) if d["season"] == int(season) and d["episode"] == int(episode)
], ],
key=lambda d: d["stream"], key=lambda d: d["index"],
) )
if availableFileSubtitleDescriptors if availableFileSubtitleDescriptors
else [] else []
) )
for mfsd in matchingFileSubtitleDescriptors: click.echo(f"matchingSubtitleFileDescriptors: {matchingSubtitleFileDescriptors}")
matchingSubtitleTrackDescriptor = [
s for s in subtitleTracks if s.getIndex() == mfsd["stream"] for msfd in matchingSubtitleFileDescriptors:
] matchingSubtitleTrackDescriptor = [s for s in subtitleTracks if s.getIndex() == msfd["index"]]
if matchingSubtitleTrackDescriptor: if matchingSubtitleTrackDescriptor:
matchingSubtitleTrackDescriptor[0].setExternalSourceFilePath( click.echo(f"Found matching subtitle file {msfd["path"]}\n")
mfsd["path"] matchingSubtitleTrackDescriptor[0].setExternalSourceFilePath(msfd["path"])
)

View File

@@ -156,6 +156,7 @@ class MediaDetailsScreen(Screen):
#HINT: This is None if the filename did not match anything in database #HINT: This is None if the filename did not match anything in database
self.__currentPattern = self.__mediaFileProperties.getPattern() self.__currentPattern = self.__mediaFileProperties.getPattern()
# keine tags vorhanden
self.__targetMediaDescriptor = self.__currentPattern.getMediaDescriptor() if self.__currentPattern is not None else None self.__targetMediaDescriptor = self.__currentPattern.getMediaDescriptor() if self.__currentPattern is not None else None
# Enumerating differences between media descriptors # Enumerating differences between media descriptors
@@ -392,8 +393,8 @@ class MediaDetailsScreen(Screen):
# 7 # 7
yield Static(" ") yield Static(" ")
yield Button("Select Default", id="select_default_button") yield Button("Set Default", id="select_default_button")
yield Button("Select Forced", id="select_forced_button") yield Button("Set Forced", id="select_forced_button")
yield Static(" ") yield Static(" ")
# 8 # 8
yield Static("Streams") yield Static("Streams")
@@ -403,8 +404,15 @@ class MediaDetailsScreen(Screen):
yield Footer() yield Footer()
def getPatternFromInput(self): def getPatternDescriptorFromInput(self):
return str(self.query_one("#pattern_input", Input).value) """Returns show id and pattern from corresponding inputs"""
patternDescriptor = {}
try:
patternDescriptor['show_id'] = self.getSelectedShowDescriptor().getId()
patternDescriptor['pattern'] = str(self.query_one("#pattern_input", Input).value)
except:
pass
return patternDescriptor
def on_button_pressed(self, event: Button.Pressed) -> None: def on_button_pressed(self, event: Button.Pressed) -> None:
@@ -492,19 +500,25 @@ class MediaDetailsScreen(Screen):
if showRowIndex is not None: if showRowIndex is not None:
self.showsTable.move_cursor(row=showRowIndex) self.showsTable.move_cursor(row=showRowIndex)
patternDescriptor = {} patternDescriptor = self.getPatternDescriptorFromInput()
patternDescriptor['show_id'] = showDescriptor.getId()
patternDescriptor['pattern'] = self.getPatternFromInput()
self.__pc.addPattern(patternDescriptor)
self.highlightPattern(False) if patternDescriptor:
patternId = self.__pc.addPattern(patternDescriptor)
self.action_update_pattern() self.highlightPattern(False)
for tagKey, tagValue in self.__currentMediaDescriptor.getTags().items():
self.__tac.updateMediaTag(patternId, tagKey, tagValue)
for trackDescriptor in self.__currentMediaDescriptor.getAllTrackDescriptors():
self.__tc.addTrack(trackDescriptor, patternId = patternId)
def action_new_pattern(self): def action_new_pattern(self):
if not self.__currentMediaDescriptor.checkDefaultAndForcedDispositions(): try:
self.__currentMediaDescriptor.checkConfiguration()
except ValueError:
return return
selectedShowDescriptor = self.getSelectedShowDescriptor() selectedShowDescriptor = self.getSelectedShowDescriptor()
@@ -521,13 +535,10 @@ class MediaDetailsScreen(Screen):
"""When updating the database the actions must reverse the difference (eq to diff db->file)""" """When updating the database the actions must reverse the difference (eq to diff db->file)"""
if self.__currentPattern is not None: if self.__currentPattern is not None:
inputPattern = self.getPatternFromInput() patternDescriptor = self.getPatternDescriptorFromInput()
if self.__currentPattern.getPattern() != inputPattern: if (patternDescriptor
patternDescriptor = {} and self.__currentPattern.getPattern() != patternDescriptor['pattern']):
patternDescriptor['show_id'] = self.getSelectedShowDescriptor().getId() return self.__pc.updatePattern(self.__currentPattern.getId(), patternDescriptor)
patternDescriptor['pattern'] = inputPattern
self.__pc.updatePattern(self.__currentPattern.getId(), patternDescriptor)
self.loadProperties() self.loadProperties()
@@ -614,7 +625,7 @@ class MediaDetailsScreen(Screen):
def action_edit_pattern(self): def action_edit_pattern(self):
patternDescriptor = {} patternDescriptor = {}
patternDescriptor['show_id'] = self.getSelectedShow()['id'] patternDescriptor['show_id'] = self.getSelectedShowDescriptor().getId()
patternDescriptor['pattern'] = self.getPatternFromInput() patternDescriptor['pattern'] = self.getPatternFromInput()
if patternDescriptor['pattern']: if patternDescriptor['pattern']:
@@ -624,7 +635,7 @@ class MediaDetailsScreen(Screen):
if selectedPatternId is None: if selectedPatternId is None:
raise click.ClickException(f"MediaDetailsScreen.action_edit_pattern(): Pattern to edit has no id") raise click.ClickException(f"MediaDetailsScreen.action_edit_pattern(): Pattern to edit has no id")
self.app.push_screen(PatternDetailsScreen(patternId = selectedPatternId, showId = self.getSelectedShow()['id']), self.handle_edit_pattern) # <- self.app.push_screen(PatternDetailsScreen(patternId = selectedPatternId, showId = self.getSelectedShowDescriptor().getId()), self.handle_edit_pattern) # <-
def handle_edit_pattern(self, screenResult): def handle_edit_pattern(self, screenResult):

View File

@@ -62,7 +62,13 @@ class Pattern(Base):
kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY] = [] kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY] = []
# Set ordered subindices
subIndexCounter = {}
for track in self.tracks: for track in self.tracks:
kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY].append(track.getDescriptor()) trackType = track.getType()
if not trackType in subIndexCounter.keys():
subIndexCounter[trackType] = 0
kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY].append(track.getDescriptor(subIndex = subIndexCounter[trackType]))
subIndexCounter[trackType] += 1
return MediaDescriptor(**kwargs) return MediaDescriptor(**kwargs)

View File

@@ -44,6 +44,7 @@ class Track(Base):
disposition_flags = Column(Integer) disposition_flags = Column(Integer)
codec_name = Column(String)
audio_layout = Column(Integer) audio_layout = Column(Integer)
@@ -133,6 +134,7 @@ class Track(Base):
return cls(pattern_id = patternId, return cls(pattern_id = patternId,
track_type = trackType, track_type = trackType,
codec_name = streamObj[TrackDescriptor.FFPROBE_CODEC_NAME_KEY],
disposition_flags = sum([2**t.index() for (k,v) in streamObj[TrackDescriptor.FFPROBE_DISPOSITION_KEY].items() disposition_flags = sum([2**t.index() for (k,v) in streamObj[TrackDescriptor.FFPROBE_DISPOSITION_KEY].items()
if v and (t := TrackDisposition.find(k)) is not None]), if v and (t := TrackDisposition.find(k)) is not None]),
audio_layout = AudioLayout.identify(streamObj)) audio_layout = AudioLayout.identify(streamObj))
@@ -150,6 +152,9 @@ class Track(Base):
def getType(self): def getType(self):
return TrackType.fromIndex(self.track_type) return TrackType.fromIndex(self.track_type)
def getCodec(self):
return str(self.codec_name)
def getIndex(self): def getIndex(self):
return int(self.index) if self.index is not None else -1 return int(self.index) if self.index is not None else -1
@@ -198,6 +203,8 @@ class Track(Base):
kwargs[TrackDescriptor.SUB_INDEX_KEY] = subIndex kwargs[TrackDescriptor.SUB_INDEX_KEY] = subIndex
kwargs[TrackDescriptor.TRACK_TYPE_KEY] = self.getType() kwargs[TrackDescriptor.TRACK_TYPE_KEY] = self.getType()
kwargs[TrackDescriptor.CODEC_NAME_KEY] = self.getCodec()
kwargs[TrackDescriptor.DISPOSITION_SET_KEY] = self.getDispositionSet() kwargs[TrackDescriptor.DISPOSITION_SET_KEY] = self.getDispositionSet()
kwargs[TrackDescriptor.TAGS_KEY] = self.getTags() kwargs[TrackDescriptor.TAGS_KEY] = self.getTags()

View File

@@ -16,14 +16,14 @@ class PatternController():
try: try:
s = self.Session() s = self.Session()
q = s.query(Pattern).filter(Pattern.show_id == int(patternDescriptor['show_id']), Pattern.pattern == str(patternDescriptor['pattern'])) q = s.query(Pattern).filter(Pattern.show_id == int(patternDescriptor['show_id']))
if not q.count(): if not q.count():
pattern = Pattern(show_id = int(patternDescriptor['show_id']), pattern = Pattern(show_id = int(patternDescriptor['show_id']),
pattern = str(patternDescriptor['pattern'])) pattern = str(patternDescriptor['pattern']))
s.add(pattern) s.add(pattern)
s.commit() s.commit()
return int(pattern.getId()) return pattern.getId()
else: else:
return None return None

View File

@@ -1,6 +1,9 @@
import subprocess import subprocess
from typing import List
def executeProcess(commandSequence): def executeProcess(commandSequence: List[str]):
process = subprocess.Popen(commandSequence, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # process = subprocess.Popen([t.encode('utf-8') for t in commandSequence], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
process = subprocess.Popen(commandSequence, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8')
output, error = process.communicate() output, error = process.communicate()
return output.decode('utf-8'), error.decode('utf-8'), process.returncode # return output.decode('utf-8'), error.decode('utf-8'), process.returncode
return output, error, process.returncode

View File

@@ -1,4 +1,4 @@
import os, click, requests import os, click, requests, json
class TmdbController(): class TmdbController():
@@ -57,6 +57,17 @@ class TmdbController():
#TODO Check for result #TODO Check for result
try: try:
#TODO: Content Type aware processing
# response = requests.get(tmdbUrl)
# response.encoding = 'utf-8'
# return response.json()
# response = requests.get(tmdbUrl)
# contentType = response.headers.get('Content-Type')
# print(content_type) # Example: 'application/json; charset=UTF-8'
# decoded_content = response.content.decode('utf-8')
# return json.loads(decoded_content)
return requests.get(tmdbUrl).json() return requests.get(tmdbUrl).json()
except: except:
return {} return {}

View File

@@ -30,6 +30,7 @@ class TrackController():
s = self.Session() s = self.Session()
track = Track(pattern_id = patId, track = Track(pattern_id = patId,
track_type = int(trackDescriptor.getType().index()), track_type = int(trackDescriptor.getType().index()),
codec_name = str(trackDescriptor.getCodec()),
index = int(trackDescriptor.getIndex()), index = int(trackDescriptor.getIndex()),
source_index = int(trackDescriptor.getSourceIndex()), source_index = int(trackDescriptor.getSourceIndex()),
disposition_flags = int(TrackDisposition.toFlags(trackDescriptor.getDispositionSet())), disposition_flags = int(TrackDisposition.toFlags(trackDescriptor.getDispositionSet())),
@@ -66,6 +67,7 @@ class TrackController():
track : Track = q.first() track : Track = q.first()
track.track_type = int(trackDescriptor.getType().index()) track.track_type = int(trackDescriptor.getType().index())
track.codec_name = str(trackDescriptor.getCodec())
track.audio_layout = int(trackDescriptor.getAudioLayout().index()) track.audio_layout = int(trackDescriptor.getAudioLayout().index())
track.disposition_flags = int(TrackDisposition.toFlags(trackDescriptor.getDispositionSet())) track.disposition_flags = int(TrackDisposition.toFlags(trackDescriptor.getDispositionSet()))

View File

@@ -21,12 +21,16 @@ class TrackDescriptor:
TAGS_KEY = "tags" TAGS_KEY = "tags"
TRACK_TYPE_KEY = "track_type" TRACK_TYPE_KEY = "track_type"
CODEC_NAME_KEY = "codec_name"
AUDIO_LAYOUT_KEY = "audio_layout" AUDIO_LAYOUT_KEY = "audio_layout"
FFPROBE_INDEX_KEY = "index" FFPROBE_INDEX_KEY = "index"
FFPROBE_DISPOSITION_KEY = "disposition" FFPROBE_DISPOSITION_KEY = "disposition"
FFPROBE_TAGS_KEY = "tags" FFPROBE_TAGS_KEY = "tags"
FFPROBE_CODEC_TYPE_KEY = "codec_type" FFPROBE_CODEC_TYPE_KEY = "codec_type"
FFPROBE_CODEC_NAME_KEY = "codec_name"
CODEC_PGS = 'hdmv_pgs_subtitle'
def __init__(self, **kwargs): def __init__(self, **kwargs):
@@ -55,7 +59,7 @@ class TrackDescriptor:
) )
self.__externalSourceFilePath = kwargs[TrackDescriptor.EXTERNAL_SOURCE_FILE_PATH_KEY] self.__externalSourceFilePath = kwargs[TrackDescriptor.EXTERNAL_SOURCE_FILE_PATH_KEY]
else: else:
self.__externalSourceFilePath = None self.__externalSourceFilePath = ''
if TrackDescriptor.INDEX_KEY in kwargs.keys(): if TrackDescriptor.INDEX_KEY in kwargs.keys():
if type(kwargs[TrackDescriptor.INDEX_KEY]) is not int: if type(kwargs[TrackDescriptor.INDEX_KEY]) is not int:
@@ -92,6 +96,15 @@ class TrackDescriptor:
else: else:
self.__trackType = TrackType.UNKNOWN self.__trackType = TrackType.UNKNOWN
if TrackDescriptor.CODEC_NAME_KEY in kwargs.keys():
if type(kwargs[TrackDescriptor.CODEC_NAME_KEY]) is not str:
raise TypeError(
f"TrackDesciptor.__init__(): Argument {TrackDescriptor.CODEC_NAME_KEY} is required to be of type str"
)
self.__codecName = kwargs[TrackDescriptor.CODEC_NAME_KEY]
else:
self.__codecName = ''
if TrackDescriptor.TAGS_KEY in kwargs.keys(): if TrackDescriptor.TAGS_KEY in kwargs.keys():
if type(kwargs[TrackDescriptor.TAGS_KEY]) is not dict: if type(kwargs[TrackDescriptor.TAGS_KEY]) is not dict:
raise TypeError( raise TypeError(
@@ -187,6 +200,8 @@ class TrackDescriptor:
kwargs[TrackDescriptor.SUB_INDEX_KEY] = subIndex kwargs[TrackDescriptor.SUB_INDEX_KEY] = subIndex
kwargs[TrackDescriptor.TRACK_TYPE_KEY] = trackType kwargs[TrackDescriptor.TRACK_TYPE_KEY] = trackType
kwargs[TrackDescriptor.CODEC_NAME_KEY] = str(streamObj[TrackDescriptor.FFPROBE_CODEC_NAME_KEY])
kwargs[TrackDescriptor.DISPOSITION_SET_KEY] = ( kwargs[TrackDescriptor.DISPOSITION_SET_KEY] = (
{ {
t t
@@ -226,6 +241,9 @@ class TrackDescriptor:
def getIndex(self): def getIndex(self):
return self.__index return self.__index
def setIndex(self, index):
self.__index = index
def getSourceIndex(self): def getSourceIndex(self):
return self.__sourceIndex return self.__sourceIndex
@@ -238,6 +256,9 @@ class TrackDescriptor:
def getType(self): def getType(self):
return self.__trackType return self.__trackType
def getCodec(self):
return self.__codecName
def getLanguage(self): def getLanguage(self):
if "language" in self.__trackTags.keys(): if "language" in self.__trackTags.keys():
return IsoLanguage.findThreeLetter(self.__trackTags["language"]) return IsoLanguage.findThreeLetter(self.__trackTags["language"])