mp3_metadata/
metadata.rs

1use std::fs::File;
2use std::io::Read;
3use std::path::Path;
4use std::time::Duration;
5
6use consts::{BITRATES, SAMPLING_FREQ};
7use enums::{ChannelType, Copyright, CRC, Emphasis, Error, Genre, Layer, Status, Version};
8use types::{AudioTag, Frame, MP3Metadata, OptionalAudioTags};
9use utils::{compute_duration, create_utf8_str, get_line, get_samp_line, get_text_field, get_text_fields};
10use utils::{get_url_field, get_url_fields};
11
12fn get_id3(i: &mut u32, buf: &[u8], meta: &mut MP3Metadata) -> Result<(), Error> {
13    let mut x = *i as usize;
14    // Get extended information
15    if buf.len() > 32 && x + 32 < buf.len() && // APE
16       buf[x] == b'A' && buf[x + 1] == b'P' && buf[x + 2] == b'E' && buf[x + 3] == b'T' &&
17       buf[x + 4] == b'A' && buf[x + 5] == b'G' && buf[x + 6] == b'E' && buf[x + 7] == b'X' {
18           *i += 31; // skip APE header / footer
19           Ok(())
20    } else if buf.len() > 127 && x + 127 < buf.len() && // V1
21       buf[x] == b'T' && buf[x + 1] == b'A' && buf[x + 2] == b'G' {
22        if meta.tag.is_some() {
23            return Err(Error::DuplicatedIDV3);
24        }
25        if let Some(last) = meta.frames.last_mut() {
26            if *i <= last.size {
27                return Ok(());
28            }
29            last.size = *i - last.size - 1;
30        }
31        *i += 126;
32        // tag v1
33        meta.tag = Some(AudioTag {
34            title: create_utf8_str(&buf[x + 3..][..30]),
35            artist: create_utf8_str(&buf[x + 33..][..30]),
36            album: create_utf8_str(&buf[x + 63..][..30]),
37            year: create_utf8_str(&buf[x + 93..][..4]).parse::<u16>().unwrap_or(0),
38            comment: create_utf8_str(&buf[x + 97..][..if buf[x + 97 + 28] != 0 { 30 } else { 28 }]),
39            genre: Genre::from(buf[x + 127]),
40        });
41        Ok(())
42    } else if buf.len() > x + 13 && // V2 and above
43              buf[x] == b'I' && buf[x + 1] == b'D' && buf[x + 2] == b'3' {
44        let maj_version = buf[x + 3];
45        let min_version = buf[x + 4];
46
47        if maj_version > 4 {
48            return Ok(());
49        }
50
51        let tag_size = ((buf[x + 9] as usize) & 0xFF) |
52                       (((buf[x + 8] as usize) & 0xFF) << 7) |
53                       (((buf[x + 7] as usize) & 0xFF) << 14) |
54                       ((((buf[x + 6] as usize) & 0xFF) << 21) + 10);
55        let use_sync = buf[x + 5] & 0x80 != 0;
56        let has_extended_header = buf[x + 5] & 0x40 != 0;
57
58        x += 10;
59
60        if has_extended_header {
61            if x + 4 >= buf.len() {
62                *i = x as u32;
63                return Ok(())
64            }
65            let header_size = ((buf[x] as u32) << 21) |
66                              ((buf[x + 1] as u32) << 14) |
67                              ((buf[x + 2] as u32) << 7) |
68                              buf[x + 3] as u32;
69            if header_size < 4 {
70                return Ok(())
71            }
72            x += header_size as usize - 4;
73        }
74
75        *i = x as u32 + tag_size as u32;
76        if x + tag_size >= buf.len() {
77            return Ok(())
78        }
79
80        // Recreate the tag if desynchronization is used inside; we need to replace
81        // 0xFF 0x00 with 0xFF
82        let mut v = Vec::new();
83        let (buf, length) = if use_sync {
84            let mut new_pos = 0;
85            let mut skip = false;
86            v.reserve(tag_size);
87
88            for i in 0..tag_size {
89                if skip {
90                    skip = false;
91                    continue;
92                }
93                if i + 1 >= buf.len() {
94                    return Ok(());
95                }
96                if i + 1 < tag_size && buf[i] == 0xFF && buf[i + 1] == 0 {
97                    v[new_pos] = 0xFF;
98                    new_pos += 1;
99                    skip = true;
100                    continue;
101                }
102                if new_pos >= v.len() {
103                    return Ok(());
104                }
105                v[new_pos] = buf[i];
106                new_pos += 1;
107            }
108            (v.as_slice(), new_pos)
109        } else {
110            (buf, tag_size)
111        };
112
113        let mut pos = x;
114        let id3_frame_size = if maj_version < 3 { 6 } else { 10 };
115        let mut op = OptionalAudioTags::default();
116        let mut changes = false;
117        loop {
118            if pos + id3_frame_size > x + length {
119                break;
120            }
121
122            // Check if there is there a frame.
123            if buf[pos] < b'A' || buf[pos] > b'Z' {
124                break;
125            }
126
127            // Frame name is 3 chars in pre-ID3v3 and 4 chars after
128            let (frame_name, frame_size) = if maj_version < 3 {
129                (create_utf8_str(&buf[pos..][..3]), (buf[pos + 5] as u32 & 0xFF) |
130                                          ((buf[pos + 4] as u32 & 0xFF) << 8) |
131                                          ((buf[pos + 3] as u32 & 0xFF) << 16))
132            } else if maj_version < 4 {
133                (create_utf8_str(&buf[pos..][..4]), (buf[pos + 7] as u32 & 0xFF) |
134                                          ((buf[pos + 6] as u32 & 0xFF) << 8) |
135                                          ((buf[pos + 5] as u32 & 0xFF) << 16) |
136                                          ((buf[pos + 4] as u32 & 0xFF) << 24))
137            } else {
138                (create_utf8_str(&buf[pos..][..4]), (buf[pos + 7] as u32 & 0xFF) |
139                                          ((buf[pos + 6] as u32 & 0xFF) << 7) |
140                                          ((buf[pos + 5] as u32 & 0xFF) << 14) |
141                                          ((buf[pos + 4] as u32 & 0xFF) << 21))
142            };
143
144            pos += id3_frame_size;
145            if pos + frame_size as usize > x + length {
146                break;
147            }
148
149            // http://id3.org/id3v2.3.0#Declared_ID3v2_frames
150            match &frame_name[..] {
151                // -----------------------
152                // ----- TEXT FRAMES -----
153                // -----------------------
154                "TALB" => get_text_field(buf, pos, frame_size, &mut changes,
155                                         &mut op.album_movie_show),
156                "TBPM" => get_text_field(buf, pos, frame_size, &mut changes,
157                                         &mut op.bpm),
158                "TCOM" => get_text_fields(buf, pos, frame_size, &mut changes,
159                                          &mut op.composers),
160                "TCON" => {
161                    let mut s = None;
162                    get_text_field(buf, pos, frame_size, &mut changes, &mut s);
163                    if let Some(s) = s {
164                        if !s.is_empty() {
165                            if s.starts_with('(') && s.ends_with(')') {
166                                let v = s.split(')').collect::<Vec<&str>>().into_iter().filter_map(|a| {
167                                    match a.replace('(', "").parse::<u8>() {
168                                        Ok(num) => Some(Genre::from(num)),
169                                        _ => None,
170                                    }
171                                }).collect::<Vec<Genre>>();
172                                if !v.is_empty() {
173                                    for entry in v {
174                                        op.content_type.push(entry);
175                                    }
176                                } else {
177                                    op.content_type.push(Genre::from(s.as_str()));
178                                }
179                            } else {
180                                op.content_type.push(Genre::from(s.as_str()));
181                            }
182                        }
183                    }
184
185                }
186                "TCOP" => get_text_field(buf, pos, frame_size, &mut changes,
187                                         &mut op.copyright),
188                "TDAT" => get_text_field(buf, pos, frame_size, &mut changes,
189                                         &mut op.date),
190                "TDLY" => get_text_field(buf, pos, frame_size, &mut changes,
191                                         &mut op.playlist_delay),
192                "TENC" => get_text_field(buf, pos, frame_size, &mut changes,
193                                         &mut op.encoded_by),
194                "TEXT" => get_text_fields(buf, pos, frame_size, &mut changes,
195                                          &mut op.text_writers),
196                "TFLT" => get_text_field(buf, pos, frame_size, &mut changes,
197                                         &mut op.file_type),
198                "TIME" => get_text_field(buf, pos, frame_size, &mut changes,
199                                         &mut op.time),
200                "TIT" | "TIT2" => get_text_field(buf, pos, frame_size,
201                                                 &mut changes, &mut op.title),
202                "TIT1" => get_text_field(buf, pos, frame_size, &mut changes,
203                                         &mut op.content_group_description),
204                "TIT3" => get_text_field(buf, pos, frame_size, &mut changes,
205                                         &mut op.subtitle_refinement_description),
206                "TKEY" => get_text_field(buf, pos, frame_size, &mut changes,
207                                         &mut op.initial_key),
208                "TLAN" => get_text_field(buf, pos, frame_size, &mut changes,
209                                         &mut op.language),
210                "TLEN" => get_text_field(buf, pos, frame_size, &mut changes,
211                                         &mut op.length),
212                "TMED" => get_text_field(buf, pos, frame_size, &mut changes,
213                                         &mut op.media_type),
214                "TOAL" => get_text_field(buf, pos, frame_size, &mut changes,
215                                         &mut op.original_album_move_show_title),
216                "TOFN" => get_text_field(buf, pos, frame_size, &mut changes,
217                                         &mut op.original_filename),
218                "TOLY" => get_text_fields(buf, pos, frame_size, &mut changes,
219                                          &mut op.original_text_writers),
220                "TOPE" => get_text_fields(buf, pos, frame_size, &mut changes,
221                                          &mut op.original_artists),
222                "TORY" => get_text_field(buf, pos, frame_size, &mut changes,
223                                         &mut op.original_release_year),
224                "TOWN" => get_text_field(buf, pos, frame_size, &mut changes,
225                                         &mut op.file_owner),
226                "TPE1" => get_text_fields(buf, pos, frame_size, &mut changes,
227                                          &mut op.performers),
228                "TPE2" => get_text_field(buf, pos, frame_size, &mut changes,
229                                         &mut op.band),
230                "TPE3" => get_text_field(buf, pos, frame_size, &mut changes,
231                                         &mut op.conductor),
232                "TPE4" => get_text_field(buf, pos, frame_size, &mut changes,
233                                         &mut op.interpreted),
234                "TPOS" => get_text_field(buf, pos, frame_size, &mut changes,
235                                         &mut op.part_of_a_set),
236                "TPUB" => get_text_field(buf, pos, frame_size, &mut changes,
237                                         &mut op.publisher),
238                "TRCK" => get_text_field(buf, pos, frame_size, &mut changes,
239                                         &mut op.track_number),
240                "TRDA" => get_text_field(buf, pos, frame_size, &mut changes,
241                                         &mut op.recording_dates),
242                "TRSN" => get_text_field(buf, pos, frame_size, &mut changes,
243                                         &mut op.internet_radio_station_name),
244                "TRSO" => get_text_field(buf, pos, frame_size, &mut changes,
245                                         &mut op.internet_radio_station_owner),
246                "TSIZ" => get_text_field(buf, pos, frame_size, &mut changes,
247                                         &mut op.size),
248                "TSRC" => get_text_field(buf, pos, frame_size, &mut changes,
249                                         &mut op.international_standard_recording_code),
250                "TSSE" => get_text_field(buf, pos, frame_size, &mut changes,
251                                         &mut op.soft_hard_setting),
252                "TYER" => get_text_field(buf, pos, frame_size, &mut changes,
253                                         &mut op.year),
254                "IPLS" => get_text_field(buf, pos, frame_size, &mut changes,
255                                         &mut op.involved_people),
256                // ----------------------
257                // ----- URL FRAMES -----
258                // ----------------------
259                "WCOM" => get_url_fields(buf, pos, frame_size, &mut changes,
260                                         &mut op.commercial_info_url),
261                "WCOP" => get_url_field(buf, pos, frame_size, &mut changes,
262                                        &mut op.copyright_info_url),
263                "WOAF" => get_url_field(buf, pos, frame_size, &mut changes,
264                                        &mut op.official_webpage),
265                "WOAR" => get_url_fields(buf, pos, frame_size, &mut changes,
266                                         &mut op.official_artist_webpage),
267                "WOAS" => get_url_field(buf, pos, frame_size, &mut changes,
268                                        &mut op.official_audio_source_webpage),
269                "WORS" => get_url_field(buf, pos, frame_size, &mut changes,
270                                        &mut op.official_internet_radio_webpage),
271                "WPAY" => get_url_field(buf, pos, frame_size, &mut changes,
272                                        &mut op.payment_url),
273                "WPUB" => get_url_field(buf, pos, frame_size, &mut changes,
274                                        &mut op.publishers_official_webpage),
275                _ => {
276                    // TODO: handle other type of fields, like picture
277                }
278            };
279
280            pos += frame_size as usize;
281        }
282        if changes {
283            op.position = meta.frames.len() as u32;
284            op.minor_version = min_version;
285            op.major_version = maj_version;
286            meta.optional_info.push(op);
287        }
288        Ok(())
289    } else {
290        Ok(())
291    }
292}
293
294fn read_header(buf: &[u8], i: &mut u32, meta: &mut MP3Metadata) -> Result<bool, Error> {
295    let header = (buf[*i as usize] as u32) << 24 | (buf[*i as usize + 1] as u32) << 16 |
296                 (buf[*i as usize + 2] as u32) << 8 | buf[*i as usize + 3] as u32;
297    if header & 0xffe00000 == 0xffe00000 && header & (3 << 17) != 0 &&
298       header & (0xf << 12) != (0xf << 12) && header & (3 << 10) != (3 << 10) {
299        let mut frame: Frame = Default::default();
300
301        frame.version = Version::from((header >> 19) & 3);
302        frame.layer = Layer::from((header >> 17) & 3);
303        frame.crc = CRC::from((header >> 16) & 1);
304
305        frame.bitrate = BITRATES[get_line(frame.version, frame.layer)]
306                                [((header >> 12) & 0xF) as usize];
307        frame.sampling_freq = SAMPLING_FREQ[get_samp_line(frame.version)]
308                                           [((header >> 10) & 0x3) as usize];
309        frame.padding = (header >> 9) & 1 == 1;
310        frame.private_bit = (header >> 8) & 1 == 1;
311
312        frame.chan_type = ChannelType::from((header >> 6) & 3);
313        let (intensity, ms_stereo) = match (header >> 4) & 3 {
314            0x1 => (true, false),
315            0x2 => (false, true),
316            0x3 => (true, true),
317            /*0x00*/ _ => (false, false),
318        };
319        frame.intensity_stereo = intensity;
320        frame.ms_stereo = ms_stereo;
321        frame.copyright = Copyright::from((header >> 3) & 1);
322        frame.status = Status::from((header >> 2) & 1);
323        frame.emphasis = Emphasis::from(header & 0x03);
324        frame.duration = compute_duration(frame.version,
325                                          frame.layer,
326                                          frame.sampling_freq);
327        frame.position = meta.duration;
328        frame.offset = *i;
329
330        if let Some(dur) = frame.duration {
331            meta.duration += dur;
332        }
333        /*frame.size = if frame.layer == Layer::Layer1 && frame.sampling_freq > 0 {
334            /*println!("{:4}: (12000 * {} / {} + {}) * 4 = {}", i, frame.bitrate as u64, frame.sampling_freq as u64,
335                if frame.slot { 1 } else { 0 },
336                    (12000 * frame.bitrate as u64 / frame.sampling_freq as u64 +
337                if frame.slot { 1 } else { 0 }) * 4);*/
338
339            (12000 * frame.bitrate as u64 / frame.sampling_freq as u64 +
340                if frame.slot { 1 } else { 0 }) * 4
341        } else if (frame.layer == Layer::Layer2 || frame.layer == Layer::Layer3) && frame.sampling_freq > 0 {
342            /*println!("{:4}: 144000 * {} / {} + {} = {}", i, frame.bitrate as u64, frame.sampling_freq as u64,
343                if frame.slot { 1 } else { 0 },
344                    144000 * frame.bitrate as u64 / frame.sampling_freq as u64 +
345                if frame.slot { 1 } else { 0 });*/
346
347            144000 * frame.bitrate as u64 / frame.sampling_freq as u64 +
348                if frame.slot { 1 } else { 0 }
349        } else {
350            continue 'a;
351        } as u32;*/
352        let samples_per_frame = match frame.layer {
353            Layer::Layer3 => {
354                if frame.version == Version::MPEG1 {
355                    1152
356                } else {
357                    576
358                }
359            }
360            Layer::Layer2 => 1152,
361            Layer::Layer1 => 384,
362            _ => unreachable!(),
363        };
364        frame.size = (samples_per_frame as u64 / 8 * frame.bitrate as u64 * 1000 /
365                      frame.sampling_freq as u64) as u32;
366        if frame.size < 1 {
367            return Ok(false);
368        }
369        if frame.padding {
370            frame.size += 1;
371        }
372        *i += frame.size;
373        meta.frames.push(frame);
374        Ok(true)
375    } else {
376        Ok(false)
377    }
378}
379
380pub fn read_from_file<P>(file: P) -> Result<MP3Metadata, Error>
381where P: AsRef<Path> {
382    if let Ok(mut fd) = File::open(file) {
383        let mut buf = Vec::new();
384
385        match fd.read_to_end(&mut buf) {
386            Ok(_) => read_from_slice(&buf),
387            Err(_) => Err(Error::FileError),
388        }
389    } else {
390        Err(Error::FileError)
391    }
392}
393
394pub fn read_from_slice(buf: &[u8]) -> Result<MP3Metadata, Error> {
395    let mut meta = MP3Metadata {
396        frames: Vec::new(),
397        duration: Duration::new(0, 0),
398        tag: None,
399        optional_info: Vec::new(),
400    };
401    let mut i = 0u32;
402
403    'a: while i < buf.len() as u32 {
404        loop {
405            get_id3(&mut i, buf, &mut meta)?;
406            if i + 3 >= buf.len() as u32 {
407                break 'a;
408            }
409            match read_header(buf, &mut i, &mut meta) {
410                Ok(true) => continue 'a,
411                Err(e) => return Err(e),
412                _ => {}
413            }
414            let old_i = i;
415            get_id3(&mut i, buf, &mut meta)?;
416            if i == old_i {
417                i += 1;
418            }
419            if i >= buf.len() as u32 {
420                break 'a;
421            }
422        }
423    }
424    if meta.tag.is_none() {
425        if let Some(last) = meta.frames.last_mut() {
426            if i <= last.size {
427                return Err(Error::InvalidData);
428            }
429        }
430    }
431    if meta.frames.is_empty() {
432        Err(Error::NotMP3)
433    } else {
434        Ok(meta)
435    }
436}
437
438#[test]
439fn not_mp3() {
440    let ret = read_from_file("src/lib.rs");
441
442    match ret {
443        Ok(_) => panic!("Wasn't supposed to be ok!"),
444        Err(e) => assert_eq!(e, Error::NotMP3),
445    }
446}
447
448#[test]
449fn double_id() {
450    let ret = read_from_file("assets/double_id.mp3");
451
452    match ret {
453        Ok(_) => panic!("Wasn't supposed to be ok!"),
454        Err(e) => assert_eq!(e, Error::DuplicatedIDV3),
455    }
456}