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