anni_flac/blocks/
picture.rs

1use crate::error::FlacError;
2use crate::prelude::*;
3use crate::utils::*;
4use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
5use num_traits::FromPrimitive;
6use std::borrow::Cow;
7use std::fmt;
8use std::io::{Read, Write};
9use std::path::Path;
10use std::str::FromStr;
11
12pub struct BlockPicture {
13    /// <32> The picture type according to the ID3v2 APIC frame
14    /// Others are reserved and should not be used.
15    /// There may only be one each of picture type 1 and 2 in a file.
16    pub picture_type: PictureType,
17    // <32> The length of the MIME type string in bytes.
18    /// <n*8> The MIME type string, in printable ASCII characters 0x20-0x7e.
19    /// The MIME type may also be --> to signify that the data part is a URL of the picture instead of the picture data itself.
20    pub mime_type: String,
21    // <32> The length of the description string in bytes.
22    /// <n*8> The description of the picture, in UTF-8.
23    pub description: String,
24    /// <32> The width of the picture in pixels.
25    pub width: u32,
26    /// <32> The height of the picture in pixels.
27    pub height: u32,
28    /// <32> The color depth of the picture in bits-per-pixel.
29    pub depth: u32,
30    /// <32> For indexed-color pictures (e.g. GIF), the number of colors used, or 0 for non-indexed pictures.
31    pub colors: u32,
32    // <32> The length of the picture data in bytes.
33    /// <n*8> The binary picture data.
34    pub data: Vec<u8>,
35}
36
37impl Decode for BlockPicture {
38    fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
39        let picture_type: PictureType = FromPrimitive::from_u32(reader.read_u32::<BigEndian>()?)
40            .unwrap_or(PictureType::Unknown);
41        let mime_type_length = reader.read_u32::<BigEndian>()?;
42        let mime_type = take_string(reader, mime_type_length as usize)?;
43        let description_length = reader.read_u32::<BigEndian>()?;
44        let description = take_string(reader, description_length as usize)?;
45
46        let width = reader.read_u32::<BigEndian>()?;
47        let height = reader.read_u32::<BigEndian>()?;
48
49        let depth = reader.read_u32::<BigEndian>()?;
50        let colors = reader.read_u32::<BigEndian>()?;
51
52        let picture_length = reader.read_u32::<BigEndian>()?;
53        let data = take(reader, picture_length as usize)?;
54        Ok(BlockPicture {
55            picture_type,
56            mime_type,
57            description,
58            width,
59            height,
60            depth,
61            colors,
62            data,
63        })
64    }
65}
66
67#[cfg(feature = "async")]
68#[async_trait::async_trait]
69impl AsyncDecode for BlockPicture {
70    async fn from_async_reader<R>(reader: &mut R) -> Result<Self>
71    where
72        R: AsyncRead + Unpin + Send,
73    {
74        let picture_type: PictureType =
75            FromPrimitive::from_u32(reader.read_u32().await?).unwrap_or(PictureType::Unknown);
76        let mime_type_length = reader.read_u32().await?;
77        let mime_type = take_string_async(reader, mime_type_length as usize).await?;
78        let description_length = reader.read_u32().await?;
79        let description = take_string_async(reader, description_length as usize).await?;
80
81        let width = reader.read_u32().await?;
82        let height = reader.read_u32().await?;
83
84        let depth = reader.read_u32().await?;
85        let colors = reader.read_u32().await?;
86
87        let picture_length = reader.read_u32().await?;
88        let data = take_async(reader, picture_length as usize).await?;
89        Ok(BlockPicture {
90            picture_type,
91            mime_type,
92            description,
93            width,
94            height,
95            depth,
96            colors,
97            data,
98        })
99    }
100}
101
102impl Encode for BlockPicture {
103    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
104        writer.write_u32::<BigEndian>(self.picture_type as u32)?;
105
106        writer.write_u32::<BigEndian>(self.mime_type.len() as u32)?;
107        writer.write_all(self.mime_type.as_bytes())?;
108
109        writer.write_u32::<BigEndian>(self.description.len() as u32)?;
110        writer.write_all(self.description.as_bytes())?;
111
112        writer.write_u32::<BigEndian>(self.width)?;
113        writer.write_u32::<BigEndian>(self.height)?;
114
115        writer.write_u32::<BigEndian>(self.depth)?;
116        writer.write_u32::<BigEndian>(self.colors)?;
117
118        writer.write_u32::<BigEndian>(self.data.len() as u32)?;
119        writer.write_all(&self.data)?;
120        Ok(())
121    }
122}
123
124impl fmt::Debug for BlockPicture {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        let mut prefix = "".to_owned();
127        if let Some(width) = f.width() {
128            prefix = " ".repeat(width);
129        }
130        writeln!(
131            f,
132            "{prefix}type: {} ({})",
133            self.picture_type as u8,
134            self.picture_type.as_str(),
135            prefix = prefix
136        )?;
137        writeln!(f, "{prefix}MIME type: {}", self.mime_type, prefix = prefix)?;
138        writeln!(
139            f,
140            "{prefix}description: {}",
141            self.description,
142            prefix = prefix
143        )?;
144        writeln!(f, "{prefix}width: {}", self.width, prefix = prefix)?;
145        writeln!(f, "{prefix}height: {}", self.height, prefix = prefix)?;
146        writeln!(f, "{prefix}depth: {}", self.depth, prefix = prefix)?;
147        writeln!(
148            f,
149            "{prefix}colors: {}{}",
150            self.colors,
151            if self.color_indexed() {
152                ""
153            } else {
154                " (unindexed)"
155            },
156            prefix = prefix
157        )?;
158        writeln!(
159            f,
160            "{prefix}data length: {}",
161            self.data.len(),
162            prefix = prefix
163        )?;
164        Ok(())
165    }
166}
167
168impl BlockPicture {
169    pub fn new<P: AsRef<Path>>(
170        file: P,
171        picture_type: PictureType,
172        description: String,
173    ) -> Result<Self> {
174        let img = image::open(file.as_ref())?;
175        let mut data = Vec::new();
176        std::fs::File::open(file.as_ref())?.read_to_end(&mut data)?;
177
178        let mut ext = file.as_ref().extension().unwrap().to_string_lossy();
179        if ext == "jpg" {
180            ext = Cow::Borrowed("jpeg");
181        }
182
183        Ok(Self {
184            picture_type,
185            mime_type: format!("image/{}", ext),
186            description,
187            width: img.width(),
188            height: img.height(),
189            depth: img.color().bits_per_pixel() as u32,
190            colors: 0, // TODO: support format with indexed-color support
191            data,
192        })
193    }
194
195    pub fn color_indexed(&self) -> bool {
196        self.colors != 0
197    }
198}
199
200/// The picture type according to the ID3v2 APIC frame:
201/// Others are reserved and should not be used. There may only be one each of picture type 1 and 2 in a file.
202#[repr(u32)]
203#[derive(Copy, Clone, Debug, FromPrimitive, PartialEq, Eq)]
204pub enum PictureType {
205    /// 0 - Other
206    Other,
207    /// 1 - 32x32 pixels 'file icon' (PNG only)
208    FileIcon,
209    /// 2 - Other file icon
210    OtherFileIcon,
211    /// 3 - Cover (front)
212    CoverFront,
213    /// 4 - Cover (back)
214    CoverBack,
215    /// 5 - Leaflet page
216    LeafletPage,
217    /// 6 - Media (e.g. label side of CD)
218    Media,
219    /// 7 - Lead artist/lead performer/soloist
220    LeadArtist,
221    /// 8 - Artist/performer
222    Artist,
223    /// 9 - Conductor
224    Conductor,
225    /// 10 - Band/Orchestra
226    Band,
227    /// 11 - Composer
228    Composer,
229    /// 12 - Lyricist/text writer
230    Lyricist,
231    /// 13 - Recording Location
232    RecordingLocation,
233    /// 14 - During recording
234    DuringRecording,
235    /// 15 - During performance
236    DuringPerformance,
237    /// 16 - Movie/video screen capture
238    MovieVideoScreenCapture,
239    /// 17 - A bright coloured fish
240    BrightColoredFish,
241    /// 18 - Illustration
242    Illustration,
243    /// 19 - Band/artist logotype
244    BandArtistLogotype,
245    /// 20 - Publisher/Studio logotype
246    PublisherStudioLogotype,
247    /// Unknown Picture Type
248    Unknown,
249}
250
251impl PictureType {
252    pub fn as_str(&self) -> &'static str {
253        match self {
254            PictureType::Other => "Other",
255            PictureType::FileIcon => "32x32 pixels 'file icon' (PNG only)",
256            PictureType::OtherFileIcon => "Other file icon",
257            PictureType::CoverFront => "Cover (front)",
258            PictureType::CoverBack => "Cover (back)",
259            PictureType::LeafletPage => "Leaflet page",
260            PictureType::Media => "Media (e.g. label side of CD)",
261            PictureType::LeadArtist => "Lead artist/lead performer/soloist",
262            PictureType::Artist => "Artist/performer",
263            PictureType::Conductor => "Conductor",
264            PictureType::Band => "Band/Orchestra",
265            PictureType::Composer => "Composer",
266            PictureType::Lyricist => "Lyricist/text writer",
267            PictureType::RecordingLocation => "Recording Location",
268            PictureType::DuringRecording => "During recording",
269            PictureType::DuringPerformance => "During performance",
270            PictureType::MovieVideoScreenCapture => "Movie/video screen capture",
271            PictureType::BrightColoredFish => "A bright coloured fish",
272            PictureType::Illustration => "Illustration",
273            PictureType::BandArtistLogotype => "Band/artist logotype",
274            PictureType::PublisherStudioLogotype => "Publisher/Studio logotype",
275            PictureType::Unknown => "Unknown",
276        }
277    }
278}
279
280impl FromStr for PictureType {
281    type Err = FlacError;
282
283    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
284        if let Ok(n) = u32::from_str(s) {
285            if n <= 20 {
286                // n is valid, should not fail
287                return Ok(FromPrimitive::from_u32(n).unwrap());
288            }
289        }
290
291        match s.to_ascii_lowercase().as_str() {
292            "other" => Ok(PictureType::Other),
293            "file_icon" => Ok(PictureType::FileIcon),
294            "other_file_icon" => Ok(PictureType::OtherFileIcon),
295            "cover" | "front_cover" => Ok(PictureType::CoverFront),
296            "back_cover" => Ok(PictureType::CoverBack),
297            "leaflet" => Ok(PictureType::LeafletPage),
298            "media" => Ok(PictureType::Media),
299            "lead_artist" => Ok(PictureType::LeadArtist),
300            "artist" => Ok(PictureType::Artist),
301            "conductor" => Ok(PictureType::Conductor),
302            "band" => Ok(PictureType::Band),
303            "composer" => Ok(PictureType::Composer),
304            "lyricist" => Ok(PictureType::Lyricist),
305            "recording_location" => Ok(PictureType::RecordingLocation),
306            "during_recording" => Ok(PictureType::DuringRecording),
307            "during_performance" => Ok(PictureType::DuringPerformance),
308            "screen_capture" => Ok(PictureType::MovieVideoScreenCapture),
309            "bright_colored_fish" => Ok(PictureType::BrightColoredFish),
310            "illustration" => Ok(PictureType::Illustration),
311            "band_logo" | "artist_logo" => Ok(PictureType::BandArtistLogotype),
312            "publisher_logo" | "studio_logo" => Ok(PictureType::PublisherStudioLogotype),
313            &_ => Err(Self::Err::InvalidPictureType),
314        }
315    }
316}