1. BACKGROUND
Workflow time again! Put your feet up and let me send you to sleep....
Ever since I got my Panasonic GH4 I've been searching for a way to smart render the UHD MP4 footage that I shoot with it for archiving, in a batch. By that I mean trim files without recompression. This is super-important for me because I'm building a huge archive of marine life footage, and so much of the original footage is useless while the camera is rolling, waiting for the moment. File size is important because I like to carry this archive with me in the field on HDD for reference purposes, but I hate losing quality with recompression. The best (IMO) lossless codec (MagicYUV) creates files 18 times larger than the original. The strongest lossy contender (Sony XAVC Intra) creates files 3.3 times larger (link). Me no like! My frustrating search for a solution is documented here.
2. TRIMMING A FILE WITH FFMPEG
However I've found out that FFmpeg will smart render MP4 files. Here's an example command line:
ffmpeg -ss 00:00:02.603 -i input.MP4 -c copy -t 00:00:17.384 output.mp4
Like many other users, I have been shooting MP4 with LPCM audio on my GH4. Unfortunately FFmpeg does not support LPCM audio. I think this might be because PCM audio is only supported in an mp42 brand container, while FFmpeg only supports mp41.
One option is to delete the audio with the -an option:
ffmpeg -ss 00:00:02.603 -i E:\source.mp4 -an -c copy -t 00:00:17.384 E:\output-no-audio.mp4
Another option is transcode the audio to reasonable-quality AAC:
ffmpeg -ss 00:00:02.603 -i E:\source.mp4 -acodec libvo_aacenc -q:a 100 -vcodec copy -t 00:00:17.384 E:\output-libvo-aac.mp4
You can get better AAC audio using the Fraunhofer FDK AAC codec, but unfortunately, due to licensing restrictions, you will struggle to find an ffmpeg build online that includes it. I compiled my own using the instructions at here (but using the latest versions of the tools in that guide):
ffmpeg -ss 00:00:02.603 -i E:\source.mp4 -acodec libfdk_aac -b:a 128k -vcodec copy -t 00:00:17.384 E:\output-libfdk-aac.mp4
Now then... A Quicktime (MOV) wrapper will support H.264 with LPCM audio. And if you trim and re-wrap MP4/LPCM as .MOV files, they still play back in Vegas Pro with the same performance as the .MP4 files, because they are treated as "Sony AVC" format and get decoded with the compoundplug.dll codec, not nasty 32-bit Quicktime as you might think:
So simply by changing the output extension to ".mov" we can trim and re-wrap to MOV, keep our high-quality LPCM audio, and not lose timeline performance:
[code]ffmpeg -ss 00:00:02.603 -i input.MP4 -c copy -t 00:00:17.384 output.mov
So there are a number of options there, depending on which format audio you shot, and how important it is to you.
3. THE ENDS AREN'T QUITE RIGHT
As far as I can tell, FFmpeg trims the file to the first i-frame outside the selected range at each end, so it only copies whole GOPs. But information is embedded in the trimmed file to tell it how many frames to hide when decoded. I think Premiere Pro ignores that information when the file is put on its timeline, so just shows a clean file, but a bit longer than you thought you had trimmed. Vegas Pro makes an effort to get it right but often displays a small number of fewer or extra frames at the end. It might be FFmpeg's problem, I don't know. In the case of GH4 UHD 30p trimmed footage you typically get 2 extra duplicate frames at the start and 3 extra at the end. Files that are only trimmed by 1 or 2 frames at the ends, actually lose 1 or 2 frames when decoded.
4. WRITING A LIST FOR A BATCH-TRIMMING (WITH COMPENSATION FOR THE ENDS)
So I've written a script to export the first Vegas Pro video track as a list in a text file, trimlist.txt, which can then be used by a batch file to trim the files with FFmpeg. Each line contains the name of the media file, the timecode in, and the length of the event on the timeline, separated by spaces. Copy and save the text as "FFmpeg Trim List.cs" in your scripts folder (such as C:\Program Files\Sony\Vegas Pro 13.0\Script Menu). As my starting point I used a script that JohnnyRoy wrote over 9 years ago for exporting a list of MPEG-2 files for Womble smart rendering.
To compensate for the decoding peculiarities in Vegas Pro, the script adds frames to the start and end of the file, for later trimming with another script (see section 6 below) when the files are put back on the timeline. It also detects how many frames you have trimmed from each end and compensates further if you are only trimming only 0, 1 or 2 frames. The priority is to completely avoid duplicate or messed up frames at the end of this workflow. The 2nd priority is to preserve as much of the wanted section as possible. There are 7 user-settable figures in this script. As they are shown, with GH4 UHD 30p footage, you don't lose any frames with normally trimmed clips, and the most you would lose from an end of an untrimmed clip, or one trimmed really close to the end, is 2 frames.
Edit: There is later version of this script, in a later post, that automatically runs the batch file described later in section 5, along with other various changes!
/** * Program: Export FFmpeg Trim List.cs * * Place this file in your scripts folder (e.g. C:\Program Files\Sony\Vegas Pro 13.0\Script Menu) * * Author: Nick Hope. Inspired by the script WombleExport.cs by John Rofrano * * Purpose: This script will export the first Vegas Pro video track as a list in a text file, trimlist.txt, * which can then be used by a batch file to trim the files with FFmpeg. * * Each line contains the name of the media file, the timecode in, * and the length of the event on the timeline separated by spaces. * * ffmpeg will copy an H.264 video stream without recompression ("smart rendering") but the trimmed files * usually decode in Vegas Pro with anomalies at the ends such as duplicate frames. To deal with this, extra * frames can be added added to the start and end of each clip before export by editing the figures for "startAdd" * and "endAdd" below, then trimmed when the exported clips are brought back to the the Vegas Pro timeline * with a script such as Trim Captured Clips v1.0.cs * * In my experience with GH4 UHD 30p clips, after trimming with ffmpeg and bringing the clips back to the timeline: * Clips which had been trimmed >=2 frames at the start will decode later with frame accuracy (0 duplicates at the start) * Clips which had been trimmed just 1 frame at the start will decode later with 1 duplicate frame at the start. * Clips which had not been trimmed at all at the start will decode later with 2 duplicate frames at the start. * Clips which had been trimmed >=2 frames at the end will decode later with 2 or 3 extra frames at the end, sometimes all duplicates. * Clips which had been trimmed 1 frames at the end will decode later with 1 extra ok frame and 1 extra duplicate frame at the end. * Clips which had not been trimmed at all at the end will decode later with 1 extra duplicate frame at the end. * To compensate for these variations. * * The aim is to avoid all duplicate frames and to accept a very small loss of frames at the ends if necessary. * * End result for GH4 UHD 30p footage, after clips are brought back to the timeline and trimmed 2 at start and 3 at end: * Original trimmed >= 2 frames at start : frame-accurate at start * Original trimmed 1 at start : lose 1 frame at start * Original trimmed 0 at start : lose 2 frames at start * Original trimmed >= 2 frames at end : mix of frame-accurate at end or 1 good extra frame * Original trimmed 1 at end : lose 1 frame at end * Original trimmed 0 at end : lose 2 or 1 frames at end * * Please report any errors to nick@bubblevision.com * * Version 1.0, Oct. 01, 2015 **/ using System; using System.IO; using System.Windows.Forms; using Sony.Vegas; class EntryPoint { public void FromVegas(Vegas vegas) { // Enter number of frames to add to the start. // This is the maximum amount of rubbish you get at the START when you put // ffmpeg-processed clips back on the timeline. // For me, this occurs with untrimmed clips (or clips that were not trimmed just at the start) // Match this figure with the clipStartTrim variable in your trimming script int startAdd = 2; // 2 is good for GH4 UHD 30p // Enter number of frames to add to the end. // This is the maximum amount of rubbish you get at the END // when you put ANY ffmpeg-processed clips back on the timeline // Match this figure with the clipEndTrim variable in your trimming script int endAdd = 3; // 3 is good for GH4 UHD 30p // get the first video track VideoTrack videoTrack = null; foreach (Track track in vegas.Project.Tracks) { if (track.IsVideo()) { videoTrack = (VideoTrack)track; break; } } // if we didn't find a track then tell the user we can't continue if (videoTrack == null) { MessageBox.Show("You must have at least one video track in your project", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // make sure the project has a name if (vegas.Project.FilePath == null) { MessageBox.Show("You must give your project a name by saving it before running this script", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } string trimlistFilename = String.Format("E:\\trimlist.txt"); // Alternative to create the trim list name from the project name // string trimlistFilename = Path.ChangeExtension(vegas.Project.FilePath, ".txt"); int eventsProcessed = 0; StreamWriter writer = null; try { writer = new StreamWriter(trimlistFilename); for (int i = 0; i < videoTrack.Events.Count; i++) { VideoEvent videoEvent = (VideoEvent)videoTrack.Events[i]; // Get the input file name string infile = videoEvent.ActiveTake.MediaPath; // Get the path and filename without the extension string basename = Path.Combine(Path.GetDirectoryName(infile), Path.GetFileNameWithoutExtension(infile)); // COMPENSATE OFFSET ("START") // Get the offset as frames (integer) int eventOffset = (int)videoEvent.ActiveTake.Offset.FrameCount; // Calculate the number of frames to adjust offet by. Never less than zero int offsetAdjuster = Math.Max(0, (startAdd - eventOffset)); // Add the frames and adjustments to the offset int offsetAdjusted = eventOffset - startAdd + offsetAdjuster; // COMPENSATE LENGTH // Get media length as frames (integer) int mediaLength = (int)videoEvent.ActiveTake.Media.Length.FrameCount; // Get event length as frames (integer) int eventLength = (int)videoEvent.Length.FrameCount; // Calculate how many frames from the end of the media the event is trimmed by int tailRoom = mediaLength - (eventOffset + eventLength); int lengthAdjuster; // Set length adjustment depending on tailroom. // Sadly didn't follow a neat pattern. Figures established by testing. Edit the figures below if necessary for other formats. if (tailRoom < 2) // 2 is good for GH4 UHD 30p { lengthAdjuster = -1; // -1 is good for GH4 UHD 30p } else if (tailRoom == 2) // 2 is good for GH4 UHD 30p { lengthAdjuster = 0; // 0 is good for GH4 UHD 30p } else { lengthAdjuster = 2; // 2 is good for GH4 UHD 30p } // Didn't quite work for GH4 UHD 30p but following Min.Max method might be // more elegant than the if-else-if-else-if loop above for some formats //int lengthAdjuster = Math.Max(2, (tailRoom - 2)); // Add the frames and adjustments to the length int lengthAdjusted = eventLength + startAdd + endAdd - offsetAdjuster - lengthAdjuster; // Convert offset and length adjustments from frames to timecode Timecode offsetAdjustedTC = Timecode.FromFrames(offsetAdjusted); Timecode lengthAdjustedTC = Timecode.FromFrames(lengthAdjusted); // Convert from timecode to seconds double start = offsetAdjustedTC.ToMilliseconds() / 1000; double duration = lengthAdjustedTC.ToMilliseconds() / 1000; // Change ".mp4" if source is in a different container e.g. .mov string triminfo = String.Format(basename + ".mp4 " + start + " " + duration); writer.WriteLine(triminfo); eventsProcessed++; } writer.Close(); } catch (Exception e) { if (null != writer) { writer.Close(); } MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } MessageBox.Show("Processed: " + eventsProcessed + " events", "Complete", MessageBoxButtons.OK, MessageBoxIcon.Information); } }
That hard-codes the list to "E:\\trimlist.txt", so you'll almost certainly want to edit that path for your system.
5. PROCESSING THE BATCH-TRIM
The next step is to process the list with FFmpeg. I am doing this with Windows batch files, which I got some help with on the wonderful Stack Overflow. They include code to add an incrementing suffix to the filename if you are trimming more than one clip from the same media.
Here is one to smart-trim MP4 files. As explained above, it doesn't work for LPCM audio. Copy and save this text as *.bat. Edit the location of the trim list file. Double click to run it:
Edit: The later version of above script, in a later post, automatically runs the batch file
Edit 2: You must change to delims= " to delim=;" for this to work with version 5.0 and later of this script.
@echo off setlocal enableDelayedExpansion set prevfile= for /F "tokens=1,2,3 delims= " %%F in (E:\trimlist.txt) do ( if "%%F"=="!prevfile!" ( if "!counter!"=="" (set counter=1) else (set /a counter+=1) ) else ( set counter= set "prevfile=%%F" ) ffmpeg -ss "%%G" -i "%%F" -c copy -t "%%H" "%%~dpnF!counter!-trimmed.mp4" ) pause
I don't fully understand the warnings (in yellow on Windows 10) about global headers but they can be ignored in relation to this workflow.
Once it's working nicely you could remove "pause" from the end so the command prompt window closes itself, but I like to review the command prompt window to make sure there were no errors (shown in red on Windows 10).
Here is one to smart-trim and re-wrap to MOV files, keeping the LPCM audio. For other variations, change the options in the ffmpeg line, referencing either the individual command lines in section 2 of this post (above), or the FFmpeg documentation.
Edit: You must change to delims= " to delim=;" for this to work with version 5.0 and later of this script.
@echo off setlocal enableDelayedExpansion set prevfile= for /F "tokens=1,2,3 delims= " %%F in (E:\trimlist.txt) do ( if "%%F"=="!prevfile!" ( if "!counter!"=="" (set counter=1) else (set /a counter+=1) ) else ( set counter= set "prevfile=%%F" ) ffmpeg -ss "%%G" -i "%%F" -c copy -t "%%H" "%%~dpnF!counter!-trimmed.mov" ) pause
6. TRIMMING THE TRIMMED FILES
When you put the trimmed files back on the timeline, you have to trim off the duplicate frames (or other rubbish) from the ends of the files. You can use jonask's old script, Trim Captured Clips v1.0.cs. Just select the files to trim on the timeline and run the script. You can download a version here that has the right numbers for trimming GH4 UHD 30p clips previously exported using the above workflow (2 at the start and 3 at the end). Or copy and paste this to a .cs file in your scripts folder.
/* This script will remove the first two and the last three frames of each selected event. Note that associated audio events also needs to be selected. If multiple events are selected on the same track, they will be shifted to the left in order to not get any gaps between the events. This script has been verified using Sony HDR-HC3 (NTSC version), HDVSlit v0.75 (http://strony.aster.pl/paviko/hdvsplit.htm) and Vegas version 7.0b. Your camera and capture program might require a different number of frames to be removed. In that case, make changes in the code below (the clipStartTrim and clipEndTrim variables). The script has been verified using the project templates "HDV 1080-60i (1440x1080, 29.970 fps)" and "HDV 1080-50i (1440x1080, 25.000 fps)". Other templates with either 29.970 fps or 25.000 fps should also work. However, there is no guarantee that it works. Use it at your own risk. Place this file in the following folder C:\Program Files\Sony\Vegas 7.0\Script Menu In Vegas, execute Tools->Scripting->Rescan Script Menu Folder This script will then be available at Tools->Scripting. History: v1.0 2006-10-15 Verfified that v0.9 also works for 25.000 fps (PAL). v0.9 2006-10-14 First version. Supports 29.970 fps (NTSC). */ using System; using System.Text; using System.Collections; using System.Windows.Forms; using Sony.Vegas; public class EntryPoint { public void FromVegas(Vegas vegas) { Timecode clipStartTrim = Timecode.FromFrames(2); // Enter number of frames to delete from start of clips. 2 is good for GH4 UHD 30p Timecode clipEndTrim = Timecode.FromFrames(3); // Enter number of frames to delete from end of clips. 3 is good for GH4 UHD 30p Project proj = vegas.Project; foreach (Track track in proj.Tracks) { Timecode totalTrim = Timecode.FromFrames(0); foreach (TrackEvent trackEvent in track.Events) { if (!trackEvent.Selected) continue; Take activeTake = trackEvent.ActiveTake; if (activeTake == null) continue; Media media = activeTake.Media; if (media == null) continue; Timecode eventStart = trackEvent.Start; Timecode eventEnd = trackEvent.Start + trackEvent.Length; Timecode takeOffset = activeTake.Offset; // Modify the beginning of the clip takeOffset = takeOffset + clipStartTrim; activeTake.Offset = takeOffset; // Shift the clip to the left and make the starting point correct for a 29.970 fps project (as well as for 25.000 fps) trackEvent.Start = Timecode.FromNanos(((((eventStart.Nanos - totalTrim.Nanos)*3 + 500)/1000)*1000)/3); // Update how much the followin clip on the same track should be shifted. totalTrim = totalTrim + clipStartTrim + clipEndTrim; // Make the length correct for a 29.970 fps project (as well as for 25.000 fps) trackEvent.Length = Timecode.FromNanos(((((eventEnd.Nanos - totalTrim.Nanos)*3 + 500)/1000)*1000)/3 - trackEvent.Start.Nanos); } } } }
7. CONCLUSION
So it's a working solution and a way forward. The trickiest thing for me will no doubt be remembering to run the Trim Captured Clips script when the files are brought back to the timeline, especially as my archive will contain a mixture of files that do or don't need such trimming.
I really went through the mill putting this together, because I'm not a programmer, but I needed it so badly for my workflow. I hope it's useful for some other too! I can't yet bring myself to test other GH4 formats such as C4K 24p to come up the right numbers for the script. If I do, I'll come back and report. I did do a quick test on a downloaded YouTube video and the basic method works, and it seemed no added frames were required at all, so it might even be that the peculiarities at the end are limited to Panasonic GH* files.
Note that Vegasaur 2.0 uses FFmpeg under the hood in it's Smart Trim feature. That is a simpler and more elegant solution than mine for many formats, but in it's current form (version 2.2) it won't handle MP4/LPCM input. You could add the extra frames quite easily with it, but it doesn't include the code for compensating if the cuts happen at or near the end of events. Like I say, it might only be GH*-originated footage that requires these workarounds.
I think it would be great if someone wrote an FFmpeg GUI as a Vegas Pro extension. There is an awful lot that program can do, including transcoding to ProRes. A GUI could have boxes for the main options, and then a field where you could manually enter a command line, in a similar way to the x264 configuration dialog.
It would also be great if SCS fixed the decoder in compoundplug.dll, to deal with the mess at the ends of decoded files. I bet the problems lie in the same code that makes a mess of GH* 24p files when they are put on the timeline.