//ttt2 in a way this could take care of Xing headers for CBR streams as well, but it doesn't seem the best place to do it, especially as we ignore most of the data in the Lame header and "restoring a header" means just putting back byte count and frame count
/*override*/ Transformation::Result VbrRepairerBase::repair(const Mp3Handler& h, const TransfConfig& transfConfig, const std::string& strOrigSrcName, std::string& strTempName, bool bForceRebuild)
{
    const vector<DataStream*>& vpStreams (h.getStreams());
    ifstream_utf8 in (h.getName().c_str(), ios::binary);
    set<int> sVbriPos;
    int nAudioPos (-1);
    set<int> sXingPos;
    int n (cSize(vpStreams));
    int nXingPos (-1);
    XingStreamBase* pXingStreamBase (0);
    for (int i = 0; i < n; ++i)
    {
        MpegStream* pAudio (dynamic_cast<MpegStream*>(vpStreams[i]));
        if (0 != pAudio)
        {
            nAudioPos = i;
            break;
        }

        VbriStream* pVbriStream (dynamic_cast<VbriStream*>(vpStreams[i]));
        if (0 != pVbriStream)
        {
            sVbriPos.insert(i);
        }

        XingStreamBase* q (dynamic_cast<XingStreamBase*>(vpStreams[i]));
        if (0 != q)
        {
            sXingPos.insert(i);
            nXingPos = i;
            pXingStreamBase = q;
        }
    }

    if (-1 == nAudioPos) { return NOT_CHANGED; } // no audio
    MpegStream* pAudio (dynamic_cast<MpegStream*>(vpStreams[nAudioPos]));
    if (!pAudio->isVbr()) { return NOT_CHANGED; } // CBR audio

    bool bXingOk (1 == cSize(sXingPos) && 1 == sXingPos.count(nAudioPos - 1) && pXingStreamBase->matches(pAudio));
    if (!bForceRebuild && sVbriPos.empty() && bXingOk) { return NOT_CHANGED; } // exit if there's no VBRI and there's one matching Xing right before the audio


    bool bRepairMp3Fixer (false);
    bool bRemoveXing (true); // if true, existing headers are removed
    bool bAddXing (true); // if true, a header is added before the first Audio

    if (1 == cSize(sXingPos) && nXingPos < n - 2 && pXingStreamBase->isBrokenByMp3Fixer(vpStreams[nXingPos + 1], vpStreams[nXingPos + 2]))
    {
        // ttt2 see also http://www.kde-apps.org/content/show.php/Mp3Fixer?content=31539 for a bug that makes mp3fix.rb write its fix at a wrong address for mono files, but that's not going to be fixed now; at least don't try to fix this on mono files (or better: fix only stereo mpeg1 L III)

        bRemoveXing = bAddXing = false;
        bRepairMp3Fixer = true;
    }

    { // temp
        transfConfig.getTempName(strOrigSrcName, getActionName(), strTempName);
        ofstream_utf8 out (strTempName.c_str(), ios::binary);
        in.seekg(0);

        int i (0);
        for (; i < n; ++i)
        {
            DataStream* p (vpStreams[i]);
            if (0 != dynamic_cast<VbriStream*>(p))
            { // nothing to do; this gets discarded
            }
            else if (0 != dynamic_cast<XingStreamBase*>(p))
            {
                if (bRepairMp3Fixer)
                {
                    XingStreamBase* pXing (dynamic_cast<XingStreamBase*>(p));
                    MpegStream* pAudio (dynamic_cast<MpegStream*>(vpStreams[i + 2]));
                    CB_ASSERT(0 != pXing && 0 != pAudio); // that's what isBrokenByMp3Fixer() is about

                    int nOffs (pXing->getFirstFrame().getSideInfoSize() + MpegFrame::MPEG_FRAME_HDR_SIZE);

                    createXing(out, pXing->getFirstFrame(), pAudio->getFrameCount() + 1, pAudio->getSize() + pXing->getSize());

                    appendFilePart(in, out, p->getPos(), nOffs);
                    streampos pos (p->getPos());

/*#ifdef GENERATE_TOC //!!! Doesn't matter whether GENERATE_TOC is defined or not. Mp3Fixer broke the first audio frame by adding length info and no TOC (although it claims to add TOC as well). What we do here is create a new Xing header (by calling createXing()) and remove the 16 bytes Mp3Fixer added, to restore the first frame to an audio frame, the way it was before Mp3Fixer messed it up. It's up to createXing() to add TOC or not.
                    //const int nXingSize (16 + 100);
#else
                    const int nXingSize (16);
#endif*/

                    const int nMp3FixXingSize (16); // Mp3Fix always adds 16 bytes, because it doesn't use a TOC
                    pos += nMp3FixXingSize + nOffs;
                    appendFilePart(in, out, pos, p->getSize() - nOffs - nMp3FixXingSize);
                }
                else if (!bRemoveXing)
                {
                    p->copy(in, out);
                }
            }
            else if (0 != dynamic_cast<MpegStream*>(p))
            {
                if (bAddXing)
                {
                    dynamic_cast<MpegStream*>(p)->createXing(out);
                }
                break;
            }
            else
            { // any other stream
                p->copy(in, out);
            }
        }

        for (; i < n; ++i)
        {
            DataStream* p (vpStreams[i]);
            p->copy(in, out);
        }
    }

    return CHANGED_NO_RECALL;
}
Example #2
0
void Mp3Handler::parse(ifstream_utf8& in) // ttt2 this function is a mess; needs rethinking
{
    in.seekg(0, ios::end);
    m_posEnd = in.tellg();
    in.seekg(0, ios::beg);
    STRM_ASSERT (in);
    int nIndex (0);
    //NoteColl& notes (m_notes);

    streampos pos (0);
    while (pos < m_posEnd)
    {
        in.clear();
        in.seekg(pos);

        //if (m_vpAllStreams.size() > 48)
        if (cSize(m_vpAllStreams) > 100) //ttt2 perhaps make this configurable
        {
            {
                m_notes.resetCounter();
                NoteColl& notes (m_notes);
                MP3_NOTE (pos, tooManyStreams);
            }

            UnknownDataStream* p (new UnknownDataStream(nIndex, m_notes, in, m_posEnd - pos));
            UnknownDataStream* pPrev (m_vpAllStreams.empty() ? 0 : dynamic_cast<UnknownDataStream*>(m_vpAllStreams.back()));
            if (0 != pPrev)
            {   // last stream is "unknown" too
                pPrev->append(*p);
                delete p;
            }
            else
            {
                m_vpAllStreams.push_back(p);
                ++nIndex;
            }
            pos = m_posEnd;
            break;
        }

        bool bBrokenMpegFrameFound (false);
        int nBrokenMpegFrameCount (0);

        string strBrokenInfo;
        const char* szBrokenName (0);
        string strUnsupportedInfo;
        const char* szUnsupportedName (0);

        try
        {
            m_vpAllStreams.push_back(new Id3V230Stream(nIndex, m_notes, in, m_pFileName));
            pos += m_vpAllStreams.back()->getSize();
            ++nIndex;
            continue;
        }
        catch (const StreamIsBroken& ex) {
            if (0 == szBrokenName) {
                szBrokenName = ex.getStreamName();
                strBrokenInfo = ex.getInfo();
            }
        }
        catch (const StreamIsUnsupported& ex) {
            if (0 == szUnsupportedName) {
                szUnsupportedName = ex.getStreamName();
                strUnsupportedInfo = ex.getInfo();
            }
        }
        catch (const std::bad_alloc&) {
            throw;
        }
        catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions
        in.clear();
        in.seekg(pos);

        try
        {
            m_vpAllStreams.push_back(new Id3V240Stream(nIndex, m_notes, in, m_pFileName));
            pos += m_vpAllStreams.back()->getSize();
            ++nIndex;
            continue;
        }
        catch (const StreamIsBroken& ex) {
            if (0 == szBrokenName) {
                szBrokenName = ex.getStreamName();
                strBrokenInfo = ex.getInfo();
            }
        }
        catch (const StreamIsUnsupported& ex) {
            if (0 == szUnsupportedName) {
                szUnsupportedName = ex.getStreamName();
                strUnsupportedInfo = ex.getInfo();
            }
        }
        catch (const std::bad_alloc&) {
            throw;
        }
        catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions
        in.clear();
        in.seekg(pos);

        try
        {
            m_vpAllStreams.push_back(new LameStream(nIndex, m_notes, in));
            pos += m_vpAllStreams.back()->getSize();
            ++nIndex;
            continue;
        }
        catch (const MpegFrame::PrematurelyEndedMpegFrame& ex)
        {   // this could as well be in the Xing, Vbri or MPEG block and the program would perform pretty much the same (the only difference is that more exceptions would be thrown and caught as "...", with no side effect, if one of the other blocks is used); the exception means that what began as an MPEG frame should be longer than what is left in the file
            bBrokenMpegFrameFound = true;
            szBrokenName = MpegStream::getClassDisplayName();
            strBrokenInfo = ex.m_strInfo;
            goto e1;
        }
        catch (const StreamIsBroken& ex) {
            if (0 == szBrokenName) {
                szBrokenName = ex.getStreamName();
                strBrokenInfo = ex.getInfo();
            }
        }
        catch (const StreamIsUnsupported& ex) {
            if (0 == szUnsupportedName) {
                szUnsupportedName = ex.getStreamName();
                strUnsupportedInfo = ex.getInfo();
            }
        }
        catch (const std::bad_alloc&) {
            throw;
        }
        catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions
        in.clear();
        in.seekg(pos);

        try
        {
            m_vpAllStreams.push_back(new XingStream(nIndex, m_notes, in));
            pos += m_vpAllStreams.back()->getSize();
            ++nIndex;
            continue;
        }
        catch (const StreamIsBroken& ex) {
            if (0 == szBrokenName) {
                szBrokenName = ex.getStreamName();
                strBrokenInfo = ex.getInfo();
            }
        }
        catch (const StreamIsUnsupported& ex) {
            if (0 == szUnsupportedName) {
                szUnsupportedName = ex.getStreamName();
                strUnsupportedInfo = ex.getInfo();
            }
        }
        catch (const std::bad_alloc&) {
            throw;
        }
        catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions
        in.clear();
        in.seekg(pos);

        try
        {
            m_vpAllStreams.push_back(new VbriStream(nIndex, m_notes, in));
            pos += m_vpAllStreams.back()->getSize();
            ++nIndex;
            continue;
        }
        catch (const StreamIsBroken& ex) {
            if (0 == szBrokenName) {
                szBrokenName = ex.getStreamName();
                strBrokenInfo = ex.getInfo();
            }
        }
        catch (const StreamIsUnsupported& ex) {
            if (0 == szUnsupportedName) {
                szUnsupportedName = ex.getStreamName();
                strUnsupportedInfo = ex.getInfo();
            }
        }
        catch (const std::bad_alloc&) {
            throw;
        }
        catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions
        in.clear();
        in.seekg(pos);

        try
        {
            m_vpAllStreams.push_back(new MpegStream(nIndex, m_notes, in));
            trace("enter checkLastFrameInMpegStream()");
            checkLastFrameInMpegStream(in);
            trace("exit checkLastFrameInMpegStream()");
            pos += m_vpAllStreams.back()->getSize();
            ++nIndex;
            continue;
        }
        catch (const MpegStream::StreamTooShort& ex)
        {   // not 100% correct, but most likely it gets here after in the previous step it managed to read an MPEG stream which had a truncated last frame, which was removed from that stream and now it has thrown this exception; quite similar to what PrematurelyEndedMpegFrame as caught in the LameStream block above is doing, the difference here being that there are enough bytes left in the file to read a full "frame", while there it's EOF; both cases are likely to have some other streams inside them
            bBrokenMpegFrameFound = true;
            szBrokenName = MpegStream::getClassDisplayName();
            strBrokenInfo = ex.m_strInfo;
            nBrokenMpegFrameCount = ex.m_nFrameCount;
            goto e1;
        }
        catch (const StreamIsBroken& ex) {
            if (0 == szBrokenName) {
                szBrokenName = ex.getStreamName();
                strBrokenInfo = ex.getInfo();
            }
        }
        catch (const StreamIsUnsupported& ex) {
            if (0 == szUnsupportedName) {
                szUnsupportedName = ex.getStreamName();
                strUnsupportedInfo = ex.getInfo();
            }
        }
        catch (const std::bad_alloc&) {
            throw;
        }
        catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions
        in.clear();
        in.seekg(pos);

        try
        {
            m_vpAllStreams.push_back(new ApeStream(nIndex, m_notes, in));
            pos += m_vpAllStreams.back()->getSize();
            ++nIndex;
            continue;
        }
        catch (const StreamIsBroken& ex) {
            if (0 == szBrokenName) {
                szBrokenName = ex.getStreamName();
                strBrokenInfo = ex.getInfo();
            }
        }
        catch (const StreamIsUnsupported& ex) {
            if (0 == szUnsupportedName) {
                szUnsupportedName = ex.getStreamName();
                strUnsupportedInfo = ex.getInfo();
            }
        }
        catch (const std::bad_alloc&) {
            throw;
        }
        catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions
        in.clear();
        in.seekg(pos);

        try
        {
            m_vpAllStreams.push_back(new Id3V1Stream(nIndex, m_notes, in));
            pos += m_vpAllStreams.back()->getSize();
            ++nIndex;
            continue;
        }
        catch (const StreamIsBroken& ex) {
            if (0 == szBrokenName) {
                szBrokenName = ex.getStreamName();
                strBrokenInfo = ex.getInfo();
            }
        }
        catch (const StreamIsUnsupported& ex) {
            if (0 == szUnsupportedName) {
                szUnsupportedName = ex.getStreamName();
                strUnsupportedInfo = ex.getInfo();
            }
        }
        catch (const std::bad_alloc&) {
            throw;
        }
        catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions
        in.clear();
        in.seekg(pos);

        try
        {
            m_vpAllStreams.push_back(new LyricsStream(nIndex, m_notes, in, m_pFileName->s));
            pos += m_vpAllStreams.back()->getSize();
            ++nIndex;
            continue;
        }
        catch (const StreamIsBroken& ex) {
            if (0 == szBrokenName) {
                szBrokenName = ex.getStreamName();
                strBrokenInfo = ex.getInfo();
            }
        }
        catch (const StreamIsUnsupported& ex) {
            if (0 == szUnsupportedName) {
                szUnsupportedName = ex.getStreamName();
                strUnsupportedInfo = ex.getInfo();
            }
        }
        catch (const std::bad_alloc&) {
            throw;
        }
        catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions
        in.clear();
        in.seekg(pos);

        try
        {
            m_vpAllStreams.push_back(new NullDataStream(nIndex, m_notes, in));
            pos += m_vpAllStreams.back()->getSize();
            ++nIndex;
            continue;
        }
        catch (const StreamIsBroken& ex) {
            if (0 == szBrokenName) {
                szBrokenName = ex.getStreamName();
                strBrokenInfo = ex.getInfo();
            }
        }
        catch (const StreamIsUnsupported& ex) {
            if (0 == szUnsupportedName) {
                szUnsupportedName = ex.getStreamName();
                strUnsupportedInfo = ex.getInfo();
            }
        }
        catch (const std::bad_alloc&) {
            throw;
        }
        catch (...) {} //ttt2 replace "..." with something app-specific, to avoid catching system exceptions
        in.clear();
        in.seekg(pos);

        //------------------------------------------------------------------
e1:
        streampos posNextFrame;
        try
        {
            posNextFrame = getNextStream(in, pos);
        }
        catch (const EndOfFile&)
        {
            posNextFrame = m_posEnd;
            in.clear();
        }

        //try
        {
            bool bAdded (false);

            if (bBrokenMpegFrameFound) //ttt2 review this: "bBrokenMpegFrameFound gets set if MpegStream::StreamTooShort is thrown"; it's probably OK
            {
                try
                {
                    MpegStream* pPrev (m_vpAllStreams.empty() ? 0 : dynamic_cast<MpegStream*>(m_vpAllStreams.back()));
                    m_vpAllStreams.push_back(new TruncatedMpegDataStream(pPrev, nIndex, m_notes, in, posNextFrame - pos));
                    pos += m_vpAllStreams.back()->getSize();
                    ++nIndex;
                    bAdded = true;
                }
                catch (const TruncatedMpegDataStream::NotTruncatedMpegDataStream&)
                {
                    in.clear();
                    in.seekg(pos);
                    /*
                                          ttt2 perhaps add limit of 10 broken streams per file
                                                keep in mind that a transformation that removes broken streams will have trouble processing a file with "10 broken streams", because after it removes the first, a new one will get created, always having 10 of them
                    */

                }
            }

//ttt2 consider this: there's a stream beginning identified at 1000 and the next one is at 8000; if the first begins with a valid MPEG frame and the second is something else, the whole block from 1000 up to 7999 is identified as "broken mpeg"; this isn't quite right: since no other mpeg frame can be found in the block (if it could, it would be the second stream beginning), there's a lot of other data in the stream besides an mpeg frame; this doesn't feel right;

            if (!bAdded)
            {
                streamoff nSize (posNextFrame - pos);
                if (0 != szUnsupportedName)
                {
                    m_vpAllStreams.push_back(new UnsupportedDataStream(nIndex, m_notes, in, nSize, szUnsupportedName, strUnsupportedInfo));
                    //pos += m_vpAllStreams.back()->getSize();
                    ++nIndex;
                }
                else
                {   // either broken or unknown; these have little useful information, so try to append to the previous one, in these cases: both broken and unknown can be appended to truncated and unknown alone can be appended to unknown
                    DataStream* pPrev (m_vpAllStreams.empty() ? 0 : m_vpAllStreams.back());

                    TruncatedMpegDataStream* pPrevTrunc (dynamic_cast<TruncatedMpegDataStream*>(pPrev));
                    UnknownDataStream* pPrevUnkn (dynamic_cast<UnknownDataStream*>(pPrev));
                    UnsupportedDataStream* pPrevUnsupp (dynamic_cast<UnsupportedDataStream*>(pPrev));

                    if (MpegStream::getClassDisplayName() == szBrokenName && 1 == nBrokenMpegFrameCount)
                    {   // a "broken audio" with a single sense doesn't make much sense at this point (but it mattered above, to add a truncated audio stream)
                        szBrokenName = 0;
                    }

                    if (0 != pPrevTrunc && pPrevTrunc->hasSpace(nSize))
                    {   // append to truncated
                        UnknownDataStream* p (new UnknownDataStream(nIndex, m_notes, in, nSize));
                        pPrevTrunc->append(*p);
                        delete p;
                    }
                    else if (0 != szBrokenName)
                    {   // create broken
                        m_vpAllStreams.push_back(new BrokenDataStream(nIndex, m_notes, in, nSize, szBrokenName, strBrokenInfo));
                        //pos += m_vpAllStreams.back()->getSize();
                        ++nIndex;
                    }
                    else if (0 != pPrevUnkn)
                    {   // append to unknown
                        UnknownDataStream* p (new UnknownDataStream(nIndex, m_notes, in, nSize));
                        pPrevUnkn->append(*p);
                        delete p;
                    }
                    else if (0 != pPrevUnsupp)
                    {   // append to unknown
                        UnknownDataStream* p (new UnknownDataStream(nIndex, m_notes, in, nSize));
                        pPrevUnsupp->append(*p);
                        delete p;
                    }
                    else
                    {   // create unknown
                        UnknownDataStream* p (new UnknownDataStream(nIndex, m_notes, in, nSize));
                        m_vpAllStreams.push_back(p);
                        ++nIndex;
                    }
                }

                pos += nSize;
            }
        }
        /*catch (...) // !!! DON'T catch anything; there's no point; the file is changed or something else pretty bad happened, because creating an Unknown stream shouldn't throw; most likely the existing streams became invalid too; also, there's a chance that the implementation of UnknownDataStream has a bug and an infinite loop will be entered, as all the constructors fail and the position in the file doesn't advance
        {
            in.clear();
            in.seekg(pos); not ok
        }*/

    }

    //cout << "=======================\n";

    //CB_ASSERT (!m_vpAllStreams.empty());
    STRM_ASSERT (pos == m_posEnd);
    pos = 0;
    for (int i = 0; i < cSize(m_vpAllStreams); ++i)
    {
        DataStream* p (m_vpAllStreams[i]);
        //cout << p->getInfo() << endl;
        STRM_ASSERT (p->getPos() == pos);
        pos += p->getSize();
    }
    STRM_ASSERT (pos == m_posEnd);
}