2 # Desc: Encodes video in parallel by processing time ranges directly from the input file.
3 # Usage: ./ffmpeg_parallel_encode.sh input_video_file segment_duration_in_seconds
6 # Limit the number of parallel jobs if necessary
7 MAX_JOBS
=8; # Adjust this number based on your system's CPU capacity
8 MAX_MEM
=512M
; # Adjust this number based on your system's memory
10 set -e; # Exit immediately if a command exits with a non-zero status
11 set -u; # Treat unset variables as an error
13 yell
() { echo "$0: $*" >&2; } # Print script path and all args to stderr
14 die
() { yell
"$*"; exit 111; } # Same as yell() but exits with status 111
15 must
() { "$@" || die
"cannot $*"; } # Runs command, dies if it fails
18 if [[ -n "${temp_dir:-}" && -d "$temp_dir" ]]; then
19 echo "Cleaning up temporary files...";
22 if [[ -n "${temp_list_file:-}" && -f "$temp_list_file" ]]; then
23 rm -f "$temp_list_file";
25 }; # Function to clean up temporary files
27 # Trap signals to ensure cleanup happens on script exit or interruption
28 trap cleanup EXIT INT TERM
;
30 if [[ "$#" -ne 2 ]]; then
31 yell
"Usage: $0 input_video_file segment_duration_in_seconds";
38 # Check if input file exists
39 if [[ ! -f "$input_file" ]]; then
40 die
"Input file '$input_file' does not exist.";
43 base_name
="$(basename "$input_file" | sed 's/\.[^.]*$//')"; # Strip extension
44 segment_prefix
="${base_name}_segment";
45 encode_suffix
="_encoded";
46 declare -a segment_filenames
;
48 # Set umask to ensure files and directories are not accessible by others
51 # Create a unique temporary directory for encoded files in the working directory
52 timestamp
="$(date +%s)";
53 temp_dir
="$(mktemp -d "${base_name}_
${timestamp}_XXXXXX
")";
55 # Safety check: Ensure temp_dir is not empty and is a directory
56 if [[ -z "${temp_dir}" ||
! -d "${temp_dir}" ]]; then
57 die
"Temporary directory creation failed.";
60 yell
"Temporary directory created at $temp_dir";
62 # Get the total duration of the input video in seconds
63 yell
"Getting total duration of input video...";
64 duration_str
="$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$input_file")";
65 if [[ -z "$duration_str" ]]; then
66 die
"Failed to get duration of input video.";
69 duration
="$(printf "%.0f
" "$duration_str")"; # Convert to integer seconds
71 yell
"Total duration: $duration seconds";
73 # Calculate the number of segments
74 num_segments
=$
(( (duration
+ segment_time
- 1) / segment_time
)); # Ceiling division
76 yell
"Number of segments: $num_segments";
78 # Generate start times and segment filenames, write to a temporary file
79 yell
"Preparing list of start times and filenames...";
80 temp_list_file
="$(mktemp -p "$temp_dir")";
82 # Use a separator that is unlikely to appear in the data (ASCII Unit Separator)
85 for (( i
=0; i
<num_segments
; i
++ )); do
86 start_time
=$
(( i
* segment_time
));
87 segment_filename
="${segment_prefix}_$(printf "%03d
" "$i")${encode_suffix}.mp4";
88 segment_filepath
="${temp_dir}/${segment_filename}";
89 printf '%s%s%s\n' "$start_time" "$separator" "$segment_filepath" >> "$temp_list_file";
90 segment_filenames
[i
]="$segment_filename"; # Store for later use
93 # Encode segments in parallel without writing unencoded segments to disk
94 yell
"Encoding segments in parallel...";
96 # Use GNU Parallel with the custom separator
97 must parallel
-j "$MAX_JOBS" --memsuspend "$MAX_MEM" --no-notice --bar --colsep "$separator" --arg-file "$temp_list_file" \
98 ffmpeg
-hide_banner -i "$input_file" -ss '{1}' -t "$segment_time" -c:v libx264
-preset fast
-crf 23 -c:a copy
'{2}';
100 # Step 3: Create a list file for concatenation
101 segments_file
="${temp_dir}/segments.txt";
102 yell
"Preparing for concatenation...";
103 for (( i
=0; i
<num_segments
; i
++ )); do
104 segment_filename
="${segment_filenames[i]}";
105 echo "file '$segment_filename'" >> "$segments_file";
108 # Safety check: Ensure segments_file is not empty
109 if [[ ! -s "$segments_file" ]]; then
110 die
"Segments file is empty.";
113 # Step 4: Concatenate the encoded segments into a single output file
114 output_file
="${base_name}_output.mp4";
115 yell
"Concatenating encoded segments into $output_file...";
117 cd "$temp_dir" || die
"Failed to change directory to $temp_dir";
118 must ffmpeg
-f concat
-safe 0 -i "segments.txt" -c copy
"$output_file";
122 yell
"Process completed successfully in ${SECONDS} seconds. Output video is $output_file";
124 # Author: Steven Baltakatei Sandoval