scuffle_flv/tag.rs
1//! FLV Tag processing
2
3use byteorder::{BigEndian, ReadBytesExt};
4use bytes::Bytes;
5use nutype_enum::nutype_enum;
6use scuffle_bytes_util::BytesCursorExt;
7
8use super::audio::AudioData;
9use super::script::ScriptData;
10use super::video::VideoData;
11use crate::error::FlvError;
12
13/// An FLV Tag
14///
15/// Tags have different types and thus different data structures. To accommodate
16/// this the [`FlvTagData`] enum is used.
17///
18/// Defined by:
19/// - Legacy FLV spec, Annex E.4.1
20///
21/// The v10.1 spec adds some additional fields to the tag to accomodate
22/// encryption. We dont support this because it is not needed for our use case.
23/// (and I suspect it is not used anywhere anymore.)
24#[derive(Debug, Clone, PartialEq)]
25pub struct FlvTag<'a> {
26 /// The timestamp of this tag in milliseconds
27 pub timestamp_ms: u32,
28 /// The stream id of this tag
29 pub stream_id: u32,
30 /// The actual data of the tag
31 pub data: FlvTagData<'a>,
32}
33
34impl FlvTag<'_> {
35 /// Demux a FLV tag from the given reader.
36 ///
37 /// The reader will be advanced to the end of the tag.
38 ///
39 /// The reader needs to be a [`std::io::Cursor`] with a [`Bytes`] buffer because we
40 /// take advantage of zero-copy reading.
41 pub fn demux(reader: &mut std::io::Cursor<Bytes>) -> Result<Self, FlvError> {
42 let first_byte = reader.read_u8()?;
43
44 // encrypted
45 let filter = (first_byte & 0b0010_0000) != 0;
46
47 // Only the last 5 bits are the tag type.
48 let tag_type = FlvTagType::from(first_byte & 0b00011111);
49
50 let data_size = reader.read_u24::<BigEndian>()?;
51 // The timestamp bit is weird. Its 24bits but then there is an extended 8 bit
52 // number to create a 32bit number.
53 let timestamp_ms = reader.read_u24::<BigEndian>()? | ((reader.read_u8()? as u32) << 24);
54
55 // The stream id according to the spec is ALWAYS 0. (likely not true)
56 let stream_id = reader.read_u24::<BigEndian>()?;
57
58 // We then extract the data from the reader. (advancing the cursor to the end of
59 // the tag)
60 let data = reader.extract_bytes(data_size as usize)?;
61
62 let data = if !filter {
63 // Finally we demux the data.
64 FlvTagData::demux(tag_type, &mut std::io::Cursor::new(data))?
65 } else {
66 // If the tag is encrypted we just return the data as is.
67 FlvTagData::Encrypted { data }
68 };
69
70 Ok(FlvTag {
71 timestamp_ms,
72 stream_id,
73 data,
74 })
75 }
76}
77
78nutype_enum! {
79 /// FLV Tag Type
80 ///
81 /// This is the type of the tag.
82 ///
83 /// Defined by:
84 /// - video_file_format_spec_v10.pdf (Chapter 1 - The FLV File Format - FLV tags)
85 /// - video_file_format_spec_v10_1.pdf (Annex E.4.1 - FLV Tag)
86 ///
87 /// The 3 types that are supported are:
88 /// - Audio(8)
89 /// - Video(9)
90 /// - ScriptData(18)
91 pub enum FlvTagType(u8) {
92 /// [`AudioData`]
93 Audio = 8,
94 /// [`VideoData`]
95 Video = 9,
96 /// [`ScriptData`]
97 ScriptData = 18,
98 }
99}
100
101/// FLV Tag Data
102///
103/// This is a container for the actual media data.
104/// This enum contains the data for the different types of tags.
105///
106/// Defined by:
107/// - Legacy FLV spec, Annex E.4.1
108#[derive(Debug, Clone, PartialEq)]
109pub enum FlvTagData<'a> {
110 /// AudioData when the FlvTagType is Audio(8)
111 ///
112 /// Defined by:
113 /// - Legacy FLV spec, Annex E.4.2.1
114 Audio(AudioData),
115 /// VideoData when the FlvTagType is Video(9)
116 ///
117 /// Defined by:
118 /// - Legacy FLV spec, Annex E.4.3.1
119 Video(VideoData<'a>),
120 /// ScriptData when the FlvTagType is ScriptData(18)
121 ///
122 /// Defined by:
123 /// - Legacy FLV spec, Annex E.4.4.1
124 ScriptData(ScriptData<'a>),
125 /// Encrypted tag.
126 ///
127 /// This library neither supports demuxing nor decrypting encrypted tags.
128 Encrypted {
129 /// The raw unencrypted tag data.
130 ///
131 /// This includes all data that follows the StreamID field.
132 /// See the legacy FLV spec, Annex E.4.1 for more information.
133 data: Bytes,
134 },
135 /// Any tag type that we dont know how to demux, with the corresponding data
136 /// being the raw bytes of the tag.
137 Unknown {
138 /// The tag type.
139 tag_type: FlvTagType,
140 /// The raw data of the tag.
141 data: Bytes,
142 },
143}
144
145impl FlvTagData<'_> {
146 /// Demux a FLV tag data from the given reader.
147 ///
148 /// The reader will be enirely consumed.
149 ///
150 /// The reader needs to be a [`std::io::Cursor`] with a [`Bytes`] buffer because we
151 /// take advantage of zero-copy reading.
152 pub fn demux(tag_type: FlvTagType, reader: &mut std::io::Cursor<Bytes>) -> Result<Self, FlvError> {
153 match tag_type {
154 FlvTagType::Audio => Ok(FlvTagData::Audio(AudioData::demux(reader)?)),
155 FlvTagType::Video => Ok(FlvTagData::Video(VideoData::demux(reader)?)),
156 FlvTagType::ScriptData => Ok(FlvTagData::ScriptData(ScriptData::demux(reader)?)),
157 _ => Ok(FlvTagData::Unknown {
158 tag_type,
159 data: reader.extract_remaining(),
160 }),
161 }
162 }
163}