#!/bin/bash # # Timelapse Tuner - tt # Copyright (C) 2024 Roger Gonzalez # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Function to check if a command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Function to print usage information print_usage() { echo "Usage: $0 --video [ ...] --output [--fade ] [--vertical]" echo "Options:" echo " --video Specify one or more input video files" echo " --output Specify the output MP4 file name (default: output_video.mp4)" echo " --fade Specify the fade duration in seconds (default: 2)" echo " --vertical Convert the video to vertical format (default: horizontal)" } # Function to log messages with timestamps log_message() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" } # Initialize variables video_files=() output_file="output_video.mp4" fade_duration=2 vertical=false # Parse command-line arguments while [[ $# -gt 0 ]]; do case $1 in --video) shift while [[ $# -gt 0 && ! $1 == --* ]]; do video_files+=("$1") shift done ;; --output) output_file="$2" shift 2 ;; --fade) fade_duration="$2" shift 2 ;; --vertical) vertical=true shift ;; *) log_message "Error: Unknown option: $1" print_usage exit 1 ;; esac done # Check if required programs exist log_message "Checking for required programs..." for cmd in ffmpeg ffprobe bc; do if ! command_exists "$cmd"; then log_message "Error: $cmd is not installed or not in PATH" exit 1 fi done log_message "All required programs found" # Check if video files are provided if [ ${#video_files[@]} -eq 0 ]; then log_message "Error: No video files specified" print_usage exit 1 fi # Check if all video files exist for video_file in "${video_files[@]}"; do if [ ! -f "$video_file" ]; then log_message "Error: Video file '$video_file' not found" exit 1 fi done log_message "Input video files: ${video_files[*]}" # Pick a random .mp3 file from the current directory log_message "Selecting a random MP3 file..." audio_file=$(ls *.mp3 2>/dev/null | shuf -n 1) # Check if an audio file was found if [ -z "$audio_file" ]; then log_message "Error: No .mp3 files found in the current directory" exit 1 fi log_message "Selected audio file: $audio_file" # Get the length of the audio file in seconds log_message "Calculating audio file length..." audio_length=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$audio_file") log_message "Audio length: $audio_length seconds" log_message "Fade duration set to $fade_duration seconds" # Prepare FFmpeg command for video scaling and concatenation concat_filter="concat=n=${#video_files[@]}:v=1:a=0" video_inputs="" scaled_inputs="" for i in "${!video_files[@]}"; do video_inputs+="-i \"${video_files[$i]}\" " scaled_inputs+="[${i}:v]scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1[v${i}];" done concat_inputs=$(for i in $(seq 0 $((${#video_files[@]}-1))); do echo -n "[v${i}]"; done) # Calculate total video length total_video_length=0 for video_file in "${video_files[@]}"; do video_length=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$video_file") total_video_length=$(echo "$total_video_length + $video_length" | bc) done log_message "Total video length: $total_video_length seconds" # Use the shorter of the two lengths for fade-out calculation log_message "Calculating minimum length for fade-out..." min_length=$(echo "if ($total_video_length < $audio_length) $total_video_length else $audio_length" | bc) log_message "Minimum length: $min_length seconds" # Ensure fade-out starts at a valid time log_message "Calculating fade-out start time..." fade_out_start=$(echo "$min_length - $fade_duration" | bc) if (( $(echo "$fade_out_start < 0" | bc -l) )); then fade_out_start=0 fi log_message "Fade-out starts at: $fade_out_start seconds" # Calculate a random start time within the audio length minus the total video length log_message "Calculating random start time for audio..." max_start=$(echo "$audio_length - $total_video_length" | bc | cut -d'.' -f1) if [ "$max_start" -lt 0 ]; then random_start=0 else random_start=$(shuf -i 0-"$max_start" -n 1) fi log_message "Random start time: $random_start seconds" # Prepare FFmpeg command ffmpeg_command="ffmpeg -y -loglevel error -stats $video_inputs -ss $random_start -i \"$audio_file\" -filter_complex \"" if $vertical; then log_message "Converting to vertical format..." ffmpeg_command+="$scaled_inputs $concat_inputs$concat_filter [v_concat]; [v_concat]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1[v];" ffmpeg_command+="[${#video_files[@]}:a]afade=t=in:st=0:d=$fade_duration,afade=t=out:st=$fade_out_start:d=$fade_duration[a]\"" ffmpeg_command+=" -map \"[v]\" -map \"[a]\"" else ffmpeg_command+="$scaled_inputs $concat_inputs$concat_filter [v_concat];" ffmpeg_command+="[${#video_files[@]}:a]afade=t=in:st=0:d=$fade_duration,afade=t=out:st=$fade_out_start:d=$fade_duration[a]\"" ffmpeg_command+=" -map \"[v_concat]\" -map \"[a]\"" fi ffmpeg_command+=" -c:v libx264 -c:a aac -shortest \"$output_file\"" # Run FFmpeg command log_message "Starting ffmpeg process..." eval $ffmpeg_command # Check if ffmpeg was successful if [ $? -eq 0 ]; then log_message "ffmpeg process completed successfully" log_message "Merged ${#video_files[@]} video files with random audio: $audio_file" log_message "Audio starts at $random_start seconds, with fade-in/out effects" if $vertical; then log_message "Video converted to vertical format" fi log_message "Output saved as: $output_file" else log_message "Error: ffmpeg process failed" exit 1 fi