#!/bin/sh
#set -x   ## uncomment for a trace
# (tabs are 3-spaces in width)

# 2025-May-10
# This material is free and without warranty.  Use it in any way you see fit.

#+++++ START: USER CONFIGURATION AREA ++++++++++++++++++++++++++++++++++++++++++
#
# Search your system for an "ffmpeg" executable and update the path below.  You
# can use the command "sudo find / -name 'ffmpeg'" to locate a copy of ffmpeg.
# Alternatively, the command "which ffmpeg" will tell you the path if any ffmpeg
# executable is on your search path.  If multiple ffmpeg executables are found,
# determine the version of each ffmpeg by using the command "<path>/ffmpeg -h"
# on each ffmpeg found and choose the latest version.  If no ffmpeg is found,
# find, download and install it (https://ffmpeg.org/download.html).
#
# set FFMPEG_DIR to be the directory containing the ffmpeg executable/program
#FFMPEG_DIR='/Apps/Multimedia/Misc/FFmpeg/ffmpeg-20200424-a501947-macos64-static/bin'
FFMPEG_DIR='/Users/admin/bin'
#
#+++++ END: USER CONFIGURATION AREA ++++++++++++++++++++++++++++++++++++++++++++

if test \( "$1" != '-h' \) -a \( ! -x "$FFMPEG_DIR/ffmpeg" \)
then
	echo "\nERROR: the FFMPEG_DIR needs updating (read the addVideoToVBOdata.sh script) ... exiting${BEL}"
	exit 1
fi

# help prevent "sed: RE error: illegal byte sequence" errors (OS X 10.8!)
LC_CTYPE=C

# "macros" -- well, conceptually, at least
bON=`/usr/bin/tput bold`  # set terminal to bold
bOFF=`/usr/bin/tput rmso`  # set terminal to non-bold
BEL=`/usr/bin/tput bel`  # ring terminal's bell or flash, if set to visual bell

# trap interrupts and cancel processing
trap 'doInterrupt; exit 255' TERM INT
#-------------------------------------------------------------------------------
# handle some interrupts
function doInterrupt()
{
	echo "\nProcessing interrupted -- quitting.${BEL}\n"
	/bin/rm -f "$VBO_WORK_PATHNAME"
}
#-------------------------------------------------------------------------------

# save current IFS (inter-field separator) and set IFS to be a newline, only
IFS_SAVE=$IFS
IFS='
'

OFFSET_ADJUSTMENT_PROCESS="${bON}"'To get the offset time'"${bOFF}"':
- run the addVideoToVBOdata.sh script specifying the VBO and video files and
  using an offset time of 0 msec

- open the generated ...+video.vbo file in Circuit Tools

- select only lap 1 and position at the start of the data

- if the video shows that the lap timing has started (this is most common):
  * write down the lap time that is shown -- i.e., the time the video is
    positioned "into" the lap
    + if the video does not have a lap-time overlay, then get the time using the
      method suggested below, for when the lap timing has not started
  * re-run the addVideoToVBOdata.sh script using an offset that is the negative
    value of the above lap time, converted to milliseconds

- if the lap timing has not started:
  * using either the Utc time or, if you included it, the RaceCapture interval
    time/milliseconds:
    + subtract the time shown at the start of lap 1 position from the time
      shown where the lap timng starts in the video
    + use that time difference as the offset time
  * re-run the addVideoToVBOdata.sh script using the computed offset ime,
    converted to milliseconds'

# option defaults
USE_DOS_EOL='0'

# quick 'n dirty argument/options parsing
if test "$1" = '-h'
then
	echo '
'"${bON}"'Description'"${bOFF}"':
This script reads a .vbo file exported from the rcCSVtoVBO.sh script or from
Harry'"'"'s LapTimer and adds indexing data for a corresponding MP4 or AVI video
file so the video can be shown by RaceLogic'"'"'s Circuit Tools application.

Synopsis:
'"${bON}"'addVideoToVBOdata.sh'"${bOFF}"' [[-h] [-hu] [-w]] [<VBO file pathname>] [<video file pathname>] [<msec timing offset>]

Where: (each of the last 3 arguments must appear last, in that order, if provided)
'"${bON}"'-h'"${bOFF}"' : produces this help message, then exits
'"${bON}"'-hu'"${bOFF}"' : shows this script'"'"'s internal '"${bON}"'USER CONFIGURATION AREA'"${bOFF}"' content, then quits
'"${bON}"'-w'"${bOFF}"' : generates the VBO file with DOS/Windows-style line endings (instead of UNIX)
'"${bON}"'<VBO file pathname>'"${bOFF}"' : provides the full pathname to the .vbo data file
                      (it will be queried for, if not provided as an argument)
'"${bON}"'<video file pathname>'"${bOFF}"' : provides the full pathname to the MP4 or AVI video file
                        (it will be queried for, if not provided as an argument)
'"${bON}"'<msec timing offset>'"${bOFF}"' : provides the time offset in msec between the starting
                       time of the first data entry and the video'"'"'s starting time
                       and is used to synchronize the data entries with the video
                       (it will be queried for, if not provided as an argument)
                       ---
                       it can be a positive or negative value:
                       - '"${bON}"'a negative value'"${bOFF}"' indicates that time of the initial data
                         starts before the video begins -- make offset value more
                         positive (less negative) if video is shown too soon ...
                         i.e., the video is ahead of the data and you want the
                         video to show later frames, earlier/sooner
                       + '"${bON}"'a positive value'"${bOFF}"' indicates that time of the initial data
                         starts after the video begins -- make offset value more
                         negative (less positive) if video is shown too late ...
                         i.e., the video is behind the data and you want the
                         video to show earlier frames, later
                       ---
'"${bON}"'NOTEs'"${bOFF}"':
- only a single video is allowed -- if your video is multiple files, use a
  utility (e.g., ffmpeg) to combine the applicable files into one video
- it is expected that using anything less than a 10 Hz GPS will yield poor
  results due to the limited position/GPS data
- it may be necessary to "fine tune" the offset after viewing in the Circuit
  Tools application

'"$OFFSET_ADJUSTMENT_PROCESS"
	exit
fi

if test "$1" = '-hu'
then
	/usr/bin/sed -e '/START\: USER CONFIG/,/END\: USER CONFIG/ !d' "$0" | \
		/usr/bin/sed -e 's/^\([A-Z][A-Za-z0-9_]*\)=/'"${bON}"'\1'"${bOFF}"'=/'
	exit
fi

if test "$1" = '-w'
then
	USE_DOS_EOL='1'
	shift
	continue
fi

echo '
This script gets a .vbo data file, a video file and, optionally, a timing offset
for that video file then adds data to the .vbo file that enables the video file
'"to be indexed and used by RaceLogic's Circuit Tools application."'


'"From the Finder, drag the .vbo file's icon onto this Terminal to enter it's full pathname."'
-----'
echo 'Enter the full pathname for the .vbo file: '

if test "$1" != ''
then
	VBO_PATHNAME="$1"
	echo "$1"
	shift
else
	read ENTRY
	VBO_PATHNAME=`echo "$ENTRY" | /usr/bin/sed -e 's/^ *//' -e 's/ *$//'`
fi

if test ! -e "$VBO_PATHNAME"
then
	echo "\nERROR: the VBO data file"
	echo "$VBO_PATHNAME"
	echo "could not be found ... exiting${BEL}"
	exit 1
fi

echo
echo
echo "From the Finder, drag the video file's icon onto this Terminal to enter it's pathname."
echo '-----'
echo 'Enter the full pathname for the (MP4 or AVI) video file: '

if test "$1" != ''
then
	VIDEO_PATHNAME="$1"
	echo "$1"
	shift
else
	read ENTRY
	VIDEO_PATHNAME=`echo "$ENTRY" | /usr/bin/sed -e 's/^ *//' -e 's/ *$//'`
fi

if test ! -e "$VIDEO_PATHNAME"
then
	echo "\nERROR: the video file "$VIDEO_PATHNAME" could not be found -- exiting${BEL}"
	echo "$VIDEO_PATHNAME"
	echo "could not be found -- exiting${BEL}"
	exit 1
fi

echo
echo
echo "Enter the time offset in msec between the .vbo file's laptiming start point"
echo "and the video's starting point where:"
/bin/echo -n '-----
it can be a positive or negative value:
- '"${bON}"'a negative value'"${bOFF}"' indicates that time of the initial data
  starts before the video begins -- make offset value more
  positive (less negative) if video is shown too soon ...
  i.e., the video is ahead of the data and you want the
  video to show later frames, earlier/sooner
+ '"${bON}"'a positive value'"${bOFF}"' indicates that time of the initial data
  starts after the video begins -- make offset value more
  negative (less positive) if video is shown too late ...
  i.e., the video is behind the data and you want the
  video to show earlier frames, later

'"$OFFSET_ADJUSTMENT_PROCESS"'
'
/bin/echo -n 'Enter the number of msec offset (no entry means zero): '

if test "$1" != ''
then
	OFFSET_MSEC="$1"
	echo "${bON}$1${bOFF}"
	shift
else
	read OFFSET_MSEC

	if test "$OFFSET_MSEC" = ''
	then
		OFFSET_MSEC=0
	fi
fi

echo '====='

VBO_DIR=`/usr/bin/dirname "$VBO_PATHNAME"`
VBO_FILENAME=`/usr/bin/basename "$VBO_PATHNAME" | \
														/usr/bin/sed -e 's/^\(.*\)\..*$/\1/'`
VBO_SUFFIX=`/usr/bin/basename "$VBO_PATHNAME" | \
														/usr/bin/sed -e 's/^.*\.\(.*\)$/\1/'`
VBO_WORK_PATHNAME="$VBO_DIR/${VBO_FILENAME}-work.$VBO_SUFFIX"
VBO_wVIDEO_PATHNAME="$VBO_DIR/${VBO_FILENAME}+video.$VBO_SUFFIX"
VIDEO_DIR=`/usr/bin/dirname "$VIDEO_PATHNAME"`
VIDEO_FILENAME=`/usr/bin/basename "$VIDEO_PATHNAME" | \
														/usr/bin/sed -e 's/^\(.*\)\..*$/\1/'`
VIDEO_SUFFIX=`/usr/bin/basename "$VIDEO_PATHNAME" | \
														/usr/bin/sed -e 's/^.*\.\(.*\)$/\1/'`

if test "$VBO_SUFFIX" != 'vbo'
then
	echo "\nERROR: the VBO data file"
	echo "$VBO_PATHNAME"
	echo "does not have a .vbo suffix -- exiting${BEL}"
	exit 1
fi

if test "`echo $VIDEO_SUFFIX | /usr/bin/grep -E '[Aa][Vv][Ii]|[Mm][Pp]4'`" = ''
then
	echo "\nERROR: the file"
	echo "$VIDEO_PATHNAME"
	echo "is neither an MP4 nor an AVI video -- exiting${BEL}"
	exit 1
fi

echo

# create a copy of the VBO file that's been converted to UNIX line endings
/usr/bin/tr -d '\r' < "$VBO_PATHNAME" > "$VBO_WORK_PATHNAME"

# get the name of the last field name in the  "[header]" section
LAST_HEADER_ITEM=`\
			/usr/bin/sed -e '1,/\[comments\]/!d' -e '/^$/d' -e '/\[comments\]/d' \
												"$VBO_WORK_PATHNAME" | /usr/bin/tail -n -1`

# determine the duration of the video file, in msec
VIDEO_DURATION=`\
  "$FFMPEG_DIR"/ffmpeg -i "$VIDEO_PATHNAME" 2>&1 | /usr/bin/sed \
   -e '/Duration: /!d' \
   -e 's/^.*Duration: \([0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9][0-9]*\),.*$/\1/'`
HRS=`echo "$VIDEO_DURATION" | /usr/bin/sed -e 's/^\([0-9][0-9]\):.*$/\1/'`
MINS=`echo "$VIDEO_DURATION" | \
								/usr/bin/sed -e 's/^[0-9][0-9]:\([0-9][0-9]\):.*$/\1/'`
SECS=`echo "$VIDEO_DURATION" | \
				/usr/bin/sed -e 's/^[0-9][0-9]:[0-9][0-9]:\([0-9][0-9]\)\..*$/\1/'`
MSECS=`echo "$VIDEO_DURATION" | \
							/usr/bin/sed -e 's/^[0-9][0-9:]*\.\([0-9][0-9]\)$/\1/'`"0"
VIDEO_DURATION_MSEC=`/bin/expr \( $HRS \* 3600000 \) + \( $MINS \* 60000 \) + \
																	\( $SECS \* 1000 \) + $MSECS`

# uncomment for debugging
#echo "VIDEO_DURATION = $VIDEO_DURATION"
#echo "HRS = $HRS"
#echo "MINS = $MINS"
#echo "SECS = $SECS"
#echo "MSECS = $MSECS"
#echo "VIDEO_DURATION_MSEC = $VIDEO_DURATION_MSEC"
# add the video header and the video file entries after the EngineSpeed entry
# NOTE: adding an extra blank/empty line breaks Circuit Tools!

# process all the lines in the VBO file
# - add the time increment (as an integer indicating msec) from one entry to the
#   next as the "avisynctime" padded with leading zeros to 8 digits
/usr/bin/awk -F ' ' '
	BEGIN {
		# load some shell variables into awk
		lastHeaderItem = "'"$LAST_HEADER_ITEM"'"
		vboFilename = "'"$VBO_FILENAME"'"
		videoSuffix = "'"$VIDEO_SUFFIX"'"
		offsetMsec = "'"$OFFSET_MSEC"'"
		videoDuration = "'"$VIDEO_DURATION"'"
		videoDurationMsec = "'"$VIDEO_DURATION_MSEC"'"

		# initialize some needed variables
		inHeaderSection = 0      # if 1, currently processing the header section
		inCommentsSection = 0    # if 1, currently processing the comments section
		inColumnNamesSection = 0 # if 1, currently processing column names section
		inDataSection = 0        # if 1, currently processing the data section
		inUnknownSection = 1     # if 1, currently processing an unknown section
		isFirstDataLine = 1      # if 1, this is the first line in data section
		columnNamesOn1Line = 0   # if 1, column names are all on a single line
		hrs = 0
		mins = 0
		secs = 0
		msecs = 0
		videoIndexMsecs = 0   # number of msec into the video at current data line
		startDataMsec = 0     # time of day in msec of first data-line entry
		currDataMsec = 0      # time of day in msec of current data-line entry
		prevDataMsec = 0      # time of day in msec of previous data-line entry
		currVideoMsec = 0     # msec into video for the current data-line entry
		prevVideoMsec = 0     # msec into video for the previous data-line entry
	}
	{
		# determine the section of the input/VBO file in which this line resides
		if ($1 == "[header]") {
			inHeaderSection = 1
			inCommentsSection = 0
			inColumnNamesSection = 0
			inDataSection = 0
			inUnknownSection = 0
		}
		else if ($1 == "[comments]") {
			inHeaderSection = 0
			inCommentsSection = 1
			inColumnNamesSection = 0
			inDataSection = 0
			inUnknownSection = 0
		}
		else if (($1" "$2) == "[column names]") {
			inHeaderSection = 0
			inCommentsSection = 0
			inColumnNamesSection = 1
			inDataSection = 0
			inUnknownSection = 0
		}
		else if ($1 == "[data]") {
			inHeaderSection = 0
			inCommentsSection = 0
			inColumnNamesSection = 0
			inDataSection = 1
			inUnknownSection = 0
		}
		else if ((substr($0, 1, 1) == "[") &&
					(substr($0, length($0), 1) == "]")) {
			inHeaderSection = 0
			inCommentsSection = 0
			inColumnNamesSection = 0
			inDataSection = 0
			inUnknownSection = 1
		}

		# process lines from various sections of the input/VBO file
		if (inHeaderSection == 1) {
			# copy/output the existing [header] section
			if ($1 != lastHeaderItem) { printf("%s\n", $0) }  # copy/output headers
			else {
				# copy/output the last header item
				printf("%s\n", $0)

				# add the avifileindex & avisynctime fields
				printf("avifileindex\navisynctime\n\n")

				# add the [avi] section
				printf("[avi]\n%s_\n%s\n", vboFilename, videoSuffix)
			}
		}

		if (inCommentsSection == 1) {
			# copy/output the existing [comments] then add some video-linking info
			# section
			if ($1 == "[comments]") {
				printf("%s\n", $0)
			}
			else if (length($0) == 0) {
				# at end of existing comments ... add some video-related comments
				printf("=====\nVideo integration via addVideoToVBOdata.sh\n")
				printf("---\nVideo duration = %s (%s msec)\n", \
															videoDuration, videoDurationMsec)
				printf("msec offset = %s\n", offsetMsec)
				printf("---\nUses video file \"%s_0001.%s\"\n\n", \
																		vboFilename, videoSuffix)
			}
			else { printf("%s\n", $0) }  # copy/output existing comments
		}		

		if (inColumnNamesSection == 1) {
			if (($1" "$2) == "[column names]") { printf("%s\n", $0) } #output names
			else if (length($0) == 0) {
				# at the end of existing column names ... if each column name is on
				# a separate line (not on 1 line), add the video-field column names
				if (columnNamesOn1Line != 1) {
					printf("avifileindex\navisynctime\n")
				}

				printf("\n")
			}
			else {
				# have one or the line of existing column names ...
				# determine whether the column names are formatted as a single line
				if (length($2) != 0) {
					# indicate that all column names are on a single line
					columnNamesOn1Line = 1

					# copy/output the existing single line of column names along with
					# the added avifileindex & avisynctime column names
					printf("%s avifileindex avisynctime", $0)
				}
				else { printf("%s", $0) }  # copy/output a single column name

				printf("\n")
			}
		}

		if (inDataSection == 1) {
			if ($1 == "[data]") { printf("%s\n", $0) }  # copy/output the data line
			else if (length($0) == 0) { printf("\n") }  # copy/output empty lines
			else {
				# from the "time" field as HHMMSS.sss (in UTC + timezone offset),
				# calculate the "time of day" as milliseconds for current data line
				hrs = substr($1, 1, 2)
				mins = substr($1, 3, 2)
				secs =  substr($1, 5, 2)
				msecs = substr($1, 8, 3)
				currDataMsec = \
								(hrs * 3600000) + (mins * 60000) + (secs * 1000) + msecs

				# calculate the video index for the current data line -- i.e., the
				# "location" (number of msec "into" the video) at current data line
				if (isFirstDataLine == 1) {
					if (offsetMsec < 0) {
						currVideoMsec = 0
						videoIndexMsecs = -1
					}
					else {
						currVideoMsec = 0
						videoIndexMsecs = prevVideoMsec + offsetMsec
					}

					startDataMsec = currDataMsec
					isFirstDataLine = 0
				}
				else {
					# if the current data-line time value is less than the previous
					# data-line time value, the time crossed midnight so remove 1 day
					# from the previous data-line time value
					if (currDataMsec < prevDataMsec) {
						prevDataMsec = prevDataMsec - 86400000
						system("echo >&2 ; echo WARNING: the time crossed midnight so Circuit Tools may fail -- fix via a different time-zone offset for rcCSVtoVBO.sh >&2 ; echo >&2")
					}

					# to the current video offset value, add the msec interval
					# between the previous and current "time of day in msec" values
					currVideoMsec = prevVideoMsec + (currDataMsec - prevDataMsec)
					videoIndexMsecs = currVideoMsec + offsetMsec

					if (videoIndexMsecs < 0) { videoIndexMsecs = -1 }
				}

				# copy/output the existing field values and add the avifileindex &
				# avisynctime field values
				printf("%s 0001 %010d\n", $0, videoIndexMsecs)

				# remember previous values
				prevDataMsec = currDataMsec
				prevVideoMsec = currVideoMsec
			}
		}

		if (inUnknownSection == 1) {
				printf("%s\n", $0)  # copy/output all lines in "unknown" sections
		}
	}
' "$VBO_WORK_PATHNAME" > "$VBO_wVIDEO_PATHNAME"

echo 'Created the "VBO with Video" file:'
echo "$VBO_wVIDEO_PATHNAME"

# if required, (remove/re)create a symbolic link to the video so the video is
# accessible via the required video name
LINK_WAS_CREATED='0'

if test -L "$VBO_DIR/${VBO_FILENAME}_0001.$VIDEO_SUFFIX"
then
	# a properly named symbolic link to the supplied video file exists so,
	# re-create the symbolic link to the supplied video file (i.e., to ensure
	# it's linked to the correct/supplied file)
	(cd "$VBO_DIR" ; /bin/ln -fs "./$VIDEO_FILENAME.$VIDEO_SUFFIX" \
									"./${VBO_FILENAME}_0001.$VIDEO_SUFFIX" 2>/dev/null)
	echo
	echo "A symbolic link to the supplied video file was re-created: ${VBO_FILENAME}_0001.$VIDEO_SUFFIX"
else
	if test ! -f "$VBO_DIR/${VBO_FILENAME}_0001.$VIDEO_SUFFIX"
	then
		# the required video file or link does not exist, so create a symbolic link
		# to the supplied video file
		(cd "$VBO_DIR" ; /bin/ln -s "./$VIDEO_FILENAME.$VIDEO_SUFFIX" \
													"./${VBO_FILENAME}_0001.$VIDEO_SUFFIX")
		echo
		echo "A symbolic link to the supplied video file was created: ${VBO_FILENAME}_0001.$VIDEO_SUFFIX"
	else
		echo
		echo "The required video file exists: ${VBO_FILENAME}_0001.$VIDEO_SUFFIX"
	fi
fi

# if required, convert the file's EOLs to DOS/Windows-style EOLs
if test "$USE_DOS_EOL" = '1'
then
	/usr/bin/awk \
		'{ printf("%s\r\n", $0) }' < "$VBO_wVIDEO_PATHNAME" > "$VBO_WORK_PATHNAME"
	/bin/mv "$VBO_WORK_PATHNAME" "$VBO_wVIDEO_PATHNAME"
	echo "\n${bON}NOTE${bOFF}: for Windows, copy/rename the video"
	echo "$VIDEO_PATHNAME"
	echo "to"
	echo "$VBO_DIR/${VBO_FILENAME}_0001.$VIDEO_SUFFIX"
else
	/bin/rm -f "$VBO_WORK_PATHNAME"
fi

echo
echo 'Processing completed.'

# restore the IFS (in case this script gets "inlined" into another script)
IFS=$IFS_SAVE
