summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xtranscode.sh731
1 files changed, 402 insertions, 329 deletions
diff --git a/transcode.sh b/transcode.sh
index 1283ca3..33794bf 100755
--- a/transcode.sh
+++ b/transcode.sh
@@ -59,6 +59,24 @@
# Dennis Fink <me+coding@dennisfink.me>
###############################################################################
+###############################################################################
+# FILE STRUCTURE
+#
+# 1. Header, metadata, globals and exit codes
+# 2. Function definitions
+# - Output helpers
+# - Help/version output
+# - Small utility functions
+# - Terminal color handling
+# - Configuration handling
+# - Codec skip-list handling
+# - Preset handling
+# - Cleanup handling
+# - Size report handling
+# - Encoding
+# 3. Main execution
+###############################################################################
+
# TRACE enables bash execution tracing and is intended for debugging control
# flow / expansions. DEBUG enable structured debug messages (see debug()) and
# is intended for high-level state reporting.
@@ -107,6 +125,7 @@ set -o pipefail
# for informational messages. They are marked readonly to prevent accidental
# modification at runtime.
###############################################################################
+
readonly SCRIPTNAME=${0##*/}
readonly DESCRIPTION="Batch transcode helper for media files using ffmpeg."
readonly DATE_OF_CREATION=2025-08-06
@@ -189,12 +208,8 @@ EXIT_CONFIG_ERROR=3
EXIT_MISSING_DEPENDENCY=127
readonly EXIT_OK EXIT_RUNTIME_FAILURE EXIT_USAGE_ERROR EXIT_CONFIG_ERROR EXIT_MISSING_DEPENDENCY
-##############################################################################
-# FUNCTIONS
-#
-# This section defines the helper functions used throughout the script. They
-# handle formatted output, error reporting, version information, utilities
-# functions and runtime behaviour such as displaying usage instructions.
+###############################################################################
+# OUTPUT HELPERS
###############################################################################
emit() {
@@ -251,6 +266,10 @@ debug() {
fi
}
+###############################################################################
+# HELP AND VERSION OUTPUT
+###############################################################################
+
print_help() {
printf "${BOLD}${MAGENTA}%s${ALL_OFF} - ${BOLD}${CYAN}%s${ALL_OFF} - ${BOLD}%s${ALL_OFF}
@@ -362,6 +381,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" \
"$DATE_OF_REVISION" "$LICENSE"
}
+###############################################################################
+# SMALL UTILITY FUNCTIONS
+###############################################################################
+
# Return the size of a file in bytes.
# Uses GNU stat -c on Linux with a fallback to BSD/macOS stat -f.
filesize() {
@@ -381,35 +404,17 @@ format_filesize() {
}
###############################################################################
-# TERMINAL COLOR SETUP
+# TERMINAL COLOR HANDLING
#
# Colors and style escape sequences are configured dynamically through the
-# `setup_colors` function, which enables or disables color output based on the
-# value of ENABLE_COLOR. When enabled, terminal capabilities are detected via
-# `tput`, falling back to ANSI escapes if unavailable.
+# setup_colors function. Runtime color detection happens in MAIN EXECUTION so
+# all function definitions stay together before the executable flow.
#
# This script respects the NO_COLOR standard (https://no-color.org/). If the
# environment variable NO_COLOR is set (to any value), all color output is
# disabled. If FORCE_COLOR is set, colors are always enabled.
###############################################################################
-# NOTE: This block calls debug() and reads $DEBUG. Both must be defined before
-# this point. Do not move this block above the GLOBAL VARIABLES or FUNCTIONS
-# sections without adjusting those dependencies.
-if [[ -n "${NO_COLOR:-}" ]]; then
- debug "Colors are disabled via NO_COLOR"
- ENABLE_COLOR=0
-elif [[ -n "${FORCE_COLOR:-}" ]]; then
- debug "Colors are forced via FORCE_COLOR"
- ENABLE_COLOR=1
-elif [[ -t 1 ]]; then
- debug "Colors are enabled via interactive terminal"
- ENABLE_COLOR=1
-else
- debug "Colors are disabled"
- ENABLE_COLOR=0
-fi
-
setup_colors() {
if [[ "$ENABLE_COLOR" -eq 1 ]]; then
if tput setaf 0 >/dev/null 2>&1; then
@@ -446,14 +451,9 @@ setup_colors() {
CYAN=""
fi
}
-setup_colors
###############################################################################
-# MAIN EXECUTION
-###############################################################################
-
-###############################################################################
-# CONFIGURATION FILE
+# CONFIGURATION HANDLING
#
# Load defaults from config.toml if it exists. Values are only applied when the
# corresponding CLI flag has NOT already been set (CLI takes precedence).
@@ -635,11 +635,14 @@ load_config() {
fi
}
+###############################################################################
+# CODEC SKIP-LIST HANDLING
+###############################################################################
+
# Add a codec name to the SKIP_CODECS associative array.
# - Normalizes to lowercase
# - Ignores empty tokens
# - Used both for CLI --skip-codec and for codecs defined
-# in the config file
add_skip_codec_token() {
local token="${1,,}" # lowercase
if [[ ! -v SKIP_CODECS["$token"] ]]; then
@@ -662,6 +665,10 @@ parse_skip_codec_parameter() {
done
}
+###############################################################################
+# PRESET HANDLING
+###############################################################################
+
# Load ffmpeg argument preset by name.
#
# Security:
@@ -792,300 +799,9 @@ list_presets() {
done
}
-if [[ $# -eq 0 ]]; then
- debug "No parameters were specified"
- printf "${BOLD}${BLUE}Usage:${ALL_OFF} ${BOLD}${MAGENTA}%s${ALL_OFF} ${BOLD}${YELLOW}[OPTION] [--]${ALL_OFF} ${BOLD}${GREEN}FILE...${ALL_OFF}
-Run ${BOLD}${MAGENTA}%s${ALL_OFF} ${BOLD}${YELLOW}--help${ALL_OFF} for full usage information.\n" "$SCRIPTNAME" "$SCRIPTNAME" >&2
- exit $EXIT_USAGE_ERROR
-fi
-
-while [[ $# -gt 0 ]]; do
- case "$1" in
- -h | --help | -\?)
- print_help
- exit $EXIT_OK
- ;;
- --version)
- print_version
- exit $EXIT_OK
- ;;
- --list-presets)
- list_presets
- exit $EXIT_OK
- ;;
- -q | --quiet)
- QUIET=1
- _CLI_QUIET=1
- shift
- ;;
- -v | --verbose)
- VERBOSE=1
- _CLI_VERBOSE=1
- shift
- ;;
- --color)
- ENABLE_COLOR=1
- setup_colors
- shift
- ;;
- --no-color)
- ENABLE_COLOR=0
- setup_colors
- shift
- ;;
- -c | --continue)
- CONTINUE_ON_FAIL=1
- _CLI_CONTINUE=1
- shift
- ;;
- --no-continue)
- CONTINUE_ON_FAIL=0
- _CLI_CONTINUE=1
- shift
- ;;
- --verify-output)
- VERIFY_OUTPUT=1
- _CLI_VERIFY_OUTPUT=1
- shift
- ;;
- --no-verify-output)
- VERIFY_OUTPUT=0
- _CLI_VERIFY_OUTPUT=1
- shift
- ;;
- --config-file)
- if [[ $# -lt 2 ]]; then
- error "Missing value for $1"
- exit $EXIT_USAGE_ERROR
- fi
- if [[ ! -n "$2" ]]; then
- error "Value for $1 must not be empty"
- exit $EXIT_USAGE_ERROR
- fi
- CONFIG_FILE="$2"
- _CLI_CONFIG_FILE=1
- shift 2
- ;;
- --config-file=*)
- if [[ ! -n "${1#*=}" ]]; then
- error "Value for --config-file must not be empty"
- exit $EXIT_USAGE_ERROR
- fi
- CONFIG_FILE="${1#*=}"
- _CLI_CONFIG_FILE=1
- shift
- ;;
- --no-config)
- CONFIG_FILE="/dev/null"
- _CLI_CONFIG_FILE=1
- shift
- ;;
- -f | --encode-file)
- if [[ $# -lt 2 ]]; then
- error "Missing value for $1"
- exit $EXIT_USAGE_ERROR
- fi
- if [[ ! -n "$2" ]]; then
- error "Value for $1 must not be empty"
- exit $EXIT_USAGE_ERROR
- fi
- ENCODE_FILE="$2"
- shift 2
- ;;
- --encode-file=*)
- if [[ ! -n "${1#*=}" ]]; then
- error "Value for --encode-file must not be empty"
- exit $EXIT_USAGE_ERROR
- fi
- ENCODE_FILE="${1#*=}"
- shift
- ;;
- -n | --dry-run)
- DRY_RUN=1
- shift
- ;;
- -N | --nice)
- if [[ $# -lt 2 ]]; then
- error "Missing value for $1"
- exit $EXIT_USAGE_ERROR
- fi
- NICE_VALUE="$2"
- _CLI_NICE=1
- shift 2
- ;;
- --nice=*)
- NICE_VALUE="${1#*=}"
- _CLI_NICE=1
- shift
- ;;
- -s | --size-report)
- SIZE_REPORT=1
- _CLI_SIZE_REPORT=1
- shift
- ;;
- --no-size-report)
- SIZE_REPORT=0
- _CLI_SIZE_REPORT=1
- shift
- ;;
- --size-report-file)
- if [[ $# -lt 2 ]]; then
- error "Missing value for $1"
- exit $EXIT_USAGE_ERROR
- fi
- if [[ ! -n "$2" ]]; then
- error "Value for $1 must not be empty"
- exit $EXIT_USAGE_ERROR
- fi
- SIZE_REPORT_FILE="$2"
- _CLI_SIZE_REPORT_FILE=1
- shift 2
- ;;
- --size-report-file=*)
- if [[ ! -n "${1#*=}" ]]; then
- error "Value for --size-report-file must not be empty"
- exit $EXIT_USAGE_ERROR
- fi
- SIZE_REPORT_FILE="${1#*=}"
- _CLI_SIZE_REPORT_FILE=1
- shift
- ;;
- -S | --skip-codec)
- if [[ $# -lt 2 ]]; then
- error "Missing value for $1"
- exit $EXIT_USAGE_ERROR
- fi
- parse_skip_codec_parameter "$2"
- shift 2
- ;;
- --skip-codec=*)
- parse_skip_codec_parameter "${1#*=}"
- shift
- ;;
- -l | --only-if-smaller)
- ONLY_IF_SMALLER=1
- _CLI_ONLY_IF_SMALLER=1
- shift
- ;;
- --no-only-if-smaller)
- ONLY_IF_SMALLER=0
- _CLI_ONLY_IF_SMALLER=1
- shift
- ;;
- --hwaccel)
- HWACCEL=1
- # Peek at the next argument: use it as the METHOD only if it is
- # non-empty and does not look like an option flag.
- if [[ $# -ge 2 && -n "$2" && "$2" != -* ]]; then
- HWACCEL_VALUE="$2"
- shift 2
- else
- HWACCEL_VALUE="auto"
- shift
- fi
- _CLI_HWACCEL=1
- ;;
- --hwaccel=*)
- HWACCEL=1
- HWACCEL_VALUE="${1#*=}"
- if [[ ! -n "$HWACCEL_VALUE" ]]; then
- error "Value for --hwaccel must not be empty"
- exit $EXIT_USAGE_ERROR
- fi
- _CLI_HWACCEL=1
- shift
- ;;
- --no-hwaccel)
- HWACCEL=0
- _CLI_HWACCEL=1
- shift
- ;;
- -p | --preset)
- if [[ $# -lt 2 ]]; then
- error "Missing value for $1"
- exit $EXIT_USAGE_ERROR
- fi
- if [[ ! -n "$2" ]]; then
- error "Value for $1 must not be empty"
- exit $EXIT_USAGE_ERROR
- fi
- PRESET_NAME="$2"
- _CLI_PRESET=1
- shift 2
- ;;
- --preset=*)
- if [[ ! -n "${1#*=}" ]]; then
- error "Value for --preset must not be empty"
- exit $EXIT_USAGE_ERROR
- fi
- PRESET_NAME="${1#*=}"
- _CLI_PRESET=1
- shift
- ;;
- --)
- shift
- REMAINING_ARGS+=("$@")
- break
- ;;
- -*)
- error "Unknown option: $1"
- exit $EXIT_USAGE_ERROR
- ;;
- *)
- REMAINING_ARGS+=("$1")
- shift
- ;;
- esac
-done
-
-# Apply config file defaults. Must run after CLI parsing so _CLI_* sentinels
-# are set, but before validation so config-supplied values are validated too.
-load_config
-
-printf -v joined ' %s' "${!SKIP_CODECS[@]}"
-
-debug "Effective configuration:"
-debug " config-file:" "$CONFIG_FILE"
-debug " dry-run:" "$DRY_RUN"
-debug " encode-file:" "$ENCODE_FILE"
-debug " hwaccel:" "$HWACCEL"
-debug " hwaccel_value:" "$HWACCEL_VALUE"
-debug " nice:" "$NICE_VALUE"
-debug " only-if-smaller:" "$ONLY_IF_SMALLER"
-debug " preset:" "$PRESET_NAME"
-debug " quiet:" "$QUIET"
-debug " size-report-file:" "$SIZE_REPORT_FILE"
-debug " size-report:" "$SIZE_REPORT"
-debug " skip-codecs:" "${joined# }"
-debug " verbose:" "$VERBOSE"
-debug " verify-output:" "$VERIFY_OUTPUT"
-
-command -v ffmpeg >/dev/null 2>&1 || {
- error "ffmpeg not found."
- exit $EXIT_MISSING_DEPENDENCY
-}
-command -v ffprobe >/dev/null 2>&1 || {
- error "ffprobe not found."
- exit $EXIT_MISSING_DEPENDENCY
-}
-command -v nice >/dev/null 2>&1 || {
- error "nice not found."
- exit $EXIT_MISSING_DEPENDENCY
-}
-
-if [[ $_CLI_SIZE_REPORT_FILE -eq 1 && $SIZE_REPORT -eq 0 ]]; then
- warn "--size-report-file has no effect without --size-report"
-fi
-
-if [[ ! "$NICE_VALUE" =~ ^-?[0-9]+$ || $NICE_VALUE -lt -20 || $NICE_VALUE -gt 19 ]]; then
- error "Invalid nice value (must be an integer between -20 and 19):" "$NICE_VALUE"
- exit $EXIT_USAGE_ERROR
-fi
-
-if [[ ! -z "${joined# }" ]]; then
- verbose "Skipping files with codecs:" "${joined# }"
-fi
-unset joined
+###############################################################################
+# CLEANUP HANDLING
+###############################################################################
# Remove temporary output files created during encoding.
# Registered via trap EXIT so it runs on normal exit and on failures
@@ -1100,7 +816,10 @@ cleanup() {
fi
done
}
-trap cleanup EXIT
+
+###############################################################################
+# SIZE REPORT HANDLING
+###############################################################################
# Calculate integer percentage saved from original/new byte counts.
calculate_saved_percentage() {
@@ -1187,6 +906,10 @@ print_size_report_summary() {
printf "${BOLD}${BLUE}Average saved:${ALL_OFF} %s%%\n" "$average_saved_pct"
}
+###############################################################################
+# ENCODING
+###############################################################################
+
# Encode a single media file according to PRESET_NAME.
#
# Arguments:
@@ -1210,6 +933,10 @@ encode_one() {
local current_file_index="$2"
local amount_total_files="$3"
+ # ---------------------------------------------------------------------------
+ # Validate input file
+ # ---------------------------------------------------------------------------
+
# Ignore empty input entries so callers can safely pass filtered file lists
# without treating blank lines as errors.
if [[ -z "$input_file" ]]; then
@@ -1242,6 +969,10 @@ encode_one() {
return $EXIT_RUNTIME_FAILURE
fi
+ # ---------------------------------------------------------------------------
+ # Probe input stream metadata
+ # ---------------------------------------------------------------------------
+
# Probe codec, pixel format and frame rate in a single ffprobe invocation to
# decrease the startup overhead on large batches.
local probe_out
@@ -1286,6 +1017,10 @@ encode_one() {
return $EXIT_RUNTIME_FAILURE
fi
+ # ---------------------------------------------------------------------------
+ # Derive output pixel format and GOP size
+ # ---------------------------------------------------------------------------
+
# Some files report "yuvj*" formats (full-range JPEG-style). For encoding
# purposes we normalize to the equivalent "yuv*" formats.
input_pixel_format="${input_pixel_format/yuvj/yuv}"
@@ -1342,6 +1077,10 @@ encode_one() {
load_preset "$PRESET_NAME"
+ # ---------------------------------------------------------------------------
+ # Build temporary output path and ffmpeg command
+ # ---------------------------------------------------------------------------
+
local extension directory temporary_output_file base stem
base="${input_file##*/}"
@@ -1389,6 +1128,10 @@ encode_one() {
"${ffargs[@]}"
"file:$temporary_output_file")
+ # ---------------------------------------------------------------------------
+ # Dry-run output
+ # ---------------------------------------------------------------------------
+
if [[ $DRY_RUN -eq 1 ]]; then
msg "DRY RUN:" "Would encode with preset $PRESET_NAME"
verbose "Input codec:" "$input_codec"
@@ -1401,6 +1144,10 @@ encode_one() {
return $EXIT_OK
fi
+ # ---------------------------------------------------------------------------
+ # Run encode, verify output, and replace original
+ # ---------------------------------------------------------------------------
+
msg "Encoding:" "$input_file"
if nice -n "$NICE_VALUE" "${encode_cmd[@]}"; then
@@ -1467,6 +1214,332 @@ encode_one() {
fi
}
+###############################################################################
+# MAIN EXECUTION
+###############################################################################
+
+# Configure terminal colors before any user-facing output.
+#
+# NOTE: This block calls debug() and reads $DEBUG. Both must be defined before
+# this point. Do not move this block above the GLOBAL VARIABLES or FUNCTION
+# DEFINITIONS sections without adjusting those dependencies.
+if [[ -n "${NO_COLOR:-}" ]]; then
+ debug "Colors are disabled via NO_COLOR"
+ ENABLE_COLOR=0
+elif [[ -n "${FORCE_COLOR:-}" ]]; then
+ debug "Colors are forced via FORCE_COLOR"
+ ENABLE_COLOR=1
+elif [[ -t 1 ]]; then
+ debug "Colors are enabled via interactive terminal"
+ ENABLE_COLOR=1
+else
+ debug "Colors are disabled"
+ ENABLE_COLOR=0
+fi
+setup_colors
+
+# Reject calls without any positional arguments or options before doing more
+# setup work.
+if [[ $# -eq 0 ]]; then
+ debug "No parameters were specified"
+ printf "${BOLD}${BLUE}Usage:${ALL_OFF} ${BOLD}${MAGENTA}%s${ALL_OFF} ${BOLD}${YELLOW}[OPTION] [--]${ALL_OFF} ${BOLD}${GREEN}FILE...${ALL_OFF}
+Run ${BOLD}${MAGENTA}%s${ALL_OFF} ${BOLD}${YELLOW}--help${ALL_OFF} for full usage information.\n" "$SCRIPTNAME" "$SCRIPTNAME" >&2
+ exit $EXIT_USAGE_ERROR
+fi
+
+# Parse command-line options. CLI flags are recorded in _CLI_* sentinel
+# variables so load_config() can respect CLI-over-config precedence.
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -h | --help | -\?)
+ print_help
+ exit $EXIT_OK
+ ;;
+ --version)
+ print_version
+ exit $EXIT_OK
+ ;;
+ --list-presets)
+ list_presets
+ exit $EXIT_OK
+ ;;
+ -q | --quiet)
+ QUIET=1
+ _CLI_QUIET=1
+ shift
+ ;;
+ -v | --verbose)
+ VERBOSE=1
+ _CLI_VERBOSE=1
+ shift
+ ;;
+ --color)
+ ENABLE_COLOR=1
+ setup_colors
+ shift
+ ;;
+ --no-color)
+ ENABLE_COLOR=0
+ setup_colors
+ shift
+ ;;
+ -c | --continue)
+ CONTINUE_ON_FAIL=1
+ _CLI_CONTINUE=1
+ shift
+ ;;
+ --no-continue)
+ CONTINUE_ON_FAIL=0
+ _CLI_CONTINUE=1
+ shift
+ ;;
+ --verify-output)
+ VERIFY_OUTPUT=1
+ _CLI_VERIFY_OUTPUT=1
+ shift
+ ;;
+ --no-verify-output)
+ VERIFY_OUTPUT=0
+ _CLI_VERIFY_OUTPUT=1
+ shift
+ ;;
+ --config-file)
+ if [[ $# -lt 2 ]]; then
+ error "Missing value for $1"
+ exit $EXIT_USAGE_ERROR
+ fi
+ if [[ ! -n "$2" ]]; then
+ error "Value for $1 must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ CONFIG_FILE="$2"
+ _CLI_CONFIG_FILE=1
+ shift 2
+ ;;
+ --config-file=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --config-file must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ CONFIG_FILE="${1#*=}"
+ _CLI_CONFIG_FILE=1
+ shift
+ ;;
+ --no-config)
+ CONFIG_FILE="/dev/null"
+ _CLI_CONFIG_FILE=1
+ shift
+ ;;
+ -f | --encode-file)
+ if [[ $# -lt 2 ]]; then
+ error "Missing value for $1"
+ exit $EXIT_USAGE_ERROR
+ fi
+ if [[ ! -n "$2" ]]; then
+ error "Value for $1 must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ ENCODE_FILE="$2"
+ shift 2
+ ;;
+ --encode-file=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --encode-file must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ ENCODE_FILE="${1#*=}"
+ shift
+ ;;
+ -n | --dry-run)
+ DRY_RUN=1
+ shift
+ ;;
+ -N | --nice)
+ if [[ $# -lt 2 ]]; then
+ error "Missing value for $1"
+ exit $EXIT_USAGE_ERROR
+ fi
+ NICE_VALUE="$2"
+ _CLI_NICE=1
+ shift 2
+ ;;
+ --nice=*)
+ NICE_VALUE="${1#*=}"
+ _CLI_NICE=1
+ shift
+ ;;
+ -s | --size-report)
+ SIZE_REPORT=1
+ _CLI_SIZE_REPORT=1
+ shift
+ ;;
+ --no-size-report)
+ SIZE_REPORT=0
+ _CLI_SIZE_REPORT=1
+ shift
+ ;;
+ --size-report-file)
+ if [[ $# -lt 2 ]]; then
+ error "Missing value for $1"
+ exit $EXIT_USAGE_ERROR
+ fi
+ if [[ ! -n "$2" ]]; then
+ error "Value for $1 must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ SIZE_REPORT_FILE="$2"
+ _CLI_SIZE_REPORT_FILE=1
+ shift 2
+ ;;
+ --size-report-file=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --size-report-file must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ SIZE_REPORT_FILE="${1#*=}"
+ _CLI_SIZE_REPORT_FILE=1
+ shift
+ ;;
+ -S | --skip-codec)
+ if [[ $# -lt 2 ]]; then
+ error "Missing value for $1"
+ exit $EXIT_USAGE_ERROR
+ fi
+ parse_skip_codec_parameter "$2"
+ shift 2
+ ;;
+ --skip-codec=*)
+ parse_skip_codec_parameter "${1#*=}"
+ shift
+ ;;
+ -l | --only-if-smaller)
+ ONLY_IF_SMALLER=1
+ _CLI_ONLY_IF_SMALLER=1
+ shift
+ ;;
+ --no-only-if-smaller)
+ ONLY_IF_SMALLER=0
+ _CLI_ONLY_IF_SMALLER=1
+ shift
+ ;;
+ --hwaccel)
+ HWACCEL=1
+ # Peek at the next argument: use it as the METHOD only if it is
+ # non-empty and does not look like an option flag.
+ if [[ $# -ge 2 && -n "$2" && "$2" != -* ]]; then
+ HWACCEL_VALUE="$2"
+ shift 2
+ else
+ HWACCEL_VALUE="auto"
+ shift
+ fi
+ _CLI_HWACCEL=1
+ ;;
+ --hwaccel=*)
+ HWACCEL=1
+ HWACCEL_VALUE="${1#*=}"
+ if [[ ! -n "$HWACCEL_VALUE" ]]; then
+ error "Value for --hwaccel must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ _CLI_HWACCEL=1
+ shift
+ ;;
+ --no-hwaccel)
+ HWACCEL=0
+ _CLI_HWACCEL=1
+ shift
+ ;;
+ -p | --preset)
+ if [[ $# -lt 2 ]]; then
+ error "Missing value for $1"
+ exit $EXIT_USAGE_ERROR
+ fi
+ if [[ ! -n "$2" ]]; then
+ error "Value for $1 must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ PRESET_NAME="$2"
+ _CLI_PRESET=1
+ shift 2
+ ;;
+ --preset=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --preset must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ PRESET_NAME="${1#*=}"
+ _CLI_PRESET=1
+ shift
+ ;;
+ --)
+ shift
+ REMAINING_ARGS+=("$@")
+ break
+ ;;
+ -*)
+ error "Unknown option: $1"
+ exit $EXIT_USAGE_ERROR
+ ;;
+ *)
+ REMAINING_ARGS+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# Apply config file defaults. Must run after CLI parsing so _CLI_* sentinels
+# are set, but before validation so config-supplied values are validated too.
+load_config
+
+printf -v joined ' %s' "${!SKIP_CODECS[@]}"
+
+debug "Effective configuration:"
+debug " config-file:" "$CONFIG_FILE"
+debug " dry-run:" "$DRY_RUN"
+debug " encode-file:" "$ENCODE_FILE"
+debug " hwaccel:" "$HWACCEL"
+debug " hwaccel_value:" "$HWACCEL_VALUE"
+debug " nice:" "$NICE_VALUE"
+debug " only-if-smaller:" "$ONLY_IF_SMALLER"
+debug " preset:" "$PRESET_NAME"
+debug " quiet:" "$QUIET"
+debug " size-report-file:" "$SIZE_REPORT_FILE"
+debug " size-report:" "$SIZE_REPORT"
+debug " skip-codecs:" "${joined# }"
+debug " verbose:" "$VERBOSE"
+debug " verify-output:" "$VERIFY_OUTPUT"
+
+command -v ffmpeg >/dev/null 2>&1 || {
+ error "ffmpeg not found."
+ exit $EXIT_MISSING_DEPENDENCY
+}
+command -v ffprobe >/dev/null 2>&1 || {
+ error "ffprobe not found."
+ exit $EXIT_MISSING_DEPENDENCY
+}
+command -v nice >/dev/null 2>&1 || {
+ error "nice not found."
+ exit $EXIT_MISSING_DEPENDENCY
+}
+
+if [[ $_CLI_SIZE_REPORT_FILE -eq 1 && $SIZE_REPORT -eq 0 ]]; then
+ warn "--size-report-file has no effect without --size-report"
+fi
+
+if [[ ! "$NICE_VALUE" =~ ^-?[0-9]+$ || $NICE_VALUE -lt -20 || $NICE_VALUE -gt 19 ]]; then
+ error "Invalid nice value (must be an integer between -20 and 19):" "$NICE_VALUE"
+ exit $EXIT_USAGE_ERROR
+fi
+
+if [[ ! -z "${joined# }" ]]; then
+ verbose "Skipping files with codecs:" "${joined# }"
+fi
+unset joined
+
+# Remove temporary output files on normal exit and on failures.
+trap cleanup EXIT
+
STATUS=$EXIT_OK
if [[ -n $ENCODE_FILE ]]; then
if [[ ! -f "$ENCODE_FILE" ]]; then