"Fossies" - the Fresh Open Source Software Archive

Member "MP3Diags-unstable-1.5.01/src/Id3V2Stream.cpp" (9 Jul 2017, 52259 Bytes) of package /linux/privat/MP3Diags-unstable-1.5.01.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Id3V2Stream.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.3.04_vs_1.5.01.

    1 /***************************************************************************
    2  *   MP3 Diags - diagnosis, repairs and tag editing for MP3 files          *
    3  *                                                                         *
    4  *   Copyright (C) 2009 by Marian Ciobanu                                  *
    5  *   ciobi@inbox.com                                                       *
    6  *                                                                         *
    7  *   This program is free software; you can redistribute it and/or modify  *
    8  *   it under the terms of the GNU General Public License version 2 as     *
    9  *   published by the Free Software Foundation.                            *
   10  *                                                                         *
   11  *   This program is distributed in the hope that it will be useful,       *
   12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
   13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
   14  *   GNU General Public License for more details.                          *
   15  *                                                                         *
   16  *   You should have received a copy of the GNU General Public License     *
   17  *   along with this program; if not, write to the                         *
   18  *   Free Software Foundation, Inc.,                                       *
   19  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
   20  ***************************************************************************/
   21 
   22 
   23 #include  <sstream>
   24 #include  "fstream_unicode.h"
   25 
   26 #include  "Id3V2Stream.h"
   27 
   28 #include  "MpegStream.h"
   29 #include  "Helpers.h"
   30 #include  "CommonData.h"
   31 #include  "Widgets.h"  // for GlobalTranslHlp
   32 
   33 
   34 using namespace std;
   35 using namespace pearl;
   36 
   37 
   38 
   39 
   40 
   41 
   42 
   43 
   44 
   45 /*
   46      ID3V2.3
   47 
   48      +-----------------------------+
   49      |      Header (10 bytes)      |
   50      +-----------------------------+
   51      |       Extended Header       |
   52      | (variable length, OPTIONAL) |
   53      +-----------------------------+
   54      |   Frames (variable length)  |
   55      +-----------------------------+
   56      |           Padding           |
   57      | (variable length, OPTIONAL) |
   58      +-----------------------------+
   59      | Footer (10 bytes, OPTIONAL) |
   60      +-----------------------------+
   61 
   62 http://osdir.com/ml/multimedia.id3v2/2007-08/msg00008.html :
   63 
   64 For v2.3 tags, you would read the first ten bytes (tag header) and then, if there is an extended header, read 4 bytes to get its size and then skip that many bytes. (The padding that is given in the extended header comes AFTER the frames.)
   65 
   66 For v2.4 tags, you would read the first ten bytes (tag header) and then, if there is an extended header, read 4 bytes to get its size and then skip (size - 4) bytes ahead. (I believe in v2.4 tags, the size given in  the extended header includes the 4 bytes you would have already read.)  And don't forget that in v2.4 tags, the extended header size is stored as a syncsafe integer.
   67 
   68 
   69 
   70 
   71 */
   72 
   73 // the total size, including the 10-byte header
   74 int getId3V2Size(char* pId3Header)
   75 {
   76     unsigned char* p (reinterpret_cast<unsigned char*>(pId3Header));
   77     return
   78         (p[6] << 21) +
   79         (p[7] << 14) +
   80         (p[8] << 7) +
   81         (p[9] << 0) +
   82         10;
   83 }
   84 
   85 
   86 
   87 
   88 
   89 
   90 // reads nCount bytes into pDest;
   91 // if bHasUnsynch is true, it actually reads more bytes, applying the unsynchronization algorithm, so pDest gets nCount bytes;
   92 // returns the number of bytes it could read;
   93 // posNext is the position where the next block begins (might be EOF); nothing starting at that position should be read; needed to take care of an ID3V2 tag whose last frame ends with 0xff and has no padding;
   94 // asserts that posNext is not before the current position in the stream
   95 streamsize readID3V2(bool bHasUnsynch, istream& in, char* pDest, streamsize nCount, streampos posNext, int& nBytesSkipped)
   96 {
   97     nBytesSkipped = 0;
   98     if (0 == nCount) { return 0; }
   99     streampos posCrt (in.tellg());
  100     STRM_ASSERT (posNext >= posCrt); // not always right
  101     if (nCount > posNext - posCrt)
  102     {
  103         nCount = posNext - posCrt;
  104     }
  105 
  106     if (!bHasUnsynch)
  107     {
  108         return read(in, pDest, nCount);
  109     }
  110 
  111     const int BFR_SIZE (256);
  112     char bfr [BFR_SIZE];
  113     char cPrev (0);
  114     char* q (pDest);
  115     for (;;)
  116     {
  117         streamsize nTarget (min(streamsize(BFR_SIZE), streamsize(posNext - posCrt)));
  118         streamsize nRead (read(in, bfr, nTarget));
  119         if (0 == nRead)
  120         { // doesn't matter what was read before; EOF reached
  121             goto e1;
  122         }
  123         char* p (bfr);
  124         for (;;)
  125         {
  126             if (posCrt >= posNext) { goto e1; }
  127             if (p >= bfr + nRead) { break; }
  128             if (0 == *p && char(0xff) == cPrev)
  129             {
  130                 ++p;
  131                 posCrt += 1;
  132                 ++nBytesSkipped;
  133                 cPrev = 0;
  134                 if (posCrt >= posNext) { goto e1; }
  135                 if (p >= bfr + nRead) { break; }
  136             }
  137 
  138             if (q >= pDest + nCount) { goto e1; }
  139 
  140             cPrev = *q++ = *p++;
  141             posCrt += 1;
  142         }
  143     }
  144 
  145 e1:
  146     in.clear();
  147     in.seekg(posCrt);
  148     return streamsize(q - pDest);
  149 }
  150 
  151 
  152 
  153 //============================================================================================================
  154 //============================================================================================================
  155 //============================================================================================================
  156 
  157 
  158 Id3V2Frame::Id3V2Frame(streampos pos, bool bHasUnsynch, StringWrp* pFileName) :
  159         m_nMemDataSize(-1),
  160         m_nDiskDataSize(-1),
  161         m_pos(pos),
  162         m_pFileName(pFileName),
  163         m_bHasUnsynch(bHasUnsynch),
  164         m_bHasLatin1NonAscii(false),
  165         m_eApicStatus(NO_APIC),
  166         m_nPictureType(-1),
  167         m_nImgOffset(-1),
  168         m_nImgSize(-1),
  169         m_eCompr(ImageInfo::INVALID),
  170         m_nWidth(-1),
  171         m_nHeight(-1)
  172 {
  173 }
  174 
  175 /*virtual*/ Id3V2Frame::~Id3V2Frame()
  176 {
  177     //qDebug("Id3V2Frame::~Id3V2Frame(%p)", this);
  178 }
  179 
  180 static bool isReadable(char c)
  181 {
  182     return c > 32 && c < 127;
  183 }
  184 
  185 
  186 // normally returns m_szName, but if it has invalid characters (<=32 or >=127), returns a hex representation
  187 string Id3V2Frame::getReadableName() const
  188 {
  189     if (isReadable(m_szName[0]) && isReadable(m_szName[1]) && isReadable(m_szName[2]) && isReadable(m_szName[3]))
  190     {
  191         return m_szName;
  192     }
  193     char a [32];
  194     sprintf(a, "0x%02x 0x%02x 0x%02x 0x%02x",
  195         (unsigned)(unsigned char)m_szName[0],
  196         (unsigned)(unsigned char)m_szName[1],
  197         (unsigned)(unsigned char)m_szName[2],
  198         (unsigned)(unsigned char)m_szName[3]);
  199     return a;
  200 }
  201 
  202 
  203 static const char* findAfter(const char* p, unsigned char cEnc, const char* pLast)
  204 {
  205 //inspect(p, pLast - p);
  206     if (0 == p) { return 0; }
  207     switch (cEnc)
  208     {
  209     case 0:
  210     case 3:
  211         {
  212             while (p < pLast - 1 && 0 != *p) { ++p; }
  213             if (p + 1 < pLast) { return p + 1; }
  214             return 0;
  215         }
  216 
  217     case 1:
  218     case 2:
  219         {
  220             while (p < pLast - 2 && (0 != *p || 0 != *(p + 1)))
  221             {
  222                 p += 2;
  223                 //inspect(p, pLast - p);
  224             }
  225             if (p + 2 < pLast) { return p + 2; }
  226             return 0;
  227         }
  228 
  229     default:
  230         CB_ASSERT(false);
  231     }
  232 }
  233 
  234 
  235 
  236 void Id3V2Frame::print(ostream& out, bool bFullInfo) const
  237 {
  238     out << m_szName;
  239     if ('T' == m_szName[0])
  240     {
  241         string s (getUtf8String());
  242         if (!bFullInfo && cSize(s) > 100)
  243         {
  244             s.erase(90);
  245             s += " ...";
  246         }
  247 
  248         out << "=\"" << s << "\""; //ttt2 probably specific to particular versions of Linux and GCC
  249 //cout << " value=\"" << getUtf8String() << "\""; //ttt2 probably specific to particular versions of Linux and GCC
  250         //out << " value=\"" << "RRRRRRRR" << "\"";
  251     }
  252     else if (bFullInfo && string("USLT") == m_szName)
  253     {
  254         Id3V2FrameDataLoader wrp (*this);
  255         const char* pData (wrp.getData());
  256         //const char* q (pData + 1);
  257         out << ": ";
  258         int nBeg (1);
  259 
  260         { // skip description
  261             if (0 == pData[0] || 3 == pData[0])
  262             {
  263                 for (; nBeg < m_nMemDataSize && 0 != pData[nBeg]; ++nBeg) {}
  264                 ++nBeg;
  265             }
  266             else
  267             {
  268                 for (; nBeg < m_nMemDataSize - 1 && (0 != pData[nBeg] || 0 != pData[nBeg + 1]); ++nBeg) {}
  269                 nBeg += 2;
  270             }
  271         }
  272 
  273         QString qs;
  274         switch (pData[0])
  275         {
  276         case 0: // Latin-1
  277             qs = QString::fromLatin1(pData + nBeg, m_nMemDataSize - nBeg);
  278             break;
  279 
  280         case 1:
  281             try
  282             {
  283                 qs = QString::fromUtf8(utf8FromBomUtf16(pData + nBeg, m_nMemDataSize - nBeg).c_str());
  284             }
  285             catch (const NotId3V2Frame&)
  286             {
  287                 qs = tr("<< error decoding string >>");
  288             }
  289             break;
  290 
  291         case 2:
  292             qs = tr("<< unsupported encoding >>");
  293             break;
  294 
  295         case 3:
  296             qs = QString::fromUtf8(pData + nBeg, m_nMemDataSize - nBeg); // ttt3 not quite OK for ID3V2.3.0, but it's probably better this way
  297             break;
  298         }
  299 
  300         //qs.replace('\n', " / "); qs.replace('\r', "");
  301         qs = "\n" + qs + "\n";
  302 
  303         out << qs.toUtf8().constData();
  304     }
  305     else
  306     {
  307         out << " " << convStr(tr("size=")) << m_nMemDataSize;
  308     }
  309 
  310 
  311     if (bFullInfo && string("GEOB") == m_szName)
  312     { // !!! "size" is already written
  313         //ttt2  perhaps try and guess the data type
  314         Id3V2FrameDataLoader wrp (*this);
  315         const char* pData (wrp.getData());
  316         unsigned char cEnc (*pData);
  317         const char* pMime (0);
  318         const char* pFile (0);
  319         const char* pDescr (0);
  320         const char* pBinData (0);
  321 
  322         QString qstrMime, qstrFile, qstrDescr;
  323 
  324         if (cEnc > 3)
  325         {
  326             out << " " << convStr(tr("invalid text encoding"));
  327         }
  328         else if (2 == cEnc)
  329         {
  330             out << " " << convStr(tr("unsupported text encoding"));
  331         }
  332         else
  333         {
  334             const char* pLast (pData + m_nMemDataSize); // actually first after last
  335 
  336             pMime = pData + 1;
  337 
  338             pFile = findAfter(pMime, 0, pLast); // !!! mime is always UTF-8
  339             pDescr = findAfter(pFile, cEnc, pLast);
  340             pBinData = findAfter(pDescr, cEnc, pLast);
  341 
  342             if (0 != pBinData)
  343             {
  344                 qstrMime = QString::fromLatin1(pMime);
  345                 switch (cEnc)
  346                 {
  347                 case 0: // Latin-1
  348                     qstrFile = QString::fromLatin1(pFile);
  349                     qstrDescr = QString::fromLatin1(pDescr);
  350                     break;
  351 
  352                 case 1:
  353                     try
  354                     {
  355                         qstrFile = QString::fromUtf8(utf8FromBomUtf16(pFile, pDescr - pFile).c_str());
  356                     }
  357                     catch (const NotId3V2Frame&)
  358                     { // invalid encoding
  359                         qstrFile = tr("<encoding error>");
  360                     }
  361 
  362                     try
  363                     {
  364                         qstrDescr = QString::fromUtf8(utf8FromBomUtf16(pDescr, pBinData - pDescr).c_str());
  365                     }
  366                     catch (const NotId3V2Frame&)
  367                     { // invalid encoding
  368                         qstrDescr = tr("<encoding error>");
  369                     }
  370                     break;
  371 
  372                 /*case 2:
  373                     qs = tr("<< unsupported encoding >>");
  374                     break;*/
  375 
  376                 case 3:  // ttt3 not quite OK for ID3V2.3.0, but it's probably better this way
  377                     qstrFile = QString::fromUtf8(pFile);
  378                     qstrDescr = QString::fromUtf8(pDescr);
  379                     break;
  380 
  381                 default: CB_ASSERT1 (false, m_pFileName->s);
  382                 }
  383             }
  384 
  385             if (0 == pBinData)
  386             {
  387                 out << " " << convStr(tr("invalid data"));
  388             }
  389             else
  390             {
  391                 int nBinSize (pLast - pBinData);
  392                 int nPrintedBinSize (min(1024, nBinSize));
  393                 //out << " MIME=\"" << convStr(qstrMime) << "\" File=\"" << convStr(qstrFile) << "\" Descr=\"" << convStr(qstrDescr) << "\" Binary data size=" << pLast - pBinData << (nPrintedBinSize != nBinSize ? " Begins with: " : " Content: ") << asHex(pBinData, nPrintedBinSize);
  394                 out << " " << convStr(tr("MIME=\"%1\" File=\"%2\" Descr=\"%3\" Binary data size=%4").arg(qstrMime).arg(qstrFile).arg(qstrDescr).arg(pLast - pBinData));
  395                 out << " " << convStr((nPrintedBinSize != nBinSize ? tr("Begins with: %1") : tr("Content: %1")).arg(convStr(asHex(pBinData, nPrintedBinSize))));
  396             }
  397         }
  398     }
  399 
  400 
  401     if (Id3V2Frame::NO_APIC != m_eApicStatus)
  402     {
  403         out << " " << convStr(tr("status=%1").arg(tr(getImageStatus())));
  404     }
  405 
  406 }
  407 
  408 
  409 const char* Id3V2Frame::getImageType() const
  410 {
  411     return ImageInfo::getImageType(m_nPictureType);
  412 }
  413 
  414 
  415 const char* Id3V2Frame::getImageStatus() const
  416 {
  417     switch(m_eApicStatus)
  418     {
  419     case USES_LINK: return QT_TR_NOOP("link");
  420     case NON_COVER: return QT_TR_NOOP("non-cover");
  421     case ERR: return QT_TR_NOOP("error");
  422     case COVER: return QT_TR_NOOP("cover");
  423     default: CB_ASSERT1 (false, m_pFileName->s);
  424     }
  425 }
  426 
  427 
  428 double Id3V2Frame::getRating() const // asserts it's POPM
  429 {
  430     CB_ASSERT (0 == strcmp(KnownFrames::LBL_RATING(), m_szName));
  431 
  432     Id3V2FrameDataLoader wrp (*this);
  433     const char* pData (wrp.getData());
  434     int k (0);
  435     while (k < m_nMemDataSize && 0 != pData[k]) { ++k; } // skip email addr
  436     ++k;
  437     if (k >= m_nMemDataSize)
  438     { // error //ttt2 add warning on constructor
  439         return -1;
  440     }
  441     unsigned char c (pData[k]);
  442     return 5*(double(c) - 1)/254;  // ttt2 not sure this is the best mapping
  443 }
  444 
  445 
  446 
  447 /*static*/ string Id3V2Frame::utf8FromBomUtf16(const char* pData, int nSize)
  448 {
  449     CB_CHECK (nSize > 1, NotId3V2Frame); // UNICODE string entries must have a size of 3 or more."
  450     if (2 == nSize && 0 == *pData && 0 == *(pData + 1)) { return ""; } // not quite correct, but seems to happen; even if a string is null, it must begin with BOM //ttt1 add a note
  451     const unsigned char* p (reinterpret_cast<const unsigned char*> (pData));
  452     CB_CHECK ((0xff == p[0] && 0xfe == p[1]) || (0xff == p[1] && 0xfe == p[0]), NotId3V2Frame); //ttt2 perhaps use other exception
  453 
  454 #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
  455     bool bIsFffeOk (true); // x86
  456 #else
  457     bool bIsFffeOk (false);
  458 #endif
  459 
  460     pData += 2;
  461 
  462     vector<char> v;
  463     int nUSize ((nSize - 2)/2); // ttt3 maybe check that nSize is an even number, but not sure what to do if it isn't
  464     if ((0xff == *p && !bIsFffeOk) || (0xff != *p && bIsFffeOk))
  465     { // swap bytes so QString would understand them; it might seem useful for QString to understand BOM, but it doesn't; so ...
  466         v.resize(nUSize*2);
  467         for (int i = 0; i < nUSize; ++i)
  468         {
  469             v[i*2] = pData[i*2 + 1];
  470             v[i*2 + 1] = pData[i*2];
  471         }
  472 
  473         pData = &v[0];
  474     }
  475     const ushort* pUs (reinterpret_cast<const ushort*>(pData));
  476     QString qs (QString::fromUtf16(pUs, nUSize));
  477     string s (convStr(qs));
  478 
  479     return s;
  480 }
  481 
  482 
  483 
  484 // for display / export; for frames in KnownFrames::getKnownFrames only the data up to the first 0 is used (effectively removing comments in 2.3.0), while for the others, including TXXX, nulls and all other characters with codes below 32 are replaced with spaces (as a result, both description and value are shown for TXXX), so in either case the return value doesn't contain nulls;
  485 // whitespaces at the end of the string are removed;
  486 string Id3V2Frame::getUtf8String() const
  487 {
  488     try
  489     {
  490         string s (getUtf8StringImpl());
  491         if (0 == KnownFrames::getKnownFrames().count(m_szName) || isTxxx())
  492         { // text frames may contain null characters, and what's after a null char isn't supposed to be displayed; however, for frames that aren't used we may want to see what's after the null
  493             // ttt2 null characters mean different things inside ID3V2.3.0 (comment follows) vs. ID3V2.4.0 (multiple values separated by 0); however, this doesn't matter in current implementation, because Id3V240Frame::getUtf8StringImpl() replaces nulls with commas
  494             for (int i = 0; i < cSize(s); ++i)
  495             {
  496                 unsigned char c (s[i]);
  497                 if (c < 32) // ttt3 ASCII only
  498                 {
  499                     s[i] = 32;
  500                 }
  501             }
  502         }
  503 
  504         s = s.c_str(); // !!! so now s doesn't contain null chars
  505         rtrim(s);
  506 
  507         return s;
  508     }
  509     catch (const Id3V2FrameDataLoader::LoadFailure&)
  510     {
  511         return convStr(TagReader::tr("<error loading frame>")); //ttt2 not sure if this is the best thing to do, but at least avoids crashes;
  512     }
  513     catch (const Id3V2Frame::NotId3V2Frame&)
  514     {
  515         return convStr(TagReader::tr("<error decoding frame>")); //ttt2 not sure if this is the best thing to do, but at least avoids crashes;
  516     }
  517 }
  518 
  519 
  520 // for internal processing; similar to getUtf8String() but doesn't replace internal null characters with spaces;
  521 // removes trailing nulls and whitespaces, as well as 2.3.0 comments;
  522 string Id3V2Frame::getRawUtf8String() const
  523 {
  524     try
  525     {
  526         string s (getUtf8StringImpl());
  527 
  528         int i (cSize(s) - 1);
  529         for (; i >= 0; --i)
  530         {
  531             unsigned char c (s[i]);
  532             if (0 != c && (c >= ' ' || (c < ' ' && QChar(c).isSpace())))
  533             {
  534                 break;
  535             }
  536         }
  537 
  538         ++i;
  539         if (i < cSize(s))
  540         {
  541             s.erase(i);
  542         }
  543 
  544         return s;
  545     }
  546     catch (const Id3V2FrameDataLoader::LoadFailure&)
  547     {
  548         return convStr(TagReader::tr("<error loading frame>")); //ttt2 not sure if this is the best thing to do, but at least avoids crashes;
  549     }
  550     catch (const Id3V2Frame::NotId3V2Frame&)
  551     {
  552         return convStr(TagReader::tr("<error decoding frame>")); //ttt2 not sure if this is the best thing to do, but at least avoids crashes;
  553     }
  554 }
  555 
  556 
  557 bool Id3V2Frame::isTxxx() const
  558 {
  559     return 0 == strcmp(KnownFrames::LBL_TXXX(), m_szName);
  560 }
  561 
  562 
  563 
  564 //============================================================================================================
  565 //============================================================================================================
  566 //============================================================================================================
  567 
  568 
  569 Id3V2FrameDataLoader::Id3V2FrameDataLoader(const Id3V2Frame& frame) : m_frame(frame)
  570 {
  571     if (cSize(frame.m_vcData) < m_frame.m_nMemDataSize)
  572     {
  573         int nDiscard (frame.getOffset()); // for DataLengthIndicator
  574         //m_bOwnsData = true;
  575         CB_ASSERT1 (frame.m_vcData.empty(), m_frame.m_pFileName->s);
  576         CB_ASSERT1 (0 != frame.m_pFileName, m_frame.m_pFileName->s);
  577 
  578         m_vcOwnData.resize(m_frame.m_nMemDataSize);
  579         ifstream_utf8 in (m_frame.m_pFileName->s.c_str(), ios::binary);
  580         in.seekg(m_frame.m_pos);
  581         in.seekg(m_frame.m_nDiskHdrSize, ios_base::cur);
  582         streampos posNext (m_frame.m_pos);
  583         posNext += m_frame.m_nDiskDataSize + m_frame.m_nDiskHdrSize;
  584         int nContentBytesSkipped (0);
  585         int nRead (0);
  586         in.seekg(nDiscard, ios_base::cur);
  587         nRead = readID3V2(m_frame.m_bHasUnsynch, in, &m_vcOwnData[0], cSize(m_vcOwnData), posNext, nContentBytesSkipped);
  588         //qDebug("nRead %d ; m_frame.m_nMemDataSize %d ; nContentBytesSkipped %d ", nRead, m_frame.m_nMemDataSize, nContentBytesSkipped);
  589         if (cSize(m_vcOwnData) != nRead)
  590         {
  591             CB_THROW(LoadFailure); // triggered on 2017.01.12 in XP, but there were no files on the disk (was a user's data, with logs turned off)
  592         }
  593 
  594         m_pData = &m_vcOwnData[0];
  595     }
  596     else
  597     {
  598         if (frame.m_vcData.empty())
  599         {
  600             static char c (0);
  601             m_pData = &c;
  602         }
  603         else
  604         {
  605             m_pData = &frame.m_vcData[0];
  606         }
  607     }
  608 }
  609 
  610 
  611 Id3V2FrameDataLoader::~Id3V2FrameDataLoader()
  612 {
  613 }
  614 
  615 
  616 
  617 
  618 
  619 
  620 
  621 
  622 //============================================================================================================
  623 //============================================================================================================
  624 //============================================================================================================
  625 
  626 /*static*/ const char* KnownFrames::LBL_TITLE() { return "TIT2"; }
  627 /*static*/ const char* KnownFrames::LBL_ARTIST() { return "TPE1"; }
  628 /*static*/ const char* KnownFrames::LBL_TRACK_NUMBER() { return "TRCK"; }
  629 /*static*/ const char* KnownFrames::LBL_TIME_YEAR_230() { return "TYER"; }
  630 /*static*/ const char* KnownFrames::LBL_TIME_DATE_230() { return "TDAT"; }
  631 /*static*/ const char* KnownFrames::LBL_TIME_240() { return "TDRC"; }
  632 /*static*/ const char* KnownFrames::LBL_GENRE() { return "TCON"; }
  633 /*static*/ const char* KnownFrames::LBL_IMAGE() { return "APIC"; }
  634 /*static*/ const char* KnownFrames::LBL_ALBUM() { return "TALB"; }
  635 /*static*/ const char* KnownFrames::LBL_RATING() { return "POPM"; }
  636 /*static*/ const char* KnownFrames::LBL_COMPOSER() { return "TCOM"; }
  637 
  638 /*static*/ const char* KnownFrames::LBL_WMP_VAR_ART() { return "TPE2"; }
  639 /*static*/ const char* KnownFrames::LBL_ITUNES_VAR_ART() { return "TCMP"; }
  640 
  641 /*static*/ const char* KnownFrames::LBL_TXXX() { return "TXXX"; }
  642 
  643 
  644 //============================================================================================================
  645 //============================================================================================================
  646 //============================================================================================================
  647 
  648 
  649 
  650 Id3V2StreamBase::Id3V2StreamBase(int nIndex, istream& in, StringWrp* pFileName) :
  651         DataStream(nIndex),
  652 
  653         m_nPaddingSize(0),
  654         m_pos(in.tellg()),
  655         m_pFileName(pFileName),
  656 
  657         m_eImageStatus(ImageInfo::NO_PICTURE_FOUND),
  658         m_pPicFrame(0)
  659 {
  660 }
  661 
  662 
  663 
  664 /*override*/ Id3V2StreamBase::~Id3V2StreamBase()
  665 {
  666     clearPtrContainer(m_vpFrames);
  667 }
  668 
  669 
  670 
  671 
  672 
  673 bool Id3V2StreamBase::hasUnsynch() const
  674 {
  675     return 0 != (m_cFlags & 0x80);
  676 }
  677 
  678 
  679 void Id3V2StreamBase::printFrames(ostream& out) const
  680 {
  681     for (vector<Id3V2Frame*>::const_iterator it = m_vpFrames.begin(), end = m_vpFrames.end(); it != end; ++it)
  682     {
  683         (*it)->print(out, Id3V2Frame::FULL_INFO);
  684 //(*it)->print(cout);
  685     }
  686 }
  687 
  688 
  689 
  690 /*override*/ void Id3V2StreamBase::copy(std::istream& in, std::ostream& out)
  691 {
  692     appendFilePart(in, out, m_pos, m_nTotalSize); //ttt2
  693 }
  694 
  695 
  696 
  697 /*override*/ std::string Id3V2StreamBase::getInfo() const
  698 {
  699     ostringstream out;
  700     out << convStr(DataStream::tr("padding=")) << m_nPaddingSize << ", " << convStr(DataStream::tr("unsynch=")) << convStr(GlobalTranslHlp::tr(boolAsYesNo(hasUnsynch()))) << "; " << convStr(DataStream::tr("frames")) << ": ";
  701     bool bFirst (true);
  702     for (vector<Id3V2Frame*>::const_iterator it = m_vpFrames.begin(), end = m_vpFrames.end(); it != end; ++it)
  703     {
  704         if (!bFirst) { out << ", "; }
  705         bFirst = false;
  706         (*it)->print(out, Id3V2Frame::SHORT_INFO);
  707     }
  708     string s (out.str());
  709 //cout << s << endl;
  710 //printHex(s, cout, false);
  711     return s;
  712 }
  713 
  714 
  715 
  716 Id3V2Frame* Id3V2StreamBase::findFrame(const char* szFrameName) //ttt2 finds the first frame, but doesn't care about duplicates
  717 {
  718     for (int i = 0, n = cSize(m_vpFrames); i < n; ++i)
  719     {
  720         Id3V2Frame* p = m_vpFrames[i];
  721         if (0 == strcmp(szFrameName, p->m_szName)) { return p; }
  722     }
  723     return 0;
  724 }
  725 
  726 
  727 const Id3V2Frame* Id3V2StreamBase::findFrame(const char* szFrameName) const //ttt2 finds the first frame, but doesn't care about duplicates
  728 {
  729     for (int i = 0, n = cSize(m_vpFrames); i < n; ++i)
  730     {
  731         const Id3V2Frame* p = m_vpFrames[i];
  732         if (0 == strcmp(szFrameName, p->m_szName)) { return p; }
  733     }
  734     return 0;
  735 }
  736 
  737 
  738 
  739 
  740 
  741 /*override*/ std::string Id3V2StreamBase::getTitle(bool* pbFrameExists /* = 0*/) const
  742 {
  743     const Id3V2Frame* p (findFrame(KnownFrames::LBL_TITLE()));
  744     if (0 != pbFrameExists) { *pbFrameExists = 0 != p; }
  745     if (0 == p) { return ""; }
  746     return p->getUtf8String();
  747 }
  748 
  749 
  750 
  751 /*override*/ std::string Id3V2StreamBase::getArtist(bool* pbFrameExists /* = 0*/) const
  752 {
  753     const Id3V2Frame* p (findFrame(KnownFrames::LBL_ARTIST()));
  754     if (0 != pbFrameExists) { *pbFrameExists = 0 != p; }
  755     if (0 == p) { return ""; }
  756     return p->getUtf8String();
  757 }
  758 
  759 
  760 /*override*/ std::string Id3V2StreamBase::getTrackNumber(bool* pbFrameExists /* = 0*/) const
  761 {
  762     const Id3V2Frame* p (findFrame(KnownFrames::LBL_TRACK_NUMBER()));
  763     if (0 != pbFrameExists) { *pbFrameExists = 0 != p; }
  764     if (0 == p) { return ""; }
  765     return p->getUtf8String();
  766 }
  767 
  768 /*static bool isNum(const string& s)
  769 {
  770     if (s.empty()) { return false; }
  771     for (int i = 0, n = cSize(s); i < n; ++i)
  772     {
  773         if (!isdigit(s[i])) { return false; }
  774     }
  775     return true;
  776 }*/
  777 
  778 static string decodeGenre(const string& s)
  779 {
  780     string strRes;
  781     const char* q (s.c_str());
  782     while ('(' == *q && '(' != *(q + 1))
  783     {
  784         const char* q1 (q + 1);
  785         if (!isdigit(*q1)) { return s; } // error
  786         for (; isdigit(*q1); ++q1) {}
  787         if (')' != *q1) { return s; } // error
  788 
  789         if (!strRes.empty()) { strRes += " / "; }
  790         strRes += getId3V1Genre(atoi(q + 1));
  791         q = q1 + 1;
  792     }
  793 
  794     if ('(' == *q && '(' == *(q + 1)) { ++q; }
  795     if (0 != *q)
  796     {
  797         if (!strRes.empty()) { strRes += " / "; }
  798         strRes += q;
  799     }
  800 
  801     return strRes;
  802 }
  803 
  804 
  805 /*override*/ std::string Id3V2StreamBase::getGenre(bool* pbFrameExists /* = 0*/) const
  806 {
  807     const Id3V2Frame* p (findFrame(KnownFrames::LBL_GENRE())); // for valid formats see tstGenre() and http://www.id3.org/id3v2.3.0#head-42b02d20fb8bf48e38ec5415e34909945dd849dc
  808     if (0 != pbFrameExists) { *pbFrameExists = 0 != p; }
  809     if (0 == p) { return ""; }
  810 
  811     string s (p->getUtf8String());
  812     /*int n (cSize(s));
  813     if (n > 2 && '(' == s[0] && ')' == s[n - 1] && isNum(s.substr(1, n - 2)))
  814     {
  815         return getId3V1Genre(atoi(s.c_str() + 1));
  816     }
  817 
  818     if (isNum(s))
  819     {
  820         return getId3V1Genre(atoi(s.c_str()));
  821     }*/
  822 
  823     return decodeGenre(s);
  824 }
  825 
  826 /*
  827 void tstGenre() //ttt2 remove
  828 {
  829     cout << "\nGenre test\n";
  830     { string s ("gaga"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; }
  831     { string s ("(10)gaga"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; }
  832     { string s ("(10)(83)gaga"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; }
  833     { string s ("(10)(83)((gaga)"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; }
  834     { string s ("(10a)gaga"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; }
  835     { string s ("(b10)gaga"); cout << "*" << s << "*" << decodeGenre(s) << "*\n"; }
  836 }
  837 */
  838 
  839 
  840 void Id3V2StreamBase::checkFrames(NoteColl& notes) // various checks to be called from derived class' constructor
  841 {
  842     const Id3V2Frame* p (findFrame(KnownFrames::LBL_GENRE()));
  843     if (0 != p)
  844     {
  845         string s (p->getUtf8String());
  846         /*int n (cSize(s));
  847         if (n > 2 && '(' == s[0] && ')' == s[n - 1] && isNum(s.substr(1, n - 2)))
  848         {
  849             MP3_NOTE (p->m_pos, "Numerical value between parantheses found as track genre. The standard specifies a numerical value, but most applications use a descriptive name instead.");
  850         }
  851         else if (isNum(s))
  852         {
  853             MP3_NOTE (p->m_pos, "Numerical value found as track genre. While this is consistent with the standard, most applications use a descriptive name instead.");
  854         }
  855         else */if (s.empty())
  856         {
  857             MP3_NOTE (p->m_pos, id3v2EmptyTcon); //ttt2 perhaps replace this with something more generic
  858         }
  859     }
  860 
  861     //ttt2 add other checks
  862 }
  863 //ttt2 perhaps use links to pictures in crt dir
  864 
  865 
  866 /*override*/ ImageInfo Id3V2StreamBase::getImage(bool* pbFrameExists /* = 0*/) const
  867 {
  868 //if (0 != pbFrameExists) { *pbFrameExists = false; } return ImageInfo(ImageInfo::NO_PICTURE_FOUND);
  869 
  870     if (0 != pbFrameExists)
  871     {
  872         const Id3V2Frame* p (findFrame(KnownFrames::LBL_IMAGE()));
  873         *pbFrameExists = 0 != p;
  874     }
  875 
  876     //ImageInfo res;
  877 
  878     //res.m_eStatus = m_eImageStatus;
  879 
  880     if (ImageInfo::OK != m_eImageStatus && ImageInfo::LOADED_NOT_COVER != m_eImageStatus)
  881     {
  882         CB_ASSERT1 (0 == m_pPicFrame, m_pFileName->s);
  883         return ImageInfo(-1, m_eImageStatus);
  884     }
  885 
  886     CB_ASSERT1 (0 != m_pPicFrame, m_pFileName->s);
  887     try
  888     {
  889         Id3V2FrameDataLoader wrp (*m_pPicFrame);
  890         const char* pCrtData (wrp.getData());
  891         const char* pBinData (pCrtData + m_pPicFrame->m_nImgOffset);
  892         //CB_OLD_CHECK (pixmap.loadFromData(pBinData, m_nImgSize));
  893 
  894         // make sure the data is still available and correct (the file might have been modified externally)
  895         if (-1 == m_pPicFrame->m_nWidth)
  896         {
  897             QPixmap pic;
  898             if (!pic.loadFromData(reinterpret_cast<const unsigned char*>(pBinData), m_pPicFrame->m_nImgSize)) // this takes a lot of time
  899             {
  900                 goto e1;
  901             }
  902             m_pPicFrame->m_nWidth = short(pic.width());
  903             m_pPicFrame->m_nHeight = short(pic.height());
  904         }
  905 
  906         QByteArray b (QByteArray::fromRawData(pBinData, m_pPicFrame->m_nImgSize));
  907         b.append('x'); b.resize(b.size() - 1); // !!! these are needed because fromRawData() doesn't create copies of the memory used for the byte array
  908         return ImageInfo(m_pPicFrame->m_nPictureType, m_eImageStatus, m_pPicFrame->m_eCompr, b, m_pPicFrame->m_nWidth, m_pPicFrame->m_nHeight);
  909 
  910         //QBuffer bfr (&res.m_compressedImg);
  911         //bfr.
  912         //res.m_compressedImg = QByteArray(fromRawData
  913         //delete pPictureInfo;
  914     }
  915     catch (const Id3V2FrameDataLoader::LoadFailure&)
  916     {
  917         //eImageStatus = ImageInfo::ERROR_LOADING;
  918     }
  919 e1:
  920     trace("The picture could be loaded before but now this is no longer possible. The most likely reason is that the file was moved or changed by an external application."); //ttt0 is there a macro?
  921 
  922     return ImageInfo(-1, ImageInfo::ERROR_LOADING);
  923 }
  924 
  925 
  926 /*override*/ vector<ImageInfo> Id3V2StreamBase::getImages() const
  927 {
  928     vector<ImageInfo> v;
  929 
  930     for (int i = 0; i < cSize(m_vpFrames); ++i)
  931     {
  932         Id3V2Frame* pFrame (m_vpFrames[i]);
  933         if (Id3V2Frame::NON_COVER == pFrame->m_eApicStatus || Id3V2Frame::COVER == pFrame->m_eApicStatus)
  934         {
  935             try
  936             {
  937                 Id3V2FrameDataLoader wrp (*pFrame);
  938                 const char* pCrtData (wrp.getData());
  939                 const char* pBinData (pCrtData + pFrame->m_nImgOffset);
  940                 //CB_OLD_CHECK (pixmap.loadFromData(pBinData, m_nImgSize));
  941 
  942                 // make sure the data is still available and correct (the file might have been modified externally)
  943                 if (-1 == pFrame->m_nWidth)
  944                 {
  945                     QPixmap pic;
  946                     if (!pic.loadFromData(reinterpret_cast<const unsigned char*>(pBinData), pFrame->m_nImgSize)) // this takes a lot of time
  947                     {
  948                         v.push_back(ImageInfo(pFrame->m_nPictureType, ImageInfo::ERROR_LOADING));
  949                         continue;
  950                     }
  951                     pFrame->m_nWidth = short(pic.width());
  952                     pFrame->m_nHeight = short(pic.height());
  953                 }
  954 
  955                 QByteArray b (QByteArray::fromRawData(pBinData, pFrame->m_nImgSize));
  956                 b.append('x'); b.resize(b.size() - 1); // !!! these are needed because fromRawData() doesn't create copies of the memory used for the byte array
  957                 v.push_back(ImageInfo(pFrame->m_nPictureType, Id3V2Frame::COVER == pFrame->m_eApicStatus ? ImageInfo::OK : ImageInfo::LOADED_NOT_COVER, pFrame->m_eCompr, b, pFrame->m_nWidth, pFrame->m_nHeight));
  958             }
  959             catch (const Id3V2FrameDataLoader::LoadFailure&)
  960             {
  961                 v.push_back(ImageInfo(pFrame->m_nPictureType, ImageInfo::ERROR_LOADING));
  962             }
  963 
  964             continue;
  965         }
  966 
  967         if (Id3V2Frame::ERR == pFrame->m_eApicStatus)
  968         {
  969             v.push_back(ImageInfo(pFrame->m_nPictureType, ImageInfo::ERROR_LOADING));
  970         }
  971 
  972         if (Id3V2Frame::USES_LINK == pFrame->m_eApicStatus)
  973         {
  974             v.push_back(ImageInfo(pFrame->m_nPictureType, ImageInfo::USES_LINK));
  975         }
  976     }
  977 
  978     return v;
  979 }
  980 
  981 
  982 /*override*/ std::string Id3V2StreamBase::getImageData(bool* pbFrameExists /* = 0*/) const
  983 {
  984     if (0 != pbFrameExists)
  985     {
  986         *pbFrameExists = 0 != m_pPicFrame;
  987     }
  988 
  989     if (0 == m_pPicFrame) { return ""; }
  990 
  991     ostringstream out; // !!! don't translate, as this is used by XML export only //ttt1 review exporting as XML
  992     out << "type:" << m_pPicFrame->getImageType() << ", status:" << m_pPicFrame->getImageStatus() << ", size:" << m_pPicFrame->m_nWidth << "x" << m_pPicFrame->m_nHeight << ", compr:" << ImageInfo::getComprStr(m_pPicFrame->m_eCompr);
  993 
  994     return out.str();
  995 }
  996 
  997 /*static*/ const char* Id3V2StreamBase::decodeApic(NoteColl& notes, int nDataSize, streampos pos, const char* const pData, const char*& szMimeType, int& nPictureType, const char*& szDescription)
  998 {
  999     MP3_CHECK (0 == pData[0] || 3 == pData[0], pos, id3v2UnsupApicTextEnc, CB_EXCP(NotSupTextEnc)); // !!! there's no need for StreamIsUnsupported here, because this error is not fatal, and it isn't allowed to propagate, therefore doesn't cause a stream to be Unsupported; //ttt2 review, support
 1000     szMimeType = pData + 1; // ttt3 type 0 is Latin1, while type 3 is UTF8, so this isn't quite right; however, MIME types should probably be plain ASCII, so it's the same; and anyway, we only recognize JPEG and PNG, which are ASCII
 1001     //int nMimeSize (strnlen(pData, nDataSize));
 1002 
 1003     const char* p (pData + 1);
 1004     for (; p < pData + nDataSize && 0 != *p; ++p) {}
 1005     MP3_CHECK (p < pData + nDataSize - 1, pos, id3v2UnsupApicTextEnc, CB_EXCP(ErrorDecodingApic)); // "-1" to account for szDescription
 1006     CB_ASSERT (0 == *p);
 1007     ++p;
 1008 
 1009     nPictureType = *p++;
 1010 
 1011     szDescription = p;
 1012     for (; p < pData + nDataSize; ++p)
 1013     {
 1014         if (0 == *p)
 1015         {
 1016             return p + 1;
 1017         }
 1018     }
 1019 
 1020     CB_THROW(ErrorDecodingApic);
 1021 }
 1022 
 1023 
 1024 
 1025 void Id3V2StreamBase::preparePictureHlp(NoteColl& notes, Id3V2Frame* pFrame, const char* pFrameData, const char* pImgData, const char* szMimeType)
 1026 {
 1027     if (0 == strcmp("-->", szMimeType))
 1028     {
 1029         MP3_NOTE (pFrame->m_pos, id3v2LinkInApic);
 1030         pFrame->m_eApicStatus = Id3V2Frame::USES_LINK;
 1031         return;
 1032     }
 1033 
 1034     //QPixmap img; // !!! QPixmap can only be used in GUI threads, so QImage must be used instead: http://lists.trolltech.com/qt-interest/2005-02/thread00008-0.html or http://lists.trolltech.com/qt-interest/2006-11/thread00045-0.html
 1035     QImage img;
 1036     const unsigned char* pBinData (reinterpret_cast<const unsigned char*>(pImgData));
 1037     int nSize (pFrame->m_nMemDataSize - (pImgData - pFrameData));
 1038     if (img.loadFromData(pBinData, nSize))
 1039     {
 1040         pFrame->m_nImgSize = nSize;
 1041         pFrame->m_nImgOffset = pImgData - pFrameData;
 1042         pFrame->m_eApicStatus = pFrame->m_nPictureType == Id3V2Frame::PT_COVER ? Id3V2Frame::COVER : Id3V2Frame::NON_COVER;
 1043         pFrame->m_nWidth = short(img.width());
 1044         pFrame->m_nHeight = short(img.height());
 1045         if (0 == strcmp("image/jpeg", szMimeType) || 0 == strcmp("image/jpg", szMimeType))
 1046         {
 1047             pFrame->m_eCompr = ImageInfo::JPG;
 1048             const unsigned char* p (pBinData);
 1049             for (int i = 0; i < nSize - 1; i++)
 1050             {
 1051                 unsigned char c1 (*(p + i));
 1052                 if (c1 != 0xff)
 1053                 {
 1054                     continue;
 1055                 }
 1056                 unsigned char c2 (*(p + i + 1));
 1057                 if (c2 == 0xc0)
 1058                 {
 1059                     // baseline (i.e. non-progressive)
 1060                     break;
 1061                 }
 1062                 if (c2 == 0xc2)
 1063                 {
 1064                     // progressive
 1065                     MP3_NOTE (pFrame->m_pos, id3v2ProgressiveJpeg);
 1066                     break;
 1067                 }
 1068             }
 1069         }
 1070         else if (0 == strcmp("image/png", szMimeType))
 1071         {
 1072             pFrame->m_eCompr = ImageInfo::PNG;
 1073         }
 1074         else
 1075         {
 1076             pFrame->m_eCompr = ImageInfo::INVALID;
 1077         } //ttt2 perhaps support GIF or other formats; (well, GIFs can be loaded, but are recompressed when saving)
 1078         return;
 1079     }
 1080 
 1081     pFrame->m_eApicStatus = Id3V2Frame::ERR;
 1082 
 1083     if (pFrame->m_nMemDataSize > 100)
 1084     {
 1085         MP3_NOTE (pFrame->m_pos, id3v2ErrorLoadingApic);
 1086     }
 1087     else
 1088     {
 1089         MP3_NOTE (pFrame->m_pos, id3v2ErrorLoadingApicTooShort);
 1090     }
 1091 }
 1092 
 1093 
 1094 
 1095 void Id3V2StreamBase::preparePicture(NoteColl& notes) // initializes fields used by the APIC frame
 1096 {
 1097     const char* szMimeType;
 1098     const char* szDescription;
 1099     //Id3V2Frame* pFirstLoadableCover (0);
 1100     Id3V2Frame* pFirstLoadableNonCover (0);
 1101     Id3V2Frame* pFirstApic (0); // might have errors, might be link, ...
 1102 
 1103     for (int i = 0, n = cSize(m_vpFrames); i < n; ++i)
 1104     { // go through the frame list and set m_eApicStatus
 1105         Id3V2Frame* p = m_vpFrames[i];
 1106         if (0 == strcmp(KnownFrames::LBL_IMAGE(), p->m_szName))
 1107         {
 1108             if (0 == pFirstApic) { pFirstApic = p; } // !!! regardless of what exceptions might get thrown, p will have an m_eApicStatus other than NO_APIC; that happens either in preparePictureHlp() or with explicit assignments
 1109             try
 1110             {
 1111                 Id3V2FrameDataLoader wrp (*p);
 1112                 const char* pData (wrp.getData());
 1113                 const char* pCrtData (0);
 1114 
 1115                 try
 1116                 {
 1117                     pCrtData = decodeApic(notes, p->m_nMemDataSize, p->m_pos, pData, szMimeType, p->m_nPictureType, szDescription);
 1118                 }
 1119                 catch (const NotSupTextEnc&)
 1120                 {
 1121                     p->m_eApicStatus = Id3V2Frame::ERR;
 1122                     continue;
 1123                 }
 1124                 catch (const ErrorDecodingApic&)
 1125                 {
 1126                     p->m_eApicStatus = Id3V2Frame::ERR;
 1127                     continue;
 1128                 }
 1129 
 1130                 if (0 != *szDescription) { MP3_NOTE (p->m_pos, id3v2PictDescrIgnored); }
 1131 
 1132                 preparePictureHlp(notes, p, pData, pCrtData, szMimeType);
 1133 
 1134                 if (Id3V2Frame::COVER == p->m_eApicStatus)
 1135                 {
 1136                     if (0 == m_pPicFrame)
 1137                     {
 1138                         m_eImageStatus = ImageInfo::OK;
 1139                         m_pPicFrame = p;
 1140                     }
 1141                 }
 1142 
 1143                 if (0 == pFirstLoadableNonCover && Id3V2Frame::NON_COVER == p->m_eApicStatus)
 1144                 {
 1145                     pFirstLoadableNonCover = p;
 1146                 }
 1147             }
 1148             catch (const Id3V2FrameDataLoader::LoadFailure&)
 1149             {
 1150                 MP3_NOTE (p->m_pos, fileWasChanged);
 1151                 p->m_eApicStatus = Id3V2Frame::ERR;
 1152             }
 1153         }
 1154     }
 1155 
 1156 
 1157     if (ImageInfo::OK == m_eImageStatus)
 1158     {
 1159         return;
 1160     }
 1161 
 1162     // no cover frame was found; just pick the first loadable APIC frame and use it
 1163     CB_ASSERT (ImageInfo::NO_PICTURE_FOUND == m_eImageStatus);
 1164 
 1165     if (0 != pFirstLoadableNonCover)
 1166     {
 1167         m_eImageStatus = ImageInfo::LOADED_NOT_COVER;
 1168         m_pPicFrame = pFirstLoadableNonCover;
 1169         return;
 1170     }
 1171 
 1172     if (0 == pFirstApic)
 1173     {
 1174         return;
 1175     }
 1176 
 1177     switch (pFirstApic->m_eApicStatus)
 1178     {
 1179     case Id3V2Frame::USES_LINK: m_eImageStatus = ImageInfo::USES_LINK; return;
 1180     case Id3V2Frame::ERR: m_eImageStatus = ImageInfo::ERROR_LOADING; return;
 1181     default: CB_ASSERT1 (false, m_pFileName->s); // all cases should have been covered
 1182     }
 1183 
 1184 }
 1185 
 1186 
 1187 
 1188 
 1189 /*override*/ std::string Id3V2StreamBase::getAlbumName(bool* pbFrameExists /* = 0*/) const
 1190 {
 1191     const Id3V2Frame* p (findFrame(KnownFrames::LBL_ALBUM()));
 1192     if (0 != pbFrameExists) { *pbFrameExists = 0 != p; }
 1193     if (0 == p) { return ""; }
 1194     return p->getUtf8String();
 1195 }
 1196 
 1197 
 1198 /*override*/ std::string Id3V2StreamBase::getComposer(bool* pbFrameExists /* = 0*/) const
 1199 {
 1200     const Id3V2Frame* p (findFrame(KnownFrames::LBL_COMPOSER()));
 1201     if (0 != pbFrameExists) { *pbFrameExists = 0 != p; }
 1202     if (0 == p) { return ""; }
 1203     return p->getUtf8String();
 1204 }
 1205 
 1206 
 1207 // *pbFrameExists gets set if at least one frame exists
 1208 /*override*/ int Id3V2StreamBase::getVariousArtists(bool* pbFrameExists /* = 0*/) const
 1209 {
 1210     int nRes (0);
 1211     if (0 != pbFrameExists) { *pbFrameExists = false; }
 1212 
 1213     try
 1214     {
 1215         {
 1216             const Id3V2Frame* p (findFrame(KnownFrames::LBL_WMP_VAR_ART()));
 1217             if (0 != p)
 1218             {
 1219                 if (0 != pbFrameExists)
 1220                 {
 1221                     *pbFrameExists = true;
 1222                 }
 1223                 if (0 == convStr(p->getUtf8String()).compare("VaRiOuS Artists", Qt::CaseInsensitive))
 1224                 {
 1225                     nRes += TagReader::VA_WMP;
 1226                 }
 1227             }
 1228         }
 1229 
 1230         {
 1231             const Id3V2Frame* p (findFrame(KnownFrames::LBL_ITUNES_VAR_ART()));
 1232             if (0 != p)
 1233             {
 1234                 if (0 != pbFrameExists)
 1235                 {
 1236                     *pbFrameExists = true;
 1237                 }
 1238                 if ("1" == p->getUtf8String())
 1239                 {
 1240                     nRes += TagReader::VA_ITUNES;
 1241                 }
 1242             }
 1243         }
 1244     }
 1245     catch (const Id3V2Frame::NotId3V2Frame&)
 1246     { // !!! nothing
 1247     }
 1248     catch (const Id3V2Frame::UnsupportedId3V2Frame&)
 1249     { // !!! nothing
 1250     }
 1251 
 1252     return nRes;
 1253 }
 1254 
 1255 
 1256 /*override*/ double Id3V2StreamBase::getRating(bool* pbFrameExists /* = 0*/) const
 1257 {
 1258     const Id3V2Frame* p (findFrame(KnownFrames::LBL_RATING()));
 1259     if (0 != pbFrameExists) { *pbFrameExists = 0 != p; }
 1260     if (0 == p) { return -1; }
 1261 
 1262     return p->getRating();
 1263 }
 1264 
 1265 
 1266 static const char* REPLAY_GAIN_ROOT ("replaygain");
 1267 
 1268 
 1269 //ttt1 mp3gain 1.5.1 can use ID3V2 instead of APE, so that should be tested as well; see TXXX="MP3GAIN_MINMAX[...]" in "Arctic Monkeys - 01 - My Propeller.mp3" (which also has TXXX="replaygain_track_gain[...]") - also review Id3V2Cleaner::processId3V2Stream
 1270 bool Id3V2StreamBase::hasReplayGain() const
 1271 {
 1272     for (int i = 0; i < cSize(m_vpFrames); ++i)
 1273     {
 1274         const Id3V2Frame* pFrame (m_vpFrames[i]);
 1275         if (pFrame->isTxxx())
 1276         {
 1277             QString qs (convStr(pFrame->getUtf8String()));
 1278 
 1279             if (qs.startsWith(REPLAY_GAIN_ROOT, Qt::CaseInsensitive))
 1280             {
 1281                 return true;
 1282             }
 1283         }
 1284     }
 1285 
 1286     return false;
 1287 }
 1288 
 1289 
 1290 static const char* getId3V2ClassDisplayName() // needed so pointer comparison can be performed for Id3V2StreamBase::getClassDisplayName() regardless of the template param
 1291 {
 1292     return "ID3V2";
 1293 }
 1294 
 1295 
 1296 
 1297 /*static*/ const char* Id3V2StreamBase::getClassDisplayName()
 1298 {
 1299     return getId3V2ClassDisplayName();
 1300 }
 1301 
 1302 
 1303 
 1304 // returns a frame with the given name; normally it returns the first such frame, but it may return another if there's a good reason; returns 0 if no frame was found;
 1305 const Id3V2Frame* Id3V2StreamBase::getFrame(const char* szName) const
 1306 {
 1307     if (0 == strcmp(szName, KnownFrames::LBL_IMAGE()))
 1308     {
 1309         return m_pPicFrame;
 1310     }
 1311 
 1312     return findFrame(szName);
 1313 }
 1314 
 1315 
 1316 /*static*/ const set<string>& KnownFrames::getExcludeFromInfoFrames() // frames that shouldn't be part of "other info"; doesn't include TXXX and "Various Artists" frames
 1317 {
 1318     static bool bFirstTime (true);
 1319     static set<string> s;
 1320     if (bFirstTime)
 1321     {
 1322         s.insert(KnownFrames::LBL_TITLE());
 1323         s.insert(KnownFrames::LBL_ARTIST());
 1324         s.insert(KnownFrames::LBL_TRACK_NUMBER());
 1325         s.insert(KnownFrames::LBL_TIME_YEAR_230());
 1326         s.insert(KnownFrames::LBL_TIME_DATE_230());
 1327         s.insert(KnownFrames::LBL_TIME_240()); //ttt2 perhaps this shouldn't be used for 2.3.0, but it covers cases like reading 2.4.0 and writing 2.3.0 or bugs by some tools
 1328         s.insert(KnownFrames::LBL_GENRE());
 1329         s.insert(KnownFrames::LBL_IMAGE());
 1330         s.insert(KnownFrames::LBL_ALBUM());
 1331         s.insert(KnownFrames::LBL_RATING());
 1332         s.insert(KnownFrames::LBL_COMPOSER());
 1333 
 1334         bFirstTime = false;
 1335     }
 1336 
 1337     return s;
 1338 }
 1339 
 1340 // includes "Various Artists" frames; doesn't include TXXX
 1341 /*static*/ const set<string>& KnownFrames::getKnownFrames()
 1342 {
 1343     static bool bFirstTime (true);
 1344     static set<string> sKnownFrames (getExcludeFromInfoFrames());
 1345     if (bFirstTime)
 1346     {
 1347         sKnownFrames.insert(KnownFrames::LBL_WMP_VAR_ART());
 1348         sKnownFrames.insert(KnownFrames::LBL_ITUNES_VAR_ART());
 1349 
 1350         bFirstTime = false;
 1351     }
 1352 
 1353     return sKnownFrames;
 1354 }
 1355 
 1356 
 1357 
 1358 /*override*/ std::string Id3V2StreamBase::getOtherInfo() const
 1359 {
 1360     set<string> sUsedFrames;
 1361 
 1362     //string strRes;
 1363     ostringstream out;
 1364     bool b (false);
 1365 
 1366     for (int i = 0, n = cSize(m_vpFrames); i < n; ++i)
 1367     {
 1368         Id3V2Frame* p = m_vpFrames[i];
 1369         if (KnownFrames::getExcludeFromInfoFrames().count(p->m_szName) > 0 && sUsedFrames.count(p->m_szName) == 0)
 1370         {
 1371             sUsedFrames.insert(p->m_szName);
 1372         }
 1373         else
 1374         {
 1375             if (Id3V2Frame::NON_COVER != p->m_eApicStatus && Id3V2Frame::COVER != p->m_eApicStatus) // images that can be loaded are shown, so there shouldn't be another entry for them
 1376             {
 1377                 if (b) { out << ", "; }
 1378                 b = true;
 1379                 p->print(out, Id3V2Frame::FULL_INFO);
 1380             }
 1381         }
 1382     }
 1383     return out.str();
 1384 }
 1385 
 1386 
 1387 
 1388 void Id3V2StreamBase::checkDuplicates(NoteColl& notes) const
 1389 {
 1390     // for some it's OK to be duplicated, e.g. for APIC and various "Picture type" pictures;
 1391 
 1392     set<pair<string, int> > sUsedFrames;
 1393     int nImgCnt (0);
 1394     streampos secondImgPos (-1);
 1395 
 1396     for (int i = 0, n = cSize(m_vpFrames); i < n; ++i)
 1397     {
 1398         Id3V2Frame* p = m_vpFrames[i];
 1399 //if (0 == strcmp("TDOR", p->m_szName)) { MP3_NOTE (p->m_pos, "TDOR found. See if it should be processed."); } //ttt remove
 1400 //if (0 == strcmp("TDRC", p->m_szName)) { MP3_NOTE (p->m_pos, "TDRC found. See if it should be processed."); }
 1401 //if (0 == strcmp("TDRL", p->m_szName)) { MP3_NOTE (p->m_pos, "TDRL found. See if it should be processed."); }
 1402         if (KnownFrames::getKnownFrames().count(p->m_szName) > 0)
 1403         {
 1404             if (0 == strcmp(KnownFrames::LBL_IMAGE(), p->m_szName))
 1405             {
 1406                 ++nImgCnt;
 1407                 if (2 == nImgCnt)
 1408                 {
 1409                     secondImgPos = p->m_pos;
 1410                 }
 1411             }
 1412 
 1413             if (sUsedFrames.count(make_pair(p->m_szName, p->m_nPictureType)) == 0)
 1414             {
 1415                 sUsedFrames.insert(make_pair(p->m_szName, p->m_nPictureType));
 1416             }
 1417             else
 1418             {
 1419                 if (0 == strcmp(KnownFrames::LBL_IMAGE(), p->m_szName))
 1420                 {
 1421                     MP3_NOTE (p->m_pos, id3v2DuplicatePic);
 1422                 }
 1423                 else if (0 == strcmp(KnownFrames::LBL_RATING(), p->m_szName))
 1424                 {
 1425                     MP3_NOTE (p->m_pos, id3v2DuplicatePopm);
 1426                 }
 1427                 else
 1428                 {
 1429                     MP3_NOTE_D (p->m_pos, id3v2MultipleFramesWithSameName, tr("%1 (Frame: %2)").arg(noteTr(id3v2MultipleFramesWithSameName)).arg(p->m_szName)); //ttt2 m_pos should be replaced with the position of the second frame with this ID
 1430                 }
 1431             }
 1432         }
 1433     }
 1434 
 1435     if (nImgCnt > 1)
 1436     {
 1437         MP3_NOTE (secondImgPos, id3v2MultipleApic);
 1438     }
 1439 }
 1440 
 1441 
 1442 TagTimestamp Id3V2StreamBase::get230TrackTime(bool* pbFrameExists) const
 1443 {
 1444     const Id3V2Frame* p (findFrame(KnownFrames::LBL_TIME_YEAR_230()));
 1445     if (0 != pbFrameExists) { *pbFrameExists = 0 != p; }
 1446     if (0 == p) { return TagTimestamp(""); }
 1447     string strYear (p->getUtf8String());
 1448     if (4 != cSize(strYear)) { return TagTimestamp(""); }
 1449 
 1450     p = findFrame(KnownFrames::LBL_TIME_DATE_230());
 1451     try
 1452     {
 1453         if (0 == p) { return TagTimestamp(strYear); }
 1454         string strDate (p->getUtf8String());
 1455         if (4 != cSize(strDate)) { return TagTimestamp(strYear); }
 1456         return TagTimestamp(strYear + "-" + strDate.substr(2, 2) + "-" + strDate.substr(0, 2));
 1457     }
 1458 
 1459     catch (const TagTimestamp::InvalidTime&)
 1460     {
 1461         return TagTimestamp("");
 1462     }
 1463 }
 1464 
 1465 
 1466 vector<const Id3V2Frame*> Id3V2StreamBase::getKnownFrames() const // to be used by Id3V2Cleaner;
 1467 {
 1468     vector<const Id3V2Frame*> v;
 1469 
 1470     for (int i = 0; i < cSize(m_vpFrames); ++i)
 1471     {
 1472         const Id3V2Frame* p (m_vpFrames[i]);
 1473 
 1474         if (KnownFrames::getKnownFrames().count(p->m_szName) > 0 || p->isTxxx())
 1475         {
 1476             // add if known except if a picture with the same type was already added; don't add duplicates even if Id3V230StreamWriter could take care of them, because it keeps the last value and we want the first one
 1477             bool bAdd (true);
 1478             for (int j = 0; j < cSize(v); ++j)
 1479             {
 1480                 if (p->m_nPictureType == v[j]->m_nPictureType && 0 == strcmp(v[j]->m_szName, p->m_szName))
 1481                 {
 1482                     // !!! important for both image and non-image frames; while Id3V230StreamWriter would remove duplicates as configured, we want to get rid of them now, so the first value is kept; Id3V230StreamWriter removes old values as new ones are added, which would keep the last value
 1483                     //ttt2 not always right for multiple pictures if "keep one" is checked;
 1484 
 1485                     bAdd = false;
 1486                     break;
 1487                 }
 1488             }
 1489 
 1490             if (p->isTxxx())
 1491             {
 1492                 string s (p->getUtf8String());
 1493                 QString qs (convStr(s));
 1494 
 1495                 if (qs.startsWith(REPLAY_GAIN_ROOT, Qt::CaseInsensitive))
 1496                 {
 1497                     bAdd = true; //ttt2 perhaps check it's no duplicate; not sure about case
 1498                 }
 1499             }
 1500 
 1501             if (bAdd && 0 == strcmp(KnownFrames::LBL_IMAGE(), p->m_szName) && Id3V2Frame::COVER != p->m_eApicStatus && Id3V2Frame::NON_COVER != p->m_eApicStatus)
 1502             { // !!! get rid of broken pictures and links
 1503                 bAdd = false;
 1504             }
 1505 
 1506             if (bAdd)
 1507             {
 1508                 v.push_back(p);
 1509             }
 1510         }
 1511     }
 1512 
 1513     return v;
 1514 }
 1515 
 1516 
 1517 //============================================================================================================
 1518 //============================================================================================================
 1519 //============================================================================================================
 1520 
 1521 
 1522 
 1523 // explicit instantiation
 1524 //template class Id3V2Stream<Id3V230Frame>;
 1525 //template class Id3V2Stream<Id3V240Frame>;
 1526 
 1527 
 1528 
 1529 //============================================================================================================
 1530 //============================================================================================================
 1531 //============================================================================================================
 1532 
 1533 
 1534 
 1535 /*static*/ const char* KnownFrames::getFrameName (int n)
 1536 {
 1537     switch (n)
 1538     {
 1539     case 0: return LBL_TITLE();
 1540     case 1: return LBL_ARTIST();
 1541     case 2: return LBL_TRACK_NUMBER();
 1542     case 3: return LBL_TIME_YEAR_230();
 1543     case 4: return LBL_TIME_DATE_230();
 1544     case 5: return LBL_TIME_240();
 1545     case 6: return LBL_GENRE();
 1546     case 7: return LBL_IMAGE();
 1547     case 8: return LBL_ALBUM();
 1548     case 9: return LBL_RATING();
 1549     case 10: return LBL_COMPOSER();
 1550     }
 1551 
 1552     CB_TRACE_AND_THROW (InvalidIndex);
 1553 }
 1554 
 1555 
 1556 /*static*/ bool KnownFrames::canHaveDuplicates(const char* szName)
 1557 { //ttt2 make this more sophisticated; maybe allow multiple LBL_RATING, each with its own email;
 1558     //if (0 == strcmp(szName, LBL_IMAGE())) { return true; }
 1559 
 1560     if (1 == getKnownFrames().count(szName)) { return false; } // !!! OK for LBL_IMAGE, because when actually using this the image type should be compared as well
 1561 
 1562     return true;
 1563 }
 1564 
 1565 //ttt2 note for mismatch between file name and rating