From 99bccf6281f902236948c9aa899509453c818130 Mon Sep 17 00:00:00 2001 From: "craig.p.drummond" Date: Tue, 22 Oct 2013 18:22:41 +0000 Subject: [PATCH] Read/write tags in an external app - as per Clementine and Amarok. Isolates Cantata from TagLib crashes. --- CMakeLists.txt | 32 ++-- ChangeLog | 2 + INSTALL | 6 + config.h.cmake | 1 + context/songview.cpp | 4 +- devices/actiondialog.cpp | 4 +- devices/device.cpp | 10 +- devices/extractjob.cpp | 4 +- devices/fsdevice.cpp | 4 +- gui/covers.cpp | 6 +- gui/mainwindow.cpp | 14 +- http/httpserver.cpp | 6 +- http/httpsocket.cpp | 2 +- mpd/mpdparseutils.cpp | 24 --- mpd/mpdparseutils.h | 1 - mpd/song.cpp | 32 +++- mpd/song.h | 8 + replaygain/rgdialog.cpp | 17 +- replaygain/tagreader.cpp | 4 +- support/utils.cpp | 25 +++ support/utils.h | 1 + tags/CMakeLists.txt | 38 ++++ tags/main.cpp | 36 ++++ tags/tagclient.cpp | 365 +++++++++++++++++++++++++++++++++++++++ tags/tagclient.h | 91 ++++++++++ tags/tageditor.cpp | 12 +- tags/tags.cpp | 48 ++--- tags/tags.h | 33 +++- tags/tagserver.cpp | 93 ++++++++++ tags/tagserver.h | 48 +++++ 30 files changed, 864 insertions(+), 107 deletions(-) create mode 100644 tags/CMakeLists.txt create mode 100644 tags/main.cpp create mode 100644 tags/tagclient.cpp create mode 100644 tags/tagclient.h create mode 100644 tags/tagserver.cpp create mode 100644 tags/tagserver.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 685a9f0e0..3d1368d23 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ option(ENABLE_CDPARANOIA "Enable CDParanoia libraries(required for AudioCD suppo option(ENABLE_CDDB "Enable CDDB libraries(either this or MusicBrianz required for AudioCD support)" ON) option(ENABLE_MUSICBRAINZ "Enable MusicBrianz libraries(either this or CDDB required for AudioCD support)" ON) option(ENABLE_PROXY_CONFIG "Enable proxy config in settings dialog" OFF) +option(ENABLE_EXTERNAL_TAGS "Enable usage of external app for reading/writing tags" ON) if (ENABLE_QT5) set(ENABLE_KDE FALSE) @@ -126,7 +127,8 @@ if (ENABLE_QT5) find_package(Qt5Network REQUIRED) find_package(Qt5Concurrent REQUIRED) set(QTCORELIBS ${Qt5Core_LIBRARIES}) - set(QTLIBS ${QTCORELIBS} ${Qt5Widgets_LIBRARIES} ${Qt5Network_LIBRARIES} ${Qt5Xml_LIBRARIES} ${Qt5Concurrent_LIBRARIES}) + set(QTNETWORKLIBS ${Qt5Network_LIBRARIES}) + set(QTLIBS ${QTCORELIBS} ${Qt5Widgets_LIBRARIES} ${QTNETWORKLIBS} ${Qt5Xml_LIBRARIES} ${Qt5Concurrent_LIBRARIES}) set(QTINCLUDES ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ${Qt5Xml_INCLUDE_DIRS} ${Qt5Core_INCLUDE_DIRS} ${Qt5Concurrent_INCLUDE_DIRS}) add_definitions(${Qt5Widgets_DEFINITIONS} ${Qt5Network_DEFINITIONS} ${Qt5Xml_DEFINITIONS} ${Qt5Concurrent_DEFINITIONS}) set(CMAKE_CXX_FLAGS "${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") @@ -140,7 +142,8 @@ if (ENABLE_QT5) else (ENABLE_QT5) find_package(Qt4 REQUIRED QtCore QtGui QtXml QtNetwork) set(QTCORELIBS ${QT_QTCORE_LIBRARY}) - set(QTLIBS ${QT_QTSVG_LIBRARY} ${QT_QTXML_LIBRARY} ${QTCORELIBS} ${QT_QTGUI_LIBRARY} ${QT_QTNETWORK_LIBRARY}) + set(QTNETWORKLIBS ${QT_QTNETWORK_LIBRARY}) + set(QTLIBS ${QT_QTSVG_LIBRARY} ${QT_QTXML_LIBRARY} ${QTCORELIBS} ${QT_QTGUI_LIBRARY} ${QTNETWORKLIBS}) if (QT_QTDBUS_FOUND) set(QTLIBS ${QTLIBS} ${QT_QTDBUS_LIBRARY}) endif (QT_QTDBUS_FOUND) @@ -307,10 +310,10 @@ set(CANTATA_RCS cantata.qrc) if (TAGLIB_FOUND) set(CANTATA_SRCS ${CANTATA_SRCS} - tags/tageditor.cpp tags/trackorganiser.cpp tags/tags.cpp + tags/tageditor.cpp tags/trackorganiser.cpp tags/tagclient.cpp devices/filenameschemedialog.cpp) set(CANTATA_MOC_HDRS ${CANTATA_MOC_HDRS} - tags/tageditor.h tags/trackorganiser.h + tags/tageditor.h tags/trackorganiser.h tags/tagclient.h devices/filenameschemedialog.h devices/device.h) set(CANTATA_UIS ${CANTATA_UIS} tags/tageditor.ui tags/trackorganiser.ui @@ -324,9 +327,14 @@ if (TAGLIB_FOUND) set(ENABLE_REPLAYGAIN_SUPPORT 1) add_subdirectory(replaygain) endif (FFMPEG_FOUND OR MPG123_FOUND) + if (ENABLE_EXTERNAL_TAGS) + add_subdirectory(tags) + else (ENABLE_EXTERNAL_TAGS) + set(CANTATA_SRCS ${CANTATA_SRCS} tags/tags.cpp tags/filetyperesolver.cpp) + endif (ENABLE_EXTERNAL_TAGS) if (WIN32 OR APPLE) - set(CANTATA_SRCS ${CANTATA_SRCS} devices/device.cpp tags/filetyperesolver.cpp) + set(CANTATA_SRCS ${CANTATA_SRCS} devices/device.cpp) else (WIN32 OR APPLE) set(ENABLE_DEVICES_SUPPORT 1) if (NOT ENABLE_KDE) @@ -354,7 +362,7 @@ if (TAGLIB_FOUND) set(CANTATA_SRCS ${CANTATA_SRCS} devices/devicespage.cpp devices/filejob.cpp devices/device.cpp devices/fsdevice.cpp devices/umsdevice.cpp - models/devicesmodel.cpp tags/filetyperesolver.cpp devices/actiondialog.cpp devices/devicepropertieswidget.cpp + models/devicesmodel.cpp devices/actiondialog.cpp devices/devicepropertieswidget.cpp devices/devicepropertiesdialog.cpp devices/encoders.cpp devices/freespaceinfo.cpp devices/transcodingjob.cpp devices/valueslider.cpp devices/syncdialog.cpp devices/synccollectionwidget.cpp online/onlinedevice.cpp) @@ -530,12 +538,16 @@ target_link_libraries(cantata support qtiocompressor ${QTLIBS} ${ZLIB_LIBRARIES} include_directories(${QTINCLUDES} ${ZLIB_INCLUDE_DIRS}) if (TAGLIB_FOUND) + # Cantata still links to taglib, even if external tag reader/writer is used, + # because JamendoService uses taglib for ID3 genres. target_link_libraries(cantata ${TAGLIB_LIBRARIES}) include_directories(${TAGLIB_INCLUDES}) - if (TAGLIB-EXTRAS_FOUND) - target_link_libraries(cantata ${TAGLIB-EXTRAS_LIBRARIES}) - include_directories(${TAGLIB-EXTRAS_INCLUDES}) - endif (TAGLIB-EXTRAS_FOUND) + if (NOT ENABLE_EXTERNAL_TAGS) + if (TAGLIB-EXTRAS_FOUND) + target_link_libraries(cantata ${TAGLIB-EXTRAS_LIBRARIES}) + include_directories(${TAGLIB-EXTRAS_INCLUDES}) + endif (TAGLIB-EXTRAS_FOUND) + endif (NOT ENABLE_EXTERNAL_TAGS) if (NOT ENABLE_KDE AND NOT WIN32 AND NOT APPLE) target_link_libraries(cantata solidlite) endif (NOT ENABLE_KDE AND NOT WIN32 AND NOT APPLE) diff --git a/ChangeLog b/ChangeLog index ea3ba19eb..3c01312cd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -81,6 +81,8 @@ 46. When displaying tag editor, track orgainiser, or replay gain dialogs, check that the song files can be accessed. (For speed reasons, only the 1st few files are checked) +47. Read/write tags in an external app - as per Clementine and Amarok. Isolates + Cantata from TagLib crashes. 1.1.3 ----- diff --git a/INSTALL b/INSTALL index 2948b373e..1437a6407 100644 --- a/INSTALL +++ b/INSTALL @@ -76,6 +76,12 @@ The following options may be passed to CMake: soundcloud, etc. Default: ON (OFF for windows) + -DENABLE_EXTERNAL_TAGS=ON + Enable usage of external app for reading/writing tags. Helps to + isoloate Cantata from TagLib crashes. + Default: ON + + Windows specific: -DCANTATA_WINDOWS_INSTALLER_DEST= diff --git a/config.h.cmake b/config.h.cmake index b8771d396..8d28892b5 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -33,6 +33,7 @@ #cmakedefine ENABLE_REMOTE_DEVICES 1 #cmakedefine TAGLIB_CAN_SAVE_ID3VER 1 #cmakedefine ENABLE_PROXY_CONFIG 1 +#cmakedefine ENABLE_EXTERNAL_TAGS 1 #cmakedefine CDPARANOIA_HAS_CACHEMODEL_SIZE 1 #cmakedefine QT_QTDBUS_FOUND 1 diff --git a/context/songview.cpp b/context/songview.cpp index 5c7d23433..d7675965b 100644 --- a/context/songview.cpp +++ b/context/songview.cpp @@ -31,7 +31,7 @@ #include "messagebox.h" #include "localize.h" #ifdef TAGLIB_FOUND -#include "tags.h" +#include "tagclient.h" #endif #include "icons.h" #include "utils.h" @@ -309,7 +309,7 @@ void SongView::update(const Song &s, bool force) return; } else { #ifdef TAGLIB_FOUND - QString tagLyrics=Tags::readLyrics(MPDConnection::self()->getDetails().dir+songFile); + QString tagLyrics=TagClient::self()->readLyrics(MPDConnection::self()->getDetails().dir+songFile); if (!tagLyrics.isEmpty()) { text->setText(fixNewLines(tagLyrics)); diff --git a/devices/actiondialog.cpp b/devices/actiondialog.cpp index 6b3e9a161..a49d49071 100644 --- a/devices/actiondialog.cpp +++ b/devices/actiondialog.cpp @@ -41,7 +41,7 @@ #include "freespaceinfo.h" #include "icons.h" #include "config.h" -#include "tags.h" +#include "tagclient.h" #include "treeview.h" #include "onlineservicesmodel.h" #ifdef ENABLE_REPLAYGAIN_SUPPORT @@ -600,7 +600,7 @@ void ActionDialog::actionStatus(int status, bool copiedCover) actionedTime+=currentSong.time; #endif #ifdef ENABLE_REPLAYGAIN_SUPPORT - if (Copy==mode && sourceIsAudioCd && !albumsWithoutRgTags.contains(currentSong.album) && Tags::readReplaygain(destFile).isEmpty()) { + if (Copy==mode && sourceIsAudioCd && !albumsWithoutRgTags.contains(currentSong.album) && TagClient::self()->readReplaygain(destFile).isEmpty()) { albumsWithoutRgTags.insert(currentSong.album); } #endif diff --git a/devices/device.cpp b/devices/device.cpp index a9c0d9b51..116abe8e5 100644 --- a/devices/device.cpp +++ b/devices/device.cpp @@ -39,7 +39,7 @@ #include "audiocddevice.h" #endif // defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND #include "encoders.h" -#include "tags.h" +#include "tagclient.h" #include "song.h" #include "mpdparseutils.h" #include "musiclibraryitemartist.h" @@ -198,7 +198,7 @@ bool Device::fixVariousArtists(const QString &file, Song &song, bool applyFix) { Song orig=song; if (!file.isEmpty() && song.albumartist.isEmpty()) { - song=Tags::read(file); + song=TagClient::self()->read(file); } if (song.artist.isEmpty() || song.albumartist.isEmpty() || !Song::isVariousArtists(song.albumartist)) { @@ -214,7 +214,7 @@ bool Device::fixVariousArtists(const QString &file, Song &song, bool applyFix) needsUpdating=song.fixVariousArtists(); } - if (needsUpdating && (file.isEmpty() || Tags::Update_Modified==Tags::updateArtistAndTitle(file, song))) { + if (needsUpdating && (file.isEmpty() || Tags::Update_Modified==TagClient::self()->updateArtistAndTitle(file, song))) { return true; } song=orig; @@ -232,7 +232,7 @@ static QByteArray save(const QImage &img) { void Device::embedCover(const QString &file, Song &song, unsigned int coverMaxSize) { - if (Tags::readImage(file).isNull()) { + if (TagClient::self()->readImage(file).isNull()) { Covers::Image coverImage=Covers::self()->getImage(song); if (!coverImage.img.isNull()) { QByteArray imgData; @@ -248,7 +248,7 @@ void Device::embedCover(const QString &file, Song &song, unsigned int coverMaxSi imgData=save(coverImage.img); } } - Tags::embedImage(file, imgData); + TagClient::self()->embedImage(file, imgData); } } } diff --git a/devices/extractjob.cpp b/devices/extractjob.cpp index e99cfd22f..33837cc0f 100644 --- a/devices/extractjob.cpp +++ b/devices/extractjob.cpp @@ -23,7 +23,7 @@ #include "extractjob.h" #include "device.h" #include "utils.h" -#include "tags.h" +#include "tagclient.h" #include "cdparanoia.h" #include "covers.h" #include "mpdconnection.h" @@ -150,7 +150,7 @@ void ExtractJob::run() process.closeWriteChannel(); process.waitForFinished(); Utils::setFilePerms(destFile); - Tags::update(destFile, Song(), song, 3); + TagClient::self()->update(destFile, Song(), song, 3); if (!stopRequested && !coverFile.isEmpty()) { QString mpdCover=MPDConnection::self()->getDetails().coverName; diff --git a/devices/fsdevice.cpp b/devices/fsdevice.cpp index b3ab1843d..dff2d6744 100644 --- a/devices/fsdevice.cpp +++ b/devices/fsdevice.cpp @@ -22,7 +22,7 @@ */ #include "umsdevice.h" -#include "tags.h" +#include "tagclient.h" #include "musiclibrarymodel.h" #include "musiclibraryitemsong.h" #include "musiclibraryitemalbum.h" @@ -162,7 +162,7 @@ void MusicScanner::scanFolder(MusicLibraryItemRoot *library, const QString &topL song.file=fname; QSet::iterator it=existing.find(song); if (existing.end()==it) { - song=Tags::read(info.absoluteFilePath()); + song=TagClient::self()->read(info.absoluteFilePath()); song.file=fname; } else { song=*it; diff --git a/gui/covers.cpp b/gui/covers.cpp index a2c5e9d22..45230b5da 100644 --- a/gui/covers.cpp +++ b/gui/covers.cpp @@ -34,7 +34,7 @@ #include "podcastservice.h" #include "onlineservicesmodel.h" #ifdef TAGLIB_FOUND -#include "tags.h" +#include "tagclient.h" #endif #include #include @@ -907,7 +907,7 @@ Covers::Image Covers::locateImage(const Song &song) #ifdef TAGLIB_FOUND QImage img; if (prevFileName.startsWith(constCoverInTagPrefix)) { - img=Tags::readImage(prevFileName.mid(constCoverInTagPrefix.length())); + img=TagClient::self()->readImage(prevFileName.mid(constCoverInTagPrefix.length())); } else { img=QImage(prevFileName); } @@ -990,7 +990,7 @@ Covers::Image Covers::locateImage(const Song &song) #ifdef TAGLIB_FOUND QString fileName=haveAbsPath ? song.file : (MPDConnection::self()->getDetails().dir+songFile); if (QFile::exists(fileName)) { - QImage img(Tags::readImage(fileName)); + QImage img(TagClient::self()->readImage(fileName)); if (!img.isNull()) { DBUG_CLASS("Covers") << "Got cover image from tag" << fileName; return Image(img, constCoverInTagPrefix+fileName); diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index d7bcb400d..95c874a26 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -93,6 +93,7 @@ #ifdef TAGLIB_FOUND #include "trackorganiser.h" #include "tageditor.h" +#include "tagclient.h" #ifdef ENABLE_REPLAYGAIN_SUPPORT #include "rgdialog.h" #endif @@ -878,6 +879,9 @@ MainWindow::~MainWindow() #ifndef ENABLE_KDE_SUPPORT MediaKeys::self()->stop(); #endif + #ifdef TAGLIB_FOUND + TagClient::self()->stop(); + #endif } void MainWindow::initSizes() @@ -1454,8 +1458,8 @@ void MainWindow::showServerInfo() "Uptime:%4" "Time playing:%5", (version>>16)&0xFF, (version>>8)&0xFF, version&0xFF, - MPDParseUtils::formatDuration(MPDStats::self()->uptime()), - MPDParseUtils::formatDuration(MPDStats::self()->playtime()))+ + Utils::formatDuration(MPDStats::self()->uptime()), + Utils::formatDuration(MPDStats::self()->playtime()))+ QLatin1String("")+ i18n("Database" "Artists:%1" @@ -1465,7 +1469,7 @@ void MainWindow::showServerInfo() "Total duration:%5" "Last update:%6

", MPDStats::self()->artists(), MPDStats::self()->albums(), MPDStats::self()->songs(), handlers.join(", "), - MPDParseUtils::formatDuration(MPDStats::self()->dbPlaytime()), MPDStats::self()->dbUpdate().toString(Qt::SystemLocaleShortDate)), + Utils::formatDuration(MPDStats::self()->dbPlaytime()), MPDStats::self()->dbUpdate().toString(Qt::SystemLocaleShortDate)), i18n("Server Information")); } @@ -2179,9 +2183,9 @@ void MainWindow::updatePlayQueueStats(int songs, quint32 time) } #ifdef ENABLE_KDE_SUPPORT - playQueueStatsLabel->setText(i18np("1 Track (%2)", "%1 Tracks (%2)", songs, MPDParseUtils::formatDuration(time))); + playQueueStatsLabel->setText(i18np("1 Track (%2)", "%1 Tracks (%2)", songs, Utils::formatDuration(time))); #else - playQueueStatsLabel->setText(QTP_TRACKS_DURATION_STR(songs, MPDParseUtils::formatDuration(time))); + playQueueStatsLabel->setText(QTP_TRACKS_DURATION_STR(songs, Utils::formatDuration(time))); #endif } diff --git a/http/httpserver.cpp b/http/httpserver.cpp index d36c2db64..6fc95654b 100644 --- a/http/httpserver.cpp +++ b/http/httpserver.cpp @@ -24,7 +24,7 @@ #include "httpserver.h" #include "httpsocket.h" #ifdef TAGLIB_FOUND -#include "tags.h" +#include "tagclient.h" #endif #include "settings.h" #include "thread.h" @@ -201,13 +201,13 @@ QByteArray HttpServer::encodeUrl(const QString &file) const } } #ifdef TAGLIB_FOUND - s=Tags::read(f); + s=TagClient::self()->read(f); #endif s.file=f; #else DBUG << "file" << file; #ifdef TAGLIB_FOUND - s=Tags::read(file); + s=TagClient::self()->read(file); #endif s.file=file; #endif diff --git a/http/httpsocket.cpp b/http/httpsocket.cpp index 128c4e7c0..3bd388f05 100644 --- a/http/httpsocket.cpp +++ b/http/httpsocket.cpp @@ -64,7 +64,7 @@ static QString detectMimeType(const QString &file) if (suffix == QLatin1String("mp3")) { return QLatin1String("audio/mpeg"); } - #ifdef TAGLIB_FOUND + #if defined TAGLIB_FOUND && !defined ENABLE_EXTERNAL_TAGS if (suffix == QLatin1String("ogg")) { #ifdef Q_OS_WIN32 const wchar_t *encodedName = reinterpret_cast< const wchar_t * >(file.utf16()); diff --git a/mpd/mpdparseutils.cpp b/mpd/mpdparseutils.cpp index bfcf8b14f..7dd911659 100644 --- a/mpd/mpdparseutils.cpp +++ b/mpd/mpdparseutils.cpp @@ -636,30 +636,6 @@ QList MPDParseUtils::parseOuputs(const QByteArray &data) return outputs; } -QString MPDParseUtils::formatDuration(const quint32 totalseconds) -{ - //Get the days,hours,minutes and seconds out of the total seconds - quint32 days = totalseconds / 86400; - quint32 rest = totalseconds - (days * 86400); - quint32 hours = rest / 3600; - rest = rest - (hours * 3600); - quint32 minutes = rest / 60; - quint32 seconds = rest - (minutes * 60); - - //Convert hour,minutes and seconds to a QTime for easier parsing - QTime time(hours, minutes, seconds); - - #ifdef ENABLE_KDE_SUPPORT - return 0==days - ? time.toString("h:mm:ss") - : i18np("1 day %2", "%1 days %2", days, time.toString("h:mm:ss")); - #else - return 0==days - ? time.toString("h:mm:ss") - : QString("%1:%2").arg(days).arg(time.toString("hh:mm:ss")); - #endif -} - static const QString constHashReplacement=QLatin1String("${hash}"); QString MPDParseUtils::addStreamName(const QString &url, const QString &name) diff --git a/mpd/mpdparseutils.h b/mpd/mpdparseutils.h index af5d5619f..7adc203c0 100644 --- a/mpd/mpdparseutils.h +++ b/mpd/mpdparseutils.h @@ -63,7 +63,6 @@ namespace MPDParseUtils extern MusicLibraryItemRoot * parseLibraryItems(const QByteArray &data, const QString &mpdDir, long mpdVersion); extern DirViewItemRoot * parseDirViewItems(const QByteArray &data); extern QList parseOuputs(const QByteArray &data); - extern QString formatDuration(const quint32 totalseconds); extern QString addStreamName(const QString &url, const QString &name); extern QString getStreamName(const QString &url); extern QString getAndRemoveStreamName(QString &url); diff --git a/mpd/song.cpp b/mpd/song.cpp index 57e66e050..26cdc1a94 100644 --- a/mpd/song.cpp +++ b/mpd/song.cpp @@ -27,9 +27,11 @@ #include #include "config.h" #include "song.h" -#include "mpdparseutils.h" #include "musiclibraryitemalbum.h" #include "localize.h" +#ifndef CANTATA_NO_SONG_TIME_FUNCTION +#include "utils.h" +#endif #include #include #include @@ -291,6 +293,7 @@ void Song::clear() type = Standard; } +#ifndef CANTATA_NO_SONG_TIME_FUNCTION QString Song::formattedTime(quint32 seconds, bool zeroIsUnknown) { if (0==seconds && zeroIsUnknown) { @@ -299,7 +302,7 @@ QString Song::formattedTime(quint32 seconds, bool zeroIsUnknown) static const quint32 constHour=60*60; if (seconds>constHour) { - return MPDParseUtils::formatDuration(seconds); + return Utils::formatDuration(seconds); } QString result(QString::number(floor(seconds / 60.0))+QChar(':')); @@ -308,6 +311,7 @@ QString Song::formattedTime(quint32 seconds, bool zeroIsUnknown) } return result+QString::number(seconds % 60); } +#endif /* * Genarate a string with song info. @@ -476,3 +480,27 @@ void Song::setIsFromOnlineService(const QString &service) album=service; albumartist=service; } + +#ifdef ENABLE_EXTERNAL_TAGS +QDataStream & operator<<(QDataStream &stream, const Song &song) +{ + stream << song.id << song.file << song.album << song.artist << song.albumartist << song.composer << song.title + << song.genre << song.name << song.disc << song.priority << song.time << song.track << (quint16)song.year + << (quint16)song.type << (bool)song.guessed << song.size; + return stream; +} + +QDataStream & operator>>(QDataStream &stream, Song &song) +{ + quint16 type; + quint16 year; + bool guessed; + stream >> song.id >> song.file >> song.album >> song.artist >> song.albumartist >> song.composer >> song.title + >> song.genre >> song.name >> song.disc >> song.priority >> song.time >> song.track >> year + >> type >> guessed >> song.size; + song.type=(Song::Type)type; + song.year=year; + song.guessed=guessed; + return stream; +} +#endif diff --git a/mpd/song.h b/mpd/song.h index 4d0c9c075..049a1204f 100644 --- a/mpd/song.h +++ b/mpd/song.h @@ -31,6 +31,7 @@ #include #include #include +#include "config.h" struct Song { @@ -90,7 +91,9 @@ struct Song void fillEmptyFields(); void setKey(); virtual void clear(); + #ifndef CANTATA_NO_SONG_TIME_FUNCTION static QString formattedTime(quint32 seconds, bool zeroIsUnknown=false); + #endif QString format(); QString entryName() const; QString artistOrComposer() const; @@ -143,6 +146,11 @@ struct Song Q_DECLARE_METATYPE(Song) +#ifdef ENABLE_EXTERNAL_TAGS +QDataStream & operator<<(QDataStream &stream, const Song &song); +QDataStream & operator>>(QDataStream &stream, Song &song); +#endif + inline uint qHash(const Song &key) { return qHash(key.albumArtist()+key.album+key.title+key.file); diff --git a/replaygain/rgdialog.cpp b/replaygain/rgdialog.cpp index d235d0043..25cd43022 100644 --- a/replaygain/rgdialog.cpp +++ b/replaygain/rgdialog.cpp @@ -27,7 +27,7 @@ #endif #include "devicesmodel.h" #include "settings.h" -#include "tags.h" +#include "tagclient.h" #include "tagreader.h" #include "utils.h" #include "localize.h" @@ -411,9 +411,22 @@ void RgDialog::saveTags() QMap::ConstIterator end=tagsToSave.constEnd(); for (; it!=end; ++it) { - if (Tags::Update_Failed==Tags::updateReplaygain(base+origSongs.at(it.key()).file, it.value())) { + switch (TagClient::self()->updateReplaygain(base+origSongs.at(it.key()).file, it.value())) { + case Tags::Update_Failed: failed.append(origSongs.at(it.key()).file); + break; + #ifdef ENABLE_EXTERNAL_TAGS + case Tags::Update_Timedout: + failed.append(i18nc("filename (Timeout)", "%1 (Timeout)", origSongs.at(it.key()).file)); + break; + case Tags::Update_BadFile: + failed.append(i18nc("filename (Corrupt tags?)", "%1 (Corrupt tags?)", origSongs.at(it.key()).file)); + break; + #endif + default: + break; } + progress->setValue(progress->value()+1); if (0==count++%10) { QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); diff --git a/replaygain/tagreader.cpp b/replaygain/tagreader.cpp index 6ea8ed9fb..10e1d8340 100644 --- a/replaygain/tagreader.cpp +++ b/replaygain/tagreader.cpp @@ -22,7 +22,7 @@ */ #include "tagreader.h" -#include "tags.h" +#include "tagclient.h" void TagReader::setDetails(const QList &s, const QString &dir) { @@ -38,7 +38,7 @@ void TagReader::run() return; } - emit progress(i, Tags::readReplaygain(baseDir+songs.at(i).file)); + emit progress(i, TagClient::self()->readReplaygain(baseDir+songs.at(i).file)); } setFinished(true); } diff --git a/support/utils.cpp b/support/utils.cpp index 8ce94fafc..c809359f0 100644 --- a/support/utils.cpp +++ b/support/utils.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #ifdef ENABLE_KDE_SUPPORT @@ -524,6 +525,30 @@ QString Utils::findExe(const QString &appname, const QString &pstr) #endif +QString Utils::formatDuration(const quint32 totalseconds) +{ + //Get the days,hours,minutes and seconds out of the total seconds + quint32 days = totalseconds / 86400; + quint32 rest = totalseconds - (days * 86400); + quint32 hours = rest / 3600; + rest = rest - (hours * 3600); + quint32 minutes = rest / 60; + quint32 seconds = rest - (minutes * 60); + + //Convert hour,minutes and seconds to a QTime for easier parsing + QTime time(hours, minutes, seconds); + + #ifdef ENABLE_KDE_SUPPORT + return 0==days + ? time.toString("h:mm:ss") + : i18np("1 day %2", "%1 days %2", days, time.toString("h:mm:ss")); + #else + return 0==days + ? time.toString("h:mm:ss") + : QString("%1:%2").arg(days).arg(time.toString("hh:mm:ss")); + #endif +} + QString Utils::cleanPath(const QString &p) { QString path(p); diff --git a/support/utils.h b/support/utils.h index 174ee4e99..efd7a4e4f 100644 --- a/support/utils.h +++ b/support/utils.h @@ -75,6 +75,7 @@ namespace Utils extern QString findExe(const QString &appname, const QString &pathstr=QString()); extern QString formatByteSize(double size); #endif + extern QString formatDuration(const quint32 totalseconds); extern QString cleanPath(const QString &p); extern QString configDir(const QString &sub=QString(), bool create=false); diff --git a/tags/CMakeLists.txt b/tags/CMakeLists.txt new file mode 100644 index 000000000..fab696f86 --- /dev/null +++ b/tags/CMakeLists.txt @@ -0,0 +1,38 @@ +include_directories(${QTINCLUDES}) +include_directories(${CMAKE_SOURCE_DIR}/tags) +set(CANTATA_TAGS_SRCS ${CANTATA_TAGS_SRCS} main.cpp tagserver.cpp tags.cpp filetyperesolver.cpp ../mpd/song.cpp) +set(CANTATA_TAGS_MOC_HDRS tagserver.h) + +if (ENABLE_KDE_SUPPORT) + kde4_add_executable(cantata-tags ${CANTATA_TAGS_SRCS} ${CANTATA_TAGS_MOC_SRCS}) + install(TARGETS cantata-tags RUNTIME DESTINATION lib/cantata) +else (ENABLE_KDE_SUPPORT) + if (ENABLE_QT5) + QT5_WRAP_CPP(CANTATA_TAGS_MOC_SRCS ${CANTATA_TAGS_MOC_HDRS}) + else (ENABLE_QT5) + INCLUDE(${QT_USE_FILE}) + QT4_WRAP_CPP(CANTATA_TAGS_MOC_SRCS ${CANTATA_TAGS_MOC_HDRS}) + endif (ENABLE_QT5) + + if (WIN32) + set(CMAKE_BUILD_TYPE "Release") + ADD_EXECUTABLE(cantata-tags WIN32 ${CANTATA_TAGS_SRCS} ${CANTATA_TAGS_MOC_SRCS}}) + install(TARGETS cantata-tags DESTINATION ${CMAKE_INSTALL_PREFIX}) + else (WIN32) + ADD_EXECUTABLE(cantata-tags ${CANTATA_TAGS_SRCS} ${CANTATA_TAGS_MOC_SRCS}) + install(TARGETS cantata-tags RUNTIME DESTINATION lib/cantata) + endif (WIN32) +endif (ENABLE_KDE_SUPPORT) + +add_definitions(-DCANTATA_NO_SONG_TIME_FUNCTION) +include_directories(${TAGLIB_INCLUDES}) +target_link_libraries(cantata-tags ${TAGLIB_LIBRARIES} ${QTCORELIBS} ${QTNETWORKLIBS}) +if (TAGLIB-EXTRAS_FOUND) + target_link_libraries(cantata-tags ${TAGLIB-EXTRAS_LIBRARIES}) + include_directories(${TAGLIB-EXTRAS_INCLUDES}) +endif (TAGLIB-EXTRAS_FOUND) + + + + + diff --git a/tags/main.cpp b/tags/main.cpp new file mode 100644 index 000000000..d076a9d29 --- /dev/null +++ b/tags/main.cpp @@ -0,0 +1,36 @@ +/* + * Cantata + * + * Copyright (c) 2011-2013 Craig Drummond + * + * ---- + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include +#include +#include "tagserver.h" + +int main(int argc, char *argv[]) +{ + if (argc<2) { + return -1; + } + QCoreApplication app(argc, argv); + TagServer *srv=new TagServer(argv[1]); + return srv->ok() ? app.exec() : -1; +} diff --git a/tags/tagclient.cpp b/tags/tagclient.cpp new file mode 100644 index 000000000..3680f5d68 --- /dev/null +++ b/tags/tagclient.cpp @@ -0,0 +1,365 @@ +/* + * Cantata + * + * Copyright (c) 2011-2013 Craig Drummond + * + * ---- + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "tagclient.h" +#include +#include +#include +#ifdef ENABLE_KDE_SUPPORT +#include +K_GLOBAL_STATIC(TagClient, instance) +#endif +#ifdef ENABLE_EXTERNAL_TAGS +#include +#include +#include +#include +#include +#endif +#include + +static const int constMaxWait=5000; + +TagClient * TagClient::self() +{ + #ifdef ENABLE_KDE_SUPPORT + return instance; + #else + static TagClient *instance=0; + if(!instance) { + instance=new TagClient; + } + return instance; + #endif +} + +TagClient::TagClient() + : mutex(new QMutex) + #ifdef ENABLE_EXTERNAL_TAGS + , loop(0) + , proc(0) + , server(0) + , socket(0) + #endif +{ +} + +TagClient::~TagClient() +{ + stop(); + delete mutex; +} + +void TagClient::stop() +{ + QMutexLocker locker(mutex); + #ifdef ENABLE_EXTERNAL_TAGS + stopHelper(); + #endif +} + +Song TagClient::read(const QString &fileName) +{ + QMutexLocker locker(mutex); + #ifdef ENABLE_EXTERNAL_TAGS + Song resp; + if (startHelper()) { + QDataStream stream(socket); + stream << QString(__FUNCTION__) << fileName; + if (Wait_Ok==waitForReply()) { + stream >> resp; + } + } + return resp; + #else + return Tags::read(fileName); + #endif +} + +QImage TagClient::readImage(const QString &fileName) +{ + QMutexLocker locker(mutex); + #ifdef ENABLE_EXTERNAL_TAGS + QByteArray data; + if (startHelper()) { + QDataStream stream(socket); + stream << QString(__FUNCTION__) << fileName; + if (Wait_Ok==waitForReply()) { + stream >> data; + } + } + #else + QByteArray data=Tags::readImage(fileName); + #endif + QImage img; + if (!data.isEmpty()) { + img.loadFromData(data); + if (img.isNull()) { + img.loadFromData(QByteArray::fromBase64(data)); + } + } + return img; +} + +QString TagClient::readLyrics(const QString &fileName) +{ + QMutexLocker locker(mutex); + #ifdef ENABLE_EXTERNAL_TAGS + QString resp; + if (startHelper()) { + QDataStream stream(socket); + stream << QString(__FUNCTION__) << fileName; + if (Wait_Ok==waitForReply()) { + stream >> resp; + } + } + return resp; + #else + return Tags::readLyrics(fileName); + #endif +} + +Tags::Update TagClient::updateArtistAndTitle(const QString &fileName, const Song &song) +{ + QMutexLocker locker(mutex); + #ifdef ENABLE_EXTERNAL_TAGS + int resp=Tags::Update_Failed; + if (startHelper()) { + QDataStream stream(socket); + stream << QString(__FUNCTION__) << fileName << song; + WaitReply wr=waitForReply(); + if (Wait_Ok==wr) { + stream >> resp; + } else { + resp=Wait_Timeout==wr ? Tags::Update_Timedout : Tags::Update_BadFile; + } + } + return (Tags::Update)resp; + #else + return Tags::updateArtistAndTitle(fileName, song); + #endif +} + +Tags::Update TagClient::update(const QString &fileName, const Song &from, const Song &to, int id3Ver) +{ + QMutexLocker locker(mutex); + #ifdef ENABLE_EXTERNAL_TAGS + int resp=Tags::Update_Failed; + if (startHelper()) { + QDataStream stream(socket); + stream << QString(__FUNCTION__) << fileName << from << to << id3Ver; + WaitReply wr=waitForReply(); + if (Wait_Ok==wr) { + stream >> resp; + } else { + resp=Wait_Timeout==wr ? Tags::Update_Timedout : Tags::Update_BadFile; + } + } + return (Tags::Update)resp; + #else + return Tags::update(fileName, from, to, id3Ver); + #endif +} + +Tags::ReplayGain TagClient::readReplaygain(const QString &fileName) +{ + QMutexLocker locker(mutex); + #ifdef ENABLE_EXTERNAL_TAGS + Tags::ReplayGain resp; + if (startHelper()) { + QDataStream stream(socket); + stream << QString(__FUNCTION__) << fileName; + if (Wait_Ok==waitForReply()) { + stream >> resp; + } + } + return resp; + #else + return Tags::readReplaygain(fileName); + #endif +} + +Tags::Update TagClient::updateReplaygain(const QString &fileName, const Tags::ReplayGain &rg) +{ + QMutexLocker locker(mutex); + #ifdef ENABLE_EXTERNAL_TAGS + int resp=Tags::Update_Failed; + if (startHelper()) { + QDataStream stream(socket); + stream << QString(__FUNCTION__) << fileName << rg; + WaitReply wr=waitForReply(); + if (Wait_Ok==wr) { + stream >> resp; + } else { + resp=Wait_Timeout==wr ? Tags::Update_Timedout : Tags::Update_BadFile; + } + } + return (Tags::Update)resp; + #else + return Tags::updateReplaygain(fileName, rg); + #endif +} + +Tags::Update TagClient::embedImage(const QString &fileName, const QByteArray &cover) +{ + QMutexLocker locker(mutex); + #ifdef ENABLE_EXTERNAL_TAGS + int resp=Tags::Update_Failed; + if (startHelper()) { + QDataStream stream(socket); + stream << QString(__FUNCTION__) << fileName << cover; + WaitReply wr=waitForReply(); + if (Wait_Ok==wr) { + stream >> resp; + } else { + resp=Wait_Timeout==wr ? Tags::Update_Timedout : Tags::Update_BadFile; + } + } + return (Tags::Update)resp; + #else + return Tags::embedImage(fileName, cover); + #endif +} + +#ifdef ENABLE_EXTERNAL_TAGS +enum ErrorCodes { + Connected = 0, + FailedToStart = 1, + OtherError = 2 +}; + +#endif + +void TagClient::processError(QProcess::ProcessError error) +{ + #ifdef ENABLE_EXTERNAL_TAGS + switch (error) { + case QProcess::FailedToStart: + qWarning() << "Failed to start tag reader/writer"; + if (loop && loop->isRunning()) { + loop->exit(FailedToStart); + } + break; + default: + qWarning() << "Tag reader/writer failed with error " << error << " - restarting"; + stopHelper(); + if (loop && loop->isRunning()) { + loop->exit(OtherError); + } + break; + } + #endif +} + +void TagClient::newConnection() +{ + #ifdef ENABLE_EXTERNAL_TAGS + socket = server->nextPendingConnection(); + socket->setParent(this); + closeServerSocket(); + if (loop && loop->isRunning()) { + loop->exit(Connected); + } + #endif +} + +#ifdef ENABLE_EXTERNAL_TAGS +bool TagClient::startHelper() +{ + if (!proc) { + for (int i=0; i<5; ++i) { // Max5 start attempts... + proc=new QProcess(this); + server=new QLocalServer(this); + + connect(server, SIGNAL(newConnection()), this, SLOT(newConnection())); + connect(proc, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); + + forever { + const QString name = QString("cantata_tags_%1").arg(qrand() ^ ((int)(quint64(this) & 0xFFFFFFFF))); + if (server->listen(name)) { + break; + } + } + + proc->setProcessChannelMode(QProcess::ForwardedChannels); + #ifdef Q_OS_WIN + proc->start(qApp->applicationDirPath()+"/cantata-tags.exe", QStringList() << server->fullServerName()); + #else + proc->start(INSTALL_PREFIX"/lib/cantata/cantata-tags", QStringList() << server->fullServerName()); + #endif + + if (!loop) { + loop=new QEventLoop(this); + } + int rv=loop->exec(QEventLoop::ExcludeUserInputEvents); + loop->deleteLater(); + loop=0; + if (rv!=OtherError) { + break; + } + } + } + + return 0!=socket; +} + +void TagClient::stopHelper() +{ + QProcess *p=proc; + QLocalSocket *s=socket; + + proc=0; + socket=0; + if (p) { + disconnect(p, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); + p->terminate(); + p->deleteLater(); + } + if (s) { + s->deleteLater(); + } + closeServerSocket(); +} + +void TagClient::closeServerSocket() +{ + QLocalServer *s=server; + server=0; + if (s) { + disconnect(s, SIGNAL(newConnection()), this, SLOT(newConnection())); + s->deleteLater(); + } +} + +TagClient::WaitReply TagClient::waitForReply() +{ + if (!socket) { + return Wait_Closed; + } + if (socket->waitForReadyRead(constMaxWait)) { + return Wait_Ok; + } + return !socket || QLocalSocket::ConnectedState!=socket->state() ? Wait_Closed : Wait_Timeout; +} + +#endif diff --git a/tags/tagclient.h b/tags/tagclient.h new file mode 100644 index 000000000..90b530e91 --- /dev/null +++ b/tags/tagclient.h @@ -0,0 +1,91 @@ +/* + * Cantata + * + * Copyright (c) 2011-2013 Craig Drummond + * + * ---- + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef TAG_CLIENT_H +#define TAG_CLIENT_H + +#include "tags.h" +#include "config.h" +#ifdef ENABLE_EXTERNAL_TAGS +#include +#include +#endif +#include + +#ifdef ENABLE_EXTERNAL_TAGS +class QEventLoop; +class QLocalServer; +class QLocalSocket; +#endif +class QMutex; + +class TagClient : public QObject +{ + Q_OBJECT + +public: + static TagClient * self(); + + TagClient(); + ~TagClient(); + + void stop(); + + Song read(const QString &fileName); + QImage readImage(const QString &fileName); + QString readLyrics(const QString &fileName); + Tags::Update updateArtistAndTitle(const QString &fileName, const Song &song); + Tags::Update update(const QString &fileName, const Song &from, const Song &to, int id3Ver=-1); + Tags::ReplayGain readReplaygain(const QString &fileName); + Tags::Update updateReplaygain(const QString &fileName, const Tags::ReplayGain &rg); + Tags::Update embedImage(const QString &fileName, const QByteArray &cover); + +private Q_SLOTS: + void processError(QProcess::ProcessError error); + void newConnection(); + +private: + #ifdef ENABLE_EXTERNAL_TAGS + enum WaitReply { + Wait_Ok, + Wait_Timeout, + Wait_Closed + }; + + bool startHelper(); + void stopHelper(); + void closeServerSocket(); + WaitReply waitForReply(); + #endif + +private: + QMutex *mutex; + #ifdef ENABLE_EXTERNAL_TAGS + QEventLoop *loop; + QProcess *proc; + QLocalServer *server; + QLocalSocket *socket; + #endif +}; + +#endif diff --git a/tags/tageditor.cpp b/tags/tageditor.cpp index 3be73d679..fd1e2d089 100644 --- a/tags/tageditor.cpp +++ b/tags/tageditor.cpp @@ -22,7 +22,7 @@ */ #include "tageditor.h" -#include "tags.h" +#include "tagclient.h" #include "musiclibrarymodel.h" #include "mpdconnection.h" #include "settings.h" @@ -724,7 +724,7 @@ void TagEditor::applyUpdates() continue; } - switch(Tags::update(baseDir+orig.file, orig, edit)) { + switch(TagClient::self()->update(baseDir+orig.file, orig, edit)) { case Tags::Update_Modified: #ifdef ENABLE_DEVICES_SUPPORT if (!deviceUdi.isEmpty()) { @@ -748,6 +748,14 @@ void TagEditor::applyUpdates() case Tags::Update_Failed: failed.append(orig.file); break; + #ifdef ENABLE_EXTERNAL_TAGS + case Tags::Update_Timedout: + failed.append(i18nc("filename (Timeout)", "%1 (Timeout)", orig.file)); + break; + case Tags::Update_BadFile: + failed.append(i18nc("filename (Corrupt tags?)", "%1 (Corrupt tags?)", orig.file)); + break; + #endif default: break; } diff --git a/tags/tags.cpp b/tags/tags.cpp index 7fbc8e6e6..963c5a049 100644 --- a/tags/tags.cpp +++ b/tags/tags.cpp @@ -33,8 +33,6 @@ #include #include #include -#include -#include #include #include #include @@ -112,11 +110,6 @@ static double parseRgString(const TagLib::String &str) { return ok ? v : 0.0; } -bool ReplayGain::isEmpty() const -{ - return Utils::equal(trackGain, 0.0) && Utils::equal(trackPeak, 0.0) && Utils::equal(albumGain, 0.0) && Utils::equal(albumPeak, 0.0); -} - struct RgTags : public ReplayGain { RgTags(const ReplayGain &r) @@ -258,7 +251,7 @@ static void setRva2Tag(TagLib::ID3v2::Tag* tag, const std::string &tagName, doub } // -- taken from rgtag.cpp from libebur128 -- END -static void readID3v2Tags(TagLib::ID3v2::Tag *tag, Song *song, ReplayGain *rg, QImage *img, QString *lyrics) +static void readID3v2Tags(TagLib::ID3v2::Tag *tag, Song *song, ReplayGain *rg, QByteArray *img, QString *lyrics) { if (song) { const TagLib::ID3v2::FrameList &albumArtist = tag->frameListMap()["TPE2"]; @@ -317,8 +310,8 @@ static void readID3v2Tags(TagLib::ID3v2::Tag *tag, Song *song, ReplayGain *rg, Q for (; it != end && !found; ++it) { TagLib::ID3v2::AttachedPictureFrame *pic=dynamic_cast(*it); if (pic && TagLib::ID3v2::AttachedPictureFrame::FrontCover==pic->type()) { - img->loadFromData((const uchar *) pic->picture().data(), pic->picture().size()); - if (!img->isNull()) { + *img=QByteArray((const char *) pic->picture().data(), (int) pic->picture().size()); + if (!img->isEmpty()) { found=true; } } @@ -327,7 +320,7 @@ static void readID3v2Tags(TagLib::ID3v2::Tag *tag, Song *song, ReplayGain *rg, Q if (!found) { // Just use first image! TagLib::ID3v2::AttachedPictureFrame *pic=static_cast(frames.front()); - img->loadFromData((const uchar *) pic->picture().data(), pic->picture().size()); + *img=QByteArray((const char *) pic->picture().data(), (int) pic->picture().size()); } } } @@ -523,7 +516,7 @@ static TagLib::String readVorbisTag(TagLib::Ogg::XiphComment *tag, const char *f return TagLib::String(); } -static void readVorbisCommentTags(TagLib::Ogg::XiphComment *tag, Song *song, ReplayGain *rg, QImage *img) +static void readVorbisCommentTags(TagLib::Ogg::XiphComment *tag, Song *song, ReplayGain *rg, QByteArray *img) { if (song) { TagLib::String str=readVorbisTag(tag, "ALBUMARTIST"); @@ -552,22 +545,17 @@ static void readVorbisCommentTags(TagLib::Ogg::XiphComment *tag, Song *song, Rep // Ogg lacks a definitive standard for embedding cover art, but it seems // b64 encoding a field called COVERART is the general convention if (map.contains("COVERART")) { - QByteArray data=map["COVERART"].toString().toCString(); - img->loadFromData(QByteArray::fromBase64(data)); - if (img->isNull()) { - img->loadFromData(data); // not base64?? - } + *img=map["COVERART"].toString().toCString(); } } } #if (TAGLIB_MAJOR_VERSION > 1) || (TAGLIB_MAJOR_VERSION == 1 && TAGLIB_MINOR_VERSION >= 7) -static void readFlacPicture(const TagLib::List &pics, QImage *img) +static void readFlacPicture(const TagLib::List &pics, QByteArray *img) { if (!pics.isEmpty() && 1==pics.size()) { TagLib::FLAC::Picture *picture = *(pics.begin()); - QByteArray data(picture->data().data(), picture->data().size()); - img->loadFromData(data); + *img=QByteArray(picture->data().data(), picture->data().size()); } } #endif @@ -629,7 +617,7 @@ static bool writeVorbisCommentTags(TagLib::Ogg::XiphComment *tag, const Song &fr } #ifdef TAGLIB_MP4_FOUND -static void readMP4Tags(TagLib::MP4::Tag *tag, Song *song, ReplayGain *rg, QImage *img) +static void readMP4Tags(TagLib::MP4::Tag *tag, Song *song, ReplayGain *rg, QByteArray *img) { TagLib::MP4::ItemListMap &map = tag->itemListMap(); @@ -664,7 +652,7 @@ static void readMP4Tags(TagLib::MP4::Tag *tag, Song *song, ReplayGain *rg, QImag TagLib::MP4::CoverArtList coverArtList = coverItem.toCoverArtList(); if (!coverArtList.isEmpty()) { TagLib::MP4::CoverArt coverArt = coverArtList.front(); - img->loadFromData((const uchar *) coverArt.data().data(), coverArt.data().size()); + *img=QByteArray((const char *) coverArt.data().data(), (int) coverArt.data().size()); } } } @@ -787,7 +775,7 @@ static bool writeASFTags(TagLib::ASF::Tag *tag, const Song &from, const Song &to } #endif -static void readTags(const TagLib::FileRef fileref, Song *song, ReplayGain *rg, QImage *img, QString *lyrics) +static void readTags(const TagLib::FileRef fileref, Song *song, ReplayGain *rg, QByteArray *img, QString *lyrics) { TagLib::Tag *tag=fileref.tag(); if (song) { @@ -994,11 +982,8 @@ static bool writeTags(const TagLib::FileRef fileref, const Song &from, const Son return changed; } -static QMutex mutex; - Song read(const QString &fileName) { - QMutexLocker locker(&mutex); Song song; TagLib::FileRef fileref = getFileRef(fileName); @@ -1012,10 +997,9 @@ Song read(const QString &fileName) return song; } -QImage readImage(const QString &fileName) +QByteArray readImage(const QString &fileName) { - QMutexLocker locker(&mutex); - QImage img; + QByteArray img; TagLib::FileRef fileref = getFileRef(fileName); if (fileref.isNull()) { @@ -1028,7 +1012,6 @@ QImage readImage(const QString &fileName) QString readLyrics(const QString &fileName) { - QMutexLocker locker(&mutex); QString lyrics; TagLib::FileRef fileref = getFileRef(fileName); @@ -1064,7 +1047,6 @@ static Update update(const TagLib::FileRef fileref, const Song &from, const Song Update updateArtistAndTitle(const QString &fileName, const Song &song) { - QMutexLocker locker(&mutex); TagLib::FileRef fileref = getFileRef(fileName); if (fileref.isNull()) { @@ -1092,14 +1074,12 @@ Update updateArtistAndTitle(const QString &fileName, const Song &song) Update update(const QString &fileName, const Song &from, const Song &to, int id3Ver) { - QMutexLocker locker(&mutex); TagLib::FileRef fileref = getFileRef(fileName); return fileref.isNull() ? Update_Failed : update(fileref, from, to, RgTags(), QByteArray(), id3Ver); } ReplayGain readReplaygain(const QString &fileName) { - QMutexLocker locker(&mutex); TagLib::FileRef fileref = getFileRef(fileName); if (fileref.isNull()) { @@ -1113,14 +1093,12 @@ ReplayGain readReplaygain(const QString &fileName) Update updateReplaygain(const QString &fileName, const ReplayGain &rg) { - QMutexLocker locker(&mutex); TagLib::FileRef fileref = getFileRef(fileName); return fileref.isNull() ? Update_Failed : update(fileref, Song(), Song(), RgTags(rg), QByteArray()); } Update embedImage(const QString &fileName, const QByteArray &cover) { - QMutexLocker locker(&mutex); TagLib::FileRef fileref = getFileRef(fileName); return fileref.isNull() ? Update_Failed : update(fileref, Song(), Song(), RgTags(), cover); } diff --git a/tags/tags.h b/tags/tags.h index ad391d310..0311a7b12 100644 --- a/tags/tags.h +++ b/tags/tags.h @@ -25,7 +25,9 @@ #define TAGS_H #include "song.h" -#include +#include "utils.h" +#include +#include namespace Tags { @@ -38,7 +40,10 @@ namespace Tags , albumPeak(ap) { } - bool isEmpty() const; + bool isEmpty() const + { + return Utils::equal(trackGain, 0.0) && Utils::equal(trackPeak, 0.0) && Utils::equal(albumGain, 0.0) && Utils::equal(albumPeak, 0.0); + } double trackGain; double albumGain; @@ -51,16 +56,36 @@ namespace Tags Update_Failed, Update_None, Update_Modified + #ifdef ENABLE_EXTERNAL_TAGS + , Update_Timedout + , Update_BadFile + #endif }; extern Song read(const QString &fileName); - extern QImage readImage(const QString &fileName); + extern QByteArray readImage(const QString &fileName); extern QString readLyrics(const QString &fileName); extern Update updateArtistAndTitle(const QString &fileName, const Song &song); extern Update update(const QString &fileName, const Song &from, const Song &to, int id3Ver=-1); extern ReplayGain readReplaygain(const QString &fileName); extern Update updateReplaygain(const QString &fileName, const ReplayGain &rg); extern Update embedImage(const QString &fileName, const QByteArray &cover); -}; +} + +#ifdef ENABLE_EXTERNAL_TAGS +Q_DECLARE_METATYPE(Tags::ReplayGain) + +inline QDataStream & operator<<(QDataStream &stream, const Tags::ReplayGain &rg) +{ + stream << rg.trackGain << rg.albumGain << rg.trackPeak << rg.albumPeak; + return stream; +} + +inline QDataStream & operator>>(QDataStream &stream, Tags::ReplayGain &rg) +{ + stream >> rg.trackGain >> rg.albumGain >> rg.trackPeak >> rg.albumPeak; + return stream; +} +#endif #endif diff --git a/tags/tagserver.cpp b/tags/tagserver.cpp new file mode 100644 index 000000000..4d9a9487e --- /dev/null +++ b/tags/tagserver.cpp @@ -0,0 +1,93 @@ +/* + * Cantata + * + * Copyright (c) 2011-2013 Craig Drummond + * + * ---- + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "tagserver.h" +#include "tags.h" +#include +#include +#include + +TagServer::TagServer(const char *socketName) + : QObject(0) + , socket(new QLocalSocket(this)) +{ + socket->connectToServer(socketName); + if (socket->waitForConnected(1000)) { + connect(socket, SIGNAL(readyRead()), this, SLOT(readRequest())); + connect(socket, SIGNAL(stateChanged(QLocalSocket::LocalSocketState)), this, SLOT(stateChanged(QLocalSocket::LocalSocketState))); + } else { + socket->close(); + socket->deleteLater(); + socket=0; + } +} + +TagServer::~TagServer() +{ + if (socket) { + socket->close(); + socket->deleteLater(); + } +} + +void TagServer::readRequest() +{ + QDataStream stream(socket); + QString request; + QString fileName; + stream >> request >> fileName; + + if (QLatin1String("read")==request) { + stream << Tags::read(fileName); + } else if (QLatin1String("readImage")==request) { + stream << Tags::readImage(fileName); + } else if (QLatin1String("readLyrics")==request) { + stream << Tags::readLyrics(fileName); + } else if (QLatin1String("updateArtistAndTitle")==request) { + Song song; + stream << (int)Tags::updateArtistAndTitle(fileName, song); + } else if (QLatin1String("update")==request) { + Song from; + Song to; + int id3Ver; + stream >> from >> to >> id3Ver; + stream << (int)Tags::update(fileName, from, to, id3Ver); + } else if (QLatin1String("readReplaygain")==request) { + stream << Tags::readReplaygain(fileName); + } else if (QLatin1String("updateReplaygain")==request) { + Tags::ReplayGain rg; + stream >> rg; + stream << (int)Tags::updateReplaygain(fileName, rg); + } else if (QLatin1String("embedImage")==request) { + QByteArray cover; + stream >> cover; + stream << (int)Tags::embedImage(fileName, cover); + } +} + +void TagServer::stateChanged(QLocalSocket::LocalSocketState state) +{ + if (QLocalSocket::ClosingState==state) { + qApp->exit(0); + } +} diff --git a/tags/tagserver.h b/tags/tagserver.h new file mode 100644 index 000000000..48059eeb8 --- /dev/null +++ b/tags/tagserver.h @@ -0,0 +1,48 @@ +/* + * Cantata + * + * Copyright (c) 2011-2013 Craig Drummond + * + * ---- + * + * 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef TAG_SERVER_H +#define TAG_SERVER_H + +#include +#include + +class TagServer : public QObject +{ + Q_OBJECT + +public: + TagServer(const char *socketName); + ~TagServer(); + + bool ok() const { return 0!=socket; } + +private Q_SLOTS: + void readRequest(); + void stateChanged(QLocalSocket::LocalSocketState state); + +private: + QLocalSocket *socket; +}; + +#endif