Skip to content

Commit 741ae2b

Browse files
committed
chore: organize code
1 parent 4bb8e3e commit 741ae2b

File tree

1 file changed

+117
-106
lines changed

1 file changed

+117
-106
lines changed

lib/index.js

Lines changed: 117 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { spawn } from "node:child_process"
21
import debug from "debug"
2+
import assert from "node:assert"
3+
import { spawn } from "node:child_process"
34
import { createReadStream, createWriteStream } from "node:fs"
45
import { unlink } from "node:fs/promises"
56
import { tmpdir } from "node:os"
@@ -9,93 +10,6 @@ import { PassThrough } from "node:stream"
910
const dbg = debug("ffmpeg-stream")
1011
const EXIT_CODES = [0, 255]
1112

12-
/**
13-
* @param {import("node:stream").Readable | import("node:stream").Writable} stream
14-
* @param {string} name
15-
*/
16-
function debugStream(stream, name) {
17-
stream.on("error", err => {
18-
dbg(`${name} error: ${err.message}`)
19-
})
20-
stream.on(
21-
"data",
22-
/** @type {(data: Buffer | string) => void} */ data => {
23-
dbg(`${name} data: ${data.length} bytes`)
24-
},
25-
)
26-
stream.on("finish", () => {
27-
dbg(`${name} finish`)
28-
})
29-
}
30-
31-
/**
32-
* @param {string} [prefix]
33-
* @param {string} [suffix]
34-
*/
35-
function getTmpPath(prefix = "", suffix = "") {
36-
const dir = tmpdir()
37-
const id = Math.random().toString(32).substr(2, 10)
38-
return join(dir, `${prefix}${id}${suffix}`)
39-
}
40-
41-
/**
42-
* FFmpeg options object.
43-
*
44-
* These are the same options that you normally pass to the ffmpeg command in the terminal.
45-
* Documentation for individual options can be found in the [ffmpeg docs](https://ffmpeg.org/ffmpeg.html#Main-options).
46-
*
47-
* To specify a boolean option, set it to `true`.
48-
* To specify an option multiple times, use an array.
49-
* Options with nullish or `false` values are ignored.
50-
*
51-
* @example
52-
*
53-
* ```js
54-
* const options = { f: "image2", vcodec: "png" }
55-
* ```
56-
*
57-
* @typedef {Record<string, string | number | boolean | Array<string | null | undefined> | null | undefined>} ConverterOptions
58-
*/
59-
60-
/**
61-
* @ignore
62-
* @internal
63-
* @typedef {Object} ConverterPipe
64-
* @property {"input" | "output"} type
65-
* @property {ConverterOptions} options
66-
* @property {string} file
67-
* @property {() => Promise<void>} [onBegin]
68-
* @property {(process: import("node:child_process").ChildProcess) => void} [onSpawn]
69-
* @property {() => Promise<void>} [onFinish]
70-
*/
71-
72-
/**
73-
* @param {ConverterOptions} options
74-
* @returns {string[]}
75-
*/
76-
function getArgs(options) {
77-
/** @type {string[]} */
78-
const args = []
79-
80-
for (const [option, value] of Object.entries(options)) {
81-
if (Array.isArray(value)) {
82-
for (const element of value) {
83-
if (element != null) {
84-
args.push(`-${option}`)
85-
args.push(String(element))
86-
}
87-
}
88-
} else if (value != null && value !== false) {
89-
args.push(`-${option}`)
90-
if (typeof value != "boolean") {
91-
args.push(String(value))
92-
}
93-
}
94-
}
95-
96-
return args
97-
}
98-
9913
/**
10014
* A class which wraps a FFmpeg process.
10115
*
@@ -154,7 +68,7 @@ export class Converter {
15468
* This builds a command like the one you would normally use in the terminal.
15569
*
15670
* @param {string} file Path to the input file.
157-
* @param {ConverterOptions} [options] FFmpeg options for this input.
71+
* @param {ConverterPipeOptions} [options] FFmpeg options for this input.
15872
*
15973
* @example
16074
*
@@ -183,7 +97,7 @@ export class Converter {
18397
* This builds a command like the one you would normally use in the terminal.
18498
*
18599
* @param {string} file Path to the output file.
186-
* @param {ConverterOptions} [options] FFmpeg options for this output.
100+
* @param {ConverterPipeOptions} [options] FFmpeg options for this output.
187101
*
188102
* @example
189103
*
@@ -211,9 +125,10 @@ export class Converter {
211125
*
212126
* Internally, it adds a special `pipe:<number>` input argument to the FFmpeg command.
213127
*
214-
* Remember to specify the [`f` option](https://ffmpeg.org/ffmpeg.html#Main-options), which specifies the format of the input data.
128+
* Remember to specify the [`f` option](https://ffmpeg.org/ffmpeg.html#Main-options),
129+
* which specifies the format of the input data.
215130
*
216-
* @param {ConverterOptions} options FFmpeg options for this input.
131+
* @param {ConverterPipeOptions} options FFmpeg options for this input.
217132
* @returns {import("node:stream").Writable} A stream which will be written to the FFmpeg process' stdio.
218133
*
219134
* @example
@@ -255,9 +170,10 @@ export class Converter {
255170
*
256171
* Internally, it adds a special `pipe:<number>` output argument to the FFmpeg command.
257172
*
258-
* Remember to specify the [`f` option](https://ffmpeg.org/ffmpeg.html#Main-options), which specifies the format of the output data.
173+
* Remember to specify the [`f` option](https://ffmpeg.org/ffmpeg.html#Main-options),
174+
* which specifies the format of the output data.
259175
*
260-
* @param {ConverterOptions} options FFmpeg options for this output.
176+
* @param {ConverterPipeOptions} options FFmpeg options for this output.
261177
* @returns {import("node:stream").Readable} A stream which will be read from the FFmpeg process' stdio.
262178
*
263179
* @example
@@ -299,7 +215,7 @@ export class Converter {
299215
*
300216
* Use this method if the format you want to read doesn't support non-seekable input.
301217
*
302-
* @param {ConverterOptions} options FFmpeg options for this input.
218+
* @param {ConverterPipeOptions} options FFmpeg options for this input.
303219
* @returns {import("node:stream").Writable} A stream which will be written to the temporary file.
304220
*/
305221
createBufferedInputStream(options) {
@@ -338,7 +254,7 @@ export class Converter {
338254
*
339255
* Use this method if the format you want to write doesn't support non-seekable output.
340256
*
341-
* @param {ConverterOptions} options FFmpeg options for this output.
257+
* @param {ConverterPipeOptions} options FFmpeg options for this output.
342258
* @returns {import("node:stream").Readable} A stream which will be read from the temporary file.
343259
*/
344260
createBufferedOutputStream(options) {
@@ -384,11 +300,11 @@ export class Converter {
384300
pipes.push(pipe)
385301
}
386302

387-
const command = this.getSpawnArgs()
303+
const args = this.getSpawnArgs()
388304
const stdio = this.getStdioArg()
389-
dbg(`spawn: ${this.ffmpegPath} ${command.join(" ")}`)
305+
dbg(`spawn: ${this.ffmpegPath} ${args.join(" ")}`)
390306
dbg(`spawn stdio: ${stdio.join(" ")}`)
391-
this.process = spawn(this.ffmpegPath, command, { stdio })
307+
this.process = spawn(this.ffmpegPath, args, { stdio })
392308
const finished = this.handleProcess()
393309

394310
for (const pipe of this.pipes) {
@@ -427,6 +343,7 @@ export class Converter {
427343
}
428344

429345
/**
346+
* Returns stdio pipes which can be passed to {@link spawn}.
430347
* @private
431348
* @returns {Array<"ignore" | "pipe">}
432349
*/
@@ -440,25 +357,26 @@ export class Converter {
440357
}
441358

442359
/**
360+
* Returns arguments which can be passed to {@link spawn}.
443361
* @private
444362
* @returns {string[]}
445363
*/
446364
getSpawnArgs() {
447365
/** @type {string[]} */
448-
const command = []
366+
const args = []
449367

450368
for (const pipe of this.pipes) {
451369
if (pipe.type !== "input") continue
452-
command.push(...getArgs(pipe.options))
453-
command.push("-i", pipe.file)
370+
args.push(...stringifyArgs(pipe.options))
371+
args.push("-i", pipe.file)
454372
}
455373
for (const pipe of this.pipes) {
456374
if (pipe.type !== "output") continue
457-
command.push(...getArgs(pipe.options))
458-
command.push(pipe.file)
375+
args.push(...stringifyArgs(pipe.options))
376+
args.push(pipe.file)
459377
}
460378

461-
return command
379+
return args
462380
}
463381

464382
/**
@@ -470,7 +388,7 @@ export class Converter {
470388
/** @type {string[]} */
471389
const logLines = []
472390

473-
if (this.process == null) return reject(Error(`Converter not started`))
391+
assert(this.process != null, "process should be initialized")
474392

475393
if (this.process.stderr != null) {
476394
this.process.stderr.setEncoding("utf8")
@@ -509,3 +427,96 @@ export class Converter {
509427
})
510428
}
511429
}
430+
431+
/**
432+
* Stringifies FFmpeg options object into command line arguments array.
433+
*
434+
* @param {ConverterPipeOptions} options
435+
* @returns {string[]}
436+
*/
437+
function stringifyArgs(options) {
438+
/** @type {string[]} */
439+
const args = []
440+
441+
for (const [option, value] of Object.entries(options)) {
442+
if (Array.isArray(value)) {
443+
for (const element of value) {
444+
if (element != null) {
445+
args.push(`-${option}`)
446+
args.push(String(element))
447+
}
448+
}
449+
} else if (value != null && value !== false) {
450+
args.push(`-${option}`)
451+
if (typeof value != "boolean") {
452+
args.push(String(value))
453+
}
454+
}
455+
}
456+
457+
return args
458+
}
459+
460+
/**
461+
* Returns a random file path in the system's temporary directory.
462+
*
463+
* @param {string} [prefix]
464+
* @param {string} [suffix]
465+
*/
466+
function getTmpPath(prefix = "", suffix = "") {
467+
const dir = tmpdir()
468+
const id = Math.random().toString(32).substr(2, 10)
469+
return join(dir, `${prefix}${id}${suffix}`)
470+
}
471+
472+
/**
473+
* @param {import("node:stream").Readable | import("node:stream").Writable} stream
474+
* @param {string} name
475+
*/
476+
function debugStream(stream, name) {
477+
stream.on("error", err => {
478+
dbg(`${name} error: ${err.message}`)
479+
})
480+
stream.on(
481+
"data",
482+
/** @type {(data: Buffer | string) => void} */ data => {
483+
dbg(`${name} data: ${data.length} bytes`)
484+
},
485+
)
486+
stream.on("finish", () => {
487+
dbg(`${name} finish`)
488+
})
489+
}
490+
491+
/**
492+
* Options object for a single input or output of a {@link Converter}.
493+
*
494+
* These are the same options that you normally pass to the ffmpeg command in the terminal.
495+
* Documentation for individual options can be found in the [ffmpeg docs](https://ffmpeg.org/ffmpeg.html#Main-options).
496+
*
497+
* To specify a boolean option, set it to `true`.
498+
* To specify an option multiple times, use an array.
499+
* Options with nullish or `false` values are ignored.
500+
*
501+
* @example
502+
*
503+
* ```js
504+
* const options = { f: "image2", vcodec: "png" }
505+
* ```
506+
*
507+
* @typedef {Record<string, string | number | boolean | Array<string | null | undefined> | null | undefined>} ConverterPipeOptions
508+
*/
509+
510+
/**
511+
* Data about a single input or output of a {@link Converter}.
512+
*
513+
* @ignore
514+
* @internal
515+
* @typedef {Object} ConverterPipe
516+
* @property {"input" | "output"} type
517+
* @property {ConverterPipeOptions} options
518+
* @property {string} file
519+
* @property {() => Promise<void>} [onBegin]
520+
* @property {(process: import("node:child_process").ChildProcess) => void} [onSpawn]
521+
* @property {() => Promise<void>} [onFinish]
522+
*/

0 commit comments

Comments
 (0)