mirror of
https://github.com/AnimeThemes/animethemes-batch-encoder.git
synced 2026-11-13 01:30:45 +01:00
feat: prompt bitrate values for cbr mode (#33)
This commit is contained in:
1 parent
45242df1f2
commit
bc11ce7aae
6 files changed
+85
-46
No files matched your search
@@ -19,7 +19,7 @@ Ideally we are iterating over a combination of filters and settings, picking the
|
||||
|
||||
### Usage
|
||||
|
||||
python -m batch_encoder [-h] [--generate | -g] [--execute | -e] [--custom | -c] [--file [FILE]] [--configfile [CONFIGFILE]] --loglevel [{debug,info,error}]
|
||||
python -m batch_encoder [-h] [--generate | -g] [--execute | -e] [--custom | -c] [--file [FILE]] [--configfile [CONFIGFILE]] [--inputfile [INPUTFILES]] --loglevel [{debug,info,error}]
|
||||
|
||||
**Mode**
|
||||
|
||||
@@ -90,6 +90,10 @@ Available bitrate control modes are:
|
||||
|
||||
`CRFs` is a comma-separated listing of ordered CRF values to use with `VBR` and/or `CQ` bitrate control modes.
|
||||
|
||||
`CBRBitrates` is comma-separated listing of ordered bitrate values to use with `CBR`.
|
||||
|
||||
`CBRMaxBitrates` is comma-separated listing of ordered maximum bitrate values to use with `CBR`.
|
||||
|
||||
`Threads` is the number of threads used to encode. Default is 4.
|
||||
|
||||
`LimitSizeEnable` is a flag for including the `-fs` argument to terminate an encode when it exceeds the allowed size. Default is True.
|
||||
|
||||
@@ -59,6 +59,8 @@ def main():
|
||||
config['Encoding'] = {EncodingConfig.config_allowed_filetypes: EncodingConfig.default_allowed_filetypes,
|
||||
EncodingConfig.config_encoding_modes: EncodingConfig.default_encoding_modes,
|
||||
EncodingConfig.config_crfs: EncodingConfig.default_crfs,
|
||||
EncodingConfig.config_cbr_bitrates: EncodingConfig.default_cbr_bitrates,
|
||||
EncodingConfig.config_cbr_max_bitrates: EncodingConfig.default_cbr_max_bitrates,
|
||||
EncodingConfig.config_threads: EncodingConfig.default_threads,
|
||||
EncodingConfig.config_limit_size_enable: EncodingConfig.default_limit_size_enable,
|
||||
EncodingConfig.config_alternate_source_files: EncodingConfig.default_alternate_source_files,
|
||||
|
||||
@@ -112,25 +112,25 @@ class EncodeWebM:
|
||||
f'-vcodec copy -c:a aac -b:a 128k -sn -f mp4 {webm_filename}.mp4'
|
||||
|
||||
# First-pass encode
|
||||
def get_first_pass(self, encoding_mode, crf=None, threads=4):
|
||||
def get_first_pass(self, encoding_mode, crf=None, cbr_bitrate=None, cbr_max_bitrate=None, threads=4):
|
||||
return f'ffmpeg {self.seek.get_seek_string()} ' \
|
||||
f'-pass 1 -passlogfile {self.seek.output_name} ' \
|
||||
f'-map 0:v:{self.source_file.selected_video_stream} ' \
|
||||
f'-map 0:a:{self.source_file.selected_audio_stream} ' \
|
||||
f'-c:v libvpx-vp9 ' \
|
||||
f'{encoding_mode.first_pass_rate_control(self.cbr_bitrate, self.cbr_max_bitrate, crf)} ' \
|
||||
f'{encoding_mode.first_pass_rate_control(cbr_bitrate, cbr_max_bitrate, crf)} ' \
|
||||
f'-cpu-used 4 -g {self.g} -threads {threads} -tile-columns 6 -frame-parallel 0 -auto-alt-ref 1 ' \
|
||||
f'-lag-in-frames 25 -row-mt 1 -pix_fmt yuv420p {self.colorspace.get_args()} -an -sn -f webm -y NUL'
|
||||
|
||||
# Second-pass encode
|
||||
def get_second_pass(self, encoding_mode, crf=None, threads=4, video_filters='', limit_size_enable=True, webm_filename=''):
|
||||
def get_second_pass(self, encoding_mode, crf=None, cbr_bitrate=None, cbr_max_bitrate=None, threads=4, video_filters='', limit_size_enable=True, webm_filename=''):
|
||||
limit_size = '-fs ' + EncodeWebM.get_limit_file_size(self, video_filters=video_filters) + ' ' if limit_size_enable else ''
|
||||
return f'ffmpeg {self.seek.get_seek_string()} ' \
|
||||
f'-pass 2 -passlogfile {self.seek.output_name} ' \
|
||||
f'-map 0:v:{self.source_file.selected_video_stream} ' \
|
||||
f'-map 0:a:{self.source_file.selected_audio_stream} ' \
|
||||
f'-c:v libvpx-vp9 ' \
|
||||
f'{encoding_mode.second_pass_rate_control(self.cbr_bitrate, self.cbr_max_bitrate, crf)} ' \
|
||||
f'{encoding_mode.second_pass_rate_control(cbr_bitrate, cbr_max_bitrate, crf)} ' \
|
||||
f'-cpu-used 0 -g {self.g} -threads {threads} {self.get_audio_filters()}{video_filters} -tile-columns 6 ' \
|
||||
f'-frame-parallel 0 -auto-alt-ref 1 -lag-in-frames 25 -row-mt 1 -pix_fmt yuv420p ' \
|
||||
f'{self.colorspace.get_args()} ' \
|
||||
@@ -160,14 +160,17 @@ class EncodeWebM:
|
||||
return ' -vf ' + ','.join(video_filters)
|
||||
|
||||
# Build unique WebM filename for encodes
|
||||
def get_webm_filename(self, crf=None, cbr_bitrate=None, filter_name=None):
|
||||
def get_webm_filename(self, crf=None, cbr_bitrate=None, cbr_max_bitrate=None, filter_name=None):
|
||||
webm_filename = self.seek.output_name
|
||||
|
||||
if crf is not None:
|
||||
webm_filename += f'-{crf}'
|
||||
|
||||
if cbr_bitrate is not None:
|
||||
webm_filename += f'-{self.cbr_bitrate}'
|
||||
webm_filename += f'-{cbr_bitrate}'
|
||||
|
||||
if cbr_max_bitrate is not None:
|
||||
webm_filename += f'-{cbr_max_bitrate}'
|
||||
|
||||
if filter_name is not None:
|
||||
webm_filename += f'-{filter_name}'
|
||||
@@ -193,16 +196,24 @@ class EncodeWebM:
|
||||
|
||||
for encoding_mode in encoding_config.encoding_modes:
|
||||
if BitrateMode.CBR.name == encoding_mode.upper():
|
||||
file_commands.append(self.get_first_pass(BitrateMode.CBR, threads=encoding_config.threads))
|
||||
for filter_name, filter_value in encoding_config.video_filters:
|
||||
file_commands.append(self.get_second_pass(BitrateMode.CBR,
|
||||
threads=encoding_config.threads,
|
||||
video_filters=EncodeWebM.get_video_filters(
|
||||
config_filter=filter_value),
|
||||
limit_size_enable=encoding_config.limit_size_enable,
|
||||
webm_filename=self.get_webm_filename(
|
||||
cbr_bitrate=self.cbr_bitrate,
|
||||
filter_name=filter_name)))
|
||||
cbr_bitrates = encoding_config.cbr_bitrates if encoding_config.cbr_bitrates is not None else [self.cbr_bitrate]
|
||||
cbr_max_bitrates = encoding_config.cbr_max_bitrates if encoding_config.cbr_max_bitrates is not None else [self.cbr_max_bitrate]
|
||||
|
||||
for cbr_bitrate in cbr_bitrates:
|
||||
for cbr_max_bitrate in cbr_max_bitrates:
|
||||
file_commands.append(self.get_first_pass(BitrateMode.CBR, cbr_bitrate=cbr_bitrate, cbr_max_bitrate=cbr_max_bitrate, threads=encoding_config.threads))
|
||||
for filter_name, filter_value in encoding_config.video_filters:
|
||||
file_commands.append(self.get_second_pass(BitrateMode.CBR,
|
||||
cbr_bitrate=cbr_bitrate,
|
||||
cbr_max_bitrate=cbr_max_bitrate,
|
||||
threads=encoding_config.threads,
|
||||
video_filters=EncodeWebM.get_video_filters(
|
||||
config_filter=filter_value),
|
||||
limit_size_enable=encoding_config.limit_size_enable,
|
||||
webm_filename=self.get_webm_filename(
|
||||
cbr_bitrate=cbr_bitrate,
|
||||
cbr_max_bitrate=cbr_max_bitrate,
|
||||
filter_name=filter_name)))
|
||||
elif BitrateMode.VBR.name == encoding_mode.upper():
|
||||
for crf in encoding_config.crfs:
|
||||
file_commands.append(self.get_first_pass(BitrateMode.VBR, crf=crf, threads=encoding_config.threads))
|
||||
|
||||
@@ -6,6 +6,8 @@ class EncodingConfig:
|
||||
config_allowed_filetypes = 'AllowedFileTypes'
|
||||
config_encoding_modes = 'EncodingModes'
|
||||
config_crfs = 'CRFs'
|
||||
config_cbr_bitrates = 'CBRBitrates'
|
||||
config_cbr_max_bitrates = 'CBRMaxBitrates'
|
||||
config_threads = 'Threads'
|
||||
config_limit_size_enable = 'LimitSizeEnable'
|
||||
config_alternate_source_files = 'AlternateSourceFiles'
|
||||
@@ -20,6 +22,8 @@ class EncodingConfig:
|
||||
default_allowed_filetypes = '.avi,.m2ts,.mkv,.mp4,.wmv'
|
||||
default_encoding_modes = f'{BitrateMode.VBR.name},{BitrateMode.CBR.name}'
|
||||
default_crfs = '12,15,18,21,24'
|
||||
default_cbr_bitrates = '5600'
|
||||
default_cbr_max_bitrates = '6400'
|
||||
default_threads = '4'
|
||||
default_limit_size_enable = True
|
||||
default_alternate_source_files = False
|
||||
@@ -30,11 +34,13 @@ class EncodingConfig:
|
||||
'heavydenoise': 'hqdn3d=1.5:1.5:6:6',
|
||||
'unsharp': 'unsharp'}
|
||||
|
||||
def __init__(self, allowed_filetypes, encoding_modes, crfs, threads, limit_size_enable, alternate_source_files, create_preview, include_unfiltered, video_filters, default_video_stream,
|
||||
def __init__(self, allowed_filetypes, encoding_modes, crfs, cbr_bitrates, cbr_max_bitrates, threads, limit_size_enable, alternate_source_files, create_preview, include_unfiltered, video_filters, default_video_stream,
|
||||
default_audio_stream):
|
||||
self.allowed_filetypes = allowed_filetypes
|
||||
self.encoding_modes = encoding_modes
|
||||
self.crfs = crfs
|
||||
self.cbr_bitrates = cbr_bitrates
|
||||
self.cbr_max_bitrates = cbr_max_bitrates
|
||||
self.threads = threads
|
||||
self.limit_size_enable = limit_size_enable
|
||||
self.alternate_source_files = alternate_source_files
|
||||
@@ -51,6 +57,8 @@ class EncodingConfig:
|
||||
encoding_modes = config['Encoding'].get(EncodingConfig.config_encoding_modes,
|
||||
EncodingConfig.default_encoding_modes).split(',')
|
||||
crfs = config['Encoding'].get(EncodingConfig.config_crfs, EncodingConfig.default_crfs).split(',')
|
||||
cbr_bitrates = config['Encoding'].get(EncodingConfig.config_cbr_bitrates, EncodingConfig.default_cbr_bitrates).split(',')
|
||||
cbr_max_bitrates = config['Encoding'].get(EncodingConfig.config_cbr_max_bitrates, EncodingConfig.default_cbr_max_bitrates).split(',')
|
||||
threads = config['Encoding'].get(EncodingConfig.config_threads,
|
||||
EncodingConfig.default_threads)
|
||||
limit_size_enable = config.getboolean('Encoding', EncodingConfig.config_limit_size_enable, fallback=EncodingConfig.default_limit_size_enable)
|
||||
@@ -63,7 +71,7 @@ class EncodingConfig:
|
||||
default_video_stream = config['Encoding'].get(EncodingConfig.config_default_video_stream)
|
||||
default_audio_stream = config['Encoding'].get(EncodingConfig.config_default_audio_stream)
|
||||
|
||||
return cls(allowed_filetypes, encoding_modes, crfs, threads, limit_size_enable, alternate_source_files, create_preview, include_unfiltered, video_filters, default_video_stream,
|
||||
return cls(allowed_filetypes, encoding_modes, crfs, cbr_bitrates, cbr_max_bitrates, threads, limit_size_enable, alternate_source_files, create_preview, include_unfiltered, video_filters, default_video_stream,
|
||||
default_audio_stream)
|
||||
|
||||
def get_default_stream(self, stream_type):
|
||||
|
||||
+40
-26
@@ -1,3 +1,5 @@
|
||||
from ._bitrate_mode import BitrateMode
|
||||
|
||||
import inquirer
|
||||
import logging
|
||||
import re
|
||||
@@ -9,13 +11,12 @@ class Interface:
|
||||
|
||||
# Validations
|
||||
validate_time = lambda _, x: all(Interface.time_pattern.match(y) for y in x.split(','))
|
||||
validate_crfs = lambda _, x: all(y.strip().isdigit() for y in x.split(','))
|
||||
validate_encoding_modes = lambda _, x: all(y.strip().upper() in ['VBR', 'CBR', 'CQ'] for y in x.split(','))
|
||||
validate_encoding_modes = lambda _, x: all(y.strip().upper() in [BitrateMode.VBR.name, BitrateMode.CBR.name, BitrateMode.CQ.name] for y in x.split(','))
|
||||
validate_digits = lambda _, x: all(y.strip().isdigit() for y in x.split(',')) or len(x.strip()) == 0
|
||||
|
||||
# Prompt the user for text questions
|
||||
def prompt_text(message, validate=lambda _, x: x):
|
||||
question = [inquirer.Text('text', message=message, validate=validate)]
|
||||
answer = inquirer.prompt(question)
|
||||
answer = inquirer.prompt([inquirer.Text('text', message=message, validate=validate)])
|
||||
|
||||
if answer is None:
|
||||
return 'NoName'
|
||||
@@ -26,8 +27,7 @@ class Interface:
|
||||
|
||||
# Prompt the user for time questions
|
||||
def prompt_time(message, validate=validate_time):
|
||||
question = [inquirer.Text('time', message=message, validate=validate)]
|
||||
answer = inquirer.prompt(question)
|
||||
answer = inquirer.prompt([inquirer.Text('time', message=message, validate=validate)])
|
||||
|
||||
if answer is None:
|
||||
return ''
|
||||
@@ -39,8 +39,7 @@ class Interface:
|
||||
# Prompt the user for our mode options to run to the user
|
||||
def choose_mode():
|
||||
modes = [('Generate commands', 1), ('Execute commands', 2), ('Generate and execute commands', 3)]
|
||||
question = [inquirer.List('mode', message='Mode (Enter)', choices=modes)]
|
||||
answer = inquirer.prompt(question)
|
||||
answer = inquirer.prompt([inquirer.List('mode', message='Mode (Enter)', choices=modes)])
|
||||
|
||||
if answer is None:
|
||||
sys.exit()
|
||||
@@ -51,8 +50,7 @@ class Interface:
|
||||
|
||||
# Prompt the user for source files to choose
|
||||
def choose_source_files(source_files):
|
||||
question = [inquirer.Checkbox('source_files', message='Source Files (Space to select)', choices=source_files)]
|
||||
answer = inquirer.prompt(question)
|
||||
answer = inquirer.prompt([inquirer.Checkbox('source_files', message='Source Files (Space to select)', choices=source_files)])
|
||||
|
||||
if answer is None:
|
||||
sys.exit()
|
||||
@@ -83,8 +81,7 @@ class Interface:
|
||||
|
||||
while not audio_filters['Exit']:
|
||||
print(f'\n\033[92mOutput Name: {output_name}\033[0m')
|
||||
question = [inquirer.List('audio_filters', message='Audio Filters (Enter)', choices=list(audio_filters.keys()))]
|
||||
answer = inquirer.prompt(question)
|
||||
answer = inquirer.prompt([inquirer.List('audio_filters', message='Audio Filters (Enter)', choices=list(audio_filters.keys()))])
|
||||
|
||||
if answer is None:
|
||||
audio_filters['Exit'] = True
|
||||
@@ -146,9 +143,10 @@ class Interface:
|
||||
if encoding_config.include_unfiltered:
|
||||
encoding_config.video_filters.append((None, 'No Filters'))
|
||||
|
||||
question = [inquirer.Checkbox('video_filters', message='Select Video Filters (Space to select)',
|
||||
choices=video_filters_options.keys(), default=[tp[1] for tp in encoding_config.video_filters])]
|
||||
answer = inquirer.prompt(question)
|
||||
answer = inquirer.prompt([
|
||||
inquirer.Checkbox('video_filters', message='Select Video Filters (Space to select)',
|
||||
choices=video_filters_options.keys(), default=[tp[1] for tp in encoding_config.video_filters])
|
||||
])
|
||||
|
||||
if answer is None:
|
||||
return encoding_config
|
||||
@@ -170,32 +168,48 @@ class Interface:
|
||||
def custom_options(encoding_config):
|
||||
create_preview = encoding_config.create_preview
|
||||
limit_size_enable = encoding_config.limit_size_enable
|
||||
crfs = encoding_config.crfs
|
||||
encoding_modes = encoding_config.encoding_modes
|
||||
crfs = encoding_config.crfs
|
||||
cbr_bitrates = encoding_config.cbr_bitrates
|
||||
cbr_max_bitrates = encoding_config.cbr_max_bitrates
|
||||
|
||||
questions = [
|
||||
inquirer.Confirm('create_preview', message=f'Create Preview?', default=create_preview),
|
||||
inquirer.Confirm('limit_size_enable', message=f'Limit Size Enable?', default=limit_size_enable),
|
||||
inquirer.Text('crfs', message='CRFs', default=','.join(crfs), validate=Interface.validate_crfs),
|
||||
inquirer.Text('encoding_modes', message='Encoding Modes', default=','.join(encoding_modes), validate=Interface.validate_encoding_modes)
|
||||
]
|
||||
|
||||
answer = inquirer.prompt(questions)
|
||||
answer = inquirer.prompt([
|
||||
inquirer.Confirm('create_preview', message='Create Preview?', default=create_preview),
|
||||
inquirer.Confirm('limit_size_enable', message='Limit Size Enable?', default=limit_size_enable),
|
||||
inquirer.Text('encoding_modes', message='Encoding Modes', default=','.join(encoding_modes), validate=Interface.validate_encoding_modes),
|
||||
])
|
||||
|
||||
if answer is None:
|
||||
return encoding_config
|
||||
|
||||
encoding_mode_questions = []
|
||||
for encoding_mode in answer['encoding_modes'].split(','):
|
||||
if encoding_mode == BitrateMode.VBR.name or encoding_mode == BitrateMode.CQ.name:
|
||||
encoding_mode_questions.append(inquirer.Text('crfs', message='CRFs', default=','.join(crfs), validate=Interface.validate_digits))
|
||||
if encoding_mode == BitrateMode.CBR.name:
|
||||
encoding_mode_questions.append(inquirer.Text('cbr_bitrates', message='Bit Rates', default=','.join(cbr_bitrates), validate=Interface.validate_digits))
|
||||
encoding_mode_questions.append(inquirer.Text('cbr_max_bitrates', message='Max Bit Rates', default=','.join(cbr_max_bitrates), validate=Interface.validate_digits))
|
||||
|
||||
answer_em = inquirer.prompt(encoding_mode_questions)
|
||||
|
||||
encoding_config.create_preview = answer['create_preview']
|
||||
encoding_config.limit_size_enable = answer['limit_size_enable']
|
||||
encoding_config.crfs = answer['crfs'].split(',')
|
||||
encoding_config.encoding_modes = answer['encoding_modes'].split(',')
|
||||
|
||||
if 'crfs' in answer_em:
|
||||
encoding_config.crfs = answer_em['crfs'].split(',')
|
||||
if 'cbr_bitrates' in answer_em and 'cbr_max_bitrates' in answer_em:
|
||||
encoding_config.cbr_bitrates = [x + 'k' for x in answer_em['cbr_bitrates'].split(',')] if len(answer_em['cbr_bitrates'].strip()) != 0 else None
|
||||
encoding_config.cbr_max_bitrates = [x + 'k' for x in answer_em['cbr_max_bitrates'].split(',')] if len(answer_em['cbr_max_bitrates'].strip()) != 0 else None
|
||||
|
||||
logging.debug(
|
||||
f'[Interface.custom_options] '
|
||||
f'encoding_config.create_preview: \'{encoding_config.create_preview}\', '
|
||||
f'encoding_config.limit_size_enable: \'{encoding_config.create_preview}\', '
|
||||
f'encoding_config.encoding_modes: \'{encoding_config.encoding_modes}\', '
|
||||
f'encoding_config.crfs: \'{encoding_config.crfs}\', '
|
||||
f'encoding_config.encoding_modes: \'{encoding_config.encoding_modes}\''
|
||||
f'encoding_config.cbr_bitrates: \'{encoding_config.cbr_bitrates}\', '
|
||||
f'encoding_config.cbr_max_bitrates: \'{encoding_config.cbr_max_bitrates}\''
|
||||
)
|
||||
|
||||
return encoding_config
|
||||
Reference in new issue
Block a user