mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94e75278f8 |
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user