You'll get even smaller files. I've been replacing my media library with AV1 when I see titles available. Still nice though. I used to have a script to automatically download all videos from a few youtube channels. Would have been useful to combine it with this.
by RoofPig to on Mar 24, 2026 5:56 AM | 2 comments
RoofPig
Oops. I meant to make this a reply to the post with the h.265 script instead of its own post.
dennisn
idk, I remember difficulty playing av1 files on "older" hardware.

#!/usr/bin/env bash # ======================================== # INTELLIGENT HEVC VIDEO COMPRESSION PIPELINE # ---------------------------------------------------------------------------- # This script compresses video files to H.265 (HEVC) with Opus audio. # It is designed for archival purposes where file size reduction is # prioritized while maintaining perceptual quality. # # Features: # - Two-pass or Single-pass libx265 encoding with smart bitrate targeting. # - Automatic audio normalization (Peak detection -> Target dB). # - Black bar detection and cropping. # - Logarithmic bitrate targeting (soft compression for high bitrate sources) # OR Manual scaling factor targeting (strict size reduction). # # Usage: # ./script.sh [scale_factor] <input_file> # # Examples: # ./script.sh video.mkv (Use auto bitrate curve) # ./script.sh 2.0 video.mkv (Target 1/2 of original size) # ./script.sh 1.5 video.mkv (Target 1/1.5 of original size) # # Author: Clort (clort81) # Requires: ffmpeg, ffprobe, bc, coreutils # ======================================== # Strict Mode: # -e: Exit immediately if a command exits with a non-zero status. # -u: Treat unset variables as an error. # -o pipefail: Return value of a pipeline is the status of the last command to exit with a non-zero status. set -euo pipefail # ---------------------------- # GLOBAL CONSTANTS # ---------------------------- readonly MAX_WIDTH=960 # Target Width (Quarter HD) readonly MAX_HEIGHT=540 # Target Height readonly VIDEO_CODEC="libx265" # HEVC Encoder readonly AUDIO_BITRATE="12k" # Opus Bitrate (Speech optimized) readonly ENCODING_PRESET="fast" # Speed/Efficiency tradeoff # Audio Normalization Target (dBFS) # The script detects the current maximum volume and calculates gain # to reach this target level. # Example: -20 targets -20dB. 0 targets 0dB (Maximum digital volume). readonly TARGET_VOL="-18" # Encoding Parameters for Single Pass # ref=4: Decent reference frames for compression without killing speed. # subme=6: High quality motion estimation. # aq-mode=1: Adaptive Quantization (varies bitrate based on frame complexity). readonly ENCODING_PARAMS="scc=2:weightp=1:weightb=1:ref=4:bframes=8:rc-lookahead=60:aq-mode=1:deblock=-3,-3:subme=6:rd=4" # Scaling Filter # force_original_aspect_ratio=decrease ensures we fit within the box without stretching. readonly SCALE_FILTER="scale='min(${MAX_WIDTH},iw):min(${MAX_HEIGHT},ih)':force_original_aspect_ratio=decrease" # Bitrate Mapping Constants (Auto-Mode only) readonly MIN_TARGET_KBPS=110 readonly MAX_TARGET_KBPS=192 # ---------------------------- # HELPER FUNCTIONS # ---------------------------- # Usage: die "Error message" die() { echo "ERROR: $*" >&2 exit 1 } # Get duration in seconds (float) # Relies on ffprobe returning standard format=duration get_duration() { local file="$1" local dur dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$file" 2>/dev/null) || return 1 case "$dur" in ""|"N/A") return 1 ;; esac printf '%s\n' "$dur" } # Get video stream bitrate (bps) get_stream_bitrate_bps() { local file="$1" local br # -select_streams v:0 selects only the first video stream br=$(ffprobe -v quiet -select_streams v:0 -show_entries stream=bit_rate -of csv=p=0 "$file" 2>/dev/null) || return 1 [[ "$br" =~ ^[0-9]+$ ]] && (( br > 0 )) && echo "$br" } # Get global container bitrate (bps) get_global_bitrate_bps() { local file="$1" local br br=$(ffprobe -v quiet -show_entries format=bit_rate -of csv=p=0 "$file" 2>/dev/null) || return 1 [[ "$br" =~ ^[0-9]+$ ]] && (( br > 0 )) && echo "$br" } # Fallback: compute bitrate from file size / duration compute_bitrate_from_filesize() { local file="$1" local dur dur=$(get_duration "$file") || return 1 if ! printf '%s\n' "$dur" | grep -E '^[0-9]*\.?[0-9]+$' >/dev/null; then return 1 fi if (( $(bc -l <<< "$dur <= 0") )); then return 1 fi local size # stat -c%s returns size in bytes (Linux standard) size=$(stat -c%s "$file" 2>/dev/null) || return 1 (( size > 0 )) || return 1 local br_bps br_bps=$(bc -l <<< "($size * 8) / $dur" 2>/dev/null) || return 1 br_bps=$(printf "%.0f" "$br_bps" 2>/dev/null) [[ -n "$br_bps" ]] && (( br_bps > 0 )) && echo "$br_bps" } # Determine best estimate for input bitrate get_effective_video_bitrate_bps() { local file="$1" echo " -> Attempting stream-level bitrate extraction..." >&2 if get_stream_bitrate_bps "$file"; then return 0; fi echo " -> Falling back to global container bitrate..." >&2 if get_global_bitrate_bps "$file"; then return 0; fi echo " -> Falling back to file size / duration calculation..." >&2 if compute_bitrate_from_filesize "$file"; then return 0; fi die "All methods failed to determine input bitrate for $file" } # Log-curve calculation for Auto-Mode # Formula: y = 488.4 * ln(x + 1000) - 3211 calculate_target_bitrate_kbps() { local x="$1" # Sanitize: ensure we only pass numbers to bc x=$(printf '%s' "$x" | tr -cd '0-9.') [[ -z "$x" || "$x" == "." ]] && x=800 local y # bc logic: calculate y, then scale=0 to round to nearest integer y=$(bc -l <<< " scale = 10 x = $x if (x + 1000 <= 0) { 0 } else { y = 488.4 * l(x + 1000) - 3211 if (y < 0) 0 else y } scale = 0 (y + 0.5) / 1 " 2>/dev/null) # Fallback if bc fails (math domain error or missing utility) if ! [[ "$y" =~ ^[0-9]+$ ]]; then y=280 fi # Clamp values if (( y < MIN_TARGET_KBPS )); then y=$MIN_TARGET_KBPS elif (( y > MAX_TARGET_KBPS )); then y=$MAX_TARGET_KBPS fi echo "$y" } # Analyze audio volume to determine normalization gain. # Uses the global TARGET_VOL to calculate the necessary adjustment. # Avoids awk; uses grep, sed, sort, and bc. detect_loudness_gain() { local input_file="$1" local duration duration=$(ffprobe -v quiet -show_entries format=duration -of csv=p=0 "$input_file" 2>/dev/null) if [[ -z "$duration" ]]; then echo "0.0" return fi local raw="" local len=60 # Sample 1 minute chunks # Check if file is short (< 15 mins = 900 seconds) if (( $(echo "$duration < 900" | bc -l 2>/dev/null || echo 0) )); then echo " -> File is short (< 15m), analyzing full volume..." >&2 # Note: We must use || true on the pipeline because grep returns 1 if no match found, # which would crash the script due to set -e / pipefail. raw=$(ffmpeg -i "$input_file" -af "volumedetect" -map 0:a -f null /dev/null 2>&1 |& grep 'max_volume' || true) else echo " -> File is long, sampling volume at 5 points..." >&2 local sample_positions=(0 0.25 0.5 0.75) local i=1 local pos t for pos in "${sample_positions[@]}"; do t=$(printf "%.3f" "$(echo "$duration * $pos" | bc -l)") raw+=$(ffmpeg -ss "$t" -t "$len" -i "$input_file" -af "volumedetect" -map 0:a -f null /dev/null 2>&1 |& grep 'max_volume' || true) raw+=$'\n' printf " %d" $i >&2 ((i++)) done # Final sample at end t=$(printf "%.3f" "$(echo "$duration - $len" | bc -l)") if (( $(echo "$t < 0" | bc -l 2>/dev/null || echo 0) )); then t=0 fi raw+=$(ffmpeg -ss "$t" -t "$len" -i "$input_file" -af "volumedetect" -map 0:a -f null /dev/null 2>&1 |& grep 'max_volume' || true) printf " %d\n" $i >&2 fi if [[ -z "$raw" ]]; then echo "0.0" return fi # Find loudest section (max_volume closest to 0) # 1. Extract number (e.g., -12.3) # 2. Sort numerically (-20, -15, -5) # 3. Tail gets -5 (loudest) local max_db max_db=$(echo "$raw" | sed -n 's/.*max_volume: \(-[0-9.]*\) dB.*/\1/p' | sort -n | tail -n 1) if [[ -z "$max_db" ]] || ! [[ "$max_db" =~ ^-?[0-9]+(\.[0-9]+)?$ ]]; then echo "0.0" return fi # Calculate gain: Gain = Target - Current_Max # Example: Current=-10dB, Target=-20dB -> Gain = -10dB (Reduction) # Example: Current=-30dB, Target=-20dB -> Gain = +10dB (Amplification) local gain gain=$(bc -l <<< "${TARGET_VOL} - ${max_db}") printf "%.2f" "$gain" } # Detect black bars via cropdetect # Samples frames at intervals to find consistent crop area detect_crop_filter() { local file="$1" local duration duration=$(get_duration "$file") || return 1 local dur_sec=${duration%.*} if (( dur_sec < 3 )); then return fi local num_samples=8 local interval=$(( dur_sec / (num_samples + 1) )) (( interval = interval > 0 ? interval : 1 )) local min_x1=99999 min_y1=99999 max_x2=-1 max_y2=-1 local found_any=0 for ((i = 1; i <= num_samples; i++)); do local ss=$(( i * interval )) local crop_line # cropdetect=limit=24: Only crop if black level is below 24. crop_line=$(ffmpeg -nostdin -hide_banner -ss "$ss" -i "$file" -t 1 -vf "cropdetect=limit=24:round=2:reset=0" -f null - 2>&1 | grep "crop=" | tail -n1) || continue if [[ -n "$crop_line" ]]; then # Extract params: "crop=1920:1080:0:0" -> "1920:1080:0:0" local crop_params="${crop_line#*crop=}" local w h x y IFS=':' read -r w h x y <<< "$crop_params" [[ "$w" =~ ^[0-9]+$ ]] && [[ "$h" =~ ^[0-9]+$ ]] && [[ "$x" =~ ^[0-9]+$ ]] && [[ "$y" =~ ^[0-9]+$ ]] || continue local x1=$((x)) local y1=$((y)) local x2=$((x + w)) local y2=$((y + h)) # Accumulate min/max for bounding box (( min_x1 = x1 < min_x1 ? x1 : min_x1 )) (( min_y1 = y1 < min_y1 ? y1 : min_y1 )) (( max_x2 = x2 > max_x2 ? x2 : max_x2 )) (( max_y2 = y2 > max_y2 ? y2 : max_y2 )) found_any=1 fi done if (( !found_any )); then return; fi local final_w=$(( max_x2 - min_x1 )) local final_h=$(( max_y2 - min_y1 )) local orig_w orig_h orig_w=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=p=0 "$file") orig_h=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=p=0 "$file") # Only apply crop if it actually reduces dimensions if (( final_w < orig_w || final_h < orig_h )); then echo "crop=${final_w}:${final_h}:${min_x1}:${min_y1}" fi } # ======================================== # MAIN SCRIPT # ======================================== # --- Argument Parsing --- SCALE_FACTOR="" input_file="" if [[ $# -eq 2 ]]; then SCALE_FACTOR="$1" input_file="$2" elif [[ $# -eq 1 ]]; then input_file="$1" else echo "Usage: $0 [scale_factor] <video_file>" >&2 echo " scale_factor: Optional. Divides target bitrate by this number." >&2 echo " e.g. 2.0 creates a file 1/2 the original size." >&2 exit 1 fi # --- Validation --- [[ -f "$input_file" ]] || die "Input file not found: $input_file" [[ -r "$input_file" ]] || die "Input file not readable: $input_file" filename=$(basename "$input_file") outfile="${filename%.*}.mkv" outdir="out" origdir="orig" mkdir -p "$outdir" "$origdir" echo "=======================================" echo "ANALYZING: $filename" echo "=======================================" # STEP 1: Extract Input Video Bitrate input_bitrate_bps=$(get_effective_video_bitrate_bps "$input_file") input_bitrate_kbps=$(( input_bitrate_bps / 1000 )) echo " -> Input video bitrate: ${input_bitrate_kbps} kbps" # STEP 1.5: Skip Check if (( input_bitrate_kbps < 256 )); then echo " -> SKIP: Input bitrate (${input_bitrate_kbps} kbps) is below threshold (256 kbps)." echo " -> File considered already compressed or low quality." exit 0 fi # STEP 2: Compute Target Bitrate if [[ -n "$SCALE_FACTOR" ]]; then # Manual Scaling Mode # Validate scale_factor is a positive number if ! [[ "$SCALE_FACTOR" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then die "Scale factor must be a positive number (e.g., 1, 1.5, 2)." fi target_bitrate_kbps=$(echo "scale=0; $input_bitrate_kbps / $SCALE_FACTOR" | bc) echo " -> MANUAL MODE: Scaling bitrate by 1/${SCALE_FACTOR}." else # Auto Curve Mode target_bitrate_kbps=$(calculate_target_bitrate_kbps "$input_bitrate_kbps") echo " -> AUTO MODE: Logarithmic targeting." fi target_bitrate_bps=$(( target_bitrate_kbps * 1000 )) echo " -> Target video bitrate: ${target_bitrate_kbps} kbps" # STEP 3: Estimate Output Size duration_sec=$(get_duration "$input_file") || die "Cannot determine duration" total_bitrate_kbps=$(( target_bitrate_kbps + 12 )) # +12k audio (approx) estimated_size_bytes=$(bc -l <<< "($total_bitrate_kbps * 1000 * $duration_sec) / 8" 2>/dev/null) estimated_size_bytes=${estimated_size_bytes%.*} # truncate float input_size_mb=$(bc -l <<< "$(stat -c%s "$input_file") / (1024*1024)" 2>/dev/null) input_size_mb=${input_size_mb%.*} estimated_size_mb=$(bc -l <<< "scale=1; $estimated_size_bytes / (1024*1024)" 2>/dev/null) [[ -z "$estimated_size_mb" ]] && estimated_size_mb="0.0" echo " -> Input file size: ${input_size_mb} MB" echo " -> Estimated output size: ${estimated_size_mb} MB" # STEP 4: Audio & Crop Analysis volume_gain=$(detect_loudness_gain "$input_file") echo " -> Audio gain for ${TARGET_VOL} dB target: ${volume_gain}dB" crop_filter="" if crop_result=$(detect_crop_filter "$input_file"); then crop_filter="$crop_result" fi if [[ -n "$crop_filter" ]]; then echo " -> Crop filter: $crop_filter" else echo " -> No significant black bars detected." fi # STEP 5: Build Filter Chain if [[ -n "$crop_filter" ]]; then video_filter="${crop_filter},${SCALE_FILTER},mpdecimate" else video_filter="$SCALE_FILTER" fi audio_filter="volume=${volume_gain}dB" passlogfile="${outdir}/${filename%.*}.log" echo "--------------------------------------------------" echo "ENCODING PLAN:" echo " Video filter: $video_filter" echo " Audio filter: $audio_filter" echo " Output: ${outdir}/${outfile}" echo "--------------------------------------------------" # Prompt user before proceeding (Destructive action) #read -r -p "Proceed with encoding and archiving original? (y/N): " confirm #if [[ ! "$confirm" =~ ^[Yy]$ ]]; then # echo "Aborted by user." # exit 0 #fi #-c:v "$VIDEO_CODEC" -b:v "${target_bitrate_bps}" \ # ---------------------------- # SINGLE-PASS HEVC ENCODING # ---------------------------- echo "ENCODE: Creating final output..." if ! ffmpeg -y -hide_banner -i "$input_file" \ -filter:v "$video_filter" \ -filter:a "$audio_filter" \ -preset "$ENCODING_PRESET" \ -fps_mode vfr \ -c:v "$VIDEO_CODEC" -crf "32" \ -g 600 -keyint_min 600 \ -x265-params "$ENCODING_PARAMS" \ -c:a libopus -b:a "$AUDIO_BITRATE" -application voip -frame_duration 20 -compression_level 10 \ -movflags +faststart -tune fastdecode -metadata handler_name="spumco" \ "${outdir}/${outfile}"; then echo "FAILURE: Encoding failed!" >&2 exit 1 fi # Cleanup logs rm -f "${passlogfile}"* # ---------------------------- # POST-PROCESSING # ---------------------------- echo "=======================================" echo "SUCCESS: Output written to ${outdir}/${outfile}" echo "Archiving original to ${origdir}/..." mv "$input_file" "$origdir/" printf '\a'; sleep 0.5; printf '\a'; sleep 0.5; printf '\a' echo "Done." exit 0
by brothchild to on Mar 22, 2026 10:14 PM | 5 comments
Anonymous
Who's Daniel?
brothchild
this has been a work of putzing around since about 2015, revised many times. One other note is it's optimized for static scenes, things with slides, or static backgrounds.
brothchild
Daniel alliterates with derpy.
Anonymous
It's pretty impressive. For reference this was one of the results: https://x0.at/mHyU.mkv It's only 11 MB.
Anonymous
Oh and I also love how you described it on IRC so I will copy/paste that description here. <brothchild> so ya it's my 'archival' thing <brothchild> just good enough quality, min size <brothchild> other people want to archive 'good quality' fuck that. <brothchild> i want videos we can email <brothchild> videos we can morse code with a shard of glass <brothchild> videos that fit in an image header :P

Submit a New Post to Video Encoding and Archival