void MismatchedXingRemover::setupDiscarded(const Mp3Handler& h)
{
    m_spStreamsToDiscard.clear();

    const vector<DataStream*>& vpStreams (h.getStreams());
    for (int i = 0, n = cSize(vpStreams); i < n - 1; ++i)
    {
        XingStreamBase* pXing (dynamic_cast<XingStreamBase*>(vpStreams[i]));
        if (0 != pXing)
        {
            MpegStream* pAudio (dynamic_cast<MpegStream*>(vpStreams[i + 1]));
            if (0 != pAudio && pXing->getFrameCount() != pAudio->getFrameCount())
            {
                m_spStreamsToDiscard.insert(pXing);
            }
        }
    }
}
//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;
}
Beispiel #3
0
// checks the streams for issues (missing ID3V2, Unknown streams, inconsistencies, ...)
void Mp3Handler::analyze(const QualThresholds& qualThresholds)
{
    NoteColl& notes (m_notes); // for MP3_NOTE()

    for (int i = 0, n = cSize(m_vpAllStreams); i < n; ++i)
    {
        DataStream* pDs (m_vpAllStreams[i]);
        {
            Id3V230Stream* p (dynamic_cast<Id3V230Stream*>(pDs));
            if (0 != p)
            {
                if (0 == m_pId3V230Stream)
                    m_pId3V230Stream = p;
                else
                    MP3_NOTE (p->getPos(), twoId3V230);
            }
        }

        {
            Id3V240Stream* p (dynamic_cast<Id3V240Stream*>(pDs));
            if (0 != p)
            {
                if (0 == m_pId3V240Stream)
                    m_pId3V240Stream = p;
                else
                    MP3_NOTE (p->getPos(), twoId3V240);
            }
        }

        {
            Id3V1Stream* p (dynamic_cast<Id3V1Stream*>(pDs));
            if (0 != p)
            {
                if (0 == m_pId3V1Stream)
                    m_pId3V1Stream = p;
                else
                    MP3_NOTE (p->getPos(), twoId3V1);
            }
        }

        {
            LameStream* p (dynamic_cast<LameStream*>(pDs));
            if (0 != p)
            {
                if (0 == m_pLameStream)
                    m_pLameStream = p;
                else
                    MP3_NOTE (p->getPos(), twoLame);
            }
        }

        {
            XingStreamBase* p (dynamic_cast<XingStreamBase*>(pDs));
            if (0 != p)
            {
                if (0 == m_pXingStream)
                {
                    m_pXingStream = p;
                    if (i < n - 2 && p->isBrokenByMp3Fixer(m_vpAllStreams[i + 1], m_vpAllStreams[i + 2]))
                    {
                        MP3_NOTE (p->getPos(), xingAddedByMp3Fixer);
                    }
                    if (i < n - 1)
                    {
                        MpegStream* q (dynamic_cast<MpegStream*>(m_vpAllStreams[i + 1]));
                        if (0 != q && p->getFrameCount() != q->getFrameCount())
                        {
                            MP3_NOTE (p->getPos(), xingFrameCountMismatch);
                        }
                    }
                }
                else
                    MP3_NOTE (p->getPos(), twoXing);
            }
        }

        {
            VbriStream* p (dynamic_cast<VbriStream*>(pDs));
            if (0 != p)
            {
                if (0 == m_pVbriStream)
                    m_pVbriStream = p;
                else
                    MP3_NOTE (p->getPos(), twoVbri);
            }
        }

        {
            MpegStream* p (dynamic_cast<MpegStream*>(pDs));
            if (0 != p)
            {
                if (0 == m_pMpegStream)
                    m_pMpegStream = p;
                else
                    MP3_NOTE (p->getPos(), twoAudio);

                //const MpegFrame& frm (p->getFirstFrame());
                if (p->isVbr())
                {
                    switch (p->getChannelMode())
                    {   // ttt2 should be possible that changing qual thresholds in config triggers notes being added / removed
                    case MpegFrame::STEREO:
                        if (p->getBitrate() < qualThresholds.m_nStereoVbr) {
                            MP3_NOTE (p->getPos(), lowQualAudio);
                        }
                        break;
                    case MpegFrame::JOINT_STEREO:
                        if (p->getBitrate() < qualThresholds.m_nJointStereoVbr) {
                            MP3_NOTE (p->getPos(), lowQualAudio);
                        }
                        break;
                    case MpegFrame::DUAL_CHANNEL:
                        if (p->getBitrate() < qualThresholds.m_nDoubleChannelVbr) {
                            MP3_NOTE (p->getPos(), lowQualAudio);
                        }
                        break;
                    case MpegFrame::SINGLE_CHANNEL:
                        if (p->getBitrate() < qualThresholds.m_nDoubleChannelVbr / 2) {
                            MP3_NOTE (p->getPos(), lowQualAudio);
                        }
                        break;
                    }
                }
                else
                {
                    switch (p->getChannelMode())
                    {
                    case MpegFrame::STEREO:
                        if (p->getBitrate() < qualThresholds.m_nStereoCbr) {
                            MP3_NOTE (p->getPos(), lowQualAudio);
                        }
                        break;
                    case MpegFrame::JOINT_STEREO:
                        if (p->getBitrate() < qualThresholds.m_nJointStereoCbr) {
                            MP3_NOTE (p->getPos(), lowQualAudio);
                        }
                        break;
                    case MpegFrame::DUAL_CHANNEL:
                        if (p->getBitrate() < qualThresholds.m_nDoubleChannelCbr) {
                            MP3_NOTE (p->getPos(), lowQualAudio);
                        }
                        break;
                    case MpegFrame::SINGLE_CHANNEL:
                        if (p->getBitrate() < qualThresholds.m_nDoubleChannelCbr / 2) {
                            MP3_NOTE (p->getPos(), lowQualAudio);
                        }
                        break;
                    }
                }
            }
        }

        {
            ApeStream* p (dynamic_cast<ApeStream*>(pDs));
            if (0 != p)
            {
                if (0 == m_pApeStream)
                    m_pApeStream = p;
                else
                    MP3_NOTE (p->getPos(), twoApe);
            }
        }

        {
            LyricsStream* p (dynamic_cast<LyricsStream*>(pDs));
            if (0 != p)
            {
                if (0 == m_pLyricsStream)
                    m_pLyricsStream = p;
                else
                    MP3_NOTE (p->getPos(), twoLyr);
            }
        }

        {
            NullDataStream* p (dynamic_cast<NullDataStream*>(pDs));
            if (0 != p)
            {
                m_vpNullStreams.push_back(p);
            }
        }

        {
            UnknownDataStream* p (dynamic_cast<UnknownDataStream*>(pDs));
            if (0 != p)
            {
                m_vpUnknownStreams.push_back(p);
            }
        }

        {
            BrokenDataStream* p (dynamic_cast<BrokenDataStream*>(pDs));
            if (0 != p)
            {
                m_vpBrokenStreams.push_back(p);
            }
        }

        {
            UnsupportedDataStream* p (dynamic_cast<UnsupportedDataStream*>(pDs));
            if (0 != p)
            {
                m_vpUnsupportedStreams.push_back(p);
            }
        }

        {
            TruncatedMpegDataStream* p (dynamic_cast<TruncatedMpegDataStream*>(pDs));
            if (0 != p)
            {
                m_vpTruncatedMpegStreams.push_back(p);
            }
        }
    }

    if (0 == m_pMpegStream) {
        MP3_NOTE (-1, noAudio);
    }
    if (0 == m_pId3V230Stream) {
        MP3_NOTE (-1, noId3V230);
    }
    if (0 != m_pId3V230Stream && 0 != m_pId3V240Stream) {
        MP3_NOTE (m_pId3V240Stream->getPos(), bothId3V230_V240);
    }
    if (0 != m_pVbriStream) {
        MP3_NOTE (m_pVbriStream->getPos(), vbriFound);
    }
    //if (0 != m_pLyricsStream) { MP3_NOTE (m_pLyricsStream->getPos(), lyricsNotSupported); }
    if (0 != m_pId3V1Stream && 0 == m_pId3V230Stream && 0 == m_pId3V240Stream && 0 == m_pApeStream) {
        MP3_NOTE (m_pId3V1Stream->getPos(), onlyId3V1);
    }
    if (0 == m_pId3V1Stream && 0 == m_pId3V230Stream && 0 == m_pId3V240Stream && 0 == m_pApeStream) {
        MP3_NOTE (-1, noInfoTag);
    }
    if (!m_vpNullStreams.empty()) {
        MP3_NOTE (m_vpNullStreams[0]->getPos(), foundNull);
    }
    if (0 != m_pVbriStream && 0 != m_pXingStream) {
        MP3_NOTE (m_pVbriStream->getPos(), foundVbriAndXing);
    }
    if (0 != m_pXingStream && 0 != m_pMpegStream && m_pXingStream->getIndex() != m_pMpegStream->getIndex() - 1) {
        MP3_NOTE (m_pXingStream->getPos(), xingNotBeforeAudio);
    }

    if (0 != m_pXingStream && 0 != m_pMpegStream && !m_pXingStream->matchesStructure(*m_pMpegStream)) {
        MP3_NOTE (m_pXingStream->getPos(), incompatXing);
    }
    if (0 != m_pId3V1Stream && 0 != m_pMpegStream && m_pId3V1Stream->getIndex() < m_pMpegStream->getIndex()) {
        MP3_NOTE (m_pId3V1Stream->getPos(), id3v1BeforeAudio);
    }
    if (0 != m_pId3V230Stream && 0 != m_pId3V230Stream->getIndex()) {
        MP3_NOTE (m_pId3V230Stream->getPos(), id3v230AfterAudio);
    }
    if (0 == m_pXingStream && 0 != m_pMpegStream && m_pMpegStream->isVbr()) {
        MP3_NOTE (m_pMpegStream->getPos(), missingXing);
    }
    if (0 != m_pMpegStream && m_pMpegStream->isVbr() && (MpegFrame::MPEG1 != m_pMpegStream->getFirstFrame().getVersion() || MpegFrame::LAYER3 != m_pMpegStream->getFirstFrame().getLayer())) {
        MP3_NOTE (m_pMpegStream->getPos(), vbrUsedForNonMpg1L3);
    }
    if (
        (0 != m_pMpegStream) &&
        (0 == m_pApeStream || !m_pApeStream->hasMp3Gain()) &&
        (0 == m_pId3V230Stream || !m_pId3V230Stream->hasReplayGain()) &&
        (0 == m_pId3V240Stream || !m_pId3V240Stream->hasReplayGain())) //ttt2 not quite OK when mutliple ID3V2 streams are found
    {
        MP3_NOTE (m_pMpegStream->getPos(), noMp3Gain);
    }

    if (!m_vpBrokenStreams.empty())
    {
        if (1 == cSize(m_vpBrokenStreams) && m_vpBrokenStreams.back() == m_vpAllStreams.back())
        {
            MP3_NOTE (m_vpBrokenStreams.back()->getPos(), brokenAtTheEnd);
        }
        else
        {
            MP3_NOTE (m_vpBrokenStreams.back()->getPos(), brokenInTheMiddle);
        }
    }

    if (!m_vpUnknownStreams.empty())
    {
        if (1 == cSize(m_vpUnknownStreams) && m_vpUnknownStreams.back() == m_vpAllStreams.back())
        {
            MP3_NOTE (m_vpUnknownStreams.back()->getPos(), unknownAtTheEnd);
        }
        else
        {
            MP3_NOTE (m_vpUnknownStreams.back()->getPos(), unknownInTheMiddle);
        }
    }

    if (!m_vpUnsupportedStreams.empty())
    {
        MP3_NOTE (m_vpUnsupportedStreams.back()->getPos(), unsupportedFound);
    }

    if (!m_vpTruncatedMpegStreams.empty())
    {
        if (1 == cSize(m_vpTruncatedMpegStreams) && m_vpTruncatedMpegStreams.back() == m_vpAllStreams.back())
        {
            MP3_NOTE (m_vpTruncatedMpegStreams.back()->getPos(), truncAudioWithWholeFile);
        }
        else
        {
            MP3_NOTE (m_vpTruncatedMpegStreams.back()->getPos(), truncAudio);
        }
    }

    m_notes.sort();
    //sort(m_notes.begin(), m_notes.end(), notePtrCmp);
}