{"version":3,"file":"transformers_asr.mjs","names":[],"sources":["../../../../src/batteries/media/engines/transformers_asr.ts"],"sourcesContent":["/**\n * A cross-environment transcription {@link @nhtio/adk/batteries/media/contracts!MediaEngine}\n * backed by `@huggingface/transformers` Whisper (ONNX in Node, WASM/WebGPU in browsers).\n *\n * @module @nhtio/adk/batteries/media/engines/transformers_asr\n *\n * @remarks\n * The local-by-default speech-to-text engine: no external API, no binary. Declares one\n * convert capability: the virtual `pcm` token to `txt`/`srt`/`vtt`/`json` (transcription is a\n * format conversion — PCM in, text out). The Whisper model id is REQUIRED — models are\n * multi-hundred-megabyte downloads on first use and the battery never triggers one silently\n * (the loud-config rule). Input is 16 kHz mono PCM, exactly what the transcribe step\n * supplies; `lang`/`translate` ride `request.options`. `srt`/`vtt` output is assembled from\n * chunk timestamps; `json` returns the chunk structure verbatim.\n *\n * `@huggingface/transformers` is an optional peer dependency, lazily imported on first\n * actual use.\n */\n\nimport { isError } from '@nhtio/adk/guards'\nimport { bytesToPcm, PCM_MIME } from '../contracts'\nimport { E_INVALID_MEDIA_PIPELINE_CONFIG } from '../exceptions'\nimport type * as TransformersNS from '@huggingface/transformers'\nimport type { MediaEngine, ConvertRequest, ConvertResult } from '../contracts'\n\ntype TransformersModule = typeof TransformersNS\n\ninterface AsrChunk {\n  timestamp: [number, number | null]\n  text: string\n}\n\ninterface AsrPipelineOutput {\n  text: string\n  chunks?: AsrChunk[]\n}\n\ntype AsrPipelineFn = (\n  audio: Float32Array,\n  options: Record<string, unknown>\n) => Promise<AsrPipelineOutput | AsrPipelineOutput[]>\n\n/** Options for {@link transformersAsrEngine}. */\nexport interface TransformersAsrEngineOptions {\n  /**\n   * The Whisper model id, e.g. `onnx-community/whisper-base`. REQUIRED — no default, no\n   * surprise downloads.\n   */\n  model: string\n  /** Override the module resolution. Default: `import('@huggingface/transformers')`. */\n  transformers?: () => TransformersModule | Promise<TransformersModule>\n  /** Extra options forwarded to `pipeline()` (device, dtype, cache_dir…). */\n  pipelineOptions?: Record<string, unknown>\n}\n\nconst pad = (n: number, width = 2): string => String(Math.floor(n)).padStart(width, '0')\n\nconst formatTimestamp = (seconds: number, separator: ',' | '.'): string => {\n  const h = Math.floor(seconds / 3600)\n  const m = Math.floor((seconds % 3600) / 60)\n  const s = Math.floor(seconds % 60)\n  const ms = Math.floor((seconds % 1) * 1000)\n  return `${pad(h)}:${pad(m)}:${pad(s)}${separator}${String(ms).padStart(3, '0')}`\n}\n\nconst toSubtitles = (chunks: AsrChunk[], kind: 'srt' | 'vtt'): string => {\n  const sep = kind === 'srt' ? ',' : '.'\n  const blocks = chunks.map((chunk, i) => {\n    const [start, end] = chunk.timestamp\n    const range = `${formatTimestamp(start, sep)} --> ${formatTimestamp(end ?? start, sep)}`\n    return kind === 'srt'\n      ? `${i + 1}\\n${range}\\n${chunk.text.trim()}`\n      : `${range}\\n${chunk.text.trim()}`\n  })\n  return kind === 'vtt' ? `WEBVTT\\n\\n${blocks.join('\\n\\n')}\\n` : `${blocks.join('\\n\\n')}\\n`\n}\n\n/**\n * Construct the transformers.js Whisper ASR engine.\n *\n * @param options - Model id (required) and overrides.\n * @returns The engine.\n */\nexport const transformersAsrEngine = (options: TransformersAsrEngineOptions): MediaEngine => {\n  if (typeof options?.model !== 'string' || options.model.length === 0) {\n    throw new E_INVALID_MEDIA_PIPELINE_CONFIG([\n      'transformersAsrEngine requires an explicit Whisper model id (e.g. onnx-community/whisper-base) — models are large downloads and are never chosen silently',\n    ])\n  }\n  let pipePromise: Promise<AsrPipelineFn> | undefined\n  const getPipeline = (): Promise<AsrPipelineFn> => {\n    pipePromise ??= (async () => {\n      let mod: TransformersModule\n      try {\n        mod = await (options.transformers\n          ? options.transformers()\n          : import('@huggingface/transformers'))\n      } catch (err) {\n        const detail = isError(err) ? err.message : String(err)\n        throw new E_INVALID_MEDIA_PIPELINE_CONFIG([\n          `the transformers ASR engine could not load its peer dependency \"@huggingface/transformers\": ${detail} — install it (pnpm add @huggingface/transformers)`,\n        ])\n      }\n      const pipe = await mod.pipeline('automatic-speech-recognition', options.model, {\n        ...(options.pipelineOptions ?? {}),\n      } as never)\n      return pipe as unknown as AsrPipelineFn\n    })()\n    return pipePromise\n  }\n\n  const convert = async (request: ConvertRequest): Promise<ConvertResult> => {\n    const pipe = await getPipeline()\n    const lang = request.options?.lang\n    const translate = request.options?.translate\n    const wantsTimestamps = request.to === 'srt' || request.to === 'vtt' || request.to === 'json'\n    const result = await pipe(bytesToPcm(request.bytes), {\n      ...(lang ? { language: lang } : {}),\n      ...(translate ? { task: 'translate' } : {}),\n      ...(wantsTimestamps ? { return_timestamps: true } : {}),\n      chunk_length_s: 30,\n    })\n    const output = Array.isArray(result) ? result[0] : result\n    let text: string\n    let mimeType = 'text/plain'\n    if (request.to === 'json') {\n      text = JSON.stringify({ text: output.text, chunks: output.chunks ?? [] })\n      mimeType = 'application/json'\n    } else if (request.to === 'srt' || request.to === 'vtt') {\n      const chunks = output.chunks ?? [\n        { timestamp: [0, null] as [number, null], text: output.text },\n      ]\n      text = toSubtitles(chunks, request.to)\n    } else {\n      text = output.text.trim()\n    }\n    return { outputs: [{ bytes: new TextEncoder().encode(text), mimeType }] }\n  }\n\n  return {\n    id: 'transformers-asr',\n    converts: [\n      {\n        from: [PCM_MIME],\n        to: ['txt', 'srt', 'vtt', 'json'],\n        convert,\n      },\n    ],\n  }\n}\n"],"mappings":"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"}