feat(cli): init --keep-original copies a clip as is, without transcoding (#5612)

* feat(cli): init --keep-original copies a clip as is, without transcoding

`hyperframes init --keep-original` copies the --video file under its own
name and wires it into the project the way a web-safe clip is wired today:
no transcode and no prompt, interactive or not. Default behaviour is unchanged.

Theora leaves init's web-safe codecs and joins the preview proxy's
browser-hostile codecs, so init converts it and the preview proxy serves an
H.264 copy instead of a blank picture.

* fix(studio-server): keep the codec table's comment share at its base level
This commit is contained in:
Miguel Ángel
2026-10-14 18:19:17 +00:00
committed by GitHub
parent 14e2ccf3bb
commit d65eefad5e
7 changed files with 54 additions and 5 deletions
+1
View File
@@ -110,6 +110,7 @@ npx hyperframes init my-video --tailwind
| `--tailwind` | Add Tailwind CSS browser-runtime support to the scaffolded HTML |
| `--non-interactive` | Never prompt. For agents and CI. |
| `--skip-transcribe` | Skip the automatic Whisper transcription |
| `--keep-original` | Copy the `--video` file as is instead of converting a non-web codec to H.264 MP4 |
| `--model` | Whisper model for that transcription (for example `small.en`, `medium.en`, `large`) |
| `--language` | Language code for transcription (`en`, `es`, `ja`, …). Filters out speech in other languages. |
| `--skip-skills` | Currently ignored. Set `HYPERFRAMES_SKIP_SKILLS=1` to opt out in CI or tests. |
Binary file not shown.
Binary file not shown.
+41
View File
@@ -251,6 +251,47 @@ describe("hyperframes init flag rename", () => {
}
});
it("converts a Theora --video to H.264 MP4", () => {
const dir = mkdtempSync(join(tmpdir(), "hf-init-theora-"));
const target = join(dir, "proj");
const clip = join(dir, "clip.ogv");
copyFileSync(resolve(fileURLToPath(import.meta.url), "../__fixtures__/theora.ogv"), clip);
try {
const res = runInit([target, "--non-interactive", "--skip-transcribe", "--video", clip]);
expect(res.status).toBe(0);
const video = readFileSync(join(target, "index.html"), "utf-8").match(/<video\b[^>]*>/)?.[0];
expect(video).toContain('src="clip.mp4"');
expect(existsSync(join(target, "clip.ogv"))).toBe(false);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
it("--keep-original copies a non-web --video as is instead of converting it", () => {
const dir = mkdtempSync(join(tmpdir(), "hf-init-keep-"));
const target = join(dir, "proj");
const clip = join(dir, "clip.mov");
const fixture = resolve(fileURLToPath(import.meta.url), "../__fixtures__/mpeg4.mp4");
copyFileSync(fixture, clip);
try {
const res = runInit([
target,
"--non-interactive",
"--skip-transcribe",
"--keep-original",
"--video",
clip,
]);
expect(res.status).toBe(0);
const video = readFileSync(join(target, "index.html"), "utf-8").match(/<video\b[^>]*>/)?.[0];
expect(video).toContain('src="clip.mov"');
expect(readFileSync(join(target, "clip.mov"))).toEqual(readFileSync(fixture));
expect(existsSync(join(target, "clip.mp4"))).toBe(false);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
it("wires --audio into the composition without creating a video clip", () => {
const dir = mkdtempSync(join(tmpdir(), "hf-init-audio-test-"));
const target = join(dir, "proj");
+10 -4
View File
@@ -77,7 +77,7 @@ interface VideoMeta {
videoCodec: string;
}
const WEB_CODECS = new Set(["h264", "vp8", "vp9", "av1", "theora"]);
const WEB_CODECS = new Set(["h264", "vp8", "vp9", "av1"]);
const DEFAULT_META: VideoMeta = {
durationSeconds: 5,
@@ -394,6 +394,7 @@ async function handleVideoFile(
videoPath: string,
destDir: string,
interactive: boolean,
keepOriginal: boolean,
): Promise<{ meta: VideoMeta; localVideoName: string }> {
const probed = probeVideo(videoPath);
let meta: VideoMeta = { ...DEFAULT_META };
@@ -416,7 +417,7 @@ async function handleVideoFile(
}
// Check codec compatibility
if (probed && !isWebCompatible(probed.videoCodec)) {
if (probed && !keepOriginal && !isWebCompatible(probed.videoCodec)) {
if (interactive) {
clack.log.warn(
c.warn(`Video codec "${probed.videoCodec}" is not supported by web browsers.`),
@@ -717,6 +718,10 @@ export default defineCommand({
type: "boolean",
description: "Skip whisper transcription",
},
"keep-original": {
type: "boolean",
description: "Copy the --video file as is, without converting it to H.264 MP4",
},
model: {
type: "string",
description:
@@ -781,6 +786,7 @@ export default defineCommand({
const videoFlag = args.video;
const audioFlag = args.audio;
const skipTranscribe = args["skip-transcribe"] === true;
const keepOriginal = args["keep-original"] === true;
// Temporary measure while the skills.sh registry sync lags GitHub main: the
// `--skip-skills` FLAG is neutered so an agent (or user) that passes it can
// NOT dodge the GitHub skills freshness check. The "don't pass --skip-skills"
@@ -874,7 +880,7 @@ export default defineCommand({
// Handle video
if (videoPath) {
sourceFilePath = videoPath;
const result = await handleVideoFile(videoPath, destDir, false);
const result = await handleVideoFile(videoPath, destDir, false, keepOriginal);
localVideoName = result.localVideoName;
videoHasAudio = result.meta.hasAudio;
videoDuration = result.meta.durationSeconds;
@@ -1050,7 +1056,7 @@ export default defineCommand({
}
mkdirSync(destDir, { recursive: true });
sourceFilePath = videoPath;
const result = await handleVideoFile(videoPath, destDir, true);
const result = await handleVideoFile(videoPath, destDir, true, keepOriginal);
localVideoName = result.localVideoName;
videoHasAudio = result.meta.hasAudio;
videoDuration = result.meta.durationSeconds;
@@ -206,7 +206,7 @@ describe("shouldPrewarmProxy", () => {
return { codecName, browserHostile: true, representativeMime: null, hasAlpha: false };
}
it.each(["hevc", "prores"])(
it.each(["hevc", "prores", "theora"])(
"pre-warms %s, which has no cross-platform browser decode",
(codecName) => {
expect(shouldPrewarmProxy(facts(codecName))).toBe(true);
@@ -58,6 +58,7 @@ export const BROWSER_HOSTILE_CODECS: Record<string, BrowserHostileCodec> = {
// Windows/Linux and Firefox everywhere need the substitute.
hevc: { representativeMime: 'video/mp4; codecs="hvc1.1.6.L120.B0"', prewarm: true },
prores: { representativeMime: null, prewarm: true },
theora: { representativeMime: 'video/ogg; codecs="theora"', prewarm: true },
// Every mainstream engine decodes AV1 and VP9, so canPlayType keeps the
// original and only the rare browser that cannot pays for a transcode.
av1: { representativeMime: 'video/mp4; codecs="av01.0.08M.08"', prewarm: false },