CI on the parent branch proved that four of the five containers the router
claimed for Media3 cannot be written by Transformer at all: WebmMuxer, OggMuxer,
WavMuxer and AacMuxer each throw UnsupportedOperationException from
addMetadataEntry, which MuxerWrapper calls for every metadata entry on the track
format.
Consequences here beyond the merge itself:
- MEDIA3_MUXABLE_VIDEO and MEDIA3_MUXABLE_AUDIO drop to a single MP4 entry.
Every other container is already on its way to FFmpeg before those maps are
consulted.
- Reason.WEBM_CODEC_UNSUPPORTED is removed. WebM now fails the container check
first, so nothing could ever produce that reason, and a routing reason no code
path can reach is worse than no reason at all.
- Media3Muxers gains null branches for the six containers this branch adds. MOV
is among them despite being MP4's own family: Mp4Muxer exposes no QuickTime
file format.
- The README no longer claims Media3 writes five containers.
The remux behaviour this branch exists for is unaffected: MKV -> MP4 was always
the hardware direction, because Media3 reads Matroska but has never been able to
write it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
OutputFormat was a closed enum of twelve (container, videoCodec, audioCodec)
triples, defended on the grounds that a closed set was what made routing
decidable. Two things it could not express: changing the container while copying
the streams, and choosing codecs per track.
OutputSpec replaces it as the vocabulary; OutputFormat stays as presets over it.
Decidability moves to ContainerCapabilities, which is explicit and unit-tested
rather than implicit in whichever combinations somebody enumerated.
The matrix is indexed by (container, codec, trackType, mode), not one boolean.
"Can MP4 carry AV1" and "can this app encode AV1" have different answers, and
copy is where the difference shows: a single flag would refuse a legitimate
remux or promise an encode neither engine can deliver.
COPY is a codec value rather than a flag, so every exhaustive `when` in the
codebase had to say what it does about copying. CopyPlanner resolves it before
anything else reads the request, and inherits ConcatPlanner's rule that an
unproven match is never a copy — a needless re-encode costs time, a wrong stream
copy costs a file that will not play.
Container now drives -f, the extension and the SAF MIME type, so Matroska
without video is .mka and MP4 without video is .m4a without a preset for each.
FLAC was declared as Container.MKV with a .flac extension, inert only while
nothing read the container; it now has its own. Six containers added: MOV, MKV
audio, MPEG-TS, AVI, FLV and WMV/ASF.
Routing asks the plan, never the request. COPY belongs to none of the capability
sets, so testing the request directly sends every remux to FFmpeg on the first
check — and nothing notices, because -c copy produces a correct file, just on
the CPU. The router also learns what Media3 can *carry* as opposed to encode:
its MP4 muxer takes AAC, Opus, Vorbis and PCM but neither MP3 nor FLAC.
MediaProbe now separates "no video track" from "could not parse" and reports the
source container, which MediaExtractor cannot supply at all. FFprobe runs on
every pick for that reason, not as a fallback.
The Advanced picker shows the whole matrix and lets an impossible combination be
selected on purpose, then explains it and offers alternatives. Convert is what
blocks the job. ConversionWorker validates too, so a stale queued spec fails with
the reason rather than being coerced into something else.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>