The blog for the bleeding-edge news in the server of Research and Development.

Trimming an h264 video and preserving most of the original video stream

← Blog

I figured I'd spend some time archiving old family footage. I've actually been meaning to, but recent events that we're all aware of gave me a better excuse for once.

I'll revisit the rest of the story later so this post doesn't end up like one of those food recipes; here's the goods:

The process

This process assumes that the video being trimmed has a video stream encoded in h264 and an audio stream in AAC.

The trimming process reencodes only the starting portion; the middle is copied from the original video and the end is truncated.

Most of the information below is copied from this stack exchange discussion, with some alterations.

Generate the segments, specifying the desired start and end time.

ffmpeg -ss {start} -i {input} -to {end} -c copy -copyts -an -f segment -segment_time 0.01 -segment_list "segments.ffconcat" -muxpreload 0 -muxdelay 0 "seg%05d.ts"
  • This differs from the original answer by using ffmpeg's built-in segment_list option to generate a concatenation list which will be used when rebuilding the output video.
  • The low segment_time splits segments on keyframes.
  • The parameters -muxpreload 0 -muxdelay 0 removes the 1.4 seconds offset for TS. I haven't looked into other containers to find one that supports start offsets without this delay oddity.
  • The parameter -ss {start} performs input seeking on the file, allowing for the first sequence in the output to be on the first keyframe prior to our desired start time.
  • {end} truncates the last sequence. This is lossless (rather, not re-encoded), as we're only lopping off literal bits at the end.

Extract the audio and losslessly cut start and end points (we'll just stick it in a container so we don't have to worry about the underlying codec):

ffmpeg -vn -i {input} -ss {start} -to {end} -c:a copy audio.mkv
  • -vn must be specified before the input file to make seeking fast on longer files (it skips decoding the video, which we discard anyways). We use output seeking as we don't want to be bound to keyframes.

Do a lossy reencode of the starting segment, cutting at our desired start point:

ffmpeg -i seg00000.ts -ss {start} -c:v libx264 -copyts -x264opts stitchable seg00000a.ts
  • We use the output seek with -copyts so ffmpeg seeks relative to the original input file. This matters if the actual video content starts a few keyframes / sequences into the video.

Modify the segments.ffconcat file to use the new first segment. The following snippet is Python:

with open('segments.ffconcat', 'rt') as in_seg, open('segments_modified.ffconcat', 'wt') as out_seg:
    for line in map(lambda l: l.strip(), in_seg):
        if line == 'file seg00000.ts':
            line = 'file seg00000a.ts'
        print(line, file = out_seg)

Concatenate the segments and mux the trimmed audio back in:

ffmpeg -f concat -i "segments_modified.ffconcat" -i audio.mkv -c copy -map 0:v:0 -map 1:a:0 {out}

The backstory

So yeah. Digitizing stuff from the analog days. My dad recorded a lot.

I'm not a professional archiver by any metric, and at the moment I don't want to spend hundreds on things like a good time base corrector or very good recording equipment in general.

Even though I just spent a good $140 on a decent scanner…

I also don't have the care to do lossless post-production editing at this time. Everything comes in from some cheap EasyCap and straight out to single-pass h264 / AAC via VLC.

The capture process is semi-automated, but I still have to start and stop the recording process manually. That means I have gaps at the start and end of the footage. A few of the recordings have lengthy silence periods as well, or otherwise should be split into multiple videos.

I personally find that a non-issue for storage, but for playback purposes, I'd prefer to have them trimmed proper.

After a bunch of searching and testing, I settled on this solution.

I have a Python script written up, but it still needs cleanup before it can be published. Though Python's command execution isn't as elegant as a proper shell, it:

  • is cross platform
  • cleans up after itself if it has to bail out of the process (using tempfile to store our working segments)

Hoping to publish it in the future; watch this space if you want it (or implement it with the information given above).

This has been your obligatory "breaking the silence with COVID" post.

(Up you go!)