/* * Cantata * * Copyright (c) 2011-2019 Craig Drummond * */ /* * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and * Roeland Douma (roeland AT rullzer DOT com) * * This file is part of QtMPC. * * QtMPC 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. * * QtMPC 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 QtMPC. If not, see . */ #include #include "config.h" #include "song.h" #if !defined CANTATA_NO_UI_FUNCTIONS #include "online/onlineservice.h" #include "online/podcastservice.h" #endif #include #include #include #include #include #include #include #include //static const quint8 constOnlineDiscId=0xEE; const QString Song::constCddaProtocol=QLatin1String("/[cantata-cdda]/"); const QString Song::constMopidyLocal=QLatin1String("local:track:"); const QString Song::constForkedDaapdLocal=QLatin1String("file:"); static QString unknownStr; static QString variousArtistsStr; static QString singleTracksStr; static bool useOrigYear = false; const QString & Song::unknown() { return unknownStr; } const QString & Song::variousArtists() { return variousArtistsStr; } const QString & Song::singleTracks() { return singleTracksStr; } void Song::initTranslations() { unknownStr=QObject::tr("Unknown"); variousArtistsStr=QObject::tr("Various Artists"); singleTracksStr=QObject::tr("Single Tracks"); } // When displaying albums, we use the 1st track's year as the year of the album. // The map below stores the mapping from artist+album to year. // This way the grouped view can find this quickly... static QHash albumYears; void Song::storeAlbumYear(const Song &s) { albumYears.insert(s.albumKey(), s.displayYear()); } int Song::albumYear(const Song &s) { QHash::ConstIterator it=albumYears.find(s.albumKey()); return it==albumYears.end() ? s.displayYear() : it.value(); } static int songType(const Song &s) { static QStringList extensions=QStringList() << QLatin1String(".flac") << QLatin1String(".wav") << QLatin1String(".dff") << QLatin1String(".dsf") << QLatin1String(".aac") << QLatin1String(".m4a") << QLatin1String(".m4b") << QLatin1String(".m4p") << QLatin1String(".mp4") << QLatin1String(".ogg") << QLatin1String(".opus") << QLatin1String(".mp3") << QLatin1String(".wma"); for (int i=0; i &songs) { qSort(songs.begin(), songs.end(), songTypeSort); } QString Song::decodePath(const QString &file, bool cdda) { if (cdda) { return QString(file).replace("/", "_").replace(":", "_"); } if (file.startsWith(constMopidyLocal)) { return QUrl::fromPercentEncoding(file.mid(constMopidyLocal.length()).toLatin1()); } if (file.startsWith(constForkedDaapdLocal)) { return file.mid(constForkedDaapdLocal.length()); } return file; } QString Song::encodePath(const QString &file) { return constMopidyLocal+QString(QUrl::toPercentEncoding(file, "/")); } static QSet compGenres; const QSet &Song::composerGenres() { return compGenres; } void Song::setComposerGenres(const QSet &g) { compGenres=g; } Song::Song() : extraFields(0) , priority(0) , disc(0) , blank(0) , time(0) , track(0) , origYear(0) , year(0) , type(Standard) , guessed(false) , id(-1) , size(0) , rating(Rating_Null) , lastModified(0) , key(Null_Key) { } Song & Song::operator=(const Song &s) { id = s.id; file = s.file; time = s.time; album = s.album; artist = s.artist; albumartist = s.albumartist; title = s.title; track = s.track; // pos = s.pos; disc = s.disc; blank = s.blank; priority = s.priority; origYear = s.origYear; year = s.year; for (int i=0; i0 && dot constSeparators=QSet() << QLatin1Char(' ') << QLatin1Char('-') << QLatin1Char('_') << QLatin1Char('.'); int separator=0; for (const QChar &sep: constSeparators) { separator=title.indexOf(sep); if (1==separator || 2==separator) { break; } } if ( (1==separator && title[separator-1].isDigit()) || (2==separator && title[separator-2].isDigit() && title[separator-1].isDigit()) ) { if (0==track) { track=title.left(separator).toInt(); } title=title.mid(separator+1); while (!title.isEmpty() && constSeparators.contains(title[0])) { title=title.mid(1); } } } } } void Song::revertGuessedTags() { title=artist=album=unknownStr; } void Song::fillEmptyFields() { if (artist.isEmpty()) { artist = unknownStr; blank |= BlankArtist; } if (album.isEmpty()) { album = unknownStr; blank |= BlankAlbum; } if (title.isEmpty()) { title = unknownStr; blank |= BlankTitle; } if (genres[0].isEmpty()) { genres[0]=unknownStr; } } struct KeyStore { KeyStore() : currentKey(0) { } quint16 currentKey; QHash keys; }; static QHash storeMap; static QMutex storeMapMutex; void Song::clearKeyStore(int location) { QMutexLocker locker(&storeMapMutex); storeMap.remove(location); } QString Song::displayAlbum(const QString &albumName, quint16 albumYear) { return albumYear>0 ? albumName+QLatin1String(" (")+QString::number(albumYear)+QLatin1Char(')') : albumName; } static QSet prefixesToIngore=QSet() << QLatin1String("The"); QSet Song::ignorePrefixes() { return prefixesToIngore; } void Song::setIgnorePrefixes(const QSet &prefixes) { prefixesToIngore=prefixes; } static QString ignorePrefix(const QString &str) { for (const QString &p: prefixesToIngore) { if (str.startsWith(p+QLatin1Char(' '))) { return str.mid(p.length()+1)+QLatin1String(", ")+p; } } return QString(); } QString Song::sortString(const QString &str) { QString sort=ignorePrefix(str); if (sort.isEmpty()) { sort=str; } sort=sort.remove('.'); return sort==str ? QString() : sort; } bool Song::useOriginalYear() { return useOrigYear; } void Song::setUseOriginalYear(bool u) { useOrigYear = u; } quint16 Song::setKey(int location) { if (isStandardStream()) { key=0; return 0; } QString songKey(albumKey()); QMutexLocker locker(&storeMapMutex); KeyStore &store=storeMap[location]; QHash::ConstIterator it=store.keys.find(songKey); if (it!=store.keys.end()) { key=it.value(); } else { store.currentKey++; // Key 0 is for streams, so we need to increment before setting... store.keys.insert(songKey, store.currentKey); key=store.currentKey; } return key; } bool Song::isUnknownAlbum() const { return (album.isEmpty() || album==unknownStr) && (albumArtist().isEmpty() || albumArtist()==unknownStr); } bool Song::isInvalid() const { return 0==time && guessed && !file.contains("://") && (genres[0].isEmpty() || genres[0]==unknownStr) && name().isEmpty(); } void Song::clear() { id = -1; file.clear(); time = 0; album.clear(); artist.clear(); title.clear(); track = 0; // pos = 0; disc = 0; blank = 0; year = 0; origYear = 0; for (int i=0; i0 ? origYear : year; } QString Song::entryName() const { if (title.isEmpty()) { return file; } return title+constFieldSep+artist+constFieldSep+album; } QString Song::albumArtistOrComposer() const { if (useComposer()) { QString c=composer(); if (!c.isEmpty()) { return c; } } return albumArtist(); } QString Song::trackArtistOrComposer() const { if (useComposer()) { QString c=composer(); if (!c.isEmpty()) { return c; } } return artist; } QString Song::albumName() const { if (useComposer()) { QString c=composer(); if (!c.isEmpty() && c!=albumArtist()) { return album+QLatin1String(" (")+albumArtist()+QLatin1Char(')'); } } return album; } QString Song::albumId() const { QString mb=mbAlbumId(); return mb.isEmpty() ? album : mb; } QString Song::artistSong() const { //return artist+constSep+title; return title+constSep+artist; } QString Song::trackAndTitleStr(bool showArtistIfDifferent) const { #if !defined CANTATA_NO_UI_FUNCTIONS if ((OnlineSvrTrack==type || Song::CantataStream) && OnlineService::showLogoAsCover(*this)) { return artistSong(); } #endif // if (isFromOnlineService()) { // return (disc>0 && disc!=constOnlineDiscId ? (QString::number(disc)+QLatin1Char('.')) : QString())+ // (track>0 ? (track>9 ? QString::number(track) : (QLatin1Char('0')+QString::number(track))) : QString())+ // QLatin1Char(' ')+(addArtist ? artistSong() : title); // } return //(disc>0 ? (QString::number(disc)+QLatin1Char('.')) : QString())+ (track>0 && SingleTracks!=type ? (track>9 ? QString::number(track)+QLatin1Char(' ') : (QLatin1Char('0')+QString::number(track)+QLatin1Char(' '))) : QString())+ (showArtistIfDifferent && diffArtist() ? artistSong() : title) + (origYear>0 && origYear != year ? QLatin1String(" (")+QString::number(origYear)+QLatin1Char(')') : QString()); } #ifndef CANTATA_NO_UI_FUNCTIONS static void addField(const QString &name, const QString &val, QString &tt) { if (!val.isEmpty()) { tt+=QString("%1:  %2").arg(name).arg(val); } } #endif QString Song::toolTip() const { #ifdef CANTATA_NO_UI_FUNCTIONS return QString(); #else QString toolTip=QLatin1String(""); addField(QObject::tr("Title"), title, toolTip); addField(QObject::tr("Artist"), artist, toolTip); if (albumartist!=artist) { addField(QObject::tr("Album artist"), albumartist, toolTip); } addField(QObject::tr("Composer"), composer(), toolTip); addField(QObject::tr("Performer"), performer(), toolTip); addField(QObject::tr("Album"), albumName(), toolTip); if (track>0) { addField(QObject::tr("Track number"), QString::number(track), toolTip); } if (disc>0) { addField(QObject::tr("Disc number"), QString::number(disc), toolTip); } addField(QObject::tr("Genre"), displayGenre(), toolTip); if (year>0) { addField(QObject::tr("Year"), QString::number(year), toolTip); } if (origYear>0) { addField(QObject::tr("Original Year"), QString::number(origYear), toolTip); } if (time>0) { addField(QObject::tr("Length"), Utils::formatTime(time, true), toolTip); } toolTip+=QLatin1String("
"); if (isNonMPD()) { return toolTip; } return toolTip+QLatin1String("

")+filePath()+QLatin1String(""); #endif } QString Song::displayGenre() const { QString g=genres[0]; for (int i=1; i0 && sepPos")+albumText+QLatin1String(""); } return artist.isEmpty() ? QObject::tr("%1 on %2", "Song on Album").arg(title).arg(albumText) : QObject::tr("%1 by %2 on %3", "Song by Artist on Album").arg(title).arg(artist).arg(albumText); } QString Song::mainText() const { if (isEmpty()) { return QString(); } QString n = name(); if (isStream() && !isCantataStream() && !isCdda() && !isDlnaStream()) { return n.isEmpty() ? Song::unknown() : n; } else if (title.isEmpty() && artist.isEmpty() && (!n.isEmpty() || !file.isEmpty())) { return n.isEmpty() ? file : n; } else { return title+(origYear>0 && !Song::useOriginalYear() && origYear!=year ? QLatin1String(" (")+QString::number(origYear)+QLatin1Char(')') : QString()); } } QString Song::subText() const { if (isEmpty()) { return QString(); } if (isStream() && !isCantataStream() && !isCdda() && !isDlnaStream()) { if (artist.isEmpty() && title.isEmpty() && !name().isEmpty()) { return QObject::tr("(Stream)"); } else { return artist.isEmpty() ? title : artistSong(); } } else if (album.isEmpty() && artist.isEmpty() && (!useComposer() || composer().isEmpty())) { return mainText().isEmpty() ? QString() : Song::unknown(); } else { QString comp = useComposer() ? composer() : QString(); if (album.isEmpty()) { return artist.isEmpty() ? comp : comp.isEmpty() ? artist : (comp + constSep + artist); } else { // Artist here is always artist (or 'composer - artist'), and not album artist return (artist.isEmpty() ? comp : comp.isEmpty() ? artist : (comp + constSep + artist)) + constSep+displayAlbum(!comp.isEmpty()); } } } bool Song::useComposer() const { if (compGenres.isEmpty()) { return false; } for (int i=0; i>(QDataStream &stream, Song &song) { quint16 type; quint16 year; quint8 disc; bool guessed; stream >> song.id >> song.file >> song.album >> song.artist >> song.albumartist >> song.title >> disc >> song.priority >> song.time >> song.track >> year // >> song.origYear >> type >> guessed >> song.size >> song.extra >> song.extraFields; song.type=(Song::Type)type; song.year=year; song.guessed=guessed; song.disc=disc; for (int i=0; i> song.genres[i]; } return stream; }