echogarden 1.1.1 → 1.2.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/schemas/options.json +56 -1
- package/dist/alignment/DTWSequenceAlignmentWindowed.js +6 -1
- package/dist/alignment/DTWSequenceAlignmentWindowed.js.map +1 -1
- package/dist/alignment/SpeechAlignment.js +1 -1
- package/dist/alignment/SpeechAlignment.js.map +1 -1
- package/dist/api/APIOptions.d.ts +3 -0
- package/dist/api/GlobalOptions.d.ts +10 -0
- package/dist/api/GlobalOptions.js +21 -3
- package/dist/api/GlobalOptions.js.map +1 -1
- package/dist/api/VoiceActivityDetection.d.ts +4 -1
- package/dist/api/VoiceActivityDetection.js +6 -3
- package/dist/api/VoiceActivityDetection.js.map +1 -1
- package/dist/cli/CLI.js +224 -191
- package/dist/cli/CLI.js.map +1 -1
- package/dist/cli/CLIConfigFile.js +7 -7
- package/dist/cli/CLIConfigFile.js.map +1 -1
- package/dist/cli/CLIOptions.d.ts +7 -0
- package/dist/cli/CLIOptions.js +2 -0
- package/dist/cli/CLIOptions.js.map +1 -0
- package/dist/cli/CLIParser.d.ts +5 -6
- package/dist/cli/CLIParser.js +16 -12
- package/dist/cli/CLIParser.js.map +1 -1
- package/dist/recognition/WhisperSTT.js +20 -13
- package/dist/recognition/WhisperSTT.js.map +1 -1
- package/dist/utilities/FileDownloader.js +3 -1
- package/dist/utilities/FileDownloader.js.map +1 -1
- package/dist/utilities/Logger.d.ts +5 -4
- package/dist/utilities/Logger.js +19 -8
- package/dist/utilities/Logger.js.map +1 -1
- package/dist/utilities/PackageManager.js +3 -2
- package/dist/utilities/PackageManager.js.map +1 -1
- package/dist/utilities/Utilities.d.ts +1 -1
- package/dist/utilities/Utilities.js +9 -9
- package/dist/utilities/Utilities.js.map +1 -1
- package/docs/API.md +3 -10
- package/docs/CLI.md +92 -81
- package/docs/Options.md +23 -0
- package/docs/Releases.md +1 -1
- package/docs/Tasklist.md +3 -5
- package/docs/Technical.md +2 -2
- package/package.json +7 -7
- package/src/alignment/DTWSequenceAlignmentWindowed.ts +7 -1
- package/src/alignment/SpeechAlignment.ts +1 -1
- package/src/api/APIOptions.ts +3 -0
- package/src/api/GlobalOptions.ts +33 -5
- package/src/api/VoiceActivityDetection.ts +6 -3
- package/src/cli/CLI.ts +258 -206
- package/src/cli/CLIConfigFile.ts +7 -7
- package/src/cli/CLIOptions.ts +8 -0
- package/src/cli/CLIParser.ts +20 -17
- package/src/recognition/WhisperSTT.ts +20 -11
- package/src/utilities/FileDownloader.ts +5 -1
- package/src/utilities/Logger.ts +22 -8
- package/src/utilities/PackageManager.ts +4 -3
- package/src/utilities/Utilities.ts +9 -9
package/src/cli/CLIConfigFile.ts
CHANGED
|
@@ -56,10 +56,10 @@ export async function parseJSONConfigFile(path: string): Promise<ParsedConfigFil
|
|
|
56
56
|
|
|
57
57
|
const result: ParsedConfigFile = new Map()
|
|
58
58
|
|
|
59
|
-
for (const
|
|
60
|
-
const
|
|
59
|
+
for (const operation in parsedJSON) {
|
|
60
|
+
const operationObj = parsedJSON[operation]
|
|
61
61
|
|
|
62
|
-
const
|
|
62
|
+
const operationMap = new Map<string, string>()
|
|
63
63
|
|
|
64
64
|
function addFromObject(obj: any, pathPrefix: string): void {
|
|
65
65
|
for (const propertyName in obj) {
|
|
@@ -70,19 +70,19 @@ export async function parseJSONConfigFile(path: string): Promise<ParsedConfigFil
|
|
|
70
70
|
|
|
71
71
|
if (typeof propertyValue == 'object') {
|
|
72
72
|
if (Array.isArray(propertyValue)) {
|
|
73
|
-
|
|
73
|
+
operationMap.set(propertyPath, JSON.stringify(propertyValue))
|
|
74
74
|
} else {
|
|
75
75
|
addFromObject(propertyValue, propertyPath)
|
|
76
76
|
}
|
|
77
77
|
} else {
|
|
78
|
-
|
|
78
|
+
operationMap.set(propertyPath, propertyValue)
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
addFromObject(
|
|
83
|
+
addFromObject(operationObj, '')
|
|
84
84
|
|
|
85
|
-
result.set(
|
|
85
|
+
result.set(operation, operationMap)
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
return result
|
package/src/cli/CLIParser.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
export function parseCLIArguments(
|
|
2
|
-
const parsedArgs:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
options: new Map(),
|
|
1
|
+
export function parseCLIArguments(args: string[]): ParsedCLIArguments {
|
|
2
|
+
const parsedArgs: ParsedCLIArguments = {
|
|
3
|
+
operationArgs: [],
|
|
4
|
+
parsedArgumentsLookup: new Map(),
|
|
6
5
|
}
|
|
7
6
|
|
|
8
7
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -16,26 +15,30 @@ export function parseCLIArguments(command: string, args: string[]): CLIArguments
|
|
|
16
15
|
|
|
17
16
|
const optionText = currentArg.substring(2)
|
|
18
17
|
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
18
|
+
const indexOfEqualSign = optionText.indexOf('=')
|
|
19
|
+
if (indexOfEqualSign == 0) {
|
|
21
20
|
throw new Error('An option cannot have an empty name.')
|
|
22
|
-
} else if (
|
|
23
|
-
|
|
21
|
+
} else if (indexOfEqualSign == -1) {
|
|
22
|
+
if (optionText.startsWith('no-')) {
|
|
23
|
+
parsedArgs.parsedArgumentsLookup.set(optionText.substring(3), 'false')
|
|
24
|
+
} else {
|
|
25
|
+
parsedArgs.parsedArgumentsLookup.set(optionText, '')
|
|
26
|
+
}
|
|
24
27
|
} else {
|
|
25
|
-
const key = optionText.substring(0,
|
|
26
|
-
const value = optionText.substring(
|
|
27
|
-
|
|
28
|
+
const key = optionText.substring(0, indexOfEqualSign)
|
|
29
|
+
const value = optionText.substring(indexOfEqualSign + 1)
|
|
30
|
+
|
|
31
|
+
parsedArgs.parsedArgumentsLookup.set(key, value)
|
|
28
32
|
}
|
|
29
33
|
} else {
|
|
30
|
-
parsedArgs.
|
|
34
|
+
parsedArgs.operationArgs.push(currentArg)
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
return parsedArgs
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
export
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
options: Map<string, string>
|
|
41
|
+
export interface ParsedCLIArguments {
|
|
42
|
+
operationArgs: string[]
|
|
43
|
+
parsedArgumentsLookup: Map<string, string>
|
|
41
44
|
}
|
|
@@ -35,7 +35,7 @@ export async function recognize(
|
|
|
35
35
|
options = extendDeep(defaultWhisperOptions, options)
|
|
36
36
|
|
|
37
37
|
if (sourceRawAudio.sampleRate != 16000) {
|
|
38
|
-
throw new Error('Source audio must have a sampling rate of 16000')
|
|
38
|
+
throw new Error('Source audio must have a sampling rate of 16000 Hz')
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
sourceLanguage = getShortLanguageCode(sourceLanguage)
|
|
@@ -716,7 +716,8 @@ export class Whisper {
|
|
|
716
716
|
|
|
717
717
|
// Start decoding loop
|
|
718
718
|
for (let decodedTokenCount = 0; decodedTokenCount < options.maxTokensPerPart!; decodedTokenCount++) {
|
|
719
|
-
const isInitialState = decodedTokens.length
|
|
719
|
+
const isInitialState = decodedTokens.length === initialTokens.length
|
|
720
|
+
const atLeastOneTextTokenDecoded = decodedTokens.slice(initialTokens.length).some(token => this.isTextToken(token))
|
|
720
721
|
|
|
721
722
|
// If not in initial state, reshape KV Cache tensor to accomodate a new output token
|
|
722
723
|
if (!isInitialState) {
|
|
@@ -759,7 +760,7 @@ export class Whisper {
|
|
|
759
760
|
const crossAttentionQKsForToken = makeOnnxLikeFloat32Tensor(crossAttentionQKsForTokenOnnx)
|
|
760
761
|
crossAttentionQKsForTokenOnnx.dispose()
|
|
761
762
|
|
|
762
|
-
//
|
|
763
|
+
// Get logits
|
|
763
764
|
const resultLogitsFloatArrays = splitFloat32Array(logitsBuffer, logitsBuffer.length / decoderOutputs['logits'].dims[1])
|
|
764
765
|
const allTokenLogits = Array.from(resultLogitsFloatArrays[resultLogitsFloatArrays.length - 1])
|
|
765
766
|
|
|
@@ -768,7 +769,7 @@ export class Whisper {
|
|
|
768
769
|
allTokenLogits[suppressedTokenIndex] = -Infinity
|
|
769
770
|
}
|
|
770
771
|
|
|
771
|
-
if (
|
|
772
|
+
if (!atLeastOneTextTokenDecoded) {
|
|
772
773
|
// If in initial state, suppress end-of-text token
|
|
773
774
|
allTokenLogits[endOfTextToken] = -Infinity
|
|
774
775
|
}
|
|
@@ -783,13 +784,21 @@ export class Whisper {
|
|
|
783
784
|
return false
|
|
784
785
|
}
|
|
785
786
|
|
|
787
|
+
// If this is the first token in the part, unconditionally decode a timestamp token
|
|
788
|
+
// for time 0.0
|
|
789
|
+
if (isInitialState) {
|
|
790
|
+
addToken(timestampTokensStart, timestampTokenLogits, 1.0, crossAttentionQKsForToken)
|
|
791
|
+
|
|
792
|
+
return true
|
|
793
|
+
}
|
|
794
|
+
|
|
786
795
|
const previousTokenWasTimestamp = this.isTimestampToken(decodedTokens[decodedTokens.length - 1])
|
|
787
796
|
const secondPreviousTokenWasTimestamp = this.isTimestampToken(decodedTokens[decodedTokens.length - 2])
|
|
788
797
|
|
|
789
798
|
// If there are two successive timestamp tokens decoded, or the previous timestamp was the first token,
|
|
790
799
|
// don't decode a timestamp
|
|
791
800
|
if (previousTokenWasTimestamp &&
|
|
792
|
-
(decodedTokens.length === initialTokens.length + 1) || secondPreviousTokenWasTimestamp) {
|
|
801
|
+
((decodedTokens.length === initialTokens.length + 1) || secondPreviousTokenWasTimestamp)) {
|
|
793
802
|
return false
|
|
794
803
|
}
|
|
795
804
|
|
|
@@ -854,9 +863,9 @@ export class Whisper {
|
|
|
854
863
|
|
|
855
864
|
let shouldDecodeEndfOfTextToken = false
|
|
856
865
|
|
|
857
|
-
// If
|
|
858
|
-
// the second highest ranked token, then accept end-of-text
|
|
859
|
-
if (
|
|
866
|
+
// If at least one text token was decoded, and the end-of-text token's probability is
|
|
867
|
+
// sufficiently higher than the second highest ranked token, then accept end-of-text
|
|
868
|
+
if (atLeastOneTextTokenDecoded) {
|
|
860
869
|
const endOfTextTokenLogit = nonTimestampTokenLogits[endOfTextToken]
|
|
861
870
|
|
|
862
871
|
const otherTokensLogits = nonTimestampTokenLogits.slice()
|
|
@@ -931,8 +940,8 @@ export class Whisper {
|
|
|
931
940
|
if (options.suppressRepetition) {
|
|
932
941
|
// Using some hardcoded constants, for now
|
|
933
942
|
const tokenWindowSize = 30
|
|
934
|
-
const thresholdMatchLength =
|
|
935
|
-
const thresholdCycleRepetition =
|
|
943
|
+
const thresholdMatchLength = 4
|
|
944
|
+
const thresholdCycleRepetition = 3
|
|
936
945
|
|
|
937
946
|
const filteredCandidates: typeof topCandidates = []
|
|
938
947
|
|
|
@@ -1772,7 +1781,7 @@ export function normalizeWhisperModelName(modelName: WhisperModelName, languageC
|
|
|
1772
1781
|
modelName = modelName.slice(0, modelName.length - 3) as WhisperModelName
|
|
1773
1782
|
|
|
1774
1783
|
const logger = new Logger()
|
|
1775
|
-
logger.logTitledMessage(`Warning`, `The model '${originalModelName}' is English only and cannot be used to transcribe language '${languageCode}'.
|
|
1784
|
+
logger.logTitledMessage(`Warning`, `The model '${originalModelName}' is English only and cannot be used to transcribe language '${languageCode}'. Using '${modelName}' instead.`, chalk.yellowBright, 'warning')
|
|
1776
1785
|
}
|
|
1777
1786
|
|
|
1778
1787
|
return modelName
|
|
@@ -9,6 +9,7 @@ import path from 'node:path'
|
|
|
9
9
|
import { extractTarball } from './Compression.js'
|
|
10
10
|
import { createWriteStream, move, remove, readdir, ensureDir } from './FileSystem.js'
|
|
11
11
|
import chalk from 'chalk'
|
|
12
|
+
import { logLevelGreaterOrEqualTo } from '../api/GlobalOptions.js'
|
|
12
13
|
|
|
13
14
|
export async function downloadAndExtractTarball(options: GaxiosOptions, targetDir: string, baseTempPath: string, displayName = 'archive') {
|
|
14
15
|
const logger = new Logger()
|
|
@@ -18,7 +19,10 @@ export async function downloadAndExtractTarball(options: GaxiosOptions, targetDi
|
|
|
18
19
|
const tempDirPath = path.join(baseTempPath, `/${randomID}`)
|
|
19
20
|
await ensureDir(tempDirPath)
|
|
20
21
|
|
|
22
|
+
logger.end()
|
|
23
|
+
|
|
21
24
|
await downloadFile(options, tempTarballPath, `${chalk.cyanBright('Downloading')} ${chalk.greenBright(displayName)}`)
|
|
25
|
+
|
|
22
26
|
logger.end()
|
|
23
27
|
|
|
24
28
|
logger.start(`Extracting ${displayName}`)
|
|
@@ -40,7 +44,7 @@ export async function downloadAndExtractTarball(options: GaxiosOptions, targetDi
|
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
export async function downloadFile(options: GaxiosOptions, targetFilePath: string, prompt = 'Downloading') {
|
|
43
|
-
const write = writeToStderr
|
|
47
|
+
const write = logLevelGreaterOrEqualTo('info') ? writeToStderr : () => {}
|
|
44
48
|
|
|
45
49
|
const downloadPromise = new OpenPromise<void>()
|
|
46
50
|
|
package/src/utilities/Logger.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
import { Timer } from './Timer.js'
|
|
3
3
|
import { logToStderr, writeToStderr, yieldToEventLoop } from './Utilities.js'
|
|
4
|
+
import { LogLevel, logLevelGreaterOrEqualTo, logLevelSmallerThan } from '../api/GlobalOptions.js'
|
|
4
5
|
|
|
5
6
|
let currentActiveLogger: Logger | null = null
|
|
6
7
|
|
|
@@ -23,7 +24,9 @@ export class Logger {
|
|
|
23
24
|
await yieldToEventLoop()
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
if (logLevelGreaterOrEqualTo('info')) {
|
|
28
|
+
writeToStderr(`${titleColor(title)}.. `)
|
|
29
|
+
}
|
|
27
30
|
|
|
28
31
|
this.setAsActiveLogger()
|
|
29
32
|
|
|
@@ -44,30 +47,41 @@ export class Logger {
|
|
|
44
47
|
if (this.active && currentActiveLogger == this) {
|
|
45
48
|
const elapsedTime = this.timer.elapsedTime
|
|
46
49
|
|
|
47
|
-
|
|
50
|
+
if (logLevelGreaterOrEqualTo('info')) {
|
|
51
|
+
writeToStderr(`${elapsedTime.toFixed(1)}ms\n`)
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
currentActiveLogger = null
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
this.active = false
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
logDuration(message: any, startTime: number, titleColor = chalk.cyanBright) {
|
|
60
|
+
logDuration(message: any, startTime: number, titleColor = chalk.cyanBright, logLevel: LogLevel = 'info') {
|
|
55
61
|
const duration = Timer.currentTime - startTime
|
|
56
62
|
|
|
57
|
-
this.log(`${titleColor(message)}: ${duration.toFixed(1)}ms
|
|
63
|
+
this.log(`${titleColor(message)}: ${duration.toFixed(1)}ms`, logLevel)
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
logTitledMessage(title: string, content:
|
|
61
|
-
this.log(`${titleColor(title)}: ${content}
|
|
66
|
+
logTitledMessage(title: string, content: any, titleColor = chalk.cyanBright, logLevel: LogLevel = 'info') {
|
|
67
|
+
this.log(`${titleColor(title)}: ${content}`, logLevel)
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
log(message: any) {
|
|
70
|
+
log(message: any, logLevel: LogLevel = 'info') {
|
|
71
|
+
if (logLevelSmallerThan(logLevel)) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
65
75
|
if (currentActiveLogger == this || currentActiveLogger == null) {
|
|
66
76
|
logToStderr(message)
|
|
67
77
|
}
|
|
68
78
|
}
|
|
69
79
|
|
|
70
|
-
write(message: any) {
|
|
80
|
+
write(message: any, logLevel: LogLevel = 'info') {
|
|
81
|
+
if (logLevelSmallerThan(logLevel)) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
71
85
|
if (currentActiveLogger == this || currentActiveLogger == null) {
|
|
72
86
|
writeToStderr(message)
|
|
73
87
|
}
|
|
@@ -4,6 +4,7 @@ import { getAppDataDir, ensureDir, existsSync, remove } from './FileSystem.js'
|
|
|
4
4
|
import { appName } from '../api/Common.js'
|
|
5
5
|
import { GaxiosOptions } from 'gaxios'
|
|
6
6
|
import { getAppTempDir } from './PathUtilities.js'
|
|
7
|
+
import { getGlobalOption } from '../api/GlobalOptions.js'
|
|
7
8
|
|
|
8
9
|
export async function loadPackage(packageName: string) {
|
|
9
10
|
packageName = resolveToVersionedPackageNameIfNeeded(packageName)
|
|
@@ -16,13 +17,15 @@ export async function loadPackage(packageName: string) {
|
|
|
16
17
|
return packagePath
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
const packageBaseURL = getGlobalOption('packageBaseURL')
|
|
21
|
+
|
|
19
22
|
const tempPath = getAppTempDir(appName)
|
|
20
23
|
|
|
21
24
|
const headers = {
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
const options: GaxiosOptions = {
|
|
25
|
-
url: `${
|
|
28
|
+
url: `${packageBaseURL}${packageName}.tar.gz`,
|
|
26
29
|
headers
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -76,8 +79,6 @@ export function resolveVersionTagForUnversionedPackageName(unversionedPackageNam
|
|
|
76
79
|
return packageVersionTagResolutionLookup[unversionedPackageName] || defaultVersionTag
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
const basePackageUrl = 'https://huggingface.co/echogarden/echogarden-packages/resolve/main/'
|
|
80
|
-
|
|
81
82
|
const defaultVersionTag = '20230718'
|
|
82
83
|
|
|
83
84
|
const packageVersionTagResolutionLookup: { [packageName: string]: string } = {
|
|
@@ -517,7 +517,7 @@ export function getTokenRepetitionScore(tokens: string[] | number[]) {
|
|
|
517
517
|
longestMatch = matchLength
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
-
const cycleCount = matchLength / i
|
|
520
|
+
const cycleCount = (matchLength / i) + 1
|
|
521
521
|
|
|
522
522
|
if (cycleCount > longestCycleRepetition) {
|
|
523
523
|
longestCycleRepetition = cycleCount
|
|
@@ -540,7 +540,7 @@ export async function resolveModuleScriptPath(moduleName: string) {
|
|
|
540
540
|
export async function runOperationWithRetries<R>(
|
|
541
541
|
operationFunc: () => Promise<R>,
|
|
542
542
|
logger: Logger,
|
|
543
|
-
|
|
543
|
+
operationName = 'Operation',
|
|
544
544
|
delayBetweenRetries = 2000,
|
|
545
545
|
maxRetries = 200) {
|
|
546
546
|
|
|
@@ -560,21 +560,21 @@ export async function runOperationWithRetries<R>(
|
|
|
560
560
|
|
|
561
561
|
logger.setAsActiveLogger()
|
|
562
562
|
|
|
563
|
-
logger.logTitledMessage(`Error`, e.message, chalk.redBright)
|
|
564
|
-
logger.log(
|
|
565
|
-
logger.logTitledMessage(`${
|
|
563
|
+
logger.logTitledMessage(`Error`, e.message, chalk.redBright, 'error')
|
|
564
|
+
logger.log('', 'error')
|
|
565
|
+
logger.logTitledMessage(`${operationName} failed`, `Trying again in ${delayBetweenRetries}ms..`, chalk.redBright, 'error')
|
|
566
566
|
|
|
567
567
|
await delay(delayBetweenRetries)
|
|
568
568
|
|
|
569
|
-
logger.log(
|
|
570
|
-
logger.logTitledMessage(`Starting retry attempt`, `${retryIndex} / ${maxRetries}`, chalk.yellowBright)
|
|
571
|
-
logger.log(
|
|
569
|
+
logger.log(``, 'warning')
|
|
570
|
+
logger.logTitledMessage(`Starting retry attempt`, `${retryIndex} / ${maxRetries}`, chalk.yellowBright, 'warning')
|
|
571
|
+
logger.log(``, 'warning')
|
|
572
572
|
|
|
573
573
|
logger.unsetAsActiveLogger()
|
|
574
574
|
}
|
|
575
575
|
}
|
|
576
576
|
|
|
577
|
-
throw new Error(`${
|
|
577
|
+
throw new Error(`${operationName} failed after ${maxRetries} retry attempts`)
|
|
578
578
|
}
|
|
579
579
|
|
|
580
580
|
export function writeToStdinInChunks(process: ChildProcessWithoutNullStreams, buffer: Buffer, chunkSize: number) {
|