echogarden 1.6.2 → 1.7.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 (86) hide show
  1. package/data/schemas/options.json +30 -4
  2. package/dist/api/SourceSeparation.d.ts +3 -2
  3. package/dist/api/SourceSeparation.js +10 -8
  4. package/dist/api/SourceSeparation.js.map +1 -1
  5. package/dist/api/Synthesis.js +3 -1
  6. package/dist/api/Synthesis.js.map +1 -1
  7. package/dist/api/TextTranslation.d.ts +3 -1
  8. package/dist/api/TextTranslation.js +12 -6
  9. package/dist/api/TextTranslation.js.map +1 -1
  10. package/dist/build-tools/MakeTarballsForInstalledPackages.d.ts +1 -0
  11. package/dist/build-tools/MakeTarballsForInstalledPackages.js +20 -0
  12. package/dist/build-tools/MakeTarballsForInstalledPackages.js.map +1 -0
  13. package/dist/cli/CLI.js +4 -3
  14. package/dist/cli/CLI.js.map +1 -1
  15. package/dist/dsp/FFT.d.ts +2 -3
  16. package/dist/dsp/FFT.js +25 -23
  17. package/dist/dsp/FFT.js.map +1 -1
  18. package/dist/nlp/Segmentation.js +2 -1
  19. package/dist/nlp/Segmentation.js.map +1 -1
  20. package/dist/nlp/TextNormalizer.d.ts +2 -0
  21. package/dist/nlp/TextNormalizer.js +51 -0
  22. package/dist/nlp/TextNormalizer.js.map +1 -1
  23. package/dist/recognition/WhisperSTT.d.ts +2 -0
  24. package/dist/recognition/WhisperSTT.js +7 -6
  25. package/dist/recognition/WhisperSTT.js.map +1 -1
  26. package/dist/source-separation/MDXNetSourceSeparation.d.ts +20 -2
  27. package/dist/source-separation/MDXNetSourceSeparation.js +134 -76
  28. package/dist/source-separation/MDXNetSourceSeparation.js.map +1 -1
  29. package/dist/subtitles/Subtitles.js +2 -1
  30. package/dist/subtitles/Subtitles.js.map +1 -1
  31. package/dist/synthesis/EspeakTTS.js +5 -5
  32. package/dist/synthesis/EspeakTTS.js.map +1 -1
  33. package/dist/tests/Test.js +0 -1
  34. package/dist/tests/Test.js.map +1 -1
  35. package/dist/text-translation/GoogleTranslateTextTranslation.d.ts +9 -3
  36. package/dist/text-translation/GoogleTranslateTextTranslation.js +59 -29
  37. package/dist/text-translation/GoogleTranslateTextTranslation.js.map +1 -1
  38. package/dist/utilities/OnnxUtilities.d.ts +1 -0
  39. package/dist/utilities/OnnxUtilities.js +5 -5
  40. package/dist/utilities/OnnxUtilities.js.map +1 -1
  41. package/dist/utilities/PackageManager.js +3 -0
  42. package/dist/utilities/PackageManager.js.map +1 -1
  43. package/dist/utilities/StringUtilities.d.ts +28 -0
  44. package/dist/utilities/StringUtilities.js +150 -0
  45. package/dist/utilities/StringUtilities.js.map +1 -0
  46. package/dist/utilities/TarballMaker.d.ts +3 -4
  47. package/dist/utilities/TarballMaker.js +6 -19
  48. package/dist/utilities/TarballMaker.js.map +1 -1
  49. package/dist/utilities/Timeline.js +2 -1
  50. package/dist/utilities/Timeline.js.map +1 -1
  51. package/dist/utilities/Utilities.d.ts +1 -33
  52. package/dist/utilities/Utilities.js +3 -176
  53. package/dist/utilities/Utilities.js.map +1 -1
  54. package/dist/utilities/WebReader.js +1 -1
  55. package/dist/utilities/WebReader.js.map +1 -1
  56. package/dist/utilities/WikipediaReader.js +1 -1
  57. package/dist/utilities/WikipediaReader.js.map +1 -1
  58. package/dist/utilities/WindowedList.d.ts +10 -0
  59. package/dist/utilities/WindowedList.js +36 -0
  60. package/dist/utilities/WindowedList.js.map +1 -0
  61. package/docs/Engines.md +1 -1
  62. package/docs/Options.md +8 -2
  63. package/package.json +8 -9
  64. package/src/api/SourceSeparation.ts +15 -10
  65. package/src/api/Synthesis.ts +3 -1
  66. package/src/api/TextTranslation.ts +17 -9
  67. package/src/build-tools/MakeTarballsForInstalledPackages.ts +25 -0
  68. package/src/cli/CLI.ts +4 -3
  69. package/src/dsp/FFT.ts +30 -33
  70. package/src/nlp/Segmentation.ts +2 -1
  71. package/src/nlp/TextNormalizer.ts +60 -0
  72. package/src/recognition/WhisperSTT.ts +7 -6
  73. package/src/source-separation/MDXNetSourceSeparation.ts +183 -85
  74. package/src/subtitles/Subtitles.ts +2 -1
  75. package/src/synthesis/EspeakTTS.ts +5 -5
  76. package/src/tests/Test.ts +0 -4
  77. package/src/text-translation/GoogleTranslateTextTranslation.ts +91 -30
  78. package/src/utilities/OnnxUtilities.ts +7 -8
  79. package/src/utilities/PackageManager.ts +3 -0
  80. package/src/utilities/StringUtilities.ts +203 -0
  81. package/src/utilities/TarballMaker.ts +6 -23
  82. package/src/utilities/Timeline.ts +2 -1
  83. package/src/utilities/Utilities.ts +4 -237
  84. package/src/utilities/WebReader.ts +1 -1
  85. package/src/utilities/WikipediaReader.ts +1 -1
  86. package/src/utilities/WindowedList.ts +45 -0
@@ -1,35 +1,55 @@
1
1
  import { request } from "gaxios"
2
2
  import { Logger } from "../utilities/Logger.js"
3
- import { Fragment, splitToFragments, splitToLines, splitToParagraphs, splitToSentences } from "../nlp/Segmentation.js"
3
+ import { Fragment, splitToFragments, splitToLines } from "../nlp/Segmentation.js"
4
4
  import { TranslationPair } from "../api/TextTranslation.js"
5
5
  import { getChromeOnWindowsHeaders, getChromeOnAndroidHeaders } from "../utilities/BrowserRequestHeaders.js"
6
- import { logToStderr, splitAndPreserveSeparators } from "../utilities/Utilities.js"
6
+ import { logToStderr } from "../utilities/Utilities.js"
7
7
  import { getShortLanguageCode } from "../utilities/Locale.js"
8
+ import { PlainTextOptions } from "../api/Common.js"
9
+ import { extendDeep } from "../utilities/ObjectUtilities.js"
10
+ import { splitAndPreserveSeparators } from "../utilities/StringUtilities.js"
8
11
 
9
12
  const log = logToStderr
10
13
 
11
- export async function translateText(text: string, sourceLanguage: string, targetLanguage: string, tld = 'com') {
14
+ export async function translateText(
15
+ text: string,
16
+ sourceLanguage: string,
17
+ targetLanguage: string,
18
+ plainTextOptions: PlainTextOptions,
19
+ options: GoogleTranslateTextTranslationOptions) {
20
+
12
21
  const logger = new Logger()
13
22
 
14
- const maxTextLengthPerRequest = 2000
23
+ if (!supportsLanguage(sourceLanguage)) {
24
+ throw new Error(`Language code ${sourceLanguage} is not supported by the Google Translate engine. Supported language codes are ${supportedLanguageCodes.join(', ')}`)
25
+ }
26
+
27
+ if (!supportsLanguage(targetLanguage)) {
28
+ throw new Error(`Language code ${sourceLanguage} is not supported by the Google Translate engine. Supported language codes are ${supportedLanguageCodes.join(', ')}`)
29
+ }
30
+
31
+ options = extendDeep(defaultGoogleTranslateTextTranslationOptions, options)
32
+
33
+ const maxCharactersPerPart = options.maxCharactersPerPart!
15
34
 
16
- //const paragraphs = splitAndPreserveSeparators(text, /(\r?\n)(\r?\n)/g)
17
- const paragraphs = splitToParagraphs(text, 'double', 'preserve')
35
+ const paragraphSeperatorPattern = plainTextOptions.paragraphBreaks === 'double' ? /(\r?\n)(\r?\n)+/g : /(\r?\n)+/g
36
+
37
+ const paragraphs = splitAndPreserveSeparators(text, paragraphSeperatorPattern)
18
38
 
19
39
  const fragmentsForParagraph: Fragment[][] = []
20
40
 
21
41
  for (const paragraph of paragraphs) {
22
- const fragments = await splitToFragments(paragraph, maxTextLengthPerRequest, getShortLanguageCode(sourceLanguage))
42
+ const fragments = await splitToFragments(paragraph, maxCharactersPerPart, getShortLanguageCode(sourceLanguage))
23
43
 
24
44
  fragmentsForParagraph.push(fragments)
25
45
  }
26
46
 
27
- const fragmentBundles: Fragment[][] = [[]]
47
+ const parts: Fragment[][] = [[]]
28
48
 
29
- function totalLengthOfLastBundle() {
49
+ function totalLengthOfLastPart() {
30
50
  let total = 0
31
51
 
32
- for (const fragment of fragmentBundles[fragmentBundles.length - 1]) {
52
+ for (const fragment of parts[parts.length - 1]) {
33
53
  total += fragment.length
34
54
  }
35
55
 
@@ -42,14 +62,14 @@ export async function translateText(text: string, sourceLanguage: string, target
42
62
  const fragments = fragmentsForParagraph[paragraphIndex]
43
63
 
44
64
  for (const fragment of fragments) {
45
- let lastBundle = fragmentBundles[fragmentBundles.length - 1]
65
+ let lastPart = parts[parts.length - 1]
46
66
 
47
- if (totalLengthOfLastBundle() + fragment.length > maxTextLengthPerRequest) {
48
- lastBundle = []
49
- fragmentBundles.push(lastBundle)
67
+ if (totalLengthOfLastPart() + fragment.length > maxCharactersPerPart) {
68
+ lastPart = []
69
+ parts.push(lastPart)
50
70
  }
51
71
 
52
- lastBundle.push(fragment)
72
+ lastPart.push(fragment)
53
73
 
54
74
  fragmentToParagraphIndex.set(fragment, paragraphIndex)
55
75
  }
@@ -57,25 +77,25 @@ export async function translateText(text: string, sourceLanguage: string, target
57
77
 
58
78
  const translatedFragmentsForParagraphs = paragraphs.map(_ => [] as string[])
59
79
 
60
- for (let fragmentBundleIndex = 0; fragmentBundleIndex < fragmentBundles.length; fragmentBundleIndex++) {
61
- const bundle = fragmentBundles[fragmentBundleIndex]
80
+ for (let partIndex = 0; partIndex < parts.length; partIndex++) {
81
+ const part = parts[partIndex]
62
82
 
63
- const joinedBundle = bundle.map(x => x.text.replaceAll('|', ' ')).join(` | `)
83
+ const joinedFragmentsInPart = part.map(x => x.text.replaceAll('|', ' ')).join(`\n|\n`)
64
84
 
65
- logger.logTitledMessage(`\nTranslate part ${fragmentBundleIndex + 1} of ${fragmentBundles.length}`, joinedBundle)
85
+ logger.logTitledMessage(`\nTranslate part ${partIndex + 1} of ${parts.length}`, joinedFragmentsInPart.replaceAll('\n|\n', ''))
66
86
 
67
87
  logger.start(`Request translation from Google Translate`)
68
- const fragmentTranslationPair = await translateText_MobileWeb(joinedBundle, sourceLanguage, targetLanguage)
88
+ const fragmentTranslationPair = await translateText_MobileWeb(joinedFragmentsInPart, sourceLanguage, targetLanguage, options)
69
89
  logger.end()
70
90
 
71
- const translatedTextForBundle = fragmentTranslationPair[0].translatedText
91
+ const translatedTextForPart = fragmentTranslationPair[0].translatedText
72
92
 
73
- logger.logTitledMessage(`Translated part`, `"${translatedTextForBundle}"`)
93
+ logger.logTitledMessage(`Translated part`, `"${translatedTextForPart.replaceAll(' | ', '\n') }"`)
74
94
 
75
- const splitTranslation = translatedTextForBundle.split(`|`)
95
+ const splitTranslation = translatedTextForPart.split(`|`)
76
96
 
77
- for (let fragmentIndex = 0; fragmentIndex < bundle.length; fragmentIndex++) {
78
- const fragment = bundle[fragmentIndex]
97
+ for (let fragmentIndex = 0; fragmentIndex < part.length; fragmentIndex++) {
98
+ const fragment = part[fragmentIndex]
79
99
  const translatedFragment = splitTranslation[fragmentIndex].trim()
80
100
 
81
101
  const paragraphIndex = fragmentToParagraphIndex.get(fragment)!
@@ -87,18 +107,46 @@ export async function translateText(text: string, sourceLanguage: string, target
87
107
  const translationPairs: TranslationPair[] = []
88
108
 
89
109
  for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
110
+ const sourceParagraph = paragraphs[paragraphIndex]
111
+ const translatedParagraph = translatedFragmentsForParagraphs[paragraphIndex].join(' ')
112
+
113
+ let translatedParagraphWithWhitespace = ''
114
+
115
+ const sourceParagraphLeadingWhitespaceMatches = sourceParagraph.match(/^\s+/)
116
+
117
+ if (sourceParagraphLeadingWhitespaceMatches) {
118
+ translatedParagraphWithWhitespace += sourceParagraphLeadingWhitespaceMatches[0]
119
+ }
120
+
121
+ translatedParagraphWithWhitespace += translatedParagraph
122
+
123
+ const sourceParagraphTrailingWhitespaceMatches = sourceParagraph.match(/\s+$/)
124
+
125
+ if (sourceParagraphTrailingWhitespaceMatches) {
126
+ translatedParagraphWithWhitespace += sourceParagraphTrailingWhitespaceMatches[0]
127
+ }
128
+
90
129
  translationPairs.push({
91
130
  sourceText: paragraphs[paragraphIndex],
92
- translatedText: translatedFragmentsForParagraphs[paragraphIndex].join(' ')
131
+ translatedText: translatedParagraphWithWhitespace,
93
132
  })
94
133
  }
95
134
 
96
- const translatedText = translationPairs.map(pair => pair.translatedText).join('\n\n')
135
+ const translatedParagraphs = translationPairs.map(pair => pair.translatedText)
136
+
137
+ const translatedText = translatedParagraphs.join('')
97
138
 
98
139
  return { translationPairs, translatedText }
99
140
  }
100
141
 
101
- export async function translateText_MobileWeb(text: string, sourceLanguage: string, targetLanguage: string, tld = 'com') {
142
+ export async function translateText_MobileWeb(
143
+ text: string,
144
+ sourceLanguage: string,
145
+ targetLanguage: string,
146
+ options: GoogleTranslateTextTranslationOptions) {
147
+
148
+ const tld = options.tld
149
+
102
150
  const logger = new Logger()
103
151
 
104
152
  logger.start(`Request translation from Google Translate`)
@@ -419,11 +467,11 @@ function normalizeLanguageCodeForGoogleTranslate(languageCode: string) {
419
467
  }
420
468
 
421
469
  export function supportsLanguage(langCode: string) {
422
- return supportedLanguages.includes(normalizeLanguageCodeForGoogleTranslate(langCode))
470
+ return supportedLanguageCodes.includes(normalizeLanguageCodeForGoogleTranslate(langCode))
423
471
  }
424
472
 
425
473
  // 243 Languages supported
426
- export const supportedLanguages = [
474
+ export const supportedLanguageCodes = [
427
475
  'ab', // Abkhaz
428
476
  'ace', // Acehnese
429
477
  'ach', // Acholi
@@ -464,6 +512,7 @@ export const supportedLanguages = [
464
512
  'ch', // Chamorro
465
513
  'ce', // Chechen
466
514
  'ny', // Chichewa
515
+ 'zh', // Chinese (same as zh-CN)
467
516
  'zh-CN', // Chinese (Simplified)
468
517
  'zh-TW', // Chinese (Traditional)
469
518
  'chk', // Chuukese
@@ -505,6 +554,7 @@ export const supportedLanguages = [
505
554
  'cnh', // Hakha Chin
506
555
  'ha', // Hausa
507
556
  'haw', // Hawaiian
557
+ 'he', // Hebrew
508
558
  'iw', // Hebrew
509
559
  'hil', // Hiligaynon
510
560
  'hi', // Hindi
@@ -520,6 +570,7 @@ export const supportedLanguages = [
520
570
  'it', // Italian
521
571
  'jam', // Jamaican Patois
522
572
  'ja', // Japanese
573
+ 'jv', // Javanese
523
574
  'jw', // Javanese
524
575
  'kac', // Jingpo
525
576
  'kl', // Kalaallisut
@@ -668,3 +719,13 @@ export const supportedLanguages = [
668
719
  'zap', // Zapotec
669
720
  'zu', // Zulu
670
721
  ]
722
+
723
+ export interface GoogleTranslateTextTranslationOptions {
724
+ tld?: string
725
+ maxCharactersPerPart?: number
726
+ }
727
+
728
+ export const defaultGoogleTranslateTextTranslationOptions: GoogleTranslateTextTranslationOptions = {
729
+ tld: 'com',
730
+ maxCharactersPerPart: 2000,
731
+ }
@@ -1,19 +1,11 @@
1
1
  import type * as Onnx from 'onnxruntime-node'
2
2
 
3
3
  export function getOnnxSessionOptions(options: OnnxSessionOptions) {
4
-
5
4
  const onnxOptions: Onnx.InferenceSession.SessionOptions = {
6
5
  executionProviders: ['cpu'],
7
6
  logSeverityLevel: 3,
8
7
  }
9
8
 
10
- function dmlProviderAvailable() {
11
- const platform = process.platform
12
- const arch = process.arch
13
-
14
- return platform === 'win32' && arch === 'x64'
15
- }
16
-
17
9
  if (options) {
18
10
  if (options.executionProviders != null) {
19
11
  let executionProviders = options.executionProviders.filter(provider => {
@@ -54,6 +46,13 @@ export function makeOnnxLikeFloat32Tensor(onnxTensor: Onnx.Tensor): OnnxLikeFloa
54
46
  }
55
47
  }
56
48
 
49
+ export function dmlProviderAvailable() {
50
+ const platform = process.platform
51
+ const arch = process.arch
52
+
53
+ return platform === 'win32' && arch === 'x64'
54
+ }
55
+
57
56
  export interface OnnxLikeFloat32Tensor {
58
57
  readonly data: Float32Array
59
58
  readonly dims: number[]
@@ -157,6 +157,9 @@ const packageVersionTagResolutionLookup: { [packageName: string]: string } = {
157
157
  'mdxnet-UVR_MDXNET_2_9682': '20240330',
158
158
  'mdxnet-UVR_MDXNET_3_9662': '20240330',
159
159
  'mdxnet-UVR_MDXNET_KARA': '20240330',
160
+ 'mdxnet-UVR_MDXNET_Main': '20241008',
161
+ 'mdxnet-Kim_Vocal_1': '20241008',
162
+ 'mdxnet-Kim_Vocal_2': '20241008',
160
163
 
161
164
  // whisper.cpp models
162
165
  'whisper.cpp-tiny': '20240405',
@@ -0,0 +1,203 @@
1
+ export function includesAnyOf(str: string, substrings: string[]) {
2
+ return indexOfAnyOf(str, substrings) >= 0
3
+ }
4
+
5
+ export function indexOfAnyOf(str: string, substrings: string[]) {
6
+ for (const substring of substrings) {
7
+ const index = str.indexOf(substring)
8
+
9
+ if (index >= 0) {
10
+ return index
11
+ }
12
+ }
13
+
14
+ return -1
15
+ }
16
+
17
+ export function startsWithAnyOf(str: string, prefixes: string[]) {
18
+ for (const prefix of prefixes) {
19
+ if (str.startsWith(prefix)) {
20
+ return true
21
+ }
22
+ }
23
+
24
+ return false
25
+ }
26
+
27
+ export function formatHMS(timeHMS: { hours: number, minutes: number, seconds: number, milliseconds: number }, decimalSeparator = '.') {
28
+ return `${formatIntegerWithLeadingZeros(timeHMS.hours, 2)}:${formatIntegerWithLeadingZeros(timeHMS.minutes, 2)}:${formatIntegerWithLeadingZeros(timeHMS.seconds, 2)}${decimalSeparator}${formatIntegerWithLeadingZeros(timeHMS.milliseconds, 3)}`
29
+ }
30
+
31
+ export function formatMS(timeMS: { minutes: number, seconds: number, milliseconds: number }, decimalSeparator = '.') {
32
+ return `${formatIntegerWithLeadingZeros(timeMS.minutes, 2)}:${formatIntegerWithLeadingZeros(timeMS.seconds, 2)}${decimalSeparator}${formatIntegerWithLeadingZeros(timeMS.milliseconds, 3)}`
33
+ }
34
+
35
+ export function formatIntegerWithLeadingZeros(num: number, minDigitCount: number) {
36
+ num = Math.floor(num)
37
+
38
+ let numAsString = `${num}`
39
+
40
+ while (numAsString.length < minDigitCount) {
41
+ numAsString = `0${numAsString}`
42
+ }
43
+
44
+ return numAsString
45
+ }
46
+
47
+ export function formatListWithQuotedElements(strings: string[], quoteSymbol = `'`) {
48
+ return strings.map(str => `${quoteSymbol}${str}${quoteSymbol}`).join(', ')
49
+ }
50
+
51
+ export function getUTF32Chars(str: string) {
52
+ const utf32chars: string[] = []
53
+ const mapping: number[] = []
54
+
55
+ let utf32Index = 0
56
+
57
+ for (const utf32char of str) {
58
+ utf32chars.push(utf32char)
59
+
60
+ for (let i = 0; i < utf32char.length; i++) {
61
+ mapping.push(utf32Index)
62
+ }
63
+
64
+ utf32Index += 1
65
+ }
66
+
67
+ mapping.push(utf32Index)
68
+
69
+ return { utf32chars, mapping }
70
+ }
71
+
72
+ export function containsInvalidCodepoint(str: string) {
73
+ for (const char of str) {
74
+ if (char.codePointAt(0) === 65533) {
75
+ return true
76
+ }
77
+ }
78
+
79
+ return false
80
+ }
81
+
82
+
83
+ export function splitAndPreserveSeparators(text: string, separatorRegex: RegExp): string[] {
84
+ if (!separatorRegex.flags.includes('g')) {
85
+ throw new Error('RegExp must be global')
86
+ }
87
+
88
+ // Use the match method to find all matches for the separators
89
+ const matches = text.match(separatorRegex)
90
+
91
+ // If no matches are found, return the original text as a single element array
92
+ if (!matches) {
93
+ return [text]
94
+ }
95
+
96
+ // Initialize the result array
97
+ const result: string[] = []
98
+
99
+ // Initialize the start position
100
+ let lastIndex = 0
101
+
102
+ // Iterate through the matches
103
+ matches.forEach(match => {
104
+ // Get the index of the current match
105
+ const matchIndex = text.indexOf(match, lastIndex)
106
+
107
+ // Add the substring before the match to the result, joined with the match itself
108
+ result.push(text.substring(lastIndex, matchIndex) + match)
109
+
110
+ // Update the last index to the end of the current match
111
+ lastIndex = matchIndex + match.length
112
+ })
113
+
114
+ // Add the remaining substring after the last match to the result
115
+ {
116
+ const remainingText = text.substring(lastIndex)
117
+
118
+ if (remainingText.length > 0) {
119
+ result.push(remainingText)
120
+ }
121
+ }
122
+
123
+ return result
124
+ }
125
+
126
+ export function getTokenRepetitionScore(tokens: string[] | number[]) {
127
+ const maxCycleLength = Math.floor(tokens.length / 2)
128
+
129
+ const matchLengthForCycleLength: number[] = [0]
130
+
131
+ for (let cycleLength = 1; cycleLength <= maxCycleLength; cycleLength++) {
132
+ let matchCount = 0
133
+
134
+ for (let leftIndex = cycleLength; leftIndex < tokens.length; leftIndex++) {
135
+ const referenceIndex = leftIndex - cycleLength
136
+
137
+ if (tokens[leftIndex] !== tokens[referenceIndex]) {
138
+ break
139
+ }
140
+
141
+ matchCount += 1
142
+ }
143
+
144
+ const score = matchCount
145
+
146
+ matchLengthForCycleLength.push(score)
147
+ }
148
+
149
+ let longestMatch = -Infinity
150
+ let longestCycleRepetition = -Infinity
151
+
152
+ for (let i = 1; i <= matchLengthForCycleLength.length; i++) {
153
+ const matchLength = matchLengthForCycleLength[i]
154
+
155
+ if (matchLength > longestMatch) {
156
+ longestMatch = matchLength
157
+ }
158
+
159
+ const cycleCount = (matchLength / i) + 1
160
+
161
+ if (cycleCount > longestCycleRepetition) {
162
+ longestCycleRepetition = cycleCount
163
+ }
164
+ }
165
+
166
+ return { longestMatch, longestCycleRepetition }
167
+ }
168
+
169
+ export async function convertHtmlToText(html: string) {
170
+ const { htmlToText } = await import('html-to-text')
171
+
172
+ const text = htmlToText(html, {
173
+ wordwrap: false,
174
+
175
+ selectors: [
176
+ { selector: 'a', options: { ignoreHref: true } },
177
+ { selector: 'img', format: 'skip' },
178
+ { selector: 'h1', options: { uppercase: false } },
179
+ { selector: 'h2', options: { uppercase: false } },
180
+ { selector: 'h3', options: { uppercase: false } },
181
+ { selector: 'h4', options: { uppercase: false } },
182
+ { selector: 'table', options: { uppercaseHeaderCells: false } }
183
+ ]
184
+ })
185
+
186
+ return text || ''
187
+ }
188
+
189
+ export function substituteCharactersUsingLookup(text: string, substitutionLookup: Record<string, string>) {
190
+ let resultText = ''
191
+
192
+ for (const char of text) {
193
+ const substitution = substitutionLookup[char]
194
+
195
+ if (substitution !== undefined) {
196
+ resultText += substitution
197
+ } else {
198
+ resultText += char
199
+ }
200
+ }
201
+
202
+ return resultText
203
+ }
@@ -1,28 +1,11 @@
1
1
  import path from 'path'
2
2
  import { createTarball } from './Compression.js'
3
- import { ensureDir, existsSync, move, readdir, stat } from './FileSystem.js'
4
- import { ensureAndGetPackagesDir } from './PackageManager.js'
3
+ import { ensureDir, move, readdir, stat } from './FileSystem.js'
5
4
  import { appName } from '../api/Common.js'
6
5
  import { getRandomHexString } from './Utilities.js'
7
6
  import { getAppTempDir } from './PathUtilities.js'
8
7
 
9
- const tarballDir = '../resources/tarballs'
10
-
11
- export async function makeTarballsForInstalledPackages(skipIfExists = false) {
12
- const packagesDir = await ensureAndGetPackagesDir()
13
- const packageList = await readdir(packagesDir)
14
-
15
- for (const packageName of packageList) {
16
- if (skipIfExists && existsSync(path.join(tarballDir, `${packageName}.tar.gz`))) {
17
- continue
18
- }
19
-
20
- const packagePath = path.join(packagesDir, packageName)
21
- await createNamedTarball(packagePath, packageName)
22
- }
23
- }
24
-
25
- export async function createNamedTarball(inputPath: string, name: string) {
8
+ export async function createNamedTarball(inputPath: string, name: string, tarballDir: string) {
26
9
  const tempDir = getAppTempDir(appName)
27
10
  await ensureDir(tempDir)
28
11
  const tempFilename = path.join(tempDir, getRandomHexString(16))
@@ -34,7 +17,7 @@ export async function createNamedTarball(inputPath: string, name: string) {
34
17
  await move(tempFilename, targetFilname)
35
18
  }
36
19
 
37
- export async function createTarballForEachDirIn(baseDir: string, namePrefix: String) {
20
+ export async function createTarballForEachDirIn(baseDir: string, namePrefix: string, tarballDir: string) {
38
21
  for (const dirName of await readdir(baseDir)) {
39
22
  const dirPath = path.join(baseDir, dirName)
40
23
 
@@ -46,11 +29,11 @@ export async function createTarballForEachDirIn(baseDir: string, namePrefix: Str
46
29
 
47
30
  const archiveName = `${namePrefix}-${dirName}`
48
31
 
49
- await createNamedTarball(dirPath, archiveName)
32
+ await createNamedTarball(dirPath, archiveName, tarballDir)
50
33
  }
51
34
  }
52
35
 
53
- export async function createTarballForEachFileIn(baseDir: string, namePrefix: String) {
36
+ export async function createTarballForEachFileIn(baseDir: string, namePrefix: string, tarballDir: string) {
54
37
  for (const filename of await readdir(baseDir)) {
55
38
  const filenameWithoutExtension = path.parse(filename).name
56
39
 
@@ -64,6 +47,6 @@ export async function createTarballForEachFileIn(baseDir: string, namePrefix: St
64
47
 
65
48
  const archiveName = `${namePrefix}-${filenameWithoutExtension}`
66
49
 
67
- await createNamedTarball(filePath, archiveName)
50
+ await createNamedTarball(filePath, archiveName, tarballDir)
68
51
  }
69
52
  }
@@ -1,7 +1,8 @@
1
1
  import { ParagraphBreakType, WhitespaceProcessing } from '../api/Common.js'
2
2
  import { applyWhitespaceProcessing, isWord, isWordOrSymbolWord, splitToParagraphs, splitToSentences, splitToWords } from '../nlp/Segmentation.js'
3
3
  import { deepClone } from './ObjectUtilities.js'
4
- import { getUTF32Chars, roundToDigits, splitAndPreserveSeparators } from './Utilities.js'
4
+ import { getUTF32Chars, splitAndPreserveSeparators } from './StringUtilities.js'
5
+ import { roundToDigits } from './Utilities.js'
5
6
 
6
7
  export function addTimeOffsetToTimeline(targetTimeline: Timeline, timeOffset: number) {
7
8
  if (!targetTimeline) {