Last active
February 19, 2023 09:20
-
-
Save wjlafrance/d5ceb7957287d37b41fa38f43d71dab8 to your computer and use it in GitHub Desktop.
Ingest GoPro videos, extract telemetry with exiftool, and merge/compress with ffmpeg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#------------------------------------------------------------------------------ | |
# File: gpx.fmt | |
# | |
# Description: Example ExifTool print format file to generate a GPX track log | |
# | |
# Usage: exiftool -p gpx.fmt -ee3 FILE [...] > out.gpx | |
# | |
# Requires: ExifTool version 10.49 or later | |
# | |
# Revisions: 2010/02/05 - P. Harvey created | |
# 2018/01/04 - PH Added IF to be sure position exists | |
# 2018/01/06 - PH Use DateFmt function instead of -d option | |
# 2019/10/24 - PH Preserve sub-seconds in GPSDateTime value | |
# 2023/02/19 - WJL Change version to 1.1 to support Garmin VIRB Edit | |
# | |
# Notes: 1) Input file(s) must contain GPSLatitude and GPSLongitude. | |
# 2) The -ee3 option is to extract the full track from video files. | |
# 3) The -fileOrder option may be used to control the order of the | |
# generated track points when processing multiple files. | |
# 4) Coordinates are written at full resolution. To change this, | |
# remove the "#" from the GPSLatitude/Longitude tag names below | |
# and use the -c option to set the desired precision. | |
#------------------------------------------------------------------------------ | |
#[HEAD]<?xml version="1.1" encoding="utf-8"?> | |
#[HEAD]<gpx version="1.1" | |
#[HEAD] creator="ExifTool $ExifToolVersion" | |
#[HEAD] xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
#[HEAD] xmlns="http://www.topografix.com/GPX/1/1" | |
#[HEAD] xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/0/gpx.xsd"> | |
#[HEAD]<trk> | |
#[HEAD]<number>1</number> | |
#[HEAD]<trkseg> | |
#[IF] $gpslatitude $gpslongitude | |
#[BODY]<trkpt lat="$gpslatitude#" lon="$gpslongitude#"> | |
#[BODY] <ele>$gpsaltitude#</ele> | |
#[BODY] <time>${gpsdatetime#;my ($ss)=/\.\d+/g;DateFmt("%Y-%m-%dT%H:%M:%SZ");s/Z/${ss}Z/ if $ss}</time> | |
#[BODY]</trkpt> | |
#[TAIL]</trkseg> | |
#[TAIL]</trk> | |
#[TAIL]</gpx> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bb | |
(require '[clojure.tools.cli :refer [parse-opts]]) | |
(require '[babashka.process :refer [shell]]) | |
(def sibling-files | |
(set (map #(.getName %) | |
(file-seq (clojure.java.io/file "."))))) | |
(defn ingest-video [video-number] | |
(let [files (filter #(contains? sibling-files %) | |
(map #(format "GH%02d%s.MP4" % video-number) | |
(range 1 100)))] | |
(println "Ingesting files:" files) | |
(let [gpx-data (->> (shell {:out :string} (str "exiftool -ee -p gpx.fmt " (str/join " " files))) | |
:out | |
str/split-lines) | |
timestamp (->> gpx-data | |
(map #(re-matches #"\s*<time>(.+)</time>" %)) | |
(remove nil?) | |
first | |
second | |
(filter #(Character/isDigit %)) | |
(apply str)) | |
gpx-filename (str "GoPro-" timestamp ".gpx") | |
files-filename (str "GoPro-" timestamp ".files") | |
mp4-filename (str "GoPro-" timestamp ".mp4")] | |
(println "Writing filelist: " files-filename) | |
(spit files-filename (str/join "\n" (map #(str "file '" % "'") files))) | |
(println "Writing GPX file:" gpx-filename) | |
(spit gpx-filename (str/join "\n" gpx-data)) | |
(println "Ready to run ffmpeg:") | |
(println (str " $ ffmpeg -f concat -i " files-filename " -vcodec libx265 -crf 30 -acodec copy " mp4-filename))))) | |
(defn preflight-ingest-video [first-filename] | |
(if (not (and (= "GH" (subs first-filename 0 2)) | |
(= ".MP4" (subs first-filename 8 12)))) | |
(println "Filename does not match expected pattern.") | |
(let [video-number (subs first-filename 4 8)] | |
(if (not (= "01" (subs first-filename 2 4))) | |
(println "Does not appear to be the first video. Try" (str "GH01" video-number ".MP4")) | |
(ingest-video video-number))))) | |
(def required-opts #{:input}) | |
(def cli-options | |
[["-i" "--input INPUT.MP4" "First video file" | |
:validate-fn #(contains? sibling-files %) | |
:validate-msg "File not found in current directory"]]) | |
(defn missing-required? | |
"Returns true if opts is missing any of the required-opts" | |
[opts] | |
(not-every? opts required-opts)) | |
(let [{:keys [options errors]} (parse-opts *command-line-args* cli-options)] | |
(if errors | |
(doseq [error errors] | |
(println error)) | |
(if (missing-required? options) | |
(println "Missing required option(s).") | |
(preflight-ingest-video (:input options))))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment