430 lines
14 KiB
Bash
Executable File
430 lines
14 KiB
Bash
Executable File
#!/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<h_clip_count; i++)); do
|
|
input_file="${h_files[$i]}"
|
|
intermediate_file="$temp_dir/$(printf "%05d" $file_index).ts"
|
|
|
|
show_progress "$((file_index + 1))" "$h_clip_count"
|
|
|
|
# Try NVENC encoding (software decode, GPU encode)
|
|
nvenc_output=$(ffmpeg -nostdin -i "$input_file" \
|
|
-c:v $ENCODER $ENCODER_OPTS \
|
|
-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 \
|
|
-y "$intermediate_file" 2>&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<v_clip_count; i++)); do
|
|
input_file="${v_files[$i]}"
|
|
intermediate_file="$temp_dir/$(printf "%05d" $file_index).ts"
|
|
|
|
show_progress "$((file_index + 1))" "$v_clip_count"
|
|
|
|
# Try NVENC encoding (software decode, GPU encode)
|
|
nvenc_output=$(ffmpeg -nostdin -i "$input_file" \
|
|
-c:v $ENCODER $ENCODER_OPTS \
|
|
-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 \
|
|
-y "$intermediate_file" 2>&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 ""
|