mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 23:05:49 +00:00
you can now seperate clip audio for each clip
This commit is contained in:
@@ -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")}
|
||||
|
||||
@@ -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",
|
||||
}));
|
||||
|
||||
|
||||
@@ -169,6 +169,7 @@ export interface ClipRegion {
|
||||
endMs: number;
|
||||
speed: number;
|
||||
muted?: boolean;
|
||||
showSourceAudio?: boolean;
|
||||
}
|
||||
|
||||
export function getClipSourceEndMs(clip: ClipRegion): number {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"clip": {
|
||||
"title": "클립",
|
||||
"muteAudio": "오디오 음소거",
|
||||
"unmuteAudio": "음소거 해제",
|
||||
"separateClipFromAudio": "클립과 오디오 분리",
|
||||
"delete": "클립 삭제"
|
||||
},
|
||||
"effects": {
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"clip": {
|
||||
"title": "Clip",
|
||||
"muteAudio": "Audio dempen",
|
||||
"unmuteAudio": "Geluid inschakelen",
|
||||
"separateClipFromAudio": "Clip scheiden van audio",
|
||||
"delete": "Clip verwijderen"
|
||||
},
|
||||
"effects": {
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"clip": {
|
||||
"title": "Clipe",
|
||||
"muteAudio": "Silenciar áudio",
|
||||
"unmuteAudio": "Ativar som",
|
||||
"separateClipFromAudio": "Separar clipe do áudio",
|
||||
"delete": "Excluir clipe"
|
||||
},
|
||||
"effects": {
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"clip": {
|
||||
"title": "Клип",
|
||||
"muteAudio": "Выключить звук",
|
||||
"unmuteAudio": "Включить звук",
|
||||
"separateClipFromAudio": "Отделить клип от аудио",
|
||||
"delete": "Удалить"
|
||||
},
|
||||
"effects": {
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"clip": {
|
||||
"title": "片段",
|
||||
"muteAudio": "静音音频",
|
||||
"unmuteAudio": "取消静音",
|
||||
"separateClipFromAudio": "将剪辑与音频分离",
|
||||
"delete": "删除片段"
|
||||
},
|
||||
"effects": {
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
"clip": {
|
||||
"title": "片段",
|
||||
"muteAudio": "靜音",
|
||||
"unmuteAudio": "取消靜音",
|
||||
"separateClipFromAudio": "將片段與音訊分離",
|
||||
"delete": "刪除片段"
|
||||
},
|
||||
"effects": {
|
||||
|
||||
Reference in New Issue
Block a user