you can now seperate clip audio for each clip

This commit is contained in:
Alan Trebugeais
2026-05-09 15:28:28 +02:00
parent 3df4ca873a
commit 934cd470fd
15 changed files with 97 additions and 59 deletions
+33 -42
View File
@@ -469,11 +469,11 @@ interface SettingsPanelProps {
selectedClipId?: string | null;
selectedClipSpeed?: number | null;
selectedClipMuted?: boolean | null;
selectedClipShowSourceAudio?: boolean | null;
hasClipSourceAudio?: boolean;
showClipSourceAudioTrack?: boolean;
onClipSpeedChange?: (speed: number) => void;
onClipMutedChange?: (muted: boolean) => void;
onShowClipSourceAudioTrackChange?: (show: boolean) => void;
onClipShowSourceAudioChange?: (show: boolean) => void;
sourceAudioTrackMeta?: Array<{ id: string; label: string }>;
sourceAudioTrackSettings?: Record<string, { volume: number; normalize: boolean }>;
onSourceAudioTrackVolumeChange?: (id: string, volume: number) => void;
@@ -869,11 +869,11 @@ export function SettingsPanel({
selectedClipId,
selectedClipSpeed,
selectedClipMuted,
selectedClipShowSourceAudio = false,
hasClipSourceAudio = false,
showClipSourceAudioTrack = false,
onClipSpeedChange,
onClipMutedChange,
onShowClipSourceAudioTrackChange,
onClipShowSourceAudioChange,
sourceAudioTrackMeta = [],
sourceAudioTrackSettings = {},
onSourceAudioTrackVolumeChange,
@@ -3059,42 +3059,7 @@ export function SettingsPanel({
</span>
)}
</div>
<div className="flex items-center justify-between rounded-lg bg-foreground/[0.03] px-2.5 py-1.5">
<span className="text-[10px] text-muted-foreground">
{tSettings("clip.muteAudio", "Mute Audio")}
</span>
<div className="flex items-center gap-2">
<span
className={cn(
"rounded-full px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-wider",
selectedClipMuted
? "bg-red-500/15 text-red-300"
: "bg-emerald-500/15 text-emerald-300",
)}
>
{selectedClipMuted
? tSettings("clip.mutedState", "Muted")
: tSettings("clip.audioOnState", "Audio On")}
</span>
<Switch
checked={!(selectedClipMuted ?? false)}
onCheckedChange={(v) => onClipMutedChange?.(!v)}
className="data-[state=checked]:bg-[#06b6d4] scale-75"
/>
</div>
</div>
{hasClipSourceAudio && (
<div className="flex items-center justify-between rounded-lg bg-foreground/[0.03] px-2.5 py-1.5">
<span className="text-[10px] text-muted-foreground">
{tSettings("clip.separateSourceAudio", "Separate Clip Audio")}
</span>
<Switch
checked={showClipSourceAudioTrack}
onCheckedChange={(v) => onShowClipSourceAudioTrackChange?.(v)}
className="data-[state=checked]:bg-[#06b6d4] scale-75"
/>
</div>
)}
<div className="flex items-center gap-3">
<SectionLabel>{tSettings("speed.label", "Speed")}</SectionLabel>
</div>
@@ -3135,6 +3100,32 @@ export function SettingsPanel({
);
})}
</div>
<div className="mt-2 flex items-center justify-between rounded-lg bg-foreground/[0.03] px-2.5 py-1.5">
<span className="text-[10px] text-muted-foreground">
{selectedClipMuted
? tSettings("clip.unmuteAudio", "Unmute Audio")
: tSettings("clip.muteAudio", "Mute Audio")}
</span>
<Switch
checked={!(selectedClipMuted ?? false)}
onCheckedChange={(v) => onClipMutedChange?.(!v)}
className="data-[state=checked]:bg-[#06b6d4] scale-75"
/>
</div>
{hasClipSourceAudio && (
<div className="mt-2 flex items-center justify-between rounded-lg bg-foreground/[0.03] px-2.5 py-1.5">
<span className="text-[10px] text-muted-foreground">
{tSettings("clip.separateClipFromAudio", "Separate clip from audio")}
</span>
<Switch
checked={selectedClipShowSourceAudio ?? false}
onCheckedChange={(v) => onClipShowSourceAudioChange?.(v)}
className="data-[state=checked]:bg-[#06b6d4] scale-75"
/>
</div>
)}
{selectedClipId && (
<Button
onClick={() => {
@@ -3603,7 +3594,7 @@ export function SettingsPanel({
className={cn(
"flex-shrink-0 border-t border-foreground/10 bg-editor-header p-4 pt-3",
!selectedAudioId &&
!(selectedClipId && hasClipSourceAudio && showClipSourceAudioTrack) &&
!(selectedClipId && hasClipSourceAudio && selectedClipShowSourceAudio) &&
"hidden",
)}
>
@@ -3638,7 +3629,7 @@ export function SettingsPanel({
{tSettings("audio.deleteRegion", "Delete Audio")}
</Button>
</div>
) : selectedClipId && hasClipSourceAudio && showClipSourceAudioTrack ? (
) : selectedClipId && hasClipSourceAudio && selectedClipShowSourceAudio ? (
<div className="flex flex-col gap-3">
<div className="text-sm font-medium text-foreground">
{tSettings("audio.sourceTracksTitle", "Clip Source Audio")}
+24 -16
View File
@@ -665,7 +665,6 @@ export default function VideoEditor() {
const [audioRegions, setAudioRegions] = useState<AudioRegion[]>([]);
const [selectedAudioId, setSelectedAudioId] = useState<string | null>(null);
const [hasClipSourceAudio, setHasClipSourceAudio] = useState(false);
const [showClipSourceAudioTrack, setShowClipSourceAudioTrack] = useState(false);
const [autoCaptions, setAutoCaptions] = useState<CaptionCue[]>([]);
const [autoCaptionSettings, setAutoCaptionSettings] = useState<AutoCaptionSettings>(
DEFAULT_AUTO_CAPTION_SETTINGS,
@@ -3709,6 +3708,18 @@ export default function VideoEditor() {
[selectedClipId],
);
const handleClipShowSourceAudioChange = useCallback(
(showSourceAudio: boolean) => {
if (!selectedClipId) return;
setClipRegions((prev) =>
prev.map((clip) =>
clip.id === selectedClipId ? { ...clip, showSourceAudio } : clip,
),
);
},
[selectedClipId],
);
const handleClipDelete = useCallback(
(id: string) => {
const deletedClip = clipRegions.find((clip) => clip.id === id);
@@ -5697,26 +5708,26 @@ export default function VideoEditor() {
selectedClipId={selectedClipId}
selectedClipSpeed={
selectedClipId
? (clipRegions.find((c) => c.id === selectedClipId)
?.speed ?? 1)
? clipRegions.find((c) => c.id === selectedClipId)?.speed ?? 1
: null
}
selectedClipMuted={
selectedClipId
? (clipRegions.find((c) => c.id === selectedClipId)
?.muted ?? false)
? clipRegions.find((c) => c.id === selectedClipId)?.muted ??
false
: null
}
onClipSpeedChange={(speed) =>
selectedClipId && handleClipSpeedChange(speed)
}
onClipMutedChange={(muted) =>
selectedClipId && handleClipMutedChange(muted)
selectedClipShowSourceAudio={
selectedClipId
? clipRegions.find((c) => c.id === selectedClipId)
?.showSourceAudio ?? false
: null
}
onClipSpeedChange={handleClipSpeedChange}
onClipMutedChange={handleClipMutedChange}
onClipShowSourceAudioChange={handleClipShowSourceAudioChange}
onClipDelete={handleClipDelete}
hasClipSourceAudio={hasClipSourceAudio}
showClipSourceAudioTrack={showClipSourceAudioTrack}
onShowClipSourceAudioTrackChange={setShowClipSourceAudioTrack}
sourceAudioTrackMeta={audio.sourceAudioTrackMeta}
sourceAudioTrackSettings={audio.selectedClipSourceAudioTrackSettings}
onSourceAudioTrackVolumeChange={
@@ -6314,16 +6325,13 @@ export default function VideoEditor() {
selectedAnnotationId={selectedAnnotationId}
onSelectAnnotation={handleSelectAnnotation}
aspectRatio={aspectRatio}
showSourceAudioTrack={showClipSourceAudioTrack}
showSourceAudioTrack={clipRegions.some((c) => c.showSourceAudio)}
sourceAudioTrackSettings={audio.activeSourceAudioTrackSettings}
getSourceAudioTrackSettingsForClip={
audio.getSourceAudioTrackSettingsForClip
}
onSourceAudioAvailabilityChange={(available) => {
setHasClipSourceAudio(available);
if (!available) {
setShowClipSourceAudioTrack(false);
}
}}
onSourceAudioTracksMetaChange={(tracks) => {
audio.onSourceAudioTracksMetaChange(tracks);
@@ -378,7 +378,7 @@ const TimelineCanvasRows = memo(function TimelineCanvasRows({
{showSourceAudioTrack &&
sourceAudioTracks.map((track) => (
<Row key={track.id} id={`${SOURCE_AUDIO_ROW_ID}-${track.id}`}>
{clipItems.map((item) => {
{clipItems.filter(item => item.showSourceAudio).map((item) => {
const settings = getSourceAudioTrackSettingsForClip?.(item.id)?.[
track.id
] ?? { volume: 1, normalize: false };
@@ -36,6 +36,7 @@ export interface TimelineRenderItem {
zoomDepth?: number;
zoomMode?: ZoomMode;
speedValue?: number;
showSourceAudio?: boolean;
variant: "zoom" | "trim" | "clip" | "annotation" | "speed" | "audio";
}
@@ -52,6 +52,7 @@ export function buildTimelineItems(params: {
rowId: CLIP_ROW_ID,
span: { start: region.startMs, end: region.endMs },
label: `Clip ${index + 1}`,
showSourceAudio: region.showSourceAudio,
variant: "clip",
}));
+1
View File
@@ -169,6 +169,7 @@ export interface ClipRegion {
endMs: number;
speed: number;
muted?: boolean;
showSourceAudio?: boolean;
}
export function getClipSourceEndMs(clip: ClipRegion): number {
+8
View File
@@ -20,6 +20,8 @@
"clip": {
"title": "Clip",
"muteAudio": "Mute Audio",
"unmuteAudio": "Unmute Audio",
"separateClipFromAudio": "Separate clip from audio",
"delete": "Delete Clip"
},
"effects": {
@@ -201,5 +203,11 @@
"exportVideo": "Export {{format}}",
"reportBug": "Report Bug",
"starOnGithub": "Star on GitHub"
},
"audio": {
"sourceTracksTitle": "Clip Source Audio",
"volumeTitle": "Volume",
"normalize": "Normalize",
"deleteRegion": "Delete Audio"
}
}
+8
View File
@@ -20,6 +20,8 @@
"clip": {
"title": "Clip",
"muteAudio": "Silenciar audio",
"unmuteAudio": "Activar sonido",
"separateClipFromAudio": "Separar clip del audio",
"delete": "Eliminar clip"
},
"effects": {
@@ -181,5 +183,11 @@
"exportVideo": "Exportar {{format}}",
"reportBug": "Reportar error",
"starOnGithub": "Estrella en GitHub"
},
"audio": {
"sourceTracksTitle": "Audio fuente del clip",
"volumeTitle": "Volumen",
"normalize": "Normalizar",
"deleteRegion": "Eliminar audio"
}
}
+8
View File
@@ -20,6 +20,8 @@
"clip": {
"title": "Clip",
"muteAudio": "Couper le son",
"unmuteAudio": "Réactiver le son",
"separateClipFromAudio": "Séparer le clip de l'audio",
"delete": "Supprimer le clip"
},
"effects": {
@@ -181,5 +183,11 @@
"exportVideo": "Exporter en {{format}}",
"reportBug": "Signaler un bug",
"starOnGithub": "Mettre une étoile sur GitHub"
},
"audio": {
"sourceTracksTitle": "Audio source du clip",
"volumeTitle": "Volume",
"normalize": "Normaliser",
"deleteRegion": "Supprimer l'audio"
}
}
+2
View File
@@ -20,6 +20,8 @@
"clip": {
"title": "클립",
"muteAudio": "오디오 음소거",
"unmuteAudio": "음소거 해제",
"separateClipFromAudio": "클립과 오디오 분리",
"delete": "클립 삭제"
},
"effects": {
+2
View File
@@ -20,6 +20,8 @@
"clip": {
"title": "Clip",
"muteAudio": "Audio dempen",
"unmuteAudio": "Geluid inschakelen",
"separateClipFromAudio": "Clip scheiden van audio",
"delete": "Clip verwijderen"
},
"effects": {
+2
View File
@@ -20,6 +20,8 @@
"clip": {
"title": "Clipe",
"muteAudio": "Silenciar áudio",
"unmuteAudio": "Ativar som",
"separateClipFromAudio": "Separar clipe do áudio",
"delete": "Excluir clipe"
},
"effects": {
+2
View File
@@ -20,6 +20,8 @@
"clip": {
"title": "Клип",
"muteAudio": "Выключить звук",
"unmuteAudio": "Включить звук",
"separateClipFromAudio": "Отделить клип от аудио",
"delete": "Удалить"
},
"effects": {
+2
View File
@@ -20,6 +20,8 @@
"clip": {
"title": "片段",
"muteAudio": "静音音频",
"unmuteAudio": "取消静音",
"separateClipFromAudio": "将剪辑与音频分离",
"delete": "删除片段"
},
"effects": {
+2
View File
@@ -20,6 +20,8 @@
"clip": {
"title": "片段",
"muteAudio": "靜音",
"unmuteAudio": "取消靜音",
"separateClipFromAudio": "將片段與音訊分離",
"delete": "刪除片段"
},
"effects": {