@wovin/tranz 0.1.8 → 0.1.11
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/dist/audio.min.js +103 -16
- package/dist/index.min.js +142 -22
- package/dist/providers.min.js +142 -22
- package/dist/utils/audio/split.d.ts +2 -0
- package/dist/utils/audio/split.d.ts.map +1 -1
- package/dist/utils/transcription/transcribe.d.ts.map +1 -1
- package/package.json +2 -3
package/dist/audio.min.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/utils/audio/split.ts
|
|
2
|
-
import
|
|
2
|
+
import { execa } from "execa";
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { spawn } from "child_process";
|
|
@@ -11,21 +11,103 @@ var DEFAULT_SPLIT_CONFIG = {
|
|
|
11
11
|
preferLongerSilence: true,
|
|
12
12
|
silenceBuffer: 0.2
|
|
13
13
|
};
|
|
14
|
+
async function execFFprobe(audioPath) {
|
|
15
|
+
try {
|
|
16
|
+
const { stdout } = await execa("ffprobe", [
|
|
17
|
+
"-v",
|
|
18
|
+
"error",
|
|
19
|
+
"-print_format",
|
|
20
|
+
"json",
|
|
21
|
+
"-show_format",
|
|
22
|
+
"-show_streams",
|
|
23
|
+
audioPath
|
|
24
|
+
]);
|
|
25
|
+
return JSON.parse(stdout);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
throw new Error(`Failed to probe audio: ${err instanceof Error ? err.message : String(err)}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function extractAudioSegment(inputPath, outputPath, startSec, durationSec) {
|
|
31
|
+
try {
|
|
32
|
+
await execa("ffmpeg", [
|
|
33
|
+
"-ss",
|
|
34
|
+
startSec.toString(),
|
|
35
|
+
"-t",
|
|
36
|
+
durationSec.toString(),
|
|
37
|
+
"-i",
|
|
38
|
+
inputPath,
|
|
39
|
+
"-ar",
|
|
40
|
+
"16000",
|
|
41
|
+
// 16kHz sample rate (Whisper-compatible)
|
|
42
|
+
"-ac",
|
|
43
|
+
"1",
|
|
44
|
+
// mono
|
|
45
|
+
"-c:a",
|
|
46
|
+
"pcm_s16le",
|
|
47
|
+
// 16-bit PCM codec
|
|
48
|
+
"-y",
|
|
49
|
+
// overwrite output
|
|
50
|
+
outputPath
|
|
51
|
+
]);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
throw new Error(`Failed to extract segment: ${err instanceof Error ? err.message : String(err)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function getDurationViaFfmpeg(audioPath) {
|
|
57
|
+
try {
|
|
58
|
+
const { stderr } = await execa("ffmpeg", [
|
|
59
|
+
"-i",
|
|
60
|
+
audioPath,
|
|
61
|
+
"-f",
|
|
62
|
+
"null",
|
|
63
|
+
"-"
|
|
64
|
+
], { reject: false });
|
|
65
|
+
const durationMatch = stderr.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);
|
|
66
|
+
if (durationMatch) {
|
|
67
|
+
const hours = parseFloat(durationMatch[1]);
|
|
68
|
+
const minutes = parseFloat(durationMatch[2]);
|
|
69
|
+
const seconds = parseFloat(durationMatch[3]);
|
|
70
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
71
|
+
}
|
|
72
|
+
const timeMatches = [...stderr.matchAll(/time=(\d+):(\d+):(\d+(?:\.\d+)?)/g)];
|
|
73
|
+
if (timeMatches.length > 0) {
|
|
74
|
+
const lastMatch = timeMatches[timeMatches.length - 1];
|
|
75
|
+
const hours = parseFloat(lastMatch[1]);
|
|
76
|
+
const minutes = parseFloat(lastMatch[2]);
|
|
77
|
+
const seconds = parseFloat(lastMatch[3]);
|
|
78
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
14
84
|
async function getAudioDuration(audioPath) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
85
|
+
const metadata = await execFFprobe(audioPath);
|
|
86
|
+
if (metadata.format?.duration) {
|
|
87
|
+
const duration = parseFloat(String(metadata.format.duration));
|
|
88
|
+
if (!isNaN(duration) && duration > 0) {
|
|
89
|
+
return duration;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (metadata.streams?.length) {
|
|
93
|
+
for (const stream of metadata.streams) {
|
|
94
|
+
if (stream.duration) {
|
|
95
|
+
const duration = parseFloat(String(stream.duration));
|
|
96
|
+
if (!isNaN(duration) && duration > 0) {
|
|
97
|
+
return duration;
|
|
98
|
+
}
|
|
25
99
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const ffmpegDuration = await getDurationViaFfmpeg(audioPath);
|
|
103
|
+
if (ffmpegDuration !== void 0 && ffmpegDuration > 0) {
|
|
104
|
+
return ffmpegDuration;
|
|
105
|
+
}
|
|
106
|
+
const hasFormat = !!metadata.format;
|
|
107
|
+
const hasStreams = !!metadata.streams?.length;
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Could not determine audio duration (format: ${hasFormat}, streams: ${hasStreams}). File may be corrupted or in an unsupported format.`
|
|
110
|
+
);
|
|
29
111
|
}
|
|
30
112
|
async function detectSilenceRegions(audioPath, config = {}) {
|
|
31
113
|
const { minSilenceDurSec, silenceThreshold } = { ...DEFAULT_SPLIT_CONFIG, ...config };
|
|
@@ -143,8 +225,13 @@ async function splitAudioAtPoints(audioPath, splitPoints, totalDuration, outputD
|
|
|
143
225
|
outputPath
|
|
144
226
|
};
|
|
145
227
|
segments.push(segment);
|
|
146
|
-
const extractPromise =
|
|
147
|
-
|
|
228
|
+
const extractPromise = extractAudioSegment(
|
|
229
|
+
audioPath,
|
|
230
|
+
outputPath,
|
|
231
|
+
startSec,
|
|
232
|
+
durationSec
|
|
233
|
+
).catch((err) => {
|
|
234
|
+
throw new Error(`Failed to extract segment ${i}: ${err instanceof Error ? err.message : String(err)}`);
|
|
148
235
|
});
|
|
149
236
|
splitPromises.push(extractPromise);
|
|
150
237
|
}
|
package/dist/index.min.js
CHANGED
|
@@ -349,7 +349,7 @@ var GreenPTProvider = class {
|
|
|
349
349
|
};
|
|
350
350
|
|
|
351
351
|
// src/utils/audio/split.ts
|
|
352
|
-
import
|
|
352
|
+
import { execa } from "execa";
|
|
353
353
|
import * as fs2 from "fs";
|
|
354
354
|
import path2 from "path";
|
|
355
355
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -361,21 +361,103 @@ var DEFAULT_SPLIT_CONFIG = {
|
|
|
361
361
|
preferLongerSilence: true,
|
|
362
362
|
silenceBuffer: 0.2
|
|
363
363
|
};
|
|
364
|
+
async function execFFprobe(audioPath) {
|
|
365
|
+
try {
|
|
366
|
+
const { stdout } = await execa("ffprobe", [
|
|
367
|
+
"-v",
|
|
368
|
+
"error",
|
|
369
|
+
"-print_format",
|
|
370
|
+
"json",
|
|
371
|
+
"-show_format",
|
|
372
|
+
"-show_streams",
|
|
373
|
+
audioPath
|
|
374
|
+
]);
|
|
375
|
+
return JSON.parse(stdout);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
throw new Error(`Failed to probe audio: ${err instanceof Error ? err.message : String(err)}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async function extractAudioSegment(inputPath, outputPath, startSec, durationSec) {
|
|
381
|
+
try {
|
|
382
|
+
await execa("ffmpeg", [
|
|
383
|
+
"-ss",
|
|
384
|
+
startSec.toString(),
|
|
385
|
+
"-t",
|
|
386
|
+
durationSec.toString(),
|
|
387
|
+
"-i",
|
|
388
|
+
inputPath,
|
|
389
|
+
"-ar",
|
|
390
|
+
"16000",
|
|
391
|
+
// 16kHz sample rate (Whisper-compatible)
|
|
392
|
+
"-ac",
|
|
393
|
+
"1",
|
|
394
|
+
// mono
|
|
395
|
+
"-c:a",
|
|
396
|
+
"pcm_s16le",
|
|
397
|
+
// 16-bit PCM codec
|
|
398
|
+
"-y",
|
|
399
|
+
// overwrite output
|
|
400
|
+
outputPath
|
|
401
|
+
]);
|
|
402
|
+
} catch (err) {
|
|
403
|
+
throw new Error(`Failed to extract segment: ${err instanceof Error ? err.message : String(err)}`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
async function getDurationViaFfmpeg(audioPath) {
|
|
407
|
+
try {
|
|
408
|
+
const { stderr } = await execa("ffmpeg", [
|
|
409
|
+
"-i",
|
|
410
|
+
audioPath,
|
|
411
|
+
"-f",
|
|
412
|
+
"null",
|
|
413
|
+
"-"
|
|
414
|
+
], { reject: false });
|
|
415
|
+
const durationMatch = stderr.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);
|
|
416
|
+
if (durationMatch) {
|
|
417
|
+
const hours = parseFloat(durationMatch[1]);
|
|
418
|
+
const minutes = parseFloat(durationMatch[2]);
|
|
419
|
+
const seconds = parseFloat(durationMatch[3]);
|
|
420
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
421
|
+
}
|
|
422
|
+
const timeMatches = [...stderr.matchAll(/time=(\d+):(\d+):(\d+(?:\.\d+)?)/g)];
|
|
423
|
+
if (timeMatches.length > 0) {
|
|
424
|
+
const lastMatch = timeMatches[timeMatches.length - 1];
|
|
425
|
+
const hours = parseFloat(lastMatch[1]);
|
|
426
|
+
const minutes = parseFloat(lastMatch[2]);
|
|
427
|
+
const seconds = parseFloat(lastMatch[3]);
|
|
428
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
429
|
+
}
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
return void 0;
|
|
433
|
+
}
|
|
364
434
|
async function getAudioDuration(audioPath) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
435
|
+
const metadata = await execFFprobe(audioPath);
|
|
436
|
+
if (metadata.format?.duration) {
|
|
437
|
+
const duration = parseFloat(String(metadata.format.duration));
|
|
438
|
+
if (!isNaN(duration) && duration > 0) {
|
|
439
|
+
return duration;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (metadata.streams?.length) {
|
|
443
|
+
for (const stream of metadata.streams) {
|
|
444
|
+
if (stream.duration) {
|
|
445
|
+
const duration = parseFloat(String(stream.duration));
|
|
446
|
+
if (!isNaN(duration) && duration > 0) {
|
|
447
|
+
return duration;
|
|
448
|
+
}
|
|
375
449
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const ffmpegDuration = await getDurationViaFfmpeg(audioPath);
|
|
453
|
+
if (ffmpegDuration !== void 0 && ffmpegDuration > 0) {
|
|
454
|
+
return ffmpegDuration;
|
|
455
|
+
}
|
|
456
|
+
const hasFormat = !!metadata.format;
|
|
457
|
+
const hasStreams = !!metadata.streams?.length;
|
|
458
|
+
throw new Error(
|
|
459
|
+
`Could not determine audio duration (format: ${hasFormat}, streams: ${hasStreams}). File may be corrupted or in an unsupported format.`
|
|
460
|
+
);
|
|
379
461
|
}
|
|
380
462
|
async function detectSilenceRegions(audioPath, config = {}) {
|
|
381
463
|
const { minSilenceDurSec, silenceThreshold } = { ...DEFAULT_SPLIT_CONFIG, ...config };
|
|
@@ -493,8 +575,13 @@ async function splitAudioAtPoints(audioPath, splitPoints, totalDuration, outputD
|
|
|
493
575
|
outputPath
|
|
494
576
|
};
|
|
495
577
|
segments.push(segment);
|
|
496
|
-
const extractPromise =
|
|
497
|
-
|
|
578
|
+
const extractPromise = extractAudioSegment(
|
|
579
|
+
audioPath,
|
|
580
|
+
outputPath,
|
|
581
|
+
startSec,
|
|
582
|
+
durationSec
|
|
583
|
+
).catch((err) => {
|
|
584
|
+
throw new Error(`Failed to extract segment ${i}: ${err instanceof Error ? err.message : String(err)}`);
|
|
498
585
|
});
|
|
499
586
|
splitPromises.push(extractPromise);
|
|
500
587
|
}
|
|
@@ -688,17 +775,44 @@ var defaultLogger = {
|
|
|
688
775
|
}
|
|
689
776
|
// silent by default
|
|
690
777
|
};
|
|
778
|
+
var MIME_TO_EXT = {
|
|
779
|
+
"audio/mpeg": ".mp3",
|
|
780
|
+
"audio/mp3": ".mp3",
|
|
781
|
+
"audio/wav": ".wav",
|
|
782
|
+
"audio/x-wav": ".wav",
|
|
783
|
+
"audio/ogg": ".ogg",
|
|
784
|
+
"audio/flac": ".flac",
|
|
785
|
+
"audio/x-flac": ".flac",
|
|
786
|
+
"audio/mp4": ".m4a",
|
|
787
|
+
"audio/m4a": ".m4a",
|
|
788
|
+
"audio/aac": ".aac",
|
|
789
|
+
"audio/webm": ".webm",
|
|
790
|
+
"audio/opus": ".opus"
|
|
791
|
+
};
|
|
792
|
+
function getExtFromContentType(contentType, url) {
|
|
793
|
+
if (contentType) {
|
|
794
|
+
const mimeType = contentType.split(";")[0].trim().toLowerCase();
|
|
795
|
+
if (MIME_TO_EXT[mimeType]) {
|
|
796
|
+
return MIME_TO_EXT[mimeType];
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
try {
|
|
800
|
+
const urlPath = new URL(url).pathname;
|
|
801
|
+
const ext = path3.extname(urlPath).toLowerCase();
|
|
802
|
+
if (ext && [".mp3", ".wav", ".ogg", ".flac", ".m4a", ".aac", ".webm", ".opus"].includes(ext)) {
|
|
803
|
+
return ext;
|
|
804
|
+
}
|
|
805
|
+
} catch {
|
|
806
|
+
}
|
|
807
|
+
return ".audio";
|
|
808
|
+
}
|
|
691
809
|
async function downloadToTempFile(url, outputDir) {
|
|
692
|
-
const tempPath = path3.join(outputDir, `download-${Date.now()}.audio`);
|
|
693
|
-
const file = fs3.createWriteStream(tempPath);
|
|
694
810
|
return new Promise((resolve, reject) => {
|
|
695
811
|
const protocol = url.startsWith("https") ? https : http;
|
|
696
812
|
protocol.get(url, (response) => {
|
|
697
813
|
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
698
814
|
const redirectUrl = response.headers.location;
|
|
699
815
|
if (redirectUrl) {
|
|
700
|
-
file.close();
|
|
701
|
-
fs3.unlinkSync(tempPath);
|
|
702
816
|
downloadToTempFile(redirectUrl, outputDir).then(resolve).catch(reject);
|
|
703
817
|
return;
|
|
704
818
|
}
|
|
@@ -707,14 +821,20 @@ async function downloadToTempFile(url, outputDir) {
|
|
|
707
821
|
reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
|
|
708
822
|
return;
|
|
709
823
|
}
|
|
824
|
+
const ext = getExtFromContentType(response.headers["content-type"], url);
|
|
825
|
+
const tempPath = path3.join(outputDir, `download-${Date.now()}${ext}`);
|
|
826
|
+
const file = fs3.createWriteStream(tempPath);
|
|
710
827
|
response.pipe(file);
|
|
711
828
|
file.on("finish", () => {
|
|
712
829
|
file.close();
|
|
713
830
|
resolve(tempPath);
|
|
714
831
|
});
|
|
715
|
-
|
|
716
|
-
|
|
832
|
+
file.on("error", (err) => {
|
|
833
|
+
fs3.unlink(tempPath, () => {
|
|
834
|
+
});
|
|
835
|
+
reject(err);
|
|
717
836
|
});
|
|
837
|
+
}).on("error", (err) => {
|
|
718
838
|
reject(err);
|
|
719
839
|
});
|
|
720
840
|
});
|
package/dist/providers.min.js
CHANGED
|
@@ -356,7 +356,7 @@ import * as os from "os";
|
|
|
356
356
|
import * as path3 from "path";
|
|
357
357
|
|
|
358
358
|
// src/utils/audio/split.ts
|
|
359
|
-
import
|
|
359
|
+
import { execa } from "execa";
|
|
360
360
|
import * as fs2 from "fs";
|
|
361
361
|
import path2 from "path";
|
|
362
362
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -368,21 +368,103 @@ var DEFAULT_SPLIT_CONFIG = {
|
|
|
368
368
|
preferLongerSilence: true,
|
|
369
369
|
silenceBuffer: 0.2
|
|
370
370
|
};
|
|
371
|
+
async function execFFprobe(audioPath) {
|
|
372
|
+
try {
|
|
373
|
+
const { stdout } = await execa("ffprobe", [
|
|
374
|
+
"-v",
|
|
375
|
+
"error",
|
|
376
|
+
"-print_format",
|
|
377
|
+
"json",
|
|
378
|
+
"-show_format",
|
|
379
|
+
"-show_streams",
|
|
380
|
+
audioPath
|
|
381
|
+
]);
|
|
382
|
+
return JSON.parse(stdout);
|
|
383
|
+
} catch (err) {
|
|
384
|
+
throw new Error(`Failed to probe audio: ${err instanceof Error ? err.message : String(err)}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
async function extractAudioSegment(inputPath, outputPath, startSec, durationSec) {
|
|
388
|
+
try {
|
|
389
|
+
await execa("ffmpeg", [
|
|
390
|
+
"-ss",
|
|
391
|
+
startSec.toString(),
|
|
392
|
+
"-t",
|
|
393
|
+
durationSec.toString(),
|
|
394
|
+
"-i",
|
|
395
|
+
inputPath,
|
|
396
|
+
"-ar",
|
|
397
|
+
"16000",
|
|
398
|
+
// 16kHz sample rate (Whisper-compatible)
|
|
399
|
+
"-ac",
|
|
400
|
+
"1",
|
|
401
|
+
// mono
|
|
402
|
+
"-c:a",
|
|
403
|
+
"pcm_s16le",
|
|
404
|
+
// 16-bit PCM codec
|
|
405
|
+
"-y",
|
|
406
|
+
// overwrite output
|
|
407
|
+
outputPath
|
|
408
|
+
]);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
throw new Error(`Failed to extract segment: ${err instanceof Error ? err.message : String(err)}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function getDurationViaFfmpeg(audioPath) {
|
|
414
|
+
try {
|
|
415
|
+
const { stderr } = await execa("ffmpeg", [
|
|
416
|
+
"-i",
|
|
417
|
+
audioPath,
|
|
418
|
+
"-f",
|
|
419
|
+
"null",
|
|
420
|
+
"-"
|
|
421
|
+
], { reject: false });
|
|
422
|
+
const durationMatch = stderr.match(/Duration:\s*(\d+):(\d+):(\d+(?:\.\d+)?)/);
|
|
423
|
+
if (durationMatch) {
|
|
424
|
+
const hours = parseFloat(durationMatch[1]);
|
|
425
|
+
const minutes = parseFloat(durationMatch[2]);
|
|
426
|
+
const seconds = parseFloat(durationMatch[3]);
|
|
427
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
428
|
+
}
|
|
429
|
+
const timeMatches = [...stderr.matchAll(/time=(\d+):(\d+):(\d+(?:\.\d+)?)/g)];
|
|
430
|
+
if (timeMatches.length > 0) {
|
|
431
|
+
const lastMatch = timeMatches[timeMatches.length - 1];
|
|
432
|
+
const hours = parseFloat(lastMatch[1]);
|
|
433
|
+
const minutes = parseFloat(lastMatch[2]);
|
|
434
|
+
const seconds = parseFloat(lastMatch[3]);
|
|
435
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
436
|
+
}
|
|
437
|
+
} catch {
|
|
438
|
+
}
|
|
439
|
+
return void 0;
|
|
440
|
+
}
|
|
371
441
|
async function getAudioDuration(audioPath) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
442
|
+
const metadata = await execFFprobe(audioPath);
|
|
443
|
+
if (metadata.format?.duration) {
|
|
444
|
+
const duration = parseFloat(String(metadata.format.duration));
|
|
445
|
+
if (!isNaN(duration) && duration > 0) {
|
|
446
|
+
return duration;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (metadata.streams?.length) {
|
|
450
|
+
for (const stream of metadata.streams) {
|
|
451
|
+
if (stream.duration) {
|
|
452
|
+
const duration = parseFloat(String(stream.duration));
|
|
453
|
+
if (!isNaN(duration) && duration > 0) {
|
|
454
|
+
return duration;
|
|
455
|
+
}
|
|
382
456
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const ffmpegDuration = await getDurationViaFfmpeg(audioPath);
|
|
460
|
+
if (ffmpegDuration !== void 0 && ffmpegDuration > 0) {
|
|
461
|
+
return ffmpegDuration;
|
|
462
|
+
}
|
|
463
|
+
const hasFormat = !!metadata.format;
|
|
464
|
+
const hasStreams = !!metadata.streams?.length;
|
|
465
|
+
throw new Error(
|
|
466
|
+
`Could not determine audio duration (format: ${hasFormat}, streams: ${hasStreams}). File may be corrupted or in an unsupported format.`
|
|
467
|
+
);
|
|
386
468
|
}
|
|
387
469
|
async function detectSilenceRegions(audioPath, config = {}) {
|
|
388
470
|
const { minSilenceDurSec, silenceThreshold } = { ...DEFAULT_SPLIT_CONFIG, ...config };
|
|
@@ -500,8 +582,13 @@ async function splitAudioAtPoints(audioPath, splitPoints, totalDuration, outputD
|
|
|
500
582
|
outputPath
|
|
501
583
|
};
|
|
502
584
|
segments.push(segment);
|
|
503
|
-
const extractPromise =
|
|
504
|
-
|
|
585
|
+
const extractPromise = extractAudioSegment(
|
|
586
|
+
audioPath,
|
|
587
|
+
outputPath,
|
|
588
|
+
startSec,
|
|
589
|
+
durationSec
|
|
590
|
+
).catch((err) => {
|
|
591
|
+
throw new Error(`Failed to extract segment ${i}: ${err instanceof Error ? err.message : String(err)}`);
|
|
505
592
|
});
|
|
506
593
|
splitPromises.push(extractPromise);
|
|
507
594
|
}
|
|
@@ -610,17 +697,44 @@ var defaultLogger = {
|
|
|
610
697
|
}
|
|
611
698
|
// silent by default
|
|
612
699
|
};
|
|
700
|
+
var MIME_TO_EXT = {
|
|
701
|
+
"audio/mpeg": ".mp3",
|
|
702
|
+
"audio/mp3": ".mp3",
|
|
703
|
+
"audio/wav": ".wav",
|
|
704
|
+
"audio/x-wav": ".wav",
|
|
705
|
+
"audio/ogg": ".ogg",
|
|
706
|
+
"audio/flac": ".flac",
|
|
707
|
+
"audio/x-flac": ".flac",
|
|
708
|
+
"audio/mp4": ".m4a",
|
|
709
|
+
"audio/m4a": ".m4a",
|
|
710
|
+
"audio/aac": ".aac",
|
|
711
|
+
"audio/webm": ".webm",
|
|
712
|
+
"audio/opus": ".opus"
|
|
713
|
+
};
|
|
714
|
+
function getExtFromContentType(contentType, url) {
|
|
715
|
+
if (contentType) {
|
|
716
|
+
const mimeType = contentType.split(";")[0].trim().toLowerCase();
|
|
717
|
+
if (MIME_TO_EXT[mimeType]) {
|
|
718
|
+
return MIME_TO_EXT[mimeType];
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
try {
|
|
722
|
+
const urlPath = new URL(url).pathname;
|
|
723
|
+
const ext = path3.extname(urlPath).toLowerCase();
|
|
724
|
+
if (ext && [".mp3", ".wav", ".ogg", ".flac", ".m4a", ".aac", ".webm", ".opus"].includes(ext)) {
|
|
725
|
+
return ext;
|
|
726
|
+
}
|
|
727
|
+
} catch {
|
|
728
|
+
}
|
|
729
|
+
return ".audio";
|
|
730
|
+
}
|
|
613
731
|
async function downloadToTempFile(url, outputDir) {
|
|
614
|
-
const tempPath = path3.join(outputDir, `download-${Date.now()}.audio`);
|
|
615
|
-
const file = fs3.createWriteStream(tempPath);
|
|
616
732
|
return new Promise((resolve, reject) => {
|
|
617
733
|
const protocol = url.startsWith("https") ? https : http;
|
|
618
734
|
protocol.get(url, (response) => {
|
|
619
735
|
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
620
736
|
const redirectUrl = response.headers.location;
|
|
621
737
|
if (redirectUrl) {
|
|
622
|
-
file.close();
|
|
623
|
-
fs3.unlinkSync(tempPath);
|
|
624
738
|
downloadToTempFile(redirectUrl, outputDir).then(resolve).catch(reject);
|
|
625
739
|
return;
|
|
626
740
|
}
|
|
@@ -629,14 +743,20 @@ async function downloadToTempFile(url, outputDir) {
|
|
|
629
743
|
reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
|
|
630
744
|
return;
|
|
631
745
|
}
|
|
746
|
+
const ext = getExtFromContentType(response.headers["content-type"], url);
|
|
747
|
+
const tempPath = path3.join(outputDir, `download-${Date.now()}${ext}`);
|
|
748
|
+
const file = fs3.createWriteStream(tempPath);
|
|
632
749
|
response.pipe(file);
|
|
633
750
|
file.on("finish", () => {
|
|
634
751
|
file.close();
|
|
635
752
|
resolve(tempPath);
|
|
636
753
|
});
|
|
637
|
-
|
|
638
|
-
|
|
754
|
+
file.on("error", (err) => {
|
|
755
|
+
fs3.unlink(tempPath, () => {
|
|
756
|
+
});
|
|
757
|
+
reject(err);
|
|
639
758
|
});
|
|
759
|
+
}).on("error", (err) => {
|
|
640
760
|
reject(err);
|
|
641
761
|
});
|
|
642
762
|
});
|
|
@@ -50,6 +50,8 @@ export interface AudioSegment {
|
|
|
50
50
|
export declare const DEFAULT_SPLIT_CONFIG: SplitConfig;
|
|
51
51
|
/**
|
|
52
52
|
* Get the duration of an audio file in seconds
|
|
53
|
+
* Tries format.duration first, then falls back to stream duration,
|
|
54
|
+
* and finally uses ffmpeg decode as last resort
|
|
53
55
|
*/
|
|
54
56
|
export declare function getAudioDuration(audioPath: string): Promise<number>;
|
|
55
57
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/utils/audio/split.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,gBAAgB,EAAE,MAAM,CAAA;IACxB,kDAAkD;IAClD,gBAAgB,EAAE,MAAM,CAAA;IACxB,wDAAwD;IACxD,mBAAmB,EAAE,OAAO,CAAA;IAC5B,uDAAuD;IACvD,aAAa,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,eAAe,EAAE,MAAM,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAMlC,CAAA;
|
|
1
|
+
{"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/utils/audio/split.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,iEAAiE;IACjE,cAAc,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,gBAAgB,EAAE,MAAM,CAAA;IACxB,kDAAkD;IAClD,gBAAgB,EAAE,MAAM,CAAA;IACxB,wDAAwD;IACxD,mBAAmB,EAAE,OAAO,CAAA;IAC5B,uDAAuD;IACvD,aAAa,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,eAAe,EAAE,MAAM,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAMlC,CAAA;AAsFD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoCzE;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAChC,OAAO,CAAC,aAAa,EAAE,CAAC,CAsD1B;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,aAAa,EAAE,EAC/B,aAAa,EAAE,MAAM,EACrB,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAChC,UAAU,EAAE,CAwEd;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,UAAU,EAAE,EACzB,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,EAAE,CAAC,CA2CzB;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAChC,OAAO,CAAC,YAAY,EAAE,CAAC,CAuCzB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,cAAc,EAAE,aAAa,EAAE,CAAA;IAC/B,UAAU,EAAE,OAAO,CAAA;CACpB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAChC,OAAO,CAAC,aAAa,CAAC,CA0BxB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transcribe.d.ts","sourceRoot":"","sources":["../../../src/utils/transcription/transcribe.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,OAAO,EAA6B,KAAK,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AAErG,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B;AAQD,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,+EAA+E;IAC/E,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,uCAAuC;IACvC,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,mCAAmC;IACnC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;
|
|
1
|
+
{"version":3,"file":"transcribe.d.ts","sourceRoot":"","sources":["../../../src/utils/transcription/transcribe.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,OAAO,EAA6B,KAAK,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AAErG,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B;AAQD,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,+EAA+E;IAC/E,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,uCAAuC;IACvC,MAAM,CAAC,EAAE,gBAAgB,CAAA;IACzB,mCAAmC;IACnC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA6FD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAA;CAC3E;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,wBAAwB,GAAG,kBAAkB,CA4K7F;AAED,+BAA+B;AAC/B,eAAO,MAAM,UAAU,iCAA2B,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wovin/tranz",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Audio transcription library with provider support and auto-splitting",
|
|
6
6
|
"author": "gotjoshua @gotjoshua",
|
|
@@ -41,10 +41,9 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mistralai/mistralai": "^1.14.0",
|
|
44
|
-
"
|
|
44
|
+
"execa": "^9.6.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@types/fluent-ffmpeg": "^2.1.21",
|
|
48
47
|
"@types/node": "^24.10.1",
|
|
49
48
|
"@types/ws": "^8.5.13",
|
|
50
49
|
"@types/yargs": "^17.0.33",
|