Refuse a spec that would leave the output with no tracks at all
Validation already refused two ways of asking for an empty file: None on both codec axes, and Copy for a video track the input does not have. It missed the third, because it read only the spec. Name H.265 with the audio off, hand it an MP3, and the spec looks fine — it names a video codec — while CopyPlanner drops that track anyway, because the *input* has no video to encode. The plan is (Drop, Drop), the router still says Media3, and EditedMediaItem.Builder refuses to build a composition with both tracks removed. It refuses it on Transformer's own HandlerThread, where the user sees the app die rather than a reason. Asking the probe as well as the spec catches all three faces with one guard, and the equivalence is exact rather than approximate: CopyPlanner drops video for None or for an input with none, and audio for None, so "(Drop, Drop)" and this condition are the same set. A sweep over every non-image container by codec by codec against both probes asserts that, so a new container or codec cannot reopen the gap on an axis nobody wrote a case for. This newly refuses a combination the Advanced picker accepts today, and that is the point: today it crashes. What it must not do is refuse without a way out. The Copy face had one only nominally — its single hand-built suggestion was None + None, which validation rejects in the next breath, so the one-tap fix fixed nothing. All three faces now go through the shared repair-and-filter path, which for an MP3 into MP4 offers "copy the audio across" and nothing that has to be re-refused. Repair is also stopped from naming a video codec for a file with no video track. It used to fall through to the first codec the container could encode, so the fix offered for an MP3 was "H.264" — a codec CopyPlanner then drops, making the offer a fiction that happened to validate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -129,9 +129,25 @@ object ContainerCapabilities {
|
||||
}
|
||||
}
|
||||
|
||||
if (spec.videoCodec == VideoCodec.NONE && spec.audioCodec == AudioCodec.NONE) {
|
||||
// Two faces of one rule: the output would carry no tracks at all.
|
||||
//
|
||||
// The first is visible in the spec alone — NONE on both axes. The second only emerges once
|
||||
// the spec meets the probe, because [CopyPlanner] drops a video track the *input* does not
|
||||
// have no matter which codec was named for it, so "H.265 + no audio" on an MP3 plans to
|
||||
// (Drop, Drop) exactly as "None + None" does. Asking the spec alone answered the first and
|
||||
// missed the second, and the miss was not cosmetic: `EditedMediaItem.Builder` refuses that
|
||||
// composition with IllegalStateException("Audio and video cannot both be removed"), on
|
||||
// Transformer's own thread, where the user would have seen a dead app rather than a reason.
|
||||
if (spec.audioCodec == AudioCodec.NONE && (spec.videoCodec == VideoCodec.NONE || !probe.hasVideo)) {
|
||||
return Validation.Invalid(
|
||||
"This would produce an empty file — keep at least one track.",
|
||||
if (spec.videoCodec == VideoCodec.NONE) {
|
||||
"This would produce an empty file — keep at least one track."
|
||||
} else {
|
||||
// Names both halves. "No video track" alone reads as though the video setting
|
||||
// were the only thing wrong, and the user would fix that and still be stuck.
|
||||
"This file has no video track, so turning the audio off too would produce an " +
|
||||
"empty file."
|
||||
},
|
||||
suggestions(
|
||||
// Ask for both tracks back, then let repair settle what this container and
|
||||
// this input can actually give.
|
||||
@@ -284,8 +300,12 @@ object ContainerCapabilities {
|
||||
private fun repairVideo(spec: OutputSpec, probe: InputProbe): VideoCodec {
|
||||
val container = spec.container
|
||||
if (spec.videoCodec == VideoCodec.NONE || !container.canHoldVideo) return VideoCodec.NONE
|
||||
// There is no video track to make one out of, so naming a codec would be a suggestion
|
||||
// [CopyPlanner] drops on the floor. It also read as a non-sequitur: before this line, the
|
||||
// repair offered for an MP3 was "H.264", the first codec MP4 happens to encode.
|
||||
if (!probe.hasVideo) return VideoCodec.NONE
|
||||
|
||||
val source = CodecNames.videoFromName(probe.videoCodec).takeIf { probe.hasVideo }
|
||||
val source = CodecNames.videoFromName(probe.videoCodec)
|
||||
val copyable = source != null && accepts(container, source, CodecMode.COPY)
|
||||
|
||||
return when {
|
||||
|
||||
Reference in New Issue
Block a user