summaryrefslogtreecommitdiff
path: root/apps/web/src/services/storage/migrations/transformers
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-15 04:45:15 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-15 04:45:15 +0200
commit6312fc5b078689bc82c52fda9b7f92ee0a21d311 (patch)
tree692f81fb70eef0f257d62268bd81b4b3b08dce3f /apps/web/src/services/storage/migrations/transformers
parent2da93aa98f1b2ee5d7fac81ad7daa0f44ff902d2 (diff)
downloadopencut-6312fc5b078689bc82c52fda9b7f92ee0a21d311.tar.gz
fix: repair scene tracks broken by v23-to-v24 migration
Diffstat (limited to 'apps/web/src/services/storage/migrations/transformers')
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v23-to-v24.ts17
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v25-to-v26.ts129
2 files changed, 142 insertions, 4 deletions
diff --git a/apps/web/src/services/storage/migrations/transformers/v23-to-v24.ts b/apps/web/src/services/storage/migrations/transformers/v23-to-v24.ts
index 710d4dc1..736ca8dd 100644
--- a/apps/web/src/services/storage/migrations/transformers/v23-to-v24.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v23-to-v24.ts
@@ -51,9 +51,7 @@ function migrateScene({ scene }: { scene: unknown }): unknown {
}
const mainTrack = findMainTrack({ tracks: scene.tracks });
- if (!mainTrack) {
- return scene;
- }
+ const finalMainTrack = mainTrack ?? buildEmptyMainTrack();
return {
...scene,
@@ -65,7 +63,7 @@ function migrateScene({ scene }: { scene: unknown }): unknown {
(track): track is ProjectRecord =>
isRecord(track) && track.type !== "audio",
),
- main: migrateTrack({ track: mainTrack }),
+ main: migrateTrack({ track: finalMainTrack }),
audio: scene.tracks
.map((track) => migrateTrack({ track }))
.filter(
@@ -76,6 +74,17 @@ function migrateScene({ scene }: { scene: unknown }): unknown {
};
}
+function buildEmptyMainTrack(): ProjectRecord {
+ return {
+ id: crypto.randomUUID(),
+ name: "Main",
+ type: "video",
+ elements: [],
+ muted: false,
+ hidden: false,
+ };
+}
+
function findMainTrack({
tracks,
}: {
diff --git a/apps/web/src/services/storage/migrations/transformers/v25-to-v26.ts b/apps/web/src/services/storage/migrations/transformers/v25-to-v26.ts
new file mode 100644
index 00000000..f00383e6
--- /dev/null
+++ b/apps/web/src/services/storage/migrations/transformers/v25-to-v26.ts
@@ -0,0 +1,129 @@
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV25ToV26({
+ 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 >= 26) {
+ return { project, skipped: true, reason: "already v26" };
+ }
+ if (version !== 25) {
+ return { project, skipped: true, reason: "not v25" };
+ }
+
+ return {
+ project: {
+ ...migrateProject({ project }),
+ version: 26,
+ },
+ 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)) {
+ return scene;
+ }
+
+ const tracks = scene.tracks;
+
+ // Already in the correct shape — nothing to repair
+ if (isRecord(tracks) && Array.isArray(tracks.overlay) && Array.isArray(tracks.audio)) {
+ return scene;
+ }
+
+ // Reconstruct the flat track array from whatever broken state it's in.
+ // v24-to-v25 spread a flat array into a numeric-keyed object, so
+ // Object.values recovers the original elements. A true flat array is
+ // also handled for safety.
+ let trackArray: unknown[];
+ if (Array.isArray(tracks)) {
+ trackArray = tracks;
+ } else if (isRecord(tracks)) {
+ trackArray = Object.values(tracks);
+ } else {
+ trackArray = [];
+ }
+
+ const mainTrack = findMainTrack({ tracks: trackArray });
+ const finalMainTrack = mainTrack ?? buildEmptyMainTrack();
+
+ return {
+ ...scene,
+ tracks: {
+ main: migrateTrack({ track: finalMainTrack }),
+ overlay: trackArray
+ .filter((track) => track !== mainTrack)
+ .map((track) => migrateTrack({ track }))
+ .filter(
+ (track): track is ProjectRecord =>
+ isRecord(track) && track.type !== "audio",
+ ),
+ audio: trackArray
+ .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;
+}
+
+function buildEmptyMainTrack(): ProjectRecord {
+ return {
+ id: crypto.randomUUID(),
+ name: "Main",
+ type: "video",
+ elements: [],
+ muted: false,
+ hidden: false,
+ };
+}