mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: separate audio from elements
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { transformProjectV19ToV20 } from "../transformers/v19-to-v20";
|
||||
|
||||
describe("V19 to V20 Migration", () => {
|
||||
test("backfills source audio enabled state on video elements", () => {
|
||||
const result = transformProjectV19ToV20({
|
||||
project: {
|
||||
id: "project-v19-source-audio",
|
||||
version: 19,
|
||||
metadata: {
|
||||
id: "project-v19-source-audio",
|
||||
name: "Project",
|
||||
createdAt: "2024-01-01T00:00:00.000Z",
|
||||
updatedAt: "2024-01-01T00:00:00.000Z",
|
||||
},
|
||||
settings: {
|
||||
fps: 30,
|
||||
canvasSize: { width: 1920, height: 1080 },
|
||||
background: { type: "color", color: "#000000" },
|
||||
},
|
||||
currentSceneId: "scene-main",
|
||||
scenes: [
|
||||
{
|
||||
id: "scene-main",
|
||||
name: "Main",
|
||||
isMain: true,
|
||||
bookmarks: [],
|
||||
createdAt: "2024-01-01T00:00:00.000Z",
|
||||
updatedAt: "2024-01-01T00:00:00.000Z",
|
||||
tracks: [
|
||||
{
|
||||
id: "track-video",
|
||||
type: "video",
|
||||
name: "Video",
|
||||
isMain: true,
|
||||
muted: false,
|
||||
hidden: false,
|
||||
elements: [
|
||||
{
|
||||
id: "video-1",
|
||||
type: "video",
|
||||
name: "Clip",
|
||||
mediaId: "media-1",
|
||||
duration: 5,
|
||||
startTime: 0,
|
||||
trimStart: 0,
|
||||
trimEnd: 0,
|
||||
transform: {
|
||||
position: { x: 0, y: 0 },
|
||||
scale: { x: 1, y: 1 },
|
||||
rotation: 0,
|
||||
},
|
||||
opacity: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "track-audio",
|
||||
type: "audio",
|
||||
name: "Audio",
|
||||
muted: false,
|
||||
elements: [
|
||||
{
|
||||
id: "audio-1",
|
||||
type: "audio",
|
||||
sourceType: "upload",
|
||||
mediaId: "media-audio-1",
|
||||
name: "Audio",
|
||||
duration: 5,
|
||||
startTime: 0,
|
||||
trimStart: 0,
|
||||
trimEnd: 0,
|
||||
volume: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.skipped).toBe(false);
|
||||
expect(result.project.version).toBe(20);
|
||||
expect(
|
||||
((result.project.scenes as Array<Record<string, unknown>>)[0]
|
||||
.tracks as Array<Record<string, unknown>>)[0].elements,
|
||||
).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "video-1",
|
||||
isSourceAudioEnabled: true,
|
||||
}),
|
||||
]);
|
||||
expect(
|
||||
(((result.project.scenes as Array<Record<string, unknown>>)[0]
|
||||
.tracks as Array<Record<string, unknown>>)[1]
|
||||
.elements as Array<Record<string, unknown>>)[0].isSourceAudioEnabled,
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
test("preserves existing explicit source audio state", () => {
|
||||
const result = transformProjectV19ToV20({
|
||||
project: {
|
||||
id: "project-v19-existing-state",
|
||||
version: 19,
|
||||
scenes: [
|
||||
{
|
||||
tracks: [
|
||||
{
|
||||
elements: [
|
||||
{
|
||||
type: "video",
|
||||
isSourceAudioEnabled: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
(((result.project.scenes as Array<Record<string, unknown>>)[0]
|
||||
.tracks as Array<Record<string, unknown>>)[0]
|
||||
.elements as Array<Record<string, unknown>>)[0].isSourceAudioEnabled,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test("skips projects already on v20", () => {
|
||||
const result = transformProjectV19ToV20({
|
||||
project: {
|
||||
id: "project-v20",
|
||||
version: 20,
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.skipped).toBe(true);
|
||||
expect(result.reason).toBe("already v20");
|
||||
});
|
||||
});
|
||||
@@ -18,10 +18,11 @@ import { V15toV16Migration } from "./v15-to-v16";
|
||||
import { V16toV17Migration } from "./v16-to-v17";
|
||||
import { V17toV18Migration } from "./v17-to-v18";
|
||||
import { V18toV19Migration } from "./v18-to-v19";
|
||||
import { V19toV20Migration } from "./v19-to-v20";
|
||||
export { runStorageMigrations } from "./runner";
|
||||
export type { MigrationProgress } from "./runner";
|
||||
|
||||
export const CURRENT_PROJECT_VERSION = 19;
|
||||
export const CURRENT_PROJECT_VERSION = 20;
|
||||
|
||||
export const migrations = [
|
||||
new V0toV1Migration(),
|
||||
@@ -43,4 +44,5 @@ export const migrations = [
|
||||
new V16toV17Migration(),
|
||||
new V17toV18Migration(),
|
||||
new V18toV19Migration(),
|
||||
new V19toV20Migration(),
|
||||
];
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { MigrationResult, ProjectRecord } from "./types";
|
||||
import { getProjectId, isRecord } from "./utils";
|
||||
|
||||
export function transformProjectV19ToV20({
|
||||
project,
|
||||
}: {
|
||||
project: ProjectRecord;
|
||||
}): MigrationResult<ProjectRecord> {
|
||||
if (!getProjectId({ project })) {
|
||||
return { project, skipped: true, reason: "no project id" };
|
||||
}
|
||||
|
||||
if (typeof project.version === "number" && project.version >= 20) {
|
||||
return { project, skipped: true, reason: "already v20" };
|
||||
}
|
||||
|
||||
return {
|
||||
project: {
|
||||
...migrateVideoSourceAudioState({ project }),
|
||||
version: 20,
|
||||
},
|
||||
skipped: false,
|
||||
};
|
||||
}
|
||||
|
||||
function migrateVideoSourceAudioState({
|
||||
project,
|
||||
}: {
|
||||
project: ProjectRecord;
|
||||
}): ProjectRecord {
|
||||
if (!Array.isArray(project.scenes)) {
|
||||
return project;
|
||||
}
|
||||
|
||||
return {
|
||||
...project,
|
||||
scenes: project.scenes.map((scene) => migrateScene({ scene })),
|
||||
};
|
||||
}
|
||||
|
||||
function migrateScene({
|
||||
scene,
|
||||
}: {
|
||||
scene: unknown;
|
||||
}): unknown {
|
||||
if (!isRecord(scene) || !Array.isArray(scene.tracks)) {
|
||||
return scene;
|
||||
}
|
||||
|
||||
return {
|
||||
...scene,
|
||||
tracks: scene.tracks.map((track) => migrateTrack({ track })),
|
||||
};
|
||||
}
|
||||
|
||||
function migrateTrack({
|
||||
track,
|
||||
}: {
|
||||
track: unknown;
|
||||
}): unknown {
|
||||
if (!isRecord(track) || !Array.isArray(track.elements)) {
|
||||
return track;
|
||||
}
|
||||
|
||||
return {
|
||||
...track,
|
||||
elements: track.elements.map((element) => migrateElement({ element })),
|
||||
};
|
||||
}
|
||||
|
||||
function migrateElement({
|
||||
element,
|
||||
}: {
|
||||
element: unknown;
|
||||
}): unknown {
|
||||
if (!isRecord(element) || element.type !== "video") {
|
||||
return element;
|
||||
}
|
||||
|
||||
return {
|
||||
...element,
|
||||
isSourceAudioEnabled:
|
||||
typeof element.isSourceAudioEnabled === "boolean"
|
||||
? element.isSourceAudioEnabled
|
||||
: true,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { StorageMigration } from "./base";
|
||||
import type { ProjectRecord } from "./transformers/types";
|
||||
import { transformProjectV19ToV20 } from "./transformers/v19-to-v20";
|
||||
|
||||
export class V19toV20Migration extends StorageMigration {
|
||||
from = 19;
|
||||
to = 20;
|
||||
|
||||
async transform(project: ProjectRecord): Promise<{
|
||||
project: ProjectRecord;
|
||||
skipped: boolean;
|
||||
reason?: string;
|
||||
}> {
|
||||
return transformProjectV19ToV20({ project });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user