#!/bin/bash # Script to create random compilation videos from all GIF directories # Creates horizontal (1920x1080) and vertical (1080x1920) compilations # Uses 100 random clips from all creators # Uses NVIDIA NVENC GPU acceleration for fast encoding # Enable strict error handling set -o pipefail # Catch failures in piped commands # Use environment variables if set (for Docker), otherwise use default paths GIFS_DIR="${GIFS_PATH:-/media/bunker-admin/Internal/plex/xxx/media/local/gifs}" OUTPUT_DIR="${INBOX_PATH:-/media/bunker-admin/Internal/plex/xxx/media/local/inbox}" # Directory exclusion (colon-separated list of directory names to exclude) EXCLUDED_DIRS="${EXCLUDED_DIRS:-}" # Filter function for excluding directories (handles newline-terminated input) filter_excluded() { if [[ -z "$EXCLUDED_DIRS" ]]; then cat # Pass through unchanged else local pattern pattern=$(echo "$EXCLUDED_DIRS" | sed 's/:/|/g') grep -Ev "/($pattern)/" fi } cd "$GIFS_DIR" || exit 1 # Check if ffmpeg is available if ! command -v ffmpeg &> /dev/null; then echo "Error: ffmpeg is not installed. Please install ffmpeg." exit 1 fi # Check if ffprobe is available if ! command -v ffprobe &> /dev/null; then echo "Error: ffprobe is not installed. Please install ffprobe." exit 1 fi # Check if NVENC is available if ! ffmpeg -hide_banner -encoders 2>/dev/null | grep -q h264_nvenc; then echo "Warning: NVENC encoder not available. Falling back to CPU encoding." ENCODER="libx264" ENCODER_OPTS="-preset medium -crf 23" else # Check if NVENC encoder is currently in use (e.g., by Steam) enc_usage=$(nvidia-smi dmon -c 1 2>/dev/null | tail -1 | awk '{print $7}') if [[ "$enc_usage" -gt 50 ]]; then if [[ "${NONINTERACTIVE:-0}" == "1" ]]; then # In non-interactive mode, proceed with GPU anyway echo "⚠️ GPU encoder in use (${enc_usage}%), proceeding anyway in non-interactive mode" ENCODER="h264_nvenc" ENCODER_OPTS="-preset p4 -cq 23 -rc vbr -gpu 0 -b_ref_mode 0" else echo "" echo "⚠️ WARNING: GPU encoder is currently in use (${enc_usage}% utilization)" echo " This is likely Steam or another application using NVENC." echo "" echo " Options:" echo " 1) Close Steam/other apps and press Enter to continue with GPU" echo " 2) Type 'cpu' to use CPU encoding instead (slower)" echo " 3) Press Ctrl+C to cancel" echo "" read -p "Choice: " enc_choice if [[ "$enc_choice" == "cpu" ]]; then echo "Using CPU encoding..." ENCODER="libx264" ENCODER_OPTS="-preset medium -crf 23" else # Re-check encoder usage enc_usage=$(nvidia-smi dmon -c 1 2>/dev/null | tail -1 | awk '{print $7}') if [[ "$enc_usage" -gt 50 ]]; then echo "⚠️ Encoder still in use (${enc_usage}%). Will try GPU but may fall back to CPU." else echo "✓ GPU encoder now available" fi ENCODER="h264_nvenc" ENCODER_OPTS="-preset p4 -cq 23 -rc vbr -gpu 0 -b_ref_mode 0" fi fi else echo "✓ NVENC GPU encoder detected and available" ENCODER="h264_nvenc" # Using -gpu 0 to explicitly set GPU, -rc vbr for variable bitrate # Adding -b_ref_mode 0 to disable B-frame references (fixes issues on some GPUs) ENCODER_OPTS="-preset p4 -cq 23 -rc vbr -gpu 0 -b_ref_mode 0" fi fi # Get current date for filename current_date=$(date +%d-%m-%Y) echo "" echo "=========================================" echo " Random Compilation Generator" echo " Encoder: $ENCODER" echo " Date: $current_date" echo "=========================================" echo "" # Non-interactive mode: use NUM_CLIPS from environment or default to 100 if [[ "${NONINTERACTIVE:-0}" == "1" ]]; then NUM_CLIPS="${NUM_CLIPS:-100}" echo "Non-interactive mode: Using $NUM_CLIPS clips" else # Interactive menu to select number of clips echo "Select compilation size:" echo " 1) 30 clips" echo " 2) 60 clips" echo " 3) 100 clips (default)" echo "" read -p "Enter choice [1-3] (default: 3): " size_choice echo "" case "$size_choice" in 1) NUM_CLIPS=30 ;; 2) NUM_CLIPS=60 ;; *) NUM_CLIPS=100 ;; esac fi echo "Creating compilation with $NUM_CLIPS clips per orientation" # Log excluded directories if any if [[ -n "$EXCLUDED_DIRS" ]]; then echo "Excluding directories: $EXCLUDED_DIRS" fi echo "" start_time=$(date +%s) # Function to get video duration in seconds get_duration_seconds() { local file="$1" ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file" 2>/dev/null | cut -d. -f1 } # Function to format duration as H:MM format_duration() { local seconds="$1" local hours=$((seconds / 3600)) local minutes=$(((seconds % 3600) / 60)) printf "%d:%02d" "$hours" "$minutes" } # Function to get quality label from video height get_quality() { local file="$1" local height=$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 "$file" 2>/dev/null) if [[ -z "$height" ]]; then echo "1080p" return fi if [[ $height -ge 2160 ]]; then echo "4K" elif [[ $height -ge 1440 ]]; then echo "1440p" elif [[ $height -ge 1080 ]]; then echo "1080p" elif [[ $height -ge 720 ]]; then echo "720p" elif [[ $height -ge 480 ]]; then echo "480p" else echo "SD" fi } # Function to show progress bar show_progress() { local current=$1 local total=$2 local width=40 local percentage=$((current * 100 / total)) local filled=$((width * current / total)) local empty=$((width - filled)) printf "\r → Processing clip %d/%d [" "$current" "$total" printf "%${filled}s" | tr ' ' '█' printf "%${empty}s" | tr ' ' '░' printf "] %3d%%" "$percentage" } # ========================================= # HORIZONTAL COMPILATION # ========================================= echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Creating HORIZONTAL Random Compilation" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Find all horizontal MP4 files echo " → Scanning for horizontal clips..." mapfile -t h_files < <(find "$GIFS_DIR" -path "*/horizontal/*.mp4" -type f 2>/dev/null | filter_excluded | shuf) h_total=${#h_files[@]} echo " → Found $h_total horizontal clips" if [[ $h_total -lt $NUM_CLIPS ]]; then h_clip_count=$h_total echo " → Using all $h_clip_count clips (less than $NUM_CLIPS available)" else h_clip_count=$NUM_CLIPS echo " → Selecting $h_clip_count random clips" fi if [[ $h_clip_count -gt 0 ]]; then # Create temp directory for intermediate files temp_dir=$(mktemp -d) echo "" echo " → Encoding individual clips..." total_duration=0 file_index=0 for ((i=0; i&1) nvenc_exit=$? # Check if NVENC failed if [[ $nvenc_exit -ne 0 ]] || [[ "$nvenc_output" == *"No capable devices"* ]] || [[ "$nvenc_output" == *"CUDA_ERROR"* ]]; then ffmpeg -nostdin -i "$input_file" \ -c:v libx264 -preset fast -crf 23 \ -vf "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,fps=30" \ -c:a aac -b:a 128k \ -f mpegts \ -loglevel error \ -y "$intermediate_file" 2>&1 fi # Small delay to let GPU resources release sleep 0.1 ((file_index++)) done echo "" # Concatenate clips echo " → Merging clips..." concat_list_file="$temp_dir/concat_list.txt" for ts_file in "$temp_dir"/*.ts; do echo "file '$(realpath "$ts_file")'" >> "$concat_list_file" done # Temporary output for metadata extraction temp_output="$temp_dir/output.mp4" ffmpeg -f concat -safe 0 -i "$concat_list_file" \ -c copy \ -movflags +faststart \ -loglevel error \ -y "$temp_output" 2>&1 ffmpeg_exit=$? if [[ $ffmpeg_exit -eq 0 ]] && [[ -f "$temp_output" ]]; then # Get final video metadata total_duration=$(get_duration_seconds "$temp_output") duration_formatted=$(format_duration "$total_duration") quality=$(get_quality "$temp_output") # Create final filename with metadata final_filename="Random Comp [${current_date}] [${duration_formatted}] [${quality}] [E] [H].mp4" final_path="$OUTPUT_DIR/$final_filename" mv "$temp_output" "$final_path" file_size_human=$(du -h "$final_path" | cut -f1) echo " ✓ Success! Created: $final_filename ($file_size_human)" else echo " ✗ Failed to create horizontal compilation (exit code: $ffmpeg_exit)" fi # Cleanup rm -rf "$temp_dir" else echo " ⚠ No horizontal clips found, skipping" fi echo "" # ========================================= # VERTICAL COMPILATION # ========================================= echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Creating VERTICAL Random Compilation" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Find all vertical MP4 files echo " → Scanning for vertical clips..." mapfile -t v_files < <(find "$GIFS_DIR" -path "*/vertical/*.mp4" -type f 2>/dev/null | filter_excluded | shuf) v_total=${#v_files[@]} echo " → Found $v_total vertical clips" if [[ $v_total -lt $NUM_CLIPS ]]; then v_clip_count=$v_total echo " → Using all $v_clip_count clips (less than $NUM_CLIPS available)" else v_clip_count=$NUM_CLIPS echo " → Selecting $v_clip_count random clips" fi if [[ $v_clip_count -gt 0 ]]; then # Create temp directory for intermediate files temp_dir=$(mktemp -d) echo "" echo " → Encoding individual clips..." file_index=0 for ((i=0; i&1) nvenc_exit=$? # Check if NVENC failed if [[ $nvenc_exit -ne 0 ]] || [[ "$nvenc_output" == *"No capable devices"* ]] || [[ "$nvenc_output" == *"CUDA_ERROR"* ]]; then ffmpeg -nostdin -i "$input_file" \ -c:v libx264 -preset fast -crf 23 \ -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,fps=30" \ -c:a aac -b:a 128k \ -f mpegts \ -loglevel error \ -y "$intermediate_file" 2>&1 fi # Small delay to let GPU resources release sleep 0.1 ((file_index++)) done echo "" # Concatenate clips echo " → Merging clips..." concat_list_file="$temp_dir/concat_list.txt" for ts_file in "$temp_dir"/*.ts; do echo "file '$(realpath "$ts_file")'" >> "$concat_list_file" done # Temporary output for metadata extraction temp_output="$temp_dir/output.mp4" ffmpeg -f concat -safe 0 -i "$concat_list_file" \ -c copy \ -movflags +faststart \ -loglevel error \ -y "$temp_output" 2>&1 ffmpeg_exit=$? if [[ $ffmpeg_exit -eq 0 ]] && [[ -f "$temp_output" ]]; then # Get final video metadata total_duration=$(get_duration_seconds "$temp_output") duration_formatted=$(format_duration "$total_duration") quality=$(get_quality "$temp_output") # Create final filename with metadata final_filename="Random Comp [${current_date}] [${duration_formatted}] [${quality}] [E] [V].mp4" final_path="$OUTPUT_DIR/$final_filename" mv "$temp_output" "$final_path" file_size_human=$(du -h "$final_path" | cut -f1) echo " ✓ Success! Created: $final_filename ($file_size_human)" else echo " ✗ Failed to create vertical compilation (exit code: $ffmpeg_exit)" fi # Cleanup rm -rf "$temp_dir" else echo " ⚠ No vertical clips found, skipping" fi # Calculate elapsed time end_time=$(date +%s) elapsed=$((end_time - start_time)) hours=$((elapsed / 3600)) minutes=$(((elapsed % 3600) / 60)) seconds=$((elapsed % 60)) echo "" echo "" echo "=========================================" echo " Random Compilation Complete!" echo "=========================================" echo " Time elapsed: ${hours}h ${minutes}m ${seconds}s" echo "=========================================" echo ""