aboutsummaryrefslogblamecommitdiff
path: root/visualization/renderer/qmpvisrendercore.cpp
blob: 9d58206c9f3b7053df7404af3cc1b44523fd930a (plain) (tree)
1
2
3
4
5
6
7
8





                               
                    
                  


                                   

                                               
     
                  
      

                   
                             

                  
                                                   
 
                                                                                         
 





                                                                                                                                                                                                               

 
                                                 
 
             





                                                                                                 
     





                                                                                              
      























                                                                                                        



                                                   

                 

 

                                     













                                                                       

 

                                                   
                                



                                    
























































































                                                                                                                                                                       

 
                                                                                       
 


















































































                                                               

 
                                                                                   
 
                                                 
 
#include "qmpvisrendercore.hpp"
#include "qmppluginapistub.hpp"
#include "qmpsettingsro.hpp"
#include "qmpmidiplay.hpp"
#include "qmpcorepublic.hpp"

#include <algorithm>
#include <cassert>
#ifdef _WIN32
#include <windows.h>
#define dlopen(a,b) LoadLibraryW(a)
#define dlsym(a,b) GetProcAddress((HMODULE)a,b)
#define dlclose(a) FreeLibrary((HMODULE)a)
#else
#include <dlfcn.h>
#endif

#include <QProcess>
#include <QCommandLineParser>
#include <QDebug>
#include <QThread>
qmpVisRenderCore *qmpVisRenderCore::inst = nullptr;

qmpVisRenderCore::qmpVisRenderCore(QCommandLineParser *_clp): QObject(nullptr), clp(_clp)
{
    inst = this;
    player = new CMidiPlayer();
    api = new qmpPluginAPIStub(this);
    msettings = new qmpSettingsRO();
    frameno = 0;
    msettings->registerOptionEnumInt("MIDI", "Text encoding", "Midi/TextEncoding", {"Unicode", "Big5", "Big5-HKSCS", "CP949", "EUC-JP", "EUC-KR", "GB18030", "KOI8-R", "KOI8-U", "Macintosh", "Shift-JIS"}, 0);
}

bool qmpVisRenderCore::loadVisualizationLibrary()
{
#ifdef _WIN32
    std::vector<std::wstring> libpath =
    {
        QCoreApplication::applicationDirPath().toStdWString() + L"/plugins/libvisualization.dll",
        L"libvisualization.dll",
        L"../libvisualization.dll"//for debugging only...?
    };
#else
    std::vector<std::string> libpath =
    {
        QCoreApplication::applicationDirPath().toStdString() + "/plugins/libvisualization.so",
        QT_STRINGIFY(INSTALL_PREFIX) + std::string("/lib/qmidiplayer/libvisualization.so"),
        "../libvisualization.so"//for debugging only
    };
#endif
    for (auto &l : libpath)
    {
        mp = dlopen(l.c_str(), RTLD_LAZY);
        if (mp)
            break;
    }
    if (!mp)
    {
        fprintf(stderr, "failed to load the visualization module!\n");
        return false;
    }
    GetInterface_func getintf = reinterpret_cast<GetInterface_func>(dlsym(mp, "qmpPluginGetInterface"));
    SwitchMode_func switchmode = reinterpret_cast<SwitchMode_func>(dlsym(mp, "switchToRenderMode"));
    vf = nullptr;
    vp = getintf(api);
    switchmode(&qmpVisRenderCore::framefunc, !clp->isSet("show-window"));
    vp->init();
    resetcb(nullptr, nullptr);
    if (clp->isSet("list-options"))
    {
        msettings->listopt();
        exit(0);
    }
    return true;
}

void qmpVisRenderCore::unloadVisualizationLibrary()
{
    vp->deinit();
    dlclose(mp);
}

void qmpVisRenderCore::loadSettings()
{
    if (clp->isSet("config"))
        msettings->load(clp->value("config").toStdString().c_str());
    for (auto &o : clp->values("option"))
    {
        int sp = o.indexOf('=');
        if (!~sp)
        {
            qDebug("invalid option pair: %s", o.toStdString().c_str());
            continue;
        }
        QString key = o.left(sp);
        QString value = o.mid(sp + 1);
        msettings->setopt(key.toStdString(), value.toStdString());
    }
}

void qmpVisRenderCore::setMIDIFile(const char *url)
{
    player->playerLoadFile(url);
}

void qmpVisRenderCore::startRender()
{
    assert(vf);
    subst =
    {
        {'w', QString::number(msettings->getOptionInt("Visualization/wwidth"))},
        {'h', QString::number(msettings->getOptionInt("Visualization/wheight"))},
        {'r', QString::number(msettings->getOptionInt("Visualization/tfps"))},
        {
            'i',
            QStringList()
                    << "-f" << "rawvideo"
                        << "-pixel_format" << "rgba"
                        << "-video_size" << QString("%1x%2").arg(msettings->getOptionInt("Visualization/wwidth")).arg(msettings->getOptionInt("Visualization/wheight"))
                        << "-framerate" << QString::number(msettings->getOptionInt("Visualization/tfps"))
                        << "-i" << "pipe:"
        },
        {'o', clp->value("output-file")}
    };
    if (clp->value("receiver-execution") == "per-frame")
    {
        subst['o'] = clp->value("output-file").replace("%f", QString("%1").arg(frameno, 6, 10, QChar('0')));
        oneshot = false;
    }
    else
    {
        oneshot = true;
        if (clp->value("receiver-execution") != "one-shot")
            qWarning("Invalid value set for --receiver-execution. Using default value.");
    }
    rxproc = new QProcess();
    QStringList arguments = process_arguments(clp->value("receiver"), subst);
    assert(arguments.length() > 0);
    rxproc->setProgram(arguments.front());
    arguments.pop_front();
    rxproc->setArguments(arguments);
    frameconn = connect(this, &qmpVisRenderCore::frameRendered, this,
            [this](void *px, size_t sz, uint32_t c, uint32_t t)
    {
        if (sz)
        {
            if (!oneshot)
            {
                subst['f'] = QString("%1").arg(frameno, 6, 10, QChar('0'));
                subst['o'] = clp->value("output-file").replace("%f", QString("%1").arg(frameno, 6, 10, QChar('0')));
                frameno++;
                QStringList arguments = process_arguments(clp->value("receiver"), subst);
                arguments.pop_front();
                rxproc->setArguments(arguments);
                rxproc->start();
                rxproc->waitForStarted();
            }
            if (!rxproc->isOpen())
                return;
            rxproc->write(static_cast<const char *>(px), static_cast<qint64>(sz));
            while (rxproc->bytesToWrite() > (oneshot ? (1 << 26) : 0))
                rxproc->waitForBytesWritten();
            if (!oneshot)
            {
                rxproc->closeWriteChannel();
                rxproc->waitForFinished(-1);
            }
        }
        fprintf(stderr, "Rendered tick %u of %u, %.2f%% done.\r", c, t, std::min(100., 100.*c / t));
        if (c > t)
        {
            this->rxproc->closeWriteChannel();
            disconnect(frameconn);
            qApp->exit(0);
        }
    }, Qt::ConnectionType::BlockingQueuedConnection);
    connect(rxproc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
        [this](int x, QProcess::ExitStatus st)
    {
        qDebug("%s", this->rxproc->readAllStandardError().data());
        if (oneshot)
        {
            disconnect(frameconn);
            if (x || st == QProcess::ExitStatus::CrashExit)
                qApp->exit(1);
            else
                qApp->exit(0);
        }
    });
    QMetaObject::invokeMethod(this, [this]()
    {
        if (oneshot)
            rxproc->start();
        vf->show();
        startcb(nullptr, nullptr);
    }, Qt::ConnectionType::QueuedConnection);
}

QStringList qmpVisRenderCore::process_arguments(QString a, QMap<QChar, QVariant> subst)
{
    QStringList ret;
    QString buf;
    bool escaped = false;
    bool substi = false;
    for (int i = 0; i < a.length(); ++i)
    {
        if (a[i] == '%')
        {
            if (escaped)
            {
                buf += '%';
                escaped = false;
            }
            else if (substi)
            {
                buf += '%';
                substi = false;
            }
            else substi = true;
        }
        else if (a[i] == '\\')
        {
            if (substi)
            {
                buf += '%';
                substi = false;
            }
            if (escaped)
            {
                buf += '\\';
                escaped = false;
            }
            else escaped = true;
        }
        else if (a[i] == ' ')
        {
            if (substi)
            {
                buf += '%';
                substi = false;
            }
            if (escaped)
                buf += ' ';
            else
            {
                if (buf.length())
                    ret.append(buf);
                buf.clear();
            }
            escaped = false;
        }
        else
        {
            if (substi && subst.contains(a[i]))
            {
                if (subst[a[i]].canConvert(QMetaType::QString))
                    buf += subst[a[i]].toString();
                else
                {
                    if (buf.length())
                    {
                        ret.append(buf);
                        buf.clear();
                    }
                    for (auto &it : subst[a[i]].toStringList())
                        ret.append(it);
                }
                substi = false;
            }
            else
            {
                if (escaped)
                {
                    buf += '\\';
                    escaped = false;
                }
                buf += a[i];
            }
        }
    }
    if (buf.length())
        ret.append(buf);
    return ret;
}

void qmpVisRenderCore::framefunc(void *px, size_t sz, uint32_t curf, uint32_t totf)
{
    emit inst->frameRendered(px, sz, curf, totf);
}