fix: captions view ui + use BatchCommand to add text elements in transcription at once

This commit is contained in:
Maze Winther
2026-03-04 21:52:22 +01:00
parent a2580216f8
commit 5638974f4b
@@ -11,6 +11,11 @@ import { useState, useRef } from "react";
import { extractTimelineAudio } from "@/lib/media/mediabunny"; import { extractTimelineAudio } from "@/lib/media/mediabunny";
import { useEditor } from "@/hooks/use-editor"; import { useEditor } from "@/hooks/use-editor";
import { DEFAULT_TEXT_ELEMENT } from "@/constants/text-constants"; import { DEFAULT_TEXT_ELEMENT } from "@/constants/text-constants";
import {
BatchCommand,
AddTrackCommand,
InsertElementCommand,
} from "@/lib/commands";
import { TRANSCRIPTION_LANGUAGES } from "@/constants/transcription-constants"; import { TRANSCRIPTION_LANGUAGES } from "@/constants/transcription-constants";
import type { import type {
TranscriptionLanguage, TranscriptionLanguage,
@@ -20,7 +25,12 @@ import { transcriptionService } from "@/services/transcription/service";
import { decodeAudioToFloat32 } from "@/lib/media/audio"; import { decodeAudioToFloat32 } from "@/lib/media/audio";
import { buildCaptionChunks } from "@/lib/transcription/caption"; import { buildCaptionChunks } from "@/lib/transcription/caption";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import { Label } from "@/components/ui/label"; import {
Section,
SectionContent,
SectionField,
SectionFields,
} from "@/components/editor/panels/properties/section";
export function Captions() { export function Captions() {
const [selectedLanguage, setSelectedLanguage] = const [selectedLanguage, setSelectedLanguage] =
@@ -60,29 +70,28 @@ export function Captions() {
onProgress: handleProgress, onProgress: handleProgress,
}); });
setProcessingStep("Generating captions..."); setProcessingStep("Generating captions...");
const captionChunks = buildCaptionChunks({ segments: result.segments }); const captionChunks = buildCaptionChunks({ segments: result.segments });
const captionTrackId = editor.timeline.addTrack({ const addTrackCommand = new AddTrackCommand("text", 0);
type: "text", const insertCommands = captionChunks.map((caption, i) =>
index: 0, new InsertElementCommand({
}); placement: { mode: "explicit", trackId: addTrackCommand.getTrackId() },
element: {
...DEFAULT_TEXT_ELEMENT,
name: `Caption ${i + 1}`,
content: caption.text,
duration: caption.duration,
startTime: caption.startTime,
fontSize: 65,
fontWeight: "bold",
},
})
);
for (let i = 0; i < captionChunks.length; i++) { editor.command.execute({
const caption = captionChunks[i]; command: new BatchCommand([addTrackCommand, ...insertCommands]),
editor.timeline.insertElement({ });
placement: { mode: "explicit", trackId: captionTrackId },
element: {
...DEFAULT_TEXT_ELEMENT,
name: `Caption ${i + 1}`,
content: caption.text,
duration: caption.duration,
startTime: caption.startTime,
fontSize: 65,
fontWeight: "bold",
},
});
}
} catch (error) { } catch (error) {
console.error("Transcription failed:", error); console.error("Transcription failed:", error);
setError( setError(
@@ -108,43 +117,49 @@ export function Captions() {
}; };
return ( return (
<PanelView title="Captions" ref={containerRef}> <PanelView title="Captions" contentClassName="px-0 flex flex-col h-full" ref={containerRef}>
<div className="flex flex-col gap-3"> <Section showTopBorder={false} className="flex-1">
<Label>Language</Label> <SectionContent className="flex flex-col gap-4 h-full">
<Select <SectionFields>
value={selectedLanguage} <SectionField label="Language">
onValueChange={(value) => handleLanguageChange({ value })} <Select
> value={selectedLanguage}
<SelectTrigger> onValueChange={(value) => handleLanguageChange({ value })}
<SelectValue placeholder="Select a language" /> >
</SelectTrigger> <SelectTrigger>
<SelectContent> <SelectValue placeholder="Select a language" />
<SelectItem value="auto">Auto detect</SelectItem> </SelectTrigger>
{TRANSCRIPTION_LANGUAGES.map((language) => ( <SelectContent>
<SelectItem key={language.code} value={language.code}> <SelectItem value="auto">Auto detect</SelectItem>
{language.name} {TRANSCRIPTION_LANGUAGES.map((language) => (
</SelectItem> <SelectItem key={language.code} value={language.code}>
))} {language.name}
</SelectContent> </SelectItem>
</Select> ))}
</div> </SelectContent>
</Select>
<div className="flex flex-col gap-4"> </SectionField>
{error && ( </SectionFields>
<div className="bg-destructive/10 border-destructive/20 rounded-md border p-3">
<p className="text-destructive text-sm">{error}</p>
</div>
)}
{error && (
<div className="bg-destructive/10 border-destructive/20 rounded-md border p-3">
<p className="text-destructive text-sm">{error}</p>
</div>
)}
</SectionContent>
</Section>
<Section showBottomBorder={false} showTopBorder={false}>
<SectionContent>
<Button <Button
className="w-full" className="w-full"
onClick={handleGenerateTranscript} onClick={handleGenerateTranscript}
disabled={isProcessing} disabled={isProcessing}
> >
{isProcessing && <Spinner className="mr-1" />} {isProcessing && <Spinner className="mr-1" />}
{isProcessing ? processingStep : "Generate transcript"} {isProcessing ? processingStep : "Generate transcript"}
</Button> </Button>
</div> </SectionContent>
</Section>
</PanelView> </PanelView>
); );
} }