id3lib  3.8.3
tag_parse.cpp
Go to the documentation of this file.
1 // $Id: tag_parse.cpp,v 1.47 2002/11/24 17:33:30 t1mpy Exp $
2 
3 // id3lib: a C++ library for creating and manipulating id3v1/v2 tags
4 // Copyright 1999, 2000 Scott Thomas Haug
5 // Copyright 2002 Thijmen Klok (thijmen@id3lib.org)
6 
7 // This library is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU Library General Public License as published by
9 // the Free Software Foundation; either version 2 of the License, or (at your
10 // option) any later version.
11 //
12 // This library is distributed in the hope that it will be useful, but WITHOUT
13 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 // FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
15 // License for more details.
16 //
17 // You should have received a copy of the GNU Library General Public License
18 // along with this library; if not, write to the Free Software Foundation,
19 // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 
21 // The id3lib authors encourage improvements and optimisations to be sent to
22 // the id3lib coordinator. Please see the README file for details on where to
23 // send such submissions. See the AUTHORS file for a list of people who have
24 // contributed to id3lib. See the ChangeLog file for a list of changes to
25 // id3lib. These files are distributed with id3lib at
26 // http://download.sourceforge.net/id3lib/
27 
28 //#if defined HAVE_CONFIG_H
29 //#include <config.h> // Must include before zlib.h to compile on WinCE
30 //#endif
31 
32 //#include <zlib.h>
33 //#include <string.h>
34 //#include <memory.h>
35 
36 #include "tag_impl.h" //has <stdio.h> "tag.h" "header_tag.h" "frame.h" "field.h" "spec.h" "id3lib_strings.h" "utils.h"
37 //#include "id3/io_decorators.h" //has "readers.h" "io_helpers.h" "utils.h"
38 #include "io_strings.h"
39 
40 using namespace dami;
41 
42 namespace
43 {
44  bool parseFrames(ID3_TagImpl& tag, ID3_Reader& rdr)
45  {
46  ID3_Reader::pos_type beg = rdr.getCur();
47  io::ExitTrigger et(rdr, beg);
48  ID3_Reader::pos_type last_pos = beg;
49  size_t totalSize = 0;
50  size_t frameSize = 0;
51  while (!rdr.atEnd() && rdr.peekChar() != '\0')
52  {
53  ID3D_NOTICE( "id3::v2::parseFrames(): rdr.getBeg() = " << rdr.getBeg() );
54  ID3D_NOTICE( "id3::v2::parseFrames(): rdr.getCur() = " << rdr.getCur() );
55  ID3D_NOTICE( "id3::v2::parseFrames(): rdr.getEnd() = " << rdr.getEnd() );
56  last_pos = rdr.getCur();
57  ID3_Frame* f = new ID3_Frame;
58  f->SetSpec(tag.GetSpec());
59  bool goodParse = f->Parse(rdr);
60  frameSize = rdr.getCur() - last_pos;
61  ID3D_NOTICE( "id3::v2::parseFrames(): frameSize = " << frameSize );
62  totalSize += frameSize;
63 
64  if (frameSize == 0)
65  {
66  // There is a problem.
67  // If the frame size is 0, then we can't progress.
68  ID3D_WARNING( "id3::v2::parseFrames(): frame size is 0, can't " <<
69  "continue parsing frames");
70  delete f;
71  // Break for now.
72  break;
73  }
74  else if (!goodParse)
75  {
76  // bad parse! we can't attach this frame.
77  ID3D_WARNING( "id3::v2::parseFrames(): bad parse, deleting frame");
78  delete f;
79  }
80  else if (f->GetID() != ID3FID_METACOMPRESSION)
81  {
82  ID3D_NOTICE( "id3::v2::parseFrames(): attaching non-compressed " <<
83  "frame");
84  // a good, uncompressed frame. attach away!
85  tag.AttachFrame(f);
86  }
87  else
88  {
89  ID3D_NOTICE( "id3::v2::parseFrames(): parsing ID3v2.2.1 " <<
90  "compressed frame");
91  // hmm. an ID3v2.2.1 compressed frame. It contains 1 or more
92  // compressed frames. Uncompress and call parseFrames recursively.
93  ID3_Field* fld = f->GetField(ID3FN_DATA);
94  if (fld)
95  {
96  ID3_MemoryReader mr(fld->GetRawBinary(), fld->BinSize());
97  ID3_Reader::char_type ch = mr.readChar();
98  if (ch != 'z')
99  {
100  // unknown compression method
101  ID3D_WARNING( "id3::v2::parseFrames(): unknown compression id " <<
102  " = '" << ch << "'" );
103  }
104  else
105  {
106  uint32 newSize = io::readBENumber(mr, sizeof(uint32));
107  size_t oldSize = f->GetDataSize() - sizeof(uint32) - 1;
108  io::CompressedReader cr(mr, newSize);
109  parseFrames(tag, cr);
110  if (!cr.atEnd())
111  {
112  // hmm. it didn't parse the entire uncompressed data. wonder
113  // why.
114  ID3D_WARNING( "id3::v2::parseFrames(): didn't parse entire " <<
115  "id3v2.2.1 compressed memory stream");
116  }
117  }
118  }
119  delete f;
120  }
121  et.setExitPos(rdr.getCur());
122  }
123  if (rdr.peekChar() == '\0')
124  {
125  ID3D_NOTICE( "id3::v2::parseFrames: done parsing, padding at postion " <<
126  rdr.getCur() );
127  }
128  else
129  {
130  ID3D_NOTICE( "id3::v2::parseFrames: done parsing, [cur, end] = [" <<
131  rdr.getCur() << ", " << rdr.getEnd() << "]" );
132  }
133  return true;
134  }
135 };
136 
138 {
139  ID3_Reader::pos_type beg = reader.getCur();
140  io::ExitTrigger et(reader);
141 
142  ID3_TagHeader hdr;
143 
144  io::WindowedReader wr(reader, ID3_TagHeader::SIZE);
145 
146  if (!hdr.Parse(wr) || wr.getCur() == beg)
147  {
148  ID3D_NOTICE( "id3::v2::parse(): parsing header failes" );
149  return false;
150  }
151  if (hdr.GetExtended())
152  {
153  hdr.ParseExtended(reader);
154  }
155  tag.SetSpec(hdr.GetSpec());
156 
157  size_t dataSize = hdr.GetDataSize();
158  ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): dataSize = " << dataSize);
159 
160  wr.setWindow(wr.getCur(), dataSize);
161  et.setExitPos(wr.getEnd());
162 
163  ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): data window beg = " << wr.getBeg() );
164  ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): data window cur = " << wr.getCur() );
165  ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): data window end = " << wr.getEnd() );
166  tag.SetExtended(hdr.GetExtended());
167  if (!hdr.GetUnsync())
168  {
169  tag.SetUnsync(false);
170  parseFrames(tag, wr);
171  }
172  else
173  {
174  // The buffer has been unsynced. It will have to be resynced to be
175  // readable. This has to be done a character at a time.
176  //
177  // The original reader may be reading in characters from a file. Doing
178  // this a character at a time is quite slow. To improve performance, read
179  // in the entire buffer into a string, then create an UnsyncedReader from
180  // the string.
181  //
182  // It might be better to implement a BufferedReader so that the details
183  // of this can be abstracted away behind a class
184  tag.SetUnsync(true);
185  BString raw = io::readAllBinary(wr);
186  io::BStringReader bsr(raw);
187  io::UnsyncedReader ur(bsr);
188  ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): unsync beg = " << ur.getBeg() );
189  ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): unsync cur = " << ur.getCur() );
190  ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): unsync end = " << ur.getEnd() );
191 
192  // Now read the UnsyncedReader into another string, and parse the frames
193  // from the string. This is done so that 1. the unsynced reader is
194  // unsynced exactly once, removing the possibility of multiple unsyncings
195  // of the same string, and 2) so that calls to readChars aren't done a
196  // character at a time for every call
197  BString synced = io::readAllBinary(ur);
198  io::BStringReader sr(synced);
199  parseFrames(tag, sr);
200  }
201 
202  return true;
203 }
204 
206 {
207  ifstream file;
208  if (ID3E_NoError != openReadableFile(this->GetFileName(), file))
209  {
210  // log this...
211  return;
212  }
213  ID3_IFStreamReader ifsr(file);
214  ParseReader(ifsr);
215  file.close();
216 }
217 
218 //used for streaming media
220 {
221  size_t mp3_core_size;
222  size_t bytes_till_sync;
223 
224  io::WindowedReader wr(reader);
225  wr.setBeg(wr.getCur());
226 
227  _file_tags.clear();
228  _file_size = reader.getEnd();
229 
230  ID3_Reader::pos_type beg = wr.getBeg();
231  ID3_Reader::pos_type cur = wr.getCur();
232  ID3_Reader::pos_type end = wr.getEnd();
233 
234  ID3_Reader::pos_type last = cur;
235 
236  if (_tags_to_parse.test(ID3TT_ID3V2))
237  {
238  do
239  {
240  last = cur;
241  // Parse tags at the beginning of the file first...
242  if (id3::v2::parse(*this, wr))
243  {
244  _file_tags.add(ID3TT_ID3V2);
245  }
246  cur = wr.getCur();
247  wr.setBeg(cur);
248  } while (!wr.atEnd() && cur > last);
249  }
250  // add silly padding outside the tag to _prepended_bytes
251  if (!wr.atEnd() && wr.peekChar() == '\0')
252  {
253  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): found padding outside tag" );
254  do
255  {
256  last = cur;
257  cur = wr.getCur() + 1;
258  wr.setBeg(cur);
259  wr.setCur(cur);
260  } while (!wr.atEnd() && cur > last && wr.peekChar() == '\0');
261  }
262  if (!wr.atEnd() && _file_size - (cur - beg) > 4 && wr.peekChar() == 255)
263  { //unfortunatly, this is necessary for finding an invalid padding
264  wr.setCur(cur + 1); //cur is known by peekChar
265  if (wr.readChar() == '\0' && wr.readChar() == '\0' && wr.peekChar() == '\0')
266  { //three empty bytes found, enough for me, this is stupid padding
267  cur += 3; //those are now allready read in (excluding the peekChar, since it will be added by do{})
268  do
269  {
270  last = cur;
271  cur = wr.getCur() + 1;
272  wr.setBeg(cur);
273  wr.setCur(cur);
274  } while (!wr.atEnd() && cur > last && wr.peekChar() == '\0');
275  }
276  else
277  wr.setCur(cur);
278  }
279  _prepended_bytes = cur - beg;
280  // go looking for the first sync byte to add to bytes_till_sync
281  // by not adding it to _prepended_bytes, we preserve this 'unknown' data
282  // The routine's only effect is helping the lib to find things as bitrate etc.
283  beg = wr.getBeg();
284  if (!wr.atEnd() && wr.peekChar() != 0xFF) //no sync byte, so, either this is not followed by a mp3 file or it's a fLaC file, or an encapsulating format, better check it
285  {
286  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): Didn't find mp3 sync byte" );
287  if ((_file_size - (cur - beg)) >= 4)
288  { //there is room to search for some kind of ID
289  unsigned char buf[5];
290  wr.readChars(buf, 4);
291  buf[4] = '\0';
292  // check for RIFF (an encapsulating format) ID
293  if (strncmp((char*)buf, "RIFF", 4) == 0 || strncmp((char*)buf, "RIFX", 4) == 0)
294  {
295  // next 4 bytes are RIFF size, skip them
296  cur = wr.getCur() + 4;
297  wr.setCur(cur);
298  // loop until first possible sync byte
299  if (!wr.atEnd() && wr.peekChar() != 0xFF)
300  {
301  do
302  {
303  last = cur;
304  cur = wr.getCur() + 1;
305  wr.setCur(cur);
306  } while (!wr.atEnd() && cur > last && wr.peekChar() != 0xFF);
307  }
308  }
309  else if (strncmp((char*)buf, "fLaC", 4) == 0)
310  { //a FLAC file, no need looking for a sync byte
311  beg = cur;
312  }
313  else
314  { //since we set the cursor 4 bytes ahead for looking for RIFF, RIFX or fLaC, better set it back
315  // but peekChar allready checked the first one, so we add one
316  cur = cur + 1;
317  wr.setCur(cur);
318  //go looking for a sync byte
319  if (!wr.atEnd() && wr.peekChar() != 0xFF) //no sync byte, we have an unknown byte
320  {
321  do
322  {
323  last = cur;
324  cur = wr.getCur() + 1;
325  wr.setCur(cur);
326  } while (!wr.atEnd() && cur > last && wr.peekChar() != 0xFF);
327  }
328  }
329  } //if ((_file_size - (cur - beg)) >= 4)
330  else
331  { //remaining size is smaller than 4 bytes, can't be useful, but leave it for now
332  beg = cur;
333  //file.close();
334  //return;
335  }
336  }
337  bytes_till_sync = cur - beg;
338 
339  cur = wr.setCur(end);
340  if (_file_size > _prepended_bytes)
341  {
342  do
343  {
344  last = cur;
345  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): beg = " << wr.getBeg() );
346  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): cur = " << wr.getCur() );
347  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): end = " << wr.getEnd() );
348  // ...then the tags at the end
349  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): musicmatch? cur = " << wr.getCur() );
350  if (_tags_to_parse.test(ID3TT_MUSICMATCH) && mm::parse(*this, wr))
351  {
352  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): musicmatch! cur = " << wr.getCur() );
353  _file_tags.add(ID3TT_MUSICMATCH);
354  wr.setEnd(wr.getCur());
355  }
356  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v1? cur = " << wr.getCur() );
357  if (_tags_to_parse.test(ID3TT_LYRICS3) && lyr3::v1::parse(*this, wr))
358  {
359  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v1! cur = " << wr.getCur() );
360  _file_tags.add(ID3TT_LYRICS3);
361  wr.setEnd(wr.getCur());
362  }
363  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v2? cur = " << wr.getCur() );
364  if (_tags_to_parse.test(ID3TT_LYRICS3V2) && lyr3::v2::parse(*this, wr))
365  {
366  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v2! cur = " << wr.getCur() );
367  _file_tags.add(ID3TT_LYRICS3V2);
368  cur = wr.getCur();
369  wr.setCur(wr.getEnd());//set to end to seek id3v1 tag
370  //check for id3v1 tag and set End accordingly
371  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1? cur = " << wr.getCur() );
372  if (_tags_to_parse.test(ID3TT_ID3V1) && id3::v1::parse(*this, wr))
373  {
374  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1! cur = " << wr.getCur() );
375  _file_tags.add(ID3TT_ID3V1);
376  }
377  wr.setCur(cur);
378  wr.setEnd(cur);
379  }
380  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1? cur = " << wr.getCur() );
381  if (_tags_to_parse.test(ID3TT_ID3V1) && id3::v1::parse(*this, wr))
382  {
383  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1! cur = " << wr.getCur() );
384  wr.setEnd(wr.getCur());
385  _file_tags.add(ID3TT_ID3V1);
386  }
387  cur = wr.getCur();
388  } while (cur != last);
389  _appended_bytes = end - cur;
390 
391  // Now get the mp3 header
392  mp3_core_size = (_file_size - _appended_bytes) - (_prepended_bytes + bytes_till_sync);
393  if (mp3_core_size >= 4)
394  { //it has at least the size for a mp3 header (a mp3 header is 4 bytes)
395  wr.setBeg(_prepended_bytes + bytes_till_sync);
396  wr.setCur(_prepended_bytes + bytes_till_sync);
397  wr.setEnd(_file_size - _appended_bytes);
398 
399  _mp3_info = new Mp3Info;
400  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): mp3header? cur = " << wr.getCur() );
401 
402  if (_mp3_info->Parse(wr, mp3_core_size))
403  {
404  ID3D_NOTICE( "ID3_TagImpl::ParseReader(): mp3header! cur = " << wr.getCur() );
405  }
406  else
407  {
408  delete _mp3_info;
409  _mp3_info = NULL;
410  }
411  }
412  }
413  else
414  this->SetPadding(false); //no need to pad an empty file
415 }
416 
The representative class of an ID3v2 field.
Definition: field.h:37
virtual const uchar * GetRawBinary() const =0
virtual size_t BinSize() const =0
The representative class of an id3v2 frame.
ID3_V2Spec GetSpec() const
Definition: header.h:62
size_t GetDataSize() const
Definition: header.h:71
virtual pos_type getCur()=0
Return the current position in the reader.
virtual bool atEnd()
Definition: reader.h:125
virtual pos_type getEnd()
Return the ending position in the reader.
Definition: reader.h:51
uint32 pos_type
Definition: reader.h:38
uint8 char_type
Definition: reader.h:37
virtual pos_type getBeg()
Return the beginning position in the reader.
Definition: reader.h:48
virtual int_type peekChar()=0
Return the next character to be read without advancing the internal position.
bool GetUnsync() const
Definition: header_tag.h:72
bool Parse(ID3_Reader &)
Definition: header_tag.cpp:101
void ParseExtended(ID3_Reader &)
Definition: header_tag.cpp:134
bool GetExtended() const
Definition: header_tag.h:79
bool SetSpec(ID3_V2Spec)
Definition: tag_impl.cpp:225
void ParseReader(ID3_Reader &reader)
Definition: tag_parse.cpp:219
bool SetExtended(bool)
Definition: tag_impl.cpp:244
bool SetUnsync(bool)
Definition: tag_impl.cpp:237
void ParseFile()
Definition: tag_parse.cpp:205
ID3_V2Spec GetSpec() const
Definition: tag_impl.cpp:232
bool AttachFrame(ID3_Frame *)
Definition: tag_impl.cpp:167
#define NULL
Definition: globals.h:743
@ ID3E_NoError
No error reported.
Definition: globals.h:365
@ ID3FN_DATA
Data field.
Definition: globals.h:203
@ ID3TT_LYRICS3
Represents a Lyrics3 tag.
Definition: globals.h:179
@ ID3TT_ID3V2
Represents an id3v2 tag.
Definition: globals.h:178
@ ID3TT_LYRICS3V2
Represents a Lyrics3 v2.00 tag.
Definition: globals.h:180
@ ID3TT_MUSICMATCH
Represents a MusicMatch tag.
Definition: globals.h:181
@ ID3TT_ID3V1
Represents an id3v1 or id3v1.1 tag.
Definition: globals.h:177
@ ID3FID_METACOMPRESSION
Compressed meta frame (id3v2.2.1)
Definition: globals.h:325
bool parse(ID3_TagImpl &, ID3_Reader &)
bool parse(ID3_TagImpl &tag, ID3_Reader &rdr)
Definition: tag_parse.cpp:137
bool parse(ID3_TagImpl &, ID3_Reader &)
bool parse(ID3_TagImpl &, ID3_Reader &)
bool parse(ID3_TagImpl &, ID3_Reader &)
Definition: tag_impl.h:42
ID3_Err ID3_C_EXPORT openReadableFile(String, fstream &)
Definition: utils.cpp:335