this repo has no description
1/**
2 * Audio file utilities: voice IDs, filename conventions, and file listing.
3 */
4
5import { join } from '@std/path'
6import { Locale } from '$/enums.ts'
7
8/** Azure Neural TTS voice ID used per locale. */
9export const VOICE_IDS: Record<Locale, string> = {
10 [Locale.zh_CN]: 'zh-CN-XiaoxiaoNeural',
11 [Locale.zh_HK]: 'zh-HK-WanLungNeural',
12 [Locale.zh_TW]: 'zh-TW-YunJheNeural',
13 [Locale.ja]: 'ja-JP-NanamiNeural',
14}
15
16/**
17 * Returns the audio filename for a given subject id and locale.
18 * When col and reading are both provided, appends `_{col}_{reading}` to disambiguate
19 * subjects with multiple readings (e.g. polyphonic characters).
20 */
21export function getFilename(id: string, locale: Locale, col?: string, reading?: string): string {
22 const base = `${id}_${locale.replace('_', '-')}_${VOICE_IDS[locale]}`
23 if (col && reading) return `${base}_${col}_${reading}.mp3`
24 return `${base}.mp3`
25}
26
27/**
28 * Parses an audio filename into its components.
29 * Handles both formats:
30 * {id}_{locale}_{voiceId}.mp3
31 * {id}_{locale}_{voiceId}_{col}_{reading}.mp3
32 * Returns null if the filename doesn't match.
33 */
34export function parseAudioFilename(
35 filename: string,
36): { id: string; locale: string; voiceId: string; col?: string; reading?: string } | null {
37 const parts = filename.replace('.mp3', '').split('_')
38 if (parts.length < 3) return null
39 const [id, localeHyphen, voiceId, col, reading] = parts
40 const locale = localeHyphen.replace('-', '_')
41 return { id, locale, voiceId, col, reading }
42}
43
44/**
45 * Returns all existing audio filenames (not full paths) for the given locales.
46 * Skips locales whose audio directories don't exist yet (e.g. before first audio run).
47 */
48export function listAudioFiles(locales: string[] = ['zh_CN', 'zh_HK', 'zh_TW', 'ja']): string[] {
49 return locales
50 .filter((locale) => locale !== 'tmp')
51 .flatMap((locale) => {
52 try {
53 return Array.from(Deno.readDirSync(join('www/static/gen/audio', locale)))
54 } catch {
55 return []
56 }
57 })
58 .filter(({ name }) => /.*\.mp3$/.test(name))
59 .map((file) => file.name)
60}