This commit is contained in:
ct
2025-06-14 23:35:16 +08:00
parent 0f7fa96c40
commit 4547fe61ef
13 changed files with 1404 additions and 51 deletions

BIN
bash/.DS_Store vendored Normal file

Binary file not shown.

347
bash/check_webm_alpha_frames.sh Executable file
View File

@@ -0,0 +1,347 @@
#!/bin/bash
# Script to check all WebM files with alpha for empty start and end frames
# Uses libvpx-vp9 decoder to properly handle alpha channels
# Check if directory parameter is provided
if [ $# -eq 0 ]; then
echo "Usage: sh check_webm_alpha_frames.sh <absolute_path_to_directory>"
echo "Example: sh check_webm_alpha_frames.sh /Users/charlesteh/Desktop/mag-group-1/"
exit 1
fi
# Get the target directory from parameter
target_dir="$1"
# Check if directory exists
if [ ! -d "$target_dir" ]; then
echo "Error: Directory '$target_dir' does not exist"
exit 1
fi
echo "Checking all WebM files in '$target_dir' for empty start and end frames..."
# Check if ffmpeg is installed
if ! command -v ffmpeg &> /dev/null; then
echo "Error: ffmpeg is not installed or not in PATH"
exit 1
fi
# Change to target directory
cd "$target_dir" || {
echo "Error: Cannot access directory '$target_dir'"
exit 1
}
# Count total WebM files
webm_count=$(ls *.webm 2>/dev/null | wc -l)
if [ $webm_count -eq 0 ]; then
echo "No WebM files found in current directory"
exit 0
fi
echo "Found $webm_count WebM file(s) to check"
echo ""
# Create temporary directory for frame extraction
temp_dir=$(mktemp -d)
trap "rm -rf $temp_dir" EXIT
# Function to check if a frame is empty (fully transparent)
check_frame_empty() {
local frame_file="$1"
local frame_name="$2"
# Check if frame file exists
if [ ! -f "$frame_file" ]; then
echo "$frame_name: Frame file not found"
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
echo "$frame_name: EMPTY (tiny file: ${file_size} bytes)"
return 0 # definitely empty
fi
# Use simpler alpha analysis: extract alpha channel and get histogram
alpha_info=$(ffmpeg -i "$frame_file" -vf "extractplanes=a" -f null - 2>&1 | grep "mean\|avg")
if [ -n "$alpha_info" ]; then
# Look for mean value in the output
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
echo "$frame_name: EMPTY (alpha mean: $alpha_mean)"
return 0 # definitely empty
else
echo "$frame_name: Content detected (alpha mean: $alpha_mean)"
return 1 # definitely has content
fi
fi
fi
# If file is reasonably large, assume it has content
if [ "$file_size" -gt 50000 ]; then
echo "$frame_name: Content detected (large file: ${file_size} bytes)"
return 1 # definitely has content
else
echo " ? $frame_name: UNCLEAR - needs manual review (size: ${file_size} bytes)"
return 2 # unclear/unsure
fi
}
# Function to get video duration and frame count
get_video_info() {
local video_file="$1"
# Get duration in seconds
duration=$(ffprobe -vcodec libvpx-vp9 -v quiet -show_entries format=duration -of csv=p=0 "$video_file" 2>/dev/null)
# Get total frame count
frame_count=$(ffprobe -vcodec libvpx-vp9 -v quiet -select_streams v:0 -count_frames -show_entries stream=nb_read_frames -csv=p=0 "$video_file" 2>/dev/null)
echo "$duration $frame_count"
}
# Counter for progress
count=0
files_with_empty_frames=0
files_with_unclear_frames=0
# Arrays to track files with empty frames
files_with_empty_first=()
files_with_empty_last=()
files_with_both_empty=()
# Arrays to track files with unclear frames
files_with_unclear_first=()
files_with_unclear_last=()
files_with_both_unclear=()
files_with_mixed_issues=()
# Loop through all WebM files in current directory
for webm_file in *.webm; do
# Check if file actually exists (in case glob doesn't match)
if [ ! -f "$webm_file" ]; then
continue
fi
# Increment counter
count=$((count + 1))
echo "[$count/$webm_count] Checking: $webm_file"
# 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"
echo ""
continue
fi
# Detect codec type (VP8 or VP9) to use correct decoder - from working script
CODEC_INFO=$(ffprobe -v quiet -select_streams v:0 -show_entries stream=codec_name -of csv=p=0 "$webm_file" 2>/dev/null)
# Set appropriate decoder based on codec
if [ "$CODEC_INFO" = "vp9" ]; then
DECODER="libvpx-vp9"
echo " → Using VP9 decoder"
else
DECODER="libvpx" # Default to VP8 decoder
echo " → Using VP8 decoder"
fi
# Get video info - use direct commands
duration=$(ffprobe -v quiet -show_entries format=duration -of csv=p=0 "$webm_file" 2>/dev/null)
frame_count=$(ffprobe -v quiet -select_streams v:0 -count_frames -show_entries stream=nb_read_frames -of csv=p=0 "$webm_file" 2>/dev/null)
if [ -z "$duration" ] || [ "$duration" = "N/A" ]; then
echo " ✗ Error: Could not get video duration"
echo ""
continue
fi
echo " Duration: ${duration}s, Frames: $frame_count"
# Extract first frame (at 0 seconds) - using proper decoder syntax from working script
first_frame="$temp_dir/first_frame_${count}.png"
ffmpeg -c:v "$DECODER" -i "$webm_file" -vf "select=eq(n\,0)" -frames:v 1 -y "$first_frame" -v quiet 2>/dev/null
# Extract last frame - using proper decoder syntax
last_frame="$temp_dir/last_frame_${count}.png"
# Calculate time for last frame (duration - small offset to ensure we get the last frame)
last_frame_time=$(awk "BEGIN {printf \"%.3f\", $duration - 0.1}")
# Ensure we don't go before 0
if [ $(awk "BEGIN {print ($last_frame_time < 0) ? 1 : 0}") = "1" ]; then
last_frame_time="0"
fi
ffmpeg -c:v "$DECODER" -ss "$last_frame_time" -i "$webm_file" -frames:v 1 -y "$last_frame" -v quiet 2>/dev/null
# Check if frame extraction was successful
if [ ! -f "$first_frame" ] || [ ! -f "$last_frame" ]; then
echo " ✗ Error: Could not extract frames"
echo ""
continue
fi
# Check if frames are empty
has_empty_frames=false
has_unclear_frames=false
has_empty_first=false
has_empty_last=false
has_unclear_first=false
has_unclear_last=false
check_frame_empty "$first_frame" "First frame"
first_result=$?
if [ $first_result -eq 0 ]; then
has_empty_first=true
has_empty_frames=true
elif [ $first_result -eq 2 ]; then
has_unclear_first=true
has_unclear_frames=true
fi
check_frame_empty "$last_frame" "Last frame"
last_result=$?
if [ $last_result -eq 0 ]; then
has_empty_last=true
has_empty_frames=true
elif [ $last_result -eq 2 ]; then
has_unclear_last=true
has_unclear_frames=true
fi
# Add to tracking arrays
if [ "$has_empty_first" = true ] && [ "$has_empty_last" = true ]; then
files_with_both_empty+=("$webm_file")
elif [ "$has_unclear_first" = true ] && [ "$has_unclear_last" = true ]; then
files_with_both_unclear+=("$webm_file")
elif ([ "$has_empty_first" = true ] && [ "$has_unclear_last" = true ]) || ([ "$has_unclear_first" = true ] && [ "$has_empty_last" = true ]); then
files_with_mixed_issues+=("$webm_file")
elif [ "$has_empty_first" = true ]; then
files_with_empty_first+=("$webm_file")
elif [ "$has_empty_last" = true ]; then
files_with_empty_last+=("$webm_file")
elif [ "$has_unclear_first" = true ]; then
files_with_unclear_first+=("$webm_file")
elif [ "$has_unclear_last" = true ]; then
files_with_unclear_last+=("$webm_file")
fi
if [ "$has_empty_frames" = true ]; then
files_with_empty_frames=$((files_with_empty_frames + 1))
fi
if [ "$has_unclear_frames" = true ]; then
files_with_unclear_frames=$((files_with_unclear_frames + 1))
fi
# Clean up temporary frames
rm -f "$first_frame" "$last_frame"
if [ "$has_empty_frames" = true ]; then
echo " ⚠️ WARNING: This file has empty frames!"
elif [ "$has_unclear_frames" = true ]; then
echo " ❓ NOTE: This file has unclear frames that need manual review"
fi
echo ""
done
echo "==================================="
echo "Analysis complete!"
echo "Total files checked: $count"
echo "Files with empty start/end frames: $files_with_empty_frames"
echo "Files with unclear frames (need review): $files_with_unclear_frames"
total_issues=$((files_with_empty_frames + files_with_unclear_frames))
if [ $total_issues -gt 0 ]; then
echo ""
echo "==================================="
echo "DETAILED SUMMARY - FILES NEEDING ATTENTION:"
echo "==================================="
# Files with both first and last frames empty
if [ ${#files_with_both_empty[@]} -gt 0 ]; then
echo ""
echo "🚨 Files with BOTH first AND last frames EMPTY (${#files_with_both_empty[@]} files):"
for file in "${files_with_both_empty[@]}"; do
echo "$(basename "$file")"
done
fi
# Files with only first frame empty
if [ ${#files_with_empty_first[@]} -gt 0 ]; then
echo ""
echo "⚠️ Files with FIRST frame EMPTY (${#files_with_empty_first[@]} files):"
for file in "${files_with_empty_first[@]}"; do
echo "$(basename "$file")"
done
fi
# Files with only last frame empty
if [ ${#files_with_empty_last[@]} -gt 0 ]; then
echo ""
echo "⚠️ Files with LAST frame EMPTY (${#files_with_empty_last[@]} files):"
for file in "${files_with_empty_last[@]}"; do
echo "$(basename "$file")"
done
fi
# Files with both first and last frames unclear
if [ ${#files_with_both_unclear[@]} -gt 0 ]; then
echo ""
echo "❓ Files with BOTH first AND last frames UNCLEAR (${#files_with_both_unclear[@]} files):"
for file in "${files_with_both_unclear[@]}"; do
echo "$(basename "$file")"
done
fi
# Files with only first frame unclear
if [ ${#files_with_unclear_first[@]} -gt 0 ]; then
echo ""
echo "❓ Files with FIRST frame UNCLEAR (${#files_with_unclear_first[@]} files):"
for file in "${files_with_unclear_first[@]}"; do
echo "$(basename "$file")"
done
fi
# Files with only last frame unclear
if [ ${#files_with_unclear_last[@]} -gt 0 ]; then
echo ""
echo "❓ Files with LAST frame UNCLEAR (${#files_with_unclear_last[@]} files):"
for file in "${files_with_unclear_last[@]}"; do
echo "$(basename "$file")"
done
fi
# Files with mixed issues
if [ ${#files_with_mixed_issues[@]} -gt 0 ]; then
echo ""
echo "🔀 Files with MIXED issues (empty + unclear frames) (${#files_with_mixed_issues[@]} files):"
for file in "${files_with_mixed_issues[@]}"; do
echo "$(basename "$file")"
done
fi
echo ""
echo "==================================="
echo "RECOMMENDATIONS:"
echo "🚨 EMPTY frames: Definitely need trimming with ffmpeg"
echo "❓ UNCLEAR frames: Manually review these files"
echo "• Files with both empty first/last frames may be completely problematic"
echo "• Consider using ffmpeg to trim empty frames where confirmed"
else
echo ""
echo "✅ All files appear to have clear content in start and end frames."
fi

74
bash/greenscreen_to_webm.sh Executable file
View File

@@ -0,0 +1,74 @@
#!/bin/bash
# Script to remove green screen and convert video files to WebM
# Usage: ./greenscreen_to_webm.sh [directory_path]
# Check if ffmpeg is installed
if ! command -v ffmpeg &> /dev/null; then
echo "Error: ffmpeg is not installed or not in PATH"
exit 1
fi
# Get target directory (default to current directory)
target_dir="${1:-.}"
# Check if directory exists
if [ ! -d "$target_dir" ]; then
echo "Error: Directory '$target_dir' does not exist"
exit 1
fi
# Change to target directory
cd "$target_dir" || {
echo "Error: Cannot access directory '$target_dir'"
exit 1
}
# Create webm directory if it doesn't exist
mkdir -p webm
# Counter for processed files
processed=0
failed=0
echo "Converting video files to WebM with green screen removal..."
# Process all common video files in current directory
for video_file in *.mp4 *.mov *.avi *.mkv *.m4v *.MP4 *.MOV *.AVI *.MKV *.M4V; do
# Check if file actually exists (in case glob doesn't match)
if [ ! -f "$video_file" ]; then
continue
fi
# Get filename without extension
base_name=$(basename "$video_file" | sed 's/\.[^.]*$//')
# Output WebM filename
webm_file="webm/${base_name}.webm"
echo "Processing: $video_file -> $webm_file"
# Convert with green screen removal
if ffmpeg -i "$video_file" \
-vf 'colorkey=0x00FF00:similarity=0.1:blend=0.1,despill=green' \
-c:v libvpx-vp9 \
-c:a libvorbis \
-y \
"$webm_file" \
-v quiet -stats; then
echo "✓ Successfully converted: $webm_file"
((processed++))
else
echo "✗ Failed to convert: $video_file"
((failed++))
fi
echo ""
done
# Summary
echo "Conversion complete!"
echo "Successfully processed: $processed files"
if [ $failed -gt 0 ]; then
echo "Failed to process: $failed files"
fi
echo "Output directory: webm/"

310
bash/trim_webm_empty_frames.sh Executable file
View 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

View File