From dfd5980c6fcf75d02e0efdcd96a345e62b22a2d3 Mon Sep 17 00:00:00 2001 From: "craig.p.drummond" Date: Wed, 11 Jun 2014 18:24:13 +0000 Subject: [PATCH] Add a 'tags' view to tracks view to list all tags via taglib --- ChangeLog | 4 +- context/songview.cpp | 176 +++++++++++++++++++++++++++++++++------- context/songview.h | 5 +- tags/taghelper.cpp | 2 + tags/taghelperiface.cpp | 15 ++++ tags/taghelperiface.h | 2 + tags/tags.cpp | 25 ++++++ tags/tags.h | 4 + 8 files changed, 200 insertions(+), 33 deletions(-) diff --git a/ChangeLog b/ChangeLog index af773a08e..8bba47821 100644 --- a/ChangeLog +++ b/ChangeLog @@ -141,8 +141,8 @@ 88. Open search-widget as soon as user starts typing in view. 89. If artist is different to album-artist, then show track title as "title - artist" -90. Show track info in context-view as well as artist and album info. Track - info is stacked behind lyrics. +90. In context-view rename Lyrics pane to Track. This is now a stack of lyrcs, + information, and tags. Tags contains all tags as read by taglib. 91. Add option to re-load lyric from disk. 92. Add "Open In File Manager" to folders page for windows and mac builds. diff --git a/context/songview.cpp b/context/songview.cpp index ae9ef386d..285c81199 100644 --- a/context/songview.cpp +++ b/context/songview.cpp @@ -89,14 +89,14 @@ static QString lyricsCacheFileName(const Song &song, bool createDir=false) return dir+Covers::encodeName(title)+SongView::constExtension; } -static inline QString mpdFilePath(const QString &songFile) +static inline QString mpdLyricsFilePath(const QString &songFile) { return Utils::changeExtension(MPDConnection::self()->getDetails().dir+songFile, SongView::constExtension); } -static inline QString mpdFilePath(const Song &song) +static inline QString mpdLyricsFilePath(const Song &song) { - return mpdFilePath(song.filePath()); + return mpdLyricsFilePath(song.filePath()); } static inline QString fixNewLines(const QString &o) @@ -104,8 +104,24 @@ static inline QString fixNewLines(const QString &o) return QString(o).replace(QLatin1String("\n\n\n"), QLatin1String("\n\n")).replace("\n", "
"); } +static QString actualFile(const Song &song) +{ + QString songFile=song.filePath(); + + if (song.isCantataStream()) { + #if QT_VERSION < 0x050000 + QUrl u(songFile); + #else + QUrl qu(songFile); + QUrlQuery u(qu); + #endif + songFile=u.hasQueryItem("file") ? u.queryItemValue("file") : QString(); + } + return songFile; +} + SongView::SongView(QWidget *p) - : View(p, QStringList() << i18n("Lyrics") << i18n("Information")) + : View(p, QStringList() << i18n("Lyrics") << i18n("Information") << i18n("Tags")) , scrollTimer(0) , songPos(0) , currentProvider(-1) @@ -114,6 +130,7 @@ SongView::SongView(QWidget *p) , job(0) , currentProv(0) , infoNeedsUpdating(true) + , tagsNeedsUpdating(true) { scrollAction = ActionCollection::get()->createAction("scrolllyrics", i18n("Scroll Lyrics"), "go-down"); refreshAction = ActionCollection::get()->createAction("refreshlyrics", i18n("Refresh Lyrics"), "view-refresh"); @@ -390,13 +407,18 @@ void SongView::scroll() void SongView::curentViewChanged() { - if (infoNeedsUpdating) { - loadInfo(); + switch (currentView()) { + case Page_Information: loadInfo(); break; + case Page_Tags: loadTags(); break; + default: break; } } void SongView::loadInfo() { + if (!infoNeedsUpdating) { + return; + } infoNeedsUpdating=false; foreach (const QString &lang, engine->getLangs()) { QString prefix=engine->getPrefix(lang); @@ -419,6 +441,114 @@ void SongView::loadInfo() searchForInfo(); } +static QString addEntry(const QString &key, const QString &value) +{ + return value.isEmpty() ? QString() : QString("%1: %2").arg(key).arg(value); +} + +void SongView::loadTags() +{ + if (!tagsNeedsUpdating) { + return; + } + tagsNeedsUpdating=false; + + QString tagInfo; + #ifdef TAGLIB_FOUND + if (!currentSong.isStandardStream() && !MPDConnection::self()->getDetails().dir.startsWith(QLatin1String("http:/"))) { + QString songFile=actualFile(currentSong); + if (!songFile.isEmpty()) { + static QMap tagMap; + static QMap tagTimeMap; + static const QString constTitle=QLatin1String("TITLE"); + static const QString constPerformer=QLatin1String("PERFORMER:"); + static const QString constAudio=QLatin1String("X-AUDIO:"); + + if (tagMap.isEmpty()) { + tagMap.insert(QLatin1String("ALBUM"), i18n("Album")); + tagMap.insert(QLatin1String("ARTIST"), i18n("Artist")); + tagMap.insert(QLatin1String("ALBUMARTIST"), i18n("Album artist")); + tagMap.insert(QLatin1String("SUBTITLE"), i18n("Subtitle")); + tagMap.insert(QLatin1String("TRACKNUMBER"), i18n("Track number")); + tagMap.insert(QLatin1String("DISCNUMBER"), i18n("Disc number")); + tagMap.insert(QLatin1String("DATE"), i18n("Date")); + tagMap.insert(QLatin1String("ORIGINALDATE"), i18n("Original date")); + tagMap.insert(QLatin1String("GENRE"), i18n("Genre")); + tagMap.insert(QLatin1String("COMMENT"), i18n("Comment")); + tagMap.insert(QLatin1String("TITLESORT"), i18n("Title sort")); + tagMap.insert(QLatin1String("ALBUMSORT"), i18n("Album sort")); + tagMap.insert(QLatin1String("ARTISTSORT"), i18n("Artist sort")); + tagMap.insert(QLatin1String("ALBUMARTISTSORT"), i18n("Album artist sort")); + tagMap.insert(QLatin1String("COMPOSER"), i18n("Composer")); + tagMap.insert(QLatin1String("LYRICIST"), i18n("Lyricist")); + tagMap.insert(QLatin1String("CONDUCTOR"), i18n("Conductor")); + tagMap.insert(QLatin1String("REMIXER"), i18n("Remixer")); + tagMap.insert(QLatin1String("COPYRIGHT"), i18n("Copyright")); + tagMap.insert(QLatin1String("ENCODEDBY"), i18n("Encoded by")); + tagMap.insert(QLatin1String("MOOD"), i18n("Mood")); + tagMap.insert(QLatin1String("MEDIA"), i18n("Media")); + tagMap.insert(QLatin1String("LABEL"), i18n("Label")); + tagMap.insert(QLatin1String("CATALOGNUMBER"), i18n("Catalogue number")); + tagMap.insert(QLatin1String("ENCODING"), i18n("Encoder")); + tagMap.insert(QLatin1String("REPLAYGAIN_ALBUM_GAIN"), i18n("ReplayGain album gain")); + tagMap.insert(QLatin1String("REPLAYGAIN_ALBUM_PEAK"), i18n("ReplayGain album peak")); + tagMap.insert(QLatin1String("REPLAYGAIN_TRACK_GAIN"), i18n("ReplayGain track gain")); + tagMap.insert(QLatin1String("REPLAYGAIN_TRACK_PEAK"), i18n("ReplayGain track peak")); + tagMap.insert(constAudio+QLatin1String("BITRATE"), i18n("Bitrate")); + tagMap.insert(constAudio+QLatin1String("SAMPLERATE"), i18n("Sample rate")); + tagMap.insert(constAudio+QLatin1String("CHANNELS"), i18n("Channels")); + + tagTimeMap.insert(QLatin1String("TAGGING TIME"), i18n("Tagging time")); + } + + QMap allTags=Tags::readAll(MPDConnection::self()->getDetails().dir+actualFile(currentSong)); + + if (!allTags.isEmpty()) { + QMap::ConstIterator it=allTags.constBegin(); + QMap::ConstIterator end=allTags.constEnd(); + bool addedAudioSep=false; + + for (; it!=end; ++it) { + if (it.key()==constTitle) { + continue; + } + if (tagInfo.isEmpty()) { + tagInfo=QLatin1String(""); + } else if (!addedAudioSep && it.key().startsWith(constAudio)) { + addedAudioSep=true; + tagInfo+=QLatin1String(""); + } + if (tagMap.contains(it.key())) { + tagInfo+=addEntry(tagMap[it.key()], it.value()); + } else if (tagTimeMap.contains(it.key())) { + tagInfo+=addEntry(tagTimeMap[it.key()], QString(it.value()).replace("T", " ")); + } else if (it.key().startsWith(constPerformer)) { + tagInfo+=addEntry(i18n("Performer (%1)", Song::capitalize(it.key().mid(constPerformer.length()))), it.value()); + } else { + tagInfo+=addEntry(Song::capitalize(it.key()), it.value()); + } + } + } + } + } + #endif + + if (tagInfo.isEmpty()) { + tagInfo=QLatin1String("
"); + tagInfo+=addEntry(i18n("Artist"), currentSong.artist); + tagInfo+=addEntry(i18n("Album artist"), currentSong.albumartist); + tagInfo+=addEntry(i18n("Composer"), currentSong.composer); + //tagInfo+=addEntry(i18n("Performer"), currentSong.performer); + tagInfo+=addEntry(i18n("Album"), currentSong.album); + tagInfo+=addEntry(i18n("Disc number"), 0==currentSong.disc ? QString() : QString::number(currentSong.disc)); + tagInfo+=addEntry(i18n("Track number"), 0==currentSong.track ? QString() : QString::number(currentSong.track)); + tagInfo+=addEntry(i18n("Genre"), currentSong.genres().join(", ")); + tagInfo+=addEntry(i18n("Year"), 0==currentSong.track ? QString() : QString::number(currentSong.year)); + } + tagInfo+=QLatin1String("
"); + setHtml(tagInfo, Page_Tags); +} + void SongView::refreshInfo() { if (currentSong.isEmpty()) { @@ -491,7 +621,7 @@ void SongView::abort() text->setText(QString()); // Set lyrics file anyway - so that editing is enabled! lyricsFile=Settings::self()->storeLyricsInMpdDir() && !currentSong.isNonMPD() - ? mpdFilePath(currentSong) + ? mpdLyricsFilePath(currentSong) : lyricsCacheFileName(currentSong); setMode(Mode_Display); } @@ -507,7 +637,7 @@ void SongView::update(const Song &s, bool force) if (s.isEmpty() || s.title.isEmpty() || s.artist.isEmpty()) { currentSong=s; - infoNeedsUpdating=false; + infoNeedsUpdating=tagsNeedsUpdating=false; clear(); abort(); return; @@ -538,12 +668,9 @@ void SongView::update(const Song &s, bool force) return; } + infoNeedsUpdating=tagsNeedsUpdating=true; setHeader(song.title); - if (Page_Information==currentView()) { - loadInfo(); - } else { - infoNeedsUpdating=true; - } + curentViewChanged(); // Only reset the provider if the refresh was an automatic one or if the song has // changed. Otherwise we'll keep the provider so the user can cycle through the lyrics @@ -553,19 +680,8 @@ void SongView::update(const Song &s, bool force) } if (!MPDConnection::self()->getDetails().dir.isEmpty() && !song.file.isEmpty() && !song.isNonMPD()) { - QString songFile=song.filePath(); - - if (song.isCantataStream()) { - #if QT_VERSION < 0x050000 - QUrl u(songFile); - #else - QUrl qu(songFile); - QUrlQuery u(qu); - #endif - songFile=u.hasQueryItem("file") ? u.queryItemValue("file") : QString(); - } - - QString mpdLyrics=mpdFilePath(songFile); + QString songFile=actualFile(song); + QString mpdLyrics=mpdLyricsFilePath(songFile); if (MPDConnection::self()->getDetails().dir.startsWith(QLatin1String("http:/"))) { QUrl url(mpdLyrics); @@ -661,7 +777,7 @@ void SongView::lyricsReady(int id, QString lyrics) text->setText(fixNewLines(plain)); lyricsFile=QString(); if (! ( Settings::self()->storeLyricsInMpdDir() && !currentSong.isNonMPD() && - saveFile(mpdFilePath(currentSong))) ) { + saveFile(mpdLyricsFilePath(currentSong))) ) { saveFile(lyricsCacheFileName(currentSong, true)); } setMode(Mode_Display); @@ -686,7 +802,7 @@ bool SongView::saveFile(const QString &fileName) QString SongView::mpdFileName() const { return currentSong.file.isEmpty() || MPDConnection::self()->getDetails().dir.isEmpty() || currentSong.isNonMPD() - ? QString() : mpdFilePath(currentSong); + ? QString() : mpdLyricsFilePath(currentSong); } QString SongView::cacheFileName() const @@ -706,7 +822,7 @@ void SongView::getLyrics() currentProvider=-1; // Set lyrics file anyway - so that editing is enabled! lyricsFile=Settings::self()->storeLyricsInMpdDir() && !currentSong.isNonMPD() - ? mpdFilePath(currentSong) + ? mpdLyricsFilePath(currentSong) : lyricsCacheFileName(currentSong); setMode(Mode_Display); } @@ -726,7 +842,7 @@ void SongView::setMode(Mode m) saveAction->setEnabled(Mode_Edit==m); cancelEditAction->setEnabled(Mode_Edit==m); editAction->setEnabled(editable); - delAction->setEnabled(editable && !MPDConnection::self()->getDetails().dir.isEmpty() && QFile::exists(mpdFilePath(currentSong))); + delAction->setEnabled(editable && !MPDConnection::self()->getDetails().dir.isEmpty() && QFile::exists(mpdLyricsFilePath(currentSong))); refreshAction->setEnabled(editable); setEditable(Mode_Edit==m); if (scrollAction->isChecked()) { diff --git a/context/songview.h b/context/songview.h index 41756919c..5126f34d7 100644 --- a/context/songview.h +++ b/context/songview.h @@ -47,7 +47,8 @@ class SongView : public View enum Pages { Page_Lyrics, - Page_Information + Page_Information, + Page_Tags }; public: @@ -89,6 +90,7 @@ private Q_SLOTS: private: void loadInfo(); + void loadTags(); void searchForInfo(); void hideSpinner(); void abort(); @@ -127,6 +129,7 @@ private: UltimateLyricsProvider *currentProv; bool infoNeedsUpdating; + bool tagsNeedsUpdating; Action *refreshInfoAction; Action *cancelInfoJobAction; ContextEngine *engine; diff --git a/tags/taghelper.cpp b/tags/taghelper.cpp index 8b6a5579f..caf0ea6b3 100644 --- a/tags/taghelper.cpp +++ b/tags/taghelper.cpp @@ -155,6 +155,8 @@ void TagHelper::process() outStream << (int)Tags::embedImage(fileName, cover); } else if (QLatin1String("oggMimeType")==request) { outStream << Tags::oggMimeType(fileName); + } else if (QLatin1String("readAll")==request) { + outStream << Tags::readAll(fileName); } else { qApp->exit(); } diff --git a/tags/taghelperiface.cpp b/tags/taghelperiface.cpp index 05f97f702..f349a80fe 100644 --- a/tags/taghelperiface.cpp +++ b/tags/taghelperiface.cpp @@ -230,6 +230,21 @@ QString TagHelperIface::oggMimeType(const QString &fileName) return resp; } +QMap TagHelperIface::readAll(const QString &fileName) +{ + DBUG << fileName; + QMap resp; + QByteArray message; + QDataStream outStream(&message, QIODevice::WriteOnly); + outStream << QString(__FUNCTION__) << fileName; + Reply reply=sendMessage(message); + if (reply.status) { + QDataStream inStream(reply.data); + inStream >> resp; + } + return resp; +} + TagHelperIface::Reply TagHelperIface::sendMessage(const QByteArray &msg) { QMutexLocker locker(&mutex); diff --git a/tags/taghelperiface.h b/tags/taghelperiface.h index f4146c17e..4368e69de 100644 --- a/tags/taghelperiface.h +++ b/tags/taghelperiface.h @@ -29,6 +29,7 @@ #include #include #include +#include class QLocalServer; class QLocalSocket; @@ -66,6 +67,7 @@ public: int updateReplaygain(const QString &fileName, const Tags::ReplayGain &rg); int embedImage(const QString &fileName, const QByteArray &cover); QString oggMimeType(const QString &fileName); + QMap readAll(const QString &fileName); private: bool helperIsRunning(); diff --git a/tags/tags.cpp b/tags/tags.cpp index 6a882e4ae..d17234a69 100644 --- a/tags/tags.cpp +++ b/tags/tags.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #ifdef TAGLIB_ASF_FOUND @@ -1308,6 +1309,30 @@ QString oggMimeType(const QString &fileName) return QLatin1String("audio/ogg"); } +QMap readAll(const QString &fileName) +{ + QMap allTags; + TagLib::FileRef fileref = getFileRef(fileName); + if (fileref.isNull()) { + return allTags; + } + + TagLib::PropertyMap properties=fileref.file()->properties(); + TagLib::PropertyMap::ConstIterator it = properties.begin(); + TagLib::PropertyMap::ConstIterator end = properties.end(); + for (; it!=end; ++it) { + allTags.insert(tString2QString(it->first.upper()), tString2QString(it->second.toString(", "))); + } + + if (fileref.audioProperties()) { + TagLib::AudioProperties *properties = fileref.audioProperties(); + allTags.insert(QLatin1String("X-AUDIO:BITRATE"), QString("%1 kb/s").arg(properties->bitrate())); + allTags.insert(QLatin1String("X-AUDIO:SAMPLERATE"), QString("%1 Hz").arg(properties->sampleRate())); + allTags.insert(QLatin1String("X-AUDIO:CHANNELS"), QString::number(properties->channels())); + } + return allTags; +} + QString id3Genre(int id) { // Clementine: In theory, genre 0 is "blues"; in practice it's invalid. diff --git a/tags/tags.h b/tags/tags.h index cd3ad417f..d918361eb 100644 --- a/tags/tags.h +++ b/tags/tags.h @@ -26,7 +26,9 @@ #include "mpd/song.h" #include "support/utils.h" +#include "support/localize.h" #include "config.h" +#include #include #include @@ -77,6 +79,7 @@ namespace Tags inline Update updateReplaygain(const QString &fileName, const ReplayGain &rg) { return (Update)TagHelperIface::self()->updateReplaygain(fileName, rg); } inline Update embedImage(const QString &fileName, const QByteArray &cover) { return (Update)TagHelperIface::self()->embedImage(fileName, cover); } inline QString oggMimeType(const QString &fileName) { return TagHelperIface::self()->oggMimeType(fileName); } + inline QMap readAll(const QString &fileName) { return TagHelperIface::self()->readAll(fileName); } #else inline void init() { } inline void stop() { } @@ -90,6 +93,7 @@ namespace Tags extern Update updateReplaygain(const QString &fileName, const ReplayGain &rg); extern Update embedImage(const QString &fileName, const QByteArray &cover); extern QString oggMimeType(const QString &fileName); + extern QMap readAll(const QString &fileName); #endif extern QString id3Genre(int id); }