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.
Files changed (51) hide show
  1. package/data/schemas/options.json +56 -1
  2. package/dist/alignment/DTWSequenceAlignmentWindowed.js +6 -1
  3. package/dist/alignment/DTWSequenceAlignmentWindowed.js.map +1 -1
  4. package/dist/alignment/SpeechAlignment.js +1 -1
  5. package/dist/alignment/SpeechAlignment.js.map +1 -1
  6. package/dist/api/APIOptions.d.ts +3 -0
  7. package/dist/api/GlobalOptions.d.ts +10 -0
  8. package/dist/api/GlobalOptions.js +21 -3
  9. package/dist/api/GlobalOptions.js.map +1 -1
  10. package/dist/cli/CLI.js +224 -191
  11. package/dist/cli/CLI.js.map +1 -1
  12. package/dist/cli/CLIConfigFile.js +7 -7
  13. package/dist/cli/CLIConfigFile.js.map +1 -1
  14. package/dist/cli/CLIOptions.d.ts +7 -0
  15. package/dist/cli/CLIOptions.js +2 -0
  16. package/dist/cli/CLIOptions.js.map +1 -0
  17. package/dist/cli/CLIParser.d.ts +5 -6
  18. package/dist/cli/CLIParser.js +16 -12
  19. package/dist/cli/CLIParser.js.map +1 -1
  20. package/dist/recognition/WhisperSTT.js +17 -12
  21. package/dist/recognition/WhisperSTT.js.map +1 -1
  22. package/dist/utilities/FileDownloader.js +3 -1
  23. package/dist/utilities/FileDownloader.js.map +1 -1
  24. package/dist/utilities/Logger.d.ts +5 -4
  25. package/dist/utilities/Logger.js +19 -8
  26. package/dist/utilities/Logger.js.map +1 -1
  27. package/dist/utilities/PackageManager.js +3 -2
  28. package/dist/utilities/PackageManager.js.map +1 -1
  29. package/dist/utilities/Utilities.d.ts +1 -1
  30. package/dist/utilities/Utilities.js +9 -9
  31. package/dist/utilities/Utilities.js.map +1 -1
  32. package/docs/API.md +3 -10
  33. package/docs/CLI.md +88 -77
  34. package/docs/Options.md +23 -0
  35. package/docs/Releases.md +1 -1
  36. package/docs/Tasklist.md +3 -5
  37. package/docs/Technical.md +2 -2
  38. package/package.json +3 -3
  39. package/src/alignment/DTWSequenceAlignmentWindowed.ts +7 -1
  40. package/src/alignment/SpeechAlignment.ts +1 -1
  41. package/src/api/APIOptions.ts +3 -0
  42. package/src/api/GlobalOptions.ts +33 -5
  43. package/src/cli/CLI.ts +257 -205
  44. package/src/cli/CLIConfigFile.ts +7 -7
  45. package/src/cli/CLIOptions.ts +8 -0
  46. package/src/cli/CLIParser.ts +20 -17
  47. package/src/recognition/WhisperSTT.ts +17 -10
  48. package/src/utilities/FileDownloader.ts +5 -1
  49. package/src/utilities/Logger.ts +22 -8
  50. package/src/utilities/PackageManager.ts +4 -3
  51. package/src/utilities/Utilities.ts +9 -9
@@ -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 command in parsedJSON) {
60
- const commandObj = parsedJSON[command]
59
+ for (const operation in parsedJSON) {
60
+ const operationObj = parsedJSON[operation]
61
61
 
62
- const commandMap = new Map<string, string>()
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
- commandMap.set(propertyPath, JSON.stringify(propertyValue))
73
+ operationMap.set(propertyPath, JSON.stringify(propertyValue))
74
74
  } else {
75
75
  addFromObject(propertyValue, propertyPath)
76
76
  }
77
77
  } else {
78
- commandMap.set(propertyPath, propertyValue)
78
+ operationMap.set(propertyPath, propertyValue)
79
79
  }
80
80
  }
81
81
  }
82
82
 
83
- addFromObject(commandObj, '')
83
+ addFromObject(operationObj, '')
84
84
 
85
- result.set(command, commandMap)
85
+ result.set(operation, operationMap)
86
86
  }
87
87
 
88
88
  return result
@@ -0,0 +1,8 @@
1
+ export interface CLIOptions {
2
+ play?: boolean
3
+ overwrite?: boolean
4
+ debug?: boolean
5
+ config?: string
6
+ }
7
+
8
+ export const CLIOptionsKeys: (keyof CLIOptions)[] = ['play', 'overwrite', 'debug', 'config']
@@ -1,8 +1,7 @@
1
- export function parseCLIArguments(command: string, args: string[]): CLIArguments {
2
- const parsedArgs: CLIArguments = {
3
- command,
4
- commandArgs: [],
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 indexOfEquals = optionText.indexOf('=')
20
- if (indexOfEquals == 0) {
18
+ const indexOfEqualSign = optionText.indexOf('=')
19
+ if (indexOfEqualSign == 0) {
21
20
  throw new Error('An option cannot have an empty name.')
22
- } else if (indexOfEquals == -1) {
23
- parsedArgs.options.set(optionText, '')
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, indexOfEquals)
26
- const value = optionText.substring(indexOfEquals + 1)
27
- parsedArgs.options.set(key, value)
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.commandArgs.push(currentArg)
34
+ parsedArgs.operationArgs.push(currentArg)
31
35
  }
32
36
  }
33
37
 
34
38
  return parsedArgs
35
39
  }
36
40
 
37
- export type CLIArguments = {
38
- command: string
39
- commandArgs: string[]
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 == initialTokens.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
- // Compute logits
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 (isInitialState) {
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 not in initial state, and the end-of-text token's probability is sufficiently higher than
858
- // the second highest ranked token, then accept end-of-text
859
- if (!isInitialState) {
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 = 6
935
- const thresholdCycleRepetition = 2.0
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}'. using '${modelName}' instead.`, chalk.yellowBright)
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
 
@@ -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
- writeToStderr(`${titleColor(title)}.. `)
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
- writeToStderr(`${elapsedTime.toFixed(1)}ms\n`)
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: string, titleColor = chalk.cyanBright) {
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: `${basePackageUrl}${packageName}.tar.gz`,
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
- opreationName = 'Operation',
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(`${opreationName} failed`, `Trying again in ${delayBetweenRetries}ms..`, chalk.redBright)
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(`${opreationName} failed after ${maxRetries} retry attempts`)
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) {