Summary
After a successful run populates the capability cache (codecs / encoders / formats / filters), calling setFfmpegPath(differentPath) only swaps the binary path — the cache stays. The next run silently uses stale capability data scraped from the previous binary, and any downstream _checkCapabilities check passes against information that no longer reflects reality.
In the worst case the new binary is a totally different ffmpeg build (e.g. a stripped-down statically-linked binary that lacks the codecs the cache claims are available); ffmpeg fails to spawn with an obscure error, while a fresh probe would have surfaced the problem cleanly.
Upstream issues
Reproduction
ffmpeg.setFfmpegPath("/usr/bin/ffmpeg");
await encodeOnce(); // populates cache.codecs, cache.formats, ...
ffmpeg.setFfmpegPath("/some/stripped/build/ffmpeg");
await encodeAgain(); // _checkCapabilities passes against stale data
// → ffmpeg actually crashes during exec
Fix plan
In lib/capabilities.ts setFfmpegPath and setFfprobePath, invalidate the parse caches by assigning undefined (not delete), so @typescript-eslint/no-dynamic-delete is not triggered. The existing readers test the parse caches via truthy check (if (cache.codecs), if (cache.filters) etc.), which return false for an undefined slot — semantically identical to the original delete-based reset for these particular keys.
proto.setFfmpegPath = function (this: FfmpegCommandThis, ffmpegPath: string) {
cache.ffmpegPath = ffmpegPath;
+ cache.codecs = undefined;
+ cache.encoders = undefined;
+ cache.formats = undefined;
+ cache.filters = undefined;
return this;
};
PathCache.codecs etc. are already declared ?: Record<string, …>, so undefined assignment is type-safe.
Note the existing path caches (ffmpegPath / ffprobePath / flvtoolPath) are NOT invalidated here — those are gated by 'X' in cache checks (line 294, 305, 325), where the difference between delete and undefined is observable. We keep those as-is; setFfmpegPath is re-setting cache.ffmpegPath rather than invalidating it, so the resolution check still reads the new path correctly.
setFfprobePath similarly invalidates only cache.codecs / cache.formats (less critical because the probe sidecar uses them less, but symmetrical).
Test plan
A unit test that:
- Calls
setFfmpegPath('/path/a'), then internally seeds cache.codecs, cache.formats, etc. via availableCodecs / availableFormats.
- Calls
setFfmpegPath('/path/b').
- Asserts
cache.codecs === undefined && cache.formats === undefined.
Caveat
Cache flushing on every path-set means consumers who set the path multiple times incur an extra capability query. Trade-off is correctness for ~50ms of one-time spawn — acceptable.
Summary
After a successful run populates the capability cache (codecs / encoders / formats / filters), calling
setFfmpegPath(differentPath)only swaps the binary path — the cache stays. The next run silently uses stale capability data scraped from the previous binary, and any downstream_checkCapabilitiescheck passes against information that no longer reflects reality.In the worst case the new binary is a totally different ffmpeg build (e.g. a stripped-down statically-linked binary that lacks the codecs the cache claims are available); ffmpeg fails to spawn with an obscure error, while a fresh probe would have surfaced the problem cleanly.
Upstream issues
Reproduction
Fix plan
In
lib/capabilities.tssetFfmpegPathandsetFfprobePath, invalidate the parse caches by assigningundefined(notdelete), so@typescript-eslint/no-dynamic-deleteis not triggered. The existing readers test the parse caches via truthy check (if (cache.codecs),if (cache.filters)etc.), which returnfalsefor anundefinedslot — semantically identical to the originaldelete-based reset for these particular keys.proto.setFfmpegPath = function (this: FfmpegCommandThis, ffmpegPath: string) { cache.ffmpegPath = ffmpegPath; + cache.codecs = undefined; + cache.encoders = undefined; + cache.formats = undefined; + cache.filters = undefined; return this; };PathCache.codecsetc. are already declared?: Record<string, …>, soundefinedassignment is type-safe.Note the existing path caches (
ffmpegPath/ffprobePath/flvtoolPath) are NOT invalidated here — those are gated by'X' in cachechecks (line 294, 305, 325), where the difference betweendeleteandundefinedis observable. We keep those as-is;setFfmpegPathis re-settingcache.ffmpegPathrather than invalidating it, so the resolution check still reads the new path correctly.setFfprobePathsimilarly invalidates onlycache.codecs / cache.formats(less critical because the probe sidecar uses them less, but symmetrical).Test plan
A unit test that:
setFfmpegPath('/path/a'), then internally seedscache.codecs,cache.formats, etc. viaavailableCodecs/availableFormats.setFfmpegPath('/path/b').cache.codecs === undefined && cache.formats === undefined.Caveat
Cache flushing on every path-set means consumers who set the path multiple times incur an extra capability query. Trade-off is correctness for ~50ms of one-time spawn — acceptable.