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
3 changes: 3 additions & 0 deletions docs/guides/color-grading.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ labelled `original` unless you pass `--no-baseline`. Up to sixteen cells land
four to a row in `grade-compare.png`, or wherever `--out` points. Swap `--grades`
for `--luts looks/a.cube,looks/b.cube` to compare LUT files the same way.

Phone photos keep their displayed orientation and aspect ratio in the sheet;
the source image file stays unchanged.

{/* VISUAL NEEDED (4): a real grade-compare.png sheet — the labelled grid with
`original` in the first cell. */}

Expand Down
65 changes: 65 additions & 0 deletions packages/cli/src/commands/grade-compare.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { basename, join } from "node:path";
import { HF_COLOR_GRADING_ATTR, serializeHfColorGrading } from "@hyperframes/core";
import sharp from "sharp";
import { describe, expect, it, vi } from "vitest";
import {
buildGradeCompareHtml,
buildGradeCompareSuccessPayload,
capCandidateCells,
loadReferenceFrame,
parseGradeCompareArgs,
parseGradesFile,
prepareGradeCompareTempProject,
Expand Down Expand Up @@ -134,6 +136,69 @@ describe("buildGradeCompareHtml", () => {
});
});

describe("loadReferenceFrame", () => {
it.each([
{ orientation: 1, width: 160, height: 80 },
{ orientation: 2, width: 160, height: 80 },
{ orientation: 3, width: 160, height: 80 },
{ orientation: 4, width: 160, height: 80 },
{ orientation: 5, width: 80, height: 160 },
{ orientation: 6, width: 80, height: 160 },
{ orientation: 7, width: 80, height: 160 },
{ orientation: 8, width: 80, height: 160 },
])("uses displayed dimensions for JPEG orientation $orientation", async (expected) => {
const dir = tempDir();
try {
const framePath = join(dir, "phone.jpg");
const buffer = await sharp({
create: { width: 160, height: 80, channels: 3, background: "red" },
})
.jpeg()
.withMetadata({ orientation: expected.orientation })
.toBuffer();
writeFileSync(framePath, buffer);

const frame = await loadReferenceFrame(framePath);

expect(frame).toMatchObject({ width: expected.width, height: expected.height });
expect(frame.buffer).toEqual(buffer);
expect(readFileSync(framePath)).toEqual(buffer);
const html = buildGradeCompareHtml({
cells: [{ label: "warm", grading: { preset: "warm-daylight" } }],
frameSrc: frame.stagedName,
frameWidth: frame.width,
frameHeight: frame.height,
});
const sheetHeight = expected.width === 80 ? 1184 : 344;
expect(html).toContain(`data-height="${sheetHeight}"`);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});

it("keeps the dimensions and bytes of a PNG without orientation metadata", async () => {
const dir = tempDir();
try {
const framePath = join(dir, "frame.png");
const buffer = await sharp({
create: { width: 160, height: 80, channels: 3, background: "red" },
})
.png()
.toBuffer();
writeFileSync(framePath, buffer);

expect(await loadReferenceFrame(framePath)).toEqual({
buffer,
width: 160,
height: 80,
stagedName: "frame.png",
});
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
});

describe("prepareGradeCompareTempProject", () => {
it("copies the frame and LUTs into a temp project and rewrites LUT src values", async () => {
const dir = tempDir();
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/commands/grade-compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ async function extractVideoFrameToBuffer(videoPath: string): Promise<Buffer | nu
}
}

async function loadReferenceFrame(framePath: string): Promise<ReferenceFrame> {
export async function loadReferenceFrame(framePath: string): Promise<ReferenceFrame> {
if (!existsSync(framePath)) {
throw new Error(`Reference frame not found: ${framePath}`);
}
Expand All @@ -528,15 +528,15 @@ async function loadReferenceFrame(framePath: string): Promise<ReferenceFrame> {
}

const sharp = await loadSharp("grade-compare");
const metadata = await sharp(buffer).metadata();
if (!metadata.width || !metadata.height) {
const { width, height } = (await sharp(buffer).metadata()).autoOrient;
if (!width || !height) {
throw new Error(`Could not read reference frame dimensions: ${framePath}`);
}

return {
buffer,
width: metadata.width,
height: metadata.height,
width,
height,
stagedName: isVideoPath(framePath) ? "frame.png" : frameFileNameForPath(framePath),
};
}
Expand Down
Loading