summaryrefslogtreecommitdiff
path: root/apps/web/src/services/storage/migrations/transformers
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/services/storage/migrations/transformers')
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v10-to-v11.ts226
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v11-to-v12.ts392
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v12-to-v13.ts204
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v13-to-v14.ts212
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts134
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v15-to-v16.ts120
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v16-to-v17.ts214
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts210
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v18-to-v19.ts92
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts378
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v4-to-v5.ts354
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v5-to-v6.ts176
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v6-to-v7.ts212
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v7-to-v8.ts192
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v8-to-v9.ts198
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v9-to-v10.ts42
16 files changed, 1678 insertions, 1678 deletions
diff --git a/apps/web/src/services/storage/migrations/transformers/v10-to-v11.ts b/apps/web/src/services/storage/migrations/transformers/v10-to-v11.ts
index b48f5e88..2f803d67 100644
--- a/apps/web/src/services/storage/migrations/transformers/v10-to-v11.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v10-to-v11.ts
@@ -1,113 +1,113 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV10ToV11({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 11) {
- return { project, skipped: true, reason: "already v11" };
- }
-
- const migratedProject = migrateProjectScale({ project });
-
- return {
- project: { ...migratedProject, version: 11 },
- skipped: false,
- };
-}
-
-function migrateProjectScale({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) return project;
-
- const migratedScenes = scenesValue.map((scene) =>
- migrateSceneScale({ scene }),
- );
-
- return { ...project, scenes: migratedScenes };
-}
-
-function migrateSceneScale({ scene }: { scene: unknown }): unknown {
- if (!isRecord(scene)) return scene;
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) return scene;
-
- const migratedTracks = tracksValue.map((track) =>
- migrateTrackScale({ track }),
- );
-
- return { ...scene, tracks: migratedTracks };
-}
-
-function migrateTrackScale({ track }: { track: unknown }): unknown {
- if (!isRecord(track)) return track;
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) return track;
-
- const migratedElements = elementsValue.map((element) =>
- migrateElementScale({ element }),
- );
-
- return { ...track, elements: migratedElements };
-}
-
-function migrateElementScale({ element }: { element: unknown }): unknown {
- if (!isRecord(element)) return element;
-
- const transform = element.transform;
- if (!isRecord(transform)) return element;
-
- const scale = transform.scale;
- if (typeof scale !== "number") return element;
-
- const migratedTransform = {
- ...transform,
- scaleX: scale,
- scaleY: scale,
- };
- delete (migratedTransform as Record<string, unknown>).scale;
-
- let migratedElement: ProjectRecord = {
- ...element,
- transform: migratedTransform,
- };
-
- const animations = element.animations;
- if (isRecord(animations) && isRecord(animations.channels)) {
- const channels = animations.channels as Record<string, unknown>;
- const scaleChannel = channels["transform.scale"];
- if (scaleChannel && isRecord(scaleChannel)) {
- const keyframes = (scaleChannel as { keyframes?: unknown[] }).keyframes;
- if (Array.isArray(keyframes)) {
- const newChannels = { ...channels };
- delete newChannels["transform.scale"];
- newChannels["transform.scaleX"] = {
- ...scaleChannel,
- keyframes: [...keyframes],
- };
- newChannels["transform.scaleY"] = {
- ...scaleChannel,
- keyframes: keyframes.map((kf) => (isRecord(kf) ? { ...kf } : kf)),
- };
- migratedElement = {
- ...migratedElement,
- animations: { ...animations, channels: newChannels },
- };
- }
- }
- }
-
- return migratedElement;
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV10ToV11({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 11) {
+ return { project, skipped: true, reason: "already v11" };
+ }
+
+ const migratedProject = migrateProjectScale({ project });
+
+ return {
+ project: { ...migratedProject, version: 11 },
+ skipped: false,
+ };
+}
+
+function migrateProjectScale({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) return project;
+
+ const migratedScenes = scenesValue.map((scene) =>
+ migrateSceneScale({ scene }),
+ );
+
+ return { ...project, scenes: migratedScenes };
+}
+
+function migrateSceneScale({ scene }: { scene: unknown }): unknown {
+ if (!isRecord(scene)) return scene;
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) return scene;
+
+ const migratedTracks = tracksValue.map((track) =>
+ migrateTrackScale({ track }),
+ );
+
+ return { ...scene, tracks: migratedTracks };
+}
+
+function migrateTrackScale({ track }: { track: unknown }): unknown {
+ if (!isRecord(track)) return track;
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) return track;
+
+ const migratedElements = elementsValue.map((element) =>
+ migrateElementScale({ element }),
+ );
+
+ return { ...track, elements: migratedElements };
+}
+
+function migrateElementScale({ element }: { element: unknown }): unknown {
+ if (!isRecord(element)) return element;
+
+ const transform = element.transform;
+ if (!isRecord(transform)) return element;
+
+ const scale = transform.scale;
+ if (typeof scale !== "number") return element;
+
+ const migratedTransform = {
+ ...transform,
+ scaleX: scale,
+ scaleY: scale,
+ };
+ delete (migratedTransform as Record<string, unknown>).scale;
+
+ let migratedElement: ProjectRecord = {
+ ...element,
+ transform: migratedTransform,
+ };
+
+ const animations = element.animations;
+ if (isRecord(animations) && isRecord(animations.channels)) {
+ const channels = animations.channels as Record<string, unknown>;
+ const scaleChannel = channels["transform.scale"];
+ if (scaleChannel && isRecord(scaleChannel)) {
+ const keyframes = (scaleChannel as { keyframes?: unknown[] }).keyframes;
+ if (Array.isArray(keyframes)) {
+ const newChannels = { ...channels };
+ delete newChannels["transform.scale"];
+ newChannels["transform.scaleX"] = {
+ ...scaleChannel,
+ keyframes: [...keyframes],
+ };
+ newChannels["transform.scaleY"] = {
+ ...scaleChannel,
+ keyframes: keyframes.map((kf) => (isRecord(kf) ? { ...kf } : kf)),
+ };
+ migratedElement = {
+ ...migratedElement,
+ animations: { ...animations, channels: newChannels },
+ };
+ }
+ }
+ }
+
+ return migratedElement;
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v11-to-v12.ts b/apps/web/src/services/storage/migrations/transformers/v11-to-v12.ts
index fa1bd371..4efc5b25 100644
--- a/apps/web/src/services/storage/migrations/transformers/v11-to-v12.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v11-to-v12.ts
@@ -1,196 +1,196 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV11ToV12({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 12) {
- return { project, skipped: true, reason: "already v12" };
- }
-
- const migratedProject = migrateProjectPosition({ project });
-
- return {
- project: { ...migratedProject, version: 12 },
- skipped: false,
- };
-}
-
-function migrateProjectPosition({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) return project;
-
- const migratedScenes = scenesValue.map((scene) =>
- migrateScenePosition({ scene }),
- );
-
- return { ...project, scenes: migratedScenes };
-}
-
-function migrateScenePosition({ scene }: { scene: unknown }): unknown {
- if (!isRecord(scene)) return scene;
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) return scene;
-
- const migratedTracks = tracksValue.map((track) =>
- migrateTrackPosition({ track }),
- );
-
- return { ...scene, tracks: migratedTracks };
-}
-
-function migrateTrackPosition({ track }: { track: unknown }): unknown {
- if (!isRecord(track)) return track;
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) return track;
-
- const migratedElements = elementsValue.map((element) =>
- migrateElementPosition({ element }),
- );
-
- return { ...track, elements: migratedElements };
-}
-
-function migrateElementPosition({ element }: { element: unknown }): unknown {
- if (!isRecord(element)) return element;
-
- const animations = element.animations;
- if (!isRecord(animations) || !isRecord(animations.channels)) return element;
-
- const channels = animations.channels as Record<string, unknown>;
- const xChannel = channels["transform.position.x"];
- const yChannel = channels["transform.position.y"];
-
- if (!xChannel && !yChannel) return element;
-
- const baseTransform = isRecord(element.transform) ? element.transform : {};
- const basePosition = isRecord(baseTransform.position)
- ? baseTransform.position
- : {};
- const baseX = typeof basePosition.x === "number" ? basePosition.x : 0;
- const baseY = typeof basePosition.y === "number" ? basePosition.y : 0;
-
- const xKeyframes = getKeyframes({ channel: xChannel });
- const yKeyframes = getKeyframes({ channel: yChannel });
-
- const allTimes = Array.from(
- new Set([
- ...xKeyframes.map((kf) => kf.time),
- ...yKeyframes.map((kf) => kf.time),
- ]),
- ).sort((a, b) => a - b);
-
- const vectorKeyframes = allTimes.map((time) => {
- const xKf = xKeyframes.find((kf) => Math.abs(kf.time - time) < 0.001);
- const yKf = yKeyframes.find((kf) => Math.abs(kf.time - time) < 0.001);
- const x =
- typeof xKf?.value === "number"
- ? xKf.value
- : interpolateScalarAtTime({
- keyframes: xKeyframes,
- time,
- fallback: baseX,
- });
- const y =
- typeof yKf?.value === "number"
- ? yKf.value
- : interpolateScalarAtTime({
- keyframes: yKeyframes,
- time,
- fallback: baseY,
- });
- const interpolation = xKf?.interpolation ?? yKf?.interpolation ?? "linear";
- return {
- id: xKf?.id ?? yKf?.id ?? crypto.randomUUID(),
- time,
- value: { x, y },
- interpolation,
- };
- });
-
- const newChannels = { ...channels };
- delete newChannels["transform.position.x"];
- delete newChannels["transform.position.y"];
- newChannels["transform.position"] = {
- valueKind: "vector",
- keyframes: vectorKeyframes,
- };
-
- return {
- ...element,
- animations: { ...animations, channels: newChannels },
- };
-}
-
-interface ScalarKeyframe {
- id: string;
- time: number;
- value: number;
- interpolation: string;
-}
-
-function getKeyframes({ channel }: { channel: unknown }): ScalarKeyframe[] {
- if (!isRecord(channel)) return [];
- if (!Array.isArray(channel.keyframes)) return [];
- return channel.keyframes.flatMap((kf) => {
- if (!isRecord(kf)) return [];
- if (
- typeof kf.id !== "string" ||
- typeof kf.time !== "number" ||
- typeof kf.value !== "number"
- )
- return [];
- return [
- {
- id: kf.id,
- time: kf.time,
- value: kf.value,
- interpolation:
- typeof kf.interpolation === "string" ? kf.interpolation : "linear",
- },
- ];
- });
-}
-
-function interpolateScalarAtTime({
- keyframes,
- time,
- fallback,
-}: {
- keyframes: ScalarKeyframe[];
- time: number;
- fallback: number;
-}): number {
- if (keyframes.length === 0) return fallback;
-
- const sorted = [...keyframes].sort((a, b) => a.time - b.time);
- const first = sorted[0];
- const last = sorted[sorted.length - 1];
-
- if (!first || !last) return fallback;
- if (time <= first.time) return first.value;
- if (time >= last.time) return last.value;
-
- for (let i = 0; i < sorted.length - 1; i++) {
- const left = sorted[i];
- const right = sorted[i + 1];
- if (time < left.time || time > right.time) continue;
- if (left.interpolation === "hold") return left.value;
- const t = (time - left.time) / (right.time - left.time);
- return left.value + (right.value - left.value) * t;
- }
-
- return last.value;
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV11ToV12({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 12) {
+ return { project, skipped: true, reason: "already v12" };
+ }
+
+ const migratedProject = migrateProjectPosition({ project });
+
+ return {
+ project: { ...migratedProject, version: 12 },
+ skipped: false,
+ };
+}
+
+function migrateProjectPosition({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) return project;
+
+ const migratedScenes = scenesValue.map((scene) =>
+ migrateScenePosition({ scene }),
+ );
+
+ return { ...project, scenes: migratedScenes };
+}
+
+function migrateScenePosition({ scene }: { scene: unknown }): unknown {
+ if (!isRecord(scene)) return scene;
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) return scene;
+
+ const migratedTracks = tracksValue.map((track) =>
+ migrateTrackPosition({ track }),
+ );
+
+ return { ...scene, tracks: migratedTracks };
+}
+
+function migrateTrackPosition({ track }: { track: unknown }): unknown {
+ if (!isRecord(track)) return track;
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) return track;
+
+ const migratedElements = elementsValue.map((element) =>
+ migrateElementPosition({ element }),
+ );
+
+ return { ...track, elements: migratedElements };
+}
+
+function migrateElementPosition({ element }: { element: unknown }): unknown {
+ if (!isRecord(element)) return element;
+
+ const animations = element.animations;
+ if (!isRecord(animations) || !isRecord(animations.channels)) return element;
+
+ const channels = animations.channels as Record<string, unknown>;
+ const xChannel = channels["transform.position.x"];
+ const yChannel = channels["transform.position.y"];
+
+ if (!xChannel && !yChannel) return element;
+
+ const baseTransform = isRecord(element.transform) ? element.transform : {};
+ const basePosition = isRecord(baseTransform.position)
+ ? baseTransform.position
+ : {};
+ const baseX = typeof basePosition.x === "number" ? basePosition.x : 0;
+ const baseY = typeof basePosition.y === "number" ? basePosition.y : 0;
+
+ const xKeyframes = getKeyframes({ channel: xChannel });
+ const yKeyframes = getKeyframes({ channel: yChannel });
+
+ const allTimes = Array.from(
+ new Set([
+ ...xKeyframes.map((kf) => kf.time),
+ ...yKeyframes.map((kf) => kf.time),
+ ]),
+ ).sort((a, b) => a - b);
+
+ const vectorKeyframes = allTimes.map((time) => {
+ const xKf = xKeyframes.find((kf) => Math.abs(kf.time - time) < 0.001);
+ const yKf = yKeyframes.find((kf) => Math.abs(kf.time - time) < 0.001);
+ const x =
+ typeof xKf?.value === "number"
+ ? xKf.value
+ : interpolateScalarAtTime({
+ keyframes: xKeyframes,
+ time,
+ fallback: baseX,
+ });
+ const y =
+ typeof yKf?.value === "number"
+ ? yKf.value
+ : interpolateScalarAtTime({
+ keyframes: yKeyframes,
+ time,
+ fallback: baseY,
+ });
+ const interpolation = xKf?.interpolation ?? yKf?.interpolation ?? "linear";
+ return {
+ id: xKf?.id ?? yKf?.id ?? crypto.randomUUID(),
+ time,
+ value: { x, y },
+ interpolation,
+ };
+ });
+
+ const newChannels = { ...channels };
+ delete newChannels["transform.position.x"];
+ delete newChannels["transform.position.y"];
+ newChannels["transform.position"] = {
+ valueKind: "vector",
+ keyframes: vectorKeyframes,
+ };
+
+ return {
+ ...element,
+ animations: { ...animations, channels: newChannels },
+ };
+}
+
+interface ScalarKeyframe {
+ id: string;
+ time: number;
+ value: number;
+ interpolation: string;
+}
+
+function getKeyframes({ channel }: { channel: unknown }): ScalarKeyframe[] {
+ if (!isRecord(channel)) return [];
+ if (!Array.isArray(channel.keyframes)) return [];
+ return channel.keyframes.flatMap((kf) => {
+ if (!isRecord(kf)) return [];
+ if (
+ typeof kf.id !== "string" ||
+ typeof kf.time !== "number" ||
+ typeof kf.value !== "number"
+ )
+ return [];
+ return [
+ {
+ id: kf.id,
+ time: kf.time,
+ value: kf.value,
+ interpolation:
+ typeof kf.interpolation === "string" ? kf.interpolation : "linear",
+ },
+ ];
+ });
+}
+
+function interpolateScalarAtTime({
+ keyframes,
+ time,
+ fallback,
+}: {
+ keyframes: ScalarKeyframe[];
+ time: number;
+ fallback: number;
+}): number {
+ if (keyframes.length === 0) return fallback;
+
+ const sorted = [...keyframes].sort((a, b) => a.time - b.time);
+ const first = sorted[0];
+ const last = sorted[sorted.length - 1];
+
+ if (!first || !last) return fallback;
+ if (time <= first.time) return first.value;
+ if (time >= last.time) return last.value;
+
+ for (let i = 0; i < sorted.length - 1; i++) {
+ const left = sorted[i];
+ const right = sorted[i + 1];
+ if (time < left.time || time > right.time) continue;
+ if (left.interpolation === "hold") return left.value;
+ const t = (time - left.time) / (right.time - left.time);
+ return left.value + (right.value - left.value) * t;
+ }
+
+ return last.value;
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v12-to-v13.ts b/apps/web/src/services/storage/migrations/transformers/v12-to-v13.ts
index 59b29732..518de59a 100644
--- a/apps/web/src/services/storage/migrations/transformers/v12-to-v13.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v12-to-v13.ts
@@ -1,102 +1,102 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV12ToV13({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 13) {
- return { project, skipped: true, reason: "already v13" };
- }
-
- const migratedProject = migrateProjectMasks({ project });
-
- return {
- project: { ...migratedProject, version: 13 },
- skipped: false,
- };
-}
-
-function migrateProjectMasks({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) return project;
-
- const migratedScenes = scenesValue.map((scene) =>
- migrateSceneMasks({ scene }),
- );
-
- return { ...project, scenes: migratedScenes };
-}
-
-function migrateSceneMasks({ scene }: { scene: unknown }): unknown {
- if (!isRecord(scene)) return scene;
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) return scene;
-
- const migratedTracks = tracksValue.map((track) =>
- migrateTrackMasks({ track }),
- );
-
- return { ...scene, tracks: migratedTracks };
-}
-
-function migrateTrackMasks({ track }: { track: unknown }): unknown {
- if (!isRecord(track)) return track;
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) return track;
-
- const migratedElements = elementsValue.map((element) =>
- migrateElementMasks({ element }),
- );
-
- return { ...track, elements: migratedElements };
-}
-
-function migrateElementMasks({ element }: { element: unknown }): unknown {
- if (!isRecord(element)) return element;
-
- const masksValue = element.masks;
- if (!Array.isArray(masksValue)) return element;
-
- const migratedMasks = masksValue.map((mask) => migrateMask({ mask }));
-
- return { ...element, masks: migratedMasks };
-}
-
-function migrateMask({ mask }: { mask: unknown }): unknown {
- if (!isRecord(mask)) return mask;
-
- const params = isRecord(mask.params) ? { ...mask.params } : {};
- const result = { ...mask };
-
- result.params = {
- ...params,
- feather: typeof mask.feather === "number" ? mask.feather : 0,
- inverted: typeof mask.inverted === "boolean" ? mask.inverted : false,
- strokeColor:
- isRecord(mask.stroke) && typeof mask.stroke.color === "string"
- ? mask.stroke.color
- : "#ffffff",
- strokeWidth:
- isRecord(mask.stroke) && typeof mask.stroke.width === "number"
- ? mask.stroke.width
- : 0,
- };
-
- delete (result as Record<string, unknown>).feather;
- delete (result as Record<string, unknown>).inverted;
- delete (result as Record<string, unknown>).stroke;
-
- return result;
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV12ToV13({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 13) {
+ return { project, skipped: true, reason: "already v13" };
+ }
+
+ const migratedProject = migrateProjectMasks({ project });
+
+ return {
+ project: { ...migratedProject, version: 13 },
+ skipped: false,
+ };
+}
+
+function migrateProjectMasks({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) return project;
+
+ const migratedScenes = scenesValue.map((scene) =>
+ migrateSceneMasks({ scene }),
+ );
+
+ return { ...project, scenes: migratedScenes };
+}
+
+function migrateSceneMasks({ scene }: { scene: unknown }): unknown {
+ if (!isRecord(scene)) return scene;
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) return scene;
+
+ const migratedTracks = tracksValue.map((track) =>
+ migrateTrackMasks({ track }),
+ );
+
+ return { ...scene, tracks: migratedTracks };
+}
+
+function migrateTrackMasks({ track }: { track: unknown }): unknown {
+ if (!isRecord(track)) return track;
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) return track;
+
+ const migratedElements = elementsValue.map((element) =>
+ migrateElementMasks({ element }),
+ );
+
+ return { ...track, elements: migratedElements };
+}
+
+function migrateElementMasks({ element }: { element: unknown }): unknown {
+ if (!isRecord(element)) return element;
+
+ const masksValue = element.masks;
+ if (!Array.isArray(masksValue)) return element;
+
+ const migratedMasks = masksValue.map((mask) => migrateMask({ mask }));
+
+ return { ...element, masks: migratedMasks };
+}
+
+function migrateMask({ mask }: { mask: unknown }): unknown {
+ if (!isRecord(mask)) return mask;
+
+ const params = isRecord(mask.params) ? { ...mask.params } : {};
+ const result = { ...mask };
+
+ result.params = {
+ ...params,
+ feather: typeof mask.feather === "number" ? mask.feather : 0,
+ inverted: typeof mask.inverted === "boolean" ? mask.inverted : false,
+ strokeColor:
+ isRecord(mask.stroke) && typeof mask.stroke.color === "string"
+ ? mask.stroke.color
+ : "#ffffff",
+ strokeWidth:
+ isRecord(mask.stroke) && typeof mask.stroke.width === "number"
+ ? mask.stroke.width
+ : 0,
+ };
+
+ delete (result as Record<string, unknown>).feather;
+ delete (result as Record<string, unknown>).inverted;
+ delete (result as Record<string, unknown>).stroke;
+
+ return result;
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v13-to-v14.ts b/apps/web/src/services/storage/migrations/transformers/v13-to-v14.ts
index 01a3cc3e..89dde38e 100644
--- a/apps/web/src/services/storage/migrations/transformers/v13-to-v14.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v13-to-v14.ts
@@ -1,106 +1,106 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV13ToV14({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 14) {
- return { project, skipped: true, reason: "already v14" };
- }
-
- const migratedProject = migrateProjectSplitMasks({ project });
-
- return {
- project: { ...migratedProject, version: 14 },
- skipped: false,
- };
-}
-
-function migrateProjectSplitMasks({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) return project;
-
- const migratedScenes = scenesValue.map((scene) =>
- migrateSceneSplitMasks({ scene }),
- );
-
- return { ...project, scenes: migratedScenes };
-}
-
-function migrateSceneSplitMasks({ scene }: { scene: unknown }): unknown {
- if (!isRecord(scene)) return scene;
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) return scene;
-
- const migratedTracks = tracksValue.map((track) =>
- migrateTrackSplitMasks({ track }),
- );
-
- return { ...scene, tracks: migratedTracks };
-}
-
-function migrateTrackSplitMasks({ track }: { track: unknown }): unknown {
- if (!isRecord(track)) return track;
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) return track;
-
- const migratedElements = elementsValue.map((element) =>
- migrateElementSplitMasks({ element }),
- );
-
- return { ...track, elements: migratedElements };
-}
-
-function migrateElementSplitMasks({ element }: { element: unknown }): unknown {
- if (!isRecord(element)) return element;
-
- const masksValue = element.masks;
- if (!Array.isArray(masksValue)) return element;
-
- const migratedMasks = masksValue.map((mask) => migrateSplitMask({ mask }));
-
- return { ...element, masks: migratedMasks };
-}
-
-/**
- * Converts split mask params from position-along-normal to explicit (x, y) anchor.
- *
- * Old: center = (cx + (position-0.5)*W*cos(rot), cy + (position-0.5)*W*sin(rot))
- * New: center = (cx + x*W, cy + y*W)
- *
- * Conversion: x = (position-0.5)*cos(rot), y = (position-0.5)*sin(rot)
- */
-function migrateSplitMask({ mask }: { mask: unknown }): unknown {
- if (!isRecord(mask)) return mask;
- if (mask.type !== "split") return mask;
-
- const params = isRecord(mask.params) ? mask.params : {};
- const position = typeof params.position === "number" ? params.position : 0.5;
- const rotation = typeof params.rotation === "number" ? params.rotation : 0;
-
- const angleRad = (rotation * Math.PI) / 180;
- const x = (position - 0.5) * Math.cos(angleRad);
- const y = (position - 0.5) * Math.sin(angleRad);
-
- const { position: _removed, ...restParams } = params as Record<
- string,
- unknown
- > & { position: unknown };
-
- return {
- ...mask,
- params: { ...restParams, x, y },
- };
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV13ToV14({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 14) {
+ return { project, skipped: true, reason: "already v14" };
+ }
+
+ const migratedProject = migrateProjectSplitMasks({ project });
+
+ return {
+ project: { ...migratedProject, version: 14 },
+ skipped: false,
+ };
+}
+
+function migrateProjectSplitMasks({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) return project;
+
+ const migratedScenes = scenesValue.map((scene) =>
+ migrateSceneSplitMasks({ scene }),
+ );
+
+ return { ...project, scenes: migratedScenes };
+}
+
+function migrateSceneSplitMasks({ scene }: { scene: unknown }): unknown {
+ if (!isRecord(scene)) return scene;
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) return scene;
+
+ const migratedTracks = tracksValue.map((track) =>
+ migrateTrackSplitMasks({ track }),
+ );
+
+ return { ...scene, tracks: migratedTracks };
+}
+
+function migrateTrackSplitMasks({ track }: { track: unknown }): unknown {
+ if (!isRecord(track)) return track;
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) return track;
+
+ const migratedElements = elementsValue.map((element) =>
+ migrateElementSplitMasks({ element }),
+ );
+
+ return { ...track, elements: migratedElements };
+}
+
+function migrateElementSplitMasks({ element }: { element: unknown }): unknown {
+ if (!isRecord(element)) return element;
+
+ const masksValue = element.masks;
+ if (!Array.isArray(masksValue)) return element;
+
+ const migratedMasks = masksValue.map((mask) => migrateSplitMask({ mask }));
+
+ return { ...element, masks: migratedMasks };
+}
+
+/**
+ * Converts split mask params from position-along-normal to explicit (x, y) anchor.
+ *
+ * Old: center = (cx + (position-0.5)*W*cos(rot), cy + (position-0.5)*W*sin(rot))
+ * New: center = (cx + x*W, cy + y*W)
+ *
+ * Conversion: x = (position-0.5)*cos(rot), y = (position-0.5)*sin(rot)
+ */
+function migrateSplitMask({ mask }: { mask: unknown }): unknown {
+ if (!isRecord(mask)) return mask;
+ if (mask.type !== "split") return mask;
+
+ const params = isRecord(mask.params) ? mask.params : {};
+ const position = typeof params.position === "number" ? params.position : 0.5;
+ const rotation = typeof params.rotation === "number" ? params.rotation : 0;
+
+ const angleRad = (rotation * Math.PI) / 180;
+ const x = (position - 0.5) * Math.cos(angleRad);
+ const y = (position - 0.5) * Math.sin(angleRad);
+
+ const { position: _removed, ...restParams } = params as Record<
+ string,
+ unknown
+ > & { position: unknown };
+
+ return {
+ ...mask,
+ params: { ...restParams, x, y },
+ };
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts b/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts
index beef05d4..e75c541b 100644
--- a/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v14-to-v15.ts
@@ -1,67 +1,67 @@
-import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/constants/sticker-constants";
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV14ToV15({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 15) {
- return { project, skipped: true, reason: "already v15" };
- }
-
- const migratedProject = backfillStickerIntrinsicDimensions({ project });
-
- return {
- project: { ...migratedProject, version: 15 },
- skipped: false,
- };
-}
-
-function backfillStickerIntrinsicDimensions({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) return project;
-
- const migratedScenes = scenesValue.map((scene) => {
- if (!isRecord(scene)) return scene;
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) return scene;
-
- const migratedTracks = tracksValue.map((track) => {
- if (!isRecord(track)) return track;
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) return track;
-
- const migratedElements = elementsValue.map((element) => {
- if (!isRecord(element)) return element;
- if (element.type !== "sticker") return element;
- if (
- typeof element.intrinsicWidth === "number" &&
- typeof element.intrinsicHeight === "number"
- ) {
- return element;
- }
- return {
- ...element,
- intrinsicWidth: STICKER_INTRINSIC_SIZE_FALLBACK,
- intrinsicHeight: STICKER_INTRINSIC_SIZE_FALLBACK,
- };
- });
-
- return { ...track, elements: migratedElements };
- });
-
- return { ...scene, tracks: migratedTracks };
- });
-
- return { ...project, scenes: migratedScenes };
-}
+import { STICKER_INTRINSIC_SIZE_FALLBACK } from "@/constants/sticker-constants";
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV14ToV15({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 15) {
+ return { project, skipped: true, reason: "already v15" };
+ }
+
+ const migratedProject = backfillStickerIntrinsicDimensions({ project });
+
+ return {
+ project: { ...migratedProject, version: 15 },
+ skipped: false,
+ };
+}
+
+function backfillStickerIntrinsicDimensions({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) return project;
+
+ const migratedScenes = scenesValue.map((scene) => {
+ if (!isRecord(scene)) return scene;
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) return scene;
+
+ const migratedTracks = tracksValue.map((track) => {
+ if (!isRecord(track)) return track;
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) return track;
+
+ const migratedElements = elementsValue.map((element) => {
+ if (!isRecord(element)) return element;
+ if (element.type !== "sticker") return element;
+ if (
+ typeof element.intrinsicWidth === "number" &&
+ typeof element.intrinsicHeight === "number"
+ ) {
+ return element;
+ }
+ return {
+ ...element,
+ intrinsicWidth: STICKER_INTRINSIC_SIZE_FALLBACK,
+ intrinsicHeight: STICKER_INTRINSIC_SIZE_FALLBACK,
+ };
+ });
+
+ return { ...track, elements: migratedElements };
+ });
+
+ return { ...scene, tracks: migratedTracks };
+ });
+
+ return { ...project, scenes: migratedScenes };
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v15-to-v16.ts b/apps/web/src/services/storage/migrations/transformers/v15-to-v16.ts
index 15e2f3bb..746938d0 100644
--- a/apps/web/src/services/storage/migrations/transformers/v15-to-v16.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v15-to-v16.ts
@@ -1,60 +1,60 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV15ToV16({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 16) {
- return { project, skipped: true, reason: "already v16" };
- }
-
- return {
- project: {
- ...renameStickerTracksToGraphic({ project }),
- version: 16,
- },
- skipped: false,
- };
-}
-
-function renameStickerTracksToGraphic({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) {
- return project;
- }
-
- const migratedScenes = scenesValue.map((scene) => {
- if (!isRecord(scene) || !Array.isArray(scene.tracks)) {
- return scene;
- }
-
- return {
- ...scene,
- tracks: scene.tracks.map((track) => {
- if (!isRecord(track) || track.type !== "sticker") {
- return track;
- }
-
- return {
- ...track,
- type: "graphic",
- };
- }),
- };
- });
-
- return {
- ...project,
- scenes: migratedScenes,
- };
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV15ToV16({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 16) {
+ return { project, skipped: true, reason: "already v16" };
+ }
+
+ return {
+ project: {
+ ...renameStickerTracksToGraphic({ project }),
+ version: 16,
+ },
+ skipped: false,
+ };
+}
+
+function renameStickerTracksToGraphic({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) {
+ return project;
+ }
+
+ const migratedScenes = scenesValue.map((scene) => {
+ if (!isRecord(scene) || !Array.isArray(scene.tracks)) {
+ return scene;
+ }
+
+ return {
+ ...scene,
+ tracks: scene.tracks.map((track) => {
+ if (!isRecord(track) || track.type !== "sticker") {
+ return track;
+ }
+
+ return {
+ ...track,
+ type: "graphic",
+ };
+ }),
+ };
+ });
+
+ return {
+ ...project,
+ scenes: migratedScenes,
+ };
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v16-to-v17.ts b/apps/web/src/services/storage/migrations/transformers/v16-to-v17.ts
index 7fec70e9..00474188 100644
--- a/apps/web/src/services/storage/migrations/transformers/v16-to-v17.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v16-to-v17.ts
@@ -1,107 +1,107 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV16ToV17({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 17) {
- return { project, skipped: true, reason: "already v17" };
- }
-
- return {
- project: {
- ...backfillMaskStrokeAlign({ project }),
- version: 17,
- },
- skipped: false,
- };
-}
-
-function backfillMaskStrokeAlign({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) {
- return project;
- }
-
- const migratedScenes = scenesValue.map((scene) => {
- if (!isRecord(scene)) {
- return scene;
- }
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) {
- return scene;
- }
-
- const migratedTracks = tracksValue.map((track) => {
- if (!isRecord(track)) {
- return track;
- }
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) {
- return track;
- }
-
- const migratedElements = elementsValue.map((element) => {
- if (!isRecord(element)) {
- return element;
- }
-
- const masksValue = element.masks;
- if (!Array.isArray(masksValue)) {
- return element;
- }
-
- const migratedMasks = masksValue.map((mask) => {
- if (!isRecord(mask)) {
- return mask;
- }
-
- const paramsValue = mask.params;
- if (!isRecord(paramsValue) || typeof paramsValue.strokeAlign === "string") {
- return mask;
- }
-
- return {
- ...mask,
- params: {
- ...paramsValue,
- strokeAlign: "center",
- },
- };
- });
-
- return {
- ...element,
- masks: migratedMasks,
- };
- });
-
- return {
- ...track,
- elements: migratedElements,
- };
- });
-
- return {
- ...scene,
- tracks: migratedTracks,
- };
- });
-
- return {
- ...project,
- scenes: migratedScenes,
- };
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV16ToV17({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 17) {
+ return { project, skipped: true, reason: "already v17" };
+ }
+
+ return {
+ project: {
+ ...backfillMaskStrokeAlign({ project }),
+ version: 17,
+ },
+ skipped: false,
+ };
+}
+
+function backfillMaskStrokeAlign({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) {
+ return project;
+ }
+
+ const migratedScenes = scenesValue.map((scene) => {
+ if (!isRecord(scene)) {
+ return scene;
+ }
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) {
+ return scene;
+ }
+
+ const migratedTracks = tracksValue.map((track) => {
+ if (!isRecord(track)) {
+ return track;
+ }
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) {
+ return track;
+ }
+
+ const migratedElements = elementsValue.map((element) => {
+ if (!isRecord(element)) {
+ return element;
+ }
+
+ const masksValue = element.masks;
+ if (!Array.isArray(masksValue)) {
+ return element;
+ }
+
+ const migratedMasks = masksValue.map((mask) => {
+ if (!isRecord(mask)) {
+ return mask;
+ }
+
+ const paramsValue = mask.params;
+ if (!isRecord(paramsValue) || typeof paramsValue.strokeAlign === "string") {
+ return mask;
+ }
+
+ return {
+ ...mask,
+ params: {
+ ...paramsValue,
+ strokeAlign: "center",
+ },
+ };
+ });
+
+ return {
+ ...element,
+ masks: migratedMasks,
+ };
+ });
+
+ return {
+ ...track,
+ elements: migratedElements,
+ };
+ });
+
+ return {
+ ...scene,
+ tracks: migratedTracks,
+ };
+ });
+
+ return {
+ ...project,
+ scenes: migratedScenes,
+ };
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts b/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts
index 31a60121..5dcf6feb 100644
--- a/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v17-to-v18.ts
@@ -1,105 +1,105 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { VOLUME_DB_MIN } from "@/lib/timeline/audio-constants";
-import { clampDb } from "@/lib/timeline/audio-state";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV17ToV18({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 18) {
- return { project, skipped: true, reason: "already v18" };
- }
-
- return {
- project: {
- ...migrateElementVolumes({ project }),
- version: 18,
- },
- skipped: false,
- };
-}
-
-function migrateElementVolumes({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) {
- return project;
- }
-
- const migratedScenes = scenesValue.map((scene) => {
- if (!isRecord(scene)) {
- return scene;
- }
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) {
- return scene;
- }
-
- const migratedTracks = tracksValue.map((track) => {
- if (!isRecord(track)) {
- return track;
- }
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) {
- return track;
- }
-
- const migratedElements = elementsValue.map((element) => {
- if (!isRecord(element)) {
- return element;
- }
-
- if (element.type !== "audio" && element.type !== "video") {
- return element;
- }
-
- const legacyVolume = element.volume;
- return {
- ...element,
- volume:
- typeof legacyVolume === "number"
- ? linearGainToDb(legacyVolume)
- : 0,
- };
- });
-
- return {
- ...track,
- elements: migratedElements,
- };
- });
-
- return {
- ...scene,
- tracks: migratedTracks,
- };
- });
-
- return {
- ...project,
- scenes: migratedScenes,
- };
-}
-
-function linearGainToDb(gain: number): number {
- if (!Number.isFinite(gain)) {
- return 0;
- }
-
- if (gain <= 0) {
- return VOLUME_DB_MIN;
- }
-
- return clampDb(20 * Math.log10(gain));
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { VOLUME_DB_MIN } from "@/lib/timeline/audio-constants";
+import { clampDb } from "@/lib/timeline/audio-state";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV17ToV18({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 18) {
+ return { project, skipped: true, reason: "already v18" };
+ }
+
+ return {
+ project: {
+ ...migrateElementVolumes({ project }),
+ version: 18,
+ },
+ skipped: false,
+ };
+}
+
+function migrateElementVolumes({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) {
+ return project;
+ }
+
+ const migratedScenes = scenesValue.map((scene) => {
+ if (!isRecord(scene)) {
+ return scene;
+ }
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) {
+ return scene;
+ }
+
+ const migratedTracks = tracksValue.map((track) => {
+ if (!isRecord(track)) {
+ return track;
+ }
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) {
+ return track;
+ }
+
+ const migratedElements = elementsValue.map((element) => {
+ if (!isRecord(element)) {
+ return element;
+ }
+
+ if (element.type !== "audio" && element.type !== "video") {
+ return element;
+ }
+
+ const legacyVolume = element.volume;
+ return {
+ ...element,
+ volume:
+ typeof legacyVolume === "number"
+ ? linearGainToDb(legacyVolume)
+ : 0,
+ };
+ });
+
+ return {
+ ...track,
+ elements: migratedElements,
+ };
+ });
+
+ return {
+ ...scene,
+ tracks: migratedTracks,
+ };
+ });
+
+ return {
+ ...project,
+ scenes: migratedScenes,
+ };
+}
+
+function linearGainToDb(gain: number): number {
+ if (!Number.isFinite(gain)) {
+ return 0;
+ }
+
+ if (gain <= 0) {
+ return VOLUME_DB_MIN;
+ }
+
+ return clampDb(20 * Math.log10(gain));
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v18-to-v19.ts b/apps/web/src/services/storage/migrations/transformers/v18-to-v19.ts
index 1946b829..3afa5203 100644
--- a/apps/web/src/services/storage/migrations/transformers/v18-to-v19.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v18-to-v19.ts
@@ -1,46 +1,46 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV18ToV19({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 19) {
- return { project, skipped: true, reason: "already v19" };
- }
-
- return {
- project: {
- ...migrateCanvasSizeSettings({ project }),
- version: 19,
- },
- skipped: false,
- };
-}
-
-function migrateCanvasSizeSettings({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const settingsValue = project.settings;
- if (!isRecord(settingsValue)) {
- return project;
- }
-
- const migratedSettings: Record<string, unknown> = {
- ...settingsValue,
- canvasSizeMode: "preset",
- lastCustomCanvasSize: null,
- };
-
- return {
- ...project,
- settings: migratedSettings,
- };
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV18ToV19({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 19) {
+ return { project, skipped: true, reason: "already v19" };
+ }
+
+ return {
+ project: {
+ ...migrateCanvasSizeSettings({ project }),
+ version: 19,
+ },
+ skipped: false,
+ };
+}
+
+function migrateCanvasSizeSettings({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const settingsValue = project.settings;
+ if (!isRecord(settingsValue)) {
+ return project;
+ }
+
+ const migratedSettings: Record<string, unknown> = {
+ ...settingsValue,
+ canvasSizeMode: "preset",
+ lastCustomCanvasSize: null,
+ };
+
+ return {
+ ...project,
+ settings: migratedSettings,
+ };
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts b/apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts
index d6e1efee..84d97fd8 100644
--- a/apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v3-to-v4.ts
@@ -1,189 +1,189 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-const LEGACY_FONT_WEIGHT_MAP = {
- normal: "400",
- bold: "700",
-} as const;
-
-const VALID_NUMERIC_FONT_WEIGHTS = new Set([
- "100",
- "200",
- "300",
- "400",
- "500",
- "600",
- "700",
- "800",
- "900",
-]);
-
-export function transformProjectV3ToV4({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- const projectId = getProjectId({ project });
- if (!projectId) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (isV4Project({ project })) {
- return { project, skipped: true, reason: "already v4" };
- }
-
- const migratedProject = normalizeProjectTextFontWeights({ project });
-
- return {
- project: {
- ...migratedProject,
- version: 4,
- },
- skipped: false,
- };
-}
-
-function normalizeProjectTextFontWeights({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) {
- return project;
- }
-
- let hasSceneChanges = false;
- const normalizedScenes = scenesValue.map((scene) => {
- const normalizedScene = normalizeSceneTextFontWeights({ scene });
- if (normalizedScene !== scene) {
- hasSceneChanges = true;
- }
- return normalizedScene;
- });
-
- if (!hasSceneChanges) {
- return project;
- }
-
- return {
- ...project,
- scenes: normalizedScenes,
- };
-}
-
-function normalizeSceneTextFontWeights({ scene }: { scene: unknown }): unknown {
- if (!isRecord(scene)) {
- return scene;
- }
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) {
- return scene;
- }
-
- let hasTrackChanges = false;
- const normalizedTracks = tracksValue.map((track) => {
- const normalizedTrack = normalizeTrackTextFontWeights({ track });
- if (normalizedTrack !== track) {
- hasTrackChanges = true;
- }
- return normalizedTrack;
- });
-
- if (!hasTrackChanges) {
- return scene;
- }
-
- return {
- ...scene,
- tracks: normalizedTracks,
- };
-}
-
-function normalizeTrackTextFontWeights({ track }: { track: unknown }): unknown {
- if (!isRecord(track)) {
- return track;
- }
-
- if (track.type !== "text") {
- return track;
- }
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) {
- return track;
- }
-
- let hasElementChanges = false;
- const normalizedElements = elementsValue.map((element) => {
- const normalizedElement = normalizeTextElementFontWeight({ element });
- if (normalizedElement !== element) {
- hasElementChanges = true;
- }
- return normalizedElement;
- });
-
- if (!hasElementChanges) {
- return track;
- }
-
- return {
- ...track,
- elements: normalizedElements,
- };
-}
-
-function normalizeTextElementFontWeight({
- element,
-}: {
- element: unknown;
-}): unknown {
- if (!isRecord(element) || element.type !== "text") {
- return element;
- }
-
- const normalizedWeight = normalizeFontWeight({ value: element.fontWeight });
- if (normalizedWeight === element.fontWeight) {
- return element;
- }
-
- return {
- ...element,
- fontWeight: normalizedWeight,
- };
-}
-
-function normalizeFontWeight({ value }: { value: unknown }): unknown {
- if (typeof value === "number") {
- const numericWeight = String(value);
- if (VALID_NUMERIC_FONT_WEIGHTS.has(numericWeight)) {
- return numericWeight;
- }
- return value;
- }
-
- if (typeof value !== "string") {
- return value;
- }
-
- const normalized = value.trim().toLowerCase();
- if (normalized in LEGACY_FONT_WEIGHT_MAP) {
- return LEGACY_FONT_WEIGHT_MAP[
- normalized as keyof typeof LEGACY_FONT_WEIGHT_MAP
- ];
- }
-
- if (VALID_NUMERIC_FONT_WEIGHTS.has(normalized)) {
- return normalized;
- }
-
- return value;
-}
-
-export { getProjectId } from "./utils";
-
-function isV4Project({ project }: { project: ProjectRecord }): boolean {
- const versionValue = project.version;
- return typeof versionValue === "number" && versionValue >= 4;
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+const LEGACY_FONT_WEIGHT_MAP = {
+ normal: "400",
+ bold: "700",
+} as const;
+
+const VALID_NUMERIC_FONT_WEIGHTS = new Set([
+ "100",
+ "200",
+ "300",
+ "400",
+ "500",
+ "600",
+ "700",
+ "800",
+ "900",
+]);
+
+export function transformProjectV3ToV4({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ const projectId = getProjectId({ project });
+ if (!projectId) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (isV4Project({ project })) {
+ return { project, skipped: true, reason: "already v4" };
+ }
+
+ const migratedProject = normalizeProjectTextFontWeights({ project });
+
+ return {
+ project: {
+ ...migratedProject,
+ version: 4,
+ },
+ skipped: false,
+ };
+}
+
+function normalizeProjectTextFontWeights({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) {
+ return project;
+ }
+
+ let hasSceneChanges = false;
+ const normalizedScenes = scenesValue.map((scene) => {
+ const normalizedScene = normalizeSceneTextFontWeights({ scene });
+ if (normalizedScene !== scene) {
+ hasSceneChanges = true;
+ }
+ return normalizedScene;
+ });
+
+ if (!hasSceneChanges) {
+ return project;
+ }
+
+ return {
+ ...project,
+ scenes: normalizedScenes,
+ };
+}
+
+function normalizeSceneTextFontWeights({ scene }: { scene: unknown }): unknown {
+ if (!isRecord(scene)) {
+ return scene;
+ }
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) {
+ return scene;
+ }
+
+ let hasTrackChanges = false;
+ const normalizedTracks = tracksValue.map((track) => {
+ const normalizedTrack = normalizeTrackTextFontWeights({ track });
+ if (normalizedTrack !== track) {
+ hasTrackChanges = true;
+ }
+ return normalizedTrack;
+ });
+
+ if (!hasTrackChanges) {
+ return scene;
+ }
+
+ return {
+ ...scene,
+ tracks: normalizedTracks,
+ };
+}
+
+function normalizeTrackTextFontWeights({ track }: { track: unknown }): unknown {
+ if (!isRecord(track)) {
+ return track;
+ }
+
+ if (track.type !== "text") {
+ return track;
+ }
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) {
+ return track;
+ }
+
+ let hasElementChanges = false;
+ const normalizedElements = elementsValue.map((element) => {
+ const normalizedElement = normalizeTextElementFontWeight({ element });
+ if (normalizedElement !== element) {
+ hasElementChanges = true;
+ }
+ return normalizedElement;
+ });
+
+ if (!hasElementChanges) {
+ return track;
+ }
+
+ return {
+ ...track,
+ elements: normalizedElements,
+ };
+}
+
+function normalizeTextElementFontWeight({
+ element,
+}: {
+ element: unknown;
+}): unknown {
+ if (!isRecord(element) || element.type !== "text") {
+ return element;
+ }
+
+ const normalizedWeight = normalizeFontWeight({ value: element.fontWeight });
+ if (normalizedWeight === element.fontWeight) {
+ return element;
+ }
+
+ return {
+ ...element,
+ fontWeight: normalizedWeight,
+ };
+}
+
+function normalizeFontWeight({ value }: { value: unknown }): unknown {
+ if (typeof value === "number") {
+ const numericWeight = String(value);
+ if (VALID_NUMERIC_FONT_WEIGHTS.has(numericWeight)) {
+ return numericWeight;
+ }
+ return value;
+ }
+
+ if (typeof value !== "string") {
+ return value;
+ }
+
+ const normalized = value.trim().toLowerCase();
+ if (normalized in LEGACY_FONT_WEIGHT_MAP) {
+ return LEGACY_FONT_WEIGHT_MAP[
+ normalized as keyof typeof LEGACY_FONT_WEIGHT_MAP
+ ];
+ }
+
+ if (VALID_NUMERIC_FONT_WEIGHTS.has(normalized)) {
+ return normalized;
+ }
+
+ return value;
+}
+
+export { getProjectId } from "./utils";
+
+function isV4Project({ project }: { project: ProjectRecord }): boolean {
+ const versionValue = project.version;
+ return typeof versionValue === "number" && versionValue >= 4;
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v4-to-v5.ts b/apps/web/src/services/storage/migrations/transformers/v4-to-v5.ts
index 971e3510..243b130c 100644
--- a/apps/web/src/services/storage/migrations/transformers/v4-to-v5.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v4-to-v5.ts
@@ -1,177 +1,177 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-const KNOWN_STICKER_PROVIDER_IDS = new Set([
- "icons",
- "emoji",
- "flags",
- "shapes",
-]);
-
-export function transformProjectV4ToV5({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- const projectId = getProjectId({ project });
- if (!projectId) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (isV5Project({ project })) {
- return { project, skipped: true, reason: "already v5" };
- }
-
- const migratedProject = migrateProjectStickerElements({ project });
-
- return {
- project: {
- ...migratedProject,
- version: 5,
- },
- skipped: false,
- };
-}
-
-function migrateProjectStickerElements({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) {
- return project;
- }
-
- let hasSceneChanges = false;
- const migratedScenes = scenesValue.map((scene) => {
- const migratedScene = migrateSceneStickerElements({ scene });
- if (migratedScene !== scene) {
- hasSceneChanges = true;
- }
- return migratedScene;
- });
-
- if (!hasSceneChanges) {
- return project;
- }
-
- return {
- ...project,
- scenes: migratedScenes,
- };
-}
-
-function migrateSceneStickerElements({ scene }: { scene: unknown }): unknown {
- if (!isRecord(scene)) {
- return scene;
- }
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) {
- return scene;
- }
-
- let hasTrackChanges = false;
- const migratedTracks = tracksValue.map((track) => {
- const migratedTrack = migrateTrackStickerElements({ track });
- if (migratedTrack !== track) {
- hasTrackChanges = true;
- }
- return migratedTrack;
- });
-
- if (!hasTrackChanges) {
- return scene;
- }
-
- return {
- ...scene,
- tracks: migratedTracks,
- };
-}
-
-function migrateTrackStickerElements({ track }: { track: unknown }): unknown {
- if (!isRecord(track)) {
- return track;
- }
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) {
- return track;
- }
-
- let hasElementChanges = false;
- const migratedElements = elementsValue.map((element) => {
- const migratedElement = migrateStickerElement({ element });
- if (migratedElement !== element) {
- hasElementChanges = true;
- }
- return migratedElement;
- });
-
- if (!hasElementChanges) {
- return track;
- }
-
- return {
- ...track,
- elements: migratedElements,
- };
-}
-
-function migrateStickerElement({ element }: { element: unknown }): unknown {
- if (!isRecord(element) || element.type !== "sticker") {
- return element;
- }
-
- const existingStickerId =
- typeof element.stickerId === "string" ? element.stickerId : null;
- const legacyIconName =
- typeof element.iconName === "string" ? element.iconName : null;
-
- const normalizedStickerId =
- normalizeStickerId({
- value: existingStickerId ?? legacyIconName,
- }) ?? null;
-
- const hasLegacyIconName = "iconName" in element;
- const hasLegacyColor = "color" in element;
- const shouldUpdateStickerId = normalizedStickerId !== existingStickerId;
-
- if (!hasLegacyIconName && !hasLegacyColor && !shouldUpdateStickerId) {
- return element;
- }
-
- const {
- iconName: _legacyIconName,
- color: _legacyColor,
- ...remaining
- } = element;
- return normalizedStickerId
- ? { ...remaining, stickerId: normalizedStickerId }
- : remaining;
-}
-
-function normalizeStickerId({ value }: { value: unknown }): string | null {
- if (typeof value !== "string" || value.length === 0) {
- return null;
- }
-
- const separatorIndex = value.indexOf(":");
- if (separatorIndex === -1) {
- return `icons:${value}`;
- }
-
- const maybeProvider = value.slice(0, separatorIndex);
- if (KNOWN_STICKER_PROVIDER_IDS.has(maybeProvider)) {
- return value;
- }
-
- return `icons:${value}`;
-}
-
-function isV5Project({ project }: { project: ProjectRecord }): boolean {
- const versionValue = project.version;
- return typeof versionValue === "number" && versionValue >= 5;
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+const KNOWN_STICKER_PROVIDER_IDS = new Set([
+ "icons",
+ "emoji",
+ "flags",
+ "shapes",
+]);
+
+export function transformProjectV4ToV5({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ const projectId = getProjectId({ project });
+ if (!projectId) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (isV5Project({ project })) {
+ return { project, skipped: true, reason: "already v5" };
+ }
+
+ const migratedProject = migrateProjectStickerElements({ project });
+
+ return {
+ project: {
+ ...migratedProject,
+ version: 5,
+ },
+ skipped: false,
+ };
+}
+
+function migrateProjectStickerElements({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) {
+ return project;
+ }
+
+ let hasSceneChanges = false;
+ const migratedScenes = scenesValue.map((scene) => {
+ const migratedScene = migrateSceneStickerElements({ scene });
+ if (migratedScene !== scene) {
+ hasSceneChanges = true;
+ }
+ return migratedScene;
+ });
+
+ if (!hasSceneChanges) {
+ return project;
+ }
+
+ return {
+ ...project,
+ scenes: migratedScenes,
+ };
+}
+
+function migrateSceneStickerElements({ scene }: { scene: unknown }): unknown {
+ if (!isRecord(scene)) {
+ return scene;
+ }
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) {
+ return scene;
+ }
+
+ let hasTrackChanges = false;
+ const migratedTracks = tracksValue.map((track) => {
+ const migratedTrack = migrateTrackStickerElements({ track });
+ if (migratedTrack !== track) {
+ hasTrackChanges = true;
+ }
+ return migratedTrack;
+ });
+
+ if (!hasTrackChanges) {
+ return scene;
+ }
+
+ return {
+ ...scene,
+ tracks: migratedTracks,
+ };
+}
+
+function migrateTrackStickerElements({ track }: { track: unknown }): unknown {
+ if (!isRecord(track)) {
+ return track;
+ }
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) {
+ return track;
+ }
+
+ let hasElementChanges = false;
+ const migratedElements = elementsValue.map((element) => {
+ const migratedElement = migrateStickerElement({ element });
+ if (migratedElement !== element) {
+ hasElementChanges = true;
+ }
+ return migratedElement;
+ });
+
+ if (!hasElementChanges) {
+ return track;
+ }
+
+ return {
+ ...track,
+ elements: migratedElements,
+ };
+}
+
+function migrateStickerElement({ element }: { element: unknown }): unknown {
+ if (!isRecord(element) || element.type !== "sticker") {
+ return element;
+ }
+
+ const existingStickerId =
+ typeof element.stickerId === "string" ? element.stickerId : null;
+ const legacyIconName =
+ typeof element.iconName === "string" ? element.iconName : null;
+
+ const normalizedStickerId =
+ normalizeStickerId({
+ value: existingStickerId ?? legacyIconName,
+ }) ?? null;
+
+ const hasLegacyIconName = "iconName" in element;
+ const hasLegacyColor = "color" in element;
+ const shouldUpdateStickerId = normalizedStickerId !== existingStickerId;
+
+ if (!hasLegacyIconName && !hasLegacyColor && !shouldUpdateStickerId) {
+ return element;
+ }
+
+ const {
+ iconName: _legacyIconName,
+ color: _legacyColor,
+ ...remaining
+ } = element;
+ return normalizedStickerId
+ ? { ...remaining, stickerId: normalizedStickerId }
+ : remaining;
+}
+
+function normalizeStickerId({ value }: { value: unknown }): string | null {
+ if (typeof value !== "string" || value.length === 0) {
+ return null;
+ }
+
+ const separatorIndex = value.indexOf(":");
+ if (separatorIndex === -1) {
+ return `icons:${value}`;
+ }
+
+ const maybeProvider = value.slice(0, separatorIndex);
+ if (KNOWN_STICKER_PROVIDER_IDS.has(maybeProvider)) {
+ return value;
+ }
+
+ return `icons:${value}`;
+}
+
+function isV5Project({ project }: { project: ProjectRecord }): boolean {
+ const versionValue = project.version;
+ return typeof versionValue === "number" && versionValue >= 5;
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v5-to-v6.ts b/apps/web/src/services/storage/migrations/transformers/v5-to-v6.ts
index ca855149..f3890f24 100644
--- a/apps/web/src/services/storage/migrations/transformers/v5-to-v6.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v5-to-v6.ts
@@ -1,88 +1,88 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV5ToV6({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- const projectId = getProjectId({ project });
- if (!projectId) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (isV6Project({ project })) {
- return { project, skipped: true, reason: "already v6" };
- }
-
- const migratedProject = migrateProjectBookmarks({ project });
-
- return {
- project: {
- ...migratedProject,
- version: 6,
- },
- skipped: false,
- };
-}
-
-function migrateProjectBookmarks({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) {
- return project;
- }
-
- let hasSceneChanges = false;
- const migratedScenes = scenesValue.map((scene) => {
- const migratedScene = migrateSceneBookmarks({ scene });
- if (migratedScene !== scene) {
- hasSceneChanges = true;
- }
- return migratedScene;
- });
-
- if (!hasSceneChanges) {
- return project;
- }
-
- return {
- ...project,
- scenes: migratedScenes,
- };
-}
-
-function migrateSceneBookmarks({ scene }: { scene: unknown }): unknown {
- if (!isRecord(scene)) {
- return scene;
- }
-
- const bookmarksValue = scene.bookmarks;
- if (!Array.isArray(bookmarksValue)) {
- return scene;
- }
-
- const needsMigration = bookmarksValue.some(
- (bookmark) => typeof bookmark === "number",
- );
- if (!needsMigration) {
- return scene;
- }
-
- const migratedBookmarks = bookmarksValue.map((bookmark) =>
- typeof bookmark === "number" ? { time: bookmark } : bookmark,
- );
-
- return {
- ...scene,
- bookmarks: migratedBookmarks,
- };
-}
-
-function isV6Project({ project }: { project: ProjectRecord }): boolean {
- const versionValue = project.version;
- return typeof versionValue === "number" && versionValue >= 6;
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV5ToV6({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ const projectId = getProjectId({ project });
+ if (!projectId) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (isV6Project({ project })) {
+ return { project, skipped: true, reason: "already v6" };
+ }
+
+ const migratedProject = migrateProjectBookmarks({ project });
+
+ return {
+ project: {
+ ...migratedProject,
+ version: 6,
+ },
+ skipped: false,
+ };
+}
+
+function migrateProjectBookmarks({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) {
+ return project;
+ }
+
+ let hasSceneChanges = false;
+ const migratedScenes = scenesValue.map((scene) => {
+ const migratedScene = migrateSceneBookmarks({ scene });
+ if (migratedScene !== scene) {
+ hasSceneChanges = true;
+ }
+ return migratedScene;
+ });
+
+ if (!hasSceneChanges) {
+ return project;
+ }
+
+ return {
+ ...project,
+ scenes: migratedScenes,
+ };
+}
+
+function migrateSceneBookmarks({ scene }: { scene: unknown }): unknown {
+ if (!isRecord(scene)) {
+ return scene;
+ }
+
+ const bookmarksValue = scene.bookmarks;
+ if (!Array.isArray(bookmarksValue)) {
+ return scene;
+ }
+
+ const needsMigration = bookmarksValue.some(
+ (bookmark) => typeof bookmark === "number",
+ );
+ if (!needsMigration) {
+ return scene;
+ }
+
+ const migratedBookmarks = bookmarksValue.map((bookmark) =>
+ typeof bookmark === "number" ? { time: bookmark } : bookmark,
+ );
+
+ return {
+ ...scene,
+ bookmarks: migratedBookmarks,
+ };
+}
+
+function isV6Project({ project }: { project: ProjectRecord }): boolean {
+ const versionValue = project.version;
+ return typeof versionValue === "number" && versionValue >= 6;
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v6-to-v7.ts b/apps/web/src/services/storage/migrations/transformers/v6-to-v7.ts
index 6f4848d4..fe3b75fd 100644
--- a/apps/web/src/services/storage/migrations/transformers/v6-to-v7.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v6-to-v7.ts
@@ -1,106 +1,106 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV6ToV7({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- const projectId = getProjectId({ project });
- if (!projectId) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (isV7Project({ project })) {
- return { project, skipped: true, reason: "already v7" };
- }
-
- const migratedProject = migrateProjectTextElements({ project });
-
- return {
- project: { ...migratedProject, version: 7 },
- skipped: false,
- };
-}
-
-function migrateProjectTextElements({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) return project;
-
- let hasChanges = false;
- const migratedScenes = scenesValue.map((scene) => {
- const migrated = migrateSceneTextElements({ scene });
- if (migrated !== scene) hasChanges = true;
- return migrated;
- });
-
- if (!hasChanges) return project;
- return { ...project, scenes: migratedScenes };
-}
-
-function migrateSceneTextElements({ scene }: { scene: unknown }): unknown {
- if (!isRecord(scene)) return scene;
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) return scene;
-
- let hasChanges = false;
- const migratedTracks = tracksValue.map((track) => {
- const migrated = migrateTrackTextElements({ track });
- if (migrated !== track) hasChanges = true;
- return migrated;
- });
-
- if (!hasChanges) return scene;
- return { ...scene, tracks: migratedTracks };
-}
-
-function migrateTrackTextElements({ track }: { track: unknown }): unknown {
- if (!isRecord(track)) return track;
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) return track;
-
- let hasChanges = false;
- const migratedElements = elementsValue.map((element) => {
- const migrated = migrateTextElement({ element });
- if (migrated !== element) hasChanges = true;
- return migrated;
- });
-
- if (!hasChanges) return track;
- return { ...track, elements: migratedElements };
-}
-
-function migrateTextElement({ element }: { element: unknown }): unknown {
- if (!isRecord(element)) return element;
- if (element.type !== "text") return element;
- if (isRecord(element.background)) return element;
-
- const backgroundColor =
- typeof element.backgroundColor === "string"
- ? element.backgroundColor
- : "transparent";
-
- const { backgroundColor: _removed, ...rest } = element;
-
- return {
- ...rest,
- background: {
- color: backgroundColor,
- cornerRadius: 0,
- paddingX: 8,
- paddingY: 4,
- offsetX: 0,
- offsetY: 0,
- },
- };
-}
-
-function isV7Project({ project }: { project: ProjectRecord }): boolean {
- return typeof project.version === "number" && project.version >= 7;
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV6ToV7({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ const projectId = getProjectId({ project });
+ if (!projectId) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (isV7Project({ project })) {
+ return { project, skipped: true, reason: "already v7" };
+ }
+
+ const migratedProject = migrateProjectTextElements({ project });
+
+ return {
+ project: { ...migratedProject, version: 7 },
+ skipped: false,
+ };
+}
+
+function migrateProjectTextElements({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) return project;
+
+ let hasChanges = false;
+ const migratedScenes = scenesValue.map((scene) => {
+ const migrated = migrateSceneTextElements({ scene });
+ if (migrated !== scene) hasChanges = true;
+ return migrated;
+ });
+
+ if (!hasChanges) return project;
+ return { ...project, scenes: migratedScenes };
+}
+
+function migrateSceneTextElements({ scene }: { scene: unknown }): unknown {
+ if (!isRecord(scene)) return scene;
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) return scene;
+
+ let hasChanges = false;
+ const migratedTracks = tracksValue.map((track) => {
+ const migrated = migrateTrackTextElements({ track });
+ if (migrated !== track) hasChanges = true;
+ return migrated;
+ });
+
+ if (!hasChanges) return scene;
+ return { ...scene, tracks: migratedTracks };
+}
+
+function migrateTrackTextElements({ track }: { track: unknown }): unknown {
+ if (!isRecord(track)) return track;
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) return track;
+
+ let hasChanges = false;
+ const migratedElements = elementsValue.map((element) => {
+ const migrated = migrateTextElement({ element });
+ if (migrated !== element) hasChanges = true;
+ return migrated;
+ });
+
+ if (!hasChanges) return track;
+ return { ...track, elements: migratedElements };
+}
+
+function migrateTextElement({ element }: { element: unknown }): unknown {
+ if (!isRecord(element)) return element;
+ if (element.type !== "text") return element;
+ if (isRecord(element.background)) return element;
+
+ const backgroundColor =
+ typeof element.backgroundColor === "string"
+ ? element.backgroundColor
+ : "transparent";
+
+ const { backgroundColor: _removed, ...rest } = element;
+
+ return {
+ ...rest,
+ background: {
+ color: backgroundColor,
+ cornerRadius: 0,
+ paddingX: 8,
+ paddingY: 4,
+ offsetX: 0,
+ offsetY: 0,
+ },
+ };
+}
+
+function isV7Project({ project }: { project: ProjectRecord }): boolean {
+ return typeof project.version === "number" && project.version >= 7;
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v7-to-v8.ts b/apps/web/src/services/storage/migrations/transformers/v7-to-v8.ts
index 7393f776..e592cde9 100644
--- a/apps/web/src/services/storage/migrations/transformers/v7-to-v8.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v7-to-v8.ts
@@ -1,96 +1,96 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV7ToV8({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- const projectId = getProjectId({ project });
- if (!projectId) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (isV8Project({ project })) {
- return { project, skipped: true, reason: "already v8" };
- }
-
- const migratedProject = migrateProjectElements({ project });
-
- return {
- project: { ...migratedProject, version: 8 },
- skipped: false,
- };
-}
-
-function migrateProjectElements({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) return project;
-
- let hasChanges = false;
- const migratedScenes = scenesValue.map((scene) => {
- const migrated = migrateSceneElements({ scene });
- if (migrated !== scene) hasChanges = true;
- return migrated;
- });
-
- if (!hasChanges) return project;
- return { ...project, scenes: migratedScenes };
-}
-
-function migrateSceneElements({ scene }: { scene: unknown }): unknown {
- if (!isRecord(scene)) return scene;
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) return scene;
-
- let hasChanges = false;
- const migratedTracks = tracksValue.map((track) => {
- const migrated = migrateTrackElements({ track });
- if (migrated !== track) hasChanges = true;
- return migrated;
- });
-
- if (!hasChanges) return scene;
- return { ...scene, tracks: migratedTracks };
-}
-
-function migrateTrackElements({ track }: { track: unknown }): unknown {
- if (!isRecord(track)) return track;
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) return track;
-
- let hasChanges = false;
- const migratedElements = elementsValue.map((element) => {
- const migrated = migrateElement({ element });
- if (migrated !== element) hasChanges = true;
- return migrated;
- });
-
- if (!hasChanges) return track;
- return { ...track, elements: migratedElements };
-}
-
-function migrateElement({ element }: { element: unknown }): unknown {
- if (!isRecord(element)) return element;
- if (element.type !== "video" && element.type !== "audio") return element;
- if (typeof element.sourceDuration === "number") return element;
-
- const trimStart = typeof element.trimStart === "number" ? element.trimStart : 0;
- const duration = typeof element.duration === "number" ? element.duration : 0;
- const trimEnd = typeof element.trimEnd === "number" ? element.trimEnd : 0;
-
- return {
- ...element,
- sourceDuration: trimStart + duration + trimEnd,
- };
-}
-
-function isV8Project({ project }: { project: ProjectRecord }): boolean {
- return typeof project.version === "number" && project.version >= 8;
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV7ToV8({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ const projectId = getProjectId({ project });
+ if (!projectId) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (isV8Project({ project })) {
+ return { project, skipped: true, reason: "already v8" };
+ }
+
+ const migratedProject = migrateProjectElements({ project });
+
+ return {
+ project: { ...migratedProject, version: 8 },
+ skipped: false,
+ };
+}
+
+function migrateProjectElements({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) return project;
+
+ let hasChanges = false;
+ const migratedScenes = scenesValue.map((scene) => {
+ const migrated = migrateSceneElements({ scene });
+ if (migrated !== scene) hasChanges = true;
+ return migrated;
+ });
+
+ if (!hasChanges) return project;
+ return { ...project, scenes: migratedScenes };
+}
+
+function migrateSceneElements({ scene }: { scene: unknown }): unknown {
+ if (!isRecord(scene)) return scene;
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) return scene;
+
+ let hasChanges = false;
+ const migratedTracks = tracksValue.map((track) => {
+ const migrated = migrateTrackElements({ track });
+ if (migrated !== track) hasChanges = true;
+ return migrated;
+ });
+
+ if (!hasChanges) return scene;
+ return { ...scene, tracks: migratedTracks };
+}
+
+function migrateTrackElements({ track }: { track: unknown }): unknown {
+ if (!isRecord(track)) return track;
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) return track;
+
+ let hasChanges = false;
+ const migratedElements = elementsValue.map((element) => {
+ const migrated = migrateElement({ element });
+ if (migrated !== element) hasChanges = true;
+ return migrated;
+ });
+
+ if (!hasChanges) return track;
+ return { ...track, elements: migratedElements };
+}
+
+function migrateElement({ element }: { element: unknown }): unknown {
+ if (!isRecord(element)) return element;
+ if (element.type !== "video" && element.type !== "audio") return element;
+ if (typeof element.sourceDuration === "number") return element;
+
+ const trimStart = typeof element.trimStart === "number" ? element.trimStart : 0;
+ const duration = typeof element.duration === "number" ? element.duration : 0;
+ const trimEnd = typeof element.trimEnd === "number" ? element.trimEnd : 0;
+
+ return {
+ ...element,
+ sourceDuration: trimStart + duration + trimEnd,
+ };
+}
+
+function isV8Project({ project }: { project: ProjectRecord }): boolean {
+ return typeof project.version === "number" && project.version >= 8;
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v8-to-v9.ts b/apps/web/src/services/storage/migrations/transformers/v8-to-v9.ts
index 1dc895bc..628eebb6 100644
--- a/apps/web/src/services/storage/migrations/transformers/v8-to-v9.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v8-to-v9.ts
@@ -1,99 +1,99 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId, isRecord } from "./utils";
-
-export function transformProjectV8ToV9({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- const projectId = getProjectId({ project });
- if (!projectId) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (isV9Project({ project })) {
- return { project, skipped: true, reason: "already v9" };
- }
-
- const migratedProject = migrateProjectTextElements({ project });
-
- return {
- project: { ...migratedProject, version: 9 },
- skipped: false,
- };
-}
-
-function migrateProjectTextElements({
- project,
-}: {
- project: ProjectRecord;
-}): ProjectRecord {
- const scenesValue = project.scenes;
- if (!Array.isArray(scenesValue)) return project;
-
- let hasChanges = false;
- const migratedScenes = scenesValue.map((scene) => {
- const migrated = migrateSceneTextElements({ scene });
- if (migrated !== scene) hasChanges = true;
- return migrated;
- });
-
- if (!hasChanges) return project;
- return { ...project, scenes: migratedScenes };
-}
-
-function migrateSceneTextElements({ scene }: { scene: unknown }): unknown {
- if (!isRecord(scene)) return scene;
-
- const tracksValue = scene.tracks;
- if (!Array.isArray(tracksValue)) return scene;
-
- let hasChanges = false;
- const migratedTracks = tracksValue.map((track) => {
- const migrated = migrateTrackTextElements({ track });
- if (migrated !== track) hasChanges = true;
- return migrated;
- });
-
- if (!hasChanges) return scene;
- return { ...scene, tracks: migratedTracks };
-}
-
-function migrateTrackTextElements({ track }: { track: unknown }): unknown {
- if (!isRecord(track)) return track;
- if (track.type !== "text") return track;
-
- const elementsValue = track.elements;
- if (!Array.isArray(elementsValue)) return track;
-
- let hasChanges = false;
- const migratedElements = elementsValue.map((element) => {
- const migrated = migrateTextElement({ element });
- if (migrated !== element) hasChanges = true;
- return migrated;
- });
-
- if (!hasChanges) return track;
- return { ...track, elements: migratedElements };
-}
-
-function migrateTextElement({ element }: { element: unknown }): unknown {
- if (!isRecord(element)) return element;
- if (element.type !== "text") return element;
-
- const bg = element.background;
- if (!isRecord(bg)) return element;
- if (typeof bg.enabled === "boolean") return element;
-
- const color = typeof bg.color === "string" ? bg.color : "transparent";
- const enabled = color !== "transparent";
-
- return {
- ...element,
- background: { ...bg, enabled },
- };
-}
-
-function isV9Project({ project }: { project: ProjectRecord }): boolean {
- return typeof project.version === "number" && project.version >= 9;
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId, isRecord } from "./utils";
+
+export function transformProjectV8ToV9({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ const projectId = getProjectId({ project });
+ if (!projectId) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (isV9Project({ project })) {
+ return { project, skipped: true, reason: "already v9" };
+ }
+
+ const migratedProject = migrateProjectTextElements({ project });
+
+ return {
+ project: { ...migratedProject, version: 9 },
+ skipped: false,
+ };
+}
+
+function migrateProjectTextElements({
+ project,
+}: {
+ project: ProjectRecord;
+}): ProjectRecord {
+ const scenesValue = project.scenes;
+ if (!Array.isArray(scenesValue)) return project;
+
+ let hasChanges = false;
+ const migratedScenes = scenesValue.map((scene) => {
+ const migrated = migrateSceneTextElements({ scene });
+ if (migrated !== scene) hasChanges = true;
+ return migrated;
+ });
+
+ if (!hasChanges) return project;
+ return { ...project, scenes: migratedScenes };
+}
+
+function migrateSceneTextElements({ scene }: { scene: unknown }): unknown {
+ if (!isRecord(scene)) return scene;
+
+ const tracksValue = scene.tracks;
+ if (!Array.isArray(tracksValue)) return scene;
+
+ let hasChanges = false;
+ const migratedTracks = tracksValue.map((track) => {
+ const migrated = migrateTrackTextElements({ track });
+ if (migrated !== track) hasChanges = true;
+ return migrated;
+ });
+
+ if (!hasChanges) return scene;
+ return { ...scene, tracks: migratedTracks };
+}
+
+function migrateTrackTextElements({ track }: { track: unknown }): unknown {
+ if (!isRecord(track)) return track;
+ if (track.type !== "text") return track;
+
+ const elementsValue = track.elements;
+ if (!Array.isArray(elementsValue)) return track;
+
+ let hasChanges = false;
+ const migratedElements = elementsValue.map((element) => {
+ const migrated = migrateTextElement({ element });
+ if (migrated !== element) hasChanges = true;
+ return migrated;
+ });
+
+ if (!hasChanges) return track;
+ return { ...track, elements: migratedElements };
+}
+
+function migrateTextElement({ element }: { element: unknown }): unknown {
+ if (!isRecord(element)) return element;
+ if (element.type !== "text") return element;
+
+ const bg = element.background;
+ if (!isRecord(bg)) return element;
+ if (typeof bg.enabled === "boolean") return element;
+
+ const color = typeof bg.color === "string" ? bg.color : "transparent";
+ const enabled = color !== "transparent";
+
+ return {
+ ...element,
+ background: { ...bg, enabled },
+ };
+}
+
+function isV9Project({ project }: { project: ProjectRecord }): boolean {
+ return typeof project.version === "number" && project.version >= 9;
+}
diff --git a/apps/web/src/services/storage/migrations/transformers/v9-to-v10.ts b/apps/web/src/services/storage/migrations/transformers/v9-to-v10.ts
index 52d13295..18885b62 100644
--- a/apps/web/src/services/storage/migrations/transformers/v9-to-v10.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v9-to-v10.ts
@@ -1,21 +1,21 @@
-import type { MigrationResult, ProjectRecord } from "./types";
-import { getProjectId } from "./utils";
-
-export function transformProjectV9ToV10({
- project,
-}: {
- project: ProjectRecord;
-}): MigrationResult<ProjectRecord> {
- if (!getProjectId({ project })) {
- return { project, skipped: true, reason: "no project id" };
- }
-
- if (typeof project.version === "number" && project.version >= 10) {
- return { project, skipped: true, reason: "already v10" };
- }
-
- return {
- project: { ...project, version: 10 },
- skipped: false,
- };
-}
+import type { MigrationResult, ProjectRecord } from "./types";
+import { getProjectId } from "./utils";
+
+export function transformProjectV9ToV10({
+ project,
+}: {
+ project: ProjectRecord;
+}): MigrationResult<ProjectRecord> {
+ if (!getProjectId({ project })) {
+ return { project, skipped: true, reason: "no project id" };
+ }
+
+ if (typeof project.version === "number" && project.version >= 10) {
+ return { project, skipped: true, reason: "already v10" };
+ }
+
+ return {
+ project: { ...project, version: 10 },
+ skipped: false,
+ };
+}