Re-encoding a video with a timecode was more complicated than I expected.
Lets assume that I have a video file, where I want to measure duration (time elapsed) of a “part of the video”.
Usually, for such things, we can simply use MPV and add timecode:
mpv --osd-fractions --osd-level=2 ${your_video_file}
However, what I needed is to overlay timecode to an existing video where I can demonstrate it to future viewers.
Kdenlive. There used to be an easy way to use “Dynamic Text” as an effect, and use it as a timecode. However, as documented here: https://docs.kdenlive.org/en/effects_and_filters/video_effects/generate/dynamic_text.html it is not possible to define START & END times for the timecodes to be displayed: the timecode always starts with the video file.
STARTSEC=5
ENDSEC=20
STARTSEC2=25
ENDSEC2=70
ffmpeg -i ${your_input_video_file} -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf: \
text='%{eif\\:(t-${STARTSEC})/3600\\:d\\:2}\\:%{eif\\:mod((t-${STARTSEC})/60\\,60)\\:d\\:2}\\:%{eif\\:mod(t-${STARTSEC}\\,60)\\:d\\:2}.%{eif\\:mod((t-${STARTSEC})*1000\\,1000)\\:d\\:3}': \
x=w-tw-30: y=h-th-80: fontsize=48: fontcolor=yellow: box=1: boxcolor=black@0.5: \
enable='between(t,${STARTSEC},${ENDSEC})', \
drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf: \
text='%{eif\\:(t-${STARTSEC2})/3600\\:d\\:2}\\:%{eif\\:mod((t-${STARTSEC2})/60\\,60)\\:d\\:2}\\:%{eif\\:mod(t-${STARTSEC2}\\,60)\\:d\\:2}.%{eif\\:mod((t-${STARTSEC2})*1000\\,1000)\\:d\\:3}': \
x=w-tw-30: y=h-th-80: fontsize=48: fontcolor=yellow: box=1: boxcolor=black@0.5: \
enable='between(t,${STARTSEC2},${ENDSEC2})'
" \
-t 100 ${your_output_video_with_timecode}
Lets breakdown what does this oneliner do:
ffmpeg -i your_input ... your output
this is pretty simple, we declare the input and output files
ffmpeg -i .... -t 100 ...
I want to transcode only 100 seconds of the input video file
ffmpeg .... -vf " ...." ...
we are using a filter. in -vf " " everything here needs to be separated with a column , such as blabla:blabla:blabla
ffmpeg ... -vf "drawtext:...." ..
and the filter is “drawtext”
ffmpeg ... -vf "drawtext=fontfile=/pth/to/your/fancy/font.ttf:...
selects the font.
also
fontsize=48: fontcolor=yellow: box=1: boxcolor=black@0.5:
is related to the font.
x=w-tw-30: y=h-th-80:
the position of the text is negative 30 pixel from width and negative 80 pixel from the height ( bottom right of the video). We could write x=XXX , y=YYY here.
Now here comes the fancy part:
STARTSEC=5
ENDSEC=20
STARTSEC2=25
ENDSEC2=70
...
enable='between(t,${STARTSEC},${ENDSEC})'
enable='between(t,${STARTSEC2},${ENDSEC2})'
Lets assume I have a 2 minutes ( 120 seconds ) long video. And I want to display timecode in two portions of the video: one with starts 5 seconds after the video starts and until 20th second of the video, and a second timecode to start at 25th second of the video until 70th second of the video.
And I want these timecodes to appear in the video exactly on the times I wanted.
we use these two variable in the complicated part below:
text='%{eif\\:(t-${STARTSEC})/3600\\:d\\:2}\\:%{eif\\:mod((t-${STARTSEC})/60\\,60)\\:d\\:2}\\:%{eif\\:mod(t-${STARTSEC}\\,60)\\:d\\:2}.%{eif\\:mod((t-${STARTSEC})*1000\\,1000)\\:d\\:3}':
This statement looks pretty complicated but gives us following format:
00.00.00:000
to achieve what I needed, I had to substract the integer STARTSEC value from the current time value.
Otherwise, the value of t variable always starts with the beginning of the video, I would end up with a timecode that always starts with the beginning of the video. I didn’t wanted that. I wanted my counter to start with zero when I wanted to show my timecodes.
All those “\” are escape sequences when running these inside the bash.
And dividing t-STARTSEC/3600 gives us HOUR, mod(t-STARTSEC)/60,60 gives us minutes, mod(t-STARTSEC),60 gives us seconds, and mod(t-STARTSEC)*1000,1000 gives us millisecons.
Another critical syntax here was : How to define the number of digits?
I handled that by adding “\:d\:3” -> This prints milliseconds within 3 digits for example.
And finally, since I wanted to have two separate timecodes to be displayed, I just put a comma in between “text=…” statements.
Sadly, AI makes me more lazy these days. I used to read manuals , to earn many things much more efficiently. Now we all use AI, and we just leave such things to be figured our by the AI ( where ChatGPT, Claude, Deepseek all failed to figure out the syntax errors they suggested for this example ), which is sometimes causes more time then learning the syntax by yourself.