00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027 #if defined HAVE_CONFIG_H
00028 #include <config.h>
00029 #endif
00030
00031
00032
00033 #include <stdlib.h>
00034 #include <ctype.h>
00035 #include <memory.h>
00036 #include "tag_impl.h"
00037 #include "utils.h"
00038 #include "helpers.h"
00039 #include "io_decorators.h"
00040 #include "io_helpers.h"
00041 #include "io_strings.h"
00042
00043 using namespace dami;
00044
00045 namespace
00046 {
00047 uint32 readIntegerString(ID3_Reader& reader, size_t numBytes)
00048 {
00049 uint32 val = 0;
00050 for (size_t i = 0; i < numBytes && isdigit(reader.peekChar()); ++i)
00051 {
00052 val = (val * 10) + (reader.readChar() - '0');
00053 }
00054 ID3D_NOTICE( "readIntegerString: val = " << val );
00055 return val;
00056 }
00057
00058 uint32 readIntegerString(ID3_Reader& reader)
00059 {
00060 return readIntegerString(reader, reader.remainingBytes());
00061 }
00062
00063 bool isTimeStamp(ID3_Reader& reader)
00064 {
00065 ID3_Reader::pos_type cur = reader.getCur();
00066 if (reader.getEnd() < cur + 7)
00067 {
00068 return false;
00069 }
00070 bool its = ('[' == reader.readChar() &&
00071 isdigit(reader.readChar()) && isdigit(reader.readChar()) &&
00072 ':' == reader.readChar() &&
00073 isdigit(reader.readChar()) && isdigit(reader.readChar()) &&
00074 ']' == reader.readChar());
00075 reader.setCur(cur);
00076 if (its)
00077 {
00078 ID3D_NOTICE( "isTimeStamp(): found timestamp, cur = " <<
00079 reader.getCur() );
00080 }
00081 return its;
00082 }
00083
00084 uint32 readTimeStamp(ID3_Reader& reader)
00085 {
00086 reader.skipChars(1);
00087 size_t sec = readIntegerString(reader, 2) * 60;
00088 reader.skipChars(1);
00089 sec += readIntegerString(reader, 2);
00090 reader.skipChars(1);
00091 ID3D_NOTICE( "readTimeStamp(): timestamp = " << sec );
00092 return sec * 1000;
00093 }
00094
00095 bool findText(ID3_Reader& reader, String text)
00096 {
00097 if (text.empty())
00098 {
00099 return true;
00100 }
00101
00102 size_t index = 0;
00103 while (!reader.atEnd())
00104 {
00105 ID3_Reader::char_type ch = reader.readChar();
00106 if (ch == text[index])
00107 {
00108 index++;
00109 }
00110 else if (ch == text[0])
00111 {
00112 index = 1;
00113 }
00114 else
00115 {
00116 index = 0;
00117 }
00118 if (index == text.size())
00119 {
00120 reader.setCur(reader.getCur() - index);
00121 ID3D_NOTICE( "findText: found \"" << text << "\" at " <<
00122 reader.getCur() );
00123 break;
00124 }
00125 }
00126 return !reader.atEnd();
00127 };
00128
00129 void lyrics3ToSylt(ID3_Reader& reader, ID3_Writer& writer)
00130 {
00131 while (!reader.atEnd())
00132 {
00133 bool lf = false;
00134 size_t ms = 0;
00135 size_t count = 0;
00136 while (isTimeStamp(reader))
00137 {
00138
00139 if (count++ > 0)
00140 {
00141 readTimeStamp(reader);
00142 }
00143 else
00144 {
00145 ms = readTimeStamp(reader);
00146 }
00147 }
00148 while (!reader.atEnd() && !isTimeStamp(reader))
00149 {
00150 ID3_Reader::char_type ch = reader.readChar();
00151 if (0x0A == ch && (reader.atEnd() || isTimeStamp(reader)))
00152 {
00153 lf = true;
00154 break;
00155 }
00156 else
00157 {
00158 writer.writeChar(ch);
00159 }
00160 }
00161
00162
00163 writer.writeChar('\0');
00164
00165
00166 ID3D_NOTICE( "lyrics3toSylt: ms = " << ms );
00167
00168 io::writeBENumber(writer, ms, sizeof(uint32));
00169 if (lf)
00170 {
00171 ID3D_NOTICE( "lyrics3toSylt: adding lf" );
00172
00173
00174 writer.writeChar(0x0A);
00175 }
00176 }
00177 }
00178 };
00179
00180 bool lyr3::v1::parse(ID3_TagImpl& tag, ID3_Reader& reader)
00181 {
00182 io::ExitTrigger et(reader);
00183 ID3_Reader::pos_type end = reader.getCur();
00184 if (end < reader.getBeg() + 9 + 128)
00185 {
00186 ID3D_NOTICE( "id3::v1::parse: bailing, not enough bytes to parse, pos = " << end );
00187 return false;
00188 }
00189 reader.setCur(end - (9 + 128));
00190
00191 {
00192 if (io::readText(reader, 9) != "LYRICSEND" ||
00193 io::readText(reader, 3) != "TAG")
00194 {
00195 return false;
00196 }
00197 }
00198
00199
00200 if (end < reader.getBeg() + 11 + 9 + 128)
00201 {
00202
00203 ID3D_WARNING( "id3::v1::parse: not enough data to parse lyrics3" );
00204 return false;
00205 }
00206
00207
00208 size_t window = end - reader.getBeg();
00209 size_t lyrDataSize = min<size_t>(window, 11 + 5100 + 9 + 128);
00210 reader.setCur(end - lyrDataSize);
00211 io::WindowedReader wr(reader, lyrDataSize - (9 + 128));
00212
00213 if (!findText(wr, "LYRICSBEGIN"))
00214 {
00215 ID3D_WARNING( "id3::v1::parse: couldn't find LYRICSBEGIN, bailing" );
00216 return false;
00217 }
00218
00219 et.setExitPos(wr.getCur());
00220 wr.skipChars(11);
00221 wr.setBeg(wr.getCur());
00222
00223 io::LineFeedReader lfr(wr);
00224 String lyrics = io::readText(lfr, wr.remainingBytes());
00225 id3::v2::setLyrics(tag, lyrics, "Converted from Lyrics3 v1.00", "XXX");
00226
00227 return true;
00228 }
00229
00230
00231 bool lyr3::v2::parse(ID3_TagImpl& tag, ID3_Reader& reader)
00232 {
00233 io::ExitTrigger et(reader);
00234 ID3_Reader::pos_type end = reader.getCur();
00235 if (end < reader.getBeg() + 6 + 9 + 128)
00236 {
00237 ID3D_NOTICE( "lyr3::v2::parse: bailing, not enough bytes to parse, pos = " << reader.getCur() );
00238 return false;
00239 }
00240
00241 reader.setCur(end - (6 + 9 + 128));
00242 uint32 lyrSize = 0;
00243
00244 ID3_Reader::pos_type beg = reader.getCur();
00245 lyrSize = readIntegerString(reader, 6);
00246 if (reader.getCur() < beg + 6)
00247 {
00248 ID3D_NOTICE( "lyr3::v2::parse: couldn't find numeric string, lyrSize = " <<
00249 lyrSize );
00250 return false;
00251 }
00252
00253 if (io::readText(reader, 9) != "LYRICS200" ||
00254 io::readText(reader, 3) != "TAG")
00255 {
00256 return false;
00257 }
00258
00259 if (end < reader.getBeg() + lyrSize + 6 + 9 + 128)
00260 {
00261 ID3D_WARNING( "lyr3::v2::parse: not enough data to parse tag, lyrSize = " << lyrSize );
00262 return false;
00263 }
00264 reader.setCur(end - (lyrSize + 6 + 9 + 128));
00265
00266 io::WindowedReader wr(reader);
00267 wr.setWindow(wr.getCur(), lyrSize);
00268
00269 beg = wr.getCur();
00270
00271 if (io::readText(wr, 11) != "LYRICSBEGIN")
00272 {
00273
00274 ID3D_WARNING( "lyr3::v2::parse: couldn't find LYRICSBEGIN, bailing" );
00275 return false;
00276 }
00277
00278 bool has_time_stamps = false;
00279
00280 ID3_Frame* lyr_frame = NULL;
00281
00282 while (!wr.atEnd())
00283 {
00284 uint32 fldSize;
00285
00286 String fldName = io::readText(wr, 3);
00287 ID3D_NOTICE( "lyr3::v2::parse: fldName = " << fldName );
00288 fldSize = readIntegerString(wr, 5);
00289 ID3D_NOTICE( "lyr3::v2::parse: fldSize = " << fldSize );
00290
00291 String fldData;
00292
00293 io::WindowedReader wr2(wr, fldSize);
00294 io::LineFeedReader lfr(wr2);
00295
00296 fldData = io::readText(lfr, fldSize);
00297 ID3D_NOTICE( "lyr3::v2::parse: fldData = \"" << fldData << "\"" );
00298
00299
00300 if (fldName == "IND")
00301 {
00302 has_time_stamps = (fldData.size() > 1 && fldData[1] == '1');
00303 }
00304
00305
00306 else if (fldName == "ETT")
00307 {
00308
00309 id3::v2::setTitle(tag, fldData);
00310 }
00311
00312
00313 else if (fldName == "EAR")
00314 {
00315
00316 id3::v2::setArtist(tag, fldData);
00317 }
00318
00319
00320 else if (fldName == "EAL")
00321 {
00322
00323 id3::v2::setAlbum(tag, fldData);
00324 }
00325
00326
00327 else if (fldName == "AUT")
00328 {
00329
00330 id3::v2::setLyricist(tag, fldData);
00331 }
00332
00333
00334 else if (fldName == "INF")
00335 {
00336
00337 id3::v2::setComment(tag, fldData, "Lyrics3 v2.00 INF", "XXX");
00338 }
00339
00340
00341 else if (fldName == "LYR")
00342 {
00343
00344 String desc = "Converted from Lyrics3 v2.00";
00345
00346 if (!has_time_stamps)
00347 {
00348 lyr_frame = id3::v2::setLyrics(tag, fldData, desc, "XXX");
00349 }
00350 else
00351 {
00352
00353 io::StringReader sr(fldData);
00354 ID3D_NOTICE( "lyr3::v2::parse: determining synced lyrics" );
00355 BString sylt;
00356 io::BStringWriter sw(sylt);
00357 lyrics3ToSylt(sr, sw);
00358
00359 lyr_frame = id3::v2::setSyncLyrics(tag, sylt, ID3TSF_MS, desc,
00360 "XXX", ID3CT_LYRICS);
00361 ID3D_NOTICE( "lyr3::v2::parse: determined synced lyrics" );
00362 }
00363 }
00364 else if (fldName == "IMG")
00365 {
00366
00367 ID3D_WARNING( "lyr3::v2::parse: IMG field unsupported" );
00368 }
00369 else
00370 {
00371 ID3D_WARNING( "lyr3::v2::parse: undefined field id: " <<
00372 fldName );
00373 }
00374 }
00375
00376 et.setExitPos(beg);
00377 return true;
00378 }