From d84797e6a5ed7fe1f5533ef9429df6836cea218f Mon Sep 17 00:00:00 2001 From: Javanaut Date: Sun, 15 Sep 2024 16:09:01 +0200 Subject: [PATCH] inc --- bin/ffx.py | 126 ++++++++++++++++++++++++++++------------------------- 1 file changed, 66 insertions(+), 60 deletions(-) diff --git a/bin/ffx.py b/bin/ffx.py index 3f5ec6f..c352753 100755 --- a/bin/ffx.py +++ b/bin/ffx.py @@ -362,6 +362,32 @@ def generateDispositionTokens(subDescriptor): return dispositionTokens +def searchSubtitleFiles(dir, prefix): + + sesl_match = re.compile(SEASON_EPISODE_STREAM_LANGUAGE_MATCH) + + availableFileSubtitles = [] + for subtitleFilename in os.listdir(dir): + if subtitleFilename.startswith(prefix) and subtitleFilename.endswith('.' + SUBTITLE_FILE_EXTENSION): + sesl_result = sesl_match.search(subtitleFilename) + if sesl_result is not None: + subtitleFilePath = os.path.join(dir, subtitleFilename) + if os.path.isfile(subtitleFilePath): + + subtitleFileDescriptor = {} + 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['language'] = sesl_result.group(4) + + availableFileSubtitles.append(subtitleFileDescriptor) + + click.echo(f"Found {len(availableFileSubtitles)} subtitles in files\n") + + return availableFileSubtitles + + @click.group() @click.pass_context def ffx(ctx): @@ -434,7 +460,7 @@ def streams(filename): @click.option("-c", "--clear-metadata", is_flag=True, default=False) @click.option("-d", "--denoise", is_flag=True, default=False) -@click.option("-j", "--no-jellyfin-tweaks", is_flag=True, default=False) +@click.option("-j", "--jellyfin", is_flag=True, default=False) @click.option("--dry-run", is_flag=True, default=False) @@ -461,7 +487,7 @@ def convert(ctx, output_directory, clear_metadata, denoise, - no_jellyfin_tweaks, + jellyfin, dry_run): """Batch conversion of audiovideo files in format suitable for web playback, e.g. jellyfin @@ -471,10 +497,10 @@ def convert(ctx, or if the filename has not changed.""" startTime = time.perf_counter() + context = ctx.obj - click.echo(f"\nVideo encoder: {video_encoder}") qualityTokens = quality.split(',') @@ -482,7 +508,6 @@ def convert(ctx, click.echo(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_bitrate) if str(ac3_bitrate).endswith('k') else f"{ac3_bitrate}k" @@ -492,67 +517,45 @@ def convert(ctx, click.echo(f"AC3 bitrate: {context['bitrates']['ac3']}") click.echo(f"DTS bitrate: {context['bitrates']['dts']}") - # Parse subtitle files - context['import_subtitles'] = (subtitle_directory and subtitle_prefix) se_match = re.compile(SEASON_EPISODE_INDICATOR_MATCH) e_match = re.compile(EPISODE_INDICATOR_MATCH) - sesl_match = re.compile(SEASON_EPISODE_STREAM_LANGUAGE_MATCH) - availableFileSubtitles = [] - if context['import_subtitles']: - for subtitleFilename in os.listdir(subtitle_directory): - if subtitleFilename.startswith(subtitle_prefix) and subtitleFilename.endswith('.' + SUBTITLE_FILE_EXTENSION): - sesl_result = sesl_match.search(subtitleFilename) - if sesl_result is not None: - subtitleFilePath = os.path.join(subtitle_directory, subtitleFilename) - if os.path.isfile(subtitleFilePath): - subtitleFileDescriptor = {} - 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['language'] = sesl_result.group(4) - availableFileSubtitles.append(subtitleFileDescriptor) - - click.echo(f"Found {len(availableFileSubtitles)} subtitles in files") - + # Parse subtitle files + context['import_subtitles'] = (subtitle_directory and subtitle_prefix) + availableFileSubtitles = searchSubtitleFiles(subtitle_directory, subtitle_prefix) if context['import_subtitles'] else [] - subtitleLanguages = subtitle_language - subtitleTitles = subtitle_title + # Overwrite audio tags if set audioLanguages = audio_language audioTitles = audio_title + # Overwrite subtitle tags if set + subtitleLanguages = subtitle_language + subtitleTitles = subtitle_title + # Process crop parameters context['perform_crop'] = (crop != 'none') - if context['perform_crop']: - cropTokens = crop.split(',') - if cropTokens and len(cropTokens) == 2: - context['crop_start'], context['crop_length'] = crop.split(',') else: context['crop_start'] = DEFAULT_CROP_START context['crop_length'] = DEFAULT_CROP_LENGTH - click.echo(f"crop start={context['crop_start']} length={context['crop_length']}") - existingSourcePaths = [p for p in paths if os.path.isfile(p)] - + job_index = 0 + existingSourcePaths = [p for p in paths if os.path.isfile(p)] click.echo(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs") - - job_index = 0 - for sourcePath in existingSourcePaths: + # Separate basedir, basename and extension for current source file sourceDirectory = os.path.dirname(sourcePath) sourceFilename = os.path.basename(sourcePath) sourcePathTokens = sourceFilename.split('.') @@ -564,9 +567,10 @@ def convert(ctx, sourceFileBasename = sourceFilename sourceFilenameExtension = '' - click.echo(f"\nProcessing file {sourcePath}") + + # Determine season and episode if present in current filename season_digits = 2 episode_digits = 2 index_digits = 3 @@ -589,7 +593,7 @@ def convert(ctx, print(f"season={season} episode={episode} file={file_index}") - # File specific tokens + # Assemble target filename tokens targetFilenameTokens = [] targetFilenameExtension = DEFAULT_FILE_EXTENSION @@ -608,15 +612,17 @@ def convert(ctx, try: - streamDescriptor = getStreamDescriptor(sourcePath) + sourceStreamDescriptor = getStreamDescriptor(sourcePath) + targetStreamDescriptor = sourceStreamDescriptor.copy() + except Exception: click.echo(f"File with path {sourcePath} does not contain any audiovisual data, skipping ...") continue - for aStream in streamDescriptor[STREAM_TYPE_AUDIO]: + for aStream in sourceStreamDescriptor[STREAM_TYPE_AUDIO]: click.echo(f"audio stream lang={aStream['language']}") - for sStream in streamDescriptor[STREAM_TYPE_SUBTITLE]: + for sStream in sourceStreamDescriptor[STREAM_TYPE_SUBTITLE]: click.echo(f"subtitle stream lang={sStream['language']}") @@ -639,11 +645,10 @@ def convert(ctx, if streamIndex <= len(subtitleTitles) -1: mSubtitles[streamIndex]['title'] = subtitleTitles[streamIndex] - if default_subtitle == -1 or no_jellyfin_tweaks: - matchingSubtitles = mSubtitles - else: + if default_subtitle != -1 and jellyfin: matchingSubtitles = getReorderedSubstreams(mSubtitles, default_subtitle) - + else: + matchingSubtitles = mSubtitles @@ -660,8 +665,8 @@ def convert(ctx, audioTokens = [] # Source stream descriptors - audioStreams = streamDescriptor[STREAM_TYPE_AUDIO] - subtitleStreams = streamDescriptor[STREAM_TYPE_SUBTITLE] + audioStreams = sourceStreamDescriptor[STREAM_TYPE_AUDIO] + subtitleStreams = sourceStreamDescriptor[STREAM_TYPE_SUBTITLE] # Set language and title in source stream descriptors if given per command line option for streamIndex in range(len(audioStreams)): @@ -689,10 +694,7 @@ def convert(ctx, for streamIndex in range(len(audioStreams)): audioStreams[streamIndex]['disposition']['default'] = 1 if streamIndex == default_audio else 0 - if no_jellyfin_tweaks: - sourceAudioStreams = audioStreams - else: - sourceAudioStreams = getReorderedSubstreams(audioStreams, default_audio) + sourceAudioStreams = getReorderedSubstreams(audioStreams, default_audio) if jellyfin else audioStreams dispositionTokens += generateDispositionTokens(sourceAudioStreams) @@ -708,10 +710,7 @@ def convert(ctx, for streamIndex in range(len(subtitleStreams)): subtitleStreams[streamIndex]['disposition']['default'] = 1 if streamIndex == default_subtitle else 0 - if no_jellyfin_tweaks: - sourceSubtitleStreams = subtitleStreams - else: - sourceSubtitleStreams = getReorderedSubstreams(subtitleStreams, default_subtitle) + sourceSubtitleStreams = getReorderedSubstreams(subtitleStreams, default_subtitle) if jellyfin else subtitleStreams dispositionTokens += generateDispositionTokens(sourceSubtitleStreams) @@ -735,11 +734,18 @@ def convert(ctx, # Create mapping and ffmpeg options for subtitle streams subtitleMetadataTokens = [] if context['import_subtitles']: + + numMatchingSubtitles = len(matchingSubtitles) + + if jellyfin and default_subtitle != -1: + subtitleSequence = getModifiedStreamOrder(numMatchingSubtitles, default_subtitle) + else: + subtitleSequence = range(numMatchingSubtitles) - for fileIndex in range(len(matchingSubtitles)): + for fileIndex in range(numMatchingSubtitles): # Create mapping for subtitle streams when imported from files - mappingTokens += ['-map', f"{fileIndex+1}:s:0"] + mappingTokens += ['-map', f"{subtitleSequence[fileIndex]+1}:s:0"] msg = matchingSubtitles[fileIndex] subtitleMetadataTokens += [f"-metadata:s:s:{fileIndex}", f"language={msg['language']}"] @@ -789,7 +795,7 @@ def convert(ctx, + generateAV1Tokens(q, preset) + audioTokens) if clear_metadata: - commandSequence += generateClearTokens(streamDescriptor) + commandSequence += generateClearTokens(sourceStreamDescriptor) if context['perform_crop']: commandSequence += generateCropTokens(context['crop_start'], context['crop_length']) @@ -833,7 +839,7 @@ def convert(ctx, commandSequence2 += generateVP9Pass2Tokens(q) + audioTokens if clear_metadata: - commandSequence2 += generateClearTokens(streamDescriptor) + commandSequence2 += generateClearTokens(sourceStreamDescriptor) if context['perform_crop']: commandSequence2 += generateCropTokens(context['crop_start'], context['crop_length'])