Browser view only. Use the download link if you want to save the file.
Back to Toolbox#!/bin/bash
SKIP_PEAK_ANALYSIS=1
source ./launch_terminal.sh
source ./load_video_files.sh
source ./pick_conversions.sh
# 0 = fastest (copy video, copy audio) [default]
# 1 = quick spackle (copy video, re-encode audio)
# 2 = full safe (re-encode video to x265, audio to FLAC, output MKV)
SAFE_MODE=0
my-notify () {
local message="$1"
USE_SYSTEM_NOTIFY="0"
statusfile="$HOME/status.log"
this_script=$(basename "$script_name" .sh)
if [ "$USE_SYSTEM_NOTIFY" == "1" ]; then
notify-send -t 3000 "$message"
else
echo -e "${color} $this_script: \033[0m $message" >> "$statusfile"
fi
}
show_mode_banner () {
echo
case "$1" in
0)
echo -e "\e[1;97;46m [ MODE 0 ] FAST CONCAT — copy video, copy audio \e[0m"
;;
1)
echo -e "\e[1;30;43m [ MODE 1 ] QUICK SPACKLE — copy video, re-encode audio \e[0m"
;;
2)
echo -e "\e[1;97;45m [ MODE 2 ] FULL SAFE + ARCHIVE — x265 + FLAC -> MKV \e[0m"
;;
esac
echo
}
show_detect_banner () {
echo
echo -e "\e[1;97;44m [ DETECT ] Consistent Ninja / Gen1 batch filenames detected \e[0m"
echo -e "\e[1;97;44m [ DETECT ] Mode 0 is likely safe and faster for this batch \e[0m"
echo
}
show_fail_banner () {
echo
echo -e "\e[1;97;41m [ FAIL ] Concatenation failed \e[0m"
echo
}
show_suggest_banner () {
local suggestion="$1"
echo -e "\e[1;30;43m [ SUGGEST ] $suggestion \e[0m"
}
detect_consistent_batch () {
local f
local base
local matched_any=0
for f in "${filenames[@]}"; do
base=$(basename "$f")
if [[ "$base" =~ ^NinjaV.*\.(mov|MOV|mkv|MKV)$ ]] || \
[[ "$base" =~ ^Njaflm.*\.(mov|MOV|mkv|MKV)$ ]] || \
[[ "$base" =~ ^SgnFlm.*\.(mov|MOV|mkv|MKV)$ ]]; then
matched_any=1
else
return 1
fi
done
[ "$matched_any" -eq 1 ]
}
suggest_retry_mode () {
local logfile="$1"
# If Mode 1 already failed, jump to Mode 2 suggestion.
if [ "$SAFE_MODE" = "1" ]; then
show_suggest_banner "Retry with SAFE_MODE=2 for full safe archive MKV mode."
return
fi
# If Mode 2 already failed, no higher mode remains.
if [ "$SAFE_MODE" = "2" ]; then
show_suggest_banner "SAFE_MODE=2 already used. Problem is likely source-file related."
return
fi
# Mode 0 failed: try to distinguish likely audio/timestamp trouble from broader mismatch trouble.
if grep -Eqi \
'non[- ]?monoton|dts|invalid.*dts|aac|audio|Queue input is backward in time|malformed aac|timestamp' \
"$logfile"; then
show_suggest_banner "Retry with SAFE_MODE=1 for quick audio/timestamp repair."
else
show_suggest_banner "Retry with SAFE_MODE=2 for full safe archive MKV mode."
fi
}
run_ffmpeg_live () {
local logfile="$1"
if [ "$SAFE_MODE" = "2" ]; then
ffmpeg -fflags +genpts -avoid_negative_ts make_zero \
-f concat -safe 0 -i "mylist.txt" \
-c:v libx265 -crf 20 -preset medium \
-c:a flac \
${FFMPEG_EXTRA_ARGS[@]} \
"$output_file" 2>&1 | tee "$logfile"
return ${PIPESTATUS[0]}
elif [ "$SAFE_MODE" = "1" ]; then
ffmpeg -fflags +genpts -avoid_negative_ts make_zero \
-f concat -safe 0 -i "mylist.txt" \
-c:v copy -c:a aac -b:a 256k \
${FFMPEG_EXTRA_ARGS[@]} \
"$output_file" 2>&1 | tee "$logfile"
return ${PIPESTATUS[0]}
else
ffmpeg -fflags +genpts -avoid_negative_ts make_zero \
-f concat -safe 0 -i "mylist.txt" \
-c:v copy -c:a copy \
${FFMPEG_EXTRA_ARGS[@]} \
"$output_file" 2>&1 | tee "$logfile"
return ${PIPESTATUS[0]}
fi
}
# Get the extension from the first enabled video file
ext=""
for i in "${!filenames[@]}"; do
ext="${filenames[$i]##*.}"
break
done
# If no enabled files were found, exit the script
if [ -z "$ext" ]; then
echo "No enabled video files to concatenate. Exiting in 5 secs..."
sleep 5
kill -- "$PPID"
exit 1
fi
# If detection finds a likely consistent Ninja / Gen1 batch, offer Mode 0
if detect_consistent_batch; then
show_detect_banner
read -rsn1 -p $'\e[1;97;44m Switch to MODE 0 for this detected batch? (Y/N) \e[0m ' mode0_choice
echo
if [[ "$mode0_choice" =~ [Yy] ]]; then
SAFE_MODE=0
echo -e "\e[92mUsing detected fast path: SAFE_MODE=0\e[0m"
else
echo -e "\e[93mKeeping current SAFE_MODE=$SAFE_MODE\e[0m"
fi
echo
fi
# --- FFDASH PROGRESS SUPPORT ---
FFDASH_DIR="/tmp/ffdash"
mkdir -p "$FFDASH_DIR"
if [ "$SAFE_MODE" = "2" ]; then
output_file="joined.mkv"
else
output_file="joined.$ext"
fi
jobbase="${output_file##*/}"
paramfile="$FFDASH_DIR/${jobbase}.params"
progressfile="$FFDASH_DIR/${jobbase}.progress"
fflog="/tmp/concat_ffmpeg_${jobbase}_$$.log"
# Build the concat list
echo "Generating file list for concatenation..."
> mylist.txt
basename_guess=""
for i in "${!filenames[@]}"; do
echo "file '$(basename "${filenames[$i]}")'" >> mylist.txt
if [[ -z "$basename_guess" ]]; then
if [[ "${filenames[$i]}" =~ ^(.*)[[:space:]]+[0-9]+\..*$ ]]; then
basename_guess="${BASH_REMATCH[1]}"
fi
fi
done
# Display chosen mode
show_mode_banner "$SAFE_MODE"
# Display the list and guess
echo -e "Best guess at good filename : \e[1;97;44m ${basename_guess##*/} \e[0m"
echo "The following files will be concatenated:"
cat mylist.txt
sleep 2
ffmpeg_status=1
FFMPEG_EXTRA_ARGS=()
# If ffdash is active, create progress tracking files
if [[ -f "$HOME/bin/ffdash_running.flag" ]]; then
echo "Detected ffdash is running — enabling live progress tracking."
total_duration=0
for f in "${filenames[@]}"; do
dur=$(ffprobe -v error -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 "$f" 2>/dev/null)
total_duration=$(awk "BEGIN {print $total_duration + $dur}")
done
cat >"$paramfile" <<EOF
src=mylist.txt
out=$output_file
duration=$total_duration
job=1
total=1
EOF
FFMPEG_EXTRA_ARGS=(-progress "$progressfile")
run_ffmpeg_live "$fflog"
ffmpeg_status=$?
rm -f "$progressfile" "$paramfile"
else
echo "ffdash not active — running normally."
FFMPEG_EXTRA_ARGS=()
run_ffmpeg_live "$fflog"
ffmpeg_status=$?
fi
# Clean up
rm -f mylist.txt
# --- END FFDASH PROGRESS SUPPORT ---
# Display completion message
if [ "$ffmpeg_status" -eq 0 ]; then
my-notify "Merge successful -> $output_file"
echo -e "\e[92mConcatenation completed successfully. Output: $output_file\e[0m"
# Convert extension to uppercase
ext_upper=$(echo "${output_file##*.}" | tr '[:lower:]' '[:upper:]')
# Prompt user to move original files
read -rsn1 -p $'\e[1;97;44m Do you want to move the original files to '\''orig '"$ext_upper"' files'\''? (Y/N) ' move_choice
echo -e "\e[0m"
if [[ "$move_choice" =~ [Yy] ]]; then
dest_folder="orig $ext_upper files"
mkdir -p "$dest_folder"
for i in "${!filenames[@]}"; do
mv "${filenames[$i]}" "$dest_folder"
done
echo -e "\n\e[92mFiles moved to '$dest_folder'\e[0m"
else
echo -e "\n\e[93mFiles were not moved.\e[0m"
fi
# Offer to rename the output file to the guessed basename if it's found
if [[ -n "$basename_guess" ]]; then
if [ "$SAFE_MODE" = "2" ]; then
new_output_file="$basename_guess.mkv"
else
new_output_file="$basename_guess.$ext"
fi
read -rsn1 -p $'\e[1;97;44m Do you want to rename the output file to '"${new_output_file##*/}"'? (Y/N) ' rename_choice
echo -e "\e[0m"
if [[ "$rename_choice" =~ [Yy] ]]; then
mv "$output_file" "$new_output_file"
my-notify "${output_file##*/} -> ${new_output_file##*/}"
output_file="$new_output_file"
echo -e "\n\e[92mFile renamed to '$new_output_file'\e[0m"
else
echo -e "\n\e[93mFile was not renamed.\e[0m"
fi
fi
else
my-notify "Concat failed!"
show_fail_banner
suggest_retry_mode "$fflog"
echo -e "\e[90mLog saved to: $fflog\e[0m"
fi
# Prompt the user to press any key to exit
echo "Press any key to exit..."
read -n1
kill -- "$PPID"
exit 0