echogarden 2.0.7 → 2.0.9

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 (48) hide show
  1. package/dist/audio/AudioBufferConversion.d.ts +11 -11
  2. package/dist/audio/AudioBufferConversion.js +1 -1
  3. package/dist/audio/AudioBufferConversion.js.map +1 -1
  4. package/dist/audio/AudioUtilities.d.ts +7 -7
  5. package/dist/cli/CLI.js +19 -28
  6. package/dist/cli/CLI.js.map +1 -1
  7. package/dist/codecs/FFMpegTranscoder.d.ts +3 -3
  8. package/dist/codecs/TIMITCodec.d.ts +2 -2
  9. package/dist/codecs/WaveCodec.d.ts +3 -3
  10. package/dist/dsp/FFT.d.ts +8 -8
  11. package/dist/dsp/MFCC.d.ts +6 -6
  12. package/dist/dsp/MelSpectogram.d.ts +7 -7
  13. package/dist/encodings/Ascii.d.ts +1 -1
  14. package/dist/encodings/Hex.d.ts +2 -2
  15. package/dist/encodings/Utf16.d.ts +1 -1
  16. package/dist/encodings/Utf32.d.ts +1 -1
  17. package/dist/encodings/Utf8.d.ts +1 -1
  18. package/dist/math/VectorMath.d.ts +24 -24
  19. package/dist/nlp/ChineseSegmentation.js +1 -1
  20. package/dist/nlp/ChineseSegmentation.js.map +1 -1
  21. package/dist/speech-embeddings/WavToVec2BertFeatureEmbeddings.d.ts +2 -2
  22. package/dist/synthesis/AwsPollyTTS.d.ts +1 -1
  23. package/dist/synthesis/CoquiServerTTS.d.ts +1 -1
  24. package/dist/synthesis/ElevenlabsTTS.d.ts +1 -1
  25. package/dist/synthesis/FliteTTS.d.ts +1 -1
  26. package/dist/synthesis/GoogleCloudTTS.d.ts +1 -1
  27. package/dist/synthesis/GoogleTranslateTTS.d.ts +2 -2
  28. package/dist/synthesis/StreamlabsPollyTTS.d.ts +2 -2
  29. package/dist/synthesis/VitsTTS.js +2 -2
  30. package/dist/synthesis/VitsTTS.js.map +1 -1
  31. package/dist/text-translation/GoogleTranslateTextTranslation.d.ts +0 -2
  32. package/dist/text-translation/GoogleTranslateTextTranslation.js +6 -181
  33. package/dist/text-translation/GoogleTranslateTextTranslation.js.map +1 -1
  34. package/dist/utilities/BinaryArrayConversion.d.ts +14 -14
  35. package/dist/utilities/Compression.d.ts +1 -1
  36. package/dist/utilities/FileSystem.d.ts +1 -1
  37. package/dist/utilities/Utilities.d.ts +3 -3
  38. package/dist/utilities/Utilities.js +2 -2
  39. package/dist/utilities/Utilities.js.map +1 -1
  40. package/dist/utilities/WasmMemoryManager.d.ts +1 -1
  41. package/package.json +8 -8
  42. package/src/audio/AudioBufferConversion.ts +1 -1
  43. package/src/cli/CLI.ts +21 -41
  44. package/src/nlp/ChineseSegmentation.ts +1 -1
  45. package/src/synthesis/VitsTTS.ts +2 -2
  46. package/src/text-translation/GoogleTranslateTextTranslation.ts +7 -240
  47. package/src/typings/Fillers.d.ts +0 -1
  48. package/src/utilities/Utilities.ts +2 -2
package/src/cli/CLI.ts CHANGED
@@ -22,7 +22,7 @@ import { removePackage } from '../utilities/PackageManager.js'
22
22
  import { appName } from '../api/Common.js'
23
23
  import { ServerOptions, startServer } from '../server/Server.js'
24
24
  import { OpenPromise } from '../utilities/OpenPromise.js'
25
- import { getLowercaseFileExtension, joinPath, parsePath, resolveToModuleRootDir } from '../utilities/PathUtilities.js'
25
+ import { getDirName, getFileNameWithoutExtension, getLowercaseFileExtension, joinPath, parsePath, resolveToModuleRootDir } from '../utilities/PathUtilities.js'
26
26
  import { CLIOptions, CLIOptionsKeys } from './CLIOptions.js'
27
27
  import { convertHtmlToText, formatIntegerWithLeadingZeros, formatListWithQuotedElements } from '../utilities/StringUtilities.js'
28
28
 
@@ -517,9 +517,7 @@ export async function speak(operationData: CLIOperationData) {
517
517
  }
518
518
 
519
519
  for (const outputFilename of outputFilenames) {
520
- const placeholderPatternMatch = outputFilename.match(filenamePlaceholderPattern)
521
-
522
- if (placeholderPatternMatch) {
520
+ if (isPlaceholderFilePath(outputFilename)) {
523
521
  continue
524
522
  }
525
523
 
@@ -562,9 +560,7 @@ export async function transcribe(operationData: CLIOperationData) {
562
560
  }
563
561
 
564
562
  for (const outputFilename of outputFilenames) {
565
- const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
566
-
567
- if (partPatternMatch) {
563
+ if (isPlaceholderFilePath(outputFilename)) {
568
564
  continue
569
565
  }
570
566
 
@@ -658,9 +654,7 @@ export async function align(operationData: CLIOperationData) {
658
654
  }
659
655
 
660
656
  for (const outputFilename of outputFilenames) {
661
- const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
662
-
663
- if (partPatternMatch) {
657
+ if (isPlaceholderFilePath(outputFilename)) {
664
658
  continue
665
659
  }
666
660
 
@@ -763,9 +757,7 @@ export async function alignTranslation(operationData: CLIOperationData) {
763
757
  }
764
758
 
765
759
  for (const outputFilename of outputFilenames) {
766
- const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
767
-
768
- if (partPatternMatch) {
760
+ if (isPlaceholderFilePath(outputFilename)) {
769
761
  continue
770
762
  }
771
763
 
@@ -892,9 +884,7 @@ export async function alignTranscriptAndTranslation(operationData: CLIOperationD
892
884
  }
893
885
 
894
886
  for (const outputFilename of outputFilenames) {
895
- const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
896
-
897
- if (partPatternMatch) {
887
+ if (isPlaceholderFilePath(outputFilename)) {
898
888
  continue
899
889
  }
900
890
 
@@ -994,9 +984,7 @@ export async function alignTimelineTranslation(operationData: CLIOperationData)
994
984
  const allowOverwrite = getWithDefault(cliOptions.overwrite, overwriteByDefault)
995
985
 
996
986
  for (const outputFilename of outputFilenames) {
997
- const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
998
-
999
- if (partPatternMatch) {
987
+ if (isPlaceholderFilePath(outputFilename)) {
1000
988
  continue
1001
989
  }
1002
990
 
@@ -1071,9 +1059,7 @@ export async function translateText(operationData: CLIOperationData) {
1071
1059
  logger.start('\nWrite output files')
1072
1060
 
1073
1061
  for (const outputFilename of outputFilenames) {
1074
- const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
1075
-
1076
- if (partPatternMatch) {
1062
+ if (isPlaceholderFilePath(outputFilename)) {
1077
1063
  continue
1078
1064
  }
1079
1065
 
@@ -1134,9 +1120,7 @@ export async function translateSpeech(operationData: CLIOperationData) {
1134
1120
  }
1135
1121
 
1136
1122
  for (const outputFilename of outputFilenames) {
1137
- const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
1138
-
1139
- if (partPatternMatch) {
1123
+ if (isPlaceholderFilePath(outputFilename)) {
1140
1124
  continue
1141
1125
  }
1142
1126
 
@@ -1298,9 +1282,7 @@ export async function detectVoiceActivity(operationData: CLIOperationData) {
1298
1282
  }
1299
1283
 
1300
1284
  for (const outputFilename of outputFilenames) {
1301
- const partPatternMatch = outputFilename.match(filenamePlaceholderPattern)
1302
-
1303
- if (partPatternMatch) {
1285
+ if (isPlaceholderFilePath(outputFilename)) {
1304
1286
  continue
1305
1287
  }
1306
1288
 
@@ -1879,13 +1861,7 @@ async function checkOutputFilenames(outputFilenames: string[], acceptMediaOutput
1879
1861
  throw new Error(errorText)
1880
1862
  }
1881
1863
 
1882
- const placeholderPatternMatch = outputFilename.match(filenamePlaceholderPattern)
1883
-
1884
- if (placeholderPatternMatch) {
1885
- if (placeholderPatternMatch[1] != 'segment') {
1886
- throw new Error(`Invalid placeholder pattern: '${placeholderPatternMatch[1]}'. Placeholder output filename pattern currently only supports 'segment'. For example: '/out/[segment].wav'`)
1887
- }
1888
-
1864
+ if (isPlaceholderFilePath(outputFilename)) {
1889
1865
  includesPlaceholderPattern = true
1890
1866
  }
1891
1867
  }
@@ -1911,13 +1887,13 @@ async function writeOutputFilesForSegment(outputFilenames: string[], index: numb
1911
1887
  }
1912
1888
 
1913
1889
  for (const outputFilename of outputFilenames) {
1914
- const placeholderPatternMatch = outputFilename.match(filenamePlaceholderPattern)
1915
-
1916
- if (!placeholderPatternMatch) {
1890
+ if (!isPlaceholderFilePath(outputFilename)) {
1917
1891
  continue
1918
1892
  }
1919
1893
 
1920
- const segmentFilename = outputFilename.replace(filenamePlaceholderPattern, `${formatIntegerWithLeadingZeros(index + 1, digitCount)} - ${initialText}.$2`)
1894
+ const fileDir = getDirName(outputFilename)
1895
+ const fileExtension = getLowercaseFileExtension(outputFilename)
1896
+ const segmentFilename = joinPath(fileDir, `${formatIntegerWithLeadingZeros(index + 1, digitCount)} - ${initialText}.${fileExtension}`)
1921
1897
 
1922
1898
  const fileSaver = getFileSaver(segmentFilename, allowOverwrite)
1923
1899
  await fileSaver(audio, timeline, text)
@@ -2016,12 +1992,16 @@ function getFileSaver(outputFilePath: string, allowOverwrite: boolean): FileSave
2016
1992
  return fileSaver
2017
1993
  }
2018
1994
 
1995
+ function isPlaceholderFilePath(filePath: string) {
1996
+ const filenameWithoutExtension = getFileNameWithoutExtension(filePath)
1997
+
1998
+ return filenameWithoutExtension === '[segment]'
1999
+ }
2000
+
2019
2001
  const supportedMetadataFileExtensions = ['txt', 'json']
2020
2002
  const supportedSubtitleFileExtensions = ['srt', 'vtt']
2021
2003
  const supportedOutputMediaFileExtensions = ['wav', 'mp3', 'opus', 'm4a', 'ogg', 'flac']
2022
2004
 
2023
- const filenamePlaceholderPattern = /\[(.*)\]\.(.*)$/
2024
-
2025
2005
  const overwriteByDefault = false
2026
2006
 
2027
2007
  startIfInWorkerThread()
@@ -61,7 +61,7 @@ let JiebaWasmInstance: typeof import('jieba-wasm')
61
61
 
62
62
  async function getJiebaWasmInstance() {
63
63
  if (!JiebaWasmInstance) {
64
- const { default: JiebaWasm } = await import('jieba-wasm')
64
+ const JiebaWasm = await import('jieba-wasm')
65
65
 
66
66
  JiebaWasmInstance = JiebaWasm
67
67
  }
@@ -149,7 +149,7 @@ export class VitsTTS {
149
149
 
150
150
  await logger.startAsync('Generate audio using synthesis model')
151
151
 
152
- const { default: Onnx } = await import('onnxruntime-node')
152
+ const Onnx = await import('onnxruntime-node')
153
153
 
154
154
  const inputTensor = new Onnx.Tensor('int64', bigIntIds, [1, bigIntIds.length])
155
155
  const inputLengthsTensor = new Onnx.Tensor('int64', idLengths, [1])
@@ -187,7 +187,7 @@ export class VitsTTS {
187
187
  const logger = new Logger()
188
188
  await logger.startAsync('Initialize VITS ONNX synthesis model')
189
189
 
190
- const { default: Onnx } = await import('onnxruntime-node')
190
+ const Onnx = await import('onnxruntime-node')
191
191
 
192
192
  const filesInModelPath = await readdir(this.modelPath)
193
193
  const onnxModelFilename = filesInModelPath.find(filename => filename.endsWith('.onnx'))
@@ -1,8 +1,8 @@
1
1
  import { request } from "gaxios"
2
2
  import { Logger } from "../utilities/Logger.js"
3
- import { Fragment, splitToFragments, splitToLines } from "../nlp/Segmentation.js"
3
+ import { Fragment, splitToFragments } from "../nlp/Segmentation.js"
4
4
  import { TranslationPair } from "../api/TextTranslation.js"
5
- import { getChromeOnWindowsHeaders, getChromeOnAndroidHeaders } from "../utilities/BrowserRequestHeaders.js"
5
+ import { getChromeOnAndroidHeaders } from "../utilities/BrowserRequestHeaders.js"
6
6
  import { logToStderr } from "../utilities/Utilities.js"
7
7
  import { getShortLanguageCode } from "../utilities/Locale.js"
8
8
  import { PlainTextOptions } from "../api/Common.js"
@@ -90,7 +90,7 @@ export async function translateText(
90
90
 
91
91
  const translatedTextForPart = fragmentTranslationPair[0].translatedText
92
92
 
93
- logger.logTitledMessage(`Translated part`, `"${translatedTextForPart.replaceAll(' | ', '\n') }"`)
93
+ logger.logTitledMessage(`Translated part`, `"${translatedTextForPart.replaceAll(' | ', '\n')}"`)
94
94
 
95
95
  const splitTranslation = translatedTextForPart.split(`|`)
96
96
 
@@ -211,241 +211,6 @@ export async function translateText_MobileWeb(
211
211
  }]
212
212
  }
213
213
 
214
- export async function translateText_WebAPI(text: string, sourceLanguage: string, targetLanguage: string, tld = 'com') {
215
- const logger = new Logger()
216
-
217
- logger.start(`Prepare request`)
218
-
219
- const bodyFormParameters = [[text, sourceLanguage, targetLanguage, 1], []]
220
-
221
- const requestForm = [[['MkEWBc', JSON.stringify(bodyFormParameters), null, 'generic']]]
222
- const stringifiedForm = JSON.stringify(requestForm)
223
- const requestBody =
224
- `f.req=${encodeURIComponent(stringifiedForm)}&`// +
225
-
226
- // Example:
227
- //
228
- // f.req: [[["MkEWBc","[[\"Hey there!\\n\\nHow are you doing today?\",\"en\",\"es\",1],[]]",null,"generic"]]]
229
- // at: AFS6Qyg7vBwAdNvYB3ncQi_YvmYH:1717146157247
230
-
231
- logger.start(`Request translation from Google Translate`)
232
-
233
- const response = await request<string>({
234
- method: 'POST',
235
-
236
- url: `https://translate.google.${tld}/_/TranslateWebserverUi/data/batchexecute`,
237
-
238
- params: {
239
- 'rpcids': 'MkEWBc',
240
- /*'source-path': '/',
241
- 'f.sid': '4165351081036910547',
242
- 'bl': 'boq_translate-webserver_20240528.08_p1',
243
- 'hl': 'en-US',
244
- 'soc-app': '1',
245
- 'soc-platform': '1',
246
- 'soc-device': '1',
247
- '_reqid': '435214',
248
- */
249
- 'rt': 'c',
250
- },
251
-
252
- headers: {
253
- ...getChromeOnWindowsHeaders({
254
- origin: `https://translate.google.${tld}`,
255
- referrer: `https://translate.google.${tld}/`
256
- }),
257
-
258
- 'Content-Type': 'application/x-www-form-urlencoded',
259
- },
260
-
261
- body: requestBody,
262
-
263
- //proxy: 'http://localhost:8080',
264
-
265
- responseType: 'text'
266
- })
267
-
268
- logger.start('Parse response')
269
-
270
- const responseLines = splitToLines(response.data).filter(line => line.trim().length > 0)
271
-
272
- const dataLine = responseLines[2]
273
-
274
- const outerArray = JSON.parse(dataLine)
275
- const innerArray = JSON.parse(outerArray[0][2])
276
- const sentenceEntries: any[] = innerArray[1][0][0][5]
277
-
278
- const translationsPairs = sentenceEntries.map<TranslationPair>((element: any) => {
279
- const translationPair: TranslationPair = {
280
- sourceText: element[6],
281
- translatedText: element[0],
282
- }
283
-
284
- return translationPair
285
- })
286
-
287
- logger.end()
288
-
289
- return translationsPairs
290
- }
291
-
292
- export async function translateText_OldWebAPI(text: string, sourceLanguage: string, targetLanguage: string, tld = 'com') {
293
- const logger = new Logger()
294
-
295
- logger.start(`Prepare request`)
296
-
297
- const token = getToken(text)
298
-
299
- logger.start(`Request translation from Google Translate`)
300
-
301
- const response = await request<string>({
302
- method: 'GET',
303
-
304
- url: `https://translate.google.${tld}/translate_a/single`,
305
-
306
- params: {
307
- client: 't',
308
- sl: normalizeLanguageCodeForGoogleTranslate(sourceLanguage),
309
- tl: normalizeLanguageCodeForGoogleTranslate(targetLanguage),
310
- hl: normalizeLanguageCodeForGoogleTranslate(targetLanguage),
311
- dt: ['at', 'bd', 'ex', 'ld', 'md', 'qca', 'rw', 'rm', 'ss', 't'],
312
- ie: 'UTF-8',
313
- oe: 'UTF-8',
314
- otf: 1,
315
- ssel: 0,
316
- tsel: 0,
317
- kc: 7,
318
- q: text,
319
- tk: token,
320
- },
321
-
322
- headers: getChromeOnWindowsHeaders({
323
- origin: `https://translate.google.${tld}`,
324
- referrer: `https://translate.google.${tld}/`
325
- }),
326
-
327
- responseType: 'json'
328
- })
329
-
330
- const translationsPairs: TranslationPair[] = []
331
-
332
- for (const element of response.data[0] as any as any[]) {
333
- if (element == null || element[0] == null || element[1] == null) {
334
- continue
335
- }
336
-
337
- translationsPairs.push({
338
- sourceText: element[1],
339
- translatedText: element[0],
340
- })
341
- }
342
-
343
- logger.end()
344
-
345
- return translationsPairs
346
- }
347
-
348
- function getToken(query: any) {
349
- function shiftLeftOrRightThenSumOrXor(num: number, optString: any) {
350
- for (let i = 0; i < optString.length - 2; i += 3) {
351
- let acc = optString.charAt(i + 2)
352
-
353
- if ('a' <= acc) {
354
- acc = acc.charCodeAt(0) - 87
355
- } else {
356
- acc = Number(acc)
357
- }
358
-
359
- if (optString.charAt(i + 1) == '+') {
360
- acc = num >>> acc
361
- } else {
362
- acc = num << acc
363
- }
364
-
365
- if (optString.charAt(i) == '+') {
366
- num += acc & 4294967295
367
- } else {
368
- num ^= acc
369
- }
370
- }
371
-
372
- return num
373
- }
374
-
375
- function transformQuery(query: any) {
376
- const bytesArray: number[] = []
377
-
378
- let idx: number = 0
379
-
380
- for (let i = 0; i < query.length; i++) {
381
- let charCode = query.charCodeAt(i)
382
-
383
- if (128 > charCode) {
384
- bytesArray[idx++] = charCode
385
- } else {
386
- if (2048 > charCode) {
387
- bytesArray[idx++] = (charCode >> 6) | 192
388
- } else {
389
- if (
390
- 55296 == (charCode & 64512) &&
391
- i + 1 < query.length &&
392
- 56320 == (query.charCodeAt(i + 1) & 64512)
393
- ) {
394
- charCode =
395
- 65536 +
396
- ((charCode & 1023) << 10) +
397
- (query.charCodeAt(++i) & 1023)
398
-
399
- bytesArray[idx++] = (charCode >> 18) | 240
400
- bytesArray[idx++] = ((charCode >> 12) & 63) | 128
401
- } else {
402
- bytesArray[idx++] = (charCode >> 12) | 224
403
- }
404
-
405
- bytesArray[idx++] = ((charCode >> 6) & 63) | 128
406
- }
407
-
408
- bytesArray[idx++] = (charCode & 63) | 128
409
- }
410
- }
411
-
412
- return bytesArray
413
- }
414
-
415
- function calcHash(query: any, windowTkk: any) {
416
- const tkkSplited = windowTkk.split('.')
417
- const tkkIndex = Number(tkkSplited[0]) || 0
418
- const tkkKey = Number(tkkSplited[1]) || 0
419
-
420
- const bytesArray = transformQuery(query)
421
-
422
- let encondingRound = tkkIndex
423
-
424
- for (let i = 0; i < bytesArray.length; i++) {
425
- encondingRound += bytesArray[i]
426
-
427
- encondingRound = shiftLeftOrRightThenSumOrXor(encondingRound, '+-a^+6')
428
- }
429
-
430
- encondingRound = shiftLeftOrRightThenSumOrXor(encondingRound, '+-3^+b+-f')
431
-
432
- encondingRound ^= tkkKey
433
-
434
- if (encondingRound <= 0) {
435
- encondingRound = (encondingRound & 2147483647) + 2147483648
436
- }
437
-
438
- const normalizedResult = encondingRound % 1000000
439
-
440
- return normalizedResult.toString() + '.' + (normalizedResult ^ tkkIndex)
441
- }
442
-
443
- // TKK value from https://github.com/FilipePS/Traduzir-paginas-web/blob/f3a4956a1aa96b7a9124864158a5200827694521/background/translationService.js
444
- const googleTranslateTKK = '448487.932609646'
445
-
446
- return calcHash(query, googleTranslateTKK)
447
- }
448
-
449
214
  function normalizeLanguageCodeForGoogleTranslate(languageCode: string) {
450
215
  switch (languageCode) {
451
216
  case 'zh': {
@@ -555,6 +320,8 @@ export const supportedLanguageCodes = [
555
320
  'ha', // Hausa
556
321
  'haw', // Hawaiian
557
322
  'he', // Hebrew
323
+ 'iu', // Inuktitut (Syllabics)
324
+ 'iu-Latn', // Inuktitut (Latin script)
558
325
  'iw', // Hebrew
559
326
  'hil', // Hiligaynon
560
327
  'hi', // Hindi
@@ -657,7 +424,7 @@ export const supportedLanguageCodes = [
657
424
  'sm', // Samoan
658
425
  'sg', // Sango
659
426
  'sa', // Sanskrit
660
- 'sat-Latn', // Santali
427
+ 'sat-Latn', // Santali (Latin script)
661
428
  'gd', // Scots Gaelic
662
429
  'nso', // Sepedi
663
430
  'sr', // Serbian
@@ -680,7 +447,7 @@ export const supportedLanguageCodes = [
680
447
  'sv', // Swedish
681
448
  'ty', // Tahitian
682
449
  'tg', // Tajik
683
- 'ber-Latn', // Tamazight
450
+ 'ber-Latn', // Tamazight (Latin script)
684
451
  'ber', // Tamazight (Tifinagh)
685
452
  'ta', // Tamil
686
453
  'tt', // Tatar
@@ -21,7 +21,6 @@ declare module 'cldr-segmentation' {
21
21
  }
22
22
  }
23
23
 
24
- declare module 'sam-js'
25
24
  declare module 'winax'
26
25
 
27
26
  declare module 'tinyld' {
@@ -145,7 +145,7 @@ export function printMatrix(matrix: Float32Array[]) {
145
145
 
146
146
  export async function parseJson(jsonText: string, useJson5 = false) {
147
147
  if (useJson5) {
148
- const { default: JSON5 } = await import('json5')
148
+ const JSON5 = await import('json5')
149
149
 
150
150
  return JSON5.parse(jsonText)
151
151
  } else {
@@ -157,7 +157,7 @@ export async function stringifyAndFormatJson(obj: any, useJson5 = false) {
157
157
  let textContent: string
158
158
 
159
159
  if (useJson5) {
160
- const { default: JSON5 } = await import('json5')
160
+ const JSON5 = await import('json5')
161
161
 
162
162
  textContent = JSON5.stringify(obj, undefined, 4)
163
163
  } else {