2

One may losslessly concatenate two MP4 files using FFmpeg as follows:

ffmpeg -safe 0 -f concat -i list.txt -c copy output.mp4

with list.txt:

file 'C:\file1.mp4'
file 'C:\file2.mp4'

However, if the input files C:\file1.mp4 and C:\file2.mp4 have telemetry information, output.mp4 won't have it, as ffprobe can confirm. In my case, C:\file1.mp4 and C:\file2.mp4 have GPMF™ formatted telemetry data used within GoPro® cameras.

How can I losslessly concatenate two MP4 files and keep the telemetry information using FFmpeg?


ffprobe ouput for one of the input file with telemetry information:

C:\>ffprobe GX010013.MP4
ffprobe version 5.0.1-full_build-www.gyan.dev Copyright (c) 2007-2022 the FFmpeg developers
  built with gcc 11.2.0 (Rev7, Built by MSYS2 project)
  configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libdav1d --enable-libdavs2 --enable-libuavs3d --enable-libzvbi --enable-librav1e --enable-libsvtav1 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-mediafoundation --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-liblensfun --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-libshaderc --enable-vulkan --enable-libplacebo --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint
  libavutil      57. 17.100 / 57. 17.100
  libavcodec     59. 18.100 / 59. 18.100
  libavformat    59. 16.100 / 59. 16.100
  libavdevice    59.  4.100 / 59.  4.100
  libavfilter     8. 24.100 /  8. 24.100
  libswscale      6.  4.100 /  6.  4.100
  libswresample   4.  3.100 /  4.  3.100
  libpostproc    56.  3.100 / 56.  3.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'GX010013.MP4':
  Metadata:
    major_brand     : mp41
    minor_version   : 538120216
    compatible_brands: mp41
    creation_time   : 2022-07-16T20:36:19.000000Z
    firmware        : H21.01.01.42.00
  Duration: 00:08:54.55, start: 0.000000, bitrate: 59971 kb/s
  Stream #0:0[0x1](eng): Video: hevc (Main) (hvc1 / 0x31637668), yuvj420p(pc, bt709), 5312x2988 [SAR 1:1 DAR 16:9], 59702 kb/s, 59.94 fps, 59.94 tbr, 60k tbn (default)
    Metadata:
      creation_time   : 2022-07-16T20:36:19.000000Z
      handler_name    : GoPro H.265
      vendor_id       : [0][0][0][0]
      encoder         : GoPro H.265 encoder
      timecode        : 20:35:05:32
  Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 189 kb/s (default)
    Metadata:
      creation_time   : 2022-07-16T20:36:19.000000Z
      handler_name    : GoPro AAC
      vendor_id       : [0][0][0][0]
      timecode        : 20:35:05:32
  Stream #0:2[0x3](eng): Data: none (tmcd / 0x64636D74) (default)
    Metadata:
      creation_time   : 2022-07-16T20:36:19.000000Z
      handler_name    : GoPro TCD
      timecode        : 20:35:05:32
  Stream #0:3[0x4](eng): Data: bin_data (gpmd / 0x646D7067), 60 kb/s (default)
    Metadata:
      creation_time   : 2022-07-16T20:36:19.000000Z
      handler_name    : GoPro MET
Unsupported codec with id 0 for input stream 2
Unsupported codec with id 98314 for input stream 3

https://github.com/gopro/gpmf-parser gives an overview of the GoPro's MP4 structure:

Telemetry carrying MP4 files will have a minimum of four tracks: Video, audio, timecode and telemetry (GPMF). A fifth track ('SOS') is used in file recovery in HERO4 and HERO5, can be ignored.

File structure:

  ftyp [type ‘mp41’]
  mdat [all the data for all tracks are interleaved]
  moov [all the header/index info]
    ‘trak’ subtype ‘vide’, name “GoPro AVC”, H.264 video data 
    ‘trak’ subtype ‘soun’, name “GoPro AAC”, to AAC audio data
    ‘trak’ subtype ‘tmcd’, name “GoPro TCD”, starting timecode (time of day as frame since midnight)
    ‘trak’ subtype ‘meta’, name “GoPro MET”, GPMF telemetry
Franck Dernoncourt
  • 24,246
  • 64
  • 231
  • 400

2 Answers2

1

From this SO post by Andrew, one has to add the attributes: -map 0:v -map 0:a -map 0:3 -copy_unknown -tag:2 gpmd

Full command:

ffmpeg -safe 0 -f concat -i list.txt -map 0:v -map 0:a -map 0:3 -copy_unknown -tag:2 gpmd -c copy output.mp4

Note that https://community.gopro.com/s/question/0D53b00008BtrlQCAR/merge-files-and-keep-telemetry?language=en_US reports some issues:

ReelSteady Go refuses to read the data.

I assume that's because ffmpeg is merging the gpmd stream roughly, since we use -copy_unknown because ffmpeg has no clue what gpmd is.

However, https://goprotelemetryextractor.com/free/ seems to be able to properly extract the GPX information.


Bonus script to list all mp4 files in a folder and create list.txt:

@echo off
(for %%f in (*.mp4) do echo file '%%f') > list.txt

or to make sure that the mp4 files are lexicographically ordered:

@echo off
(for /f "delims=" %%f in ('dir /b /o:n *.mp4') do echo file '%%f') >  list_videos.txt

(/o:n specifies sorting by name).

Example of output:

file '20240914_121500.mp4'
file '20240914_122513.mp4'
file '20240914_124099.mp4'
file '20240914_125165.mp4'
Franck Dernoncourt
  • 24,246
  • 64
  • 231
  • 400
0

One can use mp4-merge (mirror):

  • binaries provided for Windows, macOS and Linux
  • Apache-2.0 and MIT licenses
  • lossless merge

Usage example:

mp4_merge IN_FILE1.mp4 IN_FILE2.mp4 IN_FILE3.mp4 ... --out result.mp4

Upside of mp4-merge compared to FFmpeg: mp4-merge doesn't inverse the GCMD and TMCD stream order, unlike FFmpeg. Details:

Running:

ffmpeg -safe 0 -f concat -i list.txt -map 0:v -map 0:a -map 0:3 -copy_unknown -tag:2 gpmd -c copy output.mp4

causes the GCMD and TMCD stream order to get reversed.

E.g., before:

      libavutil      59. 39.100 / 59. 39.100
      libavcodec     61. 19.100 / 61. 19.100
      libavformat    61.  7.100 / 61.  7.100
      libavdevice    61.  3.100 / 61.  3.100
      libavfilter    10.  4.100 / 10.  4.100
      libswscale      8.  3.100 /  8.  3.100
      libswresample   5.  3.100 /  5.  3.100
      libpostproc    58.  3.100 / 58.  3.100
    Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'a.MP4':
      Metadata:
        major_brand     : mp41
        minor_version   : 538120216
        compatible_brands: mp41
        creation_time   : 2022-07-13T08:46:54.000000Z
        firmware        : H21.01.01.42.00
      Duration: 00:00:02.90, start: 0.000000, bitrate: 53612 kb/s
      Stream #0:0[0x1](eng): Video: hevc (Main) (hvc1 / 0x31637668), yuvj420p(pc, bt709), 5312x2988 [SAR 1:1 DAR 16:9], 53273 kb/s, 29.97 fps, 29.97 tbr, 30k tbn (default)
          Metadata:
            creation_time   : 2022-07-13T08:46:54.000000Z
            handler_name    : GoPro H.265
            vendor_id       : [0][0][0][0]
            encoder         : GoPro H.265 encoder
            timecode        : 08:46:23:01
      Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 189 kb/s (default)
          Metadata:
            creation_time   : 2022-07-13T08:46:54.000000Z
            handler_name    : GoPro AAC
            vendor_id       : [0][0][0][0]
            timecode        : 08:46:23:01
      Stream #0:2[0x3](eng): Data: none (tmcd / 0x64636D74), 0 kb/s (default)
          Metadata:
            creation_time   : 2022-07-13T08:46:54.000000Z
            handler_name    : GoPro TCD
            timecode        : 08:46:23:01
      Stream #0:3[0x4](eng): Data: bin_data (gpmd / 0x646D7067), 55 kb/s (default)
          Metadata:
            creation_time   : 2022-07-13T08:46:54.000000Z
            handler_name    : GoPro MET

After:

    ffmpeg version 7.1-full_build-www.gyan.dev Copyright (c) 2000-2024 the FFmpeg developers
      built with gcc 14.2.0 (Rev1, Built by MSYS2 project)
      configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libaribb24 --enable-libaribcaption --enable-libdav1d --enable-libdavs2 --enable-libopenjpeg --enable-libquirc --enable-libuavs3d --enable-libxevd --enable-libzvbi --enable-libqrencode --enable-librav1e --enable-libsvtav1 --enable-libvvenc --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxeve --enable-libxvid --enable-libaom --enable-libjxl --enable-libvpx --enable-mediafoundation --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-liblensfun --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11va --enable-d3d12va --enable-ffnvcodec --enable-libvpl --enable-nvdec --enable-nvenc --enable-vaapi --enable-libshaderc --enable-vulkan --enable-libplacebo --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libcodec2 --enable-libilbc --enable-libgsm --enable-liblc3 --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint
      libavutil      59. 39.100 / 59. 39.100
      libavcodec     61. 19.100 / 61. 19.100
      libavformat    61.  7.100 / 61.  7.100
      libavdevice    61.  3.100 / 61.  3.100
      libavfilter    10.  4.100 / 10.  4.100
      libswscale      8.  3.100 /  8.  3.100
      libswresample   5.  3.100 /  5.  3.100
      libpostproc    58.  3.100 / 58.  3.100
    Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'output.MP4':
      Metadata:
        major_brand     : isom
        minor_version   : 512
        compatible_brands: isomiso2mp41
        encoder         : Lavf61.7.100
      Duration: 00:00:01.00, start: 0.000000, bitrate: 40941 kb/s
      Stream #0:0[0x1](eng): Video: hevc (Main) (hvc1 / 0x31637668), yuvj420p(pc, bt709), 5312x2988 [SAR 1:1 DAR 16:9], 40739 kb/s, 29.97 fps, 29.97 tbr, 30k tbn (default)
          Metadata:
            handler_name    : GoPro H.265
            vendor_id       : [0][0][0][0]
            encoder         : GoPro H.265 encoder
            timecode        : 08:46:23:01
      Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 189 kb/s (default)
          Metadata:
            handler_name    : GoPro AAC
            vendor_id       : [0][0][0][0]
      Stream #0:2[0x3](eng): Data: bin_data (gpmd / 0x646D7067), 54 kb/s (default)
          Metadata:
            handler_name    : GoPro MET
      Stream #0:3[0x4](eng): Data: none (tmcd / 0x64636D74), 0 kb/s
          Metadata:
            handler_name    : GoPro H.265
            timecode        : 08:46:23:01
Franck Dernoncourt
  • 24,246
  • 64
  • 231
  • 400