Fix Motion Tracker keyframes not applying after save/reload#1854
Fix Motion Tracker keyframes not applying after save/reload#1854skuznetsov wants to merge 2 commits into
Conversation
MotionTrackerModel::trackingData parsed each keyframe's time key with
QString::toInt, which only accepts integer frame numbers ("5"). When a
project is saved, Shotcut serializes animated properties (including the
opencv.tracker `results`) in clock time format (00:00:00.167). After a
save+reload, toInt then failed on every entry, trackingData returned an
empty list, and "Load Keyframes from Motion Tracker" silently applied
zero keyframes (the masked rect stayed static).
Accept both frame-number and clock/timecode keys. The parsed frame value
is not used downstream (only the rectangles are consumed by applyTracking
and reset), so fall back to the running index when toInt fails.
843c062 to
4b45983
Compare
| // downstream, so the exact frame value is not significant here. | ||
| int frame = pair.at(0).toInt(&ok); | ||
| if (!ok) | ||
| frame = int(result.size()); |
There was a problem hiding this comment.
You should provide clear steps to reproduce the bug since I did not reproduce it. I think there was a change in the past year or so to ensure property animations would save as time values instead of frame numbers to ensure they adapt to changing the project video mode's frame rate. So, it would not surprise me if this bug occurred as a result. However, I must be able to confirm a fix.
I added a debug log line inside this method to log each pair. The MLT XML contains
<property name="results">00:00:00.000~=1333 671 192 108 0;00:00:00.167~=1334 669 192 108 0;00:00:00.334~=1332 664 196 110 0;00:00:00.501~=1335 661 192 108 0;00:00:00.667~=1335 657 196 110 0;00:00:00.834~=1338 655 192 108 0;00:00:01.001~=1337 652 196 110 0;00:00:01.168~=1339 649 196 110 0;00:00:01.335~=1342 649 192 108 0;00:00:01.502~=1342 646 196 110 0;00:00:01.668~=1343 646 196 110 0;00:00:01.835~=1342 646 196 110 0;00:00:02.002~=1345 647 192 108 0;00:00:02.169~=1345 648 192 108 0;00:00:02.336~=1345 650 192 108 0;00:00:02.503~=1344 650 192 108 0;00:00:02.669~=1343 652 192 108 0;00:00:02.836~=1342 653 192 108 0;00:00:03.003~=1340 654 192 108 0;00:00:03.170~=1339 654 192 108 0;00:00:03.203~=1339 654 192 108 0</property>And the debug log shows:
[Debug ] <MotionTrackerModel::trackingData> QList("0", "1333 671 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("5", "1334 669 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("10", "1332 664 196 110 0")
[Debug ] <MotionTrackerModel::trackingData> QList("15", "1335 661 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("20", "1335 657 196 110 0")
[Debug ] <MotionTrackerModel::trackingData> QList("25", "1338 655 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("30", "1337 652 196 110 0")
[Debug ] <MotionTrackerModel::trackingData> QList("35", "1339 649 196 110 0")
[Debug ] <MotionTrackerModel::trackingData> QList("40", "1342 649 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("45", "1342 646 196 110 0")
[Debug ] <MotionTrackerModel::trackingData> QList("50", "1343 646 196 110 0")
[Debug ] <MotionTrackerModel::trackingData> QList("55", "1342 646 196 110 0")
[Debug ] <MotionTrackerModel::trackingData> QList("60", "1345 647 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("65", "1345 648 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("70", "1345 650 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("75", "1344 650 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("80", "1343 652 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("85", "1342 653 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("90", "1340 654 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("95", "1339 654 192 108 0")
[Debug ] <MotionTrackerModel::trackingData> QList("96", "1339 654 192 108 0")
The first pair item is already a numeric string. It appears something already converted them from time values.
Secondly, your change is wrong; it cannot simply use the index as the frame number because it currently makes a keyframe every 5 frames. It needs to use MLT->consumer()->time_to_frames().
There was a problem hiding this comment.
Thanks for the review.
Reproduction (it only triggers once the results are read back as clock time, which is what happens here after a save + reload):
- Add a Motion Tracker filter to a clip, set the rect, and Analyze.
- Add a Mask: Simple Shape filter and use "Load Keyframes from Motion Tracker" - this works.
- Save the project, close it, and reopen it.
- Select the mask and "Load Keyframes from Motion Tracker" again → zero keyframes are applied (the masked rect stays static).
On reopen the saved results are in clock form, e.g. 00:00:00.000~=789 457 154 13 0;00:00:00.167~=..., and trackingData() receives those clock strings. The original code did pair.at(0).toInt(&ok) and only appended the item if (ok). toInt() fails on 00:00:00.167, so every entry was skipped and trackingData() returned an empty list, so no keyframes applied. That if (ok) gate is the real bug.
Responding to review feedback: rather than falling back to the running
index when QString::toInt fails on a clock-time key, parse the keyframe
time with Mlt::Consumer::time_to_frames. It converts both the clock form
("00:00:00.167") and the frame-number form ("5") to the correct frame
using the project frame rate, so a project reloaded with clock-time
results no longer yields an empty list and the keyframes apply.
Problem
After saving and reopening a project, Load Keyframes from Motion Tracker silently applies zero keyframes — the masked rectangle stays static instead of following the tracked motion.
Root cause
MotionTrackerModel::trackingData()parses each keyframe's time key withQString::toInt, which only accepts integer frame numbers (e.g.5). When a project is saved, Shotcut serializes animated properties — including theopencv.trackerresults— in clock time format (e.g.00:00:00.167). After a save + reload,toIntthen fails on every entry, sotrackingDatareturns an empty list andapplyTrackinglays down no keyframes.This is why tracking "works" right after Analyze (in-memory
resultsare frame-numbered) but stops working once the project has been saved and reopened.Fix
Accept both frame-number and clock/timecode time keys. The parsed frame value is not used downstream (only the rectangles are consumed by
applyTracking/reset), so fall back to the running index whentoIntfails.Verification
On a real project whose tracker
resultswere stored as clock timecodes, the parser yielded 0 rectangles before the change and all 46 after, and the masked filter's keyframes are applied across the whole clip as expected.