fix(mp4): refuse a video track with no resolved dimensions
Resolution::pixels() returned (0, 0) for Unknown, and the MP4 sink wrote it verbatim into tkhd (ISO/IEC 14496-12 8.3.2) and VisualSampleEntry (12.1.3). Both fields are MANDATORY there, so unlike Matroska — which omits the optional PixelWidth/PixelHeight elements — MP4 has nothing to leave out. The result was a structurally complete file that passes every container check, declares a 0x0 video track, cannot be rendered, and is written with no error anywhere. WHY IT WAS POSSIBLE, which is the part worth keeping: pixels() previously fabricated 1920x1080 for Unknown. That was wrong but playable, so this sink never needed a guard and the absence of one was invisible. Changing the sentinel to (0, 0) moved the defect instead of removing it — a zero PAIR still reads as a usable value, so the sink stored it and serialised it. The accessor's doc comment then ENUMERATED the callers it believed were safe: "the Matroska sink omits the optional elements, the VobSub writer omits its size: line, and no caller divides by either dimension." Two of those three are true. MP4 was not on the list because MP4 has no guard at all, and a prose list cannot enforce itself. mkv.rs's own comment even states the principle — "the check belongs in the one accessor rather than in each caller that remembered to write it" — and labels/mod.rs still carried its own duplicate Unknown test long after the accessor took that job over. So: pixels() now returns Option. Not because Option is tidier, but because every caller genuinely needs a DIFFERENT answer and the compiler is the only thing that reliably makes them choose one. Matroska and the metadata sinks take unwrap_or((0, 0)) with the reason stated at each site; the VobSub path degrades to a palette-only .idx; MP4 fails with E_MP4_UNKNOWN_RESOLUTION (9055). Six call sites, not the five my first grep showed — I piped it through `head` and acted on a truncated list. The compiler caught the sixth. That is the same mistake as trusting a lens that reported silence.
This commit is contained in:
+15
-3
@@ -207,6 +207,12 @@ pub const E_MP4_INVALID: u16 = 9049;
|
||||
/// `mp4://` video track is missing its codec-configuration record
|
||||
/// (`hvcC`/`avcC`), without which the sample entry can't be written.
|
||||
pub const E_MP4_MISSING_CODEC_PRIVATE: u16 = 9050;
|
||||
/// `mp4://` video track has no resolved frame dimensions. ISO/IEC 14496-12
|
||||
/// makes width and height mandatory in both `tkhd` (8.3.2) and
|
||||
/// VisualSampleEntry (12.1.3), so unlike Matroska there is no element to omit:
|
||||
/// the sink would have to write 0x0, producing a structurally complete file no
|
||||
/// player can render. Refuse instead.
|
||||
pub const E_MP4_UNKNOWN_RESOLUTION: u16 = 9055;
|
||||
/// READ CAPACITY returned a short or overflowing transfer.
|
||||
pub const E_DISC_CAPACITY_MALFORMED: u16 = 9047;
|
||||
|
||||
@@ -548,6 +554,9 @@ pub enum Error {
|
||||
Mp4Invalid,
|
||||
/// `mp4://` video track is missing its `hvcC`/`avcC` configuration record.
|
||||
Mp4MissingCodecPrivate,
|
||||
/// `mp4://` video track has no resolved frame dimensions. See
|
||||
/// [`E_MP4_UNKNOWN_RESOLUTION`].
|
||||
Mp4UnknownResolution,
|
||||
PesFrameTooLarge {
|
||||
size: usize,
|
||||
},
|
||||
@@ -737,6 +746,7 @@ impl Error {
|
||||
Error::Mp4NoVideoTrack => E_MP4_NO_VIDEO_TRACK,
|
||||
Error::Mp4Invalid => E_MP4_INVALID,
|
||||
Error::Mp4MissingCodecPrivate => E_MP4_MISSING_CODEC_PRIVATE,
|
||||
Error::Mp4UnknownResolution => E_MP4_UNKNOWN_RESOLUTION,
|
||||
Error::PesFrameTooLarge { .. } => E_PES_FRAME_TOO_LARGE,
|
||||
Error::PesInvalidMagic => E_PES_INVALID_MAGIC,
|
||||
Error::PesTrackTooLarge { .. } => E_PES_TRACK_TOO_LARGE,
|
||||
@@ -992,9 +1002,10 @@ impl From<Error> for std::io::Error {
|
||||
// mp4:// demux errors: a malformed/truncated source file
|
||||
// (E_MP4_INVALID), or a source whose tracks the mux can't use — no
|
||||
// video track / missing codec-private config. All are invalid data.
|
||||
E_MP4_NO_VIDEO_TRACK | E_MP4_INVALID | E_MP4_MISSING_CODEC_PRIVATE => {
|
||||
std::io::ErrorKind::InvalidData
|
||||
}
|
||||
E_MP4_NO_VIDEO_TRACK
|
||||
| E_MP4_INVALID
|
||||
| E_MP4_MISSING_CODEC_PRIVATE
|
||||
| E_MP4_UNKNOWN_RESOLUTION => std::io::ErrorKind::InvalidData,
|
||||
// 9030 ExtentNotUnitAligned: a malformed/non-AACS-aligned
|
||||
// extent was handed to the prefetch producer.
|
||||
9030 => std::io::ErrorKind::InvalidInput,
|
||||
@@ -1696,6 +1707,7 @@ mod tests {
|
||||
(Error::Mp4NoVideoTrack, E_MP4_NO_VIDEO_TRACK),
|
||||
(Error::Mp4Invalid, E_MP4_INVALID),
|
||||
(Error::Mp4MissingCodecPrivate, E_MP4_MISSING_CODEC_PRIVATE),
|
||||
(Error::Mp4UnknownResolution, E_MP4_UNKNOWN_RESOLUTION),
|
||||
(Error::M2tsPacketMalformed, E_M2TS_PACKET_MALFORMED),
|
||||
(Error::ExtentNotUnitAligned, E_EXTENT_NOT_UNIT_ALIGNED),
|
||||
(Error::DiscCapacityMalformed, E_DISC_CAPACITY_MALFORMED),
|
||||
|
||||
Reference in New Issue
Block a user