lofty/id3/v2/items/
encapsulated_object.rs1use crate::error::{ErrorKind, Id3v2Error, Id3v2ErrorKind, LoftyError, Result};
2use crate::id3::v2::{FrameFlags, FrameHeader, FrameId};
3use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text};
4
5use std::io::{Cursor, Read};
6
7const FRAME_ID: FrameId<'static> = FrameId::Valid(std::borrow::Cow::Borrowed("GEOB"));
8
9#[derive(PartialEq, Clone, Debug, Eq, Hash)]
11pub struct GeneralEncapsulatedObject<'a> {
12 pub(crate) header: FrameHeader<'a>,
13 pub encoding: TextEncoding,
15 pub mime_type: Option<String>,
17 pub file_name: Option<String>,
19 pub descriptor: Option<String>,
21 pub data: Vec<u8>,
23}
24
25impl GeneralEncapsulatedObject<'_> {
26 pub fn new(
28 encoding: TextEncoding,
29 mime_type: Option<String>,
30 file_name: Option<String>,
31 descriptor: Option<String>,
32 data: Vec<u8>,
33 ) -> Self {
34 let header = FrameHeader::new(FRAME_ID, FrameFlags::default());
35 Self {
36 header,
37 encoding,
38 mime_type,
39 file_name,
40 descriptor,
41 data,
42 }
43 }
44
45 pub fn id(&self) -> FrameId<'_> {
47 FRAME_ID
48 }
49
50 pub fn flags(&self) -> FrameFlags {
52 self.header.flags
53 }
54
55 pub fn set_flags(&mut self, flags: FrameFlags) {
57 self.header.flags = flags;
58 }
59
60 pub fn parse(data: &[u8], frame_flags: FrameFlags) -> Result<Self> {
68 if data.len() < 4 {
69 return Err(Id3v2Error::new(Id3v2ErrorKind::BadFrameLength).into());
70 }
71
72 let encoding = TextEncoding::from_u8(data[0])
73 .ok_or_else(|| LoftyError::new(ErrorKind::TextDecode("Found invalid encoding")))?;
74
75 let mut cursor = Cursor::new(&data[1..]);
76
77 let mime_type = decode_text(
78 &mut cursor,
79 TextDecodeOptions::new()
80 .encoding(TextEncoding::Latin1)
81 .terminated(true),
82 )?;
83
84 let text_decode_options = TextDecodeOptions::new().encoding(encoding).terminated(true);
85
86 let file_name = decode_text(&mut cursor, text_decode_options)?;
87 let descriptor = decode_text(&mut cursor, text_decode_options)?;
88
89 let mut data = Vec::new();
90 cursor.read_to_end(&mut data)?;
91
92 let header = FrameHeader::new(FRAME_ID, frame_flags);
93 Ok(Self {
94 header,
95 encoding,
96 mime_type: mime_type.text_or_none(),
97 file_name: file_name.text_or_none(),
98 descriptor: descriptor.text_or_none(),
99 data,
100 })
101 }
102
103 pub fn as_bytes(&self) -> Vec<u8> {
107 let encoding = self.encoding;
108
109 let mut bytes = vec![encoding as u8];
110
111 if let Some(ref mime_type) = self.mime_type {
112 bytes.extend(mime_type.as_bytes())
113 }
114
115 bytes.push(0);
116
117 let file_name = self.file_name.as_deref();
118 bytes.extend(&*encode_text(file_name.unwrap_or(""), encoding, true));
119
120 let descriptor = self.descriptor.as_deref();
121 bytes.extend(&*encode_text(descriptor.unwrap_or(""), encoding, true));
122
123 bytes.extend(&self.data);
124
125 bytes
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use crate::id3::v2::{FrameFlags, FrameHeader, GeneralEncapsulatedObject};
132 use crate::util::text::TextEncoding;
133
134 fn expected() -> GeneralEncapsulatedObject<'static> {
135 GeneralEncapsulatedObject {
136 header: FrameHeader::new(super::FRAME_ID, FrameFlags::default()),
137 encoding: TextEncoding::Latin1,
138 mime_type: Some(String::from("audio/mpeg")),
139 file_name: Some(String::from("a.mp3")),
140 descriptor: Some(String::from("Test Asset")),
141 data: crate::tag::utils::test_utils::read_path(
142 "tests/files/assets/minimal/full_test.mp3",
143 ),
144 }
145 }
146
147 #[test_log::test]
148 fn geob_decode() {
149 let expected = expected();
150
151 let cont = crate::tag::utils::test_utils::read_path("tests/tags/assets/id3v2/test.geob");
152
153 let parsed_geob = GeneralEncapsulatedObject::parse(&cont, FrameFlags::default()).unwrap();
154
155 assert_eq!(parsed_geob, expected);
156 }
157
158 #[test_log::test]
159 fn geob_encode() {
160 let to_encode = expected();
161
162 let encoded = to_encode.as_bytes();
163
164 let expected_bytes =
165 crate::tag::utils::test_utils::read_path("tests/tags/assets/id3v2/test.geob");
166
167 assert_eq!(encoded, expected_bytes);
168 }
169}