Harden mux + decrypt paths; fail-loud on unresolvable keys
mp4 demuxer (untrusted input): bound every allocation sized from a box field (stsz/stco/stsc counts, stts/ctts run-lengths, per-sample and moov sizes, plus an absolute cap so a sparse file can't inflate file_len); guard the parse_stsd slice and a zero mdhd timescale; cap track count so the per-track PID can't overflow; rewrite read_moov to handle size==0 / size<8 / 64-bit largesize; parse esds/AudioSpecificConfig for AAC; write tkhd duration in the movie timescale. decrypt: resolve_mux_key_map now fails loud on an extent no key can classify instead of inheriting the previous extent's key, so a keymap never silently carries a wrong key; the sweep/patch key-fetch recovery fails loud when a unit is still unresolved after the retry. AACS: reject inverted forensic segments in both range builders; compare the forensic index in u16 space so an out-of-range value can't truncate onto a valid u8 index. RECOVERED_ERROR no longer latches the damage zone, preserving the 30s wedge cooldown for a following hard error. audio: AAC/MP2/MP3/FLAC carry the last PTS across a PES with no timestamp; the DTS-HD extension-sync search is bounded to after the core; the MP4 16.16 sample-rate field saturates. demux_sink records the video reference before the kind filter so audio:// / sub:// keep multi-clip PTS continuity and the DELAY tag. Remove a dead error variant and the AACS-unsupported-video code; codec comments cite the primary format specs; assorted doc/naming fixes and regression tests throughout.
This commit is contained in:
+22
-5
@@ -34,16 +34,28 @@ fn chapters_vtt(chapters: &[Chapter]) -> String {
|
||||
let mut s = String::from("WEBVTT\n\n");
|
||||
for (i, c) in chapters.iter().enumerate() {
|
||||
let start = c.time_secs.max(0.0);
|
||||
// Each cue runs until the next chapter. WebVTT drops a cue whose end is not
|
||||
// strictly after its start, so the last chapter (and any degenerate
|
||||
// equal-timestamp pair) gets a 1 s minimum duration rather than being lost.
|
||||
let end = chapters
|
||||
.get(i + 1)
|
||||
.map(|n| n.time_secs.max(0.0))
|
||||
.unwrap_or(start);
|
||||
.filter(|&e| e > start)
|
||||
.unwrap_or(start + 1.0);
|
||||
// No localized prose in the library (see Chapter::name): emit the bare
|
||||
// name, or a plain ordinal when unnamed — the app prepends any "Chapter "
|
||||
// prefix in the user's language. Matches chapters_xml / chapters_ogm.
|
||||
let name = if c.name.is_empty() {
|
||||
(i + 1).to_string()
|
||||
} else {
|
||||
c.name.clone()
|
||||
};
|
||||
s.push_str(&format!(
|
||||
"{}\n{} --> {}\nChapter {}\n\n",
|
||||
"{}\n{} --> {}\n{}\n\n",
|
||||
i + 1,
|
||||
vtt_time(start),
|
||||
vtt_time(end),
|
||||
c.name
|
||||
name
|
||||
));
|
||||
}
|
||||
s
|
||||
@@ -231,8 +243,13 @@ pub struct JsonSink {
|
||||
|
||||
impl JsonSink {
|
||||
pub fn create(path: &Path, title: &DiscTitle) -> io::Result<Self> {
|
||||
let doc =
|
||||
serde_json::to_string_pretty(&title_json(title)).unwrap_or_else(|_| "{}".to_string());
|
||||
// Serializing our own `Value` is infallible in practice (serde_json maps
|
||||
// any non-finite float to `null` at Value construction, so `title_json`
|
||||
// never holds an unencodable value); still, propagate rather than silently
|
||||
// writing "{}" if that ever changes — an empty metadata file must not
|
||||
// masquerade as a successful json:// export.
|
||||
let doc = serde_json::to_string_pretty(&title_json(title))
|
||||
.map_err(|_| crate::error::Error::MkvInvalid)?;
|
||||
let mut f = File::create(path)?;
|
||||
f.write_all(doc.as_bytes())?;
|
||||
f.write_all(b"\n")?;
|
||||
|
||||
Reference in New Issue
Block a user