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 }