Skip to content

Commit

Permalink
refactor: migrated audio and video filters to enums (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyrch authored Nov 24, 2023
1 parent bc11ce7 commit f7ce09a
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 83 deletions.
12 changes: 6 additions & 6 deletions batch_encoder/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from ._encode_webm import EncodeWebM
from ._encoding_config import EncodingConfig
from ._interface import Interface
from ._cli import CLI
from ._seek_collector import SeekCollector
from ._source_file import SourceFile
from ._utils import commandfile_arg_type
Expand Down Expand Up @@ -64,7 +64,7 @@ def main():
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,
EncodingConfig.create_preview: EncodingConfig.default_create_preview,
EncodingConfig.config_create_preview: EncodingConfig.default_create_preview,
EncodingConfig.config_include_unfiltered: EncodingConfig.default_include_unfiltered,
EncodingConfig.config_default_video_stream: '',
EncodingConfig.config_default_audio_stream: ''}
Expand All @@ -88,7 +88,7 @@ def main():
elif args.execute:
mode = 2
else:
mode = Interface.choose_mode()
mode = CLI.choose_mode()

# Generate commands from source file candidates in current directory
if mode == 1 or mode == 3:
Expand All @@ -99,7 +99,7 @@ def main():
logging.error('No source file candidates in current directory')
sys.exit()

source_files = Interface.choose_source_files(source_file_candidates)
source_files = CLI.choose_source_files(source_file_candidates)
else:
source_files = args.inputfile.split(',,')

Expand All @@ -121,11 +121,11 @@ def main():
new_encoding_config = copy.copy(encoding_config)

print(f'\033[92mOutput Name: {seek.output_name}\033[0m')
new_encoding_config = Interface.video_filters(new_encoding_config)
new_encoding_config = CLI.video_filters(new_encoding_config)

if args.custom:
print(f'\033[92mOutput Name: {seek.output_name}\033[0m')
new_encoding_config = Interface.custom_options(new_encoding_config)
new_encoding_config = CLI.custom_options(new_encoding_config)

logging.info(f'Generating commands with seek ss: \'{seek.ss}\', to: \'{seek.to}\'')
encode_webm = EncodeWebM(file_value, seek)
Expand Down
32 changes: 32 additions & 0 deletions batch_encoder/_audio_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import enum


# The Audio Filter Enumerated List
# Convert filter values to their strings
class AudioFilter(enum.Enum):
def __new__(cls, name, toText):
obj = object.__new__(cls)
obj.toText = toText
return obj

FADE_IN = ('Fade In', lambda value: f'afade=d={value}:curve=exp')
FADE_OUT = ('Fade Out', lambda start, value: f'afade=t=out:st={start}:d={value}')
MUTE = ('Mute', lambda start, end: f"volume=enable='between(t,{start},{end})':volume=0")
CUSTOM = ('Custom', lambda text: text)

# Get the object to prompt to the user
@classmethod
def get_obj(self):
return {
'Exit': False,
self.CUSTOM._value_[0]: '',
self.FADE_IN._value_[0]: 0,
self.FADE_OUT._value_[0]: {
'Start Time': 0,
'Exp': 0
},
self.MUTE._value_[0]: {
'Start Time': 0,
'End Time': 0
}
}
134 changes: 62 additions & 72 deletions batch_encoder/_interface.py → batch_encoder/_cli.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
from ._audio_filter import AudioFilter
from ._bitrate_mode import BitrateMode
from ._video_filter import VideoFilter

import inquirer
import logging
import re
import sys

class Interface:

# Use the inquirer lib to create a CLI
class CLI:
# Time Duration Specification: https://ffmpeg.org/ffmpeg-utils.html#time-duration-syntax
time_pattern = re.compile('^([0-5]?\d:){1,2}[0-5]?\d(?=\.\d+$|$)|\d+(?=\.\d+$|$)')

# Validations
validate_time = lambda _, x: all(Interface.time_pattern.match(y) for y in x.split(','))
validate_time = lambda _, x: all(CLI.time_pattern.match(y) 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

Expand All @@ -21,7 +25,7 @@ def prompt_text(message, validate=lambda _, x: x):
if answer is None:
return 'NoName'

logging.debug(f'[Interface.prompt_text] answer["text"]: \'{answer["text"]}\'')
logging.debug(f'[CLI.prompt_text] answer["text"]: \'{answer["text"]}\'')

return answer['text']

Expand All @@ -32,7 +36,7 @@ def prompt_time(message, validate=validate_time):
if answer is None:
return ''

logging.debug(f'[Interface.prompt_time] answer["time"]: \'{answer["time"]}\'')
logging.debug(f'[CLI.prompt_time] answer["time"]: \'{answer["time"]}\'')

return answer['time']

Expand All @@ -44,7 +48,7 @@ def choose_mode():
if answer is None:
sys.exit()

logging.debug(f'[Interface.choose_mode] answer["mode"]: \'{answer["mode"]}\'')
logging.debug(f'[CLI.choose_mode] answer["mode"]: \'{answer["mode"]}\'')

return answer['mode']

Expand All @@ -59,86 +63,71 @@ def choose_source_files(source_files):
logging.error('Select at least one file')
sys.exit()

logging.debug(f'[Interface.choose_source_files] answer["source_files"]: \'{answer["source_files"]}\'')
logging.debug(f'[CLI.choose_source_files] answer["source_files"]: \'{answer["source_files"]}\'')

return answer['source_files']

# Prompt the user for audio filters options
def audio_filters_options(output_name):
audio_filters = {
'Exit': False,
'Custom': '',
'Fade In': 0,
'Fade Out': {
'Start Time': 0,
'Exp': 0
},
'Mute': {
'Start Time': 0,
'End Time': 0
}
}

while not audio_filters['Exit']:
af = AudioFilter.get_obj()
fadein, fadeout, mute, custom = (
AudioFilter.FADE_IN._value_[0],
AudioFilter.FADE_OUT._value_[0],
AudioFilter.MUTE._value_[0],
AudioFilter.CUSTOM._value_[0]
)

while not af['Exit']:
print(f'\n\033[92mOutput Name: {output_name}\033[0m')
answer = inquirer.prompt([inquirer.List('audio_filters', message='Audio Filters (Enter)', choices=list(audio_filters.keys()))])
answer = inquirer.prompt([inquirer.List('af', message='Audio Filters (Enter)', choices=list(af.keys()))])

if answer is None:
audio_filters['Exit'] = True
af['Exit'] = True

elif answer['audio_filters'] == 'Fade In':
audio_filters['Fade In'] = Interface.prompt_time('Exponential Value').strip() or '0'
elif answer['af'] == fadein:
af[fadein] = CLI.prompt_time('Exponential Value').strip() or '0'

elif answer['audio_filters'] == 'Fade Out':
audio_filters['Fade Out']['Start Time'] = Interface.prompt_time('Start Time').strip() or '0'
audio_filters['Fade Out']['Exp'] = Interface.prompt_time('Exponential Value').strip() or '0'
elif answer['af'] == fadeout:
af[fadeout]['Start Time'] = CLI.prompt_time('Start Time').strip() or '0'
af[fadeout]['Exp'] = CLI.prompt_time('Exponential Value').strip() or '0'

elif answer['audio_filters'] == 'Mute':
audio_filters['Mute']['Start Time'] = Interface.prompt_time('Start Time').strip() or '0'
audio_filters['Mute']['End Time'] = Interface.prompt_time('End Time').strip() or '0'
elif answer['af'] == mute:
af[mute]['Start Time'] = CLI.prompt_time('Start Time').strip() or '0'
af[mute]['End Time'] = CLI.prompt_time('End Time').strip() or '0'

elif answer['audio_filters'] == 'Custom':
custom_audio_filter = Interface.prompt_text('Custom Audio Filter(s)').strip()
audio_filters['Custom'] = custom_audio_filter
elif answer['af'] == custom:
custom_audio_filter = CLI.prompt_text('Custom Audio Filter(s)').strip()
af[custom] = custom_audio_filter

else:
audio_filters['Exit'] = True
af['Exit'] = True

audio_filters_list = []
af_list = []

if float(audio_filters['Fade In']) > 0:
audio_filters_list.append(f"afade=d={audio_filters['Fade In']}:curve=exp")
if float(audio_filters['Fade Out']['Exp']) > 0:
audio_filters_list.append(f"afade=t=out:st={audio_filters['Fade Out']['Start Time']}:d={audio_filters['Fade Out']['Exp']}")
if float(audio_filters['Mute']['Start Time']) > 0 or float(audio_filters['Mute']['End Time']) > 0:
audio_filters_list.append(f"volume=enable='between(t,{audio_filters['Mute']['Start Time']},{audio_filters['Mute']['End Time']})':volume=0")
if len(audio_filters['Custom']) > 0:
audio_filters_list.append(audio_filters['Custom'])
if float(af[fadein]) > 0:
af_list.append(AudioFilter.FADE_IN.toText(af[fadein]))
if float(af[fadeout]['Exp']) > 0:
af_list.append(AudioFilter.FADE_OUT.toText(af[fadeout]['Start Time'], af[fadein]['Exp']))
if float(af[mute]['Start Time']) > 0 or float(af[mute]['End Time']) > 0:
af_list.append(AudioFilter.MUTE.toText(af[mute]['Start Time'], af[mute]['End Time']))
if len(af[custom]) > 0:
af_list.append(AudioFilter.CUSTOM.toText(af[custom]))

logging.debug(
f'[Interface.audio_filters_options] '
f'audio_filters["Fade In"]: \'{audio_filters["Fade In"]}\', '
f'audio_filters["Fade Out"]["Start Time"]: \'{audio_filters["Fade Out"]["Start Time"]}\', '
f'audio_filters["Fade Out"]["Exp"]: \'{audio_filters["Fade Out"]["Exp"]}\', '
f'audio_filters["Mute"]["Start Time"]: \'{audio_filters["Mute"]["Start Time"]}\', '
f'audio_filters["Mute"]["End Time"]: \'{audio_filters["Mute"]["End Time"]}\', '
f'audio_filters["Custom"]: \'{audio_filters["Custom"]}\''
f'[CLI.af_options] '
f'af[{fadein}]: \'{af[fadein]}\', '
f'af[{fadeout}]["Start Time"]: \'{af[fadeout]["Start Time"]}\', '
f'af[{fadeout}]["Exp"]: \'{af[fadeout]["Exp"]}\', '
f'af[{mute}]["Start Time"]: \'{af[mute]["Start Time"]}\', '
f'af[{mute}]["End Time"]: \'{af[mute]["End Time"]}\', '
f'af[{custom}]: \'{af[custom]}\''
)

return ','.join(audio_filters_list)
return ','.join(af_list)

# Prompt the user for our list of video filters
def video_filters(encoding_config):
video_filters_options = {
'No Filters': None,
'scale=-1:720': '720p',
'scale=-1:720,hqdn3d=0:0:3:3,gradfun,unsharp': 'filtered-720p',
'hqdn3d=0:0:3:3,gradfun,unsharp': 'filtered',
'hqdn3d=0:0:3:3': 'lightdenoise',
'hqdn3d=1.5:1.5:6:6': 'heavydenoise',
'unsharp': 'unsharp',
'Custom': 'custom'
}
video_filters_options = VideoFilter.get_obj()

if encoding_config.include_unfiltered:
encoding_config.video_filters.append((None, 'No Filters'))
Expand All @@ -152,15 +141,16 @@ def video_filters(encoding_config):
return encoding_config

tp_list = [(name, filter_string) for filter_string, name in video_filters_options.items() if filter_string in answer['video_filters']]
custom = VideoFilter.CUSTOM

if 'Custom' in answer['video_filters']:
tp_list.remove(('custom', 'Custom'))
custom_video_filters = Interface.prompt_text('Custom Video Filters (Separate with ",," if more than one)').split(',,')
tp_list.extend([(f'custom{i + 1}', value) for i, value in enumerate(custom_video_filters)])
if VideoFilter.CUSTOM._value_[1] in answer['video_filters']:
tp_list.remove((custom._value_[0], custom._value_[1]))
custom_video_filters = CLI.prompt_text('Custom Video Filters (Separate with ",," if more than one)').split(',,')
tp_list.extend([(f'{custom._value_[0]}{i + 1}', value) for i, value in enumerate(custom_video_filters)])

encoding_config.video_filters = tp_list

logging.debug(f'[Interface.video_filters] tp_list: \'{tp_list}\'')
logging.debug(f'[CLI.video_filters] tp_list: \'{tp_list}\'')

return encoding_config

Expand All @@ -176,7 +166,7 @@ def custom_options(encoding_config):
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),
inquirer.Text('encoding_modes', message='Encoding Modes', default=','.join(encoding_modes), validate=CLI.validate_encoding_modes),
])

if answer is None:
Expand All @@ -185,10 +175,10 @@ def custom_options(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))
encoding_mode_questions.append(inquirer.Text('crfs', message='CRFs', default=','.join(crfs), validate=CLI.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))
encoding_mode_questions.append(inquirer.Text('cbr_bitrates', message='Bit Rates', default=','.join(cbr_bitrates), validate=CLI.validate_digits))
encoding_mode_questions.append(inquirer.Text('cbr_max_bitrates', message='Max Bit Rates', default=','.join(cbr_max_bitrates), validate=CLI.validate_digits))

answer_em = inquirer.prompt(encoding_mode_questions)

Expand All @@ -203,7 +193,7 @@ def custom_options(encoding_config):
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'[CLI.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}\', '
Expand Down
8 changes: 4 additions & 4 deletions batch_encoder/_seek_collector.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ._interface import Interface
from ._cli import CLI
from ._seek import Seek
from ._utils import string_to_seconds

Expand Down Expand Up @@ -43,7 +43,7 @@ def __init__(self, source_file):
# For ending positions, a blank input value is the end position of the source file
@staticmethod
def prompt_time(prompt_text):
positions = Interface.prompt_time(prompt_text, validate=SeekCollector.validate_seek).split(',')
positions = CLI.prompt_time(prompt_text, validate=SeekCollector.validate_seek).split(',')
if prompt_text == 'Start time(s)':
SeekCollector.start_positions = len(positions)

Expand All @@ -52,7 +52,7 @@ def prompt_time(prompt_text):
# Prompt the user for our list of name for our passlog/WebMs
@staticmethod
def prompt_output_name():
filenames = Interface.prompt_text(message='Output file name(s)', validate=SeekCollector.validate_output_name).split(',')
filenames = CLI.prompt_text(message='Output file name(s)', validate=SeekCollector.validate_output_name).split(',')
SeekCollector.all_output_names.extend(filenames)
SeekCollector.start_positions = 0

Expand All @@ -63,7 +63,7 @@ def prompt_output_name():
def prompt_new_audio_filters(self):
new_audio_filters = []
for output_name in self.output_names:
new_audio_filters.append(Interface.audio_filters_options(output_name))
new_audio_filters.append(CLI.audio_filters_options(output_name))

return new_audio_filters

Expand Down
32 changes: 32 additions & 0 deletions batch_encoder/_video_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import enum


# The Video Filter Enumerated List
# Set video filters
class VideoFilter(enum.Enum):
def __new__(cls, value, name):
obj = object.__new__(cls)
return obj

NO_FILTERS = ('No Filters', None)
R720P = ('scale=-1:720', '720p')
FILTERED_720P = ('scale=-1:720,hqdn3d=0:0:3:3,gradfun,unsharp', 'filtered-720p')
FILTERED = ('hqdn3d=0:0:3:3,gradfun,unsharp', 'filtered')
LIGHTDENOISE = ('hqdn3d=0:0:3:3', 'lightdenoise')
HEAVYDENOISE = ('hqdn3d=1.5:1.5:6:6', 'heavydenoise')
UNSHARP = ('unsharp', 'unsharp')
CUSTOM = ('custom', 'Custom')

# Get the object to prompt to the user
@classmethod
def get_obj(self):
return {
self.NO_FILTERS._value_[0]: self.NO_FILTERS._value_[1],
self.R720P._value_[0]: self.R720P._value_[1],
self.FILTERED_720P._value_[0]: self.FILTERED_720P._value_[1],
self.FILTERED._value_[0]: self.FILTERED._value_[1],
self.LIGHTDENOISE._value_[0]: self.LIGHTDENOISE._value_[1],
self.HEAVYDENOISE._value_[0]: self.HEAVYDENOISE._value_[1],
self.UNSHARP._value_[0]: self.UNSHARP._value_[1],
self.CUSTOM._value_[0]: self.CUSTOM._value_[1]
}
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.2',
version='2.2.1',
author='AnimeThemes',
author_email='admin@animethemes.moe',
url='https://github.com/AnimeThemes/animethemes-batch-encoder',
Expand Down

0 comments on commit f7ce09a

Please sign in to comment.