echogarden 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/data/tables/lcid-table.json +7427 -7427
  2. package/dist/alignment/TextAlignment.d.ts +16 -0
  3. package/dist/alignment/TextAlignment.js +63 -0
  4. package/dist/alignment/TextAlignment.js.map +1 -0
  5. package/dist/api/Alignment.js +11 -4
  6. package/dist/api/Alignment.js.map +1 -1
  7. package/dist/api/Denoising.js +0 -1
  8. package/dist/api/Denoising.js.map +1 -1
  9. package/dist/api/LanguageDetection.js +2 -2
  10. package/dist/api/LanguageDetection.js.map +1 -1
  11. package/dist/api/Recognition.js +15 -11
  12. package/dist/api/Recognition.js.map +1 -1
  13. package/dist/api/Synthesis.js +11 -15
  14. package/dist/api/Synthesis.js.map +1 -1
  15. package/dist/api/Translation.js +24 -15
  16. package/dist/api/Translation.js.map +1 -1
  17. package/dist/api/TranslationAlignment.js +12 -5
  18. package/dist/api/TranslationAlignment.js.map +1 -1
  19. package/dist/api/VoiceActivityDetection.d.ts +0 -4
  20. package/dist/api/VoiceActivityDetection.js +43 -20
  21. package/dist/api/VoiceActivityDetection.js.map +1 -1
  22. package/dist/audio/AudioUtilities.d.ts +1 -1
  23. package/dist/audio/AudioUtilities.js +2 -2
  24. package/dist/audio/AudioUtilities.js.map +1 -1
  25. package/dist/cli/CLILauncher.js +1 -2
  26. package/dist/cli/CLILauncher.js.map +1 -1
  27. package/dist/codecs/FFMpegTranscoder.js +4 -1
  28. package/dist/codecs/FFMpegTranscoder.js.map +1 -1
  29. package/dist/codecs/WaveCodec.d.ts +1 -1
  30. package/dist/codecs/WaveCodec.js +13 -5
  31. package/dist/codecs/WaveCodec.js.map +1 -1
  32. package/dist/dsp/SpeexResampler.js +49 -21
  33. package/dist/dsp/SpeexResampler.js.map +1 -1
  34. package/dist/recognition/WhisperSTT.js +4 -4
  35. package/dist/recognition/WhisperSTT.js.map +1 -1
  36. package/dist/synthesis/CoquiServerTTS.js +1 -1
  37. package/dist/synthesis/CoquiServerTTS.js.map +1 -1
  38. package/dist/synthesis/VitsTTS.js +1 -1
  39. package/dist/synthesis/VitsTTS.js.map +1 -1
  40. package/dist/text-translation/NLLBTextTranslation.d.ts +1 -0
  41. package/dist/text-translation/NLLBTextTranslation.js +237 -0
  42. package/dist/text-translation/NLLBTextTranslation.js.map +1 -0
  43. package/dist/utilities/Locale.d.ts +14 -8
  44. package/dist/utilities/Locale.js +52 -11
  45. package/dist/utilities/Locale.js.map +1 -1
  46. package/dist/utilities/PackageManager.js +2 -0
  47. package/dist/utilities/PackageManager.js.map +1 -1
  48. package/docs/Development.md +1 -1
  49. package/docs/Server.md +1 -1
  50. package/docs/Tasklist.md +11 -6
  51. package/package.json +12 -11
  52. package/src/alignment/TextAlignment.ts +96 -0
  53. package/src/api/Alignment.ts +19 -4
  54. package/src/api/Denoising.ts +0 -2
  55. package/src/api/LanguageDetection.ts +2 -3
  56. package/src/api/Recognition.ts +18 -13
  57. package/src/api/Synthesis.ts +13 -21
  58. package/src/api/Translation.ts +29 -22
  59. package/src/api/TranslationAlignment.ts +17 -6
  60. package/src/api/VoiceActivityDetection.ts +56 -22
  61. package/src/audio/AudioUtilities.ts +2 -2
  62. package/src/cli/CLILauncher.ts +1 -2
  63. package/src/codecs/FFMpegTranscoder.ts +5 -1
  64. package/src/codecs/WaveCodec.ts +15 -5
  65. package/src/dsp/SpeexResampler.ts +62 -23
  66. package/src/recognition/WhisperSTT.ts +4 -4
  67. package/src/synthesis/CoquiServerTTS.ts +1 -1
  68. package/src/synthesis/VitsTTS.ts +2 -2
  69. package/src/text-translation/NLLBTextTranslation.ts +252 -0
  70. package/src/utilities/Locale.ts +84 -22
  71. package/src/utilities/PackageManager.ts +3 -0
@@ -18,18 +18,20 @@ export function encodeWave(rawAudio: RawAudio, bitDepth: BitDepth = 16, sampleFo
18
18
 
19
19
  const dataSubChunkBuffer = Buffer.alloc(4 + 4 + audioDataLength)
20
20
  dataSubChunkBuffer.write('data', 0, 'ascii')
21
- dataSubChunkBuffer.writeUint32LE(audioDataLength, 4)
21
+ const dataChunkLength = Math.min(audioDataLength, 4294967295) // Ensure large data chunk length is clipped to max
22
+ dataSubChunkBuffer.writeUint32LE(dataChunkLength, 4)
22
23
  dataSubChunkBuffer.set(audioBuffer, 8)
23
24
 
24
25
  const riffChunkHeaderBuffer = Buffer.alloc(12)
25
26
  riffChunkHeaderBuffer.write('RIFF', 0, 'ascii')
26
- riffChunkHeaderBuffer.writeUint32LE(4 + formatSubChunkBuffer.length + dataSubChunkBuffer.length, 4)
27
+ const riffChunkLength = Math.min(4 + formatSubChunkBuffer.length + dataSubChunkBuffer.length, 4294967295) // Ensure large RIFF chunk length is clipped to max
28
+ riffChunkHeaderBuffer.writeUint32LE(riffChunkLength, 4)
27
29
  riffChunkHeaderBuffer.write('WAVE', 8, 'ascii')
28
30
 
29
31
  return Buffer.concat([riffChunkHeaderBuffer, formatSubChunkBuffer, dataSubChunkBuffer])
30
32
  }
31
33
 
32
- export function decodeWave(waveData: Buffer, ignoreTruncatedChunks = false) {
34
+ export function decodeWave(waveData: Buffer, ignoreTruncatedChunks = true, ignoreOverflowingDataChunks = true) {
33
35
  let readOffset = 0
34
36
 
35
37
  const riffId = waveData.subarray(readOffset, readOffset + 4).toString('ascii')
@@ -40,7 +42,7 @@ export function decodeWave(waveData: Buffer, ignoreTruncatedChunks = false) {
40
42
 
41
43
  readOffset += 4
42
44
 
43
- const riffChunkSize = waveData.readUInt32LE(readOffset)
45
+ let riffChunkSize = waveData.readUInt32LE(readOffset)
44
46
 
45
47
  readOffset += 4
46
48
 
@@ -50,6 +52,10 @@ export function decodeWave(waveData: Buffer, ignoreTruncatedChunks = false) {
50
52
  throw new Error('Not a valid wave file. No WAVE id found at offset 8.')
51
53
  }
52
54
 
55
+ if (ignoreOverflowingDataChunks && riffChunkSize === 4294967295) {
56
+ riffChunkSize = waveData.length - 8
57
+ }
58
+
53
59
  if (riffChunkSize < waveData.length - 8) {
54
60
  throw new Error(`RIFF chunk length ${riffChunkSize} is smaller than the remaining size of the buffer (${waveData.length - 8})`)
55
61
  }
@@ -67,7 +73,7 @@ export function decodeWave(waveData: Buffer, ignoreTruncatedChunks = false) {
67
73
  const subChunkIdentifier = waveData.subarray(readOffset, readOffset + 4).toString('ascii')
68
74
  readOffset += 4
69
75
 
70
- const subChunkSize = waveData.readUInt32LE(readOffset)
76
+ let subChunkSize = waveData.readUInt32LE(readOffset)
71
77
  readOffset += 4
72
78
 
73
79
  if (!ignoreTruncatedChunks && subChunkSize > waveData.length - readOffset) {
@@ -81,6 +87,10 @@ export function decodeWave(waveData: Buffer, ignoreTruncatedChunks = false) {
81
87
  throw new Error('A data subchunk was encountered before a format subchunk')
82
88
  }
83
89
 
90
+ if (ignoreOverflowingDataChunks && subChunkSize === 4294967295) {
91
+ subChunkSize = waveData.length - readOffset
92
+ }
93
+
84
94
  // If the data chunk is truncated, but truncations are ignored,
85
95
  // it would be read up to the end of the buffer
86
96
  dataBuffers.push(waveData.subarray(readOffset, readOffset + subChunkSize))
@@ -1,16 +1,27 @@
1
1
  import { RawAudio, cloneRawAudio } from '../audio/AudioUtilities.js'
2
+ import { concatFloat32Arrays } from '../utilities/Utilities.js'
2
3
  import { WasmMemoryManager } from '../utilities/WasmMemoryManager.js'
3
4
 
4
5
  let speexResamplerInstance: any
5
6
 
6
- export async function resampleAudioSpeex(rawAudio: RawAudio, outSampleRate: number, quality = 0) {
7
+ export async function resampleAudioSpeex(rawAudio: RawAudio, outSampleRate: number, quality = 0): Promise<RawAudio> {
7
8
  const channelCount = rawAudio.audioChannels.length
8
9
  const inSampleRate = rawAudio.sampleRate
9
10
 
10
- if (inSampleRate == outSampleRate) {
11
+ const totalSampleCount = rawAudio.audioChannels[0].length
12
+ const sampleRateRatio = outSampleRate / inSampleRate
13
+
14
+ if (inSampleRate === outSampleRate) {
11
15
  return cloneRawAudio(rawAudio)
12
16
  }
13
17
 
18
+ if (totalSampleCount === 0) {
19
+ return {
20
+ ...cloneRawAudio(rawAudio),
21
+ sampleRate: outSampleRate
22
+ } as RawAudio
23
+ }
24
+
14
25
  const m = await getSpeexResamplerInstance()
15
26
  const wasmMemory = new WasmMemoryManager(m)
16
27
 
@@ -23,48 +34,76 @@ export async function resampleAudioSpeex(rawAudio: RawAudio, outSampleRate: numb
23
34
  }
24
35
 
25
36
  const initErrRef = wasmMemory.allocInt32()
26
- const resamplerState = m._speex_resampler_init(channelCount, inSampleRate, outSampleRate, quality, initErrRef.address)
37
+ const resamplerStateAddress = m._speex_resampler_init(channelCount, inSampleRate, outSampleRate, quality, initErrRef.address)
27
38
  let resultCode = initErrRef.value
28
39
 
29
40
  if (resultCode != 0) {
30
41
  throw new Error(`Speex resampler failed while initializing with code ${resultCode}: ${speexResultCodeToString(resultCode)}`)
31
42
  }
32
43
 
33
- const resampledAudio: RawAudio = { audioChannels: [], sampleRate: outSampleRate }
44
+ const inputLatency = m._speex_resampler_get_input_latency(resamplerStateAddress)
45
+ const outputLatency = m._speex_resampler_get_output_latency(resamplerStateAddress)
46
+
47
+ const maxChunkSize = 2 ** 20
48
+
49
+ const inputChunkSampleCountRef = wasmMemory.allocInt32()
50
+ const outputChunkSampleCountRef = wasmMemory.allocInt32()
51
+
52
+ const inputChunkSamplesRef = wasmMemory.allocFloat32Array(maxChunkSize * 2)
53
+ const outputChunkSamplesRef = wasmMemory.allocFloat32Array(Math.floor(maxChunkSize * sampleRateRatio) * 2)
54
+
55
+ const resampledAudioChunksForChannels: Float32Array[][] = []
56
+
57
+ for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
58
+ resampledAudioChunksForChannels.push([])
59
+ }
34
60
 
35
61
  for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
36
- const channelSamples = rawAudio.audioChannels[channelIndex]
62
+ for (let readOffset = 0; readOffset < totalSampleCount;) {
63
+ const isLastChunk = readOffset + maxChunkSize >= totalSampleCount
37
64
 
38
- const inputLatency = m._speex_resampler_get_input_latency(resamplerState)
39
- const outputLatency = m._speex_resampler_get_output_latency(resamplerState)
65
+ const inputPaddingSize = isLastChunk ? inputLatency : 0
66
+ const maxSamplesToRead = Math.min(maxChunkSize, totalSampleCount - readOffset) + inputPaddingSize
40
67
 
41
- const inSampleCount = channelSamples.length
42
- const inSampleCountRef = wasmMemory.allocInt32()
43
- inSampleCountRef.value = inSampleCount + inputLatency
68
+ const maxSamplesToWrite = outputChunkSamplesRef.length
44
69
 
45
- const inSamplesRef = wasmMemory.allocFloat32Array(inSampleCountRef.value)
46
- inSamplesRef.view.set(channelSamples)
70
+ const inputChunkSamplesForChannel = rawAudio.audioChannels[channelIndex].slice(readOffset, readOffset + maxSamplesToRead)
47
71
 
48
- const outSampleCount = Math.floor((inSampleCount / inSampleRate) * outSampleRate)
49
- const outSampleCountRef = wasmMemory.allocInt32()
50
- outSampleCountRef.value = outSampleCount + outputLatency
72
+ inputChunkSampleCountRef.value = maxSamplesToRead
73
+ outputChunkSampleCountRef.value = maxSamplesToWrite
51
74
 
52
- const outSamplesRef = wasmMemory.allocFloat32Array(outSampleCountRef.value)
75
+ inputChunkSamplesRef.view.set(inputChunkSamplesForChannel)
76
+ resultCode = m._speex_resampler_process_float(resamplerStateAddress, channelIndex, inputChunkSamplesRef.address, inputChunkSampleCountRef.address, outputChunkSamplesRef.address, outputChunkSampleCountRef.address)
53
77
 
54
- resultCode = m._speex_resampler_process_float(resamplerState, channelIndex, inSamplesRef.address, inSampleCountRef.address, outSamplesRef.address, outSampleCountRef.address)
78
+ if (resultCode != 0) {
79
+ throw new Error(`Speex resampler failed while resampling with code ${resultCode}: ${speexResultCodeToString(resultCode)}`)
80
+ }
55
81
 
56
- if (resultCode != 0) {
57
- throw new Error(`Speex resampler failed while resampling with code ${resultCode}: ${speexResultCodeToString(resultCode)}`)
58
- }
82
+ const samplesReadCount = inputChunkSampleCountRef.value
83
+ const samplesWrittenCount = outputChunkSampleCountRef.value
84
+
85
+ const resampledChannelAudio = outputChunkSamplesRef.view.slice(0, samplesWrittenCount)
59
86
 
60
- const resampledChannelAudio = outSamplesRef.view.slice(outputLatency)
87
+ resampledAudioChunksForChannels[channelIndex].push(resampledChannelAudio)
61
88
 
62
- resampledAudio.audioChannels.push(resampledChannelAudio)
89
+ readOffset += samplesReadCount
90
+ }
63
91
  }
64
92
 
65
- m._speex_resampler_destroy(resamplerState)
93
+ m._speex_resampler_destroy(resamplerStateAddress)
66
94
  wasmMemory.freeAll()
67
95
 
96
+ const resampledAudio: RawAudio = {
97
+ audioChannels: [],
98
+ sampleRate: outSampleRate
99
+ }
100
+
101
+ for (let i = 0; i < channelCount; i++) {
102
+ resampledAudioChunksForChannels[i][0] = resampledAudioChunksForChannels[i][0].slice(outputLatency)
103
+
104
+ resampledAudio.audioChannels.push(concatFloat32Arrays(resampledAudioChunksForChannels[i]))
105
+ }
106
+
68
107
  return resampledAudio
69
108
  }
70
109
 
@@ -13,7 +13,7 @@ import { getRawAudioDuration, RawAudio, sliceRawAudio } from '../audio/AudioUtil
13
13
  import { readFile } from '../utilities/FileSystem.js'
14
14
  import path from 'path'
15
15
  import type { LanguageDetectionResults } from '../api/API.js'
16
- import { getShortLanguageCode, languageCodeToName } from '../utilities/Locale.js'
16
+ import { formatLanguageCodeWithName, getShortLanguageCode, languageCodeToName } from '../utilities/Locale.js'
17
17
  import { loadPackage } from '../utilities/PackageManager.js'
18
18
  import chalk from 'chalk'
19
19
  import { XorShift32RNG } from '../utilities/RandomGenerator.js'
@@ -41,7 +41,7 @@ export async function recognize(
41
41
  sourceLanguage = getShortLanguageCode(sourceLanguage)
42
42
 
43
43
  if (!(sourceLanguage in languageIdLookup)) {
44
- throw new Error(`The language ${languageCodeToName(sourceLanguage)} is not supported by the Whisper engine.`)
44
+ throw new Error(`The language ${formatLanguageCodeWithName(sourceLanguage)} is not supported by the Whisper engine.`)
45
45
  }
46
46
 
47
47
  if (isEnglishOnlyModel(modelName) && sourceLanguage != 'en') {
@@ -93,7 +93,7 @@ export async function align(
93
93
  sourceLanguage = getShortLanguageCode(sourceLanguage)
94
94
 
95
95
  if (!(sourceLanguage in languageIdLookup)) {
96
- throw new Error(`The language ${languageCodeToName(sourceLanguage)} is not supported by the Whisper engine.`)
96
+ throw new Error(`The language ${formatLanguageCodeWithName(sourceLanguage)} is not supported by the Whisper engine.`)
97
97
  }
98
98
 
99
99
  if (isEnglishOnlyModel(modelName) && sourceLanguage != 'en') {
@@ -134,7 +134,7 @@ export async function alignEnglishTranslation(
134
134
  sourceLanguage = getShortLanguageCode(sourceLanguage)
135
135
 
136
136
  if (!(sourceLanguage in languageIdLookup)) {
137
- throw new Error(`The source language ${languageCodeToName(sourceLanguage)} is not supported by the Whisper engine.`)
137
+ throw new Error(`The source language ${formatLanguageCodeWithName(sourceLanguage)} is not supported by the Whisper engine.`)
138
138
  }
139
139
 
140
140
  if (isEnglishOnlyModel(modelName)) {
@@ -21,7 +21,7 @@ export async function synthesize(text: string, speakerId: string | null, serverU
21
21
 
22
22
  const waveData = Buffer.from(response.data)
23
23
 
24
- const rawAudio = decodeWaveToRawAudio(waveData).rawAudio
24
+ const { rawAudio } = decodeWaveToRawAudio(waveData)
25
25
 
26
26
  logger.end()
27
27
 
@@ -52,7 +52,7 @@ export class VitsTTS {
52
52
 
53
53
  await this.initializeIfNeeded()
54
54
 
55
- await logger.startAsync('Prepare for synthesis')
55
+ await logger.startAsync('Prepare for VITS synthesis')
56
56
 
57
57
  const metadata = this.metadata
58
58
  const phonemeMap = this.phonemeMap!
@@ -78,7 +78,7 @@ export class VitsTTS {
78
78
  voice: espeakVoice,
79
79
  useKlatt: false
80
80
  }
81
-
81
+
82
82
  const { referenceSynthesizedAudio, referenceTimeline, fragments, phonemizedFragmentsSubstitutions, phonemizedSentence } = await Espeak.preprocessAndSynthesize(sentence, languageCode, espeakOptions, lexicons)
83
83
 
84
84
  if (phonemizedSentence.length == 0) {
@@ -0,0 +1,252 @@
1
+ import { Logger } from '../utilities/Logger.js'
2
+ import { loadPackage } from '../utilities/PackageManager.js'
3
+
4
+ export async function translateText(sourceText: string, sourceLanguage: string, targetLanguage: string) {
5
+ const logger = new Logger()
6
+
7
+ const { AutoTokenizer, M2M100ForConditionalGeneration } = await import('@echogarden/transformers-nodejs-lite')
8
+
9
+ const modelPath = await loadPackage(`xenova-nllb-200-distilled-600M-quantized`)
10
+
11
+ const tokenizer = await AutoTokenizer.from_pretrained(modelPath)
12
+ const model = await M2M100ForConditionalGeneration.from_pretrained(modelPath)
13
+
14
+ const config = {
15
+ src_lang: 'eng_Latn',
16
+ tgt_lang: 'fra_Latn'
17
+ }
18
+
19
+ const inputs = (tokenizer as any)._build_translation_inputs(sourceText, {
20
+ padding: true,
21
+ truncation: true,
22
+ }, config)
23
+
24
+ const result = await model.generate(inputs.input_ids, config)
25
+
26
+ logger.log(tokenizer.model.convert_ids_to_tokens(result[0]))
27
+
28
+ const inputTokens = tokenizer.model.convert_ids_to_tokens(Array.from(inputs.input_ids.data))
29
+ const embeddingResult = await model(inputs)
30
+
31
+ const lastHiddenState = embeddingResult.last_hidden_state
32
+
33
+ const tokenCount = lastHiddenState.dims[1]
34
+ const embeddingSize = lastHiddenState.dims[2]
35
+
36
+ for (let i = 0; i < tokenCount; i++) {
37
+ const tokenEmbedding = lastHiddenState.data.slice(i * embeddingSize, (i + 1) * embeddingSize)
38
+
39
+ const tokenId = inputTokens[i]
40
+
41
+ logger.log(`Token ${i} (${tokenId}):`, tokenEmbedding);
42
+ }
43
+
44
+ logger.log(inputTokens)
45
+ }
46
+
47
+ const languageNameToNLLBCode: Record<string, string> = {
48
+ 'Acehnese (Arabic script)': 'ace_Arab',
49
+ 'Acehnese (Latin script)': 'ace_Latn',
50
+ 'Afrikaans': 'afr_Latn',
51
+ 'Akan': 'aka_Latn',
52
+ 'Amharic': 'amh_Ethi',
53
+ 'Armenian': 'hye_Armn',
54
+ 'Assamese': 'asm_Beng',
55
+ 'Asturian': 'ast_Latn',
56
+ 'Awadhi': 'awa_Deva',
57
+ 'Ayacucho Quechua': 'quy_Latn',
58
+ 'Balinese': 'ban_Latn',
59
+ 'Bambara': 'bam_Latn',
60
+ 'Banjar (Arabic script)': 'bjn_Arab',
61
+ 'Banjar (Latin script)': 'bjn_Latn',
62
+ 'Bashkir': 'bak_Cyrl',
63
+ 'Basque': 'eus_Latn',
64
+ 'Belarusian': 'bel_Cyrl',
65
+ 'Bemba': 'bem_Latn',
66
+ 'Bengali': 'ben_Beng',
67
+ 'Bhojpuri': 'bho_Deva',
68
+ 'Bosnian': 'bos_Latn',
69
+ 'Buginese': 'bug_Latn',
70
+ 'Bulgarian': 'bul_Cyrl',
71
+ 'Burmese': 'mya_Mymr',
72
+ 'Catalan': 'cat_Latn',
73
+ 'Cebuano': 'ceb_Latn',
74
+ 'Central Atlas Tamazight': 'tzm_Tfng',
75
+ 'Central Aymara': 'ayr_Latn',
76
+ 'Central Kanuri (Arabic script)': 'knc_Arab',
77
+ 'Central Kanuri (Latin script)': 'knc_Latn',
78
+ 'Central Kurdish': 'ckb_Arab',
79
+ 'Chhattisgarhi': 'hne_Deva',
80
+ 'Chinese (Simplified)': 'zho_Hans',
81
+ 'Chinese (Traditional)': 'zho_Hant',
82
+ 'Chokwe': 'cjk_Latn',
83
+ 'Crimean Tatar': 'crh_Latn',
84
+ 'Croatian': 'hrv_Latn',
85
+ 'Czech': 'ces_Latn',
86
+ 'Danish': 'dan_Latn',
87
+ 'Dari': 'prs_Arab',
88
+ 'Dutch': 'nld_Latn',
89
+ 'Dyula': 'dyu_Latn',
90
+ 'Dzongkha': 'dzo_Tibt',
91
+ 'Eastern Panjabi': 'pan_Guru',
92
+ 'Eastern Yiddish': 'ydd_Hebr',
93
+ 'Egyptian Arabic': 'arz_Arab',
94
+ 'English': 'eng_Latn',
95
+ 'Esperanto': 'epo_Latn',
96
+ 'Estonian': 'est_Latn',
97
+ 'Ewe': 'ewe_Latn',
98
+ 'Faroese': 'fao_Latn',
99
+ 'Fijian': 'fij_Latn',
100
+ 'Finnish': 'fin_Latn',
101
+ 'Fon': 'fon_Latn',
102
+ 'French': 'fra_Latn',
103
+ 'Friulian': 'fur_Latn',
104
+ 'Galician': 'glg_Latn',
105
+ 'Ganda': 'lug_Latn',
106
+ 'Georgian': 'kat_Geor',
107
+ 'German': 'deu_Latn',
108
+ 'Greek': 'ell_Grek',
109
+ 'Guarani': 'grn_Latn',
110
+ 'Gujarati': 'guj_Gujr',
111
+ 'Haitian Creole': 'hat_Latn',
112
+ 'Halh Mongolian': 'khk_Cyrl',
113
+ 'Hausa': 'hau_Latn',
114
+ 'Hebrew': 'heb_Hebr',
115
+ 'Hindi': 'hin_Deva',
116
+ 'Hungarian': 'hun_Latn',
117
+ 'Icelandic': 'isl_Latn',
118
+ 'Igbo': 'ibo_Latn',
119
+ 'Ilocano': 'ilo_Latn',
120
+ 'Indonesian': 'ind_Latn',
121
+ 'Irish': 'gle_Latn',
122
+ 'Italian': 'ita_Latn',
123
+ 'Japanese': 'jpn_Jpan',
124
+ 'Javanese': 'jav_Latn',
125
+ 'Jingpho': 'kac_Latn',
126
+ 'Kabiyè': 'kbp_Latn',
127
+ 'Kabuverdianu': 'kea_Latn',
128
+ 'Kabyle': 'kab_Latn',
129
+ 'Kamba': 'kam_Latn',
130
+ 'Kannada': 'kan_Knda',
131
+ 'Kashmiri (Arabic script)': 'kas_Arab',
132
+ 'Kashmiri (Devanagari script)': 'kas_Deva',
133
+ 'Kazakh': 'kaz_Cyrl',
134
+ 'Khmer': 'khm_Khmr',
135
+ 'Kikongo': 'kon_Latn',
136
+ 'Kikuyu': 'kik_Latn',
137
+ 'Kimbundu': 'kmb_Latn',
138
+ 'Kinyarwanda': 'kin_Latn',
139
+ 'Korean': 'kor_Hang',
140
+ 'Kyrgyz': 'kir_Cyrl',
141
+ 'Lao': 'lao_Laoo',
142
+ 'Latgalian': 'ltg_Latn',
143
+ 'Ligurian': 'lij_Latn',
144
+ 'Limburgish': 'lim_Latn',
145
+ 'Lingala': 'lin_Latn',
146
+ 'Lithuanian': 'lit_Latn',
147
+ 'Lombard': 'lmo_Latn',
148
+ 'Luba-Kasai': 'lua_Latn',
149
+ 'Luo': 'luo_Latn',
150
+ 'Luxembourgish': 'ltz_Latn',
151
+ 'Macedonian': 'mkd_Cyrl',
152
+ 'Magahi': 'mag_Deva',
153
+ 'Maithili': 'mai_Deva',
154
+ 'Malayalam': 'mal_Mlym',
155
+ 'Maltese': 'mlt_Latn',
156
+ 'Maori': 'mri_Latn',
157
+ 'Marathi': 'mar_Deva',
158
+ 'Meitei (Bengali script)': 'mni_Beng',
159
+ 'Mesopotamian Arabic': 'acm_Arab',
160
+ 'Minangkabau (Arabic script)': 'min_Arab',
161
+ 'Minangkabau (Latin script)': 'min_Latn',
162
+ 'Mizo': 'lus_Latn',
163
+ 'Modern Standard Arabic (Romanized)': 'arb_Latn',
164
+ 'Modern Standard Arabic': 'arb_Arab',
165
+ 'Moroccan Arabic': 'ary_Arab',
166
+ 'Mossi': 'mos_Latn',
167
+ 'Najdi Arabic': 'ars_Arab',
168
+ 'Nepali': 'npi_Deva',
169
+ 'Nigerian Fulfulde': 'fuv_Latn',
170
+ 'North Azerbaijani': 'azj_Latn',
171
+ 'North Levantine Arabic': 'apc_Arab',
172
+ 'Northern Kurdish': 'kmr_Latn',
173
+ 'Northern Sotho': 'nso_Latn',
174
+ 'Northern Uzbek': 'uzn_Latn',
175
+ 'Norwegian Bokmål': 'nob_Latn',
176
+ 'Norwegian Nynorsk': 'nno_Latn',
177
+ 'Nuer': 'nus_Latn',
178
+ 'Nyanja': 'nya_Latn',
179
+ 'Occitan': 'oci_Latn',
180
+ 'Odia': 'ory_Orya',
181
+ 'Pangasinan': 'pag_Latn',
182
+ 'Papiamento': 'pap_Latn',
183
+ 'Plateau Malagasy': 'plt_Latn',
184
+ 'Polish': 'pol_Latn',
185
+ 'Portuguese': 'por_Latn',
186
+ 'Romanian': 'ron_Latn',
187
+ 'Rundi': 'run_Latn',
188
+ 'Russian': 'rus_Cyrl',
189
+ 'Samoan': 'smo_Latn',
190
+ 'Sango': 'sag_Latn',
191
+ 'Sanskrit': 'san_Deva',
192
+ 'Santali': 'sat_Olck',
193
+ 'Sardinian': 'srd_Latn',
194
+ 'Scottish Gaelic': 'gla_Latn',
195
+ 'Serbian': 'srp_Cyrl',
196
+ 'Shan': 'shn_Mymr',
197
+ 'Shona': 'sna_Latn',
198
+ 'Sicilian': 'scn_Latn',
199
+ 'Silesian': 'szl_Latn',
200
+ 'Sindhi': 'snd_Arab',
201
+ 'Sinhala': 'sin_Sinh',
202
+ 'Slovak': 'slk_Latn',
203
+ 'Slovenian': 'slv_Latn',
204
+ 'Somali': 'som_Latn',
205
+ 'South Azerbaijani': 'azb_Arab',
206
+ 'South Levantine Arabic': 'ajp_Arab',
207
+ 'Southern Pashto': 'pbt_Arab',
208
+ 'Southern Sotho': 'sot_Latn',
209
+ 'Southwestern Dinka': 'dik_Latn',
210
+ 'Spanish': 'spa_Latn',
211
+ 'Standard Latvian': 'lvs_Latn',
212
+ 'Standard Malay': 'zsm_Latn',
213
+ 'Standard Tibetan': 'bod_Tibt',
214
+ 'Sundanese': 'sun_Latn',
215
+ 'Swahili': 'swh_Latn',
216
+ 'Swati': 'ssw_Latn',
217
+ 'Swedish': 'swe_Latn',
218
+ 'Tagalog': 'tgl_Latn',
219
+ 'Tajik': 'tgk_Cyrl',
220
+ 'Tamasheq (Latin script)': 'taq_Latn',
221
+ 'Tamasheq (Tifinagh script)': 'taq_Tfng',
222
+ 'Tamil': 'tam_Taml',
223
+ 'Tatar': 'tat_Cyrl',
224
+ 'Ta’izzi-Adeni Arabic': 'acq_Arab',
225
+ 'Telugu': 'tel_Telu',
226
+ 'Thai': 'tha_Thai',
227
+ 'Tigrinya': 'tir_Ethi',
228
+ 'Tok Pisin': 'tpi_Latn',
229
+ 'Tosk Albanian': 'als_Latn',
230
+ 'Tsonga': 'tso_Latn',
231
+ 'Tswana': 'tsn_Latn',
232
+ 'Tumbuka': 'tum_Latn',
233
+ 'Tunisian Arabic': 'aeb_Arab',
234
+ 'Turkish': 'tur_Latn',
235
+ 'Turkmen': 'tuk_Latn',
236
+ 'Twi': 'twi_Latn',
237
+ 'Ukrainian': 'ukr_Cyrl',
238
+ 'Umbundu': 'umb_Latn',
239
+ 'Urdu': 'urd_Arab',
240
+ 'Uyghur': 'uig_Arab',
241
+ 'Venetian': 'vec_Latn',
242
+ 'Vietnamese': 'vie_Latn',
243
+ 'Waray': 'war_Latn',
244
+ 'Welsh': 'cym_Latn',
245
+ 'West Central Oromo': 'gaz_Latn',
246
+ 'Western Persian': 'pes_Arab',
247
+ 'Wolof': 'wol_Latn',
248
+ 'Xhosa': 'xho_Latn',
249
+ 'Yoruba': 'yor_Latn',
250
+ 'Yue Chinese': 'yue_Hant',
251
+ 'Zulu': 'zul_Latn',
252
+ }
@@ -22,6 +22,39 @@ export function formatLanguageCodeWithName(languageCode: string, styleId: 1 | 2
22
22
  }
23
23
  }
24
24
 
25
+ export async function normalizeIdentifierToLangaugeCode(langIdentifier: string) {
26
+ const result = await parseLangIdentifier(langIdentifier)
27
+
28
+ return result.Name
29
+ }
30
+
31
+ export async function normalizeIdentifierToShortLanguageCode(langIdentifier: string) {
32
+ const result = await parseLangIdentifier(langIdentifier)
33
+
34
+ return result.TwoLetterISOLanguageName
35
+ }
36
+
37
+ export async function parseLangIdentifier(langIdentifier: string) {
38
+ if (!langIdentifier) {
39
+ return emptyLangInfoEntry
40
+ }
41
+
42
+ await loadLangInfoEntriesIfNeeded()
43
+
44
+ langIdentifier = langIdentifier.trim().toLowerCase()
45
+
46
+ for (const entry of langInfoEntries) {
47
+ if (langIdentifier === entry.NameLowerCase ||
48
+ langIdentifier === entry.ThreeLetterISOLanguageName ||
49
+ langIdentifier === entry.EnglishNameLowerCase) {
50
+
51
+ return entry
52
+ }
53
+ }
54
+
55
+ throw new Error(`Couldn't parse language identifier '${langIdentifier}'.`)
56
+ }
57
+
25
58
  export function getShortLanguageCode(langCode: string) {
26
59
  const dashIndex = langCode.indexOf('-')
27
60
 
@@ -48,7 +81,7 @@ export function normalizeLanguageCode(langCode: string) {
48
81
 
49
82
  const isoToLcidLookup = new Map<string, number>()
50
83
  const lcidToIsoLookup = new Map<number, string[]>()
51
- const lcidEntries: LCIDEntry[] = []
84
+ let langInfoEntries: LangInfoEntry[] = []
52
85
 
53
86
  export async function isoToLcidLanguageCode(iso: string) {
54
87
  await loadLcidLookupIfNeeded()
@@ -63,19 +96,13 @@ export async function lcidToIsoLanguageCode(lcid: number) {
63
96
  }
64
97
 
65
98
  async function loadLcidLookupIfNeeded() {
66
- if (lcidEntries.length > 0) {
67
- return lcidEntries
68
- }
69
-
70
- const lcidLookup: LCIDLookup = await readAndParseJsonFile(resolveToModuleRootDir('data/tables/lcid-table.json'))
71
-
72
- for (const isoName in lcidLookup) {
73
- const lcidEntry = lcidLookup[isoName]
74
- lcidEntries.push(lcidEntry)
99
+ await loadLangInfoEntriesIfNeeded()
75
100
 
101
+ for (const lcidEntry of langInfoEntries) {
102
+ const name = lcidEntry.Name
76
103
  const lcidValue = lcidEntry.LCID
77
104
 
78
- isoToLcidLookup.set(isoName, lcidValue)
105
+ isoToLcidLookup.set(name, lcidValue)
79
106
 
80
107
  let entry = lcidToIsoLookup.get(lcidValue)
81
108
 
@@ -84,10 +111,25 @@ async function loadLcidLookupIfNeeded() {
84
111
  lcidToIsoLookup.set(lcidValue, entry)
85
112
  }
86
113
 
87
- entry.push(isoName)
114
+ entry.push(name)
88
115
  }
89
116
 
90
- return lcidEntries
117
+ return langInfoEntries
118
+ }
119
+
120
+ async function loadLangInfoEntriesIfNeeded() {
121
+ if (langInfoEntries.length > 0) {
122
+ return
123
+ }
124
+
125
+ const entries = await readAndParseJsonFile(resolveToModuleRootDir('data/tables/lcid-table.json')) as LangInfoEntry[]
126
+
127
+ for (const entry of entries) {
128
+ entry.NameLowerCase = entry.Name.toLowerCase()
129
+ entry.EnglishNameLowerCase = entry.EnglishName.toLowerCase()
130
+
131
+ langInfoEntries.push(entry)
132
+ }
91
133
  }
92
134
 
93
135
  export function getDefaultDialectForLanguageCodeIfPossible(langCode: string) {
@@ -107,14 +149,34 @@ export const defaultDialectForLanguageCode: { [lang: string]: string } = {
107
149
  'nl': 'nl-NL'
108
150
  }
109
151
 
110
- type LCIDLookup = { [isoLangCode: string]: LCIDEntry }
152
+ export interface LangInfoEntry {
153
+ LCID: number
154
+
155
+ Name: string
156
+ NameLowerCase: string
157
+
158
+ TwoLetterISOLanguageName: string
159
+ ThreeLetterISOLanguageName: string
160
+ ThreeLetterWindowsLanguageName: string
161
+
162
+ EnglishName: string
163
+ EnglishNameLowerCase: string
164
+
165
+ ANSICodePage: string
166
+ }
167
+
168
+ export const emptyLangInfoEntry: LangInfoEntry = {
169
+ LCID: -1,
170
+
171
+ Name: '',
172
+ NameLowerCase: '',
173
+
174
+ TwoLetterISOLanguageName: '',
175
+ ThreeLetterISOLanguageName: '',
176
+ ThreeLetterWindowsLanguageName: '',
177
+
178
+ EnglishName: 'Empty',
179
+ EnglishNameLowerCase: 'empty',
111
180
 
112
- export interface LCIDEntry {
113
- 'LCID': number
114
- 'Name': string
115
- 'TwoLetterISOLanguageName': string,
116
- 'ThreeLetterISOLanguageName': string,
117
- 'ThreeLetterWindowsLanguageName': string,
118
- 'EnglishName': string
119
- 'ANSICodePage': string
181
+ ANSICodePage: ''
120
182
  }
@@ -182,4 +182,7 @@ const packageVersionTagResolutionLookup: { [packageName: string]: string } = {
182
182
 
183
183
  'whisper.cpp-binaries-windows-x64-cublas-12.4.0-latest-patched': '20240409',
184
184
  'whisper.cpp-binaries-windows-x64-cublas-11.8.0-latest-patched': '20240409',
185
+
186
+ 'xenova-multilingual-e5-small-quantized': '20240504',
187
+ 'xenova-nllb-200-distilled-600M-quantized': '20240505',
185
188
  }