summaryrefslogtreecommitdiffstats
path: root/amarok/src/metabundle.h
blob: 538ac1036a66b39d2552152ea619e5c8f3a02166 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
// Max Howell <max.howell@methylblue.com>, (C) 2004
// Alexandre Pereira de Oliveira <aleprj@gmail.com>, (C) 2005
// Shane King <kde@dontletsstart.com>, (C) 2006
// Peter C. Ndikuwera <pndiku@gmail.com>, (C) 2006
// License: GNU General Public License V2

#ifndef METABUNDLE_H
#define METABUNDLE_H

#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3)
#define PRETTY_TITLE_CACHE
#endif

#include <tqstringlist.h>
#include <kurl.h>    //inline functions
#include <tdelocale.h> //inline functions
#include <taglib/audioproperties.h>
#include "expression.h"
#include "atomicstring.h"
#include "moodbar.h"

#include "amarok_export.h"

class KFileMetaInfo;
class TQDir;
class TQTextStream;
template<class T> class TQValueList;
namespace TagLib {
    class ByteVector;
    class File;
    class FileRef;
    class String;
    namespace ID3v2 {
        class UniqueFileIdentifierFrame;
        class Tag;
    }
    namespace MPEG {
        class File;
    }
}
class PodcastEpisodeBundle;

namespace LastFm {
    class Bundle;
}

/**
 * @class MetaBundle
 * @author Max Howell <max.howell@methylblue.com>
 *
 * If this class doesn't work for you in some way, extend it sensibly :)
 *
 */

class LIBAMAROK_EXPORT MetaBundle
{

public:
    enum Column
    {
        Filename = 0,
        Title,
        Artist,
        AlbumArtist,
        Composer,
        Year,
        Album,
        DiscNumber,
        Track,
        Bpm,
        Genre,
        Comment,
        Directory,
        Type,
        Length,
        Bitrate,
        SampleRate,
        Score,
        Rating,
        PlayCount,
        LastPlayed,
        Mood,
        Filesize,
        NUM_COLUMNS
    };

    class LIBAMAROK_EXPORT EmbeddedImage {
    public:
        EmbeddedImage() {}
        EmbeddedImage( const TagLib::ByteVector& data, const TagLib::String& description );

        const TQCString &hash() const;
        const TQString &description() const { return m_description; }
        bool save( const TQDir& dir ) const;

    private:
        TQByteArray m_data;
        TQString m_description;
        mutable TQCString m_hash;
   };

    typedef TQValueList<EmbeddedImage> EmbeddedImageList;

    /** This is a bit vector for selecting columns. It's very fast to compare
        in matchFast. It might be a good idea to replace the TQValue<int>
        column masks with this eventually. */
    typedef TQ_UINT32 ColumnMask;

    /** Returns the name of the column at \p index as a string -- not i18ned, for internal purposes. */
    static const TQString &exactColumnName( int index );
    /** Returns the name of the column at \p index as a string -- i18ned, for display purposes. */
    static const TQString prettyColumnName( int index );
    /** Returns the index of the column with the not i18ned name \p name. */
    static int columnIndex( const TQString &name );

    // These values are stored on the Database, so, don't change the order. Only append new ones to the end.
    enum FileType { other, mp3, ogg, wma, mp4, flac, ra, rv, rm, rmj, rmvb, asf };

    //for the audioproperties
    static const int Undetermined = -2; /// we haven't yet read the tags
    static const int Irrelevant   = -1; /// not applicable to this stream/media type, eg length for http streams
    static const int Unavailable  =  0; /// cannot be obtained

    // whether file is part of a compilation
    enum Compilation { CompilationNo = 0, CompilationYes = 1, CompilationUnknown = -1 };

    /// Creates an empty MetaBundle
    LIBAMAROK_EXPORT MetaBundle();

    /// Creates a MetaBundle for url, tags will be obtained and set
    LIBAMAROK_EXPORT explicit MetaBundle( const KURL &url,
                                          bool noCache = false,
                                          TagLib::AudioProperties::ReadStyle = TagLib::AudioProperties::Fast,
                                          EmbeddedImageList* images = 0 );

    /** For the StreamProvider */
    LIBAMAROK_EXPORT MetaBundle( const TQString &title,
            const TQString &streamUrl,
            const int bitrate,
            const TQString &genre,
            const TQString &streamName,
            const KURL &url );

    LIBAMAROK_EXPORT MetaBundle( const MetaBundle &bundle );

    ~MetaBundle();

    MetaBundle& operator=( const MetaBundle& bundle );
    bool operator==( const MetaBundle& bundle ) const;
    bool operator!=( const MetaBundle& bundle ) const;

    /** Test for an empty metabundle */
    bool isEmpty() const;

    /** Empty the metabundle */
    void clear();

    /** Is it media that has metadata? Note currently we don't check for an audio mimetype */
    bool isValidMedia() const;

    /** The bundle doesn't yet know its audioProperties */
    bool audioPropertiesUndetermined() const;

    /** The embedded artwork in the file (loaded from file into images variable, unmodified if no images present/loadable) */
    void embeddedImages(EmbeddedImageList &images) const;

    /** If you want Accurate reading say so. If EmbeddedImageList != NULL, embedded art is loaded into it */
    void readTags( TagLib::AudioProperties::ReadStyle = TagLib::AudioProperties::Fast, EmbeddedImageList* images = 0 );

    /** Saves the changes to the file using the transactional algorithm for safety. */
    bool safeSave();

    /** Saves the changes to the file. Returns false on error. */
    bool save( TagLib::FileRef* fileref = 0 );

    /** Saves the MetaBundle's data as XML to a text stream. */
    bool save( TQTextStream &stream, const TQStringList &attributes = TQStringList() ) const;

    /** Returns whether the url referred to is a local file */
    bool isFile() const;

    /** Returns whether the url referred to can be accessed via tdeio slaves */
    bool isKioUrl() const;

    /** Returns whether url can be accessed via tdeio slaves */
    static bool isKioUrl( const KURL &url );

    /** Returns whether composer, disc number and bpm fields are available. */
    bool hasExtendedMetaInformation() const;

    void copyFrom( const MetaBundle& bundle );

    void copyFrom( const PodcastEpisodeBundle &peb );

    /** Returns a string representation of the tag at \p column, in a format suitable for internal purposes.
        For example, for a track 3:24 long, it'll return "204" (seconds).
        This should not be used for displaying the tag to the user. */
    TQString exactText( int column, bool ensureCached = false ) const;

    /** Sets the tag at \p column from a string in the same format as returned by exactText(). */
    void setExactText( int column, const TQString &text );

    /** Returns the tag at \p column in a format suitable for displaying to the user. */
    TQString prettyText( int column ) const;

    /** Returns whether the bundle matches \p expression.
        This is fast and doesn't take advanced syntax into account,
        and should only be used when it is certain none is present.
        The tags in \p columns are checked for matches.
        @see ExpressionParser::isAdvancedExpression() */
    bool matchesSimpleExpression( const TQString &expression, const TQValueList<int> &columns ) const;

    /** A faster version of the above, that pre-caches all the data to be
        searched in a single string, to avoid re-building integer and lower
        case strings over and over. It is designed to be called from a
        playlist search *only* -- it is not entirely thread-safe for efficiency,
        although it's highly unlikely to crash. Consider this the beginning
        of a real super-efficient index (e.g. suffix tree).
        \p terms is a list of lower-case words. */
    bool matchesFast(const TQStringList &terms, ColumnMask columns) const;

    /** Returns whether the bundle matches \p expression.
        This takes advanced syntax into account, and is slightly slower than matchesSimpleExpression().
        The tags in \p defaultColumns are checked for matches where the expression doesn't specify any manually. */
    bool matchesExpression( const TQString &expression, const TQValueList<int> &defaultColumns ) const;

    /** Returns whether the bundle matches the pre-parsed expression \p parsedData.
        The tags in \p defaultColumns are checked for matches where the expression doesn't specify any manually.
        @see ExpressionParser */
    bool matchesParsedExpression( const ParsedExpression &parsedData, const TQValueList<int> &defaultColumns ) const;

    /** PlaylistItem reimplements this so it can be informed of moodbar
        data events without having to use signals */
    virtual void moodbarJobEvent( int newState )
        { (void) newState; }

public:
    /**
     * A class to load MetaBundles from XML.
     * #include "xmlloader.h"
     */
    class XmlLoader;

public: //accessors
    const KURL &url()               const;
    TQString      title()     const;
    AtomicString artist()    const;
    AtomicString albumArtist() const;
    AtomicString composer()  const;
    AtomicString album()     const;
    AtomicString genre()     const;
    AtomicString comment()   const;
    TQString      filename()  const;
    TQString      directory() const;
    TQString      type()      const;
    int     year()        const;
    int     discNumber()  const;
    int     track()       const;
    float   bpm()         const;
    int     length()      const;
    int     bitrate()     const;
    int     sampleRate()  const;
    float   score( bool ensureCached = false )      const;
    int     rating( bool ensureCached = false )     const; //returns rating * 2, to accommodate .5 ratings
    int     playCount( bool ensureCached = false )  const;
    uint    lastPlay( bool ensureCached = false )   const;

    Moodbar       &moodbar();
    const Moodbar &moodbar_const() const;

    int     filesize()    const;

    int compilation() const;
    int fileType() const;  // returns a value from enum FileType
    bool exists() const; // true for everything but local files that aren't there
    PodcastEpisodeBundle *podcastBundle() const;
    LastFm::Bundle *lastFmBundle() const;
    TQString streamName() const;
    TQString streamUrl()  const;
    TQString uniqueId() const;

    TQString prettyTitle() const;
    TQString veryNiceTitle() const;
    TQString prettyURL() const;
    TQString prettyBitrate() const;
    TQString prettyLength() const;
    TQString prettySampleRate( bool shortened = false ) const;
    TQString prettyFilesize() const;
    TQString prettyRating() const;

    bool safeToSave() { return m_safeToSave; }

    TQString getRandomString( int size, bool numbersOnly = false );

public: //modifiers
    void setUrl( const KURL &url );
    void setPath( const TQString &path );
    void setTitle( const TQString &title );
    void setArtist( const AtomicString &artist );
    void setAlbumArtist( const AtomicString &albumArtist );
    void setComposer( const AtomicString &composer );
    void setAlbum( const AtomicString &album );
    void setGenre( const AtomicString &genre );
    void setComment( const AtomicString &comment );
    void setYear( int year );
    void setDiscNumber( int discNumber );
    void setTrack( int track );
    void setBpm( float bpm );
    void setLength( int length );
    void setBitrate( int bitrate );
    void setSampleRate( int sampleRate );
    void setScore( float score );
    void setRating( int rating );
    void setPlayCount( int playcount );
    void setLastPlay( uint lastplay );
    void setFilesize( int bytes );
    // No direct moodbar mutator -- moodbar should not be separated
    // from the metabundle

    void updateFilesize();
    void setFileType( int type );
    void setCompilation( int compilation );
    bool checkExists();
    void setPodcastBundle( const PodcastEpisodeBundle &peb );
    void setLastFmBundle( const LastFm::Bundle &last );
    void setUniqueId(); //uses database for lookup
    void setUniqueId( const TQString &id ); //SEE COMMENT in .CPP
    const TagLib::ByteVector readUniqueIdHelper( TagLib::FileRef fileref ) const;
    TQString readUniqueId( TagLib::FileRef *fileref = 0  );
    void scannerAcknowledged() {}

    void detach(); // for being able to apply TQDeepCopy<>

public: //static helper functions
    static TQString prettyBitrate( int );
    static TQString prettyLength( int, bool showHours = false ); //must be int, see Unavailable, etc. above
    static TQString prettyFilesize( int );
    static TQString prettyRating( int rating, bool trailingzero = false );
    static TQString ratingDescription( int );
    static TQStringList ratingList();
    static TQString prettyTime( uint, bool showHours = true );
    static TQString fuzzyTime( int );
    static TQString veryPrettyTime( int );
    static TQString zeroPad( uint i );
    static TQString prettyTitle( const TQString &filename );
    static TQStringList genreList();

protected:
    enum ExtendedTags { composerTag, albumArtistTag, discNumberTag, bpmTag, compilationTag };

    /** Called before the tags in \p columns are changed. */
    virtual void aboutToChange( const TQValueList<int> &columns );

    /** Convenience method. */
    void aboutToChange( int column );

    /** Called after the tags in \p columns are changed. */
    virtual void reactToChanges( const TQValueList<int> &columns );

    /** Convenience method. */
    void reactToChange( int column );

    KURL m_url;
    TQString m_title;
    AtomicString m_artist;
    AtomicString m_albumArtist;
    AtomicString m_composer;
    AtomicString m_album;
    AtomicString m_comment;
    AtomicString m_genre;
    TQString m_streamName;
    TQString m_streamUrl;
    TQString m_uniqueId;

    int m_year;
    int m_discNumber;
    int m_track;
    float m_bpm;
    int m_bitrate;
    int m_length;
    int m_sampleRate;

    float m_score;
    int m_rating;
    int m_playCount;
    uint m_lastPlay;
    int m_filesize;

    Moodbar *m_moodbar;

    int m_type;

    bool m_exists: 1;
    bool m_isValidMedia: 1;
    bool m_isCompilation: 1;
    bool m_notCompilation: 1;
    bool m_safeToSave: 1;
    int m_waitingOnKIO;
    TQString m_tempSavePath;
    TQString m_origRenamedSavePath;
    TQCString m_tempSaveDigest;
    TagLib::FileRef* m_saveFileref;

    PodcastEpisodeBundle *m_podcastBundle;
    LastFm::Bundle *m_lastFmBundle;

    // The vars below are used to optimize search by storing
    // the full text to be searched. They are mutable, as they
    // act like a sort of cache for the const method matchesFast

    // whether the search text should be rebuilt
    volatile mutable bool m_isSearchDirty;
    // which columns the search string contains
    mutable ColumnMask m_searchColumns;
    // the search string: textualized columns separated by space
    // note that matchFast searches by words, hence a word cannot span
    // space-separated columns
    mutable TQString m_searchStr;
private:

    static inline TQString prettyGeneric( const TQString &s, const int i )
    {
        return (i > 0) ? s.arg( i ) : (i == Undetermined) ? "?" : "-";
    }

    void init( TagLib::AudioProperties *ap = 0 );
    void init( const KFileMetaInfo& info );

    void setExtendedTag( TagLib::File *file, int tag, const TQString value );

    void loadImagesFromTag( const TagLib::ID3v2::Tag &tag, EmbeddedImageList& images ) const;

    int getRand();
};

/// for your convenience
typedef TQValueList<MetaBundle> BundleList;



inline bool MetaBundle::operator!=(const MetaBundle &bundle) const { return !operator==( bundle ); }

inline bool MetaBundle::isEmpty() const { return url().isEmpty(); }

inline bool MetaBundle::isValidMedia() const { return m_isValidMedia; }

inline bool MetaBundle::audioPropertiesUndetermined() const
{
    return m_bitrate == Undetermined || m_sampleRate == Undetermined || m_length == Undetermined;
}

inline void MetaBundle::aboutToChange( const TQValueList<int>& ) { }
inline void MetaBundle::aboutToChange( int column ) { aboutToChange( TQValueList<int>() << column ); }
inline void MetaBundle::reactToChange( int column ) { reactToChanges( TQValueList<int>() << column ); }

inline bool MetaBundle::exists() const { return m_exists; }

inline bool MetaBundle::isFile() const { return url().isLocalFile(); }
inline bool MetaBundle::isKioUrl() const { return isKioUrl( url() ); }
inline bool MetaBundle::isKioUrl( const KURL &url ) { return url.protocol() != "daap" && url.protocol() != "cdda" && url.protocol() != "lastfm"; }

inline int MetaBundle::track()      const { return m_track == Undetermined ? 0 : m_track; }
inline int MetaBundle::year()       const { return m_year  == Undetermined ? 0 : m_year; }
inline int MetaBundle::length()     const { return m_length > 0 ? m_length : 0; }
inline int MetaBundle::bitrate()    const { return m_bitrate == Undetermined ? 0 : m_bitrate; }
inline int MetaBundle::sampleRate() const { return m_sampleRate == Undetermined ? 0 : m_sampleRate; }
inline int MetaBundle::filesize()   const { return m_filesize == Undetermined ? 0 : m_filesize; }
inline int MetaBundle::fileType()   const { return m_type; }

inline Moodbar &MetaBundle::moodbar()
{
  if( m_moodbar == 0 ) m_moodbar = new Moodbar( this );
  return *m_moodbar;
}
inline const Moodbar &MetaBundle::moodbar_const() const
{
  // Anyone know of a better way to do this?
  if( m_moodbar == 0 )
    const_cast<MetaBundle*>(this)->m_moodbar
      = new Moodbar( const_cast<MetaBundle*>(this) );
  return *m_moodbar;
}

inline const KURL&     MetaBundle::url()        const { return m_url; }
inline TQString  MetaBundle::filename()   const { return url().fileName(); }
inline TQString  MetaBundle::directory()  const
{
    return url().isLocalFile() ? url().directory() : url().upURL().prettyURL();
}
inline TQString MetaBundle::title()            const { return m_title; }
inline AtomicString MetaBundle::artist()      const { return m_artist; }
inline AtomicString MetaBundle::album()       const { return m_album; }
inline AtomicString MetaBundle::comment()     const { return m_comment; }
inline AtomicString MetaBundle::genre()       const { return m_genre; }
inline AtomicString MetaBundle::composer()    const { return m_composer; }
inline AtomicString MetaBundle::albumArtist() const { return m_albumArtist; }
inline TQString MetaBundle::streamName() const { return m_streamName; }
inline TQString MetaBundle::streamUrl()  const { return m_streamUrl; }
inline TQString MetaBundle::uniqueId()   const { return m_uniqueId; }

inline int MetaBundle::discNumber() const { return m_discNumber == Undetermined ? 0 : m_discNumber; }
inline float MetaBundle::bpm() const { return m_bpm == Undetermined ? 0 : m_bpm; }
inline int MetaBundle::compilation() const
{
    if( m_isCompilation )
        return CompilationYes;
    else if( m_notCompilation )
        return CompilationNo;
    else
        return CompilationUnknown;
}


inline TQString MetaBundle::type() const
{
    return isFile()
           ? filename().mid( filename().findRev( '.' ) + 1 )
           : i18n( "Stream" );
}
inline PodcastEpisodeBundle *MetaBundle::podcastBundle() const { return m_podcastBundle; }
inline LastFm::Bundle *MetaBundle::lastFmBundle() const { return m_lastFmBundle; }

inline TQString MetaBundle::prettyURL() const { return url().prettyURL(); }
inline TQString MetaBundle::prettyBitrate() const { return prettyBitrate( m_bitrate ); }
inline TQString MetaBundle::prettyLength() const { return prettyLength( m_length, true ); }
inline TQString MetaBundle::prettyFilesize() const { return prettyFilesize( filesize() ); }
inline TQString MetaBundle::prettyRating() const { return prettyRating( rating() ); }
inline TQString MetaBundle::prettySampleRate( bool shortened ) const
    {
        if ( shortened )
            return prettyGeneric( i18n( "SampleRate", "%1 kHz" ), m_sampleRate / 1000 );
        else
            return prettyGeneric( i18n( "SampleRate", "%1 Hz" ), m_sampleRate );
    }

inline TQString MetaBundle::zeroPad( uint i ) { return ( i < 10 ) ? TQString( "0%1" ).arg( i ) : TQString::number( i ); }

inline bool MetaBundle::hasExtendedMetaInformation() const
{
    return ( m_type == mp3 || m_type == ogg ||
             m_type== mp4  || m_type == flac );
}


#endif