@t3lnet/sceneforge 1.0.25 → 1.0.27

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.
@@ -182,6 +182,7 @@ export async function runRecordDemoCommand(argv) {
182
182
  const hqVideo = hasFlag(args, "--hq-video");
183
183
  const hqFormat = getFlagValue(args, "--hq-format") ?? "jpeg";
184
184
  const hqQuality = parseInt(getFlagValue(args, "--hq-quality") ?? "100", 10);
185
+ const hqFps = parseInt(getFlagValue(args, "--hq-fps") ?? "30", 10);
185
186
 
186
187
  if (!baseUrl) {
187
188
  console.error("[error] --base-url is required");
@@ -291,8 +292,9 @@ export async function runRecordDemoCommand(argv) {
291
292
  }
292
293
 
293
294
  // Stop CDP recorder before closing context
295
+ let cdpStopInfo = null;
294
296
  if (cdpRecorder) {
295
- await cdpRecorder.stop();
297
+ cdpStopInfo = await cdpRecorder.stop();
296
298
  }
297
299
 
298
300
  await context.close();
@@ -303,10 +305,12 @@ export async function runRecordDemoCommand(argv) {
303
305
  const finalVideoPath = path.join(outputPaths.videosDir, `${definition.name}.webm`);
304
306
  try {
305
307
  if (!demoError && result?.success) {
306
- await cdpRecorder.assembleVideo(finalVideoPath);
308
+ await cdpRecorder.assembleVideo(finalVideoPath, { fps: hqFps });
307
309
  console.log(`[record] High-quality video saved: ${finalVideoPath}`);
308
310
  if (result.scriptPath) {
309
- await alignScriptToVideo(result.scriptPath, finalVideoPath, videoRecordingStartTime);
311
+ // Use the actual first frame time for alignment, not when we started the recorder
312
+ const actualVideoStartTime = cdpStopInfo?.firstFrameTime || videoRecordingStartTime;
313
+ await alignScriptToVideo(result.scriptPath, finalVideoPath, actualVideoStartTime);
310
314
  }
311
315
  }
312
316
  } finally {
@@ -55,6 +55,7 @@ export function createCDPRecorder(page, options) {
55
55
  let frameTimestamps = [];
56
56
  let isRecording = false;
57
57
  let startTime = null;
58
+ let firstFrameTime = null; // Wall clock time when first frame was received
58
59
  let writeQueue = Promise.resolve();
59
60
  let nextFrameNumber = 0;
60
61
 
@@ -64,6 +65,11 @@ export function createCDPRecorder(page, options) {
64
65
  const { data, metadata, sessionId } = params;
65
66
  frameCount += 1;
66
67
 
68
+ // Track when we receive the first frame (wall clock time)
69
+ if (firstFrameTime === null) {
70
+ firstFrameTime = Date.now();
71
+ }
72
+
67
73
  // Acknowledge the frame immediately to continue receiving
68
74
  cdpSession.send("Page.screencastFrameAck", { sessionId }).catch(() => {
69
75
  // Session may be closed
@@ -156,15 +162,21 @@ export function createCDPRecorder(page, options) {
156
162
  // Wait for all pending frame writes to complete
157
163
  await writeQueue;
158
164
 
165
+ const delayToFirstFrame = firstFrameTime ? firstFrameTime - startTime : 0;
159
166
  console.log(
160
167
  `[cdp-recorder] Captured ${frameTimestamps.length} frames in ${(duration / 1000).toFixed(2)}s`
161
168
  );
169
+ if (delayToFirstFrame > 100) {
170
+ console.log(`[cdp-recorder] First frame delay: ${delayToFirstFrame}ms`);
171
+ }
162
172
 
163
173
  return {
164
174
  frameCount: frameTimestamps.length,
165
175
  duration,
166
176
  framesDir,
167
177
  frameTimestamps,
178
+ firstFrameTime, // Wall clock time when first frame was captured
179
+ startTime, // Wall clock time when recording was started
168
180
  };
169
181
  },
170
182
 
@@ -183,17 +195,8 @@ export function createCDPRecorder(page, options) {
183
195
  throw new Error("No frames captured - cannot assemble video");
184
196
  }
185
197
 
186
- // Calculate actual FPS from captured frame timestamps
187
- let fps = fpsOverride;
188
- if (!fps && frameTimestamps.length > 1) {
189
- const firstTimestamp = frameTimestamps[0].timestamp;
190
- const lastTimestamp = frameTimestamps[frameTimestamps.length - 1].timestamp;
191
- const durationSec = lastTimestamp - firstTimestamp;
192
- if (durationSec > 0) {
193
- fps = Math.round(frameTimestamps.length / durationSec);
194
- }
195
- }
196
- fps = fps || 30; // Default to 30fps
198
+ // Use provided fps or default to 30
199
+ const fps = fpsOverride || 30;
197
200
 
198
201
  console.log(`[cdp-recorder] Assembling video at ${fps} fps`);
199
202
 
@@ -259,5 +262,6 @@ High-Quality Recording Options:
259
262
  --hq-video Use CDP-based high-quality recording instead of Playwright
260
263
  Captures frames at maximum quality and assembles with FFmpeg
261
264
  --hq-format <format> Frame format: jpeg (default) or png (lossless, larger files)
262
- --hq-quality <1-100> JPEG quality (default: 100, ignored for PNG)`;
265
+ --hq-quality <1-100> JPEG quality (default: 100, ignored for PNG)
266
+ --hq-fps <fps> Output framerate (default: 30)`;
263
267
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t3lnet/sceneforge",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "SceneForge runner and generation utilities for YAML-driven demos",
5
5
  "license": "MIT",
6
6
  "author": "T3LNET",