id3lib 3.8.3
|
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