Last active
June 15, 2026 17:10
-
-
Save gphg/e20597972a3c69f7f174dfd8281bd8b5 to your computer and use it in GitHub Desktop.
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 | |
| # ========================================== | |
| # Configuration & Helpers | |
| # ========================================== | |
| VIDEO_IN="$1" | |
| AUDIO_SRC="${2:-${VIDEO_IN%.*}.mp4}" | |
| TARGET_MB="${3:-20}" | |
| # Check dependencies | |
| for cmd in ffmpeg ffprobe bc; do | |
| if ! command -v $cmd &> /dev/null; then | |
| echo "Error: '$cmd' is not installed." | |
| exit 1 | |
| fi | |
| done | |
| if [[ -z "$VIDEO_IN" || ! -f "$VIDEO_IN" || ! -f "$AUDIO_SRC" ]]; then | |
| echo "Usage: $0 <input_video> [audio_source_mp4] [target_size_mb]" | |
| exit 1 | |
| fi | |
| # Opus bitrates constraints | |
| MIN_BITRATE=12000 | |
| MAX_BITRATE=512000 | |
| TARGET_SIZE_BYTES=$(echo "$TARGET_MB * 1048576" | bc) | |
| TEMP_FILE=".tmp_remux_$$.webm" | |
| trap 'rm -f "$TEMP_FILE"' EXIT INT TERM | |
| # ========================================== | |
| # 1. Analyze Components | |
| # ========================================== | |
| VIDEO_SIZE_BYTES=$(ffprobe -v error -select_streams v:0 -show_entries packet=size -bitexact -of csv=p=0 "$VIDEO_IN" | awk '{sum+=$1} END {print sum}') | |
| DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$VIDEO_IN") | |
| VIDEO_MB=$(echo "scale=2; $VIDEO_SIZE_BYTES / 1048576" | bc) | |
| echo "Video Size: $VIDEO_MB MB and $DURATION seconds." | |
| # Estimate initial bitrate to get into the ballpark | |
| AVAILABLE_BYTES=$(echo "$TARGET_SIZE_BYTES - $VIDEO_SIZE_BYTES" | bc) | |
| INITIAL_BITRATE=$(echo "($AVAILABLE_BYTES * 8) / $DURATION" | bc) | |
| # ========================================== | |
| # 2. Refined Binary Search | |
| # ========================================== | |
| # Define range +/- 20% of the initial estimate | |
| LOW=$(echo "$INITIAL_BITRATE * 0.8" / 1 | bc) | |
| HIGH=$(echo "$INITIAL_BITRATE * 1.2" / 1 | bc) | |
| BEST_BITRATE=$INITIAL_BITRATE | |
| ITERATIONS=16 # Sufficient for bitrate precision | |
| echo "Optimizing audio bitrate with ${ITERATIONS}-iteration refinement..." | |
| [ "$LOW" -lt "$MIN_BITRATE" ] && LOW=$MIN_BITRATE | |
| [ "$HIGH" -gt "$MAX_BITRATE" ] && HIGH=$MAX_BITRATE | |
| # If constraints make search range invalid, just set to max or min | |
| [ "$LOW" -gt "$HIGH" ] && LOW=$MAX_BITRATE && HIGH=$MAX_BITRATE | |
| for ((i=1; i<=ITERATIONS; i++)); do | |
| MID=$(( (LOW + HIGH) / 2 )) | |
| # Run ffmpeg and capture the exit code | |
| ffmpeg -nostdin -y -i "$VIDEO_IN" -i "$AUDIO_SRC" \ | |
| -map 0:v:0 -map 1:a \ | |
| -c:v copy -c:a libopus -b:a "${MID}" \ | |
| "$TEMP_FILE" > /dev/null 2>&1 | |
| _EXIT_CODE=$? | |
| # Only process results if ffmpeg succeeded | |
| if [ $_EXIT_CODE -eq 0 ]; then | |
| FILE_SIZE=$(stat -c%s "$TEMP_FILE") | |
| if [ "$FILE_SIZE" -le "$TARGET_SIZE_BYTES" ]; then | |
| DIFF=$(( TARGET_SIZE_BYTES - FILE_SIZE )) | |
| BEST_BITRATE=$MID | |
| printf "\r[Iter %2d/%d] Captured best: %dbps... (diff: %d)" "$i" "$ITERATIONS" "$MID" "$DIFF" | |
| LOW=$(( MID + 1 )) | |
| else | |
| HIGH=$(( MID - 1 )) | |
| fi | |
| else | |
| echo "Warning: FFmpeg failed at bitrate ${MID}. Skipping iteration." | |
| # Optionally adjust your bounds or break if the failure is critical | |
| HIGH=$(( MID - 1 )) | |
| fi | |
| done | |
| echo "" | |
| # Final check | |
| FINAL_SIZE=$(stat -c%s "$TEMP_FILE") | |
| if [ "$FINAL_SIZE" -gt "$TARGET_SIZE_BYTES" ]; then | |
| echo "Warning: Even at the lowest bitrate, the file exceeds the target size." | |
| fi | |
| # ========================================== | |
| # 3. Final Output | |
| # ========================================== | |
| FILENAME=$(basename "${VIDEO_IN%.*}") | |
| OUTPUT="audio_optimized_${FILENAME}_${TARGET_MB}MB.webm" | |
| ffmpeg -nostdin -y -i "$VIDEO_IN" -i "$AUDIO_SRC" \ | |
| -map 0:v:0 -map 1:a \ | |
| -c:v copy -c:a libopus -b:a "${BEST_BITRATE}" \ | |
| "$OUTPUT" > /dev/null 2>&1 | |
| BEST_BITRATE_KB=$(( BEST_BITRATE / 1024 )) | |
| FINAL_MB=$(echo "scale=2; $(stat -c%s "$OUTPUT") / 1048576" | bc) | |
| echo "------------------------------------------------" | |
| echo "Success! Final Size: ${FINAL_MB} MB (target: ${TARGET_MB} MB)" | |
| echo "Selected Audio Bitrate: ${BEST_BITRATE_KB} kbps (${BEST_BITRATE})" | |
| echo "Saved to: $OUTPUT" | |
| echo "------------------------------------------------" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment