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