diff options
| -rw-r--r-- | README.md | 3 | ||||
| -rwxr-xr-x | transcode.sh | 54 | ||||
| -rw-r--r-- | transcode.sh.1 | 26 |
3 files changed, 79 insertions, 4 deletions
@@ -85,6 +85,9 @@ The following variables are available to presets at source time: | `input_codec` | Video codec of the input file (e.g. `h264`, `hevc`) | | `input_pixel_format` | Pixel format of the input after `yuvj*` normalisation (e.g. `yuv420p`) | | `output_pixel_format` | Recommended output pixel format, derived from the input to preserve chroma subsampling and bit depth | +| `input_frame_rate` | Raw frame rate fraction of the input file as reported by `ffprobe` (e.g. `30000/1001`, `25/1`) | +| `input_fps` | `input_frame_rate` rounded to the nearest integer (e.g. `30`, `25`, `60`) | +| `output_gop_size` | Recommended GOP size derived from `input_fps` (≈ 5 s of video, capped at 300 frames) | Before the preset's `ffargs` are appended, the script always passes these base arguments to `ffmpeg`: diff --git a/transcode.sh b/transcode.sh index 00c7bd5..9d66f49 100755 --- a/transcode.sh +++ b/transcode.sh @@ -20,6 +20,12 @@ # input_pixel_format - pixel format after yuvj* normalisation (e.g. yuv420p) # output_pixel_format - recommended output pixel format, derived from the # input to preserve chroma subsampling and bit depth +# input_frame_rate - raw frame rate fraction as reported by ffprobe +# (e.g. 30000/1001, 25/1, 60/1) +# input_fps - input_frame_rate rounded to nearest integer +# (e.g. 30, 25, 60) +# output_gop_size - recommended GOP size derived from input_fps +# (≈ 5 s of video, capped at 300 frames) # # Note: Presets are sourced as shell code. Only use trusted presets. # @@ -112,7 +118,7 @@ set -o pipefail readonly SCRIPTNAME=${0##*/} readonly DESCRIPTION="Batch transcode helper for media files using ffmpeg." readonly DATE_OF_CREATION=2025-08-06 -readonly DATE_OF_REVISION=2026-02-25 +readonly DATE_OF_REVISION=2026-05-08 readonly VERSION=1.0.1 readonly AUTHOR="Dennis Fink <me+coding@dennisfink.me>" readonly LICENSE="BSD-3-Clause" @@ -266,6 +272,9 @@ ${BOLD}${BLUE}Configuration files:${ALL_OFF} ${BOLD}${GREEN}input_codec${ALL_OFF} e.g. h264, hevc ${BOLD}${GREEN}input_pixel_format${ALL_OFF} e.g. yuv420p ${BOLD}${GREEN}output_pixel_format${ALL_OFF} derived from input + ${BOLD}${GREEN}input_frame_rate${ALL_OFF} raw fraction from ffprobe (e.g. 30000/1001) + ${BOLD}${GREEN}input_fps${ALL_OFF} input_frame_rate rounded to nearest integer + ${BOLD}${GREEN}output_gop_size${ALL_OFF} ~5 s GOP derived from input_fps, capped at 300 ${BOLD}${RED}presets are sourced as shell code; only use trusted presets from trusted directories.${ALL_OFF} @@ -560,7 +569,8 @@ parse_skip_codec_parameter() { # Contract: # The sourced preset MUST set the bash array `ffargs`. # Presets may reference variables from the caller (encode_one) such as: -# output_pixel_format, input_pixel_format, input_codec +# output_pixel_format, input_pixel_format, input_codec, input_frame_rate, +# input_fps, output_gop_size load_preset() { local preset="$1" # Strict allowlist: only alphanumerics, hyphens, and underscores are permitted. @@ -923,12 +933,16 @@ encode_one() { # 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 \ + -show_entries stream=codec_name,pix_fmt,r_frame_rate \ -of default=nk=1:nw=1 "file:$file" 2>/dev/null) - local input_codec input_pixel_format + local input_codec input_pixel_format input_frame_rate input_fps + input_codec=$(printf '%s\n' "$probe_out" | sed -n '1p') + debug "Input codec:" "$input_codec" + input_pixel_format=$(printf '%s\n' "$probe_out" | sed -n '2p') + input_frame_rate=$(printf "%s\n" "$probe_out" | sed -n '3p') if [[ -z "$input_codec" ]]; then error "Could not determine input video codec:" "$file" @@ -938,6 +952,10 @@ encode_one() { error "Could not determine input pixel format:" "$file" return $EXIT_RUNTIME_FAILURE fi + if [[ -z "$input_frame_rate" ]]; then + error "Could not determine input frame rate:" "$file" + return $EXIT_RUNTIME_FAILURE + fi if [[ -v SKIP_CODECS["$input_codec"] ]]; then msg "Skipping (codec excluded):" "$input_codec" @@ -975,6 +993,31 @@ encode_one() { output_pixel_format="yuv${subsampling}${depth}" debug "Selected output pixel format:" "$output_pixel_format" + # Convert ffprobe frame rate fractions like 30000/1001 to rounded integer FPS + # values for simpler encoder heuristics and logging. + input_fps=$( + awk -F/ ' + { + if ($2) + printf "%.0f\n", $1 / $2 + else + printf "%.0f\n", $1 + } + ' <<<"$input_frame_rate" + ) + debug "Calculated input fps:" "$input_fps" + + # Use a larger GOP for better compression efficiency while keeping seek + # performance reasonable. A common rule-of-thumb is roughly five seconds of + # video per GOP, capped at 300 frames. + local output_gop_size + output_gop_size=$(((10 * input_fps) / 2)) + if [[ "$output_gop_size" -ge 300 ]]; then + debug "Calculated GOP size exceeds 300 frames, capping to 300" + output_gop_size=300 + fi + debug "Calculated output GOP size:" "$output_gop_size" + load_preset "$PRESET_NAME" local extension directory tmp base stem @@ -1027,6 +1070,9 @@ encode_one() { verbose "Input codec:" "$input_codec" verbose "Input pixel format:" "$input_pixel_format" verbose "Output pixel format:" "$output_pixel_format" + verbose "Input frame rate:" "$input_frame_rate" + verbose "Input FPS:" "$input_fps" + verbose "Output GOP size:" "$output_gop_size" verbose "Command:" "${cmd[*]}" return $EXIT_OK fi diff --git a/transcode.sh.1 b/transcode.sh.1 index 8fe1e90..9a4c894 100644 --- a/transcode.sh.1 +++ b/transcode.sh.1 @@ -195,6 +195,32 @@ normalisation (e.g.\& The recommended output pixel format, automatically derived from the input to preserve chroma subsampling (4:2:0, 4:2:2, 4:4:4) and bit depth (8, 10, 12, 16\-bit). +.TP +.B input_frame_rate +The raw frame rate of the input video stream as reported by +.BR ffprobe (1), +expressed as a fraction (e.g.\& +.IR 30000/1001 , +.IR 25/1 , +.IR 60/1 ). +.TP +.B input_fps +The input frame rate rounded to the nearest integer (e.g.\& +.IR 30 , +.IR 25 , +.IR 60 ). +Derived from +.B input_frame_rate +and provided as a convenience for encoder heuristics that require a whole +number. +.TP +.B output_gop_size +The recommended GOP (Group of Pictures) size for the output, derived from +.B input_fps +as approximately five seconds of video (capped at 300 frames). +Use this in presets to set +.B \-g +for encoders that accept an explicit GOP size. .RE .IP Before the preset's |
