Add a 'tags' view to tracks view to list all tags via taglib

This commit is contained in:
craig.p.drummond
2014-06-11 18:24:13 +00:00
committed by craig.p.drummond
parent 4538f73e9b
commit dfd5980c6f
8 changed files with 200 additions and 33 deletions

View File

@@ -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.

View File

@@ -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", "<br/>");
}
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("<tr><td>%1:&nbsp;</td><td>%2</td></tr>").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<QString, QString> tagMap;
static QMap<QString, QString> 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<QString, QString> allTags=Tags::readAll(MPDConnection::self()->getDetails().dir+actualFile(currentSong));
if (!allTags.isEmpty()) {
QMap<QString, QString>::ConstIterator it=allTags.constBegin();
QMap<QString, QString>::ConstIterator end=allTags.constEnd();
bool addedAudioSep=false;
for (; it!=end; ++it) {
if (it.key()==constTitle) {
continue;
}
if (tagInfo.isEmpty()) {
tagInfo=QLatin1String("<table>");
} else if (!addedAudioSep && it.key().startsWith(constAudio)) {
addedAudioSep=true;
tagInfo+=QLatin1String("<tr/>");
}
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("<table>");
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("</table>");
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()) {

View File

@@ -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;

View File

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

View File

@@ -230,6 +230,21 @@ QString TagHelperIface::oggMimeType(const QString &fileName)
return resp;
}
QMap<QString, QString> TagHelperIface::readAll(const QString &fileName)
{
DBUG << fileName;
QMap<QString, QString> 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);

View File

@@ -29,6 +29,7 @@
#include <QString>
#include <QMutex>
#include <QSemaphore>
#include <QMap>
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<QString, QString> readAll(const QString &fileName);
private:
bool helperIsRunning();

View File

@@ -36,6 +36,7 @@
#include <QString>
#include <QStringList>
#include <QTextCodec>
#include <taglib/tpropertymap.h>
#include <taglib/fileref.h>
#include <taglib/aifffile.h>
#ifdef TAGLIB_ASF_FOUND
@@ -1308,6 +1309,30 @@ QString oggMimeType(const QString &fileName)
return QLatin1String("audio/ogg");
}
QMap<QString, QString> readAll(const QString &fileName)
{
QMap<QString, QString> 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.

View File

@@ -26,7 +26,9 @@
#include "mpd/song.h"
#include "support/utils.h"
#include "support/localize.h"
#include "config.h"
#include <QMap>
#include <QImage>
#include <QMetaType>
@@ -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<QString, QString> 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<QString, QString> readAll(const QString &fileName);
#endif
extern QString id3Genre(int id);
}