1 // Max Howell <max.howell@methylblue.com>, (C) 2004
2 // Alexandre Pereira de Oliveira <aleprj@gmail.com>, (C) 2005
3 // Shane King <kde@dontletsstart.com>, (C) 2006
4 // Peter C. Ndikuwera <pndiku@gmail.com>, (C) 2006
5 // License: GNU General Public License V2
10 #if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3)
11 #define PRETTY_TITLE_CACHE
14 #include <QStringList>
16 #include <Q3ValueList>
18 #include <kurl.h> //inline functions
19 #include <klocale.h> //inline functions
20 #include <audioproperties.h>
21 #include "expression.h"
22 #include "atomicstring.h"
25 #include "amarok_export.h"
30 template<class T
> class Q3ValueList
;
37 class UniqueFileIdentifierFrame
;
44 class PodcastEpisodeBundle
;
52 * @author Max Howell <max.howell@methylblue.com>
54 * If this class doesn't work for you in some way, extend it sensibly :)
58 class AMAROK_EXPORT MetaBundle
90 class AMAROK_EXPORT EmbeddedImage
{
93 EmbeddedImage( const TagLib::ByteVector
& data
, const TagLib::String
& description
);
95 const QByteArray
&hash() const;
96 const QString
&description() const { return m_description
; }
97 bool save( const QDir
& dir
) const;
101 QString m_description
;
102 mutable QByteArray m_hash
;
105 typedef Q3ValueList
<EmbeddedImage
> EmbeddedImageList
;
107 /** This is a bit vector for selecting columns. It's very fast to compare
108 in matchFast. It might be a good idea to replace the QValue<int>
109 column masks with this eventually. */
110 typedef quint32 ColumnMask
;
112 /** Returns the name of the column at \p index as a string -- not i18ned, for internal purposes. */
113 static const QString
&exactColumnName( int index
);
114 /** Returns the name of the column at \p index as a string -- i18ned, for display purposes. */
115 static const QString
prettyColumnName( int index
);
116 /** Returns the index of the column with the not i18ned name \p name. */
117 static int columnIndex( const QString
&name
);
119 // These values are stored on the Database, so, don't change the order. Only append new ones to the end.
120 enum FileType
{ other
, mp3
, ogg
, wma
, mp4
, flac
, ra
, rv
, rm
, rmj
, rmvb
};
122 //for the audioproperties
123 static const int Undetermined
= -2; /// we haven't yet read the tags
124 static const int Irrelevant
= -1; /// not applicable to this stream/media type, eg length for http streams
125 static const int Unavailable
= 0; /// cannot be obtained
127 // whether file is part of a compilation
128 enum Compilation
{ CompilationNo
= 0, CompilationYes
= 1, CompilationUnknown
= -1 };
130 /// Creates an empty MetaBundle
131 AMAROK_EXPORT
MetaBundle();
133 /// Creates a MetaBundle for url, tags will be obtained and set
134 AMAROK_EXPORT
explicit MetaBundle( const KUrl
&url
,
135 bool noCache
= false,
136 TagLib::AudioProperties::ReadStyle
= TagLib::AudioProperties::Fast
,
137 EmbeddedImageList
* images
= 0 );
139 /** For the StreamProvider */
140 AMAROK_EXPORT
MetaBundle( const QString
&title
,
141 const QString
&streamUrl
,
143 const QString
&genre
,
144 const QString
&streamName
,
147 AMAROK_EXPORT
MetaBundle( const MetaBundle
&bundle
);
149 AMAROK_EXPORT
virtual ~MetaBundle();
151 MetaBundle
& operator=( const MetaBundle
& bundle
);
152 bool operator==( const MetaBundle
& bundle
) const;
153 bool operator!=( const MetaBundle
& bundle
) const;
155 /** Test for an empty metabundle */
156 bool isEmpty() const;
158 /** Empty the metabundle */
161 /** Is it media that has metadata? Note currently we don't check for an audio mimetype */
162 bool isValidMedia() const;
164 /** The bundle doesn't yet know its audioProperties */
165 bool audioPropertiesUndetermined() const;
167 /** The embedded artwork in the file (loaded from file into images variable, unmodified if no images present/loadable) */
168 void embeddedImages(EmbeddedImageList
&images
) const;
170 /** If you want Accurate reading say so. If EmbeddedImageList != NULL, embedded art is loaded into it */
171 void readTags( TagLib::AudioProperties::ReadStyle
= TagLib::AudioProperties::Fast
, EmbeddedImageList
* images
= 0 );
173 /** Saves the changes to the file using the transactional algorithm for safety. */
176 /** Saves the changes to the file. Returns false on error. */
177 bool save( TagLib::FileRef
* fileref
= 0 );
179 /** Saves the MetaBundle's data as XML to a text stream. */
180 bool save( QTextStream
&stream
, const QStringList
&attributes
= QStringList() ) const;
182 /** Returns whether the url referred to is a local file */
185 /** Returns whether the url referred to can be accessed via kio slaves */
186 bool isKioUrl() const;
188 /** Returns whether url can be accessed via kio slaves */
189 static bool isKioUrl( const KUrl
&url
);
191 /** Returns whether composer, disc number and bpm fields are available. */
192 bool hasExtendedMetaInformation() const;
194 void copyFrom( const MetaBundle
& bundle
);
196 void copyFrom( const PodcastEpisodeBundle
&peb
);
198 /** Returns a string representation of the tag at \p column, in a format suitable for internal purposes.
199 For example, for a track 3:24 long, it'll return "204" (seconds).
200 This should not be used for displaying the tag to the user. */
201 QString
exactText( int column
, bool ensureCached
= false ) const;
203 /** Sets the tag at \p column from a string in the same format as returned by exactText(). */
204 void setExactText( int column
, const QString
&text
);
206 /** Returns the tag at \p column in a format suitable for displaying to the user. */
207 QString
prettyText( int column
) const;
209 /** Returns whether the bundle matches \p expression.
210 This is fast and doesn't take advanced syntax into account,
211 and should only be used when it is certain none is present.
212 The tags in \p columns are checked for matches.
213 @see ExpressionParser::isAdvancedExpression() */
214 bool matchesSimpleExpression( const QString
&expression
, const Q3ValueList
<int> &columns
) const;
216 /** A faster version of the above, that pre-caches all the data to be
217 searched in a single string, to avoid re-building integer and lower
218 case strings over and over. It is designed to be called from a
219 playlist search *only* -- it is not entirely thread-safe for efficiency,
220 although it's highly unlikely to crash. Consider this the beginning
221 of a real super-efficient index (e.g. suffix tree).
222 \p terms is a list of lower-case words. */
223 bool matchesFast(const QStringList
&terms
, ColumnMask columns
) const;
225 /** Returns whether the bundle matches \p expression.
226 This takes advanced syntax into account, and is slightly slower than matchesSimpleExpression().
227 The tags in \p defaultColumns are checked for matches where the expression doesn't specify any manually. */
228 bool matchesExpression( const QString
&expression
, const Q3ValueList
<int> &defaultColumns
) const;
230 /** Returns whether the bundle matches the pre-parsed expression \p parsedData.
231 The tags in \p defaultColumns are checked for matches where the expression doesn't specify any manually.
232 @see ExpressionParser */
233 bool matchesParsedExpression( const ParsedExpression
&parsedData
, const Q3ValueList
<int> &defaultColumns
) const;
235 /** PlaylistItem reimplements this so it can be informed of moodbar
236 data events without having to use signals */
237 virtual void moodbarJobEvent( int newState
)
242 * A class to load MetaBundles from XML.
243 * #include "xmlloader.h"
248 const KUrl
&url() const;
249 QString
title() const;
250 AtomicString
artist() const;
251 AtomicString
albumArtist() const;
252 AtomicString
composer() const;
253 AtomicString
album() const;
254 AtomicString
genre() const;
255 AtomicString
comment() const;
256 QString
filename() const;
257 QString
directory() const;
258 QString
type() const;
260 int discNumber() const;
265 int sampleRate() const;
266 float score( bool ensureCached
= false ) const;
267 int rating( bool ensureCached
= false ) const; //returns rating * 2, to accommodate .5 ratings
268 int playCount( bool ensureCached
= false ) const;
269 uint
lastPlay( bool ensureCached
= false ) const;
272 const Moodbar
&moodbar_const() const;
274 int filesize() const;
276 int compilation() const;
277 int fileType() const; // returns a value from enum FileType
278 bool exists() const; // true for everything but local files that aren't there
279 PodcastEpisodeBundle
*podcastBundle() const;
280 LastFm::Bundle
*lastFmBundle() const;
281 QString
streamName() const;
282 QString
streamUrl() const;
283 QString
uniqueId() const;
285 QString
prettyTitle() const;
286 QString
veryNiceTitle() const;
287 QString
prettyUrl() const;
288 QString
prettyBitrate() const;
289 QString
prettyLength() const;
290 QString
prettySampleRate( bool shortened
= false ) const;
291 QString
prettyFilesize() const;
292 QString
prettyRating() const;
294 bool safeToSave() { return m_safeToSave
; }
296 QString
getRandomString( int size
, bool numbersOnly
= false );
299 void setUrl( const KUrl
&url
);
300 void setPath( const QString
&path
);
301 void setTitle( const QString
&title
);
302 void setArtist( const AtomicString
&artist
);
303 void setAlbumArtist( const AtomicString
&albumArtist
);
304 void setComposer( const AtomicString
&composer
);
305 void setAlbum( const AtomicString
&album
);
306 void setGenre( const AtomicString
&genre
);
307 void setComment( const AtomicString
&comment
);
308 void setYear( int year
);
309 void setDiscNumber( int discNumber
);
310 void setTrack( int track
);
311 void setBpm( float bpm
);
312 void setLength( int length
);
313 void setBitrate( int bitrate
);
314 void setSampleRate( int sampleRate
);
315 void setScore( float score
);
316 void setRating( int rating
);
317 void setPlayCount( int playcount
);
318 void setLastPlay( uint lastplay
);
319 void setFilesize( int bytes
);
320 // No direct moodbar mutator -- moodbar should not be separated
321 // from the metabundle
323 void updateFilesize();
324 void setFileType( int type
);
325 void setCompilation( int compilation
);
327 void setPodcastBundle( const PodcastEpisodeBundle
&peb
);
328 void setLastFmBundle( const LastFm::Bundle
&last
);
329 void setUniqueId(); //uses database for lookup
330 void setUniqueId( const QString
&id
); //SEE COMMENT in .CPP
331 const TagLib::ByteVector
readUniqueIdHelper( TagLib::FileRef fileref
) const;
332 QString
readUniqueId( TagLib::FileRef
*fileref
= 0 );
333 void scannerAcknowledged() {}
335 public: //static helper functions
336 static QString
prettyBitrate( int );
337 static QString
prettyLength( int, bool showHours
= false ); //must be int, see Unavailable, etc. above
338 static QString
prettyFilesize( int );
339 static QString
prettyRating( int rating
, bool trailingzero
= false );
340 static QString
ratingDescription( int );
341 static QStringList
ratingList();
342 static QString
prettyTime( uint
, bool showHours
= true );
343 static QString
fuzzyTime( int );
344 static QString
veryPrettyTime( int );
345 static QString
zeroPad( uint i
);
346 static QString
prettyTitle( const QString
&filename
);
347 static QStringList
genreList();
350 enum ExtendedTags
{ composerTag
, albumArtistTag
, discNumberTag
, bpmTag
, compilationTag
};
352 /** Called before the tags in \p columns are changed. */
353 virtual void aboutToChange( const Q3ValueList
<int> &columns
);
355 /** Convenience method. */
356 void aboutToChange( int column
);
358 /** Called after the tags in \p columns are changed. */
359 virtual void reactToChanges( const Q3ValueList
<int> &columns
);
361 /** Convenience method. */
362 void reactToChange( int column
);
366 AtomicString m_artist
;
367 AtomicString m_albumArtist
;
368 AtomicString m_composer
;
369 AtomicString m_album
;
370 AtomicString m_comment
;
371 AtomicString m_genre
;
372 QString m_streamName
;
395 bool m_isValidMedia
: 1;
396 bool m_isCompilation
: 1;
397 bool m_notCompilation
: 1;
398 bool m_safeToSave
: 1;
400 QString m_tempSavePath
;
401 QString m_origRenamedSavePath
;
402 QByteArray m_tempSaveDigest
;
403 TagLib::FileRef
* m_saveFileref
;
405 PodcastEpisodeBundle
*m_podcastBundle
;
406 LastFm::Bundle
*m_lastFmBundle
;
408 // The vars below are used to optimize search by storing
409 // the full text to be searched. They are mutable, as they
410 // act like a sort of cache for the const method matchesFast
412 // whether the search text should be rebuilt
413 volatile mutable bool m_isSearchDirty
;
414 // which columns the search string contains
415 mutable ColumnMask m_searchColumns
;
416 // the search string: textualized columns separated by space
417 // note that matchFast searches by words, hence a word cannot span
418 // space-separated columns
419 mutable QString m_searchStr
;
422 static inline QString
prettyGeneric( const QString
&s
, const int i
)
424 return (i
> 0) ? s
.arg( i
) : (i
== Undetermined
) ? "?" : "-";
427 void init( TagLib::AudioProperties
*ap
= 0 );
428 void init( const KFileMetaInfo
& info
);
430 void setExtendedTag( TagLib::File
*file
, int tag
, const QString value
);
432 void loadImagesFromTag( const TagLib::ID3v2::Tag
&tag
, EmbeddedImageList
& images
) const;
437 Q_DECLARE_METATYPE(MetaBundle
)
440 /// for your convenience
441 typedef Q3ValueList
<MetaBundle
> BundleList
;
445 inline bool MetaBundle::operator!=(const MetaBundle
&bundle
) const { return !operator==( bundle
); }
447 inline bool MetaBundle::isEmpty() const { return url().isEmpty(); }
449 inline bool MetaBundle::isValidMedia() const { return m_isValidMedia
; }
451 inline bool MetaBundle::audioPropertiesUndetermined() const
453 return m_bitrate
== Undetermined
|| m_sampleRate
== Undetermined
|| m_length
== Undetermined
;
456 inline void MetaBundle::aboutToChange( const Q3ValueList
<int>& ) { }
457 inline void MetaBundle::aboutToChange( int column
) { aboutToChange( Q3ValueList
<int>() << column
); }
458 inline void MetaBundle::reactToChange( int column
) { reactToChanges( Q3ValueList
<int>() << column
); }
460 inline bool MetaBundle::exists() const { return m_exists
; }
462 inline bool MetaBundle::isFile() const { return url().isLocalFile(); }
463 inline bool MetaBundle::isKioUrl() const { return isKioUrl( url() ); }
464 inline bool MetaBundle::isKioUrl( const KUrl
&url
) { return url
.protocol() != "daap" && url
.protocol() != "cdda" && url
.protocol() != "lastfm"; }
466 inline int MetaBundle::track() const { return m_track
== Undetermined
? 0 : m_track
; }
467 inline int MetaBundle::year() const { return m_year
== Undetermined
? 0 : m_year
; }
468 inline int MetaBundle::length() const { return m_length
> 0 ? m_length
: 0; }
469 inline int MetaBundle::bitrate() const { return m_bitrate
== Undetermined
? 0 : m_bitrate
; }
470 inline int MetaBundle::sampleRate() const { return m_sampleRate
== Undetermined
? 0 : m_sampleRate
; }
471 inline int MetaBundle::filesize() const { return m_filesize
== Undetermined
? 0 : m_filesize
; }
472 inline int MetaBundle::fileType() const { return m_type
; }
474 inline Moodbar
&MetaBundle::moodbar()
476 if( m_moodbar
== 0 ) m_moodbar
= new Moodbar( this );
479 inline const Moodbar
&MetaBundle::moodbar_const() const
481 // Anyone know of a better way to do this?
483 const_cast<MetaBundle
*>(this)->m_moodbar
484 = new Moodbar( const_cast<MetaBundle
*>(this) );
488 inline const KUrl
& MetaBundle::url() const { return m_url
; }
489 inline QString
MetaBundle::filename() const { return url().fileName(); }
490 inline QString
MetaBundle::directory() const
492 return url().isLocalFile() ? url().directory() : url().upUrl().prettyUrl();
494 inline QString
MetaBundle::title() const { return m_title
; }
495 inline AtomicString
MetaBundle::artist() const { return m_artist
; }
496 inline AtomicString
MetaBundle::album() const { return m_album
; }
497 inline AtomicString
MetaBundle::comment() const { return m_comment
; }
498 inline AtomicString
MetaBundle::genre() const { return m_genre
; }
499 inline AtomicString
MetaBundle::composer() const { return m_composer
; }
500 inline AtomicString
MetaBundle::albumArtist() const { return m_albumArtist
; }
501 inline QString
MetaBundle::streamName() const { return m_streamName
; }
502 inline QString
MetaBundle::streamUrl() const { return m_streamUrl
; }
503 inline QString
MetaBundle::uniqueId() const { return m_uniqueId
; }
505 inline int MetaBundle::discNumber() const { return m_discNumber
== Undetermined
? 0 : m_discNumber
; }
506 inline float MetaBundle::bpm() const { return m_bpm
== Undetermined
? 0 : m_bpm
; }
507 inline int MetaBundle::compilation() const
509 if( m_isCompilation
)
510 return CompilationYes
;
511 else if( m_notCompilation
)
512 return CompilationNo
;
514 return CompilationUnknown
;
518 inline QString
MetaBundle::type() const
521 ? filename().mid( filename().lastIndexOf( '.' ) + 1 )
524 inline PodcastEpisodeBundle
*MetaBundle::podcastBundle() const { return m_podcastBundle
; }
525 inline LastFm::Bundle
*MetaBundle::lastFmBundle() const { return m_lastFmBundle
; }
527 inline QString
MetaBundle::prettyUrl() const { return url().prettyUrl(); }
528 inline QString
MetaBundle::prettyBitrate() const { return prettyBitrate( m_bitrate
); }
529 inline QString
MetaBundle::prettyLength() const { return prettyLength( m_length
, true ); }
530 inline QString
MetaBundle::prettyFilesize() const { return prettyFilesize( filesize() ); }
531 inline QString
MetaBundle::prettyRating() const { return prettyRating( rating() ); }
532 inline QString
MetaBundle::prettySampleRate( bool shortened
) const
535 return prettyGeneric( i18nc( "SampleRate", "%1 kHz" ), m_sampleRate
/ 1000 );
537 return prettyGeneric( i18nc( "SampleRate", "%1 Hz" ), m_sampleRate
);
540 inline QString
MetaBundle::zeroPad( uint i
) { return ( i
< 10 ) ? QString( "0%1" ).arg( i
) : QString::number( i
); }
542 inline bool MetaBundle::hasExtendedMetaInformation() const
544 return ( m_type
== mp3
|| m_type
== ogg
||
545 m_type
== mp4
|| m_type
== flac
);