File indexing completed on 2024-04-28 12:34:45

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  *
0011  *   it under the terms of the GNU General Public License as published by  *
0012  *   the Free Software Foundation; either version 2 of the License, or     *
0013  *   (at your option) any later version.                                   *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program; if not, write to the                         *
0022  *   Free Software Foundation, Inc.,                                       *
0023  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
0024  ***************************************************************************/
0025 
0026 #include "MetaTagLib.h"
0027 
0028 #include "FileType.h"
0029 #include "TagsFromFileNameGuesser.h"
0030 #include <config.h>
0031 
0032 #include <KEncodingProber>
0033 
0034 #include <QImage>
0035 #include <QBuffer>
0036 #include <QDir>
0037 #include <QFile>
0038 #include <QFileInfo>
0039 #include <QCryptographicHash>
0040 #include <QMutex>
0041 #include <QMutexLocker>
0042 #include <QRandomGenerator>
0043 #include <QString>
0044 #include <QTime>
0045 
0046 #include "FileTypeResolver.h"
0047 #include "MetaReplayGain.h"
0048 #include "tag_helpers/TagHelper.h"
0049 #include "tag_helpers/StringHelper.h"
0050 
0051 //Taglib:
0052 #include <audioproperties.h>
0053 
0054 #ifdef TAGLIB_EXTRAS_FOUND
0055 #pragma GCC diagnostic push
0056 #pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
0057 #include <audiblefiletyperesolver.h>
0058 #include <realmediafiletyperesolver.h>
0059 #pragma GCC diagnostic pop
0060 #endif // TAGLIB_EXTRAS_FOUND
0061 
0062 
0063 namespace Meta
0064 {
0065     namespace Tag
0066     {
0067         QMutex s_mutex;
0068 
0069         static void addRandomness( QCryptographicHash *md5 );
0070 
0071         /** Get a taglib fileref for a path */
0072         static TagLib::FileRef getFileRef( const QString &path );
0073 
0074         /** Returns a byte vector that can be used to generate the unique id based on the tags. */
0075         static TagLib::ByteVector generatedUniqueIdHelper( const TagLib::FileRef &fileref );
0076 
0077         static QString generateUniqueId( const QString &path );
0078 
0079     }
0080 }
0081 
0082 TagLib::FileRef
0083 Meta::Tag::getFileRef( const QString &path )
0084 {
0085 #ifdef Q_OS_WIN32
0086     const wchar_t *encodedName = reinterpret_cast< const wchar_t * >( path.utf16() );
0087 #else
0088 #ifdef COMPLEX_TAGLIB_FILENAME
0089     const wchar_t *encodedName = reinterpret_cast< const wchar_t * >( path.utf16() );
0090 #else
0091     QByteArray fileName = QFile::encodeName( path );
0092     const char *encodedName = fileName.constData(); // valid as long as fileName exists
0093 #endif
0094 #endif
0095 
0096     // Tests reveal the following:
0097     //
0098     // TagLib::AudioProperties   Relative Time Taken
0099     //
0100     //  No AudioProp Reading        1
0101     //  Fast                        1.18
0102     //  Average                     Untested
0103     //  Accurate                    Untested
0104 
0105     return TagLib::FileRef( encodedName, true, TagLib::AudioProperties::Fast );
0106 }
0107 
0108 
0109 // ----------------------- unique id ------------------------
0110 
0111 
0112 void
0113 Meta::Tag::addRandomness( QCryptographicHash *md5 )
0114 {
0115     //md5 has size of file already added for some little extra randomness for the hash
0116     md5->addData( QString::number( QRandomGenerator::global()->generate() ).toLatin1() );
0117     md5->addData( QString::number( QRandomGenerator::global()->generate() ).toLatin1() );
0118     md5->addData( QString::number( QRandomGenerator::global()->generate() ).toLatin1() );
0119     md5->addData( QString::number( QRandomGenerator::global()->generate() ).toLatin1() );
0120     md5->addData( QString::number( QRandomGenerator::global()->generate() ).toLatin1() );
0121     md5->addData( QString::number( QRandomGenerator::global()->generate() ).toLatin1() );
0122     md5->addData( QString::number( QRandomGenerator::global()->generate() ).toLatin1() );
0123 }
0124 
0125 TagLib::ByteVector
0126 Meta::Tag::generatedUniqueIdHelper( const TagLib::FileRef &fileref )
0127 {
0128     TagLib::ByteVector bv;
0129 
0130     TagHelper *tagHelper = selectHelper( fileref );
0131 
0132     if( tagHelper )
0133     {
0134         bv = tagHelper->render();
0135         delete tagHelper;
0136     }
0137 
0138     return bv;
0139 }
0140 
0141 QString
0142 Meta::Tag::generateUniqueId( const QString &path )
0143 {
0144     QCryptographicHash md5( QCryptographicHash::Md5 );
0145     QFile qfile( path );
0146     QByteArray size;
0147     md5.addData( size.setNum( qfile.size() ) );
0148 
0149     TagLib::FileRef fileref = getFileRef( path );
0150     TagLib::ByteVector bv = generatedUniqueIdHelper( fileref );
0151     md5.addData( bv.data(), bv.size() );
0152 
0153     char databuf[16384];
0154     int readlen = 0;
0155 
0156     if( qfile.open( QIODevice::ReadOnly ) )
0157     {
0158         if( ( readlen = qfile.read( databuf, 16384 ) ) > 0 )
0159         {
0160             md5.addData( databuf, readlen );
0161             qfile.close();
0162         }
0163         else
0164         {
0165             qfile.close();
0166             addRandomness( &md5 );
0167         }
0168     }
0169     else
0170         addRandomness( &md5 );
0171 
0172     return QString::fromLatin1( md5.result().toHex() );
0173 }
0174 
0175 
0176 // --------- file type resolver ----------
0177 
0178 /** Will ensure that we have our file type resolvers added */
0179 static void ensureFileTypeResolvers()
0180 {
0181     static bool alreadyAdded = false;
0182     if( !alreadyAdded ) {
0183         alreadyAdded = true;
0184 
0185 #ifdef TAGLIB_EXTRAS_FOUND
0186         TagLib::FileRef::addFileTypeResolver(new AudibleFileTypeResolver);
0187         TagLib::FileRef::addFileTypeResolver(new RealMediaFileTypeResolver);
0188 #endif
0189         TagLib::FileRef::addFileTypeResolver(new Meta::Tag::FileTypeResolver());
0190     }
0191 }
0192 
0193 // ----------------------- reading ------------------------
0194 
0195 Meta::FieldHash
0196 Meta::Tag::readTags( const QString &path, bool /*useCharsetDetector*/ )
0197 {
0198     Meta::FieldHash result;
0199 
0200     // we do not rely on taglib being thread safe especially when writing the same file from different threads.
0201     QMutexLocker locker( &s_mutex );
0202     ensureFileTypeResolvers();
0203 
0204     TagLib::FileRef fileref = getFileRef( path );
0205 
0206     if( fileref.isNull() )
0207         return result;
0208 
0209     Meta::ReplayGainTagMap replayGainTags = Meta::readReplayGainTags( fileref );
0210     if( replayGainTags.contains( Meta::ReplayGain_Track_Gain ) )
0211         result.insert( Meta::valTrackGain, replayGainTags[Meta::ReplayGain_Track_Gain] );
0212     if( replayGainTags.contains( Meta::ReplayGain_Track_Peak ) )
0213         result.insert( Meta::valTrackGainPeak, replayGainTags[Meta::ReplayGain_Track_Peak] );
0214 
0215     // strangely: the album gain defaults to the track gain
0216     if( replayGainTags.contains( Meta::ReplayGain_Album_Gain ) )
0217         result.insert( Meta::valAlbumGain, replayGainTags[Meta::ReplayGain_Album_Gain] );
0218     else if( replayGainTags.contains( Meta::ReplayGain_Track_Gain ) )
0219         result.insert( Meta::valAlbumGain, replayGainTags[Meta::ReplayGain_Track_Gain] );
0220     if( replayGainTags.contains( Meta::ReplayGain_Album_Peak ) )
0221         result.insert( Meta::valAlbumGainPeak, replayGainTags[Meta::ReplayGain_Album_Peak] );
0222     else if( replayGainTags.contains( Meta::ReplayGain_Track_Peak ) )
0223         result.insert( Meta::valAlbumGainPeak, replayGainTags[Meta::ReplayGain_Track_Peak] );
0224 
0225     TagHelper *tagHelper = selectHelper( fileref );
0226     if( tagHelper )
0227     {
0228         if( 0/* useCharsetDetector */ )
0229         {
0230             KEncodingProber prober;
0231             if( prober.feed( tagHelper->testString() ) != KEncodingProber::NotMe )
0232                 Meta::Tag::setCodecByName( prober.encoding() );
0233         }
0234 
0235         result.insert( Meta::valFormat, tagHelper->fileType() );
0236         result.unite( tagHelper->tags() );
0237         delete tagHelper;
0238     }
0239 
0240     TagLib::AudioProperties *properties = fileref.audioProperties();
0241     if( properties )
0242     {
0243         if( !result.contains( Meta::valBitrate ) && properties->bitrate() )
0244             result.insert( Meta::valBitrate, properties->bitrate() );
0245         if( !result.contains( Meta::valLength ) && properties->length() )
0246             result.insert( Meta::valLength, properties->length() * 1000 );
0247         if( !result.contains( Meta::valSamplerate ) && properties->sampleRate() )
0248             result.insert( Meta::valSamplerate, properties->sampleRate() );
0249     }
0250 
0251     //If tags doesn't contains title and artist, try to guess It from file name
0252     if( !result.contains( Meta::valTitle ) ||
0253         result.value( Meta::valTitle ).toString().isEmpty() )
0254         result.unite( TagGuesser::guessTags( path ) );
0255 
0256     //we didn't set a FileType till now, let's look it up via FileExtension
0257     if( !result.contains( Meta::valFormat ) )
0258     {
0259         QString ext = path.mid( path.lastIndexOf( QLatin1Char('.') ) + 1 );
0260         result.insert( Meta::valFormat, Amarok::FileTypeSupport::fileType( ext ) );
0261     }
0262 
0263     QFileInfo fileInfo( path );
0264     result.insert( Meta::valFilesize, fileInfo.size() );
0265     result.insert( Meta::valModified, fileInfo.lastModified() );
0266 
0267     if( !result.contains( Meta::valUniqueId ) )
0268         result.insert( Meta::valUniqueId, generateUniqueId( path ) );
0269 
0270     // compute bitrate if it is not already set and we know length
0271     if( !result.contains( Meta::valBitrate ) && result.contains( Meta::valLength ) )
0272         result.insert( Meta::valBitrate, ( fileInfo.size() * 8 * 1000 ) /
0273                        ( result.value( Meta::valLength ).toInt() * 1024 ) );
0274 
0275     return result;
0276 }
0277 
0278 QImage
0279 Meta::Tag::embeddedCover( const QString &path )
0280 {
0281     // we do not rely on taglib being thread safe especially when writing the same file from different threads.
0282     QMutexLocker locker( &s_mutex );
0283 
0284     ensureFileTypeResolvers();
0285     TagLib::FileRef fileref = getFileRef( path );
0286     if( fileref.isNull() )
0287         return QImage();
0288 
0289     QImage img;
0290     TagHelper *tagHelper = selectHelper( fileref );
0291     if( tagHelper )
0292     {
0293         img = tagHelper->embeddedCover();
0294         delete tagHelper;
0295     }
0296     return img;
0297 }
0298 
0299 void
0300 Meta::Tag::writeTags( const QString &path, const FieldHash &changes, bool writeStatistics )
0301 {
0302     FieldHash data = changes;
0303 
0304     if( !writeStatistics )
0305     {
0306         data.remove( Meta::valFirstPlayed );
0307         data.remove( Meta::valLastPlayed );
0308         data.remove( Meta::valPlaycount );
0309         data.remove( Meta::valScore );
0310         data.remove( Meta::valRating );
0311     }
0312 
0313     // we do not rely on taglib being thread safe especially when writing the same file from different threads.
0314     QMutexLocker locker( &s_mutex );
0315 
0316     ensureFileTypeResolvers();
0317     TagLib::FileRef fileref = getFileRef( path );
0318     if( fileref.isNull() || data.isEmpty() )
0319         return;
0320 
0321     QScopedPointer<TagHelper> tagHelper( selectHelper( fileref, true ) );
0322     if( !tagHelper )
0323         return;
0324 
0325     if( tagHelper->setTags( data ) )
0326         fileref.save();
0327 }
0328 
0329 void
0330 Meta::Tag::setEmbeddedCover( const QString &path, const QImage &cover )
0331 {
0332     // we do not rely on taglib being thread safe especially when writing the same file from different threads.
0333     QMutexLocker locker( &s_mutex );
0334     ensureFileTypeResolvers();
0335 
0336     TagLib::FileRef fileref = getFileRef( path );
0337 
0338     if( fileref.isNull() )
0339         return;
0340 
0341     TagHelper *tagHelper = selectHelper( fileref, true );
0342     if( !tagHelper )
0343         return;
0344 
0345     if( tagHelper->setEmbeddedCover( cover ) )
0346         fileref.save();
0347 
0348     delete tagHelper;
0349 }
0350 
0351 #undef Qt4QStringToTString