aboutsummaryrefslogtreecommitdiff
path: root/src/plugins/Effect/crossfade/crossfadeplugin.cpp
Commit message (Collapse)AuthorAgeFilesLines
* some api changestrialuser022018-04-151-3/+3
| | | | git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@7905 90c681e8-e032-0410-971d-27865f9a5e38
* changed contact informationtrialuser022017-10-071-1/+1
| | | | git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@7514 90c681e8-e032-0410-971d-27865f9a5e38
* fixed remaining gcc warningstrialuser022017-06-231-6/+4
| | | | git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@7254 90c681e8-e032-0410-971d-27865f9a5e38
* ported all effect pluginstrialuser022015-12-271-38/+16
| | | | git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@5899 90c681e8-e032-0410-971d-27865f9a5e38
* ported effect pluginstrialuser022014-10-061-3/+3
| | | | git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@4532 90c681e8-e032-0410-971d-27865f9a5e38
* fixed FSF headerstrialuser022012-07-311-1/+1
| | | | git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@2845 90c681e8-e032-0410-971d-27865f9a5e38
* fixed FSF address (trunk)trialuser022012-07-311-1/+1
| | | | git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@2844 90c681e8-e032-0410-971d-27865f9a5e38
* fixed possible engine conflictstrialuser022011-09-161-1/+1
| | | | git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@2352 90c681e8-e032-0410-971d-27865f9a5e38
* crossfade plugin: added 8/24/32-bit formats supporttrialuser022010-12-131-4/+32
| | | | git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@2007 90c681e8-e032-0410-971d-27865f9a5e38
* fixed problem with http/mms streams and crossfade plugin (thanks to Algirdas ↵trialuser022010-10-211-2/+3
| | | | | | Butkus) git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@1950 90c681e8-e032-0410-971d-27865f9a5e38
* crossfade plugin: fixed cutting off last tracktrialuser022010-09-101-3/+7
| | | | git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@1884 90c681e8-e032-0410-971d-27865f9a5e38
* added crossfade plugin (experimental) (Closes issue 272)trialuser022010-09-051-0/+98
git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@1874 90c681e8-e032-0410-971d-27865f9a5e38
'n287' href='#n287'>287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
/***************************************************************************
 *   Copyright (C) 2013 by Ilya Kotov                                      *
 *   forkotov02@hotmail.ru                                                 *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/

#include <QSettings>
#include <QProgressBar>
#include <QThreadPool>
#include <QSettings>
#include <qmmpui/playlisttrack.h>
#include <qmmpui/metadataformatter.h>
#include <qmmpui/filedialog.h>
#include <qmmp/metadatamanager.h>
#include <taglib/mpegfile.h>
#include <taglib/apetag.h>
#include <taglib/flacfile.h>
#include <taglib/xiphcomment.h>
#include <taglib/oggflacfile.h>
#include <taglib/vorbisfile.h>
#include <taglib/wavpackfile.h>
#include <taglib/id3v2tag.h>
#include <taglib/textidentificationframe.h>
#include "rgscanner.h"
#include "gain_analysis.h"
#include "rgscandialog.h"

#define QStringToTString_qt4(s) TagLib::String(s.toUtf8().constData(), TagLib::String::UTF8)

struct ReplayGainInfoItem
{
    QMap<Qmmp::ReplayGainKey, double> info;
    QString url;
    GainHandle_t *handle;
};

RGScanDialog::RGScanDialog(QList <PlayListTrack *> tracks,  QWidget *parent) : QDialog(parent)
{
    m_ui.setupUi(this);
    m_ui.tableWidget->verticalHeader()->setDefaultSectionSize(fontMetrics().height() + 3);
    m_ui.tableWidget->verticalHeader()->setResizeMode(QHeaderView::Fixed);

    QStringList paths;
    MetaDataFormatter formatter("%if(%p&%t,%p - %t,%f) - %l");
    foreach(PlayListTrack *track, tracks)
    {
        //skip streams
        if(track->length() == 0 || track->url().contains("://"))
            continue;
        //skip duplicates
        if(paths.contains(track->url()))
            continue;

        QString ext = track->url().section(".", -1).toLower();
        if(ext == "mp3" || //mpeg 1 layer 3
                ext == "flac" || //native flac
                ext == "oga" || //ogg flac
                ext == "ogg" ||  //ogg vorbis
                ext == "wv") //wavpack
        {
            paths.append(track->url());
            QString name = formatter.parse(track);
            QTableWidgetItem *item = new QTableWidgetItem(name);
            item->setData(Qt::UserRole, track->url());
            item->setData(Qt::ToolTipRole, track->url());
            m_ui.tableWidget->insertRow(m_ui.tableWidget->rowCount());
            m_ui.tableWidget->setItem(m_ui.tableWidget->rowCount() - 1, 0, item);
            QProgressBar *progressBar = new QProgressBar(this);
            progressBar->setRange(0, 100);
            m_ui.tableWidget->setCellWidget(m_ui.tableWidget->rowCount() - 1, 1, progressBar);
        }
    }

    m_ui.tableWidget->resizeColumnsToContents();
    m_ui.writeButton->setEnabled(false);
    //read settings
    QSettings settings(Qmmp::configFile(), QSettings::IniFormat);
    restoreGeometry(settings.value("RGScanner/geometry").toByteArray());
    m_ui.trackCheckBox->setChecked(settings.value("RGScanner/write_track",true).toBool());
    m_ui.albumCheckBox->setChecked(settings.value("RGScanner/write_album",true).toBool());
}

RGScanDialog::~RGScanDialog()
{
    stop();
    qDeleteAll(m_replayGainItemList);
    m_replayGainItemList.clear();
}

void RGScanDialog::on_calculateButton_clicked()
{
    m_ui.writeButton->setEnabled(false);
    for(int i = 0; i < m_ui.tableWidget->rowCount(); ++i)
    {
        QString url = m_ui.tableWidget->item(i, 0)->data(Qt::UserRole).toString();
        RGScanner *scanner = new RGScanner();

        if(!scanner->prepare(url))
        {
            m_ui.tableWidget->setItem(i, 2, new QTableWidgetItem(tr("Error")));
            delete scanner;
            continue;
        }
        scanner->setAutoDelete(false);
        m_scanners.append(scanner);
        connect(scanner, SIGNAL(progress(int)), m_ui.tableWidget->cellWidget(i, 1), SLOT(setValue(int)));
        connect(scanner, SIGNAL(finished(QString)), SLOT(onScanFinished(QString)));
        QThreadPool::globalInstance()->start(scanner);
    }
}

void RGScanDialog::onScanFinished(QString url)
{

    for(int i = 0; i < m_ui.tableWidget->rowCount(); ++i)
    {
        if(url != m_ui.tableWidget->item(i, 0)->data(Qt::UserRole).toString())
            continue;
        RGScanner *scanner = findScannerByUrl(url);
        if(!scanner)
            qFatal("RGScanDialog: unable to find scanner by URL!");
        m_ui.tableWidget->setItem(i, 2, new QTableWidgetItem(tr("%1 dB").arg(scanner->gain())));
        m_ui.tableWidget->setItem(i, 4, new QTableWidgetItem(QString::number(scanner->peak())));
        break;
    }

    bool stopped = true;

    foreach (RGScanner *scanner, m_scanners)
    {
        if(scanner->isRunning() || scanner->isPending())
            stopped = false;
    }

    if(stopped)
    {
        qDebug("RGScanDialog: all threads finished");
        QThreadPool::globalInstance()->waitForDone();

        QMultiMap<QString, ReplayGainInfoItem*> itemGroupMap; //items grouped  by album

        //group by album name
        foreach (RGScanner *scanner, m_scanners)
        {
            if(!scanner->hasValues())
                continue;
            ReplayGainInfoItem *item = new ReplayGainInfoItem;
            item->info[Qmmp::REPLAYGAIN_TRACK_GAIN] = scanner->gain();
            item->info[Qmmp::REPLAYGAIN_TRACK_PEAK] = scanner->peak();
            item->url = scanner->url();
            item->handle = scanner->handle();
            QString album = getAlbumName(item->url);
            itemGroupMap.insert(album, item);
        }
        //calculate album peak and gain
        foreach (QString album, itemGroupMap.keys())
        {
            QList<ReplayGainInfoItem*> items = itemGroupMap.values(album);
            GainHandle_t **a = (GainHandle_t **) malloc(items.count()*sizeof(GainHandle_t *));
            double album_peak = 0;
            for(int i = 0; i < items.count(); ++i)
            {
                a[i] = items[i]->handle;
                album_peak = qMax(items[i]->info[Qmmp::REPLAYGAIN_TRACK_PEAK], album_peak);
            }
            double album_gain = GetAlbumGain(a, items.count());
            free(a);
            foreach (ReplayGainInfoItem *item, items)
            {
                item->info[Qmmp::REPLAYGAIN_ALBUM_PEAK] = album_peak;
                item->info[Qmmp::REPLAYGAIN_ALBUM_GAIN] = album_gain;
            }
        }
        //clear scanners
        qDeleteAll(m_scanners);
        m_scanners.clear();
        //clear previous replaygain information
        qDeleteAll(m_replayGainItemList);
        m_replayGainItemList.clear();
        //update table
        m_replayGainItemList = itemGroupMap.values();
        for(int i = 0; i < m_ui.tableWidget->rowCount(); ++i)
        {
            QString url = m_ui.tableWidget->item(i, 0)->data(Qt::UserRole).toString();
            bool found = false;
            foreach (ReplayGainInfoItem *item, m_replayGainItemList)
            {
                if(item->url == url)
                {
                    found = true;
                    double album_gain = item->info[Qmmp::REPLAYGAIN_ALBUM_GAIN];
                    double album_peak = item->info[Qmmp::REPLAYGAIN_ALBUM_PEAK];
                    m_ui.tableWidget->setItem(i, 3, new QTableWidgetItem(tr("%1 dB").arg(album_gain)));
                    m_ui.tableWidget->setItem(i, 5, new QTableWidgetItem(QString::number(album_peak)));
                }
            }
            if(!found)
                m_ui.tableWidget->setItem(i, 3, new QTableWidgetItem(tr("Error")));
        }

        //clear items
        itemGroupMap.clear();

        m_ui.writeButton->setEnabled(true);
    }
}

void RGScanDialog::reject()
{
    QSettings settings(Qmmp::configFile(), QSettings::IniFormat);
    settings.setValue("RGScanner/geometry", saveGeometry());
    settings.setValue("RGScanner/write_track", m_ui.trackCheckBox->isChecked());
    settings.setValue("RGScanner/write_album", m_ui.albumCheckBox->isChecked());
    QDialog::reject();
}

void RGScanDialog::stop()
{
    if(m_scanners.isEmpty())
        return;
    foreach (RGScanner *scaner, m_scanners)
    {
        scaner->stop();
    }
    QThreadPool::globalInstance()->waitForDone();
    qDeleteAll(m_scanners);
    m_scanners.clear();
}

RGScanner *RGScanDialog::findScannerByUrl(const QString &url)
{
    foreach (RGScanner *scanner, m_scanners)
    {
        if(scanner->url() == url)
            return scanner;
    }
    return 0;
}

QString RGScanDialog::getAlbumName(const QString &url)
{
    QList <FileInfo *> infoList = MetaDataManager::instance()->createPlayList(url);
    if(infoList.isEmpty())
        return QString();
    QString album = infoList.first()->metaData(Qmmp::ALBUM);
    qDeleteAll(infoList);
    return album;
}

TagLib::String RGScanDialog::gainToString(double value)
{
    return QStringToTString_qt4(QString("%1 dB").arg(value, 0, 'f', 2));
}

TagLib::String RGScanDialog::peakToString(double value)
{
    return QStringToTString_qt4(QString("%1").arg(value, 0, 'f', 6));
}

void RGScanDialog::writeAPETag(TagLib::APE::Tag *tag, ReplayGainInfoItem *item)
{
    if(m_ui.trackCheckBox->isChecked())
    {
        tag->addValue("REPLAYGAIN_TRACK_GAIN", gainToString(item->info[Qmmp::REPLAYGAIN_TRACK_GAIN]));
        tag->addValue("REPLAYGAIN_TRACK_PEAK", peakToString(item->info[Qmmp::REPLAYGAIN_TRACK_PEAK]));
    }
    if(m_ui.albumCheckBox->isChecked())
    {
        tag->addValue("REPLAYGAIN_ALBUM_GAIN", gainToString(item->info[Qmmp::REPLAYGAIN_ALBUM_GAIN]));
        tag->addValue("REPLAYGAIN_ALBUM_PEAK", peakToString(item->info[Qmmp::REPLAYGAIN_ALBUM_PEAK]));
    }
}

void RGScanDialog::writeID3v2Tag(TagLib::ID3v2::Tag *tag, ReplayGainInfoItem *item)
{
    tag->removeFrames("TXXX");
    if(m_ui.trackCheckBox->isChecked())
    {
        TagLib::ID3v2::UserTextIdentificationFrame *frame = new TagLib::ID3v2::UserTextIdentificationFrame();
        TagLib::StringList fields;
        fields.append("REPLAYGAIN_TRACK_GAIN");
        fields.append(gainToString(item->info[Qmmp::REPLAYGAIN_TRACK_GAIN]));
        frame->setText(fields);
        tag->addFrame(frame);

        fields.clear();
        frame = new TagLib::ID3v2::UserTextIdentificationFrame();
        fields.append("REPLAYGAIN_TRACK_PEAK");
        fields.append(peakToString(item->info[Qmmp::REPLAYGAIN_TRACK_PEAK]));
        frame->setText(fields);
        tag->addFrame(frame);
    }
    if(m_ui.albumCheckBox->isChecked())
    {
        TagLib::ID3v2::UserTextIdentificationFrame *frame = new TagLib::ID3v2::UserTextIdentificationFrame();
        TagLib::StringList fields;
        fields.append("REPLAYGAIN_ALBUM_GAIN");
        fields.append(gainToString(item->info[Qmmp::REPLAYGAIN_ALBUM_GAIN]));
        frame->setText(fields);
        tag->addFrame(frame);

        fields.clear();
        frame = new TagLib::ID3v2::UserTextIdentificationFrame();
        fields.append("REPLAYGAIN_ALBUM_PEAK");
        fields.append(peakToString(item->info[Qmmp::REPLAYGAIN_ALBUM_PEAK]));
        frame->setText(fields);
        tag->addFrame(frame);
    }
}

void RGScanDialog::writeVorbisComment(TagLib::Ogg::XiphComment *tag, ReplayGainInfoItem *item)
{
    if(m_ui.trackCheckBox->isChecked())
    {
        tag->addField("REPLAYGAIN_TRACK_GAIN", gainToString(item->info[Qmmp::REPLAYGAIN_TRACK_GAIN]));
        tag->addField("REPLAYGAIN_TRACK_PEAK", peakToString(item->info[Qmmp::REPLAYGAIN_TRACK_PEAK]));
    }
    if(m_ui.albumCheckBox->isChecked())
    {
        tag->addField("REPLAYGAIN_ALBUM_GAIN", gainToString(item->info[Qmmp::REPLAYGAIN_ALBUM_GAIN]));
        tag->addField("REPLAYGAIN_ALBUM_PEAK", peakToString(item->info[Qmmp::REPLAYGAIN_ALBUM_PEAK]));
    }
}

void RGScanDialog::on_writeButton_clicked()
{
    if(m_replayGainItemList.isEmpty())
        return;

    qDebug("RGScanDialog: writing ReplayGain values...");

    foreach (ReplayGainInfoItem *item, m_replayGainItemList)
    {
        QString ext = item->url.section(".", -1).toLower();

        if(ext == "mp3") //mpeg 1 layer 3
        {
            TagLib::MPEG::File file(qPrintable(item->url));
            writeAPETag(file.APETag(true), item);
            writeID3v2Tag(file.ID3v2Tag(true), item);
            file.save(TagLib::MPEG::File::APE | TagLib::MPEG::File::ID3v2, false);
        }
        else if(ext == "flac") //flac
        {
            TagLib::FLAC::File file(qPrintable(item->url));
            writeVorbisComment(file.xiphComment(true), item);
            file.save();
        }
        else if(ext == "oga") //ogg flac
        {
            TagLib::Ogg::FLAC::File file(qPrintable(item->url));
            writeVorbisComment(file.tag(), item);
            file.save();
        }
        else if(ext == "ogg") //ogg vorbis
        {
            TagLib::Ogg::Vorbis::File file(qPrintable(item->url));
            writeVorbisComment(file.tag(), item);
            file.save();
        }
        else if(ext == "wv") //wavpack
        {
            TagLib::WavPack::File file(qPrintable(item->url));
            writeAPETag(file.APETag(true), item);
            file.save();
        }
    }
}