Smart Rendering in a Batch with FFmpeg (GH4 etc.)

NickHope wrote on 10/2/2015, 4:15 AM

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.

Comments

NickHope wrote on 10/2/2015, 9:51 AM
Does anyone how to fire up the Windows batch script (or another type of script or executable that does the same thing) automatically from within the Vegas script?
NickHope wrote on 10/2/2015, 10:25 AM

Well that was easy, thanks again to Stack Overflow. I just added an extra line, 4 lines from the bottom, pointing to the batch file, and now it runs automatically.

Version 2 now looks like this (There is a further update in this later post!):
 

/**

* 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

* Version 2.0, Oct. 02, 2015. Added line to automatically run the batch file from this script

**/

 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);

        }

	// Edit the path to your batch file. Remove the following line if you want to run the batch file manually

	System.Diagnostics.Process.Start("D:\\Documents\\batch-files-and-command-lines\\trim-mp4-to-mp4-no-audio.bat");

        MessageBox.Show("Processed: " + eventsProcessed + " events",

            "Complete", MessageBoxButtons.OK, MessageBoxIcon.Information);

    }

}

 

 

 

astar wrote on 10/3/2015, 3:25 PM
Ffmpeg does support pcm audio. You simply do not have the audio format specified in you conversion / trim statement. You need -v codec copy and -a codec copy in your statements, or define the audio format you want.
NickHope wrote on 10/3/2015, 11:12 PM
I think you mean "-vcodec copy" and "-acodec copy". Those are both implied by "-c copy". It doesn't work. LPCM audio output is not supported in an mp4 container.
astar wrote on 10/4/2015, 1:36 PM
This thread seems more like something for CC-FFMPEG forum or at least the Vegas Scripting Forum.

Have you tried .MXF from FFMPEG? You will have to set your audio format and channel mapping for that. .MXF will avoid any 32-bit issues. Then you will end up with basically a type of XAVC-L.

Is there some reason you are tied to .mp4 container beyond avoiding Quicktime?
NormanPCN wrote on 10/4/2015, 6:05 PM
Something like this batch will remux an AVC/PCM (stereo) MP4 file(s) to AVC MXF.
Drag and drop.

MXF wants every audio channel separate.
Vegas is fine with AVC in MXF since that is XAVC-L which it supports.


@echo off

:top
ffmpeg.exe -i %1 -c:v copy -c:a pcm_s16le -filter_complex channelsplit=channel_layout=stereo[FL][FR] -map 0:v:0 -map [FL] -map [FR] "%~dpn1_mxf.mxf"

shift
if NOT %1$==$ goto top

pause

NickHope wrote on 10/5/2015, 1:27 AM
"This thread seems more like something for CC-FFMPEG forum or at least the Vegas Scripting Forum."

I agree with you if it was still in development, and I did post an early version of the script in the Scripting forum when I needed help (and didn't get any). But it's basically complete, so this is a post to show Vegas Pro users a workflow they can use to smart render formats that Vegas itself won't, and it belongs here where they'll see it. FFmpeg forum-goers probably know a lot of this stuff already and mostly wouldn't be interested in the Vegas parts.

"Is there some reason you are tied to .mp4 container beyond avoiding Quicktime?"

Not at all, which is why MOV is my chosen solution above for retaining LPCM audio with H.264. As I explained above, Quicktime is not involved in the decoding in Vegas. Thanks for the MXF suggestion, and thanks for the syntax Norman. Unfortunately it gives me this error message, so it looks like the GH4's long-GOP H.264 is not supported:

[mxf @ 000000000blahblah] AVC Intra 50/100 support only

Here's a sample GH4 UHD 30p MP4/LPCM file if anyone feels inclined to test this.

Norman, I'm not experienced with batch files so I'm not sure what exactly you mean by "drag and drop" in this context.
Gyan wrote on 10/5/2015, 2:55 AM
ffmpeg does support mp42 container. My version, compiled in Sept., says this, when encoding to mp4:

Metadata_Write:
... major_brand : mp42
NickHope wrote on 10/5/2015, 5:18 AM
Ah, so LCPM in MP4 must be not supported for some other reason. Mine also compiled in September, based on FFmpeg 2.8.
Gyan wrote on 10/5/2015, 6:18 AM
Yeah, LPCM doesn't seem to be one of the accepted audio codecs in MP4:

https://en.wikipedia.org/wiki/MPEG-4_Part_14#Data_streams
NormanPCN wrote on 10/5/2015, 11:53 AM
@Nick By drag and drop I meant that you could select any number of source files in the file browser and drop them on the script icon and it would do the conversion. That is what the shift command is doing. It is walking through each parameter/file passed to the script.

I have only used that script of mine on XDCAM data in MOV files. Not AVC. This was to work around the issue that QT will not decode mpeg-2 data without purchasing a decoder for QT. I assumed it should work with AVC but obviously not.

It seems ffmpeg is being very particular about the exact AVC content format. XAVC is just AVC with very particular settings and maybe that is what ffmpeg is griping about, although nothing in the command line is referencing XAVC in particular. Maybe AVC in MXF in ffmpeg is tied to XAVC in some way.

Interesting that the message says only AVC Intra is supported. XAVC-L is long GOP in MXF so message is bad for ffmpeg does not support XAVC-L compatible output.

I downloaded your file and will and see if I can get ffmpeg to get it into an MXF. I have found ffmpeg MXF output to be quite persnickety and exact options given.
The " -d10_channelcount requires MXF D-10 and will be ignored" just always seems to be there. The command is not trying to output MXF D10 and something internal is setting that option anyway and thus the complaint about ignoring the option.

@Gyan How do you get ffmpeg to output mp42. I always get ISOM. I have tried -tag:v mp42 with no effect.

ffmpeg and PCM audio in MP4. ffmpeg has no problem understanding this PCM/MP4 on input. Sony has been putting PCM in MP4 since XDCAM EX and the new XAVC-S also does this. It certainly can be a Sony "extension" that applications decide to support. If not an ISO listed feature, then why not support the Sony "extension" like everyone else? But hey, ffmpeg developers are kind of an odd lot. The whole ffmpeg and LAV split and fight just shows some of this. ffmbc also.
Gyan wrote on 10/5/2015, 12:29 PM
Norman, nothing, actually. I'm using stock ffmpeg Win binaries from Zeranoe - https://ffmpeg.zeranoe.com/builds/.

About PCM, that Wiki article I linked to says that almost any ISO registered codec can be placed in a MP4 as a 'private stream'. But only a few like AAC enjoy wide compatibility.
NormanPCN wrote on 10/5/2015, 1:41 PM
@Gyan

I also use Zeranoe ffmpeg builds.
For me, MediaInfo always shows the following for my MP4 AVC/x264 encodes. I got this with an ffmpeg I downloaded just now. Always ISOM.

Format : MPEG-4
Format profile : Base Media
Codec ID : isom
File size : 1.89 MiB
Duration : 10s 36ms
Overall bit rate : 1 577 Kbps
Writing application : Lavf57.3.100
NormanPCN wrote on 10/5/2015, 4:21 PM
@Nick I can't seem to get ffmpeg to put AVC LongGOP in MXF. For whatever reason it appears to be restricted to Intra only for AVC. Doesn't matter anyway, as you stated, with AVC+PCM in a MOV file (aka typical DLSR) Vegas will bypass the Quicktime (potential) nightmare for us.

NickHope wrote on 10/5/2015, 11:58 PM
Thanks for trying Norman. And thanks for showing me how to do drag-and-drop batch files.

I may ask some questions about these limitations on ffmpeg forums.
altarvic wrote on 12/18/2015, 10:45 AM
Thanks to Nick's advices and wishes, Smart Trim (in Vegasaur 2.4) can do all these steps and much more, providing interface to FFmpeg.
PeterDuke wrote on 12/18/2015, 10:12 PM
Nick, have you tried VideoReDo?

I tried 4096x2160 and 3840x2160 samples from here
http://www.dvc.uk.com/acatalog/GH4-Sample-footage.html

Both seemed to edit OK except that MediaInfo reported that the 4096 sample was 3840 with a display aspect ratio of 1.884 instead of 1.896, while Vegas said that the pixel aspect ratio was 1.0600, which I thought ought to be 4096/3840=1.0667. EDIT See later post

In both cases the time/date data was removed, but I suspect that this would also happen with your approach.
musicvid10 wrote on 12/19/2015, 8:09 AM
"
Hope you're having a great holiday season, Nick!
PeterDuke wrote on 12/29/2015, 9:03 PM
VideoReDo will smart render 4K, but you must set the maximum resolution in the output profile to 4096x2160 before you render each time. (It defaults to 3840x2160).

The support person did not know why 4K was implemented this way when it was added. He said that I was the first to raise the issue and therefore it was not of general concern. I said that 4K will become more common in the future. I also said that it looked like laziness on the part of the developer to me and I hoped that this odd behaviour of VideReDo will be fixed in a future update.
NickHope wrote on 12/30/2015, 11:36 PM

Thank you Musicvid. It's been great. Season's greetings to you too :)

Peter, I have looked into VideoReDo before and have just tested the current trial version again with some GH4 UHD 30p footage. In my first test, it crashed during the render. I did then manage to render 2 files, but they do not decode properly in VP13. Much of them appear as green thumbnails on the timeline, they don't play back properly, and the end is not quantized. So I really don't have much faith in VideoReDo for doing this. If those problems could be solved somehow (a different profile perhaps?), is there any way to get an edit list into it from Vegas etc.?

Note that for just trimming files one by one from Panasonic cameras, their own PHOTOfunSTUDIO is probably the best way as it is robust, frame-accurate, and retains metadata including exposure details.

What Altarvic has done with smart trimming in Vegasaur 2.4 is really great. It provides not just smart trimming, but also a complete interface to FFmpeg, allowing conversion to ProRes etc.. There are a few things to note:

- For Panasonic GH4 MP4 UHD footage and many similar long-GOP formats you'll still need to trim the ends of the Vegasaur-smart-trimmed files when placed back on the timeline to avoid peculiarities at the ends such as repeat frames, reversed frames. You can trim using a script as per section 6 of my guide above, and add frames in advance in Vegasaur Smart Trim to compensate.

- My workflow above adjusts the number of added frames if one end of the file is not trimmed at all, or if the cuts take place very close to the end of the file. Vegasaur does not do this and it probably shouldn't attempt to, as it's format-specific and overly-fastidious for most purposes.

- The latest FFmpeg included in Vegasaur 2.4 (download the static 64-bit build here) has a stable native AAC encoder which is apparently "of equal or greater quality than most of the other encoders available to the public", so if you want AAC encoding there's probably no longer any need to build your own FFmpeg with Fraunhofer FDK AAC codec support. I haven't compared the quality of the 2 codecs.

- There are further observations in this thread.

- Some Smart Trim custom commands from the lastest Vegasaur help file:

Re-wrap video files to MP4 (w/o re-encoding):

-ss {start} -i "{input}" -codec copy -t {duration} "{output}[mp4]"

Re-wrap video files to MOV (w/o re-encoding):

-ss {start} -i "{input}" -codec copy -t {duration} "{output}[mov]"

Re-encode audio to AAC (video is not re-encoded):

-ss {start} -i "{input}" -c:v copy -c:a aac -b:a 128k -t {duration} "{output}"

Convert to ProRes:

-ss {start} -i "{input}" -c:v  prores -profile:v 3 -c:a copy -t {duration} "{output}[mov]"
NickHope wrote on 10/31/2016, 11:00 AM

I just tested TMPGEnc Smart Renderer again, the latest version 5.0.7.9. Unlike my previous test, it does now "mostly" smart render GH4 footage. However with my UHD 30p MP4 clips it re-renders the first 64 to 74 frames (more than 2 seconds) and the final 2 to 12 frames. It does make a good job of the re-render though, and the quality of the re-rendered part can be set. The rest of the clips is identical to the original, as is the audio. And when the rendered clips are decoded in Vegas Pro there are no repeat or glitched frames that need trimming, as happens with FFmpeg.

However the bigger problem is that the batch list format (*.tmsr5b) is a closed/compiled/encrypted format so it's impossible to get a batch list into it in text format from Vegas.

noisywan wrote on 8/4/2017, 5:41 AM

Thanks for the script @Nick Hope, it really saves A LOT of time and disk space.

I just need a little help with the Trim List file though.

FFMPEG running on my Windows does not accept decimal notation with commas. (I guess due to international /regional differences)

It rejects `-ss 15,0000` and only works if it's written as `-ss15.000`

I can use Notepad's FIND& REPLACE to manually swap commas with dots but a line of code in the script would be great.

Edit: *******SOLVED******** https://forums.creativecow.net/thread/24/1018679

NickHope wrote on 8/22/2017, 10:09 AM

@noisywan Sorry I missed this. I have encountered that problem before and it is indeed because some systems use a comma as the decimal separator. I therefore changed the delimiter to a semi-colon, which appears to work OK. My batch file also needed changing to match that.

Below is my current script and below that my current batch file. Note that this version 5.0 of the script also has these changes:

  • Namespace updated for VP14 and later.
  • Now skips events that have not been trimmed (i.e. Media length = event length)
  • End-compensation values have been tweaked for recent versions of FFMPEG, which encode the start slightly differently than they used to. This makes it slightly simpler. The comments might not have been updated to reflect this change.
/**
* 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
* often 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.

* The script compensates for these variations by detecting how near the end of the media file the edit takes place,
* and adjusting the offset accordingly if you are trimming only 0, 1 or 2 frames.
*
* 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
* Version 2.0, Oct. 02, 2015. Added line to automatically run the batch file from this script
* Version 3.0, Oct. 15, 2015. Now skips events that have not been trimmed (i.e. Media length = event length)
* Version 4.0, Oct. 26, 2015. Changed delimeter to comma for compatibility with spaces in folder names
* Version 5.0, May 09, 2016. Changed delimiter to semi-colon as some systems use comma as decimal separator
*
* Note, May 05, 2016. DJI OSMO UHD 30p clips (from Zenmuse X3 camera) do not have anomalies at the ends when
* decoded on the Vegas timeline, so they do not need to be trimmed with the Trim Captured Clips script. But 3 frames
* need to be added to the end of the file in this script to avoid loss of frames.
**/
 using System;
 using System.IO;
 using System.Windows.Forms;
 using ScriptPortal.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 OLD FFMPEG
    int endAdd = 0; // 0 is good for GH4 UHD 30p NEW FFMPEG
    
        // 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;
        }
    // Alternative to create the trim list name from the project name
        // 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 = Path.ChangeExtension(vegas.Project.FilePath, ".txt");
    string trimlistFilename = String.Format("E:\\trimlist.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));

        // 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;
        
        if (mediaLength > eventLength)
        {
            // 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
            // 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 < 3) // 3 is good for GH4 UHD 30p
            {
                lengthAdjuster = 0; // 0 is good for GH4 UHD 30p
            }
            else
            {
                lengthAdjuster = 1; // 1 is good for GH4 UHD 30p
            }

/* For OLD FFMPEG        
            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);
        }
    // Edit the path to your batch file. Remove the following line if you want to run the batch file manually
    System.Diagnostics.Process.Start("D:\\Documents\\batch-files-and-command-lines\\trim-mp4-to-mov-keep-audio.bat");
        MessageBox.Show("Processed: " + eventsProcessed + " events",
            "Complete", MessageBoxButtons.OK, MessageBoxIcon.Information);
    }
}

Matching batch file:

@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" -t "%%H" -itsoffset -0.04 -ss "%%G" -i "%%F" -t "%%H" -map 0:0 -map 1:1 -c copy "%%~dpnF!counter!-tr.MOV"
)
pause