File indexing completed on 2024-05-12 08:47:35
0001 /**************************************************************************************** 0002 * Copyright (C) 2003-2005 Max Howell <max.howell@methylblue.com> * 0003 * (C) 2003-2010 Mark Kretschmann <kretschmann@kde.org> * 0004 * (C) 2005-2007 Alexandre Oliveira <aleprj@gmail.com> * 0005 * (C) 2008 Dan Meltzer <parallelgrapefruit@gmail.com> * 0006 * (C) 2008-2009 Jeff Mitchell <mitchell@kde.org> * 0007 * (C) 2010 Ralf Engels <ralf-engels@gmx.de> * 0008 * (c) 2010 Sergey Ivanov <123kash@gmail.com> * 0009 * * 0010 * This program is free software; you can redistribute it and/or modify it under * 0011 * the terms of the GNU General Public License as published by the Free Software * 0012 * Foundation; either version 2 of the License, or (at your option) any later * 0013 * version. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0016 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0017 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0018 * * 0019 * You should have received a copy of the GNU General Public License along with * 0020 * this program. If not, see <http://www.gnu.org/licenses/>. * 0021 ****************************************************************************************/ 0022 0023 0024 #include "VorbisCommentTagHelper.h" 0025 0026 #include "StringHelper.h" 0027 0028 #include <QBuffer> 0029 #include <QImage> 0030 0031 #include <flacpicture.h> 0032 0033 using namespace Meta::Tag; 0034 0035 static const TagLib::String VORBIS_PICTURE_TAG( "METADATA_BLOCK_PICTURE" ); 0036 0037 VorbisCommentTagHelper::VorbisCommentTagHelper( TagLib::Tag *tag, TagLib::Ogg::XiphComment *commentsTag, Amarok::FileType fileType, TagLib::FLAC::File *file ) 0038 : TagHelper( tag, fileType ) 0039 , m_tag( commentsTag ) 0040 , m_flacFile( file ) 0041 { 0042 m_fieldMap.insert( Meta::valAlbumArtist, TagLib::String( "ALBUMARTIST" ) ); 0043 m_fieldMap.insert( Meta::valBpm, TagLib::String( "BPM" ) ); 0044 m_fieldMap.insert( Meta::valCompilation, TagLib::String( "COMPILATION" ) ); 0045 m_fieldMap.insert( Meta::valComposer, TagLib::String( "COMPOSER" ) ); 0046 m_fieldMap.insert( Meta::valDiscNr, TagLib::String( "DISCNUMBER" ) ); 0047 m_fieldMap.insert( Meta::valHasCover, TagLib::String( "COVERART" ) ); // non-standard but frequently used 0048 m_fieldMap.insert( Meta::valPlaycount, TagLib::String( "FMPS_PLAYCOUNT" ) ); 0049 m_fieldMap.insert( Meta::valRating, TagLib::String( "FMPS_RATING" ) ); 0050 m_fieldMap.insert( Meta::valScore, TagLib::String( "FMPS_RATING_AMAROK_SCORE" ) ); 0051 m_fieldMap.insert( Meta::valLyrics, TagLib::String( "LYRICS" ) ); 0052 0053 m_uidFieldMap.insert( UIDAFT, TagLib::String( "AMAROK 2 AFTV1 - AMAROK.KDE.ORG" ) ); 0054 } 0055 0056 static inline bool 0057 isAlbumCover( const TagLib::FLAC::Picture* picture ) 0058 { 0059 return ( picture->type() == TagLib::FLAC::Picture::FrontCover || 0060 picture->type() == TagLib::FLAC::Picture::Other ) && 0061 picture->data().size() > MIN_COVER_SIZE; // must be at least 1kb 0062 } 0063 0064 /** 0065 * Extract the given FLAC Picture object to QImage picture. 0066 * 0067 * If the FLAC image is a front cover, store the result in cover and return true. 0068 * Otherwise, if otherCover is null, store the image there and return false. 0069 */ 0070 static inline bool 0071 flacPictureToQImage( const TagLib::FLAC::Picture* picture, QImage& cover, QImage& otherCover ) 0072 { 0073 QByteArray image_data( picture->data().data(), picture->data().size() ); 0074 0075 if( picture->type() == TagLib::FLAC::Picture::FrontCover ) 0076 { 0077 cover.loadFromData( image_data ); 0078 return true; 0079 } 0080 else if( otherCover.isNull() ) 0081 { 0082 otherCover.loadFromData( image_data ); 0083 } 0084 return false; 0085 } 0086 0087 Meta::FieldHash 0088 VorbisCommentTagHelper::tags() const 0089 { 0090 Meta::FieldHash data = TagHelper::tags(); 0091 0092 TagLib::Ogg::FieldListMap map = m_tag->fieldListMap(); 0093 for( TagLib::Ogg::FieldListMap::ConstIterator it = map.begin(); it != map.end(); ++it ) 0094 { 0095 qint64 field; 0096 QString value = TStringToQString( it->second.toString( '\n' ) ); 0097 0098 if( ( field = fieldName( it->first ) ) ) 0099 { 0100 if( field == Meta::valDiscNr ) 0101 { 0102 int disc; 0103 if( ( disc = splitDiscNr( value ).first ) ) 0104 data.insert( field, disc ); 0105 } 0106 else if( field == Meta::valRating ) 0107 data.insert( field, qRound( value.toFloat() * 10.0 ) ); 0108 else if( field == Meta::valScore ) 0109 data.insert( field, value.toFloat() * 100.0 ); 0110 else if( field == Meta::valHasCover ) 0111 data.insert( Meta::valHasCover, true ); 0112 else 0113 data.insert( field, value ); 0114 } 0115 else if( it->first == uidFieldName( UIDAFT ) && isValidUID( value, UIDAFT ) ) 0116 data.insert( Meta::valUniqueId, value ); 0117 else if( it->first == VORBIS_PICTURE_TAG ) 0118 { 0119 if( parsePictureBlock( it->second ) ) 0120 data.insert( Meta::valHasCover, true ); 0121 } 0122 } 0123 0124 if( m_flacFile ) 0125 { 0126 const TagLib::List<TagLib::FLAC::Picture*> picturelist = m_flacFile->pictureList(); 0127 for( TagLib::List<TagLib::FLAC::Picture*>::ConstIterator it = picturelist.begin(); it != picturelist.end(); it++ ) 0128 { 0129 TagLib::FLAC::Picture* picture = *it; 0130 0131 if( ( picture->type() == TagLib::FLAC::Picture::FrontCover || 0132 picture->type() == TagLib::FLAC::Picture::Other ) && 0133 picture->data().size() > MIN_COVER_SIZE ) // must be at least 1kb 0134 { 0135 data.insert( Meta::valHasCover, true ); 0136 break; 0137 } 0138 } 0139 } 0140 0141 return data; 0142 } 0143 0144 bool 0145 VorbisCommentTagHelper::setTags( const Meta::FieldHash &changes ) 0146 { 0147 bool modified = TagHelper::setTags( changes ); 0148 0149 foreach( const qint64 key, changes.keys() ) 0150 { 0151 QVariant value = changes.value( key ); 0152 TagLib::String field = fieldName( key ); 0153 0154 if( !field.isNull() && !field.isEmpty() ) 0155 { 0156 if( key == Meta::valHasCover ) 0157 continue; 0158 else if( key == Meta::valRating ) 0159 m_tag->addField( field, Qt4QStringToTString( QString::number( value.toFloat() / 10.0 ) ) ); 0160 else if( key == Meta::valScore ) 0161 m_tag->addField( field, Qt4QStringToTString( QString::number( value.toFloat() / 100.0 ) ) ); 0162 else 0163 m_tag->addField( field, Qt4QStringToTString( value.toString() ) ); 0164 0165 modified = true; 0166 } 0167 else if( key == Meta::valUniqueId ) 0168 { 0169 QPair < UIDType, QString > uidPair = splitUID( value.toString() ); 0170 if( uidPair.first == UIDInvalid ) 0171 continue; 0172 0173 m_tag->addField( uidFieldName( uidPair.first ), Qt4QStringToTString( uidPair.second ) ); 0174 modified = true; 0175 } 0176 } 0177 0178 return modified; 0179 } 0180 0181 TagLib::ByteVector 0182 VorbisCommentTagHelper::render() const 0183 { 0184 return m_tag->render(); 0185 } 0186 0187 bool 0188 VorbisCommentTagHelper::hasEmbeddedCover() const 0189 { 0190 if( m_flacFile ) 0191 { 0192 const TagLib::List<TagLib::FLAC::Picture*> picturelist = m_flacFile->pictureList(); 0193 for( TagLib::List<TagLib::FLAC::Picture*>::ConstIterator it = picturelist.begin(); it != picturelist.end(); it++ ) 0194 { 0195 const TagLib::FLAC::Picture *picture = *it; 0196 0197 if( ( picture->type() == TagLib::FLAC::Picture::FrontCover || 0198 picture->type() == TagLib::FLAC::Picture::Other ) && 0199 picture->data().size() > MIN_COVER_SIZE ) // must be at least 1kb 0200 { 0201 return true; 0202 } 0203 } 0204 } 0205 else if( m_tag->fieldListMap().contains( VORBIS_PICTURE_TAG ) ) 0206 { 0207 return parsePictureBlock( m_tag->fieldListMap()[ VORBIS_PICTURE_TAG ] ); 0208 } 0209 else if( !fieldName( Meta::valHasCover ).isEmpty() ) 0210 { 0211 TagLib::ByteVector field = fieldName( Meta::valHasCover ).toCString(); 0212 0213 return m_tag->fieldListMap().contains( field ); 0214 } 0215 0216 return false; 0217 } 0218 0219 QImage 0220 VorbisCommentTagHelper::embeddedCover() const 0221 { 0222 QImage cover; 0223 0224 if( m_flacFile ) 0225 { 0226 QImage otherCover; 0227 0228 const TagLib::List<TagLib::FLAC::Picture*> picturelist = m_flacFile->pictureList(); 0229 for( TagLib::List<TagLib::FLAC::Picture*>::ConstIterator it = picturelist.begin(); it != picturelist.end(); it++ ) 0230 { 0231 const TagLib::FLAC::Picture *picture = *it; 0232 0233 if( ( picture->type() == TagLib::FLAC::Picture::FrontCover || 0234 picture->type() == TagLib::FLAC::Picture::Other ) && 0235 picture->data().size() > MIN_COVER_SIZE ) // must be at least 1kb 0236 { 0237 QByteArray image_data( picture->data().data(), picture->data().size() ); 0238 0239 if( picture->type() == TagLib::FLAC::Picture::FrontCover ) 0240 { 0241 cover.loadFromData( image_data ); 0242 break; 0243 } 0244 else if( otherCover.isNull() ) 0245 { 0246 otherCover.loadFromData( image_data ); 0247 } 0248 } 0249 } 0250 0251 if( cover.isNull() && !otherCover.isNull() ) 0252 cover = otherCover; 0253 } 0254 else if( m_tag->fieldListMap().contains( VORBIS_PICTURE_TAG ) ) 0255 { 0256 QImage resultCover; 0257 parsePictureBlock( m_tag->fieldListMap()[ VORBIS_PICTURE_TAG ], &resultCover ); 0258 if( cover.isNull() && !resultCover.isNull() ) 0259 cover = resultCover; 0260 } 0261 else if( !fieldName( Meta::valHasCover ).isEmpty() ) 0262 { 0263 TagLib::ByteVector field = fieldName( Meta::valHasCover ).toCString(); 0264 0265 if( !m_tag->fieldListMap().contains( field ) ) 0266 return cover; 0267 0268 TagLib::StringList coverList = m_tag->fieldListMap()[field]; 0269 0270 for( uint i=0; i<coverList.size(); i++ ) 0271 { 0272 QByteArray image_data_b64( coverList[i].toCString() ); 0273 QByteArray image_data = QByteArray::fromBase64(image_data_b64); 0274 0275 if( image_data.size() <= MIN_COVER_SIZE ) // must be at least 1kb 0276 continue; 0277 0278 bool success = false; 0279 0280 success = cover.loadFromData( image_data ); 0281 if( !success ) 0282 success = cover.loadFromData( image_data_b64 ); 0283 0284 if( success ) 0285 break; 0286 } 0287 } 0288 0289 return cover; 0290 } 0291 0292 bool 0293 VorbisCommentTagHelper::setEmbeddedCover( const QImage &cover ) 0294 { 0295 // NOTE since the COVERART tag is not standardized and a proper way has been defined recently [1], 0296 // we should wait until taglib provides an implementation. 0297 // [1] http://wiki.xiph.org/index.php/VorbisComment#Cover_art 0298 0299 if( m_flacFile ) 0300 { 0301 QByteArray bytes; 0302 QBuffer buffer( &bytes ); 0303 0304 buffer.open( QIODevice::WriteOnly ); 0305 0306 if( !cover.save( &buffer, "JPEG" ) ) 0307 { 0308 buffer.close(); 0309 return false; 0310 } 0311 0312 buffer.close(); 0313 0314 // remove all covers 0315 m_flacFile->removePictures(); 0316 0317 // add new cover 0318 TagLib::FLAC::Picture *newPicture = new TagLib::FLAC::Picture(); 0319 newPicture->setData( TagLib::ByteVector( bytes.data(), bytes.size() ) ); 0320 newPicture->setMimeType( "image/jpeg" ); 0321 newPicture->setType( TagLib::FLAC::Picture::FrontCover ); 0322 m_flacFile->addPicture( newPicture ); 0323 0324 return true; 0325 } 0326 0327 return false; 0328 } 0329 0330 bool 0331 VorbisCommentTagHelper::parsePictureBlock( const TagLib::StringList& block, QImage* result ) 0332 { 0333 QImage otherCover; 0334 // Here's what's happening: "block" may contain several FLAC picture entries. 0335 // We need to find at least one that satisfies our needs. 0336 for( TagLib::StringList::ConstIterator i = block.begin(); i != block.end(); ++i ) 0337 { 0338 QByteArray data( QByteArray::fromBase64( i->to8Bit().c_str() ) ); 0339 TagLib::ByteVector tdata( data.data(), data.size() ); 0340 TagLib::FLAC::Picture p; 0341 0342 if(!p.parse(tdata)) 0343 continue; 0344 if(isAlbumCover(&p)) 0345 { 0346 if( result ) 0347 { 0348 // Now, if the image is a front cover, we just use it and quit 0349 // Otherwise, we store it in otherCover and wait for a better one 0350 if( flacPictureToQImage( &p, *result, otherCover ) ) 0351 return true; 0352 } 0353 else 0354 // We found some image, but we don't need best one here, so just leave 0355 return true; 0356 } 0357 } 0358 if(result) 0359 { 0360 // Now here we haven't found any front covers in the file 0361 // So we just use otherCover and exit 0362 *result = otherCover; 0363 return !result->isNull(); 0364 } 0365 return false; 0366 }