@hyperframes/producer 0.4.22 → 0.5.0-alpha.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/dist/index.js CHANGED
@@ -99130,7 +99130,7 @@ function getAttr(tag, attr) {
99130
99130
  return match2 ? match2[1] ?? null : null;
99131
99131
  }
99132
99132
  function hasAttr(tag, attr) {
99133
- return new RegExp(`${attr}=["']`).test(tag);
99133
+ return new RegExp(`\\s${attr}(?:\\s|=|>|/)`).test(tag);
99134
99134
  }
99135
99135
  function injectAttr(tag, attr, value) {
99136
99136
  return tag.replace(/>$/, ` ${attr}="${value}">`);
@@ -99241,7 +99241,8 @@ function extractResolvedMedia(html) {
99241
99241
  src: getAttr(tag, "src") ?? void 0,
99242
99242
  start: startStr !== null ? parseFloat(startStr) : 0,
99243
99243
  duration,
99244
- mediaStart: mediaStartStr ? parseFloat(mediaStartStr) : 0
99244
+ mediaStart: mediaStartStr ? parseFloat(mediaStartStr) : 0,
99245
+ loop: hasAttr(tag, "loop")
99245
99246
  });
99246
99247
  }
99247
99248
  return resolved;
@@ -101437,8 +101438,12 @@ async function initializeSession(session) {
101437
101438
  }
101438
101439
  });
101439
101440
  page.on("pageerror", (err) => {
101440
- const text = `[Browser:PAGEERROR] ${err instanceof Error ? err.message : String(err)}`;
101441
- console.error(text);
101441
+ const message = err instanceof Error ? err.message : String(err);
101442
+ const text = `[Browser:PAGEERROR] ${message}`;
101443
+ const isPlayAbort = /^AbortError:/.test(message) && message.includes("play()") && message.includes("pause()");
101444
+ if (!isPlayAbort) {
101445
+ console.error(text);
101446
+ }
101442
101447
  session.browserConsoleBuffer.push(text);
101443
101448
  if (session.browserConsoleBuffer.length > BROWSER_CONSOLE_BUFFER_SIZE) {
101444
101449
  session.browserConsoleBuffer.shift();
@@ -102709,6 +102714,7 @@ async function extractMediaMetadata(filePath) {
102709
102714
  videoCodec: "png",
102710
102715
  hasAudio: false,
102711
102716
  isVFR: false,
102717
+ hasAlpha: false,
102712
102718
  colorSpace: stillImageMeta.colorSpace
102713
102719
  };
102714
102720
  }
@@ -102723,6 +102729,9 @@ async function extractMediaMetadata(filePath) {
102723
102729
  const colorSpaceVal = videoStream.color_space || "";
102724
102730
  const ffprobeColorSpace = colorTransfer || colorPrimaries || colorSpaceVal ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null;
102725
102731
  const colorSpace = ffprobeColorSpace ?? stillImageMeta?.colorSpace ?? null;
102732
+ const pixelFormat = videoStream.pix_fmt || "";
102733
+ const alphaMode = videoStream.tags?.alpha_mode || "";
102734
+ const hasAlpha = /(^|[^a-z])yuva|rgba|argb|bgra|gbrap|gray[a-z0-9]*a/i.test(pixelFormat) || alphaMode === "1";
102726
102735
  return {
102727
102736
  durationSeconds: output2?.format.duration ? parseFloat(output2.format.duration) : 0,
102728
102737
  width: videoStream.width || stillImageMeta?.width || 0,
@@ -102731,6 +102740,7 @@ async function extractMediaMetadata(filePath) {
102731
102740
  videoCodec: videoStream.codec_name || "unknown",
102732
102741
  hasAudio: output2?.streams.some((s) => s.codec_type === "audio") ?? false,
102733
102742
  isVFR,
102743
+ hasAlpha,
102734
102744
  colorSpace
102735
102745
  };
102736
102746
  })();
@@ -103031,6 +103041,7 @@ function parseVideoElements(html) {
103031
103041
  start,
103032
103042
  end,
103033
103043
  mediaStart: mediaStartAttr ? parseFloat(mediaStartAttr) : 0,
103044
+ loop: el.hasAttribute("loop"),
103034
103045
  hasAudio: hasAudioAttr === "true"
103035
103046
  });
103036
103047
  }
@@ -103066,10 +103077,11 @@ function parseImageElements(html) {
103066
103077
  }
103067
103078
  async function extractVideoFramesRange(videoPath, videoId, startTime, duration, options, signal, config2, outputDirOverride) {
103068
103079
  const ffmpegProcessTimeout = config2?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
103069
- const { fps, outputDir, quality = 95, format: format3 = "jpg" } = options;
103080
+ const { fps, outputDir, quality = 95 } = options;
103070
103081
  const videoOutputDir = outputDirOverride ?? join9(outputDir, videoId);
103071
103082
  if (!existsSync9(videoOutputDir)) mkdirSync6(videoOutputDir, { recursive: true });
103072
103083
  const metadata = await extractMediaMetadata(videoPath);
103084
+ const format3 = resolveFrameFormat(metadata, options.format);
103073
103085
  const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${format3}`;
103074
103086
  const outputPattern = join9(videoOutputDir, framePattern);
103075
103087
  const isHdr = isHdrColorSpace(metadata.colorSpace);
@@ -103078,6 +103090,9 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
103078
103090
  if (isHdr && isMacOS) {
103079
103091
  args.push("-hwaccel", "videotoolbox");
103080
103092
  }
103093
+ if (metadata.hasAlpha && metadata.videoCodec === "vp9") {
103094
+ args.push("-c:v", "libvpx-vp9");
103095
+ }
103081
103096
  args.push("-ss", String(startTime), "-i", videoPath, "-t", String(duration));
103082
103097
  const vfFilters = [];
103083
103098
  if (isHdr && isMacOS) {
@@ -103189,6 +103204,10 @@ function resolveSegmentDuration(requested, mediaStart, metadata) {
103189
103204
  const sourceRemaining = metadata.durationSeconds - mediaStart;
103190
103205
  return sourceRemaining > 0 ? sourceRemaining : metadata.durationSeconds;
103191
103206
  }
103207
+ function resolveFrameFormat(metadata, requested) {
103208
+ if (requested) return requested;
103209
+ return metadata.hasAlpha ? "png" : "jpg";
103210
+ }
103192
103211
  async function convertVfrToCfr(inputPath, outputPath, targetFps, startTime, duration, signal, config2) {
103193
103212
  const timeout2 = config2?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
103194
103213
  const args = [
@@ -103381,12 +103400,12 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
103381
103400
  breakdown.vfrPreflightMs = Date.now() - vfrPreflightStart;
103382
103401
  const phase3Start = Date.now();
103383
103402
  const cacheRootDir = config2?.extractCacheDir;
103384
- const cacheFormat = options.format ?? "jpg";
103385
103403
  async function tryCachedExtract(video, videoPath, videoDuration, i) {
103386
103404
  if (!cacheRootDir) return null;
103387
103405
  const keyInput = cacheKeyInputs[i];
103388
103406
  const probedMeta = videoMetadata[i];
103389
103407
  if (!keyInput || !probedMeta) return null;
103408
+ const cacheFormat = resolveFrameFormat(probedMeta, options.format);
103390
103409
  const keyDuration = resolveSegmentDuration(
103391
103410
  keyInput.end - keyInput.start,
103392
103411
  keyInput.mediaStart,
@@ -103419,7 +103438,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
103419
103438
  video.id,
103420
103439
  video.mediaStart,
103421
103440
  videoDuration,
103422
- options,
103441
+ { ...options, format: cacheFormat },
103423
103442
  signal,
103424
103443
  config2,
103425
103444
  lookup.entry.dir
@@ -103449,7 +103468,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
103449
103468
  video.id,
103450
103469
  video.mediaStart,
103451
103470
  videoDuration,
103452
- options,
103471
+ { ...options, format: resolveFrameFormat(probedMeta, options.format) },
103453
103472
  signal,
103454
103473
  config2
103455
103474
  );
@@ -103482,10 +103501,17 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
103482
103501
  phaseBreakdown: breakdown
103483
103502
  };
103484
103503
  }
103485
- function getFrameAtTime(extracted, globalTime, videoStart) {
103486
- const localTime = globalTime - videoStart;
103504
+ function getFrameAtTime(extracted, globalTime, videoStart, loop = false, mediaStart = 0) {
103505
+ let localTime = globalTime - videoStart;
103487
103506
  if (localTime < 0) return null;
103507
+ const loopDuration = Math.max(0, extracted.metadata.durationSeconds - mediaStart);
103508
+ if (loop && loopDuration > 0 && localTime >= loopDuration) {
103509
+ localTime %= loopDuration;
103510
+ }
103488
103511
  const frameIndex = Math.floor(localTime * extracted.fps);
103512
+ if (loop && frameIndex >= extracted.totalFrames && extracted.totalFrames > 0) {
103513
+ return extracted.framePaths.get(extracted.totalFrames - 1) || null;
103514
+ }
103489
103515
  if (frameIndex < 0 || frameIndex >= extracted.totalFrames) return null;
103490
103516
  return extracted.framePaths.get(frameIndex) || null;
103491
103517
  }
@@ -103495,8 +103521,8 @@ var FrameLookupTable = class {
103495
103521
  activeVideoIds = /* @__PURE__ */ new Set();
103496
103522
  startCursor = 0;
103497
103523
  lastTime = null;
103498
- addVideo(extracted, start, end, mediaStart) {
103499
- this.videos.set(extracted.videoId, { extracted, start, end, mediaStart });
103524
+ addVideo(extracted, start, end, mediaStart, loop = false) {
103525
+ this.videos.set(extracted.videoId, { extracted, start, end, mediaStart, loop });
103500
103526
  this.orderedVideos = Array.from(this.videos.entries()).map(([videoId, video]) => ({ videoId, ...video })).sort((a, b) => a.start - b.start);
103501
103527
  this.resetActiveState();
103502
103528
  }
@@ -103504,7 +103530,7 @@ var FrameLookupTable = class {
103504
103530
  const video = this.videos.get(videoId);
103505
103531
  if (!video) return null;
103506
103532
  if (globalTime < video.start || globalTime >= video.end) return null;
103507
- return getFrameAtTime(video.extracted, globalTime, video.start);
103533
+ return getFrameAtTime(video.extracted, globalTime, video.start, video.loop, video.mediaStart);
103508
103534
  }
103509
103535
  resetActiveState() {
103510
103536
  this.activeVideoIds.clear();
@@ -103553,8 +103579,19 @@ var FrameLookupTable = class {
103553
103579
  for (const videoId of this.activeVideoIds) {
103554
103580
  const video = this.videos.get(videoId);
103555
103581
  if (!video) continue;
103556
- const localTime = globalTime - video.start;
103582
+ let localTime = globalTime - video.start;
103583
+ const loopDuration = Math.max(0, video.extracted.metadata.durationSeconds - video.mediaStart);
103584
+ if (video.loop && loopDuration > 0 && localTime >= loopDuration) {
103585
+ localTime %= loopDuration;
103586
+ }
103557
103587
  const frameIndex = Math.floor(localTime * video.extracted.fps);
103588
+ if (video.loop && frameIndex >= video.extracted.totalFrames) {
103589
+ const framePath2 = video.extracted.framePaths.get(video.extracted.totalFrames - 1);
103590
+ if (framePath2) {
103591
+ frames.set(videoId, { framePath: framePath2, frameIndex: video.extracted.totalFrames - 1 });
103592
+ }
103593
+ continue;
103594
+ }
103558
103595
  if (frameIndex < 0 || frameIndex >= video.extracted.totalFrames) continue;
103559
103596
  const framePath = video.extracted.framePaths.get(frameIndex);
103560
103597
  if (!framePath) continue;
@@ -103588,7 +103625,7 @@ function createFrameLookupTable(videos, extracted) {
103588
103625
  for (const ext of extracted) extractedMap.set(ext.videoId, ext);
103589
103626
  for (const video of videos) {
103590
103627
  const ext = extractedMap.get(video.id);
103591
- if (ext) table.addVideo(ext, video.start, video.end, video.mediaStart);
103628
+ if (ext) table.addVideo(ext, video.start, video.end, video.mediaStart, video.loop);
103592
103629
  }
103593
103630
  return table;
103594
103631
  }
@@ -109135,7 +109172,7 @@ async function compileHtmlFile(html, baseDir, downloadDir) {
109135
109172
  let compiledHtml = resolutions.length > 0 ? injectDurations(staticCompiled, resolutions) : staticCompiled;
109136
109173
  const preResolved = extractResolvedMedia(compiledHtml);
109137
109174
  const clampResults = await Promise.all(
109138
- preResolved.filter((el) => !!el.src).map(async (el) => {
109175
+ preResolved.filter((el) => !!el.src && !el.loop).map(async (el) => {
109139
109176
  const { duration: maxDuration } = await resolveMediaDuration(
109140
109177
  el.src,
109141
109178
  el.mediaStart,
@@ -109743,6 +109780,7 @@ async function discoverMediaFromBrowser(page) {
109743
109780
  const end = parseFloat(htmlEl.getAttribute("data-end") || "0");
109744
109781
  const duration = parseFloat(htmlEl.getAttribute("data-duration") || "0");
109745
109782
  const mediaStart = parseFloat(htmlEl.getAttribute("data-media-start") || "0");
109783
+ const loop = htmlEl.hasAttribute("loop");
109746
109784
  const hasAudio = htmlEl.getAttribute("data-has-audio") === "true";
109747
109785
  const volume = parseFloat(htmlEl.getAttribute("data-volume") || "1");
109748
109786
  results.push({
@@ -109753,6 +109791,7 @@ async function discoverMediaFromBrowser(page) {
109753
109791
  end,
109754
109792
  duration,
109755
109793
  mediaStart,
109794
+ loop,
109756
109795
  hasAudio,
109757
109796
  volume
109758
109797
  });
@@ -110627,6 +110666,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110627
110666
  if (el.hasAudio && !existing.hasAudio) {
110628
110667
  existing.hasAudio = true;
110629
110668
  }
110669
+ if (el.loop && !existing.loop) {
110670
+ existing.loop = true;
110671
+ }
110630
110672
  }
110631
110673
  } else {
110632
110674
  composition.videos.push({
@@ -110635,6 +110677,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110635
110677
  start: el.start,
110636
110678
  end: el.end,
110637
110679
  mediaStart: el.mediaStart,
110680
+ loop: el.loop,
110638
110681
  hasAudio: el.hasAudio
110639
110682
  });
110640
110683
  existingVideoIds.add(el.id);