119 lines
4.0 KiB
TypeScript
119 lines
4.0 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import {
|
|
defaultVideoCanvasAudioSeparationConfig,
|
|
getVideoAudioSplitResultCopy,
|
|
hasMp4AudioTrack,
|
|
hasVideoAudioTrack,
|
|
hasWebmAudioTrack,
|
|
normalizeVideoCanvasAudioSeparationConfig,
|
|
separateVideoAudioChannels,
|
|
} from "../../lib/video-canvas/video-audio-separation";
|
|
|
|
function mp4Box(type: string, payload: Uint8Array) {
|
|
const output = new Uint8Array(8 + payload.length);
|
|
new DataView(output.buffer).setUint32(0, output.length);
|
|
Array.from(type).forEach((character, index) => {
|
|
output[4 + index] = character.charCodeAt(0);
|
|
});
|
|
output.set(payload, 8);
|
|
return output;
|
|
}
|
|
|
|
function joinBytes(...parts: Uint8Array[]) {
|
|
const output = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
let offset = 0;
|
|
for (const part of parts) {
|
|
output.set(part, offset);
|
|
offset += part.length;
|
|
}
|
|
return output;
|
|
}
|
|
|
|
function ebmlElement(id: number[], payload: Uint8Array) {
|
|
if (payload.length >= 127) throw new Error("Test EBML payload is too large");
|
|
return joinBytes(new Uint8Array(id), new Uint8Array([0x80 | payload.length]), payload);
|
|
}
|
|
|
|
test.describe("video audio separation tools", () => {
|
|
test("normalizes configurable local separation limits", () => {
|
|
expect(normalizeVideoCanvasAudioSeparationConfig({
|
|
backgroundGain: 9,
|
|
frameRate: 100,
|
|
model: " center-channel-custom ",
|
|
voiceGain: -1,
|
|
})).toMatchObject({
|
|
backgroundGain: 4,
|
|
frameRate: 60,
|
|
model: "center-channel-custom",
|
|
voiceGain: 0,
|
|
});
|
|
});
|
|
|
|
test("extracts center and side stereo signals for local fallback modes", () => {
|
|
const left = new Float32Array([1, 0.5]);
|
|
const right = new Float32Array([1, -0.5]);
|
|
const voice = separateVideoAudioChannels(
|
|
[left, right],
|
|
"voice",
|
|
defaultVideoCanvasAudioSeparationConfig,
|
|
);
|
|
const background = separateVideoAudioChannels(
|
|
[left, right],
|
|
"background",
|
|
defaultVideoCanvasAudioSeparationConfig,
|
|
);
|
|
|
|
expect(Array.from(voice[0])).toEqual([1, 0]);
|
|
expect(Array.from(voice[1])).toEqual([1, 0]);
|
|
expect(background[0][0]).toBe(0);
|
|
expect(background[0][1]).toBeCloseTo(0.6);
|
|
expect(background[1][1]).toBeCloseTo(-0.6);
|
|
});
|
|
|
|
test("matches competitor result suffixes", () => {
|
|
expect(getVideoAudioSplitResultCopy("audio-video").audioSuffix).toBe("音轨");
|
|
expect(getVideoAudioSplitResultCopy("voice").audioSuffix).toBe("人声");
|
|
expect(getVideoAudioSplitResultCopy("background").audioSuffix).toBe("背景音");
|
|
});
|
|
|
|
test("reads the structured MP4 audio handler instead of guessing by filename", () => {
|
|
const handlerPayload = new Uint8Array(12);
|
|
Array.from("soun").forEach((character, index) => {
|
|
handlerPayload[8 + index] = character.charCodeAt(0);
|
|
});
|
|
const audioFile = mp4Box("moov", mp4Box("trak", mp4Box("mdia", mp4Box("hdlr", handlerPayload))));
|
|
const videoHandler = new Uint8Array(handlerPayload);
|
|
Array.from("vide").forEach((character, index) => {
|
|
videoHandler[8 + index] = character.charCodeAt(0);
|
|
});
|
|
const videoOnlyFile = mp4Box("moov", mp4Box("trak", mp4Box("mdia", mp4Box("hdlr", videoHandler))));
|
|
|
|
expect(hasMp4AudioTrack(audioFile.buffer)).toBe(true);
|
|
expect(hasMp4AudioTrack(videoOnlyFile.buffer)).toBe(false);
|
|
});
|
|
|
|
test("reads structured WebM TrackType elements for generated video results", () => {
|
|
const audioEntry = ebmlElement(
|
|
[0xae],
|
|
ebmlElement([0x83], new Uint8Array([2])),
|
|
);
|
|
const videoEntry = ebmlElement(
|
|
[0xae],
|
|
ebmlElement([0x83], new Uint8Array([1])),
|
|
);
|
|
const audioWebm = ebmlElement(
|
|
[0x18, 0x53, 0x80, 0x67],
|
|
ebmlElement([0x16, 0x54, 0xae, 0x6b], audioEntry),
|
|
);
|
|
const videoOnlyWebm = ebmlElement(
|
|
[0x18, 0x53, 0x80, 0x67],
|
|
ebmlElement([0x16, 0x54, 0xae, 0x6b], videoEntry),
|
|
);
|
|
|
|
expect(hasWebmAudioTrack(audioWebm.buffer)).toBe(true);
|
|
expect(hasWebmAudioTrack(videoOnlyWebm.buffer)).toBe(false);
|
|
expect(hasVideoAudioTrack(audioWebm.buffer)).toBe(true);
|
|
});
|
|
});
|