mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
chore: normalize line endings
This commit is contained in:
@@ -1,186 +1,186 @@
|
||||
import type {
|
||||
TranscriptionLanguage,
|
||||
TranscriptionResult,
|
||||
TranscriptionProgress,
|
||||
TranscriptionModelId,
|
||||
} from "@/lib/transcription/types";
|
||||
import {
|
||||
DEFAULT_TRANSCRIPTION_MODEL,
|
||||
TRANSCRIPTION_MODELS,
|
||||
} from "@/constants/transcription-constants";
|
||||
import type { WorkerMessage, WorkerResponse } from "./worker";
|
||||
|
||||
type ProgressCallback = (progress: TranscriptionProgress) => void;
|
||||
|
||||
class TranscriptionService {
|
||||
private worker: Worker | null = null;
|
||||
private currentModelId: TranscriptionModelId | null = null;
|
||||
private isInitialized = false;
|
||||
private isInitializing = false;
|
||||
|
||||
async transcribe({
|
||||
audioData,
|
||||
language = "auto",
|
||||
modelId = DEFAULT_TRANSCRIPTION_MODEL,
|
||||
onProgress,
|
||||
}: {
|
||||
audioData: Float32Array;
|
||||
language?: TranscriptionLanguage;
|
||||
modelId?: TranscriptionModelId;
|
||||
onProgress?: ProgressCallback;
|
||||
}): Promise<TranscriptionResult> {
|
||||
await this.ensureWorker({ modelId, onProgress });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.worker) {
|
||||
reject(new Error("Worker not initialized"));
|
||||
return;
|
||||
}
|
||||
|
||||
const handleMessage = (event: MessageEvent<WorkerResponse>) => {
|
||||
const response = event.data;
|
||||
|
||||
switch (response.type) {
|
||||
case "transcribe-progress":
|
||||
onProgress?.({
|
||||
status: "transcribing",
|
||||
progress: response.progress,
|
||||
message: "Transcribing audio...",
|
||||
});
|
||||
break;
|
||||
|
||||
case "transcribe-complete":
|
||||
this.worker?.removeEventListener("message", handleMessage);
|
||||
resolve({
|
||||
text: response.text,
|
||||
segments: response.segments,
|
||||
language,
|
||||
});
|
||||
break;
|
||||
|
||||
case "transcribe-error":
|
||||
this.worker?.removeEventListener("message", handleMessage);
|
||||
reject(new Error(response.error));
|
||||
break;
|
||||
|
||||
case "cancelled":
|
||||
this.worker?.removeEventListener("message", handleMessage);
|
||||
reject(new Error("Transcription cancelled"));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
this.worker.addEventListener("message", handleMessage);
|
||||
|
||||
this.worker.postMessage({
|
||||
type: "transcribe",
|
||||
audio: audioData,
|
||||
language,
|
||||
} satisfies WorkerMessage);
|
||||
});
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.worker?.postMessage({ type: "cancel" } satisfies WorkerMessage);
|
||||
}
|
||||
|
||||
private async ensureWorker({
|
||||
modelId,
|
||||
onProgress,
|
||||
}: {
|
||||
modelId: TranscriptionModelId;
|
||||
onProgress?: ProgressCallback;
|
||||
}): Promise<void> {
|
||||
const needsNewModel = this.currentModelId !== modelId;
|
||||
|
||||
if (this.worker && this.isInitialized && !needsNewModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isInitializing && !needsNewModel) {
|
||||
await this.waitForInit();
|
||||
return;
|
||||
}
|
||||
|
||||
this.terminate();
|
||||
this.isInitializing = true;
|
||||
this.isInitialized = false;
|
||||
|
||||
const model = TRANSCRIPTION_MODELS.find((m) => m.id === modelId);
|
||||
if (!model) {
|
||||
throw new Error(`Unknown model: ${modelId}`);
|
||||
}
|
||||
|
||||
this.worker = new Worker(new URL("./worker.ts", import.meta.url), {
|
||||
type: "module",
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.worker) {
|
||||
reject(new Error("Failed to create worker"));
|
||||
return;
|
||||
}
|
||||
|
||||
const handleMessage = (event: MessageEvent<WorkerResponse>) => {
|
||||
const response = event.data;
|
||||
|
||||
switch (response.type) {
|
||||
case "init-progress":
|
||||
onProgress?.({
|
||||
status: "loading-model",
|
||||
progress: response.progress,
|
||||
message: `Loading ${model.name} model...`,
|
||||
});
|
||||
break;
|
||||
|
||||
case "init-complete":
|
||||
this.worker?.removeEventListener("message", handleMessage);
|
||||
this.isInitialized = true;
|
||||
this.isInitializing = false;
|
||||
this.currentModelId = modelId;
|
||||
resolve();
|
||||
break;
|
||||
|
||||
case "init-error":
|
||||
this.worker?.removeEventListener("message", handleMessage);
|
||||
this.isInitializing = false;
|
||||
this.terminate();
|
||||
reject(new Error(response.error));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
this.worker.addEventListener("message", handleMessage);
|
||||
|
||||
this.worker.postMessage({
|
||||
type: "init",
|
||||
modelId: model.huggingFaceId,
|
||||
} satisfies WorkerMessage);
|
||||
});
|
||||
}
|
||||
|
||||
private waitForInit(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const checkInit = () => {
|
||||
if (this.isInitialized) {
|
||||
resolve();
|
||||
} else if (!this.isInitializing) {
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkInit, 100);
|
||||
}
|
||||
};
|
||||
checkInit();
|
||||
});
|
||||
}
|
||||
|
||||
terminate() {
|
||||
this.worker?.terminate();
|
||||
this.worker = null;
|
||||
this.isInitialized = false;
|
||||
this.isInitializing = false;
|
||||
this.currentModelId = null;
|
||||
}
|
||||
}
|
||||
|
||||
export const transcriptionService = new TranscriptionService();
|
||||
import type {
|
||||
TranscriptionLanguage,
|
||||
TranscriptionResult,
|
||||
TranscriptionProgress,
|
||||
TranscriptionModelId,
|
||||
} from "@/lib/transcription/types";
|
||||
import {
|
||||
DEFAULT_TRANSCRIPTION_MODEL,
|
||||
TRANSCRIPTION_MODELS,
|
||||
} from "@/constants/transcription-constants";
|
||||
import type { WorkerMessage, WorkerResponse } from "./worker";
|
||||
|
||||
type ProgressCallback = (progress: TranscriptionProgress) => void;
|
||||
|
||||
class TranscriptionService {
|
||||
private worker: Worker | null = null;
|
||||
private currentModelId: TranscriptionModelId | null = null;
|
||||
private isInitialized = false;
|
||||
private isInitializing = false;
|
||||
|
||||
async transcribe({
|
||||
audioData,
|
||||
language = "auto",
|
||||
modelId = DEFAULT_TRANSCRIPTION_MODEL,
|
||||
onProgress,
|
||||
}: {
|
||||
audioData: Float32Array;
|
||||
language?: TranscriptionLanguage;
|
||||
modelId?: TranscriptionModelId;
|
||||
onProgress?: ProgressCallback;
|
||||
}): Promise<TranscriptionResult> {
|
||||
await this.ensureWorker({ modelId, onProgress });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.worker) {
|
||||
reject(new Error("Worker not initialized"));
|
||||
return;
|
||||
}
|
||||
|
||||
const handleMessage = (event: MessageEvent<WorkerResponse>) => {
|
||||
const response = event.data;
|
||||
|
||||
switch (response.type) {
|
||||
case "transcribe-progress":
|
||||
onProgress?.({
|
||||
status: "transcribing",
|
||||
progress: response.progress,
|
||||
message: "Transcribing audio...",
|
||||
});
|
||||
break;
|
||||
|
||||
case "transcribe-complete":
|
||||
this.worker?.removeEventListener("message", handleMessage);
|
||||
resolve({
|
||||
text: response.text,
|
||||
segments: response.segments,
|
||||
language,
|
||||
});
|
||||
break;
|
||||
|
||||
case "transcribe-error":
|
||||
this.worker?.removeEventListener("message", handleMessage);
|
||||
reject(new Error(response.error));
|
||||
break;
|
||||
|
||||
case "cancelled":
|
||||
this.worker?.removeEventListener("message", handleMessage);
|
||||
reject(new Error("Transcription cancelled"));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
this.worker.addEventListener("message", handleMessage);
|
||||
|
||||
this.worker.postMessage({
|
||||
type: "transcribe",
|
||||
audio: audioData,
|
||||
language,
|
||||
} satisfies WorkerMessage);
|
||||
});
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.worker?.postMessage({ type: "cancel" } satisfies WorkerMessage);
|
||||
}
|
||||
|
||||
private async ensureWorker({
|
||||
modelId,
|
||||
onProgress,
|
||||
}: {
|
||||
modelId: TranscriptionModelId;
|
||||
onProgress?: ProgressCallback;
|
||||
}): Promise<void> {
|
||||
const needsNewModel = this.currentModelId !== modelId;
|
||||
|
||||
if (this.worker && this.isInitialized && !needsNewModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isInitializing && !needsNewModel) {
|
||||
await this.waitForInit();
|
||||
return;
|
||||
}
|
||||
|
||||
this.terminate();
|
||||
this.isInitializing = true;
|
||||
this.isInitialized = false;
|
||||
|
||||
const model = TRANSCRIPTION_MODELS.find((m) => m.id === modelId);
|
||||
if (!model) {
|
||||
throw new Error(`Unknown model: ${modelId}`);
|
||||
}
|
||||
|
||||
this.worker = new Worker(new URL("./worker.ts", import.meta.url), {
|
||||
type: "module",
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.worker) {
|
||||
reject(new Error("Failed to create worker"));
|
||||
return;
|
||||
}
|
||||
|
||||
const handleMessage = (event: MessageEvent<WorkerResponse>) => {
|
||||
const response = event.data;
|
||||
|
||||
switch (response.type) {
|
||||
case "init-progress":
|
||||
onProgress?.({
|
||||
status: "loading-model",
|
||||
progress: response.progress,
|
||||
message: `Loading ${model.name} model...`,
|
||||
});
|
||||
break;
|
||||
|
||||
case "init-complete":
|
||||
this.worker?.removeEventListener("message", handleMessage);
|
||||
this.isInitialized = true;
|
||||
this.isInitializing = false;
|
||||
this.currentModelId = modelId;
|
||||
resolve();
|
||||
break;
|
||||
|
||||
case "init-error":
|
||||
this.worker?.removeEventListener("message", handleMessage);
|
||||
this.isInitializing = false;
|
||||
this.terminate();
|
||||
reject(new Error(response.error));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
this.worker.addEventListener("message", handleMessage);
|
||||
|
||||
this.worker.postMessage({
|
||||
type: "init",
|
||||
modelId: model.huggingFaceId,
|
||||
} satisfies WorkerMessage);
|
||||
});
|
||||
}
|
||||
|
||||
private waitForInit(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const checkInit = () => {
|
||||
if (this.isInitialized) {
|
||||
resolve();
|
||||
} else if (!this.isInitializing) {
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(checkInit, 100);
|
||||
}
|
||||
};
|
||||
checkInit();
|
||||
});
|
||||
}
|
||||
|
||||
terminate() {
|
||||
this.worker?.terminate();
|
||||
this.worker = null;
|
||||
this.isInitialized = false;
|
||||
this.isInitializing = false;
|
||||
this.currentModelId = null;
|
||||
}
|
||||
}
|
||||
|
||||
export const transcriptionService = new TranscriptionService();
|
||||
|
||||
@@ -1,176 +1,176 @@
|
||||
import {
|
||||
pipeline,
|
||||
type AutomaticSpeechRecognitionPipeline,
|
||||
type AutomaticSpeechRecognitionOutput,
|
||||
} from "@huggingface/transformers";
|
||||
import type { TranscriptionSegment } from "@/lib/transcription/types";
|
||||
import {
|
||||
DEFAULT_CHUNK_LENGTH_SECONDS,
|
||||
DEFAULT_STRIDE_SECONDS,
|
||||
} from "@/constants/transcription-constants";
|
||||
|
||||
export type WorkerMessage =
|
||||
| { type: "init"; modelId: string }
|
||||
| { type: "transcribe"; audio: Float32Array; language: string }
|
||||
| { type: "cancel" };
|
||||
|
||||
export type WorkerResponse =
|
||||
| { type: "init-progress"; progress: number }
|
||||
| { type: "init-complete" }
|
||||
| { type: "init-error"; error: string }
|
||||
| { type: "transcribe-progress"; progress: number }
|
||||
| {
|
||||
type: "transcribe-complete";
|
||||
text: string;
|
||||
segments: TranscriptionSegment[];
|
||||
}
|
||||
| { type: "transcribe-error"; error: string }
|
||||
| { type: "cancelled" };
|
||||
|
||||
let transcriber: AutomaticSpeechRecognitionPipeline | null = null;
|
||||
let cancelled = false;
|
||||
let lastReportedProgress = -1;
|
||||
const fileBytes = new Map<string, { loaded: number; total: number }>();
|
||||
|
||||
self.onmessage = async (event: MessageEvent<WorkerMessage>) => {
|
||||
const message = event.data;
|
||||
|
||||
switch (message.type) {
|
||||
case "init":
|
||||
await handleInit({ modelId: message.modelId });
|
||||
break;
|
||||
case "transcribe":
|
||||
await handleTranscribe({
|
||||
audio: message.audio,
|
||||
language: message.language,
|
||||
});
|
||||
break;
|
||||
case "cancel":
|
||||
cancelled = true;
|
||||
self.postMessage({ type: "cancelled" } satisfies WorkerResponse);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
async function handleInit({ modelId }: { modelId: string }) {
|
||||
lastReportedProgress = -1;
|
||||
fileBytes.clear();
|
||||
|
||||
try {
|
||||
transcriber = (await pipeline("automatic-speech-recognition", modelId, {
|
||||
dtype: "q4",
|
||||
device: "auto",
|
||||
progress_callback: (progressInfo: {
|
||||
status?: string;
|
||||
file?: string;
|
||||
loaded?: number;
|
||||
total?: number;
|
||||
}) => {
|
||||
const file = progressInfo.file;
|
||||
if (!file) return;
|
||||
|
||||
const loaded = progressInfo.loaded ?? 0;
|
||||
const total = progressInfo.total ?? 0;
|
||||
|
||||
if (progressInfo.status === "progress" && total > 0) {
|
||||
fileBytes.set(file, { loaded, total });
|
||||
} else if (progressInfo.status === "done") {
|
||||
const existing = fileBytes.get(file);
|
||||
if (existing) {
|
||||
fileBytes.set(file, {
|
||||
loaded: existing.total,
|
||||
total: existing.total,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// sum all bytes
|
||||
let totalLoaded = 0;
|
||||
let totalSize = 0;
|
||||
for (const { loaded, total } of fileBytes.values()) {
|
||||
totalLoaded += loaded;
|
||||
totalSize += total;
|
||||
}
|
||||
|
||||
if (totalSize === 0) return;
|
||||
|
||||
const overallProgress = (totalLoaded / totalSize) * 100;
|
||||
const roundedProgress = Math.floor(overallProgress);
|
||||
|
||||
if (roundedProgress !== lastReportedProgress) {
|
||||
lastReportedProgress = roundedProgress;
|
||||
self.postMessage({
|
||||
type: "init-progress",
|
||||
progress: roundedProgress,
|
||||
} satisfies WorkerResponse);
|
||||
}
|
||||
},
|
||||
})) as unknown as AutomaticSpeechRecognitionPipeline;
|
||||
|
||||
self.postMessage({ type: "init-complete" } satisfies WorkerResponse);
|
||||
} catch (error) {
|
||||
self.postMessage({
|
||||
type: "init-error",
|
||||
error: error instanceof Error ? error.message : "Failed to load model",
|
||||
} satisfies WorkerResponse);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTranscribe({
|
||||
audio,
|
||||
language,
|
||||
}: {
|
||||
audio: Float32Array;
|
||||
language: string;
|
||||
}) {
|
||||
if (!transcriber) {
|
||||
self.postMessage({
|
||||
type: "transcribe-error",
|
||||
error: "Model not initialized",
|
||||
} satisfies WorkerResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
cancelled = false;
|
||||
|
||||
try {
|
||||
const rawResult = await transcriber(audio, {
|
||||
chunk_length_s: DEFAULT_CHUNK_LENGTH_SECONDS,
|
||||
stride_length_s: DEFAULT_STRIDE_SECONDS,
|
||||
language: language === "auto" ? undefined : language,
|
||||
return_timestamps: true,
|
||||
});
|
||||
|
||||
if (cancelled) return;
|
||||
|
||||
const result: AutomaticSpeechRecognitionOutput = Array.isArray(rawResult)
|
||||
? rawResult[0]
|
||||
: rawResult;
|
||||
|
||||
const segments: TranscriptionSegment[] = [];
|
||||
|
||||
if (result.chunks) {
|
||||
for (const chunk of result.chunks) {
|
||||
if (chunk.timestamp && chunk.timestamp.length >= 2) {
|
||||
segments.push({
|
||||
text: chunk.text,
|
||||
start: chunk.timestamp[0] ?? 0,
|
||||
end: chunk.timestamp[1] ?? chunk.timestamp[0] ?? 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.postMessage({
|
||||
type: "transcribe-complete",
|
||||
text: result.text,
|
||||
segments,
|
||||
} satisfies WorkerResponse);
|
||||
} catch (error) {
|
||||
if (cancelled) return;
|
||||
self.postMessage({
|
||||
type: "transcribe-error",
|
||||
error: error instanceof Error ? error.message : "Transcription failed",
|
||||
} satisfies WorkerResponse);
|
||||
}
|
||||
}
|
||||
import {
|
||||
pipeline,
|
||||
type AutomaticSpeechRecognitionPipeline,
|
||||
type AutomaticSpeechRecognitionOutput,
|
||||
} from "@huggingface/transformers";
|
||||
import type { TranscriptionSegment } from "@/lib/transcription/types";
|
||||
import {
|
||||
DEFAULT_CHUNK_LENGTH_SECONDS,
|
||||
DEFAULT_STRIDE_SECONDS,
|
||||
} from "@/constants/transcription-constants";
|
||||
|
||||
export type WorkerMessage =
|
||||
| { type: "init"; modelId: string }
|
||||
| { type: "transcribe"; audio: Float32Array; language: string }
|
||||
| { type: "cancel" };
|
||||
|
||||
export type WorkerResponse =
|
||||
| { type: "init-progress"; progress: number }
|
||||
| { type: "init-complete" }
|
||||
| { type: "init-error"; error: string }
|
||||
| { type: "transcribe-progress"; progress: number }
|
||||
| {
|
||||
type: "transcribe-complete";
|
||||
text: string;
|
||||
segments: TranscriptionSegment[];
|
||||
}
|
||||
| { type: "transcribe-error"; error: string }
|
||||
| { type: "cancelled" };
|
||||
|
||||
let transcriber: AutomaticSpeechRecognitionPipeline | null = null;
|
||||
let cancelled = false;
|
||||
let lastReportedProgress = -1;
|
||||
const fileBytes = new Map<string, { loaded: number; total: number }>();
|
||||
|
||||
self.onmessage = async (event: MessageEvent<WorkerMessage>) => {
|
||||
const message = event.data;
|
||||
|
||||
switch (message.type) {
|
||||
case "init":
|
||||
await handleInit({ modelId: message.modelId });
|
||||
break;
|
||||
case "transcribe":
|
||||
await handleTranscribe({
|
||||
audio: message.audio,
|
||||
language: message.language,
|
||||
});
|
||||
break;
|
||||
case "cancel":
|
||||
cancelled = true;
|
||||
self.postMessage({ type: "cancelled" } satisfies WorkerResponse);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
async function handleInit({ modelId }: { modelId: string }) {
|
||||
lastReportedProgress = -1;
|
||||
fileBytes.clear();
|
||||
|
||||
try {
|
||||
transcriber = (await pipeline("automatic-speech-recognition", modelId, {
|
||||
dtype: "q4",
|
||||
device: "auto",
|
||||
progress_callback: (progressInfo: {
|
||||
status?: string;
|
||||
file?: string;
|
||||
loaded?: number;
|
||||
total?: number;
|
||||
}) => {
|
||||
const file = progressInfo.file;
|
||||
if (!file) return;
|
||||
|
||||
const loaded = progressInfo.loaded ?? 0;
|
||||
const total = progressInfo.total ?? 0;
|
||||
|
||||
if (progressInfo.status === "progress" && total > 0) {
|
||||
fileBytes.set(file, { loaded, total });
|
||||
} else if (progressInfo.status === "done") {
|
||||
const existing = fileBytes.get(file);
|
||||
if (existing) {
|
||||
fileBytes.set(file, {
|
||||
loaded: existing.total,
|
||||
total: existing.total,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// sum all bytes
|
||||
let totalLoaded = 0;
|
||||
let totalSize = 0;
|
||||
for (const { loaded, total } of fileBytes.values()) {
|
||||
totalLoaded += loaded;
|
||||
totalSize += total;
|
||||
}
|
||||
|
||||
if (totalSize === 0) return;
|
||||
|
||||
const overallProgress = (totalLoaded / totalSize) * 100;
|
||||
const roundedProgress = Math.floor(overallProgress);
|
||||
|
||||
if (roundedProgress !== lastReportedProgress) {
|
||||
lastReportedProgress = roundedProgress;
|
||||
self.postMessage({
|
||||
type: "init-progress",
|
||||
progress: roundedProgress,
|
||||
} satisfies WorkerResponse);
|
||||
}
|
||||
},
|
||||
})) as unknown as AutomaticSpeechRecognitionPipeline;
|
||||
|
||||
self.postMessage({ type: "init-complete" } satisfies WorkerResponse);
|
||||
} catch (error) {
|
||||
self.postMessage({
|
||||
type: "init-error",
|
||||
error: error instanceof Error ? error.message : "Failed to load model",
|
||||
} satisfies WorkerResponse);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTranscribe({
|
||||
audio,
|
||||
language,
|
||||
}: {
|
||||
audio: Float32Array;
|
||||
language: string;
|
||||
}) {
|
||||
if (!transcriber) {
|
||||
self.postMessage({
|
||||
type: "transcribe-error",
|
||||
error: "Model not initialized",
|
||||
} satisfies WorkerResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
cancelled = false;
|
||||
|
||||
try {
|
||||
const rawResult = await transcriber(audio, {
|
||||
chunk_length_s: DEFAULT_CHUNK_LENGTH_SECONDS,
|
||||
stride_length_s: DEFAULT_STRIDE_SECONDS,
|
||||
language: language === "auto" ? undefined : language,
|
||||
return_timestamps: true,
|
||||
});
|
||||
|
||||
if (cancelled) return;
|
||||
|
||||
const result: AutomaticSpeechRecognitionOutput = Array.isArray(rawResult)
|
||||
? rawResult[0]
|
||||
: rawResult;
|
||||
|
||||
const segments: TranscriptionSegment[] = [];
|
||||
|
||||
if (result.chunks) {
|
||||
for (const chunk of result.chunks) {
|
||||
if (chunk.timestamp && chunk.timestamp.length >= 2) {
|
||||
segments.push({
|
||||
text: chunk.text,
|
||||
start: chunk.timestamp[0] ?? 0,
|
||||
end: chunk.timestamp[1] ?? chunk.timestamp[0] ?? 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.postMessage({
|
||||
type: "transcribe-complete",
|
||||
text: result.text,
|
||||
segments,
|
||||
} satisfies WorkerResponse);
|
||||
} catch (error) {
|
||||
if (cancelled) return;
|
||||
self.postMessage({
|
||||
type: "transcribe-error",
|
||||
error: error instanceof Error ? error.message : "Transcription failed",
|
||||
} satisfies WorkerResponse);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user