1 // Max Howell <max.howell@methylblue.com>, (C) 2004
2 // Alexandre Pereira de Oliveira <aleprj@gmail.com>, (C) 2005, 2006
3 // Gábor Lehel <illissius@gmail.com>, (C) 2005, 2006
4 // Shane King <kde@dontletsstart.com>, (C) 2006
5 // Peter C. Ndikuwera <pndiku@gmail.com>, (C) 2006
6 // License: GNU General Public License V2
9 #define DEBUG_PREFIX "MetaBundle"
16 #include <sys/types.h>
20 #include "amarokconfig.h"
22 #include "collectiondb.h"
23 #include <kapplication.h>
24 #include <kfilemetainfo.h>
25 #include <kio/global.h>
27 #include <kio/jobclasses.h>
28 #include <kio/netaccess.h>
31 #include <QFile> //decodePath()
33 #include <Q3ValueList>
35 #include <attachedpictureframe.h>
37 #include <id3v1genres.h> //used to load genre list
47 #include <oggflacfile.h>
48 #include <vorbisfile.h>
50 #include <textidentificationframe.h>
51 #include <uniquefileidentifierframe.h>
52 #include <xiphcomment.h>
54 #include "config-amarok.h"
56 #include "metadata/mp4/mp4file.h"
57 #include "metadata/mp4/mp4tag.h"
59 #include "metadata/m4a/mp4file.h"
60 #include "metadata/m4a/mp4itunestag.h"
64 #include "metabundle.h"
65 #include "podcastbundle.h"
68 MetaBundle::EmbeddedImage::EmbeddedImage( const TagLib::ByteVector
& data
, const TagLib::String
& description
)
69 : m_description( TStringToQString( description
) )
71 m_data
= QByteArray( data
.data(), data
.size() );
74 const QByteArray
&MetaBundle::EmbeddedImage::hash() const
76 if( m_hash
.isEmpty() ) {
77 m_hash
= KMD5( m_data
).hexDigest();
82 bool MetaBundle::EmbeddedImage::save( const QDir
& dir
) const
84 QFile
file( dir
.filePath( hash() ) );
86 if( file
.open( QIODevice::WriteOnly
| QIODevice::Unbuffered
) ) {
87 const Q_LONG s
= file
.write( m_data
.data(), m_data
.size() );
88 if( s
>= 0 && Q_LONG( s
) == m_data
.size() ) {
89 debug() << "EmbeddedImage::save " << file
.fileName();
94 debug() << "EmbeddedImage::save failed! " << file
.fileName();
98 /// These are untranslated and used for storing/retrieving XML playlist
99 const QString
&MetaBundle::exactColumnName( int c
) //static
101 // construct static qstrings to avoid constructing them all the time
102 static QString columns
[] = {
103 "Filename", "Title", "Artist", "AlbumArtist", "Composer", "Year", "Album", "DiscNumber", "Track", "BPM", "Genre", "Comment",
104 "Directory", "Type", "Length", "Bitrate", "SampleRate", "Score", "Rating", "PlayCount", "LastPlayed",
105 "Mood", "Filesize" };
106 static QString
error( "ERROR" );
108 if ( c
>= 0 && c
< NUM_COLUMNS
)
114 const QString
MetaBundle::prettyColumnName( int index
) //static
118 case Filename
: return i18n( "Filename" );
119 case Title
: return i18n( "Title" );
120 case Artist
: return i18n( "Artist" );
121 case AlbumArtist
:return i18n( "Album Artist");
122 case Composer
: return i18n( "Composer" );
123 case Year
: return i18n( "Year" );
124 case Album
: return i18n( "Album" );
125 case DiscNumber
: return i18n( "Disc Number" );
126 case Track
: return i18n( "Track" );
127 case Bpm
: return i18n( "BPM" );
128 case Genre
: return i18n( "Genre" );
129 case Comment
: return i18n( "Comment" );
130 case Directory
: return i18n( "Directory" );
131 case Type
: return i18n( "Type" );
132 case Length
: return i18n( "Length" );
133 case Bitrate
: return i18n( "Bitrate" );
134 case SampleRate
: return i18n( "Sample Rate" );
135 case Score
: return i18n( "Score" );
136 case Rating
: return i18n( "Rating" );
137 case PlayCount
: return i18n( "Play Count" );
138 case LastPlayed
: return i18nc( "Column name", "Last Played" );
139 case Mood
: return i18n( "Mood" );
140 case Filesize
: return i18n( "File Size" );
142 return "This is a bug.";
145 int MetaBundle::columnIndex( const QString
&name
)
147 for( int i
= 0; i
< NUM_COLUMNS
; ++i
)
148 if( exactColumnName( i
).toLower() == name
.toLower() )
153 MetaBundle::MetaBundle()
154 : m_uniqueId( QString() )
155 , m_year( Undetermined
)
156 , m_discNumber( Undetermined
)
157 , m_track( Undetermined
)
158 , m_bpm( Undetermined
)
159 , m_bitrate( Undetermined
)
160 , m_length( Undetermined
)
161 , m_sampleRate( Undetermined
)
162 , m_score( Undetermined
)
163 , m_rating( Undetermined
)
164 , m_playCount( Undetermined
)
165 , m_lastPlay( abs( Undetermined
) )
166 , m_filesize( Undetermined
)
170 , m_isValidMedia( true )
171 , m_isCompilation( false )
172 , m_notCompilation( false )
173 , m_safeToSave( false )
174 , m_waitingOnKIO( 0 )
175 , m_tempSavePath( QString() )
176 , m_origRenamedSavePath( QString() )
177 , m_tempSaveDigest( 0 )
179 , m_podcastBundle( 0 )
180 , m_lastFmBundle( 0 )
181 , m_isSearchDirty(true)
186 MetaBundle::MetaBundle( const KUrl
&url
, bool noCache
, TagLib::AudioProperties::ReadStyle readStyle
, EmbeddedImageList
* images
)
188 , m_uniqueId( QString() )
189 , m_year( Undetermined
)
190 , m_discNumber( Undetermined
)
191 , m_track( Undetermined
)
192 , m_bpm( Undetermined
)
193 , m_bitrate( Undetermined
)
194 , m_length( Undetermined
)
195 , m_sampleRate( Undetermined
)
196 , m_score( Undetermined
)
197 , m_rating( Undetermined
)
198 , m_playCount( Undetermined
)
199 , m_lastPlay( abs( Undetermined
) )
200 , m_filesize( Undetermined
)
203 , m_exists( isFile() && QFile::exists( url
.path() ) )
204 , m_isValidMedia( false )
205 , m_isCompilation( false )
206 , m_notCompilation( false )
207 , m_safeToSave( false )
208 , m_waitingOnKIO( 0 )
209 , m_tempSavePath( QString() )
210 , m_origRenamedSavePath( QString() )
211 , m_tempSaveDigest( 0 )
213 , m_podcastBundle( 0 )
214 , m_lastFmBundle( 0 )
215 , m_isSearchDirty(true)
220 m_isValidMedia
= CollectionDB::instance()->bundleForUrl( this );
222 if ( !isValidMedia() || ( !m_podcastBundle
&& m_length
<= 0 ) )
223 readTags( readStyle
, images
);
227 // if it's a podcast we might get some info this way
228 CollectionDB::instance()->bundleForUrl( this );
229 m_bitrate
= m_length
= m_sampleRate
= Unavailable
;
233 //StreamProvider ctor
234 MetaBundle::MetaBundle( const QString
& title
,
235 const QString
& streamUrl
,
237 const QString
& genre
,
238 const QString
& streamName
,
242 , m_streamName( streamName
)
243 , m_streamUrl ( streamUrl
)
244 , m_uniqueId( QString() )
248 , m_bpm( Undetermined
)
249 , m_bitrate( bitrate
)
250 , m_length( Irrelevant
)
251 , m_sampleRate( Unavailable
)
252 , m_score( Undetermined
)
253 , m_rating( Undetermined
)
254 , m_playCount( Undetermined
)
255 , m_lastPlay( abs( Undetermined
) )
256 , m_filesize( Undetermined
)
260 , m_isValidMedia( false )
261 , m_isCompilation( false )
262 , m_notCompilation( false )
263 , m_safeToSave( false )
264 , m_waitingOnKIO( 0 )
265 , m_tempSavePath( QString() )
266 , m_origRenamedSavePath( QString() )
267 , m_tempSaveDigest( 0 )
269 , m_podcastBundle( 0 )
270 , m_lastFmBundle( 0 )
271 , m_isSearchDirty(true)
273 if( title
.count( '-' ) )
275 m_title
= title
.section( '-', 1, 1 ).trimmed();
276 m_artist
= title
.section( '-', 0, 0 ).trimmed();
281 m_artist
= streamName
; //which is sort of correct..
285 MetaBundle::MetaBundle( const MetaBundle
&bundle
)
291 MetaBundle::~MetaBundle()
293 delete m_podcastBundle
;
294 delete m_lastFmBundle
;
301 MetaBundle::operator=( const MetaBundle
& bundle
)
303 m_url
= bundle
.m_url
;
304 m_title
= bundle
.m_title
;
305 m_artist
= bundle
.m_artist
;
306 m_albumArtist
= bundle
.m_albumArtist
;
307 m_composer
= bundle
.m_composer
;
308 m_album
= bundle
.m_album
;
309 m_comment
= bundle
.m_comment
;
310 m_genre
= bundle
.m_genre
;
311 m_streamName
= bundle
.m_streamName
;
312 m_streamUrl
= bundle
.m_streamUrl
;
313 m_uniqueId
= bundle
.m_uniqueId
;
314 m_year
= bundle
.m_year
;
315 m_discNumber
= bundle
.m_discNumber
;
316 m_track
= bundle
.m_track
;
317 m_bpm
= bundle
.m_bpm
;
318 m_bitrate
= bundle
.m_bitrate
;
319 m_length
= bundle
.m_length
;
320 m_sampleRate
= bundle
.m_sampleRate
;
321 m_score
= bundle
.m_score
;
322 m_rating
= bundle
.m_rating
;
323 m_playCount
= bundle
.m_playCount
;
324 m_lastPlay
= bundle
.m_lastPlay
;
325 m_filesize
= bundle
.m_filesize
;
326 m_type
= bundle
.m_type
;
327 m_exists
= bundle
.m_exists
;
328 m_isValidMedia
= bundle
.m_isValidMedia
;
329 m_isCompilation
= bundle
.m_isCompilation
;
330 m_notCompilation
= bundle
.m_notCompilation
;
331 m_safeToSave
= bundle
.m_safeToSave
;
332 m_waitingOnKIO
= bundle
.m_waitingOnKIO
;
333 m_tempSavePath
= bundle
.m_tempSavePath
;
334 m_origRenamedSavePath
= bundle
.m_origRenamedSavePath
;
335 m_tempSaveDigest
= bundle
.m_tempSaveDigest
;
336 m_saveFileref
= bundle
.m_saveFileref
;
338 if( bundle
.m_moodbar
!= 0)
341 m_moodbar
= new Moodbar( this );
342 *m_moodbar
= *bundle
.m_moodbar
;
346 // If m_moodbar != 0, it's initialized for a reason
347 // Deleting it makes the PrettySlider code more ugly,
348 // since it'd have to reconnect the jobEvent() signal.
354 // delete m_podcastBundle; why does this crash Amarok? apparently m_podcastBundle isn't always initialized.
356 if( bundle
.m_podcastBundle
)
357 setPodcastBundle( *bundle
.m_podcastBundle
);
359 // delete m_lastFmBundle; same as above
361 if( bundle
.m_lastFmBundle
)
362 setLastFmBundle( *bundle
.m_lastFmBundle
);
364 m_isSearchDirty
= true;
370 MetaBundle::checkExists()
372 debug() << "MetaBundle path is " << url().url();
373 m_exists
= !isFile() || QFile::exists( url().path() );
379 MetaBundle::operator==( const MetaBundle
& bundle
) const
381 return uniqueId() == bundle
.uniqueId() && //first, since if using IDs will return faster
382 artist() == bundle
.artist() &&
383 albumArtist() == bundle
.albumArtist() &&
384 title() == bundle
.title() &&
385 composer() == bundle
.composer() &&
386 album() == bundle
.album() &&
387 year() == bundle
.year() &&
388 comment() == bundle
.comment() &&
389 genre() == bundle
.genre() &&
390 track() == bundle
.track() &&
391 discNumber() == bundle
.discNumber() &&
392 bpm() == bundle
.bpm() &&
393 length() == bundle
.length() &&
394 bitrate() == bundle
.bitrate() &&
395 sampleRate() == bundle
.sampleRate();
396 // FIXME: check for size equality?
402 *this = MetaBundle();
406 MetaBundle::init( TagLib::AudioProperties
*ap
)
410 m_bitrate
= ap
->bitrate();
411 m_length
= ap
->length();
412 m_sampleRate
= ap
->sampleRate();
415 m_bitrate
= m_length
= m_sampleRate
= Undetermined
;
419 MetaBundle::init( const KFileMetaInfo
& info
)
423 m_artist
= info
.item( "Artist" ).value().toString();
424 m_album
= info
.item( "Album" ).value().toString();
425 m_comment
= info
.item( "Comment" ).value().toString();
426 m_genre
= info
.item( "Genre" ).value().toString();
427 m_year
= info
.item( "Year" ).value().toString().toInt();
428 m_track
= info
.item( "Track" ).value().toString().toInt();
429 m_bitrate
= info
.item( "Bitrate" ).value().toInt();
430 m_length
= info
.item( "Length" ).value().toInt();
431 m_sampleRate
= info
.item( "Sample Rate" ).value().toInt();
433 // For title, check if it is valid. If not, use prettyTitle.
435 const KFileMetaInfoItem itemtitle
= info
.item( "Title" );
436 m_title
= itemtitle
.isValid() ? itemtitle
.value().toString() : prettyTitle( m_url
.fileName() );
438 const KFileMetaInfoItem itemid
= info
.item( "Unique ID" );
439 m_uniqueId
= itemid
.isValid() ? itemid
.value().toString() : QString::null
;
441 // because whoever designed KMetaInfoItem is a donkey
442 #define makeSane( x ) if( x.string() == "---" ) x = null;
444 makeSane( m_artist
);
446 makeSane( m_comment
);
448 if ( m_title
== "---" ) m_title
.clear();
451 m_isValidMedia
= true;
455 m_bitrate
= m_length
= m_sampleRate
= m_filesize
= Undetermined
;
456 m_isValidMedia
= false;
461 MetaBundle::embeddedImages( MetaBundle::EmbeddedImageList
& images
) const
465 TagLib::FileRef fileref
= TagLib::FileRef( QFile::encodeName( url().path() ), false );
466 if ( !fileref
.isNull() ) {
467 if ( TagLib::MPEG::File
*file
= dynamic_cast<TagLib::MPEG::File
*>( fileref
.file() ) ) {
468 if ( file
->ID3v2Tag() )
469 loadImagesFromTag( *file
->ID3v2Tag(), images
);
470 } else if ( TagLib::FLAC::File
*file
= dynamic_cast<TagLib::FLAC::File
*>( fileref
.file() ) ) {
471 if ( file
->ID3v2Tag() )
472 loadImagesFromTag( *file
->ID3v2Tag(), images
);
473 } else if ( TagLib::MP4::File
*file
= dynamic_cast<TagLib::MP4::File
*>( fileref
.file() ) ) {
474 TagLib::MP4::Tag
*mp4tag
= dynamic_cast<TagLib::MP4::Tag
*>( file
->tag() );
475 if( mp4tag
&& mp4tag
->cover().size() ) {
476 images
.push_back( EmbeddedImage( mp4tag
->cover(), "" ) );
484 MetaBundle::readTags( TagLib::AudioProperties::ReadStyle readStyle
, EmbeddedImageList
* images
)
489 const QString path
= url().path();
491 TagLib::FileRef fileref
;
492 TagLib::Tag
*tag
= 0;
493 fileref
= TagLib::FileRef( QFile::encodeName( path
), true, readStyle
);
495 if( !fileref
.isNull() )
497 setUniqueId( readUniqueId( &fileref
) );
498 m_filesize
= QFile( path
).size();
503 #define strip( x ) TStringToQString( x ).trimmed()
504 setTitle( strip( tag
->title() ) );
505 setArtist( strip( tag
->artist() ) );
506 setAlbum( strip( tag
->album() ) );
507 setComment( strip( tag
->comment() ) );
508 setGenre( strip( tag
->genre() ) );
509 setYear( tag
->year() );
510 setTrack( tag
->track() );
513 m_isValidMedia
= true;
517 /* As mpeg implementation on TagLib uses a Tag class that's not defined on the headers,
518 we have to cast the files, not the tags! */
522 if ( TagLib::MPEG::File
*file
= dynamic_cast<TagLib::MPEG::File
*>( fileref
.file() ) )
525 if ( file
->ID3v2Tag() )
527 if ( !file
->ID3v2Tag()->frameListMap()["TPOS"].isEmpty() )
528 disc
= TStringToQString( file
->ID3v2Tag()->frameListMap()["TPOS"].front()->toString() ).trimmed();
530 if ( !file
->ID3v2Tag()->frameListMap()["TBPM"].isEmpty() )
531 setBpm( TStringToQString( file
->ID3v2Tag()->frameListMap()["TBPM"].front()->toString() ).trimmed().toFloat() );
533 if ( !file
->ID3v2Tag()->frameListMap()["TCOM"].isEmpty() )
534 setComposer( TStringToQString( file
->ID3v2Tag()->frameListMap()["TCOM"].front()->toString() ).trimmed() );
536 if ( !file
->ID3v2Tag()->frameListMap()["TPE2"].isEmpty() ) // non-standard: Apple, Microsoft
537 setAlbumArtist( TStringToQString( file
->ID3v2Tag()->frameListMap()["TPE2"].front()->toString() ).trimmed() );
539 if ( !file
->ID3v2Tag()->frameListMap()["TCMP"].isEmpty() )
540 compilation
= TStringToQString( file
->ID3v2Tag()->frameListMap()["TCMP"].front()->toString() ).trimmed();
543 loadImagesFromTag( *file
->ID3v2Tag(), *images
);
547 else if ( TagLib::Ogg::Vorbis::File
*file
= dynamic_cast<TagLib::Ogg::Vorbis::File
*>( fileref
.file() ) )
552 if ( !file
->tag()->fieldListMap()[ "COMPOSER" ].isEmpty() )
553 setComposer( TStringToQString( file
->tag()->fieldListMap()["COMPOSER"].front() ).trimmed() );
555 if ( !file
->tag()->fieldListMap()[ "BPM" ].isEmpty() )
556 setBpm( TStringToQString( file
->tag()->fieldListMap()["BPM"].front() ).trimmed().toFloat() );
558 if ( !file
->tag()->fieldListMap()[ "DISCNUMBER" ].isEmpty() )
559 disc
= TStringToQString( file
->tag()->fieldListMap()["DISCNUMBER"].front() ).trimmed();
561 if ( !file
->tag()->fieldListMap()[ "COMPILATION" ].isEmpty() )
562 compilation
= TStringToQString( file
->tag()->fieldListMap()["COMPILATION"].front() ).trimmed();
565 else if ( TagLib::FLAC::File
*file
= dynamic_cast<TagLib::FLAC::File
*>( fileref
.file() ) )
568 if ( file
->xiphComment() )
570 if ( !file
->xiphComment()->fieldListMap()[ "COMPOSER" ].isEmpty() )
571 setComposer( TStringToQString( file
->xiphComment()->fieldListMap()["COMPOSER"].front() ).trimmed() );
573 if ( !file
->xiphComment()->fieldListMap()[ "BPM" ].isEmpty() )
574 setBpm( TStringToQString( file
->xiphComment()->fieldListMap()["BPM"].front() ).trimmed().toFloat() );
576 if ( !file
->xiphComment()->fieldListMap()[ "DISCNUMBER" ].isEmpty() )
577 disc
= TStringToQString( file
->xiphComment()->fieldListMap()["DISCNUMBER"].front() ).trimmed();
579 if ( !file
->xiphComment()->fieldListMap()[ "COMPILATION" ].isEmpty() )
580 compilation
= TStringToQString( file
->xiphComment()->fieldListMap()["COMPILATION"].front() ).trimmed();
583 if ( images
&& file
->ID3v2Tag() ) {
584 loadImagesFromTag( *file
->ID3v2Tag(), *images
);
587 else if ( TagLib::MP4::File
*file
= dynamic_cast<TagLib::MP4::File
*>( fileref
.file() ) )
590 TagLib::MP4::Tag
*mp4tag
= dynamic_cast<TagLib::MP4::Tag
*>( file
->tag() );
593 setComposer( TStringToQString( mp4tag
->composer() ) );
594 setBpm( QString::number( mp4tag
->bpm() ).toFloat() );
595 disc
= QString::number( mp4tag
->disk() );
596 compilation
= QString::number( mp4tag
->compilation() );
597 if ( images
&& mp4tag
->cover().size() ) {
598 images
->push_back( EmbeddedImage( mp4tag
->cover(), "" ) );
603 if ( !disc
.isEmpty() )
605 int i
= disc
.indexOf('/');
607 // disc.right( i ).toInt() is total number of discs, we don't use this at the moment
608 setDiscNumber( disc
.left( i
).toInt() );
610 setDiscNumber( disc
.toInt() );
613 if ( compilation
.isEmpty() ) {
614 // well, it wasn't set, but if the artist is VA assume it's a compilation
615 if ( artist().string() == i18n( "Various Artists" ) )
616 setCompilation( CompilationYes
);
618 int i
= compilation
.toInt();
619 if ( i
== CompilationNo
)
620 setCompilation( CompilationNo
);
621 else if ( i
== CompilationYes
)
622 setCompilation( CompilationYes
);
625 init( fileref
.audioProperties() );
628 //FIXME disabled for beta4 as it's simpler to not got 100 bug reports
629 //else if( KMimeType::findByUrl( m_url )->is( "audio" ) )
630 // init( KFileMetaInfo( m_url, QString::null, KFileMetaInfo::Everything ) );
633 void MetaBundle::updateFilesize()
637 m_filesize
= Undetermined
;
641 const QString path
= url().path();
642 m_filesize
= QFile( path
).size();
645 float MetaBundle::score( bool ensureCached
) const
647 if( m_score
== Undetermined
&& !ensureCached
)
648 //const_cast is ugly, but other option was mutable, and then we lose const correctness checking
650 *const_cast<float*>(&m_score
) = CollectionDB::instance()->getSongPercentage( m_url
.path() );
654 int MetaBundle::rating( bool ensureCached
) const
656 if( m_rating
== Undetermined
&& !ensureCached
)
657 *const_cast<int*>(&m_rating
) = CollectionDB::instance()->getSongRating( m_url
.path() );
661 int MetaBundle::playCount( bool ensureCached
) const
663 if( m_playCount
== Undetermined
&& !ensureCached
)
664 *const_cast<int*>(&m_playCount
) = CollectionDB::instance()->getPlayCount( m_url
.path() );
668 uint
MetaBundle::lastPlay( bool ensureCached
) const
670 if( (int)m_lastPlay
== abs(Undetermined
) && !ensureCached
)
671 *const_cast<uint
*>(&m_lastPlay
) = CollectionDB::instance()->getLastPlay( m_url
.path() ).toTime_t();
675 void MetaBundle::copyFrom( const MetaBundle
&bundle
)
677 setTitle( bundle
.title() );
678 setArtist( bundle
.artist() );
679 setAlbumArtist( bundle
.albumArtist() );
680 setComposer( bundle
.composer() );
681 setAlbum( bundle
.album() );
682 setYear( bundle
.year() );
683 setDiscNumber( bundle
.discNumber() );
684 setBpm( bundle
.bpm() );
685 setComment( bundle
.comment() );
686 setGenre( bundle
.genre() );
687 setTrack( bundle
.track() );
688 setLength( bundle
.length() );
689 setBitrate( bundle
.bitrate() );
690 setSampleRate( bundle
.sampleRate() );
691 setScore( bundle
.score() );
692 setRating( bundle
.rating() );
693 setPlayCount( bundle
.playCount() );
694 setLastPlay( bundle
.lastPlay() );
695 setFileType( bundle
.fileType() );
696 setFilesize( bundle
.filesize() );
697 if( bundle
.m_podcastBundle
)
698 setPodcastBundle( *bundle
.m_podcastBundle
);
701 delete m_podcastBundle
;
705 if( bundle
.m_lastFmBundle
)
706 setLastFmBundle( *bundle
.m_lastFmBundle
);
709 delete m_lastFmBundle
;
714 void MetaBundle::copyFrom( const PodcastEpisodeBundle
&peb
)
716 setPodcastBundle( peb
);
717 setTitle( peb
.title() );
718 setArtist( peb
.author() );
719 PodcastChannelBundle pcb
;
720 if( CollectionDB::instance()->getPodcastChannelBundle( peb
.parent(), &pcb
) )
722 if( !pcb
.title().isEmpty() )
723 setAlbum( pcb
.title() );
725 setGenre( QString ( "Podcast" ) );
728 void MetaBundle::setExactText( int column
, const QString
&newText
)
732 case Title
: setTitle( newText
); break;
733 case Artist
: setArtist( newText
); break;
734 case AlbumArtist
: setAlbumArtist( newText
); break;
735 case Composer
: setComposer( newText
); break;
736 case Year
: setYear( newText
.toInt() ); break;
737 case Album
: setAlbum( newText
); break;
738 case DiscNumber
: setDiscNumber( newText
.toInt() ); break;
739 case Track
: setTrack( newText
.toInt() ); break;
740 case Bpm
: setBpm( newText
.toFloat() ); break;
741 case Genre
: setGenre( newText
); break;
742 case Comment
: setComment( newText
); break;
743 case Length
: setLength( newText
.toInt() ); break;
744 case Bitrate
: setBitrate( newText
.toInt() ); break;
745 case SampleRate
: setSampleRate( newText
.toInt() ); break;
746 case Score
: setScore( newText
.toFloat() ); break;
747 case Rating
: setRating( newText
.toInt() ); break;
748 case PlayCount
: setPlayCount( newText
.toInt() ); break;
749 case LastPlayed
: setLastPlay( newText
.toInt() ); break;
750 case Filesize
: setFilesize( newText
.toInt() ); break;
751 case Type
: setFileType( newText
.toInt() ); break;
752 default: warning() << "Tried to set the text of an immutable or nonexistent column! [" << column
;
756 QString
MetaBundle::exactText( int column
, bool ensureCached
) const
760 case Filename
: return filename();
761 case Title
: return title();
762 case Artist
: return artist();
763 case AlbumArtist
: return albumArtist();
764 case Composer
: return composer();
765 case Year
: return QString::number( year() );
766 case Album
: return album();
767 case DiscNumber
: return QString::number( discNumber() );
768 case Track
: return QString::number( track() );
769 case Bpm
: return QString::number( bpm() );
770 case Genre
: return genre();
771 case Comment
: return comment();
772 case Directory
: return directory();
773 case Type
: return QString::number( fileType() );
774 case Length
: return QString::number( length() );
775 case Bitrate
: return QString::number( bitrate() );
776 case SampleRate
: return QString::number( sampleRate() );
777 case Score
: return QString::number( score( ensureCached
) );
778 case Rating
: return QString::number( rating( ensureCached
) );
779 case PlayCount
: return QString::number( playCount( ensureCached
) );
780 case LastPlayed
: return QString::number( lastPlay( ensureCached
) );
781 case Filesize
: return QString::number( filesize() );
782 case Mood
: return QString();
783 default: warning() << "Tried to get the text of a nonexistent column! [" << column
;
786 return QString(); //shouldn't happen
789 QString
MetaBundle::prettyText( int column
) const
794 case Filename
: text
= isFile() ? MetaBundle::prettyTitle(filename()) : url().prettyUrl(); break;
795 case Title
: text
= title().isEmpty() ? MetaBundle::prettyTitle( filename() ) : title(); break;
796 case Artist
: text
= artist(); break;
797 case AlbumArtist
: text
= albumArtist(); break;
798 case Composer
: text
= composer(); break;
799 case Year
: text
= year() ? QString::number( year() ) : QString::null
; break;
800 case Album
: text
= album(); break;
801 case DiscNumber
: text
= discNumber() ? QString::number( discNumber() ) : QString::null
; break;
802 case Bpm
: text
= bpm() ? QString::number( bpm() ) : QString::null
; break;
803 case Track
: text
= track() ? QString::number( track() ) : QString::null
; break;
804 case Genre
: text
= genre(); break;
805 case Comment
: text
= comment(); break;
806 case Directory
: text
= url().isEmpty() ? QString() : directory(); break;
807 case Type
: text
= url().isEmpty() ? QString() : type(); break;
808 case Length
: text
= prettyLength( length(), true ); break;
809 case Bitrate
: text
= prettyBitrate( bitrate() ); break;
810 case SampleRate
: text
= prettySampleRate(); break;
811 case Score
: text
= QString::number( static_cast<int>( score() ) ); break;
812 case Rating
: text
= prettyRating(); break;
813 case PlayCount
: text
= QString::number( playCount() ); break;
814 case LastPlayed
: text
= Amarok::verboseTimeSince( lastPlay() ); break;
815 case Filesize
: text
= prettyFilesize(); break;
817 text
= moodbar_const().state() == Moodbar::JobRunning
? i18n( "Calculating..." )
818 : moodbar_const().state() == Moodbar::JobQueued
? i18n( "Queued..." )
821 default: warning() << "Tried to get the text of a nonexistent column!"; break;
824 return text
.trimmed();
827 bool MetaBundle::matchesSimpleExpression( const QString
&expression
, const Q3ValueList
<int> &columns
) const
829 const QStringList terms
= QString( expression
.toLower() ).split( ' ' );
831 for( int x
= 0; matches
&& x
< terms
.count(); ++x
)
833 int y
= 0, n
= columns
.count();
835 if ( prettyText( columns
[y
] ).toLower().count( terms
[x
] ) )
843 void MetaBundle::reactToChanges( const Q3ValueList
<int>& columns
)
845 // mark search dirty if we need to
846 for (int i
= 0; !m_isSearchDirty
&& i
< columns
.count(); i
++)
847 if ((m_searchColumns
& (1 << columns
[i
])) > 0)
848 m_isSearchDirty
= true;
851 bool MetaBundle::matchesFast(const QStringList
&terms
, ColumnMask columnMask
) const
853 // simple search for rating, last played, etc. makes no sense and it hurts us a
854 // lot if we have to fetch it from the db. so zero them out
855 columnMask
&= ~( 1<<Score
| 1<<Rating
| 1<<PlayCount
| 1<<LastPlayed
| 1<<Mood
);
857 if (m_isSearchDirty
|| m_searchColumns
!= columnMask
) {
858 // assert the size of ColumnMask is large enough. In the absence of
859 // a compile assert mechanism, this is pretty much as good for
860 // optimized code (ie, free)
862 if ( sizeof(ColumnMask
) < (NUM_COLUMNS
/ 8) ) {
863 warning() << "ColumnMask is not big enough!\n";
866 // recompute search text
867 // There is potential for mishap here if matchesFast gets called from multiple
868 // threads, but it's *highly* unlikely that something bad will happen
869 m_isSearchDirty
= false;
870 m_searchColumns
= columnMask
;
871 m_searchStr
.resize(0);
873 for (int i
= 0; i
< NUM_COLUMNS
; i
++) {
874 if ((columnMask
& (1 << i
)) > 0) {
875 if (!m_searchStr
.isEmpty()) m_searchStr
+= ' ';
876 m_searchStr
+= prettyText(i
).toLower();
882 for (int i
= 0; i
< terms
.count(); i
++) {
883 if (!m_searchStr
.count(terms
[i
])) return false;
890 bool MetaBundle::matchesExpression( const QString
&expression
, const Q3ValueList
<int> &defaultColumns
) const
892 return matchesParsedExpression( ExpressionParser::parse( expression
), defaultColumns
);
895 bool MetaBundle::matchesParsedExpression( const ParsedExpression
&data
, const Q3ValueList
<int> &defaults
) const
897 for( uint i
= 0, n
= data
.count(); i
< n
; ++i
) //check each part for matchiness
899 bool b
= false; //whether at least one matches
900 for( uint ii
= 0, count
= data
[i
].count(); ii
< count
; ++ii
)
902 expression_element e
= data
[i
][ii
];
904 if( !e
.field
.isEmpty() )
906 QString field
= e
.field
.toLower();
907 column
= columnIndex( field
);
911 if( field
== "size" )
913 else if( field
== "filetype" )
915 else if( field
== "disc" )
916 field
= "discnumber";
921 column
= columnIndex( field
);
924 if( column
>= 0 ) //a field was specified and it exists
926 QString q
= e
.text
, v
= prettyText( column
).toLower(), w
= q
.toLower();
927 //q = query, v = contents of the field, w = match against it
928 bool condition
; //whether it matches, not taking e.negateation into account
949 if( column
== Filesize
)
951 v
= QString::number( filesize() );
952 if( w
.endsWith( "m" ) )
953 w
= QString::number( w
.left( w
.length()-1 ).toLong() * 1024 * 1024 );
954 else if( w
.endsWith( "k" ) )
955 w
= QString::number( w
.left( w
.length()-1 ).toLong() * 1024 );
958 if( e
.match
== expression_element::More
)
961 condition
= v
.toInt() > w
.toInt();
962 else if( column
== Rating
)
963 condition
= v
.toFloat() > w
.toFloat();
964 else if( column
== Length
)
966 int g
= v
.indexOf( ':' ), h
= w
.indexOf( ':' );
967 condition
= v
.left( g
).toInt() > w
.left( h
).toInt() ||
968 ( v
.left( g
).toInt() == w
.left( h
).toInt() &&
969 v
.mid( g
+ 1 ).toInt() > w
.mid( h
+ 1 ).toInt() );
972 condition
= v
> w
; //compare the strings
974 else if( e
.match
== expression_element::Less
)
977 condition
= v
.toInt() < w
.toInt();
978 else if( column
== Rating
)
979 condition
= v
.toFloat() < w
.toFloat();
980 else if( column
== Length
)
982 int g
= v
.indexOf( ':' ), h
= w
.indexOf( ':' );
983 condition
= v
.left( g
).toInt() < w
.left( h
).toInt() ||
984 ( v
.left( g
).toInt() == w
.left( h
).toInt() &&
985 v
.mid( g
+ 1 ).toInt() < w
.mid( h
+ 1 ).toInt() );
993 condition
= v
.toInt() == w
.toInt();
994 else if( column
== Rating
)
995 condition
= v
.toFloat() == w
.toFloat();
996 else if( column
== Length
)
998 int g
= v
.indexOf( ':' ), h
= w
.indexOf( ':' );
999 condition
= v
.left( g
).toInt() == w
.left( h
).toInt() &&
1000 v
.mid( g
+ 1 ).toInt() == w
.mid( h
+ 1 ).toInt();
1003 condition
= v
.count( q
, Qt::CaseInsensitive
);
1005 if( condition
== ( e
.negate
? false : true ) )
1011 else //check just the default fields
1013 for( int it
= 0, end
= defaults
.size(); it
!= end
; ++it
)
1015 b
= prettyText( defaults
[it
] ).count( e
.text
, Qt::CaseInsensitive
) == ( e
.negate
? false : true );
1016 if( ( e
.negate
&& !b
) || ( !e
.negate
&& b
) )
1031 MetaBundle::prettyTitle() const
1033 QString s
= artist();
1035 //NOTE this gets regressed often, please be careful!
1036 // whatever you do, handle the stream case, streams have no artist but have an excellent title
1038 //FIXME doesn't work for resume playback
1043 s
= i18n("%1 - %2", artist(), title() );
1045 if( s
.isEmpty() ) s
= prettyTitle( filename() );
1051 MetaBundle::veryNiceTitle() const
1054 //NOTE I'm not sure, but the notes and FIXME's in the prettyTitle function should be fixed now.
1055 // If not then they do apply to this function also!
1056 if( !title().isEmpty() )
1058 if( !artist().isEmpty() )
1059 s
= i18n( "%1 by %2", title(), artist() );
1065 s
= prettyTitle( filename() );
1071 MetaBundle::prettyTitle( const QString
&filename
) //static
1073 QString s
= filename
; //just so the code is more readable
1075 //remove .part extension if it exists
1076 if (s
.endsWith( ".part" ))
1077 s
= s
.left( s
.length() - 5 );
1079 //remove file extension, s/_/ /g and decode %2f-like sequences
1080 s
= s
.left( s
.lastIndexOf( '.' ) ).replace( '_', ' ' );
1081 s
= QUrl::fromPercentEncoding( s
.toAscii() );
1087 MetaBundle::prettyLength( int seconds
, bool showHours
) //static
1089 if( seconds
> 0 ) return prettyTime( seconds
, showHours
);
1090 if( seconds
== Undetermined
) return "?";
1091 if( seconds
== Irrelevant
) return "-";
1093 return QString(); //Unavailable = ""
1097 MetaBundle::prettyTime( uint seconds
, bool showHours
) //static
1099 QString s
= QChar( ':' );
1100 s
.append( zeroPad( seconds
% 60 ) ); //seconds
1103 if( showHours
&& seconds
>= 60)
1105 s
.prepend( zeroPad( seconds
% 60 ) ); //minutes
1110 //don't zeroPad the last one, as it can be greater than 2 digits
1111 s
.prepend( QString::number( seconds
) ); //hours or minutes depending on above if block
1117 MetaBundle::veryPrettyTime( int time
)
1119 if( time
== Undetermined
)
1121 if( time
== Irrelevant
)
1125 s
<< QString::number( time
% 60 ); //seconds
1128 s
<< QString::number( time
% 60 ); //minutes
1131 s
<< QString::number( time
% 24 ); //hours
1134 s
<< QString::number( time
); //days
1138 // xgettext: no-c-format
1139 case 1: return i18nc( "seconds", "%1s", s
[0] );
1140 // xgettext: no-c-format
1141 case 2: return i18nc( "minutes, seconds", "%2m %1s", s
[0], s
[1] );
1142 // xgettext: no-c-format
1143 case 3: return i18nc( "hours, minutes, seconds", "%3h %2m %1s", s
[0], s
[1], s
[2] );
1144 // xgettext: no-c-format
1145 case 4: return i18nc( "days, hours, minutes, seconds", "%4d %3h %2m %1s", s
[0], s
[1], s
[2], s
[3] );
1146 default: return "omg bug!";
1151 MetaBundle::fuzzyTime( int time
)
1154 int secs
=0, min
=0, hr
=0, day
=0, week
=0;
1156 if( time
== Undetermined
)
1158 if( time
== Irrelevant
)
1161 secs
= time
% 60; //seconds
1163 min
= time
% 60; //minutes
1165 hr
= time
% 24 ; //hours
1167 day
= time
% 7 ; //days
1169 week
= time
; //weeks
1171 if( week
&& hr
>= 12 )
1180 else if( day
&& min
>= 30 )
1189 else if( hr
&& secs
>= 30 )
1199 QString weeks
= i18np( "1 week ", "%1 weeks ", week
);
1200 QString days
= i18np( "1 day ", "%1 days ", day
);
1201 QString hours
= i18np( "1 hour", "%1 hours", hr
);
1204 return ( weeks
+ ( day
? days
.arg("") : "" ) ).simplified();
1206 return ( days
+ ( hr
? hours
: "" ) ).simplified();
1208 return i18n( "%1:%2 hours", hr
, zeroPad( min
) );
1210 return i18n( "%1:%2", min
,zeroPad( secs
) );
1214 MetaBundle::prettyBitrate( int i
)
1216 //the point here is to force sharing of these strings returned from prettyBitrate()
1217 static const QString bitrateStore
[9] = {
1218 "?", "32", "64", "96", "128", "160", "192", "224", "256" };
1220 return (i
>=0 && i
<= 256 && i
% 32 == 0)
1221 ? bitrateStore
[ i
/ 32 ]
1222 : prettyGeneric( "%1", i
);
1226 MetaBundle::prettyFilesize( int s
)
1228 return KIO::convertSize( s
);
1232 MetaBundle::prettyRating( int r
, bool trailingzero
) //static
1235 return QString::number( float( r
) / 2, 'f', 1 );
1237 return r
? QString::number( float( r
) / 2 ) : QString();
1241 MetaBundle::ratingDescription( int r
)
1245 case 2: return i18n( "Awful" );
1246 case 3: return i18n( "Barely tolerable" );
1247 case 4: return i18n( "Tolerable" );
1248 case 5: return i18n( "Okay" );
1249 case 6: return i18n( "Good" );
1250 case 7: return i18n( "Very good" );
1251 case 8: return i18n( "Excellent" );
1252 case 9: return i18n( "Amazing" );
1253 case 10: return i18n( "Favorite" );
1254 case 0: default: return i18n( "Not rated" ); // assume weird values as not rated
1256 return "if you can see this, then that's a bad sign.";
1260 MetaBundle::ratingList()
1263 list
+= ratingDescription( 0 );
1264 for ( int i
= 2; i
<=10; i
++ )
1265 list
+= i18nc( "rating - description", "%1 - %2", prettyRating( i
, true ), ratingDescription( i
) );
1270 MetaBundle::genreList() //static
1274 TagLib::StringList genres
= TagLib::ID3v1::genreList();
1275 for( TagLib::StringList::ConstIterator it
= genres
.begin(), end
= genres
.end(); it
!= end
; ++it
)
1276 list
+= TStringToQString( (*it
) );
1284 MetaBundle::setExtendedTag( TagLib::File
*file
, int tag
, const QString value
)
1288 if ( m_type
== mp3
)
1292 case ( composerTag
): id
= "TCOM"; break;
1293 case ( discNumberTag
): id
= "TPOS"; break;
1294 case ( bpmTag
): id
= "TBPM"; break;
1295 case ( compilationTag
): id
= "TCMP"; break;
1296 case ( albumArtistTag
): id
= "TPE2"; break; // non-standard: Apple, Microsoft
1297 default: return; //don't write the tag if it is unknown
1299 fprintf(stderr
, "Setting extended tag %s to %s\n", id
, value
.toUtf8().data());
1300 TagLib::MPEG::File
*mpegFile
= dynamic_cast<TagLib::MPEG::File
*>( file
);
1301 if ( mpegFile
&& mpegFile
->ID3v2Tag() )
1303 if ( value
.isEmpty() )
1304 mpegFile
->ID3v2Tag()->removeFrames( id
);
1307 if( !mpegFile
->ID3v2Tag()->frameListMap()[id
].isEmpty() )
1308 mpegFile
->ID3v2Tag()->frameListMap()[id
].front()->setText( QStringToTString( value
) );
1311 TagLib::ID3v2::TextIdentificationFrame
*frame
= new TagLib::ID3v2::TextIdentificationFrame( id
, TagLib::ID3v2::FrameFactory::instance()->defaultTextEncoding() );
1312 frame
->setText( QStringToTString( value
) );
1313 mpegFile
->ID3v2Tag()->addFrame( frame
);
1318 else if ( m_type
== ogg
)
1322 case ( composerTag
): id
= "COMPOSER"; break;
1323 case ( discNumberTag
): id
= "DISCNUMBER"; break;
1324 case ( bpmTag
): id
= "BPM"; break;
1325 case ( compilationTag
): id
= "COMPILATION"; break;
1326 case ( albumArtistTag
): id
= "ALBUMARTIST"; break; // non-standard: Amarok
1327 default: return; //don't write the tag if it is unknown
1329 TagLib::Ogg::Vorbis::File
*oggFile
= dynamic_cast<TagLib::Ogg::Vorbis::File
*>( file
);
1330 if ( oggFile
&& oggFile
->tag() )
1333 oggFile
->tag()->removeField( id
):
1334 oggFile
->tag()->addField( id
, QStringToTString( value
), true );
1337 else if ( m_type
== flac
)
1341 case ( composerTag
): id
= "COMPOSER"; break;
1342 case ( discNumberTag
): id
= "DISCNUMBER"; break;
1343 case ( bpmTag
): id
= "BPM"; break;
1344 case ( compilationTag
): id
= "COMPILATION"; break;
1345 case ( albumArtistTag
): id
= "ALBUMARTIST"; break; // non-standard: Amarok
1346 default: return; //don't write the tag if it is unknown
1348 TagLib::FLAC::File
*flacFile
= dynamic_cast<TagLib::FLAC::File
*>( file
);
1349 if ( flacFile
&& flacFile
->xiphComment() )
1352 flacFile
->xiphComment()->removeField( id
):
1353 flacFile
->xiphComment()->addField( id
, QStringToTString( value
), true );
1356 else if ( m_type
== mp4
)
1358 TagLib::MP4::Tag
*mp4tag
= dynamic_cast<TagLib::MP4::Tag
*>( file
->tag() );
1363 case ( composerTag
): mp4tag
->setComposer( QStringToTString( value
) ); break;
1364 case ( discNumberTag
): mp4tag
->setDisk( value
.toInt() );
1365 case ( bpmTag
): mp4tag
->setBpm( value
.toInt() ); // mp4 doesn't support float bpm
1366 case ( compilationTag
): mp4tag
->setCompilation( value
.toInt() == CompilationYes
);
1373 MetaBundle::setPodcastBundle( const PodcastEpisodeBundle
&peb
)
1375 delete m_podcastBundle
;
1376 m_podcastBundle
= new PodcastEpisodeBundle
;
1377 *m_podcastBundle
= peb
;
1381 MetaBundle::setLastFmBundle( const LastFm::Bundle
&last
)
1383 delete m_lastFmBundle
;
1384 // m_lastFmBundle = new LastFm::Bundle(last);
1385 m_lastFmBundle
= new LastFm::Bundle
;
1386 *m_lastFmBundle
= last
;
1389 void MetaBundle::loadImagesFromTag( const TagLib::ID3v2::Tag
&tag
, EmbeddedImageList
& images
) const
1391 TagLib::ID3v2::FrameList l
= tag
.frameListMap()[ "APIC" ];
1392 oldForeachType( TagLib::ID3v2::FrameList
, l
) {
1393 debug() << "Found APIC frame";
1394 TagLib::ID3v2::AttachedPictureFrame
*ap
= static_cast<TagLib::ID3v2::AttachedPictureFrame
*>( *it
);
1396 const TagLib::ByteVector
&imgVector
= ap
->picture();
1397 debug() << "Size of image: " << imgVector
.size() << " byte";
1398 // ignore APIC frames without picture and those with obviously bogus size
1399 if( imgVector
.size() > 0 && imgVector
.size() < 10000000 /*10MB*/ ) {
1400 images
.push_back( EmbeddedImage( imgVector
, ap
->description() ) );
1406 MetaBundle::safeSave()
1409 MetaBundleSaver mbs( this );
1410 TagLib::FileRef* fileref = mbs.prepareToSave();
1413 debug() << "Could not get a fileref!";
1418 noproblem = save( fileref );
1422 debug() << "MetaBundle::save() didn't work!";
1427 noproblem = mbs.doSave();
1431 debug() << "Something failed during the save, cleaning up and exiting!";
1436 setUniqueId( readUniqueId() );
1437 if( CollectionDB::instance()->isFileInCollection( url().path() ) )
1438 CollectionDB::instance()->doAFTStuff( this, false );
1440 noproblem = mbs.cleanupSave();
1447 MetaBundle::save( TagLib::FileRef
* fileref
)
1453 //Set default codec to UTF-8 (see bugs 111246 and 111232)
1454 TagLib::ID3v2::FrameFactory::instance()->setDefaultTextEncoding(TagLib::String::UTF8
);
1456 bool passedin
= fileref
;
1457 bool returnval
= false;
1462 f
= new TagLib::FileRef( QFile::encodeName( url().path() ), false );
1466 if ( f
&& !f
->isNull() )
1468 TagLib::Tag
* t
= f
->tag();
1469 if ( t
) { // f.tag() can return null if the file couldn't be opened for writing
1470 t
->setTitle( QStringToTString( title().trimmed() ) );
1471 t
->setArtist( QStringToTString( artist().string().trimmed() ) );
1472 t
->setAlbum( QStringToTString( album().string().trimmed() ) );
1473 t
->setTrack( track() );
1474 t
->setYear( year() );
1475 t
->setComment( QStringToTString( comment().string().trimmed() ) );
1476 t
->setGenre( QStringToTString( genre().string().trimmed() ) );
1478 if ( hasExtendedMetaInformation() )
1480 setExtendedTag( f
->file(), albumArtistTag
, albumArtist() );
1481 setExtendedTag( f
->file(), composerTag
, composer().string().trimmed() );
1482 setExtendedTag( f
->file(), discNumberTag
, discNumber() ? QString::number( discNumber() ) : QString() );
1483 setExtendedTag( f
->file(), bpmTag
, bpm() ? QString::number( bpm() ) : QString() );
1484 if ( compilation() != CompilationUnknown
)
1485 setExtendedTag( f
->file(), compilationTag
, QString::number( compilation() ) );
1489 returnval
= f
->save();
1490 setUniqueId( readUniqueId() );
1491 if( returnval
&& CollectionDB::instance()->isFileInCollection( url().path() ) )
1492 CollectionDB::instance()->doAFTStuff( this, false );
1504 bool MetaBundle::save( QTextStream
&stream
, const QStringList
&attributes
) const
1506 QDomDocument qDomSucksItNeedsADocument
;
1507 QDomElement item
= qDomSucksItNeedsADocument
.createElement( "item" );
1508 item
.setAttribute( "url", url().url() );
1509 item
.setAttribute( "uniqueid", uniqueId() );
1510 if( m_isCompilation
)
1511 item
.setAttribute( "compilation", "true" );
1513 for( int i
= 0, n
= attributes
.count(); i
< n
; i
+= 2 )
1514 item
.setAttribute( attributes
[i
], attributes
[i
+1] );
1516 for( int i
= 0; i
< NUM_COLUMNS
; ++i
)
1518 QDomElement tag
= qDomSucksItNeedsADocument
.createElement( exactColumnName( i
) );
1519 //debug() << "exactColumName(i) = " << exactColumnName( i );
1520 QDomText text
= qDomSucksItNeedsADocument
.createTextNode( exactText( i
, true ) );
1521 //debug() << "exactText(i) = " << exactText( i );
1522 tag
.appendChild( text
);
1524 item
.appendChild( tag
);
1527 item
.save( stream
, 1 );
1531 void MetaBundle::setUrl( const KUrl
&url
)
1533 Q3ValueList
<int> changes
;
1534 for( int i
= 0; i
< NUM_COLUMNS
; ++i
) changes
<< i
;
1535 aboutToChange( changes
); m_url
= url
; reactToChanges( changes
);
1540 void MetaBundle::setPath( const QString
&path
)
1542 Q3ValueList
<int> changes
;
1543 for( int i
= 0; i
< NUM_COLUMNS
; ++i
) changes
<< i
;
1544 aboutToChange( changes
); m_url
.setPath( path
); reactToChanges( changes
);
1549 void MetaBundle::setUniqueId()
1551 //if the file isn't already in the database, not checking for amarokcollectionscanner
1552 //will result in the UID being set to QString::null during the scan...bad!
1556 m_uniqueId
= CollectionDB::instance()->uniqueIdFromUrl( url() );
1559 void MetaBundle::setUniqueId( const QString
&id
)
1561 //WARNING WARNING WARNING
1562 //Don't call this function if you don't know what you're doing!
1566 const TagLib::ByteVector
1567 MetaBundle::readUniqueIdHelper( TagLib::FileRef fileref
) const
1569 if ( TagLib::MPEG::File
*file
= dynamic_cast<TagLib::MPEG::File
*>( fileref
.file() ) )
1571 if( file
->ID3v2Tag() )
1572 return file
->ID3v2Tag()->render();
1573 else if( file
->ID3v1Tag() )
1574 return file
->ID3v1Tag()->render();
1575 else if( file
->APETag() )
1576 return file
->APETag()->render();
1578 else if ( TagLib::Ogg::Vorbis::File
*file
= dynamic_cast<TagLib::Ogg::Vorbis::File
*>( fileref
.file() ) )
1581 return file
->tag()->render();
1583 else if ( TagLib::FLAC::File
*file
= dynamic_cast<TagLib::FLAC::File
*>( fileref
.file() ) )
1585 if( file
->ID3v2Tag() )
1586 return file
->ID3v2Tag()->render();
1587 else if( file
->ID3v1Tag() )
1588 return file
->ID3v1Tag()->render();
1589 else if( file
->xiphComment() )
1590 return file
->xiphComment()->render();
1592 else if ( TagLib::Ogg::FLAC::File
*file
= dynamic_cast<TagLib::Ogg::FLAC::File
*>( fileref
.file() ) )
1595 return file
->tag()->render();
1597 else if ( TagLib::MPC::File
*file
= dynamic_cast<TagLib::MPC::File
*>( fileref
.file() ) )
1599 if( file
->ID3v1Tag() )
1600 return file
->ID3v1Tag()->render();
1601 else if( file
->APETag() )
1602 return file
->APETag()->render();
1604 TagLib::ByteVector bv
;
1609 MetaBundle::readUniqueId( TagLib::FileRef
* fileref
)
1611 //This is used in case we don't get given a fileref
1612 TagLib::FileRef tmpfileref
;
1614 if( !fileref
&& isFile() )
1616 const QString path
= url().path();
1617 //Make it get cleaned up at the end of the function automagically
1618 tmpfileref
= TagLib::FileRef( QFile::encodeName( path
), true, TagLib::AudioProperties::Fast
);
1619 fileref
= &tmpfileref
;
1622 if( !fileref
|| fileref
->isNull() )
1625 TagLib::ByteVector bv
= readUniqueIdHelper( *fileref
);
1630 QFile
qfile( url().path() );
1634 QByteArray size
= 0;
1637 md5
.update( bv
.data(), bv
.size() );
1639 if( qfile
.open( QIODevice::Unbuffered
| QIODevice::ReadOnly
) )
1641 if( ( readlen
= qfile
.read( databuf
, 8192 ) ) > 0 )
1643 md5
.update( databuf
, readlen
);
1644 md5
.update( size
.setNum( qfile
.size() ) );
1645 return QString( md5
.hexDigest().data() );
1655 MetaBundle::getRand()
1657 //KRandom supposedly exists in SVN, although it's not checked out on my machine, and it's certainly not in 3.3, so I'm just going to steal its code
1660 int fd
= open("/dev/urandom", O_RDONLY
);
1661 if (fd
< 0 || ::read(fd
, &seed
, sizeof(seed
)) != sizeof(seed
))
1663 // No /dev/urandom... try something else.
1665 seed
= rand()+time(0);
1667 if (fd
>= 0) close(fd
);
1673 MetaBundle::getRandomString( int size
, bool numbersOnly
)
1677 debug() << "Wrong size passed in!";
1682 //do a memory op once, much faster than doing multiple later, especially since we know how big it will be
1683 str
.reserve( size
);
1684 int i
= getRand(); //seed it
1688 // check your ASCII tables
1689 // we want characters you can see...93 is the range from ! to ~
1691 // shift the value to the visible characters
1693 // we don't want ", %, ', <, >, \, `, or &
1694 // so that we don't have issues with escaping/quoting in QStrings,
1695 // and so that we don't have <> in our XML files where they might cause issues
1696 // hopefully this list is final, as once users really start using this
1697 // it will be a pain to change...however, there is an ATF version in CollectionDB
1698 // which will help if this ever needs to change
1699 // In addition we can change our vendor string
1700 while ( r
==34 || r
==37 || r
== 38 || r
==39 || r
==60 ||r
== 62 || r
==92 || r
==96 )
1703 if( numbersOnly
&& ( r
< 48 || r
> 57 ) )
1710 // this next comment kept in for fun, as it was from the source of KRandomString, where I got
1711 // most of this code from to start with :-)
1712 // so what if I work backwards?
1717 void MetaBundle::setTitle( const QString
&title
)
1718 { aboutToChange( Title
); m_title
= title
; reactToChange( Title
); }
1720 void MetaBundle::setArtist( const AtomicString
&artist
)
1721 { aboutToChange( Artist
); m_artist
= artist
; reactToChange( Artist
); }
1723 void MetaBundle::setAlbum( const AtomicString
&album
)
1724 { aboutToChange( Album
); m_album
= album
; reactToChange( Album
); }
1726 void MetaBundle::setComment( const AtomicString
&comment
)
1727 { aboutToChange( Comment
); m_comment
= comment
; reactToChange( Comment
); }
1729 void MetaBundle::setGenre( const AtomicString
&genre
)
1730 { aboutToChange( Genre
); m_genre
= genre
; reactToChange( Genre
); }
1732 void MetaBundle::setYear( int year
)
1733 { aboutToChange( Year
); m_year
= year
; reactToChange( Year
); }
1735 void MetaBundle::setTrack( int track
)
1736 { aboutToChange( Track
); m_track
= track
; reactToChange( Track
); }
1738 void MetaBundle::setCompilation( int compilation
)
1740 switch( compilation
)
1742 case CompilationYes
:
1743 m_isCompilation
= true;
1744 m_notCompilation
= false;
1747 m_isCompilation
= false;
1748 m_notCompilation
= true;
1750 case CompilationUnknown
:
1751 m_isCompilation
= m_notCompilation
= false;
1756 void MetaBundle::setLength( int length
)
1757 { aboutToChange( Length
); m_length
= length
; reactToChange( Length
); }
1759 void MetaBundle::setBitrate( int bitrate
)
1760 { aboutToChange( Bitrate
); m_bitrate
= bitrate
; reactToChange( Bitrate
); }
1762 void MetaBundle::setSampleRate( int sampleRate
)
1763 { aboutToChange( SampleRate
); m_sampleRate
= sampleRate
; reactToChange( SampleRate
); }
1765 void MetaBundle::setDiscNumber( int discnumber
)
1766 { aboutToChange( DiscNumber
); m_discNumber
= discnumber
; reactToChange( DiscNumber
); }
1768 void MetaBundle::setBpm( float bpm
)
1769 { aboutToChange( Bpm
); m_bpm
= bpm
; reactToChange( Bpm
); }
1771 void MetaBundle::setComposer( const AtomicString
&composer
)
1772 { aboutToChange( Composer
); m_composer
= composer
; reactToChange( Composer
); }
1774 void MetaBundle::setAlbumArtist( const AtomicString
&albumArtist
)
1775 { aboutToChange( AlbumArtist
); m_albumArtist
= albumArtist
; reactToChange( AlbumArtist
); }
1777 void MetaBundle::setPlayCount( int playcount
)
1778 { aboutToChange( PlayCount
); m_playCount
= playcount
; reactToChange( PlayCount
); }
1780 void MetaBundle::setLastPlay( uint lastplay
)
1781 { aboutToChange( LastPlayed
); m_lastPlay
= lastplay
; reactToChange( LastPlayed
); }
1783 void MetaBundle::setRating( int rating
)
1784 { aboutToChange( Rating
); m_rating
= rating
; reactToChange( Rating
); }
1786 void MetaBundle::setScore( float score
)
1787 { aboutToChange( Score
); m_score
= score
; reactToChange( Score
); }
1789 void MetaBundle::setFilesize( int bytes
)
1790 { aboutToChange( Filesize
); m_filesize
= bytes
; reactToChange( Filesize
); }
1792 void MetaBundle::setFileType( int type
) { m_type
= type
; }