Created
July 29, 2026 21:42
-
-
Save MarcusOtter/cb4d9a7bf39fc502632210a233edf5f7 to your computer and use it in GitHub Desktop.
Compress any video to 8mb similar to 8mb.video. Usage: "./to8mb.sh input.mp4"
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -Eeuo pipefail | |
| export LC_ALL=C | |
| usage() { | |
| printf 'Usage: %s INPUT [OUTPUT]\n' "$(basename "$0")" >&2 | |
| exit 2 | |
| } | |
| die() { | |
| printf 'Error: %s\n' "$*" >&2 | |
| exit 1 | |
| } | |
| [[ $# -ge 1 && $# -le 2 ]] || usage | |
| input=$1 | |
| output=${2:-} | |
| limit_bytes=${LIMIT_BYTES:-8000000} | |
| target_bytes=$((limit_bytes - 100000)) | |
| [[ -f "$input" ]] || die "input file not found: $input" | |
| (( limit_bytes > 500000 )) || die "LIMIT_BYTES is too small" | |
| for cmd in ffmpeg ffprobe awk stat mktemp; do | |
| command -v "$cmd" >/dev/null || die "missing dependency: $cmd" | |
| done | |
| if [[ -z "$output" ]]; then | |
| dir=$(dirname -- "$input") | |
| file=$(basename -- "$input") | |
| stem=${file%.*} | |
| output="$dir/${stem}_8mb.mp4" | |
| fi | |
| [[ "$input" != "$output" ]] || die "input and output paths must differ" | |
| video_stream=$(ffprobe -v error -select_streams V:0 \ | |
| -show_entries stream=index -of csv=p=0 "$input" | head -n1) | |
| [[ -n "$video_stream" ]] || die "no video stream found" | |
| duration=$(ffprobe -v error -show_entries format=duration \ | |
| -of default=noprint_wrappers=1:nokey=1 "$input" | head -n1) | |
| awk -v d="$duration" 'BEGIN { exit !(d > 0) }' || die "could not determine video duration" | |
| has_audio=$(ffprobe -v error -select_streams a:0 \ | |
| -show_entries stream=index -of csv=p=0 "$input" | head -n1 || true) | |
| # Reserve about 1.5% for MP4/container overhead. | |
| total_kbps=$(awk -v b="$target_bytes" -v d="$duration" \ | |
| 'BEGIN { printf "%d", (b * 8 / d / 1000) * 0.985 }') | |
| if [[ -n "$has_audio" ]]; then | |
| if (( total_kbps >= 1200 )); then audio_kbps=128 | |
| elif (( total_kbps >= 600 )); then audio_kbps=96 | |
| elif (( total_kbps >= 300 )); then audio_kbps=64 | |
| elif (( total_kbps >= 180 )); then audio_kbps=48 | |
| else audio_kbps=32 | |
| fi | |
| else | |
| audio_kbps=0 | |
| fi | |
| video_kbps=$((total_kbps - audio_kbps)) | |
| if (( video_kbps < 40 && audio_kbps > 0 )); then | |
| audio_kbps=0 | |
| video_kbps=$total_kbps | |
| fi | |
| (( video_kbps >= 10 )) || die "video is too long to fit meaningfully within 8 MB" | |
| if (( video_kbps >= 2200 )); then max_dim=1920 | |
| elif (( video_kbps >= 900 )); then max_dim=1280 | |
| elif (( video_kbps >= 500 )); then max_dim=960 | |
| elif (( video_kbps >= 300 )); then max_dim=854 | |
| elif (( video_kbps >= 160 )); then max_dim=640 | |
| else max_dim=426 | |
| fi | |
| vf="scale='min(iw,${max_dim})':'min(ih,${max_dim})':force_original_aspect_ratio=decrease:force_divisible_by=2,setsar=1" | |
| tmpdir=$(mktemp -d) | |
| trap 'rm -rf "$tmpdir"' EXIT | |
| passlog="$tmpdir/pass" | |
| tmp_output="$tmpdir/output.mp4" | |
| encode() { | |
| local bitrate=$1 | |
| rm -f "${passlog}"* "$tmp_output" | |
| ffmpeg -hide_banner -loglevel warning -stats -y -i "$input" \ | |
| -map 0:V:0 -vf "$vf" -fpsmax 30 \ | |
| -c:v libx264 -preset slow -b:v "${bitrate}k" -pix_fmt yuv420p \ | |
| -pass 1 -passlogfile "$passlog" \ | |
| -an -sn -dn -f null /dev/null | |
| audio_args=(-an) | |
| if (( audio_kbps > 0 )); then | |
| audio_args=(-map '0:a:0?' -c:a aac -b:a "${audio_kbps}k" -ac 2) | |
| (( audio_kbps <= 48 )) && audio_args=(-map '0:a:0?' -c:a aac -b:a "${audio_kbps}k" -ac 1) | |
| fi | |
| ffmpeg -hide_banner -loglevel warning -stats -y -i "$input" \ | |
| -map 0:V:0 "${audio_args[@]}" -vf "$vf" -fpsmax 30 \ | |
| -c:v libx264 -preset slow -b:v "${bitrate}k" -pix_fmt yuv420p \ | |
| -pass 2 -passlogfile "$passlog" \ | |
| -sn -dn -map_metadata -1 -map_chapters -1 \ | |
| -movflags +faststart "$tmp_output" | |
| } | |
| printf 'Duration: %.1f s\n' "$duration" | |
| printf 'Video: %d kbps, audio: %d kbps, max dimension: %d px\n' \ | |
| "$video_kbps" "$audio_kbps" "$max_dim" | |
| for attempt in 1 2 3; do | |
| printf 'Encoding pass set %d/3...\n' "$attempt" | |
| encode "$video_kbps" | |
| actual_bytes=$(stat -c %s "$tmp_output") | |
| if (( actual_bytes <= limit_bytes )); then | |
| break | |
| fi | |
| video_kbps=$(awk -v r="$video_kbps" -v max="$limit_bytes" -v got="$actual_bytes" \ | |
| 'BEGIN { n = int(r * (max * 0.98 / got)); if (n < 10) n = 10; print n }') | |
| done | |
| actual_bytes=$(stat -c %s "$tmp_output") | |
| (( actual_bytes <= limit_bytes )) || die "could not get output below 8 MB after 3 attempts" | |
| mv -f -- "$tmp_output" "$output" | |
| printf 'Created: %s (%.2f MB)\n' "$output" "$(awk -v b="$actual_bytes" 'BEGIN { print b / 1000000 }')" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment