impl disposition rewrite

click
Maveno 1 year ago
parent 4d7f728f25
commit dcd79b74fd

@ -12,7 +12,6 @@ VERSION='0.1.0'
DEFAULT_VIDEO_ENCODER = 'vp9' DEFAULT_VIDEO_ENCODER = 'vp9'
DEFAULT_QUALITY = 23 DEFAULT_QUALITY = 23
DEFAULT_AV1_PRESET = 5 DEFAULT_AV1_PRESET = 5
DEFAULT_FILE_FORMAT = 'webm' DEFAULT_FILE_FORMAT = 'webm'
@ -230,10 +229,30 @@ def getStreamDescriptor(filename):
s['layout'] = 'undefined' s['layout'] = 'undefined'
descriptor[s['codec_type']].append(s) descriptor[s['codec_type']].append(s)
descriptor[s['codec_type']][-1]['src_sub_index'] = len(descriptor[s['codec_type']]) - 1
return descriptor return descriptor
def getModifiedStreamOrder(length, last):
"""This is jellyfin specific as the last stream in the order is set as default"""
seq = list(range(length))
if last < 0 or last > length -1:
return seq
seq.pop(last)
seq.append(last)
return seq
def getReorderedSubstreams(subDescriptor, last):
numSubStreams = len(subDescriptor)
modifiedOrder = getModifiedStreamOrder(numSubStreams, last)
reorderedDescriptor = []
for streamIndex in range(numSubStreams):
reorderedDescriptor.append(subDescriptor[modifiedOrder[streamIndex]])
return reorderedDescriptor
def generateAV1Tokens(q, p): def generateAV1Tokens(q, p):
return ['-c:v:0', 'libsvtav1', return ['-c:v:0', 'libsvtav1',
@ -321,6 +340,25 @@ def generateClearTokens(streams):
return clearTokens return clearTokens
def generateDispositionTokens(subDescriptor):
"""-disposition:s:X default+forced"""
dispositionTokens = []
for subStreamIndex in range(len(subDescriptor)):
subStream = subDescriptor[subStreamIndex]
streamType = subStream['codec_type'][0] # v|a|s
dispositionFlags = {k for (k,v) in subStream['disposition'].items() if v == 1} if 'disposition' in subStream.keys() else set()
if dispositionFlags:
dispositionTokens += [f"-disposition:{streamType}:{subStreamIndex}", '+'.join(dispositionFlags)]
else:
dispositionTokens += [f"-disposition:{streamType}:{subStreamIndex}", '0']
return dispositionTokens
@click.group() @click.group()
@click.pass_context @click.pass_context
def ffx(ctx): def ffx(ctx):
@ -370,10 +408,10 @@ 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('-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('-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('-ds', '--default-subtitle', type=int, help='Index of default subtitle stream') @click.option('-ds', '--default-subtitle', type=int, default=-1, help='Index of default subtitle stream')
@click.option('-fa', '--forced-audio', type=int, help='Index of forced audio stream (including default audio stream tag)') @click.option('-fa', '--forced-audio', type=int, default=-1, help='Index of forced audio stream (including default audio stream tag)')
@click.option('-da', '--default-audio', type=int, help='Index of default audio stream') @click.option('-da', '--default-audio', type=int, default=-1, help='Index of default audio stream')
@click.option("--crop", is_flag=False, flag_value="default", default="none") @click.option("--crop", is_flag=False, flag_value="default", default="none")
@ -384,10 +422,12 @@ 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("--dry-run", is_flag=True, default=False) @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_audio, default_audio, crop, output_directory, clear_metadata, denoise, dry_run): def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, ac3_bitrate, dts_bitrate, default_subtitle, forced_audio, 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 """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.
@ -483,6 +523,7 @@ def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, a
print(f"season={season} episode={episode} file={file_index}") print(f"season={season} episode={episode} file={file_index}")
# File specific tokens
targetFilenameTokens = [] targetFilenameTokens = []
targetFilenameExtension = DEFAULT_FILE_EXTENSION targetFilenameExtension = DEFAULT_FILE_EXTENSION
@ -522,22 +563,34 @@ def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, a
job_index += 1 job_index += 1
mappingVideoTokens = ['-map', 'v:0'] mappingVideoTokens = ['-map', 'v:0']
mappingTokens = mappingVideoTokens.copy() mappingTokens = mappingVideoTokens.copy()
dispositionTokens = []
audioTokens = [] audioTokens = []
audioIndex = 0
for audioStreamDescriptor in streamDescriptor[STREAM_TYPE_AUDIO]:
mappingTokens += ['-map', f"a:{audioIndex}"] if default_audio == -1:
audioTokens += generateAudioTokens(context, audioIndex, audioStreamDescriptor['layout']) sourceAudioStreams = streamDescriptor[STREAM_TYPE_AUDIO]
audioIndex += 1 else:
sourceAudioStreams = getReorderedSubstreams(streamDescriptor[STREAM_TYPE_AUDIO], default_audio)
dispositionTokens += generateDispositionTokens(sourceAudioStreams)
if default_subtitle == -1:
sourceSubtitleStreams = streamDescriptor[STREAM_TYPE_SUBTITLE]
else:
sourceSubtitleStreams = getReorderedSubstreams(streamDescriptor[STREAM_TYPE_SUBTITLE], default_subtitle)
dispositionTokens += generateDispositionTokens(sourceSubtitleStreams)
subtitleIndex = 0 for audioStream in sourceAudioStreams:
for subtitleStreamDescriptor in streamDescriptor[STREAM_TYPE_SUBTITLE]: mappingTokens += ['-map', f"a:{audioStream['src_sub_index']}"]
mappingTokens += ['-map', f"s:{subtitleIndex}"] audioTokens += generateAudioTokens(context, audioStream['src_sub_index'], audioStream['layout'])
subtitleIndex += 1
for subtitleStream in sourceSubtitleStreams:
mappingTokens += ['-map', f"s:{subtitleStream['src_sub_index']}"]
# Job specific tokens
targetFilenameJobTokens = targetFilenameTokens.copy() targetFilenameJobTokens = targetFilenameTokens.copy()
if len(q_list) > 1: if len(q_list) > 1:
@ -552,10 +605,13 @@ def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, a
click.echo(f"target filename: {targetFilename}") click.echo(f"target filename: {targetFilename}")
if video_encoder == 'av1': if video_encoder == 'av1':
commandSequence = commandTokens + mappingTokens + audioTokens + generateAV1Tokens(q, preset) + audioTokens commandSequence = (commandTokens
+ mappingTokens
+ dispositionTokens
+ audioTokens
+ generateAV1Tokens(q, preset) + audioTokens)
if clear_metadata: if clear_metadata:
commandSequence += generateClearTokens(streamDescriptor) commandSequence += generateClearTokens(streamDescriptor)
@ -589,7 +645,9 @@ def convert(ctx, paths, label, video_encoder, quality, preset, stereo_bitrate, a
executeProcess(commandSequence1) executeProcess(commandSequence1)
commandSequence2 = commandTokens + mappingTokens commandSequence2 = (commandTokens
+ mappingTokens
+ dispositionTokens)
if denoise: if denoise:
commandSequence2 += generateDenoiseTokens() commandSequence2 += generateDenoiseTokens()

Loading…
Cancel
Save