summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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