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.track_descriptor import TrackDescriptor
from ffx.track_type import TrackType
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
# Bugfixes, TMBD identify shows
# 0.1.2
# Bugfixes
# 0.1.3
# Subtitle file imports
@click.group()
@click.pass_context
@@ -56,52 +65,98 @@ def inspect(ctx, filename):
app = FfxApp(ctx.obj)
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()
# @click.pass_context
#
# @click.argument('paths', nargs=-1)
# @click.option('-l', '--label', type=str, default='', help='Label to be used as filename prefix')
#
# @click.option('-sd', '--subtitle-directory', type=str, default='', help='Load subtitles from here')
# @click.option('-sp', '--subtitle-prefix', type=str, default='', help='Subtitle filename prefix')
#
# @click.option("-o", "--output-directory", type=str, default='')
#
# @click.option("--dry-run", is_flag=True, default=False)
#
#
# def unmux(ctx,
# label,
# paths,
# subtitle_directory,
# subtitle_prefix,
# output_directory,
# dry_run):
#
# 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")
@ffx.command()
@click.pass_context
@click.argument('paths', nargs=-1)
@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("-s", "--subtitles-only", is_flag=True, default=False)
@click.option("--dry-run", is_flag=True, default=False)
def unmux(ctx,
paths,
label,
output_directory,
subtitles_only,
dry_run):
existingSourcePaths = [p for p in paths if os.path.isfile(p)]
click.echo(f"\nUnmuxing {len(existingSourcePaths)} files")
for sourcePath in existingSourcePaths:
fp = FileProperties(ctx.obj, sourcePath)
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()
@@ -157,6 +212,7 @@ def shows(ctx):
@click.option("-t", "--no-tmdb", 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)
@@ -170,6 +226,7 @@ def convert(ctx,
stereo_bitrate,
ac3_bitrate,
dts_bitrate,
subtitle_directory,
subtitle_prefix,
@@ -188,6 +245,7 @@ def convert(ctx,
denoise,
no_tmdb,
no_jellyfin,
no_pattern,
dry_run):
"""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['dry_run'] = True # dry_run
context['dry_run'] = dry_run
context['video_encoder'] = VideoEncoder.fromLabel(video_encoder)
context['jellyfin'] = not no_jellyfin
context['tmdb'] = not no_tmdb
context['use_jellyfin'] = not no_jellyfin
context['use_tmdb'] = not no_tmdb
context['use_pattern'] = not no_pattern
context['import_subtitles'] = (subtitle_directory and subtitle_prefix)
if context['import_subtitles']:
@@ -235,11 +294,11 @@ def convert(ctx,
cTokens = crop.split(',')
if cTokens and len(cTokens) == 2:
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']}")
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]
click.echo(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs")
@@ -262,7 +321,7 @@ def convert(ctx,
sourceMediaDescriptor = mediaFileProperties.getMediaDescriptor()
#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'}")
@@ -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.
# The correct tokens should then be created by
try:
sourceMediaDescriptor.getDefaultVideoTrack()
except ValueError:
if len([v for v in sourceMediaDescriptor.getVideoTracks() if v.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
defaultVideoTrackSubIndex = click.prompt("More than one default video stream detected! Please select stream", type=int)
sourceMediaDescriptor.setDefaultSubTrack(TrackType.VIDEO, defaultVideoTrackSubIndex)
try:
sourceMediaDescriptor.getForcedVideoTrack()
except ValueError:
if len([v for v in sourceMediaDescriptor.getVideoTracks() if v.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
forcedVideoTrackSubIndex = click.prompt("More than one forced video stream detected! Please select stream", type=int)
sourceMediaDescriptor.setForcedSubTrack(TrackType.VIDEO, forcedVideoTrackSubIndex)
try:
sourceMediaDescriptor.getDefaultAudioTrack()
except ValueError:
if len([a for a in sourceMediaDescriptor.getAudioTracks() if a.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
defaultAudioTrackSubIndex = click.prompt("More than one default audio stream detected! Please select stream", type=int)
sourceMediaDescriptor.setDefaultSubTrack(TrackType.AUDIO, defaultAudioTrackSubIndex)
try:
sourceMediaDescriptor.getForcedAudioTrack()
except ValueError:
if len([a for a in sourceMediaDescriptor.getAudioTracks() if a.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
forcedAudioTrackSubIndex = click.prompt("More than one forced audio stream detected! Please select stream", type=int)
sourceMediaDescriptor.setForcedSubTrack(TrackType.AUDIO, forcedAudioTrackSubIndex)
try:
sourceMediaDescriptor.getDefaultSubtitleTrack()
except ValueError:
if len([s for s in sourceMediaDescriptor.getSubtitleTracks() if s.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
defaultSubtitleTrackSubIndex = click.prompt("More than one default subtitle stream detected! Please select stream", type=int)
sourceMediaDescriptor.setDefaultSubTrack(TrackType.SUBTITLE, defaultSubtitleTrackSubIndex)
try:
sourceMediaDescriptor.getForcedSubtitleTrack()
except ValueError:
if len([s for s in sourceMediaDescriptor.getSubtitleTracks() if s.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
forcedSubtitleTrackSubIndex = click.prompt("More than one forced subtitle stream detected! Please select stream", type=int)
sourceMediaDescriptor.setForcedSubTrack(TrackType.SUBTITLE, forcedSubtitleTrackSubIndex)
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)
# mappingTokens = fc.generateMetadataTokens()
# click.echo(f"Metadata Tokens: {mappingTokens}")
dispositionTokens = fc.generateDispositionTokens()
click.echo(f"Disposition Tokens: {dispositionTokens}")
@@ -326,15 +379,14 @@ def convert(ctx,
# Case pattern matching
targetMediaDescriptor = currentPattern.getMediaDescriptor()
currentShowDescriptor = currentPattern.getShowDescriptor()
if context['tmdb']:
if context['use_tmdb']:
tmdbEpisodeResult = tc.queryEpisode(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode())
# click.echo(f"{tmdbEpisodeResult}")
if tmdbEpisodeResult:
fileBasename = tc.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(),
tmdbEpisodeResult['name'],
@@ -344,19 +396,28 @@ def convert(ctx,
currentShowDescriptor.getIndexEpisodeDigits(),
currentShowDescriptor.getIndicatorSeasonDigits(),
currentShowDescriptor.getIndicatorEpisodeDigits())
else:
fileBasename = currentShowDescriptor.getFilenamePrefix()
click.echo(f"fileBasename={fileBasename}")
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)
@@ -379,9 +440,10 @@ def convert(ctx,
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']
else mediaFileProperties.assembleTargetFileBasename(label if label else fileBasename,
q if len(q_list) > 1 else -1,
extraTokens = extra)
extraTokens = extra))
targetPath = os.path.join(output_directory if output_directory else sourceDirectory, targetFilename)
@@ -392,7 +454,7 @@ def convert(ctx,
preset,
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()
click.echo(f"\nDONE\nTime elapsed {endTime - startTime}")

View File

@@ -107,7 +107,8 @@ class FfxController():
def generateOutputTokens(self, filepath, format, ext):
return ['-f', format, f"{filepath}.{ext}"]
outputFilePath = f"{filepath}.{ext}"
return ['-f', format, outputFilePath]
def generateAudioEncodingTokens(self):
@@ -116,7 +117,8 @@ class FfxController():
audioTokens = []
#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
for trackDescriptor in targetAudioTrackDescriptors:
@@ -165,25 +167,25 @@ class FfxController():
return audioTokens
# -disposition:s:0 default -disposition:s:1 0
def generateDispositionTokens(self):
"""Source media descriptor is optional"""
sourceTrackDescriptors = [] if self.__sourceMediaDescriptor is None else self.__sourceMediaDescriptor.getAllTrackDescriptors()
targetTrackDescriptors = self.__targetMediaDescriptor.getReorderedTrackDescriptors()
# sourceTrackDescriptors = [] if self.__sourceMediaDescriptor is None else self.__sourceMediaDescriptor.getAllTrackDescriptors()
targetTrackDescriptors = self.__targetMediaDescriptor.getAllTrackDescriptors()
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:
# Calculate source sub index. This applies only if a source media descriptor is defined.
if sourceTrackDescriptors:
changedTargetTrackDescriptor : TrackDescriptor = targetTrackDescriptors[trackDescriptor.getIndex()]
changedTargetTrackSourceIndex = changedTargetTrackDescriptor.getSourceIndex()
subIndex = sourceTrackDescriptors[changedTargetTrackSourceIndex].getSubIndex()
else:
subIndex = trackDescriptor.getSubIndex()
#HINT: No dispositions for pgs subtitle tracks that have no external file source
if (trackDescriptor.getExternalSourceFilePath()
or trackDescriptor.getCodec() != TrackDescriptor.CODEC_PGS):
subIndex = trackDescriptor.getSubIndex()
streamIndicator = trackDescriptor.getType().indicator()
dispositionSet = trackDescriptor.getDispositionSet()
@@ -195,155 +197,176 @@ class FfxController():
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):
"""Source media descriptor is mandatory"""
mappingTokens = []
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()]}")
for tagKey, tagValue in self.__targetMediaDescriptor.getTags().items():
metadataTokens += [f"-metadata:g",
f"{tagKey}={tagValue}"]
# +jellyfin -jellyfin
mediaDifferences = self.__targetMediaDescriptor.compare(self.__sourceMediaDescriptor)
#HINT: With current ffmpeg version track metadata tags are not passed to the outfile
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():
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
return metadataTokens
def runJob(self,
@@ -390,11 +413,11 @@ class FfxController():
if videoEncoder == VideoEncoder.VP9:
commandSequence1 = (commandTokens
+ self.__targetMediaDescriptor.getInputMappingTokens()
+ self.__targetMediaDescriptor.getInputMappingTokens(only_video=True)
+ self.generateVP9Pass1Tokens(int(quality)))
if self.__context['perform_crop']:
commandSequence1 += FfxController.generateCropTokens()
commandSequence1 += self.generateCropTokens()
commandSequence1 += FfxController.NULL_TOKENS
@@ -420,7 +443,7 @@ class FfxController():
commandSequence2 += self.generateVP9Pass2Tokens(int(quality)) + self.generateAudioEncodingTokens()
if self.__context['perform_crop']:
commandSequence2 += FfxController.generateCropTokens()
commandSequence2 += self.generateCropTokens()
commandSequence2 += self.generateOutputTokens(targetPath,
FfxController.DEFAULT_FILE_FORMAT,
@@ -429,4 +452,7 @@ class FfxController():
click.echo(f"Command 2: {' '.join(commandSequence2)}")
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)
def getFilename(self):
return self.__sourceFilename
def getFileBasename(self):
return self.__sourceFileBasename
def assembleTargetFileBasename(self,
label: str = "",
quality: int = -1,

View File

@@ -61,15 +61,6 @@ class MediaDescriptor:
else:
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 type(kwargs[MediaDescriptor.JELLYFIN_ORDER_FLAG_KEY]) is not bool:
raise TypeError(
@@ -79,77 +70,6 @@ class MediaDescriptor:
else:
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):
for t in self.getAllTrackDescriptors():
@@ -165,56 +85,66 @@ class MediaDescriptor:
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())
audioTracks = self.sortSubIndices(self.getAudioTracks())
subtitleTracks = self.sortSubIndices(self.getSubtitleTracks())
videoTracks = self.getVideoTracks()
audioTracks = self.getAudioTracks()
subtitleTracks = self.getSubtitleTracks()
videoDefaultTrack = self.getDefaultVideoTrack()
self.getForcedVideoTrack()
audioDefaultTrack = self.getDefaultAudioTrack()
self.getForcedAudioTrack()
subtitleDefaultTrack = self.getDefaultSubtitleTrack()
self.getForcedSubtitleTrack()
if len([v for v in videoTracks if v.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
raise ValueError('More than one default video track')
if len([a for a in audioTracks if a.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
raise ValueError('More than one default audio track')
if len([s for s in subtitleTracks if s.getDispositionFlag(TrackDisposition.DEFAULT)]) > 1:
raise ValueError('More than one default subtitle track')
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))
)
if len([v for v in videoTracks if v.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
raise ValueError('More than one forced video track')
if len([a for a in audioTracks if a.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
raise ValueError('More than one forced audio track')
if len([s for s in subtitleTracks if s.getDispositionFlag(TrackDisposition.FORCED)]) > 1:
raise ValueError('More than one forced subtitle track')
reorderedTrackDescriptors = videoTracks + audioTracks + subtitleTracks
orderedSourceTrackSequence = [
t.getSourceIndex() for t in reorderedTrackDescriptors
trackDescriptors = videoTracks + audioTracks + subtitleTracks
sourceIndices = [
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
def fromFfprobe(cls, formatData, streamData):
@@ -253,43 +183,46 @@ class MediaDescriptor:
def getTags(self):
return self.__mediaTags
def sortSubIndices(
self, descriptors: List[TrackDescriptor]
) -> List[TrackDescriptor]:
subIndex = 0
for t in descriptors:
t.setSubIndex(subIndex)
for d in descriptors:
d.setSubIndex(subIndex)
subIndex += 1
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]:
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
v for v in self.__trackDescriptors if v.getType() == TrackType.VIDEO
]
def getAudioTracks(self) -> List[TrackDescriptor]:
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]:
return [
s
for s in self.__trackDescriptors.copy()
for s in self.__trackDescriptors
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):
@@ -301,6 +234,10 @@ class MediaDescriptor:
vsTags = vsMediaDescriptor.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
for emt in MediaDescriptor.EXCLUDED_MEDIA_TAGS:
if emt in tags.keys():
@@ -317,7 +254,7 @@ class MediaDescriptor:
# Target track configuration (from DB)
# tracks = self.getAllTrackDescriptors()
tracks = self.getReorderedTrackDescriptors()
tracks = self.getAllTrackDescriptors() # filtern
numTracks = len(tracks)
# Current track configuration (of file)
@@ -371,14 +308,15 @@ class MediaDescriptor:
def getImportFileTokens(self, use_sub_index: bool = True):
reorderedTrackDescriptors = self.getReorderedTrackDescriptors()
# reorderedTrackDescriptors = self.getReorderedTrackDescriptors()
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 += [
"-i",
importedFilePath,
@@ -386,39 +324,51 @@ class MediaDescriptor:
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 = []
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()
trackType = rtd.getType()
if use_sub_index:
if importedFilePath is None:
inputMappingTokens += [
"-map",
f"0:{trackType.indicator()}:{rtd.getSubIndex()}",
]
else:
if importedFilePath:
inputMappingTokens += [
"-map",
f"{filePointer}:{trackType.indicator()}:0",
]
filePointer += 1
else:
inputMappingTokens += ["-map", f"0:{rtd.getIndex()}"]
if td.getCodec() != TrackDescriptor.CODEC_PGS:
inputMappingTokens += [
"-map",
f"0:{trackType.indicator()}:{td.getSubIndex()}",
]
else:
if td.getCodec() != TrackDescriptor.CODEC_PGS:
inputMappingTokens += ["-map", f"0:{td.getIndex()}"]
return inputMappingTokens
def searchSubtitleFiles(searchDirectory, prefix):
def searchSubtitleFiles(self, searchDirectory, prefix):
sesl_match = re.compile(MediaDescriptor.SEASON_EPISODE_STREAM_LANGUAGE_MATCH)
availableFileSubtitleDescriptors = []
subtitleFileDescriptors = []
for subtitleFilename in os.listdir(searchDirectory):
if subtitleFilename.startswith(prefix) and subtitleFilename.endswith(
"." + MediaDescriptor.SUBTITLE_FILE_EXTENSION
@@ -432,48 +382,48 @@ class MediaDescriptor:
subtitleFileDescriptor["path"] = subtitleFilePath
subtitleFileDescriptor["season"] = int(sesl_result.group(1))
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)
availableFileSubtitleDescriptors.append(subtitleFileDescriptor)
subtitleFileDescriptors.append(subtitleFileDescriptor)
click.echo(
f"Found {len(availableFileSubtitleDescriptors)} subtitles in files\n"
)
click.echo(f"Available subtitle files {subtitleFileDescriptors}\n")
return availableFileSubtitleDescriptors
return subtitleFileDescriptors
def importSubtitles(
self, searchDirectory, prefix, season: int = -1, episode: int = -1
):
availableFileSubtitleDescriptors = self.searchSubtitleFiles(
searchDirectory, prefix
)
def importSubtitles(self, searchDirectory, prefix, season: int = -1, episode: int = -1):
click.echo(f"Season: {season} Episode: {episode}")
availableFileSubtitleDescriptors = self.searchSubtitleFiles(searchDirectory, prefix)
click.echo(f"availableFileSubtitleDescriptors: {availableFileSubtitleDescriptors}")
subtitleTracks = self.getSubtitleTracks()
click.echo(f"subtitleTracks: {[s.getIndex() for s in subtitleTracks]}")
# if len(availableFileSubtitleDescriptors) != len(subtitleTracks):
# raise click.ClickException(f"MediaDescriptor.importSubtitles(): Number if subtitle files not matching number of subtitle tracks")
matchingFileSubtitleDescriptors = (
matchingSubtitleFileDescriptors = (
sorted(
[
d
for d in availableFileSubtitleDescriptors
if d["season"] == int(season) and d["episode"] == int(episode)
],
key=lambda d: d["stream"],
key=lambda d: d["index"],
)
if availableFileSubtitleDescriptors
else []
)
for mfsd in matchingFileSubtitleDescriptors:
matchingSubtitleTrackDescriptor = [
s for s in subtitleTracks if s.getIndex() == mfsd["stream"]
]
click.echo(f"matchingSubtitleFileDescriptors: {matchingSubtitleFileDescriptors}")
for msfd in matchingSubtitleFileDescriptors:
matchingSubtitleTrackDescriptor = [s for s in subtitleTracks if s.getIndex() == msfd["index"]]
if matchingSubtitleTrackDescriptor:
matchingSubtitleTrackDescriptor[0].setExternalSourceFilePath(
mfsd["path"]
)
click.echo(f"Found matching subtitle file {msfd["path"]}\n")
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
self.__currentPattern = self.__mediaFileProperties.getPattern()
# keine tags vorhanden
self.__targetMediaDescriptor = self.__currentPattern.getMediaDescriptor() if self.__currentPattern is not None else None
# Enumerating differences between media descriptors
@@ -392,8 +393,8 @@ class MediaDetailsScreen(Screen):
# 7
yield Static(" ")
yield Button("Select Default", id="select_default_button")
yield Button("Select Forced", id="select_forced_button")
yield Button("Set Default", id="select_default_button")
yield Button("Set Forced", id="select_forced_button")
yield Static(" ")
# 8
yield Static("Streams")
@@ -403,8 +404,15 @@ class MediaDetailsScreen(Screen):
yield Footer()
def getPatternFromInput(self):
return str(self.query_one("#pattern_input", Input).value)
def getPatternDescriptorFromInput(self):
"""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:
@@ -492,19 +500,25 @@ class MediaDetailsScreen(Screen):
if showRowIndex is not None:
self.showsTable.move_cursor(row=showRowIndex)
patternDescriptor = {}
patternDescriptor['show_id'] = showDescriptor.getId()
patternDescriptor['pattern'] = self.getPatternFromInput()
self.__pc.addPattern(patternDescriptor)
patternDescriptor = self.getPatternDescriptorFromInput()
if patternDescriptor:
patternId = self.__pc.addPattern(patternDescriptor)
self.highlightPattern(False)
self.action_update_pattern()
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):
if not self.__currentMediaDescriptor.checkDefaultAndForcedDispositions():
try:
self.__currentMediaDescriptor.checkConfiguration()
except ValueError:
return
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)"""
if self.__currentPattern is not None:
inputPattern = self.getPatternFromInput()
if self.__currentPattern.getPattern() != inputPattern:
patternDescriptor = {}
patternDescriptor['show_id'] = self.getSelectedShowDescriptor().getId()
patternDescriptor['pattern'] = inputPattern
self.__pc.updatePattern(self.__currentPattern.getId(), patternDescriptor)
patternDescriptor = self.getPatternDescriptorFromInput()
if (patternDescriptor
and self.__currentPattern.getPattern() != patternDescriptor['pattern']):
return self.__pc.updatePattern(self.__currentPattern.getId(), patternDescriptor)
self.loadProperties()
@@ -614,7 +625,7 @@ class MediaDetailsScreen(Screen):
def action_edit_pattern(self):
patternDescriptor = {}
patternDescriptor['show_id'] = self.getSelectedShow()['id']
patternDescriptor['show_id'] = self.getSelectedShowDescriptor().getId()
patternDescriptor['pattern'] = self.getPatternFromInput()
if patternDescriptor['pattern']:
@@ -624,7 +635,7 @@ class MediaDetailsScreen(Screen):
if selectedPatternId is None:
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):

View File

@@ -62,7 +62,13 @@ class Pattern(Base):
kwargs[MediaDescriptor.TRACK_DESCRIPTOR_LIST_KEY] = []
# Set ordered subindices
subIndexCounter = {}
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)

View File

@@ -44,6 +44,7 @@ class Track(Base):
disposition_flags = Column(Integer)
codec_name = Column(String)
audio_layout = Column(Integer)
@@ -133,6 +134,7 @@ class Track(Base):
return cls(pattern_id = patternId,
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()
if v and (t := TrackDisposition.find(k)) is not None]),
audio_layout = AudioLayout.identify(streamObj))
@@ -150,6 +152,9 @@ class Track(Base):
def getType(self):
return TrackType.fromIndex(self.track_type)
def getCodec(self):
return str(self.codec_name)
def getIndex(self):
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.TRACK_TYPE_KEY] = self.getType()
kwargs[TrackDescriptor.CODEC_NAME_KEY] = self.getCodec()
kwargs[TrackDescriptor.DISPOSITION_SET_KEY] = self.getDispositionSet()
kwargs[TrackDescriptor.TAGS_KEY] = self.getTags()

View File

@@ -16,14 +16,14 @@ class PatternController():
try:
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():
pattern = Pattern(show_id = int(patternDescriptor['show_id']),
pattern = str(patternDescriptor['pattern']))
s.add(pattern)
s.commit()
return int(pattern.getId())
return pattern.getId()
else:
return None

View File

@@ -1,6 +1,9 @@
import subprocess
from typing import List
def executeProcess(commandSequence):
process = subprocess.Popen(commandSequence, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def executeProcess(commandSequence: List[str]):
# 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()
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():
@@ -57,6 +57,17 @@ class TmdbController():
#TODO Check for result
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()
except:
return {}

View File

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

View File

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