Add Timestamp to Videos#
I am involved with some experimental work, and a lot of the video footage is captured with a GoPro. They are durable and work well in the field. Unfortunately, they don’t support timestamp overlay with the date and time on the video. I used FFmpeg and a shell script to automate the process. The following script will take the video, extract the creation_time
tag from the video and use that to generate the timestamp overlay.
The script will also re-encode the video to h.265
, making it smaller, in some cases reducing the file size to 10 % of the original size.
timestamp.sh#
1#!/usr/bin/env bash
2
3# -----------
4# SPDX-License-Identifier: MIT
5# Copyright (c) 2022 Troy Williams
6
7# uuid = 70204dea-f8b9-11ec-a408-cbfc46763958
8# author = Troy Williams
9# email = troy.williams@bluebill.net
10# date = 2022-06-30
11# -----------
12
13# this script will add timestamps (like security cameras) to the bottom left
14# corner of the video. It will use the creation date as the starting point of
15# the timestamp counter. The output video will be re-encoded to h265 format.
16
17# NOTE: Some videos indicate they are in UTC, but in fact are not.They will need
18# be correct otherwise the timestamp will be incorrect.
19
20# NOTE: If the creation_time isn't set, the video will be skipped.
21
22# Usage:
23
24# $ ./timestamp.sh 20210613_191928.mp4
25
26# loop through files in a folder:
27# $ for FILE in ./2022-06-28/*; do ./timestamp.sh "${FILE}"; done
28# $ for FILE in ./2022-06-28/*; do ./timestamp.sh "${FILE}" --over-write; done
29
30# Requirements:
31
32# JSON parser: $ sudo apt install jq
33
34# References
35
36# https://www.cyberciti.biz/faq/unix-linux-bsd-appleosx-bash-assign-variable-command-output/
37# https://stackoverflow.com/questions/1955505/parsing-json-with-unix-tools
38# https://www.baeldung.com/linux/use-command-line-arguments-in-bash-script
39# https://stackoverflow.com/a/965069
40# https://www.howtogeek.com/529219/how-to-parse-json-files-on-the-linux-command-line-with-jq/
41# https://cameronnokes.com/blog/working-with-json-in-bash-using-jq/
42# https://stackoverflow.com/questions/965053/extract-filename-and-extension-in-bash
43
44# https://ourcodeworld.com/articles/read/1484/how-to-get-the-information-and-metadata-of-a-media-file-audio-or-video-in-json-format-with-ffprobe
45# ffprobe -loglevel 0 -print_format json -show_format -show_streams GX013438.MP4
46
47# ----------------
48# Set the script to halt on errors
49set -e
50
51if [[ -z "$1" ]]; then
52 echo "USAGE: $ 1./timestamp.sh video.mp4"
53 exit 1
54fi
55
56# Get the movie name
57# MOVIE=${1}
58
59# What is the full path of the movie name?
60INPUT=$(readlink -f "$1")
61
62# Strip longest match of */ from start
63filename="${INPUT##*/}"
64
65# Substring from 0 thru pos of filename
66dir="${INPUT:0:${#INPUT} - ${#filename}}"
67
68# Strip shortest match of . plus at least one non-dot char from end
69base="${filename%.[^.]*}"
70
71# Substring from len of base thru end
72ext="${filename:${#base} + 1}"
73
74# If we have an extension and no base, it's really the base
75if [[ -z "$base" && -n "$ext" ]]; then
76 base=".$ext"
77 ext=""
78fi
79
80# Create the new file name
81OUTPUT="${dir}"/"${base}".stamped."${ext}"
82
83# ----------
84# Debugging Code
85# echo "MOVIE = ${MOVIE}"
86# echo "INPUT = ${INPUT}"
87# echo "filename = ${filename}"
88# echo "dir = ${dir}"
89# echo "base = ${base}"
90# echo "ext = ${ext}"
91# echo "OUTPUT = ${OUTPUT}"
92# ----------
93
94# need to use raw mode `-r` in jq call to return a raw string otherwise it will
95# be trouble to deal with
96STARTDATE=$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "${INPUT}" | jq -r .format.tags.creation_time)
97
98if [[ -z "${STARTDATE}" ]]; then
99 echo "ERROR - No creation_time in metadata! ${INPUT}"
100 exit 1
101fi
102
103# Convert the date to EPOCH. This will be used to set the time for the draw text
104# method.
105EPOCH=$(date --date="${STARTDATE}" +%s)
106
107echo "Video Create Time: ${STARTDATE} (${EPOCH})"
108
109# we assume that the STARTDATE is in UTC 0000, Zulu time, GMT and that we want
110# to convert it to the local time on the computer.
111ffmpeg -i "${INPUT}" -vf drawtext="fontsize=30:fontcolor=yellow:text='%{pts\:localtime\:${EPOCH}}':x=(w-text_w) - 10:y=(h-text_h) - 10" -vcodec libx265 -crf 28 "${OUTPUT}"
112
113if [ "${3:-"invalid"}" == "--over-write" ]; then
114
115 echo "Moving ${OUTPUT} -> ${INPUT}"
116 mv "${OUTPUT}" "${INPUT}"
117
118else
119 echo "To overwrite the existing video, use --over-write"
120fi
I did notice that the GoPro we were using didn’t have the correct time embedded in the video. It was the local time with no timezone. The above script would attempt to convert the time to local time and the stamp would be wrong. I wrote the following script to fix that issue. It extracts the creation_time
, removes the timezone and assigns the local timezone. It then converts this back to UTC and write it back to the video.
correct_times.sh#
1#!/usr/bin/env bash
2
3# -----------
4# SPDX-License-Identifier: MIT
5# Copyright (c) 2022 Troy Williams
6
7# uuid = e577e81c-f938-11ec-9c1a-0d95d112ee30
8# author = Troy Williams
9# email = troy.williams@bluebill.net
10# date = 2022-07-01
11# -----------
12
13# This script will take the video and correct the creation time issue with it.
14# It assumes that the the creation time did not have the proper timezone set.
15# This means that other programs will attempt to convert from GMT/Zulu time and
16# get the wrong time stamp.
17
18# This script assumes that the video has the correct local time, but was
19# inserted without a timezone. It will extract that time, and configure it as a
20# local time. It will then convert the local time properly to UTC +00:00 and
21# write it back to the video.
22
23# Generally this script would be executed before the `timestamp.sh` script.
24
25#NOTE: This script should only be run if the timestamps stored in the video are
26#not correctly set to UTC time.
27
28# Usage:
29
30# List the creation time and what it would be in local time:
31# $ ./correct_times.sh GX013443.MP4
32
33# Write the corrected local time to the video.
34# $ ./correct_times.sh GX013443.MP4 --update
35
36# Write the corrected local time to the video.and overwrite the existing file
37# $ ./correct_times.sh GX013443.MP4 --update --over-write
38
39# loop through files in a folder:
40# $ for FILE in ./2022-06-28/*; do ./correct_times.sh "${FILE}"; done
41# $ for FILE in ./2022-06-28/*; do ./correct_times.sh "${FILE}" --update; done
42# $ for FILE in ./2022-06-28/*; do ./correct_times.sh "${FILE}" --update --over-write; done
43
44
45# Requirements:
46
47# JSON parser: $ sudo apt install jq
48
49# References
50
51# https://stackoverflow.com/questions/16548528/command-to-get-time-in-milliseconds
52# https://stackoverflow.com/questions/28016578/how-can-i-parse-create-a-date-time-stamp-formatted-with-fractional-seconds-utc
53# https://serverfault.com/questions/1020446/linux-get-utc-offset-from-current-or-other-timezones-on-given-date-and-time
54# https://man7.org/linux/man-pages/man1/date.1.html
55
56# ----------------
57# Set the script to halt on errors
58set -e
59
60if [[ -z "$1" ]]; then
61 echo "USAGE: $ 1./correct_times video.mp4 --update --over-write"
62 exit 1
63fi
64
65# Get the movie name
66MOVIE=$1
67
68echo "${MOVIE}"
69# need to use raw mode `-r` in jq call to return a raw string otherwise it will be trouble to deal with
70CREATED=$(ffprobe -loglevel 0 -print_format json -show_format -show_streams "${MOVIE}" | jq -r .format.tags.creation_time)
71
72if [[ -z "${CREATED}" ]]; then
73 echo "ERROR - No creation_time in metadata! ${MOVIE}"
74 exit 1
75fi
76
77echo
78echo "Video Create Time (Zulu): ${CREATED}"
79
80# Convert the creation time to local time
81LOCALTIME=$(date --date="${CREATED}" "+%FT%T %z (%Z)")
82echo "Video Create Time (local): ${LOCALTIME}"
83
84echo "--------"
85# strip the timezone from the string
86STRIPPED=$(TZ=UTC date --date="${CREATED}" "+%FT%T")
87echo "Video Create Time (without timezone):${STRIPPED}"
88
89# Express the string in the local timezone
90TO_LOCAL=$(date --date="${STRIPPED}" "+%FT%T %z (%Z)")
91echo "Video Create Time (to local):${TO_LOCAL}"
92
93echo "--------"
94# convert the local time string to UTC
95CONVERTED=$(TZ=UTC date --date="${TO_LOCAL}" "+%FT%T.%6NZ") # this is the correct time string
96echo "Video Create Time (to UTC):${CONVERTED}"
97echo
98
99# Video Create Time (Zulu): 2022-06-28T12:34:27.000000Z
100# Video Create Time (local): 2022-06-28T08:34:27 -0400 (EDT)
101# --------
102# Video Create Time (without timezone):2022-06-28T12:34:27
103# Video Create Time (to local):2022-06-28T12:34:27 -0400 (EDT)
104# --------
105# Video Create Time (to UTC):2022-06-28T16:34:27.000000Z
106
107# -----------
108
109OUTPUT="${MOVIE%%.*}".corrected."${MOVIE##*.}"
110
111if [ "${2:-"invalid"}" == "--update" ]; then
112
113 # Write the converted timezone to the video
114 echo "Updating metadata ${MOVIE} -> ${OUTPUT}"
115 ffmpeg -i "${MOVIE}" -c copy -metadata creation_time="${CONVERTED}" "${OUTPUT}"
116
117 if [ "${3:-"invalid"}" == "--over-write" ]; then
118
119 echo "Moving ${OUTPUT} -> ${MOVIE}"
120 mv "${OUTPUT}" "${MOVIE}"
121
122 else
123 echo "To overwrite the existing video, use --over-write"
124 fi
125
126else
127 echo "To update the metadata use --update"
128fi
Note
2022-08-20 - fixed a couple of bugs in the scripts that prevented them from handling files with spaces in their names correctly.