Mike Slinn
Mike Slinn

Trimming Media Files Can Be Surprisingly Subtle

Published 2022-01-23.
Time to read: 1 minutes.

This page is part of the av_studio collection, categorized under Media, OBS Studio.

When I use OBS Studio to record video from Cam Link 4K and audio from Pro Tools using RME TotalMix, I get really large mkv files. I need to be able to trim the video file so the bits before and after the good stuff are discarded.

Lots of StackOverflow conversations revolve around trimming video files. Dozens of PC and Mac programs exist to do that task, mostly low quality and / or bothersome to use. Other solutions are overkill, for example Adobe Premiere Pro and DaVinci Resolve.

In this post I present a bash script that reduces file size by > 80%, while preserving quality and metadata, and cropping to specified time periods.

For any programmers who might read this:

  • Some of the ffmpeg options this script uses are not available in older versions.
  • The most important thing to know about options that might be passed to ffmpeg is that if you want to force a new video encoding, simply do not specify the -vcodec copy option. Re-encoding means that arbitrary start and end times can be specified accurately when cropping.

This is the script:

#!/bin/bash

function help {
  echo "$(basename $0) - Trim an audio or video file using ffmpeg
Works with all formats supported by ffmpeg.
Seeks to the nearest frame positions by re-encoding.
Does not overwrite pre-existing output files.
Reduces file size procduced by OBS Studio by >80%.

Usage:
  $(basename $0) "dir/file.mkv" START [END]

START and END have the format [HH:[MM:]]SS[.XXX]
END defaults to end of audio/video file

Examples:
  $(basename $0) 'dir/file.mkv' 15      # Crop from 15.0 seconds to end
  $(basename $0) 'dir/file.mkv' 3.25 9  # Crop from 3.25 seconds to 9 seconds
"
  exit 1
}


if [ -z "$2" ]; then help; fi

if [ "$1" == -f ]; then
  shift
  OVERWRITE=-y
else
  OVERWRITE=-n
fi

FNAME="$1"
ORIGINAL_FN=${FNAME%.*}
EXT=${FNAME##*.}
COPY_FNAME="${ORIGINAL_FN}.crop.$EXT"

START="$2"
if [ "$3" ]; then 
  TO="-to $3"
else
  unset TO
fi

ffmpeg \
  "$OVERWRITE" \
  -i "$FNAME" \
  -ss "$START" $TO \
  -acodec copy \
  "$COPY_FNAME"

Here is a sample session, which extracts the sequence beginning at 00:00:25.000 until 00:02:52.000:

Shell
$ crop 'VideoFile.mkv' 25 2:52
ffmpeg version 4.4-6ubuntu5 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 11 (Ubuntu 11.2.0-7ubuntu1)
  configuration: --prefix=/usr --extra-version=6ubuntu5 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librabbitmq --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-pocketsphinx --enable-librsvg --enable-libmfx --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
  libavutil      56. 70.100 / 56. 70.100
  libavcodec     58.134.100 / 58.134.100
  libavformat    58. 76.100 / 58. 76.100
  libavdevice    58. 13.100 / 58. 13.100
  libavfilter     7.110.100 /  7.110.100
  libswscale      5.  9.100 /  5.  9.100
  libswresample   3.  9.100 /  3.  9.100
  libpostproc    55.  9.100 / 55.  9.100
Input #0, matroska,webm, from 'VideoFile.mkv':
  Metadata:
    ENCODER         : Lavf58.29.100
  Duration: 00:02:57.93, start: 0.000000, bitrate: 8137 kb/s
  Stream #0:0: Video: h264 (High), yuv420p(tv, bt709, progressive), 1280x720 [SAR 1:1 DAR 16:9], 30 fps, 30 tbr, 1k tbn, 60 tbc (default)
    Metadata:
      DURATION        : 00:02:57.933000000
  Stream #0:1: Audio: aac (LC), 48000 Hz, stereo, fltp (default)
    Metadata:
      title           : simple_aac_recording
      DURATION        : 00:02:57.813000000
Stream mapping:
  Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
  Stream #0:1 -> #0:1 (copy)
Press [q] to stop, [?] for help
[libx264 @ 0x55fe8a8a6d40] using SAR=1/1
[libx264 @ 0x55fe8a8a6d40] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX
[libx264 @ 0x55fe8a8a6d40] profile High, level 3.1, 4:2:0, 8-bit
[libx264 @ 0x55fe8a8a6d40] 264 - core 160 r3011 cde9a93 - H.264/MPEG-4 AVC codec - Copyleft 2003-2020 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=12 lookahead_threads=2 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
Output #0, matroska, to 'VideoFile.crop.mkv':
  Metadata:
    encoder         : Lavf58.76.100
  Stream #0:0: Video: h264 (H264 / 0x34363248), yuv420p(tv, bt709, progressive), 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 30 fps, 1k tbn (default)
    Metadata:
      DURATION        : 00:02:57.933000000
      encoder         : Lavc58.134.100 libx264
    Side data:
      cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: N/A
  Stream #0:1: Audio: aac (LC) ([255][0][0][0] / 0x00FF), 48000 Hz, stereo, fltp (default)
    Metadata:
      title           : simple_aac_recording
      DURATION        : 00:02:57.813000000
frame= 4410 fps= 56 q=-1.0 Lsize=   29863kB time=00:02:26.98 bitrate=1664.3kbits/s speed=1.85x
video:26295kB audio:3489kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.266039%
[libx264 @ 0x55fe8a8a6d40] frame I:18    Avg QP:19.94  size: 91473
[libx264 @ 0x55fe8a8a6d40] frame P:1111  Avg QP:22.35  size: 14426
[libx264 @ 0x55fe8a8a6d40] frame B:3281  Avg QP:26.70  size:  2820
[libx264 @ 0x55fe8a8a6d40] consecutive B-frames:  0.8%  0.0%  0.1% 99.1%
[libx264 @ 0x55fe8a8a6d40] mb I  I16..4:  9.1% 68.8% 22.1%
[libx264 @ 0x55fe8a8a6d40] mb P  I16..4:  0.1%  1.2%  0.4%  P16..4: 41.3% 11.1%  8.7%  0.0%  0.0%    skip:37.2%
[libx264 @ 0x55fe8a8a6d40] mb B  I16..4:  0.0%  0.2%  0.0%  B16..8: 25.8%  2.6%  0.6%  direct: 0.7%  skip:70.0%  L0:42.9% L1:51.0% BI: 6.1%
[libx264 @ 0x55fe8a8a6d40] 8x8 transform intra:71.2% inter:72.8%
[libx264 @ 0x55fe8a8a6d40] coded y,uvDC,uvAC intra: 81.3% 83.4% 41.2% inter: 6.8% 12.5% 0.3%
[libx264 @ 0x55fe8a8a6d40] i16 v,h,dc,p: 27% 14% 15% 44%
[libx264 @ 0x55fe8a8a6d40] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 15% 15% 12%  7% 13%  9% 13%  7%  9%
[libx264 @ 0x55fe8a8a6d40] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 15% 13% 11% 10% 17% 11% 10%  6%  7%
[libx264 @ 0x55fe8a8a6d40] i8c dc,h,v,p: 46% 21% 21% 11%
[libx264 @ 0x55fe8a8a6d40] Weighted P-Frames: Y:0.5% UV:0.1%
[libx264 @ 0x55fe8a8a6d40] ref P L0: 56.5% 10.1% 21.4% 12.0%  0.0%
[libx264 @ 0x55fe8a8a6d40] ref B L0: 87.7%  8.9%  3.4%
[libx264 @ 0x55fe8a8a6d40] ref B L1: 94.4% 

The cropped file is quite a bit smaller than the original. I have not noticed any decrease in quality.

Shell
$ ls -AlF
total 1546136
-rw-r--r-- 1 mslinn mslinn   30579665 Jan 23 13:03 'VideoFile.crop.mkv'
-rwxrwxrwx 1 mslinn mslinn  180988555 Jan 22 18:31 'VideoFile.mkv'