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.
- package/data/tables/lcid-table.json +7427 -7427
- package/dist/alignment/TextAlignment.d.ts +16 -0
- package/dist/alignment/TextAlignment.js +63 -0
- package/dist/alignment/TextAlignment.js.map +1 -0
- package/dist/api/Alignment.js +11 -4
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Denoising.js +0 -1
- package/dist/api/Denoising.js.map +1 -1
- package/dist/api/LanguageDetection.js +2 -2
- package/dist/api/LanguageDetection.js.map +1 -1
- package/dist/api/Recognition.js +15 -11
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/Synthesis.js +11 -15
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/Translation.js +24 -15
- package/dist/api/Translation.js.map +1 -1
- package/dist/api/TranslationAlignment.js +12 -5
- package/dist/api/TranslationAlignment.js.map +1 -1
- package/dist/api/VoiceActivityDetection.d.ts +0 -4
- package/dist/api/VoiceActivityDetection.js +43 -20
- package/dist/api/VoiceActivityDetection.js.map +1 -1
- package/dist/audio/AudioUtilities.d.ts +1 -1
- package/dist/audio/AudioUtilities.js +2 -2
- package/dist/audio/AudioUtilities.js.map +1 -1
- package/dist/cli/CLILauncher.js +1 -2
- package/dist/cli/CLILauncher.js.map +1 -1
- package/dist/codecs/FFMpegTranscoder.js +4 -1
- package/dist/codecs/FFMpegTranscoder.js.map +1 -1
- package/dist/codecs/WaveCodec.d.ts +1 -1
- package/dist/codecs/WaveCodec.js +13 -5
- package/dist/codecs/WaveCodec.js.map +1 -1
- package/dist/dsp/SpeexResampler.js +49 -21
- package/dist/dsp/SpeexResampler.js.map +1 -1
- package/dist/recognition/WhisperSTT.js +4 -4
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/synthesis/CoquiServerTTS.js +1 -1
- package/dist/synthesis/CoquiServerTTS.js.map +1 -1
- package/dist/synthesis/VitsTTS.js +1 -1
- package/dist/synthesis/VitsTTS.js.map +1 -1
- package/dist/text-translation/NLLBTextTranslation.d.ts +1 -0
- package/dist/text-translation/NLLBTextTranslation.js +237 -0
- package/dist/text-translation/NLLBTextTranslation.js.map +1 -0
- package/dist/utilities/Locale.d.ts +14 -8
- package/dist/utilities/Locale.js +52 -11
- package/dist/utilities/Locale.js.map +1 -1
- package/dist/utilities/PackageManager.js +2 -0
- package/dist/utilities/PackageManager.js.map +1 -1
- package/docs/Development.md +1 -1
- package/docs/Server.md +1 -1
- package/docs/Tasklist.md +11 -6
- package/package.json +12 -11
- package/src/alignment/TextAlignment.ts +96 -0
- package/src/api/Alignment.ts +19 -4
- package/src/api/Denoising.ts +0 -2
- package/src/api/LanguageDetection.ts +2 -3
- package/src/api/Recognition.ts +18 -13
- package/src/api/Synthesis.ts +13 -21
- package/src/api/Translation.ts +29 -22
- package/src/api/TranslationAlignment.ts +17 -6
- package/src/api/VoiceActivityDetection.ts +56 -22
- package/src/audio/AudioUtilities.ts +2 -2
- package/src/cli/CLILauncher.ts +1 -2
- package/src/codecs/FFMpegTranscoder.ts +5 -1
- package/src/codecs/WaveCodec.ts +15 -5
- package/src/dsp/SpeexResampler.ts +62 -23
- package/src/recognition/WhisperSTT.ts +4 -4
- package/src/synthesis/CoquiServerTTS.ts +1 -1
- package/src/synthesis/VitsTTS.ts +2 -2
- package/src/text-translation/NLLBTextTranslation.ts +252 -0
- package/src/utilities/Locale.ts +84 -22
- package/src/utilities/PackageManager.ts +3 -0
package/src/codecs/WaveCodec.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
62
|
+
for (let readOffset = 0; readOffset < totalSampleCount;) {
|
|
63
|
+
const isLastChunk = readOffset + maxChunkSize >= totalSampleCount
|
|
37
64
|
|
|
38
|
-
|
|
39
|
-
|
|
65
|
+
const inputPaddingSize = isLastChunk ? inputLatency : 0
|
|
66
|
+
const maxSamplesToRead = Math.min(maxChunkSize, totalSampleCount - readOffset) + inputPaddingSize
|
|
40
67
|
|
|
41
|
-
|
|
42
|
-
const inSampleCountRef = wasmMemory.allocInt32()
|
|
43
|
-
inSampleCountRef.value = inSampleCount + inputLatency
|
|
68
|
+
const maxSamplesToWrite = outputChunkSamplesRef.length
|
|
44
69
|
|
|
45
|
-
|
|
46
|
-
inSamplesRef.view.set(channelSamples)
|
|
70
|
+
const inputChunkSamplesForChannel = rawAudio.audioChannels[channelIndex].slice(readOffset, readOffset + maxSamplesToRead)
|
|
47
71
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
outSampleCountRef.value = outSampleCount + outputLatency
|
|
72
|
+
inputChunkSampleCountRef.value = maxSamplesToRead
|
|
73
|
+
outputChunkSampleCountRef.value = maxSamplesToWrite
|
|
51
74
|
|
|
52
|
-
|
|
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
|
-
|
|
78
|
+
if (resultCode != 0) {
|
|
79
|
+
throw new Error(`Speex resampler failed while resampling with code ${resultCode}: ${speexResultCodeToString(resultCode)}`)
|
|
80
|
+
}
|
|
55
81
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
82
|
+
const samplesReadCount = inputChunkSampleCountRef.value
|
|
83
|
+
const samplesWrittenCount = outputChunkSampleCountRef.value
|
|
84
|
+
|
|
85
|
+
const resampledChannelAudio = outputChunkSamplesRef.view.slice(0, samplesWrittenCount)
|
|
59
86
|
|
|
60
|
-
|
|
87
|
+
resampledAudioChunksForChannels[channelIndex].push(resampledChannelAudio)
|
|
61
88
|
|
|
62
|
-
|
|
89
|
+
readOffset += samplesReadCount
|
|
90
|
+
}
|
|
63
91
|
}
|
|
64
92
|
|
|
65
|
-
m._speex_resampler_destroy(
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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)
|
|
24
|
+
const { rawAudio } = decodeWaveToRawAudio(waveData)
|
|
25
25
|
|
|
26
26
|
logger.end()
|
|
27
27
|
|
package/src/synthesis/VitsTTS.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/utilities/Locale.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
114
|
+
entry.push(name)
|
|
88
115
|
}
|
|
89
116
|
|
|
90
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|