diff --git a/data/data.qrc b/data/data.qrc index a24df5e34..b8fd2e22b 100644 --- a/data/data.qrc +++ b/data/data.qrc @@ -3,6 +3,7 @@ schema/schema.sql schema/schema-1.sql schema/schema-2.sql + schema/schema-3.sql schema/device-schema.sql style/strawberry.css misc/playing_tooltip.txt diff --git a/data/schema/device-schema.sql b/data/schema/device-schema.sql index 804faf5a4..b2c8b0590 100644 --- a/data/schema/device-schema.sql +++ b/data/schema/device-schema.sql @@ -11,8 +11,6 @@ CREATE TABLE device_%deviceid_subdirectories ( CREATE TABLE device_%deviceid_songs ( - /* Metadata from taglib */ - title TEXT NOT NULL, album TEXT NOT NULL, artist TEXT NOT NULL, @@ -27,6 +25,7 @@ CREATE TABLE device_%deviceid_songs ( performer TEXT NOT NULL, grouping TEXT NOT NULL, comment TEXT NOT NULL, + lyrics TEXT NOT NULL, beginning INTEGER NOT NULL DEFAULT 0, length INTEGER NOT NULL DEFAULT 0, @@ -35,8 +34,7 @@ CREATE TABLE device_%deviceid_songs ( samplerate INTEGER NOT NULL DEFAULT 0, bitdepth INTEGER NOT NULL DEFAULT 0, - /* Information about the file on disk */ - + source INTEGER NOT NULL DEFAULT 0, directory_id INTEGER NOT NULL, filename TEXT NOT NULL, filetype INTEGER NOT NULL DEFAULT 0, @@ -45,8 +43,6 @@ CREATE TABLE device_%deviceid_songs ( ctime INTEGER NOT NULL, unavailable INTEGER DEFAULT 0, - /* Other */ - playcount INTEGER NOT NULL DEFAULT 0, skipcount INTEGER NOT NULL DEFAULT 0, lastplayed INTEGER NOT NULL DEFAULT 0, diff --git a/data/schema/schema-3.sql b/data/schema/schema-3.sql new file mode 100644 index 000000000..f37fe5f82 --- /dev/null +++ b/data/schema/schema-3.sql @@ -0,0 +1,65 @@ +ALTER TABLE songs ADD COLUMN source INTEGER NOT NULL DEFAULT 0; + +UPDATE songs SET source = 2 WHERE source = 0; + +DROP TABLE playlist_items; + +CREATE TABLE IF NOT EXISTS playlist_items ( + + playlist INTEGER NOT NULL, + type INTEGER NOT NULL DEFAULT 0, + collection_id INTEGER, + url TEXT, + + title TEXT NOT NULL, + album TEXT NOT NULL, + artist TEXT NOT NULL, + albumartist TEXT NOT NULL, + track INTEGER NOT NULL DEFAULT -1, + disc INTEGER NOT NULL DEFAULT -1, + year INTEGER NOT NULL DEFAULT -1, + originalyear INTEGER NOT NULL DEFAULT 0, + genre TEXT NOT NULL, + compilation INTEGER NOT NULL DEFAULT -1, + composer TEXT NOT NULL, + performer TEXT NOT NULL, + grouping TEXT NOT NULL, + comment TEXT NOT NULL, + lyrics TEXT NOT NULL, + + beginning INTEGER NOT NULL DEFAULT 0, + length INTEGER NOT NULL DEFAULT 0, + + bitrate INTEGER NOT NULL DEFAULT 0, + samplerate INTEGER NOT NULL DEFAULT 0, + bitdepth INTEGER NOT NULL DEFAULT 0, + + source INTEGER NOT NULL DEFAULT 0, + directory_id INTEGER, + filename TEXT, + filetype INTEGER NOT NULL DEFAULT 0, + filesize INTEGER, + mtime INTEGER, + ctime INTEGER, + unavailable INTEGER DEFAULT 0, + + playcount INTEGER NOT NULL DEFAULT 0, + skipcount INTEGER NOT NULL DEFAULT 0, + lastplayed INTEGER NOT NULL DEFAULT 0, + + compilation_detected INTEGER DEFAULT 0, + compilation_on INTEGER NOT NULL DEFAULT 0, + compilation_off INTEGER NOT NULL DEFAULT 0, + compilation_effective INTEGER NOT NULL DEFAULT 0, + + art_automatic TEXT, + art_manual TEXT, + + effective_albumartist TEXT, + effective_originalyear INTEGER NOT NULL DEFAULT 0, + + cue_path TEXT + +); + +UPDATE schema_version SET version=3; diff --git a/data/schema/schema.sql b/data/schema/schema.sql index 26e815cf2..942e7cff6 100644 --- a/data/schema/schema.sql +++ b/data/schema/schema.sql @@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version ( DELETE FROM schema_version; -INSERT INTO schema_version (version) VALUES (2); +INSERT INTO schema_version (version) VALUES (3); CREATE TABLE IF NOT EXISTS directories ( path TEXT NOT NULL, @@ -19,8 +19,6 @@ CREATE TABLE IF NOT EXISTS subdirectories ( CREATE TABLE IF NOT EXISTS songs ( - /* Metadata from taglib */ - title TEXT NOT NULL, album TEXT NOT NULL, artist TEXT NOT NULL, @@ -44,8 +42,7 @@ CREATE TABLE IF NOT EXISTS songs ( samplerate INTEGER NOT NULL DEFAULT 0, bitdepth INTEGER NOT NULL DEFAULT 0, - /* Information about the file on disk */ - + source INTEGER NOT NULL DEFAULT 0, directory_id INTEGER NOT NULL, filename TEXT NOT NULL, filetype INTEGER NOT NULL DEFAULT 0, @@ -54,8 +51,6 @@ CREATE TABLE IF NOT EXISTS songs ( ctime INTEGER NOT NULL, unavailable INTEGER DEFAULT 0, - /* Other */ - playcount INTEGER NOT NULL DEFAULT 0, skipcount INTEGER NOT NULL DEFAULT 0, lastplayed INTEGER NOT NULL DEFAULT 0, @@ -89,13 +84,10 @@ CREATE TABLE IF NOT EXISTS playlists ( CREATE TABLE IF NOT EXISTS playlist_items ( playlist INTEGER NOT NULL, - type TEXT NOT NULL, + type INTEGER NOT NULL DEFAULT 0, collection_id INTEGER, - internet_service TEXT, url TEXT, - /* Metadata from taglib */ - title TEXT NOT NULL, album TEXT NOT NULL, artist TEXT NOT NULL, @@ -119,8 +111,7 @@ CREATE TABLE IF NOT EXISTS playlist_items ( samplerate INTEGER NOT NULL DEFAULT 0, bitdepth INTEGER NOT NULL DEFAULT 0, - /* Information about the file on disk */ - + source INTEGER NOT NULL DEFAULT 0, directory_id INTEGER, filename TEXT, filetype INTEGER NOT NULL DEFAULT 0, @@ -129,8 +120,6 @@ CREATE TABLE IF NOT EXISTS playlist_items ( ctime INTEGER, unavailable INTEGER DEFAULT 0, - /* Other */ - playcount INTEGER NOT NULL DEFAULT 0, skipcount INTEGER NOT NULL DEFAULT 0, lastplayed INTEGER NOT NULL DEFAULT 0, diff --git a/ext/libstrawberry-tagreader/tagreader.cpp b/ext/libstrawberry-tagreader/tagreader.cpp index e0e1e2ef9..ee904c110 100644 --- a/ext/libstrawberry-tagreader/tagreader.cpp +++ b/ext/libstrawberry-tagreader/tagreader.cpp @@ -1,5 +1,6 @@ /* This file is part of Strawberry. Copyright 2013, David Sansome + Copyright 2018, Jonas Kvinge Strawberry is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -474,27 +475,27 @@ void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, con } -pb::tagreader::SongMetadata_Type TagReader::GuessFileType(TagLib::FileRef *fileref) const { +pb::tagreader::SongMetadata_FileType TagReader::GuessFileType(TagLib::FileRef *fileref) const { - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_WAV; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_FLAC; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_WAVPACK; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGFLAC; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGVORBIS; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGOPUS; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGSPEEX; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_MPEG; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_MP4; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_ASF; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_AIFF; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_MPC; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_TRUEAUDIO; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_WAV; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_FLAC; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_WAVPACK; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_OGGFLAC; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_OGGVORBIS; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_OGGOPUS; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_OGGSPEEX; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_MPEG; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_MP4; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_ASF; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_AIFF; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_MPC; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_TRUEAUDIO; #ifdef HAVE_TAGLIB_DSFFILE - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_DSF; - if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_Type_DSDIFF; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_DSF; + if (dynamic_cast(fileref->file())) return pb::tagreader::SongMetadata_FileType_DSDIFF; #endif - return pb::tagreader::SongMetadata_Type_UNKNOWN; + return pb::tagreader::SongMetadata_FileType_UNKNOWN; } diff --git a/ext/libstrawberry-tagreader/tagreader.h b/ext/libstrawberry-tagreader/tagreader.h index 3cd94d688..354a0f0e3 100644 --- a/ext/libstrawberry-tagreader/tagreader.h +++ b/ext/libstrawberry-tagreader/tagreader.h @@ -1,5 +1,6 @@ /* This file is part of Strawberry. Copyright 2013, David Sansome + Copyright 2018, Jonas Kvinge Strawberry is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -64,7 +65,7 @@ class TagReader { void ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const; void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const; - pb::tagreader::SongMetadata_Type GuessFileType(TagLib::FileRef *fileref) const; + pb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const; void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const; void SetUserTextFrame(const std::string &description, const std::string& value, TagLib::ID3v2::Tag *tag) const; diff --git a/ext/libstrawberry-tagreader/tagreadermessages.proto b/ext/libstrawberry-tagreader/tagreadermessages.proto index c54206c15..3802110e3 100644 --- a/ext/libstrawberry-tagreader/tagreadermessages.proto +++ b/ext/libstrawberry-tagreader/tagreadermessages.proto @@ -4,7 +4,7 @@ package pb.tagreader; message SongMetadata { - enum Type { + enum FileType { UNKNOWN = 0; WAV = 1; FLAC = 2; @@ -51,7 +51,7 @@ message SongMetadata { optional string url = 21; optional string basefilename = 22; - optional Type filetype = 23; + optional FileType filetype = 23; optional int32 filesize = 24; optional int32 mtime = 25; optional int32 ctime = 26; diff --git a/src/collection/collection.cpp b/src/collection/collection.cpp index 94eba5889..0e48366b4 100644 --- a/src/collection/collection.cpp +++ b/src/collection/collection.cpp @@ -63,7 +63,6 @@ SCollection::SCollection(Application *app, QObject *parent) } SCollection::~SCollection() { - watcher_->deleteLater(); watcher_thread_->exit(); watcher_thread_->wait(5000 /* five seconds */); diff --git a/src/collection/collectionbackend.cpp b/src/collection/collectionbackend.cpp index 19ebbcad1..7c11d2d4d 100644 --- a/src/collection/collectionbackend.cpp +++ b/src/collection/collectionbackend.cpp @@ -720,7 +720,6 @@ SongList CollectionBackend::GetSongsByUrl(const QUrl &url) { while (query.Next()) { Song song; song.InitFromQuery(query, true); - songlist << song; } } diff --git a/src/collection/collectionplaylistitem.cpp b/src/collection/collectionplaylistitem.cpp index bf380c502..74d2ef24a 100644 --- a/src/collection/collectionplaylistitem.cpp +++ b/src/collection/collectionplaylistitem.cpp @@ -29,11 +29,13 @@ class SqlRow; -CollectionPlaylistItem::CollectionPlaylistItem(const QString &type) - : PlaylistItem(type) {} +CollectionPlaylistItem::CollectionPlaylistItem(const Song::Source &source) + : PlaylistItem(source) {} CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) - : PlaylistItem("Collection"), song_(song) {} + : PlaylistItem(Song::Source_Collection), song_(song) { + song_.set_source(Song::Source_Collection); +} QUrl CollectionPlaylistItem::Url() const { return song_.url(); } @@ -44,7 +46,6 @@ void CollectionPlaylistItem::Reload() { bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) { // Rows from the songs tables come first song_.InitFromQuery(query, true); - return song_.is_valid(); } @@ -59,4 +60,3 @@ Song CollectionPlaylistItem::Metadata() const { if (HasTemporaryMetadata()) return temp_metadata_; return song_; } - diff --git a/src/collection/collectionplaylistitem.h b/src/collection/collectionplaylistitem.h index c17419d5e..0af69afec 100644 --- a/src/collection/collectionplaylistitem.h +++ b/src/collection/collectionplaylistitem.h @@ -36,7 +36,7 @@ class SqlRow; class CollectionPlaylistItem : public PlaylistItem { public: - CollectionPlaylistItem(const QString &type); + CollectionPlaylistItem(const Song::Source &source); CollectionPlaylistItem(const Song &song); bool InitFromQuery(const SqlRow &query); diff --git a/src/collection/collectionwatcher.cpp b/src/collection/collectionwatcher.cpp index 4a5832307..c86ddacbc 100644 --- a/src/collection/collectionwatcher.cpp +++ b/src/collection/collectionwatcher.cpp @@ -134,7 +134,7 @@ CollectionWatcher::ScanTransaction::~ScanTransaction() { if (watcher_->monitor_) { // Watch the new subdirectories - for (const Subdirectory& subdir : new_subdirs) { + for (const Subdirectory &subdir : new_subdirs) { watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path); } } @@ -197,8 +197,7 @@ SubdirectoryList CollectionWatcher::ScanTransaction::GetImmediateSubdirs(const Q SubdirectoryList ret; for (const Subdirectory &subdir : known_subdirs_) { - if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && - subdir.mtime != 0) { + if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && subdir.mtime != 0) { ret << subdir; } } @@ -251,7 +250,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory // Do not scan symlinked dirs that are already in collection if (path_info.isSymLink()) { QString real_path = path_info.symLinkTarget(); - for (const Directory& dir : watched_dirs_) { + for (const Directory &dir : watched_dirs_) { if (real_path.startsWith(dir.path)) { t->AddToProgress(1); return; @@ -278,7 +277,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory // If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist any more. // If one has been removed, "rescan" it to get the deleted songs SubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path); - for (const Subdirectory& subdir : previous_subdirs) { + for (const Subdirectory &subdir : previous_subdirs) { if (!QFile::exists(subdir.path) && subdir.path != path) { t->AddToProgressMax(1); ScanSubdirectory(subdir.path, subdir, t, true); @@ -322,7 +321,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory QSet cues_processed; // Now compare the list from the database with the list of files on disk - for (const QString& file : files_on_disk) { + for (const QString &file : files_on_disk) { if (stop_requested_) return; // associated cue @@ -389,16 +388,16 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory QString image = ImageForSong(file, album_art); for (Song song : song_list) { + song.set_source(Song::Source_Collection); song.set_directory_id(t->dir()); if (song.art_automatic().isEmpty()) song.set_art_automatic(image); - t->new_songs << song; } } } // Look for deleted songs - for (const Song& song : songs_in_db) { + for (const Song &song : songs_in_db) { if (!song.is_unavailable() && !files_on_disk.contains(song.url().toLocalFile())) { qLog(Debug) << "Song deleted from disk:" << song.url().toLocalFile(); t->deleted_songs << song; @@ -420,7 +419,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory // Recurse into the new subdirs that we found t->AddToProgressMax(my_new_subdirs.count()); - for (const Subdirectory& my_new_subdir : my_new_subdirs) { + for (const Subdirectory &my_new_subdir : my_new_subdirs) { if (stop_requested_) return; ScanSubdirectory(my_new_subdir.path, my_new_subdir, t, true); } @@ -435,7 +434,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file, const QStr SongList old_sections = backend_->GetSongsByUrl(QUrl::fromLocalFile(file)); QHash sections_map; - for (const Song& song : old_sections) { + for (const Song &song : old_sections) { sections_map[song.beginning_nanosec()] = song; } @@ -443,6 +442,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file, const QStr // Update every song that's in the cue and collection for (Song cue_song : cue_parser_->Load(&cue, matching_cue, path)) { + cue_song.set_source(Song::Source_Collection); cue_song.set_directory_id(t->dir()); Song matching = sections_map[cue_song.beginning_nanosec()]; @@ -450,7 +450,8 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file, const QStr if (!matching.is_valid()) { t->new_songs << cue_song; // changed section - } else { + } + else { PreserveUserSetData(file, image, matching, &cue_song, t); used_ids.insert(matching.id()); } @@ -469,8 +470,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file, const So // If a cue got deleted, we turn it's first section into the new 'raw' (cueless) song and we just remove the rest of the sections from the collection if (cue_deleted) { - for (const Song &song : - backend_->GetSongsByUrl(QUrl::fromLocalFile(file))) { + for (const Song &song : backend_->GetSongsByUrl(QUrl::fromLocalFile(file))) { if (!song.IsMetadataEqual(matching_song)) { t->deleted_songs << song; } @@ -478,6 +478,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file, const So } Song song_on_disk; + song_on_disk.set_source(Song::Source_Collection); song_on_disk.set_directory_id(t->dir()); TagReaderClient::Instance()->ReadFileBlocking(file, &song_on_disk); @@ -504,7 +505,7 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path // Also, watch out for incorrect media files. // Playlist parser for CUEs considers every entry in sheet valid and we don't want invalid media getting into collection! QString file_nfd = file.normalized(QString::NormalizationForm_D); - for (const Song& cue_song : cue_parser_->Load(&cue, matching_cue, path)) { + for (const Song &cue_song : cue_parser_->Load(&cue, matching_cue, path)) { if (cue_song.url().toLocalFile().normalized(QString::NormalizationForm_D) == file_nfd) { if (TagReaderClient::Instance()->IsMediaFileBlocking(file)) { song_list << cue_song; @@ -663,7 +664,7 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) { for (const QString &filter_text : best_image_filters_) { // The images in the images list are represented by a full path, so we need to isolate just the filename - for (const QString& image : images) { + for (const QString &image : images) { QFileInfo file_info(image); QString filename(file_info.fileName()); if (filename.contains(filter_text, Qt::CaseInsensitive)) @@ -683,7 +684,7 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) { int biggest_size = 0; QString biggest_path; - for (const QString& path : filtered) { + for (const QString &path : filtered) { QImage image(path); if (image.isNull()) continue; diff --git a/src/core/database.cpp b/src/core/database.cpp index 99b13fb35..5847933a1 100644 --- a/src/core/database.cpp +++ b/src/core/database.cpp @@ -52,7 +52,7 @@ #include "scopedtransaction.h" const char *Database::kDatabaseFilename = "strawberry.db"; -const int Database::kSchemaVersion = 2; +const int Database::kSchemaVersion = 3; const char *Database::kMagicAllSongsTables = "%allsongstables"; int Database::sNextConnectionId = 1; diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index 3d37cf5ba..f2a072722 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -293,14 +293,14 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co // Icons qLog(Debug) << "Creating UI"; - + // Help menu - + ui_->action_about_strawberry->setIcon(IconLoader::Load("strawberry")); ui_->action_about_qt->setIcon(QIcon(":/qt-project.org/qmessagebox/images/qtlogo-64.png")); - + // Music menu - + ui_->action_open_file->setIcon(IconLoader::Load("document-open")); ui_->action_open_cd->setIcon(IconLoader::Load("cd")); ui_->action_previous_track->setIcon(IconLoader::Load("media-rewind")); @@ -309,9 +309,9 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co ui_->action_stop_after_this_track->setIcon(IconLoader::Load("media-stop")); ui_->action_next_track->setIcon(IconLoader::Load("media-forward")); ui_->action_quit->setIcon(IconLoader::Load("application-exit")); - + // Playlist - + ui_->action_add_file->setIcon(IconLoader::Load("document-open")); ui_->action_add_folder->setIcon(IconLoader::Load("document-open-folder")); ui_->action_shuffle_mode->setIcon(IconLoader::Load("media-playlist-shuffle")); @@ -324,11 +324,11 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co ui_->action_shuffle->setIcon(IconLoader::Load("media-playlist-shuffle")); ui_->action_remove_duplicates->setIcon(IconLoader::Load("list-remove")); ui_->action_remove_unavailable->setIcon(IconLoader::Load("list-remove")); - + //ui_->action_remove_from_playlist->setIcon(IconLoader::Load("list-remove")); - + // Configure - + ui_->action_cover_manager->setIcon(IconLoader::Load("document-download")); ui_->action_queue_manager->setIcon(IconLoader::Load("footsteps")); ui_->action_edit_track->setIcon(IconLoader::Load("edit-rename")); @@ -452,9 +452,9 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co connect(ui_->playlist->view(), SIGNAL(BackgroundPropertyChanged()), SLOT(RefreshStyleSheet())); connect(ui_->track_slider, SIGNAL(ValueChangedSeconds(int)), app_->player(), SLOT(SeekTo(int))); - + // Context connections - + connect(context_view_->albums(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); // Collection connections @@ -1848,7 +1848,7 @@ void MainWindow::EditFileTags(const QList &urls) { Song song; song.set_url(url); song.set_valid(true); - song.set_filetype(Song::Type_MPEG); + song.set_filetype(Song::FileType_MPEG); songs << song; } diff --git a/src/core/musicstorage.h b/src/core/musicstorage.h index ea75953fe..05b2dbd02 100644 --- a/src/core/musicstorage.h +++ b/src/core/musicstorage.h @@ -71,7 +71,7 @@ class MusicStorage { virtual QString LocalPath() const { return QString(); } virtual TranscodeMode GetTranscodeMode() const { return Transcode_Never; } - virtual Song::FileType GetTranscodeFormat() const { return Song::Type_Unknown; } + virtual Song::FileType GetTranscodeFormat() const { return Song::FileType_Unknown; } virtual bool GetSupportedFiletypes(QList* ret) { return true; } virtual bool StartCopy(QList* supported_types) { return true;} diff --git a/src/core/organise.cpp b/src/core/organise.cpp index 10d9a7b9b..f271e1952 100644 --- a/src/core/organise.cpp +++ b/src/core/organise.cpp @@ -154,7 +154,7 @@ void Organise::ProcessSomeFiles() { else { // Figure out if we need to transcode it Song::FileType dest_type = CheckTranscode(song.filetype()); - if (dest_type != Song::Type_Unknown) { + if (dest_type != Song::FileType_Unknown) { // Get the preset TranscoderPreset preset = Transcoder::PresetForFileType(dest_type); qLog(Debug) << "Transcoding with" << preset.name_; @@ -206,28 +206,28 @@ void Organise::ProcessSomeFiles() { Song::FileType Organise::CheckTranscode(Song::FileType original_type) const { - //if (original_type == Song::Type_Stream) return Song::Type_Unknown; + if (original_type == Song::FileType_Stream) return Song::FileType_Unknown; const MusicStorage::TranscodeMode mode = destination_->GetTranscodeMode(); const Song::FileType format = destination_->GetTranscodeFormat(); switch (mode) { case MusicStorage::Transcode_Never: - return Song::Type_Unknown; + return Song::FileType_Unknown; case MusicStorage::Transcode_Always: - if (original_type == format) return Song::Type_Unknown; + if (original_type == format) return Song::FileType_Unknown; return format; case MusicStorage::Transcode_Unsupported: - if (supported_filetypes_.isEmpty() || supported_filetypes_.contains(original_type)) return Song::Type_Unknown; + if (supported_filetypes_.isEmpty() || supported_filetypes_.contains(original_type)) return Song::FileType_Unknown; - if (format != Song::Type_Unknown) return format; + if (format != Song::FileType_Unknown) return format; // The user hasn't visited the device properties page yet to set a preferred format for the device, so we have to pick the best available one. return Transcoder::PickBestFormat(supported_filetypes_); } - return Song::Type_Unknown; + return Song::FileType_Unknown; } diff --git a/src/core/player.cpp b/src/core/player.cpp index 7169fc1e9..e092e4ea2 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/player.h b/src/core/player.h index f38bc656b..4bce66a7a 100644 --- a/src/core/player.h +++ b/src/core/player.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/core/song.cpp b/src/core/song.cpp index 66581443d..ca3e89b19 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +54,7 @@ #include "core/logging.h" #include "core/messagehandler.h" +#include "core/iconloader.h" #include "engine/enginebase.h" #include "timeconstants.h" @@ -86,6 +89,7 @@ const QStringList Song::kColumns = QStringList() << "title" << "samplerate" << "bitdepth" + << "source" << "directory_id" << "filename" << "filetype" @@ -167,6 +171,7 @@ struct Song::Private : public QSharedData { int samplerate_; int bitdepth_; + Source source_; int directory_id_; QString basefilename_; QUrl url_; @@ -210,14 +215,16 @@ Song::Private::Private() end_(-1), bitrate_(-1), samplerate_(-1), + bitdepth_(-1), + source_(Source_Unknown), directory_id_(-1), - filetype_(Type_Unknown), + filetype_(FileType_Unknown), filesize_(-1), mtime_(-1), ctime_(-1), unavailable_(false), - + playcount_(0), skipcount_(0), lastplayed_(-1), @@ -283,6 +290,7 @@ qint64 Song::length_nanosec() const { return d->end_ - d->beginning_; } int Song::bitrate() const { return d->bitrate_; } int Song::samplerate() const { return d->samplerate_; } int Song::bitdepth() const { return d->bitdepth_; } +Song::Source Song::source() const { return d->source_; } int Song::directory_id() const { return d->directory_id_; } const QUrl &Song::url() const { return d->url_; } const QString &Song::basefilename() const { return d->basefilename_; } @@ -290,8 +298,8 @@ uint Song::mtime() const { return d->mtime_; } uint Song::ctime() const { return d->ctime_; } int Song::filesize() const { return d->filesize_; } Song::FileType Song::filetype() const { return d->filetype_; } -bool Song::is_stream() const { return d->filetype_ == Type_Stream; } -bool Song::is_cdda() const { return d->filetype_ == Type_CDDA; } +bool Song::is_stream() const { return d->source_ == Source_Stream || d->source_ == Source_Tidal; } +bool Song::is_cdda() const { return d->source_ == Source_CDDA; } bool Song::is_collection_song() const { return !is_cdda() && !is_stream() && id() != -1; } @@ -331,6 +339,7 @@ void Song::set_bitrate(int v) { d->bitrate_ = v; } void Song::set_samplerate(int v) { d->samplerate_ = v; } void Song::set_bitdepth(int v) { d->bitdepth_ = v; } +void Song::set_source(Source v) { d->source_ = v; } void Song::set_directory_id(int v) { d->directory_id_ = v; } void Song::set_url(const QUrl &v) { if (Application::kIsPortable) { @@ -366,39 +375,93 @@ QString Song::JoinSpec(const QString &table) { return Utilities::Prepend(table + ".", kColumns).join(", "); } -QString Song::TextForFiletype(FileType type) { +QString Song::TextForSource(Source source) { - switch (type) { - case Song::Type_WAV: return QObject::tr("Wav"); - case Song::Type_FLAC: return QObject::tr("FLAC"); - case Song::Type_WavPack: return QObject::tr("WavPack"); - case Song::Type_OggFlac: return QObject::tr("Ogg FLAC"); - case Song::Type_OggVorbis: return QObject::tr("Ogg Vorbis"); - case Song::Type_OggOpus: return QObject::tr("Ogg Opus"); - case Song::Type_OggSpeex: return QObject::tr("Ogg Speex"); - case Song::Type_MPEG: return QObject::tr("MP3"); - case Song::Type_MP4: return QObject::tr("MP4 AAC"); - case Song::Type_ASF: return QObject::tr("Windows Media audio"); - case Song::Type_AIFF: return QObject::tr("AIFF"); - case Song::Type_MPC: return QObject::tr("MPC"); - case Song::Type_TrueAudio: return QObject::tr("TrueAudio"); - case Song::Type_DSF: return QObject::tr("DSF"); // .dsf - case Song::Type_DSDIFF: return QObject::tr("DSDIFF"); // .dff - case Song::Type_CDDA: return QObject::tr("CDDA"); - case Song::Type_Stream: return QObject::tr("Stream"); - case Song::Type_Unknown: - default: return QObject::tr("Unknown"); + switch (source) { + case Song::Source_LocalFile: return QObject::tr("File"); + case Song::Source_Collection: return QObject::tr("Collection"); + case Song::Source_CDDA: return QObject::tr("CD"); + case Song::Source_Device: return QObject::tr("Device"); + case Song::Source_Stream: return QObject::tr("Stream"); + case Song::Source_Tidal: return QObject::tr("Tidal"); + default: return QObject::tr("Unknown"); + } + +} + +QIcon Song::IconForSource(Source source) { + + switch (source) { + case Song::Source_LocalFile: return IconLoader::Load("folder-sound"); + case Song::Source_Collection: return IconLoader::Load("vinyl"); + case Song::Source_CDDA: return IconLoader::Load("cd"); + case Song::Source_Device: return IconLoader::Load("device"); + case Song::Source_Stream: return IconLoader::Load("applications-internet"); + case Song::Source_Tidal: return IconLoader::Load("tidal"); + default: return IconLoader::Load("edit-delete"); + } + +} + +QString Song::TextForFiletype(FileType filetype) { + + switch (filetype) { + case Song::FileType_WAV: return QObject::tr("Wav"); + case Song::FileType_FLAC: return QObject::tr("FLAC"); + case Song::FileType_WavPack: return QObject::tr("WavPack"); + case Song::FileType_OggFlac: return QObject::tr("Ogg FLAC"); + case Song::FileType_OggVorbis: return QObject::tr("Ogg Vorbis"); + case Song::FileType_OggOpus: return QObject::tr("Ogg Opus"); + case Song::FileType_OggSpeex: return QObject::tr("Ogg Speex"); + case Song::FileType_MPEG: return QObject::tr("MP3"); + case Song::FileType_MP4: return QObject::tr("MP4 AAC"); + case Song::FileType_ASF: return QObject::tr("Windows Media audio"); + case Song::FileType_AIFF: return QObject::tr("AIFF"); + case Song::FileType_MPC: return QObject::tr("MPC"); + case Song::FileType_TrueAudio: return QObject::tr("TrueAudio"); + case Song::FileType_DSF: return QObject::tr("DSF"); + case Song::FileType_DSDIFF: return QObject::tr("DSDIFF"); + case Song::FileType_CDDA: return QObject::tr("CDDA"); + case Song::FileType_Stream: return QObject::tr("Stream"); + case Song::FileType_Unknown: + default: return QObject::tr("Unknown"); + } + +} + +QIcon Song::IconForFiletype(FileType filetype) { + + switch (filetype) { + case Song::FileType_WAV: return IconLoader::Load("wav"); + case Song::FileType_FLAC: return IconLoader::Load("flac"); + case Song::FileType_WavPack: return IconLoader::Load("wavpack"); + case Song::FileType_OggFlac: return IconLoader::Load("flac"); + case Song::FileType_OggVorbis: return IconLoader::Load("vorbis"); + case Song::FileType_OggOpus: return IconLoader::Load("opus"); + case Song::FileType_OggSpeex: return IconLoader::Load("speex"); + case Song::FileType_MPEG: return IconLoader::Load("mp3"); + case Song::FileType_MP4: return IconLoader::Load("mp4"); + case Song::FileType_ASF: return IconLoader::Load("wma"); + case Song::FileType_AIFF: return IconLoader::Load("aiff"); + case Song::FileType_MPC: return IconLoader::Load("mpc"); + case Song::FileType_TrueAudio: return IconLoader::Load("trueaudio"); + case Song::FileType_DSF: return IconLoader::Load("dsf"); + case Song::FileType_DSDIFF: return IconLoader::Load("dsd"); + case Song::FileType_CDDA: return IconLoader::Load("cd"); + case Song::FileType_Stream: return IconLoader::Load("applications-internet"); + case Song::FileType_Unknown: + default: return IconLoader::Load("edit-delete"); } } bool Song::IsFileLossless() const { switch (filetype()) { - case Song::Type_WAV: - case Song::Type_FLAC: - case Song::Type_OggFlac: - case Song::Type_WavPack: - case Song::Type_AIFF: + case Song::FileType_WAV: + case Song::FileType_FLAC: + case Song::FileType_OggFlac: + case Song::FileType_WavPack: + case Song::FileType_AIFF: return true; default: return false; @@ -407,20 +470,20 @@ bool Song::IsFileLossless() const { Song::FileType Song::FiletypeByExtension(QString ext) { - if (ext.toLower() == "wav" || ext.toLower() == "wave") return Song::Type_WAV; - else if (ext.toLower() == "flac") return Song::Type_FLAC; - else if (ext.toLower() == "wavpack" || ext.toLower() == "wv") return Song::Type_WavPack; - else if (ext.toLower() == "ogg" || ext.toLower() == "oga") return Song::Type_OggVorbis; - else if (ext.toLower() == "opus") return Song::Type_OggOpus; - else if (ext.toLower() == "speex" || ext.toLower() == "spx") return Song::Type_OggSpeex; - else if (ext.toLower() == "mp3") return Song::Type_MPEG; - else if (ext.toLower() == "mp4" || ext.toLower() == "m4a" || ext.toLower() == "aac") return Song::Type_MP4; - else if (ext.toLower() == "asf" || ext.toLower() == "wma") return Song::Type_ASF; - else if (ext.toLower() == "aiff" || ext.toLower() == "aif" || ext.toLower() == "aifc") return Song::Type_AIFF; - else if (ext.toLower() == "mpc" || ext.toLower() == "mp+" || ext.toLower() == "mpp") return Song::Type_MPC; - else if (ext.toLower() == "dsf") return Song::Type_DSF; - else if (ext.toLower() == "dsd" || ext.toLower() == "dff") return Song::Type_DSDIFF; - else return Song::Type_Unknown; + if (ext.toLower() == "wav" || ext.toLower() == "wave") return Song::FileType_WAV; + else if (ext.toLower() == "flac") return Song::FileType_FLAC; + else if (ext.toLower() == "wavpack" || ext.toLower() == "wv") return Song::FileType_WavPack; + else if (ext.toLower() == "ogg" || ext.toLower() == "oga") return Song::FileType_OggVorbis; + else if (ext.toLower() == "opus") return Song::FileType_OggOpus; + else if (ext.toLower() == "speex" || ext.toLower() == "spx") return Song::FileType_OggSpeex; + else if (ext.toLower() == "mp3") return Song::FileType_MPEG; + else if (ext.toLower() == "mp4" || ext.toLower() == "m4a" || ext.toLower() == "aac") return Song::FileType_MP4; + else if (ext.toLower() == "asf" || ext.toLower() == "wma") return Song::FileType_ASF; + else if (ext.toLower() == "aiff" || ext.toLower() == "aif" || ext.toLower() == "aifc") return Song::FileType_AIFF; + else if (ext.toLower() == "mpc" || ext.toLower() == "mp+" || ext.toLower() == "mpp") return Song::FileType_MPC; + else if (ext.toLower() == "dsf") return Song::FileType_DSF; + else if (ext.toLower() == "dsd" || ext.toLower() == "dff") return Song::FileType_DSDIFF; + else return Song::FileType_Unknown; } @@ -547,7 +610,7 @@ void Song::ToProtobuf(pb::tagreader::SongMetadata *pb) const { pb->set_filesize(d->filesize_); pb->set_suspicious_tags(d->suspicious_tags_); pb->set_art_automatic(DataCommaSizeFromQString(d->art_automatic_)); - pb->set_filetype(static_cast(d->filetype_)); + pb->set_filetype(static_cast(d->filetype_)); } #define tostr(n) (q.value(n).isNull() ? QString::null : q.value(n).toString()) @@ -635,6 +698,9 @@ void Song::InitFromQuery(const SqlRow &q, bool reliable_metadata, int col) { d->bitdepth_ = toint(x); } + else if (Song::kColumns.value(i) == "source") { + d->source_ = Source(q.value(x).toInt()); + } else if (Song::kColumns.value(i) == "directory_id") { d->directory_id_ = toint(x); } @@ -721,8 +787,10 @@ void Song::InitFromFilePartial(const QString &filename) { QString suffix = info.suffix().toLower(); TagLib::FileRef fileref(filename.toUtf8().constData()); - //if (TagLib::FileRef::defaultFileExtensions().contains(suffix.toUtf8().constData())) { - if (fileref.file()) d->valid_ = true; + if (fileref.file()) { + d->valid_ = true; + d->source_ = Source_LocalFile; + } else { d->valid_ = false; qLog(Error) << "File" << filename << "is not recognized by TagLib as a valid audio file."; @@ -772,6 +840,7 @@ void Song::InitFromItdb(const Itdb_Track *track, const QString &prefix) { d->samplerate_ = track->samplerate; d->bitdepth_ = -1; //track->bitdepth; + d->source_ = Source_Device; QString filename = QString::fromLocal8Bit(track->ipod_path); filename.replace(':', '/'); if (prefix.contains("://")) { @@ -780,8 +849,8 @@ void Song::InitFromItdb(const Itdb_Track *track, const QString &prefix) { set_url(QUrl::fromLocalFile(prefix + filename)); } d->basefilename_ = QFileInfo(filename).fileName(); - - d->filetype_ = track->type2 ? Type_MPEG : Type_MP4; + + d->filetype_ = track->type2 ? FileType_MPEG : FileType_MP4; d->filesize_ = track->size; d->mtime_ = track->time_modified; d->ctime_ = track->time_added; @@ -814,7 +883,7 @@ void Song::ToItdb(Itdb_Track *track) const { //track->bithdepth = d->bithdepth_; track->type1 = 0; - track->type2 = d->filetype_ == Type_MP4 ? 0 : 1; + track->type2 = d->filetype_ == FileType_MP4 ? 0 : 1; track->mediatype = 1; // Audio track->size = d->filesize_; track->time_modified = d->mtime_; @@ -854,18 +923,20 @@ void Song::InitFromMTP(const LIBMTP_track_t *track, const QString &host) { d->playcount_ = track->usecount; switch (track->filetype) { - case LIBMTP_FILETYPE_WAV: d->filetype_ = Type_WAV; break; - case LIBMTP_FILETYPE_MP3: d->filetype_ = Type_MPEG; break; - case LIBMTP_FILETYPE_WMA: d->filetype_ = Type_ASF; break; - case LIBMTP_FILETYPE_OGG: d->filetype_ = Type_OggVorbis; break; - case LIBMTP_FILETYPE_MP4: d->filetype_ = Type_MP4; break; - case LIBMTP_FILETYPE_AAC: d->filetype_ = Type_MP4; break; - case LIBMTP_FILETYPE_FLAC: d->filetype_ = Type_OggFlac; break; - case LIBMTP_FILETYPE_MP2: d->filetype_ = Type_MPEG; break; - case LIBMTP_FILETYPE_M4A: d->filetype_ = Type_MP4; break; - default: d->filetype_ = Type_Unknown; break; + case LIBMTP_FILETYPE_WAV: d->filetype_ = FileType_WAV; break; + case LIBMTP_FILETYPE_MP3: d->filetype_ = FileType_MPEG; break; + case LIBMTP_FILETYPE_WMA: d->filetype_ = FileType_ASF; break; + case LIBMTP_FILETYPE_OGG: d->filetype_ = FileType_OggVorbis; break; + case LIBMTP_FILETYPE_MP4: d->filetype_ = FileType_MP4; break; + case LIBMTP_FILETYPE_AAC: d->filetype_ = FileType_MP4; break; + case LIBMTP_FILETYPE_FLAC: d->filetype_ = FileType_OggFlac; break; + case LIBMTP_FILETYPE_MP2: d->filetype_ = FileType_MPEG; break; + case LIBMTP_FILETYPE_M4A: d->filetype_ = FileType_MP4; break; + default: d->filetype_ = FileType_Unknown; break; } + d->source_ = Source_Device; + } void Song::ToMTP(LIBMTP_track_t *track) const { @@ -897,15 +968,15 @@ void Song::ToMTP(LIBMTP_track_t *track) const { track->usecount = d->playcount_; switch (d->filetype_) { - case Type_ASF: track->filetype = LIBMTP_FILETYPE_ASF; break; - case Type_MP4: track->filetype = LIBMTP_FILETYPE_MP4; break; - case Type_MPEG: track->filetype = LIBMTP_FILETYPE_MP3; break; - case Type_FLAC: - case Type_OggFlac: track->filetype = LIBMTP_FILETYPE_FLAC; break; - case Type_OggSpeex: - case Type_OggVorbis: track->filetype = LIBMTP_FILETYPE_OGG; break; - case Type_WAV: track->filetype = LIBMTP_FILETYPE_WAV; break; - default: track->filetype = LIBMTP_FILETYPE_UNDEF_AUDIO; break; + case FileType_ASF: track->filetype = LIBMTP_FILETYPE_ASF; break; + case FileType_MP4: track->filetype = LIBMTP_FILETYPE_MP4; break; + case FileType_MPEG: track->filetype = LIBMTP_FILETYPE_MP3; break; + case FileType_FLAC: + case FileType_OggFlac: track->filetype = LIBMTP_FILETYPE_FLAC; break; + case FileType_OggSpeex: + case FileType_OggVorbis: track->filetype = LIBMTP_FILETYPE_OGG; break; + case FileType_WAV: track->filetype = LIBMTP_FILETYPE_WAV; break; + default: track->filetype = LIBMTP_FILETYPE_UNDEF_AUDIO; break; } } @@ -965,6 +1036,7 @@ void Song::BindToQuery(QSqlQuery *query) const { query->bindValue(":samplerate", intval(d->samplerate_)); query->bindValue(":bitdepth", intval(d->bitdepth_)); + query->bindValue(":source", notnullintval(d->source_)); query->bindValue(":directory_id", notnullintval(d->directory_id_)); if (Application::kIsPortable && Utilities::UrlOnSameDriveAsStrawberry(d->url_)) { @@ -1103,7 +1175,7 @@ bool Song::IsMetadataEqual(const Song &other) const { } bool Song::IsEditable() const { - return d->valid_ && !d->url_.isEmpty() && !is_stream() && d->filetype_ != Type_Unknown && !has_cue(); + return d->valid_ && !d->url_.isEmpty() && !is_stream() && d->source_ != Source_Unknown && d->filetype_ != FileType_Unknown && !has_cue(); } bool Song::operator==(const Song &other) const { diff --git a/src/core/song.h b/src/core/song.h index e8b775af0..93feb1e33 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,6 +38,7 @@ #include #include #include +#include #include #include @@ -87,29 +89,48 @@ class Song { // Don't change these values - they're stored in the database, and defined in the tag reader protobuf. // If a new lossless file is added, also add it to IsFileLossless(). - enum FileType { - Type_Unknown = 0, - Type_WAV = 1, - Type_FLAC = 2, - Type_WavPack = 3, - Type_OggFlac = 4, - Type_OggVorbis = 5, - Type_OggOpus = 6, - Type_OggSpeex = 7, - Type_MPEG = 8, - Type_MP4 = 9, - Type_ASF = 10, - Type_AIFF = 11, - Type_MPC = 12, - Type_TrueAudio = 13, - Type_DSF = 14, - Type_DSDIFF = 15, - Type_CDDA = 90, - Type_Stream = 91, + + enum Source { + Source_Unknown = 0, + Source_LocalFile = 1, + Source_Collection = 2, + Source_CDDA = 3, + Source_Device = 4, + Source_Stream = 5, + Source_Tidal = 6, }; - static QString TextForFiletype(FileType type); + enum FileType { + FileType_Unknown = 0, + FileType_WAV = 1, + FileType_FLAC = 2, + FileType_WavPack = 3, + FileType_OggFlac = 4, + FileType_OggVorbis = 5, + FileType_OggOpus = 6, + FileType_OggSpeex = 7, + FileType_MPEG = 8, + FileType_MP4 = 9, + FileType_ASF = 10, + FileType_AIFF = 11, + FileType_MPC = 12, + FileType_TrueAudio = 13, + FileType_DSF = 14, + FileType_DSDIFF = 15, + FileType_CDDA = 90, + FileType_Stream = 91, + }; + + static QString TextForSource(Source source); + static QIcon IconForSource(Source source); + static QString TextForFiletype(FileType filetype); + QIcon IconForFiletype(FileType filetype); + + QString TextForSource() const { return TextForSource(source()); } + QIcon IconForSource() const { return IconForSource(source()); } QString TextForFiletype() const { return TextForFiletype(filetype()); } + QIcon IconForFiletype(FileType filetype) const { return IconForFiletype(filetype); } + bool IsFileLossless() const; static FileType FiletypeByExtension(QString ext); @@ -182,6 +203,7 @@ class Song { int samplerate() const; int bitdepth() const; + Source source() const; int directory_id() const; const QUrl &url() const; const QString &basefilename() const; @@ -260,7 +282,8 @@ class Song { void set_bitrate(int v); void set_samplerate(int v); void set_bitdepth(int v); - + + void set_source(Source v); void set_directory_id(int v); void set_url(const QUrl &v); void set_basefilename(const QString &v); diff --git a/src/core/songloader.cpp b/src/core/songloader.cpp index 6f9633765..4cf15da4e 100644 --- a/src/core/songloader.cpp +++ b/src/core/songloader.cpp @@ -276,7 +276,7 @@ void SongLoader::EffectiveSongLoad(Song *song) { if (!song) return; - if (song->filetype() != Song::Type_Unknown) { + if (song->filetype() != Song::FileType_Unknown) { // Maybe we loaded the metadata already, for example from a cuesheet. return; } @@ -335,7 +335,7 @@ void SongLoader::LoadLocalDirectory(const QString &filename) { void SongLoader::AddAsRawStream() { Song song; song.set_valid(true); - song.set_filetype(Song::Type_Stream); + song.set_filetype(Song::FileType_Stream); song.set_url(url_); song.set_title(url_.toString()); songs_ << song; @@ -481,7 +481,7 @@ GstPadProbeReturn SongLoader::DataReady(GstPad*, GstPadProbeInfo *info, gpointer #ifdef HAVE_GSTREAMER gboolean SongLoader::BusCallback(GstBus *, GstMessage *msg, gpointer self) { - + SongLoader *instance = reinterpret_cast(self); switch (GST_MESSAGE_TYPE(msg)) { diff --git a/src/device/cddasongloader.cpp b/src/device/cddasongloader.cpp index 8bcbf3fab..e2ef24298 100644 --- a/src/device/cddasongloader.cpp +++ b/src/device/cddasongloader.cpp @@ -110,7 +110,8 @@ void CddaSongLoader::LoadSongs() { Song song; song.set_id(track_number); song.set_valid(true); - song.set_filetype(Song::Type_CDDA); + song.set_source(Song::Source_CDDA); + song.set_filetype(Song::FileType_CDDA); song.set_url(GetUrlFromTrack(track_number)); song.set_title(QString("Track %1").arg(track_number)); song.set_track(track_number); @@ -207,7 +208,8 @@ void CddaSongLoader::AudioCDTagsLoaded(const QString &artist, const QString &alb song.set_track(track_number); song.set_year(ret.year_); song.set_id(track_number); - song.set_filetype(Song::Type_CDDA); + song.set_source(Song::Source_CDDA); + song.set_filetype(Song::FileType_CDDA); song.set_valid(true); // We need to set url: that's how playlist will find the correct item to update song.set_url(GetUrlFromTrack(track_number++)); diff --git a/src/device/devicemanager.cpp b/src/device/devicemanager.cpp index 740b67c5a..04a559abf 100644 --- a/src/device/devicemanager.cpp +++ b/src/device/devicemanager.cpp @@ -100,7 +100,7 @@ const int DeviceManager::kDeviceIconOverlaySize = 16; DeviceManager::DeviceInfo::DeviceInfo() : database_id_(-1), transcode_mode_(MusicStorage::Transcode_Unsupported), - transcode_format_(Song::Type_Unknown), + transcode_format_(Song::FileType_Unknown), task_percentage_(-1) {} DeviceDatabaseBackend::Device DeviceManager::DeviceInfo::SaveToDb() const { diff --git a/src/device/deviceproperties.cpp b/src/device/deviceproperties.cpp index 0b55647cd..9d600877b 100644 --- a/src/device/deviceproperties.cpp +++ b/src/device/deviceproperties.cpp @@ -308,7 +308,7 @@ void DeviceProperties::UpdateFormatsFinished(QFuture future) { #ifdef HAVE_GSTREAMER // Set the format combobox item TranscoderPreset preset = Transcoder::PresetForFileType(Song::FileType(index_.data(DeviceManager::Role_TranscodeFormat).toInt())); - if (preset.type_ == Song::Type_Unknown) { + if (preset.type_ == Song::FileType_Unknown) { // The user hasn't chosen a format for this device yet, // so work our way down a list of some preferred formats, picking the first one that is supported preset = Transcoder::PresetForFileType(Transcoder::PickBestFormat(supported_formats_)); diff --git a/src/device/gpoddevice.cpp b/src/device/gpoddevice.cpp index efb6bd039..487a1e353 100644 --- a/src/device/gpoddevice.cpp +++ b/src/device/gpoddevice.cpp @@ -248,8 +248,8 @@ void GPodDevice::FinishDelete(bool success) { } bool GPodDevice::GetSupportedFiletypes(QList *ret) { - *ret << Song::Type_MP4; - *ret << Song::Type_MPEG; + *ret << Song::FileType_MP4; + *ret << Song::FileType_MPEG; return true; } diff --git a/src/device/gpodloader.cpp b/src/device/gpodloader.cpp index 73ec6aa00..7a954e525 100644 --- a/src/device/gpodloader.cpp +++ b/src/device/gpodloader.cpp @@ -39,7 +39,7 @@ GPodLoader::GPodLoader(const QString &mount_point, TaskManager *task_manager, Co : QObject(nullptr), device_(device), mount_point_(mount_point), - type_(Song::Type_Unknown), + type_(Song::FileType_Unknown), task_manager_(task_manager), backend_(backend) { original_thread_ = thread(); @@ -81,7 +81,7 @@ void GPodLoader::LoadDatabase() { song.InitFromItdb(track, prefix); song.set_directory_id(1); - if (type_ != Song::Type_Unknown) song.set_filetype(type_); + if (type_ != Song::FileType_Unknown) song.set_filetype(type_); songs << song; } diff --git a/src/device/mtpdevice.cpp b/src/device/mtpdevice.cpp index 74db3d8bf..9597657c5 100644 --- a/src/device/mtpdevice.cpp +++ b/src/device/mtpdevice.cpp @@ -209,21 +209,21 @@ bool MtpDevice::GetSupportedFiletypes(QList *ret, LIBMTP_mtpdevi for (int i = 0; i < length; ++i) { switch (LIBMTP_filetype_t(list[i])) { - case LIBMTP_FILETYPE_WAV: *ret << Song::Type_WAV; break; + case LIBMTP_FILETYPE_WAV: *ret << Song::FileType_WAV; break; case LIBMTP_FILETYPE_MP2: - case LIBMTP_FILETYPE_MP3: *ret << Song::Type_MPEG; break; - case LIBMTP_FILETYPE_WMA: *ret << Song::Type_ASF; break; + case LIBMTP_FILETYPE_MP3: *ret << Song::FileType_MPEG; break; + case LIBMTP_FILETYPE_WMA: *ret << Song::FileType_ASF; break; case LIBMTP_FILETYPE_MP4: case LIBMTP_FILETYPE_M4A: - case LIBMTP_FILETYPE_AAC: *ret << Song::Type_MP4; break; + case LIBMTP_FILETYPE_AAC: *ret << Song::FileType_MP4; break; case LIBMTP_FILETYPE_FLAC: - *ret << Song::Type_FLAC; - *ret << Song::Type_OggFlac; + *ret << Song::FileType_FLAC; + *ret << Song::FileType_OggFlac; break; case LIBMTP_FILETYPE_OGG: - *ret << Song::Type_OggVorbis; - *ret << Song::Type_OggSpeex; - *ret << Song::Type_OggFlac; + *ret << Song::FileType_OggVorbis; + *ret << Song::FileType_OggSpeex; + *ret << Song::FileType_OggFlac; break; default: qLog(Error) << "Unknown MTP file format" << LIBMTP_Get_Filetype_Description(LIBMTP_filetype_t(list[i])); diff --git a/src/internet/internetmodel.cpp b/src/internet/internetmodel.cpp index 81d8433a0..7891f4bc1 100644 --- a/src/internet/internetmodel.cpp +++ b/src/internet/internetmodel.cpp @@ -32,13 +32,13 @@ #include "internetservice.h" #include "tidal/tidalservice.h" -QMap* InternetModel::sServices = nullptr; +QMap* InternetModel::sServices = nullptr; InternetModel::InternetModel(Application *app, QObject *parent) : QStandardItemModel(parent), app_(app) { - if (!sServices) sServices = new QMap; + if (!sServices) sServices = new QMap; Q_ASSERT(sServices->isEmpty()); AddService(new TidalService(app, this)); @@ -47,7 +47,7 @@ InternetModel::InternetModel(Application *app, QObject *parent) void InternetModel::AddService(InternetService *service) { qLog(Debug) << "Adding internet service:" << service->name(); - sServices->insert(service->name(), service); + sServices->insert(service->source(), service); connect(service, SIGNAL(destroyed()), SLOT(ServiceDeleted())); if (service->has_initial_load_settings()) service->InitialLoadSettings(); else service->ReloadSettings(); @@ -56,8 +56,8 @@ void InternetModel::AddService(InternetService *service) { void InternetModel::RemoveService(InternetService *service) { - if (!sServices->contains(service->name())) return; - sServices->remove(service->name()); + if (!sServices->contains(service->source())) return; + sServices->remove(service->source()); disconnect(service, 0, this, 0); } @@ -69,9 +69,9 @@ void InternetModel::ServiceDeleted() { } -InternetService *InternetModel::ServiceByName(const QString &name) { +InternetService *InternetModel::ServiceBySource(const Song::Source &source) { - if (sServices->contains(name)) return sServices->value(name); + if (sServices->contains(source)) return sServices->value(source); return nullptr; } diff --git a/src/internet/internetmodel.h b/src/internet/internetmodel.h index af3cf83da..9e6500615 100644 --- a/src/internet/internetmodel.h +++ b/src/internet/internetmodel.h @@ -105,11 +105,13 @@ class InternetModel : public QStandardItemModel { }; // Needs to be static for InternetPlaylistItem::restore - static InternetService *ServiceByName(const QString &name); + static InternetService *ServiceBySource(const Song::Source &source); + //static InternetService *ServiceByName(const QString &name); template static T *Service() { - return static_cast(ServiceByName(T::kServiceName)); + //return static_cast(ServiceByName(T::kServiceName)); + return static_cast(ServiceBySource(T::kSource)); } // Add and remove services. Ownership is not transferred and the service is not reparented. @@ -124,7 +126,8 @@ class InternetModel : public QStandardItemModel { void ServiceDeleted(); private: - static QMap *sServices; + //static QMap *sServices; + static QMap *sServices; Application *app_; }; diff --git a/src/internet/internetplaylistitem.cpp b/src/internet/internetplaylistitem.cpp index 51e955ad5..612923d15 100644 --- a/src/internet/internetplaylistitem.cpp +++ b/src/internet/internetplaylistitem.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,72 +34,39 @@ #include "collection/sqlrow.h" #include "playlist/playlistbackend.h" -InternetPlaylistItem::InternetPlaylistItem(const QString &type) - : PlaylistItem(type), set_service_icon_(false) {} +InternetPlaylistItem::InternetPlaylistItem(const Song::Source &source) + : PlaylistItem(source) {} InternetPlaylistItem::InternetPlaylistItem(InternetService *service, const Song &metadata) - : PlaylistItem("Internet"), - service_name_(service->name()), - set_service_icon_(false), + : PlaylistItem(Song::Source_Stream), + source_(service->source()), metadata_(metadata) { InitMetadata(); } bool InternetPlaylistItem::InitFromQuery(const SqlRow &query) { - - // The song tables gets joined first, plus one each for the song ROWIDs - const int row = (Song::kColumns.count() + 1) * PlaylistBackend::kSongTableJoins; - - service_name_ = query.value(row + 1).toString(); - metadata_.InitFromQuery(query, false, (Song::kColumns.count() + 1) * 1); InitMetadata(); - return true; - } InternetService *InternetPlaylistItem::service() const { - - InternetService *ret = InternetModel::ServiceByName(service_name_); - - if (ret && !set_service_icon_) { - const_cast(this)->set_service_icon_ = true; - - QString icon = ret->Icon(); - if (!icon.isEmpty()) { - const_cast(this)->metadata_.set_art_manual(icon); - } - } - + InternetService *ret = InternetModel::ServiceBySource(source_); return ret; - } QVariant InternetPlaylistItem::DatabaseValue(DatabaseColumn column) const { - switch (column) { - case Column_InternetService: - return service_name_; - default: - return PlaylistItem::DatabaseValue(column); - } + return PlaylistItem::DatabaseValue(column); } void InternetPlaylistItem::InitMetadata() { - - if (metadata_.title().isEmpty()) - metadata_.set_title(metadata_.url().toString()); - if (metadata_.filetype() == Song::Type_Unknown) metadata_.set_filetype(Song::Type_Stream); + if (metadata_.title().isEmpty()) metadata_.set_title(metadata_.url().toString()); + if (metadata_.source() == Song::Source_Unknown) metadata_.set_source(Song::Source_Stream); + if (metadata_.filetype() == Song::FileType_Unknown) metadata_.set_filetype(Song::FileType_Stream); metadata_.set_valid(true); - } Song InternetPlaylistItem::Metadata() const { - if (!set_service_icon_) { - // Get the icon if we don't have it already - service(); - } - if (HasTemporaryMetadata()) return temp_metadata_; return metadata_; } diff --git a/src/internet/internetplaylistitem.h b/src/internet/internetplaylistitem.h index 2a6d8f98f..b83d2c556 100644 --- a/src/internet/internetplaylistitem.h +++ b/src/internet/internetplaylistitem.h @@ -35,7 +35,7 @@ class InternetService; class InternetPlaylistItem : public PlaylistItem { public: - explicit InternetPlaylistItem(const QString &type); + explicit InternetPlaylistItem(const Song::Source &type); InternetPlaylistItem(InternetService *service, const Song &metadata); bool InitFromQuery(const SqlRow &query); Song Metadata() const; @@ -50,8 +50,7 @@ class InternetPlaylistItem : public PlaylistItem { InternetService *service() const; private: - QString service_name_; - bool set_service_icon_; + Song::Source source_; Song metadata_; }; diff --git a/src/internet/internetservice.cpp b/src/internet/internetservice.cpp index 25139ba6f..c0a6572ca 100644 --- a/src/internet/internetservice.cpp +++ b/src/internet/internetservice.cpp @@ -27,6 +27,6 @@ #include "internetmodel.h" #include "internetservice.h" -InternetService::InternetService(const QString &name, Application *app, InternetModel *model, QObject *parent) - : QObject(parent), app_(app), model_(model), name_(name) { +InternetService::InternetService(Song::Source source, const QString &name, Application *app, InternetModel *model, QObject *parent) + : QObject(parent), app_(app), model_(model), source_(source), name_(name) { } diff --git a/src/internet/internetservice.h b/src/internet/internetservice.h index 808648f49..6ac95d01f 100644 --- a/src/internet/internetservice.h +++ b/src/internet/internetservice.h @@ -27,8 +27,10 @@ #include #include #include +#include #include "core/song.h" +#include "core/iconloader.h" #include "playlist/playlistitem.h" #include "settings/settingsdialog.h" @@ -40,14 +42,15 @@ class InternetService : public QObject { Q_OBJECT public: - InternetService(const QString &name, Application *app, InternetModel *model, QObject *parent = nullptr); + InternetService(Song::Source source, const QString &name, Application *app, InternetModel *model, QObject *parent = nullptr); virtual ~InternetService() {} + Song::Source source() const { return source_; } QString name() const { return name_; } InternetModel *model() const { return model_; } virtual bool has_initial_load_settings() const { return false; } virtual void InitialLoadSettings() {} virtual void ReloadSettings() {} - virtual QString Icon() { return QString(); } + virtual QIcon Icon() { return Song::IconForSource(source_); } public slots: virtual void ShowConfig() {} @@ -56,6 +59,7 @@ class InternetService : public QObject { Application *app_; private: InternetModel *model_; + Song::Source source_; QString name_; }; diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index 324f280ee..339694a41 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -67,6 +67,7 @@ #include "core/logging.h" #include "core/mimedata.h" #include "core/tagreaderclient.h" +#include "core/song.h" #include "collection/collection.h" #include "collection/collectionbackend.h" #include "collection/collectionplaylistitem.h" @@ -249,11 +250,11 @@ bool Playlist::set_column_value(Song &song, Playlist::Column column, const QVari break; } return true; - + } QVariant Playlist::data(const QModelIndex &index, int role) const { - + switch (role) { case Role_IsCurrent: return current_item_index_.isValid() && index.row() == current_item_index_.row(); @@ -275,40 +276,40 @@ QVariant Playlist::data(const QModelIndex &index, int role) const { // Don't forget to change Playlist::CompareItems when adding new columns switch (index.column()) { - case Column_Title: return song.PrettyTitle(); - case Column_Artist: return song.artist(); - case Column_Album: return song.album(); - case Column_Length: return song.length_nanosec(); - case Column_Track: return song.track(); - case Column_Disc: return song.disc(); - case Column_Year: return song.year(); - case Column_OriginalYear: return song.effective_originalyear(); - case Column_Genre: return song.genre(); - case Column_AlbumArtist: return song.playlist_albumartist(); - case Column_Composer: return song.composer(); - case Column_Performer: return song.performer(); - case Column_Grouping: return song.grouping(); + case Column_Title: return song.PrettyTitle(); + case Column_Artist: return song.artist(); + case Column_Album: return song.album(); + case Column_Length: return song.length_nanosec(); + case Column_Track: return song.track(); + case Column_Disc: return song.disc(); + case Column_Year: return song.year(); + case Column_OriginalYear: return song.effective_originalyear(); + case Column_Genre: return song.genre(); + case Column_AlbumArtist: return song.playlist_albumartist(); + case Column_Composer: return song.composer(); + case Column_Performer: return song.performer(); + case Column_Grouping: return song.grouping(); - case Column_PlayCount: return song.playcount(); - case Column_SkipCount: return song.skipcount(); - case Column_LastPlayed: return song.lastplayed(); + case Column_PlayCount: return song.playcount(); + case Column_SkipCount: return song.skipcount(); + case Column_LastPlayed: return song.lastplayed(); - case Column_Samplerate: return song.samplerate(); - case Column_Bitdepth: return song.bitdepth(); - case Column_Bitrate: return song.bitrate(); + case Column_Samplerate: return song.samplerate(); + case Column_Bitdepth: return song.bitdepth(); + case Column_Bitrate: return song.bitrate(); - case Column_Filename: return song.url(); - case Column_BaseFilename: return song.basefilename(); - case Column_Filesize: return song.filesize(); - case Column_Filetype: return song.filetype(); - case Column_DateModified: return song.mtime(); - case Column_DateCreated: return song.ctime(); + case Column_Filename: return song.url(); + case Column_BaseFilename: return song.basefilename(); + case Column_Filesize: return song.filesize(); + case Column_Filetype: return song.filetype(); + case Column_DateModified: return song.mtime(); + case Column_DateCreated: return song.ctime(); case Column_Comment: - if (role == Qt::DisplayRole) return song.comment().simplified(); + if (role == Qt::DisplayRole) return song.comment().simplified(); return song.comment(); - case Column_Source: return item->Url(); + case Column_Source: return song.source(); } @@ -910,7 +911,7 @@ void Playlist::InsertItemsWithoutUndo(const PlaylistItemList &items, int pos, bo items_.insert(i, item); virtual_items_ << virtual_items_.count(); - if (item->type() == "Collection") { + if (item->source() == Song::Source_Collection) { int id = item->Metadata().id(); if (id != -1) { collection_items_by_id_.insertMulti(id, item); @@ -990,16 +991,22 @@ void Playlist::UpdateItems(const SongList &songs) { const Song &song = it.next(); PlaylistItemPtr &item = items_[i]; if (item->Metadata().url() == song.url() && - (item->Metadata().filetype() == Song::Type_Unknown || + ( + item->Metadata().source() == Song::Source_Unknown || + item->Metadata().filetype() == Song::FileType_Unknown || // Stream may change and may need to be updated too - item->Metadata().filetype() == Song::Type_Stream || + item->Metadata().source() == Song::Source_Stream || + item->Metadata().source() == Song::Source_Tidal || // And CD tracks as well (tags are loaded in a second step) - item->Metadata().filetype() == Song::Type_CDDA)) { + item->Metadata().source() == Song::Source_CDDA + ) + ) { PlaylistItemPtr new_item; if (song.is_collection_song()) { new_item = PlaylistItemPtr(new CollectionPlaylistItem(song)); collection_items_by_id_.insertMulti(song.id(), new_item); - } else { + } + else { new_item = PlaylistItemPtr(new SongPlaylistItem(song)); } items_[i] = new_item; @@ -1099,7 +1106,7 @@ bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptrtype() == "Collection") { + if (item->source() == Song::Source_Collection) { int id = item->Metadata().id(); if (id != -1) { collection_items_by_id_.remove(id, item); diff --git a/src/playlist/playlistbackend.cpp b/src/playlist/playlistbackend.cpp index e38e41ac7..fa6429ada 100644 --- a/src/playlist/playlistbackend.cpp +++ b/src/playlist/playlistbackend.cpp @@ -145,7 +145,7 @@ QSqlQuery PlaylistBackend::GetPlaylistRows(int playlist) { " p.ROWID, " + Song::JoinSpec("p") + "," - " p.type, p.internet_service" + " p.type" " FROM playlist_items AS p" " LEFT JOIN songs" " ON p.collection_id = songs.ROWID" @@ -198,7 +198,7 @@ PlaylistItemPtr PlaylistBackend::NewPlaylistItemFromQuery(const SqlRow &row, std // The song tables get joined first, plus one each for the song ROWIDs const int playlist_row = (Song::kColumns.count() + 1) * kSongTableJoins; - PlaylistItemPtr item(PlaylistItem::NewFromType(row.value(playlist_row).toString())); + PlaylistItemPtr item(PlaylistItem::NewFromSource(Song::Source(row.value(playlist_row).toInt()))); if (item) { item->InitFromQuery(row); return RestoreCueData(item, state); @@ -219,8 +219,8 @@ Song PlaylistBackend::NewSongFromQuery(const SqlRow &row, std::shared_ptr state) { - // we need collection to run a CueParser; also, this method applies only to file-type PlaylistItems - if (item->type() != "File") return item; + // We need collection to run a CueParser; also, this method applies only to file-type PlaylistItems + if (item->source() != Song::Source_LocalFile) return item; CueParser cue_parser(app_->collection_backend()); @@ -279,7 +279,7 @@ void PlaylistBackend::SavePlaylist(int playlist, const PlaylistItemList &items, QSqlQuery clear(db); clear.prepare("DELETE FROM playlist_items WHERE playlist = :playlist"); QSqlQuery insert(db); - insert.prepare("INSERT INTO playlist_items (playlist, type, collection_id, internet_service, " + Song::kColumnSpec + ") VALUES (:playlist, :type, :collection_id, :internet_service, " + Song::kBindSpec + ")"); + insert.prepare("INSERT INTO playlist_items (playlist, type, collection_id, " + Song::kColumnSpec + ") VALUES (:playlist, :type, :collection_id, " + Song::kBindSpec + ")"); QSqlQuery update(db); update.prepare("UPDATE playlists SET last_played=:last_played WHERE ROWID=:playlist"); diff --git a/src/playlist/playlistdelegates.cpp b/src/playlist/playlistdelegates.cpp index 6ae34349e..1939b816d 100644 --- a/src/playlist/playlistdelegates.cpp +++ b/src/playlist/playlistdelegates.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -348,12 +349,6 @@ QString FileTypeItemDelegate::displayText(const QVariant &value, const QLocale & } -QString SamplerateBitdepthItemDelegate::displayText(const QVariant &value, const QLocale &locale) const { - - return value.toString(); - -} - QWidget *TextItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { return new QLineEdit(parent); @@ -442,37 +437,16 @@ QString SongSourceDelegate::displayText(const QVariant &value, const QLocale&) c return QString(); } -QPixmap SongSourceDelegate::LookupPixmap(const QUrl &url, const QSize &size) const { +QPixmap SongSourceDelegate::LookupPixmap(const Song::Source &source, const QSize &size) const { QPixmap pixmap; - if (cache_.find(url.scheme(), &pixmap)) { + if (cache_.find(Song::TextForSource(source), &pixmap)) { return pixmap; } - QIcon icon; - const UrlHandler *handler = player_->HandlerForUrl(url); - if (handler) { - icon = handler->icon(); - } - else { - if (url.scheme() == "file") { - icon = IconLoader::Load("folder-sound"); - } - else if (url.scheme() == "cdda") { - icon = IconLoader::Load("cd"); - } - else if (url.scheme() == "http" || url.scheme() == "https") { - if (url.host().contains(QRegExp(".*.tidal.com"))) - icon = IconLoader::Load("tidal"); - else - icon = IconLoader::Load("download"); - } - else { - icon = IconLoader::Load("folder-sound"); - } - } + QIcon icon(Song::IconForSource(source)); pixmap = icon.pixmap(size.height()); - cache_.insert(url.scheme(), pixmap); + cache_.insert(Song::TextForSource(source), pixmap); return pixmap; @@ -486,9 +460,8 @@ void SongSourceDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op QStyleOptionViewItem option_copy(option); initStyleOption(&option_copy, index); - // Find the pixmap to use for this URL - const QUrl &url = index.data().toUrl(); - QPixmap pixmap = LookupPixmap(url, option_copy.decorationSize); + const Song::Source &source = Song::Source(index.data().toInt()); + QPixmap pixmap = LookupPixmap(source, option_copy.decorationSize); float device_pixel_ratio = 1.0f; #ifdef Q_OS_MACOS diff --git a/src/playlist/playlistdelegates.h b/src/playlist/playlistdelegates.h index 98ec2b806..ae9e47bdd 100644 --- a/src/playlist/playlistdelegates.h +++ b/src/playlist/playlistdelegates.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -125,12 +126,6 @@ class FileTypeItemDelegate : public PlaylistDelegateBase { QString displayText(const QVariant &value, const QLocale &locale) const; }; -class SamplerateBitdepthItemDelegate : public PlaylistDelegateBase { - public: - SamplerateBitdepthItemDelegate(QObject *parent) : PlaylistDelegateBase(parent) {} - QString displayText(const QVariant &value, const QLocale &locale) const; -}; - class TextItemDelegate : public PlaylistDelegateBase { public: TextItemDelegate(QObject *parent) : PlaylistDelegateBase(parent) {} @@ -182,7 +177,7 @@ class SongSourceDelegate : public PlaylistDelegateBase { void paint(QPainter *paint, const QStyleOptionViewItem &option, const QModelIndex &index) const; private: - QPixmap LookupPixmap(const QUrl &url, const QSize &size) const; + QPixmap LookupPixmap(const Song::Source &type, const QSize &size) const; Player *player_; mutable QPixmapCache cache_; diff --git a/src/playlist/playlistitem.cpp b/src/playlist/playlistitem.cpp index 13eb7a1b6..5782f7464 100644 --- a/src/playlist/playlistitem.cpp +++ b/src/playlist/playlistitem.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,20 +43,18 @@ PlaylistItem::~PlaylistItem() { } -PlaylistItem* PlaylistItem::NewFromType(const QString &type) { +PlaylistItem *PlaylistItem::NewFromSource(const Song::Source &source) { - if (type == "Collection") return new CollectionPlaylistItem(type); - else if (type == "File") return new SongPlaylistItem(type); - else if (type == "Internet") return new InternetPlaylistItem("Internet"); - else if (type == "Tidal") return new InternetPlaylistItem("Tidal"); - - qLog(Warning) << "Invalid PlaylistItem type:" << type; - - return nullptr; + switch (source) { + case Song::Source_Collection: return new CollectionPlaylistItem(source); + case Song::Source_Tidal: + case Song::Source_Stream: return new InternetPlaylistItem(source); + default: return new SongPlaylistItem(source); + } } -PlaylistItem* PlaylistItem::NewFromSongsTable(const QString &table, const Song &song) { +PlaylistItem *PlaylistItem::NewFromSongsTable(const QString &table, const Song &song) { if (table == SCollection::kSongsTable) return new CollectionPlaylistItem(song); @@ -67,9 +66,8 @@ PlaylistItem* PlaylistItem::NewFromSongsTable(const QString &table, const Song & void PlaylistItem::BindToQuery(QSqlQuery *query) const { - query->bindValue(":type", type()); + query->bindValue(":type", source()); query->bindValue(":collection_id", DatabaseValue(Column_CollectionId)); - query->bindValue(":internet_service", DatabaseValue(Column_InternetService)); DatabaseSongMetadata().BindToQuery(query); diff --git a/src/playlist/playlistitem.h b/src/playlist/playlistitem.h index a7358bb1c..dce221108 100644 --- a/src/playlist/playlistitem.h +++ b/src/playlist/playlistitem.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,11 +47,11 @@ class SqlRow; class PlaylistItem : public std::enable_shared_from_this { public: - PlaylistItem(const QString &type) : should_skip_(false), type_(type) {} + PlaylistItem(const Song::Source &source) : should_skip_(false), source_(source) {} virtual ~PlaylistItem(); - static PlaylistItem* NewFromType(const QString &type); - static PlaylistItem* NewFromSongsTable(const QString &table, const Song &song); + static PlaylistItem *NewFromSource(const Song::Source &source); + static PlaylistItem *NewFromSongsTable(const QString &table, const Song &song); enum Option { Default = 0x00, @@ -63,7 +64,7 @@ class PlaylistItem : public std::enable_shared_from_this { }; Q_DECLARE_FLAGS(Options, Option); - virtual QString type() const { return type_; } + virtual Song::Source source() const { return source_; } virtual Options options() const { return Default; } @@ -104,14 +105,14 @@ class PlaylistItem : public std::enable_shared_from_this { protected: bool should_skip_; - enum DatabaseColumn { Column_CollectionId, Column_InternetService }; + enum DatabaseColumn { Column_CollectionId }; virtual QVariant DatabaseValue(DatabaseColumn) const { return QVariant(QVariant::String); } virtual Song DatabaseSongMetadata() const { return Song(); } - QString type_; + Song::Source source_; Song temp_metadata_; diff --git a/src/playlist/playlistview.cpp b/src/playlist/playlistview.cpp index f8301e02c..435a49a63 100644 --- a/src/playlist/playlistview.cpp +++ b/src/playlist/playlistview.cpp @@ -1057,11 +1057,10 @@ void PlaylistView::StretchChanged(bool stretch) { bool PlaylistView::eventFilter(QObject *object, QEvent *event) { if (event->type() == QEvent::Enter && (object == horizontalScrollBar() || object == verticalScrollBar())) { - //RatingHoverOut(); return false; } return QObject::eventFilter(object, event); - + } void PlaylistView::rowsInserted(const QModelIndex &parent, int start, int end) { diff --git a/src/playlist/songplaylistitem.cpp b/src/playlist/songplaylistitem.cpp index fbd00d5d8..252f50ac3 100644 --- a/src/playlist/songplaylistitem.cpp +++ b/src/playlist/songplaylistitem.cpp @@ -29,15 +29,13 @@ #include "playlistitem.h" #include "songplaylistitem.h" -SongPlaylistItem::SongPlaylistItem(const QString &type) : PlaylistItem(type) {} +SongPlaylistItem::SongPlaylistItem(const Song::Source &source) : PlaylistItem(source) {} SongPlaylistItem::SongPlaylistItem(const Song &song) - : PlaylistItem("File"), song_(song) {} + : PlaylistItem(Song::Source_LocalFile), song_(song) {} bool SongPlaylistItem::InitFromQuery(const SqlRow &query) { - song_.InitFromQuery(query, false, (Song::kColumns.count()+1)); - return true; } @@ -45,7 +43,6 @@ QUrl SongPlaylistItem::Url() const { return song_.url(); } void SongPlaylistItem::Reload() { if (song_.url().scheme() != "file") return; - TagReaderClient::Instance()->ReadFileBlocking(song_.url().toLocalFile(), &song_); } diff --git a/src/playlist/songplaylistitem.h b/src/playlist/songplaylistitem.h index 7def90f86..8bd7b171d 100644 --- a/src/playlist/songplaylistitem.h +++ b/src/playlist/songplaylistitem.h @@ -33,7 +33,7 @@ class SongPlaylistItem : public PlaylistItem { public: - SongPlaylistItem(const QString &type); + SongPlaylistItem(const Song::Source &source); SongPlaylistItem(const Song &song); // Restores a stream- or file-related playlist item using query row. diff --git a/src/tidal/tidalsearch.h b/src/tidal/tidalsearch.h index 702358b5e..fa211e614 100644 --- a/src/tidal/tidalsearch.h +++ b/src/tidal/tidalsearch.h @@ -140,6 +140,7 @@ class TidalSearch : public QObject { Application *app_; TidalService *service_; + Song::Source source_; QString name_; QString id_; QIcon icon_; diff --git a/src/tidal/tidalservice.cpp b/src/tidal/tidalservice.cpp index 49ad29277..2aca35de8 100644 --- a/src/tidal/tidalservice.cpp +++ b/src/tidal/tidalservice.cpp @@ -53,6 +53,7 @@ #include "tidalsearch.h" #include "settings/tidalsettingspage.h" +const Song::Source TidalService::kSource = Song::Source_Tidal; const char *TidalService::kServiceName = "Tidal"; const char *TidalService::kApiUrl = "https://listen.tidal.com/v1"; const char *TidalService::kAuthUrl = "https://listen.tidal.com/v1/login/username"; @@ -62,7 +63,7 @@ const char *TidalService::kApiToken = "P5Xbeo5LFvESeDy6"; typedef QPair Param; TidalService::TidalService(Application *app, InternetModel *parent) - : InternetService(kServiceName, app, parent, parent), + : InternetService(kSource, kServiceName, app, parent, parent), network_(new NetworkAccessManager(this)), timer_searchdelay_(new QTimer(this)), searchdelay_(1500), @@ -730,7 +731,7 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val } int album_id = json_album["id"].toInt(); if (album_id_requested != 0 && album_id_requested != album_id) { - qLog(Error) << "Tidal: Invalid Json reply, track album is wrong."; + qLog(Error) << "Tidal: Invalid Json reply, track album id is wrong."; qLog(Debug) << json_album; return song; } @@ -745,6 +746,7 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val //qLog(Debug) << "id" << id << "track" << track << "disc" << disc << "title" << title << "album" << album << "artist" << artist << cover << allow_streaming << url; + song.set_source(Song::Source_Tidal); song.set_id(song_id); song.set_album_id(album_id); song.set_artist(artist); @@ -819,9 +821,9 @@ void TidalService::GetStreamURLFinished(QNetworkReply *reply, const int search_i QString codec = json_obj["codec"].toString().toLower(); song.set_filetype(Song::FiletypeByExtension(codec)); - if (song.filetype() == Song::Type_Unknown) { + if (song.filetype() == Song::FileType_Unknown) { qLog(Debug) << "Tidal: Unknown codec" << codec; - song.set_filetype(Song::Type_Stream); + song.set_filetype(Song::FileType_Stream); } song.set_valid(true); diff --git a/src/tidal/tidalservice.h b/src/tidal/tidalservice.h index 75432c97d..a880af566 100644 --- a/src/tidal/tidalservice.h +++ b/src/tidal/tidalservice.h @@ -47,6 +47,7 @@ class TidalService : public InternetService { TidalService(Application *app, InternetModel *parent); ~TidalService(); + static const Song::Source kSource; static const char *kServiceName; void ReloadSettings(); diff --git a/src/transcoder/transcoder.cpp b/src/transcoder/transcoder.cpp index dcc74429c..5e4ba092a 100644 --- a/src/transcoder/transcoder.cpp +++ b/src/transcoder/transcoder.cpp @@ -222,15 +222,15 @@ Transcoder::Transcoder(QObject *parent, const QString &settings_postfix) QList Transcoder::GetAllPresets() { QList ret; - ret << PresetForFileType(Song::Type_FLAC); - ret << PresetForFileType(Song::Type_MP4); - ret << PresetForFileType(Song::Type_MPEG); - ret << PresetForFileType(Song::Type_OggVorbis); - ret << PresetForFileType(Song::Type_OggFlac); - ret << PresetForFileType(Song::Type_OggSpeex); - ret << PresetForFileType(Song::Type_ASF); - ret << PresetForFileType(Song::Type_WAV); - ret << PresetForFileType(Song::Type_OggOpus); + ret << PresetForFileType(Song::FileType_FLAC); + ret << PresetForFileType(Song::FileType_MP4); + ret << PresetForFileType(Song::FileType_MPEG); + ret << PresetForFileType(Song::FileType_OggVorbis); + ret << PresetForFileType(Song::FileType_OggFlac); + ret << PresetForFileType(Song::FileType_OggSpeex); + ret << PresetForFileType(Song::FileType_ASF); + ret << PresetForFileType(Song::FileType_WAV); + ret << PresetForFileType(Song::FileType_OggOpus); return ret; } @@ -238,23 +238,23 @@ QList Transcoder::GetAllPresets() { TranscoderPreset Transcoder::PresetForFileType(Song::FileType type) { switch (type) { - case Song::Type_FLAC: + case Song::FileType_FLAC: return TranscoderPreset(type, tr("FLAC"), "flac", "audio/x-flac"); - case Song::Type_MP4: + case Song::FileType_MP4: return TranscoderPreset(type, tr("M4A AAC"), "mp4", "audio/mpeg, mpegversion=(int)4", "audio/mp4"); - case Song::Type_MPEG: + case Song::FileType_MPEG: return TranscoderPreset(type, tr("MP3"), "mp3", "audio/mpeg, mpegversion=(int)1, layer=(int)3"); - case Song::Type_OggVorbis: + case Song::FileType_OggVorbis: return TranscoderPreset(type, tr("Ogg Vorbis"), "ogg", "audio/x-vorbis", "application/ogg"); - case Song::Type_OggFlac: + case Song::FileType_OggFlac: return TranscoderPreset(type, tr("Ogg FLAC"), "ogg", "audio/x-flac", "application/ogg"); - case Song::Type_OggSpeex: + case Song::FileType_OggSpeex: return TranscoderPreset(type, tr("Ogg Speex"), "spx", "audio/x-speex", "application/ogg"); - case Song::Type_OggOpus: + case Song::FileType_OggOpus: return TranscoderPreset(type, tr("Ogg Opus"), "opus", "audio/x-opus", "application/ogg"); - case Song::Type_ASF: + case Song::FileType_ASF: return TranscoderPreset(type, tr("Windows Media audio"), "wma", "audio/x-wma", "video/x-ms-asf"); - case Song::Type_WAV: + case Song::FileType_WAV: return TranscoderPreset(type, tr("Wav"), "wav", QString(), "audio/x-wav"); default: qLog(Warning) << "Unsupported format in PresetForFileType:" << type; @@ -265,12 +265,12 @@ TranscoderPreset Transcoder::PresetForFileType(Song::FileType type) { Song::FileType Transcoder::PickBestFormat(QList supported) { - if (supported.isEmpty()) return Song::Type_Unknown; + if (supported.isEmpty()) return Song::FileType_Unknown; QList best_formats; - best_formats << Song::Type_MPEG; - best_formats << Song::Type_OggVorbis; - best_formats << Song::Type_ASF; + best_formats << Song::FileType_MPEG; + best_formats << Song::FileType_OggVorbis; + best_formats << Song::FileType_ASF; for (Song::FileType type : best_formats) { if (supported.isEmpty() || supported.contains(type)) return type; diff --git a/src/transcoder/transcoder.h b/src/transcoder/transcoder.h index 64a6be7f2..a5e80348b 100644 --- a/src/transcoder/transcoder.h +++ b/src/transcoder/transcoder.h @@ -41,7 +41,7 @@ #include "core/song.h" struct TranscoderPreset { - TranscoderPreset() : type_(Song::Type_Unknown) {} + TranscoderPreset() : type_(Song::FileType_Unknown) {} TranscoderPreset(Song::FileType type, const QString &name, const QString &extension, const QString &codec_mimetype, const QString &muxer_mimetype_ = QString()); Song::FileType type_; diff --git a/src/transcoder/transcoderoptionsdialog.cpp b/src/transcoder/transcoderoptionsdialog.cpp index 798e0aee2..83563d25f 100644 --- a/src/transcoder/transcoderoptionsdialog.cpp +++ b/src/transcoder/transcoderoptionsdialog.cpp @@ -44,14 +44,14 @@ TranscoderOptionsDialog::TranscoderOptionsDialog(Song::FileType type, QWidget *p ui_->setupUi(this); switch (type) { - case Song::Type_FLAC: - case Song::Type_OggFlac: options_ = new TranscoderOptionsFlac(this); break; - case Song::Type_MP4: options_ = new TranscoderOptionsAAC(this); break; - case Song::Type_MPEG: options_ = new TranscoderOptionsMP3(this); break; - case Song::Type_OggVorbis: options_ = new TranscoderOptionsVorbis(this); break; - case Song::Type_OggOpus: options_ = new TranscoderOptionsOpus(this); break; - case Song::Type_OggSpeex: options_ = new TranscoderOptionsSpeex(this); break; - case Song::Type_ASF: options_ = new TranscoderOptionsWma(this); break; + case Song::FileType_FLAC: + case Song::FileType_OggFlac: options_ = new TranscoderOptionsFlac(this); break; + case Song::FileType_MP4: options_ = new TranscoderOptionsAAC(this); break; + case Song::FileType_MPEG: options_ = new TranscoderOptionsMP3(this); break; + case Song::FileType_OggVorbis: options_ = new TranscoderOptionsVorbis(this); break; + case Song::FileType_OggOpus: options_ = new TranscoderOptionsOpus(this); break; + case Song::FileType_OggSpeex: options_ = new TranscoderOptionsSpeex(this); break; + case Song::FileType_ASF: options_ = new TranscoderOptionsWma(this); break; default: break; }