@hyperframes/producer 0.4.22 → 0.4.23

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;
@@ -102709,6 +102710,7 @@ async function extractMediaMetadata(filePath) {
102709
102710
  videoCodec: "png",
102710
102711
  hasAudio: false,
102711
102712
  isVFR: false,
102713
+ hasAlpha: false,
102712
102714
  colorSpace: stillImageMeta.colorSpace
102713
102715
  };
102714
102716
  }
@@ -102723,6 +102725,9 @@ async function extractMediaMetadata(filePath) {
102723
102725
  const colorSpaceVal = videoStream.color_space || "";
102724
102726
  const ffprobeColorSpace = colorTransfer || colorPrimaries || colorSpaceVal ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null;
102725
102727
  const colorSpace = ffprobeColorSpace ?? stillImageMeta?.colorSpace ?? null;
102728
+ const pixelFormat = videoStream.pix_fmt || "";
102729
+ const alphaMode = videoStream.tags?.alpha_mode || "";
102730
+ const hasAlpha = /(^|[^a-z])yuva|rgba|argb|bgra|gbrap|gray[a-z0-9]*a/i.test(pixelFormat) || alphaMode === "1";
102726
102731
  return {
102727
102732
  durationSeconds: output2?.format.duration ? parseFloat(output2.format.duration) : 0,
102728
102733
  width: videoStream.width || stillImageMeta?.width || 0,
@@ -102731,6 +102736,7 @@ async function extractMediaMetadata(filePath) {
102731
102736
  videoCodec: videoStream.codec_name || "unknown",
102732
102737
  hasAudio: output2?.streams.some((s) => s.codec_type === "audio") ?? false,
102733
102738
  isVFR,
102739
+ hasAlpha,
102734
102740
  colorSpace
102735
102741
  };
102736
102742
  })();
@@ -103031,6 +103037,7 @@ function parseVideoElements(html) {
103031
103037
  start,
103032
103038
  end,
103033
103039
  mediaStart: mediaStartAttr ? parseFloat(mediaStartAttr) : 0,
103040
+ loop: el.hasAttribute("loop"),
103034
103041
  hasAudio: hasAudioAttr === "true"
103035
103042
  });
103036
103043
  }
@@ -103066,10 +103073,11 @@ function parseImageElements(html) {
103066
103073
  }
103067
103074
  async function extractVideoFramesRange(videoPath, videoId, startTime, duration, options, signal, config2, outputDirOverride) {
103068
103075
  const ffmpegProcessTimeout = config2?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
103069
- const { fps, outputDir, quality = 95, format: format3 = "jpg" } = options;
103076
+ const { fps, outputDir, quality = 95 } = options;
103070
103077
  const videoOutputDir = outputDirOverride ?? join9(outputDir, videoId);
103071
103078
  if (!existsSync9(videoOutputDir)) mkdirSync6(videoOutputDir, { recursive: true });
103072
103079
  const metadata = await extractMediaMetadata(videoPath);
103080
+ const format3 = resolveFrameFormat(metadata, options.format);
103073
103081
  const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${format3}`;
103074
103082
  const outputPattern = join9(videoOutputDir, framePattern);
103075
103083
  const isHdr = isHdrColorSpace(metadata.colorSpace);
@@ -103078,6 +103086,9 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
103078
103086
  if (isHdr && isMacOS) {
103079
103087
  args.push("-hwaccel", "videotoolbox");
103080
103088
  }
103089
+ if (metadata.hasAlpha && metadata.videoCodec === "vp9") {
103090
+ args.push("-c:v", "libvpx-vp9");
103091
+ }
103081
103092
  args.push("-ss", String(startTime), "-i", videoPath, "-t", String(duration));
103082
103093
  const vfFilters = [];
103083
103094
  if (isHdr && isMacOS) {
@@ -103189,6 +103200,10 @@ function resolveSegmentDuration(requested, mediaStart, metadata) {
103189
103200
  const sourceRemaining = metadata.durationSeconds - mediaStart;
103190
103201
  return sourceRemaining > 0 ? sourceRemaining : metadata.durationSeconds;
103191
103202
  }
103203
+ function resolveFrameFormat(metadata, requested) {
103204
+ if (requested) return requested;
103205
+ return metadata.hasAlpha ? "png" : "jpg";
103206
+ }
103192
103207
  async function convertVfrToCfr(inputPath, outputPath, targetFps, startTime, duration, signal, config2) {
103193
103208
  const timeout2 = config2?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
103194
103209
  const args = [
@@ -103381,12 +103396,12 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
103381
103396
  breakdown.vfrPreflightMs = Date.now() - vfrPreflightStart;
103382
103397
  const phase3Start = Date.now();
103383
103398
  const cacheRootDir = config2?.extractCacheDir;
103384
- const cacheFormat = options.format ?? "jpg";
103385
103399
  async function tryCachedExtract(video, videoPath, videoDuration, i) {
103386
103400
  if (!cacheRootDir) return null;
103387
103401
  const keyInput = cacheKeyInputs[i];
103388
103402
  const probedMeta = videoMetadata[i];
103389
103403
  if (!keyInput || !probedMeta) return null;
103404
+ const cacheFormat = resolveFrameFormat(probedMeta, options.format);
103390
103405
  const keyDuration = resolveSegmentDuration(
103391
103406
  keyInput.end - keyInput.start,
103392
103407
  keyInput.mediaStart,
@@ -103419,7 +103434,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
103419
103434
  video.id,
103420
103435
  video.mediaStart,
103421
103436
  videoDuration,
103422
- options,
103437
+ { ...options, format: cacheFormat },
103423
103438
  signal,
103424
103439
  config2,
103425
103440
  lookup.entry.dir
@@ -103449,7 +103464,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
103449
103464
  video.id,
103450
103465
  video.mediaStart,
103451
103466
  videoDuration,
103452
- options,
103467
+ { ...options, format: resolveFrameFormat(probedMeta, options.format) },
103453
103468
  signal,
103454
103469
  config2
103455
103470
  );
@@ -103482,10 +103497,17 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
103482
103497
  phaseBreakdown: breakdown
103483
103498
  };
103484
103499
  }
103485
- function getFrameAtTime(extracted, globalTime, videoStart) {
103486
- const localTime = globalTime - videoStart;
103500
+ function getFrameAtTime(extracted, globalTime, videoStart, loop = false, mediaStart = 0) {
103501
+ let localTime = globalTime - videoStart;
103487
103502
  if (localTime < 0) return null;
103503
+ const loopDuration = Math.max(0, extracted.metadata.durationSeconds - mediaStart);
103504
+ if (loop && loopDuration > 0 && localTime >= loopDuration) {
103505
+ localTime %= loopDuration;
103506
+ }
103488
103507
  const frameIndex = Math.floor(localTime * extracted.fps);
103508
+ if (loop && frameIndex >= extracted.totalFrames && extracted.totalFrames > 0) {
103509
+ return extracted.framePaths.get(extracted.totalFrames - 1) || null;
103510
+ }
103489
103511
  if (frameIndex < 0 || frameIndex >= extracted.totalFrames) return null;
103490
103512
  return extracted.framePaths.get(frameIndex) || null;
103491
103513
  }
@@ -103495,8 +103517,8 @@ var FrameLookupTable = class {
103495
103517
  activeVideoIds = /* @__PURE__ */ new Set();
103496
103518
  startCursor = 0;
103497
103519
  lastTime = null;
103498
- addVideo(extracted, start, end, mediaStart) {
103499
- this.videos.set(extracted.videoId, { extracted, start, end, mediaStart });
103520
+ addVideo(extracted, start, end, mediaStart, loop = false) {
103521
+ this.videos.set(extracted.videoId, { extracted, start, end, mediaStart, loop });
103500
103522
  this.orderedVideos = Array.from(this.videos.entries()).map(([videoId, video]) => ({ videoId, ...video })).sort((a, b) => a.start - b.start);
103501
103523
  this.resetActiveState();
103502
103524
  }
@@ -103504,7 +103526,7 @@ var FrameLookupTable = class {
103504
103526
  const video = this.videos.get(videoId);
103505
103527
  if (!video) return null;
103506
103528
  if (globalTime < video.start || globalTime >= video.end) return null;
103507
- return getFrameAtTime(video.extracted, globalTime, video.start);
103529
+ return getFrameAtTime(video.extracted, globalTime, video.start, video.loop, video.mediaStart);
103508
103530
  }
103509
103531
  resetActiveState() {
103510
103532
  this.activeVideoIds.clear();
@@ -103553,8 +103575,19 @@ var FrameLookupTable = class {
103553
103575
  for (const videoId of this.activeVideoIds) {
103554
103576
  const video = this.videos.get(videoId);
103555
103577
  if (!video) continue;
103556
- const localTime = globalTime - video.start;
103578
+ let localTime = globalTime - video.start;
103579
+ const loopDuration = Math.max(0, video.extracted.metadata.durationSeconds - video.mediaStart);
103580
+ if (video.loop && loopDuration > 0 && localTime >= loopDuration) {
103581
+ localTime %= loopDuration;
103582
+ }
103557
103583
  const frameIndex = Math.floor(localTime * video.extracted.fps);
103584
+ if (video.loop && frameIndex >= video.extracted.totalFrames) {
103585
+ const framePath2 = video.extracted.framePaths.get(video.extracted.totalFrames - 1);
103586
+ if (framePath2) {
103587
+ frames.set(videoId, { framePath: framePath2, frameIndex: video.extracted.totalFrames - 1 });
103588
+ }
103589
+ continue;
103590
+ }
103558
103591
  if (frameIndex < 0 || frameIndex >= video.extracted.totalFrames) continue;
103559
103592
  const framePath = video.extracted.framePaths.get(frameIndex);
103560
103593
  if (!framePath) continue;
@@ -103588,7 +103621,7 @@ function createFrameLookupTable(videos, extracted) {
103588
103621
  for (const ext of extracted) extractedMap.set(ext.videoId, ext);
103589
103622
  for (const video of videos) {
103590
103623
  const ext = extractedMap.get(video.id);
103591
- if (ext) table.addVideo(ext, video.start, video.end, video.mediaStart);
103624
+ if (ext) table.addVideo(ext, video.start, video.end, video.mediaStart, video.loop);
103592
103625
  }
103593
103626
  return table;
103594
103627
  }
@@ -109135,7 +109168,7 @@ async function compileHtmlFile(html, baseDir, downloadDir) {
109135
109168
  let compiledHtml = resolutions.length > 0 ? injectDurations(staticCompiled, resolutions) : staticCompiled;
109136
109169
  const preResolved = extractResolvedMedia(compiledHtml);
109137
109170
  const clampResults = await Promise.all(
109138
- preResolved.filter((el) => !!el.src).map(async (el) => {
109171
+ preResolved.filter((el) => !!el.src && !el.loop).map(async (el) => {
109139
109172
  const { duration: maxDuration } = await resolveMediaDuration(
109140
109173
  el.src,
109141
109174
  el.mediaStart,
@@ -109743,6 +109776,7 @@ async function discoverMediaFromBrowser(page) {
109743
109776
  const end = parseFloat(htmlEl.getAttribute("data-end") || "0");
109744
109777
  const duration = parseFloat(htmlEl.getAttribute("data-duration") || "0");
109745
109778
  const mediaStart = parseFloat(htmlEl.getAttribute("data-media-start") || "0");
109779
+ const loop = htmlEl.hasAttribute("loop");
109746
109780
  const hasAudio = htmlEl.getAttribute("data-has-audio") === "true";
109747
109781
  const volume = parseFloat(htmlEl.getAttribute("data-volume") || "1");
109748
109782
  results.push({
@@ -109753,6 +109787,7 @@ async function discoverMediaFromBrowser(page) {
109753
109787
  end,
109754
109788
  duration,
109755
109789
  mediaStart,
109790
+ loop,
109756
109791
  hasAudio,
109757
109792
  volume
109758
109793
  });
@@ -110627,6 +110662,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110627
110662
  if (el.hasAudio && !existing.hasAudio) {
110628
110663
  existing.hasAudio = true;
110629
110664
  }
110665
+ if (el.loop && !existing.loop) {
110666
+ existing.loop = true;
110667
+ }
110630
110668
  }
110631
110669
  } else {
110632
110670
  composition.videos.push({
@@ -110635,6 +110673,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110635
110673
  start: el.start,
110636
110674
  end: el.end,
110637
110675
  mediaStart: el.mediaStart,
110676
+ loop: el.loop,
110638
110677
  hasAudio: el.hasAudio
110639
110678
  });
110640
110679
  existingVideoIds.add(el.id);