Skip to content

Instantly share code, notes, and snippets.

@teknoraver
Last active August 1, 2018 15:37
Show Gist options
  • Save teknoraver/a6004d91a8dc622be4ceffe78b1d4111 to your computer and use it in GitHub Desktop.
Save teknoraver/a6004d91a8dc622be4ceffe78b1d4111 to your computer and use it in GitHub Desktop.
create map track video from Exif tags
#!/bin/sh
# getmap.sh - download static maps from Google Maps
# Copyright (C) 2018 Matteo Croce <mcroce@redhat.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
[ $# -lt 2 ] && exec echo "usage: $0 <file.mp4> *.jpg"
out=$1
shift
for i in mencoder MP4Box phototrack; do
which $i 2>/dev/null >/dev/null || exec echo "missing tool: $i"
done
key='<your google key>'
trap "rm -rf /tmp/map-$$.csv /tmp/map-$$.264 /tmp/map-$$" EXIT
phototrack "$@" >/tmp/map-$$.csv
i=0
urls=$(awk -F, '!/^timestamp/{print "https://maps.googleapis.com/maps/api/staticmap?language=it&key='"$key"'&center="$2","$3"&zoom=7&scale=2&size=320x320&markers=anchor:center|icon:http://i.imgur.com/ODjBo2X.png|"$2","$3}' /tmp/map-$$.csv)
mkdir /tmp/map-$$
for u in $urls; do
n=$(printf %05d $i)
curl -s "$u" >"/tmp/map-$$/$n.png"
i=$((i + 1))
echo -ne "\r$n"
done
mencoder "mf:///tmp/map-$$/*.png" -vf harddup -nosound -ovc x264 -x264encopts crf=22:preset=slower -of rawvideo -o /tmp/map-$$.264
MP4Box -new -no-iod -no-sys -brand mp42 -fps 25 -add /tmp/map-$$.264 "$out"
ls -lh "$out"
/*
* phototrack - get track CSV file from Exif tags
* Copyright (C) 2018 Matteo Croce <mcroce@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"os"
"regexp"
"strconv"
"time"
"github.com/xiam/exif"
)
const (
timeLayout = "2006:01:02 15:04:05"
)
type latLong struct {
lat float64
lon float64
}
func exifDate(s string) (time.Time, error) {
return time.ParseInLocation(timeLayout, s, time.Local)
}
var coordRe = regexp.MustCompile(`^(\d+),\s+(\d+),\s+(\d+\.\d+)(\s+[NESW])?$`)
func exifLatLong(s string) (latlong float64, err error) {
coord := coordRe.FindStringSubmatch(s)
if len(coord) != 4 && len(coord) != 5 {
return 0, fmt.Errorf("invalid coord %s", s)
}
deg, err := strconv.Atoi(coord[1])
if err != nil {
return 0, err
}
min, err := strconv.Atoi(coord[2])
if err != nil {
return 0, err
}
sec, err := strconv.ParseFloat(coord[3], 64)
if err != nil {
return 0, err
}
decimal := float64(deg) + float64(min)/60 + float64(sec)/3600
if len(coord[4]) > 0 {
switch coord[4] {
case "W":
fallthrough
case "S":
decimal = -decimal
case "N":
case "E":
default:
return 0, fmt.Errorf("invalid cardinal %s", coord[4])
}
}
return decimal, nil
}
func getTags(path string) (int64, *latLong, error) {
data, err := exif.Read(path)
if err != nil {
return 0, nil, err
}
date, err := exifDate(data.Tags["Date and Time"])
if err != nil {
return 0, nil, err
}
var ll *latLong
if lat, err := exifLatLong(data.Tags["Latitude"]); err == nil {
if lon, err := exifLatLong(data.Tags["Longitude"]); err == nil {
ll = &latLong{lat, lon}
}
}
return date.Unix(), ll, nil
}
func printScaled(times []int64, start, end *latLong) {
for i, ts := range times {
fmt.Printf("%d,%f,%f\n", ts,
start.lat+(end.lat-start.lat)/float64(len(times)+1)*float64(i+1),
start.lon+(end.lon-start.lon)/float64(len(times)+1)*float64(i+1))
}
}
func printTags(paths []string) error {
var lastLL *latLong
var lastTS []int64
for _, file := range paths {
date, coord, err := getTags(file)
if err != nil {
return err
}
if coord != nil {
if lastTS != nil {
printScaled(lastTS, lastLL, coord)
lastTS = nil
}
fmt.Printf("%d,%f,%f\n", date, coord.lat, coord.lon)
lastLL = coord
} else {
lastTS = append(lastTS, date)
}
}
return nil
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "files...")
os.Exit(1)
}
fmt.Println("timestamp,latitude,longitude")
if err := printTags(os.Args[1:]); err != nil {
panic(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment