summaryrefslogtreecommitdiff
path: root/devscripts
diff options
context:
space:
mode:
Diffstat (limited to 'devscripts')
-rwxr-xr-xdevscripts/generate-testfile.sh697
1 files changed, 697 insertions, 0 deletions
diff --git a/devscripts/generate-testfile.sh b/devscripts/generate-testfile.sh
new file mode 100755
index 0000000..e8bcbe8
--- /dev/null
+++ b/devscripts/generate-testfile.sh
@@ -0,0 +1,697 @@
+#!/usr/bin/env bash
+
+# SPDX-FileCopyrightText: 2026 Dennis Fink <me+coding@dennisfink.me>
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+###############################################################################
+# DESCRIPTION
+# Test file generator for transcode.sh.
+#
+# This developer script creates small synthetic video files with ffmpeg. It
+# uses a lavfi source, such as smptebars, so test inputs can be generated
+# without keeping binary media files in the repository. Codec, resolution,
+# duration, frame rate, source filter, and output path can be selected from
+# the command line.
+#
+# NOTES
+# This script respects the NO_COLOR standard (https://no-color.org/).
+#
+# AUTHOR
+# Dennis Fink <me+coding@dennisfink.me>
+###############################################################################
+
+# TRACE enables bash execution tracing and is intended for debugging control
+# flow / expansions. DEBUG enables structured debug messages (see debug()) and
+# is intended for high-level state reporting.
+#
+# Recommended usage:
+# TRACE=1 devscripts/generate-testfile.sh ... # show every command bash executes
+# DEBUG=1 devscripts/generate-testfile.sh ... # show script-defined debug messages
+
+TRACE=${TRACE:-0}
+if [[ $TRACE -eq 1 ]]; then
+ set -o xtrace
+fi
+
+###############################################################################
+# SHELL SCRIPT HARDENING
+#
+# This script enables strict and predictable behavior for improved safety,
+# security, and debuggability. These measures help avoid common pitfalls
+# such as accidental globbing, unexpected alias expansion, silent failures,
+# or unintended word splitting.
+###############################################################################
+
+# Unalias everything to avoid unexpected alias expansion
+command unalias -a
+
+# Clear the shell command hash table to avoid stale command lookups
+hash -r
+
+# Set a safe IFS to prevent word-splitting vulnerabilities
+IFS=$'\n\t'
+
+# Set a secure default file creation mask (controls default permissions)
+umask 002
+
+# Ensure ERR traps are inherited by functions and subshells
+set -o errtrace
+# Treat use of undefined variables as an error and exit
+set -o nounset
+# Make pipelines fail if any command in the pipeline fails
+set -o pipefail
+
+###############################################################################
+# SCRIPT METADATA
+#
+# These variables describe the script and are used for the --version output
+# and for informational messages. They are marked readonly to prevent
+# accidental modification at runtime.
+###############################################################################
+
+readonly SCRIPTNAME=${0##*/}
+readonly DESCRIPTION="Test file generator for transcode.sh."
+readonly DATE_OF_CREATION=2026-05-16
+readonly DATE_OF_REVISION=2026-05-16
+readonly VERSION=0.0.1
+readonly AUTHOR="Dennis Fink <me+coding@dennisfink.me>"
+readonly LICENSE="BSD-3-Clause"
+
+###############################################################################
+# GLOBAL VARIABLES
+###############################################################################
+
+ALL_OFF=""
+BOLD=""
+RED=""
+GREEN=""
+YELLOW=""
+BLUE=""
+MAGENTA=""
+CYAN=""
+
+DEBUG=${DEBUG:-0}
+DRY_RUN=0
+QUIET=0
+VERBOSE=0
+OVERWRITE=0
+
+CODEC="libx264"
+RESOLUTION="1280x720"
+LENGTH="5"
+FRAME_RATE="30"
+FILTER="smptebars"
+PIX_FMT="yuv420p"
+FFMPEG_LOGLEVEL="warning"
+OUTPUT=""
+
+FFMPEG_COMMAND=()
+
+readonly OUTPUT_DIR="testfiles"
+
+###############################################################################
+# EXIT CODES
+###############################################################################
+
+EXIT_OK=0
+EXIT_RUNTIME_FAILURE=1
+EXIT_USAGE_ERROR=2
+EXIT_CONFIG_ERROR=3
+EXIT_MISSING_DEPENDENCY=127
+readonly EXIT_OK EXIT_RUNTIME_FAILURE EXIT_USAGE_ERROR EXIT_CONFIG_ERROR EXIT_MISSING_DEPENDENCY
+
+###############################################################################
+# OUTPUT HELPERS
+###############################################################################
+
+emit() {
+ local prefix="$1"
+ local color="$2"
+ shift 2
+
+ if [[ $# -eq 0 ]]; then
+ printf "%b%s%b\n" "${BOLD}${color}" "$prefix" "$ALL_OFF"
+ return
+ fi
+
+ local mesg="$1"
+ shift
+
+ local rest=""
+ if [[ $# -gt 0 ]]; then
+ printf -v rest ' %s' "$@"
+ rest="${rest# }"
+ fi
+
+ printf "%b%s%b %b%s%b" "${BOLD}${color}" "$prefix" "$ALL_OFF" "$BOLD" "$mesg" "$ALL_OFF"
+ if [[ -n "$rest" ]]; then
+ printf " %s" "$rest"
+ fi
+ printf "\n"
+}
+
+error() {
+ emit "==> ERROR:" "$RED" "$@" >&2
+}
+
+msg() {
+ if [[ $QUIET -eq 0 ]]; then
+ emit "==>" "$GREEN" "$@"
+ fi
+}
+
+warn() {
+ if [[ $QUIET -eq 0 ]]; then
+ emit "==>" "$YELLOW" "$@"
+ fi
+}
+
+verbose() {
+ if [[ $VERBOSE -eq 1 && $QUIET -eq 0 ]]; then
+ emit "==>" "$BLUE" "$@"
+ fi
+}
+
+debug() {
+ if [[ $DEBUG -eq 1 ]]; then
+ emit "==> DEBUG:" "$MAGENTA" "$@"
+ fi
+}
+
+###############################################################################
+# HELP AND VERSION OUTPUT
+###############################################################################
+
+print_help() {
+ printf "${BOLD}${MAGENTA}%s${ALL_OFF} - ${BOLD}${CYAN}%s${ALL_OFF} - ${BOLD}%s${ALL_OFF}
+
+${BOLD}${BLUE}Usage:${ALL_OFF} ${BOLD}${MAGENTA}%s${ALL_OFF} ${BOLD}${YELLOW}[OPTION]...${ALL_OFF} ${BOLD}${GREEN}[OUTPUT]${ALL_OFF}
+
+${BOLD}${BLUE}Generation options:${ALL_OFF}
+ ${BOLD}${YELLOW}-c, --codec${ALL_OFF} ${BOLD}${GREEN}CODEC${ALL_OFF} ffmpeg video codec to use (default: libx264)
+ ${BOLD}${YELLOW}-r, --resolution${ALL_OFF} ${BOLD}${GREEN}WIDTHxHEIGHT${ALL_OFF} output resolution (default: 1280x720)
+ ${BOLD}${YELLOW}-l, --length${ALL_OFF} ${BOLD}${GREEN}DURATION${ALL_OFF} test file length, in ffmpeg duration syntax (default: 5)
+ ${BOLD}${YELLOW}--rate${ALL_OFF} ${BOLD}${GREEN}FPS${ALL_OFF} frame rate for the generated source (default: 30)
+ ${BOLD}${YELLOW}--source${ALL_OFF} ${BOLD}${GREEN}SOURCE${ALL_OFF} lavfi video source (default: smptebars)
+ ${BOLD}${YELLOW}--pix-fmt${ALL_OFF} ${BOLD}${GREEN}FORMAT${ALL_OFF} output pixel format (default: yuv420p)
+ ${BOLD}${YELLOW}--ffmpeg-loglevel${ALL_OFF} ${BOLD}${GREEN}LEVEL${ALL_OFF} ffmpeg log level (default: warning)
+ ${BOLD}${YELLOW}-o, --output${ALL_OFF} ${BOLD}${GREEN}FILE${ALL_OFF} write to FILE instead of generated testfiles/ name
+ ${BOLD}${YELLOW}-f, --force${ALL_OFF} overwrite an existing output file
+ ${BOLD}${YELLOW}-n, --dry-run${ALL_OFF} show the ffmpeg command without creating a file
+
+${BOLD}${BLUE}Output options:${ALL_OFF}
+ ${BOLD}${YELLOW}-q, --quiet${ALL_OFF} suppress informational output
+ ${BOLD}${YELLOW}-v, --verbose${ALL_OFF} explain what is being done
+ ${BOLD}${YELLOW}--color${ALL_OFF} force colored output
+ ${BOLD}${YELLOW}--no-color${ALL_OFF} disable colored output
+
+${BOLD}${BLUE}General options:${ALL_OFF}
+ ${BOLD}${YELLOW}-h, --help${ALL_OFF} display this help and exit
+ ${BOLD}${YELLOW}--version${ALL_OFF} output version information and exit
+
+${BOLD}${BLUE}Examples:${ALL_OFF}
+ %s --codec libx264 --resolution 1280x720 --length 5
+ %s --codec libx265 --resolution 1920x1080 --length 00:00:10
+ %s --codec libvpx-vp9 --resolution 640x360 --length 3 --output testfiles/vp9.webm
+ %s --source testsrc2 --codec mpeg4 --resolution 320x240 --length 2
+" \
+ "$SCRIPTNAME" "$VERSION" "$DESCRIPTION" \
+ "$SCRIPTNAME" \
+ "$SCRIPTNAME" "$SCRIPTNAME" "$SCRIPTNAME" "$SCRIPTNAME"
+}
+
+print_version() {
+ printf "${RED}${BOLD}Scriptname:${ALL_OFF} %s
+${GREEN}${BOLD}Version:${ALL_OFF} %s
+${YELLOW}${BOLD}Description:${ALL_OFF} %s
+${BLUE}${BOLD}Author:${ALL_OFF} %s
+${MAGENTA}${BOLD}Date of creation:${ALL_OFF} %s
+${CYAN}${BOLD}Date of revision:${ALL_OFF} %s
+${RED}${BOLD}License:${ALL_OFF} %s
+
+Copyright (c) 2026 Dennis Fink <me+coding@dennisfink.me>.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" \
+ "$SCRIPTNAME" "$VERSION" "$DESCRIPTION" "$AUTHOR" "$DATE_OF_CREATION" \
+ "$DATE_OF_REVISION" "$LICENSE"
+}
+
+###############################################################################
+# TERMINAL COLOR HANDLING
+#
+# Colors and style escape sequences are configured dynamically through the
+# 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.
+###############################################################################
+
+setup_colors() {
+ if [[ "$ENABLE_COLOR" -eq 1 ]]; then
+ if tput setaf 0 >/dev/null 2>&1; then
+ debug "Setting colors via tput"
+ ALL_OFF="$(tput sgr0)"
+ BOLD="$(tput bold)"
+ RED="$(tput setaf 1)"
+ GREEN="$(tput setaf 2)"
+ YELLOW="$(tput setaf 3)"
+ BLUE="$(tput setaf 4)"
+ MAGENTA="$(tput setaf 5)"
+ CYAN="$(tput setaf 6)"
+ else
+ # Hardcoded ANSI fallback: colors only (no bold embedded), matching the
+ # tput path where BOLD and color variables are kept separate.
+ debug "Using hardcoded color ANSI escape sequences"
+ ALL_OFF="\e[0m"
+ BOLD="\e[1m"
+ RED="\e[31m"
+ GREEN="\e[32m"
+ YELLOW="\e[33m"
+ BLUE="\e[34m"
+ MAGENTA="\e[35m"
+ CYAN="\e[36m"
+ fi
+ else
+ ALL_OFF=""
+ BOLD=""
+ RED=""
+ GREEN=""
+ YELLOW=""
+ BLUE=""
+ MAGENTA=""
+ CYAN=""
+ fi
+}
+
+###############################################################################
+# GENERATION HELPERS
+###############################################################################
+
+require_command() {
+ local command_name="$1"
+
+ command -v "$command_name" >/dev/null 2>&1 || {
+ error "$command_name not found."
+ exit $EXIT_MISSING_DEPENDENCY
+ }
+}
+
+validate_resolution() {
+ if [[ ! "$RESOLUTION" =~ ^[1-9][0-9]*x[1-9][0-9]*$ ]]; then
+ error "Resolution must use WIDTHxHEIGHT format:" "$RESOLUTION"
+ exit $EXIT_USAGE_ERROR
+ fi
+}
+
+validate_length() {
+ if [[ "$LENGTH" =~ ^- ]]; then
+ error "Length must be a positive ffmpeg duration:" "$LENGTH"
+ exit $EXIT_USAGE_ERROR
+ fi
+}
+
+validate_frame_rate() {
+ if [[ ! "$FRAME_RATE" =~ ^[1-9][0-9]*([.][0-9]+)?$ ]]; then
+ error "Frame rate must be a positive number:" "$FRAME_RATE"
+ exit $EXIT_USAGE_ERROR
+ fi
+}
+
+extension_for_codec() {
+ case "$CODEC" in
+ libvpx | libvpx-vp9)
+ printf "webm"
+ ;;
+ libaom-av1 | librav1e | libsvtav1)
+ printf "mkv"
+ ;;
+ *)
+ printf "mp4"
+ ;;
+ esac
+}
+
+sanitized_name_part() {
+ local value="$1"
+ value="${value//\//-}"
+ value="${value//:/-}"
+ value="${value// /-}"
+ printf "%s" "$value"
+}
+
+default_output_path() {
+ local extension codec_part length_part
+ extension="$(extension_for_codec)"
+ codec_part="$(sanitized_name_part "$CODEC")"
+ length_part="$(sanitized_name_part "$LENGTH")"
+
+ printf "%s/%s-%s-%ss.%s" "$OUTPUT_DIR" "$codec_part" "$RESOLUTION" "$length_part" "$extension"
+}
+
+print_command() {
+ local quoted_command
+ printf -v quoted_command '%q ' "${FFMPEG_COMMAND[@]}"
+ printf "%s\n" "${quoted_command% }"
+}
+
+generate_test_file() {
+ local output_file="$1"
+
+ if [[ -e "$output_file" && $OVERWRITE -eq 0 ]]; then
+ error "Output file already exists; use --force to overwrite:" "$output_file"
+ exit $EXIT_RUNTIME_FAILURE
+ fi
+
+ local overwrite_flag="-n"
+
+ if [[ $OVERWRITE -eq 1 ]]; then
+ overwrite_flag="-y"
+ fi
+
+ FFMPEG_COMMAND=(
+ ffmpeg
+ -hide_banner
+ -loglevel "$FFMPEG_LOGLEVEL"
+ "$overwrite_flag"
+ -f lavfi
+ -i "${FILTER}=size=${RESOLUTION}:rate=${FRAME_RATE}"
+ -t "$LENGTH"
+ -an
+ -c:v "$CODEC"
+ -pix_fmt "$PIX_FMT"
+ "$output_file"
+ )
+
+ if [[ $DRY_RUN -eq 1 ]]; then
+ msg "DRY RUN:" "Would generate test file: $output_file"
+ print_command
+ return $EXIT_OK
+ fi
+
+ mkdir -p -- "$(dirname -- "$output_file")"
+
+ msg "Generating test file:" "$output_file"
+ verbose "Codec:" "$CODEC"
+ verbose "Resolution:" "$RESOLUTION"
+ verbose "Length:" "$LENGTH"
+ verbose "Filter:" "$FILTER"
+ verbose "Command:"
+ if [[ $VERBOSE -eq 1 && $QUIET -eq 0 ]]; then
+ print_command
+ fi
+
+ "${FFMPEG_COMMAND[@]}"
+ msg "Created:" "$output_file"
+}
+
+###############################################################################
+# MAIN EXECUTION
+###############################################################################
+
+# 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
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -h | --help | -\?)
+ print_help
+ exit $EXIT_OK
+ ;;
+ --version)
+ print_version
+ exit $EXIT_OK
+ ;;
+ -q | --quiet)
+ QUIET=1
+ shift
+ ;;
+ -v | --verbose)
+ VERBOSE=1
+ shift
+ ;;
+ --color)
+ ENABLE_COLOR=1
+ setup_colors
+ shift
+ ;;
+ --no-color)
+ ENABLE_COLOR=0
+ setup_colors
+ shift
+ ;;
+ -n | --dry-run)
+ DRY_RUN=1
+ shift
+ ;;
+ -f | --force)
+ OVERWRITE=1
+ shift
+ ;;
+ -c | --codec)
+ 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
+ CODEC="$2"
+ shift 2
+ ;;
+ --codec=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --codec must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ CODEC="${1#*=}"
+ shift
+ ;;
+ -r | --resolution)
+ 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
+ RESOLUTION="$2"
+ shift 2
+ ;;
+ --resolution=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --resolution must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ RESOLUTION="${1#*=}"
+ shift
+ ;;
+ -l | --length | --duration)
+ 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
+ LENGTH="$2"
+ shift 2
+ ;;
+ --length=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --length must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ LENGTH="${1#*=}"
+ shift
+ ;;
+ --duration=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --duration must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ LENGTH="${1#*=}"
+ shift
+ ;;
+ --rate)
+ 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
+ FRAME_RATE="$2"
+ shift 2
+ ;;
+ --rate=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --rate must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ FRAME_RATE="${1#*=}"
+ shift
+ ;;
+ --source)
+ 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
+ FILTER="$2"
+ shift 2
+ ;;
+ --source=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --source must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ FILTER="${1#*=}"
+ shift
+ ;;
+ --pix-fmt)
+ 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
+ PIX_FMT="$2"
+ shift 2
+ ;;
+ --pix-fmt=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --pix-fmt must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ PIX_FMT="${1#*=}"
+ shift
+ ;;
+ --ffmpeg-loglevel)
+ 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
+ FFMPEG_LOGLEVEL="$2"
+ shift 2
+ ;;
+ --ffmpeg-loglevel=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --ffmpeg-loglevel must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ FFMPEG_LOGLEVEL="${1#*=}"
+ shift
+ ;;
+ -o | --output)
+ 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
+ OUTPUT="$2"
+ shift 2
+ ;;
+ --output=*)
+ if [[ ! -n "${1#*=}" ]]; then
+ error "Value for --output must not be empty"
+ exit $EXIT_USAGE_ERROR
+ fi
+ OUTPUT="${1#*=}"
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ error "Unknown option:" "$1"
+ exit $EXIT_USAGE_ERROR
+ ;;
+ *)
+ if [[ -n "$OUTPUT" ]]; then
+ error "Unexpected extra argument:" "$1"
+ exit $EXIT_USAGE_ERROR
+ fi
+ OUTPUT="$1"
+ shift
+ ;;
+ esac
+done
+
+if [[ $# -gt 0 ]]; then
+ if [[ -n "$OUTPUT" ]]; then
+ error "Unexpected extra argument:" "$1"
+ exit $EXIT_USAGE_ERROR
+ fi
+ OUTPUT="$1"
+ shift
+fi
+
+require_command ffmpeg
+validate_resolution
+validate_length
+validate_frame_rate
+
+if [[ -z "$OUTPUT" ]]; then
+ OUTPUT="$(default_output_path)"
+fi
+
+generate_test_file "$OUTPUT"
+exit $EXIT_OK