summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Fink2026-05-09 21:30:38 +0200
committerDennis Fink2026-05-09 21:30:38 +0200
commit46e56972f2b4d16ca6b3fd04611f86cf8580ea10 (patch)
treeadd502327a7f85c9b9b7af60abea617c5f246085
parentcfeb338478bb67defce2fb48222a6be3cffc4263 (diff)
downloadtranscode.sh-46e56972f2b4d16ca6b3fd04611f86cf8580ea10.tar.gz
transcode.sh-46e56972f2b4d16ca6b3fd04611f86cf8580ea10.zip
feat(core,cli): add hardware acceleration support via --hwaccel
Add --hwaccel [METHOD] / --no-hwaccel flags to control whether -hwaccel is passed to ffmpeg before the input file. The METHOD argument is optional and defaults to "auto", letting ffmpeg select the best available hardware decoder with automatic software fallback. Hardware acceleration is enabled by default (method: auto). Users on hosts where hwaccel causes compatibility or stability issues can opt out with --no-hwaccel or config.toml [encoding] hwaccel = false. The config key accepts false (disable), true (auto), or a method string such as "vaapi" or "cuda". CLI flags take precedence as usual. Bash completion is extended with a hwaccel_methods word list (auto, cuda, vaapi, dxva2, d3d11va, videotoolbox, qsv) for both the space form (--hwaccel <TAB>) and the equals form (--hwaccel=<TAB>). The completion script is also reformatted from 4-space to 2-space indentation for consistency with transcode.sh itself.
-rw-r--r--README.md7
-rwxr-xr-xtranscode.sh74
-rw-r--r--transcode.sh.145
-rw-r--r--transcode.sh.bash-completion297
4 files changed, 274 insertions, 149 deletions
diff --git a/README.md b/README.md
index 09ee1d3..a3adaf4 100644
--- a/README.md
+++ b/README.md
@@ -146,6 +146,7 @@ quiet = false
| `[encoding]` | `preset` | string | `-p` |
| `[encoding]` | `nice` | integer | `-N` |
| `[encoding]` | `verify_output` | boolean | `--verify-output` |
+| `[encoding]` | `hwaccel` | string or boolean | `--hwaccel` / `--no-hwaccel` |
| `[skip]` | `codecs` | array of strings | `-S` (merged with CLI) |
| `[saving]` | `enabled` | boolean | `-s` |
| `[saving]` | `file` | string | `--saving-file` |
@@ -155,6 +156,10 @@ quiet = false
> **Note:** `[skip] codecs` from the config file and `--skip-codec` from the
> CLI are **merged** — both sources contribute to the skip list.
+> **Note:** `[encoding] hwaccel` accepts `false` (disable), `true` (use `auto`),
+> or a method string such as `"vaapi"` or `"cuda"`. CLI `--hwaccel`/`--no-hwaccel`
+> always takes precedence.
+
### Codec skip list (deprecated)
The legacy `skip.conf` file is no longer read. If it still exists, a
@@ -177,6 +182,8 @@ transcode.sh [OPTION] [--] FILE...
| `-f PATH`, `--encode-file PATH`, `--encodefile=PATH` | Read file list from PATH (one per line) |
| `-n`, `--dry-run` | Show what would be done; don't run ffmpeg |
| `-N VALUE`, `--nice VALUE`, `--nice=VALUE` | Niceness value for ffmpeg (default: 19) |
+| `--hwaccel [METHOD]`, `--hwaccel=METHOD` | Enable hardware acceleration; METHOD defaults to `auto` if omitted (e.g. `cuda`, `vaapi`, `videotoolbox`); enabled by default |
+| `--no-hwaccel` | Disable hardware acceleration (omit `-hwaccel` from ffmpeg invocation) |
| `-s`, `--saving` | Log filesize savings after each encode |
| `--saving-file PATH`, `--saving-file=PATH` | Savings log path (default: `transcode_savings`) |
| `-S LIST`, `--skip-codec LIST`, `--skip-codec=LIST` | Codecs to skip (comma/space/colon separated) |
diff --git a/transcode.sh b/transcode.sh
index 807495d..41a22ab 100755
--- a/transcode.sh
+++ b/transcode.sh
@@ -135,6 +135,8 @@ VERBOSE=0
CONTINUE_ON_FAIL=0
DRY_RUN=0
ENCODE_FILE=""
+HWACCEL=1
+HWACCEL_VALUE="auto"
NICE_VALUE=19
ONLY_IF_SMALLER=0
PRESET_NAME="default"
@@ -157,6 +159,7 @@ readonly SKIP_CODECS_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/transcode.sh/skip.c
# knows which values have already been provided and must not be overridden.
_CLI_CONFIG_FILE=0
_CLI_CONTINUE=0
+_CLI_HWACCEL=0
_CLI_NICE=0
_CLI_ONLY_IF_SMALLER=0
_CLI_PRESET=0
@@ -234,6 +237,9 @@ ${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 value 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} --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
@@ -495,6 +501,22 @@ load_config() {
}
fi
+ # [encoding] hwaccel
+ if [[ $_CLI_HWACCEL -eq 0 ]]; then
+ val=$(_tomlq_get '.encoding.hwaccel // empty') && [[ -n "$val" ]] && {
+ debug "Config: encoding.hwaccel =" "$val"
+ if [[ "$val" == "false" ]]; then
+ HWACCEL=0
+ else
+ HWACCEL=1
+ # A bare `hwaccel = true` keeps "auto"; any other string is the METHOD.
+ if [[ "$val" != "true" ]]; then
+ HWACCEL_VALUE="$val"
+ fi
+ fi
+ }
+ fi
+
# [skip] codecs (TOML array → one element per line via tomlq -r '.skip.codecs[]')
# CLI --skip-codec entries are additive, so we always load config codecs
# regardless of _CLI_SKIP_CODEC. Both sources merge into SKIP_CODECS.
@@ -802,6 +824,34 @@ while [[ $# -gt 0 ]]; do
_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#*=}"
+ [[ -n "$HWACCEL_VALUE" ]] || {
+ error "Value for --hwaccel must not be empty"
+ exit $EXIT_USAGE_ERROR
+ }
+ _CLI_HWACCEL=1
+ shift
+ ;;
+ --no-hwaccel)
+ HWACCEL=0
+ _CLI_HWACCEL=1
+ shift
+ ;;
-p | --preset)
[[ $# -ge 2 ]] || {
error "Missing value for $1"
@@ -847,18 +897,20 @@ load_config
printf -v joined ' %s' "${!SKIP_CODECS[@]}"
debug "Effective configuration:"
-debug " preset:" "$PRESET_NAME"
-debug " nice:" "$NICE_VALUE"
+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 " verify-output:" "$VERIFY_OUTPUT"
-debug " saving:" "$SAVING"
+debug " preset:" "$PRESET_NAME"
+debug " quiet:" "$QUIET"
debug " saving-file:" "$SAVING_FILE"
+debug " saving:" "$SAVING"
debug " skip-codecs:" "${joined# }"
debug " verbose:" "$VERBOSE"
-debug " quiet:" "$QUIET"
-debug " config-file:" "$CONFIG_FILE"
-debug " encode-file:" "$ENCODE_FILE"
+debug " verify-output:" "$VERIFY_OUTPUT"
command -v ffmpeg >/dev/null 2>&1 || {
error "ffmpeg not found."
@@ -1065,13 +1117,21 @@ encode_one() {
# Build ffmpeg command:
# - Map all streams, metadata, and chapters
# - Default to stream copy for everything
+ # - Prepend -hwaccel before -i when hardware acceleration is requested
# - Let the preset override specific streams (usually video) via ffargs
+
+ local hwaccel_args=()
+ if [[ $HWACCEL -eq 1 ]]; then
+ hwaccel_args=(-hwaccel "$HWACCEL_VALUE")
+ fi
+
local cmd=(ffmpeg
-nostdin
-y
-hide_banner
-v error
-stats
+ "${hwaccel_args[@]}"
-i "file:$file"
-map 0
-map_metadata 0
diff --git a/transcode.sh.1 b/transcode.sh.1
index edeaae8..46b7526 100644
--- a/transcode.sh.1
+++ b/transcode.sh.1
@@ -86,6 +86,37 @@ Combine with
.B \-v
to see the detected codec and pixel format information.
.TP
+.BR \-\-hwaccel " [\fIMETHOD\fR], " \-\-hwaccel= \fIMETHOD\fR
+Pass
+.BI \-hwaccel " METHOD"
+to
+.BR ffmpeg (1)
+before the input file, enabling hardware-accelerated decoding.
+\fIMETHOD\fR is optional; when omitted it defaults to
+.BR auto ,
+which lets
+.BR ffmpeg (1)
+select the best available hardware decoder and fall back to software
+automatically.
+Common values include
+.BR cuda ,
+.BR vaapi ,
+.BR dxva2 ,
+and
+.BR videotoolbox .
+Hardware acceleration is
+.B enabled by default
+(method
+.BR auto ).
+.TP
+.B \-\-no\-hwaccel
+Do not pass
+.B \-hwaccel
+to
+.BR ffmpeg (1).
+Use this on hosts where hardware acceleration causes compatibility or
+stability issues.
+.TP
.BR \-N " \fIVALUE\fR, " \-\-nice " \fIVALUE\fR, " \-\-nice= \fIVALUE\fR
Run
.BR ffmpeg (1)
@@ -287,6 +318,20 @@ String. Default preset name. Equivalent to
Integer. Default niceness value. Equivalent to
.BR \-N .
.TP
+.B [encoding] hwaccel
+String or boolean.
+.B false
+disables hardware acceleration.
+.B true
+enables it with method
+.BR auto .
+Any other string (e.g.\&
+.IR vaapi ,
+.IR cuda )
+enables it with that specific method.
+Equivalent to
+.BR \-\-hwaccel " / " \-\-no\-hwaccel .
+.TP
.B [encoding] verify_output
Boolean. Equivalent to
.BR \-\-verify\-output / \-\-no\-verify\-output .
diff --git a/transcode.sh.bash-completion b/transcode.sh.bash-completion
index 9fc4701..1cae90e 100644
--- a/transcode.sh.bash-completion
+++ b/transcode.sh.bash-completion
@@ -14,155 +14,168 @@
# long equals --encode-file=list.txt
_transcode_sh() {
- local cur prev words cword
- _init_completion || return
+ local cur prev words cword
+ _init_completion || return
- local -r preset_dir="${XDG_CONFIG_HOME:-$HOME/.config}/transcode.sh/presets"
+ local -r preset_dir="${XDG_CONFIG_HOME:-$HOME/.config}/transcode.sh/presets"
- # ---------------------------------------------------------------------------
- # Helper: collect preset names from the preset directory
- # ---------------------------------------------------------------------------
- _transcode_presets() {
- local presets=()
- if [[ -d "$preset_dir" ]]; then
- local f
- while IFS= read -r f; do
- f="${f##*/}" # basename
- f="${f%.sh}" # strip .sh extension
- presets+=("$f")
- done < <(find "$preset_dir" -maxdepth 1 -name '*.sh' -type f 2>/dev/null)
- fi
- printf '%s\n' "${presets[@]}"
- }
+ # ---------------------------------------------------------------------------
+ # Helper: collect preset names from the preset directory
+ # ---------------------------------------------------------------------------
+ _transcode_presets() {
+ local presets=()
+ if [[ -d "$preset_dir" ]]; then
+ local f
+ while IFS= read -r f; do
+ f="${f##*/}" # basename
+ f="${f%.sh}" # strip .sh extension
+ presets+=("$f")
+ done < <(find "$preset_dir" -maxdepth 1 -name '*.sh' -type f 2>/dev/null)
+ fi
+ printf '%s\n' "${presets[@]}"
+ }
- local codecs='h264 hevc av1 vp8 vp9 mpeg2video mpeg4 mjpeg theora'
- local nice_vals='0 5 10 15 19'
+ local codecs='h264 hevc av1 vp8 vp9 mpeg2video mpeg4 mjpeg theora'
+ local nice_vals='0 5 10 15 19'
+ local hwaccel_methods='auto cuda vaapi dxva2 d3d11va videotoolbox qsv'
- # ---------------------------------------------------------------------------
- # Handle --option=VALUE: cur contains the entire "--opt=val" token.
- # Strip the "opt=" prefix and delegate to the appropriate completer.
- # This must be checked before the $prev dispatch below.
- # ---------------------------------------------------------------------------
- case "$cur" in
- --config-file=*)
- cur="${cur#*=}"
- _filedir
- return
- ;;
- --encode-file=*)
- cur="${cur#*=}"
- _filedir
- return
- ;;
- --saving-file=*)
- cur="${cur#*=}"
- _filedir
- return
- ;;
- --nice=*)
- cur="${cur#*=}"
- COMPREPLY=( $(compgen -W "$nice_vals" -- "$cur") )
- return
- ;;
- --skip-codec=*)
- cur="${cur#*=}"
- COMPREPLY=( $(compgen -W "$codecs" -- "$cur") )
- return
- ;;
- --preset=*)
- cur="${cur#*=}"
- COMPREPLY=( $(compgen -W "$(_transcode_presets)" -- "$cur") )
- return
- ;;
- esac
+ # ---------------------------------------------------------------------------
+ # Handle --option=VALUE: cur contains the entire "--opt=val" token.
+ # Strip the "opt=" prefix and delegate to the appropriate completer.
+ # This must be checked before the $prev dispatch below.
+ # ---------------------------------------------------------------------------
+ case "$cur" in
+ --config-file=*)
+ cur="${cur#*=}"
+ _filedir
+ return
+ ;;
+ --encode-file=*)
+ cur="${cur#*=}"
+ _filedir
+ return
+ ;;
+ --saving-file=*)
+ cur="${cur#*=}"
+ _filedir
+ return
+ ;;
+ --nice=*)
+ cur="${cur#*=}"
+ COMPREPLY=($(compgen -W "$nice_vals" -- "$cur"))
+ return
+ ;;
+ --skip-codec=*)
+ cur="${cur#*=}"
+ COMPREPLY=($(compgen -W "$codecs" -- "$cur"))
+ return
+ ;;
+ --preset=*)
+ cur="${cur#*=}"
+ COMPREPLY=($(compgen -W "$(_transcode_presets)" -- "$cur"))
+ return
+ ;;
+ --hwaccel=*)
+ cur="${cur#*=}"
+ COMPREPLY=($(compgen -W "$hwaccel_methods" -- "$cur"))
+ return
+ ;;
+ esac
- # ---------------------------------------------------------------------------
- # Handle VALUE after short form (-f, -N, -S, -p) and
- # long space form (--encode-file, --nice, --saving-file, --skip-codec, --preset).
- # $prev is the option word, $cur is the value being typed.
- # ---------------------------------------------------------------------------
- case "$prev" in
- --config-file)
- _filedir
- return
- ;;
- -f|--encode-file)
- _filedir
- return
- ;;
- -N|--nice)
- COMPREPLY=( $(compgen -W "$nice_vals" -- "$cur") )
- return
- ;;
- --saving-file)
- _filedir
- return
- ;;
- -S|--skip-codec)
- COMPREPLY=( $(compgen -W "$codecs" -- "$cur") )
- return
- ;;
- -p|--preset)
- COMPREPLY=( $(compgen -W "$(_transcode_presets)" -- "$cur") )
- return
- ;;
- esac
+ # ---------------------------------------------------------------------------
+ # Handle VALUE after short form (-f, -N, -S, -p) and
+ # long space form (--encode-file, --nice, --saving-file, --skip-codec, --preset).
+ # $prev is the option word, $cur is the value being typed.
+ # ---------------------------------------------------------------------------
+ case "$prev" in
+ --config-file)
+ _filedir
+ return
+ ;;
+ -f | --encode-file)
+ _filedir
+ return
+ ;;
+ -N | --nice)
+ COMPREPLY=($(compgen -W "$nice_vals" -- "$cur"))
+ return
+ ;;
+ --saving-file)
+ _filedir
+ return
+ ;;
+ -S | --skip-codec)
+ COMPREPLY=($(compgen -W "$codecs" -- "$cur"))
+ return
+ ;;
+ -p | --preset)
+ COMPREPLY=($(compgen -W "$(_transcode_presets)" -- "$cur"))
+ return
+ ;;
+ --hwaccel)
+ COMPREPLY=($(compgen -W "$hwaccel_methods" -- "$cur"))
+ return
+ ;;
+ esac
- # ---------------------------------------------------------------------------
- # Complete option names themselves.
- # For options that accept an argument we list both the bare long form
- # (--nice) and the equals form (--nice=) so the user can choose their
- # preferred style. compopt -o nospace is applied when the sole match
- # ends with '=' to avoid an unwanted trailing space in that case.
- # ---------------------------------------------------------------------------
- case "$cur" in
- --*)
- local longopts='
- --continue
- --no-continue
- --verify-output
- --no-verify-output
- --config-file
- --config-file=
- --encode-file
- --encode-file=
- --dry-run
- --nice
- --nice=
- --saving
- --no-saving
- --saving-file
- --saving-file=
- --skip-codec
- --skip-codec=
- --only-if-smaller
- --no-only-if-smaller
- --preset
- --preset=
- --help
- --quiet
- --verbose
- --version
- --color
- --no-color
- '
- COMPREPLY=( $(compgen -W "$longopts" -- "$cur") )
- # Suppress the trailing space when the only completion ends with '='
- [[ ${#COMPREPLY[@]} -eq 1 && "${COMPREPLY[0]}" == *= ]] \
- && compopt -o nospace
- return
- ;;
- -*)
- local shortopts='-c -f -n -N -s -S -l -p -h -? -q -v --'
- COMPREPLY=( $(compgen -W "$shortopts" -- "$cur") )
- return
- ;;
- esac
+ # ---------------------------------------------------------------------------
+ # Complete option names themselves.
+ # For options that accept an argument we list both the bare long form
+ # (--nice) and the equals form (--nice=) so the user can choose their
+ # preferred style. compopt -o nospace is applied when the sole match
+ # ends with '=' to avoid an unwanted trailing space in that case.
+ # ---------------------------------------------------------------------------
+ case "$cur" in
+ --*)
+ local longopts='
+ --color
+ --config-file
+ --config-file=
+ --continue
+ --dry-run
+ --encode-file
+ --encode-file=
+ --help
+ --hwaccel
+ --hwaccel=
+ --nice
+ --nice=
+ --no-color
+ --no-continue
+ --no-hwaccel
+ --no-only-if-smaller
+ --no-saving
+ --no-verify-output
+ --only-if-smaller
+ --preset
+ --preset=
+ --quiet
+ --saving
+ --saving-file
+ --saving-file=
+ --skip-codec
+ --skip-codec=
+ --verbose
+ --verify-output
+ --version
+ '
+ COMPREPLY=($(compgen -W "$longopts" -- "$cur"))
+ # Suppress the trailing space when the only completion ends with '='
+ [[ ${#COMPREPLY[@]} -eq 1 && "${COMPREPLY[0]}" == *= ]] &&
+ compopt -o nospace
+ return
+ ;;
+ -*)
+ local shortopts='-c -f -n -N -s -S -l -p -h -? -q -v --'
+ COMPREPLY=($(compgen -W "$shortopts" -- "$cur"))
+ return
+ ;;
+ esac
- # ---------------------------------------------------------------------------
- # Default: complete positional FILE arguments with common media extensions
- # ---------------------------------------------------------------------------
- _filedir '@(mkv|mp4|mov|avi|webm|flv|wmv|m4v|ts|mts|m2ts|mpg|mpeg|ogv|3gp|rm|rmvb)'
+ # ---------------------------------------------------------------------------
+ # Default: complete positional FILE arguments with common media extensions
+ # ---------------------------------------------------------------------------
+ _filedir '@(mkv|mp4|mov|avi|webm|flv|wmv|m4v|ts|mts|m2ts|mpg|mpeg|ogv|3gp|rm|rmvb)'
}
complete -F _transcode_sh transcode.sh