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