sketchmark 2.1.1 → 2.1.2

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 (41) hide show
  1. package/package.json +1 -1
  2. package/dist/src/builders/index.d.ts +0 -64
  3. package/dist/src/builders/index.js +0 -212
  4. package/dist/src/compounds.d.ts +0 -13
  5. package/dist/src/compounds.js +0 -118
  6. package/dist/src/deck.d.ts +0 -4
  7. package/dist/src/deck.js +0 -91
  8. package/dist/src/export/index.d.ts +0 -8
  9. package/dist/src/export/index.js +0 -15
  10. package/dist/src/kernel.d.ts +0 -8
  11. package/dist/src/kernel.js +0 -68
  12. package/dist/src/motion.d.ts +0 -4
  13. package/dist/src/motion.js +0 -262
  14. package/dist/src/patch.d.ts +0 -5
  15. package/dist/src/patch.js +0 -72
  16. package/dist/src/player/index.d.ts +0 -68
  17. package/dist/src/player/index.js +0 -600
  18. package/dist/src/project.d.ts +0 -11
  19. package/dist/src/project.js +0 -107
  20. package/dist/src/render/raw-three.d.ts +0 -7
  21. package/dist/src/render/raw-three.js +0 -17
  22. package/dist/src/render/three-html.d.ts +0 -2
  23. package/dist/src/render/three-html.js +0 -257
  24. package/dist/src/render/three-preview-svg.d.ts +0 -3
  25. package/dist/src/render/three-preview-svg.js +0 -102
  26. package/dist/src/scenes.d.ts +0 -4
  27. package/dist/src/scenes.js +0 -26
  28. package/dist/src/sequences.d.ts +0 -43
  29. package/dist/src/sequences.js +0 -109
  30. package/dist/src/shapes/builtins.d.ts +0 -2
  31. package/dist/src/shapes/builtins.js +0 -393
  32. package/dist/src/shapes/common.d.ts +0 -9
  33. package/dist/src/shapes/common.js +0 -76
  34. package/dist/src/shapes/geometry.d.ts +0 -22
  35. package/dist/src/shapes/geometry.js +0 -166
  36. package/dist/src/shapes/index.d.ts +0 -2
  37. package/dist/src/shapes/index.js +0 -18
  38. package/dist/src/shapes/registry.d.ts +0 -8
  39. package/dist/src/shapes/registry.js +0 -31
  40. package/dist/src/shapes/types.d.ts +0 -32
  41. package/dist/src/shapes/types.js +0 -2
@@ -1,600 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SketchmarkPlayer = exports.VisualPlayer = exports.documentForDeckStep = void 0;
4
- exports.createVisualPlayer = createVisualPlayer;
5
- exports.createSketchmarkPlayer = createSketchmarkPlayer;
6
- exports.exportVisualFromBrowser = exportVisualFromBrowser;
7
- exports.downloadBrowserExport = downloadBrowserExport;
8
- const deck_1 = require("../deck");
9
- Object.defineProperty(exports, "documentForDeckStep", { enumerable: true, get: function () { return deck_1.documentForDeckStep; } });
10
- const scenes_1 = require("../scenes");
11
- const sequences_1 = require("../sequences");
12
- const svg_1 = require("../render/svg");
13
- const three_preview_svg_1 = require("../render/three-preview-svg");
14
- class VisualPlayer {
15
- constructor(root, options) {
16
- var _a, _b;
17
- this.raf = 0;
18
- this.startedAt = 0;
19
- this.startedTime = 0;
20
- this.currentTime = 0;
21
- this.playing = false;
22
- this.tick = () => {
23
- if (!this.playing)
24
- return;
25
- const duration = this.duration();
26
- const elapsed = (now() - this.startedAt) / 1000;
27
- let nextTime = this.startedTime + elapsed;
28
- if (duration > 0 && nextTime > duration) {
29
- if (this.loop) {
30
- nextTime = nextTime % duration;
31
- this.startedAt = now();
32
- this.startedTime = nextTime;
33
- }
34
- else {
35
- nextTime = duration;
36
- this.playing = false;
37
- }
38
- }
39
- this.render(nextTime);
40
- if (this.playing)
41
- this.raf = requestAnimationFrame(this.tick);
42
- };
43
- this.root = root;
44
- this.document = options.document;
45
- this.loop = options.loop ?? true;
46
- this.onFrame = options.onFrame;
47
- this.onError = options.onError;
48
- this.ready = Promise.resolve(this);
49
- (_a = this.root.style).position || (_a.position = "relative");
50
- (_b = this.root.style).overflow || (_b.overflow = "hidden");
51
- this.render(0);
52
- if (options.autoplay)
53
- this.play();
54
- }
55
- play() {
56
- if (this.playing)
57
- return;
58
- this.playing = true;
59
- this.startedAt = now();
60
- this.startedTime = this.currentTime;
61
- this.tick();
62
- }
63
- pause() {
64
- this.playing = false;
65
- if (this.raf)
66
- cancelAnimationFrame(this.raf);
67
- this.raf = 0;
68
- this.emit();
69
- }
70
- seek(time) {
71
- this.currentTime = clamp(time, 0, this.duration());
72
- this.render(this.currentTime);
73
- }
74
- render(time = this.currentTime) {
75
- try {
76
- this.currentTime = clamp(time, 0, this.duration());
77
- this.root.innerHTML = this.toSvg(this.currentTime);
78
- this.emit();
79
- }
80
- catch (error) {
81
- this.handleError(error);
82
- }
83
- }
84
- setDocument(document) {
85
- this.document = document;
86
- this.seek(0);
87
- }
88
- getDocument() {
89
- return this.document;
90
- }
91
- getState() {
92
- return { time: this.currentTime, duration: this.duration(), playing: this.playing };
93
- }
94
- toSvg(time = this.currentTime) {
95
- return renderFrameToSvg(this.document, clamp(time, 0, this.duration()));
96
- }
97
- downloadSvg(filename = "sketchmark.svg", time = this.currentTime) {
98
- if (typeof document === "undefined" || typeof URL === "undefined" || typeof Blob === "undefined") {
99
- throw new Error("downloadSvg is only available in a browser.");
100
- }
101
- const blob = new Blob([this.toSvg(time)], { type: "image/svg+xml;charset=utf-8" });
102
- const url = URL.createObjectURL(blob);
103
- const link = document.createElement("a");
104
- link.href = url;
105
- link.download = filename;
106
- link.click();
107
- URL.revokeObjectURL(url);
108
- }
109
- exportBrowser(format, options = {}) {
110
- return exportVisualFromBrowser(this, format, options);
111
- }
112
- async download(format, options = {}) {
113
- const result = await this.exportBrowser(format, options);
114
- downloadBrowserExport(result);
115
- return result;
116
- }
117
- destroy() {
118
- this.pause();
119
- this.root.innerHTML = "";
120
- }
121
- duration() {
122
- return Math.max(0, visualDuration(this.document));
123
- }
124
- emit() {
125
- this.onFrame?.({ time: this.currentTime, duration: this.duration(), playing: this.playing });
126
- }
127
- handleError(error) {
128
- const normalized = error instanceof Error ? error : new Error(String(error));
129
- this.onError?.(normalized);
130
- }
131
- }
132
- exports.VisualPlayer = VisualPlayer;
133
- function createVisualPlayer(root, options) {
134
- return new VisualPlayer(root, options);
135
- }
136
- class SketchmarkPlayer extends VisualPlayer {
137
- }
138
- exports.SketchmarkPlayer = SketchmarkPlayer;
139
- function createSketchmarkPlayer(root, options) {
140
- return new SketchmarkPlayer(root, options);
141
- }
142
- async function exportVisualFromBrowser(player, format, options = {}) {
143
- ensureBrowserExport();
144
- const document = options.document ?? player.getDocument();
145
- const sourceDocument = options.sourceDocument ?? document;
146
- const title = options.title ?? "visual";
147
- const duration = Math.max(0.1, visualDuration(document));
148
- const fps = Math.max(1, Math.min(60, Number(document.canvas.fps ?? 30)));
149
- const time = clamp(options.time ?? player.getState().time, 0, duration);
150
- const svg = renderFrameToSvg(document, time);
151
- const width = document.canvas.width;
152
- const height = document.canvas.height;
153
- if (format === "svg") {
154
- return makeExportResult(new Blob([svg], { type: "image/svg+xml;charset=utf-8" }), options.filename, title, "svg", "image/svg+xml;charset=utf-8");
155
- }
156
- if (format === "json") {
157
- const json = JSON.stringify(sourceDocument, null, 2);
158
- return makeExportResult(new Blob([json], { type: "application/json;charset=utf-8" }), options.filename, title, "json", "application/json;charset=utf-8");
159
- }
160
- if (format === "html") {
161
- const html = standaloneHtml(title, svg);
162
- return makeExportResult(new Blob([html], { type: "text/html;charset=utf-8" }), options.filename, title, "html", "text/html;charset=utf-8");
163
- }
164
- if (format === "png" || format === "jpg") {
165
- const canvas = await svgToCanvas(svg, width, height);
166
- const mimeType = format === "jpg" ? "image/jpeg" : "image/png";
167
- const blob = await canvasToBlob(canvas, mimeType, 0.92);
168
- return makeExportResult(blob, options.filename, title, format, mimeType);
169
- }
170
- const video = await recordVideo(document, title, duration, fps);
171
- const result = makeExportResult(video.blob, options.filename, title, video.extension, video.mimeType);
172
- if (video.extension === "webm")
173
- result.fallback = "webm";
174
- return result;
175
- }
176
- function downloadBrowserExport(result) {
177
- ensureBrowserExport();
178
- const url = URL.createObjectURL(result.blob);
179
- const link = document.createElement("a");
180
- link.href = url;
181
- link.download = result.filename;
182
- link.click();
183
- URL.revokeObjectURL(url);
184
- }
185
- function now() {
186
- return typeof performance !== "undefined" ? performance.now() : Date.now();
187
- }
188
- function clamp(value, min, max) {
189
- return Math.max(min, Math.min(max, value));
190
- }
191
- function visualDuration(document) {
192
- const sequence = (0, sequences_1.defaultSequenceId)(document);
193
- if (sequence)
194
- return Math.max(0, (0, sequences_1.compileVisualSequence)(document, sequence).duration);
195
- const scene = firstSceneId(document);
196
- if (scene && !hasTopLevelElements(document)) {
197
- return Math.max(0, Number(document.scenes?.[scene]?.canvas?.duration ?? document.canvas.duration ?? 0));
198
- }
199
- return Math.max(0, Number(document.canvas.duration ?? 0));
200
- }
201
- function renderFrameToSvg(document, time) {
202
- const frame = frameDocument(document, time);
203
- if (frame.document.canvas.renderer === "three") {
204
- return (0, three_preview_svg_1.renderThreePreviewSvg)(frame.document, frame.localTime);
205
- }
206
- return (0, svg_1.renderToSvg)(frame.document, { time: frame.localTime });
207
- }
208
- function frameDocument(document, time) {
209
- const sequence = (0, sequences_1.defaultSequenceId)(document);
210
- if (sequence) {
211
- const frame = (0, sequences_1.documentForSequenceTime)(document, sequence, time);
212
- return { document: frame.document, localTime: frame.localTime };
213
- }
214
- const scene = firstSceneId(document);
215
- if (scene && !hasTopLevelElements(document)) {
216
- return { document: (0, scenes_1.documentForScene)(document, scene), localTime: time };
217
- }
218
- return { document, localTime: time };
219
- }
220
- function firstSceneId(document) {
221
- return Object.keys(document.scenes ?? {})[0];
222
- }
223
- function hasTopLevelElements(document) {
224
- return Array.isArray(document.elements) && document.elements.length > 0;
225
- }
226
- function ensureBrowserExport() {
227
- if (typeof document === "undefined" || typeof URL === "undefined" || typeof Blob === "undefined") {
228
- throw new Error("Browser export is only available in a browser.");
229
- }
230
- }
231
- function makeExportResult(blob, filename, title, extension, mimeType) {
232
- return {
233
- blob: blob.type === mimeType ? blob : new Blob([blob], { type: mimeType }),
234
- filename: withExtension(filename ?? slug(title), extension),
235
- mimeType,
236
- extension
237
- };
238
- }
239
- function withExtension(filename, extension) {
240
- const cleaned = filename.trim() || "visual";
241
- return cleaned.toLowerCase().endsWith(`.${extension}`) ? cleaned : `${cleaned.replace(/\.[a-z0-9]+$/i, "")}.${extension}`;
242
- }
243
- function standaloneHtml(title, svg) {
244
- return `<!doctype html>
245
- <html>
246
- <head>
247
- <meta charset="utf-8">
248
- <meta name="viewport" content="width=device-width,initial-scale=1">
249
- <title>${escapeHtml(title)}</title>
250
- <style>html,body{margin:0;min-height:100%;display:grid;place-items:center;background:#f5f3ef}svg{max-width:100vw;max-height:100vh;width:auto;height:auto}</style>
251
- </head>
252
- <body>${svg}</body>
253
- </html>`;
254
- }
255
- async function svgToCanvas(svg, width, height) {
256
- const canvas = document.createElement("canvas");
257
- canvas.width = width;
258
- canvas.height = height;
259
- await drawSvgToCanvas(svg, canvas, width, height);
260
- return canvas;
261
- }
262
- async function drawSvgToCanvas(svg, canvas, width, height) {
263
- const ctx = canvas.getContext("2d");
264
- if (!ctx)
265
- throw new Error("Canvas export is not supported in this browser.");
266
- const url = URL.createObjectURL(new Blob([svg], { type: "image/svg+xml;charset=utf-8" }));
267
- try {
268
- const image = await loadImage(url);
269
- ctx.clearRect(0, 0, width, height);
270
- ctx.drawImage(image, 0, 0, width, height);
271
- }
272
- finally {
273
- URL.revokeObjectURL(url);
274
- }
275
- }
276
- function loadImage(url) {
277
- return new Promise((resolve, reject) => {
278
- const image = new Image();
279
- image.onload = () => resolve(image);
280
- image.onerror = () => reject(new Error("Could not rasterize SVG. External images may block browser export."));
281
- image.src = url;
282
- });
283
- }
284
- function canvasToBlob(canvas, type, quality) {
285
- return new Promise((resolve, reject) => {
286
- canvas.toBlob((blob) => {
287
- if (blob)
288
- resolve(blob);
289
- else
290
- reject(new Error("Could not create image export."));
291
- }, type, quality);
292
- });
293
- }
294
- async function recordVideo(visualDocument, title, duration, fps) {
295
- const width = visualDocument.canvas.width;
296
- const height = visualDocument.canvas.height;
297
- try {
298
- return await recordMp4WithExternalMuxer(visualDocument, width, height, duration, fps);
299
- }
300
- catch (error) {
301
- console.warn("mp4-muxer WebCodecs export failed; trying built-in MP4 muxer.", error);
302
- }
303
- try {
304
- return await recordMp4WithWebCodecs(visualDocument, width, height, duration, fps);
305
- }
306
- catch (error) {
307
- console.warn("Built-in WebCodecs MP4 export failed; falling back to WebM.", error);
308
- return recordWebmWithMediaRecorder(visualDocument, width, height, duration, fps, title);
309
- }
310
- }
311
- async function recordMp4WithExternalMuxer(visualDocument, width, height, duration, fps) {
312
- const VideoEncoderCtor = globalThis.VideoEncoder;
313
- const VideoFrameCtor = globalThis.VideoFrame;
314
- if (!VideoEncoderCtor || !VideoFrameCtor) {
315
- throw new Error("WebCodecs is not available.");
316
- }
317
- const { Muxer, ArrayBufferTarget } = await loadMp4Muxer();
318
- const target = new ArrayBufferTarget();
319
- const muxer = new Muxer({
320
- target,
321
- video: { codec: "avc", width, height },
322
- fastStart: "in-memory"
323
- });
324
- const encoder = new VideoEncoderCtor({
325
- output: (chunk, metadata) => muxer.addVideoChunk(chunk, metadata),
326
- error(error) {
327
- throw error;
328
- }
329
- });
330
- encoder.configure({
331
- codec: "avc1.640028",
332
- width,
333
- height,
334
- bitrate: Math.max(1500000, Math.min(12000000, width * height * fps * 0.18)),
335
- framerate: fps
336
- });
337
- const canvas = document.createElement("canvas");
338
- canvas.width = width;
339
- canvas.height = height;
340
- const totalFrames = Math.max(1, Math.ceil(duration * fps));
341
- const frameDurationUs = Math.round(1000000 / fps);
342
- try {
343
- for (let frameIndex = 0; frameIndex < totalFrames; frameIndex += 1) {
344
- const time = Math.min(duration, frameIndex / fps);
345
- await drawSvgToCanvas(renderFrameToSvg(visualDocument, time), canvas, width, height);
346
- const frame = new VideoFrameCtor(canvas, {
347
- timestamp: frameIndex * frameDurationUs,
348
- duration: frameDurationUs
349
- });
350
- encoder.encode(frame, { keyFrame: frameIndex % Math.max(1, Math.round(fps * 2)) === 0 });
351
- frame.close();
352
- if (frameIndex % 8 === 0)
353
- await wait(0);
354
- }
355
- await encoder.flush();
356
- }
357
- finally {
358
- encoder.close();
359
- }
360
- muxer.finalize();
361
- return {
362
- blob: new Blob([muxer.target.buffer], { type: "video/mp4" }),
363
- mimeType: "video/mp4",
364
- extension: "mp4"
365
- };
366
- }
367
- async function recordMp4WithWebCodecs(visualDocument, width, height, duration, fps) {
368
- const VideoEncoderCtor = globalThis.VideoEncoder;
369
- const VideoFrameCtor = globalThis.VideoFrame;
370
- if (!VideoEncoderCtor || !VideoFrameCtor) {
371
- throw new Error("WebCodecs is not available.");
372
- }
373
- const codec = "avc1.42001f";
374
- const config = {
375
- codec,
376
- width,
377
- height,
378
- framerate: fps,
379
- bitrate: Math.max(1500000, Math.min(8000000, width * height * fps * 0.08)),
380
- hardwareAcceleration: "prefer-hardware",
381
- avc: { format: "avc" }
382
- };
383
- const support = await VideoEncoderCtor.isConfigSupported(config);
384
- if (!support.supported) {
385
- throw new Error("H.264 WebCodecs MP4 export is not supported in this browser.");
386
- }
387
- const canvas = document.createElement("canvas");
388
- canvas.width = width;
389
- canvas.height = height;
390
- const samples = [];
391
- let avcConfig;
392
- const timescale = 90000;
393
- const sampleDuration = Math.max(1, Math.round(timescale / fps));
394
- const encoder = new VideoEncoderCtor({
395
- output(chunk, metadata) {
396
- const data = new Uint8Array(chunk.byteLength);
397
- chunk.copyTo(data);
398
- const description = metadata?.decoderConfig?.description;
399
- if (description)
400
- avcConfig = new Uint8Array(description);
401
- samples.push({ data, duration: sampleDuration, isKey: chunk.type === "key" });
402
- },
403
- error(error) {
404
- throw error;
405
- }
406
- });
407
- encoder.configure(support.config);
408
- const totalFrames = Math.max(1, Math.ceil(duration * fps));
409
- const frameDurationUs = Math.round(1000000 / fps);
410
- for (let frameIndex = 0; frameIndex < totalFrames; frameIndex += 1) {
411
- const time = Math.min(duration, frameIndex / fps);
412
- await drawSvgToCanvas(renderFrameToSvg(visualDocument, time), canvas, width, height);
413
- const bitmap = typeof createImageBitmap === "function" ? await createImageBitmap(canvas) : canvas;
414
- const frame = new VideoFrameCtor(bitmap, {
415
- timestamp: frameIndex * frameDurationUs,
416
- duration: frameDurationUs
417
- });
418
- encoder.encode(frame, { keyFrame: frameIndex % Math.max(1, Math.round(fps)) === 0 });
419
- frame.close();
420
- if ("close" in bitmap && typeof bitmap.close === "function")
421
- bitmap.close();
422
- }
423
- await encoder.flush();
424
- encoder.close();
425
- if (!samples.length || !avcConfig) {
426
- throw new Error("WebCodecs did not produce H.264 MP4 data.");
427
- }
428
- const mp4Bytes = muxMp4({ width, height, timescale, samples, avcConfig });
429
- const mp4Buffer = new ArrayBuffer(mp4Bytes.byteLength);
430
- new Uint8Array(mp4Buffer).set(mp4Bytes);
431
- return {
432
- blob: new Blob([mp4Buffer], { type: "video/mp4" }),
433
- mimeType: "video/mp4",
434
- extension: "mp4"
435
- };
436
- }
437
- async function recordWebmWithMediaRecorder(visualDocument, width, height, duration, fps, title) {
438
- if (typeof MediaRecorder === "undefined") {
439
- throw new Error("Browser video export needs WebCodecs or MediaRecorder support.");
440
- }
441
- const canvas = document.createElement("canvas");
442
- canvas.width = width;
443
- canvas.height = height;
444
- const stream = canvas.captureStream(0);
445
- const track = stream.getVideoTracks()[0];
446
- const mp4 = "video/mp4";
447
- const webm = MediaRecorder.isTypeSupported("video/webm;codecs=vp9") ? "video/webm;codecs=vp9" : "video/webm";
448
- const mimeType = MediaRecorder.isTypeSupported(mp4) ? mp4 : webm;
449
- const extension = mimeType === mp4 ? "mp4" : "webm";
450
- const chunks = [];
451
- const recorder = new MediaRecorder(stream, { mimeType });
452
- const stopped = new Promise((resolve, reject) => {
453
- recorder.ondataavailable = (event) => {
454
- if (event.data.size > 0)
455
- chunks.push(event.data);
456
- };
457
- recorder.onerror = () => reject(new Error(`Could not record "${title}".`));
458
- recorder.onstop = () => resolve(new Blob(chunks, { type: mimeType }));
459
- });
460
- recorder.start();
461
- const totalFrames = Math.max(1, Math.ceil(duration * fps));
462
- for (let frame = 0; frame <= totalFrames; frame += 1) {
463
- const time = Math.min(duration, frame / fps);
464
- await drawSvgToCanvas(renderFrameToSvg(visualDocument, time), canvas, width, height);
465
- track.requestFrame?.();
466
- await wait(1000 / fps);
467
- }
468
- recorder.stop();
469
- const blob = await stopped;
470
- track.stop();
471
- return { blob, mimeType, extension };
472
- }
473
- function muxMp4(input) {
474
- const sampleBytes = concatBytes(input.samples.map((sample) => sample.data));
475
- const ftyp = box("ftyp", ascii("isom"), u32(0x200), ascii("isom"), ascii("iso2"), ascii("avc1"), ascii("mp41"));
476
- const placeholderMoov = moovBox(input, 0);
477
- const mdatHeaderSize = 8;
478
- const chunkOffset = ftyp.byteLength + placeholderMoov.byteLength + mdatHeaderSize;
479
- const moov = moovBox(input, chunkOffset);
480
- const mdat = box("mdat", sampleBytes);
481
- return concatBytes([ftyp, moov, mdat]);
482
- }
483
- function moovBox(input, chunkOffset) {
484
- const duration = input.samples.reduce((sum, sample) => sum + sample.duration, 0);
485
- return box("moov", mvhd(input.timescale, duration), trakBox(input, duration, chunkOffset));
486
- }
487
- function mvhd(timescale, duration) {
488
- return fullBox("mvhd", 0, 0, u32(0), u32(0), u32(timescale), u32(duration), u32(0x00010000), u16(0x0100), u16(0), zeros(8), matrix(), zeros(24), u32(2));
489
- }
490
- function trakBox(input, duration, chunkOffset) {
491
- return box("trak", tkhd(input.width, input.height, duration), mdiaBox(input, duration, chunkOffset));
492
- }
493
- function tkhd(width, height, duration) {
494
- return fullBox("tkhd", 0, 0x000007, u32(0), u32(0), u32(1), u32(0), u32(duration), zeros(8), u16(0), u16(0), u16(0), u16(0), matrix(), u32(width * 65536), u32(height * 65536));
495
- }
496
- function mdiaBox(input, duration, chunkOffset) {
497
- return box("mdia", mdhd(input.timescale, duration), hdlr(), minfBox(input, chunkOffset));
498
- }
499
- function mdhd(timescale, duration) {
500
- return fullBox("mdhd", 0, 0, u32(0), u32(0), u32(timescale), u32(duration), u16(0x55c4), u16(0));
501
- }
502
- function hdlr() {
503
- return fullBox("hdlr", 0, 0, u32(0), ascii("vide"), zeros(12), ascii("VideoHandler\0"));
504
- }
505
- function minfBox(input, chunkOffset) {
506
- return box("minf", fullBox("vmhd", 0, 1, u16(0), u16(0), u16(0), u16(0)), dinf(), stblBox(input, chunkOffset));
507
- }
508
- function dinf() {
509
- return box("dinf", fullBox("dref", 0, 0, u32(1), fullBox("url ", 0, 1)));
510
- }
511
- function stblBox(input, chunkOffset) {
512
- return box("stbl", stsd(input.width, input.height, input.avcConfig), stts(input.samples), stss(input.samples), stsc(input.samples.length), stsz(input.samples), stco(chunkOffset));
513
- }
514
- function stsd(width, height, avcConfig) {
515
- const avc1 = box("avc1", zeros(6), u16(1), zeros(16), u16(width), u16(height), u32(0x00480000), u32(0x00480000), u32(0), u16(1), compressorName("Sketchmark WebCodecs"), u16(0x0018), u16(0xffff), box("avcC", avcConfig));
516
- return fullBox("stsd", 0, 0, u32(1), avc1);
517
- }
518
- function stts(samples) {
519
- const groups = [];
520
- for (const sample of samples) {
521
- const last = groups[groups.length - 1];
522
- if (last && last.duration === sample.duration)
523
- last.count += 1;
524
- else
525
- groups.push({ count: 1, duration: sample.duration });
526
- }
527
- return fullBox("stts", 0, 0, u32(groups.length), ...groups.flatMap((group) => [u32(group.count), u32(group.duration)]));
528
- }
529
- function stss(samples) {
530
- const keys = samples.map((sample, index) => sample.isKey ? index + 1 : 0).filter(Boolean);
531
- return fullBox("stss", 0, 0, u32(keys.length), ...keys.map((index) => u32(index)));
532
- }
533
- function stsc(sampleCount) {
534
- return fullBox("stsc", 0, 0, u32(1), u32(1), u32(sampleCount), u32(1));
535
- }
536
- function stsz(samples) {
537
- return fullBox("stsz", 0, 0, u32(0), u32(samples.length), ...samples.map((sample) => u32(sample.data.byteLength)));
538
- }
539
- function stco(offset) {
540
- return fullBox("stco", 0, 0, u32(1), u32(offset));
541
- }
542
- function box(type, ...payloads) {
543
- const size = 8 + payloads.reduce((sum, payload) => sum + payload.byteLength, 0);
544
- return concatBytes([u32(size), ascii(type), ...payloads]);
545
- }
546
- function fullBox(type, version, flags, ...payloads) {
547
- return box(type, new Uint8Array([version, (flags >> 16) & 255, (flags >> 8) & 255, flags & 255]), ...payloads);
548
- }
549
- function concatBytes(parts) {
550
- const total = parts.reduce((sum, part) => sum + part.byteLength, 0);
551
- const output = new Uint8Array(total);
552
- let offset = 0;
553
- for (const part of parts) {
554
- output.set(part, offset);
555
- offset += part.byteLength;
556
- }
557
- return output;
558
- }
559
- function ascii(value) {
560
- const output = new Uint8Array(value.length);
561
- for (let index = 0; index < value.length; index += 1)
562
- output[index] = value.charCodeAt(index) & 255;
563
- return output;
564
- }
565
- function u16(value) {
566
- const output = new Uint8Array(2);
567
- new DataView(output.buffer).setUint16(0, value);
568
- return output;
569
- }
570
- function u32(value) {
571
- const output = new Uint8Array(4);
572
- new DataView(output.buffer).setUint32(0, value >>> 0);
573
- return output;
574
- }
575
- function zeros(length) {
576
- return new Uint8Array(length);
577
- }
578
- function matrix() {
579
- return concatBytes([u32(0x00010000), u32(0), u32(0), u32(0), u32(0x00010000), u32(0), u32(0), u32(0), u32(0x40000000)]);
580
- }
581
- function compressorName(value) {
582
- const output = new Uint8Array(32);
583
- const text = ascii(value.slice(0, 31));
584
- output[0] = text.byteLength;
585
- output.set(text, 1);
586
- return output;
587
- }
588
- function wait(ms) {
589
- return new Promise((resolve) => setTimeout(resolve, ms));
590
- }
591
- async function loadMp4Muxer() {
592
- const dynamicImport = new Function("url", "return import(url)");
593
- return dynamicImport("https://cdn.jsdelivr.net/npm/mp4-muxer@5.2.2/build/mp4-muxer.mjs");
594
- }
595
- function escapeHtml(value) {
596
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
597
- }
598
- function slug(value) {
599
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "visual";
600
- }
@@ -1,11 +0,0 @@
1
- import type { ValidationResult, VisualDocument, VisualSymbol } from "./types";
2
- export interface VisualProject {
3
- root: string;
4
- entry: string;
5
- document: VisualDocument;
6
- files: Record<string, VisualDocument>;
7
- symbols: VisualSymbol[];
8
- }
9
- export declare function loadVisualProject(entryPath: string): VisualProject;
10
- export declare function buildSymbolIndex(project: Pick<VisualProject, "files" | "document">): VisualSymbol[];
11
- export declare function validateVisualProject(project: Pick<VisualProject, "document" | "files" | "symbols">): ValidationResult;