summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDennis Fink2026-05-14 09:56:14 +0200
committerDennis Fink2026-05-14 09:56:14 +0200
commitdc3d2cb05800d866972b7ba32022428e8474beaa (patch)
treeee82bb4327456004b934293b996ad186c98e5c17
parent1674b2212e3606c3e74f454f066d5253c53b274d (diff)
downloadtranscode.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.md38
-rwxr-xr-xtranscode.sh287
-rw-r--r--transcode.sh.150
-rw-r--r--transcode.sh.bash-completion14
4 files changed, 271 insertions, 118 deletions
diff --git a/README.md b/README.md
index 3c4053f..85e45b3 100644
--- a/README.md
+++ b/README.md
@@ -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