Last active
March 9, 2026 08:16
-
-
Save kibotu/51f52443be7d6ae6b8f618b04d9599ea to your computer and use it in GitHub Desktop.
Convert mp4 or mov to webp and gif for optimized for medium articles.
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 | |
| # | |
| # convert_to_webp.sh — Convert video files to animated GIF and WebP. | |
| # | |
| # DESCRIPTION | |
| # Converts all .mp4 and .mov files in the script's directory to both | |
| # animated GIF (Medium-compatible) and animated WebP (higher quality). | |
| # Output files are placed alongside the source videos. | |
| # | |
| # Aspect ratio is preserved. Videos are scaled to TARGET_WIDTH (default 362px), | |
| # which matches Medium's inline image width. | |
| # | |
| # USAGE | |
| # ./convert_to_webp.sh | |
| # | |
| # OPTIONS | |
| # None. Edit the configuration variables below to adjust output. | |
| # | |
| # CONFIGURATION | |
| # TARGET_WIDTH Output width in pixels (default: 362). Height is auto-calculated. | |
| # FPS Frame rate for the output animations (default: 24). | |
| # GIF_QUALITY Palette size 2–256 (default: 256). Lower = smaller file, worse color. | |
| # GIF_DITHER Dithering algorithm (default: sierra2_4a). Options: none, bayer, | |
| # floyd_steinberg, sierra2, sierra2_4a. | |
| # WEBP_QUALITY Lossy quality 0–100 (default: 82). Higher = better quality, larger file. | |
| # WEBP_METHOD Compression effort 0–6 (default: 4). Higher = slower, better compression. | |
| # | |
| # REQUIREMENTS | |
| # ffmpeg — video decoding, frame extraction, GIF encoding | |
| # ffprobe — reading source video metadata (bundled with ffmpeg) | |
| # img2webp — animated WebP assembly (part of the 'webp' package) | |
| # | |
| # macOS: brew install ffmpeg webp | |
| # Ubuntu: sudo apt install ffmpeg webp | |
| # | |
| # EXAMPLES | |
| # # Place videos in the docs/ folder and run: | |
| # bash docs/convert_to_webp.sh | |
| # | |
| # # Output for input "demo.mp4": | |
| # # docs/demo.gif — upload to Medium | |
| # # docs/demo.webp — use in README / web pages | |
| # | |
| set -euo pipefail | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| TARGET_WIDTH=362 | |
| FPS=24 | |
| GIF_QUALITY=256 | |
| GIF_DITHER=sierra2_4a | |
| WEBP_QUALITY=82 | |
| WEBP_METHOD=4 | |
| for cmd in ffmpeg ffprobe img2webp; do | |
| if ! command -v "$cmd" &>/dev/null; then | |
| echo "Error: '$cmd' is required but not found in PATH." >&2 | |
| echo "Install with: brew install ffmpeg webp (macOS)" >&2 | |
| echo " sudo apt install ffmpeg webp (Ubuntu)" >&2 | |
| exit 1 | |
| fi | |
| done | |
| for src in "$SCRIPT_DIR"/*.mp4 "$SCRIPT_DIR"/*.mov; do | |
| [ -f "$src" ] || continue | |
| filename="$(basename "$src")" | |
| basename="${filename%.*}" | |
| output_gif="$SCRIPT_DIR/${basename}.gif" | |
| output_webp="$SCRIPT_DIR/${basename}.webp" | |
| echo "=== Converting: ${filename} ===" | |
| src_height=$(ffprobe -v quiet -select_streams v:0 \ | |
| -show_entries stream=height -of csv=p=0 "$src") | |
| src_width=$(ffprobe -v quiet -select_streams v:0 \ | |
| -show_entries stream=width -of csv=p=0 "$src") | |
| echo " Source: ${src_width}x${src_height}" | |
| # --- Animated GIF (Medium-compatible) --- | |
| echo " Creating animated GIF (${FPS} FPS, width ${TARGET_WIDTH})..." | |
| ffmpeg -hide_banner -loglevel warning -y \ | |
| -i "$src" \ | |
| -vf "fps=${FPS},scale=${TARGET_WIDTH}:-2:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=${GIF_QUALITY}:stats_mode=diff[p];[s1][p]paletteuse=dither=${GIF_DITHER}" \ | |
| -loop 0 \ | |
| "$output_gif" | |
| gif_size=$(du -h "$output_gif" | cut -f1) | |
| gif_dims=$(ffprobe -v quiet -select_streams v:0 \ | |
| -show_entries stream=width,height -of csv=p=0 "$output_gif") | |
| echo " GIF: ${basename}.gif (${gif_size}, ${gif_dims})" | |
| # --- Animated WebP (higher quality fallback) --- | |
| echo " Creating animated WebP..." | |
| FRAME_DIR="$(mktemp -d)" | |
| mkdir -p "$FRAME_DIR/frames" | |
| ffmpeg -hide_banner -loglevel warning -y \ | |
| -i "$src" \ | |
| -vf "scale=${TARGET_WIDTH}:-2:flags=lanczos" \ | |
| -r "$FPS" \ | |
| -pix_fmt rgb24 \ | |
| "$FRAME_DIR/frames/frame_%06d.png" | |
| frame_count=$(ls "$FRAME_DIR/frames"/frame_*.png 2>/dev/null | wc -l | tr -d ' ') | |
| frame_duration=$((1000 / FPS)) | |
| img2webp_args=() | |
| for frame in "$FRAME_DIR/frames"/frame_*.png; do | |
| img2webp_args+=(-d "$frame_duration" -lossy -q "$WEBP_QUALITY" -m "$WEBP_METHOD" "$frame") | |
| done | |
| img2webp -loop 0 "${img2webp_args[@]}" -o "$output_webp" | |
| rm -rf "$FRAME_DIR" | |
| webp_size=$(du -h "$output_webp" | cut -f1) | |
| echo " WebP: ${basename}.webp (${webp_size})" | |
| echo "" | |
| done | |
| echo "Done!" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment