diff options
| author | Dennis Fink | 2026-05-14 09:56:14 +0200 |
|---|---|---|
| committer | Dennis Fink | 2026-05-14 09:56:14 +0200 |
| commit | dc3d2cb05800d866972b7ba32022428e8474beaa (patch) | |
| tree | ee82bb4327456004b934293b996ad186c98e5c17 | |
| parent | 1674b2212e3606c3e74f454f066d5253c53b274d (diff) | |
| download | transcode.sh-dc3d2cb05800d866972b7ba32022428e8474beaa.tar.gz transcode.sh-dc3d2cb05800d866972b7ba32022428e8474beaa.zip | |
feat(size-report): replace savings log with status reports
Rename the old savings log feature to size report across the CLI,
configuration, documentation, man page, and bash completion. The new
`--size-report` option keeps the existing `-s` shorthand, while
`--saving`, `--saving-file`, and the `[saving]` config section are
replaced by `--size-report`, `--size-report-file`, and `[size_report]`.
Extend reporting beyond successful encodes. The TSV log now gains a
leading `status` column and records encoded files, codec skips,
only-if-larger skips, and failed inputs. Size-report output also prints
per-file feedback for encoded and skipped-larger files, including
human-readable original/new sizes, bytes saved, and saved percentage.
Track batch-level report counters and print an end-of-run summary for
multi-file runs with encoded/skipped/failed counts, total bytes saved,
and the overall weighted saving percentage across successful encodes.
Document the new TSV schema in the README and man page, update examples
and config snippets, and adjust bash completion for the renamed options.
BREAKING CHANGE: `--saving` is now `--size-report`. `--saving-file` is
now `--size-report-file`. `[saving]` in config.toml is now
`[size_report]`. The TSV log gains a leading `status` column and may
contain skipped and failed rows in addition to successful encodes.
| -rw-r--r-- | README.md | 38 | ||||
| -rwxr-xr-x | transcode.sh | 287 | ||||
| -rw-r--r-- | transcode.sh.1 | 50 | ||||
| -rw-r--r-- | transcode.sh.bash-completion | 14 |
4 files changed, 271 insertions, 118 deletions
@@ -18,8 +18,8 @@ replaces the original atomically on success. untouched (e.g. skip files already encoded as AV1). - **Only-if-smaller mode** (`-l`) — discard the re-encoded file if it is larger than the original. -- **Savings log** (`-s`) — append filename, original size, new size and - percentage saved to a log file after each successful encode. +- **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. - **Nice** — runs ffmpeg at niceness 19 by default to avoid starving other processes; configurable with `-N`. @@ -146,9 +146,9 @@ nice = 10 [skip] codecs = ["av1", "hevc"] -[saving] +[size_report] enabled = true -file = "transcode_savings" +file = "transcode_size_report" [output] verbose = false @@ -162,8 +162,8 @@ quiet = false | `[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` | +| `[size_report]` | `enabled` | boolean | `-s` / `--no-size-report` | +| `[size_report]` | `file` | string | `--size-report-file` | | `[output]` | `verbose` | boolean | `-v` | | `[output]` | `quiet` | boolean | `-q` | @@ -198,8 +198,9 @@ transcode.sh [OPTION] [--] FILE... | `-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`, `--size-report` | Print per-file size feedback and write a status TSV log | +| `--no-size-report` | Disable a size report enabled in `config.toml` | +| `--size-report-file PATH`, `--size-report-file=PATH` | Size report TSV path (default: `transcode_size_report`) | | `-S LIST`, `--skip-codec LIST`, `--skip-codec=LIST` | Codecs to skip (comma/space/colon separated) | | `-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 | @@ -217,8 +218,8 @@ transcode.sh [OPTION] [--] FILE... # Transcode a single file with the default preset transcode.sh video.mp4 -# Transcode a list of files and record size savings -transcode.sh --saving --encodefile list.txt +# Transcode a list of files and print/write a size report +transcode.sh --size-report --encode-file list.txt # Dry-run with verbose output to inspect detected formats transcode.sh --dry-run --verbose my_movie.mkv @@ -233,6 +234,23 @@ transcode.sh --nice 10 --continue *.mp4 transcode.sh --config-file ~/profiles/fast.toml input.mp4 ``` +## Size report TSV + +When `--size-report` is enabled, the TSV log uses this schema: + +```tsv +status filename original_bytes new_bytes saved_pct +``` + +The `status` column is one of `encoded`, `skipped_codec`, `skipped_larger`, +or `failed`. Failed rows may leave `new_bytes` and `saved_pct` empty when no +encoded output exists. `skipped_larger` rows record the temporary output size +and therefore usually have a negative `saved_pct`; the original file is kept. + +For runs with more than one input file, `--size-report` also prints an end +summary with encoded/skipped/failed counts, total bytes saved, and the overall +weighted saving percentage across successful encodes. + ## Debugging ```sh diff --git a/transcode.sh b/transcode.sh index 08ed14e..1fd1fe0 100755 --- a/transcode.sh +++ b/transcode.sh @@ -40,9 +40,9 @@ # [skip] # codecs = ["av1", "hevc"] # -# [saving] +# [size_report] # enabled = false -# file = "transcode_savings" +# file = "transcode_size_report" # # CLI flags always take precedence over config file values. # Requires tomlq (https://github.com/nicowillis/tomlq) when config.toml @@ -140,9 +140,8 @@ HWACCEL_VALUE="auto" NICE_VALUE=19 ONLY_IF_SMALLER=0 PRESET_NAME="default" -SAVING=0 -SAVING_FILE="transcode_savings" -SAVING_FILE_EXPLICIT=0 +SIZE_REPORT=0 +SIZE_REPORT_FILE="transcode_size_report" VERIFY_OUTPUT=1 declare -A SKIP_CODECS @@ -164,11 +163,20 @@ _CLI_NICE=0 _CLI_ONLY_IF_SMALLER=0 _CLI_PRESET=0 _CLI_QUIET=0 -_CLI_SAVING=0 -_CLI_SAVING_FILE=0 +_CLI_SIZE_REPORT=0 +_CLI_SIZE_REPORT_FILE=0 _CLI_VERBOSE=0 _CLI_VERIFY_OUTPUT=0 +# Values for size report +_REPORT_ENCODING_FAILED=0 +_REPORT_INTEGRITY_FAILED=0 +_REPORT_SKIPPED_CODEC=0 +_REPORT_SKIPPED_LARGER=0 +_REPORT_SUCCEEDED=0 +_REPORT_TOTAL_ORIGINAL_BYTES=0 +_REPORT_TOTAL_SAVED_BYTES=0 + ############################################################################### # EXIT CODES ############################################################################### @@ -234,39 +242,40 @@ ${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 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 - ${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 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 + ${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}--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}${BLUE}Savings log options:${ALL_OFF} - ${BOLD}${YELLOW}-s, --saving${ALL_OFF} log filesize savings to a TSV file - ${BOLD}${YELLOW}--saving-file${ALL_OFF} ${BOLD}${GREEN}FILE${ALL_OFF} path for the savings log (default: ${BOLD}${RED}%s${ALL_OFF}) +${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}${BLUE}Output options:${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}-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} @@ -292,7 +301,7 @@ ${BOLD}${BLUE}Examples:${ALL_OFF} %s -n -N 10 my_movie.mkv\n" \ "$SCRIPTNAME" "$VERSION" "$DESCRIPTION" \ "$SCRIPTNAME" \ - "$PRESET_NAME" "$NICE_VALUE" "$SAVING_FILE" \ + "$PRESET_NAME" "$NICE_VALUE" "$SIZE_REPORT_FILE" \ "$PRESET_DIR" "$CONFIG_FILE" \ "$SCRIPTNAME" "$SCRIPTNAME" "$SCRIPTNAME" } @@ -348,6 +357,12 @@ resolve_path() { readlink -f -- "$1" 2>/dev/null || realpath -- "$1" } +# Format a byte count into a human-readable IEC size. Uses numfmt so values are +# displayed with binary units such as K, M, G, etc. +format_filesize() { + numfmt --to=iec -- "$1" +} + ############################################################################### # TERMINAL COLOR SETUP # @@ -554,20 +569,26 @@ load_config() { fi done < <(tomlq -r '.skip.codecs[]?' "$CONFIG_FILE" 2>/dev/null) - # [saving] enabled - if [[ $_CLI_SAVING -eq 0 ]]; then - val=$(_tomlq_get '.saving.enabled // empty') && [[ -n "$val" ]] && { - debug "Config: saving.enabled =" "$val" - [[ "$val" == "true" ]] && SAVING=1 || SAVING=0 - } + # [size_report] enabled + if [[ $_CLI_SIZE_REPORT -eq 0 ]]; then + val=$(_tomlq_get '.size_report.enabled // empty') + if [[ -n "$val" ]]; then + debug "Config: size_report.enabled =" "$val" + if [[ "$val" == "true" ]]; then + SIZE_REPORT=1 + else + SIZE_REPORT=0 + fi + fi fi - # [saving] file - if [[ $_CLI_SAVING_FILE -eq 0 ]]; then - val=$(_tomlq_get '.saving.file // empty') && [[ -n "$val" ]] && { - debug "Config: saving.file =" "$val" - SAVING_FILE="$val" - } + # [size_report] file + if [[ $_CLI_SIZE_REPORT_FILE -eq 0 ]]; then + val=$(_tomlq_get '.size_report.file // empty') + if [[ -n "$val" ]]; then + debug "Config: size_report.file =" "$val" + SIZE_REPORT_FILE="$val" + fi fi # [output] verbose @@ -878,17 +899,17 @@ while [[ $# -gt 0 ]]; do _CLI_NICE=1 shift ;; - -s | --saving) - SAVING=1 - _CLI_SAVING=1 + -s | --size-report) + SIZE_REPORT=1 + _CLI_SIZE_REPORT=1 shift ;; - --no-saving) - SAVING=0 - _CLI_SAVING=1 + --no-size-report) + SIZE_REPORT=0 + _CLI_SIZE_REPORT=1 shift ;; - --saving-file) + --size-report-file) [[ $# -ge 2 ]] || { error "Missing value for $1" exit $EXIT_USAGE_ERROR @@ -897,19 +918,17 @@ while [[ $# -gt 0 ]]; do error "Value for $1 must not be empty" exit $EXIT_USAGE_ERROR } - SAVING_FILE="$2" - SAVING_FILE_EXPLICIT=1 - _CLI_SAVING_FILE=1 + SIZE_REPORT_FILE="$2" + _CLI_SIZE_REPORT_FILE=1 shift 2 ;; - --saving-file=*) + --size-report-file=*) [[ -n "${1#*=}" ]] || { - error "Value for --saving-file must not be empty" + error "Value for --size-report-file must not be empty" exit $EXIT_USAGE_ERROR } - SAVING_FILE="${1#*=}" - SAVING_FILE_EXPLICIT=1 - _CLI_SAVING_FILE=1 + SIZE_REPORT_FILE="${1#*=}" + _CLI_SIZE_REPORT_FILE=1 shift ;; -S | --skip-codec) @@ -1016,8 +1035,8 @@ debug " nice:" "$NICE_VALUE" debug " only-if-smaller:" "$ONLY_IF_SMALLER" debug " preset:" "$PRESET_NAME" debug " quiet:" "$QUIET" -debug " saving-file:" "$SAVING_FILE" -debug " saving:" "$SAVING" +debug " size-report-file:" "$SIZE_REPORT_FILE" +debug " size-report:" "$SIZE_REPORT" debug " skip-codecs:" "${joined# }" debug " verbose:" "$VERBOSE" debug " verify-output:" "$VERIFY_OUTPUT" @@ -1035,8 +1054,8 @@ command -v nice >/dev/null 2>&1 || { exit $EXIT_MISSING_DEPENDENCY } -if [[ $SAVING_FILE_EXPLICIT -eq 1 && $SAVING -eq 0 ]]; then - warn "--saving-file has no effect without --saving" +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 < -20 || NICE_VALUE > 19)); then @@ -1064,6 +1083,79 @@ cleanup() { } trap cleanup EXIT +# Calculate integer percentage saved from original/new byte counts. +calculate_saved_percentage() { + local original_bytes="$1" + local new_bytes="$2" + + if [[ -n "$original_bytes" && -n "$new_bytes" && "$original_bytes" -gt 0 ]]; then + printf '%s\n' "$(((original_bytes - new_bytes) * 100 / original_bytes))" + else + printf '0\n' + fi +} + +# Append one row to the size report TSV, creating the header when needed. +# Empty byte/percentage fields are allowed for failures where no output size exists. +append_size_report_row() { + [[ $SIZE_REPORT -eq 1 && $DRY_RUN -eq 0 ]] || return $EXIT_OK + + local status="$1" + local filename="$2" + local original_bytes="${3:-}" + local new_bytes="${4:-}" + local saved_pct="${5:-}" + + if [[ ! -f "$SIZE_REPORT_FILE" ]]; then + printf "status\tfilename\toriginal_bytes\tnew_bytes\tsaved_pct\n" >"$SIZE_REPORT_FILE" + fi + + if [[ -n "$saved_pct" ]]; then + saved_pct="${saved_pct}%" + fi + + printf "%s\t%s\t%s\t%s\t%s\n" "$status" "$filename" "$original_bytes" "$new_bytes" "$saved_pct" >>"$SIZE_REPORT_FILE" +} + +# Print per-file size feedback after an encode attempt that produced an output file. +print_size_report_feedback() { + [[ $SIZE_REPORT -eq 1 ]] || return $EXIT_OK + + local status="$1" + local original_bytes="$2" + local new_bytes="$3" + local saved_bytes="$4" + local saved_pct="$5" + + msg "Size report [$status]:" \ + "$(format_filesize "$original_bytes") → $(format_filesize "$new_bytes")," \ + "$(format_filesize "$saved_bytes") saved (${saved_pct}%)" +} + +# Print the end-of-run size report summary for batch runs. +print_size_report_summary() { + local amount_total_files="$1" + + [[ $SIZE_REPORT -eq 1 && $QUIET -eq 0 && $amount_total_files -gt 1 ]] || return $EXIT_OK + + local skipped_total failed_total average_saved_pct + skipped_total=$((_REPORT_SKIPPED_CODEC + _REPORT_SKIPPED_LARGER)) + failed_total=$((_REPORT_ENCODING_FAILED + _REPORT_INTEGRITY_FAILED)) + + if [[ $_REPORT_TOTAL_ORIGINAL_BYTES -gt 0 ]]; then + average_saved_pct=$((_REPORT_TOTAL_SAVED_BYTES * 100 / _REPORT_TOTAL_ORIGINAL_BYTES)) + else + average_saved_pct=0 + fi + + printf "\n${BOLD}${BLUE}Size report summary:${ALL_OFF}\n" + printf " Encoded: %s\n" "$_REPORT_SUCCEEDED" + printf " Skipped: %s\n" "$skipped_total" + printf " Failed: %s\n" "$failed_total" + printf " Total bytes saved: %s (%s bytes)\n" "$(format_filesize "$_REPORT_TOTAL_SAVED_BYTES")" "$_REPORT_TOTAL_SAVED_BYTES" + printf " Average saved: %s%%\n" "$average_saved_pct" +} + # Encode a single media file according to PRESET_NAME. # # Arguments: @@ -1078,7 +1170,7 @@ trap cleanup EXIT # 4) Select an output pixel format that preserves chroma subsampling/bit depth. # 5) Load preset ffargs. # 6) Run ffmpeg into a temp file in the same directory. -# 7) Optionally keep only-if-smaller; optionally log savings; then replace. +# 7) Optionally keep only-if-smaller; optionally report size status; then replace. # # Return: # EXIT_OK on success or if skipped; EXIT_RUNTIME_FAILURE on failure. @@ -1091,12 +1183,26 @@ encode_one() { return $EXIT_OK fi + # filename (path without extension) is used only for the size report log. + # Note: only the last extension is stripped, so multi-dot names like + # "my.show.s01e01.mkv" are logged as "my.show.s01e01". + local filename + if [[ "$input_file" == *.* ]]; then + filename="${input_file%.*}" + else + filename="$input_file" + fi + msg "Processing [$current_file_index/$amount_total_files]:" "$input_file" if [[ ! -f "$input_file" ]]; then + ((_REPORT_ENCODING_FAILED++)) + append_size_report_row "failed" "$filename" error "Not found:" "$input_file" return $EXIT_RUNTIME_FAILURE fi if [[ ! -r "$input_file" ]]; then + ((_REPORT_ENCODING_FAILED++)) + append_size_report_row "failed" "$filename" error "File is not readable:" "$input_file" return $EXIT_RUNTIME_FAILURE fi @@ -1117,19 +1223,27 @@ encode_one() { input_frame_rate=$(printf "%s\n" "$probe_out" | sed -n '3p') if [[ -z "$input_codec" ]]; then + ((_REPORT_ENCODING_FAILED++)) + append_size_report_row "failed" "$filename" "$(filesize "$input_file")" error "Could not determine input video codec:" "$input_file" return $EXIT_RUNTIME_FAILURE fi if [[ -z "$input_pixel_format" ]]; then + ((_REPORT_ENCODING_FAILED++)) + append_size_report_row "failed" "$filename" "$(filesize "$input_file")" error "Could not determine input pixel format:" "$input_file" return $EXIT_RUNTIME_FAILURE fi if [[ -z "$input_frame_rate" ]]; then + ((_REPORT_ENCODING_FAILED++)) + append_size_report_row "failed" "$filename" "$(filesize "$input_file")" error "Could not determine input frame rate:" "$input_file" return $EXIT_RUNTIME_FAILURE fi if [[ -v SKIP_CODECS["$input_codec"] ]]; then + ((_REPORT_SKIPPED_CODEC++)) + append_size_report_row "skipped_codec" "$filename" "$(filesize "$input_file")" msg "Skipping (codec excluded):" "$input_codec" return $EXIT_OK fi @@ -1191,20 +1305,14 @@ encode_one() { load_preset "$PRESET_NAME" local extension directory temporary_output_file base stem - # filename (path without extension) is used only for the savings log. - # Note: only the last extension is stripped, so multi-dot names like - # "my.show.s01e01.mkv" are logged as "my.show.s01e01". - local filename base="${input_file##*/}" if [[ "$base" == *.* ]]; then extension="${base##*.}" stem="${base%.*}" - filename="${input_file%.*}" else extension="" stem="$base" - filename="$input_file" fi directory=$(dirname -- "$input_file") @@ -1267,11 +1375,14 @@ encode_one() { if [[ $VERIFY_OUTPUT -eq 1 ]]; then debug "Verifying output integrity:" "$temporary_output_file" if ! ffprobe -v error "file:$temporary_output_file" >/dev/null 2>&1; then + ((_REPORT_INTEGRITY_FAILED++)) + append_size_report_row "failed" "$filename" "$(filesize "$input_file")" "$(filesize "$temporary_output_file")" error "Output failed integrity check:" "$temporary_output_file" rm -f -- "$temporary_output_file" if [[ $CONTINUE_ON_FAIL -eq 1 ]]; then return $EXIT_RUNTIME_FAILURE else + print_size_report_summary "$amount_total_files" exit $EXIT_RUNTIME_FAILURE fi fi @@ -1282,36 +1393,36 @@ encode_one() { original_filesize=$(filesize "$input_file") new_filesize=$(filesize "$temporary_output_file") + local filesize_percentage filesize_difference + filesize_difference=$((original_filesize - new_filesize)) + filesize_percentage=$(calculate_saved_percentage "$original_filesize" "$new_filesize") + # If requested, keep the original if the new file is larger. if [[ $ONLY_IF_SMALLER -eq 1 && $new_filesize -gt $original_filesize ]]; then + ((_REPORT_SKIPPED_LARGER++)) + append_size_report_row "skipped_larger" "$filename" "$original_filesize" "$new_filesize" "$filesize_percentage" + print_size_report_feedback "skipped_larger" "$original_filesize" "$new_filesize" "$filesize_difference" "$filesize_percentage" msg "Did not replace (new file is larger):" "$input_file" rm -f -- "$temporary_output_file" return $EXIT_OK fi - local filesize_percentage filesize_difference - filesize_difference=$((original_filesize - new_filesize)) - - if [[ $original_filesize -gt 0 ]]; then - filesize_percentage=$((filesize_difference * 100 / original_filesize)) - else - filesize_percentage=0 - fi - # Write a header line if the savings file does not yet exist. - if [[ ! -f "$SAVING_FILE" ]]; then - printf "filename\toriginal_bytes\tnew_bytes\tsaved_pct\n" >"$SAVING_FILE" - else - printf "%s\t%s\t%s\t%s%%\n" "$filename" "$original_filesize" "$new_filesize" "$filesize_percentage" >>"$SAVING_FILE" - fi - fi + append_size_report_row "encoded" "$filename" "$original_filesize" "$new_filesize" "$filesize_percentage" + print_size_report_feedback "encoded" "$original_filesize" "$new_filesize" "$filesize_difference" "$filesize_percentage" mv -f -- "$temporary_output_file" "$input_file" + ((_REPORT_SUCCEEDED++)) + _REPORT_TOTAL_ORIGINAL_BYTES=$((_REPORT_TOTAL_ORIGINAL_BYTES + original_filesize)) + _REPORT_TOTAL_SAVED_BYTES=$((_REPORT_TOTAL_SAVED_BYTES + filesize_difference)) msg "Replaced:" "$input_file" else + ((_REPORT_ENCODING_FAILED++)) + append_size_report_row "failed" "$filename" "$(filesize "$input_file")" error "Encode FAILED:" "$input_file" if [[ $CONTINUE_ON_FAIL -eq 1 ]]; then return $EXIT_RUNTIME_FAILURE # fail this but keep going else + print_size_report_summary "$amount_total_files" exit $EXIT_RUNTIME_FAILURE fi fi @@ -1342,4 +1453,6 @@ else done fi +print_size_report_summary "$total" + exit $STATUS diff --git a/transcode.sh.1 b/transcode.sh.1 index 17d9e29..c95f74e 100644 --- a/transcode.sh.1 +++ b/transcode.sh.1 @@ -126,16 +126,20 @@ with the given niceness value. Defaults to .BR 19 (lowest priority). .TP -.BR \-s ", " \-\-saving -After each successful encode, append a line to the savings log file -recording the filename, original size in bytes, new size in bytes and the -percentage saved. +.BR \-s ", " \-\-size\-report +Print per-file size feedback after successful encodes and size-based skips, +then write a TSV status log for encoded, skipped and failed files. For runs +with more than one input file, print a final summary with encoded, skipped +and failed counts, total bytes saved and the overall saved percentage. .TP -.BR \-\-saving\-file " \fIPATH\fR, " \-\-saving\-file= \fIPATH\fR -Path to the savings log file written by -.BR \-\-saving . +.BR \-\-no\-size\-report +Disable size reporting from the configuration file. +.TP +.BR \-\-size\-report\-file " \fIPATH\fR, " \-\-size\-report\-file= \fIPATH\fR +Path to the TSV status log written by +.BR \-\-size\-report . Defaults to -.B transcode_savings +.B transcode_size_report in the current working directory. .TP .BR \-S " \fILIST\fR, " \-\-skip\-codec " \fILIST\fR, " \-\-skip\-codec= \fILIST\fR @@ -222,6 +226,25 @@ which prints every command as it is executed. .B XDG_CONFIG_HOME Base directory for user configuration. Defaults to .BR $HOME/.config . +.SH SIZE REPORT TSV +When +.BR \-\-size\-report +is enabled, the TSV log uses this schema: +.PP +.RS +.nf +status filename original_bytes new_bytes saved_pct +.fi +.RE +.PP +The status column is one of +.BR encoded , +.BR skipped_codec , +.BR skipped_larger +or +.BR failed . +Failed rows may leave output-size fields empty when no encoded output exists. +.PP .SH FILES .TP .IR "${XDG_CONFIG_HOME:-$HOME/.config}/transcode.sh/presets/<name>.sh" @@ -350,14 +373,13 @@ Defaults to Array of strings. Merged with any codecs from .BR \-S . .TP -.B [saving] enabled +.B [size_report] enabled Boolean. Equivalent to -.BR \-s . +.BR \-s / \-\-no\-size\-report . .TP -.B [saving] file +.B [size_report] file String. Equivalent to -.BR \-\-saving\-file . -.TP +.BR \-\-size\-report\-file . .B [output] verbose Boolean. Equivalent to .BR \-v . @@ -409,7 +431,7 @@ transcode.sh video.mp4 .fi .RE .PP -Transcode a batch of files listed in a file and log size savings: +Transcode a batch of files listed in a file and print/write a size report: .PP .RS .nf diff --git a/transcode.sh.bash-completion b/transcode.sh.bash-completion index 2749882..7c6eefa 100644 --- a/transcode.sh.bash-completion +++ b/transcode.sh.bash-completion @@ -55,7 +55,7 @@ _transcode_sh() { _filedir return ;; - --saving-file=*) + --size-report-file=*) cur="${cur#*=}" _filedir return @@ -84,7 +84,7 @@ _transcode_sh() { # --------------------------------------------------------------------------- # Handle VALUE after short form (-f, -N, -S, -p) and - # long space form (--encode-file, --nice, --saving-file, --skip-codec, --preset). + # long space form (--encode-file, --nice, --size-report-file, --skip-codec, --preset). # $prev is the option word, $cur is the value being typed. # --------------------------------------------------------------------------- case "$prev" in @@ -100,7 +100,7 @@ _transcode_sh() { COMPREPLY=($(compgen -W "$nice_vals" -- "$cur")) return ;; - --saving-file) + --size-report-file) _filedir return ;; @@ -145,15 +145,15 @@ _transcode_sh() { --no-continue --no-hwaccel --no-only-if-smaller - --no-saving + --no-size-report --no-verify-output --only-if-smaller --preset --preset= --quiet - --saving - --saving-file - --saving-file= + --size-report + --size-report-file + --size-report-file= --skip-codec --skip-codec= --verbose |
