All files / src command.ts

69.81% Statements 74/106
72.72% Branches 16/22
80% Functions 4/5
69.81% Lines 74/106

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 1071x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 3x 1x 1x 2x 2x 2x 4x 5x             4x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 4x 1x 1x 3x 4x     4x 5x 5x 3x 3x 3x 5x 5x 3x 3x 3x 3x     3x 3x       3x 3x     3x 3x 3x 5x 5x                                   5x  
import { Readable, Writable } from 'node:stream'
import { FfmpegInput, InputDefinition } from './input'
import { FfmpegOutput, OutputDefinition } from './output'
import { FfmpegProcess, RunResult, RunOptions, ProcessOptions } from './process'
import { FilterGraph, generateFilterGraph } from './utils/filters'
 
export type CommandOptions = {
  input?: InputDefinition
  inputs?: InputDefinition[]
  output?: OutputDefinition
  outputs?: OutputDefinition[]
  complexFilters?: FilterGraph
  customArgs?: string[]
}
 
export class FfmpegCommand implements CommandOptions {
  inputs: FfmpegInput[]
  outputs: FfmpegOutput[]
  complexFilters?: FilterGraph
  customArgs: string[]
 
  constructor(options: CommandOptions) {
    if (options.input) {
      if (options.inputs) {
        throw new Error("Cannot specify both 'input' and 'inputs'")
      }
 
      options.inputs = [options.input]
    }
 
    if (options.output) {
      if (options.outputs) {
        throw new Error("Cannot specify both 'output' and 'outputs'")
      }

      options.outputs = [options.output]
    }
 
    this.inputs = (options.inputs || []).map(
      (inputOptions) => new FfmpegInput(inputOptions)
    )
 
    this.outputs = (options.outputs || []).map(
      (outputOptions) => new FfmpegOutput(outputOptions)
    )
 
    this.complexFilters = options.complexFilters
    this.customArgs = options.customArgs || []
 
    this.#validateIO()
  }
 
  #validateIO(): void {
    if (this.inputs.filter((i) => i.isStream).length > 1) {
      throw new Error(`At most one stream input is supported`)
    }
 
    if (this.outputs.filter((o) => o.isStream).length > 1) {
      throw new Error(`At most one stream output is supported`)
    }
  }
 
  getFfmpegArguments(): string[] {
    let args: string[] = []
 
    for (let input of this.inputs) {
      args.push(...input.getFfmpegArguments())
    }
 
    args.push(...this.customArgs)
 
    if (this.complexFilters) {
      args.push('-filter_complex', generateFilterGraph(this.complexFilters))
    }
 
    if (this.outputs.some((o) => o.isLocalFile)) {
      // Force overwrite outputs
      args.push('-y')
    }
 
    for (let output of this.outputs) {
      args.push(...output.getFfmpegArguments())
    }
 
    return args
  }
 
  run(options: RunOptions): Promise<RunResult> {
    let procOtions: ProcessOptions = {
      args: this.getFfmpegArguments(),
      ...options
    }

    let streamInput = this.inputs.find((i) => i.isStream)
    if (streamInput) {
      procOtions.inputStream = streamInput.source as Readable
    }

    let streamOutput = this.outputs.find((i) => i.isStream)
    if (streamOutput) {
      procOtions.outputStream = streamOutput.target as Writable
    }

    return new FfmpegProcess(procOtions).run()
  }
}