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