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

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 #include "ID3v2TagHelper.h"
0024 
0025 #include "StringHelper.h"
0026 
0027 #include <QBuffer>
0028 #include <QImage>
0029 
0030 #include <attachedpictureframe.h>
0031 #include <popularimeterframe.h>
0032 #include <textidentificationframe.h>
0033 #include <uniquefileidentifierframe.h>
0034 #include <unsynchronizedlyricsframe.h>
0035 
0036 const TagLib::ByteVector TXXX_Frame = "TXXX";
0037 const TagLib::ByteVector POPM_Frame = "POPM";
0038 
0039 using namespace Meta::Tag;
0040 
0041 ID3v2TagHelper::ID3v2TagHelper( TagLib::Tag *tag, TagLib::ID3v2::Tag *id3v2Tag, Amarok::FileType fileType )
0042               : TagHelper( tag, fileType )
0043               , m_tag( id3v2Tag )
0044 {
0045     m_fieldMap.insert( Meta::valAlbumArtist, TagLib::String( "TPE2" ) );
0046     m_fieldMap.insert( Meta::valBpm,         TagLib::String( "TBPM" ) );
0047     m_fieldMap.insert( Meta::valCompilation, TagLib::String( "TCMP" ) );
0048     m_fieldMap.insert( Meta::valComposer,    TagLib::String( "TCOM" ) );
0049     m_fieldMap.insert( Meta::valDiscNr,      TagLib::String( "TPOS" ) );
0050     m_fieldMap.insert( Meta::valHasCover,    TagLib::String( "APIC" ) );
0051     m_fieldMap.insert( Meta::valUniqueId,    TagLib::String( "UFID" ) );
0052 
0053     m_fieldMap.insert( Meta::valLyrics,      TagLib::String( "USLT" ) );
0054 
0055     m_fmpsFieldMap.insert( FMPSPlayCount,    TagLib::String( "FMPS_Playcount" ) );
0056     m_fmpsFieldMap.insert( FMPSRating,       TagLib::String( "FMPS_Rating" ) );
0057     m_fmpsFieldMap.insert( FMPSScore,        TagLib::String( "FMPS_Rating_Amarok_Score" ) );
0058 
0059     m_uidFieldMap.insert( UIDAFT,            TagLib::String( "Amarok 2 AFTv1 - amarok.kde.org" ) );
0060 }
0061 
0062 Meta::FieldHash
0063 ID3v2TagHelper::tags() const
0064 {
0065     Meta::FieldHash data = TagHelper::tags();
0066 
0067     TagLib::ID3v2::FrameList list = m_tag->frameList();
0068     for( TagLib::ID3v2::FrameList::ConstIterator it = list.begin(); it != list.end(); ++it )
0069     {
0070         qint64 field;
0071         TagLib::String frameName  = TagLib::String( ( *it )->frameID() );
0072         if( ( field = fieldName( frameName ) ) )
0073         {
0074             if( field == Meta::valUniqueId )
0075             {
0076                 TagLib::ID3v2::UniqueFileIdentifierFrame *frame =
0077                         dynamic_cast< TagLib::ID3v2::UniqueFileIdentifierFrame * >( *it );
0078 
0079                 if( !frame )
0080                     continue;
0081 
0082                 QString identifier = TStringToQString( TagLib::String( frame->identifier() ) );
0083 
0084                 if( identifier.isEmpty() )
0085                     continue;
0086 
0087                 if( frame->owner() == uidFieldName( UIDAFT ) && isValidUID( identifier, UIDAFT ) )
0088                     data.insert( Meta::valUniqueId, identifier );
0089                 continue;
0090             }
0091             else if( field == Meta::valHasCover )
0092             {
0093                 TagLib::ID3v2::AttachedPictureFrame *frame =
0094                         dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *it );
0095 
0096                 if( !frame )
0097                     continue;
0098 
0099                 if( ( frame->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover ||
0100                       frame->type() == TagLib::ID3v2::AttachedPictureFrame::Other ) &&
0101                     frame->picture().size() > MIN_COVER_SIZE ) // must be at least 1kb
0102                 {
0103                     data.insert( Meta::valHasCover, true );
0104                 }
0105                 continue;
0106             }
0107             else if( field == Meta::valLyrics )
0108             {
0109                 TagLib::ID3v2::UnsynchronizedLyricsFrame *lyricsFrame =
0110                     dynamic_cast<TagLib::ID3v2::UnsynchronizedLyricsFrame*>( *it );
0111                 if( lyricsFrame && !data.contains( Meta::valLyrics ) ) // only read the first frame for valLyrics
0112                     data.insert( Meta::valLyrics, TStringToQString( lyricsFrame->text() ) );
0113                 continue;
0114             }
0115 
0116             TagLib::ID3v2::TextIdentificationFrame *frame =
0117                     dynamic_cast< TagLib::ID3v2::TextIdentificationFrame * >( *it );
0118 
0119             if( !frame )
0120                 continue;
0121 
0122             QString value = TStringToQString( frame->fieldList().toString( '\n' ) );
0123 
0124             if( field == Meta::valDiscNr )
0125             {
0126                 int disc;
0127                 if( ( disc = splitDiscNr( value ).first ) )
0128                     data.insert( field, disc );
0129             }
0130             else if( field == Meta::valBpm )
0131                 data.insert( field, value.toFloat() );
0132             else
0133                 data.insert( field, value );
0134         }
0135         else if( frameName == POPM_Frame )
0136         {
0137             TagLib::ID3v2::PopularimeterFrame *frame =
0138                     dynamic_cast< TagLib::ID3v2::PopularimeterFrame * >( *it );
0139 
0140             if( !frame )
0141                 continue;
0142 
0143             if( TStringToQString( frame->email() ).isEmpty() ) // only read anonymous ratings
0144             {
0145                 // FMPS tags have precedence
0146                 if( !data.contains( Meta::valRating ) && frame->rating() != 0 )
0147                     data.insert( Meta::valRating, qRound( frame->rating() / 256.0 * 10.0 ) );
0148                 if( !data.contains( Meta::valPlaycount ) && frame->counter() < 10000 )
0149                     data.insert( Meta::valPlaycount, frame->counter() );
0150             }
0151         }
0152         else if( frameName == TXXX_Frame )
0153         {
0154             TagLib::ID3v2::UserTextIdentificationFrame *frame =
0155                     dynamic_cast< TagLib::ID3v2::UserTextIdentificationFrame * >( *it );
0156 
0157             if( !frame )
0158                 continue;
0159 
0160             // the value of the user text frame is stored in the
0161             // second and following fields.
0162             TagLib::StringList fields = frame->fieldList();
0163             if( fields.size() >= 2 )
0164             {
0165                 QString value = TStringToQString( fields[1] );
0166 
0167                 if( fields[0] == fmpsFieldName( FMPSRating ) )
0168                     data.insert( Meta::valRating, qRound( value.toFloat() * 10.0 ) );
0169                 else if( fields[0] == fmpsFieldName( FMPSScore ) )
0170                     data.insert( Meta::valScore, value.toFloat() * 100.0 );
0171                 else if( fields[0] == fmpsFieldName( FMPSPlayCount ) )
0172                     data.insert( Meta::valPlaycount, value.toFloat() );
0173             }
0174         }
0175     }
0176 
0177     return data;
0178 }
0179 
0180 bool
0181 ID3v2TagHelper::setTags( const Meta::FieldHash &changes )
0182 {
0183     bool modified = TagHelper::setTags( changes );
0184 
0185     foreach( const qint64 key, changes.keys() )
0186     {
0187         QVariant value = changes.value( key );
0188         TagLib::ByteVector field( fieldName( key ).toCString() );
0189 
0190         if( !field.isNull() && !field.isEmpty() )
0191         {
0192             if( key == Meta::valHasCover )
0193                 continue;
0194             else if( key == Meta::valUniqueId )
0195             {
0196                 QPair< UIDType, QString > uidPair = splitUID( value.toString() );
0197                 if( uidPair.first == UIDInvalid )
0198                     continue;
0199 
0200                 TagLib::String owner  = uidFieldName( uidPair.first );
0201                 TagLib::ByteVector uid( uidPair.second.toLatin1().data() );
0202                 TagLib::ID3v2::FrameList list = m_tag->frameList();
0203 
0204                 for( TagLib::ID3v2::FrameList::ConstIterator it = list.begin(); it != list.end(); ++it )
0205                 {
0206                     if( ( *it )->frameID() == field )
0207                     {
0208                         TagLib::ID3v2::UniqueFileIdentifierFrame *frame =
0209                                 dynamic_cast< TagLib::ID3v2::UniqueFileIdentifierFrame * >( *it );
0210                         if( !frame )
0211                             continue;
0212 
0213                         if( frame->owner() == owner )
0214                         {
0215                             m_tag->removeFrame( frame );
0216                             modified = true;
0217                             break;
0218                         }
0219                     }
0220                 }
0221 
0222                 if ( !uid.isEmpty() )
0223                 {
0224                     m_tag->addFrame( new TagLib::ID3v2::UniqueFileIdentifierFrame( owner, uid ) );
0225                     modified = true;
0226                 }
0227                 continue;
0228             }
0229             else if( key == Meta::valLyrics )
0230             {
0231                 if( !m_tag->frameList( field ).isEmpty() )
0232                 {
0233                     m_tag->removeFrames( field );
0234                     modified = true;
0235                 }
0236                 QString lyrics = changes.value( key ).toString();
0237                 if( lyrics.isEmpty() )
0238                     continue;
0239                 TagLib::ID3v2::UnsynchronizedLyricsFrame *frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame( TagLib::String::UTF8 );
0240                 frame->setText( Qt4QStringToTString( lyrics ) );
0241                 m_tag->addFrame( frame );
0242                 modified = true;
0243                 continue;
0244             }
0245 
0246             TagLib::String tValue = Qt4QStringToTString( ( key == Meta::valCompilation )
0247                                                          ? QString::number( value.toInt() )
0248                                                          : value.toString() );
0249             if( tValue.isEmpty() )
0250                 m_tag->removeFrames( field );
0251             else
0252             {
0253                 TagLib::ID3v2::TextIdentificationFrame *frame = nullptr;
0254                 if( !m_tag->frameListMap()[field].isEmpty() )
0255                     frame = dynamic_cast< TagLib::ID3v2::TextIdentificationFrame * >(
0256                                                   m_tag->frameListMap()[field].front()
0257                                                                                     );
0258                 if( !frame )
0259                 {
0260                     frame = new TagLib::ID3v2::TextIdentificationFrame( field );
0261                     m_tag->addFrame( frame );
0262                 }
0263                 // note: TagLib is smart enough to automatically set UTF8 encoding if needed.
0264                 frame->setText( tValue );
0265             }
0266             modified = true;
0267         }
0268         else if( key == Meta::valScore || key == Meta::valRating || key == Meta::valPlaycount )
0269         {
0270             TagLib::String description;
0271             TagLib::String tValue;
0272 
0273             if( key == Meta::valRating )
0274             {
0275                 description = fmpsFieldName( FMPSRating );
0276                 tValue = Qt4QStringToTString( QString::number( value.toFloat() / 10.0 ) );
0277             }
0278             else if( key == Meta::valScore )
0279             {
0280                 description = fmpsFieldName( FMPSScore );
0281                 tValue = Qt4QStringToTString( QString::number( value.toFloat() / 100.0 ) );
0282             }
0283             else if( key == Meta::valPlaycount )
0284             {
0285                 description = fmpsFieldName( FMPSPlayCount );
0286                 tValue = Qt4QStringToTString( QString::number( value.toInt() ) );
0287             }
0288 
0289             if( key == Meta::valRating || key == Meta::valPlaycount )
0290             {
0291                 TagLib::ID3v2::PopularimeterFrame *popFrame = nullptr;
0292                 if( !m_tag->frameListMap()[POPM_Frame].isEmpty() )
0293                     popFrame = dynamic_cast< TagLib::ID3v2::PopularimeterFrame * >( m_tag->frameListMap()[POPM_Frame].front() );
0294 
0295                 if( !popFrame )
0296                 {
0297                     popFrame = new TagLib::ID3v2::PopularimeterFrame( POPM_Frame );
0298                     m_tag->addFrame( popFrame );
0299                 }
0300 
0301                 if( key == Meta::valRating )
0302                     popFrame->setRating( qBound(0, int(qRound(value.toDouble() / 10.0 * 256)), 255) );
0303                 else
0304                     popFrame->setCounter( value.toInt() );
0305 
0306                 modified = true;
0307             }
0308 
0309             TagLib::ID3v2::FrameList list = m_tag->frameList();
0310             for( TagLib::ID3v2::FrameList::ConstIterator it = list.begin(); it != list.end(); ++it )
0311             {
0312                 if( ( *it )->frameID() == TXXX_Frame )
0313                 {
0314                     TagLib::ID3v2::UserTextIdentificationFrame *frame =
0315                             dynamic_cast< TagLib::ID3v2::UserTextIdentificationFrame * >( *it );
0316                     if( !frame )
0317                         continue;
0318 
0319                     if( frame->description() == description )
0320                     {
0321                         m_tag->removeFrame( frame );
0322                         modified = true;
0323                         break;
0324                     }
0325                 }
0326             }
0327 
0328             if( value.toBool() )
0329             {
0330                 TagLib::ID3v2::UserTextIdentificationFrame *frame =
0331                         new TagLib::ID3v2::UserTextIdentificationFrame( TXXX_Frame );
0332 
0333                 frame->setDescription( description );
0334                 frame->setText( tValue );
0335                 m_tag->addFrame( frame );
0336                 modified = true;
0337             }
0338         }
0339     }
0340 
0341     return modified;
0342 }
0343 
0344 TagLib::ByteVector
0345 ID3v2TagHelper::render() const
0346 {
0347     return m_tag->render();
0348 }
0349 
0350 bool
0351 ID3v2TagHelper::hasEmbeddedCover() const
0352 {
0353     TagLib::ID3v2::FrameList apicList = m_tag->frameListMap()[fieldName( Meta::valHasCover ).toCString()];
0354 
0355     for( TagLib::ID3v2::FrameList::ConstIterator it = apicList.begin(); it != apicList.end(); ++it )
0356     {
0357         TagLib::ID3v2::AttachedPictureFrame *currFrame =
0358                 dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *it );
0359 
0360         if( currFrame->picture().size() < MIN_COVER_SIZE )
0361             continue;
0362 
0363         if( currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover ||
0364             currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::Other )
0365             return true;
0366     }
0367 
0368     return false;
0369 }
0370 
0371 QImage
0372 ID3v2TagHelper::embeddedCover() const
0373 {
0374     TagLib::ID3v2::FrameList apicList = m_tag->frameListMap()[fieldName( Meta::valHasCover ).toCString()];
0375     TagLib::ID3v2::AttachedPictureFrame *cover = nullptr;
0376     TagLib::ID3v2::AttachedPictureFrame *otherCover = nullptr;
0377 
0378     for( TagLib::ID3v2::FrameList::ConstIterator it = apicList.begin(); it != apicList.end(); ++it )
0379     {
0380         TagLib::ID3v2::AttachedPictureFrame *currFrame =
0381                 dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *it );
0382 
0383         if( currFrame->picture().size() < MIN_COVER_SIZE )
0384             continue;
0385 
0386         if( currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::FrontCover )
0387         {
0388             cover = currFrame;
0389         }
0390         else if( currFrame->type() == TagLib::ID3v2::AttachedPictureFrame::Other )
0391         {
0392             otherCover = currFrame;
0393         }
0394     }
0395 
0396     if( !cover && otherCover )
0397         cover = otherCover;
0398 
0399     if( !cover )
0400         return QImage();
0401 
0402     return QImage::fromData( ( uchar * )( cover->picture().data() ), cover->picture().size() );
0403 }
0404 
0405 bool
0406 ID3v2TagHelper::setEmbeddedCover( const QImage &cover )
0407 {
0408     QByteArray bytes;
0409     QBuffer buffer( &bytes );
0410 
0411     buffer.open( QIODevice::WriteOnly );
0412 
0413     if( !cover.save( &buffer, "JPEG" ) )
0414     {
0415         buffer.close();
0416         return false;
0417     }
0418 
0419     buffer.close();
0420 
0421     TagLib::ByteVector field = fieldName( Meta::valHasCover ).toCString();
0422     TagLib::ID3v2::FrameList apicList = m_tag->frameListMap()[field];
0423     TagLib::ID3v2::AttachedPictureFrame *frontCover = nullptr;
0424 
0425     // remove covers
0426     TagLib::List<TagLib::ID3v2::AttachedPictureFrame*> backedUpPictures;
0427     for( TagLib::ID3v2::FrameList::ConstIterator it = apicList.begin(); it != apicList.end(); ++it )
0428     {
0429         TagLib::ID3v2::AttachedPictureFrame *currFrame =
0430                 dynamic_cast< TagLib::ID3v2::AttachedPictureFrame * >( *it );
0431 
0432         m_tag->removeFrame( currFrame, false );
0433     }
0434 
0435     // add new cover
0436     frontCover = new TagLib::ID3v2::AttachedPictureFrame( field );
0437     frontCover->setMimeType( "image/jpeg" );
0438     frontCover->setPicture( TagLib::ByteVector( bytes.data(), bytes.count() ) );
0439     frontCover->setType( TagLib::ID3v2::AttachedPictureFrame::FrontCover );
0440     m_tag->addFrame( frontCover );
0441 
0442     return true;
0443 }