@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,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame-by-frame render of a timeline sequence, server-side.
|
|
3
|
+
*
|
|
4
|
+
* This is the editor's export path with the browser taken out: the same
|
|
5
|
+
* {@link computeActiveLayersWithHorizon} scene description, the same animation
|
|
6
|
+
* sampling, the same placement math, and the same GPU compositor
|
|
7
|
+
* ({@link HeadlessFrameCompositor}) — so a workflow render and the preview the
|
|
8
|
+
* user watched are the same picture. Only the boundaries differ: ffmpeg decodes
|
|
9
|
+
* clip media into RGBA and encodes the composited frames, in place of
|
|
10
|
+
* `<video>`/WebCodecs.
|
|
11
|
+
*/
|
|
12
|
+
import { hasTimeRemap } from "@nodetool-ai/timeline";
|
|
13
|
+
import { HeadlessFrameCompositor, clipSourceTimeSec, computeActiveLayersWithHorizon, createAnimationCompileCache, hasActiveAnimation, measureTextWith, motionBlurSampleTimes, layerShutterTime, resolveFrameMotionBlur, shutterWindowIsStatic, resolveAnimatedLayerProps, resolveTextStaggerContext, trackZ } from "@nodetool-ai/timeline/render";
|
|
14
|
+
import { createCanvas } from "@napi-rs/canvas";
|
|
15
|
+
import { setImmediate } from "node:timers/promises";
|
|
16
|
+
import { decodeImageRgba, fitWithin, openFrameEncoder, openSourceFrameStream, openVideoFrameStream, probeVideoSize } from "./rawFrames.js";
|
|
17
|
+
import { openPngSequenceEncoder } from "./pngSequence.js";
|
|
18
|
+
import { resolveTimelineOutput } from "./outputFormats.js";
|
|
19
|
+
import { NodeRasterizer } from "./rasterizers.js";
|
|
20
|
+
/** Let the API service pending requests during a frame with many raster layers. */
|
|
21
|
+
const YIELD_EVERY_LAYERS = 8;
|
|
22
|
+
/** The GPU device is acquired lazily; this is what "no GPU here" looks like. */
|
|
23
|
+
export class CompositorUnavailableError extends Error {
|
|
24
|
+
constructor(cause) {
|
|
25
|
+
super("The timeline compositor needs a WebGPU device and none is available: " +
|
|
26
|
+
(cause instanceof Error ? cause.message : String(cause)));
|
|
27
|
+
this.name = "CompositorUnavailableError";
|
|
28
|
+
this.cause = cause;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** The rejection a cancelled render throws, named the way callers test for. */
|
|
32
|
+
function abortError() {
|
|
33
|
+
return new DOMException("Timeline render cancelled", "AbortError");
|
|
34
|
+
}
|
|
35
|
+
async function awaitWithAbort(pending, signal) {
|
|
36
|
+
if (!signal)
|
|
37
|
+
return pending;
|
|
38
|
+
if (signal.aborted)
|
|
39
|
+
throw abortError();
|
|
40
|
+
let onAbort;
|
|
41
|
+
const aborted = new Promise((_resolve, reject) => {
|
|
42
|
+
onAbort = () => reject(abortError());
|
|
43
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
return await Promise.race([pending, aborted]);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
if (onAbort)
|
|
50
|
+
signal.removeEventListener("abort", onAbort);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function acquireDevice() {
|
|
54
|
+
try {
|
|
55
|
+
const { getNodeGPUDevice } = await import("@nodetool-ai/gpu/node");
|
|
56
|
+
return await getNodeGPUDevice();
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw new CompositorUnavailableError(error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export async function renderTimelineComposited(opts) {
|
|
63
|
+
const { sequence, fps, durationMs, resolveAssetPath, outPath, signal } = opts;
|
|
64
|
+
const output = opts.output ?? resolveTimelineOutput({ format: "mp4" });
|
|
65
|
+
// H.264 and the yuv420p family require even dimensions.
|
|
66
|
+
const width = Math.max(2, Math.floor(opts.width / 2) * 2);
|
|
67
|
+
const height = Math.max(2, Math.floor(opts.height / 2) * 2);
|
|
68
|
+
const totalFrames = Math.max(1, Math.round((durationMs / 1000) * fps));
|
|
69
|
+
const frameMs = 1000 / fps;
|
|
70
|
+
// Positions, font sizes and stroke widths are in sequence pixels. A render
|
|
71
|
+
// smaller than the sequence (a preview scale) samples and rasterizes at the
|
|
72
|
+
// sequence size and lets the compositor scale the result to the frame, so
|
|
73
|
+
// the layout matches the full-size render.
|
|
74
|
+
const referenceWidth = sequence.width > 0 ? sequence.width : width;
|
|
75
|
+
const referenceHeight = sequence.height > 0 ? sequence.height : height;
|
|
76
|
+
const frameSize = { width, height };
|
|
77
|
+
const canvas = {
|
|
78
|
+
width: referenceWidth,
|
|
79
|
+
height: referenceHeight,
|
|
80
|
+
// A `"line"` stagger is counted against the wrapped line count, so the
|
|
81
|
+
// count measures through the same kind of context the rasterizer draws on.
|
|
82
|
+
// SAFETY: `RasterContext2D` is the subset of the 2D canvas API the
|
|
83
|
+
// measurement uses, and a skia context provides all of it.
|
|
84
|
+
measureText: measureTextWith(createCanvas(1, 1).getContext("2d"))
|
|
85
|
+
};
|
|
86
|
+
if (signal?.aborted)
|
|
87
|
+
throw abortError();
|
|
88
|
+
const device = await acquireDevice();
|
|
89
|
+
if (signal?.aborted)
|
|
90
|
+
throw abortError();
|
|
91
|
+
const compositor = new HeadlessFrameCompositor(device, width, height);
|
|
92
|
+
compositor.setReferenceSize(referenceWidth, referenceHeight);
|
|
93
|
+
const rasterizer = new NodeRasterizer(referenceWidth, referenceHeight);
|
|
94
|
+
const frameIndices = opts.frames
|
|
95
|
+
? [...new Set(opts.frames)]
|
|
96
|
+
.filter((frame) => Number.isInteger(frame) && frame >= 0 && frame < totalFrames)
|
|
97
|
+
.sort((a, b) => a - b)
|
|
98
|
+
: Array.from({ length: totalFrames }, (_, frame) => frame);
|
|
99
|
+
const { writeFrame } = opts;
|
|
100
|
+
const encoder = writeFrame
|
|
101
|
+
? null
|
|
102
|
+
: output.format === "png_sequence"
|
|
103
|
+
? openPngSequenceEncoder({
|
|
104
|
+
outPath,
|
|
105
|
+
width,
|
|
106
|
+
height,
|
|
107
|
+
fps,
|
|
108
|
+
alpha: output.alpha
|
|
109
|
+
})
|
|
110
|
+
: openFrameEncoder({
|
|
111
|
+
outPath,
|
|
112
|
+
width,
|
|
113
|
+
height,
|
|
114
|
+
fps,
|
|
115
|
+
encoderArgs: output.encoderArgs
|
|
116
|
+
});
|
|
117
|
+
const animCache = createAnimationCompileCache();
|
|
118
|
+
const videoSources = new Map();
|
|
119
|
+
const images = new Map();
|
|
120
|
+
const assetPaths = new Map();
|
|
121
|
+
const skippedClips = new Set();
|
|
122
|
+
const closeVideoSources = () => {
|
|
123
|
+
for (const source of videoSources.values())
|
|
124
|
+
source.close();
|
|
125
|
+
videoSources.clear();
|
|
126
|
+
};
|
|
127
|
+
signal?.addEventListener("abort", closeVideoSources, { once: true });
|
|
128
|
+
const pathFor = (assetId) => {
|
|
129
|
+
let pending = assetPaths.get(assetId);
|
|
130
|
+
if (!pending) {
|
|
131
|
+
pending = resolveAssetPath(assetId);
|
|
132
|
+
assetPaths.set(assetId, pending);
|
|
133
|
+
}
|
|
134
|
+
return pending;
|
|
135
|
+
};
|
|
136
|
+
const imageFor = async (assetId) => {
|
|
137
|
+
if (images.has(assetId))
|
|
138
|
+
return images.get(assetId) ?? null;
|
|
139
|
+
const file = await pathFor(assetId);
|
|
140
|
+
if (signal?.aborted)
|
|
141
|
+
throw abortError();
|
|
142
|
+
let decoded = null;
|
|
143
|
+
if (file) {
|
|
144
|
+
const size = await probeVideoSize(file);
|
|
145
|
+
if (signal?.aborted)
|
|
146
|
+
throw abortError();
|
|
147
|
+
if (size)
|
|
148
|
+
decoded = await decodeImageRgba(file, fitWithin(size, frameSize));
|
|
149
|
+
}
|
|
150
|
+
if (signal?.aborted)
|
|
151
|
+
throw abortError();
|
|
152
|
+
images.set(assetId, decoded);
|
|
153
|
+
return decoded;
|
|
154
|
+
};
|
|
155
|
+
const videoFor = async (clip, assetId) => {
|
|
156
|
+
// Keyed by clip *and* asset: a clip with a generated matte decodes two
|
|
157
|
+
// videos at the same source time — the picture and its mask — and keying on
|
|
158
|
+
// the clip alone handed the mask decode back the picture's stream.
|
|
159
|
+
const key = `${clip.id}:${assetId}`;
|
|
160
|
+
const existing = videoSources.get(key);
|
|
161
|
+
if (existing)
|
|
162
|
+
return existing;
|
|
163
|
+
const file = await pathFor(assetId);
|
|
164
|
+
if (signal?.aborted)
|
|
165
|
+
throw abortError();
|
|
166
|
+
if (!file)
|
|
167
|
+
return null;
|
|
168
|
+
const size = await probeVideoSize(file);
|
|
169
|
+
if (signal?.aborted)
|
|
170
|
+
throw abortError();
|
|
171
|
+
if (!size)
|
|
172
|
+
return null;
|
|
173
|
+
const decodeSize = fitWithin(size, frameSize);
|
|
174
|
+
// `clipSourceTimeSec` at the clip's own start is its in point — or, for a
|
|
175
|
+
// remapped clip, its curve's first source position — expressed the same way
|
|
176
|
+
// the preview seeks, so the first decoded frame is the frame the preview
|
|
177
|
+
// shows at the cut.
|
|
178
|
+
const startSec = clipSourceTimeSec(clip, clip.startMs);
|
|
179
|
+
const endMs = clip.startMs + clip.durationMs;
|
|
180
|
+
let source;
|
|
181
|
+
if (hasTimeRemap(clip)) {
|
|
182
|
+
// A curve can hold, revisit or reverse through the source, which a
|
|
183
|
+
// forward-only stream cannot serve; this one reopens ffmpeg to seek back.
|
|
184
|
+
const stream = openSourceFrameStream({
|
|
185
|
+
filePath: file,
|
|
186
|
+
size: decodeSize,
|
|
187
|
+
fps,
|
|
188
|
+
startSec
|
|
189
|
+
});
|
|
190
|
+
const frameVersions = new WeakMap();
|
|
191
|
+
let nextFrameVersion = 0;
|
|
192
|
+
source = {
|
|
193
|
+
width: stream.width,
|
|
194
|
+
height: stream.height,
|
|
195
|
+
endMs,
|
|
196
|
+
async frameAt(timeMs) {
|
|
197
|
+
const sourceSec = clipSourceTimeSec(clip, timeMs);
|
|
198
|
+
const rgba = await stream.frameAtSourceSec(sourceSec);
|
|
199
|
+
if (!rgba)
|
|
200
|
+
return null;
|
|
201
|
+
// A retained or held frame returns the same array. A newly decoded
|
|
202
|
+
// frame needs an upload even when a nearby source time rounds to the
|
|
203
|
+
// same timeline-frame index.
|
|
204
|
+
let version = frameVersions.get(rgba);
|
|
205
|
+
if (version === undefined) {
|
|
206
|
+
version = `${key}@${nextFrameVersion++}`;
|
|
207
|
+
frameVersions.set(rgba, version);
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
rgba,
|
|
211
|
+
version
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
close: () => stream.close()
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
const stream = openVideoFrameStream({
|
|
219
|
+
filePath: file,
|
|
220
|
+
size: decodeSize,
|
|
221
|
+
fps,
|
|
222
|
+
startSec,
|
|
223
|
+
speed: clip.speedBaked ? 1 : Math.max(0.0001, clip.speedMultiplier ?? 1)
|
|
224
|
+
});
|
|
225
|
+
source = {
|
|
226
|
+
width: stream.width,
|
|
227
|
+
height: stream.height,
|
|
228
|
+
endMs,
|
|
229
|
+
async frameAt(timeMs) {
|
|
230
|
+
const index = Math.max(0, Math.round(((timeMs - clip.startMs) * fps) / 1000));
|
|
231
|
+
const rgba = await stream.frameAt(index);
|
|
232
|
+
if (!rgba)
|
|
233
|
+
return null;
|
|
234
|
+
return { rgba, version: `${key}#${index}` };
|
|
235
|
+
},
|
|
236
|
+
close: () => stream.close()
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
videoSources.set(key, source);
|
|
240
|
+
return source;
|
|
241
|
+
};
|
|
242
|
+
try {
|
|
243
|
+
/**
|
|
244
|
+
* The scene at one instant, as something the compositor can composite.
|
|
245
|
+
*
|
|
246
|
+
* One call is a whole frame with motion blur off, and one sample of the
|
|
247
|
+
* shutter window with it on — the layers are resolved the same way either
|
|
248
|
+
* way, so a blurred render is N of the render it would otherwise have been.
|
|
249
|
+
*/
|
|
250
|
+
const sampleAt = async (timeMs, frameTimeMs = timeMs, sampleIndex = 0, sampleCount = 1) => {
|
|
251
|
+
const layers = [];
|
|
252
|
+
const { layers: active, adjustments, precomposites } = computeActiveLayersWithHorizon(sequence.tracks, sequence.clips, timeMs, {
|
|
253
|
+
canvas,
|
|
254
|
+
animationCache: animCache,
|
|
255
|
+
mediaTracks: sequence.mediaTracks,
|
|
256
|
+
camera2d: sequence.camera2d,
|
|
257
|
+
tempo: sequence.tempo,
|
|
258
|
+
layerTimeMs: (clip) => layerShutterTime(clip, frameTimeMs, sampleIndex, sampleCount, frameMs, output.motionBlur)
|
|
259
|
+
});
|
|
260
|
+
/**
|
|
261
|
+
* A resolved layer as something the compositor can upload: its pixels,
|
|
262
|
+
* its own shape mask rasterized at that size, and — for a matted layer —
|
|
263
|
+
* the matte source resolved the same way.
|
|
264
|
+
*
|
|
265
|
+
* A matte source is not in `active`; the scene model held it back so it
|
|
266
|
+
* never draws itself, which is why it is decoded here and nowhere else.
|
|
267
|
+
*/
|
|
268
|
+
const frameLayerFor = async (layer, idPrefix = "") => {
|
|
269
|
+
const layerTimeMs = layerShutterTime(layer.clip, frameTimeMs, sampleIndex, sampleCount, frameMs, output.motionBlur);
|
|
270
|
+
const anim = resolveAnimatedLayerProps(layer, layerTimeMs, canvas, animCache, { mediaTracks: sequence.mediaTracks ?? [], clips: sequence.clips, tempo: sequence.tempo });
|
|
271
|
+
const common = {
|
|
272
|
+
opacity: anim.opacity,
|
|
273
|
+
blendMode: layer.blendMode,
|
|
274
|
+
zIndex: trackZ(layer.trackIndex),
|
|
275
|
+
stackOrder: layer.stackOrder,
|
|
276
|
+
transform: anim.transform,
|
|
277
|
+
parentMatrix: layer.parentMatrix,
|
|
278
|
+
precomposeGroupId: layer.precomposeGroupId,
|
|
279
|
+
mask: anim.mask,
|
|
280
|
+
borderRadius: anim.borderRadius ?? layer.borderRadius,
|
|
281
|
+
crop: layer.crop,
|
|
282
|
+
effects: anim.effects ?? layer.effects,
|
|
283
|
+
trackEffects: layer.trackEffects,
|
|
284
|
+
transition: layer.transition
|
|
285
|
+
};
|
|
286
|
+
/** Attach the layer's own shape mask, rasterized at its source size. */
|
|
287
|
+
const finish = (built) => {
|
|
288
|
+
const shape = anim.clipMask;
|
|
289
|
+
if (!shape)
|
|
290
|
+
return built;
|
|
291
|
+
const raster = rasterizer.mask(shape, built.source.width, built.source.height);
|
|
292
|
+
if (raster)
|
|
293
|
+
built.shapeMask = raster;
|
|
294
|
+
return built;
|
|
295
|
+
};
|
|
296
|
+
const id = (kind) => `${idPrefix}${kind}:${layer.clipId}`;
|
|
297
|
+
if (layer.kind === "caption" && layer.caption) {
|
|
298
|
+
const raster = rasterizer.caption(layer.caption);
|
|
299
|
+
if (!raster)
|
|
300
|
+
return null;
|
|
301
|
+
return finish({
|
|
302
|
+
...common,
|
|
303
|
+
id: id("c"),
|
|
304
|
+
source: raster,
|
|
305
|
+
// Captions are drawn at frame resolution and composite
|
|
306
|
+
// untransformed — by the clip's transform and by its group's.
|
|
307
|
+
transform: undefined,
|
|
308
|
+
parentMatrix: undefined,
|
|
309
|
+
borderRadius: undefined,
|
|
310
|
+
crop: undefined,
|
|
311
|
+
effects: undefined,
|
|
312
|
+
trackEffects: undefined,
|
|
313
|
+
// The cut's opacity is already in `opacity`; dropping the record
|
|
314
|
+
// here keeps its geometry — and a dip's one solid — off a layer
|
|
315
|
+
// that composites untransformed anyway.
|
|
316
|
+
transition: undefined
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (layer.kind === "text" && anim.textStyle) {
|
|
320
|
+
const stagger = resolveTextStaggerContext(layer.clip, layerTimeMs, canvas, animCache, sequence.tempo);
|
|
321
|
+
const raster = rasterizer.text(anim.textStyle, stagger);
|
|
322
|
+
if (!raster)
|
|
323
|
+
return null;
|
|
324
|
+
return finish({ ...common, id: id("t"), source: raster });
|
|
325
|
+
}
|
|
326
|
+
if (layer.kind === "shape") {
|
|
327
|
+
// The animated style carries a driven trim range; without it a trim
|
|
328
|
+
// animation would rasterize its first frame and hold.
|
|
329
|
+
const shapeStyle = anim.shapeStyle ?? layer.shapeStyle;
|
|
330
|
+
if (!shapeStyle)
|
|
331
|
+
return null;
|
|
332
|
+
const raster = rasterizer.shape(shapeStyle);
|
|
333
|
+
if (!raster)
|
|
334
|
+
return null;
|
|
335
|
+
return finish({ ...common, id: id("s"), source: raster });
|
|
336
|
+
}
|
|
337
|
+
if (!layer.assetId)
|
|
338
|
+
return null;
|
|
339
|
+
if (layer.kind === "video") {
|
|
340
|
+
const stream = await videoFor(layer.clip, layer.assetId);
|
|
341
|
+
if (!stream) {
|
|
342
|
+
skippedClips.add(layer.clip.name);
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const decoded = await stream.frameAt(layerTimeMs);
|
|
346
|
+
if (!decoded)
|
|
347
|
+
return null;
|
|
348
|
+
return finish({
|
|
349
|
+
...common,
|
|
350
|
+
id: id("v"),
|
|
351
|
+
source: {
|
|
352
|
+
rgba: decoded.rgba,
|
|
353
|
+
width: stream.width,
|
|
354
|
+
height: stream.height,
|
|
355
|
+
version: decoded.version
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const image = await imageFor(layer.assetId);
|
|
360
|
+
if (!image) {
|
|
361
|
+
skippedClips.add(layer.clip.name);
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
return finish({
|
|
365
|
+
...common,
|
|
366
|
+
id: id("i"),
|
|
367
|
+
source: { ...image, version: layer.assetId }
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
for (let index = 0; index < active.length; index++) {
|
|
371
|
+
if (index > 0 && index % YIELD_EVERY_LAYERS === 0) {
|
|
372
|
+
await setImmediate();
|
|
373
|
+
}
|
|
374
|
+
if (signal?.aborted)
|
|
375
|
+
throw abortError();
|
|
376
|
+
const layer = active[index];
|
|
377
|
+
const built = await frameLayerFor(layer);
|
|
378
|
+
if (signal?.aborted)
|
|
379
|
+
throw abortError();
|
|
380
|
+
if (!built)
|
|
381
|
+
continue;
|
|
382
|
+
if (layer.matte) {
|
|
383
|
+
const source = await frameLayerFor(layer.matte.layer, "m:");
|
|
384
|
+
if (signal?.aborted)
|
|
385
|
+
throw abortError();
|
|
386
|
+
if (source) {
|
|
387
|
+
built.matte = {
|
|
388
|
+
mode: layer.matte.mode,
|
|
389
|
+
invert: layer.matte.invert,
|
|
390
|
+
layer: source,
|
|
391
|
+
strength: layer.matte.strength,
|
|
392
|
+
featherPx: layer.matte.featherPx
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
layers.push(built);
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
layers,
|
|
400
|
+
adjustments: adjustments.map((adjustment) => ({
|
|
401
|
+
id: adjustment.clipId,
|
|
402
|
+
zIndex: trackZ(adjustment.trackIndex),
|
|
403
|
+
opacity: adjustment.opacity,
|
|
404
|
+
effects: adjustment.effects,
|
|
405
|
+
shapeMask: adjustment.mask
|
|
406
|
+
? rasterizer.mask(adjustment.mask, referenceWidth, referenceHeight) ?? undefined
|
|
407
|
+
: undefined,
|
|
408
|
+
wipe: adjustment.wipe,
|
|
409
|
+
precomposeGroupId: adjustment.precomposeGroupId
|
|
410
|
+
})),
|
|
411
|
+
precomposites: precomposites.map((group) => ({
|
|
412
|
+
id: group.clipId,
|
|
413
|
+
zIndex: trackZ(group.trackIndex),
|
|
414
|
+
stackOrder: group.stackOrder,
|
|
415
|
+
opacity: group.opacity,
|
|
416
|
+
blendMode: group.blendMode,
|
|
417
|
+
effects: group.effects,
|
|
418
|
+
transition: group.transition,
|
|
419
|
+
precomposeGroupId: group.precomposeGroupId
|
|
420
|
+
}))
|
|
421
|
+
};
|
|
422
|
+
};
|
|
423
|
+
/**
|
|
424
|
+
* True when the shutter window holds one picture, so N samples would
|
|
425
|
+
* average N copies of it.
|
|
426
|
+
*
|
|
427
|
+
* Resolving the layer set is cheap — it decodes nothing — and it answers
|
|
428
|
+
* the question that N decodes and N composites would otherwise pay for. A
|
|
429
|
+
* held title at 8 samples is the case: 8× the render for the frame it
|
|
430
|
+
* already had.
|
|
431
|
+
*/
|
|
432
|
+
const staticAt = (timeMs, samplesPerFrame) => {
|
|
433
|
+
if (samplesPerFrame <= 1)
|
|
434
|
+
return false;
|
|
435
|
+
const { layers, adjustments } = computeActiveLayersWithHorizon(sequence.tracks, sequence.clips, timeMs, {
|
|
436
|
+
canvas,
|
|
437
|
+
animationCache: animCache,
|
|
438
|
+
mediaTracks: sequence.mediaTracks,
|
|
439
|
+
camera2d: sequence.camera2d,
|
|
440
|
+
tempo: sequence.tempo
|
|
441
|
+
});
|
|
442
|
+
// The animation check follows each record's clip and parent chain.
|
|
443
|
+
// Adjustments have no source layer, so include their clips here.
|
|
444
|
+
const animationLayers = [
|
|
445
|
+
...layers,
|
|
446
|
+
...adjustments.map((adjustment) => ({
|
|
447
|
+
kind: "shape",
|
|
448
|
+
clip: adjustment.clip,
|
|
449
|
+
clipId: adjustment.clipId,
|
|
450
|
+
trackIndex: adjustment.trackIndex,
|
|
451
|
+
blendMode: "normal",
|
|
452
|
+
opacity: adjustment.opacity,
|
|
453
|
+
assetId: undefined
|
|
454
|
+
}))
|
|
455
|
+
];
|
|
456
|
+
return shutterWindowIsStatic(layers, hasActiveAnimation(animationLayers, timeMs, canvas, animCache, sequence.clips, sequence.tempo));
|
|
457
|
+
};
|
|
458
|
+
for (const [done, frame] of frameIndices.entries()) {
|
|
459
|
+
if (signal?.aborted)
|
|
460
|
+
throw abortError();
|
|
461
|
+
const timeMs = (frame * 1000) / fps;
|
|
462
|
+
const motionBlur = resolveFrameMotionBlur(sequence.clips, output.motionBlur, timeMs, frameMs, sequence.tracks);
|
|
463
|
+
for (const [sourceKey, source] of videoSources) {
|
|
464
|
+
if (source.endMs < timeMs) {
|
|
465
|
+
source.close();
|
|
466
|
+
videoSources.delete(sourceKey);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const samples = [];
|
|
470
|
+
// Sequential, not concurrent: a clip's frames come off one forward-only
|
|
471
|
+
// ffmpeg stream, so two samples decoding at once would race for it.
|
|
472
|
+
const sampleTimes = staticAt(timeMs, motionBlur.samplesPerFrame)
|
|
473
|
+
? [timeMs]
|
|
474
|
+
: motionBlurSampleTimes(timeMs, frameMs, motionBlur);
|
|
475
|
+
for (const sampleMs of sampleTimes) {
|
|
476
|
+
// Per sample, not per frame: with 32 samples a cancelled render would
|
|
477
|
+
// otherwise finish 32 composites and decodes before it noticed.
|
|
478
|
+
if (signal?.aborted)
|
|
479
|
+
throw abortError();
|
|
480
|
+
samples.push(await sampleAt(sampleMs, timeMs, samples.length, sampleTimes.length));
|
|
481
|
+
if (signal?.aborted)
|
|
482
|
+
throw abortError();
|
|
483
|
+
}
|
|
484
|
+
const pixels = await awaitWithAbort(compositor.renderFrameSamples(samples, { alpha: output.alpha }), signal);
|
|
485
|
+
if (signal?.aborted)
|
|
486
|
+
throw abortError();
|
|
487
|
+
await awaitWithAbort(writeFrame
|
|
488
|
+
? writeFrame(frame, pixels)
|
|
489
|
+
: (encoder?.write(pixels) ?? Promise.resolve()), signal);
|
|
490
|
+
opts.onProgress?.(done + 1, frameIndices.length);
|
|
491
|
+
}
|
|
492
|
+
if (signal?.aborted)
|
|
493
|
+
throw abortError();
|
|
494
|
+
if (encoder)
|
|
495
|
+
await awaitWithAbort(encoder.finish(), signal);
|
|
496
|
+
return { totalFrames: frameIndices.length, skippedClips: [...skippedClips] };
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
encoder?.abort();
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
signal?.removeEventListener("abort", closeVideoSources);
|
|
504
|
+
closeVideoSources();
|
|
505
|
+
compositor.dispose();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
//# sourceMappingURL=compositeRender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compositeRender.js","sourceRoot":"","sources":["../../../src/nodes/timeline/compositeRender.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A contact sheet: rendered frames tiled into one PNG, each cell labelled in
|
|
3
|
+
* its top-left corner. `nodetool timeline render --sheet` writes one so a
|
|
4
|
+
* frame-by-frame review is a single image rather than a folder of stills.
|
|
5
|
+
*
|
|
6
|
+
* Frames are drawn into the sheet as they arrive, so only the sheet and the
|
|
7
|
+
* frame being drawn are in memory.
|
|
8
|
+
*/
|
|
9
|
+
export interface ContactSheetLayout {
|
|
10
|
+
columns: number;
|
|
11
|
+
rows: number;
|
|
12
|
+
cellWidth: number;
|
|
13
|
+
cellHeight: number;
|
|
14
|
+
/** Space between cells and around the sheet's edge. */
|
|
15
|
+
gap: number;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Lay out `count` frames in `columns` columns. Cells keep the frame's aspect
|
|
21
|
+
* and shrink so the sheet is at most `maxWidth` wide. They never grow past the
|
|
22
|
+
* frame's own size.
|
|
23
|
+
*/
|
|
24
|
+
export declare function contactSheetLayout(options: {
|
|
25
|
+
count: number;
|
|
26
|
+
columns: number;
|
|
27
|
+
frameWidth: number;
|
|
28
|
+
frameHeight: number;
|
|
29
|
+
maxWidth?: number;
|
|
30
|
+
}): ContactSheetLayout;
|
|
31
|
+
/** The top-left pixel of the cell at `index`, filled row by row. */
|
|
32
|
+
export declare function cellOrigin(layout: ContactSheetLayout, index: number): {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
};
|
|
36
|
+
export interface ContactSheet {
|
|
37
|
+
/** Draw straight-alpha RGBA8 pixels scaled into cell `index`, then its label. */
|
|
38
|
+
draw(index: number, label: string, rgba: Uint8Array, width: number, height: number): void;
|
|
39
|
+
/** The sheet as PNG bytes. */
|
|
40
|
+
encode(): Buffer;
|
|
41
|
+
}
|
|
42
|
+
export declare function createContactSheet(layout: ContactSheetLayout): ContactSheet;
|
|
43
|
+
//# sourceMappingURL=contactSheet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contactSheet.d.ts","sourceRoot":"","sources":["../../../src/nodes/timeline/contactSheet.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,kBAAkB,CA2BrB;AAED,oEAAoE;AACpE,wBAAgB,UAAU,CACxB,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,MAAM,GACZ;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAO1B;AAED,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,IAAI,CACF,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,IAAI,CAAC;IACR,8BAA8B;IAC9B,MAAM,IAAI,MAAM,CAAC;CAClB;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAgC3E"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A contact sheet: rendered frames tiled into one PNG, each cell labelled in
|
|
3
|
+
* its top-left corner. `nodetool timeline render --sheet` writes one so a
|
|
4
|
+
* frame-by-frame review is a single image rather than a folder of stills.
|
|
5
|
+
*
|
|
6
|
+
* Frames are drawn into the sheet as they arrive, so only the sheet and the
|
|
7
|
+
* frame being drawn are in memory.
|
|
8
|
+
*/
|
|
9
|
+
import { createCanvas } from "@napi-rs/canvas";
|
|
10
|
+
import { registerBundledFonts } from "@nodetool-ai/timeline/fonts/node";
|
|
11
|
+
/** Widest sheet, in pixels, before cells are scaled down to fit. */
|
|
12
|
+
const MAX_SHEET_WIDTH = 2400;
|
|
13
|
+
const GAP = 8;
|
|
14
|
+
/**
|
|
15
|
+
* Lay out `count` frames in `columns` columns. Cells keep the frame's aspect
|
|
16
|
+
* and shrink so the sheet is at most `maxWidth` wide. They never grow past the
|
|
17
|
+
* frame's own size.
|
|
18
|
+
*/
|
|
19
|
+
export function contactSheetLayout(options) {
|
|
20
|
+
const { count, frameWidth, frameHeight } = options;
|
|
21
|
+
if (!Number.isInteger(options.columns) || options.columns < 1) {
|
|
22
|
+
throw new Error(`A contact sheet needs a whole number of columns (got ${options.columns}).`);
|
|
23
|
+
}
|
|
24
|
+
const columns = Math.min(options.columns, Math.max(1, count));
|
|
25
|
+
const rows = Math.max(1, Math.ceil(count / columns));
|
|
26
|
+
const maxWidth = options.maxWidth ?? MAX_SHEET_WIDTH;
|
|
27
|
+
const cellWidth = Math.max(1, Math.min(frameWidth, Math.floor((maxWidth - GAP * (columns + 1)) / columns)));
|
|
28
|
+
const cellHeight = Math.max(1, Math.round((cellWidth * frameHeight) / frameWidth));
|
|
29
|
+
return {
|
|
30
|
+
columns,
|
|
31
|
+
rows,
|
|
32
|
+
cellWidth,
|
|
33
|
+
cellHeight,
|
|
34
|
+
gap: GAP,
|
|
35
|
+
width: columns * cellWidth + (columns + 1) * GAP,
|
|
36
|
+
height: rows * cellHeight + (rows + 1) * GAP
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** The top-left pixel of the cell at `index`, filled row by row. */
|
|
40
|
+
export function cellOrigin(layout, index) {
|
|
41
|
+
const column = index % layout.columns;
|
|
42
|
+
const row = Math.floor(index / layout.columns);
|
|
43
|
+
return {
|
|
44
|
+
x: layout.gap + column * (layout.cellWidth + layout.gap),
|
|
45
|
+
y: layout.gap + row * (layout.cellHeight + layout.gap)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function createContactSheet(layout) {
|
|
49
|
+
registerBundledFonts();
|
|
50
|
+
const sheet = createCanvas(layout.width, layout.height);
|
|
51
|
+
const ctx = sheet.getContext("2d");
|
|
52
|
+
ctx.fillStyle = "#202020";
|
|
53
|
+
ctx.fillRect(0, 0, layout.width, layout.height);
|
|
54
|
+
const fontSize = Math.max(12, Math.round(layout.cellHeight * 0.08));
|
|
55
|
+
const padding = Math.round(fontSize * 0.35);
|
|
56
|
+
return {
|
|
57
|
+
draw(index, label, rgba, width, height) {
|
|
58
|
+
const frame = createCanvas(width, height);
|
|
59
|
+
const frameCtx = frame.getContext("2d");
|
|
60
|
+
const image = frameCtx.createImageData(width, height);
|
|
61
|
+
image.data.set(rgba);
|
|
62
|
+
frameCtx.putImageData(image, 0, 0);
|
|
63
|
+
const { x, y } = cellOrigin(layout, index);
|
|
64
|
+
ctx.drawImage(frame, x, y, layout.cellWidth, layout.cellHeight);
|
|
65
|
+
ctx.font = `600 ${fontSize}px Inter, sans-serif`;
|
|
66
|
+
ctx.textBaseline = "top";
|
|
67
|
+
const textWidth = ctx.measureText(label).width;
|
|
68
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
|
|
69
|
+
ctx.fillRect(x, y, textWidth + padding * 2, fontSize + padding * 2);
|
|
70
|
+
ctx.fillStyle = "#ffffff";
|
|
71
|
+
ctx.fillText(label, x + padding, y + padding);
|
|
72
|
+
},
|
|
73
|
+
encode() {
|
|
74
|
+
return sheet.toBuffer("image/png");
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=contactSheet.js.map
|