-
-
Save ncoder-1/e3c46e1f0af4009c89f4ae48e853c336 to your computer and use it in GitHub Desktop.
#!/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" |
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
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.