@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.
Files changed (152) hide show
  1. package/README.md +58 -0
  2. package/dist/.nodetool-build-stamp +1 -0
  3. package/dist/index.d.ts +9 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +11 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/nodes/ffmpeg-helpers.d.ts +77 -0
  8. package/dist/nodes/ffmpeg-helpers.d.ts.map +1 -0
  9. package/dist/nodes/ffmpeg-helpers.js +220 -0
  10. package/dist/nodes/ffmpeg-helpers.js.map +1 -0
  11. package/dist/nodes/lib-video-download.d.ts +29 -0
  12. package/dist/nodes/lib-video-download.d.ts.map +1 -0
  13. package/dist/nodes/lib-video-download.js +353 -0
  14. package/dist/nodes/lib-video-download.js.map +1 -0
  15. package/dist/nodes/model3d/analysis.d.ts +16 -0
  16. package/dist/nodes/model3d/analysis.d.ts.map +1 -0
  17. package/dist/nodes/model3d/analysis.js +38 -0
  18. package/dist/nodes/model3d/analysis.js.map +1 -0
  19. package/dist/nodes/model3d/base.d.ts +28 -0
  20. package/dist/nodes/model3d/base.d.ts.map +1 -0
  21. package/dist/nodes/model3d/base.js +20 -0
  22. package/dist/nodes/model3d/base.js.map +1 -0
  23. package/dist/nodes/model3d/boolean-ops.d.ts +2 -0
  24. package/dist/nodes/model3d/boolean-ops.d.ts.map +1 -0
  25. package/dist/nodes/model3d/boolean-ops.js +172 -0
  26. package/dist/nodes/model3d/boolean-ops.js.map +1 -0
  27. package/dist/nodes/model3d/cleanup.d.ts +56 -0
  28. package/dist/nodes/model3d/cleanup.d.ts.map +1 -0
  29. package/dist/nodes/model3d/cleanup.js +144 -0
  30. package/dist/nodes/model3d/cleanup.js.map +1 -0
  31. package/dist/nodes/model3d/convert.d.ts +27 -0
  32. package/dist/nodes/model3d/convert.d.ts.map +1 -0
  33. package/dist/nodes/model3d/convert.js +66 -0
  34. package/dist/nodes/model3d/convert.js.map +1 -0
  35. package/dist/nodes/model3d/defaults.d.ts +34 -0
  36. package/dist/nodes/model3d/defaults.d.ts.map +1 -0
  37. package/dist/nodes/model3d/defaults.js +28 -0
  38. package/dist/nodes/model3d/defaults.js.map +1 -0
  39. package/dist/nodes/model3d/document-ops.d.ts +4 -0
  40. package/dist/nodes/model3d/document-ops.d.ts.map +1 -0
  41. package/dist/nodes/model3d/document-ops.js +40 -0
  42. package/dist/nodes/model3d/document-ops.js.map +1 -0
  43. package/dist/nodes/model3d/generation.d.ts +56 -0
  44. package/dist/nodes/model3d/generation.d.ts.map +1 -0
  45. package/dist/nodes/model3d/generation.js +203 -0
  46. package/dist/nodes/model3d/generation.js.map +1 -0
  47. package/dist/nodes/model3d/glb.d.ts +29 -0
  48. package/dist/nodes/model3d/glb.d.ts.map +1 -0
  49. package/dist/nodes/model3d/glb.js +255 -0
  50. package/dist/nodes/model3d/glb.js.map +1 -0
  51. package/dist/nodes/model3d/index.d.ts +22 -0
  52. package/dist/nodes/model3d/index.d.ts.map +1 -0
  53. package/dist/nodes/model3d/index.js +41 -0
  54. package/dist/nodes/model3d/index.js.map +1 -0
  55. package/dist/nodes/model3d/io.d.ts +81 -0
  56. package/dist/nodes/model3d/io.d.ts.map +1 -0
  57. package/dist/nodes/model3d/io.js +162 -0
  58. package/dist/nodes/model3d/io.js.map +1 -0
  59. package/dist/nodes/model3d/mesh-ops.d.ts +40 -0
  60. package/dist/nodes/model3d/mesh-ops.d.ts.map +1 -0
  61. package/dist/nodes/model3d/mesh-ops.js +775 -0
  62. package/dist/nodes/model3d/mesh-ops.js.map +1 -0
  63. package/dist/nodes/model3d/nodes.d.ts +2 -0
  64. package/dist/nodes/model3d/nodes.d.ts.map +1 -0
  65. package/dist/nodes/model3d/nodes.js +2 -0
  66. package/dist/nodes/model3d/nodes.js.map +1 -0
  67. package/dist/nodes/model3d/ops.d.ts +44 -0
  68. package/dist/nodes/model3d/ops.d.ts.map +1 -0
  69. package/dist/nodes/model3d/ops.js +142 -0
  70. package/dist/nodes/model3d/ops.js.map +1 -0
  71. package/dist/nodes/model3d/render.d.ts +51 -0
  72. package/dist/nodes/model3d/render.d.ts.map +1 -0
  73. package/dist/nodes/model3d/render.js +124 -0
  74. package/dist/nodes/model3d/render.js.map +1 -0
  75. package/dist/nodes/model3d/render3d-core.d.ts +177 -0
  76. package/dist/nodes/model3d/render3d-core.d.ts.map +1 -0
  77. package/dist/nodes/model3d/render3d-core.js +366 -0
  78. package/dist/nodes/model3d/render3d-core.js.map +1 -0
  79. package/dist/nodes/model3d/render3d-headless.d.ts +75 -0
  80. package/dist/nodes/model3d/render3d-headless.d.ts.map +1 -0
  81. package/dist/nodes/model3d/render3d-headless.js +190 -0
  82. package/dist/nodes/model3d/render3d-headless.js.map +1 -0
  83. package/dist/nodes/model3d/render3d-page.d.ts +16 -0
  84. package/dist/nodes/model3d/render3d-page.d.ts.map +1 -0
  85. package/dist/nodes/model3d/render3d-page.js +51 -0
  86. package/dist/nodes/model3d/render3d-page.js.map +1 -0
  87. package/dist/nodes/model3d/transforms.d.ts +56 -0
  88. package/dist/nodes/model3d/transforms.d.ts.map +1 -0
  89. package/dist/nodes/model3d/transforms.js +148 -0
  90. package/dist/nodes/model3d/transforms.js.map +1 -0
  91. package/dist/nodes/model3d/types.d.ts +82 -0
  92. package/dist/nodes/model3d/types.d.ts.map +1 -0
  93. package/dist/nodes/model3d/types.js +2 -0
  94. package/dist/nodes/model3d/types.js.map +1 -0
  95. package/dist/nodes/model3d/utils.d.ts +46 -0
  96. package/dist/nodes/model3d/utils.d.ts.map +1 -0
  97. package/dist/nodes/model3d/utils.js +190 -0
  98. package/dist/nodes/model3d/utils.js.map +1 -0
  99. package/dist/nodes/model3d.d.ts +2 -0
  100. package/dist/nodes/model3d.d.ts.map +1 -0
  101. package/dist/nodes/model3d.js +2 -0
  102. package/dist/nodes/model3d.js.map +1 -0
  103. package/dist/nodes/placeholder-values.d.ts +10 -0
  104. package/dist/nodes/placeholder-values.d.ts.map +1 -0
  105. package/dist/nodes/placeholder-values.js +19 -0
  106. package/dist/nodes/placeholder-values.js.map +1 -0
  107. package/dist/nodes/script.d.ts +161 -0
  108. package/dist/nodes/script.d.ts.map +1 -0
  109. package/dist/nodes/script.js +673 -0
  110. package/dist/nodes/script.js.map +1 -0
  111. package/dist/nodes/storyboard.d.ts +269 -0
  112. package/dist/nodes/storyboard.d.ts.map +1 -0
  113. package/dist/nodes/storyboard.js +1017 -0
  114. package/dist/nodes/storyboard.js.map +1 -0
  115. package/dist/nodes/timeline/assetFiles.d.ts +19 -0
  116. package/dist/nodes/timeline/assetFiles.d.ts.map +1 -0
  117. package/dist/nodes/timeline/assetFiles.js +52 -0
  118. package/dist/nodes/timeline/assetFiles.js.map +1 -0
  119. package/dist/nodes/timeline/compositeRender.d.ts +60 -0
  120. package/dist/nodes/timeline/compositeRender.d.ts.map +1 -0
  121. package/dist/nodes/timeline/compositeRender.js +508 -0
  122. package/dist/nodes/timeline/compositeRender.js.map +1 -0
  123. package/dist/nodes/timeline/contactSheet.d.ts +43 -0
  124. package/dist/nodes/timeline/contactSheet.d.ts.map +1 -0
  125. package/dist/nodes/timeline/contactSheet.js +78 -0
  126. package/dist/nodes/timeline/contactSheet.js.map +1 -0
  127. package/dist/nodes/timeline/outputFormats.d.ts +69 -0
  128. package/dist/nodes/timeline/outputFormats.d.ts.map +1 -0
  129. package/dist/nodes/timeline/outputFormats.js +173 -0
  130. package/dist/nodes/timeline/outputFormats.js.map +1 -0
  131. package/dist/nodes/timeline/pngSequence.d.ts +44 -0
  132. package/dist/nodes/timeline/pngSequence.d.ts.map +1 -0
  133. package/dist/nodes/timeline/pngSequence.js +172 -0
  134. package/dist/nodes/timeline/pngSequence.js.map +1 -0
  135. package/dist/nodes/timeline/rasterizers.d.ts +47 -0
  136. package/dist/nodes/timeline/rasterizers.d.ts.map +1 -0
  137. package/dist/nodes/timeline/rasterizers.js +103 -0
  138. package/dist/nodes/timeline/rasterizers.js.map +1 -0
  139. package/dist/nodes/timeline/rawFrames.d.ts +149 -0
  140. package/dist/nodes/timeline/rawFrames.d.ts.map +1 -0
  141. package/dist/nodes/timeline/rawFrames.js +598 -0
  142. package/dist/nodes/timeline/rawFrames.js.map +1 -0
  143. package/dist/nodes/timeline.d.ts +206 -0
  144. package/dist/nodes/timeline.d.ts.map +1 -0
  145. package/dist/nodes/timeline.js +1395 -0
  146. package/dist/nodes/timeline.js.map +1 -0
  147. package/dist/nodes/video.d.ts +705 -0
  148. package/dist/nodes/video.d.ts.map +1 -0
  149. package/dist/nodes/video.js +2672 -0
  150. package/dist/nodes/video.js.map +1 -0
  151. package/dist/render3d-page.js +4118 -0
  152. 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":"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"}
@@ -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