import { Button } from "@/components/ui/button"; import { PropertyGroup } from "../../properties-panel/property-item"; import { PanelBaseView as BaseView } from "@/components/editor/panel-base-view"; import { LanguageSelect } from "@/components/language-select"; import { useState, useRef } from "react"; import { extractTimelineAudio } from "@/lib/media/mediabunny"; import { useEditor } from "@/hooks/use-editor"; import { DEFAULT_TEXT_ELEMENT } from "@/constants/text-constants"; import { LANGUAGES } from "@/constants/captions-constants"; import { Loader2 } from "lucide-react"; import type { TranscriptionProgress } from "@/types/transcription"; import { transcriptionService } from "@/services/transcription"; import { decodeAudioToFloat32 } from "@/lib/media/audio"; import { buildCaptionChunks } from "@/lib/transcription/caption"; export function Captions() { const [selectedLanguage, setSelectedLanguage] = useState("auto"); const [isProcessing, setIsProcessing] = useState(false); const [processingStep, setProcessingStep] = useState(""); const [error, setError] = useState(null); const containerRef = useRef(null); const editor = useEditor(); const handleProgress = (progress: TranscriptionProgress) => { if (progress.status === "loading-model") { setProcessingStep(`Loading model ${Math.round(progress.progress)}%`); } else if (progress.status === "transcribing") { setProcessingStep("Transcribing..."); } }; const handleGenerateTranscript = async () => { try { setIsProcessing(true); setError(null); setProcessingStep("Extracting audio..."); const audioBlob = await extractTimelineAudio({ tracks: editor.timeline.getTracks(), mediaAssets: editor.media.getAssets(), totalDuration: editor.timeline.getTotalDuration(), }); setProcessingStep("Preparing audio..."); const { samples } = await decodeAudioToFloat32({ audioBlob }); const result = await transcriptionService.transcribe({ audioData: samples, language: selectedLanguage === "auto" ? "auto" : selectedLanguage.toLowerCase(), onProgress: handleProgress, }); setProcessingStep("Generating captions..."); const captionChunks = buildCaptionChunks({ segments: result.segments }); const captionTrackId = editor.timeline.addTrack({ type: "text", index: 0, }); for (let i = 0; i < captionChunks.length; i++) { const caption = captionChunks[i]; 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) { console.error("Transcription failed:", error); setError( error instanceof Error ? error.message : "An unexpected error occurred", ); } finally { setIsProcessing(false); setProcessingStep(""); } }; return (
{error && (

{error}

)}
); }