@pneuma-craft/video 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/dist/index.cjs +55 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +55 -27
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- eecee02: Republish to fix stale internal dependency refs. The previous publish (`@pneuma-craft/video@0.5.0`) declared `@pneuma-craft/timeline@0.3.0` instead of `@pneuma-craft/timeline@0.4.0`, because `bun pm pack` rewrote `workspace:*` against a lockfile that `bun install --no-frozen-lockfile` had not refreshed. The CI release script now nukes `bun.lock` before re-installing, so the lockfile mirrors the bumped versions and `bun pm pack` resolves to the correct ones.
|
|
8
|
+
|
|
9
|
+
`@pneuma-craft/react` is auto-patched by changesets because it depends on `@pneuma-craft/video`; the same fix flows through to its own published deps (`timeline@0.4.0`, `video@0.5.1`).
|
|
10
|
+
|
|
11
|
+
## 0.5.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- c7a09ae: Add **PreviewFrame**, a planning-layer visual attached to a track at a single time point that lets go (does not render) when a real clip on the same track covers that moment. Powers the AIGC progressive-fidelity workflow (cheap sketch → upgraded anchor → real clip) without diluting `Clip` semantics.
|
|
16
|
+
|
|
17
|
+
`@pneuma-craft/timeline` (additive)
|
|
18
|
+
|
|
19
|
+
- New types: `PreviewFrame`, `ResolvedPreviewFrame`, `ResolvedFrame.previewFrames[]`
|
|
20
|
+
- `Track` gains a sorted `previewFrames: PreviewFrame[]` array
|
|
21
|
+
- New commands (mirroring clip ops): `composition:add-preview-frame`, `remove-preview-frame`, `move-preview-frame`, `rebind-preview-frame`
|
|
22
|
+
- Matching events with undo compensations
|
|
23
|
+
- `resolveFrame()` applies a per-track let-go rule (clip wins over preview at the same time on the same track)
|
|
24
|
+
- `recomputeDuration()` extends to the last preview-frame time
|
|
25
|
+
- New utility `buildSetPreviewFrameCommand()` for agent upsert ergonomics
|
|
26
|
+
- `composition:add-track` now accepts `previewFrames` as optional in its input shape (defaults to `[]`)
|
|
27
|
+
|
|
28
|
+
`@pneuma-craft/video` (additive + small internal signature)
|
|
29
|
+
|
|
30
|
+
- `createFrameRenderer`'s 5th argument now accepts `{ subtitleRenderer?, includePreviewFrames? }`. The pre-existing positional `SubtitleRenderer` form is detected and normalized — existing call sites continue to compile.
|
|
31
|
+
- `PlaybackEngineOptions.includePreviewFrames` (defaults to `true`) — playback shows planning visuals
|
|
32
|
+
- `ExportEngineOptions.includePreviewFrames` (defaults to `false`) — export = finished cut by default; opt-in for review-grade exports of unfinished timelines
|
|
33
|
+
|
|
34
|
+
See `docs/specs/2026-05-09-preview-frame-design.md` for the full design and `docs/recipes/preview-frames.md` for an integration guide.
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [c7a09ae]
|
|
39
|
+
- @pneuma-craft/timeline@0.4.0
|
|
40
|
+
|
|
3
41
|
All notable changes to `@pneuma-craft/video` will be documented in this file.
|
|
4
42
|
|
|
5
43
|
## 0.4.0
|
package/dist/index.cjs
CHANGED
|
@@ -471,31 +471,58 @@ async function createCompositor(width, height, type = "auto") {
|
|
|
471
471
|
|
|
472
472
|
// src/frame-renderer.ts
|
|
473
473
|
var import_timeline = require("@pneuma-craft/timeline");
|
|
474
|
-
function
|
|
474
|
+
function normalizeOptions(arg) {
|
|
475
|
+
if (arg === void 0) return {};
|
|
476
|
+
if (typeof arg === "function") return { subtitleRenderer: arg };
|
|
477
|
+
return arg;
|
|
478
|
+
}
|
|
479
|
+
function createFrameRenderer(decoder, compositor, width, height, optionsOrLegacySubtitleRenderer) {
|
|
480
|
+
const options = normalizeOptions(optionsOrLegacySubtitleRenderer);
|
|
481
|
+
const { subtitleRenderer, includePreviewFrames = false } = options;
|
|
475
482
|
return {
|
|
476
483
|
async renderFrame(composition, time) {
|
|
477
484
|
const resolved = (0, import_timeline.resolveFrame)(composition, time);
|
|
478
|
-
const
|
|
479
|
-
const
|
|
485
|
+
const clipsByTrack = /* @__PURE__ */ new Map();
|
|
486
|
+
for (const rc of resolved.clips) {
|
|
487
|
+
const arr = clipsByTrack.get(rc.track.id);
|
|
488
|
+
if (arr) arr.push(rc);
|
|
489
|
+
else clipsByTrack.set(rc.track.id, [rc]);
|
|
490
|
+
}
|
|
491
|
+
const previewByTrack = /* @__PURE__ */ new Map();
|
|
492
|
+
for (const rpf of resolved.previewFrames) previewByTrack.set(rpf.track.id, rpf);
|
|
480
493
|
const layers = [];
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
+
let zIndex = 0;
|
|
495
|
+
for (const track of composition.tracks) {
|
|
496
|
+
if (track.type !== "video") continue;
|
|
497
|
+
const trackClips = clipsByTrack.get(track.id);
|
|
498
|
+
if (trackClips && trackClips.length > 0) {
|
|
499
|
+
for (const rc of trackClips) {
|
|
500
|
+
const source = await decoder.decodeVideoFrame(
|
|
501
|
+
rc.clip.assetId,
|
|
502
|
+
rc.localTime,
|
|
503
|
+
width,
|
|
504
|
+
height
|
|
505
|
+
);
|
|
506
|
+
layers.push({ source, opacity: 1, zIndex: zIndex++ });
|
|
507
|
+
}
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (includePreviewFrames) {
|
|
511
|
+
const rpf = previewByTrack.get(track.id);
|
|
512
|
+
if (rpf) {
|
|
513
|
+
const source = await decoder.decodeVideoFrame(
|
|
514
|
+
rpf.previewFrame.assetId,
|
|
515
|
+
0,
|
|
516
|
+
width,
|
|
517
|
+
height
|
|
518
|
+
);
|
|
519
|
+
layers.push({ source, opacity: 1, zIndex: zIndex++ });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
494
522
|
}
|
|
495
|
-
if (subtitleRenderer
|
|
496
|
-
const
|
|
497
|
-
for (
|
|
498
|
-
const rc = subtitleClips[i];
|
|
523
|
+
if (subtitleRenderer) {
|
|
524
|
+
const subtitleClips = resolved.clips.filter((rc) => rc.track.type === "subtitle");
|
|
525
|
+
for (const rc of subtitleClips) {
|
|
499
526
|
const rendered = await subtitleRenderer({
|
|
500
527
|
clip: rc.clip,
|
|
501
528
|
localTime: rc.localTime,
|
|
@@ -503,11 +530,7 @@ function createFrameRenderer(decoder, compositor, width, height, subtitleRendere
|
|
|
503
530
|
height
|
|
504
531
|
});
|
|
505
532
|
if (rendered) {
|
|
506
|
-
layers.push({
|
|
507
|
-
source: rendered,
|
|
508
|
-
opacity: 1,
|
|
509
|
-
zIndex: baseZ + i
|
|
510
|
-
});
|
|
533
|
+
layers.push({ source: rendered, opacity: 1, zIndex: zIndex++ });
|
|
511
534
|
}
|
|
512
535
|
}
|
|
513
536
|
}
|
|
@@ -908,6 +931,7 @@ function createPlaybackEngine(options) {
|
|
|
908
931
|
const compositorType = options?.compositorType ?? "canvas2d";
|
|
909
932
|
const decodeTimeoutMs = options?.decodeTimeoutMs ?? DEFAULT_DECODE_TIMEOUT_MS;
|
|
910
933
|
const subtitleRenderer = options?.subtitleRenderer;
|
|
934
|
+
const includePreviewFrames = options?.includePreviewFrames ?? true;
|
|
911
935
|
let _state = "idle";
|
|
912
936
|
let _playbackRate = 1;
|
|
913
937
|
let _loop = null;
|
|
@@ -1076,7 +1100,7 @@ function createPlaybackEngine(options) {
|
|
|
1076
1100
|
_compositor,
|
|
1077
1101
|
composition.settings.width,
|
|
1078
1102
|
composition.settings.height,
|
|
1079
|
-
subtitleRenderer
|
|
1103
|
+
{ subtitleRenderer, includePreviewFrames }
|
|
1080
1104
|
);
|
|
1081
1105
|
_clock = createMasterClock({
|
|
1082
1106
|
audioContext: _audioContext,
|
|
@@ -1235,6 +1259,7 @@ function createOfflineAudioRenderer() {
|
|
|
1235
1259
|
var import_mediabunny2 = require("mediabunny");
|
|
1236
1260
|
function createExportEngine(options) {
|
|
1237
1261
|
const subtitleRenderer = options?.subtitleRenderer;
|
|
1262
|
+
const includePreviewFrames = options?.includePreviewFrames ?? false;
|
|
1238
1263
|
const progressListeners = /* @__PURE__ */ new Set();
|
|
1239
1264
|
let abortController = null;
|
|
1240
1265
|
function notifyProgress(progress) {
|
|
@@ -1257,7 +1282,10 @@ function createExportEngine(options) {
|
|
|
1257
1282
|
const decoderAudioCtx = new OfflineAudioContext(1, 1, composition.settings.sampleRate || 48e3);
|
|
1258
1283
|
const decoder = createMediaDecoder(resolver, decoderAudioCtx);
|
|
1259
1284
|
const compositor = await createCompositor(width, height, "canvas2d");
|
|
1260
|
-
const renderer = createFrameRenderer(decoder, compositor, width, height,
|
|
1285
|
+
const renderer = createFrameRenderer(decoder, compositor, width, height, {
|
|
1286
|
+
subtitleRenderer,
|
|
1287
|
+
includePreviewFrames
|
|
1288
|
+
});
|
|
1261
1289
|
try {
|
|
1262
1290
|
const format = options2.format === "webm" ? new import_mediabunny2.WebMOutputFormat() : new import_mediabunny2.Mp4OutputFormat({ fastStart: "in-memory" });
|
|
1263
1291
|
const target = new import_mediabunny2.BufferTarget();
|