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