impl disposition rewrite
This commit is contained in:
92
bin/ffx.py
92
bin/ffx.py
@@ -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)
|
||||||
|
|
||||||
subtitleIndex = 0
|
if default_subtitle == -1:
|
||||||
for subtitleStreamDescriptor in streamDescriptor[STREAM_TYPE_SUBTITLE]:
|
sourceSubtitleStreams = streamDescriptor[STREAM_TYPE_SUBTITLE]
|
||||||
mappingTokens += ['-map', f"s:{subtitleIndex}"]
|
else:
|
||||||
subtitleIndex += 1
|
sourceSubtitleStreams = getReorderedSubstreams(streamDescriptor[STREAM_TYPE_SUBTITLE], default_subtitle)
|
||||||
|
dispositionTokens += generateDispositionTokens(sourceSubtitleStreams)
|
||||||
|
|
||||||
|
for audioStream in sourceAudioStreams:
|
||||||
|
mappingTokens += ['-map', f"a:{audioStream['src_sub_index']}"]
|
||||||
|
audioTokens += generateAudioTokens(context, audioStream['src_sub_index'], audioStream['layout'])
|
||||||
|
|
||||||
|
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()
|
||||||
|
|||||||
Reference in New Issue
Block a user