Refactor Timelapse Tuner to merge multiple videos with audio
This commit is contained in:
parent
0e3b2ff3c3
commit
96b3c05ac7
35
README.org
35
README.org
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[[https://gitlab.com/uploads/-/system/project/avatar/62789004/timelapse_tuner.png]]
|
[[https://gitlab.com/uploads/-/system/project/avatar/62789004/timelapse_tuner.png]]
|
||||||
|
|
||||||
Timelapse Tuner is a Bash script that automates the process of creating timelapse videos with randomly selected background music. It merges a timelapse video file with a randomly chosen audio file from the current directory, applying fade-in and fade-out effects to the audio for a polished result. It now supports both horizontal and vertical video output!
|
Timelapse Tuner is a Bash script that automates the process of creating timelapse videos with randomly selected background music. It can merge multiple timelapse video files and combine them with a randomly chosen audio file from the current directory, applying fade-in and fade-out effects to the audio for a polished result. It supports both horizontal and vertical video output!
|
||||||
|
|
||||||
* Video Demo
|
* Video Demo
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
@ -18,10 +18,11 @@ Timelapse Tuner is a Bash script that automates the process of creating timelaps
|
|||||||
:ID: f0a43fa6-6a89-409e-a72b-83145dd70e0c
|
:ID: f0a43fa6-6a89-409e-a72b-83145dd70e0c
|
||||||
:END:
|
:END:
|
||||||
|
|
||||||
- Merges a specified timelapse video file with a randomly selected MP3 audio file
|
- Merges multiple timelapse video files into a single video
|
||||||
|
- Combines the merged video with a randomly selected MP3 audio file
|
||||||
- Applies fade-in and fade-out effects to the audio for smooth transitions
|
- Applies fade-in and fade-out effects to the audio for smooth transitions
|
||||||
- Randomly selects a start point in the audio file to add variety
|
- Randomly selects a start point in the audio file to add variety
|
||||||
- Supports custom input timelapse video and output file specification
|
- Supports custom input timelapse videos and output file specification
|
||||||
- Allows customization of fade duration
|
- Allows customization of fade duration
|
||||||
- Provides option to convert horizontal videos to vertical format
|
- Provides option to convert horizontal videos to vertical format
|
||||||
- Provides detailed logging of the merging process
|
- Provides detailed logging of the merging process
|
||||||
@ -66,7 +67,7 @@ You will also need to provide your own MP3 files for background music. If you ne
|
|||||||
Run the script with the following command:
|
Run the script with the following command:
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
./tt --video <input_timelapse_video> --output <output_file> [--fade <fade_duration>] [--vertical]
|
./tt --video <video_file1> [<video_file2> ...] --output <output_file> [--fade <fade_duration>] [--vertical]
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Options:
|
** Options:
|
||||||
@ -74,7 +75,7 @@ Run the script with the following command:
|
|||||||
:ID: 9afd9a28-f9bf-47f0-a154-ff51f72c67e8
|
:ID: 9afd9a28-f9bf-47f0-a154-ff51f72c67e8
|
||||||
:END:
|
:END:
|
||||||
|
|
||||||
- =--video=: Specify the input timelapse video file (required)
|
- =--video=: Specify one or more input video files (at least one is required)
|
||||||
- =--output=: Specify the output MP4 file name (optional, default: output_video.mp4)
|
- =--output=: Specify the output MP4 file name (optional, default: output_video.mp4)
|
||||||
- =--fade=: Specify the fade duration in seconds (optional, default: 2)
|
- =--fade=: Specify the fade duration in seconds (optional, default: 2)
|
||||||
- =--vertical=: Convert the video to vertical format (optional, default: horizontal)
|
- =--vertical=: Convert the video to vertical format (optional, default: horizontal)
|
||||||
@ -84,14 +85,14 @@ Run the script with the following command:
|
|||||||
:ID: 0b930521-693e-49de-af95-fc7004264b30
|
:ID: 0b930521-693e-49de-af95-fc7004264b30
|
||||||
:END:
|
:END:
|
||||||
|
|
||||||
For horizontal output (default):
|
For merging multiple horizontal videos (default):
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
./tt --video my_timelapse.mp4 --output final_timelapse.mp4 --fade 3
|
./tt --video timelapse1.mp4 timelapse2.mp4 timelapse3.mp4 --output merged_timelapse.mp4 --fade 3
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
For vertical output:
|
For merging multiple videos and converting to vertical format:
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
./tt --video my_timelapse.mp4 --output vertical_timelapse.mp4 --fade 3 --vertical
|
./tt --video timelapse1.mp4 timelapse2.mp4 --output vertical_timelapse.mp4 --fade 3 --vertical
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
* How It Works
|
* How It Works
|
||||||
@ -100,14 +101,15 @@ For vertical output:
|
|||||||
:END:
|
:END:
|
||||||
|
|
||||||
1. The script checks for the required dependencies (FFmpeg, FFprobe, bc).
|
1. The script checks for the required dependencies (FFmpeg, FFprobe, bc).
|
||||||
2. It verifies the input timelapse video file exists.
|
2. It verifies that all input video files exist.
|
||||||
3. A random MP3 file is selected from the current directory to serve as background music.
|
3. A random MP3 file is selected from the current directory to serve as background music.
|
||||||
4. The lengths of both the timelapse video and audio files are calculated.
|
4. The lengths of all video files and the audio file are calculated.
|
||||||
5. Fade-in and fade-out durations are set (default 2 seconds, customizable with --fade) for smooth audio transitions.
|
5. If multiple video files are provided, they are concatenated in the order specified.
|
||||||
6. A random start point in the audio file is selected to ensure variety in the background music.
|
6. Fade-in and fade-out durations are set (default 2 seconds, customizable with --fade) for smooth audio transitions.
|
||||||
7. If the --vertical option is used, the video is scaled and cropped to 1080x1920 resolution.
|
7. A random start point in the audio file is selected to ensure variety in the background music.
|
||||||
8. FFmpeg is used to merge the timelapse video with the selected portion of the audio, applying fade effects and any necessary format conversion.
|
8. If the --vertical option is used, the combined video is scaled and cropped to 1080x1920 resolution.
|
||||||
9. The final timelapse video with background music is saved as an MP4 file.
|
9. FFmpeg is used to merge the concatenated video with the selected portion of the audio, applying fade effects and any necessary format conversion.
|
||||||
|
10. The final timelapse video with background music is saved as an MP4 file.
|
||||||
|
|
||||||
* Providing Background Music
|
* Providing Background Music
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
@ -136,6 +138,7 @@ If you encounter any issues:
|
|||||||
2. Check that you have read/write permissions in the current directory.
|
2. Check that you have read/write permissions in the current directory.
|
||||||
3. Verify that there are MP3 files in the same directory as the script for background music.
|
3. Verify that there are MP3 files in the same directory as the script for background music.
|
||||||
4. If FFmpeg fails, check the error message for details on what went wrong.
|
4. If FFmpeg fails, check the error message for details on what went wrong.
|
||||||
|
5. When using multiple input videos, ensure they have compatible formats and resolutions.
|
||||||
|
|
||||||
* Contributing
|
* Contributing
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
|
69
tt
69
tt
@ -24,9 +24,9 @@ command_exists() {
|
|||||||
|
|
||||||
# Function to print usage information
|
# Function to print usage information
|
||||||
print_usage() {
|
print_usage() {
|
||||||
echo "Usage: $0 --video <video_file> --output <output_file> [--fade <fade_duration>] [--vertical]"
|
echo "Usage: $0 --video <video_file1> [<video_file2> ...] --output <output_file> [--fade <fade_duration>] [--vertical]"
|
||||||
echo "Options:"
|
echo "Options:"
|
||||||
echo " --video Specify the input video file"
|
echo " --video Specify one or more input video files"
|
||||||
echo " --output Specify the output MP4 file name (default: output_video.mp4)"
|
echo " --output Specify the output MP4 file name (default: output_video.mp4)"
|
||||||
echo " --fade Specify the fade duration in seconds (default: 2)"
|
echo " --fade Specify the fade duration in seconds (default: 2)"
|
||||||
echo " --vertical Convert the video to vertical format (default: horizontal)"
|
echo " --vertical Convert the video to vertical format (default: horizontal)"
|
||||||
@ -38,7 +38,7 @@ log_message() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Initialize variables
|
# Initialize variables
|
||||||
video_file=""
|
video_files=()
|
||||||
output_file="output_video.mp4"
|
output_file="output_video.mp4"
|
||||||
fade_duration=2
|
fade_duration=2
|
||||||
vertical=false
|
vertical=false
|
||||||
@ -47,8 +47,11 @@ vertical=false
|
|||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case $1 in
|
case $1 in
|
||||||
--video)
|
--video)
|
||||||
video_file="$2"
|
shift
|
||||||
shift 2
|
while [[ $# -gt 0 && ! $1 == --* ]]; do
|
||||||
|
video_files+=("$1")
|
||||||
|
shift
|
||||||
|
done
|
||||||
;;
|
;;
|
||||||
--output)
|
--output)
|
||||||
output_file="$2"
|
output_file="$2"
|
||||||
@ -80,19 +83,21 @@ for cmd in ffmpeg ffprobe bc; do
|
|||||||
done
|
done
|
||||||
log_message "All required programs found"
|
log_message "All required programs found"
|
||||||
|
|
||||||
# Check if a video file is provided
|
# Check if video files are provided
|
||||||
if [ -z "$video_file" ]; then
|
if [ ${#video_files[@]} -eq 0 ]; then
|
||||||
log_message "Error: No video file specified"
|
log_message "Error: No video files specified"
|
||||||
print_usage
|
print_usage
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if the video file exists
|
# Check if all video files exist
|
||||||
if [ ! -f "$video_file" ]; then
|
for video_file in "${video_files[@]}"; do
|
||||||
log_message "Error: Video file '$video_file' not found"
|
if [ ! -f "$video_file" ]; then
|
||||||
exit 1
|
log_message "Error: Video file '$video_file' not found"
|
||||||
fi
|
exit 1
|
||||||
log_message "Input video file: $video_file"
|
fi
|
||||||
|
done
|
||||||
|
log_message "Input video files: ${video_files[*]}"
|
||||||
|
|
||||||
# Pick a random .mp3 file from the current directory
|
# Pick a random .mp3 file from the current directory
|
||||||
log_message "Selecting a random MP3 file..."
|
log_message "Selecting a random MP3 file..."
|
||||||
@ -112,14 +117,24 @@ log_message "Audio length: $audio_length seconds"
|
|||||||
|
|
||||||
log_message "Fade duration set to $fade_duration seconds"
|
log_message "Fade duration set to $fade_duration seconds"
|
||||||
|
|
||||||
# Get the length of the video file in seconds
|
# Prepare FFmpeg command for video concatenation
|
||||||
log_message "Calculating video file length..."
|
concat_filter="concat=n=${#video_files[@]}:v=1:a=0"
|
||||||
video_length=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$video_file")
|
video_inputs=""
|
||||||
log_message "Video length: $video_length seconds"
|
for i in "${!video_files[@]}"; do
|
||||||
|
video_inputs+="-i \"${video_files[$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
|
# Use the shorter of the two lengths for fade-out calculation
|
||||||
log_message "Calculating minimum length for fade-out..."
|
log_message "Calculating minimum length for fade-out..."
|
||||||
min_length=$(echo "if ($video_length < $audio_length) $video_length else $audio_length" | bc)
|
min_length=$(echo "if ($total_video_length < $audio_length) $total_video_length else $audio_length" | bc)
|
||||||
log_message "Minimum length: $min_length seconds"
|
log_message "Minimum length: $min_length seconds"
|
||||||
|
|
||||||
# Ensure fade-out starts at a valid time
|
# Ensure fade-out starts at a valid time
|
||||||
@ -130,9 +145,9 @@ if (( $(echo "$fade_out_start < 0" | bc -l) )); then
|
|||||||
fi
|
fi
|
||||||
log_message "Fade-out starts at: $fade_out_start seconds"
|
log_message "Fade-out starts at: $fade_out_start seconds"
|
||||||
|
|
||||||
# Calculate a random start time within the audio length minus the video length
|
# Calculate a random start time within the audio length minus the total video length
|
||||||
log_message "Calculating random start time for audio..."
|
log_message "Calculating random start time for audio..."
|
||||||
max_start=$(echo "$audio_length - $video_length" | bc | cut -d'.' -f1)
|
max_start=$(echo "$audio_length - $total_video_length" | bc | cut -d'.' -f1)
|
||||||
if [ "$max_start" -lt 0 ]; then
|
if [ "$max_start" -lt 0 ]; then
|
||||||
random_start=0
|
random_start=0
|
||||||
else
|
else
|
||||||
@ -141,15 +156,17 @@ fi
|
|||||||
log_message "Random start time: $random_start seconds"
|
log_message "Random start time: $random_start seconds"
|
||||||
|
|
||||||
# Prepare FFmpeg command
|
# Prepare FFmpeg command
|
||||||
ffmpeg_command="ffmpeg -loglevel error -stats -i \"$video_file\" -ss $random_start -i \"$audio_file\""
|
ffmpeg_command="ffmpeg -loglevel error -stats $video_inputs -ss $random_start -i \"$audio_file\" -filter_complex \""
|
||||||
|
|
||||||
if $vertical; then
|
if $vertical; then
|
||||||
log_message "Converting to vertical format..."
|
log_message "Converting to vertical format..."
|
||||||
ffmpeg_command+=" -filter_complex \"[0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1[v];[1:a]afade=t=in:st=0:d=$fade_duration,afade=t=out:st=$fade_out_start:d=$fade_duration[a]\""
|
ffmpeg_command+="$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]\""
|
ffmpeg_command+=" -map \"[v]\" -map \"[a]\""
|
||||||
else
|
else
|
||||||
ffmpeg_command+=" -filter_complex \"[1:a]afade=t=in:st=0:d=$fade_duration,afade=t=out:st=$fade_out_start:d=$fade_duration[a]\""
|
ffmpeg_command+="$concat_filter [v_concat];"
|
||||||
ffmpeg_command+=" -map 0:v -map \"[a]\""
|
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
|
fi
|
||||||
|
|
||||||
ffmpeg_command+=" -c:v libx264 -c:a aac -shortest \"$output_file\""
|
ffmpeg_command+=" -c:v libx264 -c:a aac -shortest \"$output_file\""
|
||||||
@ -161,7 +178,7 @@ eval $ffmpeg_command
|
|||||||
# Check if ffmpeg was successful
|
# Check if ffmpeg was successful
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
log_message "ffmpeg process completed successfully"
|
log_message "ffmpeg process completed successfully"
|
||||||
log_message "Merged $video_file with random audio: $audio_file"
|
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"
|
log_message "Audio starts at $random_start seconds, with fade-in/out effects"
|
||||||
if $vertical; then
|
if $vertical; then
|
||||||
log_message "Video converted to vertical format"
|
log_message "Video converted to vertical format"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user