#!/usr/bin/env bash
version="v1.1"
mode=1
loglinenum=1
loglinenummatched=0
logline="start"
logtimemsprev=0
logended=0
logtarget=0
logmatch=1
loggotpos=0
typical_offset="notset"
jpgtot=0
jpgtagged=0
jpgmiss=0
jpgnotinlog=0
camindex=0
camtot=0
trigtot=0
logmiss=0
revjumps=0
difflimit=10
running=1
cmdarg=$*
FILE=jpglist
ML="false"
logsdone=0
SKIPIMG=0
while [[ $# > 0 ]]
do
key="$1"
case $key in
-w|--write)
WRITE="true"
;;
-r|--readonly)
WRITE="false"
;;
-f|--find)
WRITE="false"
FIND="true"
;;
-p|--pos)
MODE="POS"
typical_offset="$2"
shift
;;
-dl|--difflimit)
difflimit="$2"
shift
;;
-c|--cam)
MODE="CAM"
;;
-t|--trig)
MODE="TRIG"
;;
-ns|--nosubsec)
NS="true"
;;
-ml|--multilog)
ML="true"
;;
-del|--delorg)
DELORG="true"
;;
-sp|--skipphoto)
SKIPIMG="$2"
shift
;;
-sc|--skipcam)
SKIPCAM="$2"
shift
;;
-st|--skiptrig)
SKIPTRIG="$2"
shift
;;
-sl|--skiplog)
SKIPLOG="$2"
shift
;;
-h|--help)
HELP="true"
;;
*)
;;
esac
shift
done
if [[ "$HELP" == "true" ]] || [[ "$WRITE" == "" ]]
then
echo "Geotagging script by André Kjellstrup. $version"
echo "execute inside a folder containing .JPG and .BIN"
echo "example: geotag.sh -c -sp 2 -r"
echo ""
echo "-w or --write Enables photo processing"
echo "-r or --readonly Enables photo processing"
echo "-f or --readonly, finds CAM to exif spacing for +/- 10 skipped photos, and +/- 10 skipped CAM messages"
echo "-sp x or --skipphoto x Skips x photos counting from the start"
echo "-sc x or --skipcam x Skips x CAM entrys from the start"
echo "-sl x or --skiplog x Skips x POS entrys from the start"
echo "-st x or --skiptrig x Skips x TRIG entrys from the start"
echo "-p x or --pos x Use POS log only (if camera was independendly trigged) x=offset in seconds"
echo "-dl x or --difflimit x Acceptable limit in 10ms steps for POS-log vs photo differance 10=0.1s (default)"
echo "-c or --cam Use CAM log only (do not fall back to POS pessages)"
echo "-t or --trig Use TRIG log only (if camera shutter is sensed)"
echo "-ns or --nosubsec Do not look for subsecond EXIF tag."
echo "-ml or --multilog Grab and combine all .BIN files (for processing photos from more then one flight."
echo "-del or --delorg : Delete JPG_original (backups)."
echo "EXAMPLE:"
echo "Multiple flights dataset from S100; geotag.sh -p 0 -ns -dl 30 -w -ml -del"
echo "simgle CAM flight; geotag.sh -c -w -del"
exit 1
fi
command -v mavlogdump.py >/dev/null 2>&1 || { echo >&2 "I require mavlogdump.py but it's not installed. Aborting."; exit 1; }
command -v exiftool >/dev/null 2>&1 || { echo >&2 "I require exiftool but it's not installed. Aborting."; exit 1; }
if [[ "$WRITE" == "true" ]]
then
echo "INFO: will backup and modify photos" | tee geotag.log
fi
if [[ "$WRITE" == "false" ]]
then
echo "INFO: dry run only, no photos will be modified" | tee geotag.log
fi
if [[ "$MODE" == "POS" ]]
then
echo "INFO: use POS records only, expected offset of $typical_offset , poslimit (time of photo vs log is $poslimit)" | tee geotag.log
fi
if [[ "$MODE" == "CAM" ]]
then
echo "INFO: use CAM records only" | tee geotag.log
fi
if [[ "$MODE" == "TRIG" ]]
then
echo "INFO: use TRIG records only" | tee geotag.log
fi
if [[ "$NS" == "true" ]]
then
echo "INFO: Do not look for subsecond-time in EXIF" | tee geotag.log
fi
if [[ "$SKIPIMG" -gt "0" ]]
then
echo "INFO: will skip first $SKIPIMG photos" | tee geotag.log
fi
if [[ "$SKIPCAM" -gt "0" ]]
then
echo "INFO: will skip first $SKIPCAM CAM logs" | tee geotag.log
fi
if [[ "$SKIPTRIG" -gt "0" ]]
then
echo "INFO: will skip first $SKIPTRIG TRIG logs" | tee geotag.log
fi
if [[ "$SKIPLOG" -gt "0" ]]
then
echo "INFO: will skip first $SKIPLOG POS logs" | tee geotag.log
loglinenum=$SKIPLOG
fi
if [ "$difflimit" != 10 ]
then
echo "INFO: Difflimit is changed to $difflimit (10=0.1s)" | tee geotag.log
fi
echo "INFO: using arguments $cmdarg " | tee geotag.log
jpglistindex=$(( 1 + $SKIPIMG))
function readexif
{
if [[ "$NS" == "true" ]]
then
jpgdatetime=$(exiftool -ee -p '$datetimeoriginal' "$jpgname" -n)
jpgdatetime="$jpgdatetime.50"
else
jpgdatetime=$(exiftool -ee -p '$subsecdatetimeoriginal' "$jpgname" -n)
fi
jpgdate=$(echo -n "$jpgdatetime" | head -c10)
jpgdate=$(echo "$jpgdate" | tr : -)
jpgtime=$(echo -n "$jpgdatetime" | tail -c-11)
jpgtimems=$(echo -n "$jpgtime" | tail -c-2)
jpgtimems=$(echo "scale=2; ($jpgtimems /100)" | bc -l)
}
function readlog
{
logdate=$(echo "$logline"| grep -o '^\S*')
logtime=$(echo "$logline"| awk -F" " '{print $2}')
logtime=$(echo -n "$logtime" | head -c-1)
logtimems=$(echo -n "$logtime" | tail -c-2)
logtimems=$(echo "scale=2; ($logtimems /100)" | bc -l)
if [[ "$logline" == *POS* ]]
then
lat=$(echo "$logline"| grep -o -P '(?<=Lat : ).*(?=, Lng)')
lon=$(echo "$logline"| grep -o -P '(?<=Lng : ).*(?=, Alt)')
alt=$(echo "$logline"| grep -o -P '(?<= Alt : ).*(?=, RelHomeAlt)')
fi
if [[ "$logline" == *CAM* ]]
then
lat=$(echo "$logline"| grep -o -P '(?<=Lat : ).*(?=, Lng)')
lon=$(echo "$logline"| grep -o -P '(?<=Lng : ).*(?=, Alt)')
alt=$(echo "$logline"| grep -o -P '(?<=Alt : ).*(?=, RelAlt)')
fi
}
function geotag
{
posWGS84=( $(echo "$lon $lat $alt" | cs2cs +proj=latlong +datum=WGS84))
lonWGS84ref=$(echo -n "${posWGS84[0]}" | tail -c-1)
lonWGS84pos=$(echo -n "${posWGS84[0]}" | head -c-1)
latWGS84ref=$(echo -n "${posWGS84[1]}" | tail -c-1)
latWGS84pos=$(echo -n "${posWGS84[1]}" | head -c-1)
if [[ "$WRITE" == "true" ]]
then
exiftool -exif:gpsmapdatum="WGS-84" -exif:gpsaltitude="$alt" -exif:gpsaltituderef="Above Sea Level" -exif:gpslongitude="$lonWGS84pos" -exif:gpslongituderef="$lonWGS84ref" -exif:gpslatitude="$latWGS84pos" -exif:gpslatituderef="$latWGS84ref" "$jpgname" >/dev/null &
fi
let "jpgtagged++"
echo "ALT= "$alt" LON= "$lonWGS84pos" LATref= "$lonWGS84ref" LAT= "$latWGS84pos" LATref= "$latWGS84ref""
}
function report
{
echo "Report:" | tee -a geotag.log
echo "Found $jpgtot photos" | tee -a geotag.log
echo "Found $camtot CAM log lines" | tee -a geotag.log
echo "Found $trigtot TRIG log lines" | tee -a geotag.log
echo "Time between first photo and log was ""$typical_offset""s" | tee -a geotag.log
if [[ "$WRITE" == "true" ]]
then
echo "Tagged $jpgtagged photos" | tee -a geotag.log
else
echo "Could have tagged $jpgtagged photos" | tee -a geotag.log
fi
if [[ "$MODE" != "POS" ]]
then
echo "Detected $jpgmiss missing photos" | tee -a geotag.log
echo "Detected $logmiss missing $MODE messages" | tee -a geotag.log
else
echo "(Unable to detect and report missing pictures and CAM messages in POS mode)" | tee -a geotag.log
fi
if [[ "$jpgnotinlog" -gt 0 ]]
then
echo "FAILED: to tag $jpgnotinlog jpg file(s) where no POS log matched (maybe increase difflimit?)" | tee -a geotag.log
fi
if [[ "$DELORG" == "true" ]]
then
echo "INFO: Deleting original .JPG that were cloned by EXIF" | tee -a geotag.log
sleep 1s
rm *.JPG_original
fi
if [[ "$WRITE" == "true" ]]
then
mv geotag.log geotagwrite.log
fi
}
function loadlogdump
{
echo "INFO: Loading logdump into memory..." | tee -a geotag.log
OLDIFS=$IFS
IFS=$'\012'
logarray=( $(<logdump) )
IFS=$OLDIFS
logarraysize=${#logarray[@]}
echo "INFO: done, loaded $logarraysize lines." | tee -a geotag.log
}
function loadcamdump
{
echo "INFO: Loading camdump into memory..." | tee -a geotag.log
OLDIFS=$IFS
IFS=$'\012'
logarray=( $(<camdump) )
IFS=$OLDIFS
logarraysize=${#logarray[@]}
echo "INFO: done, loaded $logarraysize lines." | tee -a geotag.log
}
function posloop
{
echo "INFO: Searching within POS log..." | tee -a geotag.log
loggotpos=0
while [[ "$running" == 1 ]]
do
if [ "$logmatch" == 1 ]
then
jpgname=$(awk 'NR=="'"$jpglistindex"'"{print;exit}' jpglist)
if [ "$jpgname" == "" ]
then
echo "END: Last photo processed" | tee -a geotag.log
running=0
report
exit 1
fi
readexif
fi
logtarget=0
if [[ "$loglinereverse" -eq 1 || "$camindex" -eq "$camtot" || "$MODE" == "POS" ]]
then
POSONLY="true"
else
POSONLY="false"
fi
while [ "$logtarget" -eq 0 ]
do
let "loglinenum++"
if [[ "$loglinenum" -gt "$logarraysize" ]]
then
echo "ERROR: Log ended at $loglinenum of $logarraysize while looking for $MODE message for filename $jpgname , maybe this photo is not a part of dataset, or in log, ignoring." | tee -a geotag.log
jpgname=$(awk 'NR=="'"$jpglistindex"'"{print;exit}' jpglist)
let "loglinenum = loglinenummatched"
let "jpglistindex++"
if [ "$jpgname" == "" ]
then
echo "END: Last photo processed" | tee -a geotag.log
running=0
report
exit 1
fi
readexif
fi
logline=${logarray["$loglinenum"]}
if [[ "$logline" == *CAM* ]]
then
logtype="CAM"
let "camindex++"
logtarget=1
readlog
fi
if [[ "$logline" == *POS* ]] && [[ "$POSONLY" == "true" || "$loglinereverse" -eq 1 ]]
then
loggotpos=1
logtype="POS"
logtarget=1
readlog
fi
if [[ "$SKIPCAM" -gt 0 ]] && [[ "$logtarget" -eq 1 ]]
then
let "SKIPCAM--"
logtarget=0
fi
done
if [[ "$loglinenum" -gt "$logarraysize" ]]
then
echo "ERROR: Log ended at $loglinenum of $logarraysize while looking for CAM message for filename $jpgname maybe there are too many photos" | tee -a geotag.log
running=0
report
exit 1
fi
diff=$(( ( $(date -ud "$jpgdate $jpgtime" +'%s') - $(date -ud "$logdate $logtime" +'%s') ) ))
difftot=$(echo "scale=2; ($diff+($jpgtimems - $logtimems))" | bc -l)
differr=$(( ( $(date -ud "$logdate $logtime" +'%s') - $(date -ud "$logdateprev $logtimeprev" +'%s') ) ))
difftoterr=$(echo "scale=2; ($differr+($logtimems - $logtimemsprev))" | bc -l)
difftotoff=$(echo "scale=2; ($difftot - $typical_offset)" | bc -l)
difftoterroff=$(echo "scale=2; ($differr - $typical_offset)" | bc -l)
if [ "$typical_offset" == "notset" ]
then
typical_offset=$difftot
echo "INFO: Expected time offset between camera and log is ""$typical_offset""s (positive = camera is ahead)" | tee -a geotag.log
fi
diffeee=$(echo "(($difftot - $typical_offset)*100)" | bc -l | cut -f1 -d"." )
if [[ "$diffeee" -gt "$difflimit" ]] || [[ "$diffeee" -lt -"$difflimit" ]]
then
if [[ "$MODE" == "POS" ]]
then
logmatch=0
if [[ "$diffeee" -gt 50 ]]
then
let "loglinenum=loglinenum+25"
fi
if [[ "$diffeee" -lt -25 ]]
then
let "loglinenum=loglinenum-30"
let "revjumps++"
if [[ "$revjumps" -gt 30 ]]
then
echo "ERROR: Failed to find log time for image $jpgname, it's not in log or does not belong to this dataset.( maybe try higher difflimit ?)" | tee -a geotag.log
let "jpgnotinlog++"
let "jpglistindex++"
let "revjumps=0"
logmatch=1
fi
fi
fi
else
percent=$(echo "scale=2; (($jpglistindex/$jpgtot)*100)" | bc -l | cut -f1 -d".")
echo "MATCHED: ""$percent""% Done, time diff.: ""$difftotoff""s between $jpgname $jpgdate $jpgtime & $logtype log $loglinenum $logdate $logtime" | tee -a geotag.log
let "revjumps=0"
geotag
logmatch=1
let "jpglistindex++"
let "loglinenummatched = loglinenum + 1"
fi
logdateprev=$logdate
logtimeprev=$logtime
logtimemsprev=$logtimems
logtarget=0
done
}
function camloop
{
echo "INFO: Searching for CAM only..." | tee -a geotag.log
loggotpos=0
while [[ "$running" == 1 ]] ; do
if [ "$logmatch" == 1 ]
then
jpgname=$(awk 'NR=="'"$jpglistindex"'"{print;exit}' jpglist)
if [ "$jpgname" == "" ] ; then
echo "END: Last photo processed" | tee -a geotag.log
running=0
report
exit 1
fi
readexif
fi
logtarget=0
while [ "$logtarget" -eq 0 ] ; do
let "loglinenum++"
logline=${logarray["$loglinenum"]}
if [[ "$logline" == *"$MODE"* ]] ; then
logtype="$MODE"
logtarget=1
readlog
fi
if [[ "$MODE" == "CAM" && "$SKIPCAM" -gt 0 ]] && [[ "$logtarget" -eq 1 ]] ; then
let "SKIPCAM--"
echo "INFO: Skipped a CAM log line." | tee -a geotag.log
logtarget=0
fi
if [[ "$MODE" == "TRIG" && "$SKIPTRIG" -gt 0 ]] && [[ "$logtarget" -eq 1 ]] ; then
let "SKIPTRIG--"
logtarget=0
fi
done
if [[ "$loglinenum" -gt "$logarraysize" ]] ; then
echo "ERROR: Log ended at $loglinenum of $logarraysize while looking for $MODE message for filename $jpgname maybe there are too many photos" | tee -a geotag.log
running=0
report
exit 1
fi
diff=$(( ( $(date -ud "$jpgdate $jpgtime" +'%s') - $(date -ud "$logdate $logtime" +'%s') ) ))
difftot=$(echo "scale=2; ($diff+($jpgtimems - $logtimems))" | bc -l)
differr=$(( ( $(date -ud "$logdate $logtime" +'%s') - $(date -ud "$logdateprev $logtimeprev" +'%s') ) ))
difftoterr=$(echo "scale=2; ($differr+($logtimems - $logtimemsprev))" | bc -l)
difftotoff=$(echo "scale=2; ($difftot - $typical_offset)" | bc -l)
difftoterroff=$(echo "scale=2; ($differr - $typical_offset)" | bc -l)
if [ "$typical_offset" == "notset" ] ; then
typical_offset=$difftot
echo "INFO: Expected time offset between camera and log is ""$typical_offset""s (positive = camera is ahead)" | tee -a geotag.log
fi
difflimit=100
diffeee=$(echo "(($difftot - $typical_offset)*100)" | bc -l | cut -f1 -d"." )
if [[ "$diffeee" -gt "$difflimit" ]] || [[ "$diffeee" -lt -"$difflimit" ]]
then
logmatch=0
if [[ "$diffeee" -lt -100 ]] ; then
echo "WARNING: Found extra photo for which there is no $MODE event. I am at logline $loglinenum (Details below)"
echo "WARNING: Big Time diff.: ""$difftotoff""s between $jpgname date $jpgtime and $logtype logline $loglinenum $logdate $logtime Time since last $MODE event was ""$difftoterr""s" | tee -a geotag.log
logmatch=1
let "loglinenum=loglinenummatched"
let "jpglistindex++"
let "logmiss++"
fi
else
if [[ $logmatch -eq 0 ]] && [[ "$logmatcgfailinrow" -lt 5 ]] ; then
let "jpgmiss++"
echo "ERROR: MISSING PHOTO #$jpgmiss detected, matched current photo with next logged $logtype logline" | tee -a geotag.log
fi
if [[ $logmatch -eq 0 ]] && [[ "$logmatcgfailinrow" -gt 4 ]] ; then
let "logmiss++"
echo "ERROR: MISSING $MODE log entry detected, matched current photo with logged $logtype logline" | tee -a geotag.log
fi
percent=$(echo "scale=2; (($jpglistindex/$jpgtot)*100)" | bc -l | cut -f1 -d".")
echo "MATCHED: ""$percent""% Done, time diff.: ""$difftotoff""s between $jpgname $jpgdate $jpgtime & $logtype log $loglinenum $logdate $logtime Time since last $MODE command ""$difftoterr""s" | tee -a geotag.log
geotag
logmatch=1
logmatcgfailinrow=0
let "jpglistindex++"
let "loglinenummatched = loglinenum + 1"
loglinereverse=0
fi
logdateprev=$logdate
logtimeprev=$logtime
logtimemsprev=$logtimems
logtarget=0
done
}
find . -maxdepth 1 -name "*.[Jj][Pp][Gg]" | sort >jpglist
jpgtot=$(wc -l < jpglist)
echo "INFO: Found $jpgtot photos" | tee -a geotag.log
if [ -f ./logdump ]; then
echo "INFO: found logdump"
else
binname=$(find . -maxdepth 1 -name "*.[Bb][Ii][Nn]")
if [[ "$binname" == "" ]]; then
echo "ERROR: .BIN log not found, make sure directory contain a .BIN or .bin log or logdump file." | tee -a geotag.log
exit 1
fi
for binname in $(find . -maxdepth 1 -name "*.[Bb][Ii][Nn]"); do
if [[ "$ML" == "true" ]]; then
echo "INFO: adding $binname to logdump..." | tee -a geotag.log
mavlogdump.py --types POS,CAM,TRIG --format standard "$binname" >> logdump
else
if [[ "$logsdone" < 1 ]]; then
echo "INFO: dumping $binname to logdump..." | tee -a geotag.log
mavlogdump.py --types POS,CAM,TRIG --format standard "$binname" > logdump
fi
fi
let "logsdone ++"
done
fi
echo "INFO: done" | tee -a geotag.log
if [ ! -f ./camdump ] && [[ "$MODE" != "POS" ]]; then
echo "INFO: no camdump found, exporting loglines from .bin to camdump..." | tee -a geotag.log
mavlogdump.py --types CAM,TRIG --format standard "$binname" > camdump
echo "INFO: done" | tee -a geotag.log
else
echo "INFO: camdump found" | tee -a geotag.log
fi
camtot=$(grep CAM logdump | wc -l)
echo "INFO: Found $camtot CAM log lines (camera shutter sensed)" | tee -a geotag.log
trigtot=$(grep TRIG logdump | wc -l)
echo "INFO: Found $trigtot TRIG log lines (commands to shoot)" | tee -a geotag.log
loadlogdump
if [[ "$MODE" == "POS" ]]; then
posloop
else
camloop
fi