Skip to content

Instantly share code, notes, and snippets.

@ncoder-1
Last active March 2, 2020 22:36
Show Gist options
  • Save ncoder-1/e3c46e1f0af4009c89f4ae48e853c336 to your computer and use it in GitHub Desktop.
Save ncoder-1/e3c46e1f0af4009c89f4ae48e853c336 to your computer and use it in GitHub Desktop.
Convert GoPro Hero7/Hero8 videos AAC to PCM which can be read with DaVinci Resolve Studio 16 in Linux
#!/usr/bin/env bash
#
# Convert GoPro Hero7/Hero8 videos AAC to PCM which can be read with DaVinci Resolve Studio 16 in Linux
# Also keeps GoPro's metadata
#
# Required dependency: ffmpeg 4.1
# To do: per-stream error handling, actually use datastream count and more efficient parsing of ffprobe
#
# Free for the taking.
#
# Source: https://askubuntu.com/questions/907398/how-to-convert-a-video-with-ffmpeg-into-the-dnxhd-dnxhr-format
# Source: https://www.reddit.com/r/ffmpeg/comments/8qosoj/merging_raw_gpmd_as_metadata_stream/
# Source: http://coderunner.io/how-to-compress-gopro-movies-and-keep-metadata/
# Source: https://trac.ffmpeg.org/wiki/FFprobeTips
#
# gopro_aac_to_pcm.sh
VERSION=1.0
# Check if input file exists
if [[ ! -f "$1" ]]; then
echo "Input file not found!"
exit 1
fi
DEBUG=1
VERBOSE=info
AUDIO_CODEC=pcm_s16le
# Set to 0 to disable copying of all GoPro datastreams
COPY_GOPROMETADATA=1
# Following handler_names has a tab character at the beginning and 2 spaces at the end
GOPROTCD=" GoPro TCD "
GOPROMET=" GoPro MET "
GOPROSOS=" GoPro SOS "
#####
##### End of user variables
#####
if [[ $DEBUG -ne 0 ]]; then echo "Settings: $AUDIO_CODEC"; fi
FFMPEG_PARAM=('-i' "$1")
FFMPEG_PARAM+=('-v' "$VERBOSE")
FFMPEG_PARAM+=('-vcodec' "copy")
FFMPEG_PARAM+=('-acodec' "$AUDIO_CODEC")
FFMPEG_PARAM+=('-copy_unknown')
FFMPEG_PARAM+=('-map' "0:v")
FFMPEG_PARAM+=('-map' "0:a")
NUM_STREAMS=$(ffprobe -v error -show_entries format=nb_streams -of default=noprint_wrappers=1:nokey=1 "$1")
if ((NUM_STREAMS < 3)); then
echo "Something is wrong here...maybe only video/audio streams?"
exit 1
fi
if [[ $DEBUG -ne 0 ]]; then echo "$NUM_STREAMS" streams found; fi
GOPRO_NAME_DATASTREAM0=$(ffprobe -v error -select_streams d:0 -show_entries stream_tags=handler_name -of default=noprint_wrappers=1:nokey=1 "$1")
GOPRO_NAME_DATASTREAM1=$(ffprobe -v error -select_streams d:1 -show_entries stream_tags=handler_name -of default=noprint_wrappers=1:nokey=1 "$1")
GOPRO_NAME_DATASTREAM2=$(ffprobe -v error -select_streams d:2 -show_entries stream_tags=handler_name -of default=noprint_wrappers=1:nokey=1 "$1")
if [[ $COPY_GOPROMETADATA -ne 0 ]]; then
# Check if GoPro TCD exists (usually missing in 120fps+ videos)
if [[ $GOPRO_NAME_DATASTREAM0 == "$GOPROTCD" ]]; then
FFMPEG_PARAM+=('-map' "0:m:handler_name:$GOPROTCD")
FFMPEG_PARAM+=('-tag:d:0' "tmcd")
FFMPEG_PARAM+=('-metadata:s:d:0' "handler=$GOPROTCD")
FFMPEG_PARAM+=('-metadata:s:d:0' "handler_name=$GOPROTCD")
if [[ $DEBUG -ne 0 ]]; then echo "GoPro TCD exists at datastream 0"; fi
fi
if [[ $GOPRO_NAME_DATASTREAM1 == "$GOPROTCD" ]]; then
FFMPEG_PARAM+=('-map' "0:m:handler_name:$GOPROTCD")
FFMPEG_PARAM+=('-tag:d:1' "tmcd")
FFMPEG_PARAM+=('-metadata:s:d:1' "handler=$GOPROTCD")
FFMPEG_PARAM+=('-metadata:s:d:1' "handler_name=$GOPROTCD")
if [[ $DEBUG -ne 0 ]]; then echo "GoPro TCD exists at datastream 1"; fi
fi
if [[ $GOPRO_NAME_DATASTREAM2 == "$GOPROTCD" ]]; then
FFMPEG_PARAM+=('-map' "0:m:handler_name:$GOPROTCD")
FFMPEG_PARAM+=('-tag:d:2' "tmcd")
FFMPEG_PARAM+=('-metadata:s:d:2' "handler=$GOPROTCD")
FFMPEG_PARAM+=('-metadata:s:d:2' "handler_name=$GOPROTCD")
if [[ $DEBUG -ne 0 ]]; then echo "GoPro TCD exists at datastream 2"; fi
fi
# Check if GoPro MET exists
if [[ $GOPRO_NAME_DATASTREAM0 == "$GOPROMET" ]]; then
FFMPEG_PARAM+=('-map' "0:m:handler_name:$GOPROMET")
FFMPEG_PARAM+=('-tag:d:0' "gpmd")
FFMPEG_PARAM+=('-metadata:s:d:0' "handler=$GOPROMET")
FFMPEG_PARAM+=('-metadata:s:d:0' "handler_name=$GOPROMET")
if [[ $DEBUG -ne 0 ]]; then echo "GoPro MET exists at datastream 0"; fi
fi
if [[ $GOPRO_NAME_DATASTREAM1 == "$GOPROMET" ]]; then
FFMPEG_PARAM+=('-map' "0:m:handler_name:$GOPROMET")
FFMPEG_PARAM+=('-tag:d:1' "gpmd")
FFMPEG_PARAM+=('-metadata:s:d:1' "handler=$GOPROMET")
FFMPEG_PARAM+=('-metadata:s:d:1' "handler_name=$GOPROMET")
if [[ $DEBUG -ne 0 ]]; then echo "GoPro MET exists at datastream 1"; fi
fi
if [[ $GOPRO_NAME_DATASTREAM2 == "$GOPROMET" ]]; then
FFMPEG_PARAM+=('-map' "0:m:handler_name:$GOPROMET")
FFMPEG_PARAM+=('-tag:d:2' "gpmd")
FFMPEG_PARAM+=('-metadata:s:d:2' "handler=$GOPROMET")
FFMPEG_PARAM+=('-metadata:s:d:2' "handler_name=$GOPROMET")
if [[ $DEBUG -ne 0 ]]; then echo "GoPro MET exists at datastream 2"; fi
fi
# Check if GoPro SOS exists
# Input stream GoPro SOS recovery (fdsc) is currently unsupported by ffmpeg, copied as gpmd for now
if [[ $GOPRO_NAME_DATASTREAM0 == "$GOPROSOS" ]]; then
FFMPEG_PARAM+=('-map' "0:m:handler_name:$GOPROSOS")
FFMPEG_PARAM+=('-tag:d:0' "gpmd")
FFMPEG_PARAM+=('-metadata:s:d:0' "handler=$GOPROSOS (original fdsc)")
FFMPEG_PARAM+=('-metadata:s:d:0' "handler_name=$GOPROSOS (original fdsc)")
if [[ $DEBUG -ne 0 ]]; then echo "GoPro SOS exists at datastream 0"; fi
fi
if [[ $GOPRO_NAME_DATASTREAM1 == "$GOPROSOS" ]]; then
FFMPEG_PARAM+=('-map' "0:m:handler_name:$GOPROSOS")
FFMPEG_PARAM+=('-tag:d:1' "gpmd")
FFMPEG_PARAM+=('-metadata:s:d:1' "handler=$GOPROSOS (original fdsc)")
FFMPEG_PARAM+=('-metadata:s:d:1' "handler_name=$GOPROSOS (original fdsc)")
if [[ $DEBUG -ne 0 ]]; then echo "GoPro SOS exists at datastream 1"; fi
fi
if [[ $GOPRO_NAME_DATASTREAM2 == "$GOPROSOS" ]]; then
FFMPEG_PARAM+=('-map' "0:m:handler_name:$GOPROSOS")
FFMPEG_PARAM+=('-tag:d:2' "gpmd")
FFMPEG_PARAM+=('-metadata:s:d:2' "handler=$GOPROSOS (original fdsc)")
FFMPEG_PARAM+=('-metadata:s:d:2' "handler_name=$GOPROSOS (original fdsc)")
if [[ $DEBUG -ne 0 ]]; then echo "GoPro SOS exists at datastream 2"; fi
fi
fi
FFMPEG_PARAM+=('-f' "mov")
#if [[ $DEBUG -ne 0 ]]; then echo "ffmpeg command: ffmpeg ${FFMPEG_PARAM[@]}" "$1-output.mov"; fi
ffmpeg "${FFMPEG_PARAM[@]}" "$1-output.mov"
@ncoder-1
Copy link
Author

Due to the tab characters in lines 33,34,35, it is best to git clone or download as zip instead of copy-pasting into an editor.

@dobrzele
Copy link

Hello! Thanks for your script - it's helping me copy (actually split) GoPro videos from a Hero 7. I can copy video, audio, and gps data, but, I'm having trouble getting the timecode data as well. I don't know if I need it, but I'd like to preserve as many of the data streams as possible. I'm getting the following error from ffmpeg, even though I have the -tag:d:0 "tmcd" argument specified. Any ideas?

I should also mention that my camera is using VERTICAL tab character (ASCII code 11 decimal, not a regular tab) in the handler names. Could I have a different firmware version or something? For brevity in the example below, I'm only trying to get audio+video+timecode data, ignoring gps for now. Using ffprobe I can confirmed that the first data stream (0:2) is in fact the GoPro TCD track. Thanks again!

ffmpeg -i GX210058.mp4 -y -copy_unknown -map_metadata 0 -c copy -map 0:v -map 0:a -map 0:m:handler_name:"�GoPro TCD  " -tag:d:0 "tmcd" -metadata:s:d:0 handler="�GoPro TCD  " GX210058-copy.mp4

[mp4 @ 0000022b726b3700] Tag tmcd incompatible with output codec id '100359' (gpmd)
Could not write header for output file #0 (incorrect codec parameters ?): Invalid data found when processing input

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment