mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
refactor: structured track layout per scene
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { TimelineTrack } from "@/lib/timeline";
|
||||
import type { SceneTracks, TimelineTrack } from "@/lib/timeline";
|
||||
import type { MediaAsset } from "@/lib/media/types";
|
||||
import { RootNode } from "./nodes/root-node";
|
||||
import { VideoNode } from "./nodes/video-node";
|
||||
@@ -12,7 +12,6 @@ import { EffectLayerNode } from "./nodes/effect-layer-node";
|
||||
import type { BaseNode } from "./nodes/base-node";
|
||||
import type { TBackground, TCanvasSize } from "@/lib/project/types";
|
||||
import { DEFAULT_BACKGROUND_BLUR_INTENSITY } from "@/lib/background/constants";
|
||||
import { isMainTrack } from "@/lib/timeline/placement";
|
||||
|
||||
const PREVIEW_MAX_IMAGE_SIZE = 2048;
|
||||
|
||||
@@ -209,7 +208,7 @@ function buildBlurBackgroundNodes({
|
||||
|
||||
export type BuildSceneParams = {
|
||||
canvasSize: TCanvasSize;
|
||||
tracks: TimelineTrack[];
|
||||
tracks: SceneTracks;
|
||||
mediaAssets: MediaAsset[];
|
||||
duration: number;
|
||||
background: TBackground;
|
||||
@@ -227,19 +226,12 @@ export function buildScene({
|
||||
const rootNode = new RootNode({ duration });
|
||||
const mediaMap = new Map(mediaAssets.map((m) => [m.id, m]));
|
||||
|
||||
const visibleTracks = tracks.filter(
|
||||
(track) => !("hidden" in track && track.hidden),
|
||||
);
|
||||
|
||||
const orderedTracksTopToBottom = [
|
||||
...visibleTracks.filter((track) => !isMainTrack(track)),
|
||||
...visibleTracks.filter((track) => isMainTrack(track)),
|
||||
const visibleTracks = [
|
||||
...tracks.overlay.filter((track) => !("hidden" in track && track.hidden)),
|
||||
...(!tracks.main.hidden ? [tracks.main] : []),
|
||||
];
|
||||
|
||||
const orderedTracksBottomToTop = orderedTracksTopToBottom.slice().reverse();
|
||||
const mainTrack = orderedTracksBottomToTop.find((track) =>
|
||||
isMainTrack(track),
|
||||
);
|
||||
const orderedTracksBottomToTop = visibleTracks.slice().reverse();
|
||||
const mainTrack = tracks.main.hidden ? undefined : tracks.main;
|
||||
|
||||
const allNodes = buildTrackNodes({
|
||||
tracks: orderedTracksBottomToTop,
|
||||
|
||||
@@ -22,10 +22,11 @@ import { V19toV20Migration } from "./v19-to-v20";
|
||||
import { V20toV21Migration } from "./v20-to-v21";
|
||||
import { V21toV22Migration } from "./v21-to-v22";
|
||||
import { V22toV23Migration } from "./v22-to-v23";
|
||||
import { V23toV24Migration } from "./v23-to-v24";
|
||||
export { runStorageMigrations } from "./runner";
|
||||
export type { MigrationProgress } from "./runner";
|
||||
|
||||
export const CURRENT_PROJECT_VERSION = 23;
|
||||
export const CURRENT_PROJECT_VERSION = 24;
|
||||
|
||||
export const migrations = [
|
||||
new V0toV1Migration(),
|
||||
@@ -51,4 +52,5 @@ export const migrations = [
|
||||
new V20toV21Migration(),
|
||||
new V21toV22Migration(),
|
||||
new V22toV23Migration(),
|
||||
new V23toV24Migration(),
|
||||
];
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import type { MigrationResult, ProjectRecord } from "./types";
|
||||
import { getProjectId, isRecord } from "./utils";
|
||||
|
||||
export function transformProjectV23ToV24({
|
||||
project,
|
||||
}: {
|
||||
project: ProjectRecord;
|
||||
}): MigrationResult<ProjectRecord> {
|
||||
if (!getProjectId({ project })) {
|
||||
return { project, skipped: true, reason: "no project id" };
|
||||
}
|
||||
|
||||
const version = project.version;
|
||||
if (typeof version !== "number") {
|
||||
return { project, skipped: true, reason: "invalid version" };
|
||||
}
|
||||
if (version >= 24) {
|
||||
return { project, skipped: true, reason: "already v24" };
|
||||
}
|
||||
if (version !== 23) {
|
||||
return { project, skipped: true, reason: "not v23" };
|
||||
}
|
||||
|
||||
return {
|
||||
project: {
|
||||
...migrateProject({ project }),
|
||||
version: 24,
|
||||
},
|
||||
skipped: false,
|
||||
};
|
||||
}
|
||||
|
||||
function migrateProject({
|
||||
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;
|
||||
}
|
||||
|
||||
const mainTrack = findMainTrack({ tracks: scene.tracks });
|
||||
if (!mainTrack) {
|
||||
return scene;
|
||||
}
|
||||
|
||||
return {
|
||||
...scene,
|
||||
tracks: {
|
||||
overlay: scene.tracks
|
||||
.filter((track) => track !== mainTrack)
|
||||
.map((track) => migrateTrack({ track }))
|
||||
.filter(
|
||||
(track): track is ProjectRecord =>
|
||||
isRecord(track) && track.type !== "audio",
|
||||
),
|
||||
main: migrateTrack({ track: mainTrack }),
|
||||
audio: scene.tracks
|
||||
.map((track) => migrateTrack({ track }))
|
||||
.filter(
|
||||
(track): track is ProjectRecord =>
|
||||
isRecord(track) && track.type === "audio",
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function findMainTrack({
|
||||
tracks,
|
||||
}: {
|
||||
tracks: unknown[];
|
||||
}): ProjectRecord | null {
|
||||
for (const track of tracks) {
|
||||
if (!isRecord(track)) {
|
||||
continue;
|
||||
}
|
||||
if (track.type === "video" && track.isMain === true) {
|
||||
return track;
|
||||
}
|
||||
}
|
||||
|
||||
for (const track of tracks) {
|
||||
if (!isRecord(track)) {
|
||||
continue;
|
||||
}
|
||||
if (track.type === "video") {
|
||||
return track;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function migrateTrack({ track }: { track: unknown }): unknown {
|
||||
if (!isRecord(track)) {
|
||||
return track;
|
||||
}
|
||||
|
||||
const nextTrack = { ...track };
|
||||
delete nextTrack.isMain;
|
||||
return nextTrack;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { StorageMigration } from "./base";
|
||||
import type { ProjectRecord } from "./transformers/types";
|
||||
import { transformProjectV23ToV24 } from "./transformers/v23-to-v24";
|
||||
|
||||
export class V23toV24Migration extends StorageMigration {
|
||||
from = 23;
|
||||
to = 24;
|
||||
|
||||
async transform(project: ProjectRecord): Promise<{
|
||||
project: ProjectRecord;
|
||||
skipped: boolean;
|
||||
reason?: string;
|
||||
}> {
|
||||
return transformProjectV23ToV24({ project });
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
migrations,
|
||||
runStorageMigrations,
|
||||
} from "@/services/storage/migrations";
|
||||
import type { Bookmark, TimelineTrack, TScene } from "@/lib/timeline";
|
||||
import type { Bookmark, SceneTracks, TScene } from "@/lib/timeline";
|
||||
|
||||
function normalizeBookmarks({ raw }: { raw: unknown }): Bookmark[] {
|
||||
if (!Array.isArray(raw)) return [];
|
||||
@@ -116,18 +116,18 @@ class StorageService {
|
||||
private stripAudioBuffers({
|
||||
tracks,
|
||||
}: {
|
||||
tracks: TimelineTrack[];
|
||||
}): TimelineTrack[] {
|
||||
return tracks.map((track) => {
|
||||
if (track.type !== "audio") return track;
|
||||
return {
|
||||
tracks: SceneTracks;
|
||||
}): SceneTracks {
|
||||
return {
|
||||
...tracks,
|
||||
audio: tracks.audio.map((track) => ({
|
||||
...track,
|
||||
elements: track.elements.map((element) => {
|
||||
const { buffer: _buffer, ...rest } = element;
|
||||
return rest;
|
||||
}),
|
||||
};
|
||||
});
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
async saveProject({ project }: { project: TProject }): Promise<void> {
|
||||
@@ -178,11 +178,7 @@ class StorageService {
|
||||
id: scene.id,
|
||||
name: scene.name,
|
||||
isMain: scene.isMain,
|
||||
tracks: (scene.tracks ?? []).map((track) =>
|
||||
track.type === "video"
|
||||
? { ...track, isMain: track.isMain ?? false } // legacy: isMain was optional
|
||||
: track,
|
||||
),
|
||||
tracks: scene.tracks,
|
||||
bookmarks: normalizeBookmarks({ raw: scene.bookmarks }),
|
||||
createdAt: new Date(scene.createdAt),
|
||||
updatedAt: new Date(scene.updatedAt),
|
||||
|
||||
Reference in New Issue
Block a user