File indexing completed on 2024-05-12 16:20:56
0001 /* 0002 * Copyright (c) 2008-2009 Jeff Mitchell <mitchell@kde.org> 0003 * QStringToTString and TStringToQString macros Copyright 2002-2008 by Scott Wheeler, wheeler@kde.org, licensed under LGPL 2.1 0004 * 0005 * This program is free software; you can redistribute it and/or modify 0006 * it under the terms of the GNU General Public License as published by 0007 * the Free Software Foundation; either version 2 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This program is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU General Public License 0016 * along with this program; if not, write to the Free Software 0017 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "AFTTagger.h" 0021 #include "SafeFileSaver.h" 0022 0023 //Taglib 0024 #pragma GCC diagnostic push 0025 #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" 0026 #include <apetag.h> 0027 #include <fileref.h> 0028 #include <flacfile.h> 0029 #include <id3v2tag.h> 0030 #include <mp4tag.h> 0031 #include <mpegfile.h> 0032 #include <oggfile.h> 0033 #include <oggflacfile.h> 0034 #include <speexfile.h> 0035 #include <tstringlist.h> 0036 #include <tfile.h> 0037 #include <uniquefileidentifierframe.h> 0038 #include <vorbisfile.h> 0039 #include <xiphcomment.h> 0040 #pragma GCC diagnostic pop 0041 0042 #include <QtDebug> 0043 #include <QCryptographicHash> 0044 #include <QDateTime> 0045 #include <QDir> 0046 #include <QFileInfo> 0047 #include <QRandomGenerator> 0048 #include <QString> 0049 #include <QTextStream> 0050 0051 #include <iostream> 0052 0053 //QT5-happy versions 0054 #define Qt5QStringToTString(s) TagLib::String(s.toUtf8().data(), TagLib::String::UTF8) 0055 0056 static int s_currentVersion = 1; 0057 0058 int main( int argc, char *argv[] ) 0059 { 0060 AFTTagger tagger( argc, argv ); 0061 return tagger.exec(); 0062 } 0063 0064 AFTTagger::AFTTagger( int &argc, char **argv ) 0065 :QCoreApplication( argc, argv ) 0066 , m_delete( false ) 0067 , m_newid( false ) 0068 , m_quiet( false ) 0069 , m_recurse( false ) 0070 , m_verbose( false ) 0071 , m_fileFolderList() 0072 , m_time() 0073 , m_textStream( stderr ) 0074 { 0075 0076 setObjectName( QStringLiteral("amarok_afttagger") ); 0077 0078 readArgs(); 0079 0080 QString terms; 0081 if( !m_quiet ) 0082 { 0083 m_textStream << qPrintable( tr( "TERMS OF USE:\n\n" 0084 "This program has been extensively tested and errs on the side of safety wherever possible.\n\n" 0085 "With that being said, since this program can modify thousands or hundreds of thousands of files\n" 0086 "at a time, here is the obligatory warning text:\n\n" 0087 "This program makes use of multiple libraries not written by the author, and as such neither\n" 0088 "the author nor the Amarok project can or do take any responsibility for any damage that may\n" 0089 "occur to your files through the use of this program.\n\n" 0090 "If you want more information, please see http://community.kde.org/Amarok/Development/AFT\n\n" 0091 "If you agree to be bound by these terms of use, enter 'y' or 'Y', or anything else to exit:\n" ) ); 0092 0093 m_textStream.flush(); 0094 std::string response; 0095 std::cin >> response; 0096 std::cin.get(); 0097 0098 if( response != "y" && response != "Y") 0099 { 0100 m_textStream << tr( "INFO: Terms not accepted; exiting..." ) << Qt::endl; 0101 ::exit( 1 ); 0102 } 0103 } 0104 0105 m_time.start(); 0106 0107 foreach( const QString &path, m_fileFolderList ) 0108 processPath( path ); 0109 0110 m_textStream << tr( "INFO: All done, exiting..." ) << Qt::endl; 0111 ::exit( 0 ); 0112 } 0113 0114 void 0115 AFTTagger::processPath( const QString &path ) 0116 { 0117 QFileInfo info( path ); 0118 if( !info.isDir() && !info.isFile() ) 0119 { 0120 if( m_verbose ) 0121 m_textStream << tr( "INFO: Skipping %1 because it is neither a directory nor file." ).arg( path ) << Qt::endl; 0122 return; 0123 } 0124 if( info.isDir() ) 0125 { 0126 if( !m_recurse ) 0127 { 0128 if( m_verbose ) 0129 m_textStream << tr( "INFO: Skipping %1 because it is a directory and recursion is not specified." ).arg( path ) << Qt::endl; 0130 return; 0131 } 0132 else 0133 { 0134 if( m_verbose ) 0135 m_textStream << tr( "INFO: Processing directory %1" ).arg( path ) << Qt::endl; 0136 foreach( const QString &pathEntry, QDir( path ).entryList() ) 0137 { 0138 if( pathEntry != QLatin1String(".") && pathEntry != QLatin1String("..") ) 0139 processPath( QDir( path ).canonicalPath() + QLatin1Char('/') + pathEntry ); 0140 } 0141 } 0142 } 0143 else //isFile() 0144 { 0145 QString filePath = info.absoluteFilePath(); 0146 0147 0148 #ifdef COMPLEX_TAGLIB_FILENAME 0149 const wchar_t *encodedName = reinterpret_cast< const wchar_t *>(filePath.utf16()); 0150 #else 0151 QByteArray fileName = QFile::encodeName( filePath ); 0152 const char *encodedName = fileName.constData(); 0153 #endif 0154 0155 TagLib::FileRef fileRef = TagLib::FileRef( encodedName, true, TagLib::AudioProperties::Fast ); 0156 0157 if( fileRef.isNull() ) 0158 { 0159 if( m_verbose ) 0160 m_textStream << tr( "INFO: file %1 not able to be opened by TagLib" ).arg( filePath ) << Qt::endl; 0161 return; 0162 } 0163 0164 m_textStream << tr( "INFO: Processing file %1" ).arg( filePath ) << Qt::endl; 0165 0166 SafeFileSaver sfs( filePath ); 0167 sfs.setVerbose( false ); 0168 sfs.setPrefix( QStringLiteral("amarok-afttagger") ); 0169 QString tempFilePath = sfs.prepareToSave(); 0170 if( tempFilePath.isEmpty() ) 0171 { 0172 m_textStream << tr( "Error: could not create temporary file when processing %1" ).arg( filePath ) << Qt::endl; 0173 return; 0174 } 0175 0176 if( m_verbose ) 0177 m_textStream << tr( "INFO: Temporary file is at %1").arg( tempFilePath ) << Qt::endl; 0178 0179 #ifdef COMPLEX_TAGLIB_FILENAME 0180 const wchar_t *encodedName = reinterpret_cast< const wchar_t * >(tempFilePath.utf16()); 0181 #else 0182 QByteArray tempFileName = QFile::encodeName( tempFilePath ); 0183 const char *tempEncodedName = tempFileName.constData(); 0184 #endif 0185 0186 bool saveNecessary = false; 0187 0188 TagLib::FileRef tempFileRef = TagLib::FileRef( tempEncodedName, true, TagLib::AudioProperties::Fast ); 0189 if( TagLib::MPEG::File *file = dynamic_cast<TagLib::MPEG::File *>( tempFileRef.file() ) ) 0190 saveNecessary = handleMPEG( file ); 0191 else if( TagLib::Ogg::File *file = dynamic_cast<TagLib::Ogg::File *>( tempFileRef.file() ) ) 0192 saveNecessary = handleOgg( file ); 0193 else if( TagLib::FLAC::File *file = dynamic_cast<TagLib::FLAC::File *>( tempFileRef.file() ) ) 0194 saveNecessary = handleFLAC( file ); 0195 else if( TagLib::MPC::File *file = dynamic_cast<TagLib::MPC::File *>( tempFileRef.file() ) ) 0196 saveNecessary = handleMPC( file ); 0197 else if( TagLib::MP4::File *file = dynamic_cast<TagLib::MP4::File *>( tempFileRef.file() ) ) 0198 saveNecessary = handleMP4( file ); 0199 else 0200 { 0201 if( m_verbose ) 0202 m_textStream << tr( "INFO: File not able to be parsed by TagLib or wrong kind (currently this program only supports MPEG, Ogg MP4, MPC, and FLAC files), cleaning up temp file" ) << Qt::endl; 0203 if( !sfs.cleanupSave() ) 0204 m_textStream << tr( "WARNING: file at %1 could not be cleaned up; check for strays" ).arg( filePath ) << Qt::endl; 0205 return; 0206 } 0207 if( saveNecessary ) 0208 { 0209 if( m_verbose ) 0210 m_textStream << tr( "INFO: Safe-saving file" ) << Qt::endl; 0211 if( !sfs.doSave() ) 0212 m_textStream << tr( "WARNING: file at %1 could not be saved" ).arg( filePath ) << Qt::endl; 0213 } 0214 if( m_verbose ) 0215 m_textStream << tr( "INFO: Cleaning up..." ) << Qt::endl; 0216 if( !sfs.cleanupSave() ) 0217 m_textStream << tr( "WARNING: file at %1 could not be cleaned up; check for strays" ).arg( filePath ) << Qt::endl; 0218 return; 0219 } 0220 } 0221 0222 bool 0223 AFTTagger::handleMPEG( TagLib::MPEG::File *file ) 0224 { 0225 if( file->readOnly() ) 0226 { 0227 m_textStream << tr( "ERROR: File is read-only or could not be opened" ) << Qt::endl; 0228 return false; 0229 } 0230 0231 QString uid; 0232 bool newUid = false; 0233 bool nothingfound = true; 0234 if( m_verbose ) 0235 m_textStream << tr( "INFO: File is a MPEG file, opening..." ) << Qt::endl; 0236 if ( file->ID3v2Tag( true ) ) 0237 { 0238 if( file->ID3v2Tag()->frameListMap()["UFID"].isEmpty() ) 0239 { 0240 if( m_verbose ) 0241 m_textStream << tr( "INFO: No UFID frames found" ) << Qt::endl; 0242 0243 if( m_delete ) 0244 return false; 0245 0246 newUid = true; 0247 } 0248 else 0249 { 0250 if( m_verbose ) 0251 m_textStream << tr( "INFO: Found existing UFID frames, parsing" ) << Qt::endl; 0252 TagLib::ID3v2::FrameList frameList = file->ID3v2Tag()->frameListMap()["UFID"]; 0253 TagLib::ID3v2::FrameList::Iterator iter; 0254 if( m_verbose ) 0255 m_textStream << tr( "INFO: Frame list size is %1" ).arg( frameList.size() ) << Qt::endl; 0256 for( iter = frameList.begin(); iter != frameList.end(); ++iter ) 0257 { 0258 TagLib::ID3v2::UniqueFileIdentifierFrame* currFrame = dynamic_cast<TagLib::ID3v2::UniqueFileIdentifierFrame*>(*iter); 0259 if( currFrame ) 0260 { 0261 QString owner = TStringToQString( currFrame->owner() ).toUpper(); 0262 if( owner.startsWith( QLatin1String("AMAROK - REDISCOVER YOUR MUSIC") ) ) 0263 { 0264 nothingfound = false; 0265 if( m_verbose ) 0266 m_textStream << tr( "INFO: Removing old-style ATF identifier" ) << Qt::endl; 0267 0268 iter = frameList.erase( iter ); 0269 file->ID3v2Tag()->removeFrame( currFrame ); 0270 file->save(); 0271 if( !m_delete ) 0272 newUid = true; 0273 else 0274 return true; 0275 } 0276 if( owner.startsWith( QLatin1String("AMAROK 2 AFT") ) ) 0277 { 0278 nothingfound = false; 0279 if( m_verbose ) 0280 m_textStream << tr( "INFO: Found an existing AFT identifier: %1" ).arg( TStringToQString( TagLib::String( currFrame->identifier() ) ) ) << Qt::endl; 0281 0282 if( m_delete ) 0283 { 0284 iter = frameList.erase( iter ); 0285 if( m_verbose ) 0286 m_textStream << tr( "INFO: Removing current AFT frame" ) << Qt::endl; 0287 file->ID3v2Tag()->removeFrame( currFrame ); 0288 file->save(); 0289 return true; 0290 } 0291 0292 int version = owner.at( 13 ).digitValue(); 0293 if( version < s_currentVersion ) 0294 { 0295 if( m_verbose ) 0296 m_textStream << tr( "INFO: Upgrading AFT identifier from version %1 to version %2" ).arg( version, s_currentVersion ) << Qt::endl; 0297 uid = upgradeUID( version, TStringToQString( TagLib::String( currFrame->identifier() ) ) ); 0298 if( m_verbose ) 0299 m_textStream << tr( "INFO: Removing current AFT frame" ) << Qt::endl; 0300 iter = frameList.erase( iter ); 0301 file->ID3v2Tag()->removeFrame( currFrame ); 0302 newUid = true; 0303 } 0304 else if( version == s_currentVersion && m_newid ) 0305 { 0306 if( m_verbose ) 0307 m_textStream << tr( "INFO: New IDs specified to be generated, doing so" ) << Qt::endl; 0308 iter = frameList.erase( iter ); 0309 file->ID3v2Tag()->removeFrame( currFrame ); 0310 newUid = true; 0311 } 0312 else 0313 { 0314 if( m_verbose ) 0315 m_textStream << tr( "INFO: ID is current" ) << Qt::endl; 0316 } 0317 } 0318 } 0319 } 0320 } 0321 if( newUid || ( nothingfound && !m_delete ) ) 0322 { 0323 QString ourId = QString( "Amarok 2 AFTv" + QString::number( s_currentVersion ) + " - amarok.kde.org" ); 0324 if( uid.isEmpty() ) 0325 uid = createCurrentUID( file ); 0326 if( m_verbose ) 0327 m_textStream << tr( "INFO: Adding new frame and saving file with UID: %1" ).arg( uid ) << Qt::endl; 0328 file->ID3v2Tag()->addFrame( new TagLib::ID3v2::UniqueFileIdentifierFrame( 0329 Qt5QStringToTString( ourId ), Qt5QStringToTString( uid ).data( TagLib::String::Latin1 ) ) ); 0330 file->save(); 0331 return true; 0332 } 0333 } 0334 return false; 0335 } 0336 0337 bool 0338 AFTTagger::handleOgg( TagLib::Ogg::File *file ) 0339 { 0340 if( file->readOnly() ) 0341 { 0342 m_textStream << tr( "ERROR: File is read-only or could not be opened" ) << Qt::endl; 0343 return false; 0344 } 0345 0346 TagLib::Ogg::XiphComment *comment = dynamic_cast<TagLib::Ogg::XiphComment *>( file->tag() ); 0347 0348 if( !comment ) 0349 return false; 0350 0351 if( handleXiphComment( comment, file ) ) 0352 { 0353 file->save(); 0354 return true; 0355 } 0356 0357 return false; 0358 } 0359 0360 bool 0361 AFTTagger::handleFLAC( TagLib::FLAC::File *file ) 0362 { 0363 if( file->readOnly() ) 0364 { 0365 m_textStream << tr( "ERROR: File is read-only or could not be opened" ) << Qt::endl; 0366 return false; 0367 } 0368 0369 TagLib::Ogg::XiphComment *comment = file->xiphComment( true ); 0370 if( !comment ) 0371 return false; 0372 0373 if( handleXiphComment( comment, file ) ) 0374 { 0375 file->save(); 0376 return true; 0377 } 0378 0379 return false; 0380 } 0381 0382 bool 0383 AFTTagger::handleXiphComment( TagLib::Ogg::XiphComment *comment, TagLib::File *file ) 0384 { 0385 QString uid; 0386 bool newUid = false; 0387 bool nothingfound = true; 0388 TagLib::StringList toRemove; 0389 if( m_verbose ) 0390 m_textStream << tr( "INFO: File has a XiphComment, opening..." ) << Qt::endl; 0391 0392 if( comment->fieldListMap().isEmpty() ) 0393 { 0394 if( m_verbose ) 0395 m_textStream << tr( "INFO: No fields found in XiphComment" ) << Qt::endl; 0396 0397 if( m_delete ) 0398 return false; 0399 } 0400 else 0401 { 0402 if( m_verbose ) 0403 m_textStream << tr( "INFO: Found existing XiphComment frames, parsing" ) << Qt::endl; 0404 TagLib::Ogg::FieldListMap fieldListMap = comment->fieldListMap(); 0405 0406 if( m_verbose ) 0407 m_textStream << tr( "INFO: fieldListMap size is %1" ).arg( fieldListMap.size() ) << Qt::endl; 0408 0409 TagLib::Ogg::FieldListMap::Iterator iter; 0410 for( iter = fieldListMap.begin(); iter != fieldListMap.end(); ++iter ) 0411 { 0412 TagLib::String key = iter->first; 0413 QString qkey = TStringToQString( key ).toUpper(); 0414 if( qkey.startsWith( QLatin1String("AMAROK - REDISCOVER YOUR MUSIC") ) ) 0415 { 0416 nothingfound = false; 0417 0418 if( m_verbose ) 0419 m_textStream << tr( "INFO: Removing old-style ATF identifier %1" ).arg( qkey ) << Qt::endl; 0420 0421 toRemove.append( key ); 0422 if( !m_delete ) 0423 newUid = true; 0424 } 0425 else if( qkey.startsWith( QLatin1String("AMAROK 2 AFT") ) ) 0426 { 0427 nothingfound = false; 0428 0429 if( m_verbose ) 0430 m_textStream << tr( "INFO: Found an existing AFT identifier: %1" ).arg( qkey ) << Qt::endl; 0431 0432 if( m_delete ) 0433 { 0434 toRemove.append( key ); 0435 if( m_verbose ) 0436 m_textStream << tr( "INFO: Removing current AFT frame" ) << Qt::endl; 0437 } 0438 else 0439 { 0440 int version = qkey.at( 13 ).digitValue(); 0441 if( m_verbose ) 0442 m_textStream << tr( "INFO: AFT identifier is version %1" ).arg( version ) << Qt::endl; 0443 if( version < s_currentVersion ) 0444 { 0445 if( m_verbose ) 0446 m_textStream << tr( "INFO: Upgrading AFT identifier from version %1 to version %2" ).arg( version, s_currentVersion ) << Qt::endl; 0447 uid = upgradeUID( version, TStringToQString( fieldListMap[key].front() ) ); 0448 if( m_verbose ) 0449 m_textStream << tr( "INFO: Removing current AFT frame" ) << Qt::endl; 0450 toRemove.append( key ); 0451 newUid = true; 0452 } 0453 else if( version == s_currentVersion && m_newid ) 0454 { 0455 if( m_verbose ) 0456 m_textStream << tr( "INFO: New IDs specified to be generated, doing so" ) << Qt::endl; 0457 toRemove.append( key ); 0458 newUid = true; 0459 } 0460 else 0461 { 0462 if( m_verbose ) 0463 m_textStream << tr( "INFO: ID is current" ) << Qt::endl; 0464 return false; 0465 } 0466 } 0467 } 0468 } 0469 for( TagLib::StringList::ConstIterator iter = toRemove.begin(); iter != toRemove.end(); ++iter ) 0470 comment->removeField( *iter ); 0471 } 0472 if( newUid || ( nothingfound && !m_delete ) ) 0473 { 0474 QString ourId = QString( "Amarok 2 AFTv" + QString::number( s_currentVersion ) + " - amarok.kde.org" ); 0475 if( uid.isEmpty() ) 0476 uid = createCurrentUID( file ); 0477 if( m_verbose ) 0478 m_textStream << tr( "INFO: Adding new field and saving file with UID: %1" ).arg( uid ) << Qt::endl; 0479 comment->addField( Qt5QStringToTString( ourId ), Qt5QStringToTString( uid ) ); 0480 return true; 0481 } 0482 else if( toRemove.size() ) 0483 return true; 0484 0485 return false; 0486 } 0487 0488 bool 0489 AFTTagger::handleMPC( TagLib::MPC::File *file ) 0490 { 0491 if( file->readOnly() ) 0492 { 0493 m_textStream << tr( "ERROR: File is read-only or could not be opened" ) << Qt::endl; 0494 return false; 0495 } 0496 0497 QString uid; 0498 bool newUid = false; 0499 bool nothingfound = true; 0500 TagLib::StringList toRemove; 0501 if( m_verbose ) 0502 m_textStream << tr( "INFO: File is a MPC file, opening..." ) << Qt::endl; 0503 0504 if( file->APETag() ) 0505 { 0506 const TagLib::APE::ItemListMap &itemsMap = file->APETag()->itemListMap(); 0507 if( itemsMap.isEmpty() ) 0508 { 0509 m_textStream << tr( "INFO: No fields found in APE tags." ) << Qt::endl; 0510 0511 if( m_delete ) 0512 return false; 0513 } 0514 0515 for( TagLib::APE::ItemListMap::ConstIterator it = itemsMap.begin(); it != itemsMap.end(); ++it ) 0516 { 0517 TagLib::String key = it->first; 0518 QString qkey = TStringToQString( key ).toUpper(); 0519 if( qkey.startsWith( QLatin1String("AMAROK - REDISCOVER YOUR MUSIC") ) ) 0520 { 0521 nothingfound = false; 0522 0523 if( m_verbose ) 0524 m_textStream << tr( "INFO: Removing old-style ATF identifier %1" ).arg( qkey ) << Qt::endl; 0525 0526 toRemove.append( key ); 0527 if( !m_delete ) 0528 newUid = true; 0529 } 0530 else if( qkey.startsWith( QLatin1String("AMAROK 2 AFT") ) ) 0531 { 0532 nothingfound = false; 0533 0534 if( m_verbose ) 0535 m_textStream << tr( "INFO: Found an existing AFT identifier: %1" ).arg( qkey ) << Qt::endl; 0536 0537 if( m_delete ) 0538 { 0539 toRemove.append( key ); 0540 if( m_verbose ) 0541 m_textStream << tr( "INFO: Removing current AFT frame" ) << Qt::endl; 0542 } 0543 else 0544 { 0545 int version = qkey.at( 13 ).digitValue(); 0546 if( m_verbose ) 0547 m_textStream << tr( "INFO: AFT identifier is version %1" ).arg( version ) << Qt::endl; 0548 if( version < s_currentVersion ) 0549 { 0550 if( m_verbose ) 0551 m_textStream << tr( "INFO: Upgrading AFT identifier from version %1 to version %2" ).arg( version, s_currentVersion ) << Qt::endl; 0552 uid = upgradeUID( version, TStringToQString( itemsMap[ key ].toString() ) ); 0553 if( m_verbose ) 0554 m_textStream << tr( "INFO: Removing current AFT frame" ) << Qt::endl; 0555 toRemove.append( key ); 0556 newUid = true; 0557 } 0558 else if( version == s_currentVersion && m_newid ) 0559 { 0560 if( m_verbose ) 0561 m_textStream << tr( "INFO: New IDs specified to be generated, doing so" ) << Qt::endl; 0562 toRemove.append( key ); 0563 newUid = true; 0564 } 0565 else 0566 { 0567 if( m_verbose ) 0568 m_textStream << tr( "INFO: ID is current" ) << Qt::endl; 0569 return false; 0570 } 0571 } 0572 } 0573 } 0574 for( TagLib::StringList::ConstIterator it = toRemove.begin(); it != toRemove.end(); ++it ) 0575 file->APETag()->removeItem( *it ); 0576 } 0577 0578 if( newUid || ( nothingfound && !m_delete ) ) 0579 { 0580 QString ourId = QString( "Amarok 2 AFTv" + QString::number( s_currentVersion ) + " - amarok.kde.org" ); 0581 if( uid.isEmpty() ) 0582 uid = createCurrentUID( file ); 0583 if( m_verbose ) 0584 m_textStream << tr( "INFO: Adding new field and saving file with UID: %1" ).arg( uid ) << Qt::endl; 0585 file->APETag()->addValue( Qt5QStringToTString( ourId.toUpper() ), Qt5QStringToTString( uid ) ); 0586 file->save(); 0587 return true; 0588 } 0589 else if( toRemove.size() ) 0590 { 0591 file->save(); 0592 return true; 0593 } 0594 0595 return false; 0596 } 0597 0598 bool 0599 AFTTagger::handleMP4( TagLib::MP4::File *file ) 0600 { 0601 if( file->readOnly() ) 0602 { 0603 m_textStream << tr( "ERROR: File is read-only or could not be opened" ) << Qt::endl; 0604 return false; 0605 } 0606 0607 QString uid; 0608 bool newUid = false; 0609 bool nothingfound = true; 0610 TagLib::StringList toRemove; 0611 if( m_verbose ) 0612 m_textStream << tr( "INFO: File is a MP4 file, opening..." ) << Qt::endl; 0613 0614 TagLib::MP4::ItemListMap &itemsMap = file->tag()->itemListMap(); 0615 if( !itemsMap.isEmpty() ) 0616 { 0617 for( TagLib::MP4::ItemListMap::Iterator it = itemsMap.begin(); it != itemsMap.end(); ++it ) 0618 { 0619 TagLib::String key = it->first; 0620 const QString qkey = TStringToQString( key ).toUpper(); 0621 if( qkey.contains( QLatin1String("AMAROK - REDISCOVER YOUR MUSIC") ) ) 0622 { 0623 nothingfound = false; 0624 0625 if( m_verbose ) 0626 m_textStream << tr( "INFO: Removing old-style ATF identifier %1" ).arg( key.toCString() ) << Qt::endl; 0627 0628 toRemove.append( key ); 0629 if( !m_delete ) 0630 newUid = true; 0631 } 0632 else if( qkey.contains( QLatin1String("AMAROK 2 AFT") ) ) 0633 { 0634 nothingfound = false; 0635 0636 if( m_verbose ) 0637 m_textStream << tr( "INFO: Found an existing AFT identifier: %1" ).arg( key.toCString() ) << Qt::endl; 0638 0639 if( m_delete ) 0640 { 0641 toRemove.append( key ); 0642 if( m_verbose ) 0643 m_textStream << tr( "INFO: Removing current AFT frame" ) << Qt::endl; 0644 } 0645 else 0646 { 0647 int version = qkey.at( qkey.indexOf( QLatin1String("AMAROK 2 AFT") ) + 13 ).digitValue(); 0648 if( m_verbose ) 0649 m_textStream << tr( "INFO: AFT identifier is version %1" ).arg( version ) << Qt::endl; 0650 if( version < s_currentVersion ) 0651 { 0652 if( m_verbose ) 0653 m_textStream << tr( "INFO: Upgrading AFT identifier from version %1 to version %2" ) 0654 .arg( QString::number( version ), QString::number( s_currentVersion ) ) 0655 << Qt::endl; 0656 uid = upgradeUID( version, TStringToQString( itemsMap[ key ].toStringList().toString() ) ); 0657 if( m_verbose ) 0658 m_textStream << tr( "INFO: Removing current AFT frame" ) << Qt::endl; 0659 toRemove.append( key ); 0660 newUid = true; 0661 } 0662 else if( version == s_currentVersion && m_newid ) 0663 { 0664 if( m_verbose ) 0665 m_textStream << tr( "INFO: New IDs specified to be generated, doing so" ) << Qt::endl; 0666 toRemove.append( key ); 0667 newUid = true; 0668 } 0669 else 0670 { 0671 if( m_verbose ) 0672 m_textStream << tr( "INFO: ID is current" ) << Qt::endl; 0673 return false; 0674 } 0675 } 0676 } 0677 } 0678 for( TagLib::StringList::ConstIterator it = toRemove.begin(); it != toRemove.end(); ++it ) 0679 itemsMap.erase( *it ); 0680 } 0681 0682 if( newUid || ( nothingfound && !m_delete ) ) 0683 { 0684 QString ourId = QString( "Amarok 2 AFTv" + QString::number( s_currentVersion ) + " - amarok.kde.org" ); 0685 if( uid.isEmpty() ) 0686 uid = createCurrentUID( file ); 0687 if( m_verbose ) 0688 m_textStream << tr( "INFO: Adding new field and saving file with UID: %1" ).arg( uid ) << Qt::endl; 0689 itemsMap.insert( Qt5QStringToTString( QString( "----:com.apple.iTunes:" + ourId ) ), 0690 TagLib::StringList( Qt5QStringToTString( uid ) ) ); 0691 file->save(); 0692 return true; 0693 } 0694 else if( toRemove.size() ) 0695 { 0696 file->save(); 0697 return true; 0698 } 0699 0700 return false; 0701 } 0702 0703 0704 0705 QString 0706 AFTTagger::createCurrentUID( TagLib::File *file ) 0707 { 0708 return createV1UID( file ); 0709 } 0710 0711 QString 0712 AFTTagger::createV1UID( TagLib::File *file ) 0713 { 0714 QCryptographicHash md5( QCryptographicHash::Md5 ); 0715 QByteArray size; 0716 md5.addData( size.setNum( (qulonglong)(file->length()) ) ); 0717 md5.addData( QString::number( m_time.elapsed() ).toUtf8() ); 0718 md5.addData( QString::number( QRandomGenerator::global()->generate() ).toUtf8() ); 0719 md5.addData( QString::number( QRandomGenerator::global()->generate() ).toUtf8() ); 0720 md5.addData( QString::number( QRandomGenerator::global()->generate() ).toUtf8() ); 0721 md5.addData( QString::number( QRandomGenerator::global()->generate() ).toUtf8() ); 0722 md5.addData( QString::number( QRandomGenerator::global()->generate() ).toUtf8() ); 0723 md5.addData( QString::number( m_time.elapsed() ).toUtf8() ); 0724 return QString( md5.result().toHex() ); 0725 } 0726 0727 QString 0728 AFTTagger::upgradeUID( int version, const QString &currValue ) 0729 { 0730 Q_UNUSED(version) 0731 return currValue + "abcd"; 0732 } 0733 0734 void 0735 AFTTagger::readArgs() 0736 { 0737 QStringList argslist = arguments(); 0738 if( argslist.size() < 2 ) 0739 displayHelp(); 0740 bool nomore = false; 0741 int argnum = 0; 0742 foreach( const QString &arg, argslist ) 0743 { 0744 ++argnum; 0745 if( arg.isEmpty() || argnum == 1 ) 0746 continue; 0747 if( nomore ) 0748 { 0749 m_fileFolderList.append( arg ); 0750 } 0751 else if( arg.startsWith( QLatin1String("--") ) ) 0752 { 0753 QString myarg = QString( arg ).remove( 0, 2 ); 0754 if ( myarg == QLatin1String("recurse") || myarg == QLatin1String("recursively") ) 0755 m_recurse = true; 0756 else if( myarg == QLatin1String("verbose") ) 0757 m_verbose = true; 0758 else if( myarg == QLatin1String("quiet") ) 0759 m_quiet = true; 0760 else if( myarg == QLatin1String("newid") ) 0761 m_newid = true; 0762 else if( myarg == QLatin1String("delete") ) 0763 m_delete = true; 0764 else 0765 displayHelp(); 0766 } 0767 else if( arg.startsWith( '-' ) ) 0768 { 0769 QString myarg = QString( arg ).remove( 0, 1 ); 0770 int pos = 0; 0771 while( pos < myarg.length() ) 0772 { 0773 if( myarg[pos] == 'd' ) 0774 m_delete = true; 0775 else if( myarg[pos] == 'n' ) 0776 m_newid = true; 0777 else if( myarg[pos] == 'q' ) 0778 m_quiet = true; 0779 else if( myarg[pos] == 'r' ) 0780 m_recurse = true; 0781 else if( myarg[pos] == 'v' ) 0782 m_verbose = true; 0783 else 0784 displayHelp(); 0785 0786 ++pos; 0787 } 0788 } 0789 else 0790 { 0791 nomore = true; 0792 m_fileFolderList.append( arg ); 0793 } 0794 } 0795 } 0796 0797 void 0798 AFTTagger::displayHelp() 0799 { 0800 m_textStream << tr( "Amarok AFT Tagger" ) << Qt::endl << Qt::endl; 0801 m_textStream << tr( "IRC:\nserver: irc.libera.chat / channels: #amarok, #amarok-de, #amarok-es, #amarok-fr\n\nFeedback:\namarok@kde.org" ) << Qt::endl << Qt::endl; 0802 m_textStream << tr( "Usage: amarok_afttagger [options] +File/Folder(s)" ) << Qt::endl << Qt::endl; 0803 m_textStream << tr( "User-modifiable Options:" ) << Qt::endl; 0804 m_textStream << tr( "+File/Folder(s) : Files or folders to tag" ) << Qt::endl; 0805 m_textStream << tr( "-h, --help : This help text" ) << Qt::endl; 0806 m_textStream << tr( "-r, --recursive : Process files and folders recursively" ) << Qt::endl; 0807 m_textStream << tr( "-d, --delete : Remove AFT tag" ) << Qt::endl; 0808 m_textStream << tr( "-n --newid : Replace any existing ID with a new one" ) << Qt::endl; 0809 m_textStream << tr( "-v, --verbose : Verbose output" ) << Qt::endl; 0810 m_textStream << tr( "-q, --quiet : Quiet output; Implies that you accept the terms of use" ) << Qt::endl; 0811 m_textStream.flush(); 0812 ::exit( 0 ); 0813 }