diff options
Diffstat (limited to '')
| -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 |
