frame count to timecode

I know I’m missing something here, but anyway…

After exporting an XML file from Final Cut Pro, the ins and outs are listed as frames as opposed to timecode. All I need is to convert these frames to timecode.

For instance, an in of 30 and out of 102 would yield an in of 00:00:01:00 and an out of 00:00:03:12.

Again, I apologize for what I’m sure is a very basic question.

Maybe Final Cut Pro has something built-in to do it, but you can compute it “manually” easily enough.

You will want to use mod and div to successively compute the frame number, second number, minute number and hour number. Then you will need to format each value so that it has at least two digits (prefixed with extra zeros if necessary).

You could do it all without loops if you are willing to have duplicate code and some hard-coded values. The version below is an attempt to reduce duplicate code and remain flexible and reusable.

to atLeast_charsOf_prefixedWith_(minChars, obj, fill)
	set str to obj as string
	set len to length of str
	if len is greater than or equal to minChars then ¬
		return str
	set fillNeeded to minChars - len
	if length of fill is 0 then error "The fill value must be a non-empty string!"
	repeat until length of fill is greater than or equal to fillNeeded
		set fill to fill & fill
	end repeat
	(text -fillNeeded through -1 of fill) & str
end atLeast_charsOf_prefixedWith_
to timeCodeFromFrameCount(frameCount, framesPerSecond)
	set timeCodeStr to ""
	set residual to frameCount as number
	set separator to ""
	repeat with factor in {framesPerSecond, 60, 60, missing value} -- frames/sec, secs/m, mins/hr, hrs
		if contents of factor is not missing value then
			set {n, residual} to ¬
				{residual mod factor, residual div factor}
		else
			set {n, residual} to {residual, 0}
		end if
		set timeCodeStr to atLeast_charsOf_prefixedWith_(2, n, "0") & separator & timeCodeStr
		set separator to ":"
	end repeat
	return timeCodeStr
end timeCodeFromFrameCount

timeCodeFromFrameCount("13284111", 30) --> "123:00:03:21"

Hi,

try this


property FrameRate : 30

set theTime to 102
display dialog "TCR " & (framesToSMTPE from theTime by FrameRate)

on framesToSMTPE from ti by rate
	set frHr to hours * rate
	set frMn to minutes * rate
	tell (ti) to set {hr, mn, sc, fr} to {it div frHr, it mod frHr div frMn, it mod frHr mod frMn div rate, it mod frHr mod frMn mod rate div 1}
	return addZero(hr) & ":" & addZero(mn) & ":" & addZero(sc) & ":" & addZero(fr)
end framesToSMTPE

on addZero(v)
	return text -2 thru -1 of ("0" & v)
end addZero


property FrameRate : 30

set theTime to "102"
display dialog "TCR " & (framesToSMTPE from theTime by FrameRate)

on framesToSMTPE from ti by rate
	tell ti div rate to tell (100000000 + it div hours * 1000000 + it mod hours div minutes * 10000 + it mod minutes * 100 + ti mod rate) as text to return text 2 thru 3 & ":" & text 4 thru 5 & ":" & text 6 thru 7 & ":" & text 8 thru 9
end framesToSMTPE

Sorry. Couldn’t resist it. :wink: Like Stefan’s code, it only gives the correct results with periods of under 100 hours.

great one-liner :slight_smile:

Which movie takes more than 99 hours? :wink:

Brilliant! Thanks to all. I knew it would have something to do with div, I just couldn’t wrap my head around it.

One final request, if its not pushing it. After looking a little closer, I need it to end up in hh:mm:ss,milliseconds format. Here’s what I’m actually doing: http://en.wikipedia.org/wiki/SubRip This is to create captions for YouTube videos.

If it can’t be done, its not a big deal, I can just use the frame number for the milliseconds and it will be close enough for captioning.

I’m going to take a stab at it, but figured I’d throw it out there. Thoughts?

Thanks again.

FYI, to fake it, I tweaked the last bit to read “text 6 thru 7 & “,” & “0” & text 8 thru 9” to at least get it in to the millisecond format.

In order for me to actually convert the remaining frames, I need to read up on div and mod.

Thanks again!

Each frame is 1000/(frame rate) milliseconds, so you have to multiply the spare frame figure by that:


property FrameRate : 30

set theTime to "102"
display dialog "TCR " & (framesToSMTPE from theTime by FrameRate)

on framesToSMTPE from ti by rate
	tell ti div rate to tell (1000000 + it div hours * 10000 + it mod hours div minutes * 100 + it mod minutes) as text to return text 2 thru 3 & ":" & text 4 thru 5 & ":" & text 6 thru 7 & "," & text 2 thru 4 of ((1000 + ti mod rate * 1000 / rate) as text)
end framesToSMTPE

This truncates the millisecond figure rather than rounding it to nearest. Rounding’s possible, but an extra calulation. It depends how finicky you want to be.

Perfect! My hack was close enough, but this gets even more exact; rounding is certainly fine.

FWIW, here is my working script. I say working, not complete, as there are still some things I’d like to do. It requires an XML from a Final Cut sequence. That sequence must contain sequence markers that have both an in and out and a comment. But of course, that is the whole point: line up transcipts of a video within the sequence, using markers to determine when the captions will turn on and off. This will then generate an XML file for Flash captions and an SRT file for YouTube captions.

It also needs the XMLLib addition from http://www.satimage.fr/software/en/downloads/downloads_companion_osaxen.html

I installed this in my User/Library/ScriptingAdditions folder as opposed to the system library.

I realize this may not be the most efficient or prettiest, but for now it does what I need it to do. That said, I’d love any comments for improvement or things that I’ve done glaringly wrong.

Thanks again for the help!

Mike



property FrameRate : 30


set theXML to XMLOpen (choose file with prompt "Select the XML file which contains your caption markers" without invisibles)

set the_root to XMLRoot theXML
set the_child to XMLChild the_root index 1

set theTitle to XMLChild the_child index 3
set youtubeTitle to (XMLGetText theTitle) & " - YouTube Captions"
set flashTitle to (XMLGetText theTitle) & " - Flash Captions"

set allMarkers to XMLXPath the_child with "marker/name"
set theMarkers to XMLGetText (allMarkers)

set startMarker to 11
set theIndex to startMarker
set theList to {}

set desktop_ to path to desktop as Unicode text


--create an .xml file on the desktop and add the xml header in the proper format for Flash
set theHeader to setHeader()
write_to_Flashfile(theHeader, (desktop_ & flashTitle & ".xml"), true)


repeat with my_item in theMarkers
	try
		set the_child_2 to XMLChild the_child index theIndex
		
		
		set theMarker to XMLGetText (XMLChild the_child_2 index 1)
		set theComment to XMLGetText (XMLChild the_child_2 index 2)
		set inFrame to XMLGetText (XMLChild the_child_2 index 3)
		set outFrame to XMLGetText (XMLChild the_child_2 index 4)
		set theDuration to ((inFrame) - (outFrame))
		
		--convert times from frames to hh:mm:ss,000
		set inYouTube to (framesToYouTubeTime from inFrame by FrameRate)
		set outYouTube to (framesToYouTubeTime from outFrame by FrameRate)
		
		--convert times from frames to hh:mm:ss:ff
		set inFlash to (framesToFlashTime from inFrame by FrameRate)
		set outFlash to (framesToFlashTime from outFrame by FrameRate)
		
		
		
		--create list of comment index, comment, in point, out point, and duration
		set tempList to {theMarker, theComment, inYouTube, outYouTube, theDuration}
		
		--write the list to an .srt file on the desktop in the proper format for YouTube
		write_to_file(return & theIndex - 10 & return & item 3 of tempList & " --> " & item 4 of tempList & return & item 2 of tempList & return, (desktop_ & youtubeTitle & ".srt"), true)
		
		
		
		--write the list to an .xml file on the desktop in the proper format for Flash
		write_to_Flashfile(return & "<p begin=\"" & inFlash & "\" end=\"" & outFlash & "\" style=\"1\">" & theComment & "</p>", (desktop_ & flashTitle & ".xml"), true)
		
		
		set theIndex to theIndex + 1
		
	end try
end repeat


--add the xml footer in the proper format for Flash
set theFooter to setFooter()
write_to_Flashfile(theFooter, (desktop_ & flashTitle & ".xml"), true)





-- Subroutine to write to UTF-8 file
to write_to_Flashfile(this_data, target_file, append_data)
	try
		set the target_file to the target_file as text
		set the open_target_file to open for access file target_file with write permission
		if append_data is false then set eof of the open_target_file to 0
		write this_data to the open_target_file as «class utf8» starting at eof
		close access the open_target_file
		return true
	on error
		try
			close access file target_file
		end try
		return false
	end try
end write_to_Flashfile




-- Subroutine to write to text file
to write_to_file(this_data, target_file, append_data)
	try
		set the target_file to the target_file as text
		set the open_target_file to open for access file target_file with write permission
		if append_data is false then set eof of the open_target_file to 0
		write this_data to the open_target_file as «class utf8» starting at eof
		close access the open_target_file
		return true
	on error
		try
			close access file target_file
		end try
		return false
	end try
end write_to_file








--Subroutine to convert frames to YouTube timecode in format hh:mm:ss,000
on framesToYouTubeTime from ti by rate
	tell ti div rate to tell (1000000 + it div hours * 10000 + it mod hours div minutes * 100 + it mod minutes) as text to return text 2 thru 3 & ":" & text 4 thru 5 & ":" & text 6 thru 7 & "," & text 2 thru 4 of ((1000 + ti mod rate * 1000 / rate) as text)
end framesToYouTubeTime


--Subroutine to convert frames to timecode in format hh:mm:ss:ff
on framesToFlashTime from ti by rate
	tell ti div rate to tell (100000000 + it div hours * 1000000 + it mod hours div minutes * 10000 + it mod minutes * 100 + ti mod rate) as text to return text 2 thru 3 & ":" & text 4 thru 5 & ":" & text 6 thru 7 & ":" & text 8 thru 9
end framesToFlashTime




--Subroutine to write XML header for Flash captions
on setHeader()
	
	set theHeader to "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" & return
	set theHeader to theHeader & "<tt xml:lang=\"en\" xmlns=\"http://www.w3.org/2006/04/ttaf1\"  xmlns:tts=\"http://www.w3.org/2006/04/ttaf1#styling\">" & return & return
	set theHeader to theHeader & "<head>" & return
	set theHeader to theHeader & "<styling>" & return
	set theHeader to theHeader & "<style id=\"1\"" & return
	set theHeader to theHeader & tab & "tts:fontFamily=\"_sans\"" & return
	set theHeader to theHeader & tab & "tts:color=\"#FFFFFF\"" & return
	set theHeader to theHeader & tab & "tts:fontWeight=\"bold\"" & return
	set theHeader to theHeader & tab & "tts:fontSize=\"16\"" & return
	set theHeader to theHeader & tab & "tts:backgroundColor=\"transparent\" />" & return
	set theHeader to theHeader & "</styling>" & return
	set theHeader to theHeader & "</head>" & return & return
	set theHeader to theHeader & "<body>" & return
	set theHeader to theHeader & "<div xml:lang=\"en\">"
	
end setHeader




--Subroutine to write XML header for Flash captions
on setFooter()
	
	set theFooter to "</div>" & return
	set theFooter to theFooter & "</body>" & return
	set theFooter to theFooter & return & "</tt>"
	
end setFooter