Compare commits

..
1 Commits
Author SHA1 Message Date
Maze Winther 94e75278f8 chore: force deployment 2026-04-15 02:20:40 +02:00
4 changed files with 145 additions and 25 deletions
@@ -57,9 +57,6 @@ export function useKeyboardShortcutsHelp() {
[TActionWithOptionalArgs, string[]]
>) {
const actionDef = ACTIONS[action];
if (!actionDef) {
continue;
}
result.push({
id: action,
keys,
@@ -0,0 +1,144 @@
import { describe, expect, test } from "bun:test";
import { upsertElementKeyframe } from "@/lib/animation";
import type { UploadAudioElement, VideoElement } from "@/lib/timeline";
import {
buildSeparatedAudioElement,
doesElementHaveEnabledAudio,
isSourceAudioEnabled,
isSourceAudioSeparated,
} from "..";
describe("audio separation", () => {
test("treats missing source audio state as enabled", () => {
const element = buildVideoElement({});
expect(isSourceAudioEnabled({ element })).toBe(true);
expect(isSourceAudioSeparated({ element })).toBe(false);
});
test("builds a detached audio element from the source clip audio state", () => {
const element = buildVideoElement({
duration: 8,
startTime: 3,
trimStart: 1.5,
trimEnd: 0.5,
sourceDuration: 12,
volume: -6,
muted: true,
retime: { rate: 1.25, maintainPitch: true },
animations: buildAnimations(),
});
const separatedAudioElement = buildSeparatedAudioElement({
sourceElement: element,
});
expect(separatedAudioElement).toMatchObject({
type: "audio",
sourceType: "upload",
mediaId: element.mediaId,
name: element.name,
duration: 8,
startTime: 3,
trimStart: 1.5,
trimEnd: 0.5,
sourceDuration: 12,
volume: -6,
muted: true,
retime: { rate: 1.25, maintainPitch: true },
});
expect(Object.keys(separatedAudioElement.animations?.bindings ?? {})).toEqual([
"volume",
]);
expect(Object.keys(separatedAudioElement.animations?.channels ?? {})).toEqual([
"volume:value",
]);
expect(
separatedAudioElement.animations?.channels["volume:value"]?.keys[0]?.id,
).not.toBe("volume-keyframe");
});
test("skips source audio collection when the source clip is separated", () => {
const mediaAsset = { hasAudio: true };
const videoElement = buildVideoElement({
isSourceAudioEnabled: false,
});
const audioElement = buildAudioElement();
expect(
doesElementHaveEnabledAudio({
element: videoElement,
mediaAsset,
}),
).toBe(false);
expect(
doesElementHaveEnabledAudio({
element: audioElement,
}),
).toBe(true);
});
});
function buildVideoElement(
overrides: Partial<VideoElement>,
): VideoElement {
return {
id: "video-1",
type: "video",
mediaId: "media-1",
name: "Clip",
duration: 5,
startTime: 0,
trimStart: 0,
trimEnd: 0,
volume: 0,
muted: false,
isSourceAudioEnabled: true,
transform: {
scaleX: 1,
scaleY: 1,
position: { x: 0, y: 0 },
rotate: 0,
},
opacity: 1,
...overrides,
};
}
function buildAudioElement(
overrides: Partial<UploadAudioElement> = {},
): UploadAudioElement {
return {
id: "audio-1",
type: "audio",
sourceType: "upload",
mediaId: "audio-media-1",
name: "Detached audio",
duration: 5,
startTime: 0,
trimStart: 0,
trimEnd: 0,
volume: 0,
...overrides,
} satisfies UploadAudioElement;
}
function buildAnimations() {
const withVolume = upsertElementKeyframe({
animations: undefined,
propertyPath: "volume",
time: 2,
value: -12,
interpolation: "linear",
keyframeId: "volume-keyframe",
});
return upsertElementKeyframe({
animations: withVolume,
propertyPath: "opacity",
time: 1,
value: 0.5,
interpolation: "linear",
keyframeId: "opacity-keyframe",
});
}
@@ -2,7 +2,6 @@ import { v2ToV3 } from "./v2-to-v3";
import { v3ToV4 } from "./v3-to-v4";
import { v4ToV5 } from "./v4-to-v5";
import { v5ToV6 } from "./v5-to-v6";
import { v6ToV7 } from "./v6-to-v7";
type MigrationFn = ({ state }: { state: unknown }) => unknown;
@@ -11,10 +10,9 @@ const migrations: Record<number, MigrationFn> = {
3: v3ToV4,
4: v4ToV5,
5: v5ToV6,
6: v6ToV7,
};
export const CURRENT_VERSION = 7;
export const CURRENT_VERSION = 6;
export function runMigrations({
state,
@@ -1,19 +0,0 @@
import type { KeybindingConfig } from "@/lib/actions/keybinding";
interface V6State {
keybindings: KeybindingConfig;
isCustomized: boolean;
}
export function v6ToV7({ state }: { state: unknown }): unknown {
const v6 = state as V6State;
const keybindings = { ...v6.keybindings };
for (const key of Object.keys(keybindings) as Array<keyof KeybindingConfig>) {
if (keybindings[key] === ("split-element" as never)) {
keybindings[key] = "split";
}
}
return { ...v6, keybindings };
}