9 Replies Latest reply on Jul 28, 2010 4:43 PM by cob906

    timeToCurrentFormat bug?

    cob906

      The problem:

       

      While creating a script within After Effects CS3, I noticed that there were certain instances where the global function timeToCurrentFormat would return the same value for different seconds values. It makes sense, of course, for seconds values that differ only slightly to return the same value if indeed the difference in the seconds values are not greater than the length of a frame in seconds. However, the time values that I was using within the timeToCurrentFormat function's first parameter were found by getting the time value of a layer's marker through the AE Object Model.

       

      There were certain seemingly arbitrary instances where, even though the time values of two markers were obtained from markers that were a frame a part, the same value was returned by the timeToCurrentFormat function for each of the markers. What's more confusing, even though the project's start time frame is set to 0, the timeToCurrentFormat function returns different values when the isDuration parameter is set to true.

       

      The test:

       

      To test this weird behavior, I setup a test by finding the length of a frame (1 divided by the composition's frameRate: 1 / 29.9700012207031 = 0.03336669867431) and utilizing a loop to go through 30 seconds worth of frames to find the corresponding frame value returned by the timeToCurrentFormat function. I quickly realized that this test was going to be unreliable since the 29.97 frame rate utilizes a drop frame. Therefore, I instead used the loop to create markers on a layer for each frame for 30 seconds, looped through all of the layer's markers, obtained their time value and used it in the timeToCurrentFormat function. Below is a link to a spreadsheet that I created that represents the time values (in seconds) taken from markers that, for 30 seconds, were spaced 1 frame apart (901 markers in all). I've also linked to the After Effects CS3 file I was working with.

       

      A link to the data can be found here:
      http://spreadsheets0.google.com/ccc?key=tqjM5RB38-fMluBGnsU2cbw&hl=en#gid=1

       

      A link to the AE file to use when running the test yourself with the code included below:

      http://aenhancers.com/download/file.php?id=165

       

      The findings:

       

      First, here is my current working environment:

       

      After Effects Version: After Effects CS3 8.0.2.2.7 dva: 2.0.0.127845
      OS: Mac OSX 10.5.08
      QT: 7.6.6

       

      Here is a set of descriptions for each of the header rows within the data to help you decipher and understand what is going on:

       

      • Key ID: - This column contains the index of the current marker whose time value is being referenced.
      • Marker Creation Time: - This column contains the seconds value that was used to create the current marker on the "Test" layer. It is provided to demonstrate that the time listed under "Marker Time Value" is not calculated based upon the time value used to create the marker.
      • Marker Time Value: - This column contains the seconds value that is returned by referencing the keyTime(keyIndex) method of the "Test" layer's "Marker" property. As stated above, this value doesn't seem to be calculated based upon the time value that the marker was created with.
      • timeToCurrentFormat W/ True: - This column contains the frame number returned by the timeToCurrentFormat function when it's isDuration parameter is set to true.
      • Matches Previously Calculated Frame: - This column contains an "*" if the current marker's "timeToCurrentFormat W/ True" value matches the same value in the previous iteration of the loop. These fields have been highlighted in red in the data.
      • timeToCurrentFormat Default: - This column contains the frame number returned by the timeToCurrentFormat function when it's isDuration parameter is not set (which then defaults to false).
      • Matches Previously Calculated Frame: - This column contains an "*" if the current marker's "timeToCurrentFormat Default" value matches the same value in the previous iteration of the loop. These fields have been highlighted in red in the data.
      • currentFormatToTime W/ True: - This column contains the value returned by the currentFormatToTime function when it's isDuration parameter is set to true. The frame value that is used within the function's first parameter is pulled from the current iteration's "timeToCurrentFormat W/ True" value.
      • Matches Initial Seconds Value: - This is used to determine whether the time value ("Marker Time Value") that is sent through the timeToCurrentFormat function (with isDuration set to true) is reproduced correctly when the value returned by the timeToCurrentFormat function is run through the currentFormatToTime function (with isDuration set to true).
      • currentFormatToTime Default: - This column contains the value returned by the currentFormatToTime function when it's  parameter is not set (which defaults to false). The frame value that is used within the function's first parameter is pulled from the current iteration's "timeToCurrentFormat Default" value.
      • Matches Initial Seconds Value: - This is used to determine whether the time value ("Marker Time Value") that is sent through the timeToCurrentFormat function (with isDuration not set) is reproduced correctly when the value returned by the timeToCurrentFormat function is run through the currentFormatToTime function (with isDuration not set).

       

      From the data, i've come to believe the calculations done within the timeToCurrentFormat function are not accurate. Although I've tried to rule out any error on my part by only using values returned by properties and methods within the AE Object Model, it is quite possible I have made a mistake somewhere along the line; hence the reason I'd like to share my findings and allow everyone to comment. Ultimately, I need the timeToCurrentFormat function to work properly in order to complete the script that I'm working on. Therefore, if you notice any errors within my coding, don't be shy, I won't be offended.

       

      The scripts:

       

      The script I used to create the markers on the "Test" layer:

       

      {
      //Before running this script, create a composition and then add
      //a null layer named "Test"
      var comp = app.project.activeItem,
         info = "",
         layer = comp.layer("Test"),
         frameRate = comp.frameRate,
         i;
         
         for (i = 0; i <= comp.duration; i += (1/frameRate))
         {
            layer.property("Marker").setValueAtTime(i, new MarkerValue(i));
         }
      }
      

       

      The script used to create the data (copy and paste the results it into a spreadsheet):

       

      {
         //Using the composition and "Test" layer created by the previous script
         var comp = app.project.activeItem,
            info = "",
            layer = comp.layer("Test"),
            markerRef, markerTime, markerFrame,
            maxSeconds = 30,
            frameRate = comp.frameRate,
            numKeys,
            i,
            timeToCurrentFormatTrue,
            timeToCurrentFormatDefault,
            currentFormatToTimeTrue,
            currentFormatToTimeDefault,
            keyId,
            keyTime,
            prevTimeToCurrentFormatTrue = null,
            prevTimeToCurrentFormatDefault = null,
            usersTimecodeType;
         
         //Get the user's timecode display type so we can change it back when
         //we're done.
         usersTimecodeType = app.project.timecodeDisplayType;   
         
         //Set the current timecode display type to frames
         app.project.timecodeDisplayType = TimecodeDisplayType.FRAMES;
         
         alertInfo = function (m)
          {
            myWindow = new Window("dialog",
               "Info",
               undefined,
               {resizeable: false}
            );
      
            myWindow.grp = myWindow.add(
            "group { orientation: 'column', margins:0," +
            "alignment: ['fill','fill'], size: [350, 450]," +
            "msg: EditText {properties: {multiline:true}," +
               "alignment: ['fill', 'fill']," +
               "size: [350,430]}," +
               "b: Button {text: 'Ok'}" +
            "}");
      
            myWindow.grp.b.onClick = function () {
               myWindow.close();
            };
      
            myWindow.center();
            myWindow.grp.msg.text = m;
            myWindow.show();
          };   
         
         
         info += "Composition Name:\t" + comp.name + "\n" +
            "Layer Name:\t" + layer.name + "\n\n" +
            "Frame Rate:\t" + frameRate + "\n" +
            "Frame Length (s):\t" + (1/frameRate) + "\n\N" +
            
            //Header Rows
            "Key ID\t" +
            "Marker Creation Time\t" +
            "Marker Time Value\t" +
            "timeToCurrentFormat W/ True\t" +
            "Matches Previously Calculated Frame\t" +
            "timeToCurrentFormat Default\t" +
            "Matches Previously Calculated Frame\t" +
            "currentFormatToTime W/ True\t" +
            "Matches Initial Seconds Value\t" +
            "currentFormatToTime Default\t" +
            "Matches Initial Seconds Value\n";
      
         numKeys = layer.property("Marker").numKeys;
         
         for (i = 1; i < numKeys; i += 1)
         {
            //Set values      
            keyId = i;
            keyCreationTime = layer.property("Marker").keyValue(i).comment;
            keyTime = layer.property("Marker").keyTime(i);
            timeToCurrentFormatTrue = timeToCurrentFormat(keyTime, frameRate, true);      
            timeToCurrentFormatDefault = timeToCurrentFormat(keyTime, frameRate);
            currentFormatToTimeTrue = currentFormatToTime(timeToCurrentFormatTrue, frameRate, true);
            currentFormatToTimeDefault = currentFormatToTime(timeToCurrentFormatDefault, frameRate);
      
            //Output row
            info += keyId + "\t" +
               keyCreationTime + "\t" +
               keyTime + "\t" +
               timeToCurrentFormatTrue + "\t";
            if (timeToCurrentFormatTrue === prevTimeToCurrentFormatTrue) {
               info += "*\t";
            } else {
               info += " \t";
            }
      
            info += timeToCurrentFormatDefault  + "\t";
            
            if (timeToCurrentFormatDefault === prevTimeToCurrentFormatDefault) {
               info += "*\t";
            } else {
               info += " \t";
            }
      
            info += currentFormatToTimeTrue + "\t";
            
            if (currentFormatToTimeTrue === keyTime) {
               info += "*\t";
            } else {
               info += " \t";
            }
               
            info += currentFormatToTimeDefault + "\t";
            
            if (currentFormatToTimeDefault === keyTime) {
               info += "*\n";
            } else {
               info += "\n";
            }
               
            prevTimeToCurrentFormatTrue = timeToCurrentFormatTrue;
            prevTimeToCurrentFormatDefault = timeToCurrentFormatDefault;
         }
         alertInfo(info);
         
         //Reset the timecode display type to the user's preference   
         app.project.timecodeDisplayType = usersTimecodeType;
      }
      

       

       

      Any help would be greatly appreciated! Thanks in advance.

        • 1. Re: timeToCurrentFormat bug?
          Rick Gerard Adobe Community Professional & MVP

          I haven't tested your script yet but I did notice something that indicates there may be a basic misunderstanding about 29.97 frame rates and timecode. You said:

          cob906 wrote:

           

          ..... I quickly realized that this test was going to be unreliable since the 29.97 frame rate utilizes a drop frame. Therefore, I instead used the loop to create markers on a layer for each frame for 30 seconds, looped through all of the layer's markers, obtained their time value and used it in the timeToCurrentFormat function.

          Drop frame time code doesn't mean that time or frames are dropped. 29.97 only used DF time code if you choose that option. NDF time code results in an inaccurate time display because the time display counts up by one frame for every frame and doesn't roll over to the next second until 30 frames have passed.  NDF Time code corrects the real time display error by skipping frame numbers so that the time displayed as timecode is accurate to real time. In order to make an hour of timecode match an hour on the clock, drop-frame timecode drops frame numbers 0 and 1 of the first second of every minute, except when the number of minutes is divisible by ten.

           

          With DF Timecode you will never see this value: 0;02;00;00 because the frame counter will go from 0;01;59;29 directly to 0;02;00;02

           

          Like I said, I haven't gone through your example but I think that the time value calculated will be accurate when compared with a real time clock within miliseconds.

           

          I hope this helps.

          1 person found this helpful
          • 2. Re: timeToCurrentFormat bug?
            cob906 Level 1

            Thank you for the prompt reply!

             

            I appreciate your explanation of how DF works. I went ahead and did another test by creating two markers on a null layer. One was placed at 0;01;59;29 and the other was placed at 0;02;00;02. When sending the time (in seconds) value of each of the markers through the timeToCurrentFormat function, the correct time was returned... This means the timeToCurrentFormat is capable of returning the correct frame, even when DF is selected.

             

            In the data that I collected and linked to in the original post, I only go through markers that have been placed 1 frame a part for a duration of 30 seconds. Therefore, I guess DF or NDF wouldn't come into play anyway since I'm only working with time values from 0 to 30 seconds?

             

            I did notice that, if I didn't set isDuration to true when using the timeToCurrentFormat, the value that was returned was one frame behind the actual time of the marker. This makes sense since setting isDuration to true does its math based upon the duration.

             

            The question still remains though: when you look at the data in the spreadsheet that I linked to in the original post, why does timeToCurrentFormat produce values that seem incorrect?

             

            Thanks in advance for any help!

            • 3. Re: timeToCurrentFormat bug?
              cob906 Level 1

              I did a little bit more testing...

              I created a null layer (theLayer1) and placed a marker at frames 118 and 119. I then ran the following on the null layer:

               

              ...
              timeToCurrentFormat(theLayer1.property("Marker").keyTime(1);
              timeToCurrentFormat(theLayer1.property("Marker").keyTime(2);
              ...
              

               

              I then sent both values to an alert and I got the proper frame numbers (118 and 119 respectively). Thinking that the way I created the markers might have caused some sort of error, I created a new null layer (theLayer2) and then placed markers on each frame from 0-30 seconds. This time, I used currentFormatToTime in order to use frame numbers to step my way through the duration of the composition.

               

              I then ran the following code:

               

              ...
              timeToCurrentFormat(theLayer2.property("Marker").keyTime(118);
              timeToCurrentFormat(theLayer2.property("Marker").keyTime(119);
              ...
              
              
              

               

              I then sent both values to an alert and noticed that both of the values were 118. To double check that the original layer that I created (theLayer1) with just the two markers still returned the correct value, I ran the first command again:

               

              ...
              timeToCurrentFormat(theLayer1.property("Marker").keyTime(1);
              timeToCurrentFormat(theLayer1.property("Marker").keyTime(2);
              ...
              

               

              To my surprise, even though these commands had originally returned the correct values (118 and 119), they now both returned 118.

               

              Quite frustrating...

              • 4. Re: timeToCurrentFormat bug?
                MichaelNatkin

                You are on the right track with the idea that creating the markers using seconds is causing the problem. All of our times internally are stored using rational numbers, e.g. frame 1 will be at 100/2997 exactly, while doing a floating point step by 1/frameDuration may not fall there precisely. Using currentFormatToTime won't help because it is still passing through floating point at the interface to JavaScript. I think your best bet is simply to add a very small fudge factor to your frame time, say 1/1000 of a second.

                1 person found this helpful
                • 5. Re: timeToCurrentFormat bug?
                  cob906 Level 1

                  I appreciate the time you've taken to look into this for me. This forum is a valuable asset!

                   

                  MichaelNatkin wrote:

                   

                  You are on the right track with the idea that creating the markers using seconds is causing the problem. All of our times internally are stored using rational numbers, e.g. frame 1 will be at 100/2997 exactly, while doing a floating point step by 1/frameDuration may not fall there precisely.

                   

                  Just to clarify, my problem is not that I can't create markers on each frame of a layer. The problem happens on the other end when trying to get the frame that a marker is set on. According to the data I linked to, there are arbitrary times that the timeToCurrentFormat function returns incorrect values even though the time argument uses times returned through the AE Object Model.

                   

                   

                  MichaelNatkin wrote:

                   

                  Using currentFormatToTime won't help because it is still passing through floating point at the interface to JavaScript. I think your best bet is simply to add a very small fudge factor to your frame time, say 1/1000 of a second.

                   

                   

                  Are you saying that I should add  1/1000 of a second to the time that I send to the timeToCurrentFormat? Should this be done every time this function is called? If so, does that verify that (a) this function is unable to return reliable values in cases where the floating point value used in JavaScript isn't as accurate as the rational numbers used internally and therefore should be used with caution or (b) that this function has some sort of bug?

                   

                  Below is a code snippet that reflects the workflow that you mention:

                   

                   

                  {
                       ...
                       //markerIndex is the index of the marker to get the frame for
                       //markerTime is the time (in seconds) of the marker
                       //nullLayerRef is the null layer that the marker lives on
                  
                       //Get the time (in seconds) of the marker.
                       markerIndex = 1;
                       markerTime = nullLayerRef.keyTime(markerIndex);
                  
                       //Value to add to the markerTime
                       timeFudge = 1/1000;
                  
                       //Convert the markerTime value to frames from seconds
                       markerFrame = timeToCurrentFormat(markerTime + timeFudge, nullLayerRef.containingComp.frameRate, true);
                  
                       alert(markerFrame);
                       ...
                  }
                  

                   

                   

                  Did I understand you correctly?

                   

                  I still feel like there is something weird going on with something behind the scenes because of the fact that, in the test that I did in my third post, the correct timecode/frame value was returned from a specific marker but, after running my script that places a marker on each frame of a different layer, the timecode/frame value was wrong when testing the time of the marker again.

                   

                  Thoughts?

                  • 6. Re: timeToCurrentFormat bug?
                    MichaelNatkin Level 1

                    Sorry, I should have been a little clearer. The problem really is on the side when you are creating the markers. Although in the UI it will appear as if they are exactly on a frame, some of them are landing just a little bit short of the precise time and then getting rounded down. You should be able to confirm this by *manually* setting markers at 118 and 119 in the UI and then verifying that timeToCurrentFormat gives the correct frame number. So what I'm suggesting is that you add a tiny amount to the time in seconds in the loop where you are setting the keys and then your timeToCurrentFormat calls should work correctly. Let me know if this works.

                    • 7. Re: timeToCurrentFormat bug?
                      cob906 Level 1

                      MichaelNatkin wrote:

                       

                      Sorry, I should have been a little clearer. The problem really is on the side when you are creating the markers. Although in the UI it will appear as if they are exactly on a frame, some of them are landing just a little bit short of the precise time and then getting rounded down. You should be able to confirm this by *manually* setting markers at 118 and 119 in the UI and then verifying that timeToCurrentFormat gives the correct frame number. So what I'm suggesting is that you add a tiny amount to the time in seconds in the loop where you are setting the keys and then your timeToCurrentFormat calls should work correctly. Let me know if this works.

                       

                      Good call Michael, when adding 1/1000 of a second to the frameLength, all of the frames did come back correct. Good catch!

                       

                      However, I'm still confused... in my initial test, even though the keyframes were created in a way that caused some of them to be rounded down, when calling the keyTime method, a different time value was returned. For example:

                       

                      The time value to create the second marker was 0.03336669867431; this value was based upon my initial frame length that caused some values to round down. The value returned by the keyTime method was 0.03336670003337; this value is greater than the value that the marker was initially created at. What type of math is keyTime doing? Is it moving the marker to the nearest frame? If so, is the value returned that actual time of frame 1?

                       

                      Should I add 1/1000 every time I receive a time value from the AE Object Model?

                       

                      Thanks again for all your help!

                      • 8. Re: timeToCurrentFormat bug?
                        MichaelNatkin Level 1

                        I'm glad it is working for you now. So what is happening is that when you do the math of 1/frameRate in JavaScript, you have an IEEE floating point number with finite resolution. As you can see it isn't 0.03336670333667... repeat forever, which is the mathematically correct value of 29.97. Then AE converts that to a fractional value, which ideally would be 1000/2997 (or some multiple thereof), but it may not be - we allow keyframes to fall not exactly on frames, because of things like time stretch. You don't have any control on whether it has rounded up or down, unfortunately. Then when you retrieve that time back, it is converted yet again to floating point and then converted back again to a fixed fraction when you pass it to timeToCurrentFrame. So basically you just don't have the control you need over the rounding. The only way I can see we could make this work more reliably would be to expose our rational fraction times directly to JavaScript, but I think that would generally be a pretty high burden on the script author. (I can feel most people's eyes , besides you, me, and a handful of hardcore geeks glazing over a long time ago .)

                         

                        So I think the workaround we've come up with for you is the best thing for now - you don't need to go adding 1/1000 everywhere, just when you are creating new keys that you deeply care about lining up exactly on a frame. For normal use, when you aren't trying to retrieve them back by an integer frame time, you wouldn't need to bother which is why I think no-one has reported this issue up to now (it has probably been there since AE 5.0 back in Y2K).

                        • 9. Re: timeToCurrentFormat bug?
                          cob906 Level 1

                          Darn IEEE floating point! One of the awful parts of JavaScript. :(  Thanks a lot for your help!