summaryrefslogtreecommitdiff
path: root/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-03-25 16:47:22 +0100
committerMaze Winther <mazewinther@gmail.com>2026-03-29 15:48:22 +0200
commit8db3bead13441a5882d8689dfc6a9f09c82375d9 (patch)
treee9d0cafa93904ffcbec3188c37961bcc16c6422d /apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts
parent39ea298a9ccba3104c549c0ca079c231981811f5 (diff)
downloadopencut-8db3bead13441a5882d8689dfc6a9f09c82375d9.tar.gz
feat: masks, properties refactor, shaders, storage migrations, and more
Diffstat (limited to 'apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts')
-rw-r--r--apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts144
1 files changed, 121 insertions, 23 deletions
diff --git a/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts b/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts
index 297d1c58..52701cd5 100644
--- a/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts
+++ b/apps/web/src/services/storage/migrations/transformers/v1-to-v2.ts
@@ -6,14 +6,6 @@ import {
} from "@/constants/project-constants";
import { IndexedDBAdapter } from "@/services/storage/indexeddb-adapter";
import type { MediaAssetData } from "@/services/storage/types";
-import type {
- AudioElement,
- ImageElement,
- TextElement,
- TimelineTrack,
- Transform,
- VideoElement,
-} from "@/types/timeline";
import type { MigrationResult, ProjectRecord } from "./types";
import { getProjectId, isRecord } from "./utils";
@@ -50,6 +42,112 @@ interface LegacyMediaTrack {
[key: string]: unknown;
}
+interface V2Transform {
+ scale: number;
+ position: { x: number; y: number };
+ rotate: number;
+}
+
+interface V2VideoElement {
+ id: string;
+ name: string;
+ type: "video";
+ mediaId: string;
+ muted: boolean;
+ hidden: boolean;
+ transform: V2Transform;
+ opacity: number;
+ duration: number;
+ startTime: number;
+ trimStart: number;
+ trimEnd: number;
+}
+
+interface V2ImageElement {
+ id: string;
+ name: string;
+ type: "image";
+ mediaId: string;
+ duration: number;
+ startTime: number;
+ trimStart: number;
+ trimEnd: number;
+ hidden: boolean;
+ transform: V2Transform;
+ opacity: number;
+}
+
+interface V2TextElement {
+ id: string;
+ name: string;
+ type: "text";
+ content: string;
+ fontSize: number;
+ fontFamily: string;
+ color: string;
+ background: {
+ enabled: boolean;
+ color: string;
+ cornerRadius: number;
+ paddingX: number;
+ paddingY: number;
+ offsetX: number;
+ offsetY: number;
+ };
+ textAlign: "left" | "center" | "right";
+ fontWeight: "normal" | "bold";
+ fontStyle: "normal" | "italic";
+ textDecoration: "none" | "underline" | "line-through";
+ hidden: boolean;
+ transform: V2Transform;
+ opacity: number;
+ duration: number;
+ startTime: number;
+ trimStart: number;
+ trimEnd: number;
+}
+
+interface V2AudioElement {
+ id: string;
+ name: string;
+ type: "audio";
+ sourceType: "upload";
+ mediaId: string;
+ volume: number;
+ duration: number;
+ startTime: number;
+ trimStart: number;
+ trimEnd: number;
+}
+
+interface V2VideoTrack {
+ id: string;
+ name: string;
+ type: "video";
+ elements: (V2VideoElement | V2ImageElement)[];
+ isMain: boolean;
+ muted: boolean;
+ hidden: boolean;
+}
+
+interface V2TextTrack {
+ id: string;
+ name: string;
+ type: "text";
+ elements: V2TextElement[];
+ hidden: boolean;
+}
+
+interface V2AudioTrack {
+ id: string;
+ name: string;
+ type: "audio";
+ elements: V2AudioElement[];
+ muted: boolean;
+}
+
+type V2TimelineTrack = V2VideoTrack | V2TextTrack | V2AudioTrack;
+
export interface TransformV1ToV2Options {
loadMediaAsset?: ({
mediaId,
@@ -259,13 +357,13 @@ async function transformTracks({
}: {
mediaId: string;
}) => Promise<MediaAssetData | null>;
-}): Promise<TimelineTrack[]> {
+}): Promise<V2TimelineTrack[]> {
if (!Array.isArray(tracks)) {
return [];
}
let isFirstVideoTrackFound = false;
- const transformedTracks: (TimelineTrack | null)[] = [];
+ const transformedTracks: (V2TimelineTrack | null)[] = [];
for (const track of tracks) {
if (!isRecord(track)) {
@@ -299,7 +397,7 @@ async function transformTracks({
}
return transformedTracks.filter(
- (track): track is TimelineTrack => track !== null,
+ (track): track is V2TimelineTrack => track !== null,
);
}
@@ -315,7 +413,7 @@ async function transformMediaTrack({
mediaId: string;
}) => Promise<MediaAssetData | null>;
isMain: boolean;
-}): Promise<TimelineTrack> {
+}): Promise<V2VideoTrack> {
const elements = Array.isArray(track.elements) ? track.elements : [];
const transformedElements = await Promise.all(
@@ -338,7 +436,7 @@ async function transformMediaTrack({
}
}
- const defaultTransform: Transform = {
+ const defaultTransform: V2Transform = {
scale: 1,
position: { x: 0, y: 0 },
rotate: 0,
@@ -347,7 +445,7 @@ async function transformMediaTrack({
const muted = mediaElement.muted === true;
if (mediaType === "image") {
- const imageElement: ImageElement = {
+ const imageElement: V2ImageElement = {
id: getStringValue({ value: element.id, fallback: "" }),
name: getStringValue({ value: element.name, fallback: "" }),
type: "image",
@@ -369,7 +467,7 @@ async function transformMediaTrack({
return imageElement;
}
- const videoElement: VideoElement = {
+ const videoElement: V2VideoElement = {
id: getStringValue({ value: element.id, fallback: "" }),
name: getStringValue({ value: element.name, fallback: "" }),
type: "video",
@@ -388,7 +486,7 @@ async function transformMediaTrack({
);
const validElements = transformedElements.filter(
- (element): element is VideoElement | ImageElement => element !== null,
+ (element): element is V2VideoElement | V2ImageElement => element !== null,
);
return {
@@ -406,11 +504,11 @@ function transformTextTrack({
track,
}: {
track: Record<string, unknown>;
-}): TimelineTrack {
+}): V2TextTrack {
const elements = Array.isArray(track.elements) ? track.elements : [];
const transformedElements = elements
- .map((element): TextElement | null => {
+ .map((element): V2TextElement | null => {
if (!isRecord(element) || element.type !== "text") {
return null;
}
@@ -427,7 +525,7 @@ function transformTextTrack({
fallback: 1,
});
- const transform: Transform = {
+ const transform: V2Transform = {
scale: 1,
position: { x, y },
rotate: rotation,
@@ -487,7 +585,7 @@ function transformTextTrack({
trimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),
};
})
- .filter((element): element is TextElement => element !== null);
+ .filter((element): element is V2TextElement => element !== null);
return {
id: getStringValue({ value: track.id, fallback: "" }),
@@ -502,11 +600,11 @@ function transformAudioTrack({
track,
}: {
track: Record<string, unknown>;
-}): TimelineTrack {
+}): V2AudioTrack {
const elements = Array.isArray(track.elements) ? track.elements : [];
const transformedElements = elements
- .map((element): AudioElement | null => {
+ .map((element): V2AudioElement | null => {
if (!isRecord(element) || element.type !== "audio") {
return null;
}
@@ -530,7 +628,7 @@ function transformAudioTrack({
trimEnd: getNumberValue({ value: element.trimEnd, fallback: 0 }),
};
})
- .filter((element): element is AudioElement => element !== null);
+ .filter((element): element is V2AudioElement => element !== null);
return {
id: getStringValue({ value: track.id, fallback: "" }),