ffmpeg
@alexkroman1/aai/ffmpeg — ffmpeg, callable from a step.
A FACADE. The subpath resolves here rather than at ffmpeg.ts, which buys two
things the direct form could not. That module can be SPLIT as it grows without
moving the published entry point — the path an implementation file happens to
have is not a thing to promise anyone — and a name it gains next reaches the
public surface only when a line is added below, rather than the moment it is
written.
Named re-exports rather than export * for the second half of that: the
wildcard form re-exports whatever arrives, and needs a noReExportAll
suppression the escape-hatch ratchet only lets move down.
Functions
Section titled “Functions”describeMedia()
Section titled “describeMedia()”describeMedia(
info):string
A probe as one phrase for a progress line — 41:20 of aac.
Duration and codec are both optional on a MediaInfo, and the phrase
degrades a field at a time rather than printing undefined of undefined:
41:20 when ffprobe measured a length but named no codec (a raw PCM file has
none), aac when the container declared no duration (a stream copy with no
index, or a non-faststart MP4 probed over a pipe — see probeMedia),
and the recording when it reported neither, so the sentence around it still
reads.
The codec is the first AUDIO stream’s, which is what a transcription step
means by “what is this file”; a video’s own codec is not the thing being
re-encoded. Duration comes from the container, rounded to the second by
formatDuration.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”string
Example
Section titled “Example”import { describeMedia, probeMedia } from "@alexkroman1/aai/ffmpeg";import { stepReport } from "@alexkroman1/aai/step";
const info = await probeMedia("/tmp/recording.m4a");await stepReport(`Re-encoding ${describeMedia(info)} to 16 kHz mono WAV.`);ffmpegBaseArgs()
Section titled “ffmpegBaseArgs()”ffmpegBaseArgs(
options?):string[]
The standing flags every ffmpeg invocation in a guest wants, before anything the caller is actually asking for.
Five spellings of this existed — four in templates, one here in transcodeToWav — and they disagreed on the two that matter:
-nostatsis not cosmetic. A failing run is diagnosed from the stderr this package captures, and it keeps only the lastFFMPEG_STDERR_TAIL_CHARSof it. ffmpeg writes a progress line several times a second, so on anything long the progress spam is what survives and the error that explains the failure is what gets evicted. Only one of the five passed it.-nostdinis about the runtime, not the job. In a guest there is no terminal, and an ffmpeg that decides to read stdin is a process that never exits. That is a fact about where this SDK runs, so it belongs here rather than in each caller’s argv.
-y overwrites the output without asking, which is right for both shapes a
step uses — a temp file it just named, or pipe:1.
loglevel defaults to "error". Pass "info" for a filter that reports
through the LOG rather than to a file — loudnorm’s print_format=json is
the case, and at error that pass runs, succeeds, and prints nothing.
ffprobe takes none of this. It rejects -nostdin and -nostats
outright, so probeMedia builds its own argv and this helper is for
ffmpeg only.
Parameters
Section titled “Parameters”options?
Section titled “options?”loglevel?
Section titled “loglevel?”string
Returns
Section titled “Returns”string[]
Example
Section titled “Example”import { ffmpegBaseArgs, runFfmpeg } from "@alexkroman1/aai/ffmpeg";
await runFfmpeg([...ffmpegBaseArgs(), "-i", "/tmp/in.m4a", "/tmp/out.wav"]);await runFfmpeg([...ffmpegBaseArgs({ loglevel: "info" }), "-i", "/tmp/in.wav", "-f", "null", "-"]);isFfmpegError()
Section titled “isFfmpegError()”isFfmpegError(
value):value is FfmpegError
Narrow an unknown catch to a failed ffmpeg run.
Parameters
Section titled “Parameters”unknown
Returns
Section titled “Returns”value is FfmpegError
probeMedia()
Section titled “probeMedia()”probeMedia(
source,options?):Promise<MediaInfo>
What ffprobe makes of a file: duration, container, and every stream.
import { probeMedia } from "@alexkroman1/aai/ffmpeg";
const info = await probeMedia("/tmp/recording.m4a");const seconds = info.durationSec ?? 0;const needsTranscode = info.audio?.codec !== "pcm_s16le";A field ffprobe did not report comes back undefined rather than zero — see
_ffmpeg-json.ts for why that distinction is load-bearing. Reading a
duration off a PIPE is the one case worth knowing about: for a format whose
duration lives in a trailing index, ffprobe cannot seek to it and answers
undefined, where the same file on disk answers exactly.
Parameters
Section titled “Parameters”source
Section titled “source”options?
Section titled “options?”Returns
Section titled “Returns”Promise<MediaInfo>
runFfmpeg()
Section titled “runFfmpeg()”runFfmpeg(
args,options?):Promise<FfmpegRunResult>
Run ffmpeg with args, exactly as given.
Resolves only on a zero exit; every other outcome is a FfmpegError naming its FfmpegFailureKind.
Parameters
Section titled “Parameters”readonly string[]
options?
Section titled “options?”Returns
Section titled “Returns”Promise<FfmpegRunResult>
Example
Section titled “Example”import { ffmpegBaseArgs, runFfmpeg } from "@alexkroman1/aai/ffmpeg";
// File to file: nothing is buffered, so this is the shape for long media.await runFfmpeg([ ...ffmpegBaseArgs(), "-i", "/tmp/in.m4a", "-ac", "1", "-ar", "16000", "-c:a", "pcm_s16le", "/tmp/out.wav",]);transcodeToWav()
Section titled “transcodeToWav()”transcodeToWav(
source,options?):Promise<Uint8Array<ArrayBufferLike>>
Re-encode anything ffmpeg can read into linear-PCM WAV bytes.
The conversion a transcription pipeline needs, because cutting a recording by byte offset is only arithmetic on uncompressed audio. Video is dropped.
The result is held in memory, so it is capped like any other piped output (64 MiB) — about an hour of 16 kHz mono at the default. Past that, go file → file with wavEncodeArgs.
Note WAV written to a PIPE carries a placeholder length in its header:
ffmpeg cannot seek back to patch it once the size is known. Every decoder
treats it as “read to EOF”, and this repo’s own parseWav intersects the
declared length with the real byte count for exactly that reason — but code
that trusts the header’s data size will read zero samples.
Parameters
Section titled “Parameters”source
Section titled “source”options?
Section titled “options?”Returns
Section titled “Returns”Promise<Uint8Array<ArrayBufferLike>>
wavEncodeArgs()
Section titled “wavEncodeArgs()”wavEncodeArgs(
options?):string[]
The encoder half of a linear-PCM WAV argv — no input, no output.
Exported because the in-memory transcodeToWav is the wrong shape for a long recording, and a caller writing file → file should not have to re-derive which of ffmpeg’s codec names is uncompressed:
import { runFfmpeg, wavEncodeArgs } from "@alexkroman1/aai/ffmpeg";
await runFfmpeg([ "-hide_banner", "-loglevel", "error", "-nostdin", "-y", "-i", inputPath, ...wavEncodeArgs({ sampleRate: 16_000, channels: 1 }), outputPath,]);Parameters
Section titled “Parameters”options?
Section titled “options?”Returns
Section titled “Returns”string[]
Classes
Section titled “Classes”FfmpegError
Section titled “FfmpegError”A failed ffmpeg run, with the diagnosis attached.
stderr is the tail of ffmpeg’s own log, which is where the reason is
(“Invalid data found when processing input”, “Output file #0 does not contain
any stream”). kind is what a caller BRANCHES on — see the module doc’s
point 3 for why a workflow step must, rather than retrying a corrupt file
until its attempts run out.
Extends
Section titled “Extends”Error
Constructors
Section titled “Constructors”Constructor
Section titled “Constructor”new FfmpegError(
options):FfmpegError
Parameters
Section titled “Parameters”options
Section titled “options”readonly string[]
binary
Section titled “binary”string
cause?
Section titled “cause?”unknown
exitCode?
Section titled “exitCode?”number | null
message
Section titled “message”string
signal?
Section titled “signal?”Signals | null
stderr?
Section titled “stderr?”string
Returns
Section titled “Returns”Overrides
Section titled “Overrides”Error.constructor
Properties
Section titled “Properties”
readonlyargv: readonlystring[]
binary
Section titled “binary”
readonlybinary:string
The binary that was spawned, and the arguments it got.
exitCode
Section titled “exitCode”
readonlyexitCode:number|null
Exit status, or null when the child was killed by a signal.
readonlykind:FfmpegFailureKind
signal
Section titled “signal”
readonlysignal:Signals|null
The signal that killed it, when one did.
stderr
Section titled “stderr”
readonlystderr:string
The tail of the child’s stderr — ffmpeg’s log.
Type Aliases
Section titled “Type Aliases”FfmpegFailureKind
Section titled “FfmpegFailureKind”FfmpegFailureKind =
"exit"|"timeout"|"aborted"|"missing-binary"|"output-too-large"
Which way a run failed — see FfmpegError.
FfmpegRunOptions
Section titled “FfmpegRunOptions”FfmpegRunOptions =
object
Properties
Section titled “Properties”binary?
Section titled “binary?”
optionalbinary?:string
The binary to spawn. Defaults to AAI_FFMPEG_PATH, FFMPEG_PATH, then ffmpeg.
optionalcwd?:string
Working directory for the child, so relative paths in args resolve.
maxOutputBytes?
Section titled “maxOutputBytes?”
optionalmaxOutputBytes?:number
Cap on captured stdout. Defaults to 64 MiB (DEFAULT_MAX_FFMPEG_OUTPUT_BYTES).
signal?
Section titled “signal?”
optionalsignal?:AbortSignal
Kill the run when this aborts. Combined with timeoutMs, not replaced by it.
stdin?
Section titled “stdin?”
optionalstdin?:Uint8Array
Bytes to write to the child’s stdin — read them in the argv as pipe:0.
timeoutMs?
Section titled “timeoutMs?”
optionaltimeoutMs?:number
Wall-clock budget. Defaults to 10 minutes (DEFAULT_FFMPEG_TIMEOUT_MS).
FfmpegRunResult
Section titled “FfmpegRunResult”FfmpegRunResult =
object
Properties
Section titled “Properties”durationMs
Section titled “durationMs”durationMs:
number
Wall-clock milliseconds the child ran for.
stderr
Section titled “stderr”stderr:
string
The tail of ffmpeg’s log, on SUCCESS too: it carries the encode summary.
stdout
Section titled “stdout”stdout:
Uint8Array
Whatever the child wrote to stdout — empty for a run that wrote to a file.
FfmpegSource
Section titled “FfmpegSource”FfmpegSource =
string|Uint8Array
A media input: a filesystem path, or the bytes themselves.
MediaInfo
Section titled “MediaInfo”MediaInfo =
object
What parseProbeJson makes of one media file — see @alexkroman1/aai/ffmpeg.
Properties
Section titled “Properties”audio?
Section titled “audio?”
optionalaudio?:MediaStreamInfo
The first audio stream — the one an audio pipeline almost always means.
bitRate?
Section titled “bitRate?”
optionalbitRate?:number
Overall bit rate in bits per second.
durationSec?
Section titled “durationSec?”
optionaldurationSec?:number
Duration in seconds, or undefined when the container does not say.
format?
Section titled “format?”
optionalformat?:string
ffprobe’s format name(s), e.g. "wav", "mov,mp4,m4a,3gp,3g2,mj2".
raw:
unknown
ffprobe’s parsed JSON, verbatim, for a field this type does not name.
sizeBytes?
Section titled “sizeBytes?”
optionalsizeBytes?:number
File size in bytes, as ffprobe measured it.
streams
Section titled “streams”streams:
MediaStreamInfo[]
Every stream, in ffprobe’s order.
video?
Section titled “video?”
optionalvideo?:MediaStreamInfo
The first video stream.
MediaStreamInfo
Section titled “MediaStreamInfo”MediaStreamInfo =
object
One elementary stream inside a container.
Properties
Section titled “Properties”channels?
Section titled “channels?”
optionalchannels?:number
Channel count (audio).
codec?
Section titled “codec?”
optionalcodec?:string
Decoder name, e.g. "pcm_s16le", "aac", "h264".
durationSec?
Section titled “durationSec?”
optionaldurationSec?:number
Stream duration in seconds, when the container declares a per-stream one.
height?
Section titled “height?”
optionalheight?:number
index:
number
ffprobe’s own stream index — what -map 0:<index> names.
kind:
string
"audio", "video", "subtitle", "data", …
sampleFormat?
Section titled “sampleFormat?”
optionalsampleFormat?:string
Sample format, e.g. "s16", "fltp" (audio).
sampleRate?
Section titled “sampleRate?”
optionalsampleRate?:number
Samples per second (audio).
width?
Section titled “width?”
optionalwidth?:number
Pixel dimensions (video).
ProbeOptions
Section titled “ProbeOptions”ProbeOptions =
Omit<FfmpegRunOptions,"stdin"|"binary"> &object
Type Declaration
Section titled “Type Declaration”binary?
Section titled “binary?”
optionalbinary?:string
The ffprobe binary. Defaults to AAI_FFPROBE_PATH, FFPROBE_PATH, then ffprobe.
TranscodeToWavOptions
Section titled “TranscodeToWavOptions”TranscodeToWavOptions =
WavEncodeOptions&Omit<FfmpegRunOptions,"stdin">
WavEncodeOptions
Section titled “WavEncodeOptions”WavEncodeOptions =
object
Properties
Section titled “Properties”bitsPerSample?
Section titled “bitsPerSample?”
optionalbitsPerSample?:16|24|32
Sample width, 16 or 24 or 32 bits. Defaults to 16.
channels?
Section titled “channels?”
optionalchannels?:number
Output channel count. Omit to keep the input’s. 1 is what STT wants.
sampleRate?
Section titled “sampleRate?”
optionalsampleRate?:number
Output sample rate. Omit to keep the input’s.