From 4547fe61ef20bbac4eb79170bb024dfbadfe5565 Mon Sep 17 00:00:00 2001 From: ct Date: Sat, 14 Jun 2025 23:35:16 +0800 Subject: [PATCH] Update --- .DS_Store | Bin 0 -> 6148 bytes .../Maintenance/MemeMediaMaintenance.php | 38 ++ app/Http/Controllers/TestController.php | 5 + bash/.DS_Store | Bin 0 -> 6148 bytes bash/check_webm_alpha_frames.sh | 347 ++++++++++ bash/greenscreen_to_webm.sh | 74 +++ bash/trim_webm_empty_frames.sh | 310 +++++++++ bash/webm_ | 0 composer.json | 1 + composer.lock | 627 ++++++++++++++++-- config/laravel-ffmpeg.php | 21 + ...4414_add_duration_to_meme_medias_table.php | 28 + routes/test.php | 4 +- 13 files changed, 1404 insertions(+), 51 deletions(-) create mode 100644 .DS_Store create mode 100644 app/Helpers/FirstParty/Maintenance/MemeMediaMaintenance.php create mode 100644 bash/.DS_Store create mode 100755 bash/check_webm_alpha_frames.sh create mode 100755 bash/greenscreen_to_webm.sh create mode 100755 bash/trim_webm_empty_frames.sh delete mode 100644 bash/webm_ create mode 100644 config/laravel-ffmpeg.php create mode 100644 database/migrations/2025_06_14_144414_add_duration_to_meme_medias_table.php diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..3e6d29e97508857a347cb245227592d64142aa5e GIT binary patch literal 6148 zcmeHK%}T>S5Z<-XrW7FuMUM+!3#L{J;w8lT0!H+pQWH{aFlI~BnnNk%sxRc5_&m<+ zZp32FB6bFLzxmzGevtiPjB$S+cNudSV>UEIj!KQ7yEe38k`XzM5f%9)N@WE0yNUgE zz;Cx$#6p&{@%QhKCTWt-T=#=-G_1|7rrosL_I)sxMKB9z^V|=ow`g5SnMRc!M%T%x z7&<%WGMj}-Hkzt}BpyM^-A$6kvhd|Ri*r>Q=z!g_TSI4WvFM(3MBnW#J7RHqcount(); + + foreach ($meme_medias_with_no_duration as $key => $meme_media) { + + $current = $key + 1; + dump("Processing {$current}/{$total}: {$meme_media->webm_url}"); + $meme_media->duration = self::getDurationUsingFfmpeg($meme_media); + $meme_media->save(); + } + } + + private static function getDurationUsingFfmpeg($meme_media) + { + $duration_milliseconds = FFMpeg::openUrl($meme_media->webm_url)->getDurationInMiliseconds(); + $duration_seconds = ($duration_milliseconds / 1000); + + return $duration_seconds; + } + + private static function getMemeMediasWithNoDuration() + { + return MemeMedia::whereNull('duration')->get(); + } +} diff --git a/app/Http/Controllers/TestController.php b/app/Http/Controllers/TestController.php index 5b683dc..2e0d19e 100644 --- a/app/Http/Controllers/TestController.php +++ b/app/Http/Controllers/TestController.php @@ -8,4 +8,9 @@ public function index() { // } + + public function populateDuration() + { + \App\Helpers\FirstParty\Maintenance\MemeMediaMaintenance::populateDurations(); + } } diff --git a/bash/.DS_Store b/bash/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e1e48830f56faeb3894dc71f5d6aec76c0ccd861 GIT binary patch literal 6148 zcmeHK!AiqG5Z!H~O({YSiXIod7Hq8+#7l_v2aM=Jr8XvLFwK@GHHT8jRe#7o@q3)v z-4>+^o zDU)D69t0P0RP>$A6Pf0NIE}_CAr2!5xxS3kP!^t?q+zCVeLZ2gWg{Xu{tx&}*)YU==_S^z)`+)7}K zy#(e6gRa3+Bg}wsoeHQ^xoI)DP6xj*ajwBqqfTesG#}jDnVSxUtGmPcLWMK#Y9vbx z5CfkXz}^qU!Snz0`~F`Aks$_%f&a+>w|aihgWAm5x>O~ewF2}O6b0i-jf)f*sG}HS e@hIK_RRVs213=ecsS!LN^dq2XAVUoNDg*DCiccK? literal 0 HcmV?d00001 diff --git a/bash/check_webm_alpha_frames.sh b/bash/check_webm_alpha_frames.sh new file mode 100755 index 0000000..f161b25 --- /dev/null +++ b/bash/check_webm_alpha_frames.sh @@ -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 " + 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 diff --git a/bash/greenscreen_to_webm.sh b/bash/greenscreen_to_webm.sh new file mode 100755 index 0000000..8ebc7de --- /dev/null +++ b/bash/greenscreen_to_webm.sh @@ -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/" diff --git a/bash/trim_webm_empty_frames.sh b/bash/trim_webm_empty_frames.sh new file mode 100755 index 0000000..f31ec7d --- /dev/null +++ b/bash/trim_webm_empty_frames.sh @@ -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 diff --git a/bash/webm_ b/bash/webm_ deleted file mode 100644 index e69de29..0000000 diff --git a/composer.json b/composer.json index fdb12a9..ecfdb1d 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "laravel/sanctum": "^4.0", "laravel/tinker": "^2.10.1", "league/flysystem-aws-s3-v3": "^3.0", + "pbmedia/laravel-ffmpeg": "^8.7", "pgvector/pgvector": "^0.2.2", "spatie/laravel-responsecache": "^7.7", "tightenco/ziggy": "^2.4", diff --git a/composer.lock b/composer.lock index 2fe5a2f..d1c230d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "160f6df94011c1b101a9fc69cf0bd4b6", + "content-hash": "fe17bc6263907633c97c7d1670d002ba", "packages": [ { "name": "artesaos/seotools", @@ -732,6 +732,53 @@ ], "time": "2025-03-06T22:45:56+00:00" }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.3.0", @@ -3202,6 +3249,85 @@ ], "time": "2024-11-21T10:39:51+00:00" }, + { + "name": "pbmedia/laravel-ffmpeg", + "version": "8.7.1", + "source": { + "type": "git", + "url": "https://github.com/protonemedia/laravel-ffmpeg.git", + "reference": "596804eee95b97bca146653770e7622159976bb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/protonemedia/laravel-ffmpeg/zipball/596804eee95b97bca146653770e7622159976bb1", + "reference": "596804eee95b97bca146653770e7622159976bb1", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0|^11.0|^12.0", + "php": "^8.1|^8.2|^8.3|^8.4", + "php-ffmpeg/php-ffmpeg": "^1.2", + "ramsey/collection": "^2.0" + }, + "require-dev": { + "laravel/pint": "^1.21", + "league/flysystem-memory": "^3.10", + "mockery/mockery": "^1.4.4", + "nesbot/carbon": "^2.66|^3.0", + "orchestra/testbench": "^8.0|^9.0|^10.0", + "phpunit/phpunit": "^10.4|^11.5.3", + "spatie/image": "^2.2|^3.3", + "spatie/phpunit-snapshot-assertions": "^5.0" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "FFMpeg": "ProtoneMedia\\LaravelFFMpeg\\Support\\FFMpeg" + }, + "providers": [ + "ProtoneMedia\\LaravelFFMpeg\\Support\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "ProtoneMedia\\LaravelFFMpeg\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Pascal Baljet", + "email": "pascal@protone.media", + "homepage": "https://protone.media", + "role": "Developer" + } + ], + "description": "FFMpeg for Laravel", + "homepage": "https://github.com/protonemedia/laravel-ffmpeg", + "keywords": [ + "ffmpeg", + "laravel", + "laravel-ffmpeg", + "protone media", + "protonemedia" + ], + "support": { + "issues": "https://github.com/protonemedia/laravel-ffmpeg/issues", + "source": "https://github.com/protonemedia/laravel-ffmpeg/tree/8.7.1" + }, + "funding": [ + { + "url": "https://github.com/pascalbaljet", + "type": "github" + } + ], + "time": "2025-04-01T21:11:35+00:00" + }, { "name": "pgvector/pgvector", "version": "v0.2.2", @@ -3259,6 +3385,95 @@ }, "time": "2025-02-15T23:27:08+00:00" }, + { + "name": "php-ffmpeg/php-ffmpeg", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/PHP-FFMpeg/PHP-FFMpeg.git", + "reference": "8e74bdc07ad200da7a6cfb21ec2652875e4368e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/8e74bdc07ad200da7a6cfb21ec2652875e4368e0", + "reference": "8e74bdc07ad200da7a6cfb21ec2652875e4368e0", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0", + "php": "^8.0 || ^8.1 || ^8.2 || ^8.3 || ^8.4", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "spatie/temporary-directory": "^2.0", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "phpunit/phpunit": "^9.5.10 || ^10.0" + }, + "suggest": { + "php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg" + }, + "type": "library", + "autoload": { + "psr-4": { + "FFMpeg\\": "src/FFMpeg", + "Alchemy\\BinaryDriver\\": "src/Alchemy/BinaryDriver" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Romain Neutron", + "email": "imprec@gmail.com", + "homepage": "http://www.lickmychip.com/" + }, + { + "name": "Phraseanet Team", + "email": "info@alchemy.fr", + "homepage": "http://www.phraseanet.com/" + }, + { + "name": "Patrik Karisch", + "email": "patrik@karisch.guru", + "homepage": "http://www.karisch.guru" + }, + { + "name": "Romain Biard", + "email": "romain.biard@gmail.com", + "homepage": "https://www.strime.io/" + }, + { + "name": "Jens Hausdorf", + "email": "hello@jens-hausdorf.de", + "homepage": "https://jens-hausdorf.de" + }, + { + "name": "Pascal Baljet", + "email": "pascal@protone.media", + "homepage": "https://protone.media" + } + ], + "description": "FFMpeg PHP, an Object Oriented library to communicate with AVconv / ffmpeg", + "keywords": [ + "audio", + "audio processing", + "avconv", + "avprobe", + "ffmpeg", + "ffprobe", + "video", + "video processing" + ], + "support": { + "issues": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/issues", + "source": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/tree/v1.3.2" + }, + "time": "2025-04-01T20:36:46+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.3", @@ -3334,6 +3549,55 @@ ], "time": "2024-07-20T21:41:07+00:00" }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, { "name": "psr/clock", "version": "1.0.0", @@ -4181,6 +4445,241 @@ ], "time": "2025-05-20T08:39:19+00:00" }, + { + "name": "spatie/temporary-directory", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/temporary-directory.git", + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\TemporaryDirectory\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily create, use and destroy temporary directories", + "homepage": "https://github.com/spatie/temporary-directory", + "keywords": [ + "php", + "spatie", + "temporary-directory" + ], + "support": { + "issues": "https://github.com/spatie/temporary-directory/issues", + "source": "https://github.com/spatie/temporary-directory/tree/2.3.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-01-13T13:04:43+00:00" + }, + { + "name": "symfony/cache", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "c4b217b578c11ec764867aa0c73e602c602965de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/c4b217b578c11ec764867aa0c73e602c602965de", + "reference": "c4b217b578c11ec764867aa0c73e602c602965de", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^3.6", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-06T19:00:13+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-03-13T15:25:07+00:00" + }, { "name": "symfony/clock", "version": "v7.2.0", @@ -6409,6 +6908,83 @@ ], "time": "2025-01-17T11:39:41+00:00" }, + { + "name": "symfony/var-exporter", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/c9a1168891b5aaadfd6332ef44393330b3498c4c", + "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-15T09:04:05+00:00" + }, { "name": "tightenco/ziggy", "version": "v2.5.2", @@ -9281,55 +9857,6 @@ ], "time": "2025-03-23T16:02:11+00:00" }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, { "name": "reliese/laravel", "version": "v1.4.0", diff --git a/config/laravel-ffmpeg.php b/config/laravel-ffmpeg.php new file mode 100644 index 0000000..25b8913 --- /dev/null +++ b/config/laravel-ffmpeg.php @@ -0,0 +1,21 @@ + [ + 'binaries' => env('FFMPEG_BINARIES', 'ffmpeg'), + + 'threads' => 12, // set to false to disable the default 'threads' filter + ], + + 'ffprobe' => [ + 'binaries' => env('FFPROBE_BINARIES', 'ffprobe'), + ], + + 'timeout' => 3600, + + 'log_channel' => env('LOG_CHANNEL', 'stack'), // set to false to completely disable logging + + 'temporary_files_root' => env('FFMPEG_TEMPORARY_FILES_ROOT', sys_get_temp_dir()), + + 'temporary_files_encrypted_hls' => env('FFMPEG_TEMPORARY_ENCRYPTED_HLS', env('FFMPEG_TEMPORARY_FILES_ROOT', sys_get_temp_dir())), +]; diff --git a/database/migrations/2025_06_14_144414_add_duration_to_meme_medias_table.php b/database/migrations/2025_06_14_144414_add_duration_to_meme_medias_table.php new file mode 100644 index 0000000..7f1cb5a --- /dev/null +++ b/database/migrations/2025_06_14_144414_add_duration_to_meme_medias_table.php @@ -0,0 +1,28 @@ +double('duration')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('meme_medias', function (Blueprint $table) { + $table->dropColumn('duration'); + }); + } +}; diff --git a/routes/test.php b/routes/test.php index f470310..7a6cc43 100644 --- a/routes/test.php +++ b/routes/test.php @@ -2,4 +2,6 @@ use App\Http\Controllers\TestController; -Route::get('/', [TestController::class, 'index'])->name('test'); +Route::get('/', [TestController::class, 'index']); + +Route::get('/populateDuration', [TestController::class, 'populateDuration']);