@zwa73/utils 1.0.186 → 1.0.188
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/UtilFfmpegTools.d.ts +9 -0
- package/dist/UtilFfmpegTools.js +26 -10
- package/dist/UtilFunctions.d.ts +9 -1
- package/dist/UtilFunctions.js +39 -0
- package/dist/UtilInterfaces.d.ts +9 -0
- package/package.json +1 -1
- package/src/UtilFfmpegTools.ts +31 -9
- package/src/UtilFunctions.ts +48 -1
- package/src/UtilInterfaces.ts +12 -1
|
@@ -23,6 +23,11 @@ declare class SFfmpegTool {
|
|
|
23
23
|
* @param quality - 质量
|
|
24
24
|
*/
|
|
25
25
|
static flac2ogg(inputFlacFile: string, outputOggPath: string, quality?: number): Promise<boolean>;
|
|
26
|
+
/**flac转wav
|
|
27
|
+
* @param inputFlacFile - 输入flac文件路径
|
|
28
|
+
* @param outputWavPath - 输出wav文件路径
|
|
29
|
+
*/
|
|
30
|
+
static flac2wav(inputFlacFile: string, outputWavPath: string): Promise<boolean>;
|
|
26
31
|
/**wav转ogg
|
|
27
32
|
* @param inputWavPath - 输入wav文件路径
|
|
28
33
|
* @param outputOggPath - 输出ogg文件路径
|
|
@@ -48,6 +53,10 @@ declare class SFfmpegTool {
|
|
|
48
53
|
* @param outputWavPath - 输出wav文件路径
|
|
49
54
|
*/
|
|
50
55
|
static resample(inputWavPath: string, outputWavPath: string, rate?: number): Promise<boolean>;
|
|
56
|
+
/**检查WAV文件是否为单声道
|
|
57
|
+
* @param filePath - 要检查的WAV文件路径
|
|
58
|
+
*/
|
|
59
|
+
static isMono(filePath: string): Promise<boolean>;
|
|
51
60
|
/**wav转ogg多线程
|
|
52
61
|
* @param ioMap - 输入输出路径映射
|
|
53
62
|
* @param quality - 质量
|
package/dist/UtilFfmpegTools.js
CHANGED
|
@@ -32,6 +32,7 @@ const pathe_1 = __importDefault(require("pathe"));
|
|
|
32
32
|
const fs = __importStar(require("fs"));
|
|
33
33
|
const UtilLogger_1 = require("./UtilLogger");
|
|
34
34
|
const UtilClass_1 = require("./UtilClass");
|
|
35
|
+
const QuickFunction_1 = require("./QuickFunction");
|
|
35
36
|
/**ffmpeg工具类
|
|
36
37
|
*/
|
|
37
38
|
class SFfmpegTool {
|
|
@@ -70,11 +71,21 @@ class SFfmpegTool {
|
|
|
70
71
|
* @param quality - 质量
|
|
71
72
|
*/
|
|
72
73
|
static async flac2ogg(inputFlacFile, outputOggPath, quality = 10) {
|
|
73
|
-
|
|
74
|
+
const wavPath = pathe_1.default.join(pathe_1.default.dirname(inputFlacFile), `tmp_${pathe_1.default.basename(inputFlacFile, ".flac")}.wav`);
|
|
75
|
+
await SFfmpegTool.flac2wav(inputFlacFile, wavPath);
|
|
76
|
+
await SFfmpegTool.wav2ogg(wavPath, outputOggPath, quality);
|
|
77
|
+
await fs.promises.unlink(wavPath);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
/**flac转wav
|
|
81
|
+
* @param inputFlacFile - 输入flac文件路径
|
|
82
|
+
* @param outputWavPath - 输出wav文件路径
|
|
83
|
+
*/
|
|
84
|
+
static async flac2wav(inputFlacFile, outputWavPath) {
|
|
74
85
|
await new Promise((resolve, reject) => {
|
|
75
86
|
const ins = (0, fluent_ffmpeg_1.default)(inputFlacFile)
|
|
76
87
|
.audioCodec("pcm_s16le")
|
|
77
|
-
.save(
|
|
88
|
+
.save(outputWavPath)
|
|
78
89
|
.on("end", () => {
|
|
79
90
|
resolve(true);
|
|
80
91
|
ins.kill('SIGTERM');
|
|
@@ -84,14 +95,6 @@ class SFfmpegTool {
|
|
|
84
95
|
ins.kill('SIGTERM');
|
|
85
96
|
});
|
|
86
97
|
});
|
|
87
|
-
await SFfmpegTool.wav2ogg(wavPath, outputOggPath, quality);
|
|
88
|
-
await new Promise((resolve, reject) => {
|
|
89
|
-
fs.unlink(wavPath, function (err) {
|
|
90
|
-
if (err)
|
|
91
|
-
UtilLogger_1.SLogger.error("SFfmpegTool.flac2ogg unlink 错误", err);
|
|
92
|
-
resolve(null);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
98
|
return true;
|
|
96
99
|
}
|
|
97
100
|
/**wav转ogg
|
|
@@ -184,6 +187,19 @@ class SFfmpegTool {
|
|
|
184
187
|
});
|
|
185
188
|
});
|
|
186
189
|
}
|
|
190
|
+
/**检查WAV文件是否为单声道
|
|
191
|
+
* @param filePath - 要检查的WAV文件路径
|
|
192
|
+
*/
|
|
193
|
+
static async isMono(filePath) {
|
|
194
|
+
const metadata = await SFfmpegTool.getAudioMetaData(filePath);
|
|
195
|
+
if (metadata == null)
|
|
196
|
+
(0, QuickFunction_1.throwError)("SFfmpegTool.isMono 获取音频元数据失败");
|
|
197
|
+
// 检查音频流的声道数
|
|
198
|
+
const audioStream = metadata.streams.find(stream => stream.codec_type === 'audio');
|
|
199
|
+
if (audioStream == null)
|
|
200
|
+
(0, QuickFunction_1.throwError)("SFfmpegTool.isMono 未找到音频流");
|
|
201
|
+
return audioStream.channels === 1;
|
|
202
|
+
}
|
|
187
203
|
//多线程处理
|
|
188
204
|
/**wav转ogg多线程
|
|
189
205
|
* @param ioMap - 输入输出路径映射
|
package/dist/UtilFunctions.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PRecord, AnyFunc, ExtractOutcome, IJData, JObject, JToken, Keyable, Literal, Matchable, MatchableFlag, Outcome, ReqVerifyFn, ProperSubsetCheck, UnionToIntersection, AnyRecord, AllExtends } from "./UtilInterfaces";
|
|
1
|
+
import { PRecord, AnyFunc, ExtractOutcome, IJData, JObject, JToken, Keyable, Literal, Matchable, MatchableFlag, Outcome, ReqVerifyFn, ProperSubsetCheck, UnionToIntersection, AnyRecord, AllExtends, SrtSegment } from "./UtilInterfaces";
|
|
2
2
|
import { LogLevel } from "./UtilLogger";
|
|
3
3
|
import { Failed, FailedLike, None, StatusSymbol, Success, SuccessLike, Timeout } from "./UtilSymbol";
|
|
4
4
|
declare const HashMethodList: readonly ["md5", "sha1", "sha256", "sha512", "sha3", "blake2", "blake3"];
|
|
@@ -303,5 +303,13 @@ export declare class UtilFunc {
|
|
|
303
303
|
* @returns 返回一个新的函数,这个函数在调用时会尝试从缓存中获取结果,如果缓存不存在或已过期,就会调用原函数并缓存其结果。
|
|
304
304
|
*/
|
|
305
305
|
static memoize<T extends (...args: any[]) => any>(fn: T, expiry?: number): T extends (...args: infer In) => any ? AllExtends<In, JToken> extends true ? T : never : never;
|
|
306
|
+
/**将hh:mm:ss,ms格式转换为毫秒 */
|
|
307
|
+
static parseSrtTime(time: string): number;
|
|
308
|
+
/**将毫秒转换为hh:mm:ss,ms格式 */
|
|
309
|
+
static formatSrtTime(milliseconds: number): string;
|
|
310
|
+
/**解析srt文本为SrtSegment[] */
|
|
311
|
+
static parseSrt(srtText: string): SrtSegment[];
|
|
312
|
+
/**转换json为srt文本 */
|
|
313
|
+
static createSrt(segments: SrtSegment[]): string;
|
|
306
314
|
}
|
|
307
315
|
export {};
|
package/dist/UtilFunctions.js
CHANGED
|
@@ -724,6 +724,45 @@ class UtilFunc {
|
|
|
724
724
|
return result;
|
|
725
725
|
});
|
|
726
726
|
}
|
|
727
|
+
/**将hh:mm:ss,ms格式转换为毫秒 */
|
|
728
|
+
static parseSrtTime(time) {
|
|
729
|
+
const [hours, minutes, seconds] = time.split(':');
|
|
730
|
+
const [secs, milliseconds] = seconds.split(',');
|
|
731
|
+
return parseInt(hours) * 3600000 + parseInt(minutes) * 60000 + parseInt(secs) * 1000 + parseInt(milliseconds);
|
|
732
|
+
}
|
|
733
|
+
/**将毫秒转换为hh:mm:ss,ms格式 */
|
|
734
|
+
static formatSrtTime(milliseconds) {
|
|
735
|
+
const hours = Math.floor(milliseconds / 3_600_000);
|
|
736
|
+
const minutes = Math.floor((milliseconds % 3_600_000) / 60_000);
|
|
737
|
+
const seconds = Math.floor((milliseconds % 60_000) / 1000);
|
|
738
|
+
const ms = Math.floor(milliseconds % 1000);
|
|
739
|
+
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')},${String(ms).padStart(3, '0')}`;
|
|
740
|
+
}
|
|
741
|
+
/**解析srt文本为SrtSegment[] */
|
|
742
|
+
static parseSrt(srtText) {
|
|
743
|
+
const segments = [];
|
|
744
|
+
const srtLines = srtText.split('\n');
|
|
745
|
+
let index = 0;
|
|
746
|
+
while (index < srtLines.length) {
|
|
747
|
+
const id = srtLines[index++].trim();
|
|
748
|
+
if (!id)
|
|
749
|
+
continue;
|
|
750
|
+
const timeRange = srtLines[index++].trim();
|
|
751
|
+
const [start, end] = timeRange.split(' --> ').map(time => UtilFunc.parseSrtTime(time));
|
|
752
|
+
let text = '';
|
|
753
|
+
while (index < srtLines.length && srtLines[index].trim())
|
|
754
|
+
text += `${srtLines[index++]}\n`;
|
|
755
|
+
index++;
|
|
756
|
+
segments.push({ start, end, text: text.trim() });
|
|
757
|
+
}
|
|
758
|
+
return segments;
|
|
759
|
+
}
|
|
760
|
+
/**转换json为srt文本 */
|
|
761
|
+
static createSrt(segments) {
|
|
762
|
+
return segments.map((segment, index) => {
|
|
763
|
+
return `${index + 1}\n${UtilFunc.formatSrtTime(segment.start)} --> ${UtilFunc.formatSrtTime(segment.end)}\n${segment.text}\n`;
|
|
764
|
+
}).join('\n');
|
|
765
|
+
}
|
|
727
766
|
}
|
|
728
767
|
exports.UtilFunc = UtilFunc;
|
|
729
768
|
__decorate([
|
package/dist/UtilInterfaces.d.ts
CHANGED
|
@@ -166,4 +166,13 @@ export type CmtTuple<T extends {
|
|
|
166
166
|
}, Tuple extends unknown[] = []> = unknown extends T[Tuple['length']] ? T & Tuple : CmtTuple<T, [...Tuple, T[Tuple['length']]]>;
|
|
167
167
|
/**非严格模式下将会判断为false的值, 不包含NaN */
|
|
168
168
|
export type Flasy = false | 0 | -0 | "" | null | undefined;
|
|
169
|
+
/**srt段 */
|
|
170
|
+
export type SrtSegment = {
|
|
171
|
+
/**开始时间, 以毫秒为单位 */
|
|
172
|
+
start: number;
|
|
173
|
+
/**结束时间, 以毫秒为单位 */
|
|
174
|
+
end: number;
|
|
175
|
+
/**字幕文本 */
|
|
176
|
+
text: string;
|
|
177
|
+
};
|
|
169
178
|
export {};
|
package/package.json
CHANGED
package/src/UtilFfmpegTools.ts
CHANGED
|
@@ -4,6 +4,7 @@ import path from "pathe";
|
|
|
4
4
|
import * as fs from "fs";
|
|
5
5
|
import { SLogger } from "@/src/UtilLogger";
|
|
6
6
|
import { Stream } from "./UtilClass";
|
|
7
|
+
import { throwError } from "./QuickFunction";
|
|
7
8
|
|
|
8
9
|
/**输入输出路径映射
|
|
9
10
|
* 输入路径:输入路径
|
|
@@ -53,14 +54,28 @@ class SFfmpegTool {
|
|
|
53
54
|
outputOggPath: string,
|
|
54
55
|
quality: number = 10
|
|
55
56
|
): Promise<boolean> {
|
|
56
|
-
|
|
57
|
+
const wavPath = path.join(
|
|
57
58
|
path.dirname(inputFlacFile),
|
|
58
59
|
`tmp_${path.basename(inputFlacFile, ".flac")}.wav`
|
|
59
60
|
);
|
|
61
|
+
await SFfmpegTool.flac2wav(inputFlacFile, wavPath);
|
|
62
|
+
await SFfmpegTool.wav2ogg(wavPath, outputOggPath, quality);
|
|
63
|
+
await fs.promises.unlink(wavPath);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**flac转wav
|
|
68
|
+
* @param inputFlacFile - 输入flac文件路径
|
|
69
|
+
* @param outputWavPath - 输出wav文件路径
|
|
70
|
+
*/
|
|
71
|
+
static async flac2wav(
|
|
72
|
+
inputFlacFile: string,
|
|
73
|
+
outputWavPath: string
|
|
74
|
+
): Promise<boolean> {
|
|
60
75
|
await new Promise((resolve, reject) => {
|
|
61
76
|
const ins = fluentFfmpeg(inputFlacFile)
|
|
62
77
|
.audioCodec("pcm_s16le")
|
|
63
|
-
.save(
|
|
78
|
+
.save(outputWavPath)
|
|
64
79
|
.on("end", () => {
|
|
65
80
|
resolve(true);
|
|
66
81
|
ins.kill('SIGTERM');
|
|
@@ -70,15 +85,10 @@ class SFfmpegTool {
|
|
|
70
85
|
ins.kill('SIGTERM');
|
|
71
86
|
});
|
|
72
87
|
});
|
|
73
|
-
await SFfmpegTool.wav2ogg(wavPath, outputOggPath, quality);
|
|
74
|
-
await new Promise((resolve, reject) => {
|
|
75
|
-
fs.unlink(wavPath, function (err) {
|
|
76
|
-
if (err) SLogger.error("SFfmpegTool.flac2ogg unlink 错误",err);
|
|
77
|
-
resolve(null);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
88
|
return true;
|
|
81
89
|
}
|
|
90
|
+
|
|
91
|
+
|
|
82
92
|
/**wav转ogg
|
|
83
93
|
* @param inputWavPath - 输入wav文件路径
|
|
84
94
|
* @param outputOggPath - 输出ogg文件路径
|
|
@@ -188,6 +198,18 @@ class SFfmpegTool {
|
|
|
188
198
|
});
|
|
189
199
|
}
|
|
190
200
|
|
|
201
|
+
/**检查WAV文件是否为单声道
|
|
202
|
+
* @param filePath - 要检查的WAV文件路径
|
|
203
|
+
*/
|
|
204
|
+
static async isMono(filePath: string): Promise<boolean> {
|
|
205
|
+
const metadata = await SFfmpegTool.getAudioMetaData(filePath);
|
|
206
|
+
if (metadata==null) throwError("SFfmpegTool.isMono 获取音频元数据失败");
|
|
207
|
+
// 检查音频流的声道数
|
|
208
|
+
const audioStream = metadata!.streams.find(stream => stream.codec_type === 'audio');
|
|
209
|
+
if (audioStream==null) throwError("SFfmpegTool.isMono 未找到音频流");
|
|
210
|
+
return audioStream!.channels === 1;
|
|
211
|
+
}
|
|
212
|
+
|
|
191
213
|
//多线程处理
|
|
192
214
|
/**wav转ogg多线程
|
|
193
215
|
* @param ioMap - 输入输出路径映射
|
package/src/UtilFunctions.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as crypto from "crypto";
|
|
2
|
-
import { PRecord, AnyFunc, ExtractOutcome, IJData, JObject, JToken, Keyable, Literal, Matchable, MatchableFlag, Outcome, ReqStat, ReqVerifyFn, ProperSubsetCheck, UnionToIntersection, AnyRecord, AllExtends } from "@/src/UtilInterfaces";
|
|
2
|
+
import { PRecord, AnyFunc, ExtractOutcome, IJData, JObject, JToken, Keyable, Literal, Matchable, MatchableFlag, Outcome, ReqStat, ReqVerifyFn, ProperSubsetCheck, UnionToIntersection, AnyRecord, AllExtends, SrtSegment } from "@/src/UtilInterfaces";
|
|
3
3
|
import * as cp from "child_process";
|
|
4
4
|
import { LogLevel, SLogger } from "@/src/UtilLogger";
|
|
5
5
|
import { Completed, Failed, FailedLike, None, StatusSymbol, Success, SuccessLike, Terminated, Timeout } from "./UtilSymbol";
|
|
@@ -855,4 +855,51 @@ static memoize<T extends (...args:any[])=>any> (fn: T, expiry = Infinity):
|
|
|
855
855
|
}
|
|
856
856
|
|
|
857
857
|
|
|
858
|
+
|
|
859
|
+
/**将hh:mm:ss,ms格式转换为毫秒 */
|
|
860
|
+
static parseSrtTime(time: string): number {
|
|
861
|
+
const [hours, minutes, seconds] = time.split(':');
|
|
862
|
+
const [secs, milliseconds] = seconds.split(',');
|
|
863
|
+
|
|
864
|
+
return parseInt(hours) * 3600000 + parseInt(minutes) * 60000 + parseInt(secs) * 1000 + parseInt(milliseconds);
|
|
865
|
+
}
|
|
866
|
+
/**将毫秒转换为hh:mm:ss,ms格式 */
|
|
867
|
+
static formatSrtTime(milliseconds: number): string {
|
|
868
|
+
const hours = Math.floor(milliseconds / 3_600_000);
|
|
869
|
+
const minutes = Math.floor((milliseconds % 3_600_000) / 60_000);
|
|
870
|
+
const seconds = Math.floor((milliseconds % 60_000) / 1000);
|
|
871
|
+
const ms = Math.floor(milliseconds % 1000);
|
|
872
|
+
|
|
873
|
+
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')},${String(ms).padStart(3, '0')}`;
|
|
874
|
+
}
|
|
875
|
+
/**解析srt文本为SrtSegment[] */
|
|
876
|
+
static parseSrt(srtText: string): SrtSegment[] {
|
|
877
|
+
const segments: SrtSegment[] = [];
|
|
878
|
+
const srtLines = srtText.split('\n');
|
|
879
|
+
|
|
880
|
+
let index = 0;
|
|
881
|
+
while (index < srtLines.length) {
|
|
882
|
+
const id = srtLines[index++].trim();
|
|
883
|
+
if (!id) continue;
|
|
884
|
+
|
|
885
|
+
const timeRange = srtLines[index++].trim();
|
|
886
|
+
const [start, end] = timeRange.split(' --> ').map(time => UtilFunc.parseSrtTime(time));
|
|
887
|
+
|
|
888
|
+
let text = '';
|
|
889
|
+
while (index < srtLines.length && srtLines[index].trim())
|
|
890
|
+
text += `${srtLines[index++]}\n`;
|
|
891
|
+
index++;
|
|
892
|
+
|
|
893
|
+
segments.push({ start, end, text: text.trim() });
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return segments;
|
|
897
|
+
}
|
|
898
|
+
/**转换json为srt文本 */
|
|
899
|
+
static createSrt(segments: SrtSegment[]): string {
|
|
900
|
+
return segments.map((segment, index) => {
|
|
901
|
+
return `${index + 1}\n${UtilFunc.formatSrtTime(segment.start)} --> ${UtilFunc.formatSrtTime(segment.end)}\n${segment.text}\n`;
|
|
902
|
+
}).join('\n');
|
|
903
|
+
}
|
|
904
|
+
|
|
858
905
|
}
|
package/src/UtilInterfaces.ts
CHANGED
|
@@ -250,4 +250,15 @@ export type CmtTuple<T extends {[K:number]:unknown},Tuple extends unknown[]=[]>
|
|
|
250
250
|
: CmtTuple<T,[...Tuple,T[Tuple['length']]]>;
|
|
251
251
|
|
|
252
252
|
/**非严格模式下将会判断为false的值, 不包含NaN */
|
|
253
|
-
export type Flasy = false | 0 | -0 | "" | null | undefined;
|
|
253
|
+
export type Flasy = false | 0 | -0 | "" | null | undefined;
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
/**srt段 */
|
|
257
|
+
export type SrtSegment = {
|
|
258
|
+
/**开始时间, 以毫秒为单位 */
|
|
259
|
+
start: number;
|
|
260
|
+
/**结束时间, 以毫秒为单位 */
|
|
261
|
+
end : number;
|
|
262
|
+
/**字幕文本 */
|
|
263
|
+
text : string;
|
|
264
|
+
};
|