src/tag_parse.cpp

Go to the documentation of this file.
00001 // $Id: tag_parse.cpp,v 1.47 2002/11/24 17:33:30 t1mpy Exp $
00002 
00003 // id3lib: a C++ library for creating and manipulating id3v1/v2 tags
00004 // Copyright 1999, 2000  Scott Thomas Haug
00005 // Copyright 2002 Thijmen Klok (thijmen@id3lib.org)
00006 
00007 // This library is free software; you can redistribute it and/or modify it
00008 // under the terms of the GNU Library General Public License as published by
00009 // the Free Software Foundation; either version 2 of the License, or (at your
00010 // option) any later version.
00011 //
00012 // This library is distributed in the hope that it will be useful, but WITHOUT
00013 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00014 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
00015 // License for more details.
00016 //
00017 // You should have received a copy of the GNU Library General Public License
00018 // along with this library; if not, write to the Free Software Foundation,
00019 // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
00020 
00021 // The id3lib authors encourage improvements and optimisations to be sent to
00022 // the id3lib coordinator.  Please see the README file for details on where to
00023 // send such submissions.  See the AUTHORS file for a list of people who have
00024 // contributed to id3lib.  See the ChangeLog file for a list of changes to
00025 // id3lib.  These files are distributed with id3lib at
00026 // http://download.sourceforge.net/id3lib/
00027 
00028 //#if defined HAVE_CONFIG_H
00029 //#include <config.h> // Must include before zlib.h to compile on WinCE
00030 //#endif
00031 
00032 //#include <zlib.h>
00033 //#include <string.h>
00034 //#include <memory.h>
00035 
00036 #include "tag_impl.h" //has <stdio.h> "tag.h" "header_tag.h" "frame.h" "field.h" "spec.h" "id3lib_strings.h" "utils.h"
00037 //#include "id3/io_decorators.h" //has "readers.h" "io_helpers.h" "utils.h"
00038 #include "io_strings.h"
00039 
00040 using namespace dami;
00041 
00042 namespace
00043 {
00044   bool parseFrames(ID3_TagImpl& tag, ID3_Reader& rdr)
00045   {
00046     ID3_Reader::pos_type beg = rdr.getCur();
00047     io::ExitTrigger et(rdr, beg);
00048     ID3_Reader::pos_type last_pos = beg;
00049     size_t totalSize = 0;
00050     size_t frameSize = 0;
00051     while (!rdr.atEnd() && rdr.peekChar() != '\0')
00052     {
00053       ID3D_NOTICE( "id3::v2::parseFrames(): rdr.getBeg() = " << rdr.getBeg() );
00054       ID3D_NOTICE( "id3::v2::parseFrames(): rdr.getCur() = " << rdr.getCur() );
00055       ID3D_NOTICE( "id3::v2::parseFrames(): rdr.getEnd() = " << rdr.getEnd() );
00056       last_pos = rdr.getCur();
00057       ID3_Frame* f = new ID3_Frame;
00058       f->SetSpec(tag.GetSpec());
00059       bool goodParse = f->Parse(rdr);
00060       frameSize = rdr.getCur() - last_pos;
00061       ID3D_NOTICE( "id3::v2::parseFrames(): frameSize = " << frameSize );
00062       totalSize += frameSize;
00063 
00064       if (frameSize == 0)
00065       {
00066         // There is a problem.
00067         // If the frame size is 0, then we can't progress.
00068         ID3D_WARNING( "id3::v2::parseFrames(): frame size is 0, can't " <<
00069                       "continue parsing frames");
00070         delete f;
00071         // Break for now.
00072         break;
00073       }
00074       else if (!goodParse)
00075       {
00076         // bad parse!  we can't attach this frame.
00077         ID3D_WARNING( "id3::v2::parseFrames(): bad parse, deleting frame");
00078         delete f;
00079       }
00080       else if (f->GetID() != ID3FID_METACOMPRESSION)
00081       {
00082         ID3D_NOTICE( "id3::v2::parseFrames(): attaching non-compressed " <<
00083                      "frame");
00084         // a good, uncompressed frame.  attach away!
00085         tag.AttachFrame(f);
00086       }
00087       else
00088       {
00089         ID3D_NOTICE( "id3::v2::parseFrames(): parsing ID3v2.2.1 " <<
00090                      "compressed frame");
00091         // hmm.  an ID3v2.2.1 compressed frame.  It contains 1 or more
00092         // compressed frames.  Uncompress and call parseFrames recursively.
00093         ID3_Field* fld = f->GetField(ID3FN_DATA);
00094         if (fld)
00095         {
00096           ID3_MemoryReader mr(fld->GetRawBinary(), fld->BinSize());
00097           ID3_Reader::char_type ch = mr.readChar();
00098           if (ch != 'z')
00099           {
00100             // unknown compression method
00101             ID3D_WARNING( "id3::v2::parseFrames(): unknown compression id " <<
00102                           " = '" << ch << "'" );
00103           }
00104           else
00105           {
00106             uint32 newSize = io::readBENumber(mr, sizeof(uint32));
00107             size_t oldSize = f->GetDataSize() - sizeof(uint32) - 1;
00108             io::CompressedReader cr(mr, newSize);
00109             parseFrames(tag, cr);
00110             if (!cr.atEnd())
00111             {
00112               // hmm.  it didn't parse the entire uncompressed data.  wonder
00113               // why.
00114               ID3D_WARNING( "id3::v2::parseFrames(): didn't parse entire " <<
00115                             "id3v2.2.1 compressed memory stream");
00116             }
00117           }
00118         }
00119         delete f;
00120       }
00121       et.setExitPos(rdr.getCur());
00122     }
00123     if (rdr.peekChar() == '\0')
00124     {
00125       ID3D_NOTICE( "id3::v2::parseFrames: done parsing, padding at postion " <<
00126                    rdr.getCur() );
00127     }
00128     else
00129     {
00130       ID3D_NOTICE( "id3::v2::parseFrames: done parsing, [cur, end] = [" <<
00131                    rdr.getCur() << ", " << rdr.getEnd() << "]" );
00132     }
00133     return true;
00134   }
00135 };
00136 
00137 bool id3::v2::parse(ID3_TagImpl& tag, ID3_Reader& reader)
00138 {
00139   ID3_Reader::pos_type beg = reader.getCur();
00140   io::ExitTrigger et(reader);
00141 
00142   ID3_TagHeader hdr;
00143 
00144   io::WindowedReader wr(reader, ID3_TagHeader::SIZE);
00145 
00146   if (!hdr.Parse(wr) || wr.getCur() == beg)
00147   {
00148     ID3D_NOTICE( "id3::v2::parse(): parsing header failes" );
00149     return false;
00150   }
00151   if (hdr.GetExtended())
00152   {
00153     hdr.ParseExtended(reader);
00154   }
00155   tag.SetSpec(hdr.GetSpec());
00156 
00157   size_t dataSize = hdr.GetDataSize();
00158   ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): dataSize = " << dataSize);
00159 
00160   wr.setWindow(wr.getCur(), dataSize);
00161   et.setExitPos(wr.getEnd());
00162 
00163   ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): data window beg = " << wr.getBeg() );
00164   ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): data window cur = " << wr.getCur() );
00165   ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): data window end = " << wr.getEnd() );
00166   tag.SetExtended(hdr.GetExtended());
00167   if (!hdr.GetUnsync())
00168   {
00169     tag.SetUnsync(false);
00170     parseFrames(tag, wr);
00171   }
00172   else
00173   {
00174     // The buffer has been unsynced.  It will have to be resynced to be
00175     // readable.  This has to be done a character at a time.
00176     //
00177     // The original reader may be reading in characters from a file.  Doing
00178     // this a character at a time is quite slow.  To improve performance, read
00179     // in the entire buffer into a string, then create an UnsyncedReader from
00180     // the string.
00181     //
00182     // It might be better to implement a BufferedReader so that the details
00183     // of this can be abstracted away behind a class
00184     tag.SetUnsync(true);
00185     BString raw = io::readAllBinary(wr);
00186     io::BStringReader bsr(raw);
00187     io::UnsyncedReader ur(bsr);
00188     ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): unsync beg = " << ur.getBeg() );
00189     ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): unsync cur = " << ur.getCur() );
00190     ID3D_NOTICE( "ID3_TagImpl::Parse(ID3_Reader&): unsync end = " << ur.getEnd() );
00191 
00192     // Now read the UnsyncedReader into another string, and parse the frames
00193     // from the string.  This is done so that 1. the unsynced reader is
00194     // unsynced exactly once, removing the possibility of multiple unsyncings
00195     // of the same string, and 2) so that calls to readChars aren't done a
00196     // character at a time for every call
00197     BString synced = io::readAllBinary(ur);
00198     io::BStringReader sr(synced);
00199     parseFrames(tag, sr);
00200   }
00201 
00202   return true;
00203 }
00204 
00205 void ID3_TagImpl::ParseFile()
00206 {
00207   ifstream file;
00208   if (ID3E_NoError != openReadableFile(this->GetFileName(), file))
00209   {
00210     // log this...
00211     return;
00212   }
00213   ID3_IFStreamReader ifsr(file);
00214   ParseReader(ifsr);
00215   file.close();
00216 }
00217 
00218 //used for streaming media
00219 void ID3_TagImpl::ParseReader(ID3_Reader &reader)
00220 {
00221   size_t mp3_core_size;
00222   size_t bytes_till_sync;
00223 
00224   io::WindowedReader wr(reader);
00225   wr.setBeg(wr.getCur());
00226 
00227   _file_tags.clear();
00228   _file_size = reader.getEnd();
00229 
00230   ID3_Reader::pos_type beg  = wr.getBeg();
00231   ID3_Reader::pos_type cur  = wr.getCur();
00232   ID3_Reader::pos_type end  = wr.getEnd();
00233 
00234   ID3_Reader::pos_type last = cur;
00235 
00236   if (_tags_to_parse.test(ID3TT_ID3V2))
00237   {
00238     do
00239     {
00240       last = cur;
00241       // Parse tags at the beginning of the file first...
00242       if (id3::v2::parse(*this, wr))
00243       {
00244         _file_tags.add(ID3TT_ID3V2);
00245       }
00246       cur  = wr.getCur();
00247       wr.setBeg(cur);
00248     } while (!wr.atEnd() && cur > last);
00249   }
00250   // add silly padding outside the tag to _prepended_bytes
00251   if (!wr.atEnd() && wr.peekChar() == '\0')
00252   {
00253     ID3D_NOTICE( "ID3_TagImpl::ParseReader(): found padding outside tag" );
00254     do
00255     {
00256       last = cur;
00257       cur = wr.getCur() + 1;
00258       wr.setBeg(cur);
00259       wr.setCur(cur);
00260     } while (!wr.atEnd() &&  cur > last && wr.peekChar() == '\0');
00261   }
00262   if (!wr.atEnd() && _file_size - (cur - beg) > 4 && wr.peekChar() == 255)
00263   { //unfortunatly, this is necessary for finding an invalid padding
00264     wr.setCur(cur + 1); //cur is known by peekChar
00265     if (wr.readChar() == '\0' && wr.readChar() == '\0' && wr.peekChar() == '\0')
00266     { //three empty bytes found, enough for me, this is stupid padding
00267       cur += 3; //those are now allready read in (excluding the peekChar, since it will be added by do{})
00268       do
00269       {
00270         last = cur;
00271         cur = wr.getCur() + 1;
00272         wr.setBeg(cur);
00273         wr.setCur(cur);
00274       } while (!wr.atEnd() &&  cur > last && wr.peekChar() == '\0');
00275     }
00276     else
00277       wr.setCur(cur);
00278   }
00279   _prepended_bytes = cur - beg;
00280   // go looking for the first sync byte to add to bytes_till_sync
00281   // by not adding it to _prepended_bytes, we preserve this 'unknown' data
00282   // The routine's only effect is helping the lib to find things as bitrate etc.
00283   beg  = wr.getBeg();
00284   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
00285   {
00286     ID3D_NOTICE( "ID3_TagImpl::ParseReader(): Didn't find mp3 sync byte" );
00287     if ((_file_size - (cur - beg)) >= 4)
00288     { //there is room to search for some kind of ID
00289       unsigned char buf[5];
00290       wr.readChars(buf, 4);
00291       buf[4] = '\0';
00292       // check for RIFF (an encapsulating format) ID
00293       if (strncmp((char*)buf, "RIFF", 4) == 0 || strncmp((char*)buf, "RIFX", 4) == 0)
00294       {
00295         // next 4 bytes are RIFF size, skip them
00296         cur = wr.getCur() + 4;
00297         wr.setCur(cur);
00298         // loop until first possible sync byte
00299         if (!wr.atEnd() && wr.peekChar() != 0xFF)
00300         {
00301           do
00302           {
00303             last = cur;
00304             cur = wr.getCur() + 1;
00305             wr.setCur(cur);
00306           } while (!wr.atEnd() &&  cur > last && wr.peekChar() != 0xFF);
00307         }
00308       }
00309       else if (strncmp((char*)buf, "fLaC", 4) == 0)
00310       { //a FLAC file, no need looking for a sync byte
00311         beg = cur;
00312       }
00313       else
00314       { //since we set the cursor 4 bytes ahead for looking for RIFF, RIFX or fLaC, better set it back
00315         // but peekChar allready checked the first one, so we add one
00316         cur = cur + 1;
00317         wr.setCur(cur);
00318         //go looking for a sync byte
00319         if (!wr.atEnd() && wr.peekChar() != 0xFF) //no sync byte, we have an unknown byte
00320         {
00321           do
00322           {
00323             last = cur;
00324             cur = wr.getCur() + 1;
00325             wr.setCur(cur);
00326           } while (!wr.atEnd() &&  cur > last && wr.peekChar() != 0xFF);
00327         }
00328       }
00329     } //if ((_file_size - (cur - beg)) >= 4)
00330     else
00331     { //remaining size is smaller than 4 bytes, can't be useful, but leave it for now
00332       beg = cur;
00333       //file.close();
00334       //return;
00335     }
00336   }
00337   bytes_till_sync = cur - beg;
00338 
00339   cur = wr.setCur(end);
00340   if (_file_size > _prepended_bytes)
00341   {
00342     do
00343     {
00344       last = cur;
00345       ID3D_NOTICE( "ID3_TagImpl::ParseReader(): beg = " << wr.getBeg() );
00346       ID3D_NOTICE( "ID3_TagImpl::ParseReader(): cur = " << wr.getCur() );
00347       ID3D_NOTICE( "ID3_TagImpl::ParseReader(): end = " << wr.getEnd() );
00348       // ...then the tags at the end
00349       ID3D_NOTICE( "ID3_TagImpl::ParseReader(): musicmatch? cur = " << wr.getCur() );
00350       if (_tags_to_parse.test(ID3TT_MUSICMATCH) && mm::parse(*this, wr))
00351       {
00352         ID3D_NOTICE( "ID3_TagImpl::ParseReader(): musicmatch! cur = " << wr.getCur() );
00353         _file_tags.add(ID3TT_MUSICMATCH);
00354         wr.setEnd(wr.getCur());
00355       }
00356       ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v1? cur = " << wr.getCur() );
00357       if (_tags_to_parse.test(ID3TT_LYRICS3) && lyr3::v1::parse(*this, wr))
00358       {
00359         ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v1! cur = " << wr.getCur() );
00360         _file_tags.add(ID3TT_LYRICS3);
00361         wr.setEnd(wr.getCur());
00362       }
00363       ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v2? cur = " << wr.getCur() );
00364       if (_tags_to_parse.test(ID3TT_LYRICS3V2) && lyr3::v2::parse(*this, wr))
00365       {
00366         ID3D_NOTICE( "ID3_TagImpl::ParseReader(): lyr3v2! cur = " << wr.getCur() );
00367         _file_tags.add(ID3TT_LYRICS3V2);
00368         cur = wr.getCur();
00369         wr.setCur(wr.getEnd());//set to end to seek id3v1 tag
00370         //check for id3v1 tag and set End accordingly
00371         ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1? cur = " << wr.getCur() );
00372         if (_tags_to_parse.test(ID3TT_ID3V1) && id3::v1::parse(*this, wr))
00373         {
00374           ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1! cur = " << wr.getCur() );
00375           _file_tags.add(ID3TT_ID3V1);
00376         }
00377         wr.setCur(cur);
00378         wr.setEnd(cur);
00379       }
00380       ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1? cur = " << wr.getCur() );
00381       if (_tags_to_parse.test(ID3TT_ID3V1) && id3::v1::parse(*this, wr))
00382       {
00383         ID3D_NOTICE( "ID3_TagImpl::ParseReader(): id3v1! cur = " << wr.getCur() );
00384         wr.setEnd(wr.getCur());
00385         _file_tags.add(ID3TT_ID3V1);
00386       }
00387       cur = wr.getCur();
00388     } while (cur != last);
00389     _appended_bytes = end - cur;
00390 
00391     // Now get the mp3 header
00392     mp3_core_size = (_file_size - _appended_bytes) - (_prepended_bytes + bytes_till_sync);
00393     if (mp3_core_size >= 4)
00394     { //it has at least the size for a mp3 header (a mp3 header is 4 bytes)
00395       wr.setBeg(_prepended_bytes + bytes_till_sync);
00396       wr.setCur(_prepended_bytes + bytes_till_sync);
00397       wr.setEnd(_file_size - _appended_bytes);
00398 
00399       _mp3_info = new Mp3Info;
00400       ID3D_NOTICE( "ID3_TagImpl::ParseReader(): mp3header? cur = " << wr.getCur() );
00401 
00402       if (_mp3_info->Parse(wr, mp3_core_size))
00403       {
00404         ID3D_NOTICE( "ID3_TagImpl::ParseReader(): mp3header! cur = " << wr.getCur() );
00405       }
00406       else
00407       {
00408         delete _mp3_info;
00409         _mp3_info = NULL;
00410       }
00411     }
00412   }
00413   else
00414     this->SetPadding(false); //no need to pad an empty file
00415 }
00416 

Generated on Mon Aug 20 17:48:46 2007 for id3lib by  doxygen 1.5.2