From 0ed85fce4ab2ca89b27f471fbadd1a1347ca2f82 Mon Sep 17 00:00:00 2001 From: Maveno Date: Mon, 4 Nov 2024 14:28:11 +0100 Subject: [PATCH] Add asterisk to filename filter, Signature --- bin/ffx.py | 105 +++++++++++++++++-------------------- bin/ffx/audio_layout.py | 2 - bin/ffx/ffx_controller.py | 11 +++- bin/ffx/file_properties.py | 5 +- bin/ffx/helper.py | 8 ++- bin/ffx/test/scenario_1.py | 3 +- bin/ffx/test/scenario_2.py | 3 +- bin/ffx/test/scenario_4.py | 2 +- 8 files changed, 71 insertions(+), 68 deletions(-) diff --git a/bin/ffx.py b/bin/ffx.py index 53b4ab3..0f12f24 100755 --- a/bin/ffx.py +++ b/bin/ffx.py @@ -19,6 +19,7 @@ from ffx.video_encoder import VideoEncoder from ffx.track_disposition import TrackDisposition from ffx.process import executeProcess +from ffx.helper import filterFilename VERSION='0.2.0' @@ -150,8 +151,7 @@ def unmux(ctx, subtitles_only): existingSourcePaths = [p for p in paths if os.path.isfile(p)] - if ctx.obj['verbosity'] > 0: - click.echo(f"\nUnmuxing {len(existingSourcePaths)} files") + ctx.obj['logger'].debug(f"\nUnmuxing {len(existingSourcePaths)} files") for sourcePath in existingSourcePaths: @@ -169,12 +169,10 @@ def unmux(ctx, targetIndicator = f"_S{season}E{episode}" if label and season != -1 and episode != -1 else '' if label and not targetIndicator: - if ctx.obj['verbosity'] > 0: - click.echo(f"Skipping file {fp.getFilename()}: Label set but no indicator recognized") + ctx.obj['logger'].warning(f"Skipping file {fp.getFilename()}: Label set but no indicator recognized") continue else: - if ctx.obj['verbosity'] > 0: - click.echo(f"\nUnmuxing file {fp.getFilename()}\n") + ctx.obj['logger'].debug(f"\nUnmuxing file {fp.getFilename()}\n") for trackDescriptor in sourceMediaDescriptor.getAllTrackDescriptors(): @@ -187,18 +185,14 @@ def unmux(ctx, if unmuxSequence: if not ctx.obj['dry_run']: - if ctx.obj['verbosity'] > 0: - click.echo(f"Executing unmuxing sequence: {' '.join(unmuxSequence)}") + ctx.obj['logger'].debug(f"Executing unmuxing sequence: {' '.join(unmuxSequence)}") out, err, rc = executeProcess(unmuxSequence) if rc: - if ctx.obj['verbosity'] > 0: - click.echo(f"Unmuxing of stream {trackDescriptor.getIndex()} failed with error ({rc}) {err}") + ctx.obj['logger'].error(f"Unmuxing of stream {trackDescriptor.getIndex()} failed with error ({rc}) {err}") else: - if ctx.obj['verbosity'] > 0: - click.echo(f"Skipping stream with unknown codec {trackDescriptor.getCodec()}") + ctx.obj['logger'].warning(f"Skipping stream with unknown codec {trackDescriptor.getCodec()}") except Exception as ex: - if ctx.obj['verbosity'] > 0: - click.echo(f"Skipping File {sourcePath} ({ex})") + ctx.obj['logger'].warning(f"Skipping File {sourcePath} ({ex})") @ffx.command() @@ -267,7 +261,7 @@ def checkUniqueDispositions(context, mediaDescriptor: MediaDescriptor): @click.option('-q', '--quality', type=str, default=FfxController.DEFAULT_QUALITY, help=f"Quality settings to be used with VP9 encoder (default: {FfxController.DEFAULT_QUALITY})") @click.option('-p', '--preset', type=str, default=FfxController.DEFAULT_AV1_PRESET, help=f"Quality preset to be used with AV1 encoder (default: {FfxController.DEFAULT_AV1_PRESET})") -@click.option('-s', '--stereo-bitrate', type=int, default=FfxController.DEFAULT_STEREO_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode stereo audio streams (default: {FfxController.DEFAULT_STEREO_BANDWIDTH})") +@click.option('-a', '--stereo-bitrate', type=int, default=FfxController.DEFAULT_STEREO_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode stereo audio streams (default: {FfxController.DEFAULT_STEREO_BANDWIDTH})") @click.option('--ac3', type=int, default=FfxController.DEFAULT_AC3_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode 5.1 audio streams (default: {FfxController.DEFAULT_AC3_BANDWIDTH})") @click.option('--dts', type=int, default=FfxController.DEFAULT_DTS_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode 6.1 audio streams (default: {FfxController.DEFAULT_DTS_BANDWIDTH})") @@ -298,8 +292,11 @@ def checkUniqueDispositions(context, mediaDescriptor: MediaDescriptor): @click.option("--no-tmdb", is_flag=True, default=False) @click.option("--no-jellyfin", is_flag=True, default=False) @click.option("--no-pattern", is_flag=True, default=False) + @click.option("--dont-pass-dispositions", is_flag=True, default=False) + @click.option("--no-prompt", is_flag=True, default=False) +@click.option("--no-signature", is_flag=True, default=False) def convert(ctx, paths, @@ -331,7 +328,8 @@ def convert(ctx, no_jellyfin, no_pattern, dont_pass_dispositions, - no_prompt): + no_prompt, + no_signature): """Batch conversion of audiovideo files in format suitable for web playback, e.g. jellyfin Files found under PATHS will be converted according to parameters. @@ -349,29 +347,28 @@ def convert(ctx, context['use_tmdb'] = not no_tmdb context['use_pattern'] = not no_pattern context['no_prompt'] = no_prompt + context['no_signature'] = no_signature context['import_subtitles'] = (subtitle_directory and subtitle_prefix) if context['import_subtitles']: context['subtitle_directory'] = subtitle_directory context['subtitle_prefix'] = subtitle_prefix -# click.echo(f"\nVideo encoder: {video_encoder}") + ctx.obj['logger'].debug(f"\nVideo encoder: {video_encoder}") qualityTokens = quality.split(',') q_list = [q for q in qualityTokens if q.isnumeric()] - if ctx.obj['verbosity'] > 0: - click.echo(f"Qualities: {q_list}") + ctx.obj['logger'].debug(f"Qualities: {q_list}") context['bitrates'] = {} context['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k" context['bitrates']['ac3'] = str(ac3) if str(ac3).endswith('k') else f"{ac3}k" context['bitrates']['dts'] = str(dts) if str(dts).endswith('k') else f"{dts}k" - if ctx.obj['verbosity'] > 0: - click.echo(f"Stereo bitrate: {context['bitrates']['stereo']}") - click.echo(f"AC3 bitrate: {context['bitrates']['ac3']}") - click.echo(f"DTS bitrate: {context['bitrates']['dts']}") + ctx.obj['logger'].debug(f"Stereo bitrate: {context['bitrates']['stereo']}") + ctx.obj['logger'].debug(f"AC3 bitrate: {context['bitrates']['ac3']}") + ctx.obj['logger'].debug(f"DTS bitrate: {context['bitrates']['dts']}") # Process crop parameters @@ -381,15 +378,14 @@ def convert(ctx, if cTokens and len(cTokens) == 2: context['crop_start'] = int(cTokens[0]) context['crop_length'] = int(cTokens[1]) - if ctx.obj['verbosity'] > 0: - click.echo(f"Crop start={context['crop_start']} length={context['crop_length']}") + ctx.obj['logger'].debug(f"Crop start={context['crop_start']} length={context['crop_length']}") 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] - if ctx.obj['verbosity'] > 0: - click.echo(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs") + ctx.obj['logger'].info(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs") + jobIndex = 0 for sourcePath in existingSourcePaths: @@ -402,8 +398,7 @@ def convert(ctx, sourceFileBasename = '.'.join(sourcePathTokens[:-1]) sourceFilenameExtension = sourcePathTokens[-1] - if ctx.obj['verbosity'] > 0: - click.echo(f"\nProcessing file {sourcePath}") + ctx.obj['logger'].info(f"\nProcessing file {sourcePath}") mediaFileProperties = FileProperties(context, sourceFilename) @@ -412,8 +407,7 @@ def convert(ctx, #HINT: This is None if the filename did not match anything in database currentPattern = mediaFileProperties.getPattern() if context['use_pattern'] else None - if ctx.obj['verbosity'] > 0: - click.echo(f"Pattern matching: {'No' if currentPattern is None else 'Yes'}") + ctx.obj['logger'].debug(f"Pattern matching: {'No' if currentPattern is None else 'Yes'}") # fileBasename = '' @@ -452,13 +446,14 @@ def convert(ctx, if context['use_tmdb']: - click.echo(f"Querying TMDB for show_id={currentShowDescriptor.getId()} season={mediaFileProperties.getSeason()} episode{mediaFileProperties.getEpisode()}") + ctx.obj['logger'].debug(f"Querying TMDB for show_id={currentShowDescriptor.getId()} season={mediaFileProperties.getSeason()} episode{mediaFileProperties.getEpisode()}") tmdbEpisodeResult = tc.queryEpisode(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode()) - click.echo(f"tmdbEpisodeResult={tmdbEpisodeResult}") + ctx.obj['logger'].debug(f"tmdbEpisodeResult={tmdbEpisodeResult}") if tmdbEpisodeResult: + filteredEpisodeName = filterFilename(tmdbEpisodeResult['name']) sourceFileBasename = TmdbController.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(), - tmdbEpisodeResult['name'], + filteredEpisodeName, mediaFileProperties.getSeason(), mediaFileProperties.getEpisode(), currentShowDescriptor.getIndexSeasonDigits(), @@ -474,50 +469,45 @@ def convert(ctx, mediaFileProperties.getSeason(), mediaFileProperties.getEpisode()) - # 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()]}") + ctx.obj['logger'].debug(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()]}") if context['use_jellyfin']: # Reorder subtracks in types with default the last, then make subindices flat again targetMediaDescriptor.applyJellyfinOrder() - # sourceMediaDescriptor.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 + ctx.obj['logger'].debug(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()]}") - if ctx.obj['verbosity'] > 0: - click.echo(f"Input mapping tokens (2nd pass): {targetMediaDescriptor.getInputMappingTokens()}") + ctx.obj['logger'].debug(f"Input mapping tokens (2nd pass): {targetMediaDescriptor.getInputMappingTokens()}") fc = FfxController(context, targetMediaDescriptor, sourceMediaDescriptor) + ctx.obj['logger'].debug(f"Season={mediaFileProperties.getSeason()} Episode={mediaFileProperties.getEpisode()}") - if ctx.obj['verbosity'] > 0: - click.echo(f"Season={mediaFileProperties.getSeason()} Episode={mediaFileProperties.getEpisode()}") - - if ctx.obj['verbosity'] > 0: - click.echo(f"fileBasename={sourceFileBasename}") + ctx.obj['logger'].debug(f"fileBasename={sourceFileBasename}") for q in q_list: - if ctx.obj['verbosity'] > 0: - click.echo(f"\nRunning job {jobIndex} file={sourcePath} q={q}") + ctx.obj['logger'].debug(f"\nRunning job {jobIndex} file={sourcePath} q={q}") jobIndex += 1 extra = ['ffx'] if sourceFilenameExtension == FfxController.DEFAULT_FILE_EXTENSION else [] - click.echo(f"label={label if label else 'Falsy'}") - click.echo(f"sourceFileBasename={sourceFileBasename}") + ctx.obj['logger'].debug(f"label={label if label else 'Falsy'}") + ctx.obj['logger'].debug(f"sourceFileBasename={sourceFileBasename}") - targetFilename = (sourceFileBasename if context['use_tmdb'] - else mediaFileProperties.assembleTargetFileBasename(label, - q if len(q_list) > 1 else -1, - extraTokens = extra)) + targetFileBasename = mediaFileProperties.assembleTargetFileBasename(label, + q if len(q_list) > 1 else -1, + extraTokens = extra) + + #TODO #387 + targetFilename = ((f"{sourceFileBasename}_q{q}" if len(q_list) > 1 else sourceFileBasename) + if context['use_tmdb'] else targetFileBasename) targetPath = os.path.join(output_directory if output_directory else sourceDirectory, targetFilename) - # media_S01E02_S01E02 - click.echo(f"targetPath={targetPath}") + #TODO: target extension anpassen + ctx.obj['logger'].info(f"Creating file {targetFilename}.webm") fc.runJob(sourcePath, targetPath, @@ -529,8 +519,7 @@ def convert(ctx, #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() - if ctx.obj['verbosity'] > 0: - click.echo(f"\nDONE\nTime elapsed {endTime - startTime}") + ctx.obj['logger'].info(f"\nDONE\nTime elapsed {endTime - startTime}") if __name__ == '__main__': diff --git a/bin/ffx/audio_layout.py b/bin/ffx/audio_layout.py index 7ac3cc8..a554ba5 100644 --- a/bin/ffx/audio_layout.py +++ b/bin/ffx/audio_layout.py @@ -28,7 +28,6 @@ class AudioLayout(Enum): return [a for a in AudioLayout if a.value['label'] == str(label)][0] except: - raise click.ClickException('fromLabel failed') return AudioLayout.LAYOUT_UNDEFINED @staticmethod @@ -36,7 +35,6 @@ class AudioLayout(Enum): try: return [a for a in AudioLayout if a.value['index'] == int(index)][0] except: - raise click.ClickException('fromIndex failed') return AudioLayout.LAYOUT_UNDEFINED @staticmethod diff --git a/bin/ffx/ffx_controller.py b/bin/ffx/ffx_controller.py index 665a664..6132507 100644 --- a/bin/ffx/ffx_controller.py +++ b/bin/ffx/ffx_controller.py @@ -43,6 +43,7 @@ class FfxController(): CHANNEL_MAP_5_1 = 'FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1' + SIGNATURE_TAGS = {'EDITED_WITH': 'FFX'} def __init__(self, context : dict, @@ -214,7 +215,15 @@ class FfxController(): metadataTokens = [] - for tagKey, tagValue in self.__targetMediaDescriptor.getTags().items(): + mediaTags = self.__targetMediaDescriptor.getTags() + + if (not 'no_signature' in self.__context.keys() + or not self.__context['no_signature']): + outputMediaTags = mediaTags | FfxController.SIGNATURE_TAGS + else: + outputMediaTags = mediaTags + + for tagKey, tagValue in outputMediaTags.items(): metadataTokens += [f"-metadata:g", f"{tagKey}={tagValue}"] diff --git a/bin/ffx/file_properties.py b/bin/ffx/file_properties.py index d75c69c..9af0220 100644 --- a/bin/ffx/file_properties.py +++ b/bin/ffx/file_properties.py @@ -229,7 +229,7 @@ class FileProperties(): # targetFilenameExtension = FfxController.DEFAULT_FILE_EXTENSION if extension is None else str(extension) - click.echo(f"assembleTargetFileBasename(): label={label} is {'truthy' if label else 'falsy'}") + self.__logger.debug(f"assembleTargetFileBasename(): label={label} is {'truthy' if label else 'falsy'}") if label: @@ -256,7 +256,6 @@ class FileProperties(): targetFilename = '_'.join(targetFilenameTokens) - #self.__logger.debug(f"assembleTargetFileBasename(): Target filename: {targetFilename}") - click.echo(f"assembleTargetFileBasename(): Target filename: {targetFilename}") + self.__logger.debug(f"assembleTargetFileBasename(): Target filename: {targetFilename}") return targetFilename diff --git a/bin/ffx/helper.py b/bin/ffx/helper.py index e4774a8..0910cf3 100644 --- a/bin/ffx/helper.py +++ b/bin/ffx/helper.py @@ -57,5 +57,11 @@ def filterFilename(fileName: str) -> str: """This filter replaces charactes from TMDB responses with characters less problemating when using in filenames or removes them""" + # This appears in TMDB episode names + fileName = str(fileName).replace(' (*)', '') + fileName = str(fileName).replace('(*)', '') + fileName = str(fileName).replace(':', ';') - return fileName + fileName = str(fileName).replace('*', '') + + return fileName.strip() diff --git a/bin/ffx/test/scenario_1.py b/bin/ffx/test/scenario_1.py index a430a8b..0acac0f 100644 --- a/bin/ffx/test/scenario_1.py +++ b/bin/ffx/test/scenario_1.py @@ -102,7 +102,8 @@ class Scenario1(Scenario): commandSequence += ['convert', mediaFilePath, - '--no-prompt'] + '--no-prompt', + '--no-signature'] if variantFilenameLabel: commandSequence += ['--label', variantFilenameLabel] diff --git a/bin/ffx/test/scenario_2.py b/bin/ffx/test/scenario_2.py index 84d692e..75f37e7 100644 --- a/bin/ffx/test/scenario_2.py +++ b/bin/ffx/test/scenario_2.py @@ -94,7 +94,8 @@ class Scenario2(Scenario): commandSequence += ['convert', mediaFilePath, - '--no-prompt'] + '--no-prompt', + '--no-signature'] if not testContext['use_jellyfin']: commandSequence += ['--no-jellyfin'] diff --git a/bin/ffx/test/scenario_4.py b/bin/ffx/test/scenario_4.py index e10845f..1fdcd9f 100644 --- a/bin/ffx/test/scenario_4.py +++ b/bin/ffx/test/scenario_4.py @@ -180,7 +180,7 @@ class Scenario4(Scenario): 'convert'] commandSequence += [tfo['filename'] for tfo in testFileList] - commandSequence += ['--no-prompt'] + commandSequence += ['--no-prompt', '--no-signature'] if not testContext['use_jellyfin']: commandSequence += ['--no-jellyfin']