From 7bf0984698e43b9aa2dd879f65951318ed410390 Mon Sep 17 00:00:00 2001 From: Maze <167211895+mazeincoding@users.noreply.github.com> Date: Sat, 31 Jan 2026 00:20:04 +0100 Subject: codebase overhaul (#697) --- .../editor/panels/assets/views/captions.tsx | 152 +++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 apps/web/src/components/editor/panels/assets/views/captions.tsx (limited to 'apps/web/src/components/editor/panels/assets/views/captions.tsx') diff --git a/apps/web/src/components/editor/panels/assets/views/captions.tsx b/apps/web/src/components/editor/panels/assets/views/captions.tsx new file mode 100644 index 00000000..306bad2a --- /dev/null +++ b/apps/web/src/components/editor/panels/assets/views/captions.tsx @@ -0,0 +1,152 @@ +import { Button } from "@/components/ui/button"; +import { PropertyGroup } from "@/components/editor/panels/properties/property-item"; +import { PanelBaseView as BaseView } from "@/components/editor/panels/panel-base-view"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/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 { TRANSCRIPTION_LANGUAGES } from "@/constants/transcription-constants"; +import type { + TranscriptionLanguage, + TranscriptionProgress, +} from "@/types/transcription"; +import { transcriptionService } from "@/services/transcription"; +import { decodeAudioToFloat32 } from "@/lib/media/audio"; +import { buildCaptionChunks } from "@/lib/transcription/caption"; +import { Spinner } from "@/components/ui/spinner"; + +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" ? undefined : selectedLanguage, + 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(""); + } + }; + + const handleLanguageChange = ({ value }: { value: string }) => { + if (value === "auto") { + setSelectedLanguage("auto"); + return; + } + + const matchedLanguage = TRANSCRIPTION_LANGUAGES.find( + (language) => language.code === value, + ); + if (!matchedLanguage) return; + setSelectedLanguage(matchedLanguage.code); + }; + + return ( + + + + + +
+ {error && ( +
+

{error}

+
+ )} + + +
+
+ ); +} -- cgit v1.3.1