Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 4 additions & 11 deletions packages/cli/src/commands/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const examples: Example[] = [
["Output as JSON", "hyperframes info --json"],
];
import { join } from "node:path";
import { parseHtml } from "@hyperframes/core";
import { parseHtml, CANVAS_DIMENSIONS } from "@hyperframes/core";
import { c } from "../ui/colors.js";
import { formatBytes, label } from "../ui/format.js";
import { ensureDOMParser } from "../utils/dom.js";
Expand Down Expand Up @@ -52,16 +52,9 @@ export default defineCommand({
const heightMatch =
html.match(/data-composition-id[^>]*data-height=["'](\d+)["']/) ||
html.match(/data-height=["'](\d+)["'][^>]*data-composition-id/);
const width = widthMatch?.[1]
? parseInt(widthMatch[1], 10)
: parsed.resolution === "portrait"
? 1080
: 1920;
const height = heightMatch?.[1]
? parseInt(heightMatch[1], 10)
: parsed.resolution === "portrait"
? 1920
: 1080;
const fallback = CANVAS_DIMENSIONS[parsed.resolution];
const width = widthMatch?.[1] ? parseInt(widthMatch[1], 10) : fallback.width;
const height = heightMatch?.[1] ? parseInt(heightMatch[1], 10) : fallback.height;
const resolution = `${width}x${height}`;
const size = totalSize(project.dir);

Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/core.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ export interface Asset {
export type TimelineElementType = "video" | "image" | "text" | "audio" | "composition";
export type MediaElementType = "video" | "image" | "audio";

export type CanvasResolution = "landscape" | "portrait";
export type CanvasResolution = "landscape" | "portrait" | "landscape-4k" | "portrait-4k";

export const CANVAS_DIMENSIONS = {
landscape: { width: 1920, height: 1080 },
portrait: { width: 1080, height: 1920 },
"landscape-4k": { width: 3840, height: 2160 },
"portrait-4k": { width: 2160, height: 3840 },
} as const;

export interface TimelineElementBase {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ describe("@hyperframes/core public API exports", () => {
expect(core.CANVAS_DIMENSIONS).toBeDefined();
expect(core.CANVAS_DIMENSIONS.landscape).toEqual({ width: 1920, height: 1080 });
expect(core.CANVAS_DIMENSIONS.portrait).toEqual({ width: 1080, height: 1920 });
expect(core.CANVAS_DIMENSIONS["landscape-4k"]).toEqual({ width: 3840, height: 2160 });
expect(core.CANVAS_DIMENSIONS["portrait-4k"]).toEqual({ width: 2160, height: 3840 });
});

it("exports TIMELINE_COLORS", () => {
Expand Down
45 changes: 45 additions & 0 deletions packages/core/src/parsers/htmlParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,51 @@ describe("parseHtml", () => {
expect(result.resolution).toBe("portrait");
});

it("detects landscape-4k resolution from data attribute", () => {
const html = `
<html data-resolution="landscape-4k">
<body>
<div id="stage">
<div id="text1" data-start="0" data-end="5"><div>Hello</div></div>
</div>
</body>
</html>
`;
const result = parseHtml(html);

expect(result.resolution).toBe("landscape-4k");
});

it("infers landscape-4k from composition dimensions", () => {
const html = `
<html data-composition-width="3840" data-composition-height="2160">
<body>
<div id="stage">
<div id="text1" data-start="0" data-end="5"><div>Hello</div></div>
</div>
</body>
</html>
`;
const result = parseHtml(html);

expect(result.resolution).toBe("landscape-4k");
});

it("infers portrait-4k from inline stage style", () => {
const html = `
<html>
<body>
<div id="stage" style="width: 2160px; height: 3840px;">
<div id="text1" data-start="0" data-end="5"><div>Hello</div></div>
</div>
</body>
</html>
`;
const result = parseHtml(html);

expect(result.resolution).toBe("portrait-4k");
});

it("extracts x, y, scale, opacity from data attributes", () => {
const html = `
<html>
Expand Down
23 changes: 18 additions & 5 deletions packages/core/src/parsers/htmlParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function parseResolutionFromCss(doc: Document, cssText: string | null): CanvasRe
const w = parseInt(inlineStyle.width, 10);
const h = parseInt(inlineStyle.height, 10);
if (w && h) {
return w > h ? "landscape" : "portrait";
return resolveResolutionFromDimensions(w, h);
}
}
}
Expand All @@ -102,15 +102,15 @@ function parseResolutionFromCss(doc: Document, cssText: string | null): CanvasRe
if (stageMatch) {
const w = parseInt(stageMatch[1] ?? "", 10);
const h = parseInt(stageMatch[2] ?? "", 10);
return w > h ? "landscape" : "portrait";
return resolveResolutionFromDimensions(w, h);
}
const stageMatchReverse = cssText.match(
/#stage\s*\{[^}]*height:\s*(\d+)px[^}]*width:\s*(\d+)px[^}]*\}/,
);
if (stageMatchReverse) {
const h = parseInt(stageMatchReverse[1] ?? "", 10);
const w = parseInt(stageMatchReverse[2] ?? "", 10);
return w > h ? "landscape" : "portrait";
return resolveResolutionFromDimensions(w, h);
}
}

Expand All @@ -120,7 +120,12 @@ function parseResolutionFromCss(doc: Document, cssText: string | null): CanvasRe
function parseResolutionFromHtml(doc: Document): CanvasResolution | null {
const htmlEl = doc.documentElement;
const resolutionAttr = htmlEl.getAttribute("data-resolution");
if (resolutionAttr === "landscape" || resolutionAttr === "portrait") {
if (
resolutionAttr === "landscape" ||
resolutionAttr === "portrait" ||
resolutionAttr === "landscape-4k" ||
resolutionAttr === "portrait-4k"
) {
return resolutionAttr;
}

Expand All @@ -130,13 +135,21 @@ function parseResolutionFromHtml(doc: Document): CanvasResolution | null {
const width = parseInt(widthAttr, 10);
const height = parseInt(heightAttr, 10);
if (width && height) {
return width > height ? "landscape" : "portrait";
return resolveResolutionFromDimensions(width, height);
}
}

return null;
}

function resolveResolutionFromDimensions(width: number, height: number): CanvasResolution {
const isLandscape = width > height;
const longSide = Math.max(width, height);
const isUhd = longSide >= 2560;
if (isLandscape) return isUhd ? "landscape-4k" : "landscape";
return isUhd ? "portrait-4k" : "portrait";
}

export function parseHtml(html: string): ParsedHtml {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
Expand Down
Loading