File indexing completed on 2025-01-19 04:24:38
0001 /**************************************************************************************** 0002 * Copyright (c) 2007-2009 Maximilian Kossick <maximilian.kossick@googlemail.com> * 0003 * Copyright (c) 2008 Peter ZHOU <peterzhoulei@gmail.com> * 0004 * Copyright (c) 2008 Seb Ruiz <ruiz@kde.org> * 0005 * * 0006 * This program is free software; you can redistribute it and/or modify it under * 0007 * the terms of the GNU General Public License as published by the Free Software * 0008 * Foundation; either version 2 of the License, or (at your option) any later * 0009 * version. * 0010 * * 0011 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0013 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0014 * * 0015 * You should have received a copy of the GNU General Public License along with * 0016 * this program. If not, see <http://www.gnu.org/licenses/>. * 0017 ****************************************************************************************/ 0018 0019 #ifndef AMAROK_META_FILE_P_H 0020 #define AMAROK_META_FILE_P_H 0021 0022 #include "amarokconfig.h" 0023 #include "core/collections/Collection.h" 0024 #include "core/support/Debug.h" 0025 #include "core/meta/Meta.h" 0026 #include "core/meta/support/MetaUtility.h" 0027 #include "MetaReplayGain.h" 0028 #include "MetaTagLib.h" 0029 #include "core-impl/collections/support/jobs/WriteTagsJob.h" 0030 #include "core-impl/collections/support/ArtistHelper.h" 0031 #include "core-impl/capabilities/AlbumActionsCapability.h" 0032 #include "covermanager/CoverCache.h" 0033 #include "File.h" 0034 0035 #include <ThreadWeaver/Queue> 0036 #include <ThreadWeaver/Job> 0037 #include <QDateTime> 0038 #include <QFile> 0039 #include <QFileInfo> 0040 #include <QObject> 0041 #include <QPointer> 0042 #include <QSet> 0043 #include <QString> 0044 0045 namespace Capabilities 0046 { 0047 class LastfmReadLabelCapability; 0048 } 0049 0050 namespace MetaFile 0051 { 0052 //d-pointer implementation 0053 0054 struct MetaData 0055 { 0056 MetaData() 0057 : created( 0 ) 0058 , discNumber( 0 ) 0059 , trackNumber( 0 ) 0060 , length( 0 ) 0061 , fileSize( 0 ) 0062 , sampleRate( 0 ) 0063 , bitRate( 0 ) 0064 , year( 0 ) 0065 , bpm( -1.0 ) 0066 , trackGain( 0.0 ) 0067 , trackPeak( 0.0 ) 0068 , albumGain( 0.0 ) 0069 , albumPeak( 0.0 ) 0070 , embeddedImage( false ) 0071 , rating( 0 ) 0072 , score( 0.0 ) 0073 , playCount( 0 ) 0074 { } 0075 QString title; 0076 QString artist; 0077 QString album; 0078 QString albumArtist; 0079 QString comment; 0080 QString composer; 0081 QString genre; 0082 uint created; 0083 int discNumber; 0084 int trackNumber; 0085 qint64 length; 0086 int fileSize; 0087 int sampleRate; 0088 int bitRate; 0089 int year; 0090 qreal bpm; 0091 qreal trackGain; 0092 qreal trackPeak; 0093 qreal albumGain; 0094 qreal albumPeak; 0095 bool embeddedImage; 0096 0097 int rating; 0098 double score; 0099 int playCount; 0100 }; 0101 0102 class Track::Private : public QObject 0103 { 0104 Q_OBJECT 0105 public: 0106 Private( Track *t ) 0107 : QObject() 0108 , url() 0109 , album() 0110 , artist() 0111 , albumArtist() 0112 , batchUpdate( 0 ) 0113 , track( t ) 0114 {} 0115 0116 QUrl url; 0117 0118 Meta::AlbumPtr album; 0119 Meta::ArtistPtr artist; 0120 Meta::ArtistPtr albumArtist; 0121 Meta::GenrePtr genre; 0122 Meta::ComposerPtr composer; 0123 Meta::YearPtr year; 0124 QPointer<Capabilities::LastfmReadLabelCapability> readLabelCapability; 0125 QPointer<Collections::Collection> collection; 0126 0127 /** 0128 * Number of current batch operations started by @see beginUpdate() and not 0129 * yet ended by @see endUpdate(). Must only be accessed with lock held. 0130 */ 0131 int batchUpdate; 0132 Meta::FieldHash changes; 0133 QReadWriteLock lock; 0134 0135 void writeMetaData() 0136 { 0137 DEBUG_BLOCK 0138 debug() << "changes:" << changes; 0139 if( AmarokConfig::writeBack() ) 0140 Meta::Tag::writeTags( url.isLocalFile() ? url.toLocalFile() : url.path(), 0141 changes, AmarokConfig::writeBackStatistics() ); 0142 changes.clear(); 0143 readMetaData(); 0144 } 0145 0146 void notifyObservers() 0147 { 0148 track->notifyObservers(); 0149 } 0150 0151 MetaData m_data; 0152 0153 private: 0154 TagLib::FileRef getFileRef(); 0155 Track *track; 0156 0157 public Q_SLOTS: 0158 void readMetaData() 0159 { 0160 QFileInfo fi( url.isLocalFile() ? url.toLocalFile() : url.path() ); 0161 m_data.created = fi.birthTime().toSecsSinceEpoch(); 0162 0163 Meta::FieldHash values = Meta::Tag::readTags( fi.absoluteFilePath() ); 0164 0165 // (re)set all fields to behave the same as the constructor. E.g. catch even complete 0166 // removal of tags etc. 0167 MetaData def; // default 0168 m_data.title = values.value( Meta::valTitle, def.title ).toString(); 0169 m_data.artist = values.value( Meta::valArtist, def.artist ).toString(); 0170 m_data.album = values.value( Meta::valAlbum, def.album ).toString(); 0171 m_data.albumArtist = values.value( Meta::valAlbumArtist, def.albumArtist ).toString(); 0172 m_data.embeddedImage = values.value( Meta::valHasCover, def.embeddedImage ).toBool(); 0173 m_data.comment = values.value( Meta::valComment, def.comment ).toString(); 0174 m_data.genre = values.value( Meta::valGenre, def.genre ).toString(); 0175 m_data.composer = values.value( Meta::valComposer, def.composer ).toString(); 0176 m_data.year = values.value( Meta::valYear, def.year ).toInt(); 0177 m_data.discNumber = values.value( Meta::valDiscNr, def.discNumber ).toInt(); 0178 m_data.trackNumber = values.value( Meta::valTrackNr, def.trackNumber ).toInt(); 0179 m_data.bpm = values.value( Meta::valBpm, def.bpm ).toReal(); 0180 m_data.bitRate = values.value( Meta::valBitrate, def.bitRate ).toInt(); 0181 m_data.length = values.value( Meta::valLength, def.length ).toLongLong(); 0182 m_data.sampleRate = values.value( Meta::valSamplerate, def.sampleRate ).toInt(); 0183 m_data.fileSize = values.value( Meta::valFilesize, def.fileSize ).toLongLong(); 0184 0185 m_data.trackGain = values.value( Meta::valTrackGain, def.trackGain ).toReal(); 0186 m_data.trackPeak= values.value( Meta::valTrackGainPeak, def.trackPeak ).toReal(); 0187 m_data.albumGain = values.value( Meta::valAlbumGain, def.albumGain ).toReal(); 0188 m_data.albumPeak= values.value( Meta::valAlbumGainPeak, def.albumPeak ).toReal(); 0189 0190 // only read the stats if we can write them later. Would be annoying to have 0191 // read-only rating that you don't like 0192 if( AmarokConfig::writeBackStatistics() ) 0193 { 0194 m_data.rating = values.value( Meta::valRating, def.rating ).toInt(); 0195 m_data.score = values.value( Meta::valScore, def.score ).toDouble(); 0196 m_data.playCount = values.value( Meta::valPlaycount, def.playCount ).toInt(); 0197 } 0198 0199 if(url.isLocalFile()) 0200 { 0201 m_data.fileSize = QFile( url.toLocalFile() ).size(); 0202 } 0203 else 0204 { 0205 m_data.fileSize = QFile( url.path() ).size(); 0206 } 0207 0208 //as a last ditch effort, use the filename as the title if nothing else has been found 0209 if ( m_data.title.isEmpty() ) 0210 { 0211 m_data.title = url.fileName(); 0212 } 0213 0214 // try to guess best album artist (even if non-empty, part of compilation detection) 0215 m_data.albumArtist = ArtistHelper::bestGuessAlbumArtist( m_data.albumArtist, 0216 m_data.artist, m_data.genre, m_data.composer ); 0217 } //Definition of slot readMetaData ends 0218 0219 }; //Definition of class Track::Private ends 0220 0221 // internal helper classes 0222 0223 class FileArtist : public Meta::Artist 0224 { 0225 public: 0226 explicit FileArtist( MetaFile::Track::Private *dptr, bool isAlbumArtist = false ) 0227 : Meta::Artist() 0228 , d( dptr ) 0229 , m_isAlbumArtist( isAlbumArtist ) 0230 {} 0231 0232 Meta::TrackList tracks() override 0233 { 0234 return Meta::TrackList(); 0235 } 0236 0237 QString name() const override 0238 { 0239 const QString artist = m_isAlbumArtist ? d.data()->m_data.albumArtist 0240 : d.data()->m_data.artist; 0241 return artist; 0242 } 0243 0244 bool operator==( const Meta::Artist &other ) const override { 0245 return name() == other.name(); 0246 } 0247 0248 QPointer<MetaFile::Track::Private> const d; 0249 const bool m_isAlbumArtist; 0250 }; 0251 0252 class FileAlbum : public Meta::Album 0253 { 0254 public: 0255 explicit FileAlbum( MetaFile::Track::Private *dptr ) 0256 : Meta::Album() 0257 , d( dptr ) 0258 {} 0259 0260 bool hasCapabilityInterface( Capabilities::Capability::Type type ) const override 0261 { 0262 switch( type ) 0263 { 0264 case Capabilities::Capability::Actions: 0265 return true; 0266 default: 0267 return false; 0268 } 0269 } 0270 0271 Capabilities::Capability* createCapabilityInterface( Capabilities::Capability::Type type ) override 0272 { 0273 switch( type ) 0274 { 0275 case Capabilities::Capability::Actions: 0276 return new Capabilities::AlbumActionsCapability( Meta::AlbumPtr( this ) ); 0277 default: 0278 return nullptr; 0279 } 0280 } 0281 0282 bool isCompilation() const override 0283 { 0284 /* non-compilation albums with no album artists may be hidden in collection 0285 * browser if certain modes are used, so force compilation in this case */ 0286 return !hasAlbumArtist(); 0287 } 0288 0289 bool hasAlbumArtist() const override 0290 { 0291 return !d.data()->albumArtist->name().isEmpty(); 0292 } 0293 0294 Meta::ArtistPtr albumArtist() const override 0295 { 0296 /* only return album artist if it would be non-empty, some Amarok parts do not 0297 * call hasAlbumArtist() prior to calling albumArtist() and it is better to be 0298 * consistent with other Meta::Track implementations */ 0299 if( hasAlbumArtist() ) 0300 return d.data()->albumArtist; 0301 return Meta::ArtistPtr(); 0302 } 0303 0304 Meta::TrackList tracks() override 0305 { 0306 return Meta::TrackList(); 0307 } 0308 0309 QString name() const override 0310 { 0311 if( d ) 0312 { 0313 const QString albumName = d.data()->m_data.album; 0314 return albumName; 0315 } 0316 else 0317 return QString(); 0318 } 0319 0320 bool hasImage( int /* size */ = 0 ) const override 0321 { 0322 if( d && d.data()->m_data.embeddedImage ) 0323 return true; 0324 return false; 0325 } 0326 0327 QImage image( int size = 0 ) const override 0328 { 0329 QImage image; 0330 if( d && d.data()->m_data.embeddedImage ) 0331 { 0332 image = Meta::Tag::embeddedCover( d.data()->url.toLocalFile() ); 0333 } 0334 0335 if( image.isNull() || size <= 0 /* do not scale */ ) 0336 return image; 0337 return image.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); 0338 } 0339 0340 bool canUpdateImage() const override 0341 { 0342 return d; // true if underlying track is not null 0343 } 0344 0345 void setImage( const QImage &image ) override 0346 { 0347 if( !d ) 0348 return; 0349 0350 Meta::FieldHash fields; 0351 fields.insert( Meta::valImage, image ); 0352 WriteTagsJob *job = new WriteTagsJob( d.data()->url.toLocalFile(), fields ); 0353 QObject::connect( job, &WriteTagsJob::done, job, &QObject::deleteLater ); 0354 0355 ThreadWeaver::Queue::instance()->enqueue( QSharedPointer<ThreadWeaver::Job>(job) ); 0356 0357 if( d.data()->m_data.embeddedImage == image.isNull() ) 0358 // we need to toggle the embeddedImage switch in this case 0359 QObject::connect( job, &WriteTagsJob::done, d.data(), &Track::Private::readMetaData ); 0360 0361 CoverCache::invalidateAlbum( this ); 0362 notifyObservers(); 0363 // following call calls Track's notifyObservers. This is needed because for example 0364 // UmsCollection justifiably listens only to Track's metadataChanged() to update 0365 // its MemoryCollection maps 0366 d.data()->notifyObservers(); 0367 } 0368 0369 void removeImage() override 0370 { 0371 setImage( QImage() ); 0372 } 0373 0374 bool operator==( const Meta::Album &other ) const override { 0375 return name() == other.name(); 0376 } 0377 0378 QPointer<MetaFile::Track::Private> const d; 0379 }; 0380 0381 class FileGenre : public Meta::Genre 0382 { 0383 public: 0384 explicit FileGenre( MetaFile::Track::Private *dptr ) 0385 : Meta::Genre() 0386 , d( dptr ) 0387 {} 0388 0389 Meta::TrackList tracks() override 0390 { 0391 return Meta::TrackList(); 0392 } 0393 0394 QString name() const override 0395 { 0396 const QString genreName = d.data()->m_data.genre; 0397 return genreName; 0398 } 0399 0400 bool operator==( const Meta::Genre &other ) const override { 0401 return name() == other.name(); 0402 } 0403 0404 QPointer<MetaFile::Track::Private> const d; 0405 }; 0406 0407 class FileComposer : public Meta::Composer 0408 { 0409 public: 0410 explicit FileComposer( MetaFile::Track::Private *dptr ) 0411 : Meta::Composer() 0412 , d( dptr ) 0413 {} 0414 0415 Meta::TrackList tracks() override 0416 { 0417 return Meta::TrackList(); 0418 } 0419 0420 QString name() const override 0421 { 0422 const QString composer = d.data()->m_data.composer; 0423 return composer; 0424 } 0425 0426 bool operator==( const Meta::Composer &other ) const override { 0427 return name() == other.name(); 0428 } 0429 0430 QPointer<MetaFile::Track::Private> const d; 0431 }; 0432 0433 class FileYear : public Meta::Year 0434 { 0435 public: 0436 explicit FileYear( MetaFile::Track::Private *dptr ) 0437 : Meta::Year() 0438 , d( dptr ) 0439 {} 0440 0441 Meta::TrackList tracks() override 0442 { 0443 return Meta::TrackList(); 0444 } 0445 0446 QString name() const override 0447 { 0448 const QString year = QString::number( d.data()->m_data.year ); 0449 return year; 0450 } 0451 0452 bool operator==( const Meta::Year &other ) const override { 0453 return name() == other.name(); 0454 } 0455 0456 QPointer<MetaFile::Track::Private> const d; 0457 }; 0458 } 0459 0460 #endif