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);
}