You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
6.6 KiB
Python
236 lines
6.6 KiB
Python
#! /usr/bin/python3
|
|
|
|
import os, sys, subprocess, json
|
|
|
|
DEFAULT_QUALITY = 23
|
|
DEFAULT_AV1_PRESET = 5
|
|
DEFAULT_STEREO_BANDWIDTH = "128"
|
|
DEFAULT_AC3_BANDWIDTH = "256"
|
|
DEFAULT_DTS_BANDWIDTH = "320"
|
|
|
|
TEMP_FILE_NAME = "ffmpeg2pass-0.log"
|
|
|
|
def executeProcess(commandSequence):
|
|
|
|
process = subprocess.Popen(commandSequence, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
output, error = process.communicate()
|
|
|
|
return output
|
|
|
|
|
|
def generateAV1Tokens(q, p):
|
|
|
|
return ['-c:v:0',
|
|
'libsvtav1',
|
|
'-svtav1-params',
|
|
f"crf={q}:preset={p}:tune=0:enable-overlays=1:scd=1:scm=0",
|
|
'-pix_fmt', 'yuv420p10le']
|
|
|
|
def generateVP9Pass1Tokens(q):
|
|
|
|
return ['-c:v:0', 'libvpx-vp9', '-row-mt', '1',
|
|
'-crf', str(q), '-pass', '1', '-speed', '4',
|
|
'-frame-parallel', '0', '-g', '9999', '-aq-mode', '0']
|
|
|
|
def generateVP9Pass2Tokens(q):
|
|
|
|
return ['-c:v:0', 'libvpx-vp9', '-row-mt', '1',
|
|
'-crf', str(q), '-pass', '2',
|
|
'-frame-parallel', '0', '-g', '9999', '-aq-mode', '0',
|
|
'-auto-alt-ref', '1', '-lag-in-frames', '25']
|
|
|
|
def generateCropTokens(start, length):
|
|
|
|
return ['-ss', str(start), '-t', str(length)]
|
|
|
|
def generateOutputTokens(f, q=''):
|
|
if q:
|
|
fTokens = f.split('.')
|
|
paddedFilename = '.'.join(fTokens[:-1]) + f" q{q}" + '.' + fTokens[-1]
|
|
return ['-f', 'webm', paddedFilename]
|
|
else:
|
|
return ['-f', 'webm', f]
|
|
|
|
|
|
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
|
|
presetTokens = [p for p in sys.argv if p.startswith('p=')]
|
|
if presetTokens:
|
|
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 = ''
|
|
cropLength = ''
|
|
cropTokens = [c for c in sys.argv if c.startswith('crop')]
|
|
if cropTokens:
|
|
if '=' in cropTokens[0]:
|
|
cropString = cropTokens[0].split('=')[1]
|
|
cropStart, cropLength = cropString.split(',')
|
|
else:
|
|
cropStart = 60
|
|
cropLength = 180
|
|
|
|
|
|
output = executeProcess(["ffprobe", "-show_streams", "-of", "json" ,inputFilename])
|
|
|
|
streamData = json.loads(output)['streams']
|
|
|
|
videoStreams = [s for s in streamData if s['codec_type'] == 'video']
|
|
audioStreams = [s for s in streamData if s['codec_type'] == 'audio']
|
|
subtitleStreams = [s for s in streamData if s['codec_type'] == 'subtitle']
|
|
|
|
for aStream in audioStreams:
|
|
if 'channel_layout' in aStream.keys():
|
|
print(f"audio stream: {aStream['channel_layout']}") #channel_layout
|
|
else:
|
|
print(f"unknown audio stream with {aStream['channels']} channels") #channel_layout
|
|
|
|
commandTokens = ['ffmpeg', '-y', '-i', inputFilename]
|
|
|
|
|
|
mappingTokens = ['-map', 'v:0']
|
|
|
|
for a in range(len(audioStreams)):
|
|
mappingTokens += ['-map', f"a:{a}"]
|
|
|
|
for s in range(len(subtitleStreams)):
|
|
mappingTokens += ['-map', f"s:{s}"]
|
|
|
|
|
|
audioTokens = []
|
|
audioStreamIndex = 0
|
|
for aStream in audioStreams:
|
|
|
|
channels = aStream['channels']
|
|
|
|
if 'channel_layout' in aStream.keys():
|
|
|
|
channelLayout = aStream['channel_layout']
|
|
|
|
if channelLayout == '6.1':
|
|
audioTokens += [f"-c:a:{audioStreamIndex}",
|
|
'libopus',
|
|
f"-filter:a:{audioStreamIndex}",
|
|
'channelmap=channel_layout=6.1',
|
|
f"-b:a:{audioStreamIndex}",
|
|
dtsBandwidth]
|
|
|
|
if channelLayout == '5.1(side)':
|
|
audioTokens += [f"-c:a:{audioStreamIndex}",
|
|
'libopus',
|
|
f"-filter:a:{audioStreamIndex}",
|
|
"channelmap=FL-FL|FR-FR|FC-FC|LFE-LFE|SL-BL|SR-BR:5.1",
|
|
f"-b:a:{audioStreamIndex}",
|
|
ac3Bandwidth]
|
|
|
|
if channelLayout == 'stereo':
|
|
audioTokens += [f"-c:a:{audioStreamIndex}",
|
|
'libopus',
|
|
f"-b:a:{audioStreamIndex}",
|
|
stereoBandwidth]
|
|
else:
|
|
|
|
if channels == 6:
|
|
audioTokens += [f"-c:a:{audioStreamIndex}",
|
|
'libopus',
|
|
f"-b:a:{audioStreamIndex}",
|
|
ac3Bandwidth]
|
|
|
|
|
|
audioStreamIndex += 1
|
|
|
|
|
|
nullTokens = ['-f', 'null', '/dev/null']
|
|
|
|
|
|
for quality in qualities:
|
|
|
|
if targetFormat == 'av1':
|
|
|
|
commandSequence = commandTokens + mappingTokens + generateAV1Tokens(quality, preset) + audioTokens
|
|
|
|
if cropStart:
|
|
commandSequence += generateCropTokens(cropStart, cropLength)
|
|
|
|
if len(qualities) > 1:
|
|
commandSequence += generateOutputTokens(outputFilename, quality)
|
|
else:
|
|
commandSequence += generateOutputTokens(outputFilename)
|
|
|
|
print(f"Command: {' '.join(commandSequence)}")
|
|
|
|
executeProcess(commandSequence)
|
|
|
|
|
|
if targetFormat == 'vp9':
|
|
|
|
commandSequence1 = commandTokens + mappingTokens + generateVP9Pass1Tokens(quality)
|
|
|
|
|
|
if cropStart:
|
|
commandSequence1 += generateCropTokens(cropStart, cropLength)
|
|
|
|
commandSequence1 += nullTokens
|
|
|
|
|
|
print(f"Command 1: {' '.join(commandSequence1)}")
|
|
|
|
if os.path.exists(TEMP_FILE_NAME):
|
|
os.remove(TEMP_FILE_NAME)
|
|
|
|
executeProcess(commandSequence1)
|
|
|
|
commandSequence2 = commandTokens + mappingTokens + generateVP9Pass2Tokens(quality) + audioTokens
|
|
|
|
|
|
if cropStart:
|
|
commandSequence2 += generateCropTokens(cropStart, cropLength)
|
|
|
|
|
|
if len(qualities) > 1:
|
|
commandSequence2 += generateOutputTokens(outputFilename, quality)
|
|
else:
|
|
commandSequence2 += generateOutputTokens(outputFilename)
|
|
|
|
|
|
print(f"Command 2: {' '.join(commandSequence2)}")
|
|
|
|
executeProcess(commandSequence2)
|
|
|