From 4b45983bb6b634b0ac501bab298abf6887dd90b3 Mon Sep 17 00:00:00 2001 From: Sergey Kuznetsov Date: Sat, 27 Jun 2026 09:22:12 -0400 Subject: [PATCH 1/2] Fix Motion Tracker keyframes not applying after save/reload 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. --- src/models/motiontrackermodel.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/models/motiontrackermodel.cpp b/src/models/motiontrackermodel.cpp index 12ba537952..b285480adc 100644 --- a/src/models/motiontrackermodel.cpp +++ b/src/models/motiontrackermodel.cpp @@ -270,12 +270,18 @@ QList MotionTrackerModel::trackingData(const Q for (const auto &i : l) { auto pair = i.split("~="); if (pair.size() == 2) { - auto frame = pair.at(0).toInt(&ok); + // The keyframe time may be serialized either as a frame number + // ("5") or as a clock value ("00:00:00.167"), depending on the MLT + // time format used when the project was written. toInt() only + // parses the frame-number form; for the clock form it fails, so we + // fall back to the running index. Only the rectangles are consumed + // downstream, so the exact frame value is not significant here. + int frame = pair.at(0).toInt(&ok); + if (!ok) + frame = int(result.size()); props.set("", pair.at(1).toLatin1().constData()); auto rect = props.get_rect(""); - if (ok) { - result << TrackingItem{frame, QRectF(rect.x, rect.y, rect.w, rect.h)}; - } + result << TrackingItem{frame, QRectF(rect.x, rect.y, rect.w, rect.h)}; } } return result; From 5246354b9802c6e873d0f120b2f55d08831d91d3 Mon Sep 17 00:00:00 2001 From: Sergey Kuznetsov Date: Sat, 27 Jun 2026 13:33:41 -0400 Subject: [PATCH 2/2] Use time_to_frames to parse tracker keyframe times 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. --- src/models/motiontrackermodel.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/models/motiontrackermodel.cpp b/src/models/motiontrackermodel.cpp index b285480adc..00610f35c6 100644 --- a/src/models/motiontrackermodel.cpp +++ b/src/models/motiontrackermodel.cpp @@ -264,21 +264,19 @@ QList MotionTrackerModel::trackingData(const Q QList result; auto s = m_data.value(key, {}).trackingData; auto l = s.split(';'); - bool ok{false}; Mlt::Properties props; + auto consumer = MLT.consumer(); for (const auto &i : l) { auto pair = i.split("~="); if (pair.size() == 2) { - // The keyframe time may be serialized either as a frame number - // ("5") or as a clock value ("00:00:00.167"), depending on the MLT - // time format used when the project was written. toInt() only - // parses the frame-number form; for the clock form it fails, so we - // fall back to the running index. Only the rectangles are consumed - // downstream, so the exact frame value is not significant here. - int frame = pair.at(0).toInt(&ok); - if (!ok) - frame = int(result.size()); + // The keyframe time is serialized either as a frame number ("5") or + // as a clock value ("00:00:00.167"), depending on the time format + // MLT used when the project was written (animated properties are + // saved as time so they adapt to a frame-rate change). time_to_frames() + // parses both forms to the correct frame using the project frame rate. + int frame = consumer ? consumer->time_to_frames(pair.at(0).toUtf8().constData()) + : pair.at(0).toInt(); props.set("", pair.at(1).toLatin1().constData()); auto rect = props.get_rect(""); result << TrackingItem{frame, QRectF(rect.x, rect.y, rect.w, rect.h)};