creates video voice memos from audio clips; with bluesky integration.
trill.ptr.pet
1import {
2 AutomaticSpeechRecognitionPipeline,
3 pipeline,
4} from "@huggingface/transformers";
5import { toaster } from "~/components/Toaster";
6import { defaultWhisperModel, whisperModel } from "./settings";
7
8let transcriberPromise: Promise<AutomaticSpeechRecognitionPipeline> | null =
9 null;
10let model: AutomaticSpeechRecognitionPipeline | null = null;
11
12const loadModel = () => {
13 if (model) return Promise.resolve(model);
14
15 if (transcriberPromise) return transcriberPromise;
16
17 let toastId: string | undefined;
18
19 const modelName = whisperModel.get() ?? defaultWhisperModel;
20
21 transcriberPromise = pipeline("automatic-speech-recognition", modelName, {
22 progress_callback: (data: any) => {
23 // data contains: { status, file, name, loaded, total, progress }
24 if (data.status === "initiate") {
25 if (!toastId) {
26 toastId = toaster.create({
27 title: "downloading transcription model",
28 description: `fetching ${data.file}...`,
29 type: "info",
30 duration: 999999,
31 });
32 }
33 } else if (data.status === "progress" && toastId) {
34 const percent = data.progress ? Math.round(data.progress) : 0;
35 toaster.update(toastId, {
36 title: "downloading transcription model",
37 description: `fetching ${data.file} (at ${percent}%)...`,
38 type: "info",
39 duration: 999999,
40 });
41 }
42 },
43 })
44 .then((transcriber) => {
45 if (toastId) {
46 toaster.update(toastId, {
47 title: "transcription model loaded",
48 description: `${modelName.split("/")[1]} is ready`,
49 type: "success",
50 duration: 3000,
51 });
52 }
53 model = transcriber;
54 return transcriber;
55 })
56 .catch((err) => {
57 const toastOpts = {
58 title: "transcription model download failed",
59 description: `${err}`,
60 type: "error",
61 duration: 5000,
62 };
63 if (toastId) toaster.update(toastId, toastOpts);
64 else toaster.create(toastOpts);
65
66 model = null;
67
68 throw err;
69 })
70 .finally(() => {
71 transcriberPromise = null;
72 });
73
74 return transcriberPromise;
75};
76
77export const preloadModel = () => {
78 model = null;
79 loadModel().catch((e) => console.error("preload failed", e));
80};
81
82export const transcribe = async (file: File): Promise<string> => {
83 const url = URL.createObjectURL(file);
84 try {
85 await loadModel();
86 if (!model) throw "model not loaded";
87
88 const output = await model(url);
89 return [output].flat()[0].text.trim();
90 } catch (err) {
91 console.error("transcription failed", err);
92 toaster.create({
93 title: "transcription failed",
94 description: `${err}`,
95 type: "error",
96 });
97 throw err;
98 } finally {
99 URL.revokeObjectURL(url);
100 }
101};