1 /*
2  * Cantata
3  *
4  * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5  *
6  */
7 
8 /*
9  * Copyright (c) 2008 Sander Knopper (sander AT knopper DOT tk) and
10  *                    Roeland Douma (roeland AT rullzer DOT com)
11  *
12  * This file is part of QtMPC.
13  *
14  * QtMPC is free software: you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation, either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * QtMPC is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with QtMPC.  If not, see <http://www.gnu.org/licenses/>.
26  */
27 
28 #ifndef SONG_H
29 #define SONG_H
30 
31 #include <QString>
32 #include <QSet>
33 #include <QHash>
34 #include <QMetaType>
35 #include "config.h"
36 #include "support/utils.h"
37 #include "cuefile.h"
38 
39 struct Song
40 {
41     enum Constants {
42         Null_Key = 0xFFFF,
43 
44         Rating_Step = 2,
45         Rating_Max = 10,
46         Rating_Requested = 0xFE,
47         Rating_Null = 0xFF
48     };
49 
50     static const int constNumGenres = 4;
51     static const QLatin1Char constFieldSep;
52     static const QString constSep;
53 
54     static const QSet<QString> & composerGenres();
55     static void setComposerGenres(const QSet<QString> &g);
56 
57     enum ExtraTags {
58         Composer             = 0x0001,
59         Performer            = 0x0002,
60         Comment              = 0x0004,
61         MusicBrainzAlbumId   = 0x0008,
62         Name                 = 0x0010,
63 
64         AlbumSort            = 0x0020,
65         ArtistSort           = 0x0040,
66         AlbumArtistSort      = 0x0080,
67 
68         // These are not real tags - but fields used elsewhere in the code...
69         PodcastPublishedDate = 0x0100,
70         LocalPath            = 0x0200, // Podcasts and HTTP files
71         PodcastImage         = 0x0400,
72         OnlineServiceName    = 0x0800,
73         OnlineImageUrl       = 0x1000,
74         OnlineImageCacheName = 0x2000,
75         DeviceId             = 0x4000,
76         DecodedPath          = 0x8000
77     };
78 
79     enum Type {
80         Standard        = 0,
81         SingleTracks    = 1,
82         Playlist        = 2,
83         Stream          = 3,
84         CantataStream   = 4,
85         Cdda            = 5,
86         OnlineSvrTrack  = 6,
87         LocalFile       = 7
88     };
89 
90     enum Blank {
91         BlankTitle  = 0x01,
92         BlankArtist = 0x02,
93         BlankAlbum  = 0x04
94     };
95 
96     QString file;
97     QString album;
98     QString artist;
99     QString albumartist;
100     QString title;
101     QString genres[constNumGenres];
102     QHash<quint16, QString> extra;
103     quint16 extraFields;
104     mutable quint8 priority;
105     quint8 disc:5;
106     quint8 blank:3; // Which field were blank, and Cantata set to Unknown
107     quint16 time;
108     quint16 track;
109     quint16 origYear;
110     quint16 year;
111     mutable Type type : 7;
112     mutable bool guessed : 1;
113     qint32 id;
114     qint32 size;
115     mutable quint8 rating;
116     uint lastModified;
117 
118     // Only used in PlayQueue/PlayLists...
119     quint16 key;
120 
121     static const QString & unknown();
122     static const QString & variousArtists();
123     static const QString & singleTracks();
124     static void initTranslations();
125     static const QString constCddaProtocol;
126     static const QString constMopidyLocal;
127     static const QString constForkedDaapdLocal;
128     static void storeAlbumYear(const Song &s);
129     static int albumYear(const Song &s);
130     static void sortViaType(QList<Song> &songs);
131     static QString decodePath(const QString &file, bool cdda=false);
132     static QString encodePath(const QString &file);
133     static void clearKeyStore(int location);
134     static QString displayAlbum(const QString &albumName, quint16 albumYear);
isComposerGenreSong135     static bool isComposerGenre(const QString &genre) { return composerGenres().contains(genre); }
136     static QSet<QString> ignorePrefixes();
137     static void setIgnorePrefixes(const QSet<QString> &prefixes);
138     static QString sortString(const QString &str);
139     static bool useOriginalYear();
140     static void setUseOriginalYear(bool u);
141 
142     Song();
SongSong143     Song(const Song &o) { *this=o; }
144     Song & operator=(const Song &o);
145     bool operator==(const Song &o) const;
146     bool operator!=(const Song &o) const { return !(*this==o); }
147     bool operator<(const Song &o) const;
148     int compareTo(const Song &o) const;
~SongSong149     virtual ~Song() { }
150     bool isEmpty() const;
isDifferentSong151     bool isDifferent(const Song &s) const { return file!=s.file || year!=s.year || track!=s.track || disc!=s.disc || artist!=s.artist || album!=s.album || title!=s.title || name()!=s.name(); }
152     bool sameMetadata(const Song &o) const;
153     void guessTags();
154     void revertGuessedTags();
155     void fillEmptyFields();
156     quint16 setKey(int location);
157     virtual void clear();
158     void addGenre(const QString &g);
159     quint16 displayYear() const;
160     QString entryName() const;
161     QString albumArtistOrComposer() const;
162     QString trackArtistOrComposer() const;
163     QString albumName() const;
164     QString albumId() const;
165     QString artistSong() const;
albumArtistSong166     const QString & albumArtist() const { return albumartist.isEmpty() ? artist : albumartist; }
displayTitleSong167     QString displayTitle() const { return !albumartist.isEmpty() && albumartist!=artist ? artistSong() : title; }
168     QString trackAndTitleStr(bool showArtistIfDifferent=true) const;
169     QString toolTip() const;
170     QString displayGenre() const;
firstGenreSong171     const QString & firstGenre() const { return genres[0]; }
172     int compareGenres(const Song &o) const;
173 
extraFieldSong174     QString extraField(quint16 f) const { return hasExtraField(f) ? extra[f] : QString(); }
hasExtraFieldSong175     bool hasExtraField(quint16 f) const { return extraFields&f; }
176     void setExtraField(quint16 f, const QString &v);
nameSong177     QString name() const { return extraField(Name); }
setNameSong178     void setName(const QString &v) { setExtraField(Name, v); }
hasNameSong179     bool hasName() const { return hasExtraField(Name); }
180 
mbAlbumIdSong181     QString mbAlbumId() const { return extraField(MusicBrainzAlbumId); }
setMbAlbumIdSong182     void setMbAlbumId(const QString &v) { setExtraField(MusicBrainzAlbumId, v); }
hasMbAlbumIdSong183     bool hasMbAlbumId() const { return hasExtraField(MusicBrainzAlbumId); }
composerSong184     QString composer() const { return extraField(Composer); }
setComposerSong185     void setComposer(const QString &v) { setExtraField(Composer, v); }
hasComposerSong186     bool hasComposer() const { return hasExtraField(Composer); }
performerSong187     QString performer() const { return extraField(Performer); }
setPerformerSong188     void setPerformer(const QString &v) { setExtraField(Performer, v); }
hasPerformerSong189     bool hasPerformer() const { return hasExtraField(Performer); }
commentSong190     QString comment() const { return extraField(Comment); }
setCommentSong191     void setComment(const QString &v) { setExtraField(Comment, v); }
hasCommentSong192     bool hasComment() const { return hasExtraField(Comment); }
albumSortSong193     QString albumSort() const { return extraField(AlbumSort); }
setAlbumSortSong194     void setAlbumSort(const QString &v) { setExtraField(AlbumSort, v); }
hasAlbumSortSong195     bool hasAlbumSort() const { return hasExtraField(AlbumSort); }
artistSortSong196     QString artistSort() const { return extraField(ArtistSort); }
setArtistSortSong197     void setArtistSort(const QString &v) { setExtraField(ArtistSort, v); }
hasArtistSortSong198     bool hasArtistSort() const { return hasExtraField(ArtistSort); }
albumArtistSortSong199     QString albumArtistSort() const { return extraField(AlbumArtistSort); }
setAlbumArtistSortSong200     void setAlbumArtistSort(const QString &v) { setExtraField(AlbumArtistSort, v); }
hasAlbumArtistSortSong201     bool hasAlbumArtistSort() const { return hasExtraField(AlbumArtistSort); }
202 
artistSortStringSong203     QString artistSortString() const { return hasAlbumArtistSort() ? albumArtistSort() : hasArtistSort() ? artistSort() : QString(); }
204 
clearExtraSong205     void clearExtra() { extra.clear(); }
206 
207     static bool isVariousArtists(const QString &str);
isVariousArtistsSong208     bool isVariousArtists() const { return isVariousArtists(albumArtist()); }
209     bool diffArtist() const;
210     bool isUnknownAlbum() const;
211     bool isInvalid() const;
212     bool fixVariousArtists();
213     bool revertVariousArtists();
214     bool setAlbumArtist();
215     static QString capitalize(const QString &s);
216     bool capitalise();
isLocalFileSong217     bool isLocalFile() const { return LocalFile==type; }
isStreamSong218     bool isStream() const { return Stream==type || CantataStream==type; }
isStandardStreamSong219     bool isStandardStream() const { return Stream==type && !isDlnaStream(); }
isDlnaStreamSong220     bool isDlnaStream() const { return Stream==type && !albumArtist().isEmpty() && !album.isEmpty() && track>0; }
isNonMPDSong221     bool isNonMPD() const { return isStream() || OnlineSvrTrack==type || Cdda==type || (!file.isEmpty() && file.startsWith(Utils::constDirSep)); }
hasProtocolOrIsAbsoluteSong222     bool hasProtocolOrIsAbsolute() const { return !file.isEmpty() && (file.startsWith(Utils::constDirSep) || (!file.startsWith(constForkedDaapdLocal) && file.contains(":/")));}
isCantataStreamSong223     bool isCantataStream() const { return CantataStream==type; }
isCddaSong224     bool isCdda() const { return Cdda==type; }
225     QString albumKey() const;
isCueFileSong226     bool isCueFile() const { return Playlist==type && file.endsWith(QLatin1String(".cue"), Qt::CaseInsensitive); }
isFromCueSong227     bool isFromCue() const { return CueFile::isCue(file); }
228     QString basicArtist(bool orComposer=false) const;
229     QString basicTitle() const;
230     QString filePath(const QString &base=QString()) const;
231     QString displayAlbum(bool useComp=true) const { return displayAlbum(useComp ? albumName() : album, displayYear()); }
232     QString describe() const;
233     // Main text + sub text for now-playing and notifications
234     QString mainText() const;
235     QString subText() const;
236     bool useComposer() const;
237     void populateSorts();
238     void setFromSingleTracks();
239 //    QString basicDescription() const;
240 
241     //
242     // The following sections contain various 'hacks' - where fields of Song are abused for other
243     // purposes. This is to keep the overall size of Song lower, as its used all over the place...
244     //
245 
246     // We pass 'Song' around to cover requester. When we want the artist image, and not album image,
247     // then we blank certain fields to indicate this!
setArtistImageRequestSong248     void setArtistImageRequest() {
249         album=QString();
250         priority=0xFF;
251         disc=0x1F;
252         key=0xFFFF;
253     }
isArtistImageRequestSong254     bool isArtistImageRequest() const { return 0x1F==disc && 0xFF==priority && 0xFFFF==key && album.isEmpty(); }
255 
setComposerImageRequestSong256     void setComposerImageRequest() {
257         album=QString();
258         priority=0xFE;
259         disc=0x1E;
260         key=0xFEFE;
261     }
isComposerImageRequestSong262     bool isComposerImageRequest() const { return 0x1E==disc && 0xFE==priority && 0xFEFE==key && album.isEmpty(); }
263 
264     // In Covers, the following is used to indicate that a specfic size is requested...
setSpecificSizeRequestSong265     void setSpecificSizeRequest(int sz) {
266         size=track=id=sz;
267         time=0xFFFF;
268     }
isSpecificSizeRequestSong269     bool isSpecificSizeRequest() const { return size>4 && size<1024 && track==size && id==size && 0xFFFF==time; }
270 
271     // podcast functions...
hasBeenPlayedSong272     bool hasBeenPlayed() const { return 0!=id; }
setPlayedSong273     void setPlayed(bool p) { id=p ? 1 : 0; }
setPodcastImageSong274     void setPodcastImage(const QString &i) { setExtraField(PodcastImage, i); }
podcastImageSong275     QString podcastImage() const { return extraField(PodcastImage); }
setPodcastPublishedDateSong276     void setPodcastPublishedDate(const QString &pd) { setExtraField(PodcastPublishedDate, pd); }
podcastPublishedDateSong277     QString podcastPublishedDate() const { return extraField(PodcastPublishedDate); }
localPathSong278     QString localPath() const { return extraField(LocalPath); }
setLocalPathSong279     void setLocalPath(const QString &l) { setExtraField(LocalPath, l); }
280 
decodedPathSong281     QString decodedPath() const { return extraField(DecodedPath); }
setDecodedPathSong282     void setDecodedPath(const QString &v) { setExtraField(DecodedPath, v); }
hasDecodedPathSong283     bool hasDecodedPath() const { return hasExtraField(DecodedPath); }
284 
285     // podcast/soundcloud functions...
setIsFromOnlineServiceSong286     void setIsFromOnlineService(const QString &service) { setExtraField(OnlineServiceName, service); }
isFromOnlineServiceSong287     bool isFromOnlineService() const { return hasExtraField(OnlineServiceName); }
onlineServiceSong288     QString onlineService() const { return extraField(OnlineServiceName); }
289 
290     // device functions...
setIsFromDeviceSong291     void setIsFromDevice(const QString &id) { setExtraField(DeviceId, id); }
isFromDeviceSong292     bool isFromDevice() const { return hasExtraField(DeviceId); }
deviceIdSong293     QString deviceId() const { return extraField(DeviceId); }
294 };
295 
296 Q_DECLARE_METATYPE(Song)
297 
298 QDataStream & operator<<(QDataStream &stream, const Song &song);
299 QDataStream & operator>>(QDataStream &stream, Song &song);
300 
qHash(const Song & key)301 inline uint qHash(const Song &key)
302 {
303     return qHash(key.albumArtist()+key.album+key.title+key.file);
304 }
305 
306 #endif
307