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(×tamp_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}