Skip to main content

moq_mux/container/mkv/
mod.rs

1//! Matroska / WebM.
2//!
3//! An EBML-based file format. moq-mux uses it as an external interchange
4//! format only, not as a wire format: [`Import`] parses MKV byte streams
5//! into a broadcast and [`Export`] does the reverse.
6
7mod export;
8mod import;
9
10pub use export::*;
11pub use import::*;
12
13#[cfg(test)]
14mod export_test;
15#[cfg(test)]
16mod import_test;
17
18/// MKV parsing and emission errors.
19#[derive(Debug, Clone, thiserror::Error)]
20#[non_exhaustive]
21pub enum Error {
22	#[error("unsupported EBML DocType: {0}")]
23	UnsupportedDocType(String),
24
25	#[error("EBML header missing DocType")]
26	MissingDocType,
27
28	#[error("invalid SimpleBlock")]
29	InvalidSimpleBlock,
30
31	#[error("invalid Block payload")]
32	InvalidBlock,
33
34	#[error("negative block timestamp")]
35	NegativeBlockTimestamp,
36
37	#[error("timestamp overflow")]
38	TimestampOverflow,
39
40	#[error("TrackEntry missing TrackNumber")]
41	MissingTrackNumber,
42
43	#[error("TrackEntry missing TrackType")]
44	MissingTrackType,
45
46	#[error("TrackEntry missing CodecID")]
47	MissingCodecId,
48
49	#[error("unsupported video CodecID: {0}")]
50	UnsupportedVideoCodec(String),
51
52	#[error("unsupported audio CodecID: {0}")]
53	UnsupportedAudioCodec(String),
54
55	#[error("{codec_id} missing CodecPrivate ({purpose})")]
56	MissingCodecPrivate {
57		codec_id: &'static str,
58		purpose: &'static str,
59	},
60
61	#[error("invalid HEVCDecoderConfigurationRecord")]
62	InvalidHvcc,
63
64	#[error("invalid AV1CodecConfigurationRecord")]
65	InvalidAv1c,
66
67	#[error("MKV track layout changed after header was emitted: track '{0}' added")]
68	HeaderAddedTrack(String),
69
70	#[error("MKV track layout changed after header was emitted: track '{0}' removed")]
71	HeaderRemovedTrack(String),
72
73	#[error("MKV export does not support CMAF {kind} track '{name}'")]
74	UnsupportedCmafTrack { kind: String, name: String },
75
76	#[error("MKV export does not support video codec {0}")]
77	UnsupportedVideoExport(String),
78
79	#[error("MKV export does not support audio codec {0}")]
80	UnsupportedAudioExport(String),
81
82	#[error("AAC track missing AudioSpecificConfig (description)")]
83	MissingAacDescription,
84
85	#[error("FLAC track missing STREAMINFO (description)")]
86	MissingFlacDescription,
87
88	#[error("H.264 track missing AVCDecoderConfigurationRecord")]
89	MissingH264Avcc,
90
91	#[error("H.265 track missing HEVCDecoderConfigurationRecord")]
92	MissingH265Hvcc,
93
94	#[error("cluster underflow")]
95	ClusterUnderflow,
96
97	#[error("block timestamp doesn't fit in i16")]
98	BlockTimestampOverflow,
99
100	#[error("missing track")]
101	MissingTrack,
102
103	#[error("timestamp doesn't fit in u64 ms")]
104	TimestampU64,
105
106	#[error("video track {0} missing in tracks map")]
107	MissingVideoTrack(String),
108
109	#[error("audio track {0} missing in tracks map")]
110	MissingAudioTrack(String),
111
112	#[error("no catalog snapshot")]
113	NoCatalogSnapshot,
114
115	#[error("matroska parse error")]
116	MatroskaParse,
117
118	#[error("matroska write error: {0}")]
119	MatroskaWrite(String),
120
121	/// Building the Opus codec-private OpusHead for an audio track failed.
122	#[error(transparent)]
123	Opus(#[from] crate::codec::opus::Error),
124}
125
126// Flattened to its message so webm-iterable stays out of this crate's public API. The chain
127// matters here: `TagWriterError::WriteError` displays only "Error writing to destination." and
128// keeps the `io::Error` saying whether the disk filled or the pipe broke in `source()`.
129impl From<webm_iterable::errors::TagWriterError> for Error {
130	fn from(err: webm_iterable::errors::TagWriterError) -> Self {
131		Error::MatroskaWrite(crate::error::message(err))
132	}
133}
134
135pub type Result<T> = std::result::Result<T, Error>;
136
137#[cfg(test)]
138mod error_tests {
139	use super::*;
140
141	/// `TagWriterError::WriteError` displays only "Error writing to destination.", so the
142	/// `io::Error` naming the actual failure has to come through the source chain or an export
143	/// failure is undiagnosable.
144	#[test]
145	fn matroska_write_keeps_the_io_cause() {
146		let source = std::io::Error::new(std::io::ErrorKind::StorageFull, "no space left on device");
147		let err = Error::from(webm_iterable::errors::TagWriterError::WriteError { source });
148
149		let message = err.to_string();
150		assert!(message.contains("no space left on device"), "{message}");
151	}
152}