diff --git a/bin/ffx.py b/bin/ffx.py index d1d5f7b..ea5ff58 100755 --- a/bin/ffx.py +++ b/bin/ffx.py @@ -118,9 +118,6 @@ def shows(ctx): @click.argument('paths', nargs=-1) -@click.option("-t", "--tmdb", is_flag=True, default=False) -@click.option("-j", "--jellyfin", is_flag=True, default=False) - @click.option('-l', '--label', type=str, default='', help='Label to be used as filename prefix') @click.option('-v', '--video-encoder', type=str, default=FfxController.DEFAULT_VIDEO_ENCODER, help=f"Target video encoder (vp9 or av1) default: {FfxController.DEFAULT_VIDEO_ENCODER}") @@ -135,11 +132,6 @@ def shows(ctx): @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('-ss', '--subtitle-language', type=str, multiple=True, help='Subtitle stream language(s)') -@click.option('-st', '--subtitle-title', type=str, multiple=True, help='Subtitle stream title(s)') - -@click.option('-ds', '--default-subtitle', type=int, default=-1, help='Index of default subtitle stream') -@click.option('-fs', '--forced-subtitle', type=int, default=-1, help='Index of forced subtitle stream') # (including default audio stream tag) @click.option('-as', '--audio-language', type=str, multiple=True, help='Audio stream language(s)') @click.option('-at', '--audio-title', type=str, multiple=True, help='Audio stream title(s)') @@ -148,23 +140,27 @@ def shows(ctx): @click.option('-da', '--forced-audio', type=int, default=-1, help='Index of forced audio stream') +@click.option('-ss', '--subtitle-language', type=str, multiple=True, help='Subtitle stream language(s)') +@click.option('-st', '--subtitle-title', type=str, multiple=True, help='Subtitle stream title(s)') + +@click.option('-ds', '--default-subtitle', type=int, default=-1, help='Index of default subtitle stream') +@click.option('-fs', '--forced-subtitle', type=int, default=-1, help='Index of forced subtitle stream') # (including default audio stream tag) + + @click.option("--crop", is_flag=False, flag_value="default", default="none") @click.option("-o", "--output-directory", type=str, default='') - -@click.option("-c", "--clear-metadata", is_flag=True, default=False) @click.option("-d", "--denoise", is_flag=True, default=False) - +@click.option("-t", "--no-tmdb", is_flag=True, default=False) +@click.option("-j", "--no-jellyfin", is_flag=True, default=False) @click.option("--dry-run", is_flag=True, default=False) def convert(ctx, paths, - tmdb, - jellyfin, label, video_encoder, quality, @@ -174,18 +170,22 @@ def convert(ctx, dts_bitrate, subtitle_directory, subtitle_prefix, - subtitle_language, - subtitle_title, - default_subtitle, - forced_subtitle, + audio_language, audio_title, default_audio, forced_audio, + + subtitle_language, + subtitle_title, + default_subtitle, + forced_subtitle, + crop, output_directory, - clear_metadata, denoise, + no_tmdb, + no_jellyfin, dry_run): """Batch conversion of audiovideo files in format suitable for web playback, e.g. jellyfin @@ -202,8 +202,8 @@ def convert(ctx, context['video_encoder'] = VideoEncoder.fromLabel(video_encoder) - context['jellyfin'] = jellyfin - context['tmdb'] = tmdb + context['jellyfin'] = not no_jellyfin + context['tmdb'] = not no_tmdb context['import_subtitles'] = (subtitle_directory and subtitle_prefix) if context['import_subtitles']: @@ -237,12 +237,6 @@ def convert(ctx, click.echo(f"Crop start={context['crop_start']} length={context['crop_length']}") -# ## Conversion parameters -# -# # Parse subtitle files -# -# availableFileSubtitleDescriptors = searchSubtitleFiles(subtitle_directory, subtitle_prefix) if context['import_subtitles'] else [] -# sc = ShowController(context) tc = TmdbController() existingSourcePaths = [p for p in paths if os.path.isfile(p) and p.split('.')[-1] in FfxController.INPUT_FILE_EXTENSIONS] @@ -270,6 +264,7 @@ def convert(ctx, click.echo(f"Pattern matching: {'No' if currentPattern is None else 'Yes'}") + fileBasename = '' if currentPattern is None: @@ -323,8 +318,6 @@ def convert(ctx, audioTokens = fc.generateAudioEncodingTokens() click.echo(f"Audio Tokens: {audioTokens}") - - tmdbFileBasename = '' else: @@ -333,22 +326,28 @@ def convert(ctx, targetMediaDescriptor = currentPattern.getMediaDescriptor() currentShowDescriptor = currentPattern.getShowDescriptor() - label = currentShowDescriptor.getFilenamePrefix() - - tmdbResult = tc.queryTmdb(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode()) - # click.echo(f"{tmdbResult}") + if context['tmdb']: + + tmdbResult = tc.queryTmdb(currentShowDescriptor.getId(), mediaFileProperties.getSeason(), mediaFileProperties.getEpisode()) + + # click.echo(f"{tmdbResult}") - tmdbFileBasename = tc.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(), - tmdbResult['name'], - mediaFileProperties.getSeason(), - mediaFileProperties.getEpisode(), - currentShowDescriptor.getIndexSeasonDigits(), - currentShowDescriptor.getIndexEpisodeDigits(), - currentShowDescriptor.getIndicatorSeasonDigits(), - currentShowDescriptor.getIndicatorEpisodeDigits()) + if tmdbResult: + fileBasename = tc.getEpisodeFileBasename(currentShowDescriptor.getFilenamePrefix(), + tmdbResult['name'], + mediaFileProperties.getSeason(), + mediaFileProperties.getEpisode(), + currentShowDescriptor.getIndexSeasonDigits(), + currentShowDescriptor.getIndexEpisodeDigits(), + currentShowDescriptor.getIndicatorSeasonDigits(), + currentShowDescriptor.getIndicatorEpisodeDigits()) - click.echo(f"tmdbFileBasename={tmdbFileBasename}") + + else: + fileBasename = currentShowDescriptor.getFilenamePrefix() + + click.echo(f"fileBasename={fileBasename}") if context['import_subtitles']: targetMediaDescriptor.importSubtitles(context['subtitle_directory'], context['subtitle_prefix']) @@ -376,14 +375,20 @@ def convert(ctx, click.echo(f"\nRunning job {jobIndex} file={sourcePath} q={q}") jobIndex += 1 - targetFilename = tmdbFileBasename if tmdbFileBasename else mediaFileProperties.assembleTargetFilename(label, q if len(q_list) > 1 else -1) - targetPath = os.path.join(sourceDirectory, targetFilename) + extra = ['ffx'] if sourceFilenameExtension == FfxController.DEFAULT_FILE_EXTENSION else [] + + targetFilename = fileBasename if context['tmdb'] else mediaFileProperties.assembleTargetFileBasename(label if label else fileBasename, + q if len(q_list) > 1 else -1, + extraTokens = extra) + + targetPath = os.path.join(output_directory if output_directory else sourceDirectory, targetFilename) fc.runJob(sourcePath, targetPath, context['video_encoder'], - q) - + q, + preset, + denoise) # #click.confirm('Warning! This file is not compliant to the defined source schema! Do you want to continue?', abort=True) diff --git a/bin/ffx/ffx_controller.py b/bin/ffx/ffx_controller.py index c093672..b0376fe 100644 --- a/bin/ffx/ffx_controller.py +++ b/bin/ffx/ffx_controller.py @@ -349,10 +349,10 @@ class FfxController(): def runJob(self, sourcePath, targetPath, - videoEncoder : VideoEncoder = VideoEncoder.VP9, - quality : int = DEFAULT_QUALITY, - preset : int = DEFAULT_AV1_PRESET, - denoise : bool = False): + videoEncoder: VideoEncoder = VideoEncoder.VP9, + quality: int = DEFAULT_QUALITY, + preset: int = DEFAULT_AV1_PRESET, + denoise: bool = False): commandTokens = FfxController.COMMAND_TOKENS + ['-i', sourcePath] @@ -366,9 +366,12 @@ class FfxController(): if not self.__sourceMediaDescriptor is None: commandSequence += self.generateMetadataTokens() - + + if denoise: + commandSequence += self.generateDenoiseTokens() + commandSequence += (self.generateAudioEncodingTokens() - + self.generateAV1Tokens(quality, preset) + + self.generateAV1Tokens(int(quality), int(preset)) + self.generateAudioEncodingTokens()) if self.__context['perform_crop']: @@ -388,7 +391,7 @@ class FfxController(): commandSequence1 = (commandTokens + self.__targetMediaDescriptor.getInputMappingTokens() - + self.generateVP9Pass1Tokens(quality)) + + self.generateVP9Pass1Tokens(int(quality))) if self.__context['perform_crop']: commandSequence1 += FfxController.generateCropTokens() @@ -414,7 +417,7 @@ class FfxController(): if denoise: commandSequence2 += self.generateDenoiseTokens() - commandSequence2 += self.generateVP9Pass2Tokens(quality) + self.generateAudioEncodingTokens() + commandSequence2 += self.generateVP9Pass2Tokens(int(quality)) + self.generateAudioEncodingTokens() if self.__context['perform_crop']: commandSequence2 += FfxController.generateCropTokens() diff --git a/bin/ffx/file_properties.py b/bin/ffx/file_properties.py index a38ee1b..7dc8b74 100644 --- a/bin/ffx/file_properties.py +++ b/bin/ffx/file_properties.py @@ -186,12 +186,12 @@ class FileProperties(): return int(self.__episode) - def assembleTargetFilename(self, + def assembleTargetFileBasename(self, label: str = "", quality: int = -1, fileIndex: int = -1, indexDigits: int = DEFAULT_INDEX_DIGITS, - extension: str = None): + extraTokens: list = []): if 'show_descriptor' in self.context.keys(): season_digits = self.context['show_descriptor'][ShowDescriptor.INDICATOR_SEASON_DIGITS_KEY] @@ -202,7 +202,7 @@ class FileProperties(): targetFilenameTokens = [] - targetFilenameExtension = FfxController.DEFAULT_FILE_EXTENSION if extension is None else str(extension) + # targetFilenameExtension = FfxController.DEFAULT_FILE_EXTENSION if extension is None else str(extension) if not label: targetFilenameTokens = [self.__sourceFileBasename] @@ -220,8 +220,9 @@ class FileProperties(): targetFilenameTokens += [f"q{quality}"] # In case source and target filenames are the same add an extension to distinct output from input - if not label and self.__sourceFilenameExtension == targetFilenameExtension: - targetFilenameTokens += ['ffx'] + #if not label and self.__sourceFilenameExtension == targetFilenameExtension: + # targetFilenameTokens += ['ffx'] + targetFilenameTokens += extraTokens targetFilename = '_'.join(targetFilenameTokens) diff --git a/bin/ffx/tmdb_controller.py b/bin/ffx/tmdb_controller.py index cea3e8a..f547672 100644 --- a/bin/ffx/tmdb_controller.py +++ b/bin/ffx/tmdb_controller.py @@ -37,8 +37,11 @@ class TmdbController(): tmdbUrl = f"https://api.themoviedb.org/3/tv/{showId}/season/{season}/episode/{episode}{urlParams}" - return requests.get(tmdbUrl).json() - + #TODO Check for result + try: + return requests.get(tmdbUrl).json() + except: + return {} def getEpisodeFileBasename(self, showName,