Skip to content

Instantly share code, notes, and snippets.

@bdurrow
Forked from palaniraja/merge-mp4.sh
Last active April 4, 2024 04:52
Show Gist options
  • Save bdurrow/b51470869dd72b2333407dbfcb947801 to your computer and use it in GitHub Desktop.
Save bdurrow/b51470869dd72b2333407dbfcb947801 to your computer and use it in GitHub Desktop.
Bash script to merge all mp4 videos in current directory (recursively 2 levels). It also updates the chapter marks to retain the folder/filename of source dir
#!/bin/bash
#http://redsymbol.net/articles/unofficial-bash-strict-mode/
set -euo pipefail
IFS=$'\n\t'
## Script to merge all mp4 videos in current directory (recursively 2 levels)
## And update chapter marks to retain the folder/filename
## Script for merging videos
temp_dir=$(mktemp -d)
function finish {
rc=$?
if [[ $rc != 0 ]]; then
echo
echo "FAILED!"
fi
echo -n "Cleaning up "
rm -rf "${temp_dir}"
echo "..........[ DONE ]"
exit $rc
}
trap finish EXIT
current_dir=$(pwd)
bname=$(basename ${current_dir})
final_mp4=${bname}.mp4
input_list=${temp_dir}/${bname}-input_list.txt
file_list=${temp_dir}/${bname}-file_list
meta_file=${temp_dir}/${bname}-metadata.txt
#Hopefully this will work for either BSD or GNU sed
extended_match="-r"
echo "" | sed ${extended_match} 's|foo|bar|' 2>/dev/null || extended_match="-E"
if [ -e "${final_mp4}" ]; then
echo "${final_mp4} already exists, please remove it."
exit 1
fi
echo -n "Generating file lists "
find -s . -maxdepth 2 -type f -iname '*.mp4' |
sed -e 's|^./||' |
tee "${file_list}" |
awk "{printf \"file '${current_dir}/%s'\n\", \$0}" > "${input_list}"
echo "..........[ DONE ]"
## chapter marks
#Do this first so we fail early
#TODO: Test (‘=’, ‘;’, ‘#’, ‘\’) are escaped
ts=0
echo -n "Generating chapter marks "
ffmpeg -i "$(head -1 "${file_list}")" -f ffmetadata "${meta_file}" -v quiet
cat "${file_list}" | while read file
do
ds=$(ffprobe -v quiet -of csv=p=0 -show_entries format=duration "${file}")
# echo "$ds"
escaped_title=$(echo ${file} | sed ${extended_match} -e 's|([=;#\])|\\\1|g' -e 's|.[Mm][Pp]4$||' )
echo "[CHAPTER]" >> "${meta_file}"
echo "TIMEBASE=1/1" >> "${meta_file}"
echo "START=${ts}" >> "${meta_file}"
ts=$(awk "BEGIN {print ${ts}+${ds}; exit}")
echo "END=${ts}" >> "${meta_file}"
echo "TITLE=${escaped_title}" >> "${meta_file}"
done
echo "..........[ DONE ]"
echo -n "Merging the files "
ffmpeg -f concat -safe 0 -i "${input_list}" -i "${meta_file}" -map_metadata 1 -codec copy "${final_mp4}" -v quiet
echo "..........[ DONE ]"
echo "Job Completed."
@jlnbxn
Copy link

jlnbxn commented Sep 9, 2020

Okay, I figured it out for anyone who has the same problem: The -s parameter in the "find" command in line 42 causes find to traverse the file hierarchies in lexicographical order , meaning 10 comes after 1, instead of 2 (which is a problem if you, say, have files that have the chapter number in their filename). If you want the chapters to be in their natural sort order (1, 2, 10), remove the -s parameter and add "sort -V |" at the end of the line. (Be aware though, there shouldn't be any quotes in the filenames using this method, otherwise some chapters were missing as evident in a small fill size.)
Thanks again for your work!

@EvanEdwards
Copy link

As a note, filenames with colons do not work correctly. ffmpeg interprets them within the commandline. This would seem to be a fairly common situation due to using timestamps as filenames.

Also, GNU find doesn't support -s. A quick edit to substitute it for sort in the pipeline works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment