1 // $Id: tag_parse_musicmatch.cpp,v 1.19 2002/07/02 22:15:18 t1mpy Exp $
2
3 // id3lib: a C++ library for creating and manipulating id3v1/v2 tags
4 // Copyright 1999, 2000 Scott Thomas Haug
5
6 // This library is free software; you can redistribute it and/or modify it
7 // under the terms of the GNU Library General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or (at your
9 // option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful, but WITHOUT
12 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14 // License for more details.
15 //
16 // You should have received a copy of the GNU Library General Public License
17 // along with this library; if not, write to the Free Software Foundation,
18 // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 // The id3lib authors encourage improvements and optimisations to be sent to
21 // the id3lib coordinator. Please see the README file for details on where to
22 // send such submissions. See the AUTHORS file for a list of people who have
23 // contributed to id3lib. See the ChangeLog file for a list of changes to
24 // id3lib. These files are distributed with id3lib at
25 // http://download.sourceforge.net/id3lib/
26
27 #if defined HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30
31
32 #include <ctype.h>
33 #include "tag_impl.h" //has <stdio.h> "tag.h" "header_tag.h" "frame.h" "field.h" "spec.h" "id3lib_strings.h" "utils.h"
34 #include "helpers.h"
35 #include "id3/io_decorators.h" //has "readers.h" "io_helpers.h" "utils.h"
36
37 using namespace dami;
38
39 namespace
40 {
readSeconds(ID3_Reader & reader,size_t len)41 uint32 readSeconds(ID3_Reader& reader, size_t len)
42 {
43 io::ExitTrigger et(reader);
44 io::WindowedReader wr(reader, len);
45 ID3_Reader::pos_type beg = wr.getCur();
46 uint32 seconds = 0;
47 uint32 cur = 0;
48 while (!wr.atEnd())
49 {
50 ID3_Reader::char_type ch = wr.readChar();
51 if (':' == ch)
52 {
53 seconds += 60 * cur;
54 cur = 0;
55 }
56 else if (!isdigit(ch))
57 {
58 return 0;
59 }
60 else
61 {
62 cur = cur * 10 + (ch - '0');
63 }
64 }
65 et.release();
66 return seconds + cur;
67 }
68
readTextFrame(ID3_Reader & reader,ID3_FrameID id,const String desc="")69 ID3_Frame* readTextFrame(ID3_Reader& reader, ID3_FrameID id, const String desc = "")
70 {
71 uint32 size = io::readLENumber(reader, 2);
72 ID3D_NOTICE( "readTextFrame: size = " << size );
73 if (size == 0)
74 {
75 return NULL;
76 }
77
78 String text;
79 if (ID3FID_SONGLEN != id)
80 {
81 io::LineFeedReader lfr(reader);
82 text = io::readText(lfr, size);
83 ID3D_NOTICE( "readTextFrame: text = " << text );
84 }
85 else
86 {
87 text = toString(readSeconds(reader, size) * 1000);
88 ID3D_NOTICE( "readTextFrame: songlen = " << text );
89 }
90
91 ID3_Frame* frame = new ID3_Frame(id);
92 if (frame)
93 {
94 if (frame->Contains(ID3FN_TEXT))
95 {
96 frame->GetField(ID3FN_TEXT)->Set(text.c_str());
97 }
98 else if (frame->Contains(ID3FN_URL))
99 {
100 frame->GetField(ID3FN_URL)->Set(text.c_str());
101 }
102 if (frame->Contains(ID3FN_LANGUAGE))
103 {
104 frame->GetField(ID3FN_LANGUAGE)->Set("XXX");
105 }
106 if (frame->Contains(ID3FN_DESCRIPTION))
107 {
108 frame->GetField(ID3FN_DESCRIPTION)->Set(desc.c_str());
109 }
110 }
111 return frame;
112 }
113 };
114
parse(ID3_TagImpl & tag,ID3_Reader & rdr)115 bool mm::parse(ID3_TagImpl& tag, ID3_Reader& rdr)
116 {
117 io::ExitTrigger et(rdr);
118 ID3_Reader::pos_type end = rdr.getCur();
119 if (end < rdr.getBeg() + 48)
120 {
121 ID3D_NOTICE( "mm::parse: bailing, not enough bytes to parse, pos = " << end );
122 return false;
123 }
124
125 rdr.setCur(end - 48);
126 String version;
127
128 {
129 if (io::readText(rdr, 32) != "Brava Software Inc. ")
130 {
131 ID3D_NOTICE( "mm::parse: bailing, couldn't find footer" );
132 return false;
133 }
134
135 version = io::readText(rdr, 4);
136 if (version.size() != 4 ||
137 !isdigit(version[0]) || version[1] != '.' ||
138 !isdigit(version[2]) ||
139 !isdigit(version[3]))
140 {
141 ID3D_WARNING( "mm::parse: bailing, nonstandard version = " << version );
142 return false;
143 }
144 }
145
146 ID3_Reader::pos_type beg = rdr.setCur(end - 48);
147 et.setExitPos(beg);
148 if (end < 68)
149 {
150 ID3D_NOTICE( "mm::parse: bailing, not enough bytes to parse offsets, pos = " << end );
151 return false;
152 }
153 rdr.setCur(end - 68);
154
155 io::WindowedReader dataWindow(rdr);
156 dataWindow.setEnd(rdr.getCur());
157
158 uint32 offsets[5];
159
160 io::WindowedReader offsetWindow(rdr, 20);
161 for (size_t i = 0; i < 5; ++i)
162 {
163 offsets[i] = io::readLENumber(rdr, sizeof(uint32));
164 }
165
166 size_t metadataSize = 0;
167 if (version <= "3.00")
168 {
169 // All MusicMatch tags up to and including version 3.0 had metadata
170 // sections exactly 7868 bytes in length.
171 metadataSize = 7868;
172 }
173 else
174 {
175 // MusicMatch tags after version 3.0 had three possible lengths for their
176 // metadata sections. We can determine which it was by searching for
177 // the version section signature that should precede the metadata section
178 // by exactly 256 bytes.
179 size_t possibleSizes[] = { 8132, 8004, 7936 };
180
181 for (size_t i = 0; i < sizeof(possibleSizes)/sizeof(size_t); ++i)
182 {
183 dataWindow.setCur(dataWindow.getEnd());
184
185 // Our offset will be exactly 256 bytes prior to our potential metadata
186 // section
187 size_t offset = possibleSizes[i] + 256;
188 if (dataWindow.getCur() < offset)
189 {
190 // if our filesize is less than the offset, then it can't possibly
191 // be the correct offset, so try again.
192 continue;
193 }
194 dataWindow.setCur(dataWindow.getCur() - offset);
195
196 // now read in the signature to see if it's a match
197 if (io::readText(dataWindow, 8) == "18273645")
198 {
199 metadataSize = possibleSizes[i];
200 break;
201 }
202 }
203 }
204 if (0 == metadataSize)
205 {
206 // if we didn't establish a size for the metadata, then something is
207 // wrong. probably should log this.
208 ID3D_WARNING( "mm::parse: bailing, couldn't find meta data signature, end = " << end );
209 return false;
210 }
211
212 // parse the offset pointers to determine the actual sizes of all the
213 // sections
214 size_t sectionSizes[5];
215 size_t tagSize = metadataSize;
216
217 // we already know the size of the last section
218 sectionSizes[4] = metadataSize;
219
220 size_t lastOffset = 0;
221 for (int i = 0; i < 5; i++)
222 {
223 size_t thisOffset = offsets[i];
224 //ASSERT(thisOffset > lastOffset);
225 if (i > 0)
226 {
227 size_t sectionSize = thisOffset - lastOffset;
228 sectionSizes[i-1] = sectionSize;
229 tagSize += sectionSize;
230 }
231 lastOffset = thisOffset;
232 }
233
234 // now check to see that our tag size is reasonable
235 if (dataWindow.getEnd() < tagSize)
236 {
237 // Ack! The tag size doesn't jive with the tag's ending position in
238 // the file. Bail!
239 ID3D_WARNING( "mm::parse: bailing, tag size is too big, tag size = " << tagSize << ", end = " << end );
240 return false;
241 }
242
243 dataWindow.setBeg(dataWindow.getEnd() - tagSize);
244 dataWindow.setCur(dataWindow.getBeg());
245
246 // Now calculate the adjusted offsets
247 offsets[0] = dataWindow.getBeg();
248 for (size_t i = 0; i < 4; ++i)
249 {
250 offsets[i+1] = offsets[i] + sectionSizes[i];
251 }
252
253 // now check for a tag header and adjust the tag_beg pointer appropriately
254 if (dataWindow.getBeg() >= 256)
255 {
256 rdr.setCur(dataWindow.getBeg() - 256);
257 if (io::readText(rdr, 8) == "18273645")
258 {
259 et.setExitPos(rdr.getCur() - 8);
260 }
261 else
262 {
263 et.setExitPos(dataWindow.getBeg());
264 }
265 dataWindow.setCur(dataWindow.getBeg());
266 }
267
268 // Now parse the various sections...
269
270 // Parse the image extension at offset 0
271 dataWindow.setCur(offsets[0]);
272 String imgExt = io::readTrailingSpaces(dataWindow, 4);
273
274 // Parse the image binary at offset 1
275 dataWindow.setCur(offsets[1]);
276 uint32 imgSize = io::readLENumber(dataWindow, 4);
277 if (imgSize == 0)
278 {
279 // no image binary. don't do anything.
280 }
281 else
282 {
283 io::WindowedReader imgWindow(dataWindow, imgSize);
284 if (imgWindow.getEnd() < imgWindow.getBeg() + imgSize)
285 {
286 // Ack! The image size given extends beyond the next offset! This is
287 // not good... log?
288 }
289 else
290 {
291 BString imgData = io::readAllBinary(imgWindow);
292 ID3_Frame* frame = new ID3_Frame(ID3FID_PICTURE);
293 if (frame)
294 {
295 String mimetype("image/");
296 mimetype += imgExt;
297 frame->GetField(ID3FN_MIMETYPE)->Set(mimetype.c_str());
298 frame->GetField(ID3FN_IMAGEFORMAT)->Set("");
299 frame->GetField(ID3FN_PICTURETYPE)->Set(static_cast<unsigned int>(0));
300 frame->GetField(ID3FN_DESCRIPTION)->Set("");
301 frame->GetField(ID3FN_DATA)->Set(reinterpret_cast<const uchar*>(imgData.data()), imgData.size());
302 tag.AttachFrame(frame);
303 }
304 }
305 }
306
307 //file.seekg(offsets[2]);
308 //file.seekg(offsets[3]);
309 dataWindow.setCur(offsets[4]);
310
311 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_TITLE));
312 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_ALBUM));
313 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_LEADARTIST));
314 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_CONTENTTYPE));
315 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Tempo"));
316 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Mood"));
317 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Situation"));
318 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Preference"));
319 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_SONGLEN));
320
321 // The next 12 bytes can be ignored. The first 8 represent the
322 // creation date as a 64 bit floating point number. The last 4 are
323 // for a play counter.
324 dataWindow.skipChars(12);
325
326 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Path"));
327 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Serial"));
328
329 // 2 bytes for track
330 uint32 trkNum = io::readLENumber(dataWindow, 2);
331 if (trkNum > 0)
332 {
333 String trkStr = toString(trkNum);
334 ID3_Frame* frame = new ID3_Frame(ID3FID_TRACKNUM);
335 if (frame)
336 {
337 frame->GetField(ID3FN_TEXT)->Set(trkStr.c_str());
338 tag.AttachFrame(frame);
339 }
340 }
341
342 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Notes"));
343 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_Bio"));
344 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_UNSYNCEDLYRICS));
345 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_WWWARTIST));
346 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_WWWCOMMERCIALINFO));
347 tag.AttachFrame(readTextFrame(dataWindow, ID3FID_COMMENT, "MusicMatch_ArtistEmail"));
348
349 // email?
350
351 return true;
352 }
353
354