Copy link to clipboard
Copied
I have a script that allows users to enter timecode as a string, and I wanted it to perform the same as the Timecode inputs in the rest of the program: if they type "1..." I want it to register as 1:00:00:00 without them having to type a correctly formatted string.
This. Took. Ages. I descended deep into the regex underworld, but I'm happy to say that I managed to placate the JS gods with my mad skillz on the lyre and returned victorious. So for any other young traveller preparing to take the journey, here's what I worked out. If you have suggestions, please, let me know.
function parseTimeString(theString, comp) {
comp = app.project.activeItem; //need an active comp in order to get the frameDuration
if (comp) {
theString = "" + theString;
//allows user to lazily enter timecode, eg. to enter 1 minute you type 1.. rather than 0:1:0:0
//this took ages to work out..
var hrsStr = theString.match(/(\d+)\D\d*\D\d*\D\d*$/); //matches "0:1:2:3" and "0..." and returns '0'
var minStr = theString.match(/(\d+)\D\d*\D\d*$/); //matches "0:1:2:3" , "1,2.3" and "1.." and returns 1
var secStr = theString.match(/(\d+)\D\d*$/); //and so on..
var frmStr = theString.match(/(\d+)$/);
//convert the strings to time values
var hrs = hrsStr
? parseInt(hrsStr)
: 0;
var min = minStr
? parseInt(minStr)
: 0;
var sec = secStr
? parseInt(secStr)
: 0;
var frm = frmStr
? parseInt(frmStr)
: 0;
//return the result as time, measured in seconds
return 3600 * hrs + 60 * min + sec + comp.frameDuration * frm;
}
return false;
}
Copy link to clipboard
Copied
One tip, for base 10 integers, always use parseInt(x, 10) or you will get some surprises, eg: parseInt('019")
And by the way, in the AE, non integer entries are allowed in the comp duration dialog!!
Also make the function return, in some situations, false instead of a number can be a source of mistake imo.
Another possibility without regexp:
function parseTimeCode(str, comp){
str = String(str);
if (!(comp instanceof CompItem)) throw "comp is not a comp";
var seconds = 0;
var splitStr = str.split(":").reverse();
var n, N=splitStr.length;
var x;
if (N>4) throw "Too many ':' !!! : Only hh:mm:ss:ff supported";
for (n=0; n<N; n++){
x = +splitStr
; if (isNaN(x)) throw "Invalid number input : " + splitStr
; splitStr
= x; };
seconds += splitStr[0]* comp.frameDuration;
if (N>1) seconds += splitStr[1];
if (N>2) seconds += splitStr[2]*60;
if (N>3) seconds += splitStr[3]*3600;
return seconds;
};
try{myTime = parseTimeCode("10.5 : 019", app.project.activeItem)}catch(e){/*alert(e);*/};
Xavier
Copy link to clipboard
Copied
Thanks for the tip about the radix parameter.
I'm going for a more permissive user input, so it allows the user to use any sort of separator, hence the regex (but it's mostly because I don't really know much about JS string methods).
I think you'll find the timecode inputs in AE work in the same way, that is any of the characters : , . ; and interestingly + get interpreted as a separator, so if you type in "10.5 : 019" it will be interpreted as 0:10:05:19. It means you can use shorthand like "1.." to mean one minute. I'm aiming to mimic that.
So I could use your algorithm but with this as the split function:
var splitStr = str.split(/\D/).reverse();
That mimics the native AE behaviour, but it actually turns out to be more permissive - you could use any non-numeric character as a separator. Having \D+ also means spaces I could make it aware of arithmetic expressions, like sliders are, but that's probably function creep.
Reversing the array is a great way to deal with the possibility of variable length input. I was originally checking the result of parseInt() on each term to see if it returned a number and that's when I learned that
NaN === NaN // = false
It's been a learning curve.
Copy link to clipboard
Copied
Turns out
var splitStr = str.split(/\D/).reverse();
doesn't cope with the example 10.5 : 019 with the colon with spaces on either side, because it acts like three characters. So it's a bit too permissive. I tried using
var splitStr = str.split(/\s*\D\s*/).reverse();
to cope with any additional whitespace.
But then I realise that while string.split() works with your example it fails with the more common example 10.. meaning 10 minutes. String.split() doesn't return an empty match after the last separator so "10..".split(".") returns ['10', ''], not ['10','','']. So it looks like it's back to the regex.
I am sooo overthinking this fairly trivial bit of UI.
Copy link to clipboard
Copied
I'd stick to a colon (:) to separate fields. It's very common. Too permissive = too much ambiguity imo.
In particular, allowing the "." to separate fields is super ambiguous.
By the way, thanks for quoting that "10.5" is interpreted as "10:05" in the comp dialog. I was abused by the fact that the created comp was longer that 10 seconds.... Luckily, i had not tried that before your post!!!!
Xavier
Copy link to clipboard
Copied
Well I'm trying to replicate the native behaviour. The current regex works the same as the AE timecode inputs, I was having conniptions trying to deal with a string like "12, 3 4" but then I tried it in a native timecode input and it failed there too, or more to the point it returned "0:00:12:03" without the 4. So a non-whitespace character followed by whitespace is valid, but whitespace on its own isn't. I could make my script able to deal with "12, 3 4", but it might be better to match the AE behaviour exactly.
Copy link to clipboard
Copied
Oh I am SO stupid! All I needed to replicate the native behaviour, was to use the native built-in function:
currentFormatToTime() global function
currentFormatToTime(formattedTime, fps, isDuration)
Description
Converts a formatted string for a frame time value to a number of seconds, given a specified frame rate. For
example, if the formatted frame time value is 0:00:12 (the exact string format is determined by a project
setting), and the frame rate is 24 fps, the time would be 0.5 seconds (12/24). If the frame rate is 30 fps, the time
would be 0.4 seconds (12/30).
If the time is a duration, the frames are counted from 0. Otherwise, the frames are counted from the project’s
starting frame
Which has exactly the right behaviour when dealing with variably formated strings
currentFormatToTime("1..", 25)
Result: 60
currentFormatToTime("1, 2.3:4", 25)
Result: 3723.16
and timeToCurrentFormat() which does the reverse:
timeToCurrentFormat(60,25)
Result: 00:01:00:00
AAAAAGH! Next time I'll RTFM!
Get ready! An upgraded Adobe Community experience is coming in January.
Learn more