deio-scroll 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/dist/api/server.d.ts +3 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +393 -0
- package/dist/api/server.js.map +1 -0
- package/dist/artifacts/store.d.ts +24 -0
- package/dist/artifacts/store.d.ts.map +1 -0
- package/dist/artifacts/store.js +43 -0
- package/dist/artifacts/store.js.map +1 -0
- package/dist/browser/componentInteraction.d.ts +24 -0
- package/dist/browser/componentInteraction.d.ts.map +1 -0
- package/dist/browser/componentInteraction.js +328 -0
- package/dist/browser/componentInteraction.js.map +1 -0
- package/dist/browser/composition.d.ts +42 -0
- package/dist/browser/composition.d.ts.map +1 -0
- package/dist/browser/composition.js +132 -0
- package/dist/browser/composition.js.map +1 -0
- package/dist/browser/cookies.d.ts +3 -0
- package/dist/browser/cookies.d.ts.map +1 -0
- package/dist/browser/cookies.js +20 -0
- package/dist/browser/cookies.js.map +1 -0
- package/dist/browser/curves.d.ts +4 -0
- package/dist/browser/curves.d.ts.map +1 -0
- package/dist/browser/curves.js +35 -0
- package/dist/browser/curves.js.map +1 -0
- package/dist/browser/detectScrollMode.d.ts +5 -0
- package/dist/browser/detectScrollMode.d.ts.map +1 -0
- package/dist/browser/detectScrollMode.js +61 -0
- package/dist/browser/detectScrollMode.js.map +1 -0
- package/dist/browser/directionGuardrails.d.ts +22 -0
- package/dist/browser/directionGuardrails.d.ts.map +1 -0
- package/dist/browser/directionGuardrails.js +99 -0
- package/dist/browser/directionGuardrails.js.map +1 -0
- package/dist/browser/goto.d.ts +9 -0
- package/dist/browser/goto.d.ts.map +1 -0
- package/dist/browser/goto.js +62 -0
- package/dist/browser/goto.js.map +1 -0
- package/dist/browser/hydrate.d.ts +8 -0
- package/dist/browser/hydrate.d.ts.map +1 -0
- package/dist/browser/hydrate.js +73 -0
- package/dist/browser/hydrate.js.map +1 -0
- package/dist/browser/mediaClock.d.ts +9 -0
- package/dist/browser/mediaClock.d.ts.map +1 -0
- package/dist/browser/mediaClock.js +86 -0
- package/dist/browser/mediaClock.js.map +1 -0
- package/dist/browser/motion.d.ts +21 -0
- package/dist/browser/motion.d.ts.map +1 -0
- package/dist/browser/motion.js +42 -0
- package/dist/browser/motion.js.map +1 -0
- package/dist/browser/networkPolicy.d.ts +11 -0
- package/dist/browser/networkPolicy.d.ts.map +1 -0
- package/dist/browser/networkPolicy.js +119 -0
- package/dist/browser/networkPolicy.js.map +1 -0
- package/dist/browser/prime.d.ts +3 -0
- package/dist/browser/prime.d.ts.map +1 -0
- package/dist/browser/prime.js +13 -0
- package/dist/browser/prime.js.map +1 -0
- package/dist/browser/sanitize.d.ts +3 -0
- package/dist/browser/sanitize.d.ts.map +1 -0
- package/dist/browser/sanitize.js +37 -0
- package/dist/browser/sanitize.js.map +1 -0
- package/dist/browser/scroll.d.ts +29 -0
- package/dist/browser/scroll.d.ts.map +1 -0
- package/dist/browser/scroll.js +420 -0
- package/dist/browser/scroll.js.map +1 -0
- package/dist/browser/scrollEasing.d.ts +5 -0
- package/dist/browser/scrollEasing.d.ts.map +1 -0
- package/dist/browser/scrollEasing.js +40 -0
- package/dist/browser/scrollEasing.js.map +1 -0
- package/dist/browser/scrollReadiness.d.ts +25 -0
- package/dist/browser/scrollReadiness.d.ts.map +1 -0
- package/dist/browser/scrollReadiness.js +105 -0
- package/dist/browser/scrollReadiness.js.map +1 -0
- package/dist/browser/urlGuard.d.ts +4 -0
- package/dist/browser/urlGuard.d.ts.map +1 -0
- package/dist/browser/urlGuard.js +22 -0
- package/dist/browser/urlGuard.js.map +1 -0
- package/dist/browser/virtualScroll.d.ts +19 -0
- package/dist/browser/virtualScroll.d.ts.map +1 -0
- package/dist/browser/virtualScroll.js +50 -0
- package/dist/browser/virtualScroll.js.map +1 -0
- package/dist/capture/detectMargins.d.ts +6 -0
- package/dist/capture/detectMargins.d.ts.map +1 -0
- package/dist/capture/detectMargins.js +145 -0
- package/dist/capture/detectMargins.js.map +1 -0
- package/dist/capture/frameRecorder.d.ts +29 -0
- package/dist/capture/frameRecorder.d.ts.map +1 -0
- package/dist/capture/frameRecorder.js +74 -0
- package/dist/capture/frameRecorder.js.map +1 -0
- package/dist/capture/stitchFrames.d.ts +9 -0
- package/dist/capture/stitchFrames.d.ts.map +1 -0
- package/dist/capture/stitchFrames.js +62 -0
- package/dist/capture/stitchFrames.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +329 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/browserLaunch.d.ts +13 -0
- package/dist/config/browserLaunch.d.ts.map +1 -0
- package/dist/config/browserLaunch.js +63 -0
- package/dist/config/browserLaunch.js.map +1 -0
- package/dist/config/recordingProfile.d.ts +10 -0
- package/dist/config/recordingProfile.d.ts.map +1 -0
- package/dist/config/recordingProfile.js +25 -0
- package/dist/config/recordingProfile.js.map +1 -0
- package/dist/config/runtimeMode.d.ts +2 -0
- package/dist/config/runtimeMode.d.ts.map +1 -0
- package/dist/config/runtimeMode.js +4 -0
- package/dist/config/runtimeMode.js.map +1 -0
- package/dist/config/virtualScroll.d.ts +8 -0
- package/dist/config/virtualScroll.d.ts.map +1 -0
- package/dist/config/virtualScroll.js +19 -0
- package/dist/config/virtualScroll.js.map +1 -0
- package/dist/contracts.d.ts +157 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +136 -0
- package/dist/contracts.js.map +1 -0
- package/dist/editor/compileVideo.d.ts +21 -0
- package/dist/editor/compileVideo.d.ts.map +1 -0
- package/dist/editor/compileVideo.js +104 -0
- package/dist/editor/compileVideo.js.map +1 -0
- package/dist/editor/composeComparison.d.ts +30 -0
- package/dist/editor/composeComparison.d.ts.map +1 -0
- package/dist/editor/composeComparison.js +315 -0
- package/dist/editor/composeComparison.js.map +1 -0
- package/dist/editor/composeResponsiveness.d.ts +19 -0
- package/dist/editor/composeResponsiveness.d.ts.map +1 -0
- package/dist/editor/composeResponsiveness.js +198 -0
- package/dist/editor/composeResponsiveness.js.map +1 -0
- package/dist/editor/frameVideo.d.ts +11 -0
- package/dist/editor/frameVideo.d.ts.map +1 -0
- package/dist/editor/frameVideo.js +181 -0
- package/dist/editor/frameVideo.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/inspection/inspectWebsite.d.ts +21 -0
- package/dist/inspection/inspectWebsite.d.ts.map +1 -0
- package/dist/inspection/inspectWebsite.js +412 -0
- package/dist/inspection/inspectWebsite.js.map +1 -0
- package/dist/jobs/manager.d.ts +123 -0
- package/dist/jobs/manager.d.ts.map +1 -0
- package/dist/jobs/manager.js +378 -0
- package/dist/jobs/manager.js.map +1 -0
- package/dist/jobs/store.d.ts +150 -0
- package/dist/jobs/store.d.ts.map +1 -0
- package/dist/jobs/store.js +446 -0
- package/dist/jobs/store.js.map +1 -0
- package/dist/jobs/thumbnail.d.ts +2 -0
- package/dist/jobs/thumbnail.d.ts.map +1 -0
- package/dist/jobs/thumbnail.js +36 -0
- package/dist/jobs/thumbnail.js.map +1 -0
- package/dist/pipeline/processRecordingRequest.d.ts +4 -0
- package/dist/pipeline/processRecordingRequest.d.ts.map +1 -0
- package/dist/pipeline/processRecordingRequest.js +17 -0
- package/dist/pipeline/processRecordingRequest.js.map +1 -0
- package/dist/pipeline/recordComparison.d.ts +4 -0
- package/dist/pipeline/recordComparison.d.ts.map +1 -0
- package/dist/pipeline/recordComparison.js +99 -0
- package/dist/pipeline/recordComparison.js.map +1 -0
- package/dist/pipeline/recordResponsiveness.d.ts +4 -0
- package/dist/pipeline/recordResponsiveness.d.ts.map +1 -0
- package/dist/pipeline/recordResponsiveness.js +122 -0
- package/dist/pipeline/recordResponsiveness.js.map +1 -0
- package/dist/pipeline/recordWebsite.d.ts +11 -0
- package/dist/pipeline/recordWebsite.d.ts.map +1 -0
- package/dist/pipeline/recordWebsite.js +446 -0
- package/dist/pipeline/recordWebsite.js.map +1 -0
- package/dist/pipeline/styleRecording.d.ts +12 -0
- package/dist/pipeline/styleRecording.d.ts.map +1 -0
- package/dist/pipeline/styleRecording.js +59 -0
- package/dist/pipeline/styleRecording.js.map +1 -0
- package/dist/transcode/ffmpeg.d.ts +4 -0
- package/dist/transcode/ffmpeg.d.ts.map +1 -0
- package/dist/transcode/ffmpeg.js +62 -0
- package/dist/transcode/ffmpeg.js.map +1 -0
- package/dist/transcode/probe.d.ts +7 -0
- package/dist/transcode/probe.d.ts.map +1 -0
- package/dist/transcode/probe.js +91 -0
- package/dist/transcode/probe.js.map +1 -0
- package/dist/transcode/quality.d.ts +8 -0
- package/dist/transcode/quality.d.ts.map +1 -0
- package/dist/transcode/quality.js +13 -0
- package/dist/transcode/quality.js.map +1 -0
- package/dist/types.d.ts +302 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/worker.d.ts +3 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +22 -0
- package/dist/worker.js.map +1 -0
- package/package.json +63 -0
- package/public/assets/index-C63t4P1L.css +1 -0
- package/public/assets/index-Sfgf7dSH.js +9 -0
- package/public/background_presets/gray_noise_gradient.png +0 -0
- package/public/background_presets/paper_blue.png +0 -0
- package/public/background_presets/red_blocks_gradient.png +0 -0
- package/public/deio-scroll-mark.svg +12 -0
- package/public/favicon.svg +1 -0
- package/public/icons.svg +24 -0
- package/public/index.html +16 -0
- package/public/scrollizard-mark.png +0 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { chromium, } from "playwright";
|
|
4
|
+
import { dismissCookieBanners } from "../browser/cookies.js";
|
|
5
|
+
import { detectScrollMode } from "../browser/detectScrollMode.js";
|
|
6
|
+
import { gotoReachablePage } from "../browser/goto.js";
|
|
7
|
+
import { hydrateLazyContent } from "../browser/hydrate.js";
|
|
8
|
+
import { primeLazyAssets } from "../browser/prime.js";
|
|
9
|
+
import { resolveScrollCurve } from "../browser/curves.js";
|
|
10
|
+
import { runScroll } from "../browser/scroll.js";
|
|
11
|
+
import { sanitizeDom } from "../browser/sanitize.js";
|
|
12
|
+
import { ensureOnTargetUrl } from "../browser/urlGuard.js";
|
|
13
|
+
import { launchArgsForHeadless, resolveBrowserLaunch, shouldWarnHeadlessVirtualCapture, } from "../config/browserLaunch.js";
|
|
14
|
+
import { resolveRecordingProfile } from "../config/recordingProfile.js";
|
|
15
|
+
import { removeFileIfExists, transcodeToMp4 } from "../transcode/ffmpeg.js";
|
|
16
|
+
import { FrameRecorder } from "../capture/frameRecorder.js";
|
|
17
|
+
import { createMediaClockSync, installMediaClock } from "../browser/mediaClock.js";
|
|
18
|
+
import { installInteractionNavigationGuards, preflightComponentInteractions, } from "../browser/componentInteraction.js";
|
|
19
|
+
import { stitchFramesToVideo } from "../capture/stitchFrames.js";
|
|
20
|
+
import { renderRecordingStyle, SOURCE_FILENAME } from "./styleRecording.js";
|
|
21
|
+
import { DEFAULT_DIRECTED_START_HOLD_MS } from "../types.js";
|
|
22
|
+
const DEFAULT_FRAMERATE = 30;
|
|
23
|
+
export async function recordWebsite(request, outputRoot, jobId, runtime = {}) {
|
|
24
|
+
runtime.signal?.throwIfAborted();
|
|
25
|
+
await runtime.onProgress?.({ stage: "preparing", percent: 2, message: "Launching Chromium and preparing the page" });
|
|
26
|
+
validateDirectionMode(request.animationConfig);
|
|
27
|
+
const resolvedJobId = jobId ?? createJobId(request.targetUrl);
|
|
28
|
+
const outputDir = path.resolve(outputRoot, resolvedJobId);
|
|
29
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
30
|
+
const viewport = request.videoConfig.viewport;
|
|
31
|
+
const profile = resolveRecordingProfile(request);
|
|
32
|
+
const { pixelsPerFrame, preRecordingDelayMs, encode, hydrateFast } = profile;
|
|
33
|
+
const deviceScaleFactor = encode.deviceScaleFactor;
|
|
34
|
+
const outputFramerate = request.videoConfig.framerate ?? DEFAULT_FRAMERATE;
|
|
35
|
+
const animation = request.animationConfig ?? {};
|
|
36
|
+
const pauseTriggers = animation.pauseTriggers ?? [];
|
|
37
|
+
const scrollCurve = resolveScrollCurve(animation.scrollCurve);
|
|
38
|
+
const removeOverlays = animation.removeOverlayElements ?? true;
|
|
39
|
+
const captureMode = animation.captureMode ?? "export";
|
|
40
|
+
// For export mode: each screenshot corresponds to one scroll step.
|
|
41
|
+
// To play the scroll animation at the correct real-time speed, we must
|
|
42
|
+
// stitch the frames at the target output framerate.
|
|
43
|
+
const captureFps = outputFramerate;
|
|
44
|
+
console.log(`Capture mode: ${captureMode}, pixelsPerFrame: ${pixelsPerFrame}, captureFps: ${captureFps}`);
|
|
45
|
+
const startedAt = Date.now();
|
|
46
|
+
const launch = resolveBrowserLaunch(animation);
|
|
47
|
+
const contextOptions = buildContextOptions(viewport, deviceScaleFactor);
|
|
48
|
+
const storageState = await runPrepSession({
|
|
49
|
+
request,
|
|
50
|
+
animation,
|
|
51
|
+
contextOptions,
|
|
52
|
+
removeOverlays,
|
|
53
|
+
hydrateFast,
|
|
54
|
+
signal: runtime.signal,
|
|
55
|
+
});
|
|
56
|
+
runtime.signal?.throwIfAborted();
|
|
57
|
+
await runtime.onProgress?.({ stage: "capturing", percent: 15, message: "Capturing the directed scroll" });
|
|
58
|
+
let captureHeadless = launch.headless;
|
|
59
|
+
let capture = null;
|
|
60
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
61
|
+
if (!captureHeadless) {
|
|
62
|
+
console.log("Using headed Chromium for smooth GPU-assisted capture.");
|
|
63
|
+
}
|
|
64
|
+
// Use the resolved headless setting (runs headed when DISPLAY is present to leverage
|
|
65
|
+
// the stable hardware GPU driver, and headless on CI/servers without display).
|
|
66
|
+
const runHeadless = captureHeadless;
|
|
67
|
+
capture = await runRecordSession({
|
|
68
|
+
request,
|
|
69
|
+
outputDir,
|
|
70
|
+
animation,
|
|
71
|
+
viewport,
|
|
72
|
+
pixelsPerFrame,
|
|
73
|
+
preRecordingDelayMs,
|
|
74
|
+
pauseTriggers,
|
|
75
|
+
scrollCurve,
|
|
76
|
+
removeOverlays,
|
|
77
|
+
storageState,
|
|
78
|
+
headless: runHeadless,
|
|
79
|
+
launchArgs: launchArgsForHeadless(runHeadless),
|
|
80
|
+
deviceScaleFactor,
|
|
81
|
+
framerate: outputFramerate,
|
|
82
|
+
captureFps,
|
|
83
|
+
captureMode,
|
|
84
|
+
runtime,
|
|
85
|
+
});
|
|
86
|
+
const shouldRetryHeaded = attempt === 0 &&
|
|
87
|
+
captureHeadless &&
|
|
88
|
+
capture.scrollStrategy === "virtual" &&
|
|
89
|
+
(animation.scrollMode ?? "auto") === "auto" &&
|
|
90
|
+
// Export mode uses frame-by-frame screenshots which work fine headless.
|
|
91
|
+
// Only retry headed for preview mode where Playwright recordVideo needs
|
|
92
|
+
// real GPU rendering for smooth WebGL captures.
|
|
93
|
+
captureMode === "preview";
|
|
94
|
+
if (!shouldRetryHeaded) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
console.log("Virtual scroll detected during headless capture; retrying with headed browser.");
|
|
98
|
+
captureHeadless = false;
|
|
99
|
+
}
|
|
100
|
+
if (!capture?.rawVideoPath) {
|
|
101
|
+
throw new Error("Playwright did not produce a recorded video file");
|
|
102
|
+
}
|
|
103
|
+
runtime.signal?.throwIfAborted();
|
|
104
|
+
await runtime.onProgress?.({ stage: "encoding", percent: 82, message: "Encoding the source video" });
|
|
105
|
+
const sourcePath = path.join(outputDir, SOURCE_FILENAME);
|
|
106
|
+
const mp4Path = path.join(outputDir, "output.mp4");
|
|
107
|
+
const targetWidth = viewport.width * deviceScaleFactor;
|
|
108
|
+
const targetHeight = viewport.height * deviceScaleFactor;
|
|
109
|
+
if (capture.isMp4) {
|
|
110
|
+
await fs.rename(capture.rawVideoPath, sourcePath);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
await transcodeToMp4(capture.rawVideoPath, sourcePath, outputFramerate, targetWidth, targetHeight, encode);
|
|
114
|
+
await removeFileIfExists(capture.rawVideoPath);
|
|
115
|
+
}
|
|
116
|
+
runtime.signal?.throwIfAborted();
|
|
117
|
+
await runtime.onProgress?.({ stage: "styling", percent: 91, message: "Applying the recording canvas" });
|
|
118
|
+
await renderRecordingStyle({
|
|
119
|
+
sourcePath,
|
|
120
|
+
outputPath: mp4Path,
|
|
121
|
+
backgroundPreset: request.backgroundPreset,
|
|
122
|
+
addShadow: request.addShadow,
|
|
123
|
+
roundedCorners: request.roundedCorners,
|
|
124
|
+
});
|
|
125
|
+
runtime.signal?.throwIfAborted();
|
|
126
|
+
await runtime.onProgress?.({ stage: "finalizing", percent: 97, message: "Finalizing recording metadata" });
|
|
127
|
+
const captureWarning = shouldWarnHeadlessVirtualCapture(capture.scrollStrategy, captureHeadless);
|
|
128
|
+
if (captureWarning) {
|
|
129
|
+
console.warn(captureWarning);
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
jobId: resolvedJobId,
|
|
133
|
+
outputDir,
|
|
134
|
+
rawVideoPath: sourcePath,
|
|
135
|
+
mp4Path,
|
|
136
|
+
durationMs: capture.mediaDurationMs,
|
|
137
|
+
renderTimeMs: Date.now() - startedAt,
|
|
138
|
+
viewport: {
|
|
139
|
+
width: viewport.width,
|
|
140
|
+
height: viewport.height,
|
|
141
|
+
deviceScaleFactor,
|
|
142
|
+
},
|
|
143
|
+
scrollStrategy: capture.scrollStrategy,
|
|
144
|
+
motionPlan: capture.motionPlan,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function validateDirectionMode(animation) {
|
|
148
|
+
if (!animation?.direction)
|
|
149
|
+
return;
|
|
150
|
+
const legacyFields = [
|
|
151
|
+
animation.durationMs,
|
|
152
|
+
animation.heroHoldMs,
|
|
153
|
+
animation.pauseTriggers,
|
|
154
|
+
animation.scrollCurve,
|
|
155
|
+
];
|
|
156
|
+
if (legacyFields.some((value) => value !== undefined)) {
|
|
157
|
+
throw new Error("animationConfig.direction cannot be combined with durationMs, heroHoldMs, pauseTriggers, or scrollCurve");
|
|
158
|
+
}
|
|
159
|
+
if (animation.direction.beats.length < 1 || animation.direction.beats.length > 12) {
|
|
160
|
+
throw new Error("animationConfig.direction must contain between 1 and 12 beats");
|
|
161
|
+
}
|
|
162
|
+
const totalDurationMs = (animation.direction.startHoldMs ?? DEFAULT_DIRECTED_START_HOLD_MS) +
|
|
163
|
+
animation.direction.beats.reduce((total, beat) => total + beat.transitionMs + (beat.holdMs ?? 0), 0);
|
|
164
|
+
if (totalDurationMs > 300_000) {
|
|
165
|
+
throw new Error("The directed recording timeline cannot exceed 300000ms");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function runPrepSession(options) {
|
|
169
|
+
const { request, animation, contextOptions, removeOverlays, hydrateFast, signal } = options;
|
|
170
|
+
let browser = null;
|
|
171
|
+
try {
|
|
172
|
+
browser = await chromium.launch({
|
|
173
|
+
headless: true,
|
|
174
|
+
args: launchArgsForHeadless(true),
|
|
175
|
+
});
|
|
176
|
+
const abort = () => void browser?.close().catch(() => undefined);
|
|
177
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
178
|
+
const prepContext = await browser.newContext(contextOptions);
|
|
179
|
+
const prepPage = await prepContext.newPage();
|
|
180
|
+
await gotoReachablePage(prepPage, request.targetUrl);
|
|
181
|
+
await dismissCookieBanners(prepPage);
|
|
182
|
+
await sanitizeDom(prepPage, removeOverlays);
|
|
183
|
+
const expectedScrollMode = await detectScrollMode(prepPage, animation.scrollMode);
|
|
184
|
+
const hydrateUsesWheel = expectedScrollMode === "virtual" || animation.scrollMode === "virtual";
|
|
185
|
+
try {
|
|
186
|
+
await hydrateLazyContent(prepPage, contextOptions.viewport.height, {
|
|
187
|
+
fast: hydrateFast,
|
|
188
|
+
useWheel: hydrateUsesWheel,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
console.warn("Lazy-content hydration failed; continuing with current page state.", error);
|
|
193
|
+
}
|
|
194
|
+
await ensureOnTargetUrl(prepPage, request.targetUrl);
|
|
195
|
+
await prepPage.evaluate(() => window.scrollTo({ top: 0, left: 0, behavior: "instant" }));
|
|
196
|
+
await prepPage.waitForTimeout(300);
|
|
197
|
+
const storageState = await prepContext.storageState();
|
|
198
|
+
await prepContext.close();
|
|
199
|
+
signal?.removeEventListener("abort", abort);
|
|
200
|
+
signal?.throwIfAborted();
|
|
201
|
+
return storageState;
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
await browser?.close();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function runRecordSession(options) {
|
|
208
|
+
const { request, outputDir, animation, viewport, pixelsPerFrame, preRecordingDelayMs, pauseTriggers, scrollCurve, removeOverlays, storageState, headless, launchArgs, deviceScaleFactor, framerate, captureFps, captureMode, runtime, } = options;
|
|
209
|
+
const profile = resolveRecordingProfile(request);
|
|
210
|
+
const { encode } = profile;
|
|
211
|
+
let browser = null;
|
|
212
|
+
let rawVideoPath = "";
|
|
213
|
+
let scrollStrategy = "document";
|
|
214
|
+
let motionPlan = null;
|
|
215
|
+
let mediaDurationMs = 0;
|
|
216
|
+
// For standard and cinematic tiers, we return to the offline screenshot capture mode.
|
|
217
|
+
// We optimize it by writing all frames to a RAM disk (/dev/shm) when available.
|
|
218
|
+
const usePlaywrightVideo = false;
|
|
219
|
+
if (usePlaywrightVideo) {
|
|
220
|
+
// Fast mode: Use Playwright's recordVideo
|
|
221
|
+
return recordWithPlaywrightVideo({
|
|
222
|
+
request,
|
|
223
|
+
outputDir,
|
|
224
|
+
animation,
|
|
225
|
+
viewport,
|
|
226
|
+
pixelsPerFrame,
|
|
227
|
+
preRecordingDelayMs,
|
|
228
|
+
pauseTriggers,
|
|
229
|
+
scrollCurve,
|
|
230
|
+
removeOverlays,
|
|
231
|
+
storageState,
|
|
232
|
+
headless,
|
|
233
|
+
launchArgs,
|
|
234
|
+
deviceScaleFactor,
|
|
235
|
+
framerate,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
// Export mode: Screenshot-based frame capture for high quality
|
|
239
|
+
// Use the logical viewport + deviceScaleFactor (not a physically scaled viewport at scale 1).
|
|
240
|
+
// Playwright's page.screenshot() honours deviceScaleFactor and returns physical-resolution images.
|
|
241
|
+
let framesDir = path.join(outputDir, ".frames");
|
|
242
|
+
let isRamDisk = false;
|
|
243
|
+
try {
|
|
244
|
+
await fs.access("/dev/shm", fs.constants.W_OK);
|
|
245
|
+
const jobId = path.basename(outputDir);
|
|
246
|
+
framesDir = path.join("/dev/shm", `websiterecorder-${jobId}`);
|
|
247
|
+
isRamDisk = true;
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Fallback to SSD
|
|
251
|
+
}
|
|
252
|
+
const recordContextOptions = buildContextOptions(viewport, deviceScaleFactor);
|
|
253
|
+
try {
|
|
254
|
+
await fs.mkdir(framesDir, { recursive: true });
|
|
255
|
+
const recordLaunchArgs = launchArgs.filter((arg) => !arg.startsWith("--force-device-scale-factor"));
|
|
256
|
+
browser = await chromium.launch({ headless, args: recordLaunchArgs });
|
|
257
|
+
const abort = () => void browser?.close().catch(() => undefined);
|
|
258
|
+
runtime.signal?.addEventListener("abort", abort, { once: true });
|
|
259
|
+
const frameRecorder = new FrameRecorder({
|
|
260
|
+
outputDir: framesDir,
|
|
261
|
+
fps: captureFps,
|
|
262
|
+
scaleFactor: deviceScaleFactor,
|
|
263
|
+
qualityJpeg: 95,
|
|
264
|
+
// Sequential captures only: parallel workers can race scroll position vs paint.
|
|
265
|
+
parallelWorkers: 1,
|
|
266
|
+
});
|
|
267
|
+
const recordContext = await browser.newContext({
|
|
268
|
+
...recordContextOptions,
|
|
269
|
+
storageState,
|
|
270
|
+
});
|
|
271
|
+
const page = await recordContext.newPage();
|
|
272
|
+
await installMediaClock(page);
|
|
273
|
+
let captureCompleted = false;
|
|
274
|
+
try {
|
|
275
|
+
await gotoReachablePage(page, request.targetUrl);
|
|
276
|
+
await ensureOnTargetUrl(page, request.targetUrl);
|
|
277
|
+
await dismissCookieBanners(page);
|
|
278
|
+
await sanitizeDom(page, removeOverlays);
|
|
279
|
+
await primeLazyAssets(page);
|
|
280
|
+
if (animation.direction?.beats.some((beat) => beat.interaction)) {
|
|
281
|
+
await installInteractionNavigationGuards(page);
|
|
282
|
+
await preflightComponentInteractions(page, animation.direction.beats);
|
|
283
|
+
}
|
|
284
|
+
frameRecorder.setBeforeCapture(await createMediaClockSync(page, captureFps));
|
|
285
|
+
await page.evaluate(() => window.scrollTo({ top: 0, left: 0, behavior: "instant" }));
|
|
286
|
+
await ensureOnTargetUrl(page, request.targetUrl);
|
|
287
|
+
await page.waitForTimeout(preRecordingDelayMs);
|
|
288
|
+
const scrollResult = await runScroll(page, {
|
|
289
|
+
pixelsPerFrame,
|
|
290
|
+
pauseTriggers: pauseTriggers ?? [],
|
|
291
|
+
bezier: scrollCurve,
|
|
292
|
+
scrollMode: animation.scrollMode,
|
|
293
|
+
animationConfig: animation,
|
|
294
|
+
viewportWidth: viewport.width,
|
|
295
|
+
viewportHeight: viewport.height,
|
|
296
|
+
fastMode: animation.fastMode ?? false,
|
|
297
|
+
frameRecorder,
|
|
298
|
+
signal: runtime.signal,
|
|
299
|
+
onProgress: (completed, total) => runtime.onProgress?.({
|
|
300
|
+
stage: "capturing",
|
|
301
|
+
percent: 15 + (completed / Math.max(1, total)) * 55,
|
|
302
|
+
message: `Capturing frame ${completed} of ${total}`,
|
|
303
|
+
}),
|
|
304
|
+
});
|
|
305
|
+
scrollStrategy = scrollResult.scrollStrategy;
|
|
306
|
+
motionPlan = scrollResult.motionPlan;
|
|
307
|
+
mediaDurationMs = scrollResult.motionPlan.durationMs;
|
|
308
|
+
console.log(`Scroll strategy: ${scrollStrategy}`);
|
|
309
|
+
if (scrollResult.frames) {
|
|
310
|
+
const metadataPath = path.join(outputDir, "frames-metadata.json");
|
|
311
|
+
await fs.writeFile(metadataPath, JSON.stringify({
|
|
312
|
+
scrollStrategy: scrollResult.scrollStrategy,
|
|
313
|
+
maxScroll: scrollResult.maxScroll,
|
|
314
|
+
frames: scrollResult.frames,
|
|
315
|
+
deviceScaleFactor,
|
|
316
|
+
viewport,
|
|
317
|
+
motionPlan: scrollResult.motionPlan,
|
|
318
|
+
}, null, 2));
|
|
319
|
+
}
|
|
320
|
+
await page.waitForTimeout(500);
|
|
321
|
+
captureCompleted = true;
|
|
322
|
+
}
|
|
323
|
+
finally {
|
|
324
|
+
await page.close().catch(() => undefined);
|
|
325
|
+
await recordContext.close().catch(() => undefined);
|
|
326
|
+
// Do not let FFmpeg's "no files found" error mask the real browser or
|
|
327
|
+
// capture failure. Encoding only belongs to a completed capture.
|
|
328
|
+
if (captureCompleted) {
|
|
329
|
+
const capturedFrames = frameRecorder.getFrameCount();
|
|
330
|
+
if (capturedFrames < 1) {
|
|
331
|
+
throw new Error("Capture completed without producing any frames");
|
|
332
|
+
}
|
|
333
|
+
await fs.access(path.join(framesDir, "frame-000000.jpg"));
|
|
334
|
+
const tempRawVideoPath = path.join(outputDir, "raw_frames.mp4");
|
|
335
|
+
runtime.signal?.throwIfAborted();
|
|
336
|
+
await runtime.onProgress?.({ stage: "encoding", percent: 72, message: `Stitching ${capturedFrames} captured frames` });
|
|
337
|
+
await stitchFramesToVideo(framesDir, tempRawVideoPath, captureFps, {
|
|
338
|
+
width: viewport.width * deviceScaleFactor,
|
|
339
|
+
height: viewport.height * deviceScaleFactor,
|
|
340
|
+
preset: encode.preset,
|
|
341
|
+
crf: encode.crf,
|
|
342
|
+
signal: runtime.signal,
|
|
343
|
+
});
|
|
344
|
+
rawVideoPath = tempRawVideoPath;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
349
|
+
await browser?.close();
|
|
350
|
+
// Clean up frames directory if it was in RAM disk or if captureMode is preview.
|
|
351
|
+
// Completed recordings retain only their MP4 artifacts.
|
|
352
|
+
if (isRamDisk || captureMode === "preview") {
|
|
353
|
+
await fs.rm(framesDir, { recursive: true, force: true }).catch(() => undefined);
|
|
354
|
+
}
|
|
355
|
+
runtime.signal?.throwIfAborted();
|
|
356
|
+
}
|
|
357
|
+
if (!motionPlan)
|
|
358
|
+
throw new Error("Recording did not produce a motion plan");
|
|
359
|
+
return { rawVideoPath, scrollStrategy, isMp4: true, mediaDurationMs, motionPlan };
|
|
360
|
+
}
|
|
361
|
+
async function recordWithPlaywrightVideo(options) {
|
|
362
|
+
const { request, outputDir, animation, viewport, pixelsPerFrame, preRecordingDelayMs, pauseTriggers, scrollCurve, removeOverlays, storageState, headless, launchArgs, deviceScaleFactor, framerate, } = options;
|
|
363
|
+
let browser = null;
|
|
364
|
+
let rawVideoPath = "";
|
|
365
|
+
let scrollStrategy = "document";
|
|
366
|
+
let motionPlan = null;
|
|
367
|
+
const recordLaunchArgs = [
|
|
368
|
+
...launchArgs.filter((arg) => !arg.startsWith("--force-device-scale-factor")),
|
|
369
|
+
"--force-device-scale-factor=1",
|
|
370
|
+
];
|
|
371
|
+
const recordContextOptions = buildContextOptions(viewport, 1);
|
|
372
|
+
try {
|
|
373
|
+
browser = await chromium.launch({ headless, args: recordLaunchArgs });
|
|
374
|
+
const recordContext = await browser.newContext({
|
|
375
|
+
...recordContextOptions,
|
|
376
|
+
storageState,
|
|
377
|
+
recordVideo: {
|
|
378
|
+
dir: outputDir,
|
|
379
|
+
size: {
|
|
380
|
+
width: viewport.width,
|
|
381
|
+
height: viewport.height,
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
const page = await recordContext.newPage();
|
|
386
|
+
try {
|
|
387
|
+
await gotoReachablePage(page, request.targetUrl);
|
|
388
|
+
await ensureOnTargetUrl(page, request.targetUrl);
|
|
389
|
+
await dismissCookieBanners(page);
|
|
390
|
+
await sanitizeDom(page, removeOverlays);
|
|
391
|
+
await primeLazyAssets(page);
|
|
392
|
+
await page.evaluate(() => window.scrollTo({ top: 0, left: 0, behavior: "instant" }));
|
|
393
|
+
await ensureOnTargetUrl(page, request.targetUrl);
|
|
394
|
+
await page.waitForTimeout(preRecordingDelayMs);
|
|
395
|
+
const scrollResult = await runScroll(page, {
|
|
396
|
+
pixelsPerFrame,
|
|
397
|
+
pauseTriggers: pauseTriggers ?? [],
|
|
398
|
+
bezier: scrollCurve,
|
|
399
|
+
scrollMode: animation.scrollMode,
|
|
400
|
+
animationConfig: animation,
|
|
401
|
+
viewportWidth: viewport.width,
|
|
402
|
+
viewportHeight: viewport.height,
|
|
403
|
+
fastMode: animation.fastMode ?? false,
|
|
404
|
+
});
|
|
405
|
+
scrollStrategy = scrollResult.scrollStrategy;
|
|
406
|
+
motionPlan = scrollResult.motionPlan;
|
|
407
|
+
console.log(`Scroll strategy: ${scrollStrategy}`);
|
|
408
|
+
await page.waitForTimeout(500);
|
|
409
|
+
}
|
|
410
|
+
finally {
|
|
411
|
+
const video = page.video();
|
|
412
|
+
await page.close();
|
|
413
|
+
await recordContext.close();
|
|
414
|
+
rawVideoPath = video ? await video.path() : "";
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
finally {
|
|
418
|
+
await browser?.close();
|
|
419
|
+
}
|
|
420
|
+
if (!motionPlan)
|
|
421
|
+
throw new Error("Recording did not produce a motion plan");
|
|
422
|
+
return {
|
|
423
|
+
rawVideoPath,
|
|
424
|
+
scrollStrategy,
|
|
425
|
+
mediaDurationMs: motionPlan.durationMs,
|
|
426
|
+
motionPlan,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function buildContextOptions(viewport, deviceScaleFactor) {
|
|
430
|
+
const isMobileViewport = viewport.width < viewport.height && viewport.width <= 500;
|
|
431
|
+
return {
|
|
432
|
+
viewport: { width: viewport.width, height: viewport.height },
|
|
433
|
+
deviceScaleFactor,
|
|
434
|
+
isMobile: isMobileViewport,
|
|
435
|
+
hasTouch: isMobileViewport,
|
|
436
|
+
userAgent: isMobileViewport
|
|
437
|
+
? "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1"
|
|
438
|
+
: undefined,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function createJobId(targetUrl) {
|
|
442
|
+
const host = new URL(targetUrl).hostname.replace(/^www\./, "");
|
|
443
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
444
|
+
return `${host}-${stamp}`;
|
|
445
|
+
}
|
|
446
|
+
//# sourceMappingURL=recordWebsite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recordWebsite.js","sourceRoot":"","sources":["../../src/pipeline/recordWebsite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EACL,QAAQ,GAIT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,gCAAgC,GACjC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AACnF,OAAO,EACL,kCAAkC,EAClC,8BAA8B,GAC/B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAQ5E,OAAO,EAAE,8BAA8B,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAe7B,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAsB,EACtB,UAAkB,EAClB,KAAc,EACd,UAA4B,EAAE;IAE9B,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;IACjC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,2CAA2C,EAAE,CAAC,CAAC;IACrH,qBAAqB,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAC/C,MAAM,aAAa,GAAG,KAAK,IAAI,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IAC1D,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/C,MAAM,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC;IAC9C,MAAM,OAAO,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IAC7E,MAAM,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,CAAC;IACnD,MAAM,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,IAAI,iBAAiB,CAAC;IAC3E,MAAM,SAAS,GAAG,OAAO,CAAC,eAAe,IAAI,EAAE,CAAC;IAChD,MAAM,aAAa,GAAG,SAAS,CAAC,aAAa,IAAI,EAAE,CAAC;IACpD,MAAM,WAAW,GAAG,kBAAkB,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC9D,MAAM,cAAc,GAAG,SAAS,CAAC,qBAAqB,IAAI,IAAI,CAAC;IAC/D,MAAM,WAAW,GAAG,SAAS,CAAC,WAAW,IAAI,QAAQ,CAAC;IAEtD,mEAAmE;IACnE,uEAAuE;IACvE,oDAAoD;IACpD,MAAM,UAAU,GAAG,eAAe,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,iBAAiB,WAAW,qBAAqB,cAAc,iBAAiB,UAAU,EAAE,CAAC,CAAC;IAG1G,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAC/C,MAAM,cAAc,GAAG,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAExE,MAAM,YAAY,GAAG,MAAM,cAAc,CAAC;QACxC,OAAO;QACP,SAAS;QACT,cAAc;QACd,cAAc;QACd,WAAW;QACX,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IACH,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;IACjC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC,CAAC;IAE1G,IAAI,eAAe,GAAG,MAAM,CAAC,QAAQ,CAAC;IACtC,IAAI,OAAO,GAAgC,IAAI,CAAC;IAEhD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAChD,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;QACxE,CAAC;QAED,qFAAqF;QACrF,+EAA+E;QAC/E,MAAM,WAAW,GAAG,eAAe,CAAC;QAEpC,OAAO,GAAG,MAAM,gBAAgB,CAAC;YAC/B,OAAO;YACP,SAAS;YACT,SAAS;YACT,QAAQ;YACR,cAAc;YACd,mBAAmB;YACnB,aAAa;YACb,WAAW;YACX,cAAc;YACd,YAAY;YACZ,QAAQ,EAAE,WAAW;YACrB,UAAU,EAAE,qBAAqB,CAAC,WAAW,CAAC;YAC9C,iBAAiB;YACjB,SAAS,EAAE,eAAe;YAC1B,UAAU;YACV,WAAW;YACX,OAAO;SACR,CAAC,CAAC;QAEH,MAAM,iBAAiB,GACrB,OAAO,KAAK,CAAC;YACb,eAAe;YACf,OAAO,CAAC,cAAc,KAAK,SAAS;YACpC,CAAC,SAAS,CAAC,UAAU,IAAI,MAAM,CAAC,KAAK,MAAM;YAC3C,wEAAwE;YACxE,wEAAwE;YACxE,gDAAgD;YAChD,WAAW,KAAK,SAAS,CAAC;QAG5B,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,MAAM;QACR,CAAC;QAED,OAAO,CAAC,GAAG,CACT,gFAAgF,CACjF,CAAC;QACF,eAAe,GAAG,KAAK,CAAC;IAC1B,CAAC;IAED,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;IACjC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,2BAA2B,EAAE,CAAC,CAAC;IAErG,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IACnD,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,GAAG,iBAAiB,CAAC;IACvD,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,GAAG,iBAAiB,CAAC;IAEzD,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACpD,CAAC;SAAM,CAAC;QACN,MAAM,cAAc,CAClB,OAAO,CAAC,YAAY,EACpB,UAAU,EACV,eAAe,EACf,WAAW,EACX,YAAY,EACZ,MAAM,CACP,CAAC;QACF,MAAM,kBAAkB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACjD,CAAC;IAED,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;IACjC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC,CAAC;IACxG,MAAM,oBAAoB,CAAC;QACzB,UAAU;QACV,UAAU,EAAE,OAAO;QACnB,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;QAC1C,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC,CAAC,CAAC;IACH,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;IACjC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,+BAA+B,EAAE,CAAC,CAAC;IAE3G,MAAM,cAAc,GAAG,gCAAgC,CACrD,OAAO,CAAC,cAAc,EACtB,eAAe,CAChB,CAAC;IACF,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO;QACL,KAAK,EAAE,aAAa;QACpB,SAAS;QACT,YAAY,EAAE,UAAU;QACxB,OAAO;QACP,UAAU,EAAE,OAAO,CAAC,eAAe;QACnC,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;QACpC,QAAQ,EAAE;YACR,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,iBAAiB;SAClB;QACD,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB,CAAC,SAA2B;IACxD,IAAI,CAAC,SAAS,EAAE,SAAS;QAAE,OAAO;IAClC,MAAM,YAAY,GAAG;QACnB,SAAS,CAAC,UAAU;QACpB,SAAS,CAAC,UAAU;QACpB,SAAS,CAAC,aAAa;QACvB,SAAS,CAAC,WAAW;KACtB,CAAC;IACF,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CACb,yGAAyG,CAC1G,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAClF,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,eAAe,GACnB,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,IAAI,8BAA8B,CAAC;QACnE,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAC9B,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,YAAY,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,EAC/D,CAAC,CACF,CAAC;IACJ,IAAI,eAAe,GAAG,OAAO,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;AACH,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,OAO7B;IACC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,EAAE,GAC/E,OAAO,CAAC;IAEV,IAAI,OAAO,GAAmB,IAAI,CAAC;IAEnC,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;YAC9B,QAAQ,EAAE,IAAI;YACd,IAAI,EAAE,qBAAqB,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,KAAK,OAAO,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACjE,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAEzD,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;QAC7D,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAE7C,MAAM,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QACrD,MAAM,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,WAAW,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QAE5C,MAAM,kBAAkB,GAAG,MAAM,gBAAgB,CAC/C,QAAQ,EACR,SAAS,CAAC,UAAU,CACrB,CAAC;QACF,MAAM,gBAAgB,GACpB,kBAAkB,KAAK,SAAS,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS,CAAC;QAEzE,IAAI,CAAC;YACH,MAAM,kBAAkB,CAAC,QAAQ,EAAE,cAAc,CAAC,QAAS,CAAC,MAAO,EAAE;gBACnE,IAAI,EAAE,WAAW;gBACjB,QAAQ,EAAE,gBAAgB;aAC3B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CACV,oEAAoE,EACpE,KAAK,CACN,CAAC;QACJ,CAAC;QAED,MAAM,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;QACrD,MAAM,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,CAC3B,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAC1D,CAAC;QACF,MAAM,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAEnC,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,YAAY,EAAE,CAAC;QACtD,MAAM,WAAW,CAAC,KAAK,EAAE,CAAC;QAC1B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC5C,MAAM,EAAE,cAAc,EAAE,CAAC;QACzB,OAAO,YAAY,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC;IACzB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,OAkB/B;IACC,MAAM,EACJ,OAAO,EACP,SAAS,EACT,SAAS,EACT,QAAQ,EACR,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,WAAW,EACX,OAAO,GACR,GAAG,OAAO,CAAC;IAEZ,MAAM,OAAO,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAE3B,IAAI,OAAO,GAAmB,IAAI,CAAC;IACnC,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,IAAI,cAAc,GAA2B,UAAU,CAAC;IACxD,IAAI,UAAU,GAA8B,IAAI,CAAC;IACjD,IAAI,eAAe,GAAG,CAAC,CAAC;IAExB,sFAAsF;IACtF,gFAAgF;IAChF,MAAM,kBAAkB,GAAG,KAAK,CAAC;IAEjC,IAAI,kBAAkB,EAAE,CAAC;QACvB,0CAA0C;QAC1C,OAAO,yBAAyB,CAAC;YAC/B,OAAO;YACP,SAAS;YACT,SAAS;YACT,QAAQ;YACR,cAAc;YACd,mBAAmB;YACnB,aAAa;YACb,WAAW;YACX,cAAc;YACd,YAAY;YACZ,QAAQ;YACR,UAAU;YACV,iBAAiB;YACjB,SAAS;SACV,CAAC,CAAC;IACL,CAAC;IACD,+DAA+D;IAC/D,8FAA8F;IAC9F,mGAAmG;IACnG,IAAI,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAChD,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACvC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,mBAAmB,KAAK,EAAE,CAAC,CAAC;QAC9D,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,kBAAkB;IACpB,CAAC;IAED,MAAM,oBAAoB,GAAG,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAE9E,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE/C,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,CACxC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,6BAA6B,CAAC,CACxD,CAAC;QACF,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;QACtE,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,KAAK,OAAO,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAEjE,MAAM,aAAa,GAAG,IAAI,aAAa,CAAC;YACtC,SAAS,EAAE,SAAS;YACpB,GAAG,EAAE,UAAU;YACf,WAAW,EAAE,iBAAiB;YAC9B,WAAW,EAAE,EAAE;YACf,gFAAgF;YAChF,eAAe,EAAE,CAAC;SACnB,CAAC,CAAC;QAEH,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC;YAC7C,GAAG,oBAAoB;YACvB,YAAY;SACb,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC;QAC3C,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,gBAAgB,GAAG,KAAK,CAAC;QAE7B,IAAI,CAAC;YACH,MAAM,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;YACjD,MAAM,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;YACjD,MAAM,oBAAoB,CAAC,IAAI,CAAC,CAAC;YACjC,MAAM,WAAW,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;YACxC,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;YAC5B,IAAI,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;gBAChE,MAAM,kCAAkC,CAAC,IAAI,CAAC,CAAC;gBAC/C,MAAM,8BAA8B,CAAC,IAAI,EAAE,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACxE,CAAC;YAED,aAAa,CAAC,gBAAgB,CAAC,MAAM,oBAAoB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;YAE7E,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CACvB,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAC1D,CAAC;YACF,MAAM,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;YACjD,MAAM,IAAI,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;YAE/C,MAAM,YAAY,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE;gBACzC,cAAc;gBACd,aAAa,EAAE,aAAa,IAAI,EAAE;gBAClC,MAAM,EAAE,WAAW;gBACnB,UAAU,EAAE,SAAS,CAAC,UAAU;gBAChC,eAAe,EAAE,SAAS;gBAC1B,aAAa,EAAE,QAAQ,CAAC,KAAK;gBAC7B,cAAc,EAAE,QAAQ,CAAC,MAAM;gBAC/B,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,KAAK;gBACrC,aAAa;gBACb,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,UAAU,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;oBACrD,KAAK,EAAE,WAAW;oBAClB,OAAO,EAAE,EAAE,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,EAAE;oBACnD,OAAO,EAAE,mBAAmB,SAAS,OAAO,KAAK,EAAE;iBACpD,CAAC;aACH,CAAC,CAAC;YACH,cAAc,GAAG,YAAY,CAAC,cAAc,CAAC;YAC7C,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC;YACrC,eAAe,GAAG,YAAY,CAAC,UAAU,CAAC,UAAU,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,oBAAoB,cAAc,EAAE,CAAC,CAAC;YAElD,IAAI,YAAY,CAAC,MAAM,EAAE,CAAC;gBACxB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;gBAClE,MAAM,EAAE,CAAC,SAAS,CAChB,YAAY,EACZ,IAAI,CAAC,SAAS,CACZ;oBACE,cAAc,EAAE,YAAY,CAAC,cAAc;oBAC3C,SAAS,EAAE,YAAY,CAAC,SAAS;oBACjC,MAAM,EAAE,YAAY,CAAC,MAAM;oBAC3B,iBAAiB;oBACjB,QAAQ;oBACR,UAAU,EAAE,YAAY,CAAC,UAAU;iBACpC,EACD,IAAI,EACJ,CAAC,CACF,CACF,CAAC;YACJ,CAAC;YACD,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;YAC/B,gBAAgB,GAAG,IAAI,CAAC;QAC1B,CAAC;gBAAS,CAAC;YACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC1C,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAEnD,sEAAsE;YACtE,iEAAiE;YACjE,IAAI,gBAAgB,EAAE,CAAC;gBACrB,MAAM,cAAc,GAAG,aAAa,CAAC,aAAa,EAAE,CAAC;gBACrD,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;oBACvB,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;gBACpE,CAAC;gBACD,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,kBAAkB,CAAC,CAAC,CAAC;gBAE1D,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;gBAChE,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;gBACjC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,aAAa,cAAc,kBAAkB,EAAE,CAAC,CAAC;gBACvH,MAAM,mBAAmB,CAAC,SAAS,EAAE,gBAAgB,EAAE,UAAU,EAAE;oBACjE,KAAK,EAAE,QAAQ,CAAC,KAAK,GAAG,iBAAiB;oBACzC,MAAM,EAAE,QAAQ,CAAC,MAAM,GAAG,iBAAiB;oBAC3C,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,GAAG,EAAE,MAAM,CAAC,GAAG;oBACf,MAAM,EAAE,OAAO,CAAC,MAAM;iBACvB,CAAC,CAAC;gBACH,YAAY,GAAG,gBAAgB,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC;QACvB,gFAAgF;QAChF,wDAAwD;QACxD,IAAI,SAAS,IAAK,WAAsB,KAAK,SAAS,EAAE,CAAC;YACvD,MAAM,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAClF,CAAC;QACD,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;IACnC,CAAC;IAED,IAAI,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC5E,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC;AACpF,CAAC;AAED,KAAK,UAAU,yBAAyB,CAAC,OAexC;IACC,MAAM,EACJ,OAAO,EACP,SAAS,EACT,SAAS,EACT,QAAQ,EACR,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,SAAS,GACV,GAAG,OAAO,CAAC;IAEZ,IAAI,OAAO,GAAmB,IAAI,CAAC;IACnC,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,IAAI,cAAc,GAA2B,UAAU,CAAC;IACxD,IAAI,UAAU,GAA8B,IAAI,CAAC;IAEjD,MAAM,gBAAgB,GAAG;QACvB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,6BAA6B,CAAC,CAAC;QAC7E,+BAA+B;KAChC,CAAC;IACF,MAAM,oBAAoB,GAAG,mBAAmB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAE9D,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAEtE,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC;YAC7C,GAAG,oBAAoB;YACvB,YAAY;YACZ,WAAW,EAAE;gBACX,GAAG,EAAE,SAAS;gBACd,IAAI,EAAE;oBACJ,KAAK,EAAE,QAAQ,CAAC,KAAK;oBACrB,MAAM,EAAE,QAAQ,CAAC,MAAM;iBACxB;aACF;SACF,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC;QAE3C,IAAI,CAAC;YACH,MAAM,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;YACjD,MAAM,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;YACjD,MAAM,oBAAoB,CAAC,IAAI,CAAC,CAAC;YACjC,MAAM,WAAW,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;YACxC,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;YAE5B,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CACvB,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAC1D,CAAC;YACF,MAAM,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;YACjD,MAAM,IAAI,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;YAC/C,MAAM,YAAY,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE;gBACzC,cAAc;gBACd,aAAa,EAAE,aAAa,IAAI,EAAE;gBAClC,MAAM,EAAE,WAAW;gBACnB,UAAU,EAAE,SAAS,CAAC,UAAU;gBAChC,eAAe,EAAE,SAAS;gBAC1B,aAAa,EAAE,QAAQ,CAAC,KAAK;gBAC7B,cAAc,EAAE,QAAQ,CAAC,MAAM;gBAC/B,QAAQ,EAAE,SAAS,CAAC,QAAQ,IAAI,KAAK;aACtC,CAAC,CAAC;YACH,cAAc,GAAG,YAAY,CAAC,cAAc,CAAC;YAC7C,UAAU,GAAG,YAAY,CAAC,UAAU,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,oBAAoB,cAAc,EAAE,CAAC,CAAC;YAClD,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC;gBAAS,CAAC;YACT,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACnB,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC;YAC5B,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjD,CAAC;IACH,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC;IACzB,CAAC;IAED,IAAI,CAAC,UAAU;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC5E,OAAO;QACL,YAAY;QACZ,cAAc;QACd,eAAe,EAAE,UAAU,CAAC,UAAU;QACtC,UAAU;KACX,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB,CAC1B,QAAkD,EAClD,iBAAyB;IAEzB,MAAM,gBAAgB,GACpB,QAAQ,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,KAAK,IAAI,GAAG,CAAC;IAE5D,OAAO;QACL,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;QAC5D,iBAAiB;QACjB,QAAQ,EAAE,gBAAgB;QAC1B,QAAQ,EAAE,gBAAgB;QAC1B,SAAS,EAAE,gBAAgB;YACzB,CAAC,CAAC,yIAAyI;YAC3I,CAAC,CAAC,SAAS;KACd,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,SAAiB;IACpC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC7D,OAAO,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StyleRequest, StyleResult } from "../types.js";
|
|
2
|
+
export declare const SOURCE_FILENAME = "source.mp4";
|
|
3
|
+
export declare const OUTPUT_FILENAME = "output.mp4";
|
|
4
|
+
export declare function restyleRecording(request: StyleRequest, outputRoot: string): Promise<StyleResult>;
|
|
5
|
+
export declare function renderRecordingStyle(options: {
|
|
6
|
+
sourcePath: string;
|
|
7
|
+
outputPath: string;
|
|
8
|
+
backgroundPreset?: StyleRequest["backgroundPreset"];
|
|
9
|
+
addShadow?: boolean;
|
|
10
|
+
roundedCorners?: boolean;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=styleRecording.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styleRecording.d.ts","sourceRoot":"","sources":["../../src/pipeline/styleRecording.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE7D,eAAO,MAAM,eAAe,eAAe,CAAC;AAC5C,eAAO,MAAM,eAAe,eAAe,CAAC;AAU5C,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,YAAY,EACrB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,WAAW,CAAC,CA2BtB;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,YAAY,CAAC,kBAAkB,CAAC,CAAC;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,iBAuBA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { frameVideoOnBackground } from "../editor/frameVideo.js";
|
|
4
|
+
export const SOURCE_FILENAME = "source.mp4";
|
|
5
|
+
export const OUTPUT_FILENAME = "output.mp4";
|
|
6
|
+
// Styling is a derived, replaceable asset. Prioritize responsive iteration while
|
|
7
|
+
// retaining enough detail for a single-generation H.264 render.
|
|
8
|
+
const STYLE_ENCODE = {
|
|
9
|
+
crf: 18,
|
|
10
|
+
preset: "superfast",
|
|
11
|
+
deviceScaleFactor: 1,
|
|
12
|
+
};
|
|
13
|
+
export async function restyleRecording(request, outputRoot) {
|
|
14
|
+
if (!request.jobId?.trim())
|
|
15
|
+
throw new Error("jobId is required");
|
|
16
|
+
const outputDir = path.resolve(outputRoot, request.jobId);
|
|
17
|
+
if (!outputDir.startsWith(path.resolve(outputRoot) + path.sep)) {
|
|
18
|
+
throw new Error("Invalid jobId");
|
|
19
|
+
}
|
|
20
|
+
const sourcePath = path.join(outputDir, SOURCE_FILENAME);
|
|
21
|
+
await fs.access(sourcePath).catch(() => {
|
|
22
|
+
throw new Error("Original recording is unavailable for restyling");
|
|
23
|
+
});
|
|
24
|
+
const outputPath = path.join(outputDir, OUTPUT_FILENAME);
|
|
25
|
+
await renderRecordingStyle({
|
|
26
|
+
sourcePath,
|
|
27
|
+
outputPath,
|
|
28
|
+
backgroundPreset: request.backgroundPreset,
|
|
29
|
+
addShadow: request.addShadow,
|
|
30
|
+
roundedCorners: request.roundedCorners,
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
jobId: request.jobId,
|
|
34
|
+
videoUrl: `/outputs/${request.jobId}/${OUTPUT_FILENAME}`,
|
|
35
|
+
mp4Path: outputPath,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export async function renderRecordingStyle(options) {
|
|
39
|
+
const { sourcePath, outputPath, backgroundPreset, addShadow, roundedCorners } = options;
|
|
40
|
+
const tempOutputPath = outputPath.endsWith(".mp4")
|
|
41
|
+
? `${outputPath.slice(0, -4)}.next.mp4`
|
|
42
|
+
: `${outputPath}.next.mp4`;
|
|
43
|
+
await fs.unlink(tempOutputPath).catch(() => undefined);
|
|
44
|
+
if (backgroundPreset && backgroundPreset !== "none") {
|
|
45
|
+
await frameVideoOnBackground({
|
|
46
|
+
inputPath: sourcePath,
|
|
47
|
+
outputPath: tempOutputPath,
|
|
48
|
+
preset: backgroundPreset,
|
|
49
|
+
addShadow: addShadow ?? true,
|
|
50
|
+
roundedCorners: roundedCorners ?? false,
|
|
51
|
+
encode: STYLE_ENCODE,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
await fs.link(sourcePath, tempOutputPath).catch(() => fs.copyFile(sourcePath, tempOutputPath));
|
|
56
|
+
}
|
|
57
|
+
await fs.rename(tempOutputPath, outputPath);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=styleRecording.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styleRecording.js","sourceRoot":"","sources":["../../src/pipeline/styleRecording.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { EncodeSettings } from "./quality.js";
|
|
2
|
+
export declare function transcodeToMp4(inputPath: string, outputPath: string, framerate: number, width: number, height: number, encode: EncodeSettings): Promise<void>;
|
|
3
|
+
export declare function removeFileIfExists(filePath: string): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=ffmpeg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../src/transcode/ffmpeg.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAc,iBAyCvB;AA4BD,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,iBAExD"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { detectVideoMargins } from "../capture/detectMargins.js";
|
|
4
|
+
export async function transcodeToMp4(inputPath, outputPath, framerate, width, height, encode) {
|
|
5
|
+
await assertFfmpegAvailable();
|
|
6
|
+
const evenWidth = width % 2 === 0 ? width : width - 1;
|
|
7
|
+
const evenHeight = height % 2 === 0 ? height : height - 1;
|
|
8
|
+
const margins = await detectVideoMargins(inputPath);
|
|
9
|
+
const filters = [];
|
|
10
|
+
if (margins.right > 0 || margins.bottom > 0) {
|
|
11
|
+
console.log(`Trimming recorder margins: right=${margins.right}px bottom=${margins.bottom}px`);
|
|
12
|
+
filters.push(`crop=iw-${margins.right}:ih-${margins.bottom}:0:0`);
|
|
13
|
+
}
|
|
14
|
+
filters.push(`scale=${evenWidth}:${evenHeight}:flags=lanczos+accurate_rnd+full_chroma_int`, "setsar=1", `fps=${framerate}`);
|
|
15
|
+
const args = [
|
|
16
|
+
"-y",
|
|
17
|
+
"-i",
|
|
18
|
+
inputPath,
|
|
19
|
+
"-c:v",
|
|
20
|
+
"libx264",
|
|
21
|
+
"-preset",
|
|
22
|
+
encode.preset,
|
|
23
|
+
"-crf",
|
|
24
|
+
String(encode.crf),
|
|
25
|
+
"-pix_fmt",
|
|
26
|
+
"yuv420p",
|
|
27
|
+
"-movflags",
|
|
28
|
+
"+faststart",
|
|
29
|
+
"-vf",
|
|
30
|
+
filters.join(","),
|
|
31
|
+
outputPath,
|
|
32
|
+
];
|
|
33
|
+
await runFfmpeg(args);
|
|
34
|
+
}
|
|
35
|
+
async function assertFfmpegAvailable() {
|
|
36
|
+
try {
|
|
37
|
+
await runFfmpeg(["-version"]);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw new Error("ffmpeg is required but was not found on PATH");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function runFfmpeg(args) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const child = spawn("ffmpeg", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
46
|
+
let stderr = "";
|
|
47
|
+
child.stderr.on("data", (chunk) => {
|
|
48
|
+
stderr += chunk.toString();
|
|
49
|
+
});
|
|
50
|
+
child.on("error", reject);
|
|
51
|
+
child.on("close", (code) => {
|
|
52
|
+
if (code === 0)
|
|
53
|
+
resolve();
|
|
54
|
+
else
|
|
55
|
+
reject(new Error(`ffmpeg exited with code ${code}: ${stderr.slice(-2000)}`));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export async function removeFileIfExists(filePath) {
|
|
60
|
+
await fs.unlink(filePath).catch(() => undefined);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=ffmpeg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../../src/transcode/ffmpeg.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function probeVideoDurationMs(inputPath: string): Promise<number | null>;
|
|
2
|
+
export declare function probeVideoSize(inputPath: string): Promise<{
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
} | null>;
|
|
6
|
+
export declare function probeVideoFps(inputPath: string): Promise<number>;
|
|
7
|
+
//# sourceMappingURL=probe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/transcode/probe.ts"],"names":[],"mappings":"AAEA,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwBxB;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CA0BnD;AAED,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgCtE"}
|