echogarden 1.1.1 → 1.2.0
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/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 +17 -12
- 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 +88 -77
- 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 +3 -3
- 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/cli/CLI.ts +257 -205
- 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 +17 -10
- 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
|
}
|
|
@@ -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,19 @@ export class Whisper {
|
|
|
783
784
|
return false
|
|
784
785
|
}
|
|
785
786
|
|
|
787
|
+
if (isInitialState) {
|
|
788
|
+
addToken(timestampTokensStart, timestampTokenLogits, 1.0, crossAttentionQKsForToken)
|
|
789
|
+
|
|
790
|
+
return true
|
|
791
|
+
}
|
|
792
|
+
|
|
786
793
|
const previousTokenWasTimestamp = this.isTimestampToken(decodedTokens[decodedTokens.length - 1])
|
|
787
794
|
const secondPreviousTokenWasTimestamp = this.isTimestampToken(decodedTokens[decodedTokens.length - 2])
|
|
788
795
|
|
|
789
796
|
// If there are two successive timestamp tokens decoded, or the previous timestamp was the first token,
|
|
790
797
|
// don't decode a timestamp
|
|
791
798
|
if (previousTokenWasTimestamp &&
|
|
792
|
-
(decodedTokens.length === initialTokens.length + 1) || secondPreviousTokenWasTimestamp) {
|
|
799
|
+
((decodedTokens.length === initialTokens.length + 1) || secondPreviousTokenWasTimestamp)) {
|
|
793
800
|
return false
|
|
794
801
|
}
|
|
795
802
|
|
|
@@ -854,9 +861,9 @@ export class Whisper {
|
|
|
854
861
|
|
|
855
862
|
let shouldDecodeEndfOfTextToken = false
|
|
856
863
|
|
|
857
|
-
// If
|
|
858
|
-
// the second highest ranked token, then accept end-of-text
|
|
859
|
-
if (
|
|
864
|
+
// If at least one text token was decoded, and the end-of-text token's probability is
|
|
865
|
+
// sufficiently higher than the second highest ranked token, then accept end-of-text
|
|
866
|
+
if (atLeastOneTextTokenDecoded) {
|
|
860
867
|
const endOfTextTokenLogit = nonTimestampTokenLogits[endOfTextToken]
|
|
861
868
|
|
|
862
869
|
const otherTokensLogits = nonTimestampTokenLogits.slice()
|
|
@@ -931,8 +938,8 @@ export class Whisper {
|
|
|
931
938
|
if (options.suppressRepetition) {
|
|
932
939
|
// Using some hardcoded constants, for now
|
|
933
940
|
const tokenWindowSize = 30
|
|
934
|
-
const thresholdMatchLength =
|
|
935
|
-
const thresholdCycleRepetition =
|
|
941
|
+
const thresholdMatchLength = 4
|
|
942
|
+
const thresholdCycleRepetition = 3
|
|
936
943
|
|
|
937
944
|
const filteredCandidates: typeof topCandidates = []
|
|
938
945
|
|
|
@@ -1772,7 +1779,7 @@ export function normalizeWhisperModelName(modelName: WhisperModelName, languageC
|
|
|
1772
1779
|
modelName = modelName.slice(0, modelName.length - 3) as WhisperModelName
|
|
1773
1780
|
|
|
1774
1781
|
const logger = new Logger()
|
|
1775
|
-
logger.logTitledMessage(`Warning`, `The model '${originalModelName}' is English only and cannot be used to transcribe language '${languageCode}'.
|
|
1782
|
+
logger.logTitledMessage(`Warning`, `The model '${originalModelName}' is English only and cannot be used to transcribe language '${languageCode}'. Using '${modelName}' instead.`, chalk.yellowBright, 'warning')
|
|
1776
1783
|
}
|
|
1777
1784
|
|
|
1778
1785
|
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) {
|