Add source to songs and playlist_items

This commit is contained in:
Jonas Kvinge 2018-09-08 12:38:02 +02:00
parent 19b645d731
commit 6d686ee66a
48 changed files with 500 additions and 401 deletions

View File

@ -3,6 +3,7 @@
<file>schema/schema.sql</file>
<file>schema/schema-1.sql</file>
<file>schema/schema-2.sql</file>
<file>schema/schema-3.sql</file>
<file>schema/device-schema.sql</file>
<file>style/strawberry.css</file>
<file>misc/playing_tooltip.txt</file>

View File

@ -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,

65
data/schema/schema-3.sql Normal file
View File

@ -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;

View File

@ -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,

View File

@ -1,5 +1,6 @@
/* This file is part of Strawberry.
Copyright 2013, David Sansome <me@davidsansome.com>
Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
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<TagLib::RIFF::WAV::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_WAV;
if (dynamic_cast<TagLib::FLAC::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_FLAC;
if (dynamic_cast<TagLib::WavPack::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_WAVPACK;
if (dynamic_cast<TagLib::Ogg::FLAC::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGFLAC;
if (dynamic_cast<TagLib::Ogg::Vorbis::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGVORBIS;
if (dynamic_cast<TagLib::Ogg::Opus::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGOPUS;
if (dynamic_cast<TagLib::Ogg::Speex::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_OGGSPEEX;
if (dynamic_cast<TagLib::MPEG::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_MPEG;
if (dynamic_cast<TagLib::MP4::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_MP4;
if (dynamic_cast<TagLib::ASF::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_ASF;
if (dynamic_cast<TagLib::RIFF::AIFF::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_AIFF;
if (dynamic_cast<TagLib::MPC::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_MPC;
if (dynamic_cast<TagLib::TrueAudio::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_TRUEAUDIO;
if (dynamic_cast<TagLib::RIFF::WAV::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_WAV;
if (dynamic_cast<TagLib::FLAC::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_FLAC;
if (dynamic_cast<TagLib::WavPack::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_WAVPACK;
if (dynamic_cast<TagLib::Ogg::FLAC::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_OGGFLAC;
if (dynamic_cast<TagLib::Ogg::Vorbis::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_OGGVORBIS;
if (dynamic_cast<TagLib::Ogg::Opus::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_OGGOPUS;
if (dynamic_cast<TagLib::Ogg::Speex::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_OGGSPEEX;
if (dynamic_cast<TagLib::MPEG::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_MPEG;
if (dynamic_cast<TagLib::MP4::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_MP4;
if (dynamic_cast<TagLib::ASF::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_ASF;
if (dynamic_cast<TagLib::RIFF::AIFF::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_AIFF;
if (dynamic_cast<TagLib::MPC::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_MPC;
if (dynamic_cast<TagLib::TrueAudio::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_TRUEAUDIO;
#ifdef HAVE_TAGLIB_DSFFILE
if (dynamic_cast<TagLib::DSF::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_DSF;
if (dynamic_cast<TagLib::DSDIFF::File*>(fileref->file())) return pb::tagreader::SongMetadata_Type_DSDIFF;
if (dynamic_cast<TagLib::DSF::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_DSF;
if (dynamic_cast<TagLib::DSDIFF::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_DSDIFF;
#endif
return pb::tagreader::SongMetadata_Type_UNKNOWN;
return pb::tagreader::SongMetadata_FileType_UNKNOWN;
}

View File

@ -1,5 +1,6 @@
/* This file is part of Strawberry.
Copyright 2013, David Sansome <me@davidsansome.com>
Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
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;

View File

@ -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;

View File

@ -63,7 +63,6 @@ SCollection::SCollection(Application *app, QObject *parent)
}
SCollection::~SCollection() {
watcher_->deleteLater();
watcher_thread_->exit();
watcher_thread_->wait(5000 /* five seconds */);

View File

@ -720,7 +720,6 @@ SongList CollectionBackend::GetSongsByUrl(const QUrl &url) {
while (query.Next()) {
Song song;
song.InitFromQuery(query, true);
songlist << song;
}
}

View File

@ -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_;
}

View File

@ -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);

View File

@ -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<QString> 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<quint64, Song> 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;

View File

@ -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;

View File

@ -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<QUrl> &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;
}

View File

@ -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<Song::FileType>* ret) { return true; }
virtual bool StartCopy(QList<Song::FileType>* supported_types) { return true;}

View File

@ -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;
}

View File

@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* 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 <QRegExp>
#include <QUrl>
#include <QImage>
#include <QIcon>
#include <QTextCodec>
#include <QSqlQuery>
#include <QtDebug>
@ -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<pb::tagreader::SongMetadata_Type>(d->filetype_));
pb->set_filetype(static_cast<pb::tagreader::SongMetadata_FileType>(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 {

View File

@ -2,6 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* 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 <QRegExp>
#include <QUrl>
#include <QImage>
#include <QIcon>
#include <QTextCodec>
#include <QSqlQuery>
@ -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