m4k 0.2.2 → 0.2.4
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/lib/audio/audio-processor.d.ts +6 -6
- package/lib/audio/audio-processor.js +31 -28
- package/lib/images/image-processor.js +3 -3
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/util/ffmpeg-processor.d.ts +3 -4
- package/lib/util/ffmpeg-processor.js +18 -5
- package/lib/videos/video-processor.d.ts +1 -1
- package/lib/videos/video-processor.js +42 -36
- package/package.json +14 -4
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { type AudioOptions } from "@m4k/common";
|
|
2
2
|
/**
|
|
3
|
-
* Process
|
|
4
|
-
* @param input - The input
|
|
5
|
-
* @param opts - The options for the
|
|
3
|
+
* Process an audio file.
|
|
4
|
+
* @param input - The input audio file. Can be a file path, a stream or a blob.
|
|
5
|
+
* @param opts - The options for the audio processing.
|
|
6
6
|
* @param signal - An optional abort signal.
|
|
7
|
-
* @returns An iterable of the processed
|
|
7
|
+
* @returns An iterable of the processed audio files.
|
|
8
8
|
*/
|
|
9
|
-
export declare function processAudio(input: string | AsyncIterable<Uint8Array> | Blob, opts: AudioOptions, { signal }?: {
|
|
9
|
+
export declare function processAudio(input: string | AsyncIterable<Uint8Array> | Blob, opts: AudioOptions | AudioOptions[], { signal }?: {
|
|
10
10
|
signal?: AbortSignal;
|
|
11
11
|
}): AsyncGenerator<import("@m4k/common").ProcessedFile | {
|
|
12
12
|
progress: number;
|
|
@@ -14,7 +14,7 @@ export declare function processAudio(input: string | AsyncIterable<Uint8Array> |
|
|
|
14
14
|
position: number;
|
|
15
15
|
}, void, unknown> | null;
|
|
16
16
|
/**
|
|
17
|
-
* The queue for
|
|
17
|
+
* The queue for audio processing.
|
|
18
18
|
*/
|
|
19
19
|
export declare const audioQueue: import("tasque").Tasque;
|
|
20
20
|
//# sourceMappingURL=audio-processor.d.ts.map
|
|
@@ -1,42 +1,45 @@
|
|
|
1
1
|
import {} from "@m4k/common";
|
|
2
2
|
import { mkdir } from "node:fs/promises";
|
|
3
3
|
import { createQueue } from "tasque";
|
|
4
|
-
import { processFfmpeg } from "../util/ffmpeg-processor";
|
|
4
|
+
import { getExtension, processFfmpeg } from "../util/ffmpeg-processor";
|
|
5
5
|
/**
|
|
6
|
-
* Process
|
|
7
|
-
* @param input - The input
|
|
8
|
-
* @param opts - The options for the
|
|
6
|
+
* Process an audio file.
|
|
7
|
+
* @param input - The input audio file. Can be a file path, a stream or a blob.
|
|
8
|
+
* @param opts - The options for the audio processing.
|
|
9
9
|
* @param signal - An optional abort signal.
|
|
10
|
-
* @returns An iterable of the processed
|
|
10
|
+
* @returns An iterable of the processed audio files.
|
|
11
11
|
*/
|
|
12
12
|
export function processAudio(input, opts, { signal } = {}) {
|
|
13
13
|
return processFfmpeg(input, () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
14
|
+
let args = [];
|
|
15
|
+
for (const o of Array.isArray(opts) ? opts : [opts]) {
|
|
16
|
+
const a = [];
|
|
17
|
+
if (o.format)
|
|
18
|
+
a.push("-f", o.format);
|
|
19
|
+
if (o.seek)
|
|
20
|
+
a.push("-ss", o.seek.toString());
|
|
21
|
+
if (o.duration)
|
|
22
|
+
a.push("-t", o.duration.toString());
|
|
23
|
+
if (o.bitrate)
|
|
24
|
+
a.push("-b:a", o.bitrate.toString());
|
|
25
|
+
if (o.filters)
|
|
26
|
+
a.push("-af", o.filters);
|
|
27
|
+
if (o.complexFilters)
|
|
28
|
+
a.push("-filter_complex", o.complexFilters);
|
|
29
|
+
if (o.codec)
|
|
30
|
+
a.push("-c:a", o.codec);
|
|
31
|
+
if (o.args)
|
|
32
|
+
a.push(...o.args);
|
|
33
|
+
args.push({
|
|
34
|
+
args: a,
|
|
35
|
+
ext: o.ext ?? getExtension(o.format ?? "mp3"),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return args;
|
|
36
39
|
}, { signal, queue: audioQueue, tmpDir });
|
|
37
40
|
}
|
|
38
41
|
/**
|
|
39
|
-
* The queue for
|
|
42
|
+
* The queue for audio processing.
|
|
40
43
|
*/
|
|
41
44
|
export const audioQueue = createQueue({
|
|
42
45
|
parallelize: Number(process.env.AUDIO_PARALLELIZE ?? 1),
|
|
@@ -19,7 +19,7 @@ export function processImage(input, opts, { signal } = {}) {
|
|
|
19
19
|
: "stream" in input
|
|
20
20
|
? input.stream()
|
|
21
21
|
: input, sharpInstance);
|
|
22
|
-
const files = opts.map(async ({ colorspace, crop, format = "avif", keepExif, keepIcc, keepMetadata, quality, resize, rotate, }, idx) => {
|
|
22
|
+
const files = opts.map(async ({ colorspace, crop, format = "avif", ext = format, keepExif, keepIcc, keepMetadata, quality, resize, rotate, }, idx) => {
|
|
23
23
|
let s = sharpInstance.clone();
|
|
24
24
|
s = s.rotate(rotate);
|
|
25
25
|
if (resize)
|
|
@@ -42,8 +42,8 @@ export function processImage(input, opts, { signal } = {}) {
|
|
|
42
42
|
top: crop.top ?? 0,
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
-
s = s.toFormat(
|
|
46
|
-
return new ProcessedFile(`file${idx + 1}.${
|
|
45
|
+
s = s.toFormat(ext, { quality });
|
|
46
|
+
return new ProcessedFile(`file${idx + 1}.${ext}`, `image/${ext}`, s);
|
|
47
47
|
});
|
|
48
48
|
let done = 0;
|
|
49
49
|
yield { progress: 0 };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { ProcessedFile, type AudioOptions, type ImageOptions, type ProcessingError, type Progress, type QueuePosition, type VideoOptions, } from "@m4k/common";
|
|
2
2
|
export { audioQueue, processAudio } from "./audio/audio-processor";
|
|
3
3
|
export { imageQueue, processImage } from "./images/image-processor";
|
|
4
4
|
export { getExtension } from "./util/ffmpeg-processor";
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { ProcessedFile, } from "@m4k/common";
|
|
2
2
|
export { audioQueue, processAudio } from "./audio/audio-processor";
|
|
3
3
|
export { imageQueue, processImage } from "./images/image-processor";
|
|
4
4
|
export { getExtension } from "./util/ffmpeg-processor";
|
|
@@ -8,10 +8,9 @@ import { type Tasque } from "tasque";
|
|
|
8
8
|
* @returns An iterable of the processed videos.
|
|
9
9
|
*/
|
|
10
10
|
export declare function processFfmpeg(input: string | AsyncIterable<Uint8Array> | Blob, buildArgs: () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}, { signal, queue, tmpDir, }: {
|
|
11
|
+
args: string[];
|
|
12
|
+
ext: string;
|
|
13
|
+
}[], { signal, queue, tmpDir, }: {
|
|
15
14
|
signal?: AbortSignal;
|
|
16
15
|
queue: Tasque;
|
|
17
16
|
tmpDir: string;
|
|
@@ -17,13 +17,21 @@ export function processFfmpeg(input, buildArgs, { signal, queue, tmpDir, }) {
|
|
|
17
17
|
const id = randomId();
|
|
18
18
|
const inputPath = typeof input === "string" ? input : createTmp(tmpDir, id, input);
|
|
19
19
|
const iterable = queue.iterate(async function* () {
|
|
20
|
-
const
|
|
21
|
-
const outputPath = `${tmpDir}/out-${id}.${getExtension(format)}`;
|
|
20
|
+
const outputPaths = [];
|
|
22
21
|
try {
|
|
23
22
|
signal?.throwIfAborted();
|
|
24
23
|
const p = await inputPath;
|
|
25
24
|
signal?.throwIfAborted();
|
|
26
|
-
const
|
|
25
|
+
const outArgs = [];
|
|
26
|
+
let i = 1;
|
|
27
|
+
for (const { args, ext } of buildArgs()) {
|
|
28
|
+
const outputPath = `${tmpDir}/out-${id}-${i++}.${ext}`;
|
|
29
|
+
outArgs.push(...args, outputPath);
|
|
30
|
+
outputPaths.push(outputPath);
|
|
31
|
+
}
|
|
32
|
+
const child = spawn(ffmpeg, ["-y", "-i", p, ...outArgs], {
|
|
33
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
34
|
+
});
|
|
27
35
|
// Use manual abort handling to avoid some uncatchable error, at least in bun.
|
|
28
36
|
signal?.addEventListener("abort", () => child.kill());
|
|
29
37
|
const decoder = new TextDecoder();
|
|
@@ -63,14 +71,19 @@ export function processFfmpeg(input, buildArgs, { signal, queue, tmpDir, }) {
|
|
|
63
71
|
if (code !== 0) {
|
|
64
72
|
throw new Error(`ffmpeg exited with code ${code}: ${errStr}`);
|
|
65
73
|
}
|
|
66
|
-
|
|
74
|
+
for (const outputPath of outputPaths) {
|
|
75
|
+
yield new ProcessedFile(outputPath, mimeTypes[outputPath.split(".").pop()]);
|
|
76
|
+
}
|
|
67
77
|
}
|
|
68
78
|
catch (e) {
|
|
69
79
|
signal?.throwIfAborted();
|
|
70
80
|
throw e;
|
|
71
81
|
}
|
|
72
82
|
finally {
|
|
73
|
-
await
|
|
83
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
84
|
+
for (const outputPath of outputPaths) {
|
|
85
|
+
await rm(outputPath, { force: true });
|
|
86
|
+
}
|
|
74
87
|
if (typeof inputPath !== "string") {
|
|
75
88
|
await rm(await inputPath, { force: true });
|
|
76
89
|
}
|
|
@@ -6,7 +6,7 @@ import { type VideoOptions } from "@m4k/common";
|
|
|
6
6
|
* @param signal - An optional abort signal.
|
|
7
7
|
* @returns An iterable of the processed videos.
|
|
8
8
|
*/
|
|
9
|
-
export declare function processVideo(input: string | AsyncIterable<Uint8Array> | Blob, opts: VideoOptions, { signal }?: {
|
|
9
|
+
export declare function processVideo(input: string | AsyncIterable<Uint8Array> | Blob, opts: VideoOptions | VideoOptions[], { signal }?: {
|
|
10
10
|
signal?: AbortSignal;
|
|
11
11
|
}): AsyncGenerator<import("@m4k/common").ProcessedFile | {
|
|
12
12
|
progress: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {} from "@m4k/common";
|
|
2
2
|
import { mkdir } from "node:fs/promises";
|
|
3
3
|
import { createQueue } from "tasque";
|
|
4
|
-
import { processFfmpeg } from "../util/ffmpeg-processor";
|
|
4
|
+
import { getExtension, processFfmpeg } from "../util/ffmpeg-processor";
|
|
5
5
|
/**
|
|
6
6
|
* Process a video.
|
|
7
7
|
* @param input - The input video. Can be a file path, a stream or a blob.
|
|
@@ -11,41 +11,47 @@ import { processFfmpeg } from "../util/ffmpeg-processor";
|
|
|
11
11
|
*/
|
|
12
12
|
export function processVideo(input, opts, { signal } = {}) {
|
|
13
13
|
return processFfmpeg(input, () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
14
|
+
let args = [];
|
|
15
|
+
for (const o of Array.isArray(opts) ? opts : [opts]) {
|
|
16
|
+
const a = [];
|
|
17
|
+
if (o.frames)
|
|
18
|
+
a.push("-frames:v", o.frames.toString());
|
|
19
|
+
if (o.format)
|
|
20
|
+
a.push("-f", o.format);
|
|
21
|
+
if (o.seek)
|
|
22
|
+
a.push("-ss", o.seek.toString());
|
|
23
|
+
if (o.duration)
|
|
24
|
+
a.push("-t", o.duration.toString());
|
|
25
|
+
if (o.audioBitrate)
|
|
26
|
+
a.push("-b:a", o.audioBitrate.toString());
|
|
27
|
+
if (o.videoBitrate)
|
|
28
|
+
a.push("-b:v", o.videoBitrate.toString());
|
|
29
|
+
if (o.audioFilters)
|
|
30
|
+
a.push("-af", o.audioFilters);
|
|
31
|
+
if (o.videoFilters)
|
|
32
|
+
a.push("-vf", o.videoFilters);
|
|
33
|
+
if (o.complexFilters)
|
|
34
|
+
a.push("-filter_complex", o.complexFilters);
|
|
35
|
+
if (o.aspect)
|
|
36
|
+
a.push("-aspect", o.aspect.toString());
|
|
37
|
+
if (o.pad)
|
|
38
|
+
a.push("-pad", o.pad);
|
|
39
|
+
if (o.fps)
|
|
40
|
+
a.push("-r", o.fps.toString());
|
|
41
|
+
if (o.size)
|
|
42
|
+
a.push("-scale", o.size);
|
|
43
|
+
if (o.videoCodec)
|
|
44
|
+
a.push("-c:v", o.videoCodec);
|
|
45
|
+
if (o.audioCodec)
|
|
46
|
+
a.push("-c:a", o.audioCodec);
|
|
47
|
+
if (o.args)
|
|
48
|
+
a.push(...o.args);
|
|
49
|
+
args.push({
|
|
50
|
+
args: a,
|
|
51
|
+
ext: o.ext ?? getExtension(o.format ?? "mp4"),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return args;
|
|
49
55
|
}, { signal, queue: videoQueue, tmpDir });
|
|
50
56
|
}
|
|
51
57
|
/**
|
package/package.json
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "m4k",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
7
7
|
"module": "./lib/index.js",
|
|
8
8
|
"license": "MIT",
|
|
9
|
+
"description": "Media toolkit for node, bun, and deno",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"media",
|
|
12
|
+
"ffmpeg",
|
|
13
|
+
"video",
|
|
14
|
+
"audio",
|
|
15
|
+
"image",
|
|
16
|
+
"sharp"
|
|
17
|
+
],
|
|
18
|
+
"author": "sv2dev",
|
|
9
19
|
"scripts": {
|
|
10
20
|
"prepublishOnly": "bun run build",
|
|
11
21
|
"build": "tsc -b",
|
|
@@ -15,13 +25,13 @@
|
|
|
15
25
|
"devDependencies": {
|
|
16
26
|
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
17
27
|
"tasque": "^0.1.2",
|
|
18
|
-
"typescript": "^5.
|
|
28
|
+
"typescript": "^5.8.3"
|
|
19
29
|
},
|
|
20
30
|
"dependencies": {
|
|
21
|
-
"@m4k/common": "^0.2.
|
|
31
|
+
"@m4k/common": "^0.2.3"
|
|
22
32
|
},
|
|
23
33
|
"peerDependencies": {
|
|
24
|
-
"sharp": "^0.
|
|
34
|
+
"sharp": "^0.34.1"
|
|
25
35
|
},
|
|
26
36
|
"repository": "github:sv2dev/m4k"
|
|
27
37
|
}
|