sheave_core/
flv.rs

1//! # The FLV File Format
2//!
3//! In RTMP, Both of the client and the server send/receive actual multi media data as the FLV file format.
4//! Its format consists of:
5//!
6//! 1. FLV header
7//!    * Signature ("FLV")
8//!    * Version (8 bits)
9//!    * Reserved (5 bits)
10//!    * Whether some audio data is contained (1 bit)
11//!    * Reserved (1 bit)
12//!    * Whether some video data is contained (1 bit)
13//!    * Offset to FLV data (that is, a size of this header = 9) (32 bits)
14//! 2. FLV file body
15//!    * Previous Tag Size (32 bits. this of the first is 0)
16//!    * FLV Tag (arbitrary size)
17//!
18//! Note the FLV header is skipped by almost RTMP tools.
19//!
20//! ## FLV Tag
21//!
22//! FLV Tag is a part of actual FLV bodies.
23//! FLV Tag consists of:
24//!
25//! * [`AudioTag`]
26//! * [`VideoTag`]
27//! * [`ScriptDataTag`]
28//!
29//! [`AudioTag`]: tags::AudioTag
30//! [`VideoTag`]: tags::VideoTag
31//! [`ScriptDataTag`]: tags::ScriptDataTag
32mod not_flv_container;
33mod unknown_tag;
34pub mod tags;
35
36use std::{
37    fmt::{
38        Display,
39        Formatter,
40        Result as FormatResult
41    },
42    fs::OpenOptions,
43    io::{
44        Read,
45        Result as IOResult,
46        Seek,
47        SeekFrom,
48        Write
49    },
50    path::Path,
51    time::Duration
52};
53use super::{
54    ByteBuffer,
55    Decoder
56};
57use self::tags::*;
58pub use self::{
59    not_flv_container::*,
60    unknown_tag::*,
61};
62
63/// Patterns of the FilterName field.
64/// Currently, FilterName consists of:
65///
66/// * `"Encryption"`
67/// * `"SE"` (Selective Encryption)
68///
69/// But these are strings so we will be hard to refuse other values at this rate.
70/// Therefore this limits any FilterName pattern to fix it to an enum.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum FilterName {
73    Encryption,
74    SelectiveEncryption
75}
76
77impl Display for FilterName {
78    fn fmt(&self, f: &mut Formatter) -> FormatResult {
79        use FilterName::*;
80
81        match *self {
82            Encryption => write!(f, "Encryption"),
83            SelectiveEncryption => write!(f, "SE")
84        }
85    }
86}
87
88/// The FLV container.
89/// This holds just 2 elements:
90///
91/// * A path to actual FLV file
92/// * Offset in FLV file (for reading).
93///
94/// By not to hold actual file handle, this makes plural users to read/write FLV file not to bump.
95/// Actual file handle is gotten only while file opens/creates and file reads/writes.
96#[derive(Debug, Clone)]
97pub struct Flv {
98    offset: u64,
99    path: String
100}
101
102impl Flv {
103    const SIGNATURE: &'static str = "FLV";
104    const LATEST_VERSION: u8 = 10;
105    const HEADER_LEN: usize = 9;
106
107    /// Constructs a FLV container from a file.
108    ///
109    /// # Errors
110    ///
111    /// When passed file isn't the FLV container:
112    ///
113    /// * It doesn't start with "FLV".
114    /// * It doesn't have the FLV header (requires 9 bytes).
115    ///
116    /// # Examples
117    ///
118    /// ```rust
119    /// use std::{
120    ///     fs::{
121    ///         File,
122    ///         OpenOptions
123    ///     },
124    ///     io::{
125    ///         Read,
126    ///         Seek,
127    ///         SeekFrom,
128    ///         Write
129    ///     }
130    /// };
131    /// use sheave_core::flv::*;
132    ///
133    /// // When the input length less than 13.
134    /// let mut input = OpenOptions::new()
135    ///     .write(true)
136    ///     .create(true)
137    ///     .truncate(true)
138    ///     .open("/tmp/err1.flv").unwrap();
139    /// let result = Flv::open("/tmp/err1.flv");
140    /// assert!(result.is_err());
141    ///
142    /// // When the signature isn't "FLV".
143    /// let mut input = OpenOptions::new()
144    ///     .write(true)
145    ///     .create(true)
146    ///     .truncate(true)
147    ///     .open("/tmp/err2.flv").unwrap();
148    /// input.write("F4V".as_bytes()).unwrap();
149    /// input.flush().unwrap();
150    /// input.seek(SeekFrom::Start(0)).unwrap();
151    /// let result = Flv::open("/tmp/err2.flv");
152    /// assert!(result.is_err());
153    ///
154    /// // Ok.
155    /// let mut input = OpenOptions::new()
156    ///     .write(true)
157    ///     .create(true)
158    ///     .truncate(true)
159    ///     .open("/tmp/ok.flv").unwrap();
160    /// let mut bytes: [u8; 9] = [0; 9];
161    /// bytes[..3].copy_from_slice("FLV".as_bytes());
162    /// input.write(&bytes).unwrap();
163    /// // NOTE: This is a previous tag size at the head position.
164    /// input.write(&0u32.to_be_bytes()).unwrap();
165    /// input.flush().unwrap();
166    /// input.seek(SeekFrom::Start(0)).unwrap();
167    /// let result = Flv::open("/tmp/ok.flv");
168    /// assert!(result.is_ok())
169    /// ```
170    pub fn open<P: AsRef<Path>>(path: P) -> IOResult<Self> {
171        let referred_path = path.as_ref();
172        let mut file = OpenOptions::new()
173            .read(true)
174            .open(referred_path)?;
175        let mut flv_header: [u8; Self::HEADER_LEN] = [0; Self::HEADER_LEN];
176        file.read(&mut flv_header)?;
177
178        let signature = &flv_header[..3];
179
180        if signature != Self::SIGNATURE.as_bytes() {
181            Err(not_flv_container(&flv_header[..3]))
182        } else {
183            Ok(
184                Self {
185                    // NOTE: Seeks to the position of first FLV tag.
186                    offset: 13,
187                    path: referred_path.to_str().unwrap().into(),
188                }
189            )
190        }
191    }
192
193    /// Constructs an empty FLV container from a name.
194    pub fn create<P: AsRef<Path>>(path: P) -> IOResult<Self> {
195        let referred_path = path.as_ref();
196        let mut file = OpenOptions::new()
197            .read(true)
198            .write(true)
199            .create_new(true)
200            .open(referred_path)?;
201        let mut flv_header: [u8; Self::HEADER_LEN] = [0; Self::HEADER_LEN];
202        flv_header[..3].copy_from_slice(Self::SIGNATURE.as_bytes());
203        flv_header[3] = Self::LATEST_VERSION;
204        flv_header[8] = Self::HEADER_LEN as u8;
205        file.write(&flv_header)?;
206        file.write(&0u32.to_be_bytes())?;
207        file.flush()?;
208        Ok(
209            Self {
210                // NOTE: Seeks to the position of first FLV tag.
211                offset: 13,
212                path: referred_path.to_str().unwrap().into(),
213            }
214        )
215    }
216
217    /// Gets the current FLV version.
218    pub fn get_version(&self) -> IOResult<u8> {
219        let mut file = OpenOptions::new()
220            .read(true)
221            .open(&self.path)?;
222        file.seek(SeekFrom::Start(3))?;
223        let mut version_byte: [u8; 1] = [0; 1];
224        file.read(&mut version_byte)?;
225        Ok(u8::from_be_bytes(version_byte))
226    }
227
228    fn set_flags(&self, flags: u8) -> IOResult<()> {
229        let mut file = OpenOptions::new()
230            .write(true)
231            .open(&self.path)?;
232        file.seek(SeekFrom::Start(4))?;
233        file.write(&flags.to_be_bytes())?;
234        file.flush()
235    }
236
237    /// Checks whether FLV container has audio data.
238    pub fn has_audio(&self) -> IOResult<bool> {
239        let mut file = OpenOptions::new()
240            .read(true)
241            .open(&self.path)?;
242        file.seek(SeekFrom::Start(4))?;
243        let mut flags_byte: [u8; 1] = [0; 1];
244        file.read(&mut flags_byte)?;
245        Ok((flags_byte[0] & 0x04) != 0)
246    }
247
248    /// Checks whether FLV container has video data.
249    pub fn has_video(&self) -> IOResult<bool> {
250        let mut file = OpenOptions::new()
251            .read(true)
252            .open(&self.path)?;
253        file.seek(SeekFrom::Start(4))?;
254        let mut flags_byte: [u8; 1] = [0; 1];
255        file.read(&mut flags_byte)?;
256        Ok((flags_byte[0] & 0x01) != 0)
257    }
258
259    /// Appends a FLV tag into the tag container.
260    ///
261    /// This reuses the Codec IDs in the metadata for checking whether FLV has audio/video data.
262    ///
263    /// That is,
264    ///
265    /// * If `audiocodecid` exists, FLV contains auduo data.
266    /// * Or if `videocodecid` exists, FLV contains video data.
267    /// * Otherwise FLV consists of just script data.
268    pub fn append_flv_tag(&self, flv_tag: FlvTag) -> IOResult<()> {
269        let mut file = OpenOptions::new()
270            .append(true)
271            .open(&self.path)?;
272
273        if let TagType::ScriptData = flv_tag.get_tag_type() {
274            let mut buffer: ByteBuffer = flv_tag.get_data().to_vec().into();
275            let script_data: ScriptDataTag = buffer.decode()?;
276            let has_audio = script_data.get_value().get_properties().get("audiocodecid").is_some() as u8;
277            let has_video = script_data.get_value().get_properties().get("videocodecid").is_some() as u8;
278            self.set_flags((has_audio << 2) | has_video)?;
279        }
280
281        let timestamp_bytes = (flv_tag.get_timestamp().as_millis() as u32).to_be_bytes();
282        let data_size = flv_tag.get_data().len();
283        let mut metadata: [u8; METADATA_LEN] = [0; METADATA_LEN];
284        metadata[0] = flv_tag.get_tag_type().into();
285        metadata[1..4].copy_from_slice(&data_size.to_be_bytes()[5..]);
286        metadata[4..7].copy_from_slice(&timestamp_bytes[1..]);
287        metadata[7] = timestamp_bytes[0];
288        // NOTE: This is the message ID that is currently always 0.
289        metadata[8..].copy_from_slice(&DEFAULT_MESSAGE_ID.to_be_bytes()[..3]);
290
291        file.write(&metadata)?;
292        file.write(flv_tag.get_data())?;
293        file.write(&(METADATA_LEN + data_size).to_be_bytes()[4..])?;
294        file.flush()
295    }
296}
297
298impl Iterator for Flv {
299    type Item = IOResult<FlvTag>;
300
301    /// Reads a FLV tag from the path.
302    ///
303    /// Note this can return some error when following causes:
304    ///
305    /// * `UnknownTag`
306    ///
307    /// When any undefined tag type found.
308    /// Currently, the tag type should be one of 8(Audio), 9(Video) or 18(Data) in the FLV container.
309    /// That is, this library doesn't know any way of handling other type.
310    ///
311    /// * Something else
312    ///
313    /// When reading/seeking got failed by some cause.
314    fn next(&mut self) -> Option<Self::Item> {
315        let mut file = match OpenOptions::new().read(true).open(&self.path) {
316            Ok(file) => file,
317            Err(e) => return Some(Err(e))
318        };
319
320        if let Err(e) = file.seek(SeekFrom::Start(self.offset)) {
321            return Some(Err(e))
322        }
323
324        let mut metadata_bytes: [u8; METADATA_LEN] = [0; METADATA_LEN];
325        match file.read(&mut metadata_bytes) {
326            Err(e) => return Some(Err(e)),
327            Ok(0) => return None,
328            _ => {}
329        }
330
331        let tag_type_byte = metadata_bytes[0] & 0x1f;
332        let tag_type: TagType = match tag_type_byte {
333            8 | 9 | 18 => tag_type_byte.into(),
334            other => return Some(Err(unknown_tag(other)))
335        };
336
337        let mut data_size_bytes: [u8; 4] = [0; 4];
338        data_size_bytes[1..].copy_from_slice(&metadata_bytes[1..4]);
339        let data_size = u32::from_be_bytes(data_size_bytes);
340        let mut data: Vec<u8> = Vec::with_capacity(data_size as usize);
341        unsafe { data.set_len(data_size as usize); }
342        if let Err(e) = file.read(&mut data) {
343            return Some(Err(e))
344        }
345
346        // NOTE: Previous Tag Size is unnecessary in reading.
347        if let Err(e) = file.seek(SeekFrom::Current(4)) {
348            return Some(Err(e))
349        }
350
351        let mut timestamp_bytes: [u8; 4] = [0; 4];
352        timestamp_bytes[1..].copy_from_slice(&metadata_bytes[4..7]);
353        let timestamp = u32::from_be_bytes(timestamp_bytes) | ((metadata_bytes[8] as u32) << 23);
354
355
356        self.offset = match file.stream_position() {
357            Err(e) => return Some(Err(e)),
358            Ok(offset) => offset
359        };
360        Some(Ok(FlvTag::new(tag_type, Duration::from_millis(timestamp as u64), data)))
361    }
362}