Files
OpenCut/apps/web/src/timeline/audio-separation/index.ts
T

134 lines
3.0 KiB
TypeScript

import { cloneAnimations } from "@/animation";
import type { ElementAnimations } from "@/animation/types";
import type { MediaAsset } from "@/media/types";
import { DEFAULTS } from "@/timeline/defaults";
import type {
CreateUploadAudioElement,
TimelineElement,
AudioElement,
VideoElement,
} from "../types";
type MediaAudioState = Pick<MediaAsset, "hasAudio">;
export function isSourceAudioEnabled({
element,
}: {
element: VideoElement;
}): boolean {
return element.isSourceAudioEnabled !== false;
}
export function isSourceAudioSeparated({
element,
}: {
element: VideoElement;
}): boolean {
return !isSourceAudioEnabled({ element });
}
export function canExtractSourceAudio(
element: TimelineElement,
mediaAsset: MediaAudioState | null | undefined,
): element is VideoElement {
return (
element.type === "video" &&
isSourceAudioEnabled({ element }) &&
!!mediaAsset &&
mediaAsset.hasAudio !== false
);
}
export function canRecoverSourceAudio(
element: TimelineElement,
): element is VideoElement {
return element.type === "video" && isSourceAudioSeparated({ element });
}
export function canToggleSourceAudio(
element: TimelineElement,
mediaAsset: MediaAudioState | null | undefined,
): element is VideoElement {
return (
canRecoverSourceAudio(element) || canExtractSourceAudio(element, mediaAsset)
);
}
export function doesElementHaveEnabledAudio({
element,
mediaAsset,
}: {
element: AudioElement | VideoElement;
mediaAsset?: MediaAudioState | null;
}): boolean {
if (element.type === "audio") {
return true;
}
return (
!!mediaAsset &&
mediaAsset.hasAudio !== false &&
isSourceAudioEnabled({ element })
);
}
export function buildSeparatedAudioElement({
sourceElement,
}: {
sourceElement: VideoElement;
}): CreateUploadAudioElement {
return {
type: "audio",
sourceType: "upload",
mediaId: sourceElement.mediaId,
name: sourceElement.name,
duration: sourceElement.duration,
startTime: sourceElement.startTime,
trimStart: sourceElement.trimStart,
trimEnd: sourceElement.trimEnd,
sourceDuration: sourceElement.sourceDuration,
params: {
volume:
typeof sourceElement.params.volume === "number"
? sourceElement.params.volume
: DEFAULTS.element.volume,
muted: sourceElement.params.muted === true,
},
retime: sourceElement.retime
? {
rate: sourceElement.retime.rate,
maintainPitch: sourceElement.retime.maintainPitch,
}
: undefined,
animations: cloneVolumeAnimations({
animations: sourceElement.animations,
}),
};
}
export function getSourceAudioActionLabel({
element,
}: {
element: VideoElement;
}): "Extract audio" | "Recover audio" {
return isSourceAudioSeparated({ element })
? "Recover audio"
: "Extract audio";
}
function cloneVolumeAnimations({
animations,
}: {
animations: ElementAnimations | undefined;
}): ElementAnimations | undefined {
const volumeData = animations?.volume;
if (!volumeData) {
return undefined;
}
return cloneAnimations({
animations: { volume: volumeData },
shouldRegenerateKeyframeIds: true,
});
}