echogarden 0.11.12 → 0.11.13
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/schemas/options.json +16 -0
- package/dist/api/Alignment.js +2 -2
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Recognition.js +2 -2
- package/dist/api/Recognition.js.map +1 -1
- package/dist/api/Synthesis.js +5 -4
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/api/Translation.js +2 -2
- package/dist/api/Translation.js.map +1 -1
- package/dist/audio/AudioUtilities.d.ts +1 -0
- package/dist/audio/AudioUtilities.js +25 -7
- package/dist/audio/AudioUtilities.js.map +1 -1
- package/dist/cli/CLI.js +2 -2
- package/dist/cli/CLI.js.map +1 -1
- package/dist/recognition/WhisperSTT.js +2 -2
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/subtitles/Subtitles.d.ts +10 -7
- package/dist/subtitles/Subtitles.js +268 -207
- package/dist/subtitles/Subtitles.js.map +1 -1
- package/docs/Options.md +4 -2
- package/package.json +7 -6
- package/src/alignment/DTWMfccSequenceAlignment.ts +43 -0
- package/src/alignment/DTWSequenceAlignment.ts +121 -0
- package/src/alignment/DTWSequenceAlignmentWindowed.ts +210 -0
- package/src/alignment/LevenshteinSequenceAlignment.ts +126 -0
- package/src/alignment/SpeechAlignment.ts +488 -0
- package/src/api/API.ts +12 -0
- package/src/api/APIOptions.ts +15 -0
- package/src/api/Alignment.ts +329 -0
- package/src/api/Common.ts +16 -0
- package/src/api/Denoising.ts +120 -0
- package/src/api/LanguageDetection.ts +286 -0
- package/src/api/Recognition.ts +344 -0
- package/src/api/Synthesis.ts +1735 -0
- package/src/api/Translation.ts +143 -0
- package/src/api/Vad.ts +172 -0
- package/src/audio/AudioBufferConversion.ts +248 -0
- package/src/audio/AudioPlayer.ts +358 -0
- package/src/audio/AudioRecorder.ts +91 -0
- package/src/audio/AudioUtilities.ts +392 -0
- package/src/audio/SoxPath.ts +24 -0
- package/src/cli/CLI.ts +1360 -0
- package/src/cli/CLIConfigFile.ts +91 -0
- package/src/cli/CLILauncher.ts +26 -0
- package/src/cli/CLIOptionsSchema.ts +54 -0
- package/src/cli/CLIParser.ts +41 -0
- package/src/cli/CLIStarter.ts +40 -0
- package/src/codecs/FFMpegTranscoder.ts +214 -0
- package/src/codecs/TIMITCodec.ts +17 -0
- package/src/codecs/WaveCodec.ts +260 -0
- package/src/denoising/RNNoise.ts +95 -0
- package/src/dsp/BiquadFilter.ts +488 -0
- package/src/dsp/FFT.ts +187 -0
- package/src/dsp/MFCC.ts +227 -0
- package/src/dsp/MelSpectogram.ts +145 -0
- package/src/dsp/Rubberband.ts +249 -0
- package/src/dsp/Sonic.ts +59 -0
- package/src/dsp/SpeexResampler.ts +79 -0
- package/src/math/VectorMath.ts +812 -0
- package/src/nlp/ChineseSegmentation.ts +68 -0
- package/src/nlp/CompromiseNLP.ts +113 -0
- package/src/nlp/EspeakPhonemizer.ts +168 -0
- package/src/nlp/IPA.ts +139 -0
- package/src/nlp/JapaneseSegmentation.ts +53 -0
- package/src/nlp/Lexicon.ts +119 -0
- package/src/nlp/PhoneConversion.ts +508 -0
- package/src/nlp/Segmentation.ts +237 -0
- package/src/nlp/TextNormalizer.ts +160 -0
- package/src/recognition/AmazonTranscribeSTT.ts +112 -0
- package/src/recognition/AzureCognitiveServicesSTT.ts +76 -0
- package/src/recognition/GoogleCloudSTT.ts +92 -0
- package/src/recognition/SileroSTT.ts +173 -0
- package/src/recognition/VoskSTT.ts +112 -0
- package/src/recognition/WhisperSTT.ts +1518 -0
- package/src/server/Client.ts +297 -0
- package/src/server/Server.ts +178 -0
- package/src/server/ServerStarter.ts +12 -0
- package/src/server/Worker.ts +400 -0
- package/src/server/WorkerStarter.ts +38 -0
- package/src/speech-language-detection/SileroLanguageDetection.ts +105 -0
- package/src/subtitles/Subtitles.ts +478 -0
- package/src/synthesis/AwsPollyTTS.ts +78 -0
- package/src/synthesis/AzureCognitiveServicesTTS.ts +146 -0
- package/src/synthesis/CoquiServerTTS.ts +29 -0
- package/src/synthesis/ElevenLabsTTS.ts +104 -0
- package/src/synthesis/EspeakTTS.ts +552 -0
- package/src/synthesis/FliteTTS.ts +387 -0
- package/src/synthesis/GoogleCloudTTS.ts +112 -0
- package/src/synthesis/GoogleTranslateTTS.ts +210 -0
- package/src/synthesis/MicrosoftEdgeTTS.ts +298 -0
- package/src/synthesis/SamTTS.ts +30 -0
- package/src/synthesis/SapiTTS.ts +222 -0
- package/src/synthesis/StreamlabsPollyTTS.ts +114 -0
- package/src/synthesis/SvoxPicoTTS.ts +318 -0
- package/src/synthesis/VitsTTS.ts +734 -0
- package/src/tests/Test.ts +24 -0
- package/src/text-language-detection/FastTextLanguageDetection.ts +53 -0
- package/src/text-language-detection/TinyLDLanguageDetection.ts +16 -0
- package/src/typings/Fillers.d.ts +41 -0
- package/src/utilities/BinaryArrayConversion.ts +159 -0
- package/src/utilities/Compression.ts +91 -0
- package/src/utilities/FileDownloader.ts +201 -0
- package/src/utilities/FileSystem.ts +265 -0
- package/src/utilities/Hashing.ts +230 -0
- package/src/utilities/Locale.ts +119 -0
- package/src/utilities/Logger.ts +72 -0
- package/src/utilities/NdArrayUtilities.ts +31 -0
- package/src/utilities/ObjectUtilities.ts +169 -0
- package/src/utilities/OpenPromise.ts +13 -0
- package/src/utilities/PackageManager.ts +97 -0
- package/src/utilities/Queue.ts +17 -0
- package/src/utilities/RandomGenerator.ts +237 -0
- package/src/utilities/SignalChannel.ts +22 -0
- package/src/utilities/TarballMaker.ts +68 -0
- package/src/utilities/Timeline.ts +231 -0
- package/src/utilities/Timer.ts +93 -0
- package/src/utilities/Utilities.ts +574 -0
- package/src/utilities/WasmMemoryManager.ts +516 -0
- package/src/utilities/WebReader.ts +55 -0
- package/src/utilities/WikipediaReader.ts +41 -0
- package/src/voice-activity-detection/SileroVAD.ts +86 -0
- package/src/voice-activity-detection/WebRtcVAD.ts +76 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readAndParseJsonFile, readFile } from "../utilities/FileSystem.js"
|
|
2
|
+
|
|
3
|
+
export async function parseConfigFile(path: string): Promise<ParsedConfigFile> {
|
|
4
|
+
const fileContent = await readFile(path, { encoding: "utf-8" })
|
|
5
|
+
|
|
6
|
+
const lines = fileContent
|
|
7
|
+
.split(/\r?\n/g)
|
|
8
|
+
.map(line => line.trim())
|
|
9
|
+
|
|
10
|
+
const sectionRegExp = /^\s*\[(.*)\]\s*$/
|
|
11
|
+
|
|
12
|
+
const sectionMap = new Map<string, Map<string, string>>()
|
|
13
|
+
|
|
14
|
+
let currentSectionName = ""
|
|
15
|
+
|
|
16
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
17
|
+
const line = lines[lineIndex]
|
|
18
|
+
|
|
19
|
+
if (line.length == 0 || line.startsWith("#")) {
|
|
20
|
+
continue
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const sectionMatch = sectionRegExp.exec(line)
|
|
24
|
+
|
|
25
|
+
if (sectionMatch) {
|
|
26
|
+
currentSectionName = sectionMatch[1]
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!(sectionMap.has(currentSectionName))) {
|
|
31
|
+
sectionMap.set(currentSectionName, new Map())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const splitPoint = line.indexOf("=")
|
|
35
|
+
|
|
36
|
+
let key: string
|
|
37
|
+
let value: string
|
|
38
|
+
|
|
39
|
+
if (splitPoint == 0) {
|
|
40
|
+
throw new Error(`Invalid empty key on line ${lineIndex + 1}`)
|
|
41
|
+
} else if (splitPoint > 0) {
|
|
42
|
+
key = line.substring(0, splitPoint).trim()
|
|
43
|
+
value = line.substring(splitPoint + 1).trim()
|
|
44
|
+
|
|
45
|
+
if (value) {
|
|
46
|
+
sectionMap.get(currentSectionName)!.set(key, value)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return sectionMap
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function parseJSONConfigFile(path: string): Promise<ParsedConfigFile> {
|
|
55
|
+
const parsedJSON = await readAndParseJsonFile(path, true)
|
|
56
|
+
|
|
57
|
+
const result: ParsedConfigFile = new Map()
|
|
58
|
+
|
|
59
|
+
for (const command in parsedJSON) {
|
|
60
|
+
const commandObj = parsedJSON[command]
|
|
61
|
+
|
|
62
|
+
const commandMap = new Map<string, string>()
|
|
63
|
+
|
|
64
|
+
function addFromObject(obj: any, pathPrefix: string): void {
|
|
65
|
+
for (const propertyName in obj) {
|
|
66
|
+
const propertyValue = obj[propertyName]
|
|
67
|
+
|
|
68
|
+
const propertyPath = pathPrefix == "" ? propertyName : `${pathPrefix}.${propertyName}`
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if (typeof propertyValue == "object") {
|
|
72
|
+
if (Array.isArray(propertyValue)) {
|
|
73
|
+
commandMap.set(propertyPath, JSON.stringify(propertyValue))
|
|
74
|
+
} else {
|
|
75
|
+
addFromObject(propertyValue, propertyPath)
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
commandMap.set(propertyPath, propertyValue)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
addFromObject(commandObj, "")
|
|
84
|
+
|
|
85
|
+
result.set(command, commandMap)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type ParsedConfigFile = Map<string, Map<string, string>>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'child_process'
|
|
4
|
+
import { setupUnhandledExceptionListeners } from '../utilities/Utilities.js'
|
|
5
|
+
import { resolveToModuleRootDir } from '../utilities/FileSystem.js'
|
|
6
|
+
|
|
7
|
+
setupUnhandledExceptionListeners()
|
|
8
|
+
|
|
9
|
+
const cmd = process.argv[0]
|
|
10
|
+
const scriptArgs = process.argv.slice(2)
|
|
11
|
+
|
|
12
|
+
const cliScriptPath = resolveToModuleRootDir("dist/cli/CLIStarter.js")
|
|
13
|
+
|
|
14
|
+
const args = [
|
|
15
|
+
"--no-warnings",
|
|
16
|
+
"--no-experimental-fetch",
|
|
17
|
+
"--experimental-wasi-unstable-preview1",
|
|
18
|
+
cliScriptPath,
|
|
19
|
+
...scriptArgs
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
const child = spawn(cmd, args, { stdio: "inherit" })
|
|
23
|
+
|
|
24
|
+
child.on("close", code => {
|
|
25
|
+
process.exit(code as number)
|
|
26
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function getOptionTypeFromSchema(path: string[], schema: any): SchemaTypeDefinition {
|
|
2
|
+
const definitions = schema.definitions as any
|
|
3
|
+
|
|
4
|
+
let currentObject = definitions
|
|
5
|
+
|
|
6
|
+
for (let keyIndex = 0; keyIndex < path.length; keyIndex++) {
|
|
7
|
+
const isLastKey = keyIndex == path.length - 1
|
|
8
|
+
|
|
9
|
+
const key = path[keyIndex]
|
|
10
|
+
|
|
11
|
+
currentObject = currentObject[key]
|
|
12
|
+
|
|
13
|
+
if (!currentObject) {
|
|
14
|
+
throw new Error(`'${key}' is not a valid property of '${path.slice(0, keyIndex).join(".")}'.`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if ("$ref" in currentObject) {
|
|
18
|
+
const refString = currentObject['$ref'] as string
|
|
19
|
+
const parsedRef = refString.substring(refString.lastIndexOf('/') + 1)
|
|
20
|
+
|
|
21
|
+
currentObject = definitions[parsedRef]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const currentObjectType = currentObject['type']
|
|
25
|
+
|
|
26
|
+
if (isLastKey) {
|
|
27
|
+
let possibleValues: any[] | undefined = undefined
|
|
28
|
+
|
|
29
|
+
if ('enum' in currentObject) {
|
|
30
|
+
possibleValues = currentObject['enum'] as any[]
|
|
31
|
+
} else if ('const' in currentObject) {
|
|
32
|
+
possibleValues = [currentObject['const']]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isUnion = ('anyOf' in currentObject)
|
|
36
|
+
|
|
37
|
+
return { type: currentObjectType, enum: possibleValues, isUnion }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (currentObjectType == 'object') {
|
|
41
|
+
currentObject = currentObject['properties']
|
|
42
|
+
} else {
|
|
43
|
+
throw new Error(`Property '${key}' is not an object, but has type ${currentObjectType}`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type SchemaTypeDefinition = {
|
|
51
|
+
type?: string
|
|
52
|
+
enum?: any[]
|
|
53
|
+
isUnion?: boolean
|
|
54
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function parseCLIArguments(command: string, args: string[]): CLIArguments {
|
|
2
|
+
const parsedArgs: CLIArguments = {
|
|
3
|
+
command,
|
|
4
|
+
commandArgs: [],
|
|
5
|
+
options: new Map(),
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
for (let i = 0; i < args.length; i++) {
|
|
9
|
+
const currentArg = args[i]
|
|
10
|
+
const currentArgIsNewOption = currentArg.startsWith("-")
|
|
11
|
+
|
|
12
|
+
if (currentArgIsNewOption) {
|
|
13
|
+
if (!currentArg.startsWith("--")) {
|
|
14
|
+
throw new Error(`'${currentArg}' has a single dash prefix. You should use '-${currentArg}' instead.`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const optionText = currentArg.substring(2)
|
|
18
|
+
|
|
19
|
+
const indexOfEquals = optionText.indexOf("=")
|
|
20
|
+
if (indexOfEquals == 0) {
|
|
21
|
+
throw new Error("An option cannot have an empty name.")
|
|
22
|
+
} else if (indexOfEquals == -1) {
|
|
23
|
+
parsedArgs.options.set(optionText, "")
|
|
24
|
+
} else {
|
|
25
|
+
const key = optionText.substring(0, indexOfEquals)
|
|
26
|
+
const value = optionText.substring(indexOfEquals + 1)
|
|
27
|
+
parsedArgs.options.set(key, value)
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
parsedArgs.commandArgs.push(currentArg)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return parsedArgs
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type CLIArguments = {
|
|
38
|
+
command: string
|
|
39
|
+
commandArgs: string[]
|
|
40
|
+
options: Map<string, string>
|
|
41
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { resolveToModuleRootDir } from '../utilities/FileSystem.js'
|
|
2
|
+
import { setupProgramTerminationListeners, setupUnhandledExceptionListeners, writeToStderr } from '../utilities/Utilities.js'
|
|
3
|
+
import { Worker, SHARE_ENV } from 'node:worker_threads'
|
|
4
|
+
|
|
5
|
+
setupUnhandledExceptionListeners()
|
|
6
|
+
|
|
7
|
+
setupProgramTerminationListeners(() => {
|
|
8
|
+
writeToStderr('\n')
|
|
9
|
+
process.kill(process.pid, 'SIGKILL')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const worker = new Worker(resolveToModuleRootDir("dist/cli/CLI.js"), {
|
|
13
|
+
argv: process.argv.slice(2),
|
|
14
|
+
env: SHARE_ENV
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
worker.postMessage({
|
|
18
|
+
name: 'init',
|
|
19
|
+
stdErrIsTTY: process.stderr.isTTY,
|
|
20
|
+
stdErrHasColors: process.stderr.hasColors ? process.stderr.hasColors() : false
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
process.stdin.on('keypress', (str, key) => {
|
|
24
|
+
worker.postMessage({
|
|
25
|
+
name: 'keypress',
|
|
26
|
+
str,
|
|
27
|
+
key,
|
|
28
|
+
timestamp: Date.now()
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
worker.on("message", (message) => {
|
|
33
|
+
if (message.name == "writeToStdErr") {
|
|
34
|
+
writeToStderr(message.text)
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
worker.on("exit", (err) => {
|
|
39
|
+
process.exit(err ? 1 : 0)
|
|
40
|
+
})
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { spawn } from "child_process"
|
|
2
|
+
|
|
3
|
+
import { encodeWaveBuffer, decodeWaveBuffer, RawAudio } from "../audio/AudioUtilities.js"
|
|
4
|
+
|
|
5
|
+
import { Logger } from "../utilities/Logger.js"
|
|
6
|
+
import { commandExists, logToStderr } from "../utilities/Utilities.js"
|
|
7
|
+
import path from "node:path"
|
|
8
|
+
import { loadPackage } from "../utilities/PackageManager.js"
|
|
9
|
+
|
|
10
|
+
const log = logToStderr
|
|
11
|
+
|
|
12
|
+
export type FFMpegOutputOptions = {
|
|
13
|
+
filename?: string
|
|
14
|
+
codec?: string
|
|
15
|
+
format?: string
|
|
16
|
+
sampleRate?: number
|
|
17
|
+
sampleFormat?: "u8" | "s16" | "s32" | "s64" | "flt" | "dbl"
|
|
18
|
+
channelCount?: number
|
|
19
|
+
bitrate?: number
|
|
20
|
+
audioOnly?: boolean
|
|
21
|
+
customOptions?: string[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function encodeFromChannels(rawAudio: RawAudio, outputOptions: FFMpegOutputOptions) {
|
|
25
|
+
return transcode(encodeWaveBuffer(rawAudio), outputOptions)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function decodeToChannels(input: string | Buffer, outSampleRate?: number, outChannelCount?: number) {
|
|
29
|
+
const outputOptions: FFMpegOutputOptions = {
|
|
30
|
+
codec: "pcm_f32le",
|
|
31
|
+
format: "wav",
|
|
32
|
+
sampleRate: outSampleRate,
|
|
33
|
+
channelCount: outChannelCount,
|
|
34
|
+
audioOnly: true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const waveAudio = await transcode(input, outputOptions)
|
|
38
|
+
|
|
39
|
+
const { rawAudio } = decodeWaveBuffer(waveAudio, true)
|
|
40
|
+
|
|
41
|
+
return rawAudio
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function transcode(input: string | Buffer, outputOptions: FFMpegOutputOptions) {
|
|
45
|
+
const executablePath = await getFFMpegExecutablePath()
|
|
46
|
+
|
|
47
|
+
if (!executablePath) {
|
|
48
|
+
throw new Error("The ffmpeg utility wasn't found. Please ensure it is available on the system path.")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return transcode_CLI(executablePath, input, outputOptions)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function transcode_CLI(ffmpegCommand: string, input: string | Buffer, outputOptions: FFMpegOutputOptions) {
|
|
55
|
+
return new Promise<Buffer>((resolve, reject) => {
|
|
56
|
+
const logger = new Logger()
|
|
57
|
+
logger.start("Transcode with command-line ffmpeg")
|
|
58
|
+
|
|
59
|
+
const args = buildCommandLineArguments(Buffer.isBuffer(input) ? "-" : input, outputOptions)
|
|
60
|
+
|
|
61
|
+
const process = spawn(ffmpegCommand, args)
|
|
62
|
+
|
|
63
|
+
if (Buffer.isBuffer(input)) {
|
|
64
|
+
process.stdin.end(input)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const stdOutChunks: Buffer[] = []
|
|
68
|
+
let stdErrOutput = ""
|
|
69
|
+
|
|
70
|
+
process.stdout.on('data', (data) => {
|
|
71
|
+
stdOutChunks.push(data)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
process.stderr.setEncoding("utf8")
|
|
75
|
+
process.stderr.on('data', (data) => {
|
|
76
|
+
//log(data)
|
|
77
|
+
stdErrOutput += data
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
process.on("error", (e) => {
|
|
81
|
+
reject(e)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
process.on('close', (exitCode) => {
|
|
85
|
+
if (exitCode == 0) {
|
|
86
|
+
resolve(Buffer.concat(stdOutChunks))
|
|
87
|
+
} else {
|
|
88
|
+
reject(`ffmpeg exited with code ${exitCode}`)
|
|
89
|
+
log(stdErrOutput)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
logger.end()
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildCommandLineArguments(inputFilename: string, outputOptions: FFMpegOutputOptions) {
|
|
98
|
+
outputOptions = { ...outputOptions }
|
|
99
|
+
|
|
100
|
+
if (!outputOptions.filename) {
|
|
101
|
+
outputOptions.filename = "-"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const args: string[] = []
|
|
105
|
+
|
|
106
|
+
args.push(
|
|
107
|
+
`-i`, `${inputFilename}`,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if (outputOptions.audioOnly) {
|
|
111
|
+
args.push(`-map`, `a`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (outputOptions.codec) {
|
|
115
|
+
args.push(`-c:a`, `${outputOptions.codec}`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (outputOptions.format) {
|
|
119
|
+
args.push(`-f:a`, `${outputOptions.format}`)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (outputOptions.sampleRate) {
|
|
123
|
+
args.push(`-ar`, `${outputOptions.sampleRate}`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (outputOptions.sampleFormat) {
|
|
127
|
+
args.push(`-sample_fmt`, `${outputOptions.sampleFormat}`)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (outputOptions.channelCount) {
|
|
131
|
+
args.push(`-ac`, `${outputOptions.channelCount}`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (outputOptions.bitrate) {
|
|
135
|
+
args.push(`-ab`, `${outputOptions.bitrate}k`)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
args.push(`-y`)
|
|
139
|
+
|
|
140
|
+
if (outputOptions.customOptions) {
|
|
141
|
+
args.push(...outputOptions.customOptions)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
args.push(outputOptions.filename)
|
|
145
|
+
|
|
146
|
+
return args
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function getFFMpegExecutablePath() {
|
|
150
|
+
if (process.platform == "win32") {
|
|
151
|
+
const ffmpegPackagePath = await loadPackage("ffmpeg-6.0-essentials-win64")
|
|
152
|
+
|
|
153
|
+
return path.join(ffmpegPackagePath, "ffmpeg.exe")
|
|
154
|
+
}
|
|
155
|
+
/* else if (process.platform == "darwin" && process.arch == "x64") {
|
|
156
|
+
const ffmpegPackagePath = await loadPackage("ffmpeg-6.0-macos64")
|
|
157
|
+
|
|
158
|
+
return path.join(ffmpegPackagePath, "ffmpeg")
|
|
159
|
+
}
|
|
160
|
+
*/ else if (process.platform == "linux" && process.arch == "x64") {
|
|
161
|
+
const ffmpegPackagePath = await loadPackage("ffmpeg-6.0-linux-amd64")
|
|
162
|
+
|
|
163
|
+
return path.join(ffmpegPackagePath, "ffmpeg")
|
|
164
|
+
} else if (await commandExists("ffmpeg")) {
|
|
165
|
+
return "ffmpeg"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return undefined
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function getDefaultFFMpegOptionsForSpeech(fileExtension: string, customBitrate?: number) {
|
|
172
|
+
let ffmpegOptions: FFMpegOutputOptions
|
|
173
|
+
|
|
174
|
+
if (fileExtension == "mp3") {
|
|
175
|
+
ffmpegOptions = {
|
|
176
|
+
format: "mp3",
|
|
177
|
+
codec: "libmp3lame",
|
|
178
|
+
bitrate: 64,
|
|
179
|
+
customOptions: []
|
|
180
|
+
}
|
|
181
|
+
} else if (fileExtension == "opus") {
|
|
182
|
+
ffmpegOptions = {
|
|
183
|
+
codec: "libopus",
|
|
184
|
+
bitrate: 48,
|
|
185
|
+
customOptions: []
|
|
186
|
+
}
|
|
187
|
+
} else if (fileExtension == "m4a") {
|
|
188
|
+
ffmpegOptions = {
|
|
189
|
+
format: "mp4",
|
|
190
|
+
codec: "aac",
|
|
191
|
+
bitrate: 48,
|
|
192
|
+
customOptions: ["-profile:a", "aac_low", "-movflags", "frag_keyframe+empty_moov"]
|
|
193
|
+
}
|
|
194
|
+
} else if (fileExtension == "ogg") {
|
|
195
|
+
ffmpegOptions = {
|
|
196
|
+
codec: "libvorbis",
|
|
197
|
+
bitrate: 48,
|
|
198
|
+
customOptions: []
|
|
199
|
+
}
|
|
200
|
+
} else if (fileExtension == "flac") {
|
|
201
|
+
ffmpegOptions = {
|
|
202
|
+
format: "flac",
|
|
203
|
+
customOptions: ["-compression_level", "6"]
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
throw new Error(`Unsupported codec extension: '${fileExtension}'`)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (customBitrate != null) {
|
|
210
|
+
ffmpegOptions.bitrate = customBitrate
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return ffmpegOptions
|
|
214
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { decodeToChannels } from "../audio/AudioBufferConversion.js"
|
|
2
|
+
import { readFile } from "../utilities/FileSystem.js"
|
|
3
|
+
import { SampleFormat } from "./WaveCodec.js"
|
|
4
|
+
|
|
5
|
+
export async function decodeTimitAudioFile(filename: string) {
|
|
6
|
+
return decodeTimitAudio(await readFile(filename))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function decodeTimitAudio(data: Buffer) {
|
|
10
|
+
if (data.subarray(0, 16).toString("ascii") != "NIST_1A\n 1024\n") {
|
|
11
|
+
throw new Error("Data is not a valid TIMIT audio file")
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const pcm = data.subarray(1024)
|
|
15
|
+
|
|
16
|
+
return { audioChannels: decodeToChannels(pcm, 1, 16, SampleFormat.PCM), sampleRate: 16000 }
|
|
17
|
+
}
|