Lots of work on album grouping in the playlist. Now handles ragging around stuff...
[amarok.git] / src / metabundle.h
blobde81a9d94a1f2815f6dcf4c3aa36677591d29f5e
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
7 #ifndef METABUNDLE_H
8 #define METABUNDLE_H
10 #if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3)
11 #define PRETTY_TITLE_CACHE
12 #endif
14 #include <QStringList>
15 //Added by qt3to4:
16 #include <Q3ValueList>
17 #include <QByteArray>
18 #include <kurl.h> //inline functions
19 #include <klocale.h> //inline functions
20 #include <audioproperties.h>
21 #include "expression.h"
22 #include "atomicstring.h"
23 #include "moodbar.h"
25 #include "amarok_export.h"
27 class KFileMetaInfo;
28 class QDir;
29 class QTextStream;
30 template<class T> class Q3ValueList;
31 namespace TagLib {
32 class ByteVector;
33 class File;
34 class FileRef;
35 class String;
36 namespace ID3v2 {
37 class UniqueFileIdentifierFrame;
38 class Tag;
40 namespace MPEG {
41 class File;
44 class PodcastEpisodeBundle;
46 namespace LastFm {
47 class Bundle;
50 /**
51 * @class MetaBundle
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
61 public:
62 enum Column
64 Filename = 0,
65 Title,
66 Artist,
67 AlbumArtist,
68 Composer,
69 Year,
70 Album,
71 DiscNumber,
72 Track,
73 Bpm,
74 Genre,
75 Comment,
76 Directory,
77 Type,
78 Length,
79 Bitrate,
80 SampleRate,
81 Score,
82 Rating,
83 PlayCount,
84 LastPlayed,
85 Mood,
86 Filesize,
87 NUM_COLUMNS
90 class AMAROK_EXPORT EmbeddedImage {
91 public:
92 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;
99 private:
100 QByteArray m_data;
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,
142 const int bitrate,
143 const QString &genre,
144 const QString &streamName,
145 const KUrl &url );
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 */
159 void clear();
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. */
174 bool safeSave();
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 */
183 bool isFile() const;
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 )
238 { (void) newState; }
240 public:
242 * A class to load MetaBundles from XML.
243 * #include "xmlloader.h"
245 class XmlLoader;
247 public: //accessors
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;
259 int year() const;
260 int discNumber() const;
261 int track() const;
262 float bpm() const;
263 int length() const;
264 int bitrate() 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;
271 Moodbar &moodbar();
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 );
298 public: //modifiers
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 );
326 bool checkExists();
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();
349 protected:
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 );
364 KUrl m_url;
365 QString m_title;
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;
373 QString m_streamUrl;
374 QString m_uniqueId;
376 int m_year;
377 int m_discNumber;
378 int m_track;
379 float m_bpm;
380 int m_bitrate;
381 int m_length;
382 int m_sampleRate;
384 float m_score;
385 int m_rating;
386 int m_playCount;
387 uint m_lastPlay;
388 int m_filesize;
390 Moodbar *m_moodbar;
392 int m_type;
394 bool m_exists: 1;
395 bool m_isValidMedia: 1;
396 bool m_isCompilation: 1;
397 bool m_notCompilation: 1;
398 bool m_safeToSave: 1;
399 int m_waitingOnKIO;
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;
420 private:
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;
434 int getRand();
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 );
477 return *m_moodbar;
479 inline const Moodbar &MetaBundle::moodbar_const() const
481 // Anyone know of a better way to do this?
482 if( m_moodbar == 0 )
483 const_cast<MetaBundle*>(this)->m_moodbar
484 = new Moodbar( const_cast<MetaBundle*>(this) );
485 return *m_moodbar;
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;
513 else
514 return CompilationUnknown;
518 inline QString MetaBundle::type() const
520 return isFile()
521 ? filename().mid( filename().lastIndexOf( '.' ) + 1 )
522 : i18n( "Stream" );
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
534 if ( shortened )
535 return prettyGeneric( i18nc( "SampleRate", "%1 kHz" ), m_sampleRate / 1000 );
536 else
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 );
549 #endif