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
1 change: 1 addition & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
cleanup.start()
atexit.register(cleanup.stop)
atexit.register(app.extensions["video_job_manager"].shutdown)
atexit.register(app.extensions["motion_preview_manager"].shutdown)
app.run(debug=False)
3 changes: 3 additions & 0 deletions check.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ def repository_consistency() -> None:
Path("docs/storage.md"),
Path("docs/recovery.md"),
Path("docs/service-contract.md"),
Path("docs/motion-preview-clips.md"),
Path("glitchcraft/contracts/motion_preview.py"),
Path("glitchcraft/jobs/motion_preview.py"),
Path("glitchcraft/storage/contracts.py"),
Path("glitchcraft/storage/repository.py"),
Path("glitchcraft/version.py"),
Expand Down
10 changes: 10 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,13 @@ from selection and feeds the existing Recipe v2 `enabled` field. Metadata loads
startup, resolver requests are isolated per effect, and selection causes no API traffic.
Preview and schedule controllers retain their existing revision guards. A failed preview
keeps the previous object URL until a successful replacement is available.

# Runtime motion-preview boundary

Motion-preview clips are a separate runtime-only job domain. The factory owns a
bounded `MotionPreviewClipManager`, but package import starts no worker. Its
queue, active work, terminal snapshots, TTL/LRU cache, media leases, and H.264
files are outside the persistent repository and manifest. Recipe v2 compiles
once against the complete source timeline; verified seeking and absolute
source-frame indexes preserve equivalence with still preview and full
rendering. Shutdown cancels work and deletes every preview artifact.
15 changes: 15 additions & 0 deletions docs/interface-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,18 @@ Focus rings remain visible, hidden panels are inert, icon-free buttons use expli
labels, reduced motion is honored, and forced-colors mode receives simplified selection and
slider treatments. Screenshot and geometry review cover 1440 x 900, 1280 x 720,
1024 x 768, 768 x 1024, 390 x 844, and 360 x 800.

## Still and motion preview roles

The media surface exposes a semantic Still/Motion tablist. Still is selected
initially. Motion adds one bounded, aspect-preserving native video player plus
compact before/after controls, anchor/total context, explicit create/update,
contextual cancel, and progress details.

Motion never auto-renders from sliders or navigation. A changed source,
timestamp, recipe, window, or seed marks the last ready clip stale and keeps it
playable. Selection, disclosure, and mobile navigation are not recipe changes.
Empty, queued, processing, finalizing, verifying, ready, stale, failed,
canceled, and playback-error states retain stable geometry, text labels,
visible focus, reduced-motion behavior, and a phase-throttled polite live
region.
110 changes: 110 additions & 0 deletions docs/motion-preview-clips.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Motion-preview clips

GlitchCraft 0.4.0 adds short, silent motion previews without turning previews
into persistent outputs or full renders. Still preview remains the default. A
user explicitly switches to Motion, chooses a before/after window, and selects
Preview motion or Update motion.

## Contract and exact timeline

`POST /api/video-sources/{sourceId}/preview-clips` accepts strict motion-preview
contract v1:

- `contractVersion` must be `1`;
- `recipe` may be Recipe v1 or Recipe v2;
- `timestampSeconds` is a finite nonnegative anchor;
- `beforeSeconds` defaults to `1` and `afterSeconds` defaults to `2`;
- their combined duration is from `0.5` through `5` seconds; and
- unknown keys, client paths, output settings, and audio settings are rejected.

The source window is derived on the full source timeline. Its inclusive start is
`floor((anchor - before) × source rate)` and its exclusive end is
`ceil((anchor + after) × source rate)`, clipped at source boundaries without
shifting the other edge. At least two source frames must remain.

Recipe v2 is compiled once against the complete source timeline. Frames are
decoded and effects are evaluated with absolute source-frame indexes, before
downscaling. This makes the clip agree with still preview and full processing
for activation, envelopes, seeded schedules, and named variation. Recipe v1
also receives the absolute source-frame index.

Seeking is verified by frame position. A decoder that lands early is advanced
one frame at a time; one that lands late is retried from bounded earlier
positions. GlitchCraft fails with `inexact_seek` if it cannot prove alignment.
OpenCV can normalize variable-frame-rate media, so frame-exact behavior is
defined against the probed rational timeline and decoded frame order, not every
container presentation timestamp.

## Bounded output profile

Preview intermediates use source frame rate and contain only the selected
frames. The final MP4 is H.264, `yuv420p`, fast-start, and has no audio. Output
is never upscaled, preserves aspect ratio, uses even dimensions, fits within
960 × 720, and is capped at 30 fps. Verification rejects empty, oversized,
wrong-codec, wrong-pixel-format, audio-bearing, over-rate, over-dimension, or
materially wrong-duration results.

The API exposes status separately from media:

- `GET /api/motion-preview-clips/{clipId}` returns redacted state and telemetry;
- `POST /api/motion-preview-clips/{clipId}/cancel` requests cancellation;
- `DELETE /api/motion-preview-clips/{clipId}` cancels and forgets it; and
- `GET` or `HEAD /api/motion-preview-clips/{clipId}/media` streams ready MP4,
including one bounded byte range.

Status and media responses use `Cache-Control: no-store`. Public responses do
not expose filesystem paths, executable details, stderr, thread names, or cache
keys.

## Runtime lifecycle and cache

The lifecycle is:

`queued → preparing → processing → finalizing → verifying → ready`

`failed`, `canceled`, and `expired` are terminal. Cancellation is polled during
seek, every processed frame, and FFmpeg finalization. Creating the same
canonical source/recipe/window/profile request deduplicates active work and
reuses a ready entry.

All clip state and bytes are runtime-only under
`temporary/motion-preview-clips`. They never enter `manifest.json`, never create
an output record, and are requested for deletion on application shutdown.
Unleased files are removed immediately; an active streaming lease removes its
file when the response closes, with the next startup as the final cleanup
boundary after an abrupt exit. Construction removes abandoned files from a
previous process. The cache defaults to 15-minute TTL, eight ready entries, and
512 MiB. Ready entries are LRU-evicted by count or bytes. Streaming leases defer
deletion until the response closes. Deleting a source invalidates its queued,
active, and ready previews.

The manager defaults to one worker and a three-item waiting queue. This is a
separate bounded coordinator from persistent rendering; on a small local
machine, a preview and full render may still compete for CPU and disk. Increasing
preview concurrency is supported as an operator setting but is not an automatic
resource scheduler.

## Interface behavior

Motion creation is explicit—timestamp, window, seed, recipe, or source edits
only mark an existing clip stale. The previous ready clip remains playable
while an update runs or fails. Effect selection, inspector disclosure, and
responsive navigation do not mark it stale or make network requests.

The player is muted, looping, inline, and uses native controls. Queued through
verifying phases show concise progress; Cancel is available only while active.
Ready, stale, failed, canceled, and playback-error states remain visible as text
and do not rely on color. Polling has one request and one timer, uses bounded
backoff, aborts obsolete revisions, and stops for terminal states or reset.

Audio preview, persistent preview history, WebSockets, arbitrary clip export,
hardware encoding, presentation-timestamp remapping, shared CPU admission with
full jobs, and a draggable timeline remain deferred.

Full renders remain authoritative. Motion previews are lower-resolution review
artifacts, do not survive restart, do not preserve audio, and never become
library outputs. Current operations are independent decoded-frame effects.
Future stateful feedback, echo, trails, or optical-flow operations would require
a separately specified preroll/state-warmup contract before a mid-source clip
could claim the same equivalence. Timeline editing and codec datamoshing remain
separate deferred domains.
17 changes: 14 additions & 3 deletions docs/product-direction.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ scaling, and coherent named variation to those existing effects. The compiled
schedule API is a stable base for a future React timeline.

Stateful temporal feedback, optical flow, frame reordering, codec datamosh,
signal modeling, new effect algorithms, draggable keyframes, and preview clips
remain intentionally deferred. Temporal modulation changes independent decoded
frames and does not use prior-frame state.
signal modeling, new effect algorithms, draggable keyframes, audio previews,
and persistent preview history remain intentionally deferred. Temporal
modulation changes independent decoded frames and does not use prior-frame
state.

A future orchestration dashboard may discover and check GlitchCraft, ColorCraft,
and Web Video Optimizer through related contracts. It is not implemented here
Expand All @@ -45,3 +46,13 @@ contracts. It is a focused control-resolution layer presented through an effects
media-first preview, selected-effect inspector, and render dock in the current Flask and
JavaScript workspace. A full timeline, draggable events, manual-event UI, presets, React,
stateful temporal feedback, optical flow, and codec datamoshing remain future work.

# Motion-preview milestone

Version 0.4.0 adds explicit, short motion-preview clips as a bounded review tool.
They are silent runtime artifacts, not saved outputs, and retain the last valid
clip while settings become stale or an update fails. Stateful temporal
feedback, optical flow, frame reordering, codec datamosh, signal modeling, new
effect algorithms, draggable keyframes, persistent preview history, audio
preview, arbitrary preview export, and shared global resource scheduling remain
deferred.
15 changes: 15 additions & 0 deletions docs/service-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,18 @@ timeline editing.

Stable capability slugs are `progressive-effect-controls`, `basic-effect-intensity`,
`effect-burst-range`, and `effect-pattern-regeneration`.

# Motion-preview discovery

Application version 0.4.0 adds motion-preview contract v1 and capability slugs
`motion-preview-clips`, `cancellable-motion-previews`, and
`ephemeral-preview-cache`. `/metadata` reports strict duration, output
profile, cache, queue, and endpoint bounds. `/ready` reports the preview manager
independently; a full preview queue does not make image work globally degraded.
Tool absence makes the capability unavailable without changing its implemented
status.

Motion-preview status and media are runtime-only and use `no-store`. Discovery
does not expose cache keys, local paths, process output, or worker identities.
See [motion-preview-clips.md](motion-preview-clips.md) for the endpoint and
lifecycle contract.
10 changes: 10 additions & 0 deletions docs/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,13 @@ migrated or rewritten. Recipe v2 jobs and outputs retain timing, envelope,
intensity, and variation configuration, but not compiled events or runtime RNG
state. Schedule compilation after restart is deterministic and does not add
per-frame persistence.

# Runtime-only preview storage

Motion-preview clips are not manifest storage. Their identifiers, states, cache
keys, telemetry, and media exist only in the owning process under
`temporary/motion-preview-clips`. Startup clears abandoned files; shutdown
clears current files. TTL and LRU enforce time, count, and byte limits, and
media leases prevent deletion during an active stream. A successful source
deletion invalidates associated preview work. Creation and eviction never write
the manifest, create output records, or alter persistent storage counts.
13 changes: 12 additions & 1 deletion docs/temporal-effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ stale responses. The previous valid still preview stays visible while updating.
Image mode stays Recipe v1.

This remains a transitional Flask/JavaScript interface. It has no draggable
timeline or complete manual-event editor, and preview remains a still frame.
timeline or complete manual-event editor. Still preview remains the default;
explicit bounded motion preview adds a short runtime-only review interval.
Future React timeline work can consume the schedule endpoint without changing
the recipe or compiler.

Expand All @@ -135,3 +136,13 @@ The Basic control resolver selects existing continuous or sporadic timing, envel
variation contracts without changing compilation. Exact schedules remain authoritative and
unchanged for identical canonical Recipe v2 fields and seeds. Basic burst bounds are exact
frames; the existing isolated RNG namespace selects deterministic event durations.

# Motion-preview equivalence

Motion-preview clips extend one-frame equivalence to a bounded source interval.
The window does not create a local timeline: Recipe v2 compiles once using full
source duration and total frames, and every operation receives the absolute
decoded source-frame index. Effects run at source resolution before the result
is downscaled and capped for delivery. Starting in the middle of a burst
therefore preserves event identity, envelope position, and variation values.
Recipe v1 also retains its absolute seeded frame behavior.
35 changes: 35 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,38 @@ On the PR development machine, 80,000 individual resolutions completed in 4.454
80,000 inferences completed in 20.133 seconds (251.658 µs per effect; 2.013 ms per batch of
eight). Resolution performs no media decoding, FFmpeg work, or schedule compilation, and
resolved recipes add no render-time overhead.

# Motion-preview validation

Run the focused Python and browser coverage with:

```powershell
python -m pytest tests/test_motion_preview.py
npm run test:browser
npm run review:ui-screens
```

Motion-preview tests cover strict v1 validation, fractional frame boundaries,
verified exact seek, absolute source-frame execution, no-upscale geometry,
active/ready deduplication, queue capacity, cancellation, TTL, count/byte LRU
eviction, streaming leases, source invalidation, startup/shutdown cleanup,
no-manifest mutation, API redaction, byte ranges, capability metadata, and all
required interface states. Marked real-media coverage verifies the bounded
silent H.264 profile with FFmpeg and FFprobe when installed.

The deterministic UI review now generates 24 captures, adding motion empty,
queued, processing, ready, stale, failed, and canceled states across desktop,
medium, tablet, and mobile layouts. Every capture is manually inspected for
containment, hierarchy, readable status, and stable previous-media retention.

On the PR development machine, a representative no-effect three-second window
from a 1280 × 720, 30 fps source completed in 1.340 seconds; the first processed
frame was observed at 0.069 seconds. Ten thousand canonical cache-key builds
averaged 23.81 microseconds, ready lookups 18.80 microseconds, initial admission
337.10 microseconds, and a ready-cache submission 112.10 microseconds. One
actual ready-entry eviction took 218.90 microseconds. The redacted ready
snapshot serialized to 932 bytes and the test clip to 34,297 bytes. These are
representative diagnostics, not cross-platform timing guarantees. Bounds on
queue, workers, clip duration, dimensions, FPS, stderr, snapshots, terminal
history, cache entries, cache bytes, TTL, and media leases are the enforced
requirements.
14 changes: 14 additions & 0 deletions docs/video-jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,17 @@ The concise telemetry phrase continues counting enabled instances, not active
effects on one scheduled frame. Event-level changes do not create milestones or
manifest writes. Individual job and output metadata expose `recipeVersion` so
clients can distinguish legacy-continuous work from temporal work.

## Runtime motion-preview lifecycle

Motion previews do not use persistent video-job records. Their independent
bounded lifecycle is `queued → preparing → processing → finalizing → verifying
→ ready`, with `failed`, `canceled`, and `expired` terminal states. The default
coordinator has one worker and three waiting slots. Active requests deduplicate
by a canonical source/recipe/window/profile key; ready requests additionally
become cache hits.

Cancellation is cooperative during exact seek and frame processing and
terminates FFmpeg during finalization. Shutdown cancels all work and deletes all
ready outputs. This independent coordinator guarantees bounded preview work but
does not claim global CPU fairness with a simultaneous persistent render.
13 changes: 13 additions & 0 deletions docs/video-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,16 @@ Regenerating changes the shared root seed, not the source or effect fields. Sche
still-preview requests remain abortable and revision guarded; the previous valid preview
and schedule remain visible while replacements arrive. Jump-to-next uses inspected events
and does not change the seed or schedule.

# Still and motion preview

The media card has explicit Still and Motion roles. Still remains the default
and retains the existing debounced single-frame workflow. Motion uses a bounded
before/after window and starts only when the user selects Preview motion or
Update motion. It submits a strict runtime-only clip request, polls redacted
status, and plays the ready silent MP4 directly.

Recipe, timestamp, window, seed, and source changes mark the clip stale without
implicit processing; the previous ready clip remains available. See
[motion-preview-clips.md](motion-preview-clips.md) for frame conversion,
seeking, output, cache, and lifecycle contracts.
Loading
Loading