Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export {
export { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes/core";

export {
extractMediaMetadata,
extractVideoMetadata,
extractAudioMetadata,
analyzeKeyframeIntervals,
Expand Down
10 changes: 5 additions & 5 deletions packages/engine/src/services/videoFrameExtractor.ts
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractVideoMetadata() is still called at line 450 even though this refactor renames the symbol to extractMediaMetadata(). On the stacked head that leaves engine and producer typecheck red, fails the VFR engine tests, and crashes both HDR regression suites at render time with extractVideoMetadata is not defined, so the rename is not complete yet.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The remaining extractVideoMetadata call in videoFrameExtractor.ts was updated to extractMediaMetadata. All five usages in the file are now on the new name:

$ rg 'extractMediaMetadata|extractVideoMetadata' packages/engine/src/services/videoFrameExtractor.ts
12:  import { extractMediaMetadata, type VideoMetadata } from "../utils/ffprobe.js";
161:  const metadata = await extractMediaMetadata(videoPath);
410: const metadata = await extractMediaMetadata(videoPath);
456: const metadata = await extractMediaMetadata(entry.videoPath);
502: const metadata = await extractMediaMetadata(videoPath);

The legacy extractVideoMetadata is preserved as a deprecated alias in packages/engine/src/utils/ffprobe.ts:

export const extractVideoMetadata = extractMediaMetadata;

Producer's ffprobe.ts shim still re-exports the alias for back-compat. Engine + producer typecheck green; HDR regression suites passing on this commit.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { spawn } from "child_process";
import { existsSync, mkdirSync, readdirSync, rmSync } from "fs";
import { join } from "path";
import { parseHTML } from "linkedom";
import { extractVideoMetadata, type VideoMetadata } from "../utils/ffprobe.js";
import { extractMediaMetadata, type VideoMetadata } from "../utils/ffprobe.js";
import {
analyzeCompositionHdr,
isHdrColorSpace as isHdrColorSpaceUtil,
Expand Down Expand Up @@ -158,7 +158,7 @@ export async function extractVideoFramesRange(
const videoOutputDir = join(outputDir, videoId);
if (!existsSync(videoOutputDir)) mkdirSync(videoOutputDir, { recursive: true });

const metadata = await extractVideoMetadata(videoPath);
const metadata = await extractMediaMetadata(videoPath);
const framePattern = `frame_%05d.${format}`;
const outputPattern = join(videoOutputDir, framePattern);

Expand Down Expand Up @@ -407,7 +407,7 @@ export async function extractAllVideoFrames(
// Phase 2: Probe color spaces and normalize if mixed HDR/SDR
const videoColorSpaces = await Promise.all(
resolvedVideos.map(async ({ videoPath }) => {
const metadata = await extractVideoMetadata(videoPath);
const metadata = await extractMediaMetadata(videoPath);
return metadata.colorSpace;
}),
);
Expand Down Expand Up @@ -453,7 +453,7 @@ export async function extractAllVideoFrames(
if (signal?.aborted) break;
const entry = resolvedVideos[i];
if (!entry) continue;
const metadata = await extractVideoMetadata(entry.videoPath);
const metadata = await extractMediaMetadata(entry.videoPath);
if (!metadata.isVFR) continue;

let segDuration = entry.video.end - entry.video.start;
Expand Down Expand Up @@ -499,7 +499,7 @@ export async function extractAllVideoFrames(
// Fallback: if no data-duration/data-end was specified (end is Infinity or 0),
// probe the actual video file to get its natural duration.
if (!Number.isFinite(videoDuration) || videoDuration <= 0) {
const metadata = await extractVideoMetadata(videoPath);
const metadata = await extractMediaMetadata(videoPath);
const sourceDuration = metadata.durationSeconds - video.mediaStart;
videoDuration = sourceDuration > 0 ? sourceDuration : metadata.durationSeconds;
video.end = video.start + videoDuration;
Expand Down
6 changes: 3 additions & 3 deletions packages/engine/src/utils/ffprobe.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFileSync } from "fs";
import { resolve } from "path";
import { describe, expect, it } from "vitest";
import { extractPngMetadataFromBuffer, extractVideoMetadata } from "./ffprobe.js";
import { extractMediaMetadata, extractPngMetadataFromBuffer } from "./ffprobe.js";

function crc32(buf: Buffer): number {
let crc = 0xffffffff;
Expand Down Expand Up @@ -51,14 +51,14 @@ function buildMinimalPng(options?: {
: buildPngWithChunks([ihdr, cicp, idat, iend]);
}

describe("extractVideoMetadata", () => {
describe("extractMediaMetadata", () => {
it("reads HDR PNG cICP metadata when ffprobe color fields are absent", async () => {
const fixturePath = resolve(
__dirname,
"../../../producer/tests/hdr-regression/src/hdr-photo-pq.png",
);

const metadata = await extractVideoMetadata(fixturePath);
const metadata = await extractMediaMetadata(fixturePath);

expect(metadata.colorSpace).toEqual({
colorPrimaries: "bt2020",
Expand Down
17 changes: 16 additions & 1 deletion packages/engine/src/utils/ffprobe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,14 @@ function parseFrameRate(frameRateStr: string | undefined): number {
return parseFloat(frameRateStr) || 0;
}

export async function extractVideoMetadata(filePath: string): Promise<VideoMetadata> {
/**
* Probe a media file (video, image, or container) and return normalized metadata.
*
* Despite the legacy name `extractVideoMetadata` (still exported as a
* deprecated alias below), this also handles still images such as PNG so it
* can be used uniformly for any visual asset the HDR pipeline encounters.
*/
export async function extractMediaMetadata(filePath: string): Promise<VideoMetadata> {
const cached = videoMetadataCache.get(filePath);
if (cached) return cached;

Expand Down Expand Up @@ -286,6 +293,14 @@ export async function extractVideoMetadata(filePath: string): Promise<VideoMetad
return probePromise;
}

/**
* @deprecated Use `extractMediaMetadata` — this name is kept for backward
* compatibility with consumers that imported the original video-only name
* before still-image (PNG) support was added. New callers should prefer
* `extractMediaMetadata`.
*/
export const extractVideoMetadata = extractMediaMetadata;

export async function extractAudioMetadata(filePath: string): Promise<AudioMetadata> {
const cached = audioMetadataCache.get(filePath);
if (cached) return cached;
Expand Down
4 changes: 2 additions & 2 deletions packages/producer/src/regression-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import process from "node:process";
import { createRenderJob, executeRenderJob } from "./services/renderOrchestrator.js";
import { compileForRender } from "./services/htmlCompiler.js";
import { validateCompilation } from "./services/compilationTester.js";
import { extractVideoMetadata } from "./utils/ffprobe.js";
import { extractMediaMetadata } from "./utils/ffprobe.js";
import { buildRmsEnvelope, compareAudioEnvelopes } from "./utils/audioRegression.js";

// ── Types ────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -625,7 +625,7 @@ async function runTestSuite(

// Visual comparison (100 frames, 1 per 1% of video duration)
logPretty("Comparing visual quality (100 checkpoints)...", "🔍");
const videoMetadata = await extractVideoMetadata(renderedOutputPath);
const videoMetadata = await extractMediaMetadata(renderedOutputPath);
const videoDuration = videoMetadata.durationSeconds;

const visualCheckpoints: Array<{ time: number; psnr: number; passed: boolean }> = [];
Expand Down
6 changes: 3 additions & 3 deletions packages/producer/src/services/htmlCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
rewriteAssetPaths,
rewriteCssAssetUrls,
} from "@hyperframes/core";
import { extractVideoMetadata, extractAudioMetadata } from "../utils/ffprobe.js";
import { extractMediaMetadata, extractAudioMetadata } from "../utils/ffprobe.js";
import { isPathInside, toExternalAssetKey } from "../utils/paths.js";
import {
parseVideoElements,
Expand Down Expand Up @@ -152,7 +152,7 @@ async function resolveMediaDuration(

const metadata =
tagName === "video"
? await extractVideoMetadata(filePath)
? await extractMediaMetadata(filePath)
: await extractAudioMetadata(filePath);

const fileDuration = metadata.durationSeconds;
Expand Down Expand Up @@ -1033,7 +1033,7 @@ export async function compileForRender(
if (isHttpUrl(video.src)) continue;
const videoPath = resolve(projectDir, video.src);
const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
Promise.all([analyzeKeyframeIntervals(videoPath), extractVideoMetadata(videoPath)])
Promise.all([analyzeKeyframeIntervals(videoPath), extractMediaMetadata(videoPath)])
.then(([analysis, metadata]) => {
if (analysis.isProblematic) {
console.warn(
Expand Down
Loading
Loading