This is an old revision of the document!


FFmpeg-based Interval Timer for Sport and Exercise

I am the one of the cycling captain of UEA Triathlon Club for the academic year of 2017-2018. I always find it hard to keep track of the set in a turbo session for some reason.

I used to use a Matlab-based countdown timer [1]. However it always freezes after 558 seconds had elapsed - if you set a 10-minute countdown timer, it will freeze at 42 seconds. I can't be bothered to fix the Matlab code.

After digging through FFmpeg documentation [2] and the Internet, I figured out how to generate a text-based video.

Here I present the program listing, and an example output. The script works fine on Debian Stretch.

I have to say FFmpeg is one of my most abused software. Last time I used FFmpeg to upload arbitrary file to Youtube [3].

Program listing

#!/bin/bash
# Copyright <2017> <Fufu Fang>
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy 
# of this software and associated documentation files (the "Software"), to deal 
# in  the Software without restriction, including without limitation the rights 
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
# copies of the Software, and to permit persons to whom the Software is 
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in all 
# copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
# SOFTWARE.
 
# The content of the set
# Format:
# min:sec text
turbo=(
#Warm up
'05:00 90 RPM Smooth pedalling'
'02:00 95 RPM Smooth pedalling'
'02:00 100 RPM Smooth pedalling'
'02:00 105 RPM Smooth pedalling'
'01:30 110 RPM Smooth pedalling'
'00:30 120-130 RPM Maintain form'
'02:00 90 RPM Relax and recover'
'00:06 MAX RPM Rev out'
'01:00 90 RPM Smooth pedalling'
'00:06 MAX RPM Rev out'
'01:00 90 RPM Smooth pedalling'
'00:06 MAX RPM Rev out'
'02:45 90 RPM Relax and recover'
#Russian Steps
'00:15 Max effort'
'00:45 Recovery'
'00:30 Max effort'
'00:30 Recovery'
'00:45 Max effort'
'00:15 Recovery'
'01:00 Max effort'
'01:00 Recovery'
'00:45 Max effort'
'00:15 Recovery'
'00:30 Max effort'
'00:30 Recovery'
'00:15 Max effort'
'05:45 Recovery'
'00:15 Max effort'
'00:45 Recovery'
'00:30 Max effort'
'00:30 Recovery'
'00:45 Max effort'
'00:15 Recovery'
'01:00 Max effort'
'01:00 Recovery'
'00:45 Max effort'
'00:15 Recovery'
'00:30 Max effort'
'00:30 Recovery'
'00:15 Max effort'
'05:45 Recovery'
'00:15 Max effort'
'00:45 Recovery'
'00:30 Max effort'
'00:30 Recovery'
'00:45 Max effort'
'00:15 Recovery'
'01:00 Max effort'
'01:00 Recovery'
'00:45 Max effort'
'00:15 Recovery'
'00:30 Max effort'
'00:30 Recovery'
'00:15 Max effort'
'10:00 Cool Down'
)
################################################################################
resolution=1280x720
framerate=30
 
# The file number counter
let counter=0
 
# The length of the set
let setLen=${#turbo[@]}-1
 
function cleanup {
    rm -f seg-*.mp4 filelist.txt intro.txt
}
 
# Calling convention: duration, font_size
function introduction {
    ffmpeg -f lavfi -i color=c=white:size=${resolution}:rate=${framerate} \
    -vf "drawtext=textfile=intro.txt: fontsize=${2}: \
    r=${framerate}: x=(w-tw)/2: y=h-h*0.9: fontcolor=black:" \
    -t ${1} -preset ultrafast -tune stillimage -y -loglevel panic -stats \
    seg-${counter}.mp4
    echo "file seg-${counter}.mp4" >> filelist.txt
    let counter++
}
 
# Calling convention: min, sec, text_now, text_next
function videoSegment {
    let duration=${1}*60+${2}
    min=${1}
    sec=${2}
    textNow=${3}
    textNext=$(echo "Next -" ${4})
    ffmpeg -f lavfi -i color=c=white:size=${resolution}:rate=${framerate} \
    -vf "\
    drawtext=text='${textNow}': fontsize=80: \
    r=${framerate}: x=(w-tw)*0.5: y=h-h*0.80: fontcolor=Black:,\
    \
    drawtext=text='Duration: ${min} min ${sec} sec': fontsize=80: \
    r=${framerate}: x=(w-tw)*0.5: y=h-h*0.60: fontcolor=Black:,
    \
    drawtext=timecode='00\:00\:00\:00': fontsize=80: \
    r=${framerate}: x=(w-tw)*0.5: y=h-h*0.40: fontcolor=Black:, \
    \
    drawtext=text='${textNext}': fontsize=50: \
    r=${framerate}: x=(w-tw)*0.5: y=h-h*0.20: fontcolor=SlateGray:" \
    \
    -t ${duration} -preset ultrafast -tune stillimage -y -loglevel panic -stats \
    seg-${counter}.mp4
    echo "file seg-${counter}.mp4" >> filelist.txt
    let counter++
}
 
function output {
    ffmpeg -f concat -i filelist.txt -c copy -y ${1}
    cleanup
}
 
# Calling convention: segment_id
function turboSegment {
    thisLine=$(echo ${turbo[${1}]})
    timing=$(echo $thisLine|cut -d ' ' -f 1)
    min=$(echo $timing|cut -d ':' -f 1)
    sec=$(echo $timing|cut -d ':' -f 2)
    textNow=$(echo $thisLine|cut -d ' ' -f 2-)
 
    let nextSeg=${1}+1
    nextLine=$(echo ${turbo[${nextSeg}]}|tr ':' 'm')
    videoSegment $min $sec "${textNow}" "${nextLine}"
}
################################################################################
cleanup
 
# Create the introduction text variable
printf "%s\n" "${turbo[@]}" > intro.txt
 
# introduction 10 40
 
# read -p "Press any key to continue, or Ctrl+C to stop..."
 
for i in $(seq 0 $setLen); do
    turboSegment ${i}
done
 
output UEA-Triathlon-$(date --rfc-3339=date).mp4

Example output

  • public/ffmpeg-based_interval_timer_for_sport_and_exercise.1518200352.txt.gz
  • Last modified: 2018/03/31 00:38
  • (external edit)