• 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

73.1
/src/utils/parsing.ts
1
import {
3!
2
  FfmpegCodec,
3✔
3
  FfmpegCodecType,
3✔
4
  FfmpegCodecs,
3✔
5
  FfmpegEncoderType,
3✔
6
  FfmpegEncoders,
3✔
7
  FfmpegFilterStreamType,
3✔
8
  FfmpegFilters,
3✔
9
  FfmpegFormats,
3✔
10
  InputCodecInformation,
3✔
11
  InputStreamCodecInformation,
3✔
12
  ProgressInformation
3✔
13
} from './data-types'
3✔
14

3✔
15
import {
3✔
16
  capCodecDecodersRegexp,
3✔
17
  capCodecEncodersRegexp,
3✔
18
  capCodecRegexp,
3✔
19
  capEncoderRegexp,
3✔
20
  capFilterRegexp,
3✔
21
  capFormatRegexp,
3✔
22
  codecAudioRegexp,
3✔
23
  codecDurRegexp,
3✔
24
  codecEndRegexp,
3✔
25
  codecInputRegexp,
3✔
26
  codecOutputRegexp,
3✔
27
  codecVideoRegexp,
3✔
28
  durationRegexp
3✔
29
} from './regexp'
3✔
30

3✔
31
/**
3✔
32
 * Parse a ffmpeg duration
3✔
33
 *
3✔
34
 * @param duration Duration as [[HH:]MM:]ss[.mmm]
3✔
35
 * @returns parsed duration in seconds
3✔
36
 */
3✔
NEW
37
export function parseDuration(duration: string): number {
×
NEW
38
  let match = duration.match(durationRegexp)
×
NEW
39
  if (!match) {
×
NEW
40
    throw new Error(`Invalid duration: ${duration}`)
×
NEW
41
  }
×
NEW
42

×
NEW
43
  let totalSeconds = 0
×
NEW
44

×
NEW
45
  let [, hours, minutes, seconds, milliseconds] = match
×
NEW
46

×
NEW
47
  if (hours !== undefined) {
×
NEW
48
    totalSeconds += Number(hours) * 3600
×
NEW
49
  }
×
NEW
50

×
NEW
51
  if (minutes !== undefined) {
×
NEW
52
    totalSeconds += Number(minutes) * 60
×
NEW
53
  }
×
NEW
54

×
NEW
55
  if (seconds !== undefined) {
×
NEW
56
    totalSeconds += Number(seconds)
×
NEW
57
  }
×
NEW
58

×
NEW
59
  if (milliseconds !== undefined) {
×
NEW
60
    totalSeconds += Number(milliseconds) / 1000
×
NEW
61
  }
×
NEW
62

×
NEW
63
  return totalSeconds
×
NEW
64
}
×
65

3✔
66
/**
3✔
67
 * Extract an error message from ffmpeg stderr
3✔
68
 *
3✔
69
 * @param stderrLines stderr output from ffmpeg as an array of lines
3✔
70
 * @returns error message
3✔
71
 */
3✔
72
export function extractErrorMessage(stderrLines: string[]): string {
1✔
73
  // Return the last block of lines that don't start with a space or square bracket
1✔
74
  return stderrLines
1✔
75
    .reduce((messages: string[], message: string): string[] => {
1✔
76
      if (message.charAt(0) === ' ' || message.charAt(0) === '[') {
7✔
77
        return []
3✔
78
      } else {
7✔
79
        messages.push(message)
4✔
80
        return messages
4✔
81
      }
4✔
82
    }, [])
1✔
83
    .join('\n')
1✔
84
}
1✔
85

3✔
86
/**
3✔
87
 * Extract progress information from ffmpeg stderr
3✔
88
 *
3✔
89
 * @param stderrLine a line from ffmpeg stderr
3✔
90
 * @returns progress information
3✔
91
 */
3✔
92
export function extractProgress(
12✔
93
  stderrLine: string
12✔
94
): ProgressInformation | undefined {
12✔
95
  let parts = stderrLine.replace(/=\s+/g, '=').trim().split(' ')
12✔
96
  let progress: ProgressInformation = {}
12✔
97

12✔
98
  for (let part of parts) {
12✔
99
    let [key, value] = part.split('=', 2)
35✔
100

35✔
101
    if (value === undefined) {
35✔
102
      // Not a progress line
7✔
103
      return
7✔
104
    }
7✔
105

28✔
106
    if (key === 'frame' || key === 'fps') {
35✔
107
      progress[key] = Number(value)
10✔
108
    } else if (key === 'bitrate') {
35✔
109
      progress.bitrate = Number(value.replace('kbits/s', ''))
5✔
110
    } else if (key === 'size' || key === 'Lsize') {
18✔
111
      progress.size = Number(value.replace('kB', ''))
3✔
112
    } else if (key === 'time') {
13✔
113
      progress.time = value
5✔
114
    } else if (key === 'speed') {
5✔
115
      progress.speed = Number(value.replace('x', ''))
5✔
116
    }
5✔
117
  }
35✔
118

5✔
119
  return progress
5✔
120
}
5✔
121

3✔
122
// TODO better output for multiple inputs / multi-stream inputs !
3✔
123
export class CodecDataExtractor {
3✔
124
  inputs: InputStreamCodecInformation[]
3✔
125
  index: number
3✔
126
  inInput: boolean
3✔
127
  done: boolean
3✔
128
  callback: (data: InputCodecInformation) => any
3✔
129

3✔
130
  constructor(callback: (data: InputCodecInformation) => any) {
3✔
131
    this.inputs = []
3✔
132
    this.index = -1
3✔
133
    this.inInput = false
3✔
134
    this.done = false
3✔
135
    this.callback = callback
3✔
136
  }
3✔
137

3✔
138
  processLine(line: string) {
3✔
139
    let matchFormat = line.match(codecInputRegexp)
25✔
140
    if (matchFormat) {
25✔
141
      this.inInput = true
3✔
142
      this.index++
3✔
143
      this.inputs[this.index] = {
3✔
144
        format: matchFormat[1]
3✔
145
      }
3✔
146

3✔
147
      return
3✔
148
    }
3✔
149

22✔
150
    if (this.inInput) {
25✔
151
      let durationMatch = line.match(codecDurRegexp)
18✔
152
      if (durationMatch) {
18✔
153
        this.inputs[this.index].duration = durationMatch[1]
3✔
154
        return
3✔
155
      }
3✔
156

15✔
157
      let audioMatch = line.match(codecAudioRegexp)
15✔
158
      if (audioMatch) {
18✔
159
        this.inputs[this.index].audio = audioMatch[1].split(', ')[0]
3✔
160
        this.inputs[this.index].audioDetails = audioMatch[1]
3✔
161
        return
3✔
162
      }
3✔
163

12✔
164
      let videoMatch = line.match(codecVideoRegexp)
12✔
165
      if (videoMatch) {
18✔
166
        this.inputs[this.index].video = videoMatch[1].split(', ')[0]
3✔
167
        this.inputs[this.index].videoDetails = videoMatch[1]
3✔
168
        return
3✔
169
      }
3✔
170
    }
18✔
171

13✔
172
    if (codecOutputRegexp.test(line)) {
25!
173
      this.inInput = false
1✔
174
    }
1✔
175

13✔
176
    if (codecEndRegexp.test(line)) {
25✔
177
      this.done = true
4✔
178
      let { callback } = this
4✔
179

4✔
180
      callback(this.inputs)
4✔
181
    }
4✔
182
  }
25✔
183
}
3✔
184

3✔
185
function parseCodecType(type: string): FfmpegCodecType {
5✔
186
  if (type === 'A') return 'audio'
5✔
187
  if (type === 'V') return 'video'
5✔
188
  if (type === 'S') return 'subtitle'
5✔
189
  if (type === 'D') return 'data'
5✔
190
  return 'attachment'
1✔
191
}
1✔
192

3✔
193
export function extractCodecs(lines: string[]): FfmpegCodecs {
1✔
194
  let codecs: FfmpegCodecs = {}
1✔
195

1✔
196
  for (let line of lines) {
1✔
197
    let match = line.match(capCodecRegexp)
7✔
198
    if (match) {
7✔
199
      let [, decode, encode, type, intra, lossy, lossless, name, description] =
5✔
200
        match
5✔
201

5✔
202
      let codec: FfmpegCodec = {
5✔
203
        description,
5✔
204
        type: parseCodecType(type),
5✔
205
        canEncode: encode === 'E',
5✔
206
        canDecode: decode === 'D',
5✔
207
        intraFrame: intra === 'I',
5✔
208
        lossy: lossy === 'L',
5✔
209
        lossless: lossless === 'S'
5✔
210
      }
5✔
211

5✔
212
      if (decode === 'D') {
5✔
213
        let decoders = description.match(capCodecDecodersRegexp)
3✔
214
        if (decoders) {
3✔
215
          codec.decoders = decoders[1].trim().split(' ')
1✔
216
          codec.description = codec.description
1✔
217
            .replace(capCodecDecodersRegexp, '')
1✔
218
            .trim()
1✔
219
        }
1✔
220
      }
3✔
221

5✔
222
      if (encode === 'E') {
5✔
223
        let encoders = description.match(capCodecEncodersRegexp)
3✔
224
        if (encoders) {
3✔
225
          codec.encoders = encoders[1].trim().split(' ')
1✔
226
          codec.description = codec.description
1✔
227
            .replace(capCodecEncodersRegexp, '')
1✔
228
            .trim()
1✔
229
        }
1✔
230
      }
3✔
231

5✔
232
      codecs[name] = codec
5✔
233
    }
5✔
234
  }
7✔
235

1✔
236
  return codecs
1✔
237
}
1✔
238

3✔
239
export function extractFormats(lines: string[]): FfmpegFormats {
1✔
240
  let formats: FfmpegFormats = {}
1✔
241

1✔
242
  for (let line of lines) {
1✔
243
    let match = line.match(capFormatRegexp)
5✔
244
    if (match) {
5✔
245
      let [, demux, mux, name, description] = match
3✔
246
      formats[name] = {
3✔
247
        description,
3✔
248
        canMux: mux === 'E',
3✔
249
        canDemux: demux === 'D'
3✔
250
      }
3✔
251
    }
3✔
252
  }
5✔
253

1✔
254
  return formats
1✔
255
}
1✔
256

3✔
NEW
257
function parseFilterStreams(
×
NEW
258
  streams: string
×
NEW
259
): FfmpegFilterStreamType[] | 'dynamic' {
×
NEW
260
  if (streams === '|') {
×
NEW
261
    return []
×
NEW
262
  } else if (streams === 'N') {
×
NEW
263
    return 'dynamic'
×
NEW
264
  } else {
×
NEW
265
    return [...streams].map((s) => (s === 'A' ? 'audio' : 'video'))
×
NEW
266
  }
×
NEW
267
}
×
268

3✔
NEW
269
export function extractFilters(lines: string[]): FfmpegFilters {
×
NEW
270
  let filters: FfmpegFilters = {}
×
NEW
271

×
NEW
272
  for (let line of lines) {
×
NEW
273
    let match = line.match(capFilterRegexp)
×
NEW
274
    if (match) {
×
NEW
275
      let [, timeline, slice, command, name, inputs, outputs, description] =
×
NEW
276
        match
×
NEW
277

×
NEW
278
      filters[name] = {
×
NEW
279
        description,
×
NEW
280
        inputs: parseFilterStreams(inputs),
×
NEW
281
        outputs: parseFilterStreams(outputs)
×
NEW
282
      }
×
NEW
283
    }
×
NEW
284
  }
×
NEW
285

×
NEW
286
  return filters
×
NEW
287
}
×
288

3✔
NEW
289
function parseEncoderType(type: string): FfmpegEncoderType {
×
NEW
290
  if (type === 'A') return 'audio'
×
NEW
291
  if (type === 'V') return 'video'
×
NEW
292
  return 'subtitle'
×
NEW
293
}
×
294

3✔
NEW
295
export function extractEncoders(lines: string[]): FfmpegEncoders {
×
NEW
296
  let encoders: FfmpegEncoders = {}
×
NEW
297

×
NEW
298
  for (let line of lines) {
×
NEW
299
    let match = line.match(capEncoderRegexp)
×
NEW
300
    if (match) {
×
NEW
301
      let [, type, frame, slice, exp, band, direct, name, description] = match
×
NEW
302

×
NEW
303
      encoders[name] = {
×
NEW
304
        description,
×
NEW
305
        type: parseEncoderType(type),
×
NEW
306
        frameMultithreading: frame === 'F',
×
NEW
307
        sliceMultithreading: slice === 'S',
×
NEW
308
        experimental: exp === 'X',
×
NEW
309
        drawHorizBand: band === 'B',
×
NEW
310
        directRendering: direct === 'D'
×
NEW
311
      }
×
NEW
312
    }
×
NEW
313
  }
×
NEW
314

×
NEW
315
  return encoders
×
NEW
316
}
×
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