• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fluent-ffmpeg / node-fluent-ffmpeg / 6889204749

16 Nov 2023 10:07AM UTC coverage: 84.691% (-6.1%) from 90.801%
6889204749

Pull #1235

github

web-flow
Merge a982927b9 into 4e02d1257
Pull Request #1235: V3 experiments

257 of 296 branches covered (0.0%)

1040 of 1228 new or added lines in 15 files covered. (84.69%)

1040 of 1228 relevant lines covered (84.69%)

8.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.86
/src/process.ts
1
import { spawn } from 'node:child_process'
3!
2

3✔
3
import { isWindows } from './utils/platform'
3✔
4
import {
3✔
5
  extractErrorMessage,
3✔
6
  extractProgress,
3✔
7
  CodecDataExtractor
3✔
8
} from './utils/parsing'
3✔
9
import LineBuffer from './utils/line-buffer'
3✔
10
import { InputCodecInformation, ProgressInformation } from './utils/data-types'
3✔
11
import { Readable, Writable } from 'node:stream'
3✔
12

3✔
13
export type RunResult = {
3✔
14
  stderr: string
3✔
15
  stdout: string
3✔
16
}
3✔
17

3✔
18
export type RunOptions = {
3✔
19
  nice?: number
3✔
20
  cwd?: string
3✔
21
  timeout?: number
3✔
22
  onProgress?: (progress: ProgressInformation) => void
3✔
23
  onCodecData?: (data: InputCodecInformation) => void
3✔
24
  onStderr?: (line: string) => void
3✔
25
}
3✔
26

3✔
27
export type ProcessOptions = RunOptions & {
3✔
28
  args: string[]
3✔
29
  captureStdout?: boolean
3✔
30
  inputStream?: Readable
3✔
31
  outputStream?: Writable
3✔
32
}
3✔
33

3✔
34
export class FfmpegProcess implements ProcessOptions {
21✔
35
  args: string[]
21✔
36
  nice?: number
21✔
37
  cwd?: string
21✔
38
  timeout?: number
21✔
39
  captureStdout?: boolean
21✔
40
  inputStream?: Readable
21✔
41
  outputStream?: Writable
21✔
42
  onProgress?: (progress: ProgressInformation) => void
21✔
43
  onCodecData?: (data: InputCodecInformation) => void
21✔
44
  onStderr?: (line: string) => void
21✔
45

21✔
46
  constructor(options: ProcessOptions) {
21✔
47
    this.args = options.args
21✔
48
    this.nice = options.nice
21✔
49
    this.cwd = options.cwd
21✔
50
    this.timeout = options.timeout
21✔
51
    this.captureStdout = options.captureStdout
21✔
52
    this.inputStream = options.inputStream
21✔
53
    this.outputStream = options.outputStream
21✔
54
    this.onProgress = options.onProgress
21✔
55
    this.onCodecData = options.onCodecData
21✔
56
    this.onStderr = options.onStderr
21✔
57

21✔
58
    this.#validateOptions()
21✔
59
  }
21✔
60

21✔
61
  #validateOptions() {
21✔
62
    if (this.outputStream && this.captureStdout) {
21!
NEW
63
      throw new Error(
×
NEW
64
        "Cannot use 'captureStdout' when a stream output is present"
×
NEW
65
      )
×
NEW
66
    }
×
67
  }
21✔
68

21✔
69
  run(callback?: (err: any, result?: any) => any): Promise<RunResult> {
21✔
70
    let cmd = process.env.FFMPEG_PATH || 'ffmpeg'
21✔
71
    let args: string[] = [...this.args]
21✔
72

21✔
73
    let { onProgress, onCodecData, onStderr } = this
21✔
74

21✔
75
    if (this.nice && this.nice !== 0 && !isWindows) {
21!
76
      args = ['-n', this.nice.toString(), cmd, ...args]
1✔
77
      cmd = 'nice'
1✔
78
    }
1✔
79

21✔
80
    let promise: Promise<RunResult> = new Promise((resolve, reject) => {
21✔
81
      let child = spawn(cmd, args, {
21✔
82
        cwd: this.cwd,
21✔
83
        timeout: this.timeout,
21✔
84
        windowsHide: true
21✔
85
      })
21✔
86

21✔
87
      let stderr = new LineBuffer()
21✔
88
      let stdout = new LineBuffer()
21✔
89

21✔
90
      if (onStderr) {
21!
91
        stderr.on('line', onStderr)
2✔
92
      }
2✔
93

21✔
94
      if (onProgress) {
21!
95
        stderr.on('line', (line: string) => {
2✔
96
          let progress = extractProgress(line)
10✔
97
          if (progress) {
10✔
98
            onProgress?.(progress)
4✔
99
          }
4✔
100
        })
2✔
101
      }
2✔
102

21✔
103
      if (onCodecData) {
21!
104
        let extractor = new CodecDataExtractor(onCodecData)
2✔
105
        stderr.on('line', (line: string) => {
2✔
106
          if (!extractor.done) {
22✔
107
            extractor.processLine(line)
14✔
108
          }
14✔
109
        })
2✔
110
      }
2✔
111

21✔
112
      child.on('error', (err) => reject(err))
21✔
113

21✔
114
      child.on('close', (code, signal) => {
21✔
115
        stderr.close()
19✔
116
        stdout.close()
19✔
117

19✔
118
        if (signal) {
19!
119
          reject(new Error(`ffmpeg was killed with signal ${signal}`))
1✔
120
        } else if (code) {
19!
121
          let message = `ffmpeg exited with code ${code}`
2✔
122
          let error = extractErrorMessage(stderr.lines)
2✔
123

2✔
124
          if (error) {
2✔
125
            message = `${message}:\n${error}`
1✔
126
          }
1✔
127

2✔
128
          reject(new Error(message))
2✔
129
        } else {
18✔
130
          resolve({
16✔
131
            stdout: stdout.toString(),
16✔
132
            stderr: stderr.toString()
16✔
133
          })
16✔
134
        }
16✔
135
      })
21✔
136

21✔
137
      if (this.inputStream) {
21!
NEW
138
        this.inputStream.pipe(child.stdin)
×
NEW
139
        this.inputStream.on('error', (err) => {
×
NEW
140
          // TODO make a specific error type
×
NEW
141
          reject(err)
×
NEW
142

×
NEW
143
          child.kill()
×
NEW
144
        })
×
NEW
145

×
NEW
146
        // Prevent stdin errors from bubbling up, ffmpeg will crash anyway
×
NEW
147
        child.stdin.on('error', () => {})
×
NEW
148
      }
×
149

21✔
150
      child.stderr.on('data', (data) => stderr.append(data.toString()))
21✔
151

21✔
152
      if (this.outputStream) {
21!
NEW
153
        child.stdout.pipe(this.outputStream)
×
NEW
154

×
NEW
155
        this.outputStream.on('error', (err) => {
×
NEW
156
          // TODO make a specific error type
×
NEW
157
          reject(err)
×
NEW
158

×
NEW
159
          child.kill()
×
NEW
160
        })
×
161
      } else if (this.captureStdout) {
21!
162
        child.stdout.on('data', (data) => stdout.append(data.toString()))
2✔
163
      }
2✔
164
    })
21✔
165

21✔
166
    if (callback) {
21!
167
      promise.then(
2✔
168
        (value) => callback(null, value),
2✔
169
        (reason) => callback(reason)
2✔
170
      )
2✔
171
    }
2✔
172

21✔
173
    return promise
21✔
174
  }
21✔
175
}
21✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc