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 }