@zwa73/utils 1.0.9 → 1.0.10
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/UtilClass.d.ts +4 -2
- package/dist/UtilClass.js +24 -2
- package/dist/UtilFfmpegTool.d.ts +7 -0
- package/dist/UtilFfmpegTool.js +26 -0
- package/dist/UtilFfmpegTools.d.ts +79 -0
- package/dist/UtilFfmpegTools.js +205 -0
- package/dist/UtilFunctions.d.ts +6 -0
- package/dist/UtilFunctions.js +26 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/input.wav +0 -0
- package/package.json +3 -1
- package/src/UtilClass.ts +25 -2
- package/src/UtilFfmpegTools.ts +253 -0
- package/src/UtilFunctions.ts +24 -0
- package/src/index.ts +2 -1
- package/test.js +8 -1
package/dist/UtilClass.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export declare class SList<T> {
|
|
|
30
30
|
* @param list 目标数组
|
|
31
31
|
* @returns 自身
|
|
32
32
|
*/
|
|
33
|
-
|
|
33
|
+
pushList(list: SList<T>): this;
|
|
34
34
|
/**截取从起始点到结束点之前的一段数组
|
|
35
35
|
* @param strat 开始点
|
|
36
36
|
* @param end 结束点
|
|
@@ -58,6 +58,8 @@ export declare class SList<T> {
|
|
|
58
58
|
contains(value: T): boolean;
|
|
59
59
|
/**获取迭代器 */
|
|
60
60
|
iterator(): SIterator<T>;
|
|
61
|
+
/**平分数组 */
|
|
62
|
+
divide(count: number): SList<SList<T>>;
|
|
61
63
|
/**从index开始删除count个元素
|
|
62
64
|
* 改变自身
|
|
63
65
|
* @param index 起始点
|
|
@@ -223,7 +225,7 @@ export declare class SEntry<K, V> {
|
|
|
223
225
|
}
|
|
224
226
|
export declare class SHashMap<K, V> {
|
|
225
227
|
private _map;
|
|
226
|
-
constructor();
|
|
228
|
+
constructor(map?: Map<K, V>);
|
|
227
229
|
/**添加一个键值对
|
|
228
230
|
* @param entry 键值对
|
|
229
231
|
* @returns 自身
|
package/dist/UtilClass.js
CHANGED
|
@@ -51,7 +51,7 @@ class SList {
|
|
|
51
51
|
* @param list 目标数组
|
|
52
52
|
* @returns 自身
|
|
53
53
|
*/
|
|
54
|
-
|
|
54
|
+
pushList(list) {
|
|
55
55
|
this._arr = this._arr.concat(list._arr);
|
|
56
56
|
return this;
|
|
57
57
|
}
|
|
@@ -97,6 +97,19 @@ class SList {
|
|
|
97
97
|
iterator() {
|
|
98
98
|
return new SIterator(this);
|
|
99
99
|
}
|
|
100
|
+
/**平分数组 */
|
|
101
|
+
divide(count) {
|
|
102
|
+
let result = new SList();
|
|
103
|
+
let chunkSize = Math.ceil(this.size() / count);
|
|
104
|
+
for (let i = 0; i < count; i++) {
|
|
105
|
+
let start = i * chunkSize;
|
|
106
|
+
let end = (i + 1) * chunkSize;
|
|
107
|
+
if (end > this.size())
|
|
108
|
+
end = this.size();
|
|
109
|
+
result.push(this.slice(start, end));
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
100
113
|
/**从index开始删除count个元素
|
|
101
114
|
* 改变自身
|
|
102
115
|
* @param index 起始点
|
|
@@ -403,7 +416,16 @@ class SEntry {
|
|
|
403
416
|
exports.SEntry = SEntry;
|
|
404
417
|
class SHashMap {
|
|
405
418
|
_map = new Map();
|
|
406
|
-
constructor() {
|
|
419
|
+
constructor(map) {
|
|
420
|
+
if (map == null)
|
|
421
|
+
return;
|
|
422
|
+
let keys = map.keys();
|
|
423
|
+
for (let key of keys) {
|
|
424
|
+
let value = map.get(key);
|
|
425
|
+
if (value != null)
|
|
426
|
+
this.put(key, value);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
407
429
|
/**添加一个键值对
|
|
408
430
|
* @param entry 键值对
|
|
409
431
|
* @returns 自身
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SFfmpegTool = void 0;
|
|
4
|
+
const fluentFfmpeg = require("fluent-ffmpeg");
|
|
5
|
+
class SFfmpegTool {
|
|
6
|
+
static setFfmpegPath(ffmpegPath) {
|
|
7
|
+
fluentFfmpeg.setFfmpegPath(ffmpegPath);
|
|
8
|
+
}
|
|
9
|
+
static async getAudioData(inputWavPath) {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
fluentFfmpeg.ffprobe(inputWavPath, function (err, metadata) {
|
|
12
|
+
if (err) {
|
|
13
|
+
console.log(err);
|
|
14
|
+
resolve(null);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
//console.log(inputWavPath+" metadata:");
|
|
18
|
+
//console.log(metadata);
|
|
19
|
+
resolve(metadata);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.SFfmpegTool = SFfmpegTool;
|
|
26
|
+
exports.default = SFfmpegTool;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { FfprobeData } from "fluent-ffmpeg";
|
|
2
|
+
/**输入输出路径映射
|
|
3
|
+
* 输入路径:输入路径
|
|
4
|
+
*/
|
|
5
|
+
export type IOMap = {
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
};
|
|
8
|
+
/**ffmpeg工具类
|
|
9
|
+
*/
|
|
10
|
+
declare class SFfmpegTool {
|
|
11
|
+
/**设置ffmpeg路径
|
|
12
|
+
*/
|
|
13
|
+
static setFfmpegPath(ffmpegPath: string): void;
|
|
14
|
+
/**获取音频文件的元数据
|
|
15
|
+
* @param {string} inputWavPath - 输入音频文件路径
|
|
16
|
+
* @returns {Promise<FfprobeData|null>} - 返回音频文件的元数据
|
|
17
|
+
*/
|
|
18
|
+
static getAudioMetaData(inputWavPath: string): Promise<FfprobeData | null>;
|
|
19
|
+
/**flac转ogg
|
|
20
|
+
* @param {string} inputFlacFile - 输入flac文件路径
|
|
21
|
+
* @param {string} outputOggPath - 输出ogg文件路径
|
|
22
|
+
* @param {number} quality - 质量
|
|
23
|
+
*/
|
|
24
|
+
static flac2ogg(inputFlacFile: string, outputOggPath: string, quality?: number): Promise<boolean>;
|
|
25
|
+
/**wav转ogg
|
|
26
|
+
* @param {string} inputWavPath - 输入wav文件路径
|
|
27
|
+
* @param {string} outputOggPath - 输出ogg文件路径
|
|
28
|
+
* @param {number} quality - 质量
|
|
29
|
+
*/
|
|
30
|
+
static wav2ogg(inputWavPath: string, outputOggPath: string, quality?: number): Promise<boolean>;
|
|
31
|
+
/**剪切音频
|
|
32
|
+
* @param {string} audioPath - 输入音频文件路径
|
|
33
|
+
* @param {string} outPath - 输出音频文件路径
|
|
34
|
+
* @param {number} start - 开始时间
|
|
35
|
+
* @param {number} time - 时长
|
|
36
|
+
*/
|
|
37
|
+
static cutAudio(audioPath: string, outPath: string, start: number, time: number): Promise<boolean>;
|
|
38
|
+
/**删除首尾静音
|
|
39
|
+
* @param {string} inputWavPath - 输入wav文件路径
|
|
40
|
+
* @param {string} outputWavPath - 输出wav文件路径
|
|
41
|
+
* @param {number} threshold - 静音阈值/dB
|
|
42
|
+
* @param {number} silence - 保留静音时长
|
|
43
|
+
*/
|
|
44
|
+
static trimSilence(inputWavPath: string, outputWavPath: string, threshold?: number, silence?: number): Promise<boolean>;
|
|
45
|
+
/**wav转ogg多线程
|
|
46
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
47
|
+
* @param {number} quality - 质量
|
|
48
|
+
* @param {number} cpCount - 并发数
|
|
49
|
+
*/
|
|
50
|
+
static wav2oggMP(iomap: IOMap, quality?: number, cpCount?: number): Promise<void>;
|
|
51
|
+
/**wav转ogg子线程
|
|
52
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
53
|
+
* @param {number} quality - 质量
|
|
54
|
+
*/
|
|
55
|
+
private static wav2oggCP;
|
|
56
|
+
/**flac转ogg多线程
|
|
57
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
58
|
+
* @param {number} quality - 质量
|
|
59
|
+
* @param {number} cpCount - 并发数
|
|
60
|
+
*/
|
|
61
|
+
static flac2oggMP(iomap: IOMap, quality?: number, cpCount?: number): Promise<void>;
|
|
62
|
+
/**flac转ogg子线程
|
|
63
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
64
|
+
* @param {number} quality - 质量
|
|
65
|
+
* @param {number} cpCount - 并发数
|
|
66
|
+
*/
|
|
67
|
+
private static flac2oggCP;
|
|
68
|
+
/**删除静音多线程
|
|
69
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
70
|
+
* @param {number} threshold - 静音阈值/dB
|
|
71
|
+
* @param {number} silence - 保留静音时长
|
|
72
|
+
*/
|
|
73
|
+
static trimSilenceMP(iomap: IOMap, threshold?: number, silence?: number, cpCount?: number): Promise<void>;
|
|
74
|
+
/**删除静音子线程
|
|
75
|
+
*/
|
|
76
|
+
private static trimSilenceCP;
|
|
77
|
+
}
|
|
78
|
+
export default SFfmpegTool;
|
|
79
|
+
export { SFfmpegTool };
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SFfmpegTool = void 0;
|
|
4
|
+
const fluentFfmpeg = require("fluent-ffmpeg");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
/**ffmpeg工具类
|
|
8
|
+
*/
|
|
9
|
+
class SFfmpegTool {
|
|
10
|
+
/**设置ffmpeg路径
|
|
11
|
+
*/
|
|
12
|
+
static setFfmpegPath(ffmpegPath) {
|
|
13
|
+
fluentFfmpeg.setFfmpegPath(ffmpegPath);
|
|
14
|
+
}
|
|
15
|
+
/**获取音频文件的元数据
|
|
16
|
+
* @param {string} inputWavPath - 输入音频文件路径
|
|
17
|
+
* @returns {Promise<FfprobeData|null>} - 返回音频文件的元数据
|
|
18
|
+
*/
|
|
19
|
+
static async getAudioMetaData(inputWavPath) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
fluentFfmpeg.ffprobe(inputWavPath, function (err, metadata) {
|
|
22
|
+
if (err) {
|
|
23
|
+
console.log(err);
|
|
24
|
+
resolve(null);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
//console.log(inputWavPath+" metadata:");
|
|
28
|
+
//console.log(metadata);
|
|
29
|
+
resolve(metadata);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**flac转ogg
|
|
35
|
+
* @param {string} inputFlacFile - 输入flac文件路径
|
|
36
|
+
* @param {string} outputOggPath - 输出ogg文件路径
|
|
37
|
+
* @param {number} quality - 质量
|
|
38
|
+
*/
|
|
39
|
+
static async flac2ogg(inputFlacFile, outputOggPath, quality = 10) {
|
|
40
|
+
let wavPath = path.join(path.dirname(inputFlacFile), `tmp_${path.basename(inputFlacFile, ".flac")}.wav`);
|
|
41
|
+
await new Promise((resolve, reject) => {
|
|
42
|
+
fluentFfmpeg(inputFlacFile)
|
|
43
|
+
.audioCodec("pcm_s16le")
|
|
44
|
+
.save(wavPath)
|
|
45
|
+
.on("end", function () {
|
|
46
|
+
resolve(null);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
await SFfmpegTool.wav2ogg(wavPath, outputOggPath, quality);
|
|
50
|
+
await new Promise((resolve, reject) => {
|
|
51
|
+
fs.unlink(wavPath, function (err) {
|
|
52
|
+
if (err)
|
|
53
|
+
console.error(err);
|
|
54
|
+
resolve(null);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
/**wav转ogg
|
|
60
|
+
* @param {string} inputWavPath - 输入wav文件路径
|
|
61
|
+
* @param {string} outputOggPath - 输出ogg文件路径
|
|
62
|
+
* @param {number} quality - 质量
|
|
63
|
+
*/
|
|
64
|
+
static async wav2ogg(inputWavPath, outputOggPath, quality = 10) {
|
|
65
|
+
//.audioQuality(10)
|
|
66
|
+
//.audioBitrate("192k")
|
|
67
|
+
//.audioChannels(channels)
|
|
68
|
+
//.noMetadata()
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
fluentFfmpeg(inputWavPath)
|
|
71
|
+
.audioQuality(quality)
|
|
72
|
+
.audioCodec("libvorbis")
|
|
73
|
+
.save(outputOggPath)
|
|
74
|
+
.on("end", () => resolve(true))
|
|
75
|
+
.on("error", (err) => reject(err));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**剪切音频
|
|
79
|
+
* @param {string} audioPath - 输入音频文件路径
|
|
80
|
+
* @param {string} outPath - 输出音频文件路径
|
|
81
|
+
* @param {number} start - 开始时间
|
|
82
|
+
* @param {number} time - 时长
|
|
83
|
+
*/
|
|
84
|
+
static async cutAudio(audioPath, outPath, start, time) {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
fluentFfmpeg(audioPath)
|
|
87
|
+
.setStartTime(start)
|
|
88
|
+
.setDuration(time)
|
|
89
|
+
.save(outPath)
|
|
90
|
+
.on("end", () => resolve(true))
|
|
91
|
+
.on("error", (err) => reject(err));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**删除首尾静音
|
|
95
|
+
* @param {string} inputWavPath - 输入wav文件路径
|
|
96
|
+
* @param {string} outputWavPath - 输出wav文件路径
|
|
97
|
+
* @param {number} threshold - 静音阈值/dB
|
|
98
|
+
* @param {number} silence - 保留静音时长
|
|
99
|
+
*/
|
|
100
|
+
static async trimSilence(inputWavPath, outputWavPath, threshold = -50, silence = 0.2) {
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
fluentFfmpeg(inputWavPath)
|
|
103
|
+
.audioFilters(`silenceremove=start_periods=1:start_threshold=${threshold}dB:start_silence=${silence}`)
|
|
104
|
+
.audioFilters("areverse")
|
|
105
|
+
.audioFilters(`silenceremove=start_periods=1:start_threshold=${threshold}dB:start_silence=${silence}`)
|
|
106
|
+
.audioFilters("areverse")
|
|
107
|
+
.save(outputWavPath)
|
|
108
|
+
.on("end", () => resolve(true))
|
|
109
|
+
.on("error", (err) => reject(err));
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
//多线程处理
|
|
113
|
+
/**wav转ogg多线程
|
|
114
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
115
|
+
* @param {number} quality - 质量
|
|
116
|
+
* @param {number} cpCount - 并发数
|
|
117
|
+
*/
|
|
118
|
+
static async wav2oggMP(iomap, quality = 10, cpCount = 16) {
|
|
119
|
+
let cpList = MPClip(iomap, cpCount);
|
|
120
|
+
for (let cpMap of cpList)
|
|
121
|
+
SFfmpegTool.wav2oggCP(cpMap, quality);
|
|
122
|
+
}
|
|
123
|
+
/**wav转ogg子线程
|
|
124
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
125
|
+
* @param {number} quality - 质量
|
|
126
|
+
*/
|
|
127
|
+
static async wav2oggCP(iomap, quality = 10) {
|
|
128
|
+
for (let inPath in iomap) {
|
|
129
|
+
let outpath = iomap[inPath];
|
|
130
|
+
console.log("正在处理:" + outpath);
|
|
131
|
+
await SFfmpegTool.wav2ogg(inPath, outpath, quality);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**flac转ogg多线程
|
|
135
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
136
|
+
* @param {number} quality - 质量
|
|
137
|
+
* @param {number} cpCount - 并发数
|
|
138
|
+
*/
|
|
139
|
+
static async flac2oggMP(iomap, quality = 10, cpCount = 16) {
|
|
140
|
+
let cpList = MPClip(iomap, cpCount);
|
|
141
|
+
for (let cpMap of cpList)
|
|
142
|
+
SFfmpegTool.flac2oggCP(cpMap, quality);
|
|
143
|
+
}
|
|
144
|
+
/**flac转ogg子线程
|
|
145
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
146
|
+
* @param {number} quality - 质量
|
|
147
|
+
* @param {number} cpCount - 并发数
|
|
148
|
+
*/
|
|
149
|
+
static async flac2oggCP(iomap, quality = 10) {
|
|
150
|
+
for (let inPath in iomap) {
|
|
151
|
+
let outpath = iomap[inPath];
|
|
152
|
+
console.log("正在处理:" + outpath);
|
|
153
|
+
await SFfmpegTool.flac2ogg(inPath, outpath, quality);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**删除静音多线程
|
|
157
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
158
|
+
* @param {number} threshold - 静音阈值/dB
|
|
159
|
+
* @param {number} silence - 保留静音时长
|
|
160
|
+
*/
|
|
161
|
+
static async trimSilenceMP(iomap, threshold = -50, silence = 0.2, cpCount = 16) {
|
|
162
|
+
let cpList = MPClip(iomap, cpCount);
|
|
163
|
+
for (let cpMap of cpList)
|
|
164
|
+
SFfmpegTool.trimSilenceCP(cpMap, threshold, silence);
|
|
165
|
+
}
|
|
166
|
+
/**删除静音子线程
|
|
167
|
+
*/
|
|
168
|
+
static async trimSilenceCP(iomap, threshold = -50, silence = 0.2) {
|
|
169
|
+
for (let inPath in iomap) {
|
|
170
|
+
let outpath = iomap[inPath];
|
|
171
|
+
console.log("正在处理:" + outpath);
|
|
172
|
+
await SFfmpegTool.trimSilence(inPath, outpath, threshold, silence);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
exports.SFfmpegTool = SFfmpegTool;
|
|
177
|
+
/**多线程任务分割器
|
|
178
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
179
|
+
* @param {number} cpCount - 并发数
|
|
180
|
+
*/
|
|
181
|
+
function MPClip(iomap, cpCount = 16) {
|
|
182
|
+
let cpList = [];
|
|
183
|
+
for (let i = 0; i < cpCount; i++)
|
|
184
|
+
cpList.push({});
|
|
185
|
+
let cpCounter = 0;
|
|
186
|
+
for (let key in iomap) {
|
|
187
|
+
let cpIndex = cpCounter % cpCount;
|
|
188
|
+
cpCounter++;
|
|
189
|
+
cpList[cpIndex][key] = iomap[key];
|
|
190
|
+
}
|
|
191
|
+
return cpList;
|
|
192
|
+
}
|
|
193
|
+
/**ffmpeg流
|
|
194
|
+
*/
|
|
195
|
+
class SFfmpegStream {
|
|
196
|
+
iomap;
|
|
197
|
+
cmdList = [];
|
|
198
|
+
constructor(iomap) {
|
|
199
|
+
this.iomap = iomap;
|
|
200
|
+
}
|
|
201
|
+
async save() { }
|
|
202
|
+
//执行函数
|
|
203
|
+
async cmdAreverse() { }
|
|
204
|
+
}
|
|
205
|
+
exports.default = SFfmpegTool;
|
package/dist/UtilFunctions.d.ts
CHANGED
|
@@ -36,3 +36,9 @@ export declare function isSafeNumber(num: number): boolean;
|
|
|
36
36
|
* @returns {Promise<boolean>}
|
|
37
37
|
*/
|
|
38
38
|
export declare function sleep(timeMs: number): Promise<boolean>;
|
|
39
|
+
/**搜索路径符合正则表达式的文件
|
|
40
|
+
* @param folder - 文件夹路径
|
|
41
|
+
* @param traitRegex - 正则表达式
|
|
42
|
+
* @returns {Record<string, string>} 文件名与路径的映射
|
|
43
|
+
*/
|
|
44
|
+
export declare function fileSearch(folder: string, traitRegex: string): Record<string, string>;
|
package/dist/UtilFunctions.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sleep = exports.isSafeNumber = exports.deepClone = exports.genUUID = exports.writeJSONFile = exports.loadJSONFile = exports.initField = exports.getTime = void 0;
|
|
3
|
+
exports.fileSearch = exports.sleep = exports.isSafeNumber = exports.deepClone = exports.genUUID = exports.writeJSONFile = exports.loadJSONFile = exports.initField = exports.getTime = void 0;
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const crypto = require("crypto");
|
|
6
6
|
const path = require("path");
|
|
@@ -116,3 +116,28 @@ function sleep(timeMs) {
|
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
118
|
exports.sleep = sleep;
|
|
119
|
+
/**搜索路径符合正则表达式的文件
|
|
120
|
+
* @param folder - 文件夹路径
|
|
121
|
+
* @param traitRegex - 正则表达式
|
|
122
|
+
* @returns {Record<string, string>} 文件名与路径的映射
|
|
123
|
+
*/
|
|
124
|
+
function fileSearch(folder, traitRegex) {
|
|
125
|
+
let outMap = {};
|
|
126
|
+
let subFiles = fs.readdirSync(folder);
|
|
127
|
+
let regex = new RegExp(traitRegex);
|
|
128
|
+
for (let subFile of subFiles) {
|
|
129
|
+
let subFilePath = path.join(folder, subFile);
|
|
130
|
+
let stat = fs.lstatSync(subFilePath);
|
|
131
|
+
//判断是否是文件夹,递归调用
|
|
132
|
+
if (stat.isDirectory()) {
|
|
133
|
+
let subMap = fileSearch(path.join(subFilePath, "/"), traitRegex);
|
|
134
|
+
for (let key in subMap)
|
|
135
|
+
outMap[key] = subMap[key];
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (regex.test(subFilePath))
|
|
139
|
+
outMap[subFile] = subFilePath;
|
|
140
|
+
}
|
|
141
|
+
return outMap;
|
|
142
|
+
}
|
|
143
|
+
exports.fileSearch = fileSearch;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/input.wav
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zwa73/utils",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "my utils",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"esm": "^3.2.25",
|
|
16
16
|
"esm-resolve": "^1.0.8",
|
|
17
|
+
"fluent-ffmpeg": "^2.1.2",
|
|
17
18
|
"html-entities": "^2.3.3",
|
|
18
19
|
"http-proxy-agent": "^5.0.0",
|
|
19
20
|
"https-proxy-agent": "^5.0.1",
|
|
20
21
|
"tiktoken": "^1.0.7"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
24
|
+
"@types/fluent-ffmpeg": "^2.1.21",
|
|
23
25
|
"@types/node": "^18.16.3"
|
|
24
26
|
}
|
|
25
27
|
}
|
package/src/UtilClass.ts
CHANGED
|
@@ -55,7 +55,7 @@ export class SList<T> {
|
|
|
55
55
|
* @param list 目标数组
|
|
56
56
|
* @returns 自身
|
|
57
57
|
*/
|
|
58
|
-
|
|
58
|
+
pushList(list: SList<T>) {
|
|
59
59
|
this._arr = this._arr.concat(list._arr);
|
|
60
60
|
return this;
|
|
61
61
|
}
|
|
@@ -109,6 +109,20 @@ export class SList<T> {
|
|
|
109
109
|
return new SIterator<T>(this);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**平分数组 */
|
|
113
|
+
divide(count:number):SList<SList<T>>{
|
|
114
|
+
let result = new SList<SList<T>>();
|
|
115
|
+
let chunkSize = Math.ceil(this.size()/count);
|
|
116
|
+
for(let i=0;i<count;i++){
|
|
117
|
+
let start = i*chunkSize;
|
|
118
|
+
let end = (i+1)*chunkSize;
|
|
119
|
+
if(end>this.size())
|
|
120
|
+
end = this.size();
|
|
121
|
+
result.push(this.slice(start,end));
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
112
126
|
/**从index开始删除count个元素
|
|
113
127
|
* 改变自身
|
|
114
128
|
* @param index 起始点
|
|
@@ -449,7 +463,16 @@ export class SEntry<K,V>{
|
|
|
449
463
|
}
|
|
450
464
|
export class SHashMap<K,V>{
|
|
451
465
|
private _map:Map<K,V> = new Map();
|
|
452
|
-
constructor(){
|
|
466
|
+
constructor(map?:Map<K,V>){
|
|
467
|
+
if(map==null)
|
|
468
|
+
return;
|
|
469
|
+
let keys = map.keys();
|
|
470
|
+
for(let key of keys){
|
|
471
|
+
let value = map.get(key);
|
|
472
|
+
if(value!=null)
|
|
473
|
+
this.put(key,value);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
453
476
|
/**添加一个键值对
|
|
454
477
|
* @param entry 键值对
|
|
455
478
|
* @returns 自身
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { FfmpegCommand, FfprobeData } from "fluent-ffmpeg";
|
|
2
|
+
import * as fluentFfmpeg from "fluent-ffmpeg";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
|
|
6
|
+
/**输入输出路径映射
|
|
7
|
+
* 输入路径:输入路径
|
|
8
|
+
*/
|
|
9
|
+
export type IOMap = { [key: string]: string };
|
|
10
|
+
|
|
11
|
+
/**ffmpeg工具类
|
|
12
|
+
*/
|
|
13
|
+
class SFfmpegTool {
|
|
14
|
+
/**设置ffmpeg路径
|
|
15
|
+
*/
|
|
16
|
+
static setFfmpegPath(ffmpegPath: string) {
|
|
17
|
+
fluentFfmpeg.setFfmpegPath(ffmpegPath);
|
|
18
|
+
}
|
|
19
|
+
/**获取音频文件的元数据
|
|
20
|
+
* @param {string} inputWavPath - 输入音频文件路径
|
|
21
|
+
* @returns {Promise<FfprobeData|null>} - 返回音频文件的元数据
|
|
22
|
+
*/
|
|
23
|
+
static async getAudioMetaData(
|
|
24
|
+
inputWavPath: string
|
|
25
|
+
): Promise<FfprobeData | null> {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
fluentFfmpeg.ffprobe(inputWavPath, function (err, metadata) {
|
|
28
|
+
if (err) {
|
|
29
|
+
console.log(err);
|
|
30
|
+
resolve(null);
|
|
31
|
+
} else {
|
|
32
|
+
//console.log(inputWavPath+" metadata:");
|
|
33
|
+
//console.log(metadata);
|
|
34
|
+
resolve(metadata);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**flac转ogg
|
|
40
|
+
* @param {string} inputFlacFile - 输入flac文件路径
|
|
41
|
+
* @param {string} outputOggPath - 输出ogg文件路径
|
|
42
|
+
* @param {number} quality - 质量
|
|
43
|
+
*/
|
|
44
|
+
static async flac2ogg(
|
|
45
|
+
inputFlacFile: string,
|
|
46
|
+
outputOggPath: string,
|
|
47
|
+
quality: number = 10
|
|
48
|
+
): Promise<boolean> {
|
|
49
|
+
let wavPath = path.join(
|
|
50
|
+
path.dirname(inputFlacFile),
|
|
51
|
+
`tmp_${path.basename(inputFlacFile, ".flac")}.wav`
|
|
52
|
+
);
|
|
53
|
+
await new Promise((resolve, reject) => {
|
|
54
|
+
fluentFfmpeg(inputFlacFile)
|
|
55
|
+
.audioCodec("pcm_s16le")
|
|
56
|
+
.save(wavPath)
|
|
57
|
+
.on("end", function () {
|
|
58
|
+
resolve(null);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
await SFfmpegTool.wav2ogg(wavPath, outputOggPath, quality);
|
|
62
|
+
await new Promise((resolve, reject) => {
|
|
63
|
+
fs.unlink(wavPath, function (err) {
|
|
64
|
+
if (err) console.error(err);
|
|
65
|
+
resolve(null);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
/**wav转ogg
|
|
71
|
+
* @param {string} inputWavPath - 输入wav文件路径
|
|
72
|
+
* @param {string} outputOggPath - 输出ogg文件路径
|
|
73
|
+
* @param {number} quality - 质量
|
|
74
|
+
*/
|
|
75
|
+
static async wav2ogg(
|
|
76
|
+
inputWavPath: string,
|
|
77
|
+
outputOggPath: string,
|
|
78
|
+
quality: number = 10
|
|
79
|
+
): Promise<boolean> {
|
|
80
|
+
//.audioQuality(10)
|
|
81
|
+
//.audioBitrate("192k")
|
|
82
|
+
//.audioChannels(channels)
|
|
83
|
+
//.noMetadata()
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
fluentFfmpeg(inputWavPath)
|
|
86
|
+
.audioQuality(quality)
|
|
87
|
+
.audioCodec("libvorbis")
|
|
88
|
+
.save(outputOggPath)
|
|
89
|
+
.on("end", () => resolve(true))
|
|
90
|
+
.on("error", (err) => reject(err));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**剪切音频
|
|
94
|
+
* @param {string} audioPath - 输入音频文件路径
|
|
95
|
+
* @param {string} outPath - 输出音频文件路径
|
|
96
|
+
* @param {number} start - 开始时间
|
|
97
|
+
* @param {number} time - 时长
|
|
98
|
+
*/
|
|
99
|
+
static async cutAudio(
|
|
100
|
+
audioPath: string,
|
|
101
|
+
outPath: string,
|
|
102
|
+
start: number,
|
|
103
|
+
time: number
|
|
104
|
+
): Promise<boolean> {
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
fluentFfmpeg(audioPath)
|
|
107
|
+
.setStartTime(start)
|
|
108
|
+
.setDuration(time)
|
|
109
|
+
.save(outPath)
|
|
110
|
+
.on("end", () => resolve(true))
|
|
111
|
+
.on("error", (err) => reject(err));
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**删除首尾静音
|
|
115
|
+
* @param {string} inputWavPath - 输入wav文件路径
|
|
116
|
+
* @param {string} outputWavPath - 输出wav文件路径
|
|
117
|
+
* @param {number} threshold - 静音阈值/dB
|
|
118
|
+
* @param {number} silence - 保留静音时长
|
|
119
|
+
*/
|
|
120
|
+
static async trimSilence(
|
|
121
|
+
inputWavPath: string,
|
|
122
|
+
outputWavPath: string,
|
|
123
|
+
threshold: number = -50,
|
|
124
|
+
silence: number = 0.2
|
|
125
|
+
): Promise<boolean> {
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
fluentFfmpeg(inputWavPath)
|
|
128
|
+
.audioFilters(
|
|
129
|
+
`silenceremove=start_periods=1:start_threshold=${threshold}dB:start_silence=${silence}`
|
|
130
|
+
)
|
|
131
|
+
.audioFilters("areverse")
|
|
132
|
+
.audioFilters(
|
|
133
|
+
`silenceremove=start_periods=1:start_threshold=${threshold}dB:start_silence=${silence}`
|
|
134
|
+
)
|
|
135
|
+
.audioFilters("areverse")
|
|
136
|
+
.save(outputWavPath)
|
|
137
|
+
.on("end", () => resolve(true))
|
|
138
|
+
.on("error", (err) => reject(err));
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
//多线程处理
|
|
142
|
+
/**wav转ogg多线程
|
|
143
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
144
|
+
* @param {number} quality - 质量
|
|
145
|
+
* @param {number} cpCount - 并发数
|
|
146
|
+
*/
|
|
147
|
+
static async wav2oggMP(iomap: IOMap, quality = 10, cpCount = 16) {
|
|
148
|
+
let cpList = MPClip(iomap, cpCount);
|
|
149
|
+
for (let cpMap of cpList) SFfmpegTool.wav2oggCP(cpMap, quality);
|
|
150
|
+
}
|
|
151
|
+
/**wav转ogg子线程
|
|
152
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
153
|
+
* @param {number} quality - 质量
|
|
154
|
+
*/
|
|
155
|
+
private static async wav2oggCP(iomap: IOMap, quality: number = 10) {
|
|
156
|
+
for (let inPath in iomap) {
|
|
157
|
+
let outpath = iomap[inPath];
|
|
158
|
+
console.log("正在处理:" + outpath);
|
|
159
|
+
await SFfmpegTool.wav2ogg(inPath, outpath, quality);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**flac转ogg多线程
|
|
163
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
164
|
+
* @param {number} quality - 质量
|
|
165
|
+
* @param {number} cpCount - 并发数
|
|
166
|
+
*/
|
|
167
|
+
static async flac2oggMP(
|
|
168
|
+
iomap: IOMap,
|
|
169
|
+
quality: number = 10,
|
|
170
|
+
cpCount: number = 16
|
|
171
|
+
) {
|
|
172
|
+
let cpList = MPClip(iomap, cpCount);
|
|
173
|
+
for (let cpMap of cpList) SFfmpegTool.flac2oggCP(cpMap, quality);
|
|
174
|
+
}
|
|
175
|
+
/**flac转ogg子线程
|
|
176
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
177
|
+
* @param {number} quality - 质量
|
|
178
|
+
* @param {number} cpCount - 并发数
|
|
179
|
+
*/
|
|
180
|
+
private static async flac2oggCP(iomap: IOMap, quality: number = 10) {
|
|
181
|
+
for (let inPath in iomap) {
|
|
182
|
+
let outpath = iomap[inPath];
|
|
183
|
+
console.log("正在处理:" + outpath);
|
|
184
|
+
await SFfmpegTool.flac2ogg(inPath, outpath, quality);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**删除静音多线程
|
|
188
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
189
|
+
* @param {number} threshold - 静音阈值/dB
|
|
190
|
+
* @param {number} silence - 保留静音时长
|
|
191
|
+
*/
|
|
192
|
+
static async trimSilenceMP(
|
|
193
|
+
iomap: IOMap,
|
|
194
|
+
threshold: number = -50,
|
|
195
|
+
silence: number = 0.2,
|
|
196
|
+
cpCount: number = 16
|
|
197
|
+
) {
|
|
198
|
+
let cpList = MPClip(iomap, cpCount);
|
|
199
|
+
for (let cpMap of cpList)
|
|
200
|
+
SFfmpegTool.trimSilenceCP(cpMap, threshold, silence);
|
|
201
|
+
}
|
|
202
|
+
/**删除静音子线程
|
|
203
|
+
*/
|
|
204
|
+
private static async trimSilenceCP(
|
|
205
|
+
iomap: IOMap,
|
|
206
|
+
threshold: number = -50,
|
|
207
|
+
silence: number = 0.2
|
|
208
|
+
) {
|
|
209
|
+
for (let inPath in iomap) {
|
|
210
|
+
let outpath = iomap[inPath];
|
|
211
|
+
console.log("正在处理:" + outpath);
|
|
212
|
+
await SFfmpegTool.trimSilence(inPath, outpath, threshold, silence);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**多线程任务分割器
|
|
218
|
+
* @param {IOMap} iomap - 输入输出路径映射
|
|
219
|
+
* @param {number} cpCount - 并发数
|
|
220
|
+
*/
|
|
221
|
+
function MPClip(iomap: IOMap, cpCount: number = 16) {
|
|
222
|
+
let cpList: Array<IOMap> = [];
|
|
223
|
+
for (let i = 0; i < cpCount; i++) cpList.push({});
|
|
224
|
+
let cpCounter = 0;
|
|
225
|
+
for (let key in iomap) {
|
|
226
|
+
let cpIndex = cpCounter % cpCount;
|
|
227
|
+
cpCounter++;
|
|
228
|
+
cpList[cpIndex][key] = iomap[key];
|
|
229
|
+
}
|
|
230
|
+
return cpList;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
type SFfmpegCmd = {
|
|
234
|
+
name: string;
|
|
235
|
+
opt: string | null;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
/**ffmpeg流
|
|
239
|
+
*/
|
|
240
|
+
class SFfmpegStream {
|
|
241
|
+
iomap;
|
|
242
|
+
cmdList: Array<SFfmpegCmd> = [];
|
|
243
|
+
constructor(iomap: IOMap) {
|
|
244
|
+
this.iomap = iomap;
|
|
245
|
+
}
|
|
246
|
+
async save() {}
|
|
247
|
+
|
|
248
|
+
//执行函数
|
|
249
|
+
private async cmdAreverse() {}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export default SFfmpegTool;
|
|
253
|
+
export { SFfmpegTool };
|
package/src/UtilFunctions.ts
CHANGED
|
@@ -111,3 +111,27 @@ export function sleep(timeMs:number):Promise<boolean>{
|
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**搜索路径符合正则表达式的文件
|
|
115
|
+
* @param folder - 文件夹路径
|
|
116
|
+
* @param traitRegex - 正则表达式
|
|
117
|
+
* @returns {Record<string, string>} 文件名与路径的映射
|
|
118
|
+
*/
|
|
119
|
+
export function fileSearch(folder: string, traitRegex: string) {
|
|
120
|
+
let outMap: Record<string, string> = {};
|
|
121
|
+
let subFiles = fs.readdirSync(folder);
|
|
122
|
+
let regex = new RegExp(traitRegex);
|
|
123
|
+
for (let subFile of subFiles) {
|
|
124
|
+
let subFilePath = path.join(folder, subFile);
|
|
125
|
+
let stat = fs.lstatSync(subFilePath);
|
|
126
|
+
|
|
127
|
+
//判断是否是文件夹,递归调用
|
|
128
|
+
if (stat.isDirectory()) {
|
|
129
|
+
let subMap = fileSearch(path.join(subFilePath, "/"), traitRegex);
|
|
130
|
+
for (let key in subMap) outMap[key] = subMap[key];
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (regex.test(subFilePath))
|
|
134
|
+
outMap[subFile] = subFilePath;
|
|
135
|
+
}
|
|
136
|
+
return outMap;
|
|
137
|
+
}
|
package/src/index.ts
CHANGED
package/test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
let {SList,SHashMap,SEntry} = require('./dist');
|
|
1
|
+
let {SList,SHashMap,SEntry,SFfmpegTool} = require('./dist');
|
|
2
2
|
|
|
3
3
|
let slist = new SList([1,2,3,3,4,5,6]);
|
|
4
4
|
slist.each(val => console.log(val));
|
|
@@ -10,3 +10,10 @@ map.put("123",456)
|
|
|
10
10
|
map.put("789",111)
|
|
11
11
|
for(let {key,value} of map)
|
|
12
12
|
console.log(key,value)
|
|
13
|
+
|
|
14
|
+
SFfmpegTool.setFfmpegPath("E:/系统工具/ffmpeg-master-latest-win64-gpl/bin/ffmpeg.exe");
|
|
15
|
+
(async function(){
|
|
16
|
+
let data = await SFfmpegTool.getAudioMetaData("input.wav");
|
|
17
|
+
console.log(data);
|
|
18
|
+
}())
|
|
19
|
+
|