Monday, May 19, 2014

Lloyd's Video Analysis Tool, Part 4: Displaying Time Code

As we have seen so far in this series on developing a video analysis tool, LiveCode has its own unique way of tracking intervals per seconds of a video. Each video can have a different number of intervals per second and this can be determined by using the timeScale property. For the video I've been using for testing purposes in my prototype, that number has been 90,000 intervals per second.

So, using this scale, the numbers 37,800,000 to 49,950,000 would mean playing the video from the seven minute mark to the nine minute 15 second mark. The way most of us are used to seeing time code expressed is in this form: hours:minutes:seconds, or...

00:07:00 to 00:09:15

Unfortunately, LiveCode does not have a function to show the time code in this way. But, there is nothing stopping us from inventing our own function to do this. Writing custom functions is a very powerful feature of most programming languages and LiveCode is no exception. Think of it as adding to LiveCode's dictionary. Just as any human language naturally grows with use over time, so too can a computer programming language. So, we'll use this problem as an opportunity to explore how to create functions in LiveCode. I'll name this new function as "convertTimeCode." You can name functions anything you want, assuming you don't pick the name of an already existing function (some programmers add a special prefix or suffix, such as their initials, to each unique function they create to ensure no existing functions names are inadvertently used).

So, what does a function look like in use? Well, we've been using LiveCode built-in functions all along in this blog. The absolute value function is a good example. Consider the following:

   abs(-109)

This returns the number 109. Or, you could say it converts -109 to its absolute value.

Consider these two examples:

abs(598-601)
abs(601-598)

Both return a value of 3. Now consider this block of code:

put 434 into varFirst
put 822 into varSecond
abs(varFirst-varSecond)

This will return a value of 388.

Similarly, let's imagine I've already created the new function "convertTimeCode" and entered the following as part of some scripts:

   convertTimeCode(49950000)
   convertTimeCode(324990000)

These each return values, respectively, of "00:09:15" and "03:00:11".

(Of course, these two examples assume a timeScale value of 90,000; remember, the timeScale value can be different depending on the video. So, we just need to always check for the timeScale when writing our scripts.)

Creating Our Own LiveCode Function


The complete script of the function I created is at the bottom of this post. I'll explain briefly how it works in a moment, but here is its basic construction:

function convertTimeCode ct
     //all of the script that makes the function work goes here
     return myTimeCode
end convertTimeCode

The number that is entered in parentheses when the function is called is put into "ct," a local variable that I get to name. (This was meant to be short for "time code," but I guess I got the letters backwards when I wrote the function!) The script that makes the function work eventually put the final value of the time code into a variable called "myTimeCode." Its value must look like the time code described above (e.g. 01:34:14).

I don't think a full explanation of how this function actually works is needed here. In short, the function takes the number of intervals and converts it to seconds. This is then divided by 3600 -- the number of seconds in an hour -- to figure out how many hours there are. The remainder is then divided by 60 -- the number of seconds in a minute -- to figure out how many remaining minutes there are. The remainder is the number of seconds. I relied on LiveCode's trunc function (short for truncate) for these steps. When you have numbers like 29.35 or 31.78, the trunc function strips away everything to the right of the decimal point, leaving just the integer, such as 29 and 31. No rounding occurs.

Along the way, the script adds extra zeros if and when they are necessary. For example, if there are only 9 minutes for the middle part of the time code, an extra zero is added in front of the 9 (e.g. "09"). Finally, colons are added where needed.

Figuring all this out was just a fun little logic puzzle for me. I'm sure there are probably more elegant ways to do this, but hey, it works.

Displaying the Time Code to the User


Great, we have a function that converts LiveCode's intervals into time code that a person can interpret. Now, what should we do with it? We need to display this time code and have the user interact with it. Of course, this nicely formatted time code isn't what LiveCode uses to actually play video snippets. So, we will continue to need the original video interval information. As in many of my previous LiveCode projects, the strategy here once again points to the use of what I call "Shadow Fields." These are fields that are actually hidden from the user's view, but do work behind the scenes.

I've copied and pasted the field "log" and named this new field simply "log2." I'll put all of the time code data inside log2 in parallel with the interval data in the original log. For now, I'm displaying both so you can see the parallel output, though I'll eventually hide the field "log" (the field "log2" is on the left and the field "log" is on the right):


[ Get the free LiveCode Community version. ]



As you can see, the two lines of the two fields match, or correspond, to each other. All I need to do now is focus the user's interaction on log2 instead of log. But, I have to be careful to use the original log when sending instructions to the movie player. This is easily done because I can refer to the same line number in each field. I just have to be careful when I reference log2 versus log. As an example, here is the revised script for the "Update Start" button:

on mouseUp
   put the hilitedLine of field "log2" into l //note: this is a lower-case L
   put the currentTime of player "player" into ct
   //error trap - starting point must be before ending point
   if ct > item 2 of line l of field "log" then //note: this is a lower-case L
      answer error "Not Allowed! Starting point must come before the ending point!" with "OK"
      exit mouseUp
   end if
   put ct into item 1 of line l of field "log" //note: read this as "into item one of line [lower-case L]"
   put convertTimeCode(ct) into item 1 of line l of field "log2" //ditto
   set the hilitedLine of field "log2" to l //note: this is a lower-case L
end mouseUp

First, notice my use of the convertTimeCode function in the second-to-last line of the script (not counting the "on mouseUp" and "end mouseUp" lines). (Note: Sorry the differences between the number one and the lower-case L are not more readily apparent. I hope my notes in red make this clearer.)

Second, notice how I switch back and forth between referencing log2 versus log. I do the same in the other buttons in the blue editing zone.

One more quick enhancement: Adding an Alert Box


Every time a new entry is added to the log, the previously selected entry is un-highlighted. This causes a problem if the user then immediately clicks on the play button in the editing zone. So, we need to communicate a reminder to the user that they first need to highlight one of the video entries first. We could dim out the play button if nothing is highlighted (which, frankly, sounds like the way to go), but for now I've just added a little alert box in the button "Play entry." Here's the script I added to that button:

   if the selectedtext of field "log2" is empty then
      answer "You first have to select an entry from the log."
      exit mouseUp
   end if

Things are coming along very nicely for this video analysis tool project.


Appendix


Here's the script for my "convertTimeCode" function - impressive, isn't it?

function convertTimeCode ct
   put the timescale of player "Player" into ts
   //Convert intervals into seconds
   put round((ct)/ts) into s
   //Determine number of hours and subtract that number of seconds from total
   if s<3600 then put "00" into myTimeCode
   if s>=3600 then
      put trunc(s/3600) into h
      put s-(h*3600) into s
      if h<10 then
         put "0"&h into myTimeCode
      else
         put h into myTimeCode
      end if
   end if
   //Determine number of minutes
   if s<60 then put myTimeCode&":00" into myTimeCode
   if s>=60 then
      put trunc(s/60) into m
      put s-(m*60) into s
      if m<10 then
         put myTimeCode&":0"&m into myTimeCode
      else
         put myTimeCode&":"&m into myTimeCode
      end if
   end if
   //Determine number of seconds
   if s>=10 then put myTimeCode&":"&s into myTimeCode
   if s<10 then put myTimeCode&":0"&s into myTimeCode
   return myTimeCode
end convertTimeCode


No comments:

Post a Comment