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