diff options
| -rw-r--r-- | README.md | 26 | ||||
| -rwxr-xr-x | transcode.sh | 147 | ||||
| -rw-r--r-- | transcode.sh.1 | 54 | ||||
| -rw-r--r-- | transcode.sh.bash-completion | 12 |
4 files changed, 194 insertions, 45 deletions
@@ -30,6 +30,8 @@ replaces the original atomically on success. - **Size report** (`-s`) — print per-file size feedback, show a batch summary, and write a status TSV log for encoded, skipped, and failed files. - **Dry-run** (`-n`) — preview what would happen without touching any files. +- **Selectable video probe stream** (`--video-stream`) — choose which input + video stream is probed for preset helper variables. - **Backup original** (`--backup-dir DIR`) — copy originals to an existing backup directory before replacing them. - **Nice** — runs ffmpeg at niceness 19 by default to avoid starving other @@ -82,8 +84,10 @@ Presets live in: ${XDG_CONFIG_HOME:-$HOME/.config}/transcode.sh/presets/<name>.sh ``` -Each preset is sourced as Bash and must define an array named `ffargs`. -The following variables are available to presets at source time: +Each preset is sourced as Bash and must define an array named `ffargs`. The +following variables are available to presets at source time. They are derived +from the selected video probe stream, which defaults to `v:0` and can be +changed with `--video-stream` or `[encoding] video_stream`: | Variable | Description | |----------|-------------| @@ -104,6 +108,12 @@ these base arguments to `ffmpeg`: -c copy # default to stream copy for every stream ``` +`--video-stream` only controls which input stream is probed for helper +variables such as `input_codec`, `input_pixel_format`, `input_frame_rate`, +`input_fps`, `output_pixel_format`, and `output_gop_size`. It does not rewrite +the preset's `ffargs`; presets remain responsible for selecting which ffmpeg +stream to encode. + A preset therefore only needs to specify the streams it wishes to re-encode (typically `-c:v:0`) and any associated encoder options. All other streams — audio, subtitles, attachments — are passed through @@ -156,6 +166,7 @@ Requires `tomlq` when the file is present. preset = "av1" nice = 10 backup_dir = "backups" +video_stream = "v:0" [skip] codecs = ["av1", "hevc"] @@ -176,6 +187,7 @@ quiet = false | `[encoding]` | `nice` | integer | `-N` | | `[encoding]` | `preset` | string | `-p` | | `[encoding]` | `verify_output` | boolean | `--verify-output` | +| `[encoding]` | `video_stream` | string | `--video-stream` | | `[output]` | `quiet` | boolean | `-q` | | `[output]` | `verbose` | boolean | `-v` | | `[output]` | `ffmpeg_loglevel` | string | `--ffmpeg-loglevel` | @@ -195,6 +207,12 @@ quiet = false > **Note:** `[encoding] backup_dir` must point to an existing directory. CLI > `--backup-dir` always takes precedence. +> **Note:** `[encoding] video_stream` selects the input video stream that is +> probed for preset helper variables. It defaults to `"v:0"`. Invalid config +> values are ignored with a warning and the default is used. CLI +> `--video-stream` always takes precedence. This setting does not change ffmpeg +> stream mapping in presets. + ### Codec skip list (deprecated) The legacy `skip.conf` file is no longer read. If it still exists, a @@ -228,6 +246,7 @@ transcode.sh [OPTION] [--] FILE... | `-l`, `--only-if-smaller` | Only replace original if new file is smaller | | `--no-only-if-smaller` | Do replace original even if new file is bigger | | `-p NAME`, `--preset NAME`, `--preset=NAME` | Preset to use (default: `default`) | +| `--video-stream STREAM_SELECTOR`, `--video-stream=STREAM_SELECTOR` | Select the input video stream to probe for preset helper variables, e.g. `v:0` or `v:1` (default: `v:0`) | | `--ffmpeg-loglevel LEVEL`, `--ffmpeg-loglevel=LEVEL` | Specify the loglevel to pass to ffmpeg (default: `fatal`) | | `-q`, `--quiet` | Suppress all output (overrides `-v`) | | `-v`, `--verbose` | More detailed output | @@ -262,6 +281,9 @@ transcode.sh --nice 10 --continue *.mp4 # Load an alternative config file transcode.sh --config-file ~/profiles/fast.toml input.mp4 + +# Probe the second video stream for preset helper variables +transcode.sh --video-stream v:1 --dry-run --verbose input.mkv ``` When `--backup-dir` is used, the directory must already exist. Backups preserve diff --git a/transcode.sh b/transcode.sh index 7f40e3b..817d8e2 100755 --- a/transcode.sh +++ b/transcode.sh @@ -164,6 +164,7 @@ PRESET_NAME="default" SIZE_REPORT=0 SIZE_REPORT_FILE="transcode_size_report" VERIFY_OUTPUT=1 +VIDEO_STREAM="v:0" declare -A SKIP_CODECS REMAINING_ARGS=() @@ -192,6 +193,7 @@ _CLI_SKIP_CODECS=() _CLI_SKIP_CODECS_OVERRIDE=0 _CLI_VERBOSE=0 _CLI_VERIFY_OUTPUT=0 +_CLI_VIDEO_STREAM=0 # Values for size report _REPORT_ENCODING_FAILED=0 @@ -284,44 +286,45 @@ ${BOLD}${BLUE}Usage:${ALL_OFF} ${BOLD}${MAGENTA}%s${ALL_OFF} ${BOLD}${YELLOW}[OP Long options that take a value also accept ${BOLD}${YELLOW}--option${ALL_OFF}${BOLD}${CYAN}=${ALL_OFF}${BOLD}${GREEN}VALUE${ALL_OFF} syntax. ${BOLD}${BLUE}Encoding options:${ALL_OFF} - ${BOLD}${YELLOW}-p, --preset${ALL_OFF} ${BOLD}${GREEN}NAME${ALL_OFF} load ffmpeg arguments from a preset (default: ${BOLD}${RED}%s${ALL_OFF}) - ${BOLD}${YELLOW}-N, --nice${ALL_OFF} ${BOLD}${GREEN}VALUE${ALL_OFF} nice adjustment for ffmpeg (default: ${BOLD}${RED}%d${ALL_OFF}) - ${BOLD}${YELLOW}-n, --dry-run${ALL_OFF} show what would be done, do not run ffmpeg - ${BOLD}${YELLOW} --backup-dir${ALL_OFF} ${BOLD}${GREEN}DIR${ALL_OFF} backup original files to DIR instead of overwriting them - ${BOLD}${YELLOW} --hwaccel${ALL_OFF} ${BOLD}${GREEN}[METHOD]${ALL_OFF} enable hardware acceleration; METHOD defaults to ${BOLD}${RED}auto${ALL_OFF} - if omitted - ${BOLD}${YELLOW} --no-hwaccel${ALL_OFF} disable hardware acceleration (do not pass ${BOLD}${YELLOW}-hwaccel${ALL_OFF} to ffmpeg) - ${BOLD}${YELLOW}-l, --only-if-smaller${ALL_OFF} only replace original if new file is smaller - ${BOLD}${YELLOW}--no-only-if-smaller${ALL_OFF} do replace original even if new file is bigger - ${BOLD}${YELLOW}-c, --continue${ALL_OFF} continue with the next file even if ffmpeg fails - ${BOLD}${YELLOW}--no-continue${ALL_OFF} do not continue with the next file if ffmpeg fails - ${BOLD}${YELLOW}--verify-output${ALL_OFF} probe output with ffprobe before replacing original (default) - ${BOLD}${YELLOW}--no-verify-output${ALL_OFF} skip the post-encode integrity check + ${BOLD}${YELLOW}-p, --preset${ALL_OFF} ${BOLD}${GREEN}NAME${ALL_OFF} load ffmpeg arguments from a preset (default: ${BOLD}${RED}%s${ALL_OFF}) + ${BOLD}${YELLOW}-N, --nice${ALL_OFF} ${BOLD}${GREEN}VALUE${ALL_OFF} nice adjustment for ffmpeg (default: ${BOLD}${RED}%d${ALL_OFF}) + ${BOLD}${YELLOW}-n, --dry-run${ALL_OFF} show what would be done, do not run ffmpeg + ${BOLD}${YELLOW} --backup-dir${ALL_OFF} ${BOLD}${GREEN}DIR${ALL_OFF} backup original files to DIR instead of overwriting them + ${BOLD}${YELLOW}--hwaccel${ALL_OFF} ${BOLD}${GREEN}[METHOD]${ALL_OFF} enable hardware acceleration; METHOD defaults to ${BOLD}${RED}auto${ALL_OFF} + if omitted + ${BOLD}${YELLOW}--video-stream${ALL_OFF} ${BOLD}${GREEN}STREAM_SELECTOR${ALL_OFF} video stream to probe for preset helper variables (default: ${BOLD}${RED}v:0${ALL_OFF}) + ${BOLD}${YELLOW} --no-hwaccel${ALL_OFF} disable hardware acceleration (do not pass ${BOLD}${YELLOW}-hwaccel${ALL_OFF} to ffmpeg) + ${BOLD}${YELLOW}-l, --only-if-smaller${ALL_OFF} only replace original if new file is smaller + ${BOLD}${YELLOW}--no-only-if-smaller${ALL_OFF} do replace original even if new file is bigger + ${BOLD}${YELLOW}-c, --continue${ALL_OFF} continue with the next file even if ffmpeg fails + ${BOLD}${YELLOW}--no-continue${ALL_OFF} do not continue with the next file if ffmpeg fails + ${BOLD}${YELLOW}--verify-output${ALL_OFF} probe output with ffprobe before replacing original (default) + ${BOLD}${YELLOW}--no-verify-output${ALL_OFF} skip the post-encode integrity check ${BOLD}${BLUE}Input options:${ALL_OFF} - ${BOLD}${YELLOW}--config-file${ALL_OFF} ${BOLD}${GREEN}FILE${ALL_OFF} load configuration from FILE instead of the default path - ${BOLD}${YELLOW}-f, --encode-file${ALL_OFF} ${BOLD}${GREEN}FILE${ALL_OFF} file containing a list of files to encode - ${BOLD}${YELLOW}-S, --skip-codec${ALL_OFF} ${BOLD}${GREEN}LIST${ALL_OFF} skip files with these codecs; - ${BOLD}${GREEN}LIST${ALL_OFF} is comma, space, or colon separated - ${BOLD}${YELLOW}--skip-codec-override${ALL_OFF} use only codecs from --skip-codec, - ignoring [skip].codecs from config + ${BOLD}${YELLOW}--config-file${ALL_OFF} ${BOLD}${GREEN}FILE${ALL_OFF} load configuration from FILE instead of the default path + ${BOLD}${YELLOW}-f, --encode-file${ALL_OFF} ${BOLD}${GREEN}FILE${ALL_OFF} file containing a list of files to encode + ${BOLD}${YELLOW}-S, --skip-codec${ALL_OFF} ${BOLD}${GREEN}LIST${ALL_OFF} skip files with these codecs; + ${BOLD}${GREEN}LIST${ALL_OFF} is comma, space, or colon separated + ${BOLD}${YELLOW}--skip-codec-override${ALL_OFF} use only codecs from --skip-codec, + ignoring [skip].codecs from config ${BOLD}${BLUE}Size report options:${ALL_OFF} - ${BOLD}${YELLOW}-s, --size-report${ALL_OFF} print size feedback and write a status TSV log - ${BOLD}${YELLOW}--no-size-report${ALL_OFF} disable size reporting from config.toml - ${BOLD}${YELLOW}--size-report-file${ALL_OFF} ${BOLD}${GREEN}FILE${ALL_OFF} path for the size report log (default: ${BOLD}${RED}%s${ALL_OFF}) + ${BOLD}${YELLOW}-s, --size-report${ALL_OFF} print size feedback and write a status TSV log + ${BOLD}${YELLOW}--no-size-report${ALL_OFF} disable size reporting from config.toml + ${BOLD}${YELLOW}--size-report-file${ALL_OFF} ${BOLD}${GREEN}FILE${ALL_OFF} path for the size report log (default: ${BOLD}${RED}%s${ALL_OFF}) ${BOLD}${BLUE}Output options:${ALL_OFF} - ${BOLD}${YELLOW}--ffmpeg-loglevel${ALL_OFF} specify the loglevel to pass to ffmpeg (default: ${BOLD}${RED}fatal${ALL_OFF}) - ${BOLD}${YELLOW}-q, --quiet${ALL_OFF} suppress all output; takes precedence over ${BOLD}${YELLOW}-v${ALL_OFF} - ${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}${YELLOW}--ffmpeg-loglevel${ALL_OFF} specify the loglevel to pass to ffmpeg (default: ${BOLD}${RED}fatal${ALL_OFF}) + ${BOLD}${YELLOW}-q, --quiet${ALL_OFF} suppress all output; takes precedence over ${BOLD}${YELLOW}-v${ALL_OFF} + ${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}${YELLOW}--list-presets${ALL_OFF} list available presets and exit + ${BOLD}${YELLOW}-h, --help${ALL_OFF} display this help and exit + ${BOLD}${YELLOW}--version${ALL_OFF} output version information and exit + ${BOLD}${YELLOW}--list-presets${ALL_OFF} list available presets and exit ${BOLD}${BLUE}Configuration files:${ALL_OFF} ${BOLD}${CYAN}%s/<NAME>.sh${ALL_OFF} @@ -347,9 +350,14 @@ ${BOLD}${BLUE}Examples:${ALL_OFF} %s -n -N 10 my_movie.mkv\n" \ "$SCRIPTNAME" "$VERSION" "$DESCRIPTION" \ "$SCRIPTNAME" \ - "$PRESET_NAME" "$NICE_VALUE" "$SIZE_REPORT_FILE" \ - "$PRESET_DIR" "$CONFIG_FILE" \ - "$SCRIPTNAME" "$SCRIPTNAME" "$SCRIPTNAME" + "$PRESET_NAME" \ + "$NICE_VALUE" \ + "$SIZE_REPORT_FILE" \ + "$PRESET_DIR" \ + "$CONFIG_FILE" \ + "$SCRIPTNAME" \ + "$SCRIPTNAME" \ + "$SCRIPTNAME" } print_version() { @@ -594,6 +602,20 @@ load_config() { fi fi + # [encoding] video_stream + if [[ $_CLI_VIDEO_STREAM -eq 0 ]]; then + val=$(_tomlq_get '.encoding.video_stream // empty') + if [[ -n "$val" ]]; then + debug "Config: encoding.video_stream =" "$val" + if ! validate_stream_selector "$val"; then + warning "Ignoring invalid config value for encoding.video_stream:" "$val" + warning "Falling back to default video stream:" "$VIDEO_STREAM" + else + VIDEO_STREAM="$val" + fi + fi + fi + if [[ $_CLI_BACKUP_DIR -eq 0 ]]; then val=$(_tomlq_get '.encoding.backup_dir // empty') if [[ -n "$val" ]]; then @@ -702,6 +724,22 @@ parse_skip_codec_parameter() { } ############################################################################### +# STREAM SELECTOR HANDLING +############################################################################### + +validate_stream_selector() { + local stream_selector="$1" + + # Accept only simple video stream indexes for metadata probing. This is not a + # general ffmpeg stream selector parser. + if [[ ! "$stream_selector" =~ ^((v|V):)?([0-9]|[1-9][0-9]+)$ ]]; then + return 1 + fi + + return 0 +} + +############################################################################### # PRESET HANDLING ############################################################################### @@ -1109,9 +1147,14 @@ encode_one() { # Probe codec, pixel format and frame rate in a single ffprobe invocation to # decrease the startup overhead on large batches. local probe_out - probe_out=$(ffprobe -v error -select_streams v:0 \ - -show_entries stream=codec_name,pix_fmt,r_frame_rate \ - -of default=nk=1:nw=1 "file:$input_file" 2>/dev/null) + probe_out=$( + ffprobe \ + -v error \ + -select_streams "$VIDEO_STREAM" \ + -show_entries stream=codec_name,pix_fmt,r_frame_rate \ + -of default=nk=1:nw=1 \ + "file:$input_file" 2>/dev/null + ) local input_codec input_pixel_format input_frame_rate input_fps @@ -1267,6 +1310,7 @@ encode_one() { if [[ $DRY_RUN -eq 1 ]]; then msg "DRY RUN:" "Would encode with preset $PRESET_NAME" + verbose "Video stream:" "$VIDEO_STREAM" verbose "Input codec:" "$input_codec" verbose "Input pixel format:" "$input_pixel_format" verbose "Output pixel format:" "$output_pixel_format" @@ -1675,6 +1719,36 @@ while [[ $# -gt 0 ]]; do _CLI_FFMPEG_LOGLEVEL=1 shift ;; + --video-stream) + 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 + if ! validate_stream_selector "$2"; then + error "Invalid stream selector:" "$2" + exit $EXIT_USAGE_ERROR + fi + VIDEO_STREAM="$2" + _CLI_VIDEO_STREAM=1 + shift 2 + ;; + --video-stream=*) + if [[ ! -n "${1#*=}" ]]; then + error "Value for --video-stream must not be empty" + exit $EXIT_USAGE_ERROR + fi + if ! validate_stream_selector "${1#*=}"; then + error "Invalid stream selector:" "${1#*=}" + exit $EXIT_USAGE_ERROR + fi + VIDEO_STREAM="${1#*=}" + _CLI_VIDEO_STREAM=1 + shift + ;; --) shift REMAINING_ARGS+=("$@") @@ -1722,6 +1796,7 @@ debug " size-report:" "$SIZE_REPORT" debug " skip-codecs:" "${joined# }" debug " verbose:" "$VERBOSE" debug " verify-output:" "$VERIFY_OUTPUT" +debug " video-stream:" "$VIDEO_STREAM" command -v ffmpeg >/dev/null 2>&1 || { error "ffmpeg not found." diff --git a/transcode.sh.1 b/transcode.sh.1 index bc3639c..e0b7f3c 100644 --- a/transcode.sh.1 +++ b/transcode.sh.1 @@ -24,9 +24,13 @@ containing the extra arguments passed to .PP The script probes every input file with .BR ffprobe (1) -to detect the input video codec and pixel format. Files whose codec -appears in the skip list are silently skipped. The output pixel format -is derived automatically so that chroma subsampling (4:2:0, 4:2:2, +to detect the input video codec, pixel format and frame rate of the selected +video probe stream. The selected stream defaults to +.B v:0 +and can be changed with +.BR \-\-video\-stream . +Files whose codec appears in the skip list are silently skipped. The output +pixel format is derived automatically so that chroma subsampling (4:2:0, 4:2:2, 4:4:4) and bit depth (8, 10, 12, 16\-bit) are preserved. .PP All streams, metadata and chapter markers from the input are retained by @@ -84,7 +88,8 @@ Print what would be done without actually invoking .BR ffmpeg (1). Combine with .B \-v -to see the detected codec and pixel format information. +to see the selected video stream and detected codec, pixel format and frame +rate information. .TP .BR \-\-backup\-dir " \fIDIR\fR, " \-\-backup\-dir= \fIDIR\fR Copy originals to \fIDIR\fR before replacing them after successful encoding. @@ -123,6 +128,17 @@ to Use this on hosts where hardware acceleration causes compatibility or stability issues. .TP +.BR \-\-video\-stream " \fISTREAM_SELECTOR\fR, " \-\-video\-stream= \fISTREAM_SELECTOR\fR +Select the input video stream that is probed for preset helper variables. +Defaults to +.BR v:0 . +The selector must be a conservative video stream selector such as +.B v:0 +or +.BR v:1 . +This option does not change ffmpeg stream mapping; presets remain responsible +for selecting which stream to encode. +.TP .BR \-N " \fIVALUE\fR, " \-\-nice " \fIVALUE\fR, " \-\-nice= \fIVALUE\fR Run .BR ffmpeg (1) @@ -195,8 +211,8 @@ Suppress all normal output. Takes precedence over .BR \-v / \-\-verbose . .TP .BR \-v ", " \-\-verbose -Emit additional informational messages (detected codec, pixel format, -etc.). +Emit additional informational messages, including the selected video stream and +detected codec, pixel format and frame rate. .TP .B \-\-version Print version, author and license information and exit. @@ -294,7 +310,15 @@ array variable named .B ffargs containing additional arguments for .BR ffmpeg (1). -The following variables are available to presets at source time: +These variables are derived from the selected video probe stream, which +defaults to +.B v:0 +and can be changed with +.BR \-\-video\-stream +or +.B [encoding] video_stream +in +.BR config.toml . .RS .TP .B input_codec @@ -418,6 +442,14 @@ encoding and before replacement. Equivalent to .BR \-\-backup\-dir . CLI \fB--backup-dir\fR takes precedence. .TP +.B [encoding] video_stream +String. Input video stream to probe for preset helper variables. Defaults to +.BR v:0 . +Equivalent to +.BR \-\-video\-stream . +Invalid configured values are ignored with a warning and the default is used. +This setting does not change ffmpeg stream mapping in presets. +.TP .B [skip] codecs Array of strings. Merged with any codecs from .BR \-S @@ -538,6 +570,14 @@ Load an alternative configuration file: transcode.sh \-\-config\-file ~/profiles/fast.toml input.mp4 .fi .RE +.PP +Probe the second video stream for preset helper variables: +.PP +.RS +.nf +transcode.sh \-\-video\-stream v:1 \-n \-v input.mkv +.fi +.RE .SH SEE ALSO .BR ffmpeg (1), .BR ffprobe (1), diff --git a/transcode.sh.bash-completion b/transcode.sh.bash-completion index 716995e..c980081 100644 --- a/transcode.sh.bash-completion +++ b/transcode.sh.bash-completion @@ -39,6 +39,7 @@ _transcode_sh() { local nice_vals='0 5 10 15 19' local hwaccel_methods='auto cuda vaapi dxva2 d3d11va videotoolbox qsv' local ffmpeg_loglevels='quiet panic fatal error warning info verbose debug trace' + local video_streams='v:0 v:1 v:2 v:3' # --------------------------------------------------------------------------- # Handle --option=VALUE: cur contains the entire "--opt=val" token. @@ -91,6 +92,11 @@ _transcode_sh() { COMPREPLY=($(compgen -W "$ffmpeg_loglevels" -- "$cur")) return ;; + --video-stream=*) + cur="${cur#*=}" + COMPREPLY=($(compgen -W "$video_streams" -- "$cur")) + return + ;; esac # --------------------------------------------------------------------------- @@ -135,6 +141,10 @@ _transcode_sh() { COMPREPLY=($(compgen -W "$ffmpeg_loglevels" -- "$cur")) return ;; + --video-stream) + COMPREPLY=($(compgen -W "$video_streams" -- "$cur")) + return + ;; esac # --------------------------------------------------------------------------- @@ -183,6 +193,8 @@ _transcode_sh() { --verbose --verify-output --version + --video-stream + --video-stream= ' COMPREPLY=($(compgen -W "$longopts" -- "$cur")) # Suppress the trailing space when the only completion ends with '=' |
