@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.
@@ -101919,7 +101919,7 @@ function getAttr(tag, attr) {
101919
101919
  return match2 ? match2[1] ?? null : null;
101920
101920
  }
101921
101921
  function hasAttr(tag, attr) {
101922
- return new RegExp(`${attr}=["']`).test(tag);
101922
+ return new RegExp(`\\s${attr}(?:\\s|=|>|/)`).test(tag);
101923
101923
  }
101924
101924
  function injectAttr(tag, attr, value) {
101925
101925
  return tag.replace(/>$/, ` ${attr}="${value}">`);
@@ -102030,7 +102030,8 @@ function extractResolvedMedia(html) {
102030
102030
  src: getAttr(tag, "src") ?? void 0,
102031
102031
  start: startStr !== null ? parseFloat(startStr) : 0,
102032
102032
  duration,
102033
- mediaStart: mediaStartStr ? parseFloat(mediaStartStr) : 0
102033
+ mediaStart: mediaStartStr ? parseFloat(mediaStartStr) : 0,
102034
+ loop: hasAttr(tag, "loop")
102034
102035
  });
102035
102036
  }
102036
102037
  return resolved;
@@ -104226,8 +104227,12 @@ async function initializeSession(session) {
104226
104227
  }
104227
104228
  });
104228
104229
  page.on("pageerror", (err) => {
104229
- const text = `[Browser:PAGEERROR] ${err instanceof Error ? err.message : String(err)}`;
104230
- console.error(text);
104230
+ const message = err instanceof Error ? err.message : String(err);
104231
+ const text = `[Browser:PAGEERROR] ${message}`;
104232
+ const isPlayAbort = /^AbortError:/.test(message) && message.includes("play()") && message.includes("pause()");
104233
+ if (!isPlayAbort) {
104234
+ console.error(text);
104235
+ }
104231
104236
  session.browserConsoleBuffer.push(text);
104232
104237
  if (session.browserConsoleBuffer.length > BROWSER_CONSOLE_BUFFER_SIZE) {
104233
104238
  session.browserConsoleBuffer.shift();
@@ -105498,6 +105503,7 @@ async function extractMediaMetadata(filePath) {
105498
105503
  videoCodec: "png",
105499
105504
  hasAudio: false,
105500
105505
  isVFR: false,
105506
+ hasAlpha: false,
105501
105507
  colorSpace: stillImageMeta.colorSpace
105502
105508
  };
105503
105509
  }
@@ -105512,6 +105518,9 @@ async function extractMediaMetadata(filePath) {
105512
105518
  const colorSpaceVal = videoStream.color_space || "";
105513
105519
  const ffprobeColorSpace = colorTransfer || colorPrimaries || colorSpaceVal ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null;
105514
105520
  const colorSpace = ffprobeColorSpace ?? stillImageMeta?.colorSpace ?? null;
105521
+ const pixelFormat = videoStream.pix_fmt || "";
105522
+ const alphaMode = videoStream.tags?.alpha_mode || "";
105523
+ const hasAlpha = /(^|[^a-z])yuva|rgba|argb|bgra|gbrap|gray[a-z0-9]*a/i.test(pixelFormat) || alphaMode === "1";
105515
105524
  return {
105516
105525
  durationSeconds: output2?.format.duration ? parseFloat(output2.format.duration) : 0,
105517
105526
  width: videoStream.width || stillImageMeta?.width || 0,
@@ -105520,6 +105529,7 @@ async function extractMediaMetadata(filePath) {
105520
105529
  videoCodec: videoStream.codec_name || "unknown",
105521
105530
  hasAudio: output2?.streams.some((s) => s.codec_type === "audio") ?? false,
105522
105531
  isVFR,
105532
+ hasAlpha,
105523
105533
  colorSpace
105524
105534
  };
105525
105535
  })();
@@ -105820,6 +105830,7 @@ function parseVideoElements(html) {
105820
105830
  start,
105821
105831
  end,
105822
105832
  mediaStart: mediaStartAttr ? parseFloat(mediaStartAttr) : 0,
105833
+ loop: el.hasAttribute("loop"),
105823
105834
  hasAudio: hasAudioAttr === "true"
105824
105835
  });
105825
105836
  }
@@ -105855,10 +105866,11 @@ function parseImageElements(html) {
105855
105866
  }
105856
105867
  async function extractVideoFramesRange(videoPath, videoId, startTime, duration, options, signal, config2, outputDirOverride) {
105857
105868
  const ffmpegProcessTimeout = config2?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
105858
- const { fps, outputDir, quality = 95, format: format3 = "jpg" } = options;
105869
+ const { fps, outputDir, quality = 95 } = options;
105859
105870
  const videoOutputDir = outputDirOverride ?? join9(outputDir, videoId);
105860
105871
  if (!existsSync9(videoOutputDir)) mkdirSync6(videoOutputDir, { recursive: true });
105861
105872
  const metadata = await extractMediaMetadata(videoPath);
105873
+ const format3 = resolveFrameFormat(metadata, options.format);
105862
105874
  const framePattern = `${FRAME_FILENAME_PREFIX}%05d.${format3}`;
105863
105875
  const outputPattern = join9(videoOutputDir, framePattern);
105864
105876
  const isHdr = isHdrColorSpace(metadata.colorSpace);
@@ -105867,6 +105879,9 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
105867
105879
  if (isHdr && isMacOS) {
105868
105880
  args.push("-hwaccel", "videotoolbox");
105869
105881
  }
105882
+ if (metadata.hasAlpha && metadata.videoCodec === "vp9") {
105883
+ args.push("-c:v", "libvpx-vp9");
105884
+ }
105870
105885
  args.push("-ss", String(startTime), "-i", videoPath, "-t", String(duration));
105871
105886
  const vfFilters = [];
105872
105887
  if (isHdr && isMacOS) {
@@ -105978,6 +105993,10 @@ function resolveSegmentDuration(requested, mediaStart, metadata) {
105978
105993
  const sourceRemaining = metadata.durationSeconds - mediaStart;
105979
105994
  return sourceRemaining > 0 ? sourceRemaining : metadata.durationSeconds;
105980
105995
  }
105996
+ function resolveFrameFormat(metadata, requested) {
105997
+ if (requested) return requested;
105998
+ return metadata.hasAlpha ? "png" : "jpg";
105999
+ }
105981
106000
  async function convertVfrToCfr(inputPath, outputPath, targetFps, startTime, duration, signal, config2) {
105982
106001
  const timeout2 = config2?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
105983
106002
  const args = [
@@ -106170,12 +106189,12 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
106170
106189
  breakdown.vfrPreflightMs = Date.now() - vfrPreflightStart;
106171
106190
  const phase3Start = Date.now();
106172
106191
  const cacheRootDir = config2?.extractCacheDir;
106173
- const cacheFormat = options.format ?? "jpg";
106174
106192
  async function tryCachedExtract(video, videoPath, videoDuration, i) {
106175
106193
  if (!cacheRootDir) return null;
106176
106194
  const keyInput = cacheKeyInputs[i];
106177
106195
  const probedMeta = videoMetadata[i];
106178
106196
  if (!keyInput || !probedMeta) return null;
106197
+ const cacheFormat = resolveFrameFormat(probedMeta, options.format);
106179
106198
  const keyDuration = resolveSegmentDuration(
106180
106199
  keyInput.end - keyInput.start,
106181
106200
  keyInput.mediaStart,
@@ -106208,7 +106227,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
106208
106227
  video.id,
106209
106228
  video.mediaStart,
106210
106229
  videoDuration,
106211
- options,
106230
+ { ...options, format: cacheFormat },
106212
106231
  signal,
106213
106232
  config2,
106214
106233
  lookup.entry.dir
@@ -106238,7 +106257,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
106238
106257
  video.id,
106239
106258
  video.mediaStart,
106240
106259
  videoDuration,
106241
- options,
106260
+ { ...options, format: resolveFrameFormat(probedMeta, options.format) },
106242
106261
  signal,
106243
106262
  config2
106244
106263
  );
@@ -106271,10 +106290,17 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
106271
106290
  phaseBreakdown: breakdown
106272
106291
  };
106273
106292
  }
106274
- function getFrameAtTime(extracted, globalTime, videoStart) {
106275
- const localTime = globalTime - videoStart;
106293
+ function getFrameAtTime(extracted, globalTime, videoStart, loop = false, mediaStart = 0) {
106294
+ let localTime = globalTime - videoStart;
106276
106295
  if (localTime < 0) return null;
106296
+ const loopDuration = Math.max(0, extracted.metadata.durationSeconds - mediaStart);
106297
+ if (loop && loopDuration > 0 && localTime >= loopDuration) {
106298
+ localTime %= loopDuration;
106299
+ }
106277
106300
  const frameIndex = Math.floor(localTime * extracted.fps);
106301
+ if (loop && frameIndex >= extracted.totalFrames && extracted.totalFrames > 0) {
106302
+ return extracted.framePaths.get(extracted.totalFrames - 1) || null;
106303
+ }
106278
106304
  if (frameIndex < 0 || frameIndex >= extracted.totalFrames) return null;
106279
106305
  return extracted.framePaths.get(frameIndex) || null;
106280
106306
  }
@@ -106284,8 +106310,8 @@ var FrameLookupTable = class {
106284
106310
  activeVideoIds = /* @__PURE__ */ new Set();
106285
106311
  startCursor = 0;
106286
106312
  lastTime = null;
106287
- addVideo(extracted, start, end, mediaStart) {
106288
- this.videos.set(extracted.videoId, { extracted, start, end, mediaStart });
106313
+ addVideo(extracted, start, end, mediaStart, loop = false) {
106314
+ this.videos.set(extracted.videoId, { extracted, start, end, mediaStart, loop });
106289
106315
  this.orderedVideos = Array.from(this.videos.entries()).map(([videoId, video]) => ({ videoId, ...video })).sort((a, b) => a.start - b.start);
106290
106316
  this.resetActiveState();
106291
106317
  }
@@ -106293,7 +106319,7 @@ var FrameLookupTable = class {
106293
106319
  const video = this.videos.get(videoId);
106294
106320
  if (!video) return null;
106295
106321
  if (globalTime < video.start || globalTime >= video.end) return null;
106296
- return getFrameAtTime(video.extracted, globalTime, video.start);
106322
+ return getFrameAtTime(video.extracted, globalTime, video.start, video.loop, video.mediaStart);
106297
106323
  }
106298
106324
  resetActiveState() {
106299
106325
  this.activeVideoIds.clear();
@@ -106342,8 +106368,19 @@ var FrameLookupTable = class {
106342
106368
  for (const videoId of this.activeVideoIds) {
106343
106369
  const video = this.videos.get(videoId);
106344
106370
  if (!video) continue;
106345
- const localTime = globalTime - video.start;
106371
+ let localTime = globalTime - video.start;
106372
+ const loopDuration = Math.max(0, video.extracted.metadata.durationSeconds - video.mediaStart);
106373
+ if (video.loop && loopDuration > 0 && localTime >= loopDuration) {
106374
+ localTime %= loopDuration;
106375
+ }
106346
106376
  const frameIndex = Math.floor(localTime * video.extracted.fps);
106377
+ if (video.loop && frameIndex >= video.extracted.totalFrames) {
106378
+ const framePath2 = video.extracted.framePaths.get(video.extracted.totalFrames - 1);
106379
+ if (framePath2) {
106380
+ frames.set(videoId, { framePath: framePath2, frameIndex: video.extracted.totalFrames - 1 });
106381
+ }
106382
+ continue;
106383
+ }
106347
106384
  if (frameIndex < 0 || frameIndex >= video.extracted.totalFrames) continue;
106348
106385
  const framePath = video.extracted.framePaths.get(frameIndex);
106349
106386
  if (!framePath) continue;
@@ -106377,7 +106414,7 @@ function createFrameLookupTable(videos, extracted) {
106377
106414
  for (const ext of extracted) extractedMap.set(ext.videoId, ext);
106378
106415
  for (const video of videos) {
106379
106416
  const ext = extractedMap.get(video.id);
106380
- if (ext) table.addVideo(ext, video.start, video.end, video.mediaStart);
106417
+ if (ext) table.addVideo(ext, video.start, video.end, video.mediaStart, video.loop);
106381
106418
  }
106382
106419
  return table;
106383
106420
  }
@@ -109300,7 +109337,7 @@ async function compileHtmlFile(html, baseDir, downloadDir) {
109300
109337
  let compiledHtml = resolutions.length > 0 ? injectDurations(staticCompiled, resolutions) : staticCompiled;
109301
109338
  const preResolved = extractResolvedMedia(compiledHtml);
109302
109339
  const clampResults = await Promise.all(
109303
- preResolved.filter((el) => !!el.src).map(async (el) => {
109340
+ preResolved.filter((el) => !!el.src && !el.loop).map(async (el) => {
109304
109341
  const { duration: maxDuration } = await resolveMediaDuration(
109305
109342
  el.src,
109306
109343
  el.mediaStart,
@@ -109908,6 +109945,7 @@ async function discoverMediaFromBrowser(page) {
109908
109945
  const end = parseFloat(htmlEl.getAttribute("data-end") || "0");
109909
109946
  const duration = parseFloat(htmlEl.getAttribute("data-duration") || "0");
109910
109947
  const mediaStart = parseFloat(htmlEl.getAttribute("data-media-start") || "0");
109948
+ const loop = htmlEl.hasAttribute("loop");
109911
109949
  const hasAudio = htmlEl.getAttribute("data-has-audio") === "true";
109912
109950
  const volume = parseFloat(htmlEl.getAttribute("data-volume") || "1");
109913
109951
  results.push({
@@ -109918,6 +109956,7 @@ async function discoverMediaFromBrowser(page) {
109918
109956
  end,
109919
109957
  duration,
109920
109958
  mediaStart,
109959
+ loop,
109921
109960
  hasAudio,
109922
109961
  volume
109923
109962
  });
@@ -110792,6 +110831,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110792
110831
  if (el.hasAudio && !existing.hasAudio) {
110793
110832
  existing.hasAudio = true;
110794
110833
  }
110834
+ if (el.loop && !existing.loop) {
110835
+ existing.loop = true;
110836
+ }
110795
110837
  }
110796
110838
  } else {
110797
110839
  composition.videos.push({
@@ -110800,6 +110842,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110800
110842
  start: el.start,
110801
110843
  end: el.end,
110802
110844
  mediaStart: el.mediaStart,
110845
+ loop: el.loop,
110803
110846
  hasAudio: el.hasAudio
110804
110847
  });
110805
110848
  existingVideoIds.add(el.id);