pyaaf2 icon indicating copy to clipboard operation
pyaaf2 copied to clipboard

Avid's keyframe values different from PyAAF2 interpolation with extreme bezier handles

Open austinwitherspoon opened this issue 1 year ago • 9 comments

This is definitely an edge case - most of the test cases I've made have been pretty much identical between pyaaf2 and what is displayed in Avid! So great work!

We've been noticing occasional anomalies though, and I finally was able to reproduce one myself.

If I intentionally push the handles of a bezier curve too far, I can get pyaaf2 to spit out some incorrect values.

image

I've attached an example aaf file, and here is the code I ran on it:

import aaf2

VALUES_IN_AVID = {
    0: 0.0,
    1: 40.2,
    2: 79.92,
    3: 119.09,
    4: 157.63,
    5: 194.45,
    6: 232.43,
    7: 268.39,
    8: 303.14,
    9: 336.38,
    10: 367.72,
    11: 396.57,
    12: 422.01,
    13: 442.48,
    14: 454.99,
    15: 452.45,
    16: 409.92,
    17: 227.30,
    18: 107.82,
    19: 55.0,
    20: 26.25,
    21: 10.24,
    22: 2.3,
    23: 0.0,
}

file = "bezier_example.aaf"
fp = aaf2.open(file)

op_group = next(fp.content.compositionmobs()).slots[8].segment.components[1]
paramY = next(
    param for param in op_group.parameters if isinstance(param, aaf2.misc.VaryingValue) and param.name == "DVE_POS_Y_U"
)
for i in range(0, 24):
    avid_value = VALUES_IN_AVID[i]
    pyaaf2_value = paramY.value_at(i)
    diff = abs(pyaaf2_value - avid_value)
    print(
        i, f"PyAAF2: {pyaaf2_value:.3f}", f"Avid: {avid_value:.2f}", f"Diff: {diff:.3f}", "OK" if diff < 0.1 else "FAIL"
    )

That dictionary at the top is manually populated with values seen in the keyframe editor in Avid Media Composer. The script will compare avid's value and pyaaf2's value and if there's a margin of error larger than .1, will print "FAIL"

With this AAF, I get the following output:

0 PyAAF2: 0.000 Avid: 0.00 Diff: 0.000 OK
1 PyAAF2: 40.149 Avid: 40.20 Diff: 0.051 OK
2 PyAAF2: 79.705 Avid: 79.92 Diff: 0.215 FAIL
3 PyAAF2: 118.582 Avid: 119.09 Diff: 0.508 FAIL
4 PyAAF2: 156.672 Avid: 157.63 Diff: 0.958 FAIL
5 PyAAF2: 193.838 Avid: 194.45 Diff: 0.612 FAIL
6 PyAAF2: 229.905 Avid: 232.43 Diff: 2.525 FAIL
7 PyAAF2: 264.645 Avid: 268.39 Diff: 3.745 FAIL
8 PyAAF2: 297.748 Avid: 303.14 Diff: 5.392 FAIL
9 PyAAF2: 328.781 Avid: 336.38 Diff: 7.599 FAIL
10 PyAAF2: 357.113 Avid: 367.72 Diff: 10.607 FAIL
11 PyAAF2: 381.771 Avid: 396.57 Diff: 14.799 FAIL
12 PyAAF2: 401.138 Avid: 422.01 Diff: 20.872 FAIL
13 PyAAF2: 412.239 Avid: 442.48 Diff: 30.241 FAIL
14 PyAAF2: 408.694 Avid: 454.99 Diff: 46.296 FAIL
15 PyAAF2: 373.544 Avid: 452.45 Diff: 78.906 FAIL
16 PyAAF2: 267.610 Avid: 409.92 Diff: 142.310 FAIL
17 PyAAF2: 150.411 Avid: 227.30 Diff: 76.889 FAIL
18 PyAAF2: 84.263 Avid: 107.82 Diff: 23.557 FAIL
19 PyAAF2: 45.857 Avid: 55.00 Diff: 9.143 FAIL
20 PyAAF2: 22.652 Avid: 26.25 Diff: 3.598 FAIL
21 PyAAF2: 9.030 Avid: 10.24 Diff: 1.210 FAIL
22 PyAAF2: 2.055 Avid: 2.30 Diff: 0.245 FAIL
23 PyAAF2: 0.000 Avid: 0.00 Diff: 0.000 OK

Again, this is definitely an edge case as far as I can tell, when you push the bezier handles too far! So it's totally understandable if this isn't a high priority.

austinwitherspoon avatar Mar 14 '24 00:03 austinwitherspoon

Thanks for taking time looking at this! I really appreciate it :) I have some ideas what it might be. Any chance you would be able to convert your verified tests to unit tests? It would be super helpful when fixing this issue. We wouldn't want to break any cases you've verified.

markreidvfx avatar Mar 14 '24 03:03 markreidvfx

Sure, I'll try to make some tests when I get into the office tomorrow!

austinwitherspoon avatar Mar 14 '24 03:03 austinwitherspoon

I think my suspicions are correct. These are the coordinates of the points and handles between 0-23

(0.0, 0.0)--(25.4089376, 1027.02703)   (10.2015177, 0.0)--(23.0, 0.0)

As we can see the first handles x coordinate is actually after the last keyframe. For this case I've been scaling the handle so its if doesn't go beyond that last keyframe. (This is typically what I've done in curve editors I've made in the past) image

In this case my code is scaling them to.

 (25.4089376, 1027.02703) --> (23.0, 929.6579834176143)

If I remove the scaling, the results are much closer.

0 PyAAF2: 0.000 Avid: 0.00 Diff: 0.000 OK
1 PyAAF2: 40.198 Avid: 40.20 Diff: 0.002 OK
2 PyAAF2: 79.916 Avid: 79.92 Diff: 0.004 OK
3 PyAAF2: 119.088 Avid: 119.09 Diff: 0.002 OK
4 PyAAF2: 157.633 Avid: 157.63 Diff: 0.003 OK
5 PyAAF2: 195.454 Avid: 194.45 Diff: 1.004 FAIL
6 PyAAF2: 232.426 Avid: 232.43 Diff: 0.004 OK
7 PyAAF2: 268.390 Avid: 268.39 Diff: 0.000 OK
8 PyAAF2: 303.135 Avid: 303.14 Diff: 0.005 OK
9 PyAAF2: 336.377 Avid: 336.38 Diff: 0.003 OK
10 PyAAF2: 367.716 Avid: 367.72 Diff: 0.004 OK
11 PyAAF2: 396.566 Avid: 396.57 Diff: 0.004 OK
12 PyAAF2: 422.007 Avid: 422.01 Diff: 0.003 OK
13 PyAAF2: 442.480 Avid: 442.48 Diff: 0.000 OK
14 PyAAF2: 454.986 Avid: 454.99 Diff: 0.004 OK
15 PyAAF2: 452.447 Avid: 452.45 Diff: 0.003 OK
16 PyAAF2: 409.922 Avid: 409.92 Diff: 0.002 OK
17 PyAAF2: 227.303 Avid: 227.30 Diff: 0.003 OK
18 PyAAF2: 107.825 Avid: 107.82 Diff: 0.005 OK
19 PyAAF2: 55.004 Avid: 55.00 Diff: 0.004 OK
20 PyAAF2: 26.253 Avid: 26.25 Diff: 0.003 OK
21 PyAAF2: 10.241 Avid: 10.24 Diff: 0.001 OK
22 PyAAF2: 2.296 Avid: 2.30 Diff: 0.004 OK
23 PyAAF2: 0.000 Avid: 0.00 Diff: 0.000 OK

I think you transcribed frame 5 wrong, my avid says its 195.50 not 194.45

I'm not entirely sure what the implications of not sanitizing the handles in extreme cases is, but it doesn't appear media composer is doing any.

markreidvfx avatar Mar 14 '24 04:03 markreidvfx

Out of curiosity, I decided to pull the handle significantly further than god ever intended: image

Some part of me was expecting Avid to crash, but nope! Interesting how they decide to clamp it like that.

austinwitherspoon avatar Mar 14 '24 16:03 austinwitherspoon

I'm not sure what's easier for you, if you copy over from my branch or if I do a merge request with a failing test?

Let me know if you'd like a pull request!

I've added two keyframe interpolation tests, one passing and one failing (this example) here: https://github.com/austinwitherspoon/pyaaf2/tree/keyframe-interpolation-tests

austinwitherspoon avatar Mar 14 '24 23:03 austinwitherspoon

Thanks @austinwitherspoon. Sure, if your cool with it I'll take test from your branch and add the fix so it all be in one nice clean commit :)

markreidvfx avatar Mar 24 '24 19:03 markreidvfx

Go ahead!

austinwitherspoon avatar Mar 24 '24 19:03 austinwitherspoon

your tests have been added to the dev branch.

markreidvfx avatar Mar 25 '24 02:03 markreidvfx