How to compress GoPro movies (and keep metadata so that Quik is happy)
Do you have loads of GoPro movies eating up disk space? Looking for a way to compress them, but in such a way that quality is still very high and they continue to play nice with GoPro software, like Quik? I did too.
I've owned a GoPro Hero 3+ and now more recently a Hero 5. With the right conditions and settings it takes some really nice video, but sometimes they're just a little bit too space hungry for my liking.
Whether you are uploading videos to the cloud, streaming them via a home NAS, or just short on drive space, you might find yourself wanting to reduce the size of your video files.
I've previously written about how to compress videos generally, but for GoPro clips we need to do a little more. That's because there are additional streams embedded in the videos, as well as proprietary metadata.
Here I will walkthrough the process of compressing a video shot from my Hero 5, using FFmpeg. If you just want to skip straight to the final command, scroll to the end.
If you have a different model GoPro, some of the metadata might be slightly different (earlier GoPro's do not have a GPS sensor for example). Hopefully there is enough information here so that you can tweak as necessary for your own camera
Here's our test video:
This video was shot at 2.7k to capture the amazing landscapes in New Zealand. I'd like to retain a decent quality, but reduce the size from 78.9MB.
If you haven't already, install FFmpeg. Now based on my previous post, we could start off by re-encoding just the video at a slightly higher crf
factor, like this:
ffmpeg -i GOPR5687.MP4 -c:a copy -c:v h264 -crf 22 output.mp4
Running this spits out a 30.4MB file, around 61.5% smaller, and the quality is fine for my purposes. Not bad!
The range of the CRF scale is 0–51, where 0 is lossless, 23 is the default, and 51 is worst quality possible. A lower value generally leads to higher quality, and a subjectively sane range is 17–28. Consider 17 or 18 to be visually lossless or nearly so; it should look the same or nearly the same as the input but it isn't technically lossless. You should experiment with different crf values to find the sweet-spot for you.
Is that it? Are we done? Not quite.
Keeping GoPro metadata
If we try to import our new smaller video into Quik so that we can do some basic editing, we hit a roadblock.
Hmm, it seems something has happened to the video during the conversion, and now GoPro's software cannot use it.
Let's investigate.
We can use FFmpeg with no options specified to sneak a peak at the metadata in the videos without actually doing anything to them, and compare the original with our new, smaller file.
Here's the original:
~/Desktop ffmpeg -i GOPR5687.MP4
ffmpeg version 4.0.2 Copyright (c) 2000-2018 the FFmpeg developers
...
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'GOPR5687.MP4':
Metadata:
major_brand : mp41
minor_version : 538120216
compatible_brands: mp41
creation_time : 2017-08-07T14:43:26.000000Z
location : -43.7941+170.1170/
location-eng : -43.7941+170.1170/
firmware : HD5.02.01.57.00
Duration: 00:00:10.41, start: 0.000000, bitrate: 60633 kb/s
Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuvj420p(pc, bt709), 2704x1520 [SAR 1:1 DAR 169:95], 60541 kb/s, 59.94 fps, 59.94 tbr, 60k tbn, 119.88 tbc (default)
Metadata:
creation_time : 2017-08-07T14:43:26.000000Z
handler_name : GoPro AVC
encoder : GoPro AVC encoder
timecode : 14:58:24:55
Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 128 kb/s (default)
Metadata:
creation_time : 2017-08-07T14:43:26.000000Z
handler_name : GoPro AAC
timecode : 14:58:24:55
Stream #0:2(eng): Data: none (tmcd / 0x64636D74), 0 kb/s (default)
Metadata:
creation_time : 2017-08-07T14:43:26.000000Z
handler_name : GoPro TCD
timecode : 14:58:24:55
Stream #0:3(eng): Data: none (gpmd / 0x646D7067), 33 kb/s (default)
Metadata:
creation_time : 2017-08-07T14:43:26.000000Z
handler_name : GoPro MET
Stream #0:4(eng): Data: none (fdsc / 0x63736466), 14 kb/s (default)
Metadata:
creation_time : 2017-08-07T14:43:26.000000Z
handler_name : GoPro SOS
And here's our smaller conversion:
~/Desktop ffmpeg -i output.mp4
ffmpeg version 4.0.2 Copyright (c) 2000-2018 the FFmpeg developers
...
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'output.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf58.12.100
location-eng : -43.7941+170.1170/
location : -43.7941+170.1170/
Duration: 00:00:10.41, start: 0.000000, bitrate: 23356 kb/s
Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuvj420p(pc), 2704x1520 [SAR 1:1 DAR 169:95], 23252 kb/s, 59.94 fps, 59.94 tbr, 60k tbn, 119.88 tbc (default)
Metadata:
handler_name : VideoHandler
timecode : 14:58:24:55
Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 128 kb/s (default)
Metadata:
handler_name : SoundHandler
Stream #0:2(eng): Data: none (tmcd / 0x64636D74), 0 kb/s
Metadata:
handler_name : TimeCodeHandler
timecode : 14:58:24:55
Straight away we can see that the output from the original file is considerably longer than the one for our converted file.
There are two main differences between the two:
- The original file contains 5 separate streams embedded inside it, our transcode just 2
- The original file uses proprietary
handler_name
's likeGoPro AVC
, whereas our transcode uses generic versions, likeVideoHandler
What happened is that during our transcoding we essentially stripped out loads of data which the GoPro camera writes into the file, meaning we've lost some information as well as the ability to use our files in GoPro software.
Not ideal, let's fix that.
Multiple Streams
As you would expect, a video is generally made up of both an audio and a video part, and each part is referred to as a stream
.
What might not be so intuitive at first though is that a file can, and often does, include more streams than just one audio and one video. For example, we might have several different audio streams for different spoken languages, plus a subtitle stream. A decent video player will allow us to choose which streams we want to use when we play the file, perhaps french audio with english subtitles.
In the case of our GoPro, we have several additional data streams, one of which contains the GPS data that is recorded if it is switched on (seen above as GoPro MET
).
By default, FFmpeg includes just one audio and one video stream in the output file, choosing what it considers to be the best of each type.
To tell FFmpeg to copy all streams we can use -map 0
. Also, to tell it to copy streams even if it doesn't recognise their content (necessary for some GoPro data streams), we can use -copy_unknown
also.
ffmpeg -i GOPR5687.MP4 -map 0 -copy_unknown -map_metadata 0 \
-c copy -c:v h264 -crf 22 output.mp4
Since we want to preserve most of the streams without modification, we pass -c copy
first, which says to just copy each stream in the input directly to the output. Then, we override the copy codec just for the video stream, using -c:v h264
as before.
I've also added -map_metadata 0
to copy all of the global metadata from input to output.
If we inspect the video again, it's better. We have now 5 streams as we wanted:
~/Desktop ffmpeg -i output.mp4
ffmpeg version 4.0.2 Copyright (c) 2000-2018 the FFmpeg developers
...
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'output.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
creation_time : 2017-08-07T14:43:26.000000Z
encoder : Lavf58.12.100
location-eng : -43.7941+170.1170/
location : -43.7941+170.1170/
Duration: 00:00:10.41, start: 0.000000, bitrate: 23407 kb/s
Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuvj420p(pc), 2704x1520 [SAR 1:1 DAR 169:95], 23252 kb/s, 59.94 fps, 59.94 tbr, 60k tbn, 119.88 tbc (default)
Metadata:
creation_time : 2017-08-07T14:43:26.000000Z
handler_name : VideoHandler
timecode : 14:58:24:55
Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 128 kb/s (default)
Metadata:
creation_time : 2017-08-07T14:43:26.000000Z
handler_name : SoundHandler
Stream #0:2(eng): Data: none (tmcd / 0x64636D74), 0 kb/s (default)
Metadata:
creation_time : 2017-08-07T14:43:26.000000Z
handler_name : TimeCodeHandler
timecode : 14:58:24:55
Stream #0:3(eng): Data: none (gpmd / 0x646D7067), 32 kb/s (default)
Metadata:
creation_time : 2017-08-07T14:43:26.000000Z
handler_name : GoPro MET
Stream #0:4(eng): Data: none (stts / 0x73747473), 14 kb/s (default)
Metadata:
creation_time : 2017-08-07T14:43:26.000000Z
handler_name : DataHandler
We still have two issues though!
- The video clip will still not open in Quik... probably has something to do with those generic handler names
- While we were transcoding, ffmpeg spat out a warning for the
SOS
stream:
[mp4 @ 0x7ff4cc006c00] Unknown hldr_type for fdsc, writing dummy values3.0kbits/s speed=0.148x
Let's continue and try to fix those now.
Correcting Handler Names
Like most things, it is possible to customise the handler name with FFmpeg. We just need to specify the handler
metadata tag for each stream and give the correct name.
For example, -metadata:s:v: handler=' GoPro AVC'
sets the handler_name
for the video stream to be called GoPro AVC
.
Although the written metadata is calledhandler_name
, the tag to set it is justhandler
. This has caused confusion for at least some others
We can now try to transcode the video, making sure to set the names for the audio and video tracks:
ffmpeg -i GOPR5687.MP4 -copy_unknown -map_metadata 0 \
-c copy -c:v h264 -crf 22 =\
-map 0 \
-metadata:s:v: handler=' GoPro AVC' \
-metadata:s:a: handler=' GoPro AAC' \
output.mp4
This time, all is well if we try to import it into Quik.
Great, so we can override the handler names to the GoPro specific ones! One slight snag though, the streams are not always in the same order. That means if we set the handler names by index, for some videos we might end up using a name of GoPro AAC
for the video, when that's the name for the audio.
This might not be a problem if you're just encoding a single file, as you can check it first to see what order it has been recorded in. But, if you want to batch convert many with the same command in a script for example, it will bite you.
Selecting streams by name
Fortunately, we can work around this by explicitly listing the input streams that we want to map, and then follow that with the handler names we would like for each in the same order. This works because FFmpeg will map the output streams in the same order as you list them from the input.
We can explicitly list the video and audio streams easily, as there is only one of them. This just requires -map 0:v
or -map 0:a
respectively, which says to map
the v
ideo or a
udio stream from the 0
th (first) input file.
The data tracks are a bit more complicated, as there are 3 of them and they may appear in any order. Luckily, we can also pick the input streams by name:
-map 0:m:handler_name:' GoPro MET'
Here we specify that we want to map the stream which has a handler_name
of GoPro MET
in its m
etadata, from the 0
th input file.
Perfect, now we have all the pieces we need!
Putting it all together
So, to compress the GoPro video named GOPR5687.MP4
to a smaller size, but still have it keep it's metadata and work in Quik, we can use something like:
ffmpeg -i GOPR5687.MP4 -copy_unknown -map_metadata 0 \
-c copy -c:v h264 -crf 22 -pix_fmt yuvj420p \
-map 0:v -map 0:a \
-map 0:m:handler_name:' GoPro TCD' \
-map 0:m:handler_name:' GoPro MET' \
-map 0:m:handler_name:' GoPro SOS' \
-tag:d:1 'gpmd' -tag:d:2 'gpmd' \
-metadata:s:v: handler=' GoPro AVC' \
-metadata:s:a: handler=' GoPro AAC' \
-metadata:s:d:0 handler=' GoPro TCD' \
-metadata:s:d:1 handler=' GoPro MET' \
-metadata:s:d:2 handler=' GoPro SOS (original fdsc stream)' \
output.mp4 \
&& touch -r GOPR5687.MP4 output.mp4
Here with the -map
lines we extract, in order, the video, the audio, and then the TCD
, MET
and SOS
data streams. Then in the -metadata
lines we name each stream accordingly.
Watch out! What looks like a space before the handler_name
's is actually a TAB character. Yes, GoPro really does record handler names starting with a tab. And yes, for some streams like MET, if it doesn't match exactly, Quik won't recognise it. Spent some time tearing my hair out before I realised this...
Remember the warning about dummy data being used for the SOS
fdsc stream I mentioned earlier?
That happens because FFmpeg doesn't know what an fdsc stream is, so strangely instead of just copying it anyway (which is what we asked), it decides to stuff the whole thing with garbage data. It seems like this SOS
stream is only used for file recovery anyway, and isn't that important.
Nevertheless, I'd still prefer to copy it over if possible. To do this, I've added a small hack which retags the 'fdsc' stream as 'gpmd' using -tag:d:2 'gpmd'
. Because FFmpeg is familiar with this type, it will happily copy across the data. Then, when I rename the handler, I've given it a name to indicate that it was originally the fdsc stream.
If you don't care about keeping this stream, you can omit these tags. Hopefully in the future FFmpeg will just copy all the data if -copy_unknown
is specified anyway, so that there is no need for it anyway
I've explicitly specified the output pixel format as -pix_fmt yuvj420p
also, so there is no guesswork on behalf of the codec.
If you have any issues with colour reproduction, you might also want to look at colour profile settings. See here or here for a little more info.
Finally, I've added atouch
command at the end, in order to copy across the original file modification timestamps to the newly created file. Handy if we're going to be sorting the files by date!
So now if we run the above command, we generate a much smaller video that retains our metadata, and keeps Quik happy:
In my tests, the GPS guage data is copied over and you can activate the guages within Quik to overlay onto your video. However, the data doesn't seem to match up correctly, despite all being there (and I can extract the gps coordinates recorded without issue). I suspect there is some timing data that is not copied exactly. There is a reddit thread that is relevant to this. There has also been commits to the FFmpeg codebase from a GoPro engineer to support the gpmd
stream, so perhaps in time this will work correctly.
That's all there is to it! And if you want to automate this process for a number of videos, I created a tool called Shrinkwrap to do this. If you're familiar with Docker you might want to check it out, using the GoPro5 preset.
If you have any comments or improvements, feel free to leave them below!
Post cover image source