Calculating timecode from Hookmark-link to QuickTime video

I am using Hookmark (https://hookproductivity.com) to create notes in Tinderbox that are linked to a specific frame in a video — technically not frame accurate, but that’s ok for my purposes. This works beautifully in principle. However, the resulting URL (hook://file…) in the Tinderbox note refers to the position in the video in seconds and hundredths of a second (e.g. 573.06), attaching this at the end of the URL in the format #t=seconds.hundredths of a second, e.g. “#t=573.06”.
Here is the challenge:
I would like to convert the seconds into a conventional timecode and copy that into an attribute $TimeCode. The desired format is hh:mm:ss. For the example above, a manual calculation leads to the timecode 00:09:33 (no need to calculate beyond the full second).
I’m just getting started with RegEx, which I presume necessary to isolate the seconds at the end of the URL, so I’d have to extract whatever follows “t=” in a first step.

Could anyone help me accomplish this and break down the steps so that a non-coder with limited mathematical insight can follow?

Thanks all,

Tilmann

1 Like

A Tinderbox interval represents a time interval, such as “3 minutes 27 seconds.” As it turns out, if you assign a number to an interval, Tinderbox assumes that the number represents the number of seconds the interval is to represent.

So, your problem breaks down into two steps.

  1. Extract the timecode from the Hook string, storing it as a number
  2. Set a Tinderbox interval attribute, such as $MyInterval, to that value.

The default display of intervals is probably what you want, but the .format() operator lets you customize it in great detail.

I see Mark Anderson is also typing, and will defer to him on step 1!

2 Likes

So the Hook app is now Hookmark (as of 23 Oct 22). I just checked and as yet the Hook.app itself is not rebranded, just the website.

In my example this is the value of $MyNumber, but in reality you’ll set vNum initially to the value extracted via regex from your URL. $TimeCode is a string, but you might want to use an Interval type (I’ve not tested the latter)

var:number vNum=round($MyNumber);
var:string vTimecode="";
vTimecode = floor(vNum/3600).format(0,2,0)+":";
vNum = mod($MyNumber,3600);
vTimecode += floor(vNum/60).format(0,2,0)+":";
vNum = mod($MyNumber,60);
vTimecode += vNum.format(0,2,0);
$TimeCode = vTimecode+"\n"+vNum;

We take the timecode:

  • we use round() to round the source figure to the nearest whole second (our timecode’s minimum granularity)
  • divide by hours (3600 seconds), use floor() to round down to the nearest whole integer and format it as a two-digit zero left-padded hours figure, and add a colon on the end
    • (ASSUMPTION: max hours value is 99)
  • we use a modulo calculation mod(), to get the leftover seconds: here still 573 seconds
  • we repeat the division but nowfor minutes (60 seconds) and generate a two-digit minute segment and add a colon
  • we repeat a modulo calculation, to get the leftover seconds: now 33 seconds
  • we append the formatted seconds firure to our string and use that to set $Timecode

You could make this a function:

function fSetTimecode(iNum){
   var:number vNum=iNum;
   var:string vTimecode="";
   vTimecode = floor(vNum/3600).format(0,2,0)+":";
   vNum = mod($MyNumber,3600);
   vTimecode += floor(vNum/60).format(0,2,0)+":";
   vNum = mod($MyNumber,60);
   vTimecode += vNum.format(0,2,0);
  $TimeCode = vTimecode+"\n"+vNum;
};

Now you’d extract your time and call the function:

var:number vTime = ; // set to your value from the URL, e.g. 573.06
fSetTimecode(vTime); // sets $TimeCode for the current note.
1 Like

In a call at present so no time to do the regex part (a full representative URL value would help, e.g. see possible problem characters. What is minimum source timecode, I’m guessing 0.01, i.e. 0 seconds 1/100th of a second?

I suspect passing the output of the above to an Interval would work, but I’ve not had time to try it.

Using a made up guess at your URL, and assuming the minimum number of vharacters is ‘N.NN’, this works for capture (using function above):

var:string vURL = "https://www.examplecom/watch?v=mmfjM-SGlGs&#t=573.06";
var:number vTime = vURL.extract("#t=(\d{1,}\.\d{2})");
fSetTimecode(vTime);

Hey there, this approach should also work.

var:number vSeconds;
vSeconds=$URL.extract("t=(.*)\.");
$MyInterval=(vSeconds/60).precision(2).replace("\.",":");`

t=(.*)\. captures the back seconds as a back reference.
We then cover the seconds to minutes, cut off all the unnecessary zeros and then replace the period to a colon so that tinerbox knows that it is a time.

Except the OP request was specifically for ‘00:00:00’ , i.e. ‘hh:mm:ss’, so whilst you’re getting minutes and seconds, you’ll lose the hours segment of the time and may thus load the wrong timecode.

Still, this also shows there’s more than one way to do things. I like your .extract() regex. As we essentially throw away the hundredths in the original time offset, it makes sense to not capture them at all, as you do. But beware using . in regex. If you mean to match a literal period you need to use \. as a dot/period is otherwise a regex special character meaning match any character. Thus:

.extract("t=(.*)."); 

will mis-detect as you’re matching t= followed by a back-reference to zero or more characters followed by , er, any single character. What you intended is:

.extract("t=(.*)\.");

but we also want/expect at least one digit of seconds before the period. So, again, writing what we want:

.extract("t=(.+)\."); // * matches zero or more, + matches 1 or more

Indeed, my earlier code code also be improved:

var:number vTime = vURL.extract("#t=(\d{1,}\.\d{2})");
// change {1,} to more succinct +
var:number vTime = vURL.extract("#t=(\d+\.\d{2})");

Here, we’re certain the characters after the ‘t=’ are numbers but ‘\d’ matches any 0-9 digit whereas . matches any character. My use of \d is perhaps overkill but I make the point that it might be what you need in some cases if a letter (bad) or number (good) follows the equals sign and avoids a mis-match…Though I doubt it affects performance any! Always more than one way… :slight_smile:

HTH

1 Like

The full URL in the example is: hook://file/JHT0knnv0?p=aWJlcm9tYW5pYS9EZXNrdG9w&n=Anonymous%20%2D%20If%20you%20think%20you%20can%20handle%20the%20truth%2C%20well%20here%20it%20is!%2Emp4#t=573.06
The first frame of a video file corresponds to the “raw” timecode #t=0.

Thank you, @eastgate, @mwra, and @satikusala, for the very clear suggestions on how to achieve the desired value. It will probably take me some digging into aTbRef and regex to fully understand every twist, but that’s part of the enjoyment of learning. I’ll experiment with the proposed solutions and report back.

2 Likes

Nice catches @mwra! Thanks.

1 Like

I do appreciate the sophistication of @mwra’s and @satikusala’s approaches (another way of saying “I’m hugely impressed without really understanding most of the code”), but I’ve found the following to be the simplest solution that produces the desired outcome, making use of $MyInterval (thanks again, @eastgate):

$MyInterval = $URL.extract("#t=(\d+)");
$TimeCode = $MyInterval;

I prefer storing the timecode in a user attribute $TimeCode rather than the comparatively cryptic $MyInterval.
For now I have saved this as a stamp, but I would be grateful for suggestions on how best to implement this for automatic execution (rule?, edict?, onAdd to parent note?) , preferably only where relevant. Since my notes on a specific film are stored within a note of the prototype “pFilm” containing production details, might a condition along the following lines work?

if ($Prototype(parent) == "pFilm"){...};

I have not been able to come up with a workable solution for that part so far. On the other hand, not all notes inside the parent note may be linked to a video file or they may not be deep-linked via Hookmark. Would Tinderbox throw a fit if the condition were met but no URL were available to parse? Apologies for asking such basic questions.

Is your $TimeCode user attribute Interval type? If so, you can just do:

$TimeCode = $URL.extract("#t=(\d+)");

The odd part, perhaps, about intervals is you are making values that look like strings with numbers in that nonetheless an Interval-type attribute will understand as interval data. Akin to the fact that a List, passed the string ant;bee ‘sees’ it as a two item list.

It is currently a string because the value of $TimeCode has (currently) no relevance beyond being read out by a human being. Would you recommend changing the type to interval? What would be the practical advantage? Or is it just safer in case I want to reuse the value for other purposes in the future?

Reply to self: Having reread @mwra’s reply I realise I missed the key idea that changing the type of $TimeCode saves a line of code. Thanks!

1 Like

In fairness, the last code depends on a hitherto undocumented fact. The code indicates that if an Interval-type attribute is passed a single number, Tinderbox parses it as a figure in seconds:

$TimeCode = 5534; // result "01:32:14"

now we know about this useful affordance, I’ll add it to aTbref, but it might be useful to add to Tinderbox Help too.

maybe I spoke too soon:

$TimeCode = 5534.26; // result "3 days 20:14:26"

So maybe don’t just pass the timecode number to an Interval. The earlier longer example , though more code (which you won’t have to revisit once stored) does work correctly.

Got it. Will be fixed in the next backstage release.

2 Likes

Since the code ignores anything beyond the full seconds (i.e. it truncates before the decimal point), this shouldn’t be a problem for the situation outlined in my original post:

$MyInterval = $URL.extract("#t=(\d+)");

Sure, as long as you only take an integer number of seconds (which that regex does). But as noticed the integer vs. decimal input will be resolved in the next.

Of course, it was only at the end it was revealed $TimeCode was an integer and not a string (user attributes default to a string unless the user sets a non-default type). Had the original request been for input to an Integer type, I’d have used a different approach. :slight_smile:

Anyway, no harm here and proof once more that there are many routes to the outcome—both in the regex and the manner of making up a timecode!

I have been trying to change the default display format for $MyInterval (or $TimeCode, for that matter) from 00:00 (mm:ss) to 00:00:00 (h:mm:ss), drawing on the information provided in aTbref9:

Interval.format("hh:mm:ss")

This doesn’t seem to do anything. I noticed that the format change returns the value as a string as opposed to an interval. I don’t think that’s a problem for my purposes, so I changed the code back to the longer:

Interval.format("hh:mm:ss");
$MyInterval = $URL.extract("#t=(\d+)");
$TimeCode = $MyInterval;

What am I missing?