echogarden 3.0.5 → 3.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/lexicons/heteronyms.en.json +2 -2
- package/data/lexicons/words.en.json +120 -0
- package/dist/alignment/SemanticTextAlignment.js +2 -2
- package/dist/alignment/SemanticTextAlignment.js.map +1 -1
- package/dist/api/Alignment.js +2 -2
- package/dist/api/Alignment.js.map +1 -1
- package/dist/api/Synthesis.js +2 -2
- package/dist/api/Synthesis.js.map +1 -1
- package/dist/encodings/Ascii.d.ts +1 -3
- package/dist/encodings/Ascii.d.ts.map +1 -1
- package/dist/encodings/Ascii.js +13 -9
- package/dist/encodings/Ascii.js.map +1 -1
- package/dist/encodings/Base64.d.ts.map +1 -1
- package/dist/encodings/Base64.js +9 -0
- package/dist/encodings/Base64.js.map +1 -1
- package/dist/encodings/Hex.d.ts.map +1 -1
- package/dist/encodings/Hex.js +11 -2
- package/dist/encodings/Hex.js.map +1 -1
- package/dist/encodings/LEB128.d.ts +1 -15
- package/dist/encodings/LEB128.d.ts.map +1 -1
- package/dist/encodings/LEB128.js +1 -247
- package/dist/encodings/LEB128.js.map +1 -1
- package/dist/encodings/Utf16.d.ts +1 -3
- package/dist/encodings/Utf16.d.ts.map +1 -1
- package/dist/encodings/Utf16.js +13 -9
- package/dist/encodings/Utf16.js.map +1 -1
- package/dist/encodings/Utf32.d.ts.map +1 -1
- package/dist/encodings/Utf32.js +6 -0
- package/dist/encodings/Utf32.js.map +1 -1
- package/dist/encodings/Utf8.d.ts +9 -5
- package/dist/encodings/Utf8.d.ts.map +1 -1
- package/dist/encodings/Utf8.js +36 -78
- package/dist/encodings/Utf8.js.map +1 -1
- package/dist/nlp/Segmentation.d.ts +2 -8
- package/dist/nlp/Segmentation.d.ts.map +1 -1
- package/dist/nlp/Segmentation.js +42 -19
- package/dist/nlp/Segmentation.js.map +1 -1
- package/dist/nlp/TextNormalizer.d.ts.map +1 -1
- package/dist/nlp/TextNormalizer.js +29 -14
- package/dist/nlp/TextNormalizer.js.map +1 -1
- package/dist/recognition/AmazonTranscribeSTT.js +3 -3
- package/dist/recognition/AmazonTranscribeSTT.js.map +1 -1
- package/dist/synthesis/AzureCognitiveServicesTTS.js +1 -1
- package/dist/synthesis/AzureCognitiveServicesTTS.js.map +1 -1
- package/dist/synthesis/EspeakTTS.d.ts.map +1 -1
- package/dist/synthesis/EspeakTTS.js +15 -8
- package/dist/synthesis/EspeakTTS.js.map +1 -1
- package/dist/synthesis/KokoroTTS.d.ts.map +1 -1
- package/dist/synthesis/KokoroTTS.js +0 -5
- package/dist/synthesis/KokoroTTS.js.map +1 -1
- package/dist/synthesis/MicrosoftEdgeTTS.js +1 -1
- package/dist/synthesis/MicrosoftEdgeTTS.js.map +1 -1
- package/dist/synthesis/VitsTTS.d.ts.map +1 -1
- package/dist/synthesis/VitsTTS.js +0 -5
- package/dist/synthesis/VitsTTS.js.map +1 -1
- package/dist/utilities/FileSystem.d.ts +12 -7
- package/dist/utilities/FileSystem.d.ts.map +1 -1
- package/dist/utilities/FileSystem.js +196 -79
- package/dist/utilities/FileSystem.js.map +1 -1
- package/dist/utilities/FileWriter.d.ts +1 -0
- package/dist/utilities/FileWriter.d.ts.map +1 -1
- package/dist/utilities/FileWriter.js +8 -1
- package/dist/utilities/FileWriter.js.map +1 -1
- package/dist/utilities/ObjectUtilities.d.ts.map +1 -1
- package/dist/utilities/ObjectUtilities.js +15 -13
- package/dist/utilities/ObjectUtilities.js.map +1 -1
- package/dist/utilities/PackageManager.js +2 -2
- package/dist/utilities/PackageManager.js.map +1 -1
- package/dist/utilities/PathUtilities.d.ts +1 -0
- package/dist/utilities/PathUtilities.d.ts.map +1 -1
- package/dist/utilities/PathUtilities.js +18 -0
- package/dist/utilities/PathUtilities.js.map +1 -1
- package/dist/utilities/StringUtilities.d.ts.map +1 -1
- package/dist/utilities/StringUtilities.js.map +1 -1
- package/dist/utilities/Timeline.js +2 -2
- package/dist/utilities/Timeline.js.map +1 -1
- package/dist/utilities/WikipediaReader.js +5 -5
- package/dist/utilities/WikipediaReader.js.map +1 -1
- package/package.json +13 -12
- package/src/alignment/SemanticTextAlignment.ts +2 -2
- package/src/api/Alignment.ts +2 -2
- package/src/api/Synthesis.ts +2 -2
- package/src/encodings/Ascii.ts +14 -10
- package/src/encodings/Base64.ts +9 -0
- package/src/encodings/Hex.ts +11 -2
- package/src/encodings/Utf16.ts +14 -10
- package/src/encodings/Utf32.ts +6 -0
- package/src/encodings/Utf8.ts +45 -82
- package/src/nlp/Segmentation.ts +53 -19
- package/src/nlp/TextNormalizer.ts +40 -16
- package/src/recognition/AmazonTranscribeSTT.ts +3 -3
- package/src/synthesis/AzureCognitiveServicesTTS.ts +1 -1
- package/src/synthesis/EspeakTTS.ts +17 -9
- package/src/synthesis/KokoroTTS.ts +0 -6
- package/src/synthesis/MicrosoftEdgeTTS.ts +1 -1
- package/src/synthesis/VitsTTS.ts +0 -6
- package/src/utilities/FileSystem.ts +224 -85
- package/src/utilities/FileWriter.ts +11 -1
- package/src/utilities/ObjectUtilities.ts +19 -15
- package/src/utilities/PackageManager.ts +2 -2
- package/src/utilities/PathUtilities.ts +19 -0
- package/src/utilities/StringUtilities.ts +0 -2
- package/src/utilities/Timeline.ts +2 -2
- package/src/utilities/WikipediaReader.ts +5 -5
- package/src/encodings/HtmlEscape.ts +0 -39
- package/src/encodings/LEB128.ts +0 -362
- package/src/utilities/StringBuilder.ts +0 -45
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as fsExtra from 'fs-extra/esm'
|
|
2
2
|
import gracefulFS from 'graceful-fs'
|
|
3
|
-
import * as os from 'node:os'
|
|
4
3
|
|
|
5
4
|
import { promisify } from 'node:util'
|
|
6
5
|
import { getRandomHexString, parseJson, stringifyAndFormatJson } from './Utilities.js'
|
|
@@ -8,7 +7,7 @@ import { appName } from '../api/Common.js'
|
|
|
8
7
|
import { getAppTempDir, getDirName, joinPath, normalizePath, parsePath } from './PathUtilities.js'
|
|
9
8
|
|
|
10
9
|
import { createDynamicUint8Array } from '../data-structures/DynamicTypedArray.js'
|
|
11
|
-
import { ChunkedUtf8Decoder } from '../encodings/Utf8.js'
|
|
10
|
+
import { ChunkedUtf8Decoder, ChunkedUtf8Encoder } from '../encodings/Utf8.js'
|
|
12
11
|
import { FileWriter } from './FileWriter.js'
|
|
13
12
|
import { FileReader } from './FileReader.js'
|
|
14
13
|
|
|
@@ -26,9 +25,10 @@ export const access = promisify(gracefulFS.access)
|
|
|
26
25
|
|
|
27
26
|
export const readdir = promisify(gracefulFS.readdir)
|
|
28
27
|
export const copyFile = promisify(gracefulFS.copyFile)
|
|
28
|
+
export const rename = promisify(gracefulFS.rename)
|
|
29
|
+
export const fsync = promisify(gracefulFS.fsync)
|
|
29
30
|
|
|
30
31
|
export const remove = fsExtra.remove
|
|
31
|
-
export const copy = fsExtra.copy
|
|
32
32
|
|
|
33
33
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
34
34
|
// File read operations
|
|
@@ -41,32 +41,35 @@ export async function readFileAsBinary(filePath: string) {
|
|
|
41
41
|
|
|
42
42
|
const fileReader = new FileReader(filePath)
|
|
43
43
|
const buffer = new Uint8Array(chunkSize)
|
|
44
|
-
const
|
|
44
|
+
const fileContent = createDynamicUint8Array(fileSize)
|
|
45
45
|
|
|
46
46
|
while (!fileReader.isFinished) {
|
|
47
47
|
const chunk = await fileReader.readChunk(buffer)
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
fileContent.addArray(chunk)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
return
|
|
52
|
+
return fileContent.toTypedArray()
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export async function readFileAsUtf8(filePath: string) {
|
|
56
|
-
const
|
|
56
|
+
const maxChunkSize = 2 ** 20
|
|
57
57
|
|
|
58
58
|
const fileReader = new FileReader(filePath)
|
|
59
|
-
const buffer = new Uint8Array(
|
|
59
|
+
const buffer = new Uint8Array(maxChunkSize)
|
|
60
|
+
|
|
61
|
+
const chunkedUtf8Decoder = new ChunkedUtf8Decoder()
|
|
60
62
|
|
|
61
|
-
|
|
63
|
+
let fileContent = ''
|
|
62
64
|
|
|
63
65
|
while (!fileReader.isFinished) {
|
|
64
|
-
const
|
|
66
|
+
const utf8Chunk = await fileReader.readChunk(buffer)
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
const stringChunk = chunkedUtf8Decoder.writeChunk(utf8Chunk)
|
|
69
|
+
fileContent += stringChunk
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
return
|
|
72
|
+
return fileContent
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
export async function readAndParseJsonFile(jsonFilePath: string, useJson5 = false) {
|
|
@@ -78,17 +81,17 @@ export async function readAndParseJsonFile(jsonFilePath: string, useJson5 = fals
|
|
|
78
81
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
79
82
|
// File write operations
|
|
80
83
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
81
|
-
export async function writeFile(filePath: string, content: Uint8Array | string) {
|
|
84
|
+
export async function writeFile(filePath: string, content: Uint8Array | string, options?: WriteFileOptions) {
|
|
82
85
|
if (content instanceof Uint8Array) {
|
|
83
|
-
return writeBinaryFile(filePath, content)
|
|
86
|
+
return writeBinaryFile(filePath, content, options)
|
|
84
87
|
} else if (typeof content === 'string') {
|
|
85
|
-
return writeUtf8File(filePath, content)
|
|
88
|
+
return writeUtf8File(filePath, content, options)
|
|
86
89
|
} else {
|
|
87
|
-
throw new Error(`Content can only be a Uint8Array or string
|
|
90
|
+
throw new Error(`Content can only be a Uint8Array or string.`)
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
export async function writeBinaryFile(filePath: string, content: Uint8Array) {
|
|
94
|
+
export async function writeBinaryFile(filePath: string, content: Uint8Array, options?: WriteFileOptions) {
|
|
92
95
|
const maxChunkSize = 2 ** 20
|
|
93
96
|
|
|
94
97
|
const fileDir = getDirName(filePath)
|
|
@@ -97,24 +100,28 @@ export async function writeBinaryFile(filePath: string, content: Uint8Array) {
|
|
|
97
100
|
|
|
98
101
|
const fileWriter = new FileWriter(filePath)
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
try {
|
|
104
|
+
// The do-while body runs at least once, even for empty content, so the
|
|
105
|
+
// file is always created (or truncated) via FileWriter's first write.
|
|
106
|
+
let readOffset = 0
|
|
101
107
|
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
do {
|
|
109
|
+
const chunk = content.subarray(readOffset, readOffset + maxChunkSize)
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
break
|
|
107
|
-
}
|
|
111
|
+
readOffset += chunk.length
|
|
108
112
|
|
|
109
|
-
|
|
113
|
+
await fileWriter.write(chunk)
|
|
114
|
+
} while (readOffset < content.length)
|
|
110
115
|
|
|
111
|
-
|
|
116
|
+
if (options?.fsync === true) {
|
|
117
|
+
await fileWriter.fsync()
|
|
118
|
+
}
|
|
119
|
+
} finally {
|
|
120
|
+
await fileWriter.dispose()
|
|
112
121
|
}
|
|
113
|
-
|
|
114
|
-
await fileWriter.dispose()
|
|
115
122
|
}
|
|
116
123
|
|
|
117
|
-
export async function writeUtf8File(filePath: string, content: string) {
|
|
124
|
+
export async function writeUtf8File(filePath: string, content: string, options?: WriteFileOptions) {
|
|
118
125
|
const maxChunkSize = 2 ** 20
|
|
119
126
|
|
|
120
127
|
const fileDir = getDirName(filePath)
|
|
@@ -123,25 +130,36 @@ export async function writeUtf8File(filePath: string, content: string) {
|
|
|
123
130
|
|
|
124
131
|
const fileWriter = new FileWriter(filePath)
|
|
125
132
|
|
|
126
|
-
const
|
|
133
|
+
const chunkedUtf8Encoder = new ChunkedUtf8Encoder()
|
|
127
134
|
|
|
128
|
-
|
|
135
|
+
try {
|
|
136
|
+
// The do-while body runs at least once, even for empty content, so the
|
|
137
|
+
// file is always created (or truncated) via FileWriter's first write.
|
|
138
|
+
let readOffset = 0
|
|
129
139
|
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
do {
|
|
141
|
+
const stringChunk = content.substring(readOffset, readOffset + maxChunkSize)
|
|
142
|
+
|
|
143
|
+
readOffset += stringChunk.length
|
|
132
144
|
|
|
133
|
-
|
|
145
|
+
const utf8Chunk = chunkedUtf8Encoder.writeChunk(stringChunk)
|
|
134
146
|
|
|
135
|
-
|
|
147
|
+
await fileWriter.write(utf8Chunk)
|
|
148
|
+
} while (readOffset < content.length)
|
|
136
149
|
|
|
137
|
-
|
|
150
|
+
const finalChunk = chunkedUtf8Encoder.finalize()
|
|
138
151
|
|
|
139
|
-
if (
|
|
140
|
-
|
|
152
|
+
if (finalChunk.length > 0) {
|
|
153
|
+
await fileWriter.write(finalChunk)
|
|
141
154
|
}
|
|
142
|
-
}
|
|
143
155
|
|
|
144
|
-
|
|
156
|
+
if (options?.fsync === true) {
|
|
157
|
+
await fileWriter.fsync()
|
|
158
|
+
}
|
|
159
|
+
} finally {
|
|
160
|
+
// Always release the file handle, even if an error interrupted the write.
|
|
161
|
+
await fileWriter.dispose()
|
|
162
|
+
}
|
|
145
163
|
}
|
|
146
164
|
|
|
147
165
|
export async function writeJsonFile(filePath: string, content: any, useJson5 = false) {
|
|
@@ -151,12 +169,25 @@ export async function writeJsonFile(filePath: string, content: any, useJson5 = f
|
|
|
151
169
|
}
|
|
152
170
|
|
|
153
171
|
export async function writeFileSafe(filePath: string, content: Uint8Array | string) {
|
|
154
|
-
const
|
|
172
|
+
const destDir = getDirName(filePath)
|
|
173
|
+
|
|
174
|
+
const tempDir = await getTemporaryDirectory(destDir)
|
|
155
175
|
const tempFilePath = joinPath(tempDir, `${getRandomHexString(16)}.partial`)
|
|
156
176
|
|
|
157
|
-
|
|
177
|
+
try {
|
|
178
|
+
await writeFile(tempFilePath, content, { fsync: true })
|
|
179
|
+
|
|
180
|
+
await move(tempFilePath, filePath)
|
|
181
|
+
} catch (e) {
|
|
182
|
+
// Best-effort cleanup so failed writes don't leave orphaned .partial files behind.
|
|
183
|
+
try {
|
|
184
|
+
await remove(tempFilePath)
|
|
185
|
+
} catch {
|
|
186
|
+
// Ignore cleanup errors so the original error is not masked.
|
|
187
|
+
}
|
|
158
188
|
|
|
159
|
-
|
|
189
|
+
throw e
|
|
190
|
+
}
|
|
160
191
|
}
|
|
161
192
|
|
|
162
193
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -176,19 +207,6 @@ export async function ensureDir(dirPath: string) {
|
|
|
176
207
|
}
|
|
177
208
|
}
|
|
178
209
|
|
|
179
|
-
export async function testDirectoryIsWritable(dir: string) {
|
|
180
|
-
const testFileName = joinPath(dir, getRandomHexString(16))
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
await fsExtra.createFile(testFileName)
|
|
184
|
-
await remove(testFileName)
|
|
185
|
-
} catch (e) {
|
|
186
|
-
return false
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return true
|
|
190
|
-
}
|
|
191
|
-
|
|
192
210
|
export async function readDirRecursive(dir: string, pathFilter?: (filePath: string) => boolean) {
|
|
193
211
|
if (!(await stat(dir)).isDirectory()) {
|
|
194
212
|
throw new Error(`'${dir}' is not a directory`)
|
|
@@ -220,33 +238,20 @@ export async function readDirRecursive(dir: string, pathFilter?: (filePath: stri
|
|
|
220
238
|
return result
|
|
221
239
|
}
|
|
222
240
|
|
|
223
|
-
export function getAppDataDir(appName: string) {
|
|
224
|
-
let dataDir: string
|
|
225
|
-
|
|
226
|
-
const platform = process.platform
|
|
227
|
-
const homeDir = os.homedir()
|
|
228
|
-
|
|
229
|
-
if (platform == 'win32') {
|
|
230
|
-
dataDir = joinPath(homeDir, 'AppData', 'Local', appName)
|
|
231
|
-
} else if (platform == 'darwin') {
|
|
232
|
-
dataDir = joinPath(homeDir, 'Library', 'Application Support', appName)
|
|
233
|
-
} else if (platform == 'linux') {
|
|
234
|
-
dataDir = joinPath(homeDir, '.local', 'share', appName)
|
|
235
|
-
} else {
|
|
236
|
-
throw new Error(`Unsupport platform ${platform}`)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return dataDir
|
|
240
|
-
}
|
|
241
|
-
|
|
242
241
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
243
|
-
// Copy operations
|
|
242
|
+
// Copy and move operations
|
|
244
243
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
245
244
|
export async function move(source: string, dest: string) {
|
|
246
245
|
source = normalizePath(source)
|
|
247
246
|
dest = normalizePath(dest)
|
|
248
247
|
|
|
249
|
-
|
|
248
|
+
const destStats = existsSync(dest) ? await stat(dest) : undefined
|
|
249
|
+
|
|
250
|
+
if (destStats?.isDirectory()) {
|
|
251
|
+
throw new Error(`The destination path '${dest}' exists and is a directory. A file cannot be moved over a directory.`)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (destStats) {
|
|
250
255
|
const destPathExistsAndIsWritable = await existsAndIsWritable(dest)
|
|
251
256
|
|
|
252
257
|
if (!destPathExistsAndIsWritable) {
|
|
@@ -254,29 +259,88 @@ export async function move(source: string, dest: string) {
|
|
|
254
259
|
}
|
|
255
260
|
} else {
|
|
256
261
|
const destDir = parsePath(dest).dir
|
|
257
|
-
const destDirIsWritable = await testDirectoryIsWritable(destDir)
|
|
258
262
|
|
|
259
|
-
|
|
260
|
-
|
|
263
|
+
await ensureDir(destDir)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
// fs.rename() replaces an existing destination file in a single step on
|
|
268
|
+
// POSIX and on Windows (where libuv uses MoveFileExW with
|
|
269
|
+
// MOVEFILE_REPLACE_EXISTING, the atomic replace flag available to Node).
|
|
270
|
+
await rename(source, dest)
|
|
271
|
+
} catch (e) {
|
|
272
|
+
const error = e as NodeJS.ErrnoException
|
|
273
|
+
|
|
274
|
+
if (error.code === 'EXDEV') {
|
|
275
|
+
// The source and destination are on different volumes, where an
|
|
276
|
+
// atomic rename is impossible; fall back to a copy and delete.
|
|
277
|
+
await copyFile(source, dest)
|
|
278
|
+
await tryFsyncFile(dest)
|
|
279
|
+
await remove(source)
|
|
280
|
+
} else {
|
|
281
|
+
throw error
|
|
261
282
|
}
|
|
262
283
|
}
|
|
263
284
|
|
|
264
|
-
|
|
285
|
+
// The rename is now durable: flush the destination directory so the name
|
|
286
|
+
// change survives a crash (best effort, see the helper).
|
|
287
|
+
await tryFsyncDirectory(parsePath(dest).dir)
|
|
265
288
|
}
|
|
266
289
|
|
|
267
290
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
268
|
-
//
|
|
291
|
+
// Synchronization operations
|
|
269
292
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
270
|
-
export async function
|
|
293
|
+
export async function tryFsyncFile(filePath: string) {
|
|
294
|
+
let fileHandle: number | undefined
|
|
295
|
+
|
|
296
|
+
let succeeded = false
|
|
297
|
+
|
|
271
298
|
try {
|
|
272
|
-
await
|
|
299
|
+
fileHandle = await open(filePath, 'r+')
|
|
300
|
+
|
|
301
|
+
await fsync(fileHandle)
|
|
302
|
+
|
|
303
|
+
succeeded = true
|
|
273
304
|
} catch {
|
|
274
|
-
|
|
305
|
+
} finally {
|
|
306
|
+
if (fileHandle !== undefined) {
|
|
307
|
+
try {
|
|
308
|
+
await close(fileHandle)
|
|
309
|
+
} catch {
|
|
310
|
+
}
|
|
311
|
+
}
|
|
275
312
|
}
|
|
276
313
|
|
|
277
|
-
return
|
|
314
|
+
return succeeded
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export async function tryFsyncDirectory(dirPath: string) {
|
|
318
|
+
let dirHandle: number | undefined
|
|
319
|
+
|
|
320
|
+
let succeeded = false
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
dirHandle = await open(dirPath, 'r')
|
|
324
|
+
|
|
325
|
+
await fsync(dirHandle)
|
|
326
|
+
|
|
327
|
+
succeeded = true
|
|
328
|
+
} catch {
|
|
329
|
+
} finally {
|
|
330
|
+
if (dirHandle !== undefined) {
|
|
331
|
+
try {
|
|
332
|
+
await close(dirHandle)
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return succeeded
|
|
278
339
|
}
|
|
279
340
|
|
|
341
|
+
///////////////////////////////////////////////////////////////////////////////////////////
|
|
342
|
+
// Misc operations
|
|
343
|
+
///////////////////////////////////////////////////////////////////////////////////////////
|
|
280
344
|
export async function chmodRecursive(rootPath: string, newMode: number) {
|
|
281
345
|
const rootPathStat = await stat(rootPath)
|
|
282
346
|
|
|
@@ -323,3 +387,78 @@ export async function computeFileSha256Hex(filePath: string) {
|
|
|
323
387
|
|
|
324
388
|
return result
|
|
325
389
|
}
|
|
390
|
+
|
|
391
|
+
export async function existsAndIsWritable(targetPath: string) {
|
|
392
|
+
try {
|
|
393
|
+
await access(targetPath, gracefulFS.constants.W_OK);
|
|
394
|
+
} catch {
|
|
395
|
+
return false
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return true
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// The OS temporary directory keeps temporary files away from the caller's
|
|
402
|
+
// directories, and the OS itself eventually cleans it up. It is used when it is
|
|
403
|
+
// on the same volume as the destination, since then the final rename is atomic.
|
|
404
|
+
//
|
|
405
|
+
// When it is on a different volume, the temporary file is written next to the
|
|
406
|
+
// destination instead: an atomic rename across volumes is impossible and would
|
|
407
|
+
// have to fall back to a copy and delete.
|
|
408
|
+
async function getTemporaryDirectory(destDir: string) {
|
|
409
|
+
const osTempDir = getAppTempDir(appName)
|
|
410
|
+
|
|
411
|
+
await ensureDir(osTempDir)
|
|
412
|
+
|
|
413
|
+
if (await areOnSameDevice(osTempDir, destDir)) {
|
|
414
|
+
return osTempDir
|
|
415
|
+
} else {
|
|
416
|
+
return destDir
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function areOnSameDevice(firstPath: string, secondPath: string) {
|
|
421
|
+
const firstDevice = await getDeviceOfExistingPath(firstPath)
|
|
422
|
+
const secondDevice = await getDeviceOfExistingPath(secondPath)
|
|
423
|
+
|
|
424
|
+
return firstDevice !== undefined && firstDevice === secondDevice
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Resolves the device of the closest existing ancestor directory of a target
|
|
428
|
+
// path, since the path itself may not exist yet. The device identifies the
|
|
429
|
+
// volume a path lives on (on Windows it is the drive number).
|
|
430
|
+
async function getDeviceOfExistingPath(targetPath: string) {
|
|
431
|
+
let currentPath = normalizePath(targetPath)
|
|
432
|
+
|
|
433
|
+
while (true) {
|
|
434
|
+
try {
|
|
435
|
+
const targetStats = await stat(currentPath)
|
|
436
|
+
|
|
437
|
+
return targetStats.dev
|
|
438
|
+
} catch (e) {
|
|
439
|
+
const error = e as NodeJS.ErrnoException
|
|
440
|
+
|
|
441
|
+
if (error.code !== 'ENOENT') {
|
|
442
|
+
// If the path cannot be inspected, treat the volumes as different;
|
|
443
|
+
// falling back to the destination directory always remains safe.
|
|
444
|
+
return undefined
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const parentPath = getDirName(currentPath)
|
|
448
|
+
|
|
449
|
+
if (parentPath === currentPath) {
|
|
450
|
+
return undefined
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
currentPath = parentPath
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
///////////////////////////////////////////////////////////////////////////////////////////
|
|
459
|
+
// Types
|
|
460
|
+
///////////////////////////////////////////////////////////////////////////////////////////
|
|
461
|
+
|
|
462
|
+
interface WriteFileOptions {
|
|
463
|
+
fsync?: boolean
|
|
464
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { write, open, close } from
|
|
1
|
+
import { write, open, close, fsync } from './FileSystem.js'
|
|
2
2
|
|
|
3
3
|
export class FileWriter {
|
|
4
4
|
private fileHandle?: number
|
|
@@ -45,6 +45,16 @@ export class FileWriter {
|
|
|
45
45
|
this.fileHandle = await open(this.filePath, 'w')
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
async fsync() {
|
|
49
|
+
if (this.isDisposed) {
|
|
50
|
+
throw new Error(`FileWriter has been disposed`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await this.openIfNeeded()
|
|
54
|
+
|
|
55
|
+
await fsync(this.fileHandle!)
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
async dispose() {
|
|
49
59
|
if (this.isDisposed) {
|
|
50
60
|
return
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export function extendDeep(base: any, extension: any): any {
|
|
2
2
|
const baseClone = deepClone(base)
|
|
3
3
|
|
|
4
|
-
if (
|
|
4
|
+
if (extension === undefined) {
|
|
5
5
|
return baseClone
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
const extensionClone = deepClone(extension)
|
|
9
|
+
|
|
9
10
|
if (!isPlainObject(base) || !isPlainObject(extension)) {
|
|
10
11
|
return extensionClone
|
|
11
12
|
}
|
|
@@ -21,15 +22,15 @@ export function extendDeep(base: any, extension: any): any {
|
|
|
21
22
|
return baseClone
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export function shallowClone<T>(val: T) {
|
|
25
|
-
return clone(val, false)
|
|
25
|
+
export function shallowClone<T>(val: T): T {
|
|
26
|
+
return clone(val, false, new Set())
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export function deepClone<T>(val: T) {
|
|
29
|
-
return clone(val, true)
|
|
29
|
+
export function deepClone<T>(val: T): T {
|
|
30
|
+
return clone(val, true, new Set())
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function clone<T>(val: T, deep = true, seenObjects: any
|
|
33
|
+
function clone<T>(val: T, deep = true, seenObjects: Set<any>): T {
|
|
33
34
|
if (val === undefined || val === null || typeof val !== 'object') {
|
|
34
35
|
return val
|
|
35
36
|
}
|
|
@@ -39,11 +40,11 @@ function clone<T>(val: T, deep = true, seenObjects: any[] = []): T {
|
|
|
39
40
|
|
|
40
41
|
switch (prototypeIdentifier) {
|
|
41
42
|
case '[object Array]': {
|
|
42
|
-
if (seenObjects.
|
|
43
|
+
if (seenObjects.has(obj)) {
|
|
43
44
|
throw new Error('deepClone: encountered a cyclic object')
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
seenObjects.
|
|
47
|
+
seenObjects.add(obj)
|
|
47
48
|
|
|
48
49
|
const clonedArray = new Array(obj.length)
|
|
49
50
|
|
|
@@ -55,7 +56,7 @@ function clone<T>(val: T, deep = true, seenObjects: any[] = []): T {
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
seenObjects.
|
|
59
|
+
seenObjects.delete(obj)
|
|
59
60
|
|
|
60
61
|
return clonedArray as any
|
|
61
62
|
}
|
|
@@ -149,7 +150,10 @@ function clone<T>(val: T, deep = true, seenObjects: any[] = []): T {
|
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
case '[object RegExp]': {
|
|
152
|
-
|
|
153
|
+
const clonedRegExp = new RegExp(obj.source, obj.flags)
|
|
154
|
+
clonedRegExp.lastIndex = obj.lastIndex
|
|
155
|
+
|
|
156
|
+
return clonedRegExp as any
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
case '[object Function]': {
|
|
@@ -157,11 +161,11 @@ function clone<T>(val: T, deep = true, seenObjects: any[] = []): T {
|
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
case '[object Object]': {
|
|
160
|
-
if (seenObjects.
|
|
161
|
-
throw new Error('deepClone: encountered a cyclic object')
|
|
164
|
+
if (seenObjects.has(obj)) {
|
|
165
|
+
throw new Error('deepClone: encountered a cyclic object.')
|
|
162
166
|
}
|
|
163
167
|
|
|
164
|
-
seenObjects.
|
|
168
|
+
seenObjects.add(obj)
|
|
165
169
|
|
|
166
170
|
const clonedObj: any = {}
|
|
167
171
|
|
|
@@ -177,13 +181,13 @@ function clone<T>(val: T, deep = true, seenObjects: any[] = []): T {
|
|
|
177
181
|
}
|
|
178
182
|
}
|
|
179
183
|
|
|
180
|
-
seenObjects.
|
|
184
|
+
seenObjects.delete(obj)
|
|
181
185
|
|
|
182
186
|
return clonedObj
|
|
183
187
|
}
|
|
184
188
|
|
|
185
189
|
default: {
|
|
186
|
-
throw new Error(`Cloning of type ${prototypeIdentifier} is not supported
|
|
190
|
+
throw new Error(`Cloning of type ${prototypeIdentifier} is not supported.`)
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
193
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { downloadAndExtractTarball, FileDownloaderCallbacks } from './FileDownloader.js'
|
|
2
|
-
import {
|
|
2
|
+
import { ensureDir, existsSync, remove } from './FileSystem.js'
|
|
3
3
|
import { appName } from '../api/Common.js'
|
|
4
|
-
import { getAppTempDir, joinPath } from './PathUtilities.js'
|
|
4
|
+
import { getAppDataDir, getAppTempDir, joinPath } from './PathUtilities.js'
|
|
5
5
|
import { getGlobalOption } from '../api/GlobalOptions.js'
|
|
6
6
|
import { EasierHttpRequestConfig } from 'easier-http-request'
|
|
7
7
|
|
|
@@ -41,6 +41,25 @@ export function getAppTempDir(appName: string) {
|
|
|
41
41
|
return tempDir
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
export function getAppDataDir(appName: string) {
|
|
45
|
+
let dataDir: string
|
|
46
|
+
|
|
47
|
+
const platform = process.platform
|
|
48
|
+
const homeDir = os.homedir()
|
|
49
|
+
|
|
50
|
+
if (platform == 'win32') {
|
|
51
|
+
dataDir = joinPath(homeDir, 'AppData', 'Local', appName)
|
|
52
|
+
} else if (platform == 'darwin') {
|
|
53
|
+
dataDir = joinPath(homeDir, 'Library', 'Application Support', appName)
|
|
54
|
+
} else if (platform == 'linux') {
|
|
55
|
+
dataDir = joinPath(homeDir, '.local', 'share', appName)
|
|
56
|
+
} else {
|
|
57
|
+
throw new Error(`Unsupport platform ${platform}`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return dataDir
|
|
61
|
+
}
|
|
62
|
+
|
|
44
63
|
export function joinPath(...paths: string[]) {
|
|
45
64
|
return NodePath.join(...paths)
|
|
46
65
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { addMissingPunctuationWordsToWordSequence, segmentWordSequence, WordSequence } from '@echogarden/text-segmentation'
|
|
2
2
|
import { ParagraphBreakType, WhitespaceProcessing } from '../api/Common.js'
|
|
3
|
-
import { applyWhitespaceProcessing,
|
|
3
|
+
import { applyWhitespaceProcessing, includesWordCharacter, isWordOrEmojiOrSymbolWord, splitToParagraphs } from '../nlp/Segmentation.js'
|
|
4
4
|
import { deepClone } from './ObjectUtilities.js'
|
|
5
5
|
import { getUTF32Chars } from './StringUtilities.js'
|
|
6
6
|
import { roundToDigits } from './Utilities.js'
|
|
@@ -213,7 +213,7 @@ function replaceSentenceEndersWithinWordsWithMaskingCharacter(transcript: string
|
|
|
213
213
|
for (const wordEntry of wordTimeline) {
|
|
214
214
|
const wordText = wordEntry.text
|
|
215
215
|
|
|
216
|
-
if (!
|
|
216
|
+
if (!includesWordCharacter(wordText)) {
|
|
217
217
|
continue
|
|
218
218
|
}
|
|
219
219
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { OperationCallbacks } from '../api/Common.js'
|
|
2
|
-
import {
|
|
2
|
+
import { isWordOrEmojiOrSymbolWord, splitToParagraphs } from '../nlp/Segmentation.js'
|
|
3
3
|
|
|
4
4
|
export async function fetchAndParseWikipediaArticle(articleName: string, language: string, callbacks: OperationCallbacks) {
|
|
5
|
-
const { default:
|
|
5
|
+
const { default: wtfWikipedia } = await import('wtf_wikipedia')
|
|
6
6
|
|
|
7
|
-
const document = await
|
|
7
|
+
const document = await wtfWikipedia.fetch(articleName, language)
|
|
8
8
|
|
|
9
9
|
if (!document) {
|
|
10
10
|
throw new Error('Error fetching Wikipedia article')
|
|
@@ -16,7 +16,7 @@ export async function fetchAndParseWikipediaArticle(articleName: string, languag
|
|
|
16
16
|
for (const section of sections) {
|
|
17
17
|
const sectionTitle = section.title()
|
|
18
18
|
|
|
19
|
-
if (
|
|
19
|
+
if (isWordOrEmojiOrSymbolWord(sectionTitle)) {
|
|
20
20
|
sectionsText.push(sectionTitle)
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -25,7 +25,7 @@ export async function fetchAndParseWikipediaArticle(articleName: string, languag
|
|
|
25
25
|
for (const paragraph of sectionParagraphs) {
|
|
26
26
|
const paragraphText = paragraph
|
|
27
27
|
|
|
28
|
-
if (
|
|
28
|
+
if (isWordOrEmojiOrSymbolWord(paragraphText)) {
|
|
29
29
|
sectionsText.push(paragraphText)
|
|
30
30
|
}
|
|
31
31
|
}
|