m4k 0.1.2 → 0.2.1

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/README.md CHANGED
@@ -3,27 +3,41 @@
3
3
  A media conversion toolkit based on [ffmpeg](https://ffmpeg.org/) and [sharp](https://sharp.pixelplumbing.com/)/[libvips](https://github.com/libvips/libvips).
4
4
  It provides queueing and progress reporting via async iterables.
5
5
 
6
+ The API is unified for audio, images and videos.
7
+
6
8
  ## Usage
7
9
 
8
- Image optimization happens completely in memory.
10
+ ### Processing audio
11
+
12
+ ```ts
13
+ import { processAudio, ProcessedFile } from "m4k";
14
+ // input is a file path or an async iterable of Uint8Arrays or a Blob
15
+ for await (const value of processAudio(input, opts)) {
16
+ if (value instanceof ProcessedFile) {
17
+ // do something with the file
18
+ }
19
+ }
20
+ ```
21
+
22
+ ### Processing images
9
23
 
10
24
  ```ts
11
- import { optimizeImage, ConvertedFile } from "m4k";
12
- // input is a ReadableStream or Blob
13
- for await (const value of optimizeImage(input, opts)) {
14
- if (value instanceof ConvertedFile) {
25
+ import { processImage, ProcessedFile } from "m4k";
26
+ // input is a file path or an async iterable of Uint8Arrays or a Blob
27
+ for await (const value of processImage(input, opts)) {
28
+ if (value instanceof ProcessedFile) {
15
29
  // do something with the file
16
30
  }
17
31
  }
18
32
  ```
19
33
 
20
- For lower memory usage, video optimization happens with files.
34
+ ### Processing videos
21
35
 
22
36
  ```ts
23
- import { optimizeVideo, ConvertedFile } from "m4k";
24
- // input is a file path
25
- for await (const value of optimizeVideo(input, opts)) {
26
- if (value instanceof ConvertedFile) {
37
+ import { processVideo, ProcessedFile } from "m4k";
38
+ // input is a file path or an async iterable of Uint8Arrays or a Blob
39
+ for await (const value of processVideo(input, opts)) {
40
+ if (value instanceof ProcessedFile) {
27
41
  // do something with the file
28
42
  }
29
43
  }
@@ -0,0 +1,20 @@
1
+ import { type AudioOptions } from "@m4k/common";
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.
6
+ * @param signal - An optional abort signal.
7
+ * @returns An iterable of the processed videos.
8
+ */
9
+ export declare function processAudio(input: string | AsyncIterable<Uint8Array> | Blob, opts: AudioOptions, { signal }?: {
10
+ signal?: AbortSignal;
11
+ }): AsyncGenerator<import("@m4k/common").ProcessedFile | {
12
+ progress: number;
13
+ } | {
14
+ position: number;
15
+ }, void, unknown> | null;
16
+ /**
17
+ * The queue for video processing.
18
+ */
19
+ export declare const audioQueue: import("tasque").Tasque;
20
+ //# sourceMappingURL=audio-processor.d.ts.map
@@ -0,0 +1,47 @@
1
+ import {} from "@m4k/common";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { createQueue } from "tasque";
4
+ import { processFfmpeg } from "../util/ffmpeg-processor";
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.
9
+ * @param signal - An optional abort signal.
10
+ * @returns An iterable of the processed videos.
11
+ */
12
+ export function processAudio(input, opts, { signal } = {}) {
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
+ };
36
+ }, { signal, queue: audioQueue, tmpDir });
37
+ }
38
+ /**
39
+ * The queue for video processing.
40
+ */
41
+ export const audioQueue = createQueue({
42
+ parallelize: Number(process.env.AUDIO_PARALLELIZE ?? 1),
43
+ max: Number(process.env.AUDIO_QUEUE_SIZE ?? 5),
44
+ });
45
+ const tmpDir = `${process.env.M4K_TMP_DIR ?? "/tmp/m4k"}/audio`;
46
+ await mkdir(tmpDir, { recursive: true });
47
+ //# sourceMappingURL=audio-processor.js.map
@@ -0,0 +1,22 @@
1
+ import { type ImageOptions, ProcessedFile } from "@m4k/common";
2
+ export type Format = Required<ImageOptions>["format"];
3
+ export type Fit = Required<Required<ImageOptions>["resize"]>["fit"];
4
+ /**
5
+ * Process an image.
6
+ * @param input - The input image. Can be a file path, a stream or a blob.
7
+ * @param opts - The options for the image processing.
8
+ * @param signal - An optional abort signal.
9
+ * @returns An iterable of the processed images.
10
+ */
11
+ export declare function processImage(input: string | AsyncIterable<Uint8Array> | Blob, opts: ImageOptions | ImageOptions[], { signal }?: {
12
+ signal?: AbortSignal;
13
+ }): AsyncGenerator<ProcessedFile | {
14
+ progress: number;
15
+ } | {
16
+ position: number;
17
+ }, void, unknown> | null;
18
+ /**
19
+ * The queue for image processing.
20
+ */
21
+ export declare const imageQueue: import("tasque").Tasque;
22
+ //# sourceMappingURL=image-processor.d.ts.map
@@ -1,13 +1,25 @@
1
- import { ConvertedFile } from "@m4k/common";
1
+ import { ProcessedFile } from "@m4k/common";
2
+ import { createReadStream } from "node:fs";
2
3
  import sharp from "sharp";
3
4
  import { createQueue } from "tasque";
4
- import { readableFromWeb, readableToWeb } from "../util/streams";
5
- export function optimizeImage(input, opts, signal) {
5
+ import { exhaustAsyncIterableToWritable } from "../util/streams";
6
+ /**
7
+ * Process an image.
8
+ * @param input - The input image. Can be a file path, a stream or a blob.
9
+ * @param opts - The options for the image processing.
10
+ * @param signal - An optional abort signal.
11
+ * @returns An iterable of the processed images.
12
+ */
13
+ export function processImage(input, opts, { signal } = {}) {
6
14
  const sharpInstance = sharp();
7
15
  const iterable = imageQueue.iterate(async function* () {
8
16
  opts = Array.isArray(opts) ? opts : [opts];
9
- readableFromWeb("stream" in input ? input.stream() : input).pipe(sharpInstance);
10
- const files = await Promise.all(opts.map(async ({ colorspace, crop, format = "avif", keepExif, keepIcc, keepMetadata, quality, resize, rotate, }, idx) => {
17
+ void exhaustAsyncIterableToWritable(typeof input === "string"
18
+ ? createReadStream(input)
19
+ : "stream" in input
20
+ ? input.stream()
21
+ : input, sharpInstance);
22
+ const files = opts.map(async ({ colorspace, crop, format = "avif", keepExif, keepIcc, keepMetadata, quality, resize, rotate, }, idx) => {
11
23
  let s = sharpInstance.clone();
12
24
  s = s.rotate(rotate);
13
25
  if (resize)
@@ -31,12 +43,12 @@ export function optimizeImage(input, opts, signal) {
31
43
  });
32
44
  }
33
45
  s = s.toFormat(format, { quality });
34
- return new ConvertedFile(`file${idx + 1}.${format}`, `image/${format}`, readableToWeb(s));
35
- }));
46
+ return new ProcessedFile(`file${idx + 1}.${format}`, `image/${format}`, s);
47
+ });
36
48
  let done = 0;
37
49
  yield { progress: 0 };
38
50
  for (const file of files) {
39
- yield file;
51
+ yield await file;
40
52
  done++;
41
53
  yield { progress: Math.round((done / files.length) * 100) };
42
54
  }
@@ -52,8 +64,11 @@ export function optimizeImage(input, opts, signal) {
52
64
  }
53
65
  })();
54
66
  }
67
+ /**
68
+ * The queue for image processing.
69
+ */
55
70
  export const imageQueue = createQueue({
56
71
  parallelize: Number(process.env.IMAGE_PARALLELIZE ?? 5),
57
72
  max: Number(process.env.IMAGE_QUEUE_SIZE ?? 100),
58
73
  });
59
- //# sourceMappingURL=image-optimizer.js.map
74
+ //# sourceMappingURL=image-processor.js.map
package/lib/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from "@m4k/common";
2
- export { imageQueue, optimizeImage } from "./images/image-optimizer";
3
- export { getExtension, optimizeVideo, tmpVideoDir, videoQueue } from "./videos/video-optimizer";
2
+ export { audioQueue, processAudio } from "./audio/audio-processor";
3
+ export { imageQueue, processImage } from "./images/image-processor";
4
+ export { getExtension } from "./util/ffmpeg-processor";
5
+ export { processVideo, videoQueue } from "./videos/video-processor";
4
6
  //# sourceMappingURL=index.d.ts.map
package/lib/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from "@m4k/common";
2
- export { imageQueue, optimizeImage } from "./images/image-optimizer";
3
- export { getExtension, optimizeVideo, tmpVideoDir, videoQueue } from "./videos/video-optimizer";
2
+ export { audioQueue, processAudio } from "./audio/audio-processor";
3
+ export { imageQueue, processImage } from "./images/image-processor";
4
+ export { getExtension } from "./util/ffmpeg-processor";
5
+ export { processVideo, videoQueue } from "./videos/video-processor";
4
6
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,30 @@
1
+ import { ProcessedFile } from "@m4k/common";
2
+ import { type Tasque } from "tasque";
3
+ /**
4
+ * Process a video.
5
+ * @param input - The input video. Can be a file path, a stream or a blob.
6
+ * @param opts - The options for the video processing.
7
+ * @param signal - An optional abort signal.
8
+ * @returns An iterable of the processed videos.
9
+ */
10
+ export declare function processFfmpeg(input: string | AsyncIterable<Uint8Array> | Blob, buildArgs: () => {
11
+ input: string[];
12
+ out: string[];
13
+ format: string;
14
+ }, { signal, queue, tmpDir, }: {
15
+ signal?: AbortSignal;
16
+ queue: Tasque;
17
+ tmpDir: string;
18
+ }): AsyncGenerator<ProcessedFile | {
19
+ progress: number;
20
+ } | {
21
+ position: number;
22
+ }, void, unknown> | null;
23
+ /**
24
+ * Get the extension for a given format.
25
+ * May not be complete yet. Most of the time, the format will be the same as the extension.
26
+ * @param format - The format to get the extension for.
27
+ * @returns The extension for the given format.
28
+ */
29
+ export declare function getExtension(format: string): string;
30
+ //# sourceMappingURL=ffmpeg-processor.d.ts.map
@@ -1,45 +1,31 @@
1
- import { ConvertedFile } from "@m4k/common";
1
+ import { ProcessedFile } from "@m4k/common";
2
2
  import { spawn } from "node:child_process";
3
3
  import { getRandomValues } from "node:crypto";
4
- import { mkdir, rm } from "node:fs/promises";
5
- import { createQueue } from "tasque";
4
+ import { createWriteStream } from "node:fs";
5
+ import { rm } from "node:fs/promises";
6
+ import {} from "tasque";
6
7
  import { mimeTypes } from "../util/mime";
7
- export function optimizeVideo(inputPath, opts, signal) {
8
- const iterable = videoQueue.iterate(async function* () {
9
- await mkdir(tmpVideoDir, { recursive: true });
10
- const outputPath = `${tmpVideoDir}/out-${Buffer.from(getRandomValues(new Uint8Array(16))).toString("base64url")}.${getExtension(opts.format ?? "mp4")}`;
11
- const inArgs = [];
12
- if (opts.inputFormat)
13
- inArgs.push("-f", opts.inputFormat);
14
- if (opts.seek)
15
- inArgs.push("-ss", opts.seek.toString());
16
- if (opts.duration)
17
- inArgs.push("-t", opts.duration.toString());
18
- const outArgs = [];
19
- if (opts.format)
20
- outArgs.push("-f", opts.format);
21
- if (opts.audioBitrate)
22
- outArgs.push("-b:a", opts.audioBitrate.toString());
23
- if (opts.videoBitrate)
24
- outArgs.push("-b:v", opts.videoBitrate.toString());
25
- if (opts.audioFilters)
26
- outArgs.push("-af", opts.audioFilters);
27
- if (opts.videoFilters)
28
- outArgs.push("-vf", opts.videoFilters);
29
- if (opts.complexFilters)
30
- outArgs.push("-filter_complex", opts.complexFilters);
31
- if (opts.aspect)
32
- outArgs.push("-aspect", opts.aspect.toString());
33
- if (opts.pad)
34
- outArgs.push("-pad", opts.pad);
35
- if (opts.fps)
36
- outArgs.push("-r", opts.fps.toString());
37
- if (opts.size)
38
- outArgs.push("-scale", opts.size);
39
- outArgs.push("-c:v", opts.videoCodec ?? "copy");
40
- outArgs.push("-c:a", opts.audioCodec ?? "copy");
8
+ import { exhaustAsyncIterableToWritable } from "../util/streams";
9
+ /**
10
+ * Process a video.
11
+ * @param input - The input video. Can be a file path, a stream or a blob.
12
+ * @param opts - The options for the video processing.
13
+ * @param signal - An optional abort signal.
14
+ * @returns An iterable of the processed videos.
15
+ */
16
+ export function processFfmpeg(input, buildArgs, { signal, queue, tmpDir, }) {
17
+ const id = randomId();
18
+ const inputPath = typeof input === "string" ? input : createTmp(tmpDir, id, input);
19
+ const iterable = queue.iterate(async function* () {
20
+ const { input: inArgs, out: outArgs, format } = buildArgs();
21
+ const outputPath = `${tmpDir}/out-${id}.${getExtension(format)}`;
41
22
  try {
42
- const child = spawn(ffmpeg, ["-y", ...inArgs, "-i", inputPath, ...outArgs, outputPath], { stdio: ["pipe", "pipe", "pipe"], signal });
23
+ signal?.throwIfAborted();
24
+ const p = await inputPath;
25
+ signal?.throwIfAborted();
26
+ const child = spawn(ffmpeg, ["-y", ...inArgs, "-i", p, ...outArgs, outputPath], { stdio: ["pipe", "pipe", "pipe"] });
27
+ // Use manual abort handling to avoid some uncatchable error, at least in bun.
28
+ signal?.addEventListener("abort", () => child.kill());
43
29
  const decoder = new TextDecoder();
44
30
  let metadataStr = "";
45
31
  let duration = null;
@@ -77,12 +63,19 @@ export function optimizeVideo(inputPath, opts, signal) {
77
63
  if (code !== 0) {
78
64
  throw new Error(`ffmpeg exited with code ${code}: ${errStr}`);
79
65
  }
80
- yield new ConvertedFile(outputPath, mimeTypes[opts.format ?? "mp4"]);
66
+ yield new ProcessedFile(outputPath, mimeTypes[format]);
67
+ }
68
+ catch (e) {
69
+ signal?.throwIfAborted();
70
+ throw e;
81
71
  }
82
72
  finally {
83
73
  await rm(outputPath, { force: true });
74
+ if (typeof inputPath !== "string") {
75
+ await rm(await inputPath, { force: true });
76
+ }
84
77
  }
85
- }, signal);
78
+ });
86
79
  if (!iterable)
87
80
  return null;
88
81
  return (async function* () {
@@ -103,11 +96,15 @@ export function optimizeVideo(inputPath, opts, signal) {
103
96
  export function getExtension(format) {
104
97
  return extMap[format] ?? format;
105
98
  }
106
- export const videoQueue = createQueue({
107
- parallelize: Number(process.env.VIDEO_PARALLELIZE ?? 1),
108
- max: Number(process.env.VIDEO_QUEUE_SIZE ?? 5),
109
- });
110
- export const tmpVideoDir = `${process.env.TMP_DIR ?? "/tmp"}/videos`;
99
+ async function createTmp(tmpDir, id, input) {
100
+ const filePath = `${tmpDir}/in-${id}`;
101
+ const writer = createWriteStream(filePath);
102
+ await exhaustAsyncIterableToWritable("stream" in input ? input.stream() : input, writer);
103
+ return filePath;
104
+ }
105
+ function randomId() {
106
+ return Buffer.from(getRandomValues(new Uint8Array(16))).toString("base64url");
107
+ }
111
108
  function parseDuration(metadataStr) {
112
109
  const match = metadataStr.match(/Duration: (\d+:\d+:\d+\.\d+)/);
113
110
  if (!match)
@@ -122,4 +119,4 @@ const ffmpeg = process.env.FFMPEG_PATH ?? (await import("@ffmpeg-installer/ffmpe
122
119
  const extMap = {
123
120
  matroska: "mkv",
124
121
  };
125
- //# sourceMappingURL=video-optimizer.js.map
122
+ //# sourceMappingURL=ffmpeg-processor.js.map
@@ -1,4 +1,3 @@
1
- import { Readable } from "node:stream";
2
- export declare function readableFromWeb(stream: ReadableStream): Readable;
3
- export declare function readableToWeb(stream: Readable): ReadableStream & AsyncIterable<Uint8Array>;
1
+ import type { Writable } from "stream";
2
+ export declare function exhaustAsyncIterableToWritable(it: AsyncIterable<Uint8Array>, writable: Writable): Promise<void>;
4
3
  //# sourceMappingURL=streams.d.ts.map
@@ -1,9 +1,9 @@
1
- import { Readable } from "node:stream";
2
- export function readableFromWeb(stream) {
3
- // Currently node types don't match with TS lib types for ReadableStream, so we need to cast to any.
4
- return Readable.fromWeb(stream);
5
- }
6
- export function readableToWeb(stream) {
7
- return Readable.toWeb(stream);
1
+ export async function exhaustAsyncIterableToWritable(it, writable) {
2
+ for await (const chunk of it) {
3
+ if (!writable.write(chunk))
4
+ await new Promise((resolve) => writable.once("drain", resolve));
5
+ }
6
+ writable.end();
7
+ await new Promise((resolve) => writable.on("finish", resolve));
8
8
  }
9
9
  //# sourceMappingURL=streams.js.map
@@ -0,0 +1,20 @@
1
+ import { type VideoOptions } from "@m4k/common";
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.
6
+ * @param signal - An optional abort signal.
7
+ * @returns An iterable of the processed videos.
8
+ */
9
+ export declare function processVideo(input: string | AsyncIterable<Uint8Array> | Blob, opts: VideoOptions, { signal }?: {
10
+ signal?: AbortSignal;
11
+ }): AsyncGenerator<import("@m4k/common").ProcessedFile | {
12
+ progress: number;
13
+ } | {
14
+ position: number;
15
+ }, void, unknown> | null;
16
+ /**
17
+ * The queue for video processing.
18
+ */
19
+ export declare const videoQueue: import("tasque").Tasque;
20
+ //# sourceMappingURL=video-processor.d.ts.map
@@ -0,0 +1,60 @@
1
+ import {} from "@m4k/common";
2
+ import { mkdir } from "node:fs/promises";
3
+ import { createQueue } from "tasque";
4
+ import { processFfmpeg } from "../util/ffmpeg-processor";
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.
9
+ * @param signal - An optional abort signal.
10
+ * @returns An iterable of the processed videos.
11
+ */
12
+ export function processVideo(input, opts, { signal } = {}) {
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
+ };
49
+ }, { signal, queue: videoQueue, tmpDir });
50
+ }
51
+ /**
52
+ * The queue for video processing.
53
+ */
54
+ export const videoQueue = createQueue({
55
+ parallelize: Number(process.env.VIDEO_PARALLELIZE ?? 1),
56
+ max: Number(process.env.VIDEO_QUEUE_SIZE ?? 5),
57
+ });
58
+ const tmpDir = `${process.env.M4K_TMP_DIR ?? "/tmp/m4k"}/video`;
59
+ await mkdir(tmpDir, { recursive: true });
60
+ //# sourceMappingURL=video-processor.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "m4k",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -1,10 +0,0 @@
1
- import { type ImageOptimizerOptions, ConvertedFile } from "@m4k/common";
2
- export type Format = Required<ImageOptimizerOptions>["format"];
3
- export type Fit = Required<Required<ImageOptimizerOptions>["resize"]>["fit"];
4
- export declare function optimizeImage(input: ReadableStream | Blob, opts: ImageOptimizerOptions | ImageOptimizerOptions[], signal?: AbortSignal): AsyncGenerator<ConvertedFile | {
5
- progress: number;
6
- } | {
7
- position: number;
8
- }, void, unknown> | null;
9
- export declare const imageQueue: import("tasque").Tasque;
10
- //# sourceMappingURL=image-optimizer.d.ts.map
@@ -1,16 +0,0 @@
1
- import { ConvertedFile, type VideoOptimizerOptions } from "@m4k/common";
2
- export declare function optimizeVideo(inputPath: string, opts: VideoOptimizerOptions, signal?: AbortSignal): AsyncGenerator<ConvertedFile | {
3
- progress: number;
4
- } | {
5
- position: number;
6
- }, void, unknown> | null;
7
- /**
8
- * Get the extension for a given format.
9
- * May not be complete yet. Most of the time, the format will be the same as the extension.
10
- * @param format - The format to get the extension for.
11
- * @returns The extension for the given format.
12
- */
13
- export declare function getExtension(format: string): string;
14
- export declare const videoQueue: import("tasque").Tasque;
15
- export declare const tmpVideoDir: string;
16
- //# sourceMappingURL=video-optimizer.d.ts.map