Skip to content

Commit

Permalink
feat: prompt bitrate values for cbr mode (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyrch authored Nov 22, 2023
1 parent 45242df commit bc11ce7
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 46 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**

Expand Down Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions batch_encoder/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
43 changes: 27 additions & 16 deletions batch_encoder/_encode_webm.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,25 +112,25 @@ def preview_seek(self, webm_filename=''):
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()} ' \
Expand Down Expand Up @@ -160,14 +160,17 @@ def get_video_filters(config_filter=None):
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}'
Expand All @@ -193,16 +196,24 @@ def get_commands(self, encoding_config):

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))
Expand Down
12 changes: 10 additions & 2 deletions batch_encoder/_encoding_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand All @@ -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
Expand All @@ -51,6 +57,8 @@ def from_config(cls, config):
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)
Expand All @@ -63,7 +71,7 @@ def from_config(cls, config):
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):
Expand Down
66 changes: 40 additions & 26 deletions batch_encoder/_interface.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from ._bitrate_mode import BitrateMode

import inquirer
import logging
import re
Expand All @@ -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'
Expand All @@ -26,8 +27,7 @@ def prompt_text(message, validate=lambda _, x: x):

# 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 ''
Expand All @@ -39,8 +39,7 @@ def prompt_time(message, validate=validate_time):
# 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()
Expand All @@ -51,8 +50,7 @@ def choose_mode():

# 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()
Expand Down Expand Up @@ -83,8 +81,7 @@ def audio_filters_options(output_name):

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
Expand Down Expand Up @@ -146,9 +143,10 @@ def video_filters(encoding_config):
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
Expand All @@ -170,32 +168,48 @@ def video_filters(encoding_config):
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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

setup(
name='animethemes-batch-encoder',
version='2.1',
version='2.2',
author='AnimeThemes',
author_email='admin@animethemes.moe',
url='https://github.com/AnimeThemes/animethemes-batch-encoder',
Expand Down

0 comments on commit bc11ce7

Please sign in to comment.