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