How to convert double (seconds) to timecode?

NickHope wrote on 2/5/2016, 1:32 PM
I've been working on a script, the reason for which is discussed here on the video forum. Rather than explain it again, it's easiest to read the purpose in the top lines of the code below.

In this first version, the end of the event does not become correctly quantized to the end of the media's video stream. It's a frame or two too long or too short. I think this may be because of problems with the way I'm setting eventLengthTC in line 75 of the script. Instead, I want to try explicitly setting it by multiplying the number of frames in the video stream by the target frame rate, but I don't know how to convert that "double" value (e.g. 149.8501498501499 for a 5 second event) into a Vegas timecode. My failed effort is shown in lines 77 and 78.

Can anyone tell me how to convert double to timecode (or suggest another way of doing it)?

(p.s. Further development of the script would be most welcome, if anyone has the mood. After this change it still might not be frame accurate at the end, and it could also do with code to close the gaps on the timeline.)

//*   Quantize_Frames.cs
//*
//* Written by Nick Hope, based on Fix30pMedia.cs, written by John Rofrano
//* and modified by wwaag. Quantization code based on quantize_selected.js
//* written by Randall Campbell. Notes from those 2 programs shown below,
//* including copyright information.
//*
//* The script is useful for quantizing variable frame rate media such as that
//* recorded by some mobile phones, wherein the file is reported as variable
//* frame rate but does in fact exhibit video of a constant (but non-standard)
//* frame rate when placed on the Vegas timeline.
//*
//* To use, set the targetfps variable in the script to the desiered frame rate, then
//* select events on the timeline and run script.
//*
//* This script sets the project frame rate to your target frame rate, then
//* stretches or squeezes selected events so that their frames line up with the
//* timeline's frame intervals, and it quantizes the start and end of the event,
//* trimming the length of the audio stream to that of the video stream.
//*
//* This script will not work on video that has a truly variable frame rate.
//*
//* Because of bugs in Vegas decoders, files placed on the timeline may have repeat
//* frames at the start or end. You can remove those before after this script by
//* downloading the following script, written by jonask:
//* https://dl.dropboxusercontent.com/u/21489814/Trim Captured Clips v1.0.cs
//*
//* Feb 6, 2016 (NH) End of file is not correctly quantized to the end of the media's video stream
//*
//****************************************************************************
//* Program: Fix30pMedia.cs
//* Author: John Rofrano
//* Description: This script changes the playback rate of 30p media to 29.97
//* Created: July 25, 2010
//* Updated: Jul 28, 2010 (JR) Added Disable Resample
//* Aug 4, 2010 (JR) Changed audio to match video
//* Feb 3, 2016 (JR) Changed to only process selected events
//* Feb 4, 2016 (wwaag) Changed to process media of any frame rate
//*
//* Copyright: (c) 2010, 2016 Sundance Media Group / VASST. All Rights Reserved
//****************************************************************************
// Author: Randall Campbell, info@peachrock.com, www.peachrock.com
// © Copyright 2004-2005, Peach Rock Productions, LLC.
// You are free to use or modify this code as long as the copyright information is not removed.
// This software is provided AS IS, no warranty is expressed or implied
//****************************************************************************

using System;
using System.Collections;
using System.Windows.Forms;
using Sony.Vegas;

class EntryPoint
{
public void FromVegas(Vegas vegas)
{
double targetfps = 29.97002997002997; // for 60p change to 59.94005994005994
vegas.Project.Video.FrameRate = targetfps;
int counter = 0;
try
{
foreach (Track track in vegas.Project.Tracks)
{
if (!track.IsVideo()) continue;

foreach (VideoEvent videoEvent in track.Events)
{
// Only process selected events
if (!videoEvent.Selected) continue;
VideoStream videoStream = videoEvent.ActiveTake.MediaStream as VideoStream;

int videoStreamFrames = (int)videoEvent.ActiveTake.MediaStream.Length.FrameCount;

//THE LINE BELOW SORT OF WORKS BUT IS NOT FRAME ACCURATE. MAYBE IT'S USING THE FRAME RATE OF THE MEDIA, NOT THE PROJECT
Timecode eventLengthTC = Timecode.FromFrames(videoStreamFrames);
//THE 2 LINES BELOW MAY BE MORE ACCURATE THAN THE ABOVE ONE BUT I DON'T KNOW HOW TO CONVERT eventLength INTO TIMECODE. HELP!
// double eventLength = videoStreamFrames * targetfps;
// Timecode eventLengthTC = Timecode(eventLength);

double adjust = 1.0 /(videoStream.FrameRate / targetfps);
videoEvent.AdjustPlaybackRate(adjust, true);
if (videoStream.FrameRate > targetfps) videoEvent.UnderSampleRate = adjust;
videoEvent.ResampleMode = VideoResampleMode.Disable;

Timecode eventStart = Quantize(videoEvent.Start);
Timecode eventEnd = Quantize(videoEvent.Start + eventLengthTC);
videoEvent.AdjustStartLength(eventStart, eventEnd - eventStart, true);

counter++;

// check for audio in the same file and change it too
if (videoEvent.IsGrouped)
{
foreach (TrackEvent trackEvent in videoEvent.Group)
{
if (!trackEvent.IsAudio()) continue;

// see if they are from the same file
if (trackEvent.ActiveTake != null && trackEvent.ActiveTake.MediaPath.Equals(videoEvent.ActiveTake.MediaPath))
{
AudioEvent audioEvent = trackEvent as AudioEvent;
audioEvent.AdjustPlaybackRate(adjust, true);
trackEvent.AdjustStartLength(eventStart, eventEnd - eventStart, true);
}
}
}
}
}

// let the user know we are done
MessageBox.Show(String.Format("{0} events changed", counter), "Complete", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Unexpected Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
// Round the timecode value to the nearest frame
// Returns the quantized timecode value
Timecode Quantize ( Timecode timecode )
{
Timecode newTimecode = new Timecode();
newTimecode.Nanos = timecode.Nanos;

long originalNanos = newTimecode.Nanos;
newTimecode.FrameCount = newTimecode.FrameCount;
long nano1 = newTimecode.Nanos;
newTimecode.FrameCount = newTimecode.FrameCount + 1;
long nano2 = newTimecode.Nanos;
long nanoMiddle = Convert.ToInt64((nano2 - nano1) / 2);
if (originalNanos < nano1 + nanoMiddle)
newTimecode.FrameCount = newTimecode.FrameCount - 1;

return newTimecode;
}
}

Comments

VEGASNeal1 wrote on 2/18/2016, 11:49 AM
For the line you had commented out, I would think this should work:

Timecode eventLengthTC = Timecode.FromSeconds(eventLength);

Also, I think the Quatize function could be simplified to :

Timecode Quantize ( Timecode timecode )
{
Timecode tcPos1 = Timecode.FromFrames(timecode.FrameCount);
Timecode tcPos2 = Timecode.FromFrames(timecode.FrameCount + 1);

long nanoMid = tcPos1.Nanos + Convert.ToInt64((tcPos2.Nanos - tcPos1.Nanos) / 2);

return (timecode.Nanos < nanoMid ) ? tcPos1 : tcPos2;
}

But maybe I'm missing something, I'm a bit new to Vegas scripting.

NickHope wrote on 3/11/2016, 5:02 AM
Thank you gret127.

Your suggested line and your simplified quantize function both work fine.

Actually the code I needed for the commented out lines 78 and 79 is this:

double eventLength = videoStreamFrames / targetfps;
Timecode eventLengthTC = Timecode.FromSeconds(eventLength);

But the result is the same as the single line 76 (apart from the +1) so I have removed them.

Duane N. wrote on 3/11/2016, 1:22 PM
You need the "new" keyword to instantiate the class:
Timecode eventLengthTC = new Timecode(eventLength);

You could also use the static FromMilliseconds method:
Timecode eventLengthTC = Timecode.FromMilliseconds(msValue)

-Duane

BTW, I saw your name and sounded familiar, then I realized you do the BubbleVision stuff. Awesome videos. I've just started diving and trying to do my own videos.. they have a ways to go.
Duane N. wrote on 3/11/2016, 1:24 PM
My bad, thought I was looking at the last message in the thread, just saw this was already answered.
Duane N. wrote on 3/11/2016, 2:49 PM
Ok, I spent a little time looking at this.. Here is what I came up with. It works for me, putting my 24p video into a 30p project. (I don't have 30p source handy). It shortens the video and so far ends on the last frame.

comment out:

// int videoStreamFrames = (int)videoEvent.ActiveTake.MediaStream.Length.FrameCount;

//THE LINE BELOW SORT OF WORKS BUT IS NOT FRAME ACCURATE. MAYBE IT'S USING THE FRAME RATE OF THE MEDIA, NOT THE PROJECT
// Timecode eventLengthTC = Timecode.FromFrames(videoStreamFrames);
//THE 2 LINES BELOW MAY BE MORE ACCURATE THAN THE ABOVE ONE BUT I DON'T KNOW HOW TO CONVERT eventLength INTO TIMECODE. HELP!
// double eventLength = videoStreamFrames * targetfps;

Add:

double videoStreamLengthMs = (int)videoEvent.ActiveTake.MediaStream.Length.ToMilliseconds();
int newVideoLengthMs = (int)(videoStreamLengthMs * (videoStream.FrameRate / targetfps));
Timecode eventLengthTC = Timecode.FromMilliseconds(newVideoLengthMs);


basically, we take the length of the video (based on the current project framerate). Then figure out what the new length should be based on the ratio of the project and media framerates. This could probably be simplified and cleaned up more.. but I didn't want to mess with the rest of the code before we knew if it was working properly.
NickHope wrote on 3/12/2016, 6:27 AM
Hi Duane, Glad you like my videos. Looking forward to seeing yours!

The code you supplied works great, but the end wasn't quantized so I applied the Quantize function to it. That enabled me to remove the Quantize function from other lines of the script and simplify it.

I've added code to close the gaps between events. I have it working with line 70 setting the initial eventStart to 0. But I want the start of the first event to stay where it is. Unfortunately I don't know how to get the timecode of the first video event. See line 72 for my failed attempt. Does anyone know how to do that? I'm sure it's simple.

I also plan to add in code to trim repeat frames from the beginning and end of each clip (hence lines 61-62).

//*   Quantize Frames by Time Stretch.cs
//*
//* Written by Nick Hope, based on Fix30pMedia.cs, written by John Rofrano
//* and modified by wwaag. Quantization code based on quantize_selected.js
//* written by Randall Campbell. Notes from those 2 programs shown below,
//* including copyright information.
//*
//* The script is useful for quantizing variable frame rate media such as that
//* recorded by some mobile phones, wherein the file is reported as variable
//* frame rate but does in fact exhibit video of a constant (but non-standard)
//* frame rate when placed on the Vegas timeline.
//*
//* To use, set the targetfps variable in the script to the desiered frame rate, then
//* select events on the timeline and run script.
//*
//* This script sets the project frame rate to your target frame rate, then
//* stretches or squeezes selected events so that their frames line up with the
//* timeline's frame intervals, and it quantizes the start and end of the event,
//* trimming the length of the audio stream to that of the video stream.
//*
//* This script will not work on video that has a truly variable frame rate.
//*
//* Because of bugs in Vegas decoders, files placed on the timeline may have repeat
//* frames at the start or end. You can remove those before after this script by
//* downloading the following script, written by jonask:
//* https://dl.dropboxusercontent.com/u/21489814/Trim Captured Clips v1.0.cs
//*
//* Feb 6, 2016 (NH) End of event is not correctly quantized to the end of the media's video stream
//* Mar 11, 2016 (NH) Quantize function simplified with code from forum member gret127
//* Mar 12, 2016 (NH) Added code from Duane N. to make the stretched event the correct length
//*
//****************************************************************************
//* Program: Fix30pMedia.cs
//* Author: John Rofrano
//* Description: This script changes the playback rate of 30p media to 29.97
//* Created: July 25, 2010
//* Updated: Jul 28, 2010 (JR) Added Disable Resample
//* Aug 4, 2010 (JR) Changed audio to match video
//* Feb 3, 2016 (JR) Changed to only process selected events
//* Feb 4, 2016 (wwaag) Changed to process media of any frame rate
//*
//* Copyright: (c) 2010, 2016 Sundance Media Group / VASST. All Rights Reserved
//****************************************************************************
// Author: Randall Campbell, info@peachrock.com, www.peachrock.com
// © Copyright 2004-2005, Peach Rock Productions, LLC.
// You are free to use or modify this code as long as the copyright information is not removed.
// This software is provided AS IS, no warranty is expressed or implied
//****************************************************************************

using System;
using System.Collections;
using System.Windows.Forms;
using Sony.Vegas;

class EntryPoint
{
public void FromVegas(Vegas vegas)
{
double targetfps = 29.97002997002997; // for 60p change to 59.94005994005994
vegas.Project.Video.FrameRate = targetfps;
// Timecode clipStartTrim = Timecode.FromFrames(1); // Enter number of frames to delete from start of clips
// Timecode clipEndTrim = Timecode.FromFrames(0); // Enter number of frames to delete from end of clips
int counter = 0;
try
{
foreach (Track track in vegas.Project.Tracks)
{
if (!track.IsVideo()) continue;

// Timecode eventStart = new Timecode(0); // THIS WORKS BUT I WANT TO LEAVE THE START OF THE 1ST EVENT WHERE IT IS
//HERE I NEED TO GET THE TIMECODE OF THE 1ST VIDEO EVENT BUT I DON'T KNOW HOW. FOLLOWING LINE DOESN'T WORK. PLEASE HELP!!!
Timecode eventStart = Quantize(VideoEvent.Start);

foreach (VideoEvent videoEvent in track.Events)
{
// Only process selected events
if (!videoEvent.Selected) continue;
VideoStream videoStream = videoEvent.ActiveTake.MediaStream as VideoStream;

int videoStreamFrames = (int)videoEvent.ActiveTake.MediaStream.Length.FrameCount;

double videoStreamLengthMs = (int)videoEvent.ActiveTake.MediaStream.Length.ToMilliseconds();
int newVideoLengthMs = (int)(videoStreamLengthMs * (videoStream.FrameRate / targetfps));
Timecode eventLengthTC = Quantize(Timecode.FromMilliseconds(newVideoLengthMs));

double adjust = 1.0 /(videoStream.FrameRate / targetfps);
videoEvent.AdjustPlaybackRate(adjust, true);
if (videoStream.FrameRate > targetfps) videoEvent.UnderSampleRate = adjust;
videoEvent.ResampleMode = VideoResampleMode.Disable;

videoEvent.AdjustStartLength(eventStart, eventLengthTC, false);

// check for audio in the same file and change it too
if (videoEvent.IsGrouped)
{
foreach (TrackEvent trackEvent in videoEvent.Group)
{
if (!trackEvent.IsAudio()) continue;

// see if they are from the same file
if (trackEvent.ActiveTake != null && trackEvent.ActiveTake.MediaPath.Equals(videoEvent.ActiveTake.MediaPath))
{
AudioEvent audioEvent = trackEvent as AudioEvent;
audioEvent.AdjustPlaybackRate(adjust, true);
trackEvent.AdjustStartLength(eventStart, eventLengthTC, false);
}
}
}
eventStart = eventStart + eventLengthTC;
counter++;
}
}

// let the user know we are done
MessageBox.Show(String.Format("{0} events changed", counter), "Complete", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Unexpected Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
// Round the timecode value to the nearest frame
// Returns the quantized timecode value
Timecode Quantize ( Timecode timecode )
{
Timecode tcPos1 = Timecode.FromFrames(timecode.FrameCount);
Timecode tcPos2 = Timecode.FromFrames(timecode.FrameCount + 1);

long nanoMid = tcPos1.Nanos + Convert.ToInt64((tcPos2.Nanos - tcPos1.Nanos) / 2);

return (timecode.Nanos < nanoMid ) ? tcPos1 : tcPos2;
}
}
Duane N. wrote on 3/12/2016, 1:41 PM
replace your line 72 with this:


//Get start time from first selected video event
Timecode eventStart = new Timecode(0);
foreach (VideoEvent videoEvent in track.Events)
{
if (!videoEvent.Selected) continue;
eventStart = Quantize(videoEvent.Start);
break;
}


basically we loop through the track events until we find the first selected one and set the eventStart to that events start time. You were pretty close, just didn't actually have any tracks in scope. I take it you are not a coder, but you look like you are doing pretty good, considering. :)

Here is the full update, I did some refactoring to make it a bit cleaner and fixed a couple spots that could be problems with losing some precision.


//* Quantize Frames by Time Stretch.cs
//*
//* Written by Nick Hope, based on Fix30pMedia.cs, written by John Rofrano
//* and modified by wwaag. Quantization code based on quantize_selected.js
//* written by Randall Campbell. Notes from those 2 programs shown below,
//* including copyright information.
//*
//* The script is useful for quantizing variable frame rate media such as that
//* recorded by some mobile phones, wherein the file is reported as variable
//* frame rate but does in fact exhibit video of a constant (but non-standard)
//* frame rate when placed on the Vegas timeline.
//*
//* To use, set the targetfps variable in the script to the desiered frame rate, then
//* select events on the timeline and run script.
//*
//* This script sets the project frame rate to your target frame rate, then
//* stretches or squeezes selected events so that their frames line up with the
//* timeline's frame intervals, and it quantizes the start and end of the event,
//* trimming the length of the audio stream to that of the video stream.
//*
//* This script will not work on video that has a truly variable frame rate.
//*
//* Because of bugs in Vegas decoders, files placed on the timeline may have repeat
//* frames at the start or end. You can remove those before after this script by
//* downloading the following script, written by jonask:
//* https://dl.dropboxusercontent.com/u/21489814/Trim Captured Clips v1.0.cs
//*
//* Wtih modifications from Duane Newman (duanenewman.net)
//*
//****************************************************************************
//* Program: Fix30pMedia.cs
//* Author: John Rofrano
//* Description: This script changes the playback rate of 30p media to 29.97
//* Created: July 25, 2010
//* Updated: Jul 28, 2010 (JR) Added Disable Resample
//* Aug 4, 2010 (JR) Changed audio to match video
//* Feb 3, 2016 (JR) Changed to only process selected events
//* Feb 4, 2016 (wwaag) Changed to process media of any frame rate
//* Feb 6, 2016 (NH) End of event is not correctly quantized to the end of the media's video stream
//* Mar 11, 2016 (NH) Quantize function simplified with code from forum member gret127
//* Mar 12, 2016 (NH) Added code from Duane N. to make the stretched event the correct length
//* Mar 13, 2016 (DN) Added code to keep start time of first selected video event, refactored code
//* for readability, removed some double -> int casting that could lose precision
//*
//* Copyright: (c) 2010, 2016 Sundance Media Group / VASST. All Rights Reserved
//****************************************************************************
// Author: Randall Campbell, info@peachrock.com, www.peachrock.com
// © Copyright 2004-2005, Peach Rock Productions, LLC.
// You are free to use or modify this code as long as the copyright information is not removed.
// This software is provided AS IS, no warranty is expressed or implied
//****************************************************************************

using System;
using System.Collections;
using System.Windows.Forms;
using Sony.Vegas;

class EntryPoint
{

private const double FrameRate30p = 29.97002997002997;
private const double FrameRate60p = 59.94005994005994;

public void FromVegas(Vegas vegas)
{
double targetfps = FrameRate30p;
vegas.Project.Video.FrameRate = targetfps;

int counter = 0;
try
{
foreach (Track track in vegas.Project.Tracks)
{
if (!track.IsVideo()) continue;

//Get start time from first selected video event
Timecode eventStart = GetFirstEventStartTime(track);

foreach (VideoEvent videoEvent in track.Events)
{
// // Only process selected events
if (!videoEvent.Selected) continue;

double playbackRate = GetAdjustedPlaybackRate(videoEvent, targetfps);

Timecode adjustedEventLength = GetAdjustedEventLength(videoEvent, playbackRate);

AdjustTrackPlaybackRate(videoEvent, eventStart, adjustedEventLength, playbackRate);

// check for audio in the same file and change it too
if (videoEvent.IsGrouped)
{
foreach (TrackEvent trackEvent in videoEvent.Group)
{
if (!trackEvent.IsAudio()) continue;

// see if they are from the same file
if (trackEvent.ActiveTake != null && trackEvent.ActiveTake.MediaPath.Equals(videoEvent.ActiveTake.MediaPath))
{
AdjustTrackPlaybackRate(trackEvent, eventStart, adjustedEventLength, playbackRate);
}
}
}
eventStart = eventStart + adjustedEventLength;
counter++;
}
}

// let the user know we are done
MessageBox.Show(String.Format("{0} events changed", counter), "Complete", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Unexpected Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}

// Round the timecode value to the nearest frame
// Returns the quantized timecode value
Timecode Quantize(Timecode timecode)
{
Timecode tcPos1 = Timecode.FromFrames(timecode.FrameCount);
Timecode tcPos2 = Timecode.FromFrames(timecode.FrameCount + 1);

long nanoMid = tcPos1.Nanos + Convert.ToInt64((tcPos2.Nanos - tcPos1.Nanos) / 2);

return (timecode.Nanos < nanoMid) ? tcPos1 : tcPos2;
}

Timecode GetFirstEventStartTime(Track track)
{
//Get start time from first selected video event
foreach (VideoEvent videoEvent in track.Events)
{
if (!videoEvent.Selected) continue;
return Quantize(videoEvent.Start);
}

return new Timecode(0);
}

double GetAdjustedPlaybackRate(VideoEvent videoEvent, double targetfps)
{
double videoStreamFrameRate = (videoEvent.ActiveTake.MediaStream as VideoStream).FrameRate;
return 1.0 / (videoStreamFrameRate / targetfps);
}

Timecode GetAdjustedEventLength(VideoEvent videoEvent, double playbackRate)
{
double videoStreamLengthMs = videoEvent.ActiveTake.MediaStream.Length.ToMilliseconds();
double newVideoLengthMs = videoStreamLengthMs / playbackRate;
return Quantize(Timecode.FromMilliseconds(newVideoLengthMs));
}

void AdjustTrackPlaybackRate(TrackEvent trackEvent, Timecode eventStart, Timecode eventLength, double playbackRate)
{
trackEvent.AdjustPlaybackRate(playbackRate, true);

if (trackEvent.IsVideo())
{
VideoEvent videoEvent = trackEvent as VideoEvent;
if (playbackRate < 1.0) videoEvent.UnderSampleRate = playbackRate;
videoEvent.ResampleMode = VideoResampleMode.Disable;
}

trackEvent.AdjustStartLength(eventStart, eventLength, false);
}

}


You can checkout some of my videos here: [LINK=https://www.youtube.com/dnewman31]

We just took a week long diving trip (been slowly working through the video) and I learned a lot of what to do different next time.. ;)
NickHope wrote on 3/12/2016, 10:44 PM
Thanks very much Duane. Great job! Seems to work fine.

I may still add in the code to trim a user-selectable number frames at the start or end of each clip after stretching. Particularly at the start as I'm getting a repeat frame or two there.

Definitely NOT a coder. Did a bit of BASIC about 35 years ago, a bit of AutoLISP about 25 years ago, some html for my own website, and since then I've just cobbled together bits of other people's scripts as I need them. I should try and learn a bit more of the fundamentals before doing any more. At the moment the Vegas Pro Scripting API Summary is complete double Dutch to me.

Keep up the underwater videos! What camera and housing are you using?
Duane N. wrote on 3/13/2016, 9:40 AM
Most of my video is from my GoPro HERO 4 black (shot in 4k) right now.

I'm into photography as well (expensive combining scuba and photography hobbies!), so along with the GoPro I'm diving with a Canon 7d Mark II with a Tokina 10-17 fisheye in an Ikelite housing.

Having a hard time deciding which one I want to do more video with.. I'm sure as I get better at both diving and UW photo/video I will start using my Canon more to get macro and other shots. The GoPro is nice though, because it is so small and I can just leaving it running (and at 4k I can crop/zoom a lot without losing quality at HD output) and when something happens (like a barracuda swimming right by me) I can just swing around and I'm already shooting.. It does take a LONG time to process the video though.

What camera and housing are you using? Always interested in hearing about other divers gear.
NickHope wrote on 3/14/2016, 3:16 AM
Have sent you an email through the forum Duane.