on par crude

click
Maveno 1 year ago
parent 0a8d3a8f85
commit 494ae89034

@ -1,6 +1,6 @@
#! /usr/bin/python3 #! /usr/bin/python3
import os, sys, subprocess, json, click import os, sys, subprocess, json, click, time
VERSION='0.1.0' VERSION='0.1.0'
@ -10,6 +10,8 @@ DEFAULT_QUALITY = 23
DEFAULT_AV1_PRESET = 5 DEFAULT_AV1_PRESET = 5
DEFAULT_FILE_SUFFIX = 'webm'
DEFAULT_STEREO_BANDWIDTH = "128" DEFAULT_STEREO_BANDWIDTH = "128"
DEFAULT_AC3_BANDWIDTH = "256" DEFAULT_AC3_BANDWIDTH = "256"
DEFAULT_DTS_BANDWIDTH = "320" DEFAULT_DTS_BANDWIDTH = "320"
@ -31,6 +33,15 @@ MKVMERGE_METADATA_KEYS = ['BPS',
COMMAND_TOKENS = ['ffmpeg', '-y', '-i'] COMMAND_TOKENS = ['ffmpeg', '-y', '-i']
NULL_TOKENS = ['-f', 'null', '/dev/null'] NULL_TOKENS = ['-f', 'null', '/dev/null']
STREAM_TYPE_VIDEO = 'video'
STREAM_TYPE_AUDIO = 'audio'
STREAM_TYPE_SUBTITLE = 'subtitle'
STREAM_LAYOUT_6_1 = '6.1'
STREAM_LAYOUT_5_1 = '5.1(side)'
STREAM_LAYOUT_STEREO = 'stereo'
STREAM_LAYOUT_6CH = '6ch'
def executeProcess(commandSequence): def executeProcess(commandSequence):
@ -60,27 +71,46 @@ def getStreamDescriptor(filename):
descriptor = [] descriptor = []
for d in [s for s in streamData if s['codec_type'] == 'video']: i = 0
for d in [s for s in streamData if s['codec_type'] == STREAM_TYPE_VIDEO]:
descriptor.append({ descriptor.append({
'index': d['index'], 'index': d['index'],
'type': 'video', 'sub_index': i,
'type': STREAM_TYPE_VIDEO,
'codec': d['codec_name'] 'codec': d['codec_name']
}) })
i += 1
for d in [s for s in streamData if s['codec_type'] == 'audio']: i = 0
descriptor.append({ for d in [s for s in streamData if s['codec_type'] == STREAM_TYPE_AUDIO]:
streamDescriptor = {
'index': d['index'], 'index': d['index'],
'type': 'audio', 'sub_index': i,
'type': STREAM_TYPE_AUDIO,
'codec': d['codec_name'], 'codec': d['codec_name'],
'channels': d['channels'] 'channels': d['channels']
}) }
if 'channel_layout' in d.keys():
streamDescriptor['layout'] = d['channel_layout']
elif d['channels'] == 6:
streamDescriptor['layout'] = STREAM_LAYOUT_6CH
else:
streamDescriptor['layout'] = 'undefined'
descriptor.append(streamDescriptor)
i += 1
for d in [s for s in streamData if s['codec_type'] == 'subtitle']: i = 0
for d in [s for s in streamData if s['codec_type'] == STREAM_TYPE_SUBTITLE]:
descriptor.append({ descriptor.append({
'index': d['index'], 'index': d['index'],
'type': 'subtitle', 'sub_index': i,
'type': STREAM_TYPE_SUBTITLE,
'codec': d['codec_name'] 'codec': d['codec_name']
}) })
i += 1
return descriptor return descriptor
@ -126,56 +156,19 @@ def generateDenoiseTokens(spatial=5, patch=7, research=7, hw=False):
return ['-vf', f"{filterName}=s={spatial}:p={patch}:r={research}"] return ['-vf', f"{filterName}=s={spatial}:p={patch}:r={research}"]
def generateOutputTokens(f, q=''): def generateOutputTokens(f, suffix, q=None):
if q:
fTokens = f.split('.') if q is None:
paddedFilename = '.'.join(fTokens[:-1]) + f" q{q}" + '.' + fTokens[-1] return ['-f', 'webm', f"{f}.{suffix}"]
return ['-f', 'webm', paddedFilename]
else: else:
return ['-f', 'webm', f] return ['-f', 'webm', f"{f}_q{q}.{suffix}"]
# inputFilename = sys.argv[1]
# outputFilename = sys.argv[2]
#
# targetFormat = 'vp9'
# if 'av1' in sys.argv:
# targetFormat = 'av1'
# if 'vp9' in sys.argv:
# targetFormat = 'vp9'
#
#
# qualities = [str(DEFAULT_QUALITY)]
# qualitiesTokens = [q for q in sys.argv if q.startswith('q=')]
# if qualitiesTokens:
# qualitiesString = qualitiesTokens[0].split('=')[1]
# qualities = qualitiesString.split(',')
#
# preset = DEFAULT_AV1_PRESET # preset = DEFAULT_AV1_PRESET
# presetTokens = [p for p in sys.argv if p.startswith('p=')] # presetTokens = [p for p in sys.argv if p.startswith('p=')]
# if presetTokens: # if presetTokens:
# preset = int(presetTokens[0].split('=')[1]) # preset = int(presetTokens[0].split('=')[1])
#
# stereoBandwidth = DEFAULT_STEREO_BANDWIDTH
# stereoTokens = [s for s in sys.argv if s.startswith('a=')]
# if stereoTokens:
# stereoBandwidth = str(stereoTokens[0].split('=')[1])
# if not stereoBandwidth.endswith('k'):
# stereoBandwidth += "k"
#
# ac3Bandwidth = DEFAULT_AC3_BANDWIDTH
# ac3Tokens = [a for a in sys.argv if a.startswith('ac3=')]
# if ac3Tokens:
# ac3Bandwidth = str(ac3Tokens[0].split('=')[1])
# if not ac3Bandwidth.endswith('k'):
# ac3Bandwidth += "k"
#
# dtsBandwidth = DEFAULT_DTS_BANDWIDTH
# dtsTokens = [d for d in sys.argv if d.startswith('dts=')]
# if dtsTokens:
# dtsBandwidth = str(dtsTokens[0].split('=')[1])
# if not dtsBandwidth.endswith('k'):
# dtsBandwidth += "k"
#
# cropStart = '' # cropStart = ''
# cropLength = '' # cropLength = ''
# cropTokens = [c for c in sys.argv if c.startswith('crop')] # cropTokens = [c for c in sys.argv if c.startswith('crop')]
@ -196,58 +189,54 @@ def generateOutputTokens(f, q=''):
# else: # else:
# print(f"unknown audio stream with {aStream['channels']} channels") #channel_layout # print(f"unknown audio stream with {aStream['channels']} channels") #channel_layout
# def generateAudioTokens(context, index, layout):
# audioTokens = []
# audioStreamIndex = 0 if layout == STREAM_LAYOUT_6_1:
# for aStream in audioStreams: return [f"-c:a:{index}",
# 'libopus',
# channels = aStream['channels'] f"-filter:a:{index}",
# 'channelmap=channel_layout=6.1',
# if 'channel_layout' in aStream.keys(): f"-b:a:{index}",
# context['bitrates']['dts']]
# channelLayout = aStream['channel_layout']
# elif layout == STREAM_LAYOUT_5_1:
# if channelLayout == '6.1': return [f"-c:a:{index}",
# audioTokens += [f"-c:a:{audioStreamIndex}", 'libopus',
# 'libopus', f"-filter:a:{index}",
# f"-filter:a:{audioStreamIndex}", "channelmap=FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1",
# 'channelmap=channel_layout=6.1', f"-b:a:{index}",
# f"-b:a:{audioStreamIndex}", context['bitrates']['ac3']]
# dtsBandwidth]
# elif layout == STREAM_LAYOUT_STEREO:
# if channelLayout == '5.1(side)': return [f"-c:a:{index}",
# audioTokens += [f"-c:a:{audioStreamIndex}", 'libopus',
# 'libopus', f"-b:a:{index}",
# f"-filter:a:{audioStreamIndex}", context['bitrates']['stereo']]
# "channelmap=FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1",
# f"-b:a:{audioStreamIndex}", elif layout == STREAM_LAYOUT_6CH:
# ac3Bandwidth] return [f"-c:a:{index}",
# 'libopus',
# if channelLayout == 'stereo': f"-filter:a:{index}",
# audioTokens += [f"-c:a:{audioStreamIndex}", "channelmap=FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1",
# 'libopus', f"-b:a:{index}",
# f"-b:a:{audioStreamIndex}", context['bitrates']['ac3']]
# stereoBandwidth] else:
# else: return []
#
# if channels == 6:
# audioTokens += [f"-c:a:{audioStreamIndex}", def generateClearTokens(streams):
# 'libopus', clearTokens = []
# f"-filter:a:{audioStreamIndex}", for s in streams:
# "channelmap=FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1", for k in MKVMERGE_METADATA_KEYS:
# f"-b:a:{audioStreamIndex}", clearTokens += [f"-metadata:s:{s['type'][0]}:{s['sub_index']}", f"{k}="]
# ac3Bandwidth] return clearTokens
#
#
# audioStreamIndex += 1
#
#
# nullTokens = ['-f', 'null', '/dev/null']
@click.group() @click.group()
@click.pass_context @click.pass_context
def ffx(ctx): def ffx(ctx):
"""FFX""" """FFX"""
ctx.obj = {}
pass pass
@ -271,7 +260,9 @@ def streams(filename):
click.echo(f"{d['codec']}{' (' + str(d['channels']) + ')' if d['type'] == 'audio' else ''}") click.echo(f"{d['codec']}{' (' + str(d['channels']) + ')' if d['type'] == 'audio' else ''}")
@ffx.command() @ffx.command()
@click.pass_context
@click.argument('args', nargs=-1) @click.argument('args', nargs=-1)
@click.option('-ve', '--video-encoder', type=str, default=DEFAULT_VIDEO_ENCODER, help='Target video encoder (vp9 or av1) default: vp9') @click.option('-ve', '--video-encoder', type=str, default=DEFAULT_VIDEO_ENCODER, help='Target video encoder (vp9 or av1) default: vp9')
@ -288,10 +279,13 @@ def streams(filename):
@click.option("--crop", is_flag=False, flag_value="default", default="none") @click.option("--crop", is_flag=False, flag_value="default", default="none")
@click.option("--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)
def convert(ctx, args, video_encoder, quality, preset, stereo_bitrate, ac3_bitrate, dts_bitrate, crop, clear_metadata, default_subtitle, default_audio, denoise):
def convert(args, video_encoder, quality, preset, stereo_bitrate, ac3_bitrate, dts_bitrate, crop, clear_metadata, default_subtitle, default_audio): startTime = time.perf_counter()
if len(args) != 2: if len(args) != 2:
raise click.BadArgumentUsage( raise click.BadArgumentUsage(
@ -302,20 +296,30 @@ def convert(args, video_encoder, quality, preset, stereo_bitrate, ac3_bitrate, d
sourcePath = args[0] sourcePath = args[0]
targetFilename = args[1] targetFilename = args[1]
if not os.path.isfile(sourcePath):
raise click.ClickException(f"There is no file with path {sourcePath}")
click.echo(f"src: {sourcePath} tgt: {targetFilename}") click.echo(f"src: {sourcePath} tgt: {targetFilename}")
click.echo(f"ve={video_encoder}") click.echo(f"ve={video_encoder}")
q_list = quality.split(',')
click.echo(f"q={q_list}") qualityTokens = quality.split(',')
q_list = [q for q in qualityTokens if q.isnumeric()]
click.echo(q_list)
click.echo(f"a={stereo_bitrate}") ctx.obj['bitrates'] = {}
click.echo(f"ac3={ac3_bitrate}") ctx.obj['bitrates']['stereo'] = str(stereo_bitrate) if str(stereo_bitrate).endswith('k') else f"{stereo_bitrate}k"
click.echo(f"dts={dts_bitrate}") ctx.obj['bitrates']['ac3'] = str(ac3_bitrate) if str(ac3_bitrate).endswith('k') else f"{ac3_bitrate}k"
ctx.obj['bitrates']['dts'] = str(dts_bitrate) if str(dts_bitrate).endswith('k') else f"{dts_bitrate}k"
click.echo(f"a={ctx.obj['bitrates']['stereo']}")
click.echo(f"ac3={ctx.obj['bitrates']['ac3']}")
click.echo(f"dts={ctx.obj['bitrates']['dts']}")
performCrop = (crop != 'none') performCrop = (crop != 'none')
@ -339,80 +343,93 @@ def convert(args, video_encoder, quality, preset, stereo_bitrate, ac3_bitrate, d
click.echo(f"\nRunning {len(q_list)} jobs") click.echo(f"\nRunning {len(q_list)} jobs")
for q in q_list: streamDescriptor = getStreamDescriptor(sourcePath)
click.echo(f"\nRunning job q={q}") commandTokens = COMMAND_TOKENS + [sourcePath]
streamDescriptor = getStreamDescriptor(sourcePath)
commandTokens = ['ffmpeg', '-y', '-i', sourcePath] for q in q_list:
click.echo(f"\nRunning job q={q}")
mappingVideoTokens = ['-map', 'v:0'] mappingVideoTokens = ['-map', 'v:0']
mappingTokens = mappingVideoTokens.copy() mappingTokens = mappingVideoTokens.copy()
audioTokens = []
for a in range(len(audioStreams)): audioIndex = 0
mappingTokens += ['-map', f"a:{a}"] for audioStreamDescriptor in streamDescriptor:
for s in range(len(subtitleStreams)): if audioStreamDescriptor['type'] == STREAM_TYPE_AUDIO:
mappingTokens += ['-map', f"a:{audioIndex}"]
audioTokens += generateAudioTokens(ctx.obj, audioIndex, audioStreamDescriptor['layout'])
audioIndex += 1
for s in range(len([d for d in streamDescriptor if d['type'] == STREAM_TYPE_SUBTITLE])):
mappingTokens += ['-map', f"s:{s}"] mappingTokens += ['-map', f"s:{s}"]
if video_encoder == 'av1': if video_encoder == 'av1':
commandSequence = COMMAND_TOKENS #+ mappingTokens + generateAV1Tokens(quality, preset) + audioTokens commandSequence = commandTokens + mappingTokens + audioTokens + generateAV1Tokens(q, preset) + audioTokens
if clear_metadata:
commandSequence += generateClearTokens(streamDescriptor)
#if cropStart: if performCrop:
# commandSequence += generateCropTokens(cropStart, cropLength) commandSequence += generateCropTokens(cropStart, cropLength)
#if len(qualities) > 1: commandSequence += generateOutputTokens(targetFilename, DEFAULT_FILE_SUFFIX, q)
# commandSequence += generateOutputTokens(outputFilename, quality)
#else:
# commandSequence += generateOutputTokens(outputFilename)
print(f"Command: {' '.join(commandSequence)}") click.echo(f"Command: {' '.join(commandSequence)}")
executeProcess(commandSequence) executeProcess(commandSequence)
if video_encoder == 'vp9': if video_encoder == 'vp9':
commandSequence1 = COMMAND_TOKENS #+ mappingVideoTokens + generateVP9Pass1Tokens(quality) commandSequence1 = commandTokens + mappingVideoTokens + generateVP9Pass1Tokens(q)
if performCrop:
commandSequence1 += generateCropTokens(cropStart, cropLength)
commandSequence1 += NULL_TOKENS
click.echo(f"Command 1: {' '.join(commandSequence1)}")
#if cropStart: if os.path.exists(TEMP_FILE_NAME):
# commandSequence1 += generateCropTokens(cropStart, cropLength) os.remove(TEMP_FILE_NAME)
#commandSequence1 += nullTokens executeProcess(commandSequence1)
print(f"Command 1: {' '.join(commandSequence1)}")
#if os.path.exists(TEMP_FILE_NAME): commandSequence2 = commandTokens + mappingTokens
# os.remove(TEMP_FILE_NAME)
#executeProcess(commandSequence1) if denoise:
commandSequence2 += generateDenoiseTokens()
commandSequence2 += generateVP9Pass2Tokens(q) + audioTokens
commandSequence2 = COMMAND_TOKENS #+ mappingTokens if clear_metadata:
commandSequence2 += generateClearTokens(streamDescriptor)
#if denoiseTokens: if performCrop:
# commandSequence2 += generateDenoiseTokens() commandSequence2 += generateCropTokens(cropStart, cropLength)
#commandSequence2 += generateVP9Pass2Tokens(quality) + audioTokens commandSequence2 += generateOutputTokens(targetFilename, DEFAULT_FILE_SUFFIX, q)
#if cropStart: click.echo(f"Command 2: {' '.join(commandSequence2)}")
# commandSequence2 += generateCropTokens(cropStart, cropLength)
#if len(qualities) > 1: executeProcess(commandSequence2)
# commandSequence2 += generateOutputTokens(outputFilename, quality)
#else:
# commandSequence2 += generateOutputTokens(outputFilename)
print(f"Command 2: {' '.join(commandSequence2)}")
#executeProcess(commandSequence2) click.echo('\nDONE\n')
endTime = time.perf_counter()
click.echo(f"Time elapsed {endTime - startTime}")
click.echo('\nDONE\n')
if __name__ == '__main__': if __name__ == '__main__':

Loading…
Cancel
Save