1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2010, David Sansome <me@davidsansome.com>
5  * Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
6  *
7  * Strawberry is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Strawberry is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include "config.h"
23 
24 #include <algorithm>
25 
26 #ifdef HAVE_LIBGPOD
27 #  include <gdk-pixbuf/gdk-pixbuf.h>
28 #  include <gpod/itdb.h>
29 #endif
30 
31 #ifdef HAVE_LIBMTP
32 #  include <libmtp.h>
33 #endif
34 
35 #include <QtGlobal>
36 #include <QObject>
37 #include <QFile>
38 #include <QFileInfo>
39 #include <QDir>
40 #include <QSharedData>
41 #include <QHash>
42 #include <QByteArray>
43 #include <QVariant>
44 #include <QString>
45 #include <QStringList>
46 #include <QRegularExpression>
47 #include <QUrl>
48 #include <QImage>
49 #include <QIcon>
50 #include <QSqlQuery>
51 #include <QStandardPaths>
52 #include <QtDebug>
53 
54 #include "core/logging.h"
55 #include "core/messagehandler.h"
56 #include "core/iconloader.h"
57 
58 #include "engine/enginebase.h"
59 #include "timeconstants.h"
60 #include "utilities.h"
61 #include "song.h"
62 #include "application.h"
63 #include "sqlquery.h"
64 #include "mpris_common.h"
65 #include "collection/sqlrow.h"
66 #include "tagreadermessages.pb.h"
67 
68 const QStringList Song::kColumns = QStringList() << "title"
69                                                  << "album"
70                                                  << "artist"
71                                                  << "albumartist"
72                                                  << "track"
73                                                  << "disc"
74                                                  << "year"
75                                                  << "originalyear"
76                                                  << "genre"
77                                                  << "compilation"
78                                                  << "composer"
79                                                  << "performer"
80                                                  << "grouping"
81                                                  << "comment"
82                                                  << "lyrics"
83 
84                                                  << "artist_id"
85                                                  << "album_id"
86                                                  << "song_id"
87 
88                                                  << "beginning"
89                                                  << "length"
90 
91                                                  << "bitrate"
92                                                  << "samplerate"
93                                                  << "bitdepth"
94 
95                                                  << "source"
96                                                  << "directory_id"
97                                                  << "url"
98                                                  << "filetype"
99                                                  << "filesize"
100                                                  << "mtime"
101                                                  << "ctime"
102                                                  << "unavailable"
103 
104                                                  << "fingerprint"
105 
106                                                  << "playcount"
107                                                  << "skipcount"
108                                                  << "lastplayed"
109                                                  << "lastseen"
110 
111                                                  << "compilation_detected"
112                                                  << "compilation_on"
113                                                  << "compilation_off"
114                                                  << "compilation_effective"
115 
116                                                  << "art_automatic"
117                                                  << "art_manual"
118 
119                                                  << "effective_albumartist"
120                                                  << "effective_originalyear"
121 
122                                                  << "cue_path"
123 
124                                                  << "rating"
125 
126 						 ;
127 
128 const QString Song::kColumnSpec = Song::kColumns.join(", ");
129 const QString Song::kBindSpec = Utilities::Prepend(":", Song::kColumns).join(", ");
130 const QString Song::kUpdateSpec = Utilities::Updateify(Song::kColumns).join(", ");
131 
132 const QStringList Song::kFtsColumns = QStringList() << "ftstitle"
133                                                     << "ftsalbum"
134                                                     << "ftsartist"
135                                                     << "ftsalbumartist"
136                                                     << "ftscomposer"
137                                                     << "ftsperformer"
138                                                     << "ftsgrouping"
139                                                     << "ftsgenre"
140                                                     << "ftscomment";
141 
142 const QString Song::kFtsColumnSpec = Song::kFtsColumns.join(", ");
143 const QString Song::kFtsBindSpec = Utilities::Prepend(":", Song::kFtsColumns).join(", ");
144 const QString Song::kFtsUpdateSpec = Utilities::Updateify(Song::kFtsColumns).join(", ");
145 
146 const QString Song::kManuallyUnsetCover = "(unset)";
147 const QString Song::kEmbeddedCover = "(embedded)";
148 
149 const QRegularExpression Song::kAlbumRemoveDisc(" ?-? ((\\(|\\[)?)(Disc|CD) ?([0-9]{1,2})((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
150 const QRegularExpression Song::kAlbumRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|([0-9]{1,4}) *Remaster|Explicit) ?((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
151 const QRegularExpression Song::kTitleRemoveMisc(" ?-? ((\\(|\\[)?)(Remastered|Remastered Version|([0-9]{1,4}) *Remaster) ?((\\)|\\])?)$", QRegularExpression::CaseInsensitiveOption);
152 const QString Song::kVariousArtists("various artists");
153 
154 const QStringList Song::kArticles = QStringList() << "the " << "a " << "an ";
155 
156 const QStringList Song::kAcceptedExtensions = QStringList() << "wav" << "flac" << "wv" << "ogg" << "oga" << "opus" << "spx" << "ape" << "mpc"
157                                                             << "mp2" << "mp3" <<  "m4a" << "mp4" << "aac" << "asf" << "asx" << "wma"
158                                                             << "aif << aiff" << "mka" << "tta" << "dsf" << "dsd"
159                                                             << "cue" << "m3u" << "m3u8" << "pls" << "xspf" << "asxini"
160                                                             << "ac3" << "dts";
161 
162 struct Song::Private : public QSharedData {
163 
164   explicit Private(Source source = Source_Unknown);
165 
166   bool valid_;
167   int id_;
168 
169   QString title_;
170   QString title_sortable_;
171   QString album_;
172   QString album_sortable_;
173   QString artist_;
174   QString artist_sortable_;
175   QString albumartist_;
176   QString albumartist_sortable_;
177   int track_;
178   int disc_;
179   int year_;
180   int originalyear_;
181   QString genre_;
182   bool compilation_;		// From the file tag
183   QString composer_;
184   QString performer_;
185   QString grouping_;
186   QString comment_;
187   QString lyrics_;
188 
189   QString artist_id_;
190   QString album_id_;
191   QString song_id_;
192 
193   qint64 beginning_;
194   qint64 end_;
195 
196   int bitrate_;
197   int samplerate_;
198   int bitdepth_;
199 
200   Source source_;
201   int directory_id_;
202   QString basefilename_;
203   QUrl url_;
204   FileType filetype_;
205   int filesize_;
206   qint64 mtime_;
207   qint64 ctime_;
208   bool unavailable_;
209 
210   QString fingerprint_;
211 
212   int playcount_;
213   int skipcount_;
214   qint64 lastplayed_;
215   qint64 lastseen_;
216 
217   bool compilation_detected_;   // From the collection scanner
218   bool compilation_on_;         // Set by the user
219   bool compilation_off_;        // Set by the user
220 
221   // Filenames to album art for this song.
222   QUrl art_automatic_;          // Guessed by CollectionWatcher
223   QUrl art_manual_;             // Set by the user - should take priority
224 
225   QString cue_path_;            // If the song has a CUE, this contains it's path.
226 
227   double rating_;               // Database rating, not read from tags.
228 
229   QUrl stream_url_;             // Temporary stream url set by url handler.
230   QImage image_;                // Album Cover image set by album cover loader.
231   bool init_from_file_;         // Whether this song was loaded from a file using taglib.
232   bool suspicious_tags_;        // Whether our encoding guesser thinks these tags might be incorrectly encoded.
233 
234 };
235 
Private(Song::Source source)236 Song::Private::Private(Song::Source source)
237     : valid_(false),
238       id_(-1),
239 
240       track_(-1),
241       disc_(-1),
242       year_(-1),
243       originalyear_(-1),
244       compilation_(false),
245 
246       beginning_(0),
247       end_(-1),
248 
249       bitrate_(-1),
250       samplerate_(-1),
251       bitdepth_(-1),
252 
253       source_(source),
254       directory_id_(-1),
255       filetype_(FileType_Unknown),
256       filesize_(-1),
257       mtime_(-1),
258       ctime_(-1),
259       unavailable_(false),
260 
261       playcount_(0),
262       skipcount_(0),
263       lastplayed_(-1),
264       lastseen_(-1),
265 
266       compilation_detected_(false),
267       compilation_on_(false),
268       compilation_off_(false),
269 
270       rating_(-1),
271 
272       init_from_file_(false),
273       suspicious_tags_(false)
274 
275       {}
276 
Song(Song::Source source)277 Song::Song(Song::Source source) : d(new Private(source)) {}
278 Song::Song(const Song &other) = default;
279 Song::~Song() = default;
280 
operator =(const Song & other)281 Song &Song::operator=(const Song &other) {
282   d = other.d;
283   return *this;
284 }
285 
is_valid() const286 bool Song::is_valid() const { return d->valid_; }
is_unavailable() const287 bool Song::is_unavailable() const { return d->unavailable_; }
id() const288 int Song::id() const { return d->id_; }
289 
artist_id() const290 QString Song::artist_id() const { return d->artist_id_.isNull() ? "" : d->artist_id_; }
album_id() const291 QString Song::album_id() const { return d->album_id_.isNull() ? "" : d->album_id_; }
song_id() const292 QString Song::song_id() const { return d->song_id_.isNull() ? "" : d->song_id_; }
293 
title() const294 const QString &Song::title() const { return d->title_; }
title_sortable() const295 const QString &Song::title_sortable() const { return d->title_sortable_; }
album() const296 const QString &Song::album() const { return d->album_; }
album_sortable() const297 const QString &Song::album_sortable() const { return d->album_sortable_; }
298 // This value is useful for singles, which are one-track albums on their own.
effective_album() const299 const QString &Song::effective_album() const { return d->album_.isEmpty() ? d->title_ : d->album_; }
artist() const300 const QString &Song::artist() const { return d->artist_; }
artist_sortable() const301 const QString &Song::artist_sortable() const { return d->artist_sortable_; }
albumartist() const302 const QString &Song::albumartist() const { return d->albumartist_; }
albumartist_sortable() const303 const QString &Song::albumartist_sortable() const { return d->albumartist_sortable_; }
effective_albumartist() const304 const QString &Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; }
effective_albumartist_sortable() const305 const QString &Song::effective_albumartist_sortable() const { return d->albumartist_.isEmpty() ? d->artist_sortable_ : d->albumartist_sortable_; }
playlist_albumartist() const306 const QString &Song::playlist_albumartist() const { return is_compilation() ? d->albumartist_ : effective_albumartist(); }
playlist_albumartist_sortable() const307 const QString &Song::playlist_albumartist_sortable() const { return is_compilation() ? d->albumartist_sortable_ : effective_albumartist_sortable(); }
track() const308 int Song::track() const { return d->track_; }
disc() const309 int Song::disc() const { return d->disc_; }
year() const310 int Song::year() const { return d->year_; }
originalyear() const311 int Song::originalyear() const { return d->originalyear_; }
effective_originalyear() const312 int Song::effective_originalyear() const { return d->originalyear_ < 0 ? d->year_ : d->originalyear_; }
genre() const313 const QString &Song::genre() const { return d->genre_; }
compilation() const314 bool Song::compilation() const { return d->compilation_; }
composer() const315 const QString &Song::composer() const { return d->composer_; }
performer() const316 const QString &Song::performer() const { return d->performer_; }
grouping() const317 const QString &Song::grouping() const { return d->grouping_; }
comment() const318 const QString &Song::comment() const { return d->comment_; }
lyrics() const319 const QString &Song::lyrics() const { return d->lyrics_; }
320 
beginning_nanosec() const321 qint64 Song::beginning_nanosec() const { return d->beginning_; }
end_nanosec() const322 qint64 Song::end_nanosec() const { return d->end_; }
length_nanosec() const323 qint64 Song::length_nanosec() const { return d->end_ - d->beginning_; }
324 
bitrate() const325 int Song::bitrate() const { return d->bitrate_; }
samplerate() const326 int Song::samplerate() const { return d->samplerate_; }
bitdepth() const327 int Song::bitdepth() const { return d->bitdepth_; }
328 
source() const329 Song::Source Song::source() const { return d->source_; }
directory_id() const330 int Song::directory_id() const { return d->directory_id_; }
url() const331 const QUrl &Song::url() const { return d->url_; }
basefilename() const332 const QString &Song::basefilename() const { return d->basefilename_; }
filetype() const333 Song::FileType Song::filetype() const { return d->filetype_; }
filesize() const334 int Song::filesize() const { return d->filesize_; }
mtime() const335 qint64 Song::mtime() const { return d->mtime_; }
ctime() const336 qint64 Song::ctime() const { return d->ctime_; }
337 
fingerprint() const338 QString Song::fingerprint() const { return d->fingerprint_; }
339 
playcount() const340 int Song::playcount() const { return d->playcount_; }
skipcount() const341 int Song::skipcount() const { return d->skipcount_; }
lastplayed() const342 qint64 Song::lastplayed() const { return d->lastplayed_; }
lastseen() const343 qint64 Song::lastseen() const { return d->lastseen_; }
344 
compilation_detected() const345 bool Song::compilation_detected() const { return d->compilation_detected_; }
compilation_off() const346 bool Song::compilation_off() const { return d->compilation_off_; }
compilation_on() const347 bool Song::compilation_on() const { return d->compilation_on_; }
348 
art_automatic() const349 const QUrl &Song::art_automatic() const { return d->art_automatic_; }
art_manual() const350 const QUrl &Song::art_manual() const { return d->art_manual_; }
has_manually_unset_cover() const351 bool Song::has_manually_unset_cover() const { return d->art_manual_.path() == kManuallyUnsetCover; }
set_manually_unset_cover()352 void Song::set_manually_unset_cover() { d->art_manual_ = QUrl::fromLocalFile(kManuallyUnsetCover); }
has_embedded_cover() const353 bool Song::has_embedded_cover() const { return d->art_automatic_.path() == kEmbeddedCover; }
set_embedded_cover()354 void Song::set_embedded_cover() { d->art_automatic_ = QUrl::fromLocalFile(kEmbeddedCover); }
355 
clear_art_automatic()356 void Song::clear_art_automatic() { d->art_automatic_.clear(); }
clear_art_manual()357 void Song::clear_art_manual() { d->art_manual_.clear(); }
358 
save_embedded_cover_supported(const FileType filetype)359 bool Song::save_embedded_cover_supported(const FileType filetype) {
360 
361   return filetype == FileType_FLAC ||
362          filetype == FileType_OggVorbis ||
363          filetype == FileType_MPEG ||
364          filetype == FileType_MP4;
365 
366 }
367 
stream_url() const368 const QUrl &Song::stream_url() const { return d->stream_url_; }
effective_stream_url() const369 const QUrl &Song::effective_stream_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
image() const370 const QImage &Song::image() const { return d->image_; }
init_from_file() const371 bool Song::init_from_file() const { return d->init_from_file_; }
372 
cue_path() const373 const QString &Song::cue_path() const { return d->cue_path_; }
has_cue() const374 bool Song::has_cue() const { return !d->cue_path_.isEmpty(); }
375 
rating() const376 double Song::rating() const { return d->rating_; }
377 
is_collection_song() const378 bool Song::is_collection_song() const { return d->source_ == Source_Collection; }
is_metadata_good() const379 bool Song::is_metadata_good() const { return !d->url_.isEmpty() && !d->artist_.isEmpty() && !d->title_.isEmpty(); }
is_stream() const380 bool Song::is_stream() const { return is_radio() || d->source_ == Source_Tidal || d->source_ == Source_Subsonic || d->source_ == Source_Qobuz; }
is_radio() const381 bool Song::is_radio() const { return d->source_ == Source_Stream || d->source_ == Source_SomaFM || d->source_ == Source_RadioParadise; }
is_cdda() const382 bool Song::is_cdda() const { return d->source_ == Source_CDDA; }
is_compilation() const383 bool Song::is_compilation() const { return (d->compilation_ || d->compilation_detected_ || d->compilation_on_) && !d->compilation_off_; }
stream_url_can_expire() const384 bool Song::stream_url_can_expire() const { return d->source_ == Song::Source_Tidal || d->source_ == Song::Source_Qobuz; }
is_module_music() const385 bool Song::is_module_music() const { return d->filetype_ == Song::FileType_MOD || d->filetype_ == Song::FileType_S3M || d->filetype_ == Song::FileType_XM || d->filetype_ == Song::FileType_IT; }
386 
art_automatic_is_valid() const387 bool Song::art_automatic_is_valid() const {
388   return !d->art_automatic_.isEmpty() &&
389          (
390          (d->art_automatic_.path() == kManuallyUnsetCover) ||
391          (d->art_automatic_.path() == kEmbeddedCover) ||
392          (d->art_automatic_.isValid() && !d->art_automatic_.isLocalFile()) ||
393          (d->art_automatic_.isLocalFile() && QFile::exists(d->art_automatic_.toLocalFile())) ||
394          (d->art_automatic_.scheme().isEmpty() && !d->art_automatic_.path().isEmpty() && QFile::exists(d->art_automatic_.path()))
395          );
396 }
397 
art_manual_is_valid() const398 bool Song::art_manual_is_valid() const {
399   return !d->art_manual_.isEmpty() &&
400          (
401          (d->art_manual_.path() == kManuallyUnsetCover) ||
402          (d->art_manual_.path() == kEmbeddedCover) ||
403          (d->art_manual_.isValid() && !d->art_manual_.isLocalFile()) ||
404          (d->art_manual_.isLocalFile() && QFile::exists(d->art_manual_.toLocalFile())) ||
405          (d->art_manual_.scheme().isEmpty() && !d->art_manual_.path().isEmpty() && QFile::exists(d->art_manual_.path()))
406          );
407 }
408 
has_valid_art() const409 bool Song::has_valid_art() const { return art_automatic_is_valid() || art_manual_is_valid(); }
410 
set_id(int id)411 void Song::set_id(int id) { d->id_ = id; }
set_valid(bool v)412 void Song::set_valid(bool v) { d->valid_ = v; }
413 
set_artist_id(const QString & v)414 void Song::set_artist_id(const QString &v) { d->artist_id_ = v; }
set_album_id(const QString & v)415 void Song::set_album_id(const QString &v) { d->album_id_ = v; }
set_song_id(const QString & v)416 void Song::set_song_id(const QString &v) { d->song_id_ = v; }
417 
sortable(const QString & v)418 QString Song::sortable(const QString &v) {
419 
420   QString copy = v.toLower();
421 
422   for (const auto &i : kArticles) {
423     if (copy.startsWith(i)) {
424       int ilen = i.length();
425       return copy.right(copy.length() - ilen) + ", " + copy.left(ilen - 1);
426     }
427   }
428 
429   return copy;
430 }
431 
set_title(const QString & v)432 void Song::set_title(const QString &v) { d->title_sortable_ = sortable(v); d->title_ = v; }
set_album(const QString & v)433 void Song::set_album(const QString &v) { d->album_sortable_ = sortable(v); d->album_ = v; }
set_artist(const QString & v)434 void Song::set_artist(const QString &v) { d->artist_sortable_ = sortable(v); d->artist_ = v; }
set_albumartist(const QString & v)435 void Song::set_albumartist(const QString &v) { d->albumartist_sortable_ = sortable(v); d->albumartist_ = v; }
set_track(int v)436 void Song::set_track(int v) { d->track_ = v; }
set_disc(int v)437 void Song::set_disc(int v) { d->disc_ = v; }
set_year(int v)438 void Song::set_year(int v) { d->year_ = v; }
set_originalyear(int v)439 void Song::set_originalyear(int v) { d->originalyear_ = v; }
set_genre(const QString & v)440 void Song::set_genre(const QString &v) { d->genre_ = v; }
set_compilation(bool v)441 void Song::set_compilation(bool v) { d->compilation_ = v; }
set_composer(const QString & v)442 void Song::set_composer(const QString &v) { d->composer_ = v; }
set_performer(const QString & v)443 void Song::set_performer(const QString &v) { d->performer_ = v; }
set_grouping(const QString & v)444 void Song::set_grouping(const QString &v) { d->grouping_ = v; }
set_comment(const QString & v)445 void Song::set_comment(const QString &v) { d->comment_ = v; }
set_lyrics(const QString & v)446 void Song::set_lyrics(const QString &v) { d->lyrics_ = v; }
447 
set_beginning_nanosec(qint64 v)448 void Song::set_beginning_nanosec(qint64 v) { d->beginning_ = qMax(0LL, v); }
set_end_nanosec(qint64 v)449 void Song::set_end_nanosec(qint64 v) { d->end_ = v; }
set_length_nanosec(qint64 v)450 void Song::set_length_nanosec(qint64 v) { d->end_ = d->beginning_ + v; }
451 
set_bitrate(int v)452 void Song::set_bitrate(int v) { d->bitrate_ = v; }
set_samplerate(int v)453 void Song::set_samplerate(int v) { d->samplerate_ = v; }
set_bitdepth(int v)454 void Song::set_bitdepth(int v) { d->bitdepth_ = v; }
455 
set_source(Source v)456 void Song::set_source(Source v) { d->source_ = v; }
set_directory_id(int v)457 void Song::set_directory_id(int v) { d->directory_id_ = v; }
set_url(const QUrl & v)458 void Song::set_url(const QUrl &v) { d->url_ = v; }
set_basefilename(const QString & v)459 void Song::set_basefilename(const QString &v) { d->basefilename_ = v; }
set_filetype(FileType v)460 void Song::set_filetype(FileType v) { d->filetype_ = v; }
set_filesize(int v)461 void Song::set_filesize(int v) { d->filesize_ = v; }
set_mtime(qint64 v)462 void Song::set_mtime(qint64 v) { d->mtime_ = v; }
set_ctime(qint64 v)463 void Song::set_ctime(qint64 v) { d->ctime_ = v; }
set_unavailable(bool v)464 void Song::set_unavailable(bool v) { d->unavailable_ = v; }
465 
set_fingerprint(const QString & v)466 void Song::set_fingerprint(const QString &v) { d->fingerprint_ = v; }
467 
set_playcount(int v)468 void Song::set_playcount(int v) { d->playcount_ = v; }
set_skipcount(int v)469 void Song::set_skipcount(int v) { d->skipcount_ = v; }
set_lastplayed(qint64 v)470 void Song::set_lastplayed(qint64 v) { d->lastplayed_ = v; }
set_lastseen(qint64 v)471 void Song::set_lastseen(qint64 v) { d->lastseen_ = v; }
472 
set_compilation_detected(bool v)473 void Song::set_compilation_detected(bool v) { d->compilation_detected_ = v; }
set_compilation_on(bool v)474 void Song::set_compilation_on(bool v) { d->compilation_on_ = v; }
set_compilation_off(bool v)475 void Song::set_compilation_off(bool v) { d->compilation_off_ = v; }
476 
set_art_automatic(const QUrl & v)477 void Song::set_art_automatic(const QUrl &v) { d->art_automatic_ = v; }
set_art_manual(const QUrl & v)478 void Song::set_art_manual(const QUrl &v) { d->art_manual_ = v; }
set_cue_path(const QString & v)479 void Song::set_cue_path(const QString &v) { d->cue_path_ = v; }
480 
set_rating(double v)481 void Song::set_rating(double v) { d->rating_ = v; }
482 
set_stream_url(const QUrl & v)483 void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
set_image(const QImage & i)484 void Song::set_image(const QImage &i) { d->image_ = i; }
485 
JoinSpec(const QString & table)486 QString Song::JoinSpec(const QString &table) {
487   return Utilities::Prepend(table + ".", kColumns).join(", ");
488 }
489 
SourceFromURL(const QUrl & url)490 Song::Source Song::SourceFromURL(const QUrl &url) {
491 
492   if (url.isLocalFile()) return Source_LocalFile;
493   else if (url.scheme() == "cdda") return Source_CDDA;
494   else if (url.scheme() == "tidal") return Source_Tidal;
495   else if (url.scheme() == "subsonic") return Source_Subsonic;
496   else if (url.scheme() == "qobuz") return Source_Qobuz;
497   else if (url.scheme() == "http" || url.scheme() == "https" || url.scheme() == "rtsp") {
498     if (url.host().endsWith("tidal.com", Qt::CaseInsensitive)) { return Source_Tidal; }
499     if (url.host().endsWith("qobuz.com", Qt::CaseInsensitive)) { return Source_Qobuz; }
500     if (url.host().endsWith("somafm.com", Qt::CaseInsensitive)) { return Source_SomaFM; }
501     if (url.host().endsWith("radioparadise.com", Qt::CaseInsensitive)) { return Source_RadioParadise; }
502     return Source_Stream;
503   }
504   else return Source_Unknown;
505 
506 }
507 
TextForSource(Source source)508 QString Song::TextForSource(Source source) {
509 
510   switch (source) {
511     case Song::Source_LocalFile:     return "file";
512     case Song::Source_Collection:    return "collection";
513     case Song::Source_CDDA:          return "cd";
514     case Song::Source_Device:        return "device";
515     case Song::Source_Stream:        return "stream";
516     case Song::Source_Tidal:         return "tidal";
517     case Song::Source_Subsonic:      return "subsonic";
518     case Song::Source_Qobuz:         return "qobuz";
519     case Song::Source_SomaFM:        return "somafm";
520     case Song::Source_RadioParadise: return "radioparadise";
521     case Song::Source_Unknown:       return "unknown";
522   }
523   return "unknown";
524 
525 }
526 
DescriptionForSource(Source source)527 QString Song::DescriptionForSource(Source source) {
528 
529   switch (source) {
530     case Song::Source_LocalFile:     return "File";
531     case Song::Source_Collection:    return "Collection";
532     case Song::Source_CDDA:          return "CD";
533     case Song::Source_Device:        return "Device";
534     case Song::Source_Stream:        return "Stream";
535     case Song::Source_Tidal:         return "Tidal";
536     case Song::Source_Subsonic:      return "Subsonic";
537     case Song::Source_Qobuz:         return "Qobuz";
538     case Song::Source_SomaFM:        return "SomaFM";
539     case Song::Source_RadioParadise: return "Radio Paradise";
540     case Song::Source_Unknown:       return "Unknown";
541   }
542   return "unknown";
543 
544 }
545 
SourceFromText(const QString & source)546 Song::Source Song::SourceFromText(const QString &source) {
547 
548   if (source.compare("file", Qt::CaseInsensitive) == 0) return Source_LocalFile;
549   if (source.compare("collection", Qt::CaseInsensitive) == 0) return Source_Collection;
550   if (source.compare("cd", Qt::CaseInsensitive) == 0) return Source_CDDA;
551   if (source.compare("device", Qt::CaseInsensitive) == 0) return Source_Device;
552   if (source.compare("stream", Qt::CaseInsensitive) == 0) return Source_Stream;
553   if (source.compare("tidal", Qt::CaseInsensitive) == 0) return Source_Tidal;
554   if (source.compare("subsonic", Qt::CaseInsensitive) == 0) return Source_Subsonic;
555   if (source.compare("qobuz", Qt::CaseInsensitive) == 0) return Source_Qobuz;
556   if (source.compare("somafm", Qt::CaseInsensitive) == 0) return Source_SomaFM;
557   if (source.compare("radioparadise", Qt::CaseInsensitive) == 0) return Source_RadioParadise;
558 
559   return Source_Unknown;
560 
561 }
562 
IconForSource(Source source)563 QIcon Song::IconForSource(Source source) {
564 
565   switch (source) {
566     case Song::Source_LocalFile:     return IconLoader::Load("folder-sound");
567     case Song::Source_Collection:    return IconLoader::Load("library-music");
568     case Song::Source_CDDA:          return IconLoader::Load("media-optical");
569     case Song::Source_Device:        return IconLoader::Load("device");
570     case Song::Source_Stream:        return IconLoader::Load("applications-internet");
571     case Song::Source_Tidal:         return IconLoader::Load("tidal");
572     case Song::Source_Subsonic:      return IconLoader::Load("subsonic");
573     case Song::Source_Qobuz:         return IconLoader::Load("qobuz");
574     case Song::Source_SomaFM:        return IconLoader::Load("somafm");
575     case Song::Source_RadioParadise: return IconLoader::Load("radioparadise");
576     case Song::Source_Unknown:       return IconLoader::Load("edit-delete");
577   }
578   return IconLoader::Load("edit-delete");
579 
580 }
581 
TextForFiletype(FileType filetype)582 QString Song::TextForFiletype(FileType filetype) {
583 
584   switch (filetype) {
585     case Song::FileType_WAV:         return "Wav";
586     case Song::FileType_FLAC:        return "FLAC";
587     case Song::FileType_WavPack:     return "WavPack";
588     case Song::FileType_OggFlac:     return "Ogg FLAC";
589     case Song::FileType_OggVorbis:   return "Ogg Vorbis";
590     case Song::FileType_OggOpus:     return "Ogg Opus";
591     case Song::FileType_OggSpeex:    return "Ogg Speex";
592     case Song::FileType_MPEG:        return "MP3";
593     case Song::FileType_MP4:         return "MP4 AAC";
594     case Song::FileType_ASF:         return "Windows Media audio";
595     case Song::FileType_AIFF:        return "AIFF";
596     case Song::FileType_MPC:         return "MPC";
597     case Song::FileType_TrueAudio:   return "TrueAudio";
598     case Song::FileType_DSF:         return "DSF";
599     case Song::FileType_DSDIFF:      return "DSDIFF";
600     case Song::FileType_PCM:         return "PCM";
601     case Song::FileType_APE:         return "Monkey's Audio";
602     case Song::FileType_MOD:         return "Module Music Format";
603     case Song::FileType_S3M:         return "Module Music Format";
604     case Song::FileType_XM:          return "Module Music Format";
605     case Song::FileType_IT:          return "Module Music Format";
606     case Song::FileType_CDDA:        return "CDDA";
607     case Song::FileType_Stream:      return "Stream";
608     case Song::FileType_Unknown:
609     default:                         return QObject::tr("Unknown");
610   }
611 
612 }
613 
ExtensionForFiletype(FileType filetype)614 QString Song::ExtensionForFiletype(FileType filetype) {
615 
616   switch (filetype) {
617     case Song::FileType_WAV:         return "wav";
618     case Song::FileType_FLAC:        return "flac";
619     case Song::FileType_WavPack:     return "wv";
620     case Song::FileType_OggFlac:     return "flac";
621     case Song::FileType_OggVorbis:   return "ogg";
622     case Song::FileType_OggOpus:     return "opus";
623     case Song::FileType_OggSpeex:    return "spx";
624     case Song::FileType_MPEG:        return "mp3";
625     case Song::FileType_MP4:         return "mp4";
626     case Song::FileType_ASF:         return "wma";
627     case Song::FileType_AIFF:        return "aiff";
628     case Song::FileType_MPC:         return "mpc";
629     case Song::FileType_TrueAudio:   return "tta";
630     case Song::FileType_DSF:         return "dsf";
631     case Song::FileType_DSDIFF:      return "dsd";
632     case Song::FileType_APE:         return "ape";
633     case Song::FileType_MOD:         return "mod";
634     case Song::FileType_S3M:         return "s3m";
635     case Song::FileType_XM:          return "xm";
636     case Song::FileType_IT:          return "it";
637     case Song::FileType_Unknown:
638     default:                         return "dat";
639   }
640 
641 }
642 
IconForFiletype(FileType filetype)643 QIcon Song::IconForFiletype(FileType filetype) {
644 
645   switch (filetype) {
646     case Song::FileType_WAV:         return IconLoader::Load("wav");
647     case Song::FileType_FLAC:        return IconLoader::Load("flac");
648     case Song::FileType_WavPack:     return IconLoader::Load("wavpack");
649     case Song::FileType_OggFlac:     return IconLoader::Load("flac");
650     case Song::FileType_OggVorbis:   return IconLoader::Load("vorbis");
651     case Song::FileType_OggOpus:     return IconLoader::Load("opus");
652     case Song::FileType_OggSpeex:    return IconLoader::Load("speex");
653     case Song::FileType_MPEG:        return IconLoader::Load("mp3");
654     case Song::FileType_MP4:         return IconLoader::Load("mp4");
655     case Song::FileType_ASF:         return IconLoader::Load("wma");
656     case Song::FileType_AIFF:        return IconLoader::Load("aiff");
657     case Song::FileType_MPC:         return IconLoader::Load("mpc");
658     case Song::FileType_TrueAudio:   return IconLoader::Load("trueaudio");
659     case Song::FileType_DSF:         return IconLoader::Load("dsf");
660     case Song::FileType_DSDIFF:      return IconLoader::Load("dsd");
661     case Song::FileType_PCM:         return IconLoader::Load("pcm");
662     case Song::FileType_APE:         return IconLoader::Load("ape");
663     case Song::FileType_MOD:         return IconLoader::Load("mod");
664     case Song::FileType_S3M:         return IconLoader::Load("s3m");
665     case Song::FileType_XM:          return IconLoader::Load("xm");
666     case Song::FileType_IT:          return IconLoader::Load("it");
667     case Song::FileType_CDDA:        return IconLoader::Load("cd");
668     case Song::FileType_Stream:      return IconLoader::Load("applications-internet");
669     case Song::FileType_Unknown:
670     default:                         return IconLoader::Load("edit-delete");
671   }
672 
673 }
674 
IsFileLossless() const675 bool Song::IsFileLossless() const {
676   switch (filetype()) {
677     case Song::FileType_WAV:
678     case Song::FileType_FLAC:
679     case Song::FileType_OggFlac:
680     case Song::FileType_WavPack:
681     case Song::FileType_AIFF:
682     case Song::FileType_DSF:
683     case Song::FileType_DSDIFF:
684     case Song::FileType_APE:
685     case Song::FileType_TrueAudio:
686     case Song::FileType_PCM:
687     case Song::FileType_CDDA:
688       return true;
689     default:
690       return false;
691   }
692 }
693 
FiletypeByMimetype(const QString & mimetype)694 Song::FileType Song::FiletypeByMimetype(const QString &mimetype) {
695 
696   if (mimetype.compare("audio/wav", Qt::CaseInsensitive) == 0 || mimetype.compare("audio/x-wav", Qt::CaseInsensitive) == 0) return Song::FileType_WAV;
697   else if (mimetype.compare("audio/x-flac", Qt::CaseInsensitive) == 0) return Song::FileType_FLAC;
698   else if (mimetype.compare("audio/x-wavpack", Qt::CaseInsensitive) == 0) return Song::FileType_WavPack;
699   else if (mimetype.compare("audio/x-vorbis", Qt::CaseInsensitive) == 0) return Song::FileType_OggVorbis;
700   else if (mimetype.compare("audio/x-opus", Qt::CaseInsensitive) == 0) return Song::FileType_OggOpus;
701   else if (mimetype.compare("audio/x-speex", Qt::CaseInsensitive) == 0)  return Song::FileType_OggSpeex;
702   // Gstreamer returns audio/mpeg for both MP3 and MP4/AAC.
703   // else if (mimetype.compare("audio/mpeg", Qt::CaseInsensitive) == 0) return Song::FileType_MPEG;
704   else if (mimetype.compare("audio/aac", Qt::CaseInsensitive) == 0) return Song::FileType_MP4;
705   else if (mimetype.compare("audio/x-wma", Qt::CaseInsensitive) == 0) return Song::FileType_ASF;
706   else if (mimetype.compare("audio/aiff", Qt::CaseInsensitive) == 0 || mimetype.compare("audio/x-aiff", Qt::CaseInsensitive) == 0) return Song::FileType_AIFF;
707   else if (mimetype.compare("application/x-project", Qt::CaseInsensitive) == 0) return Song::FileType_MPC;
708   else if (mimetype.compare("audio/x-dsf", Qt::CaseInsensitive) == 0) return Song::FileType_DSF;
709   else if (mimetype.compare("audio/x-dsd", Qt::CaseInsensitive) == 0) return Song::FileType_DSDIFF;
710   else if (mimetype.compare("audio/x-ape", Qt::CaseInsensitive) == 0 || mimetype.compare("application/x-ape", Qt::CaseInsensitive) == 0 || mimetype.compare("audio/x-ffmpeg-parsed-ape", Qt::CaseInsensitive) == 0) return Song::FileType_APE;
711   else if (mimetype.compare("audio/x-mod", Qt::CaseInsensitive) == 0) return Song::FileType_MOD;
712   else if (mimetype.compare("audio/x-s3m", Qt::CaseInsensitive) == 0) return Song::FileType_S3M;
713 
714   else return Song::FileType_Unknown;
715 
716 }
717 
FiletypeByDescription(const QString & text)718 Song::FileType Song::FiletypeByDescription(const QString &text) {
719 
720   if (text.compare("WAV", Qt::CaseInsensitive) == 0) return Song::FileType_WAV;
721   else if (text.compare("Free Lossless Audio Codec (FLAC)", Qt::CaseInsensitive) == 0) return Song::FileType_FLAC;
722   else if (text.compare("Wavpack", Qt::CaseInsensitive) == 0) return Song::FileType_WavPack;
723   else if (text.compare("Vorbis", Qt::CaseInsensitive) == 0) return Song::FileType_OggVorbis;
724   else if (text.compare("Opus", Qt::CaseInsensitive) == 0) return Song::FileType_OggOpus;
725   else if (text.compare("Speex", Qt::CaseInsensitive) == 0) return Song::FileType_OggSpeex;
726   else if (text.compare("MPEG-1 Layer 3 (MP3)", Qt::CaseInsensitive) == 0) return Song::FileType_MPEG;
727   else if (text.compare("MPEG-4 AAC", Qt::CaseInsensitive) == 0) return Song::FileType_MP4;
728   else if (text.compare("WMA", Qt::CaseInsensitive) == 0) return Song::FileType_ASF;
729   else if (text.compare("Audio Interchange File Format", Qt::CaseInsensitive) == 0) return Song::FileType_AIFF;
730   else if (text.compare("MPC", Qt::CaseInsensitive) == 0) return Song::FileType_MPC;
731   else if (text.compare("audio/x-dsf", Qt::CaseInsensitive) == 0) return Song::FileType_DSF;
732   else if (text.compare("audio/x-dsd", Qt::CaseInsensitive) == 0) return Song::FileType_DSDIFF;
733   else if (text.compare("audio/x-ffmpeg-parsed-ape", Qt::CaseInsensitive) == 0) return Song::FileType_APE;
734   else if (text.compare("Module Music Format (MOD)", Qt::CaseInsensitive) == 0) return Song::FileType_MOD;
735   else if (text.compare("Module Music Format (MOD)", Qt::CaseInsensitive) == 0) return Song::FileType_S3M;
736   else return Song::FileType_Unknown;
737 
738 }
739 
FiletypeByExtension(const QString & ext)740 Song::FileType Song::FiletypeByExtension(const QString &ext) {
741 
742   if (ext.compare("wav", Qt::CaseInsensitive) == 0 || ext.compare("wave", Qt::CaseInsensitive) == 0) return Song::FileType_WAV;
743   else if (ext.compare("flac", Qt::CaseInsensitive) == 0) return Song::FileType_FLAC;
744   else if (ext.compare("wavpack", Qt::CaseInsensitive) == 0 || ext.compare("wv", Qt::CaseInsensitive) == 0) return Song::FileType_WavPack;
745   else if (ext.compare("ogg", Qt::CaseInsensitive) == 0 || ext.compare("oga", Qt::CaseInsensitive) == 0) return Song::FileType_OggVorbis;
746   else if (ext.compare("opus", Qt::CaseInsensitive) == 0) return Song::FileType_OggOpus;
747   else if (ext.compare("speex", Qt::CaseInsensitive) == 0 || ext.compare("spx", Qt::CaseInsensitive) == 0) return Song::FileType_OggSpeex;
748   else if (ext.compare("mp3", Qt::CaseInsensitive) == 0) return Song::FileType_MPEG;
749   else if (ext.compare("mp4", Qt::CaseInsensitive) == 0 || ext.compare("m4a", Qt::CaseInsensitive) == 0 || ext.compare("aac", Qt::CaseInsensitive) == 0) return Song::FileType_MP4;
750   else if (ext.compare("asf", Qt::CaseInsensitive) == 0 || ext.compare("wma", Qt::CaseInsensitive) == 0) return Song::FileType_ASF;
751   else if (ext.compare("aiff", Qt::CaseInsensitive) == 0 || ext.compare("aif", Qt::CaseInsensitive) == 0 || ext.compare("aifc", Qt::CaseInsensitive) == 0) return Song::FileType_AIFF;
752   else if (ext.compare("mpc", Qt::CaseInsensitive) == 0 || ext.compare("mp+", Qt::CaseInsensitive) == 0 || ext.compare("mpp", Qt::CaseInsensitive) == 0) return Song::FileType_MPC;
753   else if (ext.compare("dsf", Qt::CaseInsensitive) == 0) return Song::FileType_DSF;
754   else if (ext.compare("dsd", Qt::CaseInsensitive) == 0 || ext.compare("dff", Qt::CaseInsensitive) == 0) return Song::FileType_DSDIFF;
755   else if (ext.compare("ape", Qt::CaseInsensitive) == 0) return Song::FileType_APE;
756   else if (ext.compare("mod", Qt::CaseInsensitive) == 0 ||
757            ext.compare("module", Qt::CaseInsensitive) == 0 ||
758            ext.compare("nst", Qt::CaseInsensitive) == 0||
759            ext.compare("wow", Qt::CaseInsensitive) == 0) return Song::FileType_MOD;
760   else if (ext.compare("s3m", Qt::CaseInsensitive) == 0) return Song::FileType_S3M;
761   else if (ext.compare("xm", Qt::CaseInsensitive) == 0) return Song::FileType_XM;
762   else if (ext.compare("it", Qt::CaseInsensitive) == 0) return Song::FileType_IT;
763 
764   else return Song::FileType_Unknown;
765 
766 }
767 
ImageCacheDir(const Song::Source source)768 QString Song::ImageCacheDir(const Song::Source source) {
769 
770   switch (source) {
771     case Song::Source_Collection:
772       return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/collectionalbumcovers";
773     case Song::Source_Subsonic:
774       return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/subsonicalbumcovers";
775     case Song::Source_Tidal:
776       return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/tidalalbumcovers";
777     case Song::Source_Qobuz:
778       return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/qobuzalbumcovers";
779     case Song::Source_Device:
780       return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/devicealbumcovers";
781     case Song::Source_LocalFile:
782     case Song::Source_CDDA:
783     case Song::Source_Stream:
784     case Song::Source_SomaFM:
785     case Song::Source_RadioParadise:
786     case Song::Source_Unknown:
787       return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/albumcovers";
788   }
789 
790   return QString();
791 
792 }
793 
CompareSongsName(const Song & song1,const Song & song2)794 int Song::CompareSongsName(const Song &song1, const Song &song2) {
795   return song1.PrettyTitleWithArtist().localeAwareCompare(song2.PrettyTitleWithArtist()) < 0;
796 }
797 
SortSongsListAlphabetically(SongList * songs)798 void Song::SortSongsListAlphabetically(SongList *songs) {
799   Q_ASSERT(songs);
800   std::sort(songs->begin(), songs->end(), CompareSongsName);
801 }
802 
Init(const QString & title,const QString & artist,const QString & album,qint64 length_nanosec)803 void Song::Init(const QString &title, const QString &artist, const QString &album, qint64 length_nanosec) {
804 
805   d->valid_ = true;
806 
807   set_title(title);
808   set_artist(artist);
809   set_album(album);
810 
811   set_length_nanosec(length_nanosec);
812 
813 }
814 
Init(const QString & title,const QString & artist,const QString & album,qint64 beginning,qint64 end)815 void Song::Init(const QString &title, const QString &artist, const QString &album, qint64 beginning, qint64 end) {
816 
817   d->valid_ = true;
818 
819   set_title(title);
820   set_artist(artist);
821   set_album(album);
822 
823   d->beginning_ = beginning;
824   d->end_ = end;
825 
826 }
827 
InitFromProtobuf(const spb::tagreader::SongMetadata & pb)828 void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) {
829 
830   if (d->source_ == Source_Unknown) d->source_ = Source_LocalFile;
831 
832   d->init_from_file_ = true;
833   d->valid_ = pb.valid();
834   set_title(QStringFromStdString(pb.title()));
835   set_album(QStringFromStdString(pb.album()));
836   set_artist(QStringFromStdString(pb.artist()));
837   set_albumartist(QStringFromStdString(pb.albumartist()));
838   d->track_ = pb.track();
839   d->disc_ = pb.disc();
840   d->year_ = pb.year();
841   d->originalyear_ = pb.originalyear();
842   d->genre_ = QStringFromStdString(pb.genre());
843   d->compilation_ = pb.compilation();
844   d->composer_ = QStringFromStdString(pb.composer());
845   d->performer_ = QStringFromStdString(pb.performer());
846   d->grouping_ = QStringFromStdString(pb.grouping());
847   d->comment_ = QStringFromStdString(pb.comment());
848   d->lyrics_ = QStringFromStdString(pb.lyrics());
849   set_length_nanosec(pb.length_nanosec());
850   d->bitrate_ = pb.bitrate();
851   d->samplerate_ = pb.samplerate();
852   d->bitdepth_ = pb.bitdepth();
853   set_url(QUrl::fromEncoded(QByteArray(pb.url().data(), pb.url().size())));
854   d->basefilename_ = QStringFromStdString(pb.basefilename());
855   d->filetype_ = static_cast<FileType>(pb.filetype());
856   d->filesize_ = pb.filesize();
857   d->mtime_ = pb.mtime();
858   d->ctime_ = pb.ctime();
859   d->skipcount_ = pb.skipcount();
860   d->lastplayed_ = pb.lastplayed();
861   d->lastseen_ = pb.lastseen();
862   d->suspicious_tags_ = pb.suspicious_tags();
863 
864   if (pb.has_playcount()) {
865     d->playcount_ = pb.playcount();
866   }
867 
868   if (pb.has_art_automatic()) {
869     QByteArray art_automatic(pb.art_automatic().data(), pb.art_automatic().size());
870     if (!art_automatic.isEmpty()) set_art_automatic(QUrl::fromLocalFile(art_automatic));
871   }
872 
873   InitArtManual();
874 
875 }
876 
ToProtobuf(spb::tagreader::SongMetadata * pb) const877 void Song::ToProtobuf(spb::tagreader::SongMetadata *pb) const {
878 
879   const QByteArray url(d->url_.toEncoded());
880   const QByteArray art_automatic(d->art_automatic_.toEncoded());
881 
882   pb->set_valid(d->valid_);
883   pb->set_title(DataCommaSizeFromQString(d->title_));
884   pb->set_album(DataCommaSizeFromQString(d->album_));
885   pb->set_artist(DataCommaSizeFromQString(d->artist_));
886   pb->set_albumartist(DataCommaSizeFromQString(d->albumartist_));
887   pb->set_composer(DataCommaSizeFromQString(d->composer_));
888   pb->set_performer(DataCommaSizeFromQString(d->performer_));
889   pb->set_grouping(DataCommaSizeFromQString(d->grouping_));
890   pb->set_lyrics(DataCommaSizeFromQString(d->lyrics_));
891   pb->set_track(d->track_);
892   pb->set_disc(d->disc_);
893   pb->set_year(d->year_);
894   pb->set_originalyear(d->originalyear_);
895   pb->set_genre(DataCommaSizeFromQString(d->genre_));
896   pb->set_comment(DataCommaSizeFromQString(d->comment_));
897   pb->set_compilation(d->compilation_);
898   pb->set_playcount(d->playcount_);
899   pb->set_skipcount(d->skipcount_);
900   pb->set_lastplayed(d->lastplayed_);
901   pb->set_lastseen(d->lastseen_);
902   pb->set_length_nanosec(length_nanosec());
903   pb->set_bitrate(d->bitrate_);
904   pb->set_samplerate(d->samplerate_);
905   pb->set_bitdepth(d->bitdepth_);
906   pb->set_url(url.constData(), url.size());
907   pb->set_basefilename(DataCommaSizeFromQString(d->basefilename_));
908   pb->set_mtime(d->mtime_);
909   pb->set_ctime(d->ctime_);
910   pb->set_filesize(d->filesize_);
911   pb->set_suspicious_tags(d->suspicious_tags_);
912   pb->set_art_automatic(art_automatic.constData(), art_automatic.size());
913   pb->set_filetype(static_cast<spb::tagreader::SongMetadata_FileType>(d->filetype_));
914 
915 }
916 
917 #define tostr(n) (q.value(n).isNull() ? QString() : q.value(n).toString())
918 #define toint(n) (q.value(n).isNull() ? -1 : q.value(n).toInt())
919 #define tolonglong(n) (q.value(n).isNull() ? -1 : q.value(n).toLongLong())
920 #define todouble(n) (q.value(n).isNull() ? -1 : q.value(n).toDouble())
921 
InitFromQuery(const SqlRow & q,bool reliable_metadata,int col)922 void Song::InitFromQuery(const SqlRow &q, bool reliable_metadata, int col) {
923 
924   //qLog(Debug) << "Song::kColumns.size():" << Song::kColumns.size() << "q.columns_.size():" << q.columns_.size() << "col:" << col;
925 
926   int x = col;
927   d->id_ = toint(col);
928 
929   for (int i = 0; i < Song::kColumns.size(); i++) {
930     x++;
931 
932     if (x >= q.columns_.size()) {
933       qLog(Error) << "Skipping" << Song::kColumns.value(i);
934       break;
935     }
936 
937     //qLog(Debug) << "Index:" << i << x << Song::kColumns.value(i) << q.value(x).toString();
938 
939     if (Song::kColumns.value(i) == "title") {
940       set_title(tostr(x));
941     }
942     else if (Song::kColumns.value(i) == "album") {
943       set_album(tostr(x));
944     }
945     else if (Song::kColumns.value(i) == "artist") {
946       set_artist(tostr(x));
947     }
948     else if (Song::kColumns.value(i) == "albumartist") {
949       set_albumartist(tostr(x));
950     }
951     else if (Song::kColumns.value(i) == "track") {
952       d->track_ = toint(x);
953     }
954     else if (Song::kColumns.value(i) == "disc") {
955       d->disc_ = toint(x);
956     }
957     else if (Song::kColumns.value(i) == "year") {
958       d->year_ = toint(x);
959     }
960     else if (Song::kColumns.value(i) == "originalyear") {
961       d->originalyear_ = toint(x);
962     }
963     else if (Song::kColumns.value(i) == "genre") {
964       d->genre_ = tostr(x);
965     }
966     else if (Song::kColumns.value(i) == "compilation") {
967       d->compilation_ = q.value(x).toBool();
968     }
969     else if (Song::kColumns.value(i) == "composer") {
970       d->composer_ = tostr(x);
971     }
972     else if (Song::kColumns.value(i) == "performer") {
973       d->performer_ = tostr(x);
974     }
975     else if (Song::kColumns.value(i) == "grouping") {
976       d->grouping_ = tostr(x);
977     }
978     else if (Song::kColumns.value(i) == "comment") {
979       d->comment_ = tostr(x);
980     }
981     else if (Song::kColumns.value(i) == "lyrics") {
982       d->lyrics_ = tostr(x);
983     }
984 
985     else if (Song::kColumns.value(i) == "artist_id") {
986       d->artist_id_ = tostr(x);
987     }
988     else if (Song::kColumns.value(i) == "album_id") {
989       d->album_id_ = tostr(x);
990     }
991     else if (Song::kColumns.value(i) == "song_id") {
992       d->song_id_ = tostr(x);
993     }
994 
995     else if (Song::kColumns.value(i) == "beginning") {
996       d->beginning_ = q.value(x).isNull() ? 0 : q.value(x).toLongLong();
997     }
998     else if (Song::kColumns.value(i) == "length") {
999       set_length_nanosec(tolonglong(x));
1000     }
1001 
1002     else if (Song::kColumns.value(i) == "bitrate") {
1003       d->bitrate_ = toint(x);
1004     }
1005     else if (Song::kColumns.value(i) == "samplerate") {
1006       d->samplerate_ = toint(x);
1007     }
1008     else if (Song::kColumns.value(i) == "bitdepth") {
1009       d->bitdepth_ = toint(x);
1010     }
1011 
1012     else if (Song::kColumns.value(i) == "source") {
1013       d->source_ = Source(q.value(x).toInt());
1014     }
1015     else if (Song::kColumns.value(i) == "directory_id") {
1016       d->directory_id_ = toint(x);
1017     }
1018     else if (Song::kColumns.value(i) == "url") {
1019       set_url(QUrl::fromEncoded(tostr(x).toUtf8()));
1020       d->basefilename_ = QFileInfo(d->url_.toLocalFile()).fileName();
1021     }
1022     else if (Song::kColumns.value(i) == "filetype") {
1023       d->filetype_ = FileType(q.value(x).toInt());
1024     }
1025     else if (Song::kColumns.value(i) == "filesize") {
1026       d->filesize_ = toint(x);
1027     }
1028     else if (Song::kColumns.value(i) == "mtime") {
1029       d->mtime_ = tolonglong(x);
1030     }
1031     else if (Song::kColumns.value(i) == "ctime") {
1032       d->ctime_ = tolonglong(x);
1033     }
1034     else if (Song::kColumns.value(i) == "unavailable") {
1035       d->unavailable_ = q.value(x).toBool();
1036     }
1037 
1038     else if (Song::kColumns.value(i) == "fingerprint") {
1039       d->fingerprint_ = tostr(x);
1040     }
1041 
1042     else if (Song::kColumns.value(i) == "playcount") {
1043       d->playcount_ = q.value(x).isNull() ? 0 : q.value(x).toInt();
1044     }
1045     else if (Song::kColumns.value(i) == "skipcount") {
1046       d->skipcount_ = q.value(x).isNull() ? 0 : q.value(x).toInt();
1047     }
1048     else if (Song::kColumns.value(i) == "lastplayed") {
1049       d->lastplayed_ = tolonglong(x);
1050     }
1051     else if (Song::kColumns.value(i) == "lastseen") {
1052       d->lastseen_ = tolonglong(x);
1053     }
1054 
1055     else if (Song::kColumns.value(i) == "compilation_detected") {
1056       d->compilation_detected_ = q.value(x).toBool();
1057     }
1058     else if (Song::kColumns.value(i) == "compilation_on") {
1059       d->compilation_on_ = q.value(x).toBool();
1060     }
1061     else if (Song::kColumns.value(i) == "compilation_off") {
1062       d->compilation_off_ = q.value(x).toBool();
1063     }
1064     else if (Song::kColumns.value(i) == "compilation_effective") {
1065     }
1066 
1067     else if (Song::kColumns.value(i) == "art_automatic") {
1068       QString art_automatic = tostr(x);
1069       if (art_automatic.contains(QRegularExpression("..+:.*"))) {
1070         set_art_automatic(QUrl::fromEncoded(art_automatic.toUtf8()));
1071       }
1072       else {
1073         set_art_automatic(QUrl::fromLocalFile(art_automatic));
1074       }
1075     }
1076     else if (Song::kColumns.value(i) == "art_manual") {
1077       QString art_manual = tostr(x);
1078       if (art_manual.contains(QRegularExpression("..+:.*"))) {
1079         set_art_manual(QUrl::fromEncoded(art_manual.toUtf8()));
1080       }
1081       else {
1082         set_art_manual(QUrl::fromLocalFile(art_manual));
1083       }
1084     }
1085 
1086     else if (Song::kColumns.value(i) == "effective_albumartist") {
1087     }
1088     else if (Song::kColumns.value(i) == "effective_originalyear") {
1089     }
1090 
1091     else if (Song::kColumns.value(i) == "cue_path") {
1092       d->cue_path_ = tostr(x);
1093     }
1094 
1095     else if (Song::kColumns.value(i) == "rating") {
1096       d->rating_ = todouble(x);
1097     }
1098 
1099     else {
1100       qLog(Error) << "Forgot to handle" << Song::kColumns.value(i);
1101     }
1102   }
1103 
1104   d->valid_ = true;
1105   d->init_from_file_ = reliable_metadata;
1106 
1107   InitArtManual();
1108 
1109 #undef tostr
1110 #undef toint
1111 #undef tolonglong
1112 #undef todouble
1113 
1114 }
1115 
InitFromFilePartial(const QString & filename,const QFileInfo & fileinfo)1116 void Song::InitFromFilePartial(const QString &filename, const QFileInfo &fileinfo) {
1117 
1118   set_url(QUrl::fromLocalFile(filename));
1119   d->valid_ = true;
1120   d->source_ = Source_LocalFile;
1121   d->filetype_ = FiletypeByExtension(fileinfo.suffix());
1122   d->basefilename_ = fileinfo.fileName();
1123   d->title_ = fileinfo.fileName();
1124   if (d->art_manual_.isEmpty()) InitArtManual();
1125 
1126 }
1127 
InitArtManual()1128 void Song::InitArtManual() {
1129 
1130   QString album = effective_album();
1131   album.remove(Song::kAlbumRemoveDisc);
1132 
1133   // If we don't have an art, check if we have one in the cache
1134   if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty() && !effective_albumartist().isEmpty() && !album.isEmpty()) {
1135     QString filename(Utilities::Sha1CoverHash(effective_albumartist(), album).toHex() + ".jpg");
1136     QString path(ImageCacheDir(d->source_) + "/" + filename);
1137     if (QFile::exists(path)) {
1138       d->art_manual_ = QUrl::fromLocalFile(path);
1139     }
1140   }
1141 
1142 }
1143 
InitArtAutomatic()1144 void Song::InitArtAutomatic() {
1145 
1146   if (d->source_ == Source_LocalFile && d->url_.isLocalFile() && d->art_automatic_.isEmpty()) {
1147     // Pick the first image file in the album directory.
1148     QFileInfo file(d->url_.toLocalFile());
1149     QDir dir(file.path());
1150     QStringList files = dir.entryList(QStringList() << "*.jpg" << "*.png" << "*.gif" << "*.jpeg", QDir::Files|QDir::Readable, QDir::Name);
1151     if (files.count() > 0) {
1152       d->art_automatic_ = QUrl::fromLocalFile(file.path() + QDir::separator() + files.first());
1153     }
1154   }
1155 
1156 }
1157 
1158 #ifdef HAVE_LIBGPOD
InitFromItdb(Itdb_Track * track,const QString & prefix)1159 void Song::InitFromItdb(Itdb_Track *track, const QString &prefix) {
1160 
1161   d->valid_ = true;
1162 
1163   set_title(QString::fromUtf8(track->title));
1164   set_album(QString::fromUtf8(track->album));
1165   set_artist(QString::fromUtf8(track->artist));
1166   set_albumartist(QString::fromUtf8(track->albumartist));
1167   d->track_ = track->track_nr;
1168   d->disc_ = track->cd_nr;
1169   d->year_ = track->year;
1170   d->genre_ = QString::fromUtf8(track->genre);
1171   d->compilation_ = track->compilation == 1;
1172   d->composer_ = QString::fromUtf8(track->composer);
1173   d->grouping_ = QString::fromUtf8(track->grouping);
1174   d->comment_ = QString::fromUtf8(track->comment);
1175 
1176   set_length_nanosec(track->tracklen * kNsecPerMsec);
1177 
1178   d->bitrate_ = track->bitrate;
1179   d->samplerate_ = track->samplerate;
1180   d->bitdepth_ = -1; //track->bitdepth;
1181 
1182   d->source_ = Source_Device;
1183   QString filename = QString::fromLocal8Bit(track->ipod_path);
1184   filename.replace(':', '/');
1185   if (prefix.contains("://")) {
1186     set_url(QUrl(prefix + filename));
1187   }
1188   else {
1189     set_url(QUrl::fromLocalFile(prefix + filename));
1190   }
1191   d->basefilename_ = QFileInfo(filename).fileName();
1192 
1193   d->filetype_ = track->type2 ? FileType_MPEG : FileType_MP4;
1194   d->filesize_ = track->size;
1195   d->mtime_ = track->time_modified;
1196   d->ctime_ = track->time_added;
1197 
1198   d->playcount_ = track->playcount;
1199   d->skipcount_ = track->skipcount;
1200   d->lastplayed_ = track->time_played;
1201 
1202   if (itdb_track_has_thumbnails(track) && !d->artist_.isEmpty() && !d->title_.isEmpty()) {
1203     GdkPixbuf *pixbuf = static_cast<GdkPixbuf*>(itdb_track_get_thumbnail(track, -1, -1));
1204     if (pixbuf) {
1205       QString cover_path = ImageCacheDir(Source_Device);
1206       QDir dir(cover_path);
1207       if (!dir.exists()) dir.mkpath(cover_path);
1208       QString cover_file = cover_path + "/" + Utilities::Sha1CoverHash(effective_albumartist(), effective_album()).toHex() + ".jpg";
1209       GError *error = nullptr;
1210       if (dir.exists() && gdk_pixbuf_save(pixbuf, cover_file.toUtf8().constData(), "jpeg", &error, nullptr)) {
1211         d->art_manual_ = QUrl::fromLocalFile(cover_file);
1212       }
1213       g_object_unref(pixbuf);
1214     }
1215   }
1216 
1217 }
1218 
ToItdb(Itdb_Track * track) const1219 void Song::ToItdb(Itdb_Track *track) const {
1220 
1221   track->title = strdup(d->title_.toUtf8().constData());
1222   track->album = strdup(d->album_.toUtf8().constData());
1223   track->artist = strdup(d->artist_.toUtf8().constData());
1224   track->albumartist = strdup(d->albumartist_.toUtf8().constData());
1225   track->track_nr = d->track_;
1226   track->cd_nr = d->disc_;
1227   track->year = d->year_;
1228   track->genre = strdup(d->genre_.toUtf8().constData());
1229   track->compilation = d->compilation_;
1230   track->composer = strdup(d->composer_.toUtf8().constData());
1231   track->grouping = strdup(d->grouping_.toUtf8().constData());
1232   track->comment = strdup(d->comment_.toUtf8().constData());
1233 
1234   track->tracklen = length_nanosec() / kNsecPerMsec;
1235 
1236   track->bitrate = d->bitrate_;
1237   track->samplerate = d->samplerate_;
1238 
1239   track->type1 = (d->filetype_ == FileType_MPEG ? 1 : 0);
1240   track->type2 = (d->filetype_ == FileType_MPEG ? 1 : 0);
1241   track->mediatype = 1;              // Audio
1242   track->size = d->filesize_;
1243   track->time_modified = d->mtime_;
1244   track->time_added = d->ctime_;
1245 
1246   track->playcount = d->playcount_;
1247   track->skipcount = d->skipcount_;
1248   track->time_played = d->lastplayed_;
1249 
1250 }
1251 #endif
1252 
1253 #ifdef HAVE_LIBMTP
InitFromMTP(const LIBMTP_track_t * track,const QString & host)1254 void Song::InitFromMTP(const LIBMTP_track_t *track, const QString &host) {
1255 
1256   d->valid_ = true;
1257   d->source_ = Source_Device;
1258 
1259   set_title(QString::fromUtf8(track->title));
1260   set_artist(QString::fromUtf8(track->artist));
1261   set_album(QString::fromUtf8(track->album));
1262   d->genre_ = QString::fromUtf8(track->genre);
1263   d->composer_ = QString::fromUtf8(track->composer);
1264   d->track_ = track->tracknumber;
1265 
1266   d->url_ = QUrl(QString("mtp://%1/%2").arg(host, QString::number(track->item_id)));
1267   d->basefilename_ = QString::number(track->item_id);
1268   d->filesize_ = track->filesize;
1269   d->mtime_ = track->modificationdate;
1270   d->ctime_ = track->modificationdate;
1271 
1272   set_length_nanosec(track->duration * kNsecPerMsec);
1273 
1274   d->samplerate_ = track->samplerate;
1275   d->bitdepth_ = 0;
1276   d->bitrate_ = track->bitrate;
1277 
1278   d->playcount_ = track->usecount;
1279 
1280   switch (track->filetype) {
1281       case LIBMTP_FILETYPE_WAV:  d->filetype_ = FileType_WAV;       break;
1282       case LIBMTP_FILETYPE_MP3:  d->filetype_ = FileType_MPEG;      break;
1283       case LIBMTP_FILETYPE_WMA:  d->filetype_ = FileType_ASF;       break;
1284       case LIBMTP_FILETYPE_OGG:  d->filetype_ = FileType_OggVorbis; break;
1285       case LIBMTP_FILETYPE_MP4:  d->filetype_ = FileType_MP4;       break;
1286       case LIBMTP_FILETYPE_AAC:  d->filetype_ = FileType_MP4;       break;
1287       case LIBMTP_FILETYPE_FLAC: d->filetype_ = FileType_OggFlac;   break;
1288       case LIBMTP_FILETYPE_MP2:  d->filetype_ = FileType_MPEG;      break;
1289       case LIBMTP_FILETYPE_M4A:  d->filetype_ = FileType_MP4;       break;
1290       default:
1291         d->filetype_ = FileType_Unknown;
1292         d->valid_ = false;
1293         break;
1294   }
1295 
1296 }
1297 
ToMTP(LIBMTP_track_t * track) const1298 void Song::ToMTP(LIBMTP_track_t *track) const {
1299 
1300   track->item_id = 0;
1301   track->parent_id = 0;
1302   track->storage_id = 0;
1303 
1304   track->title = strdup(d->title_.toUtf8().constData());
1305   track->artist = strdup(effective_albumartist().toUtf8().constData());
1306   track->album = strdup(d->album_.toUtf8().constData());
1307   track->genre = strdup(d->genre_.toUtf8().constData());
1308   track->date = nullptr;
1309   track->tracknumber = d->track_;
1310   if (d->composer_.isEmpty())
1311     track->composer = nullptr;
1312   else
1313     track->composer = strdup(d->composer_.toUtf8().constData());
1314 
1315   track->filename = strdup(d->basefilename_.toUtf8().constData());
1316 
1317   track->filesize = d->filesize_;
1318   track->modificationdate = d->mtime_;
1319 
1320   track->duration = length_nanosec() / kNsecPerMsec;
1321 
1322   track->bitrate = d->bitrate_;
1323   track->bitratetype = 0;
1324   track->samplerate = d->samplerate_;
1325   track->nochannels = 0;
1326   track->wavecodec = 0;
1327 
1328   track->usecount = d->playcount_;
1329 
1330   switch (d->filetype_) {
1331     case FileType_ASF:       track->filetype = LIBMTP_FILETYPE_ASF;         break;
1332     case FileType_MP4:       track->filetype = LIBMTP_FILETYPE_MP4;         break;
1333     case FileType_MPEG:      track->filetype = LIBMTP_FILETYPE_MP3;         break;
1334     case FileType_FLAC:
1335     case FileType_OggFlac:   track->filetype = LIBMTP_FILETYPE_FLAC;        break;
1336     case FileType_OggSpeex:
1337     case FileType_OggVorbis: track->filetype = LIBMTP_FILETYPE_OGG;         break;
1338     case FileType_WAV:       track->filetype = LIBMTP_FILETYPE_WAV;         break;
1339     default:                 track->filetype = LIBMTP_FILETYPE_UNDEF_AUDIO; break;
1340   }
1341 
1342 }
1343 #endif
1344 
MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle & bundle)1345 bool Song::MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle) {
1346 
1347   d->valid_ = true;
1348 
1349   bool minor = true;
1350 
1351   if (d->init_from_file_ || is_collection_song() || d->url_.isLocalFile()) {
1352     // This Song was already loaded using taglib. Our tags are probably better than the engine's.
1353     if (title() != bundle.title && title().isEmpty() && !bundle.title.isEmpty()) {
1354       set_title(bundle.title);
1355       minor = false;
1356     }
1357     if (artist() != bundle.artist && artist().isEmpty() && !bundle.artist.isEmpty()) {
1358       set_artist(bundle.artist);
1359       minor = false;
1360     }
1361     if (album() != bundle.album && album().isEmpty() && !bundle.album.isEmpty()) {
1362       set_album(bundle.album);
1363       minor = false;
1364     }
1365     if (comment().isEmpty() && !bundle.comment.isEmpty()) set_comment(bundle.comment);
1366     if (genre().isEmpty() && !bundle.genre.isEmpty()) set_genre(bundle.genre);
1367     if (lyrics().isEmpty() && !bundle.lyrics.isEmpty()) set_lyrics(bundle.lyrics);
1368   }
1369   else {
1370     if (title() != bundle.title && !bundle.title.isEmpty()) {
1371       set_title(bundle.title);
1372       minor = false;
1373     }
1374     if (artist() != bundle.artist && !bundle.artist.isEmpty()) {
1375       set_artist(bundle.artist);
1376       minor = false;
1377     }
1378     if (album() != bundle.album && !bundle.album.isEmpty()) {
1379       set_album(bundle.album);
1380       minor = false;
1381     }
1382     if (!bundle.comment.isEmpty()) set_comment(bundle.comment);
1383     if (!bundle.genre.isEmpty()) set_genre(bundle.genre);
1384     if (!bundle.lyrics.isEmpty()) set_lyrics(bundle.lyrics);
1385   }
1386 
1387   if (bundle.length > 0) set_length_nanosec(bundle.length);
1388   if (bundle.year > 0) d->year_ = bundle.year;
1389   if (bundle.track > 0) d->track_ = bundle.track;
1390   if (bundle.filetype != FileType_Unknown) d->filetype_ = bundle.filetype;
1391   if (bundle.samplerate > 0) d->samplerate_ = bundle.samplerate;
1392   if (bundle.bitdepth > 0) d->bitdepth_ = bundle.bitdepth;
1393   if (bundle.bitrate > 0) d->bitrate_ = bundle.bitrate;
1394 
1395   return minor;
1396 
1397 }
1398 
BindToQuery(SqlQuery * query) const1399 void Song::BindToQuery(SqlQuery *query) const {
1400 
1401 #define strval(x) ((x).isNull() ? "" : (x))
1402 #define intval(x) ((x) <= 0 ? -1 : (x))
1403 #define notnullintval(x) ((x) == -1 ? QVariant() : (x))
1404 
1405   // Remember to bind these in the same order as kBindSpec
1406 
1407   query->BindValue(":title", strval(d->title_));
1408   query->BindValue(":album", strval(d->album_));
1409   query->BindValue(":artist", strval(d->artist_));
1410   query->BindValue(":albumartist", strval(d->albumartist_));
1411   query->BindValue(":track", intval(d->track_));
1412   query->BindValue(":disc", intval(d->disc_));
1413   query->BindValue(":year", intval(d->year_));
1414   query->BindValue(":originalyear", intval(d->originalyear_));
1415   query->BindValue(":genre", strval(d->genre_));
1416   query->BindValue(":compilation", d->compilation_ ? 1 : 0);
1417   query->BindValue(":composer", strval(d->composer_));
1418   query->BindValue(":performer", strval(d->performer_));
1419   query->BindValue(":grouping", strval(d->grouping_));
1420   query->BindValue(":comment", strval(d->comment_));
1421   query->BindValue(":lyrics", strval(d->lyrics_));
1422 
1423   query->BindValue(":artist_id", strval(d->artist_id_));
1424   query->BindValue(":album_id", strval(d->album_id_));
1425   query->BindValue(":song_id", strval(d->song_id_));
1426 
1427   query->BindValue(":beginning", d->beginning_);
1428   query->BindValue(":length", intval(length_nanosec()));
1429 
1430   query->BindValue(":bitrate", intval(d->bitrate_));
1431   query->BindValue(":samplerate", intval(d->samplerate_));
1432   query->BindValue(":bitdepth", intval(d->bitdepth_));
1433 
1434   query->BindValue(":source", d->source_);
1435   query->BindValue(":directory_id", notnullintval(d->directory_id_));
1436   query->BindValue(":url", d->url_.toString(QUrl::FullyEncoded));
1437   query->BindValue(":filetype", d->filetype_);
1438   query->BindValue(":filesize", notnullintval(d->filesize_));
1439   query->BindValue(":mtime", notnullintval(d->mtime_));
1440   query->BindValue(":ctime", notnullintval(d->ctime_));
1441   query->BindValue(":unavailable", d->unavailable_ ? 1 : 0);
1442 
1443   query->BindValue(":fingerprint", strval(d->fingerprint_));
1444 
1445   query->BindValue(":playcount", d->playcount_);
1446   query->BindValue(":skipcount", d->skipcount_);
1447   query->BindValue(":lastplayed", intval(d->lastplayed_));
1448   query->BindValue(":lastseen", intval(d->lastseen_));
1449 
1450   query->BindValue(":compilation_detected", d->compilation_detected_ ? 1 : 0);
1451   query->BindValue(":compilation_on", d->compilation_on_ ? 1 : 0);
1452   query->BindValue(":compilation_off", d->compilation_off_ ? 1 : 0);
1453   query->BindValue(":compilation_effective", is_compilation() ? 1 : 0);
1454 
1455   query->BindValue(":art_automatic", d->art_automatic_.isValid() ? d->art_automatic_.toString(QUrl::FullyEncoded) : "");
1456   query->BindValue(":art_manual", d->art_manual_.isValid() ? d->art_manual_.toString(QUrl::FullyEncoded) : "");
1457 
1458   query->BindValue(":effective_albumartist", strval(effective_albumartist()));
1459   query->BindValue(":effective_originalyear", intval(effective_originalyear()));
1460 
1461   query->BindValue(":cue_path", d->cue_path_);
1462 
1463   query->BindValue(":rating", intval(d->rating_));
1464 
1465 #undef intval
1466 #undef notnullintval
1467 #undef strval
1468 
1469 }
1470 
BindToFtsQuery(SqlQuery * query) const1471 void Song::BindToFtsQuery(SqlQuery *query) const {
1472 
1473   query->BindValue(":ftstitle", d->title_);
1474   query->BindValue(":ftsalbum", d->album_);
1475   query->BindValue(":ftsartist", d->artist_);
1476   query->BindValue(":ftsalbumartist", d->albumartist_);
1477   query->BindValue(":ftscomposer", d->composer_);
1478   query->BindValue(":ftsperformer", d->performer_);
1479   query->BindValue(":ftsgrouping", d->grouping_);
1480   query->BindValue(":ftsgenre", d->genre_);
1481   query->BindValue(":ftscomment", d->comment_);
1482 
1483 }
1484 
PrettyTitle() const1485 QString Song::PrettyTitle() const {
1486 
1487   QString title(d->title_);
1488 
1489   if (title.isEmpty()) title = d->basefilename_;
1490   if (title.isEmpty()) title = d->url_.toString();
1491 
1492   return title;
1493 
1494 }
1495 
PrettyTitleWithArtist() const1496 QString Song::PrettyTitleWithArtist() const {
1497 
1498   QString title(PrettyTitle());
1499 
1500   if (!d->artist_.isEmpty()) title = d->artist_ + " - " + title;
1501 
1502   return title;
1503 
1504 }
1505 
PrettyLength() const1506 QString Song::PrettyLength() const {
1507 
1508   if (length_nanosec() == -1) return QString();
1509 
1510   return Utilities::PrettyTimeNanosec(length_nanosec());
1511 
1512 }
1513 
PrettyYear() const1514 QString Song::PrettyYear() const {
1515 
1516   if (d->year_ == -1) return QString();
1517 
1518   return QString::number(d->year_);
1519 
1520 }
1521 
PrettyOriginalYear() const1522 QString Song::PrettyOriginalYear() const {
1523 
1524   if (effective_originalyear() == -1) return QString();
1525 
1526   return QString::number(effective_originalyear());
1527 
1528 }
1529 
TitleWithCompilationArtist() const1530 QString Song::TitleWithCompilationArtist() const {
1531 
1532   QString title(d->title_);
1533 
1534   if (title.isEmpty()) title = d->basefilename_;
1535 
1536   if (is_compilation() && !d->artist_.isEmpty() && !d->artist_.contains("various", Qt::CaseInsensitive)) title = d->artist_ + " - " + title;
1537 
1538   return title;
1539 
1540 }
1541 
SampleRateBitDepthToText() const1542 QString Song::SampleRateBitDepthToText() const {
1543 
1544   if (d->samplerate_ == -1) return QString("");
1545   if (d->bitdepth_ == -1) return QString("%1 hz").arg(d->samplerate_);
1546 
1547   return QString("%1 hz / %2 bit").arg(d->samplerate_).arg(d->bitdepth_);
1548 
1549 }
1550 
PrettyRating() const1551 QString Song::PrettyRating() const {
1552 
1553   double rating = d->rating_;
1554 
1555   if (rating == -1.0F) return "0";
1556 
1557   return QString::number(static_cast<int>(rating * 100));
1558 
1559 }
1560 
IsMetadataEqual(const Song & other) const1561 bool Song::IsMetadataEqual(const Song &other) const {
1562 
1563   return d->title_ == other.d->title_ &&
1564          d->album_ == other.d->album_ &&
1565          d->artist_ == other.d->artist_ &&
1566          d->albumartist_ == other.d->albumartist_ &&
1567          d->track_ == other.d->track_ &&
1568          d->disc_ == other.d->disc_ &&
1569          d->year_ == other.d->year_ &&
1570          d->originalyear_ == other.d->originalyear_ &&
1571          d->genre_ == other.d->genre_ &&
1572          d->compilation_ == other.d->compilation_ &&
1573          d->composer_ == other.d->composer_ &&
1574          d->performer_ == other.d->performer_ &&
1575          d->grouping_ == other.d->grouping_ &&
1576          d->comment_ == other.d->comment_ &&
1577          d->lyrics_ == other.d->lyrics_ &&
1578          d->artist_id_ == other.d->artist_id_ &&
1579          d->album_id_ == other.d->album_id_ &&
1580          d->song_id_ == other.d->song_id_ &&
1581          d->beginning_ == other.d->beginning_ &&
1582          length_nanosec() == other.length_nanosec() &&
1583          d->bitrate_ == other.d->bitrate_ &&
1584          d->samplerate_ == other.d->samplerate_ &&
1585          d->bitdepth_ == other.d->bitdepth_ &&
1586          d->cue_path_ == other.d->cue_path_;
1587 }
1588 
IsMetadataAndMoreEqual(const Song & other) const1589 bool Song::IsMetadataAndMoreEqual(const Song &other) const {
1590 
1591   return IsMetadataEqual(other) &&
1592          d->fingerprint_ == other.d->fingerprint_ &&
1593          d->art_automatic_ == other.d->art_automatic_ &&
1594          d->art_manual_ == other.d->art_manual_;
1595 
1596 }
1597 
IsEditable() const1598 bool Song::IsEditable() const {
1599 
1600   return d->valid_ &&
1601          !d->url_.isEmpty() &&
1602          (d->url_.isLocalFile() || d->source_ == Source_Stream) &&
1603          !has_cue();
1604 
1605 }
1606 
operator ==(const Song & other) const1607 bool Song::operator==(const Song &other) const {
1608   return source() == other.source() && url() == other.url() && beginning_nanosec() == other.beginning_nanosec();
1609 }
1610 
operator !=(const Song & other) const1611 bool Song::operator!=(const Song &other) const {
1612   return source() != other.source() || url() != other.url() || beginning_nanosec() != other.beginning_nanosec();
1613 }
1614 
1615 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
qHash(const Song & song)1616 size_t qHash(const Song &song) {
1617 #else
1618 uint qHash(const Song &song) {
1619 #endif
1620   // Should compare the same fields as operator==
1621   return qHash(song.url().toString()) ^ qHash(song.beginning_nanosec());
1622 }
1623 
1624 bool Song::IsSimilar(const Song &other) const {
1625   return title().compare(other.title(), Qt::CaseInsensitive) == 0 &&
1626          artist().compare(other.artist(), Qt::CaseInsensitive) == 0 &&
1627          album().compare(other.album(), Qt::CaseInsensitive) == 0;
1628 }
1629 
1630 size_t HashSimilar(const Song &song) {
1631   // Should compare the same fields as function IsSimilar
1632   return qHash(song.title().toLower()) ^ qHash(song.artist().toLower()) ^ qHash(song.album().toLower());
1633 }
1634 
1635 bool Song::IsOnSameAlbum(const Song &other) const {
1636 
1637   if (is_compilation() != other.is_compilation()) return false;
1638 
1639   if (has_cue() && other.has_cue() && cue_path() == other.cue_path()) {
1640     return true;
1641   }
1642 
1643   if (is_compilation() && album() == other.album()) return true;
1644 
1645   return effective_album() == other.effective_album() && effective_albumartist() == other.effective_albumartist();
1646 
1647 }
1648 
1649 QString Song::AlbumKey() const {
1650   return QString("%1|%2|%3").arg(is_compilation() ? "_compilation" : effective_albumartist(), has_cue() ? cue_path() : "", effective_album());
1651 }
1652 
1653 void Song::ToXesam(QVariantMap *map) const {
1654 
1655   using mpris::AddMetadata;
1656   using mpris::AddMetadataAsList;
1657   using mpris::AsMPRISDateTimeType;
1658 
1659   AddMetadata("xesam:url", effective_stream_url().toString(), map);
1660   AddMetadata("xesam:title", PrettyTitle(), map);
1661   AddMetadataAsList("xesam:artist", artist(), map);
1662   AddMetadata("xesam:album", album(), map);
1663   AddMetadataAsList("xesam:albumArtist", albumartist(), map);
1664   AddMetadata("mpris:length", length_nanosec() / kNsecPerUsec, map);
1665   AddMetadata("xesam:trackNumber", track(), map);
1666   AddMetadataAsList("xesam:genre", genre(), map);
1667   AddMetadata("xesam:discNumber", disc(), map);
1668   AddMetadataAsList("xesam:comment", comment(), map);
1669   AddMetadata("xesam:contentCreated", AsMPRISDateTimeType(ctime()), map);
1670   AddMetadata("xesam:lastUsed", AsMPRISDateTimeType(lastplayed()), map);
1671   AddMetadataAsList("xesam:composer", composer(), map);
1672   AddMetadata("xesam:useCount", playcount(), map);
1673 
1674 }
1675 
1676 void Song::MergeUserSetData(const Song &other) {
1677 
1678   set_playcount(other.playcount());
1679   set_skipcount(other.skipcount());
1680   set_lastplayed(other.lastplayed());
1681   set_art_manual(other.art_manual());
1682   set_compilation_on(other.compilation_on());
1683   set_compilation_off(other.compilation_off());
1684   set_rating(other.rating());
1685 
1686 }
1687