#include "qmpvisrendercore.hpp" #include "qmppluginapistub.hpp" #include "qmpsettingsro.hpp" #include "qmpmidiplay.hpp" #include "qmpcorepublic.hpp" #include #include #ifdef _WIN32 #include #define dlopen(a,b) LoadLibraryW(a) #define dlsym(a,b) GetProcAddress((HMODULE)a,b) #define dlclose(a) FreeLibrary((HMODULE)a) #else #include #endif #include #include #include #include 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 libpath = { QCoreApplication::applicationDirPath().toStdWString() + L"/plugins/libvisualization.dll", L"libvisualization.dll", L"../libvisualization.dll"//for debugging only...? }; #else std::vector 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(dlsym(mp, "qmpPluginGetInterface")); SwitchMode_func switchmode = reinterpret_cast(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(px), static_cast(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::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 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); }