Automated release: version and notes generated from pull requests merged since 3.6.3.
Fixed
-
A file put at the output path during a run is no longer replaced where the tool replaces it
after a check (#470). Whether an existing
target may be replaced was decided at the output check, before the encode, and the replace
after it did not look again: a file another process saved there meanwhile (over a file
batch.py --watchhad delivered, or at a path that was empty) was replaced.run(),
place_output()and the files export.py and silence.py replace themselves (the fill beside a
key,--normalize,--timemap) now compare the target's identity under the output lock right
before the replace and refuse a changed or new file (kind: input,reason: output_exists),
removing the new output's temp. A fill and its key are both checked before either is replaced,
and the fill is put back if the key's replace then fails; a symlink created at an absent
target, or a directory link switched on the way to it, counts as a different file.
--overwritestill replaces whatever is at the path. Not covered: a process that ignores the
lock file and writes the target in the instant between the last comparison and the replace; a
target that did not exist, created between that comparison and ffmpeg opening it (ffmpeg
writes it directly); and side files written with a plain open (subtitles, EDL, chapter lists),
which are not compared again. -
sequence.pykeeps every frame on its frame grid in a long sequence (#474). Each image was
written to the concat list asduration 0.033333at 30 fps, 3.3e-7 s short of 1/30, and the
demuxer adds the durations up: after about 50,000 frames (28 minutes) the running total was half a
frame behind and a frame could be dropped or repeated; 29.97 and 23.976 drifted the same way.
Each duration is now the difference of two frame positions at the exact rate, each rounded once
to the microsecond, so the total stays within half a microsecond of the frame's time.--fps 29.97
and23.976are read as 30000/1001 and 24000/1001, as the other tools read them, and the output
is tagged at that rate. Each image is also read at that rate (option framerateper concat
entry), not the image demuxer's default 25: two images could land on one input timestamp (at
29.97 frames 3 and 4; at 120 fps a third of them) and the fps filter repeated one and dropped the
other, with the frame count still right. Test:
test_sequence_keeps_every_picture_in_order_at_a_high_rate.
Changed
- A version that was tagged but never reached npm can be finished from CI (#448). A release
whose npm publish failed kept its tag, and a re-run skipped the publish because the tag existed;
the only way out was a hand publish with no provenance.release.ymlnow takes a
workflow_dispatchwith aversion; dispatched on the tag
(gh workflow run release.yml --ref vX.Y.Z -f version=X.Y.Z), itsrecoverjob checks out the
tag, refuses a malformed version, a missing tag, a run on another commit and a version npm
already has, and publishes with--provenanceunderlatestwhen the version is above every
stable version on npm andbackportotherwise (.github/scripts/recover_release.py). It then
publishes thepluginbranch when it has no commit for that version and its tip is older (a
backport leaves a newer branch alone), and the MCP Registry entry when it is missing (never for a
backport). A tag cut before this job existed (every tag up to v3.6.3) has norecoverjob at its
commit, so dispatching on it cannot recover that version. Test:
test_release_recovery_publishes_only_a_tagged_version_npm_lacks. - fix: recheck outputs under the lock, sequence frame grid, release recovery dispatch (#480)
What's Changed
- fix: recheck outputs under the lock, sequence frame grid, release recovery dispatch by @kajisho5 in #480
Full Changelog: v3.6.3...v3.6.4