improved compatibility with latest version of Yfitops
This commit is contained in:
parent
a6199d0fdd
commit
a5936281d7
|
@ -4,6 +4,8 @@
|
|||
#include <c++utilities/io/inifile.h>
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
|
||||
#include <QStringBuilder>
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
|
@ -23,12 +25,14 @@ FfmpegLauncher::FfmpegLauncher(PlayerWatcher &watcher, QObject *parent) :
|
|||
QObject(parent),
|
||||
m_watcher(watcher),
|
||||
m_sink(QStringLiteral("default")),
|
||||
m_inputOptions(),
|
||||
m_options(),
|
||||
m_targetDir(QStringLiteral(".")),
|
||||
m_targetExtension(QStringLiteral(".m4a")),
|
||||
m_ffmpeg(new QProcess(this))
|
||||
{
|
||||
connect(&watcher, &PlayerWatcher::nextSong, this, &FfmpegLauncher::nextSong);
|
||||
connect(&watcher, &PlayerWatcher::playbackStopped, this, &FfmpegLauncher::stopFfmpeg);
|
||||
connect(m_ffmpeg, &QProcess::started, this, &FfmpegLauncher::ffmpegStarted);
|
||||
connect(m_ffmpeg, static_cast<void(QProcess::*)(QProcess::ProcessError)>(&QProcess::error), this, &FfmpegLauncher::ffmpegError);
|
||||
connect(m_ffmpeg, static_cast<void(QProcess::*)(int)>(&QProcess::finished), this, &FfmpegLauncher::ffmpegFinished);
|
||||
|
@ -46,112 +50,145 @@ void addMetaData(QStringList &args, const QString &field, const QString &value)
|
|||
|
||||
void FfmpegLauncher::nextSong()
|
||||
{
|
||||
// skip ads
|
||||
if(m_watcher.isAd()) {
|
||||
return;
|
||||
}
|
||||
// pause player until ffmpeg has been started
|
||||
m_watcher.setSilent(true);
|
||||
if(m_watcher.isPlaying()) {
|
||||
m_watcher.pause();
|
||||
}
|
||||
// terminate/kill the current process
|
||||
stopFfmpeg();
|
||||
// determine output file, create target directory
|
||||
static const QString miscCategory(QStringLiteral("misc"));
|
||||
static const QString unknownTitle(QStringLiteral("unknown track"));
|
||||
const auto targetDirPath = QStringLiteral("%1/%2").arg(m_watcher.artist().isEmpty() ? miscCategory : m_watcher.artist(), m_watcher.artist().isEmpty() ? miscCategory : m_watcher.album());
|
||||
if(!m_targetDir.mkpath(targetDirPath)) {
|
||||
cerr << "Error: Can not create target directory: " << targetDirPath << endl;
|
||||
return;
|
||||
}
|
||||
QDir targetDir(m_targetDir);
|
||||
targetDir.cd(targetDirPath);
|
||||
// determine track number
|
||||
QString number, length, year, genre, totalTracks, totalDisks;
|
||||
if(m_watcher.trackNumber()) {
|
||||
if(m_watcher.diskNumber()) {
|
||||
number = QStringLiteral("%2-%1").arg(m_watcher.trackNumber(), 2, 10, QLatin1Char('0')).arg(m_watcher.diskNumber());
|
||||
} else {
|
||||
number = QStringLiteral("%1").arg(m_watcher.trackNumber(), 2, 10, QLatin1Char('0'));
|
||||
}
|
||||
}
|
||||
if(!number.isEmpty()) {
|
||||
number.append(QStringLiteral(" - "));
|
||||
}
|
||||
// read additional meta info
|
||||
// - from an INI file called info.ini in the album directory (must be created before recording)
|
||||
// - track lengths might be specified for each track in the [length] section (useful to get rid of advertisements at the end)
|
||||
// - year, genre, total_tracks and total_disks might be specified in the [general] section
|
||||
if(targetDir.exists(QStringLiteral("info.ini"))) {
|
||||
fstream infoFile;
|
||||
infoFile.exceptions(ios_base::badbit | ios_base::failbit);
|
||||
try {
|
||||
infoFile.open((targetDir.path() + QStringLiteral("/info.ini")).toLocal8Bit().data(), ios_base::in);
|
||||
IniFile infoIni;
|
||||
infoIni.parse(infoFile);
|
||||
for(auto &scope : infoIni.data()) {
|
||||
if(scope.first == "length") {
|
||||
if(m_watcher.trackNumber()) {
|
||||
// reading length scope is only possible if track number known because the track number is used for mapping
|
||||
for(const auto &entry : scope.second) {
|
||||
try {
|
||||
if(stringToNumber<unsigned int>(entry.first) == m_watcher.trackNumber()) {
|
||||
// length entry for this track
|
||||
length = QString::fromLocal8Bit(entry.second.data());
|
||||
break;
|
||||
}
|
||||
} catch(const ConversionException &) {
|
||||
cerr << "Warning: Ignoring non-numeric key \"" << entry.first << "\" in [length] section of info.ini." << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(scope.first == "general") {
|
||||
for(const auto &entry : scope.second) {
|
||||
if(entry.first == "year") {
|
||||
year = QString::fromLocal8Bit(entry.second.data());
|
||||
} else if(entry.first == "genre") {
|
||||
genre = QString::fromLocal8Bit(entry.second.data());
|
||||
} else if(entry.first == "total_tracks") {
|
||||
totalTracks = QString::fromLocal8Bit(entry.second.data());
|
||||
} else if(entry.first == "total_disks") {
|
||||
totalDisks = QString::fromLocal8Bit(entry.second.data());
|
||||
} else {
|
||||
cerr << "Warning: Ignoring unknown property \"" << entry.first << "\" in [general] section of info.ini." << endl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cerr << "Warning: Ignoring unknown section [" << scope.first << "] in info.ini." << endl;
|
||||
}
|
||||
}
|
||||
} catch(const ios_base::failure &) {
|
||||
cerr << "Warning: Can't parse info.ini because an IO error occured." << endl;
|
||||
}
|
||||
}
|
||||
// determine target name/path
|
||||
QString targetName(QStringLiteral("%3%1%2").arg(m_watcher.title().isEmpty() ? unknownTitle : m_watcher.title(), m_targetExtension, number));
|
||||
unsigned int count = 1;
|
||||
while(targetDir.exists(targetName)) {
|
||||
++count;
|
||||
targetName = QStringLiteral("%3%1 (%4)%2").arg(m_watcher.title().isEmpty() ? unknownTitle : m_watcher.title(), m_targetExtension, number).arg(count);
|
||||
}
|
||||
auto targetPath = targetDir.absoluteFilePath(targetName);
|
||||
// set input device
|
||||
QStringList args;
|
||||
args << QStringLiteral("-f");
|
||||
args << QStringLiteral("pulse");
|
||||
args << m_inputOptions;
|
||||
args << QStringLiteral("-i");
|
||||
args << m_sink;
|
||||
// set length if specified in info.ini
|
||||
if(!length.isEmpty() || !m_watcher.length().isNull()) {
|
||||
args << "-t";
|
||||
args << (length.isEmpty() ? QString::number(m_watcher.length().totalSeconds()) : length);
|
||||
}
|
||||
// set additional options
|
||||
args << m_options;
|
||||
// set meta data
|
||||
addMetaData(args, QStringLiteral("title"), m_watcher.title());
|
||||
addMetaData(args, QStringLiteral("album"), m_watcher.album());
|
||||
addMetaData(args, QStringLiteral("artist"), m_watcher.artist());
|
||||
addMetaData(args, QStringLiteral("genre"), genre.isEmpty() ? m_watcher.genre() : genre);
|
||||
addMetaData(args, QStringLiteral("year"), year.isEmpty() ? m_watcher.year() : year);
|
||||
if(m_watcher.trackNumber()) {
|
||||
addMetaData(args, QStringLiteral("track"), totalTracks.isEmpty() ? QString::number(m_watcher.trackNumber()) : QString::number(m_watcher.trackNumber()) % QChar('/') % totalTracks);
|
||||
}
|
||||
if(m_watcher.diskNumber()) {
|
||||
addMetaData(args, QStringLiteral("disk"), totalDisks.isEmpty() ? QString::number(m_watcher.diskNumber()) : QString::number(m_watcher.diskNumber()) % QChar('/') % totalDisks);
|
||||
}
|
||||
// set output file
|
||||
args << targetPath;
|
||||
m_ffmpeg->setArguments(args);
|
||||
// start process
|
||||
m_ffmpeg->start();
|
||||
// resume player
|
||||
m_watcher.play();
|
||||
m_watcher.setSilent(false);
|
||||
}
|
||||
|
||||
void FfmpegLauncher::stopFfmpeg()
|
||||
{
|
||||
if(m_ffmpeg->state() != QProcess::NotRunning) {
|
||||
m_ffmpeg->terminate();
|
||||
m_ffmpeg->waitForFinished(250);
|
||||
m_ffmpeg->waitForFinished(10000);
|
||||
if(m_ffmpeg->state() != QProcess::NotRunning) {
|
||||
m_ffmpeg->kill();
|
||||
m_ffmpeg->waitForFinished(250);
|
||||
m_ffmpeg->waitForFinished(5000);
|
||||
if(m_ffmpeg->state() != QProcess::NotRunning) {
|
||||
throw runtime_error("Unable to terminate/kill ffmpeg process.");
|
||||
}
|
||||
}
|
||||
}
|
||||
if(m_watcher.isPlaying()) {
|
||||
// determine output file, create target directory
|
||||
static const QString miscCategory(QStringLiteral("misc"));
|
||||
static const QString unknownTitle(QStringLiteral("unknown track"));
|
||||
const auto targetDirPath = QStringLiteral("%1/%2").arg(m_watcher.artist().isEmpty() ? miscCategory : m_watcher.artist(), m_watcher.artist().isEmpty() ? miscCategory : m_watcher.album());
|
||||
if(!m_targetDir.mkpath(targetDirPath)) {
|
||||
cerr << "Error: Can not create target directory: " << targetDirPath << endl;
|
||||
return;
|
||||
}
|
||||
QDir targetDir(m_targetDir);
|
||||
targetDir.cd(targetDirPath);
|
||||
// determine track number
|
||||
QString number, length;
|
||||
if(m_watcher.trackNumber()) {
|
||||
if(m_watcher.diskNumber()) {
|
||||
number = QStringLiteral("%2-%1").arg(m_watcher.trackNumber(), 2, 10, QLatin1Char('0')).arg(m_watcher.diskNumber());
|
||||
} else {
|
||||
number = QStringLiteral("%1").arg(m_watcher.trackNumber(), 2, 10, QLatin1Char('0'));
|
||||
}
|
||||
}
|
||||
if(!number.isEmpty()) {
|
||||
number.append(QStringLiteral(" - "));
|
||||
}
|
||||
// determine additional info
|
||||
// - from a file called info.ini in the album directory
|
||||
// - currently only track length is supported (used to get rid of advertisements at the end)
|
||||
if(targetDir.exists(QStringLiteral("info.ini"))) {
|
||||
fstream infoFile;
|
||||
infoFile.exceptions(ios_base::badbit | ios_base::failbit);
|
||||
try {
|
||||
infoFile.open((targetDir.path() + QStringLiteral("/info.ini")).toLocal8Bit().data(), ios_base::in);
|
||||
IniFile infoIni;
|
||||
infoIni.parse(infoFile);
|
||||
// read length scope, only possible if track number known because the track number is used for mapping
|
||||
if(m_watcher.trackNumber()) {
|
||||
for(auto &scope : infoIni.data()) {
|
||||
if(scope.first == "length") {
|
||||
for(const auto &entry : scope.second) {
|
||||
try {
|
||||
if(stringToNumber<unsigned int>(entry.first) == m_watcher.trackNumber()) {
|
||||
// length entry for this track
|
||||
length = QString::fromLocal8Bit(entry.second.data());
|
||||
break;
|
||||
}
|
||||
} catch( const ConversionException &) {
|
||||
cerr << "Warning: Ignoring non-numeric key \"" << entry.first << "\" in info.ini." << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(const ios_base::failure &) {
|
||||
cerr << "Warning: Can't parse info.ini because an IO error occured." << endl;
|
||||
}
|
||||
}
|
||||
// determine target name/path
|
||||
QString targetName(QStringLiteral("%3%1%2").arg(m_watcher.title().isEmpty() ? unknownTitle : m_watcher.title(), m_targetExtension, number));
|
||||
unsigned int count = 1;
|
||||
while(targetDir.exists(targetName)) {
|
||||
++count;
|
||||
targetName = QStringLiteral("%3%1 (%4)%2").arg(m_watcher.title().isEmpty() ? unknownTitle : m_watcher.title(), m_targetExtension, number).arg(count);
|
||||
}
|
||||
auto targetPath = targetDir.absoluteFilePath(targetName);
|
||||
// set input device
|
||||
QStringList args;
|
||||
args << QStringLiteral("-f");
|
||||
args << QStringLiteral("pulse");
|
||||
args << QStringLiteral("-i");
|
||||
args << m_sink;
|
||||
// set length
|
||||
if(!length.isEmpty()) {
|
||||
args << "-t";
|
||||
args << length;
|
||||
}
|
||||
// set additional options
|
||||
args << m_options;
|
||||
// set meta data
|
||||
addMetaData(args, QStringLiteral("title"), m_watcher.title());
|
||||
addMetaData(args, QStringLiteral("album"), m_watcher.album());
|
||||
addMetaData(args, QStringLiteral("artist"), m_watcher.artist());
|
||||
addMetaData(args, QStringLiteral("genre"), m_watcher.genre());
|
||||
addMetaData(args, QStringLiteral("year"), m_watcher.year());
|
||||
if(m_watcher.trackNumber()) {
|
||||
addMetaData(args, QStringLiteral("track"), QString::number(m_watcher.trackNumber()));
|
||||
}
|
||||
if(m_watcher.diskNumber()) {
|
||||
addMetaData(args, QStringLiteral("disk"), QString::number(m_watcher.diskNumber()));
|
||||
}
|
||||
// set output file
|
||||
args << targetPath;
|
||||
m_ffmpeg->setArguments(args);
|
||||
// start process
|
||||
m_ffmpeg->start();
|
||||
}
|
||||
}
|
||||
|
||||
void FfmpegLauncher::ffmpegStarted()
|
||||
|
|
|
@ -16,13 +16,15 @@ public:
|
|||
explicit FfmpegLauncher(PlayerWatcher &watcher, QObject *parent = nullptr);
|
||||
|
||||
void setSink(const QString &sinkName);
|
||||
void setFfmpegBinary(const QString &path);
|
||||
void setFfmpegOptions(const QString &options);
|
||||
void setFFmpegInputOptions(const QString &options);
|
||||
void setFFmpegBinary(const QString &path);
|
||||
void setFFmpegOptions(const QString &options);
|
||||
void setTargetDir(const QString &path);
|
||||
void setTargetExtension(const QString &extension);
|
||||
|
||||
private slots:
|
||||
void nextSong();
|
||||
void stopFfmpeg();
|
||||
void ffmpegStarted();
|
||||
void ffmpegError();
|
||||
void ffmpegFinished(int exitCode);
|
||||
|
@ -30,6 +32,7 @@ private slots:
|
|||
private:
|
||||
PlayerWatcher &m_watcher;
|
||||
QString m_sink;
|
||||
QStringList m_inputOptions;
|
||||
QStringList m_options;
|
||||
QDir m_targetDir;
|
||||
QString m_targetExtension;
|
||||
|
@ -41,12 +44,17 @@ inline void FfmpegLauncher::setSink(const QString &sinkName)
|
|||
m_sink = sinkName;
|
||||
}
|
||||
|
||||
inline void FfmpegLauncher::setFfmpegBinary(const QString &path)
|
||||
inline void FfmpegLauncher::setFFmpegInputOptions(const QString &options)
|
||||
{
|
||||
m_inputOptions = options.split(QChar(' '), QString::SkipEmptyParts);
|
||||
}
|
||||
|
||||
inline void FfmpegLauncher::setFFmpegBinary(const QString &path)
|
||||
{
|
||||
m_ffmpeg->setProgram(path);
|
||||
}
|
||||
|
||||
inline void FfmpegLauncher::setFfmpegOptions(const QString &options)
|
||||
inline void FfmpegLauncher::setFFmpegOptions(const QString &options)
|
||||
{
|
||||
m_options = options.split(QChar(' '), QString::SkipEmptyParts);
|
||||
}
|
||||
|
|
18
main.cpp
18
main.cpp
|
@ -28,6 +28,9 @@ int main(int argc, char *argv[])
|
|||
sinkArg.setValueNames({"sink"});
|
||||
sinkArg.setRequiredValueCount(1);
|
||||
sinkArg.setCombinable(true);
|
||||
Argument ffmpegInputOptions("ffmpeg-input-options", "i", "specifies options for the ffmpeg input device");
|
||||
ffmpegInputOptions.setRequiredValueCount(1);
|
||||
ffmpegInputOptions.setCombinable(true);
|
||||
Argument targetDirArg("target-dir", "t", "specifies the target directory");
|
||||
targetDirArg.setValueNames({"path"});
|
||||
targetDirArg.setRequiredValueCount(1);
|
||||
|
@ -36,6 +39,8 @@ int main(int argc, char *argv[])
|
|||
targetExtArg.setValueNames({"extension"});
|
||||
targetExtArg.setRequiredValueCount(1);
|
||||
targetExtArg.setCombinable(true);
|
||||
Argument ignorePlaybackStatusArg("ignore-playback-status", string(), "ignores the playback status (does not call PlaybackStatus())");
|
||||
ignorePlaybackStatusArg.setCombinable(true);
|
||||
Argument ffmpegBinArg("ffmpeg-bin", "f", "specifies the path to the ffmpeg binary");
|
||||
ffmpegBinArg.setValueNames({"path"});
|
||||
ffmpegBinArg.setRequiredValueCount(1);
|
||||
|
@ -44,7 +49,7 @@ int main(int argc, char *argv[])
|
|||
ffmpegOptions.setValueNames({"options"});
|
||||
ffmpegOptions.setRequiredValueCount(1);
|
||||
ffmpegOptions.setCombinable(true);
|
||||
recordArg.setSecondaryArguments({&applicationArg, &sinkArg, &targetDirArg, &targetExtArg, &ffmpegBinArg, &ffmpegOptions});
|
||||
recordArg.setSecondaryArguments({&applicationArg, &sinkArg, &ffmpegInputOptions, &targetDirArg, &targetExtArg, &ignorePlaybackStatusArg, &ffmpegBinArg, &ffmpegOptions});
|
||||
parser.setMainArguments({&recordArg, &helpArg});
|
||||
parser.setIgnoreUnknownArguments(false);
|
||||
// parse command line arguments
|
||||
|
@ -60,17 +65,20 @@ int main(int argc, char *argv[])
|
|||
cerr << "Watching MPRIS service of the specified application \"" << applicationArg.values().front() << "\" ..." << endl;
|
||||
// create app loop, player watcher and ffmpeg launcher
|
||||
QCoreApplication app(argc, argv);
|
||||
PlayerWatcher watcher(QString::fromLocal8Bit(applicationArg.values().front().data()));
|
||||
PlayerWatcher watcher(QString::fromLocal8Bit(applicationArg.values().front().data()), ignorePlaybackStatusArg.isPresent());
|
||||
FfmpegLauncher ffmpeg(watcher);
|
||||
// pass specified args to ffmpeg launcher
|
||||
if(sinkArg.isPresent()) {
|
||||
ffmpeg.setSink(QString::fromLocal8Bit(sinkArg.values().front().data()));
|
||||
}
|
||||
if(ffmpegInputOptions.isPresent()) {
|
||||
ffmpeg.setFFmpegInputOptions(QString::fromLocal8Bit(ffmpegInputOptions.values().front().data()));
|
||||
}
|
||||
if(ffmpegBinArg.isPresent()) {
|
||||
ffmpeg.setFfmpegBinary(QString::fromLocal8Bit(ffmpegBinArg.values().front().data()));
|
||||
ffmpeg.setFFmpegBinary(QString::fromLocal8Bit(ffmpegBinArg.values().front().data()));
|
||||
}
|
||||
if(ffmpegOptions.isPresent()) {
|
||||
ffmpeg.setFfmpegOptions(QString::fromLocal8Bit(ffmpegOptions.values().front().data()));
|
||||
ffmpeg.setFFmpegOptions(QString::fromLocal8Bit(ffmpegOptions.values().front().data()));
|
||||
}
|
||||
if(targetDirArg.isPresent()) {
|
||||
ffmpeg.setTargetDir(QString::fromLocal8Bit(targetDirArg.values().front().data()));
|
||||
|
@ -80,7 +88,7 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
// enter app loop
|
||||
return app.exec();
|
||||
} else {
|
||||
} else if(!helpArg.isPresent()) {
|
||||
cerr << "No operation specified." << endl;
|
||||
return 2;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace ChronoUtilities;
|
||||
|
||||
namespace DBusSoundRecorder {
|
||||
|
||||
|
@ -18,18 +19,30 @@ inline ostream &operator <<(ostream &stream, const QString &str)
|
|||
return stream;
|
||||
}
|
||||
|
||||
PlayerWatcher::PlayerWatcher(const QString &appName, QObject *parent) :
|
||||
PlayerWatcher::PlayerWatcher(const QString &appName, bool ignorePlaybackStatus, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_service(QStringLiteral("org.mpris.MediaPlayer2.%1").arg(appName)),
|
||||
m_serviceWatcher(new QDBusServiceWatcher(m_service, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this)),
|
||||
m_propertiesInterface(new OrgFreedesktopDBusPropertiesInterface(m_service, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus(), this)),
|
||||
m_playerInterface(new OrgMprisMediaPlayer2PlayerInterface(m_service, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus(), this)),
|
||||
m_mediaPlayerInterfaceName(QStringLiteral("org.mpris.MediaPlayer2.%1").arg(appName)),
|
||||
m_mediaPlayerServiceWatcher(new QDBusServiceWatcher(m_mediaPlayerInterfaceName, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this)),
|
||||
m_propertiesInterface(new OrgFreedesktopDBusPropertiesInterface(m_mediaPlayerInterfaceName, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus(), this)),
|
||||
m_playerInterface(new OrgMprisMediaPlayer2PlayerInterface(m_mediaPlayerInterfaceName, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus(), this)),
|
||||
m_isPlaying(false),
|
||||
m_isAd(false),
|
||||
m_trackNumber(0),
|
||||
m_diskNumber(0)
|
||||
m_diskNumber(0),
|
||||
m_silent(false),
|
||||
m_ignorePlaybackStatus(ignorePlaybackStatus)
|
||||
{
|
||||
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &PlayerWatcher::serviceOwnerChanged);
|
||||
connect(m_propertiesInterface, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &PlayerWatcher::propertiesChanged);
|
||||
if(!connect(m_mediaPlayerServiceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &PlayerWatcher::serviceOwnerChanged)) {
|
||||
cout << "Warning: Unable to connect \"serviceOwnerChanged\" signal of service watcher." << endl;
|
||||
}
|
||||
// The code below does not work anymore with the newest version of Spotify.
|
||||
//if(!connect(m_propertiesInterface, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &PlayerWatcher::propertiesChanged)) {
|
||||
// cout << "Warning: Unable to connect \"PropertiesChanged\" signal of properties interface." << endl;
|
||||
//}
|
||||
// However, the following works:
|
||||
if(!QDBusConnection::sessionBus().connect(m_mediaPlayerInterfaceName, QStringLiteral("/org/mpris/MediaPlayer2"), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"), this, SLOT(propertiesChanged()))) {
|
||||
cout << "Warning: Unable to connect \"PropertiesChanged\" signal of properties interface." << endl;
|
||||
}
|
||||
propertiesChanged();
|
||||
}
|
||||
|
||||
|
@ -66,15 +79,22 @@ void PlayerWatcher::serviceOwnerChanged(const QString &service, const QString &o
|
|||
void PlayerWatcher::propertiesChanged()
|
||||
{
|
||||
// get meta data
|
||||
if(!m_playerInterface->playbackStatus().compare(QLatin1String("playing"), Qt::CaseInsensitive)) {
|
||||
QVariantMap metadata = m_playerInterface->metadata();
|
||||
m_isAd = metadata.value(QStringLiteral("mpris:trackid")).toString().startsWith(QLatin1String("spotify:ad"));
|
||||
QString title = metadata.value(QStringLiteral("xesam:title")).toString();
|
||||
QString album = metadata.value(QStringLiteral("xesam:album")).toString();
|
||||
QString artist = metadata.value(QStringLiteral("xesam:artist")).toString();
|
||||
bool isPlaying;
|
||||
if(m_ignorePlaybackStatus) {
|
||||
// determine playback status by checking whether there is a song title
|
||||
isPlaying = !title.isEmpty();
|
||||
} else {
|
||||
isPlaying = !m_playerInterface->playbackStatus().compare(QLatin1String("playing"), Qt::CaseInsensitive);
|
||||
}
|
||||
if(isPlaying) {
|
||||
if(!m_isPlaying) {
|
||||
m_isPlaying = true;
|
||||
cerr << "Playback started" << endl;
|
||||
}
|
||||
QVariantMap metadata = m_playerInterface->metadata();
|
||||
QString title = metadata.value(QStringLiteral("xesam:title")).toString();
|
||||
QString album = metadata.value(QStringLiteral("xesam:album")).toString();
|
||||
QString artist = metadata.value(QStringLiteral("xesam:artist")).toString();
|
||||
// use title, album and artist to identify song
|
||||
if(m_title != title || m_album != album || m_artist != artist) {
|
||||
// next song playing
|
||||
|
@ -92,23 +112,37 @@ void PlayerWatcher::propertiesChanged()
|
|||
if(!m_diskNumber) {
|
||||
m_diskNumber = metadata.value(QStringLiteral("xesam:discNumber")).toUInt();
|
||||
}
|
||||
m_length = metadata.value(QStringLiteral("xesam:length")).toULongLong();
|
||||
m_length = TimeSpan(metadata.value(QStringLiteral("mpris:length")).toULongLong() * 10);
|
||||
// notify
|
||||
cerr << "Next song: " << m_title << endl;
|
||||
if(!m_isPlaying) {
|
||||
if(!m_isPlaying && !m_silent) {
|
||||
m_isPlaying = true;
|
||||
emit playbackStarted();
|
||||
}
|
||||
emit nextSong();
|
||||
if(!m_silent) {
|
||||
m_isPlaying = true;
|
||||
emit nextSong();
|
||||
}
|
||||
} else if(!m_isPlaying) {
|
||||
emit playbackStarted();
|
||||
if(!m_silent) {
|
||||
m_isPlaying = true;
|
||||
emit playbackStarted();
|
||||
}
|
||||
}
|
||||
} else if(m_isPlaying) {
|
||||
m_isPlaying = false;
|
||||
cerr << "Playback stopped" << endl;
|
||||
emit playbackStopped();
|
||||
if(!m_silent) {
|
||||
emit playbackStopped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerWatcher::notificationReceived()
|
||||
{
|
||||
cout << "It works!" << endl;
|
||||
}
|
||||
|
||||
void PlayerWatcher::seeked(qlonglong pos)
|
||||
{
|
||||
cerr << "Seeked: " << pos << endl;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef PLAYERWATCHER_H
|
||||
#define PLAYERWATCHER_H
|
||||
|
||||
#include <c++utilities/chrono/timespan.h>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QDBusServiceWatcher)
|
||||
|
@ -14,7 +16,7 @@ class PlayerWatcher : public QObject
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PlayerWatcher(const QString &appName, QObject *parent = nullptr);
|
||||
explicit PlayerWatcher(const QString &appName, bool ignorePlaybackStatus = false, QObject *parent = nullptr);
|
||||
|
||||
void play();
|
||||
void stop();
|
||||
|
@ -22,6 +24,8 @@ public:
|
|||
void playPause();
|
||||
|
||||
bool isPlaying() const;
|
||||
bool isPlaybackStatusIgnored() const;
|
||||
bool isAd() const;
|
||||
const QString &title() const;
|
||||
const QString &album() const;
|
||||
const QString &artist() const;
|
||||
|
@ -29,7 +33,8 @@ public:
|
|||
const QString &genre() const;
|
||||
unsigned int trackNumber() const;
|
||||
unsigned int diskNumber() const;
|
||||
unsigned long long length() const;
|
||||
ChronoUtilities::TimeSpan length() const;
|
||||
void setSilent(bool silent);
|
||||
|
||||
signals:
|
||||
void nextSong();
|
||||
|
@ -39,14 +44,18 @@ signals:
|
|||
private slots:
|
||||
void serviceOwnerChanged(const QString &service, const QString &oldOwner, const QString &newOwner);
|
||||
void propertiesChanged();
|
||||
void notificationReceived();
|
||||
void seeked(qlonglong pos);
|
||||
|
||||
private:
|
||||
QString m_service;
|
||||
QDBusServiceWatcher *m_serviceWatcher;
|
||||
QString m_mediaPlayerInterfaceName;
|
||||
QDBusServiceWatcher *m_mediaPlayerServiceWatcher;
|
||||
QString m_notifyInterfaceName;
|
||||
QDBusServiceWatcher *m_notifyServiceWatcher;
|
||||
OrgFreedesktopDBusPropertiesInterface *m_propertiesInterface;
|
||||
OrgMprisMediaPlayer2PlayerInterface *m_playerInterface;
|
||||
bool m_isPlaying;
|
||||
bool m_isAd;
|
||||
QString m_title;
|
||||
QString m_album;
|
||||
QString m_artist;
|
||||
|
@ -54,7 +63,9 @@ private:
|
|||
QString m_genre;
|
||||
unsigned int m_trackNumber;
|
||||
unsigned int m_diskNumber;
|
||||
unsigned long long m_length;
|
||||
ChronoUtilities::TimeSpan m_length;
|
||||
bool m_silent;
|
||||
bool m_ignorePlaybackStatus;
|
||||
};
|
||||
|
||||
inline bool PlayerWatcher::isPlaying() const
|
||||
|
@ -62,6 +73,16 @@ inline bool PlayerWatcher::isPlaying() const
|
|||
return m_isPlaying;
|
||||
}
|
||||
|
||||
inline bool PlayerWatcher::isPlaybackStatusIgnored() const
|
||||
{
|
||||
return m_ignorePlaybackStatus;
|
||||
}
|
||||
|
||||
inline bool PlayerWatcher::isAd() const
|
||||
{
|
||||
return m_isAd;
|
||||
}
|
||||
|
||||
inline const QString &PlayerWatcher::title() const
|
||||
{
|
||||
return m_title;
|
||||
|
@ -97,11 +118,16 @@ inline unsigned int PlayerWatcher::diskNumber() const
|
|||
return m_diskNumber;
|
||||
}
|
||||
|
||||
inline unsigned long long PlayerWatcher::length() const
|
||||
inline ChronoUtilities::TimeSpan PlayerWatcher::length() const
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
|
||||
inline void PlayerWatcher::setSilent(bool silent)
|
||||
{
|
||||
m_silent = silent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // PLAYERWATCHER_H
|
||||
|
|
Loading…
Reference in New Issue