@nodetool-ai/video-nodes 0.8.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/README.md +58 -0
- package/dist/.nodetool-build-stamp +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/nodes/ffmpeg-helpers.d.ts +77 -0
- package/dist/nodes/ffmpeg-helpers.d.ts.map +1 -0
- package/dist/nodes/ffmpeg-helpers.js +220 -0
- package/dist/nodes/ffmpeg-helpers.js.map +1 -0
- package/dist/nodes/lib-video-download.d.ts +29 -0
- package/dist/nodes/lib-video-download.d.ts.map +1 -0
- package/dist/nodes/lib-video-download.js +353 -0
- package/dist/nodes/lib-video-download.js.map +1 -0
- package/dist/nodes/model3d/analysis.d.ts +16 -0
- package/dist/nodes/model3d/analysis.d.ts.map +1 -0
- package/dist/nodes/model3d/analysis.js +38 -0
- package/dist/nodes/model3d/analysis.js.map +1 -0
- package/dist/nodes/model3d/base.d.ts +28 -0
- package/dist/nodes/model3d/base.d.ts.map +1 -0
- package/dist/nodes/model3d/base.js +20 -0
- package/dist/nodes/model3d/base.js.map +1 -0
- package/dist/nodes/model3d/boolean-ops.d.ts +2 -0
- package/dist/nodes/model3d/boolean-ops.d.ts.map +1 -0
- package/dist/nodes/model3d/boolean-ops.js +172 -0
- package/dist/nodes/model3d/boolean-ops.js.map +1 -0
- package/dist/nodes/model3d/cleanup.d.ts +56 -0
- package/dist/nodes/model3d/cleanup.d.ts.map +1 -0
- package/dist/nodes/model3d/cleanup.js +144 -0
- package/dist/nodes/model3d/cleanup.js.map +1 -0
- package/dist/nodes/model3d/convert.d.ts +27 -0
- package/dist/nodes/model3d/convert.d.ts.map +1 -0
- package/dist/nodes/model3d/convert.js +66 -0
- package/dist/nodes/model3d/convert.js.map +1 -0
- package/dist/nodes/model3d/defaults.d.ts +34 -0
- package/dist/nodes/model3d/defaults.d.ts.map +1 -0
- package/dist/nodes/model3d/defaults.js +28 -0
- package/dist/nodes/model3d/defaults.js.map +1 -0
- package/dist/nodes/model3d/document-ops.d.ts +4 -0
- package/dist/nodes/model3d/document-ops.d.ts.map +1 -0
- package/dist/nodes/model3d/document-ops.js +40 -0
- package/dist/nodes/model3d/document-ops.js.map +1 -0
- package/dist/nodes/model3d/generation.d.ts +56 -0
- package/dist/nodes/model3d/generation.d.ts.map +1 -0
- package/dist/nodes/model3d/generation.js +203 -0
- package/dist/nodes/model3d/generation.js.map +1 -0
- package/dist/nodes/model3d/glb.d.ts +29 -0
- package/dist/nodes/model3d/glb.d.ts.map +1 -0
- package/dist/nodes/model3d/glb.js +255 -0
- package/dist/nodes/model3d/glb.js.map +1 -0
- package/dist/nodes/model3d/index.d.ts +22 -0
- package/dist/nodes/model3d/index.d.ts.map +1 -0
- package/dist/nodes/model3d/index.js +41 -0
- package/dist/nodes/model3d/index.js.map +1 -0
- package/dist/nodes/model3d/io.d.ts +81 -0
- package/dist/nodes/model3d/io.d.ts.map +1 -0
- package/dist/nodes/model3d/io.js +162 -0
- package/dist/nodes/model3d/io.js.map +1 -0
- package/dist/nodes/model3d/mesh-ops.d.ts +40 -0
- package/dist/nodes/model3d/mesh-ops.d.ts.map +1 -0
- package/dist/nodes/model3d/mesh-ops.js +775 -0
- package/dist/nodes/model3d/mesh-ops.js.map +1 -0
- package/dist/nodes/model3d/nodes.d.ts +2 -0
- package/dist/nodes/model3d/nodes.d.ts.map +1 -0
- package/dist/nodes/model3d/nodes.js +2 -0
- package/dist/nodes/model3d/nodes.js.map +1 -0
- package/dist/nodes/model3d/ops.d.ts +44 -0
- package/dist/nodes/model3d/ops.d.ts.map +1 -0
- package/dist/nodes/model3d/ops.js +142 -0
- package/dist/nodes/model3d/ops.js.map +1 -0
- package/dist/nodes/model3d/render.d.ts +51 -0
- package/dist/nodes/model3d/render.d.ts.map +1 -0
- package/dist/nodes/model3d/render.js +124 -0
- package/dist/nodes/model3d/render.js.map +1 -0
- package/dist/nodes/model3d/render3d-core.d.ts +177 -0
- package/dist/nodes/model3d/render3d-core.d.ts.map +1 -0
- package/dist/nodes/model3d/render3d-core.js +366 -0
- package/dist/nodes/model3d/render3d-core.js.map +1 -0
- package/dist/nodes/model3d/render3d-headless.d.ts +75 -0
- package/dist/nodes/model3d/render3d-headless.d.ts.map +1 -0
- package/dist/nodes/model3d/render3d-headless.js +190 -0
- package/dist/nodes/model3d/render3d-headless.js.map +1 -0
- package/dist/nodes/model3d/render3d-page.d.ts +16 -0
- package/dist/nodes/model3d/render3d-page.d.ts.map +1 -0
- package/dist/nodes/model3d/render3d-page.js +51 -0
- package/dist/nodes/model3d/render3d-page.js.map +1 -0
- package/dist/nodes/model3d/transforms.d.ts +56 -0
- package/dist/nodes/model3d/transforms.d.ts.map +1 -0
- package/dist/nodes/model3d/transforms.js +148 -0
- package/dist/nodes/model3d/transforms.js.map +1 -0
- package/dist/nodes/model3d/types.d.ts +82 -0
- package/dist/nodes/model3d/types.d.ts.map +1 -0
- package/dist/nodes/model3d/types.js +2 -0
- package/dist/nodes/model3d/types.js.map +1 -0
- package/dist/nodes/model3d/utils.d.ts +46 -0
- package/dist/nodes/model3d/utils.d.ts.map +1 -0
- package/dist/nodes/model3d/utils.js +190 -0
- package/dist/nodes/model3d/utils.js.map +1 -0
- package/dist/nodes/model3d.d.ts +2 -0
- package/dist/nodes/model3d.d.ts.map +1 -0
- package/dist/nodes/model3d.js +2 -0
- package/dist/nodes/model3d.js.map +1 -0
- package/dist/nodes/placeholder-values.d.ts +10 -0
- package/dist/nodes/placeholder-values.d.ts.map +1 -0
- package/dist/nodes/placeholder-values.js +19 -0
- package/dist/nodes/placeholder-values.js.map +1 -0
- package/dist/nodes/script.d.ts +161 -0
- package/dist/nodes/script.d.ts.map +1 -0
- package/dist/nodes/script.js +673 -0
- package/dist/nodes/script.js.map +1 -0
- package/dist/nodes/storyboard.d.ts +269 -0
- package/dist/nodes/storyboard.d.ts.map +1 -0
- package/dist/nodes/storyboard.js +1017 -0
- package/dist/nodes/storyboard.js.map +1 -0
- package/dist/nodes/timeline/assetFiles.d.ts +19 -0
- package/dist/nodes/timeline/assetFiles.d.ts.map +1 -0
- package/dist/nodes/timeline/assetFiles.js +52 -0
- package/dist/nodes/timeline/assetFiles.js.map +1 -0
- package/dist/nodes/timeline/compositeRender.d.ts +60 -0
- package/dist/nodes/timeline/compositeRender.d.ts.map +1 -0
- package/dist/nodes/timeline/compositeRender.js +508 -0
- package/dist/nodes/timeline/compositeRender.js.map +1 -0
- package/dist/nodes/timeline/contactSheet.d.ts +43 -0
- package/dist/nodes/timeline/contactSheet.d.ts.map +1 -0
- package/dist/nodes/timeline/contactSheet.js +78 -0
- package/dist/nodes/timeline/contactSheet.js.map +1 -0
- package/dist/nodes/timeline/outputFormats.d.ts +69 -0
- package/dist/nodes/timeline/outputFormats.d.ts.map +1 -0
- package/dist/nodes/timeline/outputFormats.js +173 -0
- package/dist/nodes/timeline/outputFormats.js.map +1 -0
- package/dist/nodes/timeline/pngSequence.d.ts +44 -0
- package/dist/nodes/timeline/pngSequence.d.ts.map +1 -0
- package/dist/nodes/timeline/pngSequence.js +172 -0
- package/dist/nodes/timeline/pngSequence.js.map +1 -0
- package/dist/nodes/timeline/rasterizers.d.ts +47 -0
- package/dist/nodes/timeline/rasterizers.d.ts.map +1 -0
- package/dist/nodes/timeline/rasterizers.js +103 -0
- package/dist/nodes/timeline/rasterizers.js.map +1 -0
- package/dist/nodes/timeline/rawFrames.d.ts +149 -0
- package/dist/nodes/timeline/rawFrames.d.ts.map +1 -0
- package/dist/nodes/timeline/rawFrames.js +598 -0
- package/dist/nodes/timeline/rawFrames.js.map +1 -0
- package/dist/nodes/timeline.d.ts +206 -0
- package/dist/nodes/timeline.d.ts.map +1 -0
- package/dist/nodes/timeline.js +1395 -0
- package/dist/nodes/timeline.js.map +1 -0
- package/dist/nodes/video.d.ts +705 -0
- package/dist/nodes/video.d.ts.map +1 -0
- package/dist/nodes/video.js +2672 -0
- package/dist/nodes/video.js.map +1 -0
- package/dist/render3d-page.js +4118 -0
- package/package.json +95 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contactSheet.js","sourceRoot":"","sources":["../../../src/nodes/timeline/contactSheet.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a timeline render is written as, and which of those carry alpha.
|
|
3
|
+
*
|
|
4
|
+
* The table is pure so the refusals can be tested without ffmpeg: picking a
|
|
5
|
+
* format is a decision about the container, the encoder, the pixel format and
|
|
6
|
+
* whether transparency survives, and all four have to agree. `mp4` is the one
|
|
7
|
+
* that cannot agree — H.264 in MP4 has no alpha plane any player reads — so
|
|
8
|
+
* asking for it with `alpha` is refused here rather than encoded opaque and
|
|
9
|
+
* handed back as if it had worked.
|
|
10
|
+
*/
|
|
11
|
+
import { type ResolvedMotionBlur } from "@nodetool-ai/timeline/render";
|
|
12
|
+
/** Containers `RenderTimeline` can write. */
|
|
13
|
+
export declare const TIMELINE_OUTPUT_FORMATS: readonly ["mp4", "webm", "mov", "png_sequence"];
|
|
14
|
+
export type TimelineOutputFormat = (typeof TIMELINE_OUTPUT_FORMATS)[number];
|
|
15
|
+
/** The formats whose pixels keep a transparency channel. */
|
|
16
|
+
export declare const ALPHA_OUTPUT_FORMATS: readonly TimelineOutputFormat[];
|
|
17
|
+
/** Thrown when the requested format cannot carry the requested alpha. */
|
|
18
|
+
export declare class AlphaFormatError extends Error {
|
|
19
|
+
readonly format: TimelineOutputFormat;
|
|
20
|
+
constructor(format: TimelineOutputFormat);
|
|
21
|
+
}
|
|
22
|
+
/** Thrown when this ffmpeg build lacks the encoder the format needs. */
|
|
23
|
+
export declare class MissingEncoderError extends Error {
|
|
24
|
+
readonly encoder: string;
|
|
25
|
+
constructor(encoder: string, format: TimelineOutputFormat);
|
|
26
|
+
}
|
|
27
|
+
/** Everything downstream of the format choice, resolved once. */
|
|
28
|
+
export interface ResolvedTimelineOutput {
|
|
29
|
+
/**
|
|
30
|
+
* Motion blur for this render, already clamped (D10). One sample is blur off,
|
|
31
|
+
* which is what a request that names neither field resolves to.
|
|
32
|
+
*/
|
|
33
|
+
motionBlur: ResolvedMotionBlur;
|
|
34
|
+
format: TimelineOutputFormat;
|
|
35
|
+
/** Whether the frames are composited over a transparent ground. */
|
|
36
|
+
alpha: boolean;
|
|
37
|
+
/** File extension of the rendered artifact, without the dot. */
|
|
38
|
+
extension: string;
|
|
39
|
+
/** What `videoRef`/`documentRef` reports as the artifact's format. */
|
|
40
|
+
mimeType: string;
|
|
41
|
+
/**
|
|
42
|
+
* ffmpeg output arguments for the frame encoder — codec, profile and pixel
|
|
43
|
+
* format. Empty for `png_sequence`, which never reaches ffmpeg.
|
|
44
|
+
*/
|
|
45
|
+
encoderArgs: string[];
|
|
46
|
+
/** The ffmpeg encoder that must exist, probed before the render starts. */
|
|
47
|
+
requiredEncoder: string | null;
|
|
48
|
+
/** Audio codec for the mux pass; null when the format carries no audio. */
|
|
49
|
+
audioCodec: string | null;
|
|
50
|
+
}
|
|
51
|
+
export interface TimelineOutputRequest {
|
|
52
|
+
format?: string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Sub-frame instants to average into each frame, and how far the shutter
|
|
55
|
+
* opens in degrees of the frame. Out-of-range values clamp rather than
|
|
56
|
+
* refusing the render; absent is blur off.
|
|
57
|
+
*/
|
|
58
|
+
motionBlurSamples?: number | null;
|
|
59
|
+
shutterAngle?: number | null;
|
|
60
|
+
alpha?: boolean;
|
|
61
|
+
/** Override the container's default video encoder. */
|
|
62
|
+
videoCodec?: string | null;
|
|
63
|
+
/** Target video bitrate in bits per second; 0 or absent leaves it to ffmpeg. */
|
|
64
|
+
bitrate?: number | null;
|
|
65
|
+
}
|
|
66
|
+
/** Narrow a caller's string to a format, or say what the choices are. */
|
|
67
|
+
export declare function parseOutputFormat(raw: string | null | undefined): TimelineOutputFormat;
|
|
68
|
+
export declare function resolveTimelineOutput(request?: TimelineOutputRequest): ResolvedTimelineOutput;
|
|
69
|
+
//# sourceMappingURL=outputFormats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outputFormats.d.ts","sourceRoot":"","sources":["../../../src/nodes/timeline/outputFormats.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,8BAA8B,CAAC;AAEtC,6CAA6C;AAC7C,eAAO,MAAM,uBAAuB,YAClC,KAAK,EACL,MAAM,EACN,KAAK,EACL,cAAc,CACN,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,4DAA4D;AAC5D,eAAO,MAAM,oBAAoB,EAAE,SAAS,oBAAoB,EAI/D,CAAC;AAEF,yEAAyE;AACzE,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IAEtC,YAAY,MAAM,EAAE,oBAAoB,EAOvC;CACF;AAED,wEAAwE;AACxE,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,EAQxD;CACF;AAED,iEAAiE;AACjE,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,UAAU,EAAE,kBAAkB,CAAC;IAC/B,MAAM,EAAE,oBAAoB,CAAC;IAC7B,mEAAmE;IACnE,KAAK,EAAE,OAAO,CAAC;IACf,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,2EAA2E;IAC3E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,2EAA2E;IAC3E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,oBAAoB,CAUtF;AAiCD,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,qBAA0B,GAClC,sBAAsB,CA4FxB"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a timeline render is written as, and which of those carry alpha.
|
|
3
|
+
*
|
|
4
|
+
* The table is pure so the refusals can be tested without ffmpeg: picking a
|
|
5
|
+
* format is a decision about the container, the encoder, the pixel format and
|
|
6
|
+
* whether transparency survives, and all four have to agree. `mp4` is the one
|
|
7
|
+
* that cannot agree — H.264 in MP4 has no alpha plane any player reads — so
|
|
8
|
+
* asking for it with `alpha` is refused here rather than encoded opaque and
|
|
9
|
+
* handed back as if it had worked.
|
|
10
|
+
*/
|
|
11
|
+
import { resolveMotionBlur } from "@nodetool-ai/timeline/render";
|
|
12
|
+
/** Containers `RenderTimeline` can write. */
|
|
13
|
+
export const TIMELINE_OUTPUT_FORMATS = [
|
|
14
|
+
"mp4",
|
|
15
|
+
"webm",
|
|
16
|
+
"mov",
|
|
17
|
+
"png_sequence"
|
|
18
|
+
];
|
|
19
|
+
/** The formats whose pixels keep a transparency channel. */
|
|
20
|
+
export const ALPHA_OUTPUT_FORMATS = [
|
|
21
|
+
"webm",
|
|
22
|
+
"mov",
|
|
23
|
+
"png_sequence"
|
|
24
|
+
];
|
|
25
|
+
/** Thrown when the requested format cannot carry the requested alpha. */
|
|
26
|
+
export class AlphaFormatError extends Error {
|
|
27
|
+
format;
|
|
28
|
+
constructor(format) {
|
|
29
|
+
super(`The "${format}" format has no alpha channel. Render with transparency ` +
|
|
30
|
+
`as one of: ${ALPHA_OUTPUT_FORMATS.join(", ")}.`);
|
|
31
|
+
this.name = "AlphaFormatError";
|
|
32
|
+
this.format = format;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Thrown when this ffmpeg build lacks the encoder the format needs. */
|
|
36
|
+
export class MissingEncoderError extends Error {
|
|
37
|
+
encoder;
|
|
38
|
+
constructor(encoder, format) {
|
|
39
|
+
super(`This ffmpeg build has no "${encoder}" encoder, which the "${format}" ` +
|
|
40
|
+
"format needs. Install an ffmpeg built with it, or render to another " +
|
|
41
|
+
"format.");
|
|
42
|
+
this.name = "MissingEncoderError";
|
|
43
|
+
this.encoder = encoder;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Narrow a caller's string to a format, or say what the choices are. */
|
|
47
|
+
export function parseOutputFormat(raw) {
|
|
48
|
+
const value = (raw ?? "mp4").trim().toLowerCase();
|
|
49
|
+
const found = TIMELINE_OUTPUT_FORMATS.find((f) => f === value);
|
|
50
|
+
if (!found) {
|
|
51
|
+
throw new Error(`Unknown render format "${raw}". Choose one of: ` +
|
|
52
|
+
`${TIMELINE_OUTPUT_FORMATS.join(", ")}.`);
|
|
53
|
+
}
|
|
54
|
+
return found;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a format request into the container, encoder and pixel format that
|
|
58
|
+
* carry it. Throws {@link AlphaFormatError} when the pair cannot exist.
|
|
59
|
+
*
|
|
60
|
+
* ProRes 4444 is the alpha-carrying profile (`-profile:v 4444`); without alpha
|
|
61
|
+
* the render drops to 422 HQ, which is half the data for the same picture.
|
|
62
|
+
* VP9 is the only WebM video codec with an alpha plane, so `webm` pins it
|
|
63
|
+
* rather than taking a `videoCodec` override that would silently lose alpha.
|
|
64
|
+
*/
|
|
65
|
+
/**
|
|
66
|
+
* Convert to BT.709 and say so in the file.
|
|
67
|
+
*
|
|
68
|
+
* The compositor hands ffmpeg sRGB RGBA. Told only a `-pix_fmt`, ffmpeg picks
|
|
69
|
+
* the BT.601 matrix (its default for an untagged input) and writes no colour
|
|
70
|
+
* metadata, so a player that assumes BT.709 for HD — which is every player —
|
|
71
|
+
* shows greens and reds that are not the ones the editor previewed. The scaler
|
|
72
|
+
* does the conversion; the three tags record it, since an untagged file is
|
|
73
|
+
* guessed at all over again by the next tool that reads it. Limited range is
|
|
74
|
+
* what a YUV video carries and what a player expects.
|
|
75
|
+
*/
|
|
76
|
+
const BT709_ARGS = [
|
|
77
|
+
"-vf",
|
|
78
|
+
"scale=out_color_matrix=bt709:out_range=tv",
|
|
79
|
+
"-colorspace",
|
|
80
|
+
"bt709",
|
|
81
|
+
"-color_primaries",
|
|
82
|
+
"bt709",
|
|
83
|
+
"-color_trc",
|
|
84
|
+
"bt709"
|
|
85
|
+
];
|
|
86
|
+
export function resolveTimelineOutput(request = {}) {
|
|
87
|
+
const format = parseOutputFormat(request.format);
|
|
88
|
+
const alpha = request.alpha === true;
|
|
89
|
+
const motionBlur = resolveMotionBlur({
|
|
90
|
+
samplesPerFrame: request.motionBlurSamples ?? undefined,
|
|
91
|
+
shutterAngle: request.shutterAngle ?? undefined
|
|
92
|
+
});
|
|
93
|
+
if (alpha && !ALPHA_OUTPUT_FORMATS.includes(format)) {
|
|
94
|
+
throw new AlphaFormatError(format);
|
|
95
|
+
}
|
|
96
|
+
const bitrateArgs = typeof request.bitrate === "number" && request.bitrate > 0
|
|
97
|
+
? ["-b:v", String(Math.round(request.bitrate))]
|
|
98
|
+
: [];
|
|
99
|
+
const override = request.videoCodec?.trim();
|
|
100
|
+
if (format === "png_sequence") {
|
|
101
|
+
return {
|
|
102
|
+
format,
|
|
103
|
+
alpha,
|
|
104
|
+
motionBlur,
|
|
105
|
+
extension: "zip",
|
|
106
|
+
mimeType: "application/zip",
|
|
107
|
+
encoderArgs: [],
|
|
108
|
+
requiredEncoder: null,
|
|
109
|
+
audioCodec: null
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (format === "webm") {
|
|
113
|
+
return {
|
|
114
|
+
format,
|
|
115
|
+
alpha,
|
|
116
|
+
motionBlur,
|
|
117
|
+
extension: "webm",
|
|
118
|
+
mimeType: "video/webm",
|
|
119
|
+
encoderArgs: [
|
|
120
|
+
"-c:v",
|
|
121
|
+
"libvpx-vp9",
|
|
122
|
+
"-pix_fmt",
|
|
123
|
+
alpha ? "yuva420p" : "yuv420p",
|
|
124
|
+
...BT709_ARGS,
|
|
125
|
+
...bitrateArgs
|
|
126
|
+
],
|
|
127
|
+
requiredEncoder: "libvpx-vp9",
|
|
128
|
+
// WebM carries Opus and Vorbis; AAC is not a legal Matroska/WebM track.
|
|
129
|
+
audioCodec: "libopus"
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (format === "mov") {
|
|
133
|
+
return {
|
|
134
|
+
format,
|
|
135
|
+
alpha,
|
|
136
|
+
motionBlur,
|
|
137
|
+
extension: "mov",
|
|
138
|
+
mimeType: "video/quicktime",
|
|
139
|
+
encoderArgs: [
|
|
140
|
+
"-c:v",
|
|
141
|
+
"prores_ks",
|
|
142
|
+
"-profile:v",
|
|
143
|
+
alpha ? "4444" : "3",
|
|
144
|
+
"-pix_fmt",
|
|
145
|
+
alpha ? "yuva444p10le" : "yuv422p10le",
|
|
146
|
+
...BT709_ARGS,
|
|
147
|
+
...bitrateArgs
|
|
148
|
+
],
|
|
149
|
+
requiredEncoder: "prores_ks",
|
|
150
|
+
audioCodec: "aac"
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const codec = override && override.length > 0 ? override : "libx264";
|
|
154
|
+
return {
|
|
155
|
+
format,
|
|
156
|
+
alpha: false,
|
|
157
|
+
motionBlur,
|
|
158
|
+
extension: "mp4",
|
|
159
|
+
mimeType: "video/mp4",
|
|
160
|
+
encoderArgs: [
|
|
161
|
+
"-c:v",
|
|
162
|
+
codec,
|
|
163
|
+
...(codec === "libx264" ? ["-preset", "veryfast"] : []),
|
|
164
|
+
"-pix_fmt",
|
|
165
|
+
"yuv420p",
|
|
166
|
+
...BT709_ARGS,
|
|
167
|
+
...bitrateArgs
|
|
168
|
+
],
|
|
169
|
+
requiredEncoder: codec,
|
|
170
|
+
audioCodec: "aac"
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=outputFormats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outputFormats.js","sourceRoot":"","sources":["../../../src/nodes/timeline/outputFormats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `png_sequence` output: one PNG per composited frame, streamed into a
|
|
3
|
+
* single zip alongside a `manifest.json`.
|
|
4
|
+
*
|
|
5
|
+
* PNG is the only format here that carries straight alpha with no chroma
|
|
6
|
+
* subsampling, which is what makes it the reference an alpha render is checked
|
|
7
|
+
* against. The frames go through `@napi-rs/canvas` rather than ffmpeg — the
|
|
8
|
+
* compositor already hands over RGBA8, so an ffmpeg round trip would only add a
|
|
9
|
+
* process per frame and a second colour conversion.
|
|
10
|
+
*
|
|
11
|
+
* The zip is written entry by entry with no compression: a PNG is already
|
|
12
|
+
* deflate-compressed, so a second pass costs CPU and saves nothing. Writing as
|
|
13
|
+
* it goes is what keeps a thousand-frame render off the heap — only the frame
|
|
14
|
+
* being encoded is in memory.
|
|
15
|
+
*/
|
|
16
|
+
import type { FrameEncoder } from "./rawFrames.js";
|
|
17
|
+
/** What a reader needs to turn the entries back into a sequence. */
|
|
18
|
+
export interface PngSequenceManifest {
|
|
19
|
+
format: "png_sequence";
|
|
20
|
+
fps: number;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
/** Frames written, which is also the number of `frame_*.png` entries. */
|
|
24
|
+
count: number;
|
|
25
|
+
/** Whether the frames carry a transparency channel. */
|
|
26
|
+
alpha: boolean;
|
|
27
|
+
/** `printf`-style pattern of the entry names, for ffmpeg's image2 demuxer. */
|
|
28
|
+
pattern: string;
|
|
29
|
+
}
|
|
30
|
+
/** Encode straight-alpha RGBA8 pixels as PNG bytes. */
|
|
31
|
+
export declare function encodeRgbaPng(rgba: Uint8Array, width: number, height: number): Buffer;
|
|
32
|
+
/**
|
|
33
|
+
* Open a {@link FrameEncoder} that writes each frame as a PNG into the zip at
|
|
34
|
+
* `outPath`, and closes with a `manifest.json` naming the rate, the size and
|
|
35
|
+
* the frame count.
|
|
36
|
+
*/
|
|
37
|
+
export declare function openPngSequenceEncoder(opts: {
|
|
38
|
+
outPath: string;
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
fps: number;
|
|
42
|
+
alpha: boolean;
|
|
43
|
+
}): FrameEncoder;
|
|
44
|
+
//# sourceMappingURL=pngSequence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pngSequence.d.ts","sourceRoot":"","sources":["../../../src/nodes/timeline/pngSequence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,cAAc,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,KAAK,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,uDAAuD;AACvD,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,MAAM,CAeR;AA2HD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,YAAY,CAkCf"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `png_sequence` output: one PNG per composited frame, streamed into a
|
|
3
|
+
* single zip alongside a `manifest.json`.
|
|
4
|
+
*
|
|
5
|
+
* PNG is the only format here that carries straight alpha with no chroma
|
|
6
|
+
* subsampling, which is what makes it the reference an alpha render is checked
|
|
7
|
+
* against. The frames go through `@napi-rs/canvas` rather than ffmpeg — the
|
|
8
|
+
* compositor already hands over RGBA8, so an ffmpeg round trip would only add a
|
|
9
|
+
* process per frame and a second colour conversion.
|
|
10
|
+
*
|
|
11
|
+
* The zip is written entry by entry with no compression: a PNG is already
|
|
12
|
+
* deflate-compressed, so a second pass costs CPU and saves nothing. Writing as
|
|
13
|
+
* it goes is what keeps a thousand-frame render off the heap — only the frame
|
|
14
|
+
* being encoded is in memory.
|
|
15
|
+
*/
|
|
16
|
+
import { createCanvas } from "@napi-rs/canvas";
|
|
17
|
+
import { createWriteStream } from "node:fs";
|
|
18
|
+
import { crc32 } from "node:zlib";
|
|
19
|
+
/** Encode straight-alpha RGBA8 pixels as PNG bytes. */
|
|
20
|
+
export function encodeRgbaPng(rgba, width, height) {
|
|
21
|
+
const expected = width * height * 4;
|
|
22
|
+
if (rgba.length < expected) {
|
|
23
|
+
throw new Error(`PNG encode got ${rgba.length} bytes for a ${width}x${height} frame (expected ${expected})`);
|
|
24
|
+
}
|
|
25
|
+
const canvas = createCanvas(width, height);
|
|
26
|
+
const ctx = canvas.getContext("2d");
|
|
27
|
+
const image = ctx.createImageData(width, height);
|
|
28
|
+
image.data.set(rgba.subarray(0, expected));
|
|
29
|
+
// `putImageData` is defined on straight alpha and bypasses compositing, so
|
|
30
|
+
// the pixels land exactly as the compositor un-premultiplied them.
|
|
31
|
+
ctx.putImageData(image, 0, 0);
|
|
32
|
+
return canvas.toBuffer("image/png");
|
|
33
|
+
}
|
|
34
|
+
/** Six-digit frame name, so the entries sort the way they play. */
|
|
35
|
+
function frameName(index) {
|
|
36
|
+
return `frame_${String(index + 1).padStart(6, "0")}.png`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Store-only zip writer.
|
|
40
|
+
*
|
|
41
|
+
* Stored entries need the CRC and the size before the data, and both are known
|
|
42
|
+
* per frame, so nothing has to be buffered to the end but the central
|
|
43
|
+
* directory. ZIP64 is not implemented: an archive that would cross 4 GiB is
|
|
44
|
+
* refused by name rather than written with truncated offsets that no reader
|
|
45
|
+
* would open.
|
|
46
|
+
*/
|
|
47
|
+
const ZIP_MAX_BYTES = 0xffffffff;
|
|
48
|
+
class StoredZipWriter {
|
|
49
|
+
stream;
|
|
50
|
+
entries = [];
|
|
51
|
+
offset = 0;
|
|
52
|
+
pending = Promise.resolve();
|
|
53
|
+
constructor(stream) {
|
|
54
|
+
this.stream = stream;
|
|
55
|
+
}
|
|
56
|
+
add(name, data) {
|
|
57
|
+
const nameBytes = Buffer.from(name, "utf8");
|
|
58
|
+
const entry = {
|
|
59
|
+
name: nameBytes,
|
|
60
|
+
crc: crc32(data),
|
|
61
|
+
size: data.length,
|
|
62
|
+
offset: this.offset
|
|
63
|
+
};
|
|
64
|
+
const header = Buffer.alloc(30 + nameBytes.length);
|
|
65
|
+
header.writeUInt32LE(0x04034b50, 0);
|
|
66
|
+
header.writeUInt16LE(20, 4); // version needed
|
|
67
|
+
header.writeUInt16LE(0, 6); // flags
|
|
68
|
+
header.writeUInt16LE(0, 8); // method: stored
|
|
69
|
+
header.writeUInt16LE(0, 10); // mod time
|
|
70
|
+
header.writeUInt16LE(0x0021, 12); // mod date: 1980-01-01, a fixed stamp
|
|
71
|
+
header.writeUInt32LE(entry.crc, 14);
|
|
72
|
+
header.writeUInt32LE(entry.size, 18);
|
|
73
|
+
header.writeUInt32LE(entry.size, 22);
|
|
74
|
+
header.writeUInt16LE(nameBytes.length, 26);
|
|
75
|
+
header.writeUInt16LE(0, 28); // extra length
|
|
76
|
+
nameBytes.copy(header, 30);
|
|
77
|
+
this.write(header);
|
|
78
|
+
this.write(data);
|
|
79
|
+
this.offset += header.length + data.length;
|
|
80
|
+
if (this.offset > ZIP_MAX_BYTES) {
|
|
81
|
+
throw new Error("The PNG sequence exceeds the 4 GiB a zip archive can address. " +
|
|
82
|
+
"Render a shorter range, a smaller frame, or a video format.");
|
|
83
|
+
}
|
|
84
|
+
this.entries.push(entry);
|
|
85
|
+
}
|
|
86
|
+
async finish() {
|
|
87
|
+
const parts = [];
|
|
88
|
+
for (const entry of this.entries) {
|
|
89
|
+
const record = Buffer.alloc(46 + entry.name.length);
|
|
90
|
+
record.writeUInt32LE(0x02014b50, 0);
|
|
91
|
+
record.writeUInt16LE(20, 4); // version made by
|
|
92
|
+
record.writeUInt16LE(20, 6); // version needed
|
|
93
|
+
record.writeUInt16LE(0, 8);
|
|
94
|
+
record.writeUInt16LE(0, 10);
|
|
95
|
+
record.writeUInt16LE(0, 12);
|
|
96
|
+
record.writeUInt16LE(0x0021, 14);
|
|
97
|
+
record.writeUInt32LE(entry.crc, 16);
|
|
98
|
+
record.writeUInt32LE(entry.size, 20);
|
|
99
|
+
record.writeUInt32LE(entry.size, 24);
|
|
100
|
+
record.writeUInt16LE(entry.name.length, 28);
|
|
101
|
+
record.writeUInt16LE(0, 30); // extra
|
|
102
|
+
record.writeUInt16LE(0, 32); // comment
|
|
103
|
+
record.writeUInt16LE(0, 34); // disk
|
|
104
|
+
record.writeUInt16LE(0, 36); // internal attrs
|
|
105
|
+
record.writeUInt32LE(0, 38); // external attrs
|
|
106
|
+
record.writeUInt32LE(entry.offset, 42);
|
|
107
|
+
entry.name.copy(record, 46);
|
|
108
|
+
parts.push(record);
|
|
109
|
+
}
|
|
110
|
+
const central = Buffer.concat(parts);
|
|
111
|
+
const end = Buffer.alloc(22);
|
|
112
|
+
end.writeUInt32LE(0x06054b50, 0);
|
|
113
|
+
end.writeUInt16LE(0, 4);
|
|
114
|
+
end.writeUInt16LE(0, 6);
|
|
115
|
+
end.writeUInt16LE(this.entries.length, 8);
|
|
116
|
+
end.writeUInt16LE(this.entries.length, 10);
|
|
117
|
+
end.writeUInt32LE(central.length, 12);
|
|
118
|
+
end.writeUInt32LE(this.offset, 16);
|
|
119
|
+
end.writeUInt16LE(0, 20);
|
|
120
|
+
this.write(central);
|
|
121
|
+
this.write(end);
|
|
122
|
+
await this.pending;
|
|
123
|
+
await new Promise((resolve, reject) => {
|
|
124
|
+
this.stream.end(() => resolve());
|
|
125
|
+
this.stream.once("error", reject);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/** Queue a chunk, keeping the backpressure wait off the caller's hot path. */
|
|
129
|
+
write(chunk) {
|
|
130
|
+
this.pending = this.pending.then(() => new Promise((resolve, reject) => {
|
|
131
|
+
this.stream.write(chunk, (error) => error ? reject(error) : resolve());
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Open a {@link FrameEncoder} that writes each frame as a PNG into the zip at
|
|
137
|
+
* `outPath`, and closes with a `manifest.json` naming the rate, the size and
|
|
138
|
+
* the frame count.
|
|
139
|
+
*/
|
|
140
|
+
export function openPngSequenceEncoder(opts) {
|
|
141
|
+
const { outPath, width, height, fps, alpha } = opts;
|
|
142
|
+
const stream = createWriteStream(outPath);
|
|
143
|
+
const zip = new StoredZipWriter(stream);
|
|
144
|
+
let count = 0;
|
|
145
|
+
let aborted = false;
|
|
146
|
+
return {
|
|
147
|
+
async write(rgba) {
|
|
148
|
+
if (aborted)
|
|
149
|
+
return;
|
|
150
|
+
zip.add(frameName(count), encodeRgbaPng(rgba, width, height));
|
|
151
|
+
count += 1;
|
|
152
|
+
},
|
|
153
|
+
async finish() {
|
|
154
|
+
const manifest = {
|
|
155
|
+
format: "png_sequence",
|
|
156
|
+
fps,
|
|
157
|
+
width,
|
|
158
|
+
height,
|
|
159
|
+
count,
|
|
160
|
+
alpha,
|
|
161
|
+
pattern: "frame_%06d.png"
|
|
162
|
+
};
|
|
163
|
+
zip.add("manifest.json", Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8"));
|
|
164
|
+
await zip.finish();
|
|
165
|
+
},
|
|
166
|
+
abort() {
|
|
167
|
+
aborted = true;
|
|
168
|
+
stream.destroy();
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=pngSequence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pngSequence.js","sourceRoot":"","sources":["../../../src/nodes/timeline/pngSequence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAoB,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAkBlC,uDAAuD;AACvD,MAAM,UAAU,aAAa,CAC3B,IAAgB,EAChB,KAAa,EACb,MAAc;IAEd,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC;IACpC,IAAI,IAAI,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,kBAAkB,IAAI,CAAC,MAAM,gBAAgB,KAAK,IAAI,MAAM,oBAAoB,QAAQ,GAAG,CAC5F,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,GAAG,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACjD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC3C,2EAA2E;IAC3E,mEAAmE;IACnE,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9B,OAAO,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACtC,CAAC;AAED,mEAAmE;AACnE,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,SAAS,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC;AAC3D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,aAAa,GAAG,UAAU,CAAC;AASjC,MAAM,eAAe;IAKU,MAAM;IAJlB,OAAO,GAAe,EAAE,CAAC;IAClC,MAAM,GAAG,CAAC,CAAC;IACX,OAAO,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAEnD,YAA6B,MAAmB;sBAAnB,MAAM;IAAgB,CAAC;IAEpD,GAAG,CAAC,IAAY,EAAE,IAAY;QAC5B,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAa;YACtB,IAAI,EAAE,SAAS;YACf,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC;YAChB,IAAI,EAAE,IAAI,CAAC,MAAM;YACjB,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QACnD,MAAM,CAAC,aAAa,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,iBAAiB;QAC9C,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ;QACpC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,iBAAiB;QAC7C,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW;QACxC,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,sCAAsC;QACxE,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACpC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe;QAC5C,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAE3B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACnB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjB,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3C,IAAI,IAAI,CAAC,MAAM,GAAG,aAAa,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACb,gEAAgE;gBAC9D,6DAA6D,CAChE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACpD,MAAM,CAAC,aAAa,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YACpC,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,kBAAkB;YAC/C,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,iBAAiB;YAC9C,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC3B,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5B,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5B,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YACjC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACpC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACrC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC5C,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ;YACrC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU;YACvC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO;YACpC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB;YAC9C,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB;YAC9C,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YACvC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7B,GAAG,CAAC,aAAa,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QACjC,GAAG,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACxB,GAAG,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACxB,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC1C,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC3C,GAAG,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACtC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACnC,GAAG,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAEzB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChB,MAAM,IAAI,CAAC,OAAO,CAAC;QACnB,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YACjC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IACtE,KAAK,CAAC,KAAa;QACzB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAC9B,GAAG,EAAE,CACH,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACpC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACjC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAClC,CAAC;QACJ,CAAC,CAAC,CACL,CAAC;IACJ,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAMtC;IACC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IACpD,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,IAAgB;YAC1B,IAAI,OAAO;gBAAE,OAAO;YACpB,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;YAC9D,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;QACD,KAAK,CAAC,MAAM;YACV,MAAM,QAAQ,GAAwB;gBACpC,MAAM,EAAE,cAAc;gBACtB,GAAG;gBACH,KAAK;gBACL,MAAM;gBACN,KAAK;gBACL,KAAK;gBACL,OAAO,EAAE,gBAAgB;aAC1B,CAAC;YACF,GAAG,CAAC,GAAG,CACL,eAAe,EACf,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAC9D,CAAC;YACF,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;QACrB,CAAC;QACD,KAAK;YACH,OAAO,GAAG,IAAI,CAAC;YACf,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side rasterization of the layer kinds that have no media behind them:
|
|
3
|
+
* captions, text clips and shapes.
|
|
4
|
+
*
|
|
5
|
+
* The drawing rules come from `@nodetool-ai/timeline/render` — the same ones
|
|
6
|
+
* the editor's preview and its in-browser export use — applied to a
|
|
7
|
+
* `@napi-rs/canvas` surface instead of an `OffscreenCanvas`. Output is
|
|
8
|
+
* straight-alpha RGBA at frame resolution, ready to hand to the compositor.
|
|
9
|
+
*
|
|
10
|
+
* The bundled faces are registered here, with the same library and the same
|
|
11
|
+
* files the agent's frame preview registers, so a title renders identically on
|
|
12
|
+
* both Node hosts (D8).
|
|
13
|
+
*/
|
|
14
|
+
import type { ClipMask, ClipShapeStyle, ClipTextStyle } from "@nodetool-ai/timeline";
|
|
15
|
+
import type { ResolvedCaption, TextRenderStagger } from "@nodetool-ai/timeline/render";
|
|
16
|
+
import type { RawImage } from "./rawFrames.js";
|
|
17
|
+
/** A rasterized layer plus the signature that identifies its pixels. */
|
|
18
|
+
interface RasterResult extends RawImage {
|
|
19
|
+
/** Changes exactly when the pixels do — the compositor's upload key. */
|
|
20
|
+
version: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Draws through the shared rules and caches by content signature, so a title
|
|
24
|
+
* that holds still for 300 frames is rasterized once.
|
|
25
|
+
*/
|
|
26
|
+
export declare class NodeRasterizer {
|
|
27
|
+
private readonly width;
|
|
28
|
+
private readonly height;
|
|
29
|
+
private readonly cache;
|
|
30
|
+
constructor(width: number, height: number);
|
|
31
|
+
caption(caption: ResolvedCaption): RasterResult | null;
|
|
32
|
+
/**
|
|
33
|
+
* A clip mask's coverage, at the size of the layer it cuts rather than the
|
|
34
|
+
* frame — the mask is authored in the layer's own normalized space, and the
|
|
35
|
+
* GPU compositor applies it to the source texture before placing it.
|
|
36
|
+
*
|
|
37
|
+
* Null when the mask names a kind this build cannot rasterize or path data
|
|
38
|
+
* that does not parse; the layer then draws unmasked and the validator
|
|
39
|
+
* reports `mask_path_invalid`.
|
|
40
|
+
*/
|
|
41
|
+
mask(mask: ClipMask, width: number, height: number): RasterResult | null;
|
|
42
|
+
shape(style: ClipShapeStyle): RasterResult | null;
|
|
43
|
+
text(style: ClipTextStyle, stagger?: TextRenderStagger | null): RasterResult | null;
|
|
44
|
+
private render;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=rasterizers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rasterizers.d.ts","sourceRoot":"","sources":["../../../src/nodes/timeline/rasterizers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EACd,aAAa,EACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAEV,eAAe,EACf,iBAAiB,EAClB,MAAM,8BAA8B,CAAC;AAetC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,wEAAwE;AACxE,UAAU,YAAa,SAAQ,QAAQ;IACrC,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;CACjB;AAID;;;GAGG;AACH,qBAAa,cAAc;IAIvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJzB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IAEzD,YACmB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EAMhC;IAED,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,YAAY,GAAG,IAAI,CAMrD;IAED;;;;;;;;OAQG;IACH,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAYvE;IAED,KAAK,CAAC,KAAK,EAAE,cAAc,GAAG,YAAY,GAAG,IAAI,CAKhD;IAED,IAAI,CACF,KAAK,EAAE,aAAa,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI,GACjC,YAAY,GAAG,IAAI,CA4BrB;IAED,OAAO,CAAC,MAAM;CA+Bf"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side rasterization of the layer kinds that have no media behind them:
|
|
3
|
+
* captions, text clips and shapes.
|
|
4
|
+
*
|
|
5
|
+
* The drawing rules come from `@nodetool-ai/timeline/render` — the same ones
|
|
6
|
+
* the editor's preview and its in-browser export use — applied to a
|
|
7
|
+
* `@napi-rs/canvas` surface instead of an `OffscreenCanvas`. Output is
|
|
8
|
+
* straight-alpha RGBA at frame resolution, ready to hand to the compositor.
|
|
9
|
+
*
|
|
10
|
+
* The bundled faces are registered here, with the same library and the same
|
|
11
|
+
* files the agent's frame preview registers, so a title renders identically on
|
|
12
|
+
* both Node hosts (D8).
|
|
13
|
+
*/
|
|
14
|
+
import { createCanvas } from "@napi-rs/canvas";
|
|
15
|
+
import { registerBundledFonts } from "@nodetool-ai/timeline/fonts/node";
|
|
16
|
+
import { captionSignature, createStaggerScratch, drawCaption, drawMask, drawShape, drawStaggeredText, drawText, maskSignature, shapeStyleSignature, staggerPhase, textStyleSignature } from "@nodetool-ai/timeline/render";
|
|
17
|
+
const MAX_CACHE_ENTRIES = 32;
|
|
18
|
+
/**
|
|
19
|
+
* Draws through the shared rules and caches by content signature, so a title
|
|
20
|
+
* that holds still for 300 frames is rasterized once.
|
|
21
|
+
*/
|
|
22
|
+
export class NodeRasterizer {
|
|
23
|
+
width;
|
|
24
|
+
height;
|
|
25
|
+
cache = new Map();
|
|
26
|
+
constructor(width, height) {
|
|
27
|
+
this.width = width;
|
|
28
|
+
this.height = height;
|
|
29
|
+
// Before any glyph is drawn, and once per process. A render whose fonts
|
|
30
|
+
// are registered later bakes the fallback face into the frames already
|
|
31
|
+
// encoded, which no re-render of the tail can undo.
|
|
32
|
+
registerBundledFonts();
|
|
33
|
+
}
|
|
34
|
+
caption(caption) {
|
|
35
|
+
if (caption.words.length === 0)
|
|
36
|
+
return null;
|
|
37
|
+
return this.render(`caption|${captionSignature(caption, this.width, this.height)}`, (ctx) => drawCaption(ctx, caption, this.width, this.height));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A clip mask's coverage, at the size of the layer it cuts rather than the
|
|
41
|
+
* frame — the mask is authored in the layer's own normalized space, and the
|
|
42
|
+
* GPU compositor applies it to the source texture before placing it.
|
|
43
|
+
*
|
|
44
|
+
* Null when the mask names a kind this build cannot rasterize or path data
|
|
45
|
+
* that does not parse; the layer then draws unmasked and the validator
|
|
46
|
+
* reports `mask_path_invalid`.
|
|
47
|
+
*/
|
|
48
|
+
mask(mask, width, height) {
|
|
49
|
+
let painted = true;
|
|
50
|
+
const result = this.render(`mask|${maskSignature(mask, width, height)}`, (ctx) => {
|
|
51
|
+
painted = drawMask(ctx, mask, width, height);
|
|
52
|
+
}, true, width, height);
|
|
53
|
+
return painted ? result : null;
|
|
54
|
+
}
|
|
55
|
+
shape(style) {
|
|
56
|
+
return this.render(`shape|${shapeStyleSignature(style, this.width, this.height)}`, (ctx) => drawShape(ctx, style, this.width, this.height));
|
|
57
|
+
}
|
|
58
|
+
text(style, stagger) {
|
|
59
|
+
if (!style.text)
|
|
60
|
+
return null;
|
|
61
|
+
const base = `text|${textStyleSignature(style, this.width, this.height)}`;
|
|
62
|
+
if (!stagger) {
|
|
63
|
+
return this.render(base, (ctx) => drawText(ctx, style, this.width, this.height));
|
|
64
|
+
}
|
|
65
|
+
const phase = staggerPhase(stagger);
|
|
66
|
+
// Mid-stagger the raster changes every frame, so it is keyed by time and
|
|
67
|
+
// never reused; a held frame outside the animation window caches by phase.
|
|
68
|
+
const key = phase === "active"
|
|
69
|
+
? `${base}|stg:${stagger.localMs}`
|
|
70
|
+
: `${base}|stg:${phase}`;
|
|
71
|
+
return this.render(key, (ctx) => drawStaggeredText(ctx, style, this.width, this.height, stagger, createStaggerScratch()), phase !== "active");
|
|
72
|
+
}
|
|
73
|
+
render(key, draw, cacheable = true, atWidth = this.width, atHeight = this.height) {
|
|
74
|
+
if (atWidth <= 0 || atHeight <= 0)
|
|
75
|
+
return null;
|
|
76
|
+
if (cacheable) {
|
|
77
|
+
const hit = this.cache.get(key);
|
|
78
|
+
if (hit)
|
|
79
|
+
return hit;
|
|
80
|
+
}
|
|
81
|
+
const canvas = createCanvas(atWidth, atHeight);
|
|
82
|
+
const ctx = canvas.getContext("2d");
|
|
83
|
+
// SAFETY: `RasterContext2D` is the subset of the 2D canvas API the
|
|
84
|
+
// drawing helpers use, and a skia canvas context provides all of it.
|
|
85
|
+
draw(ctx);
|
|
86
|
+
const result = {
|
|
87
|
+
rgba: new Uint8Array(ctx.getImageData(0, 0, atWidth, atHeight).data),
|
|
88
|
+
width: atWidth,
|
|
89
|
+
height: atHeight,
|
|
90
|
+
version: key
|
|
91
|
+
};
|
|
92
|
+
if (!cacheable)
|
|
93
|
+
return result;
|
|
94
|
+
if (this.cache.size >= MAX_CACHE_ENTRIES) {
|
|
95
|
+
const oldest = this.cache.keys().next().value;
|
|
96
|
+
if (oldest !== undefined)
|
|
97
|
+
this.cache.delete(oldest);
|
|
98
|
+
}
|
|
99
|
+
this.cache.set(key, result);
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=rasterizers.js.map
|