webm_iterable/matroska_spec/blocks/
block.rs

1use std::convert::{TryFrom, TryInto};
2use ebml_iterable::tools::{self as ebml_tools, Vint};
3
4use crate::errors::WebmCoercionError;
5use crate::MatroskaSpec;
6use super::block_utils::{read_frame_data, write_frame_data};
7
8///
9/// An enum describing different block lacing options.
10///
11/// This enum is based on the definition for [Lacing](https://www.matroska.org/technical/basics.html#lacing) as defined by the [Matroska Spec](http://www.matroska.org/technical/specs/index.html).
12///
13#[derive(PartialEq, Copy, Clone, Debug)]
14pub enum BlockLacing {
15    Xiph,
16    Ebml,
17    FixedSize,
18}
19
20///
21/// A single frame of data within a block.
22/// 
23/// There may be a single frame or multiple frames within a "Block" or "SimpleBlock".  If only one frame is present, "BlockLacing" must be None.  If more than one frame is present, "BlockLacing" must be one of: Xiph, Ebml, FixedSize.
24/// 
25#[derive(Clone, Debug)]
26pub struct Frame<'a> {
27    pub data: &'a [u8]
28}
29
30///
31/// A typed interpretation of the Matroska "Block" element.
32///
33/// This struct has fields specific to the [Block](https://www.matroska.org/technical/basics.html#block-structure) element as defined by the [Matroska Spec](http://www.matroska.org/technical/specs/index.html).  This struct implements `TryFrom<&MatroskaSpec>` and `Into<MatroskaSpec>` to simplify coercion to and from regular variants.
34///
35/// ## Example
36///
37/// ```
38/// # use std::convert::TryInto;
39/// use webm_iterable::matroska_spec::{MatroskaSpec, Block};
40///
41/// let variant = &MatroskaSpec::Block(vec![0x83,0x00,0x01,0x9d,0x00,0x00,0x00]);
42/// let mut block: Block = variant.try_into().unwrap();
43/// assert_eq!(3, block.track);
44/// ```
45///
46#[derive(Clone, Debug)]
47pub struct Block<'a> {
48    /// Raw frame data used to create the block (avoids the extra allocation of using owned_frame_data)
49    frame_data: &'a [u8],
50
51    /// Owned frame data that can be set to allow changing frame data on the block
52    owned_frame_data: Option<Vec<u8>>,
53
54    pub track: u64,
55    pub timestamp: i16,
56
57    pub invisible: bool,
58    pub lacing: Option<BlockLacing>,
59}
60
61impl<'a> Block<'a> {
62    ///
63    /// Reads the raw frame data of the block.
64    /// 
65    /// Frame data can be formatted differently depending on the block lacing.  Generally, it is easier to use [`Self::read_frame_data()`] rather than this method to access the frames in the block.  This method is provided in the event raw packet data needs to be handled in a special way (for example, if the data is encrypted).
66    /// 
67    pub fn raw_frame_data(&self) -> &[u8] {
68        self.owned_frame_data.as_deref().unwrap_or(self.frame_data)
69    }
70
71    ///
72    /// Reads the frames encoded in the block.
73    /// 
74    /// This method outputs the binary frames encoded in the block, taking into account any block lacing.  Details on block lacing can be found in the [Matroska spec](https://www.matroska.org/technical/notes.html).
75    /// 
76    /// # Errors
77    /// 
78    /// This method can return an error if the frame data is malformed.
79    /// 
80    pub fn read_frame_data(&self) -> Result<Vec<Frame>, WebmCoercionError> {
81        read_frame_data(self.owned_frame_data.as_deref().unwrap_or(self.frame_data), &self.lacing)
82    }
83
84    ///
85    /// Updates the frame data contained in the block.
86    /// 
87    /// This method writes frame data to a newly allocated vector owned by the block.  Future calls to [`Self::read_frame_data()`] and [`Self::raw_frame_data()`] will use the data set via this method.
88    /// 
89    /// # Panics
90    /// 
91    /// This method can panic if the block has its lacing set as ['BlockLacing::FixedSize`] and the input frames are not all the same length.
92    /// 
93    pub fn set_frame_data(&mut self, frames: &Vec<Frame>) {
94        let (data, new_lacing) = write_frame_data(frames, self.lacing);
95        self.lacing = new_lacing;
96        self.owned_frame_data = Some(data);
97    }
98
99    /// 
100    /// Creates a new block with the given data.
101    /// 
102    /// Primarily used if you would like to write with a given frame.
103    /// For example, when you want to remux a video with libvpx.
104    /// 
105    pub fn new_uncheked(track: u64, timestamp: i16, invisible: bool, lacing: Option<BlockLacing>, frame_data: &'a [u8]) -> Self {
106        Block {
107            frame_data,
108            owned_frame_data: None,
109            track,
110            timestamp,
111            invisible,
112            lacing,
113        }
114    }
115}
116
117impl<'a> TryFrom<&'a Vec<u8>> for Block<'a> {
118    type Error = WebmCoercionError;
119
120    fn try_from(value: &'a Vec<u8>) -> Result<Self, Self::Error> {
121       value.as_slice().try_into()
122    }
123}
124
125impl<'a> TryFrom<&'a [u8]> for Block<'a> {
126    type Error = WebmCoercionError;
127
128    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
129        let mut position: usize = 0;
130        let (track, track_size) = ebml_tools::read_vint(data)
131            .map_err(|_| WebmCoercionError::BlockCoercionError(String::from("Unable to read track data in Block.")))?
132            .ok_or_else(|| WebmCoercionError::BlockCoercionError(String::from("Unable to read track data in Block.")))?;
133
134        position += track_size;
135
136        let value: [u8; 2] = data[position..position + 2].try_into()
137            .map_err(|_| WebmCoercionError::BlockCoercionError(String::from("Attempting to create Block tag, but binary data length was not 2")))?;
138        let timestamp = i16::from_be_bytes(value);
139        position += 2;
140
141        let flags: u8 = data[position];
142        position += 1;
143        let invisible = (flags & 0x08) == 0x08;
144
145        let lacing: Option<BlockLacing>;
146        if flags & 0x06 == 0x06 {
147            lacing = Some(BlockLacing::Ebml);
148        } else if flags & 0x06 == 0x04 {
149            lacing = Some(BlockLacing::FixedSize);
150        } else if flags & 0x06 == 0x02 {
151            lacing = Some(BlockLacing::Xiph);
152        } else {
153            lacing = None;
154        }
155
156        let payload = &data[position..];
157
158        Ok(Block {
159            frame_data: payload,
160            owned_frame_data: None,
161            track,
162            timestamp,
163            invisible,
164            lacing,
165        })
166    }
167}
168
169impl<'a> TryFrom<&'a MatroskaSpec> for Block<'a> {
170    type Error = WebmCoercionError;
171
172    fn try_from(value: &'a MatroskaSpec) -> Result<Self, Self::Error> {
173        match value {
174            MatroskaSpec::Block(data) => {
175                Block::try_from(data.as_slice())
176            }
177            _ => Err(WebmCoercionError::BlockCoercionError(String::from("Expected binary tag type for Block tag, but received a different type!"))),
178        }
179    }
180}
181
182impl From<Block<'_>> for MatroskaSpec {
183    fn from(block: Block) -> Self {
184        let mut flags: u8 = 0x00;
185        if block.invisible {
186            flags |= 0x08;
187        }
188
189        if block.lacing.is_some() {
190            match block.lacing.unwrap() {
191                BlockLacing::Xiph => {
192                    flags |= 0x02;
193                }
194                BlockLacing::Ebml => {
195                    flags |= 0x06;
196                }
197                BlockLacing::FixedSize => {
198                    flags |= 0x04;
199                }
200            }
201        }
202
203        let data = block.owned_frame_data.as_deref().unwrap_or(block.frame_data);
204        let mut result = Vec::with_capacity(data.len() + 11);
205        result.extend_from_slice(&block.track.as_vint().expect("Unable to convert track value to vint"));
206        result.extend_from_slice(&block.timestamp.to_be_bytes());
207        result.extend_from_slice(&flags.to_be_bytes());
208        result.extend_from_slice(data);
209
210        MatroskaSpec::Block(result)
211    }
212}