#include "song.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "trackslider.h" #include "enginebase.h" #include "albumcoverloader.h" const char* Song::kColumnSpec = "title, album, artist, albumartist, composer, " "track, disc, bpm, year, genre, comment, compilation, " "length, bitrate, samplerate, directory, filename, " "mtime, ctime, filesize, sampler, art_automatic, art_manual"; const char* Song::kBindSpec = ":title, :album, :artist, :albumartist, :composer, " ":track, :disc, :bpm, :year, :genre, :comment, :compilation, " ":length, :bitrate, :samplerate, :directory_id, :filename, " ":mtime, :ctime, :filesize, :sampler, :art_automatic, :art_manual"; const char* Song::kUpdateSpec = "title = :title, album = :album, artist = :artist, " "albumartist = :albumartist, composer = :composer, track = :track, " "disc = :disc, bpm = :bpm, year = :year, genre = :genre, " "comment = :comment, compilation = :compilation, length = :length, " "bitrate = :bitrate, samplerate = :samplerate, " "directory = :directory_id, filename = :filename, mtime = :mtime, " "ctime = :ctime, filesize = :filesize, sampler = :sampler, " "art_automatic = :art_automatic, art_manual = :art_manual"; SongData::SongData() : valid_(false), id_(-1), track_(-1), disc_(-1), bpm_(-1), year_(-1), compilation_(false), sampler_(false), length_(-1), bitrate_(-1), samplerate_(-1), directory_id_(-1), mtime_(-1), ctime_(-1), filesize_(-1) { } Song::Song() : d(new SongData) { } Song::Song(const Song &other) : d(other.d) { } void Song::InitFromFile(const QString& filename, int directory_id) { d->filename_ = filename; d->directory_id_ = directory_id; TagLib::FileRef fileref(QFile::encodeName(filename).constData()); if( fileref.isNull() ) return; QFileInfo info(filename); d->filesize_ = info.size(); d->mtime_ = info.lastModified().toTime_t(); d->ctime_ = info.created().toTime_t(); TagLib::Tag* tag = fileref.tag(); if (tag) { #define strip(x) TStringToQString( x ).trimmed() d->title_ = strip(tag->title()); d->artist_ = strip(tag->artist()); d->album_ = strip(tag->album()); d->comment_ = strip(tag->comment()); d->genre_ = strip(tag->genre()); d->year_ = tag->year(); d->track_ = tag->track(); #undef strip d->valid_ = true; } QString disc; QString compilation; if (TagLib::MPEG::File* file = dynamic_cast(fileref.file())) { if (file->ID3v2Tag()) { if (!file->ID3v2Tag()->frameListMap()["TPOS"].isEmpty()) disc = TStringToQString(file->ID3v2Tag()->frameListMap()["TPOS"].front()->toString()).trimmed(); if (!file->ID3v2Tag()->frameListMap()["TBPM"].isEmpty()) d->bpm_ = TStringToQString(file->ID3v2Tag()->frameListMap()["TBPM"].front()->toString()).trimmed().toFloat(); if (!file->ID3v2Tag()->frameListMap()["TCOM"].isEmpty()) d->composer_ = TStringToQString(file->ID3v2Tag()->frameListMap()["TCOM"].front()->toString()).trimmed(); if (!file->ID3v2Tag()->frameListMap()["TPE2"].isEmpty()) // non-standard: Apple, Microsoft d->albumartist_ = TStringToQString(file->ID3v2Tag()->frameListMap()["TPE2"].front()->toString()).trimmed(); if (!file->ID3v2Tag()->frameListMap()["TCMP"].isEmpty()) compilation = TStringToQString(file->ID3v2Tag()->frameListMap()["TCMP"].front()->toString()).trimmed(); } } else if (TagLib::Ogg::Vorbis::File* file = dynamic_cast(fileref.file())) { if (file->tag()) { if ( !file->tag()->fieldListMap()["COMPOSER"].isEmpty() ) d->composer_ = TStringToQString(file->tag()->fieldListMap()["COMPOSER"].front()).trimmed(); if ( !file->tag()->fieldListMap()["BPM"].isEmpty() ) d->bpm_ = TStringToQString(file->tag()->fieldListMap()["BPM"].front()).trimmed().toFloat(); if ( !file->tag()->fieldListMap()["DISCNUMBER"].isEmpty() ) disc = TStringToQString(file->tag()->fieldListMap()["DISCNUMBER"].front()).trimmed(); if ( !file->tag()->fieldListMap()["COMPILATION"].isEmpty() ) compilation = TStringToQString(file->tag()->fieldListMap()["COMPILATION"].front()).trimmed(); } } else if (TagLib::FLAC::File* file = dynamic_cast(fileref.file())) { if ( file->xiphComment() ) { if (!file->xiphComment()->fieldListMap()["COMPOSER"].isEmpty()) d->composer_ = TStringToQString( file->xiphComment()->fieldListMap()["COMPOSER"].front() ).trimmed(); if (!file->xiphComment()->fieldListMap()["BPM"].isEmpty() ) d->bpm_ = TStringToQString( file->xiphComment()->fieldListMap()["BPM"].front() ).trimmed().toFloat(); if (!file->xiphComment()->fieldListMap()["DISCNUMBER"].isEmpty() ) disc = TStringToQString( file->xiphComment()->fieldListMap()["DISCNUMBER"].front() ).trimmed(); if (!file->xiphComment()->fieldListMap()["COMPILATION"].isEmpty() ) compilation = TStringToQString( file->xiphComment()->fieldListMap()["COMPILATION"].front() ).trimmed(); } } if ( !disc.isEmpty() ) { int i = disc.indexOf('/'); if ( i != -1 ) // disc.right( i ).toInt() is total number of discs, we don't use this at the moment d->disc_ = disc.left( i ).toInt(); else d->disc_ = disc.toInt(); } if ( compilation.isEmpty() ) { // well, it wasn't set, but if the artist is VA assume it's a compilation if ( d->artist_.toLower() == "various artists" ) d->compilation_ = true; } else { int i = compilation.toInt(); d->compilation_ = (i == 1); } if (fileref.audioProperties()) { d->bitrate_ = fileref.audioProperties()->bitrate(); d->length_ = fileref.audioProperties()->length(); d->samplerate_ = fileref.audioProperties()->sampleRate(); } } void Song::InitFromQuery(const QSqlQuery& q) { d->valid_ = true; #define tostr(n) (q.value(n).isNull() ? QString::null : q.value(n).toString()) #define toint(n) (q.value(n).isNull() ? -1 : q.value(n).toInt()) #define tofloat(n) (q.value(n).isNull() ? -1 : q.value(n).toDouble()) d->id_ = toint(0); d->title_ = tostr(1); d->album_ = tostr(2); d->artist_ = tostr(3); d->albumartist_ = tostr(4); d->composer_ = tostr(5); d->track_ = toint(6); d->disc_ = toint(7); d->bpm_ = tofloat(8); d->year_ = toint(9); d->genre_ = tostr(10); d->comment_ = tostr(11); d->compilation_ = q.value(12).toBool(); d->length_ = toint(13); d->bitrate_ = toint(14); d->samplerate_ = toint(15); d->directory_id_ = toint(16); d->filename_ = tostr(17); d->mtime_ = toint(18); d->ctime_ = toint(19); d->filesize_ = toint(20); d->sampler_ = q.value(21).toBool(); d->art_automatic_ = q.value(22).toString(); d->art_manual_ = q.value(23).toString(); #undef tostr #undef toint #undef tofloat } void Song::InitFromLastFM(const lastfm::Track& track) { d->valid_ = true; d->title_ = track.title(); d->album_ = track.album(); d->artist_ = track.artist(); d->track_ = track.trackNumber(); d->length_ = track.duration(); } void Song::InitFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle) { d->valid_ = true; d->title_ = bundle.title; d->artist_ = bundle.artist; d->album_ = bundle.album; d->comment_ = bundle.comment; d->genre_ = bundle.genre; d->bitrate_ = bundle.bitrate.toInt(); d->samplerate_ = bundle.samplerate.toInt(); d->length_ = bundle.length.toInt(); d->year_ = bundle.year.toInt(); d->track_ = bundle.tracknr.toInt(); } void Song::BindToQuery(QSqlQuery *query) const { #define intval(x) (x == -1 ? QVariant() : x) // Remember to bind these in the same order as kBindSpec query->bindValue(":title", d->title_); query->bindValue(":album", d->album_); query->bindValue(":artist", d->artist_); query->bindValue(":albumartist", d->albumartist_); query->bindValue(":composer", d->composer_); query->bindValue(":track", intval(d->track_)); query->bindValue(":disc", intval(d->disc_)); query->bindValue(":bpm", intval(d->bpm_)); query->bindValue(":year", intval(d->year_)); query->bindValue(":genre", d->genre_); query->bindValue(":comment", d->comment_); query->bindValue(":compilation", d->compilation_ ? 1 : 0); query->bindValue(":length", intval(d->length_)); query->bindValue(":bitrate", intval(d->bitrate_)); query->bindValue(":samplerate", intval(d->samplerate_)); query->bindValue(":directory_id", intval(d->directory_id_)); query->bindValue(":filename", d->filename_); query->bindValue(":mtime", intval(d->mtime_)); query->bindValue(":ctime", intval(d->ctime_)); query->bindValue(":filesize", intval(d->filesize_)); query->bindValue(":sampler", d->sampler_ ? 1 : 0); query->bindValue(":art_automatic", d->art_automatic_); query->bindValue(":art_manual", d->art_manual_); #undef intval } void Song::ToLastFM(lastfm::Track* track) const { lastfm::MutableTrack mtrack(*track); mtrack.setArtist(d->artist_); mtrack.setAlbum(d->album_); mtrack.setTitle(d->title_); mtrack.setDuration(d->length_); mtrack.setTrackNumber(d->track_); mtrack.setSource(lastfm::Track::Player); } QString Song::PrettyTitleWithArtist() const { QString title(d->title_); if (title.isEmpty()) title = QFileInfo(d->filename_).baseName(); if (d->compilation_ && !d->artist_.isEmpty()) title = d->artist_ + " - " + title; return title; } QString Song::PrettyTitle() const { QString title(d->title_); if (title.isEmpty()) title = QFileInfo(d->filename_).baseName(); return title; } QString Song::PrettyLength() const { if (d->length_ == -1) return QString::null; return TrackSlider::PrettyTime(d->length_); } bool Song::IsMetadataEqual(const Song& other) const { return d->title_ == other.d->title_ && d->album_ == other.d->album_ && d->artist_ == other.d->artist_ && d->albumartist_ == other.d->albumartist_ && d->composer_ == other.d->composer_ && d->track_ == other.d->track_ && d->disc_ == other.d->disc_ && qFuzzyCompare(d->bpm_, other.d->bpm_) && d->year_ == other.d->year_ && d->genre_ == other.d->genre_ && d->comment_ == other.d->comment_ && d->compilation_ == other.d->compilation_ && d->length_ == other.d->length_ && d->bitrate_ == other.d->bitrate_ && d->samplerate_ == other.d->samplerate_ && d->sampler_ == other.d->sampler_ && d->art_automatic_ == other.d->art_automatic_ && d->art_manual_ == other.d->art_manual_; } bool Song::Save() const { if (d->filename_.isNull()) return false; # define str(x) TagLib::String(x.toUtf8().constData(), TagLib::String::UTF8) TagLib::FileRef ref(QFile::encodeName(d->filename_).constData()); ref.tag()->setTitle(str(d->title_)); ref.tag()->setArtist(str(d->artist_)); ref.tag()->setAlbum(str(d->album_)); ref.tag()->setGenre(str(d->genre_)); ref.tag()->setComment(str(d->comment_)); ref.tag()->setYear(d->year_); ref.tag()->setTrack(d->track_); # undef str bool ret = ref.save(); #ifdef Q_OS_LINUX if (ret) { // Linux: inotify doesn't seem to notice the change to the file unless we // change the timestamps as well. (this is what touch does) utimensat(0, QFile::encodeName(d->filename_).constData(), NULL, 0); } #endif // Q_OS_LINUX return ret; } QImage Song::GetBestImage() const { if (!d->image_.isNull()) return d->image_; QImage art(AlbumCoverLoader::TryLoadImage(d->art_automatic_, d->art_manual_)); if (!art.isNull()) return art; return QImage(":/nocover.png"); }