click
Javanaut 1 year ago
parent e3964b0002
commit d84797e6a5

@ -362,6 +362,32 @@ def generateDispositionTokens(subDescriptor):
return dispositionTokens 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.group()
@click.pass_context @click.pass_context
def ffx(ctx): def ffx(ctx):
@ -434,7 +460,7 @@ def streams(filename):
@click.option("-c", "--clear-metadata", is_flag=True, default=False) @click.option("-c", "--clear-metadata", is_flag=True, default=False)
@click.option("-d", "--denoise", 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) @click.option("--dry-run", is_flag=True, default=False)
@ -461,7 +487,7 @@ def convert(ctx,
output_directory, output_directory,
clear_metadata, clear_metadata,
denoise, denoise,
no_jellyfin_tweaks, jellyfin,
dry_run): dry_run):
"""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
@ -472,8 +498,8 @@ def convert(ctx,
startTime = time.perf_counter() startTime = time.perf_counter()
context = ctx.obj
context = ctx.obj
click.echo(f"\nVideo encoder: {video_encoder}") click.echo(f"\nVideo encoder: {video_encoder}")
@ -482,7 +508,6 @@ def convert(ctx,
click.echo(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_bitrate) if str(ac3_bitrate).endswith('k') else f"{ac3_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"AC3 bitrate: {context['bitrates']['ac3']}")
click.echo(f"DTS bitrate: {context['bitrates']['dts']}") 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) se_match = re.compile(SEASON_EPISODE_INDICATOR_MATCH)
e_match = re.compile(EPISODE_INDICATOR_MATCH) e_match = re.compile(EPISODE_INDICATOR_MATCH)
sesl_match = re.compile(SEASON_EPISODE_STREAM_LANGUAGE_MATCH)
availableFileSubtitles = [] # Parse subtitle files
if context['import_subtitles']: context['import_subtitles'] = (subtitle_directory and subtitle_prefix)
for subtitleFilename in os.listdir(subtitle_directory): availableFileSubtitles = searchSubtitleFiles(subtitle_directory, subtitle_prefix) if context['import_subtitles'] else []
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")
subtitleLanguages = subtitle_language
subtitleTitles = subtitle_title
# Overwrite audio tags if set
audioLanguages = audio_language audioLanguages = audio_language
audioTitles = audio_title audioTitles = audio_title
# Overwrite subtitle tags if set
subtitleLanguages = subtitle_language
subtitleTitles = subtitle_title
# Process crop parameters # Process crop parameters
context['perform_crop'] = (crop != 'none') context['perform_crop'] = (crop != 'none')
if context['perform_crop']: if context['perform_crop']:
cropTokens = crop.split(',') cropTokens = crop.split(',')
if cropTokens and len(cropTokens) == 2: if cropTokens and len(cropTokens) == 2:
context['crop_start'], context['crop_length'] = crop.split(',') context['crop_start'], context['crop_length'] = crop.split(',')
else: else:
context['crop_start'] = DEFAULT_CROP_START context['crop_start'] = DEFAULT_CROP_START
context['crop_length'] = DEFAULT_CROP_LENGTH context['crop_length'] = DEFAULT_CROP_LENGTH
click.echo(f"crop start={context['crop_start']} length={context['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") click.echo(f"\nRunning {len(existingSourcePaths) * len(q_list)} jobs")
job_index = 0
for sourcePath in existingSourcePaths: for sourcePath in existingSourcePaths:
# Separate basedir, basename and extension for current source file
sourceDirectory = os.path.dirname(sourcePath) sourceDirectory = os.path.dirname(sourcePath)
sourceFilename = os.path.basename(sourcePath) sourceFilename = os.path.basename(sourcePath)
sourcePathTokens = sourceFilename.split('.') sourcePathTokens = sourceFilename.split('.')
@ -564,9 +567,10 @@ def convert(ctx,
sourceFileBasename = sourceFilename sourceFileBasename = sourceFilename
sourceFilenameExtension = '' sourceFilenameExtension = ''
click.echo(f"\nProcessing file {sourcePath}") click.echo(f"\nProcessing file {sourcePath}")
# Determine season and episode if present in current filename
season_digits = 2 season_digits = 2
episode_digits = 2 episode_digits = 2
index_digits = 3 index_digits = 3
@ -589,7 +593,7 @@ def convert(ctx,
print(f"season={season} episode={episode} file={file_index}") print(f"season={season} episode={episode} file={file_index}")
# File specific tokens # Assemble target filename tokens
targetFilenameTokens = [] targetFilenameTokens = []
targetFilenameExtension = DEFAULT_FILE_EXTENSION targetFilenameExtension = DEFAULT_FILE_EXTENSION
@ -608,15 +612,17 @@ def convert(ctx,
try: try:
streamDescriptor = getStreamDescriptor(sourcePath) sourceStreamDescriptor = getStreamDescriptor(sourcePath)
targetStreamDescriptor = sourceStreamDescriptor.copy()
except Exception: except Exception:
click.echo(f"File with path {sourcePath} does not contain any audiovisual data, skipping ...") click.echo(f"File with path {sourcePath} does not contain any audiovisual data, skipping ...")
continue continue
for aStream in streamDescriptor[STREAM_TYPE_AUDIO]: for aStream in sourceStreamDescriptor[STREAM_TYPE_AUDIO]:
click.echo(f"audio stream lang={aStream['language']}") 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']}") click.echo(f"subtitle stream lang={sStream['language']}")
@ -639,11 +645,10 @@ def convert(ctx,
if streamIndex <= len(subtitleTitles) -1: if streamIndex <= len(subtitleTitles) -1:
mSubtitles[streamIndex]['title'] = subtitleTitles[streamIndex] mSubtitles[streamIndex]['title'] = subtitleTitles[streamIndex]
if default_subtitle == -1 or no_jellyfin_tweaks: if default_subtitle != -1 and jellyfin:
matchingSubtitles = mSubtitles
else:
matchingSubtitles = getReorderedSubstreams(mSubtitles, default_subtitle) matchingSubtitles = getReorderedSubstreams(mSubtitles, default_subtitle)
else:
matchingSubtitles = mSubtitles
@ -660,8 +665,8 @@ def convert(ctx,
audioTokens = [] audioTokens = []
# Source stream descriptors # Source stream descriptors
audioStreams = streamDescriptor[STREAM_TYPE_AUDIO] audioStreams = sourceStreamDescriptor[STREAM_TYPE_AUDIO]
subtitleStreams = streamDescriptor[STREAM_TYPE_SUBTITLE] subtitleStreams = sourceStreamDescriptor[STREAM_TYPE_SUBTITLE]
# Set language and title in source stream descriptors if given per command line option # Set language and title in source stream descriptors if given per command line option
for streamIndex in range(len(audioStreams)): for streamIndex in range(len(audioStreams)):
@ -689,10 +694,7 @@ def convert(ctx,
for streamIndex in range(len(audioStreams)): for streamIndex in range(len(audioStreams)):
audioStreams[streamIndex]['disposition']['default'] = 1 if streamIndex == default_audio else 0 audioStreams[streamIndex]['disposition']['default'] = 1 if streamIndex == default_audio else 0
if no_jellyfin_tweaks: sourceAudioStreams = getReorderedSubstreams(audioStreams, default_audio) if jellyfin else audioStreams
sourceAudioStreams = audioStreams
else:
sourceAudioStreams = getReorderedSubstreams(audioStreams, default_audio)
dispositionTokens += generateDispositionTokens(sourceAudioStreams) dispositionTokens += generateDispositionTokens(sourceAudioStreams)
@ -708,10 +710,7 @@ def convert(ctx,
for streamIndex in range(len(subtitleStreams)): for streamIndex in range(len(subtitleStreams)):
subtitleStreams[streamIndex]['disposition']['default'] = 1 if streamIndex == default_subtitle else 0 subtitleStreams[streamIndex]['disposition']['default'] = 1 if streamIndex == default_subtitle else 0
if no_jellyfin_tweaks: sourceSubtitleStreams = getReorderedSubstreams(subtitleStreams, default_subtitle) if jellyfin else subtitleStreams
sourceSubtitleStreams = subtitleStreams
else:
sourceSubtitleStreams = getReorderedSubstreams(subtitleStreams, default_subtitle)
dispositionTokens += generateDispositionTokens(sourceSubtitleStreams) dispositionTokens += generateDispositionTokens(sourceSubtitleStreams)
@ -736,10 +735,17 @@ def convert(ctx,
subtitleMetadataTokens = [] subtitleMetadataTokens = []
if context['import_subtitles']: if context['import_subtitles']:
for fileIndex in range(len(matchingSubtitles)): numMatchingSubtitles = len(matchingSubtitles)
if jellyfin and default_subtitle != -1:
subtitleSequence = getModifiedStreamOrder(numMatchingSubtitles, default_subtitle)
else:
subtitleSequence = range(numMatchingSubtitles)
for fileIndex in range(numMatchingSubtitles):
# Create mapping for subtitle streams when imported from files # 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] msg = matchingSubtitles[fileIndex]
subtitleMetadataTokens += [f"-metadata:s:s:{fileIndex}", f"language={msg['language']}"] subtitleMetadataTokens += [f"-metadata:s:s:{fileIndex}", f"language={msg['language']}"]
@ -789,7 +795,7 @@ def convert(ctx,
+ generateAV1Tokens(q, preset) + audioTokens) + generateAV1Tokens(q, preset) + audioTokens)
if clear_metadata: if clear_metadata:
commandSequence += generateClearTokens(streamDescriptor) commandSequence += generateClearTokens(sourceStreamDescriptor)
if context['perform_crop']: if context['perform_crop']:
commandSequence += generateCropTokens(context['crop_start'], context['crop_length']) commandSequence += generateCropTokens(context['crop_start'], context['crop_length'])
@ -833,7 +839,7 @@ def convert(ctx,
commandSequence2 += generateVP9Pass2Tokens(q) + audioTokens commandSequence2 += generateVP9Pass2Tokens(q) + audioTokens
if clear_metadata: if clear_metadata:
commandSequence2 += generateClearTokens(streamDescriptor) commandSequence2 += generateClearTokens(sourceStreamDescriptor)
if context['perform_crop']: if context['perform_crop']:
commandSequence2 += generateCropTokens(context['crop_start'], context['crop_length']) commandSequence2 += generateCropTokens(context['crop_start'], context['crop_length'])

Loading…
Cancel
Save