from ._audio_filter import AudioFilter from ._bitrate_mode import BitrateMode from ._typing import Args, EncodingConfigType from ._video_filter import VideoFilter from typing import Literal import inquirer import logging import re import sys # 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(r"^([0-5]?\d:){1,2}[0-5]?\d(?=\.\d+$|$)|\d+(?=\.\d+$|$)") # Validations 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 ) # Prompt the user for text questions def prompt_text(message, validate=lambda _, x: x) -> str: answer = inquirer.prompt( [inquirer.Text("text", message=message, validate=validate)] ) if answer is None: return "NoName" logging.debug(f"[CLI.prompt_text] answer[\"text\"]: '{answer['text']}'") return answer["text"] # Prompt the user for time questions def prompt_time(message, validate=validate_time) -> str: answer = inquirer.prompt( [inquirer.Text("time", message=message, validate=validate)] ) if answer is None: return "" logging.debug(f"[CLI.prompt_time] answer[\"time\"]: '{answer['time']}'") return answer["time"] # Prompt the user for our mode options to run to the user def choose_mode(args: Args) -> Literal[1, 2, 3]: modes = [ ("Generate commands", 1), ("Execute commands", 2), ("Generate and execute commands", 3), ] if args.generate and args.execute: return 3 elif args.generate: return 1 elif args.execute: return 2 else: answer = inquirer.prompt( [inquirer.List("mode", message="Mode (Enter)", choices=modes)] ) if answer is None: sys.exit() logging.debug(f"[CLI.choose_mode] answer[\"mode\"]: '{answer['mode']}'") return answer["mode"] # Prompt the user for source files to choose def choose_source_files(source_files) -> list[str]: answer = inquirer.prompt( [ inquirer.Checkbox( "source_files", message="Source Files (Space to select)", choices=source_files, ) ] ) if answer is None: sys.exit() if len(answer["source_files"]) == 0: logging.error("Select at least one file") sys.exit() 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) -> str: 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( "af", message="Audio Filters (Enter)", choices=list(af.keys()) ) ] ) if answer is None: af["Exit"] = True elif answer["af"] == fadein: af[fadein] = CLI.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["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["af"] == custom: custom_audio_filter = CLI.prompt_text("Custom Audio Filter(s)").strip() af[custom] = custom_audio_filter else: af["Exit"] = True af_list = [] 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[fadeout]["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"[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(af_list) # Prompt the user for our list of video filters def video_filters(encoding_config: EncodingConfigType) -> EncodingConfigType: video_filters_options = VideoFilter.get_obj() if encoding_config.include_unfiltered: encoding_config.video_filters.append((None, "No Filters")) 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 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 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"[CLI.video_filters] tp_list: '{tp_list}'") return encoding_config # Prompt the user for custom options if requested def custom_options(encoding_config: EncodingConfigType) -> EncodingConfigType: create_preview = encoding_config.create_preview limit_size_enable = encoding_config.limit_size_enable encoding_modes = encoding_config.encoding_modes crfs = encoding_config.crfs cbr_bitrates = encoding_config.cbr_bitrates cbr_max_bitrates = encoding_config.cbr_max_bitrates 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=CLI.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=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=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) encoding_config.create_preview = answer["create_preview"] encoding_config.limit_size_enable = answer["limit_size_enable"] 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"[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}', " f"encoding_config.crfs: '{encoding_config.crfs}', " f"encoding_config.cbr_bitrates: '{encoding_config.cbr_bitrates}', " f"encoding_config.cbr_max_bitrates: '{encoding_config.cbr_max_bitrates}'" ) return encoding_config