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
34 changes: 33 additions & 1 deletion src/cli/exportProject.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { describe, expect, it, vi } from "vitest";
import {
normalizeProjectEditor,
PROJECT_VERSION,
type ProjectEditorState,
validateProjectData,
} from "@/components/video-editor/projectPersistence";
import { migrateProjectDataToAxcutDocument } from "@/lib/ai-edition/document/migrate";
import { isAxcutDocumentFile } from "@/lib/ai-edition/schema";
import { loadDocumentProject, type VideoProbe } from "./exportProject";
import { getEditorSettings } from "@/lib/ai-edition/store/editorSettings";
import { legacyProjectEditor, loadDocumentProject, type VideoProbe } from "./exportProject";

const SCREEN = "/recordings/screen.mp4";

Expand Down Expand Up @@ -52,6 +54,36 @@ describe("the project file shapes the CLI export reads", () => {
});
});

describe("legacyProjectEditor", () => {
it("keeps the render settings the normalizer does not know", () => {
const editor = {
cursorModel3d: true,
cursorClickSound: true,
cursorShow: false,
webcamBackgroundMode: "blur",
webcamBlurIntensity: 0.8,
audioGainDb: 6,
};
const document = migrateProjectDataToAxcutDocument({
...v2Project(),
editor: legacyProjectEditor(editor as Partial<ProjectEditorState>),
});
const settings = getEditorSettings(document);
expect(settings.cursor.model3d).toBe(true);
expect(settings.cursor.clickSound).toBe(true);
expect(settings.cursorShow).toBe(false);
expect(settings.webcamBackgroundMode).toBe("blur");
expect(settings.audioGainDb).toBe(6);
expect(document.legacyEditor).toMatchObject(editor);
});

it("still normalizes the keys it knows", () => {
expect(legacyProjectEditor({ aspectRatio: "nope" as never }).aspectRatio).toBe(
normalizeProjectEditor({}).aspectRatio,
);
});
});

describe("loadDocumentProject", () => {
it("reads the media, length and size off the document without probing", async () => {
const project = await loadDocumentProject(savedDocument(), noProbe);
Expand Down
11 changes: 11 additions & 0 deletions src/cli/exportProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ export interface ExportProject {
sourceDims: Dims;
}

/**
* A v2 project's editor as its migrated document keeps it: normalized, plus every key the
* normalizer does not know. The migration copies it into `legacyEditor`, where the export reads
* the render settings, so a key dropped here exported as its default.
*/
export function legacyProjectEditor(
editor: Partial<ProjectEditorState> | undefined,
): ProjectEditorState {
return { ...editor, ...normalizeProjectEditor(editor ?? {}) };
}

/**
* A project the editor saved: the AxcutDocument itself, at any `schemaVersion`, upgraded and
* validated as the editor's "Browse files…" does. The main process has already relinked moved
Expand Down
5 changes: 2 additions & 3 deletions src/cli/loadCliProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import {
type EditorProjectData,
normalizeProjectEditor,
resolveProjectMedia,
toFileUrl,
validateProjectData,
Expand All @@ -13,7 +12,7 @@ import { migrateProjectDataToAxcutDocument } from "@/lib/ai-edition/document/mig
import { applyProbedDuration } from "@/lib/ai-edition/document/timeline";
import { isAxcutDocumentFile } from "@/lib/ai-edition/schema";
import { nativeBridgeClient } from "@/native";
import { type ExportProject, loadDocumentProject } from "./exportProject";
import { type ExportProject, legacyProjectEditor, loadDocumentProject } from "./exportProject";

export function probeVideoDimensions(
url: string,
Expand Down Expand Up @@ -70,7 +69,7 @@ async function loadLegacyProject(project: EditorProjectData): Promise<ExportProj
} catch {
// Fall back to the paths stored in the project file.
}
const editor = normalizeProjectEditor(project.editor ?? {});
const editor = legacyProjectEditor(project.editor);

const probed = await probeVideoDimensions(toFileUrl(media.screenVideoPath));

Expand Down
7 changes: 3 additions & 4 deletions src/components/video-editor/projectPersistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,9 @@ export interface ProjectEditorState {
gifLoop: boolean;
gifSizePreset: GifSizePreset;
cursorTheme: string;
// Optional: the headless CLI export reads a v2 project through
// normalizeProjectEditor and then copies the editor into legacyEditor, where
// getEditorSettings reads these keys. A key missing here is a key the export
// silently drops back to its default.
// Optional: the headless CLI export copies a v2 project's editor into legacyEditor,
// where getEditorSettings reads these keys. Keys missing here still reach it
// (`legacyProjectEditor`), only unvalidated.
cursorSize?: number;
cursorSmoothing?: number;
cursorMotionBlur?: number;
Expand Down
Loading