2

My scenario is this: I have a few .mp4 files containing H264 video with AAC audio. Each file was encoded from a DVR source. There are a few frames too many at both the beginning and end of the file (less than 1 second worth). I want to edit off these few frames.

The problem is that I can tell that the first keyframe falls a couple seconds into the desired video.

I'd like to chop the first few undesired frames off of the video, but I'd like to not have to re-encode the entire file because this comes with the inherent generation loss of re-encoding already compressed video (and the time taken).

(I'm ignoring audio issues for the moment - for MP3 there are frame-level editors like MP3DirectCut and I think there's something similar for AAC audio. I can use ffmpeg to split the two streams out and work with them independently and then later remux them into a new .mp4 file.)

Here's a simple illustration of what I'd like to do:

Source file
|------GOP---------|------GOP---------|------GOP-----|
IBBPBBPBBPBBPBBPBBPIBBPBBPBBPBBPBBPBBPIBBPBBPBBPBBPBBI...
^   ^     
|   first desired frame
- undesired frame(s)

Re-encoded first GOP, other GOPs copied losslessly:
|----New GOP----|------GOP---------|------GOP-----|
IBPBBPBBPBBPBBBPIBBPBBPBBPBBPBBPBBPIBBPBBPBBPBBPBBI
|               |
+---------------+
re-encode just these
frames to a new short
GOP, copy all remaining
GOPs losslessly

I could be completely wrong, but my understanding of video encoding leads me to believe that GOPs are independent sets of frames that can stand alone; you need all of the frames of a GOP to decode the GOP, but one GOP will not be dependent upon another GOP. So, unless that assumption is completely wrong, I imagine something like this should work, theoretically:

  1. Re-encode the first couple of seconds of the video, starting at the desired frame, using the same settings (profile, level, etc.) that encoded the original source. I'd need to be able to do one of the following:
    • Extract off the first GOP of this newly-encoded file, and truncate that GOP so it ends on the frame right before the keyframe of the next GOP in the original file.
    • Specify a hard limit on the number of frames per GOP during encoding, so that I could then extract off the first GOP and it would already be the correct size.
  2. Take this new shortened GOP and append all but the first GOP of the original file. The end result would be a new H264 stream where only the first GOP has been re-encoded; all the rest of the GOPs would be copied losslessly.

I'm not necessarily against a paid tool, but pretty much any NLE is going to involve re-encoding the video at some stage. My goal is to bring as much as possible over from the original video losslessly. (Basically, if I could do this, the only area I'd be suffering any generation loss is in the first second of the file, which is likely to be a fade-in or even some black frames and of little importance to the overall presentation.)

Can this be done??

1
  • FYI, GOP nomenclature, I frames are generally equivalent to your key frames. P and B frames are equal to your I's. You may want to update your example so that people aren't confused. You should also be aware that depending on your user case, variable GOP lengths can cause problems. Mar 4, 2018 at 1:33

3 Answers 3

2

This is possible to do, in theory. The key is to match all relevant source bitstream properties when encoding.

#1 Segment source video

ffmpeg -i source.mp4 -c copy -an -f segment -segment_time 0.01 seg%d.ts

Since the segment muxer by default only splits at keyframes, by providing a very low segment time, each produced segment will be one GOP long.

(I suggest using the whole audio from the source, we don't extract it here)

#2 Re-encode first segment

ffmpeg -i seg1.ts {-encoding args} -copyts -x264opts stitchable{+any-other-args} new1.ts

It's important here to match source settings.

#3 Rejoin segments

ffmpeg -i "concat:new1.ts|seg2.ts|..." -i source.mp4 -c copy -map 0 -map 1:a new.mp4
4
  • This is a partial solution but doesn't solve the original question. Doing this as-is will simply re-encode the first GOP, but without truncating it. I tried re-encoding the first GOP with the -ss option, which worked, but then when I use the concat protocol there is a gap between the last frame of the newly encoded GOP and the first frame of the next one, which is exactly the length I cut off the beginning. It appears to be a timestamp issue. I tried using the concat demuxer, but that messes up the framerate of the video (and thus changes its length and messes up audio sync).
    – fdmillion
    Mar 6, 2018 at 18:33
  • Add -reset timestamps 1 to step 1 command and use the concat demuxer in step 3.
    – Gyan
    Mar 6, 2018 at 18:58
  • Or with my original set of cmds, use -copyts in step 2.
    – Gyan
    Mar 6, 2018 at 19:02
  • I fixed it by adding -copyts -start-at-zero to the step where I re-encode the first GOP of the video. I also had to write a Python script to generate the huge list ( seg2.ts|seg3.ts|... ) but it does seem to work. I also was able to truncate off the end by simply re-encoding the final GOP with the -t parameter. I'm giving you the answer credit since you got me in the right direction, and I'll post a more complete list of what I had to do to make it work below.
    – fdmillion
    Mar 6, 2018 at 19:14
0

I gave answer credit to @Mulvya since that post got me in the right direction. Here is what I did to trim off the beginning of the video files:

  1. ffmpeg -i source.mp4 -c copy -an -f segment -segment_time 0.01 seg%04d.ts - This split the source stream into hundreds of segments, one per GOP of source video.
  2. ffmpeg -ss 00:00:01.5 -i seg0000.ts -c:v libx264 -copyts -start_at_zero -x264opts stitchable -profile:v high -level 5.0 -crf 18 seg0000a.ts - This encodes the first GOP to a new ts stream, truncating off the beginning 1.5 seconds.
    • A few files wouldn't truncate correctly, so I first converted them to lossless FFV1 video: ffmpeg -i seg0000.ts -c:v ffv1 -level 1 -coder 1 -context 1 -g 1 seg0000.mkv and then executed the above command against the .mkv file.
  3. To trim off from the end of the video, I encoded the final GOP truncating it: ffmpeg -t 00:00:03.20 -i seg1044.ts -c:v libx264 -copyts -x264opts stitchable -profile high -level 5.0 seg1044a.ts - This encodes only the first 3.2 seconds of the final GOP, truncating the rest. (GOPs in the source video were about 5 seconds each, so this truncated 1.8 seconds.)
  4. ffmpeg -i source.mp4 -c copy -vn audio.m4a - extract the audio
  5. Trim the audio using an external tool. MP3DirectCut can do frame-accurate trimming of M4A/AAC audio, and delaycut can do frame-accurate trimming of AC3/EAC3/DTS audio.
  6. Used a python script to generate a string containing all the .ts files. There's lots of ways to accomplish this sort of thing. I ended up with a long string on the clipboard: seg0000.ts|seg0001.ts|...|seg1044.ts
  7. Pasted this long string into a text editor and changed seg0000.ts and seg1044.ts to seg000a.ts and seg1044a.ts respectively.
  8. Combine the video segments: ffmpeg -fflags +igndts -i "concat:{string generated in steps 6-7 here}" -c copy -fflags +genpts video.mkv
  9. Finally, re-combine the trimmed audio: ffmpeg -i video.mkv -i audio.m4a -c copy -map 0:0 -map 1:0 final-video.mp4

I imagine this process could probably be optimized further, but this is what ultimately worked after messing with it for over an hour.

(Hint: I found it convenient to extract all the frames of one GOP to .png files for easy browsing to determine which frame to cut at. For example: ffmpeg -i seg0000.ts seg0000-frame%03d.png)


EDIT: This is the Python script I wrote to generate the concat: list:

# ffconcat.py

import sys
from os.path import basename
from os import listdir

if (len(sys.argv) < 2):
        print("Syntax: %s [extension]" % (sys.argv[0], ))
        exit(1)

filelist = list(filter(lambda y: y.endswith("."+sys.argv[1]), sorted([basename(x) for x in listdir('.')])))
filestring = "|".join(filelist)

sys.stdout.write("concat:"+filestring)
sys.stdout.flush()

You could use it in step 7 by first removing the "original" .ts files (e.g. seg000.ts) and then using it like:

ffmpeg -fflags +igndts -i `python3 ffconcat.py ts` -c copy -fflags +genpts video.mkv

This script is very simple (it only scans the current directory) so if you wanted to put the muxed video elsewhere you could, for example:

ffmpeg -fflags +igndts -i "concat:{string generated in steps 6-7 here}" -c copy -fflags +genpts ../video.mkv

Feel free to use/improve this script.

1
  • To trim off from the end of the video --> no need to re-encode if trimming off the end. start_at_zero is only required if ss is before the input -i.
    – Gyan
    Mar 7, 2018 at 4:58
0

Avidemux is a simple, open-source video editor which allows for trimming clips without re-encoding. You open your video, choose the trimming marks, and set the "Video Output" to "Copy" before saving.

Here is a tutorial on using it for lossless trimming:
https://steemit.com/videography/@phloatingman/tutorial-how-to-trim-a-video-without-transcoding-using-avidemux

Internally because of the way H.264 works, you can trim trailing P/B frames from a GOP, but if you trim out an I frame, you will need to trim out the entire GOP. Avidemux warns you of this. If you needed an exact trim and it fell mid-GOP you might need to make two clips (with a re-encoding of only the first one) and then join them in Avidemux.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.