File indexing completed on 2024-03-24 04:48:25
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