Skip to content

Instantly share code, notes, and snippets.

@slowmove
Created September 26, 2025 07:48
Show Gist options
  • Save slowmove/69ee602f34eafc1087cdcbd38cebbbac to your computer and use it in GitHub Desktop.
Save slowmove/69ee602f34eafc1087cdcbd38cebbbac to your computer and use it in GitHub Desktop.
detect_and_crop_letterbox_enhanced
#!/bin/bash
# Check if input file is provided
if [ $# -lt 1 ]; then
echo "Usage: $0 input_video [output_video] [time_position]"
echo " time_position: Optional time to extract frame (default: 00:00:01)"
exit 1
fi
INPUT_VIDEO="$1"
OUTPUT_VIDEO="${2:-${INPUT_VIDEO%.*}_cropped.${INPUT_VIDEO##*.}}"
TIME_POSITION="${3:-00:00:01}"
# Create a temporary directory
TEMP_DIR=$(mktemp -d)
FRAME_PATH="$TEMP_DIR/frame.png"
echo "Extracting frame at $TIME_POSITION mark..."
ffmpeg -y -ss "$TIME_POSITION" -i "$INPUT_VIDEO" -vframes 1 -q:v 2 "$FRAME_PATH"
# Get video dimensions
VIDEO_INFO=$(ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 "$INPUT_VIDEO")
VIDEO_WIDTH=$(echo $VIDEO_INFO | cut -d'x' -f1)
VIDEO_HEIGHT=$(echo $VIDEO_INFO | cut -d'x' -f2)
echo "Video dimensions: ${VIDEO_WIDTH}x${VIDEO_HEIGHT}"
# Detect black bars using ffmpeg's cropdetect filter with adjusted parameters
echo "Detecting letterbox dimensions..."
# Use more sensitive detection parameters
CROP_INFO=$(ffmpeg -i "$FRAME_PATH" -vf "cropdetect=limit=24:round=2:reset=0" -f null - 2>&1 | grep -o "crop=[0-9:]*" | tail -1)
if [ -z "$CROP_INFO" ]; then
echo "Could not detect crop dimensions automatically. Trying with more aggressive settings..."
# Try with even more aggressive settings
CROP_INFO=$(ffmpeg -i "$FRAME_PATH" -vf "cropdetect=limit=16:round=2:reset=0" -f null - 2>&1 | grep -o "crop=[0-9:]*" | tail -1)
if [ -z "$CROP_INFO" ]; then
echo "Still could not detect crop dimensions. Let's try manual detection..."
# Save a debug image with grid for manual inspection
DEBUG_FRAME="$TEMP_DIR/debug_frame.png"
ffmpeg -i "$FRAME_PATH" -vf "drawgrid=width=32:height=32:thickness=1:[email protected]" "$DEBUG_FRAME"
echo "Debug frame saved to: $DEBUG_FRAME"
open "$DEBUG_FRAME"
# Extract top and bottom black bar heights using ImageMagick if available
if command -v convert &> /dev/null; then
echo "Using ImageMagick for detection..."
# Create temporary files for analysis
TOP_SCAN="$TEMP_DIR/top_scan.txt"
BOTTOM_SCAN="$TEMP_DIR/bottom_scan.txt"
# Scan top and bottom edges for black pixels
convert "$FRAME_PATH" -crop "${VIDEO_WIDTH}x20+0+0" -colorspace RGB -format "%[fx:mean]" info: > "$TOP_SCAN"
convert "$FRAME_PATH" -crop "${VIDEO_WIDTH}x20+0+$((VIDEO_HEIGHT-20))" -colorspace RGB -format "%[fx:mean]" info: > "$BOTTOM_SCAN"
TOP_VALUE=$(cat "$TOP_SCAN")
BOTTOM_VALUE=$(cat "$BOTTOM_SCAN")
echo "Top brightness: $TOP_VALUE, Bottom brightness: $BOTTOM_VALUE"
# If brightness is below threshold, consider it a black bar
THRESHOLD=0.05
TOP_CROP=0
BOTTOM_CROP=0
if (( $(echo "$TOP_VALUE < $THRESHOLD" | bc -l) )); then
# Estimate top black bar height
for ((y=1; y<VIDEO_HEIGHT/3; y+=5)); do
BRIGHTNESS=$(convert "$FRAME_PATH" -crop "${VIDEO_WIDTH}x1+0+$y" -colorspace RGB -format "%[fx:mean]" info:)
if (( $(echo "$BRIGHTNESS > $THRESHOLD" | bc -l) )); then
TOP_CROP=$y
break
fi
done
fi
if (( $(echo "$BOTTOM_VALUE < $THRESHOLD" | bc -l) )); then
# Estimate bottom black bar height
for ((y=VIDEO_HEIGHT-1; y>VIDEO_HEIGHT*2/3; y-=5)); do
BRIGHTNESS=$(convert "$FRAME_PATH" -crop "${VIDEO_WIDTH}x1+0+$y" -colorspace RGB -format "%[fx:mean]" info:)
if (( $(echo "$BRIGHTNESS > $THRESHOLD" | bc -l) )); then
BOTTOM_CROP=$((VIDEO_HEIGHT-y-1))
break
fi
done
fi
if [ $TOP_CROP -gt 0 ] || [ $BOTTOM_CROP -gt 0 ]; then
NEW_HEIGHT=$((VIDEO_HEIGHT-TOP_CROP-BOTTOM_CROP))
CROP_INFO="crop=${VIDEO_WIDTH}:${NEW_HEIGHT}:0:${TOP_CROP}"
echo "Manually detected crop: $CROP_INFO"
else
echo "No significant letterboxing detected."
exit 1
fi
else
echo "ImageMagick not found. Cannot perform manual detection."
exit 1
fi
fi
fi
echo "Detected crop settings: $CROP_INFO"
# Extract crop dimensions
CROP_WIDTH=$(echo $CROP_INFO | sed -E 's/crop=([0-9]+):.*/\1/')
CROP_HEIGHT=$(echo $CROP_INFO | sed -E 's/crop=[0-9]+:([0-9]+):.*/\1/')
CROP_X=$(echo $CROP_INFO | sed -E 's/crop=[0-9]+:[0-9]+:([0-9]+):.*/\1/')
CROP_Y=$(echo $CROP_INFO | sed -E 's/crop=[0-9]+:[0-9]+:[0-9]+:([0-9]+).*/\1/')
echo "Crop dimensions: ${CROP_WIDTH}x${CROP_HEIGHT} at position (${CROP_X},${CROP_Y})"
# Verify the crop makes sense (not cropping too much)
MIN_PERCENT=50
if (( $(echo "$CROP_WIDTH * 100 / $VIDEO_WIDTH < $MIN_PERCENT" | bc -l) )) || \
(( $(echo "$CROP_HEIGHT * 100 / $VIDEO_HEIGHT < $MIN_PERCENT" | bc -l) )); then
echo "Warning: Crop dimensions seem extreme (removing more than ${MIN_PERCENT}% of the frame)."
echo "Original: ${VIDEO_WIDTH}x${VIDEO_HEIGHT}, Crop: ${CROP_WIDTH}x${CROP_HEIGHT}"
read -p "Continue anyway? (y/n): " CONFIRM
if [[ $CONFIRM != "y" ]]; then
echo "Aborted by user."
exit 1
fi
fi
# Apply the crop to the entire video
echo "Applying crop to entire video..."
ffmpeg -i "$INPUT_VIDEO" -vf "$CROP_INFO" -c:v libx264 -crf 18 -preset medium -c:a copy "$OUTPUT_VIDEO"
# Clean up
rm -rf "$TEMP_DIR"
echo "Processing complete. Output saved to: $OUTPUT_VIDEO"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment