1use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
2use crate::id3::v2::frame::content::verify_encoding;
3use crate::id3::v2::header::Id3v2Version;
4use crate::id3::v2::{FrameFlags, FrameHeader, FrameId};
5use crate::tag::items::Lang;
6use crate::util::text::{TextDecodeOptions, TextEncoding, decode_text, encode_text};
7
8use std::borrow::Cow;
9use std::hash::{Hash, Hasher};
10use std::io::Read;
11
12use byteorder::ReadBytesExt;
13
14struct LanguageFrame {
18 pub encoding: TextEncoding,
19 pub language: Lang,
20 pub description: String,
21 pub content: String,
22}
23
24impl LanguageFrame {
25 fn parse<R>(reader: &mut R, version: Id3v2Version) -> Result<Option<Self>>
26 where
27 R: Read,
28 {
29 let Ok(encoding_byte) = reader.read_u8() else {
30 return Ok(None);
31 };
32
33 let encoding = verify_encoding(encoding_byte, version)?;
34
35 let mut language = [0; 3];
36 reader.read_exact(&mut language)?;
37
38 let description = decode_text(
39 reader,
40 TextDecodeOptions::new().encoding(encoding).terminated(true),
41 )?
42 .content;
43 let content = decode_text(reader, TextDecodeOptions::new().encoding(encoding))?.content;
44
45 Ok(Some(Self {
46 encoding,
47 language,
48 description,
49 content,
50 }))
51 }
52
53 fn create_bytes(
54 mut encoding: TextEncoding,
55 language: [u8; 3],
56 description: &str,
57 content: &str,
58 is_id3v23: bool,
59 ) -> Result<Vec<u8>> {
60 if is_id3v23 {
61 encoding = encoding.to_id3v23();
62 }
63
64 let mut bytes = vec![encoding as u8];
65
66 if language.len() != 3 || language.iter().any(|c| !c.is_ascii_alphabetic()) {
67 return Err(Id3v2Error::new(Id3v2ErrorKind::InvalidLanguage(language)).into());
68 }
69
70 bytes.extend(language);
71 bytes.extend(encode_text(description, encoding, true).iter());
72 bytes.extend(encode_text(content, encoding, false));
73
74 Ok(bytes)
75 }
76}
77
78#[derive(Clone, Debug, Eq)]
82pub struct CommentFrame<'a> {
83 pub(crate) header: FrameHeader<'a>,
84 pub encoding: TextEncoding,
86 pub language: Lang,
88 pub description: String,
90 pub content: String,
92}
93
94impl PartialEq for CommentFrame<'_> {
95 fn eq(&self, other: &Self) -> bool {
96 self.language == other.language && self.description == other.description
97 }
98}
99
100impl Hash for CommentFrame<'_> {
101 fn hash<H: Hasher>(&self, state: &mut H) {
102 self.language.hash(state);
103 self.description.hash(state);
104 }
105}
106
107impl CommentFrame<'_> {
108 const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("COMM"));
109
110 pub fn new(
112 encoding: TextEncoding,
113 language: Lang,
114 description: String,
115 content: String,
116 ) -> Self {
117 let header = FrameHeader::new(Self::FRAME_ID, FrameFlags::default());
118 Self {
119 header,
120 encoding,
121 language,
122 description,
123 content,
124 }
125 }
126
127 pub fn id(&self) -> FrameId<'_> {
129 Self::FRAME_ID
130 }
131
132 pub fn flags(&self) -> FrameFlags {
134 self.header.flags
135 }
136
137 pub fn set_flags(&mut self, flags: FrameFlags) {
139 self.header.flags = flags;
140 }
141
142 pub fn parse<R>(
154 reader: &mut R,
155 frame_flags: FrameFlags,
156 version: Id3v2Version,
157 ) -> Result<Option<Self>>
158 where
159 R: Read,
160 {
161 let Some(language_frame) = LanguageFrame::parse(reader, version)? else {
162 return Ok(None);
163 };
164
165 let header = FrameHeader::new(Self::FRAME_ID, frame_flags);
166 Ok(Some(Self {
167 header,
168 encoding: language_frame.encoding,
169 language: language_frame.language,
170 description: language_frame.description,
171 content: language_frame.content,
172 }))
173 }
174
175 pub fn as_bytes(&self, is_id3v23: bool) -> Result<Vec<u8>> {
184 LanguageFrame::create_bytes(
185 self.encoding,
186 self.language,
187 &self.description,
188 &self.content,
189 is_id3v23,
190 )
191 }
192}
193
194#[derive(Clone, Debug, Eq)]
198pub struct UnsynchronizedTextFrame<'a> {
199 pub(crate) header: FrameHeader<'a>,
200 pub encoding: TextEncoding,
202 pub language: Lang,
204 pub description: String,
206 pub content: String,
208}
209
210impl PartialEq for UnsynchronizedTextFrame<'_> {
211 fn eq(&self, other: &Self) -> bool {
212 self.language == other.language && self.description == other.description
213 }
214}
215
216impl Hash for UnsynchronizedTextFrame<'_> {
217 fn hash<H: Hasher>(&self, state: &mut H) {
218 self.language.hash(state);
219 self.description.hash(state);
220 }
221}
222
223impl UnsynchronizedTextFrame<'_> {
224 const FRAME_ID: FrameId<'static> = FrameId::Valid(Cow::Borrowed("USLT"));
225
226 pub fn new(
228 encoding: TextEncoding,
229 language: Lang,
230 description: String,
231 content: String,
232 ) -> Self {
233 let header = FrameHeader::new(Self::FRAME_ID, FrameFlags::default());
234 Self {
235 header,
236 encoding,
237 language,
238 description,
239 content,
240 }
241 }
242
243 pub fn id(&self) -> FrameId<'_> {
245 Self::FRAME_ID
246 }
247
248 pub fn flags(&self) -> FrameFlags {
250 self.header.flags
251 }
252
253 pub fn set_flags(&mut self, flags: FrameFlags) {
255 self.header.flags = flags;
256 }
257
258 pub fn parse<R>(
270 reader: &mut R,
271 frame_flags: FrameFlags,
272 version: Id3v2Version,
273 ) -> Result<Option<Self>>
274 where
275 R: Read,
276 {
277 let Some(language_frame) = LanguageFrame::parse(reader, version)? else {
278 return Ok(None);
279 };
280
281 let header = FrameHeader::new(Self::FRAME_ID, frame_flags);
282 Ok(Some(Self {
283 header,
284 encoding: language_frame.encoding,
285 language: language_frame.language,
286 description: language_frame.description,
287 content: language_frame.content,
288 }))
289 }
290
291 pub fn as_bytes(&self, is_id3v23: bool) -> Result<Vec<u8>> {
300 LanguageFrame::create_bytes(
301 self.encoding,
302 self.language,
303 &self.description,
304 &self.content,
305 is_id3v23,
306 )
307 }
308}