inc dispo

click
Maveno 1 year ago
parent e6734cb4ef
commit f381fad31f

@ -37,7 +37,7 @@ MKVMERGE_METADATA_KEYS = ['BPS',
FILE_EXTENSIONS = ['mkv', 'mp4', 'avi', 'flv', 'webm']
COMMAND_TOKENS = ['ffmpeg', '-y', '-i']
COMMAND_TOKENS = ['ffmpeg', '-y']
NULL_TOKENS = ['-f', 'null', '/dev/null']
STREAM_TYPE_VIDEO = 'video'
@ -51,6 +51,9 @@ STREAM_LAYOUT_6CH = '6ch'
SEASON_EPISODE_INDICATOR_MATCH = '[sS]([0-9]+)[eE]([0-9]+)'
EPISODE_INDICATOR_MATCH = '[eE]([0-9]+)'
SEASON_EPISODE_STREAM_LANGUAGE_MATCH = '[sS]([0-9]+)[eE]([0-9]+)_([0-9]+)_([a-z]{3})'
SUBTITLE_FILE_EXTENSION = 'vtt'
class DashboardScreen(Screen):
@ -408,8 +411,17 @@ def streams(filename):
@click.option('-ac3', '--ac3-bitrate', type=int, default=DEFAULT_AC3_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode 5.1 audio streams (default: {DEFAULT_AC3_BANDWIDTH})")
@click.option('-dts', '--dts-bitrate', type=int, default=DEFAULT_DTS_BANDWIDTH, help=f"Bitrate in kbit/s to be used to encode 6.1 audio streams (default: {DEFAULT_DTS_BANDWIDTH})")
@click.option('-sd', '--subtitle-directory', type=str, default='', help='Load subtitles from here')
@click.option('-sl', '--subtitle-label', type=str, default='', help='Subtitle filename prefix')
@click.option('-ss', '--subtitle-language', type=str, default='', help='Subtitle stream language(s)')
@click.option('-st', '--subtitle-title', type=str, default='', help='Subtitle stream title(s)')
@click.option('-ds', '--default-subtitle', type=int, default=-1, help='Index of default subtitle stream')
@click.option('-fa', '--forced-subtitle', type=int, default=-1, help='Index of forced subtitle stream') # (including default audio stream tag)
@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, default='', help='Audio stream language(s)')
@click.option('-at', '--audio-title', type=str, default='', help='Audio stream title(s)')
@click.option('-da', '--default-audio', type=int, default=-1, help='Index of default audio stream')
@ -427,7 +439,30 @@ def streams(filename):
@click.option("--dry-run", is_flag=True, default=False)
def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, ac3_bitrate, dts_bitrate, default_subtitle, forced_subtitle, default_audio, crop, output_directory, clear_metadata, denoise, no_jellyfin_tweaks, dry_run):
def convert(ctx,
paths,
label,
video_encoder,
quality,
preset,
stereo_bitrate,
ac3_bitrate,
dts_bitrate,
subtitle_directory,
subtitle_label,
subtitle_language,
subtitle_title,
default_subtitle,
forced_subtitle,
audio_language,
audio_title,
default_audio,
crop,
output_directory,
clear_metadata,
denoise,
no_jellyfin_tweaks,
dry_run):
"""Batch conversion of audiovideo files in format suitable for web playback, e.g. jellyfin
Files found under PATHS will be converted according to parameters.
@ -457,6 +492,41 @@ def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, a
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_label)
se_match = re.compile(SEASON_EPISODE_INDICATOR_MATCH)
e_match = re.compile(EPISODE_INDICATOR_MATCH)
sesl_match = re.compile(SEASON_EPISODE_STREAM_LANGUAGE_MATCH)
availableSubtitles = []
if context['import_subtitles']:
for subtitleFilename in os.listdir(subtitle_directory):
if subtitleFilename.startswith(subtitle_label) 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)
availableSubtitles.append(subtitleFileDescriptor)
click.echo(f"Found {len(availableSubtitles)} subtitles in files")
subtitleLanguages = subtitle_language.split(',') if subtitle_language else []
subtitleTitles = subtitle_title.split(',') if subtitle_title else []
audioLanguages = audio_language.split(',') if audio_language else []
audioTitles = audio_title.split(',') if audio_title else []
# Process crop parameters
context['perform_crop'] = (crop != 'none')
if context['perform_crop']:
@ -481,10 +551,6 @@ def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, a
job_index = 0
se_match = re.compile(SEASON_EPISODE_INDICATOR_MATCH)
e_match = re.compile(EPISODE_INDICATOR_MATCH)
for sourcePath in existingSourcePaths:
sourceDirectory = os.path.dirname(sourcePath)
@ -554,52 +620,134 @@ def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, a
click.echo(f"subtitle stream lang={sStream['language']}")
commandTokens = COMMAND_TOKENS + ['-i', sourcePath]
subtitleFileTokens = []
matchingSubtitles = []
if context['import_subtitles']:
subtitles = [a for a in availableSubtitles if a['season'] == season and a['episode'] == episode]
mSubtitles = sorted(subtitles, key=lambda d: d['stream'])
for sfd in mSubtitles:
subtitleFileTokens += ['-i', sfd['path']]
for streamIndex in range(len(mSubtitles)):
mSubtitles[streamIndex]['forced'] = 1 if forced_subtitle != -1 and streamIndex == forced_subtitle else 0
mSubtitles[streamIndex]['default'] = 1 if default_subtitle != -1 and streamIndex == default_subtitle else 0
if default_subtitle == -1:
matchingSubtitles = mSubtitles
else:
matchingSubtitles = getReorderedSubstreams(mSubtitles, default_subtitle)
commandTokens = COMMAND_TOKENS + [sourcePath]
for q in q_list:
click.echo(f"\nRunning job {job_index} file={sourcePath} q={q}")
job_index += 1
mappingVideoTokens = ['-map', 'v:0']
mappingVideoTokens = ['-map', '0:v:0']
mappingTokens = mappingVideoTokens.copy()
dispositionTokens = []
audioTokens = []
# Source stream descriptors
audioStreams = streamDescriptor[STREAM_TYPE_AUDIO]
subtitleStreams = streamDescriptor[STREAM_TYPE_SUBTITLE]
# Set language and title in source stream descriptors if given per command line option
for streamIndex in range(len(audioStreams)):
if 'tags' not in audioStreams[streamIndex].keys():
audioStreams[streamIndex]['tags'] = {}
if streamIndex <= len(audioLanguages) - 1:
audioStreams[streamIndex]['tags']['language'] = audioLanguages[streamIndex]
if streamIndex <= len(audioTitles) - 1:
audioStreams[streamIndex]['tags']['title'] = audioTitles[streamIndex]
for streamIndex in range(len(subtitleStreams)):
if 'tags' not in subtitleStreams[streamIndex].keys():
subtitleStreams[streamIndex]['tags'] = {}
if streamIndex <= len(subtitleLanguages) - 1:
subtitleStreams[streamIndex]['tags']['language'] = subtitleLanguages[streamIndex]
if streamIndex <= len(subtitleTitles) - 1:
subtitleStreams[streamIndex]['tags']['title'] = subtitleTitles[streamIndex]
# Reorder audio stream descriptors and create disposition options if default is given per command line option
if default_audio == -1:
sourceAudioStreams = audioStreams
else:
for streamIndex in range(len(audioStreams)):
audioStreams[streamIndex]['disposition']['default'] = 1 if streamIndex == default_audio else 0
sourceAudioStreams = getReorderedSubstreams(audioStreams, default_audio)
dispositionTokens += generateDispositionTokens(sourceAudioStreams)
# Set forced tag in subtitle descriptor if given per command line option
if forced_subtitle != -1:
for streamIndex in range(len(subtitleStreams)):
subtitleStreams[streamIndex]['disposition']['forced'] = 1 if streamIndex == forced_subtitle else 0
# Reorder subtitle stream descriptors and create disposition options if default is given per command line option
if default_subtitle == -1:
sourceSubtitleStreams = subtitleStreams
else:
for streamIndex in range(len(subtitleStreams)):
subtitleStreams[streamIndex]['disposition']['default'] = 1 if streamIndex == default_subtitle else 0
sourceSubtitleStreams = getReorderedSubstreams(subtitleStreams, default_subtitle)
dispositionTokens += generateDispositionTokens(sourceSubtitleStreams)
for audioStream in sourceAudioStreams:
mappingTokens += ['-map', f"a:{audioStream['src_sub_index']}"]
audioMetadataTokens = []
for audioStreamIndex in range(len(sourceAudioStreams)):
audioStream = sourceAudioStreams[audioStreamIndex]
# Create mapping and ffmpeg options for audio streams
mappingTokens += ['-map', f"0:a:{audioStream['src_sub_index']}"]
audioTokens += generateAudioTokens(context, audioStream['src_sub_index'], audioStream['layout'])
for subtitleStream in sourceSubtitleStreams:
if 'tags' in audioStream.keys():
if 'language' in audioStream['tags'].keys():
audioMetadataTokens += [f"-metadata:s:a:{audioStreamIndex}", f"language={audioStream['tags']['language']}"]
if 'title' in audioStream['tags'].keys():
audioMetadataTokens += [f"-metadata:s:a:{audioStreamIndex}", f"title={audioStream['tags']['title']}"]
# Create mapping and ffmpeg options for subtitle streams
subtitleMetadataTokens = []
if context['import_subtitles']:
for fileIndex in range(len(matchingSubtitles)):
# Create mapping for subtitle streams when imported from files
mappingTokens += ['-map', f"{fileIndex+1}:s:0"]
msg = matchingSubtitles[fileIndex]
subtitleMetadataTokens += [f"-metadata:s:s:{fileIndex}", f"language={msg['language']}"]
else:
for subtitleStreamIndex in range(len(sourceSubtitleStreams)):
subtitleStream = sourceSubtitleStreams[subtitleStreamIndex]
# Create mapping for subtitle streams
mappingTokens += ['-map', f"s:{subtitleStream['src_sub_index']}"]
if 'tags' in subtitleStream.keys():
if 'language' in subtitleStream['tags'].keys():
subtitleMetadataTokens += [f"-metadata:s:s:{subtitleStreamIndex}", f"language={subtitleStream['tags']['language']}"]
if 'title' in subtitleStream['tags'].keys():
subtitleMetadataTokens += [f"-metadata:s:s:{subtitleStreamIndex}", f"title={subtitleStream['tags']['title']}"]
# Job specific tokens
targetFilenameJobTokens = targetFilenameTokens.copy()
@ -619,8 +767,11 @@ def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, a
if video_encoder == 'av1':
commandSequence = (commandTokens
+ subtitleFileTokens
+ mappingTokens
+ dispositionTokens
+ audioMetadataTokens
+ subtitleMetadataTokens
+ audioTokens
+ generateAV1Tokens(q, preset) + audioTokens)
@ -657,7 +808,10 @@ def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, a
commandSequence2 = (commandTokens
+ subtitleFileTokens
+ mappingTokens
+ audioMetadataTokens
+ subtitleMetadataTokens
+ dispositionTokens)
if denoise:

Loading…
Cancel
Save