Last active
May 7, 2023 17:49
-
-
Save palaniraja/d14ba9ac49019526e0774b28e2d71b16 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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
## 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 | |
filename=`basename pwd` | |
current=`pwd` | |
bname=`basename "$current"` | |
find . -maxdepth 2 -iname '*.mp4' | xargs -L 1 echo | awk '{printf "file \x27%s\x27\n", $0}' >> list.txt | |
find . -maxdepth 2 -iname '*.mp4' | xargs -L 1 echo | awk '{print $0}' >> files.txt | |
echo -n "Merging the files" | |
ffmpeg -f concat -safe 0 -i list.txt -c copy "$bname.mp4" -v quiet | |
echo "..........[ DONE ]" | |
## extract meta | |
# ffmpeg -i all.mp4 -f ffmetadata metafile | |
metafile="metadata.txt" | |
echo -n "Extracting meta data" | |
ffmpeg -i "$bname.mp4" -f ffmetadata $metafile -v quiet | |
echo "..........[ DONE ]" | |
## chapter marks | |
#TODO: (‘=’, ‘;’, ‘#’, ‘\’) to be escaped | |
ts=0 | |
echo -n "Identifying chapters" | |
cat files.txt | while read file | |
do | |
ds=`ffprobe -v quiet -of csv=p=0 -show_entries format=duration "$file"` | |
# echo "$ds" | |
echo "[CHAPTER]" >> $metafile | |
echo "TIMEBASE=1/1" >> $metafile | |
echo "START=$ts" >> $metafile | |
ts=`echo $ts + $ds | bc` | |
echo "END=$ts" >> $metafile | |
echo "TITLE=$file" >> $metafile | |
done | |
echo "..........[ DONE ]" | |
## update meta with chaptermarks | |
echo -n "Adding chapter meta " | |
ffmpeg -i "$bname.mp4" -i $metafile -map_metadata 1 -codec copy "$bname-meta.mp4" -v quiet | |
echo "..........[ DONE ]" | |
## cleanup | |
echo -n "Cleaning up" | |
rm files.txt list.txt $metafile | |
echo "..........[ DONE ]" | |
echo "Job Completed." |
I redid this in Python to better handle some unusual whitespace in my filenames (the xargs -L1 echo
trick really does not play nice with more than 1 space at a time). Note that this hardcodes the temp file paths/will not work if run in parallel.
import sys, os, os.path, re, subprocess
re_tuple = re.compile(r'(\d+)\s+([\w\d\.\s]+)')
re_season = re.compile(r'S\d\d')
season_root = os.path.normpath(sys.argv[1])
for ep in os.listdir(season_root):
if ep[0] == '.':
continue
m = re_tuple.match(ep)
if not m:
continue
episode_num = int(m.group(1))
episode_title = m.group(2).strip()
ms = re_season.search(season_root)
if not ms:
continue
season_num = ms.group(0)
chapter_list = []
for chapter in os.listdir(os.path.join(season_root, ep)):
if chapter[-4:] != '.mp4':
continue
m = re_tuple.match(chapter)
if not m:
continue
chapter_num = int(m.group(1))
chapter_title = m.group(2).replace('.mp4','')
chapter_list.append((chapter_num, chapter_title, os.path.abspath(os.path.join(season_root, ep, chapter))))
chapter_list.sort(key=lambda x: x[0])
with open('/tmp/chapters.txt', 'w') as f:
for chapter in chapter_list:
f.write('file \'{}\'\n'.format(chapter[2]))
print('combining chapters: {}'.format(', '.join([x[1] for x in chapter_list])))
args = 'ffmpeg -f concat -safe 0 -i /tmp/chapters.txt -c copy /tmp/temp-1.mp4 -v quiet'.split(' ')
print(' '.join(args))
subprocess.call(args)
print('extracting metadata')
subprocess.call('ffmpeg -i /tmp/temp-1.mp4 -f ffmetadata /tmp/metadata.txt -v quiet'.split(' '))
ts = 0
with open('/tmp/metadata.txt', 'a') as f:
for chapter in chapter_list:
f.write('[CHAPTER]\n')
f.write('TIMEBASE=1/1\n')
f.write('START={}\n'.format(ts))
args = 'ffprobe -v quiet -of csv=p=0 -show_entries format=duration'.split(' ')
args.append(chapter[2])
cp = subprocess.check_output(args)
ts += float(cp.strip())
f.write('END={}\n'.format(ts))
f.write('TITLE={}\n'.format(chapter[1]))
print('attaching metadata')
out_path = os.path.join(season_root, '{}E{:02d} - {}.mp4'.format(season_num, episode_num, episode_title))
args = 'ffmpeg -i /tmp/temp-1.mp4 -i /tmp/metadata.txt -map_metadata 1 -codec copy'.split(' ') + [out_path] + '-v quiet'.split(' ')
subprocess.call(args)
Yeah, it is not good that you store the merged file twice: at line 14 without metadata, and at line 46 with metadata.
It is better to make the metadata first, and then do merge and inject metadata in one time. The @bdurrow fork does this.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for writing this. I looked all over for a tool to accomplish this and didn't find anything. It didn't quite meet my need so I made some changes but tried to keep it generally useful here.
Improvements are: