Update
This commit is contained in:
310
bash/trim_webm_empty_frames.sh
Executable file
310
bash/trim_webm_empty_frames.sh
Executable file
@@ -0,0 +1,310 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to trim empty frames from start and end of WebM files
|
||||
# Usage: ./trim_webm_empty_frames.sh [directory_path] [sample_rate]
|
||||
|
||||
# Function to display usage
|
||||
usage() {
|
||||
echo "Usage: $0 [directory_path] [sample_rate]"
|
||||
echo " directory_path: Path to directory containing WebM files to trim (default: current directory)"
|
||||
echo " sample_rate: How many frames to sample per second for analysis (default: 2)"
|
||||
echo " Higher values = more accurate but slower (1-10 recommended)"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 # Current directory, sample 2 frames/sec"
|
||||
echo " $0 /path/to/files # Custom directory, default sampling"
|
||||
echo " $0 /path/to/files 4 # Custom directory, sample 4 frames/sec"
|
||||
echo ""
|
||||
echo "This script will:"
|
||||
echo " - Analyze WebM files to find first and last non-empty frames"
|
||||
echo " - Trim away all empty frames from start and end"
|
||||
echo " - Preserve original quality and alpha transparency"
|
||||
echo " - Save trimmed files in a 'trim' subdirectory"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check if ffmpeg is installed
|
||||
if ! command -v ffmpeg &> /dev/null; then
|
||||
echo "Error: ffmpeg is not installed or not in PATH"
|
||||
echo "Please install ffmpeg to use this script"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if bc is available for floating point calculations
|
||||
if ! command -v bc >/dev/null 2>&1; then
|
||||
echo "Warning: 'bc' calculator not found. Using basic arithmetic (less precise)."
|
||||
echo "For better precision, install 'bc': brew install bc (macOS) or apt-get install bc (Linux)"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Set default values
|
||||
DIRECTORY="."
|
||||
SAMPLE_RATE=2
|
||||
|
||||
# Parse arguments
|
||||
if [ $# -eq 0 ]; then
|
||||
DIRECTORY="."
|
||||
elif [ $# -eq 1 ]; then
|
||||
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
|
||||
usage
|
||||
fi
|
||||
DIRECTORY="$1"
|
||||
elif [ $# -eq 2 ]; then
|
||||
DIRECTORY="$1"
|
||||
SAMPLE_RATE="$2"
|
||||
else
|
||||
echo "Error: Too many arguments"
|
||||
usage
|
||||
fi
|
||||
|
||||
# Validate sample rate
|
||||
if ! [[ "$SAMPLE_RATE" =~ ^[1-9][0-9]*$ ]] || [ "$SAMPLE_RATE" -gt 10 ]; then
|
||||
echo "Error: sample_rate must be between 1-10"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if directory exists
|
||||
if [ ! -d "$DIRECTORY" ]; then
|
||||
echo "Error: Directory '$DIRECTORY' does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Convert to absolute path
|
||||
DIRECTORY=$(realpath "$DIRECTORY")
|
||||
echo "Trimming WebM files in: $DIRECTORY"
|
||||
echo "Sample rate: $SAMPLE_RATE frames per second"
|
||||
|
||||
# Create trim output directory
|
||||
TRIM_DIR="$DIRECTORY/trim"
|
||||
if [ ! -d "$TRIM_DIR" ]; then
|
||||
mkdir -p "$TRIM_DIR"
|
||||
echo "Created output directory: $TRIM_DIR"
|
||||
else
|
||||
echo "Using existing output directory: $TRIM_DIR"
|
||||
fi
|
||||
|
||||
# Create temporary directory for frame analysis
|
||||
temp_dir=$(mktemp -d)
|
||||
trap "rm -rf $temp_dir" EXIT
|
||||
|
||||
# Function to check if a frame is empty (returns 0 for empty, 1 for content, 2 for unclear)
|
||||
check_frame_empty() {
|
||||
local frame_file="$1"
|
||||
|
||||
# Check if frame file exists
|
||||
if [ ! -f "$frame_file" ]; then
|
||||
return 2 # unclear/error
|
||||
fi
|
||||
|
||||
# Get file size - very small files are likely empty/transparent
|
||||
file_size=$(stat -f%z "$frame_file" 2>/dev/null || stat -c%s "$frame_file" 2>/dev/null)
|
||||
|
||||
# If file is very small (less than 2KB), likely empty
|
||||
if [ -n "$file_size" ] && [ "$file_size" -lt 2048 ]; then
|
||||
return 0 # definitely empty
|
||||
fi
|
||||
|
||||
# If file is reasonably large, assume it has content
|
||||
if [ "$file_size" -gt 50000 ]; then
|
||||
return 1 # definitely has content
|
||||
fi
|
||||
|
||||
# For medium-sized files, try alpha analysis
|
||||
alpha_info=$(ffmpeg -i "$frame_file" -vf "extractplanes=a" -f null - 2>&1 | grep "mean\|avg" 2>/dev/null)
|
||||
|
||||
if [ -n "$alpha_info" ]; then
|
||||
alpha_mean=$(echo "$alpha_info" | grep -o "mean:[0-9.]*" | cut -d: -f2 | head -1)
|
||||
|
||||
if [ -n "$alpha_mean" ]; then
|
||||
# Check if mean is very low (close to 0 = transparent)
|
||||
is_empty=$(awk "BEGIN {print ($alpha_mean < 2.0) ? 1 : 0}")
|
||||
|
||||
if [ "$is_empty" = "1" ]; then
|
||||
return 0 # definitely empty
|
||||
else
|
||||
return 1 # definitely has content
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Default: assume medium files have some content (conservative approach)
|
||||
return 1
|
||||
}
|
||||
|
||||
# Function to find first non-empty frame
|
||||
find_first_content_frame() {
|
||||
local video_file="$1"
|
||||
local duration="$2"
|
||||
local decoder="$3"
|
||||
|
||||
# Check frames at 0, 0.5, 1.0, 1.5 seconds etc
|
||||
for i in 0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0; do
|
||||
local test_frame="$temp_dir/start_${i}.png"
|
||||
|
||||
ffmpeg -c:v "$decoder" -ss "$i" -i "$video_file" -frames:v 1 -y "$test_frame" -v quiet 2>/dev/null
|
||||
|
||||
if [ -f "$test_frame" ]; then
|
||||
check_frame_empty "$test_frame"
|
||||
if [ $? -eq 1 ]; then
|
||||
echo "$i"
|
||||
rm -f "$test_frame"
|
||||
return 0
|
||||
fi
|
||||
rm -f "$test_frame"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "0"
|
||||
}
|
||||
|
||||
# Function to find last non-empty frame
|
||||
find_last_content_frame() {
|
||||
local video_file="$1"
|
||||
local duration="$2"
|
||||
local decoder="$3"
|
||||
|
||||
# Work backwards from end
|
||||
local dur_int=${duration%.*}
|
||||
|
||||
for i in 0.1 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0; do
|
||||
local check_time=$(echo "$duration - $i" | bc 2>/dev/null || echo "$dur_int")
|
||||
local test_frame="$temp_dir/end_${i}.png"
|
||||
|
||||
ffmpeg -c:v "$decoder" -ss "$check_time" -i "$video_file" -frames:v 1 -y "$test_frame" -v quiet 2>/dev/null
|
||||
|
||||
if [ -f "$test_frame" ]; then
|
||||
check_frame_empty "$test_frame"
|
||||
if [ $? -eq 1 ]; then
|
||||
echo "$check_time"
|
||||
rm -f "$test_frame"
|
||||
return 0
|
||||
fi
|
||||
rm -f "$test_frame"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$duration"
|
||||
}
|
||||
|
||||
# Find all WebM files in the directory
|
||||
WEBM_FILES=($(find "$DIRECTORY" -maxdepth 1 -name "*.webm" -type f))
|
||||
|
||||
# Check if any WebM files were found
|
||||
if [ ${#WEBM_FILES[@]} -eq 0 ]; then
|
||||
echo "No WebM files found in '$DIRECTORY'"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Found ${#WEBM_FILES[@]} WebM file(s) to process"
|
||||
echo ""
|
||||
|
||||
# Counters
|
||||
processed=0
|
||||
skipped=0
|
||||
failed=0
|
||||
|
||||
# Process each WebM file
|
||||
for webm_file in "${WEBM_FILES[@]}"; do
|
||||
count=$((count + 1))
|
||||
base_name=$(basename "$webm_file" .webm)
|
||||
|
||||
echo "[$((processed + skipped + failed + 1))/${#WEBM_FILES[@]}] Processing: $base_name.webm"
|
||||
|
||||
# Check if output file already exists
|
||||
output_file="$TRIM_DIR/${base_name}.webm"
|
||||
if [ -f "$output_file" ]; then
|
||||
echo " → Skipping: Output file already exists"
|
||||
((skipped++))
|
||||
echo ""
|
||||
continue
|
||||
fi
|
||||
|
||||
# Test if file is readable by ffprobe
|
||||
if ! ffprobe -v quiet "$webm_file" >/dev/null 2>&1; then
|
||||
echo " ✗ Error: File cannot be read by ffprobe"
|
||||
((failed++))
|
||||
echo ""
|
||||
continue
|
||||
fi
|
||||
|
||||
# Detect codec type and get video info
|
||||
CODEC_INFO=$(ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name -of csv=p=0 "$webm_file" 2>/dev/null)
|
||||
duration=$(ffprobe -v quiet -show_entries format=duration -of csv=p=0 "$webm_file" 2>/dev/null)
|
||||
|
||||
if [ -z "$duration" ] || [ "$duration" = "N/A" ]; then
|
||||
echo " ✗ Error: Could not get video duration"
|
||||
((failed++))
|
||||
echo ""
|
||||
continue
|
||||
fi
|
||||
|
||||
# Set appropriate decoder
|
||||
if [ "$CODEC_INFO" = "vp9" ]; then
|
||||
DECODER="libvpx-vp9"
|
||||
echo " → VP9 codec detected"
|
||||
else
|
||||
DECODER="libvpx"
|
||||
echo " → VP8 codec detected"
|
||||
fi
|
||||
|
||||
echo " → Duration: ${duration}s"
|
||||
|
||||
# Find content boundaries
|
||||
echo " → Analyzing frames..."
|
||||
start_time=$(find_first_content_frame "$webm_file" "$duration" "$DECODER")
|
||||
end_time=$(find_last_content_frame "$webm_file" "$duration" "$DECODER")
|
||||
|
||||
# Validate times
|
||||
if [ -z "$start_time" ] || [ -z "$end_time" ]; then
|
||||
echo " ✗ Error: Could not determine content boundaries"
|
||||
((failed++))
|
||||
echo ""
|
||||
continue
|
||||
fi
|
||||
|
||||
echo " → Content from ${start_time}s to ${end_time}s"
|
||||
|
||||
# Calculate new duration
|
||||
new_duration=$(echo "$end_time - $start_time" | bc 2>/dev/null || echo "$end_time")
|
||||
|
||||
# Simple trimming check
|
||||
if [ "$start_time" = "0" ] && [ "$end_time" = "$duration" ]; then
|
||||
echo " → No trimming needed, copying original..."
|
||||
cp "$webm_file" "$output_file"
|
||||
if [ $? -eq 0 ]; then
|
||||
echo " ✓ File copied successfully"
|
||||
((processed++))
|
||||
else
|
||||
echo " ✗ Failed to copy file"
|
||||
((failed++))
|
||||
fi
|
||||
else
|
||||
echo " → Trimming (new duration: ${new_duration}s)"
|
||||
|
||||
if ffmpeg -ss "$start_time" -i "$webm_file" -t "$new_duration" -c copy -y "$output_file" -v quiet 2>/dev/null; then
|
||||
echo " ✓ Successfully trimmed"
|
||||
((processed++))
|
||||
else
|
||||
echo " ✗ Failed to trim"
|
||||
((failed++))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
# Summary
|
||||
echo "==================================="
|
||||
echo "Trimming Complete!"
|
||||
echo "Total files processed: $processed"
|
||||
echo "Files skipped (already exist): $skipped"
|
||||
echo "Files failed: $failed"
|
||||
echo "Output directory: $TRIM_DIR"
|
||||
|
||||
if [ $processed -gt 0 ]; then
|
||||
echo ""
|
||||
echo "Trimmed files:"
|
||||
ls -la "$TRIM_DIR"/*.webm 2>/dev/null | while read -r line; do
|
||||
echo " $line"
|
||||
done
|
||||
fi
|
||||
Reference in New Issue
Block a user