Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ audited constant-rate imports after timestamp indexing; its edited-file Tracker
has passed for the pinned linked four-frame AVI and mixed-tab MOV archive cases. Later revisions require
their own CI and review. Archived AVI output is refused because pinned Tracker cannot reopen it;
the original case and compatibility restriction are tracked in [#140](https://github.com/mgradwohl/MoTrack/issues/140).
Movie timing/calibration/track-identity edits, broader movie containers, filters and broader settings remain unfinished. An unsupported export is refused; save a MoTrack copy to retain work.
Existing fixed movie calibration can also retain axes, distance-scale and length-label edits through
linked saves, MOV archives and paired saving. Its new actual-Tracker acceptance cases await current-revision CI.
Movie timing, varying calibration and track-identity edits, broader movie containers, filters and broader settings remain unfinished. An unsupported export is refused; save a MoTrack copy to retain work.
Image experiments can retain Clip Settings changes to the starting frame, ending frame and frame
interval in linked `.trk` files, existing image archives and portable image exports. All source images
and marks remain available, including those outside the selected clip; image timing stays unchanged.
Expand All @@ -93,7 +95,7 @@ history; exporting a copy does not replace ordinary Save or create a companion a

Export runs in the background with a Cancel button. Missing media files are named in the error message,
and failures or cancellation before replacement keep an existing archive intact. This command currently
supports audited local-image schemas and linked MOV mass/mark/clip-range edits described in the
supports audited local-image schemas and linked MOV mass/mark/clip-range and existing fixed-calibration edits described in the
[format guide](docs/native-format.md#creating-a-portable-movie-archive). The entire movie is retained, including
frames outside the selected clip. Other movie containers, arbitrary supporting documents and newly created
standalone media projects remain unfinished.
Expand Down
18 changes: 10 additions & 8 deletions docs/migration-plan.md

Large diffs are not rendered by default.

31 changes: 23 additions & 8 deletions docs/native-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -651,14 +651,27 @@ Windows/macOS and actual Tracker package acceptance, with 95.72% combined C++ li
### Linked constant-rate movie edits

The document writer accepts the explicitly audited `XuggleVideo`/`VideoClipControl` schema only through
its movie adapter. Existing point-mass values and image-coordinate marks, clip start/step/count and the
linked media path may change. Original names, membership, calibration, unit labels, numeric clip timing
and other parsed fields must remain unchanged. Filters, vectors, other track types except axes, display-orientation
its movie adapter. Existing point-mass values and image-coordinate marks, clip start/step/count, existing
fixed calibration, length-unit labels and the linked media path may change. Original names, membership,
mass/time-unit labels, numeric clip timing and other parsed fields must remain unchanged. Filters, vectors, other track types except axes, display-orientation
transforms remain refused. The selected-member archive writer uses the same audited movie adapter
after verifying its immutable source, media members and completed native index.
This opt-in schema does not enable movie renames or
membership edits through the media-free writer.

Movie calibration edits require one original and edited frame-0 key with origin, angle and scale all
fixed. The shared coordinate writer validates finite/invertible values and image/world precision before
replacing the output. Creating/removing calibration, changing fixed flags, locked or unsafe transforms
and keyed/partly fixed movie calibration edits remain refused. Existing axes/distance commands retain
undo/redo and length labels together; native reopen, selected MOV archive saves, paired recovery and
portable MOV packaging use the same validated writer.

Four new [movie-calibration corpus modes](../tests/corpus/README.md#fixed-movie-calibration-saving)
independently check a moved origin, 90-degree rotation, nonuniform scale, centimetre labels and all
calculated positions, plus complete movie bytes/times and the untouched image tab. Native export/oracle/
reimport and desktop paired-save regressions pass locally. Actual Tracker load/save/fresh-reopen acceptance
for this new calibration scope still requires current-revision Linux CI and review.

Default point-mass number formats use the same movie-aware schema audit and remain in the exported
XML after native reopen. Custom formats remain refused under the existing #64 fidelity guard.

Expand Down Expand Up @@ -732,7 +745,7 @@ actual Tracker movie-range cases and **95.72% combined C++ line coverage (8874/9
An existing `.trz` containing an audited constant-rate MOV file can use the selected-member writer and
paired desktop Save workflow. The original archive, other experiments, compressed media and opaque
members retain their existing preservation contract. The selected XML may change only within the
movie adapter's mass/mark/clip-range limits. Native close/reopen and removal of the original input do
movie adapter's mass/mark/clip-range and existing fixed-calibration limits. Native close/reopen and removal of the original input do
not break the owned archive or index. Both root and nested selected-member paths are covered.

The desktop regression edits mass and clip settings, uses undo/redo and ordinary paired Save, removes
Expand Down Expand Up @@ -783,7 +796,7 @@ project persistence does not link media decoders. The package contains `experime
`media/movie.mov`; the complete source movie is copied byte for byte, including frames excluded by a
trimmed or strided clip. No transcode, frame renumbering or timestamp change is performed.

The standalone movie writer validates mass/mark/clip-range edits and unchanged timing, calibration and
The standalone movie writer validates mass/mark/clip-range and existing fixed-calibration edits, with unchanged timing and
track identity against the retained source envelope. Staging clears runtime media bindings only in its
private snapshot, so generated members resolve inside the package. Before atomic replacement, the
normal archive reader reopens the ZIP, resolves its media, and FFmpeg decodes/indexes the staged movie.
Expand All @@ -803,9 +816,11 @@ subsequent embedded native saving and archive resave, stale timing, corrupt sour
cancellation and shared save locks/recovery. Desktop cases exercise export after indexing, ordinary Save
and undo/redo after export, cancellation, refusal of AVI, media removal/relocation, frame seeking and
paired saving of the reopened package. Three independent corpus modes require actual Tracker acceptance:
`packaged-movie`, `packaged-movie-clip` and `packaged-movie-clip-single`. Their current-head Linux
application results, three-platform CI and review are still required; local/native checks alone are
not accepted interoperability evidence. Broader movie formats and full export authoring remain unfinished.
`packaged-movie`, `packaged-movie-clip` and `packaged-movie-clip-single`. All three passed actual Tracker
load/save/fresh reopen in [PR #141](https://github.com/mgradwohl/MoTrack/pull/141), reviewed at `d0e1a66`;
[CI 42583275559](https://github.com/mgradwohl/MoTrack/actions/runs/42583275559) passed all six checks with
**95.83% combined C++ line coverage (8931/9320)**. The new `packaged-movie-calibration` case still awaits
its own current-revision acceptance. Broader movie formats and full export authoring remain unfinished.

Local Windows validation at `f18cecd` passes all ten CTest groups in **127.87 seconds** with
**95.75% C++ line coverage (8897/9292)**, 90 Python corpus tests and all three native package
Expand Down
9 changes: 9 additions & 0 deletions docs/upstream-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ record the test evidence and update the plan. The broader post-port review in
[MoTrack #39](https://github.com/mgradwohl/MoTrack/issues/39) remains separate from correctness and
compatibility work that must happen during the port.

On **2026-11-09**, Tracker #12/#13 and their complete discussions were refreshed for the fixed-movie
calibration save slice. #12 remains a confirmed upstream two-click workflow; #13 remains open with the
historical second-endpoint report unreproduced by its maintainer. The native desktop regression now
places axes and both distance endpoints through the existing input controller, checks undo/redo, and
saves/reopens fixed calibration in both file formats. Four independent movie save/reference modes are
added with current-revision application CI still pending. This is scoped phase-3 persistence evidence;
[MoTrack #78](https://github.com/mgradwohl/MoTrack/issues/78) stays open for the full phase-5 tool,
selection/dragging, zoom/DPI and cancellation acceptance. The original reported bug is not claimed reproduced.

## Defects discovered by the reference harness

| Observation | Evidence and current limit | MoTrack tracking |
Expand Down
10 changes: 9 additions & 1 deletion src/tracker_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1020,8 +1020,16 @@ bool patchLinkedMoviePath(QDomDocument& xml, const LegacyProject& project, Legac
require(project.clip.mediaReferences.size() == 1 && baseline.clip.mediaReferences.size() == 1 && project.vectors.empty() &&
std::ranges::all_of(project.unsupportedTrackTypes,
[](const auto& type) { return type == "org.opensourcephysics.cabrillo.tracker.CoordAxes"; }));
// Movie edits retain original timing, geometry and identity. Clip ranges keep source frame numbers.
// Movie edits retain original timing and identity. Clip ranges keep source frame numbers.
// Existing fixed calibration uses the shared coordinate writer and its numerical checks below.
auto unchanged = project;
if (project.calibration != baseline.calibration)
{
require(baseline.calibration && baseline.calibration->fixedOrigin && baseline.calibration->fixedAngle &&
baseline.calibration->fixedScale); // Keyed movie calibration needs its own application reference.
unchanged.calibration = baseline.calibration;
}
unchanged.lengthUnit = baseline.lengthUnit;
unchanged.clip.mediaReferences = baseline.clip.mediaReferences;
unchanged.clip.firstFrame = baseline.clip.firstFrame;
unchanged.clip.stepSize = baseline.clip.stepSize;
Expand Down
27 changes: 25 additions & 2 deletions tests/corpus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1216,5 +1216,28 @@ Tracker opens the actual `.trz`, saves through its normal application saver and
process/private home. The normal persistent cache policy is required in both reports. Every source
movie byte must remain available after saving and again after fresh reopen, including excluded frames;
independent pixels, frame ordinals, timestamps, geometry, mass and untouched content remain checked.
Native reimport of Tracker's resave closes the loop. These new modes still require their current-head
Linux application run and three-platform CI/review; native or mocked oracle checks are not that evidence.
Native reimport of Tracker's resave closes the loop. All three modes passed actual Tracker acceptance in
[PR #141](https://github.com/mgradwohl/MoTrack/pull/141) at reviewed revision `d0e1a66`, with all six checks
and **95.83% combined coverage (8931/9320)** in [CI 42583275559](https://github.com/mgradwohl/MoTrack/actions/runs/42583275559).

### Fixed movie calibration saving

`linked-movie-calibration`, `archive-movie-calibration`, `paired-archive-movie-calibration` and
`packaged-movie-calibration` reuse the pinned AVI/MOV sources and unchanged asset hashes. The native
probe edits mass/mark 1, then uses the real axes and distance commands at original frames 1 and 2.
Undo twice restores the previous project; redo twice restores the edited project. The commands move
the fixed frame-0 origin to (10,18), rotate it to 90 degrees, change nonuniform scales to (4,8), and
set the length label to `cm`. Native save/reopen and original-source removal precede standard output;
the paired archive mode interrupts and recovers both outputs, and packaging removes external media.

Independent expected coordinates use `X=(18-y)/8`, `Y=(10-x)/4`. Image marks (4,16), (8,12), (12,4)
must become (0.25,1.5), (0.75,0.5), (1.75,-0.5); probe point (13.5,17.25) must become
(0.09375,-0.875). XML comparison permits only the five coordinate values, length label and previously
specified mass/mark/path edits. Movie bytes, all frame timestamps, source-frame identities, axes display
settings and the second archive experiment remain independently checked. Negative tests corrupt origin,
rotation, scales, label or calculated positions, including the same corruption at every application stage.

The normal Linux application runner requires Tracker load, save, fresh-process reopen and native
reimport for all four modes. Local Windows native export/oracle/reimport passes; current-revision Linux
acceptance and code review remain pending. This slice does not accept keyed/partly fixed movie
calibration, timing edits, filters or broader movie containers, and does not close MoTrack #78.
3 changes: 3 additions & 0 deletions tests/corpus/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@
"xuggle-xuggler-server-all-5.7.0-20210422.000415-2.jar",
"slf4j-api-1.7.30.jar"
],
"native_archive_movie_calibration_roundtrip": true,
"native_archive_movie_roundtrip": true,
"project_member": "experiment.trk",
"session_archive": true,
Expand All @@ -163,6 +164,7 @@
},
{
"id": "linked-mov",
"native_movie_package_calibration_roundtrip": true,
"native_movie_package_roundtrip": true,
"tracker_version": "6.3.5",
"file": "linked-movie.trk",
Expand Down Expand Up @@ -1700,6 +1702,7 @@
},
{
"id": "linked-movie",
"native_linked_movie_calibration_roundtrip": true,
"native_linked_movie_roundtrip": true,
"native_linked_movie_clip_roundtrip": true,
"tracker_version": "6.3.5",
Expand Down
26 changes: 22 additions & 4 deletions tests/corpus_probe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,18 @@ void saveAndRecoverPair(const QString& path, const motrack::WorkspaceDocument& d
throw motrack::ProjectError("Recovered native companion changed the edited project or original source.");
}

void editMovieCalibration(motrack::WorkspaceDocument& document, motrack::TrackEdits& edits)
{
const auto before = document.project;
if (!edits.setCoordinateAxes(document.project, {.frame = 1, .originX = 10, .originY = 18, .positiveX = 10, .positiveY = 10}) ||
!edits.calibrateDistance(document.project, {.frame = 2, .imageDeltaX = 8, .imageDeltaY = 0, .knownLength = 2, .unit = "cm"}))
throw motrack::ProjectError("Could not calibrate the movie reference.");
const auto edited = document.project;
if (!edits.undo(document.project) || !edits.undo(document.project) || document.project != before || !edits.redo(document.project) ||
!edits.redo(document.project) || document.project != edited)
throw motrack::ProjectError("Movie calibration history did not preserve the experiment.");
}

} // namespace

int main(int argc, char** argv)
Expand All @@ -203,7 +215,8 @@ try
if (args.size() == 4 && (args[1] == "--export-archive-images" || args[1] == "--export-paired-archive-images" ||
args[1] == "--export-multitab-archive-images" || args[1] == "--export-archive-image-clip" ||
args[1] == "--export-archive-movie" || args[1] == "--export-archive-movie-clip" ||
args[1] == "--export-archive-movie-clip-single" || args[1] == "--export-paired-archive-movie"))
args[1] == "--export-archive-movie-clip-single" || args[1] == "--export-paired-archive-movie" ||
args[1] == "--export-archive-movie-calibration" || args[1] == "--export-paired-archive-movie-calibration"))
{
const bool movie = args[1].contains("movie");
QTemporaryDir temporary;
Expand Down Expand Up @@ -239,6 +252,8 @@ try
!edits.markPointMass(document.project, 0, {.frame = 1, .x = 8.0, .y = 12.0}) || !edits.undo(document.project) ||
!edits.redo(document.project))
throw motrack::ProjectError("Could not edit archived media reference.");
if (args[1].endsWith("-movie-calibration"))
editMovieCalibration(document, edits);
if (args[1] == "--export-archive-image-clip" || args[1] == "--export-archive-movie-clip" ||
args[1] == "--export-archive-movie-clip-single")
{
Expand All @@ -253,15 +268,16 @@ try
if (!QFile::remove(source))
throw motrack::ProjectError("Could not remove original archive reference.");
document = motrack::readWorkspaceDocument(native);
if (args[1] == "--export-paired-archive-images" || args[1] == "--export-paired-archive-movie")
if (args[1] == "--export-paired-archive-images" || args[1].startsWith("--export-paired-archive-movie"))
saveAndRecoverPair(args[3], document, native);
else
motrack::writeTrackerArchiveDocument(args[3], document);
return 0;
}
if (args.size() == 4 && (args[1] == "--export-linked-movie" || args[1] == "--export-linked-movie-clip" ||
args[1] == "--export-linked-movie-clip-single" || args[1] == "--export-packaged-movie" ||
args[1] == "--export-packaged-movie-clip" || args[1] == "--export-packaged-movie-clip-single"))
args[1] == "--export-packaged-movie-clip" || args[1] == "--export-packaged-movie-clip-single" ||
args[1] == "--export-linked-movie-calibration" || args[1] == "--export-packaged-movie-calibration"))
{
const bool packaged = args[1].startsWith("--export-packaged-movie");
QTemporaryDir temporary;
Expand Down Expand Up @@ -293,7 +309,9 @@ try
!edits.markPointMass(document.project, 0, {.frame = 1, .x = 8.0, .y = 12.0}) || !edits.undo(document.project) ||
!edits.redo(document.project))
throw motrack::ProjectError("Could not edit linked-movie reference.");
if (args[1] != "--export-linked-movie" && args[1] != "--export-packaged-movie")
if (args[1].endsWith("-movie-calibration"))
editMovieCalibration(document, edits);
if (args[1].contains("-clip"))
{
const bool single = args[1].endsWith("-single");
if (!edits.setClipRange(document.project, single ? 2 : 1, single ? 2 : 3, single ? 1 : 2) || !edits.undo(document.project) ||
Expand Down
Loading
Loading