I'm trying to add chapters to an mp4 file using bash and ffmpeg. The chapters are located in a file called chapters.txt
. When the bash script is run which is called add_chapters.sh
it reads the chapters.txt
file and creates a file called chapters.ffmetadata
. The issue is that the START
and END
times in the file chapters.ffmetadata
aren't being created correctly. How can I fix this?
I have a file called chapters.txt
that has the chapters in it.
00:00:00=Intro
00:02:12=What are selections
00:03:19=Booleans
The bash file I run is add_chapters.sh
The code in it is:
#!/bin/bash
input_file="chapters.txt"
output_file="chapters.ffmetadata"
# Create the FFmetadata chapter file
echo ";FFMETADATA1" > "$output_file"
previous_seconds=0
while IFS="=" read -r timestamp chapter_name; do
# Convert start time to seconds
seconds=$(date -u -d "1970-01-01 $timestamp" +"%s")
# Calculate the end time (previous chapter's start time)
end_seconds=$previous_seconds
# Update previous_seconds for the next iteration
previous_seconds=$seconds
echo "[CHAPTER]" >> "$output_file"
echo "TIMEBASE=1/1000" >> "$output_file"
echo "START=${end_seconds}000" >> "$output_file"
echo "END=${seconds}000" >> "$output_file"
echo "title=$chapter_name" >> "$output_file"
done < "$input_file"
echo "Chapter file '$output_file' created successfully."
# Run FFmpeg to add chapters to the video
ffmpeg -i input_video.mp4 -i chapters.ffmetadata -map_metadata 1 -c:v copy -c:a copy -y output_video.mp4
It creates a file called chapters.ffmetadata
;FFMETADATA1
[CHAPTER]
TIMEBASE=1/1000
START=0000
END=0000
title=Intro
[CHAPTER]
TIMEBASE=1/1000
START=0000
END=132000
title=What are selections
[CHAPTER]
TIMEBASE=1/1000
START=132000
END=199000
title=Booleans
[CHAPTER]
TIMEBASE=1/1000
START=199000
END=0000
title=
The START
and END
times aren't being calculated correctly. How can I fix this?
I'm almost there thanks to @Freeman code / answer.
The issues in the generated
chapters.ffmetadata
file are just.
The first START=
and END=
are 0000
The Intro Chapter doesn't show up (most likely caused by the first START=
and END=
being 0000
)
The Chapters seem to be shifted down by one timecode line (most likely caused by the first START=
and END=
being 0000
)
;FFMETADATA1 [CHAPTER] TIMEBASE=1/1000 START=0000 END=0000 title=Intro [CHAPTER] TIMEBASE=1/1000 START=0000 END=132000 title=What are selections? [CHAPTER] TIMEBASE=1/1000 START=132000 END=199000 title=Booleans [CHAPTER] TIMEBASE=1/1000 START=199000 END=0000 title= [CHAPTER] TIMEBASE=1/1000 START=0000 END=4459000 title=
Strange; I tried the changes @Freeman suggested but an error comes up now.
Chapter end time 0 before start 199000 chapters.ffmetadata: Cannot allocate memory
;FFMETADATA1
[CHAPTER]
TIMEBASE=1/1000
START=0000
END=0000
title=Intro
[CHAPTER]
TIMEBASE=1/1000
START=0000
END=132000
title=What are selections?
[CHAPTER]
TIMEBASE=1/1000
START=132000
END=199000
title=Booleans
[CHAPTER]
TIMEBASE=1/1000
START=199000
END=0000
title=
[CHAPTER]
TIMEBASE=1/1000
START=0000
END=4459000
title=
;FFMETADATA1
[CHAPTER]
TIMEBASE=1/1000
START=0000
END=0000
title=Intro
[CHAPTER]
TIMEBASE=1/1000
START=0000
END=132000
title=What are selections?
[CHAPTER]
TIMEBASE=1/1000
START=132000
END=199000
title=Booleans
[CHAPTER]
TIMEBASE=1/1000
START=199000
END=0000
title=
The end time of each chapter is the start time of the next chapter. You thus have to know the start time for the next chapter (or, for the last chapter, the end of the file) before you can output it.
Using the command from https://superuser.com/a/945604 to get the total duration, and Awk to perform the overall parsing,
#!/bin/sh
input_file="chapters.txt"
output_file="chapters.ffmetadata"
awk -F= -v duration=$(ffprobe -v error \
-select_streams v:0 -show_entries stream=duration \
-of default=noprint_wrappers=1:nokey=1 \
input_video.mp4) '
function chapter(start, end, title) {
print "[CHAPTER]"
print "TIMEBASE=1/1000"
print "START=" start "000"
print "END=" end "000"
print "title=" title }
BEGIN { print ";FFMETADATA1" }
{ split($1, hms, ":") ;
end=3600 * hms[1] + 60 * hms[2] + hms[3] }
title { chapter(start, end, title) }
{ start=end; title=$2 }
END { chapter(start, duration, title) }' "$input_file" > "$output_file"
ffmpeg -i input_video.mp4 -i "$output_file" -map_metadata 1 -c:v copy -c:a copy -y output_video.mp4
Nothing here uses Bash features, so I used a /bin/sh
shebang.
Properly speaking, you probably want to make the input video and output video file names into command-line arguments.
If you are unfamiliar with Awk, learning enough to write a script like this should not take long. A basic one-hour tutorial will already be quite sufficient, and pay back handsomely if you need to solve other problems like this.
Demo of the Awk part: https://ideone.com/scXWNJ