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}