Add asterisk to filename filter, Signature

main
Maveno 11 months ago
parent 1a0a5f4482
commit 0ed85fce4a

@ -19,6 +19,7 @@ from ffx.video_encoder import VideoEncoder
from ffx.track_disposition import TrackDisposition from ffx.track_disposition import TrackDisposition
from ffx.process import executeProcess from ffx.process import executeProcess
from ffx.helper import filterFilename
VERSION='0.2.0' VERSION='0.2.0'
@ -150,8 +151,7 @@ def unmux(ctx,
subtitles_only): subtitles_only):
existingSourcePaths = [p for p in paths if os.path.isfile(p)] existingSourcePaths = [p for p in paths if os.path.isfile(p)]
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].debug(f"\nUnmuxing {len(existingSourcePaths)} files")
click.echo(f"\nUnmuxing {len(existingSourcePaths)} files")
for sourcePath in existingSourcePaths: 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 '' targetIndicator = f"_S{season}E{episode}" if label and season != -1 and episode != -1 else ''
if label and not targetIndicator: if label and not targetIndicator:
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].warning(f"Skipping file {fp.getFilename()}: Label set but no indicator recognized")
click.echo(f"Skipping file {fp.getFilename()}: Label set but no indicator recognized")
continue continue
else: else:
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].debug(f"\nUnmuxing file {fp.getFilename()}\n")
click.echo(f"\nUnmuxing file {fp.getFilename()}\n")
for trackDescriptor in sourceMediaDescriptor.getAllTrackDescriptors(): for trackDescriptor in sourceMediaDescriptor.getAllTrackDescriptors():
@ -187,18 +185,14 @@ def unmux(ctx,
if unmuxSequence: if unmuxSequence:
if not ctx.obj['dry_run']: if not ctx.obj['dry_run']:
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].debug(f"Executing unmuxing sequence: {' '.join(unmuxSequence)}")
click.echo(f"Executing unmuxing sequence: {' '.join(unmuxSequence)}")
out, err, rc = executeProcess(unmuxSequence) out, err, rc = executeProcess(unmuxSequence)
if rc: if rc:
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].error(f"Unmuxing of stream {trackDescriptor.getIndex()} failed with error ({rc}) {err}")
click.echo(f"Unmuxing of stream {trackDescriptor.getIndex()} failed with error ({rc}) {err}")
else: else:
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].warning(f"Skipping stream with unknown codec {trackDescriptor.getCodec()}")
click.echo(f"Skipping stream with unknown codec {trackDescriptor.getCodec()}")
except Exception as ex: except Exception as ex:
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].warning(f"Skipping File {sourcePath} ({ex})")
click.echo(f"Skipping File {sourcePath} ({ex})")
@ffx.command() @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('-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('-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('--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})") @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-tmdb", is_flag=True, default=False)
@click.option("--no-jellyfin", 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("--no-pattern", is_flag=True, default=False)
@click.option("--dont-pass-dispositions", 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-prompt", is_flag=True, default=False)
@click.option("--no-signature", is_flag=True, default=False)
def convert(ctx, def convert(ctx,
paths, paths,
@ -331,7 +328,8 @@ def convert(ctx,
no_jellyfin, no_jellyfin,
no_pattern, no_pattern,
dont_pass_dispositions, dont_pass_dispositions,
no_prompt): no_prompt,
no_signature):
"""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
Files found under PATHS will be converted according to parameters. 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_tmdb'] = not no_tmdb
context['use_pattern'] = not no_pattern context['use_pattern'] = not no_pattern
context['no_prompt'] = no_prompt context['no_prompt'] = no_prompt
context['no_signature'] = no_signature
context['import_subtitles'] = (subtitle_directory and subtitle_prefix) context['import_subtitles'] = (subtitle_directory and subtitle_prefix)
if context['import_subtitles']: if context['import_subtitles']:
context['subtitle_directory'] = subtitle_directory context['subtitle_directory'] = subtitle_directory
context['subtitle_prefix'] = subtitle_prefix context['subtitle_prefix'] = subtitle_prefix
# click.echo(f"\nVideo encoder: {video_encoder}") ctx.obj['logger'].debug(f"\nVideo encoder: {video_encoder}")
qualityTokens = quality.split(',') qualityTokens = quality.split(',')
q_list = [q for q in qualityTokens if q.isnumeric()] q_list = [q for q in qualityTokens if q.isnumeric()]
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].debug(f"Qualities: {q_list}")
click.echo(f"Qualities: {q_list}")
context['bitrates'] = {} context['bitrates'] = {}
context['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k" context['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k"
context['bitrates']['ac3'] = str(ac3) if str(ac3).endswith('k') else f"{ac3}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" context['bitrates']['dts'] = str(dts) if str(dts).endswith('k') else f"{dts}k"
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].debug(f"Stereo bitrate: {context['bitrates']['stereo']}")
click.echo(f"Stereo bitrate: {context['bitrates']['stereo']}") ctx.obj['logger'].debug(f"AC3 bitrate: {context['bitrates']['ac3']}")
click.echo(f"AC3 bitrate: {context['bitrates']['ac3']}") ctx.obj['logger'].debug(f"DTS bitrate: {context['bitrates']['dts']}")
click.echo(f"DTS bitrate: {context['bitrates']['dts']}")
# Process crop parameters # Process crop parameters
@ -381,15 +378,14 @@ def convert(ctx,
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_length'] = int(cTokens[1]) context['crop_length'] = int(cTokens[1])
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].debug(f"Crop start={context['crop_start']} length={context['crop_length']}")
click.echo(f"Crop start={context['crop_start']} length={context['crop_length']}")
tc = TmdbController() if context['use_tmdb'] else None 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]
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].info(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs")
click.echo(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs")
jobIndex = 0 jobIndex = 0
for sourcePath in existingSourcePaths: for sourcePath in existingSourcePaths:
@ -402,8 +398,7 @@ def convert(ctx,
sourceFileBasename = '.'.join(sourcePathTokens[:-1]) sourceFileBasename = '.'.join(sourcePathTokens[:-1])
sourceFilenameExtension = sourcePathTokens[-1] sourceFilenameExtension = sourcePathTokens[-1]
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].info(f"\nProcessing file {sourcePath}")
click.echo(f"\nProcessing file {sourcePath}")
mediaFileProperties = FileProperties(context, sourceFilename) mediaFileProperties = FileProperties(context, sourceFilename)
@ -412,8 +407,7 @@ def convert(ctx,
#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() if context['use_pattern'] else None currentPattern = mediaFileProperties.getPattern() if context['use_pattern'] else None
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].debug(f"Pattern matching: {'No' if currentPattern is None else 'Yes'}")
click.echo(f"Pattern matching: {'No' if currentPattern is None else 'Yes'}")
# fileBasename = '' # fileBasename = ''
@ -452,13 +446,14 @@ def convert(ctx,
if context['use_tmdb']: 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()) tmdbEpisodeResult = tc.queryEpisode(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode())
click.echo(f"tmdbEpisodeResult={tmdbEpisodeResult}") ctx.obj['logger'].debug(f"tmdbEpisodeResult={tmdbEpisodeResult}")
if tmdbEpisodeResult: if tmdbEpisodeResult:
filteredEpisodeName = filterFilename(tmdbEpisodeResult['name'])
sourceFileBasename = TmdbController.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(), sourceFileBasename = TmdbController.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(),
tmdbEpisodeResult['name'], filteredEpisodeName,
mediaFileProperties.getSeason(), mediaFileProperties.getSeason(),
mediaFileProperties.getEpisode(), mediaFileProperties.getEpisode(),
currentShowDescriptor.getIndexSeasonDigits(), currentShowDescriptor.getIndexSeasonDigits(),
@ -474,50 +469,45 @@ def convert(ctx,
mediaFileProperties.getSeason(), mediaFileProperties.getSeason(),
mediaFileProperties.getEpisode()) mediaFileProperties.getEpisode())
# raise click.ClickException(f"tmd subindices: {[t.getSubIndex() 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()]}")
# 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()]}")
if context['use_jellyfin']: if context['use_jellyfin']:
# Reorder subtracks in types with default the last, then make subindices flat again # Reorder subtracks in types with default the last, then make subindices flat again
targetMediaDescriptor.applyJellyfinOrder() 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()]}") 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()]}")
# raise click.Abort
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].debug(f"Input mapping tokens (2nd pass): {targetMediaDescriptor.getInputMappingTokens()}")
click.echo(f"Input mapping tokens (2nd pass): {targetMediaDescriptor.getInputMappingTokens()}")
fc = FfxController(context, targetMediaDescriptor, sourceMediaDescriptor) fc = FfxController(context, targetMediaDescriptor, sourceMediaDescriptor)
ctx.obj['logger'].debug(f"Season={mediaFileProperties.getSeason()} Episode={mediaFileProperties.getEpisode()}")
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].debug(f"fileBasename={sourceFileBasename}")
click.echo(f"Season={mediaFileProperties.getSeason()} Episode={mediaFileProperties.getEpisode()}")
if ctx.obj['verbosity'] > 0:
click.echo(f"fileBasename={sourceFileBasename}")
for q in q_list: for q in q_list:
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].debug(f"\nRunning job {jobIndex} file={sourcePath} q={q}")
click.echo(f"\nRunning job {jobIndex} file={sourcePath} q={q}")
jobIndex += 1 jobIndex += 1
extra = ['ffx'] if sourceFilenameExtension == FfxController.DEFAULT_FILE_EXTENSION else [] extra = ['ffx'] if sourceFilenameExtension == FfxController.DEFAULT_FILE_EXTENSION else []
click.echo(f"label={label if label else 'Falsy'}") ctx.obj['logger'].debug(f"label={label if label else 'Falsy'}")
click.echo(f"sourceFileBasename={sourceFileBasename}") ctx.obj['logger'].debug(f"sourceFileBasename={sourceFileBasename}")
targetFilename = (sourceFileBasename if context['use_tmdb'] targetFileBasename = mediaFileProperties.assembleTargetFileBasename(label,
else mediaFileProperties.assembleTargetFileBasename(label,
q if len(q_list) > 1 else -1, q if len(q_list) > 1 else -1,
extraTokens = extra)) 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) targetPath = os.path.join(output_directory if output_directory else sourceDirectory, targetFilename)
# media_S01E02_S01E02 #TODO: target extension anpassen
click.echo(f"targetPath={targetPath}") ctx.obj['logger'].info(f"Creating file {targetFilename}.webm")
fc.runJob(sourcePath, fc.runJob(sourcePath,
targetPath, 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) #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()
if ctx.obj['verbosity'] > 0: ctx.obj['logger'].info(f"\nDONE\nTime elapsed {endTime - startTime}")
click.echo(f"\nDONE\nTime elapsed {endTime - startTime}")
if __name__ == '__main__': if __name__ == '__main__':

@ -28,7 +28,6 @@ class AudioLayout(Enum):
return [a for a in AudioLayout if a.value['label'] == str(label)][0] return [a for a in AudioLayout if a.value['label'] == str(label)][0]
except: except:
raise click.ClickException('fromLabel failed')
return AudioLayout.LAYOUT_UNDEFINED return AudioLayout.LAYOUT_UNDEFINED
@staticmethod @staticmethod
@ -36,7 +35,6 @@ class AudioLayout(Enum):
try: try:
return [a for a in AudioLayout if a.value['index'] == int(index)][0] return [a for a in AudioLayout if a.value['index'] == int(index)][0]
except: except:
raise click.ClickException('fromIndex failed')
return AudioLayout.LAYOUT_UNDEFINED return AudioLayout.LAYOUT_UNDEFINED
@staticmethod @staticmethod

@ -43,6 +43,7 @@ class FfxController():
CHANNEL_MAP_5_1 = 'FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1' 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, def __init__(self,
context : dict, context : dict,
@ -214,7 +215,15 @@ class FfxController():
metadataTokens = [] 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", metadataTokens += [f"-metadata:g",
f"{tagKey}={tagValue}"] f"{tagKey}={tagValue}"]

@ -229,7 +229,7 @@ class FileProperties():
# targetFilenameExtension = FfxController.DEFAULT_FILE_EXTENSION if extension is None else str(extension) # targetFilenameExtension = FfxController.DEFAULT_FILE_EXTENSION if extension is None else str(extension)
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: if label:
@ -256,7 +256,6 @@ class FileProperties():
targetFilename = '_'.join(targetFilenameTokens) targetFilename = '_'.join(targetFilenameTokens)
#self.__logger.debug(f"assembleTargetFileBasename(): Target filename: {targetFilename}") self.__logger.debug(f"assembleTargetFileBasename(): Target filename: {targetFilename}")
click.echo(f"assembleTargetFileBasename(): Target filename: {targetFilename}")
return targetFilename return targetFilename

@ -57,5 +57,11 @@ def filterFilename(fileName: str) -> str:
"""This filter replaces charactes from TMDB responses with characters """This filter replaces charactes from TMDB responses with characters
less problemating when using in filenames or removes them""" 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(':', ';') fileName = str(fileName).replace(':', ';')
return fileName fileName = str(fileName).replace('*', '')
return fileName.strip()

@ -102,7 +102,8 @@ class Scenario1(Scenario):
commandSequence += ['convert', commandSequence += ['convert',
mediaFilePath, mediaFilePath,
'--no-prompt'] '--no-prompt',
'--no-signature']
if variantFilenameLabel: if variantFilenameLabel:
commandSequence += ['--label', variantFilenameLabel] commandSequence += ['--label', variantFilenameLabel]

@ -94,7 +94,8 @@ class Scenario2(Scenario):
commandSequence += ['convert', commandSequence += ['convert',
mediaFilePath, mediaFilePath,
'--no-prompt'] '--no-prompt',
'--no-signature']
if not testContext['use_jellyfin']: if not testContext['use_jellyfin']:
commandSequence += ['--no-jellyfin'] commandSequence += ['--no-jellyfin']

@ -180,7 +180,7 @@ class Scenario4(Scenario):
'convert'] 'convert']
commandSequence += [tfo['filename'] for tfo in testFileList] commandSequence += [tfo['filename'] for tfo in testFileList]
commandSequence += ['--no-prompt'] commandSequence += ['--no-prompt', '--no-signature']
if not testContext['use_jellyfin']: if not testContext['use_jellyfin']:
commandSequence += ['--no-jellyfin'] commandSequence += ['--no-jellyfin']

Loading…
Cancel
Save