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.
@@ -1,12 +1,12 @@
1
1
  import { type AudioOptions } from "@m4k/common";
2
2
  /**
3
- * Process a video.
4
- * @param input - The input video. Can be a file path, a stream or a blob.
5
- * @param opts - The options for the video processing.
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 videos.
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 video processing.
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 a video.
7
- * @param input - The input video. Can be a file path, a stream or a blob.
8
- * @param opts - The options for the video processing.
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 videos.
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
- const inArgs = [];
15
- if (opts.inputFormat)
16
- inArgs.push("-f", opts.inputFormat);
17
- if (opts.seek)
18
- inArgs.push("-ss", opts.seek.toString());
19
- if (opts.duration)
20
- inArgs.push("-t", opts.duration.toString());
21
- const outArgs = [];
22
- if (opts.format)
23
- outArgs.push("-f", opts.format);
24
- if (opts.bitrate)
25
- outArgs.push("-b:a", opts.bitrate.toString());
26
- if (opts.filters)
27
- outArgs.push("-af", opts.filters);
28
- if (opts.complexFilters)
29
- outArgs.push("-filter_complex", opts.complexFilters);
30
- outArgs.push("-c:a", opts.codec ?? "copy");
31
- return {
32
- input: inArgs,
33
- out: outArgs,
34
- format: opts.format ?? "mp4",
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 video processing.
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(format, { quality });
46
- return new ProcessedFile(`file${idx + 1}.${format}`, `image/${format}`, s);
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 * from "@m4k/common";
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 * from "@m4k/common";
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
- input: string[];
12
- out: string[];
13
- format: string;
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 { input: inArgs, out: outArgs, format } = buildArgs();
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 child = spawn(ffmpeg, ["-y", ...inArgs, "-i", p, ...outArgs, outputPath], { stdio: ["pipe", "pipe", "pipe"] });
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
- yield new ProcessedFile(outputPath, mimeTypes[format]);
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 rm(outputPath, { force: true });
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
- const inArgs = [];
15
- if (opts.inputFormat)
16
- inArgs.push("-f", opts.inputFormat);
17
- if (opts.seek)
18
- inArgs.push("-ss", opts.seek.toString());
19
- if (opts.duration)
20
- inArgs.push("-t", opts.duration.toString());
21
- const outArgs = [];
22
- if (opts.format)
23
- outArgs.push("-f", opts.format);
24
- if (opts.audioBitrate)
25
- outArgs.push("-b:a", opts.audioBitrate.toString());
26
- if (opts.videoBitrate)
27
- outArgs.push("-b:v", opts.videoBitrate.toString());
28
- if (opts.audioFilters)
29
- outArgs.push("-af", opts.audioFilters);
30
- if (opts.videoFilters)
31
- outArgs.push("-vf", opts.videoFilters);
32
- if (opts.complexFilters)
33
- outArgs.push("-filter_complex", opts.complexFilters);
34
- if (opts.aspect)
35
- outArgs.push("-aspect", opts.aspect.toString());
36
- if (opts.pad)
37
- outArgs.push("-pad", opts.pad);
38
- if (opts.fps)
39
- outArgs.push("-r", opts.fps.toString());
40
- if (opts.size)
41
- outArgs.push("-scale", opts.size);
42
- outArgs.push("-c:v", opts.videoCodec ?? "copy");
43
- outArgs.push("-c:a", opts.audioCodec ?? "copy");
44
- return {
45
- input: inArgs,
46
- out: outArgs,
47
- format: opts.format ?? "mp4",
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.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.0.0"
28
+ "typescript": "^5.8.3"
19
29
  },
20
30
  "dependencies": {
21
- "@m4k/common": "^0.2.0"
31
+ "@m4k/common": "^0.2.3"
22
32
  },
23
33
  "peerDependencies": {
24
- "sharp": "^0.33.5"
34
+ "sharp": "^0.34.1"
25
35
  },
26
36
  "repository": "github:sv2dev/m4k"
27
37
  }