1 /**************************************************************************************** 2 * Copyright (c) 2007-2009 Maximilian Kossick <maximilian.kossick@googlemail.com> * 3 * Copyright (c) 2008 Peter ZHOU <peterzhoulei@gmail.com> * 4 * Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> * 5 * * 6 * This program is free software; you can redistribute it and/or modify it under * 7 * the terms of the GNU General Public License as published by the Free Software * 8 * Foundation; either version 2 of the License, or (at your option) any later * 9 * version. * 10 * * 11 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 13 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 14 * * 15 * You should have received a copy of the GNU General Public License along with * 16 * this program. If not, see <http://www.gnu.org/licenses/>. * 17 ****************************************************************************************/ 18 19 #ifndef AMAROK_META_FILE_P_H 20 #define AMAROK_META_FILE_P_H 21 22 #include "amarokconfig.h" 23 #include "core/collections/Collection.h" 24 #include "core/support/Debug.h" 25 #include "core/meta/Meta.h" 26 #include "core/meta/support/MetaUtility.h" 27 #include "MetaReplayGain.h" 28 #include "MetaTagLib.h" 29 #include "core-impl/collections/support/jobs/WriteTagsJob.h" 30 #include "core-impl/collections/support/ArtistHelper.h" 31 #include "core-impl/capabilities/AlbumActionsCapability.h" 32 #include "covermanager/CoverCache.h" 33 #include "File.h" 34 35 #include <ThreadWeaver/Queue> 36 #include <ThreadWeaver/Job> 37 #include <QDateTime> 38 #include <QFile> 39 #include <QFileInfo> 40 #include <QObject> 41 #include <QPointer> 42 #include <QSet> 43 #include <QString> 44 45 namespace Capabilities 46 { 47 class LastfmReadLabelCapability; 48 } 49 50 namespace MetaFile 51 { 52 //d-pointer implementation 53 54 struct MetaData 55 { MetaDataMetaData56 MetaData() 57 : created( 0 ) 58 , discNumber( 0 ) 59 , trackNumber( 0 ) 60 , length( 0 ) 61 , fileSize( 0 ) 62 , sampleRate( 0 ) 63 , bitRate( 0 ) 64 , year( 0 ) 65 , bpm( -1.0 ) 66 , trackGain( 0.0 ) 67 , trackPeak( 0.0 ) 68 , albumGain( 0.0 ) 69 , albumPeak( 0.0 ) 70 , embeddedImage( false ) 71 , rating( 0 ) 72 , score( 0.0 ) 73 , playCount( 0 ) 74 { } 75 QString title; 76 QString artist; 77 QString album; 78 QString albumArtist; 79 QString comment; 80 QString composer; 81 QString genre; 82 uint created; 83 int discNumber; 84 int trackNumber; 85 qint64 length; 86 int fileSize; 87 int sampleRate; 88 int bitRate; 89 int year; 90 qreal bpm; 91 qreal trackGain; 92 qreal trackPeak; 93 qreal albumGain; 94 qreal albumPeak; 95 bool embeddedImage; 96 97 int rating; 98 double score; 99 int playCount; 100 }; 101 102 class Track::Private : public QObject 103 { 104 Q_OBJECT 105 public: Private(Track * t)106 Private( Track *t ) 107 : QObject() 108 , url() 109 , album() 110 , artist() 111 , albumArtist() 112 , batchUpdate( 0 ) 113 , track( t ) 114 {} 115 116 QUrl url; 117 118 Meta::AlbumPtr album; 119 Meta::ArtistPtr artist; 120 Meta::ArtistPtr albumArtist; 121 Meta::GenrePtr genre; 122 Meta::ComposerPtr composer; 123 Meta::YearPtr year; 124 QPointer<Capabilities::LastfmReadLabelCapability> readLabelCapability; 125 QPointer<Collections::Collection> collection; 126 127 /** 128 * Number of current batch operations started by @see beginUpdate() and not 129 * yet ended by @see endUpdate(). Must only be accessed with lock held. 130 */ 131 int batchUpdate; 132 Meta::FieldHash changes; 133 QReadWriteLock lock; 134 writeMetaData()135 void writeMetaData() 136 { 137 DEBUG_BLOCK 138 debug() << "changes:" << changes; 139 if( AmarokConfig::writeBack() ) 140 Meta::Tag::writeTags( url.isLocalFile() ? url.toLocalFile() : url.path(), 141 changes, AmarokConfig::writeBackStatistics() ); 142 changes.clear(); 143 readMetaData(); 144 } 145 notifyObservers()146 void notifyObservers() 147 { 148 track->notifyObservers(); 149 } 150 151 MetaData m_data; 152 153 private: 154 TagLib::FileRef getFileRef(); 155 Track *track; 156 157 public Q_SLOTS: readMetaData()158 void readMetaData() 159 { 160 QFileInfo fi( url.isLocalFile() ? url.toLocalFile() : url.path() ); 161 m_data.created = fi.created().toSecsSinceEpoch(); 162 163 Meta::FieldHash values = Meta::Tag::readTags( fi.absoluteFilePath() ); 164 165 // (re)set all fields to behave the same as the constructor. E.g. catch even complete 166 // removal of tags etc. 167 MetaData def; // default 168 m_data.title = values.value( Meta::valTitle, def.title ).toString(); 169 m_data.artist = values.value( Meta::valArtist, def.artist ).toString(); 170 m_data.album = values.value( Meta::valAlbum, def.album ).toString(); 171 m_data.albumArtist = values.value( Meta::valAlbumArtist, def.albumArtist ).toString(); 172 m_data.embeddedImage = values.value( Meta::valHasCover, def.embeddedImage ).toBool(); 173 m_data.comment = values.value( Meta::valComment, def.comment ).toString(); 174 m_data.genre = values.value( Meta::valGenre, def.genre ).toString(); 175 m_data.composer = values.value( Meta::valComposer, def.composer ).toString(); 176 m_data.year = values.value( Meta::valYear, def.year ).toInt(); 177 m_data.discNumber = values.value( Meta::valDiscNr, def.discNumber ).toInt(); 178 m_data.trackNumber = values.value( Meta::valTrackNr, def.trackNumber ).toInt(); 179 m_data.bpm = values.value( Meta::valBpm, def.bpm ).toReal(); 180 m_data.bitRate = values.value( Meta::valBitrate, def.bitRate ).toInt(); 181 m_data.length = values.value( Meta::valLength, def.length ).toLongLong(); 182 m_data.sampleRate = values.value( Meta::valSamplerate, def.sampleRate ).toInt(); 183 m_data.fileSize = values.value( Meta::valFilesize, def.fileSize ).toLongLong(); 184 185 m_data.trackGain = values.value( Meta::valTrackGain, def.trackGain ).toReal(); 186 m_data.trackPeak= values.value( Meta::valTrackGainPeak, def.trackPeak ).toReal(); 187 m_data.albumGain = values.value( Meta::valAlbumGain, def.albumGain ).toReal(); 188 m_data.albumPeak= values.value( Meta::valAlbumGainPeak, def.albumPeak ).toReal(); 189 190 // only read the stats if we can write them later. Would be annoying to have 191 // read-only rating that you don't like 192 if( AmarokConfig::writeBackStatistics() ) 193 { 194 m_data.rating = values.value( Meta::valRating, def.rating ).toInt(); 195 m_data.score = values.value( Meta::valScore, def.score ).toDouble(); 196 m_data.playCount = values.value( Meta::valPlaycount, def.playCount ).toInt(); 197 } 198 199 if(url.isLocalFile()) 200 { 201 m_data.fileSize = QFile( url.toLocalFile() ).size(); 202 } 203 else 204 { 205 m_data.fileSize = QFile( url.path() ).size(); 206 } 207 208 //as a last ditch effort, use the filename as the title if nothing else has been found 209 if ( m_data.title.isEmpty() ) 210 { 211 m_data.title = url.fileName(); 212 } 213 214 // try to guess best album artist (even if non-empty, part of compilation detection) 215 m_data.albumArtist = ArtistHelper::bestGuessAlbumArtist( m_data.albumArtist, 216 m_data.artist, m_data.genre, m_data.composer ); 217 } //Definition of slot readMetaData ends 218 219 }; //Definition of class Track::Private ends 220 221 // internal helper classes 222 223 class FileArtist : public Meta::Artist 224 { 225 public: 226 explicit FileArtist( MetaFile::Track::Private *dptr, bool isAlbumArtist = false ) Artist()227 : Meta::Artist() 228 , d( dptr ) 229 , m_isAlbumArtist( isAlbumArtist ) 230 {} 231 tracks()232 Meta::TrackList tracks() override 233 { 234 return Meta::TrackList(); 235 } 236 name()237 QString name() const override 238 { 239 const QString artist = m_isAlbumArtist ? d.data()->m_data.albumArtist 240 : d.data()->m_data.artist; 241 return artist; 242 } 243 244 bool operator==( const Meta::Artist &other ) const override { 245 return name() == other.name(); 246 } 247 248 QPointer<MetaFile::Track::Private> const d; 249 const bool m_isAlbumArtist; 250 }; 251 252 class FileAlbum : public Meta::Album 253 { 254 public: FileAlbum(MetaFile::Track::Private * dptr)255 explicit FileAlbum( MetaFile::Track::Private *dptr ) 256 : Meta::Album() 257 , d( dptr ) 258 {} 259 hasCapabilityInterface(Capabilities::Capability::Type type)260 bool hasCapabilityInterface( Capabilities::Capability::Type type ) const override 261 { 262 switch( type ) 263 { 264 case Capabilities::Capability::Actions: 265 return true; 266 default: 267 return false; 268 } 269 } 270 createCapabilityInterface(Capabilities::Capability::Type type)271 Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ) override 272 { 273 switch( type ) 274 { 275 case Capabilities::Capability::Actions: 276 return new Capabilities::AlbumActionsCapability( Meta::AlbumPtr( this ) ); 277 default: 278 return 0; 279 } 280 } 281 isCompilation()282 bool isCompilation() const override 283 { 284 /* non-compilation albums with no album artists may be hidden in collection 285 * browser if certain modes are used, so force compilation in this case */ 286 return !hasAlbumArtist(); 287 } 288 hasAlbumArtist()289 bool hasAlbumArtist() const override 290 { 291 return !d.data()->albumArtist->name().isEmpty(); 292 } 293 albumArtist()294 Meta::ArtistPtr albumArtist() const override 295 { 296 /* only return album artist if it would be non-empty, some Amarok parts do not 297 * call hasAlbumArtist() prior to calling albumArtist() and it is better to be 298 * consistent with other Meta::Track implementations */ 299 if( hasAlbumArtist() ) 300 return d.data()->albumArtist; 301 return Meta::ArtistPtr(); 302 } 303 tracks()304 Meta::TrackList tracks() override 305 { 306 return Meta::TrackList(); 307 } 308 name()309 QString name() const override 310 { 311 if( d ) 312 { 313 const QString albumName = d.data()->m_data.album; 314 return albumName; 315 } 316 else 317 return QString(); 318 } 319 320 bool hasImage( int /* size */ = 0 ) const override 321 { 322 if( d && d.data()->m_data.embeddedImage ) 323 return true; 324 return false; 325 } 326 327 QImage image( int size = 0 ) const override 328 { 329 QImage image; 330 if( d && d.data()->m_data.embeddedImage ) 331 { 332 image = Meta::Tag::embeddedCover( d.data()->url.toLocalFile() ); 333 } 334 335 if( image.isNull() || size <= 0 /* do not scale */ ) 336 return image; 337 return image.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); 338 } 339 canUpdateImage()340 bool canUpdateImage() const override 341 { 342 return d; // true if underlying track is not null 343 } 344 setImage(const QImage & image)345 void setImage( const QImage &image ) override 346 { 347 if( !d ) 348 return; 349 350 Meta::FieldHash fields; 351 fields.insert( Meta::valImage, image ); 352 WriteTagsJob *job = new WriteTagsJob( d.data()->url.toLocalFile(), fields ); 353 QObject::connect( job, &WriteTagsJob::done, job, &QObject::deleteLater ); 354 355 ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) ); 356 357 if( d.data()->m_data.embeddedImage == image.isNull() ) 358 // we need to toggle the embeddedImage switch in this case 359 QObject::connect( job, &WriteTagsJob::done, d.data(), &Track::Private::readMetaData ); 360 361 CoverCache::invalidateAlbum( this ); 362 notifyObservers(); 363 // following call calls Track's notifyObservers. This is needed because for example 364 // UmsCollection justifiably listens only to Track's metadataChanged() to update 365 // its MemoryCollection maps 366 d.data()->notifyObservers(); 367 } 368 removeImage()369 void removeImage() override 370 { 371 setImage( QImage() ); 372 } 373 374 bool operator==( const Meta::Album &other ) const override { 375 return name() == other.name(); 376 } 377 378 QPointer<MetaFile::Track::Private> const d; 379 }; 380 381 class FileGenre : public Meta::Genre 382 { 383 public: FileGenre(MetaFile::Track::Private * dptr)384 explicit FileGenre( MetaFile::Track::Private *dptr ) 385 : Meta::Genre() 386 , d( dptr ) 387 {} 388 tracks()389 Meta::TrackList tracks() override 390 { 391 return Meta::TrackList(); 392 } 393 name()394 QString name() const override 395 { 396 const QString genreName = d.data()->m_data.genre; 397 return genreName; 398 } 399 400 bool operator==( const Meta::Genre &other ) const override { 401 return name() == other.name(); 402 } 403 404 QPointer<MetaFile::Track::Private> const d; 405 }; 406 407 class FileComposer : public Meta::Composer 408 { 409 public: FileComposer(MetaFile::Track::Private * dptr)410 explicit FileComposer( MetaFile::Track::Private *dptr ) 411 : Meta::Composer() 412 , d( dptr ) 413 {} 414 tracks()415 Meta::TrackList tracks() override 416 { 417 return Meta::TrackList(); 418 } 419 name()420 QString name() const override 421 { 422 const QString composer = d.data()->m_data.composer; 423 return composer; 424 } 425 426 bool operator==( const Meta::Composer &other ) const override { 427 return name() == other.name(); 428 } 429 430 QPointer<MetaFile::Track::Private> const d; 431 }; 432 433 class FileYear : public Meta::Year 434 { 435 public: FileYear(MetaFile::Track::Private * dptr)436 explicit FileYear( MetaFile::Track::Private *dptr ) 437 : Meta::Year() 438 , d( dptr ) 439 {} 440 tracks()441 Meta::TrackList tracks() override 442 { 443 return Meta::TrackList(); 444 } 445 name()446 QString name() const override 447 { 448 const QString year = QString::number( d.data()->m_data.year ); 449 return year; 450 } 451 452 bool operator==( const Meta::Year &other ) const override { 453 return name() == other.name(); 454 } 455 456 QPointer<MetaFile::Track::Private> const d; 457 }; 458 } 459 460 #endif 461