File indexing completed on 2024-04-28 04:45:53

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 }