@pneuma-craft/video 0.4.0 → 0.5.0
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 +30 -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 +6 -2
package/dist/index.d.cts
CHANGED
|
@@ -134,6 +134,13 @@ interface PlaybackEngineOptions {
|
|
|
134
134
|
* omitted, subtitle tracks render nothing (legacy behavior).
|
|
135
135
|
*/
|
|
136
136
|
subtitleRenderer?: SubtitleRenderer;
|
|
137
|
+
/**
|
|
138
|
+
* Render preview frames (planning-layer visuals attached to time points
|
|
139
|
+
* on a track) when no real clip covers the current time. Defaults to
|
|
140
|
+
* `true` for the playback engine — the user scrubbing the timeline
|
|
141
|
+
* should see the agent's planned vibe before any real video clips land.
|
|
142
|
+
*/
|
|
143
|
+
includePreviewFrames?: boolean;
|
|
137
144
|
}
|
|
138
145
|
declare function createPlaybackEngine(options?: PlaybackEngineOptions): PlaybackEngine;
|
|
139
146
|
|
|
@@ -144,10 +151,30 @@ interface ExportEngineOptions {
|
|
|
144
151
|
* preview. When omitted, subtitle tracks are skipped during export.
|
|
145
152
|
*/
|
|
146
153
|
subtitleRenderer?: SubtitleRenderer;
|
|
154
|
+
/**
|
|
155
|
+
* Include preview frames (planning-layer visuals attached to time points
|
|
156
|
+
* on a track) in the exported video. Defaults to `false` because an export
|
|
157
|
+
* normally represents a finished cut. Set true for review-grade exports of
|
|
158
|
+
* unfinished timelines — e.g., agent producing a "草样片" for the user to
|
|
159
|
+
* scrub before committing to expensive real renders.
|
|
160
|
+
*/
|
|
161
|
+
includePreviewFrames?: boolean;
|
|
147
162
|
}
|
|
148
163
|
declare function createExportEngine(options?: ExportEngineOptions): ExportEngine;
|
|
149
164
|
|
|
150
|
-
|
|
165
|
+
interface CreateFrameRendererOptions {
|
|
166
|
+
readonly subtitleRenderer?: SubtitleRenderer;
|
|
167
|
+
/**
|
|
168
|
+
* Render preview frames in tracks where no real clip covers the current
|
|
169
|
+
* time. Defaults to false — preview frames are a planning-layer visual
|
|
170
|
+
* that should NOT appear in final exports unless explicitly requested.
|
|
171
|
+
* Engines wire their own defaults: PlaybackEngine passes true, ExportEngine
|
|
172
|
+
* passes false.
|
|
173
|
+
*/
|
|
174
|
+
readonly includePreviewFrames?: boolean;
|
|
175
|
+
}
|
|
176
|
+
type LegacyArg = SubtitleRenderer | CreateFrameRendererOptions | undefined;
|
|
177
|
+
declare function createFrameRenderer(decoder: MediaDecoder, compositor: Compositor, width: number, height: number, optionsOrLegacySubtitleRenderer?: LegacyArg): FrameRenderer;
|
|
151
178
|
|
|
152
179
|
interface MasterClockOptions {
|
|
153
180
|
audioContext: AudioContext;
|
package/dist/index.d.ts
CHANGED
|
@@ -134,6 +134,13 @@ interface PlaybackEngineOptions {
|
|
|
134
134
|
* omitted, subtitle tracks render nothing (legacy behavior).
|
|
135
135
|
*/
|
|
136
136
|
subtitleRenderer?: SubtitleRenderer;
|
|
137
|
+
/**
|
|
138
|
+
* Render preview frames (planning-layer visuals attached to time points
|
|
139
|
+
* on a track) when no real clip covers the current time. Defaults to
|
|
140
|
+
* `true` for the playback engine — the user scrubbing the timeline
|
|
141
|
+
* should see the agent's planned vibe before any real video clips land.
|
|
142
|
+
*/
|
|
143
|
+
includePreviewFrames?: boolean;
|
|
137
144
|
}
|
|
138
145
|
declare function createPlaybackEngine(options?: PlaybackEngineOptions): PlaybackEngine;
|
|
139
146
|
|
|
@@ -144,10 +151,30 @@ interface ExportEngineOptions {
|
|
|
144
151
|
* preview. When omitted, subtitle tracks are skipped during export.
|
|
145
152
|
*/
|
|
146
153
|
subtitleRenderer?: SubtitleRenderer;
|
|
154
|
+
/**
|
|
155
|
+
* Include preview frames (planning-layer visuals attached to time points
|
|
156
|
+
* on a track) in the exported video. Defaults to `false` because an export
|
|
157
|
+
* normally represents a finished cut. Set true for review-grade exports of
|
|
158
|
+
* unfinished timelines — e.g., agent producing a "草样片" for the user to
|
|
159
|
+
* scrub before committing to expensive real renders.
|
|
160
|
+
*/
|
|
161
|
+
includePreviewFrames?: boolean;
|
|
147
162
|
}
|
|
148
163
|
declare function createExportEngine(options?: ExportEngineOptions): ExportEngine;
|
|
149
164
|
|
|
150
|
-
|
|
165
|
+
interface CreateFrameRendererOptions {
|
|
166
|
+
readonly subtitleRenderer?: SubtitleRenderer;
|
|
167
|
+
/**
|
|
168
|
+
* Render preview frames in tracks where no real clip covers the current
|
|
169
|
+
* time. Defaults to false — preview frames are a planning-layer visual
|
|
170
|
+
* that should NOT appear in final exports unless explicitly requested.
|
|
171
|
+
* Engines wire their own defaults: PlaybackEngine passes true, ExportEngine
|
|
172
|
+
* passes false.
|
|
173
|
+
*/
|
|
174
|
+
readonly includePreviewFrames?: boolean;
|
|
175
|
+
}
|
|
176
|
+
type LegacyArg = SubtitleRenderer | CreateFrameRendererOptions | undefined;
|
|
177
|
+
declare function createFrameRenderer(decoder: MediaDecoder, compositor: Compositor, width: number, height: number, optionsOrLegacySubtitleRenderer?: LegacyArg): FrameRenderer;
|
|
151
178
|
|
|
152
179
|
interface MasterClockOptions {
|
|
153
180
|
audioContext: AudioContext;
|
package/dist/index.js
CHANGED
|
@@ -436,31 +436,58 @@ async function createCompositor(width, height, type = "auto") {
|
|
|
436
436
|
|
|
437
437
|
// src/frame-renderer.ts
|
|
438
438
|
import { resolveFrame } from "@pneuma-craft/timeline";
|
|
439
|
-
function
|
|
439
|
+
function normalizeOptions(arg) {
|
|
440
|
+
if (arg === void 0) return {};
|
|
441
|
+
if (typeof arg === "function") return { subtitleRenderer: arg };
|
|
442
|
+
return arg;
|
|
443
|
+
}
|
|
444
|
+
function createFrameRenderer(decoder, compositor, width, height, optionsOrLegacySubtitleRenderer) {
|
|
445
|
+
const options = normalizeOptions(optionsOrLegacySubtitleRenderer);
|
|
446
|
+
const { subtitleRenderer, includePreviewFrames = false } = options;
|
|
440
447
|
return {
|
|
441
448
|
async renderFrame(composition, time) {
|
|
442
449
|
const resolved = resolveFrame(composition, time);
|
|
443
|
-
const
|
|
444
|
-
const
|
|
450
|
+
const clipsByTrack = /* @__PURE__ */ new Map();
|
|
451
|
+
for (const rc of resolved.clips) {
|
|
452
|
+
const arr = clipsByTrack.get(rc.track.id);
|
|
453
|
+
if (arr) arr.push(rc);
|
|
454
|
+
else clipsByTrack.set(rc.track.id, [rc]);
|
|
455
|
+
}
|
|
456
|
+
const previewByTrack = /* @__PURE__ */ new Map();
|
|
457
|
+
for (const rpf of resolved.previewFrames) previewByTrack.set(rpf.track.id, rpf);
|
|
445
458
|
const layers = [];
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
+
let zIndex = 0;
|
|
460
|
+
for (const track of composition.tracks) {
|
|
461
|
+
if (track.type !== "video") continue;
|
|
462
|
+
const trackClips = clipsByTrack.get(track.id);
|
|
463
|
+
if (trackClips && trackClips.length > 0) {
|
|
464
|
+
for (const rc of trackClips) {
|
|
465
|
+
const source = await decoder.decodeVideoFrame(
|
|
466
|
+
rc.clip.assetId,
|
|
467
|
+
rc.localTime,
|
|
468
|
+
width,
|
|
469
|
+
height
|
|
470
|
+
);
|
|
471
|
+
layers.push({ source, opacity: 1, zIndex: zIndex++ });
|
|
472
|
+
}
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (includePreviewFrames) {
|
|
476
|
+
const rpf = previewByTrack.get(track.id);
|
|
477
|
+
if (rpf) {
|
|
478
|
+
const source = await decoder.decodeVideoFrame(
|
|
479
|
+
rpf.previewFrame.assetId,
|
|
480
|
+
0,
|
|
481
|
+
width,
|
|
482
|
+
height
|
|
483
|
+
);
|
|
484
|
+
layers.push({ source, opacity: 1, zIndex: zIndex++ });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
459
487
|
}
|
|
460
|
-
if (subtitleRenderer
|
|
461
|
-
const
|
|
462
|
-
for (
|
|
463
|
-
const rc = subtitleClips[i];
|
|
488
|
+
if (subtitleRenderer) {
|
|
489
|
+
const subtitleClips = resolved.clips.filter((rc) => rc.track.type === "subtitle");
|
|
490
|
+
for (const rc of subtitleClips) {
|
|
464
491
|
const rendered = await subtitleRenderer({
|
|
465
492
|
clip: rc.clip,
|
|
466
493
|
localTime: rc.localTime,
|
|
@@ -468,11 +495,7 @@ function createFrameRenderer(decoder, compositor, width, height, subtitleRendere
|
|
|
468
495
|
height
|
|
469
496
|
});
|
|
470
497
|
if (rendered) {
|
|
471
|
-
layers.push({
|
|
472
|
-
source: rendered,
|
|
473
|
-
opacity: 1,
|
|
474
|
-
zIndex: baseZ + i
|
|
475
|
-
});
|
|
498
|
+
layers.push({ source: rendered, opacity: 1, zIndex: zIndex++ });
|
|
476
499
|
}
|
|
477
500
|
}
|
|
478
501
|
}
|
|
@@ -873,6 +896,7 @@ function createPlaybackEngine(options) {
|
|
|
873
896
|
const compositorType = options?.compositorType ?? "canvas2d";
|
|
874
897
|
const decodeTimeoutMs = options?.decodeTimeoutMs ?? DEFAULT_DECODE_TIMEOUT_MS;
|
|
875
898
|
const subtitleRenderer = options?.subtitleRenderer;
|
|
899
|
+
const includePreviewFrames = options?.includePreviewFrames ?? true;
|
|
876
900
|
let _state = "idle";
|
|
877
901
|
let _playbackRate = 1;
|
|
878
902
|
let _loop = null;
|
|
@@ -1041,7 +1065,7 @@ function createPlaybackEngine(options) {
|
|
|
1041
1065
|
_compositor,
|
|
1042
1066
|
composition.settings.width,
|
|
1043
1067
|
composition.settings.height,
|
|
1044
|
-
subtitleRenderer
|
|
1068
|
+
{ subtitleRenderer, includePreviewFrames }
|
|
1045
1069
|
);
|
|
1046
1070
|
_clock = createMasterClock({
|
|
1047
1071
|
audioContext: _audioContext,
|
|
@@ -1207,6 +1231,7 @@ import {
|
|
|
1207
1231
|
} from "mediabunny";
|
|
1208
1232
|
function createExportEngine(options) {
|
|
1209
1233
|
const subtitleRenderer = options?.subtitleRenderer;
|
|
1234
|
+
const includePreviewFrames = options?.includePreviewFrames ?? false;
|
|
1210
1235
|
const progressListeners = /* @__PURE__ */ new Set();
|
|
1211
1236
|
let abortController = null;
|
|
1212
1237
|
function notifyProgress(progress) {
|
|
@@ -1229,7 +1254,10 @@ function createExportEngine(options) {
|
|
|
1229
1254
|
const decoderAudioCtx = new OfflineAudioContext(1, 1, composition.settings.sampleRate || 48e3);
|
|
1230
1255
|
const decoder = createMediaDecoder(resolver, decoderAudioCtx);
|
|
1231
1256
|
const compositor = await createCompositor(width, height, "canvas2d");
|
|
1232
|
-
const renderer = createFrameRenderer(decoder, compositor, width, height,
|
|
1257
|
+
const renderer = createFrameRenderer(decoder, compositor, width, height, {
|
|
1258
|
+
subtitleRenderer,
|
|
1259
|
+
includePreviewFrames
|
|
1260
|
+
});
|
|
1233
1261
|
try {
|
|
1234
1262
|
const format = options2.format === "webm" ? new WebMOutputFormat() : new Mp4OutputFormat({ fastStart: "in-memory" });
|
|
1235
1263
|
const target = new BufferTarget();
|