File indexing completed on 2024-03-24 04:48:25
0001 /**************************************************************************************** 0002 * Copyright (c) 2009 Alex Merry <alex.merry@kdemail.net> * 0003 * * 0004 * This program is free software; you can redistribute it and/or modify it under * 0005 * the terms of the GNU General Public License as published by the Free Software * 0006 * Foundation; either version 2 of the License, or (at your option) any later * 0007 * version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0010 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0011 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0012 * * 0013 * You should have received a copy of the GNU General Public License along with * 0014 * this program. If not, see <http://www.gnu.org/licenses/>. * 0015 ****************************************************************************************/ 0016 0017 // NOTE: this file is used by amarokcollectionscanner and CANNOT use any amaroklib 0018 // code [this includes debug()] 0019 0020 #include "MetaReplayGain.h" 0021 0022 #include <QString> 0023 #include <cmath> 0024 0025 // Taglib 0026 #pragma GCC diagnostic push 0027 #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" 0028 #include <tag.h> 0029 #include <tlist.h> 0030 #include <tstring.h> 0031 #include <textidentificationframe.h> 0032 #include <apetag.h> 0033 #include <fileref.h> 0034 #include <flacfile.h> 0035 #include <id3v2tag.h> 0036 #include <mpcfile.h> 0037 #include <mpegfile.h> 0038 #include <oggfile.h> 0039 #include <oggflacfile.h> 0040 #include <speexfile.h> 0041 #include <trueaudiofile.h> 0042 #include <vorbisfile.h> 0043 #include <wavpackfile.h> 0044 #include <asffile.h> 0045 #include <mp4file.h> 0046 #pragma GCC diagnostic pop 0047 0048 // converts a peak value from the normal digital scale form to the more useful decibel form 0049 // decibels are relative to the /adjusted/ waveform 0050 static qreal peakToDecibels( qreal scaleVal ) 0051 { 0052 if ( scaleVal > 0 ) 0053 return 20 * log10( scaleVal ); 0054 else 0055 return 0; 0056 } 0057 0058 // NOTE: representation is taken to be a binary value with units in the first column, 0059 // 1/2 in the second and so on. 0060 static qreal readRVA2PeakValue( const TagLib::ByteVector &data, int bits, bool *ok ) 0061 { 0062 qreal peak = 0.0; 0063 // discarding digits at the end reduces precision, but doesn't otherwise change the value 0064 if ( bits > 32 ) 0065 bits = 32; 0066 // the +7 makes sure we round up when we divide by 8 0067 unsigned int bytes = (bits + 7) / 8; 0068 0069 // normalize appears not to write a peak at all, and hence sets bits to 0 0070 if ( bits == 0 ) 0071 { 0072 if ( ok ) 0073 *ok = true; 0074 } 0075 else if ( bits >= 4 && data.size() >= bytes ) // fewer than 4 bits would just be daft 0076 { 0077 // excessBits is the number of bits we have to discard at the end 0078 unsigned int excessBits = (8 * bytes) - bits; 0079 // mask has 1s everywhere but the last /excessBits/ bits 0080 quint32 mask = 0xffffffff << excessBits; 0081 quint32 rawValue = 0; 0082 for ( unsigned int i = 0; i < bytes; ++i ) 0083 { 0084 rawValue <<= 8; 0085 rawValue += (unsigned char)data[i]; 0086 } 0087 rawValue &= mask; 0088 peak = rawValue; 0089 // amount we need to "shift" the value right to make the first digit the unit column 0090 unsigned int rightShift = (8 * bytes) - 1; 0091 peak /= (qreal)(1 << rightShift); 0092 if ( ok ) 0093 *ok = true; 0094 } 0095 else 0096 { 0097 if ( ok ) 0098 *ok = false; 0099 } 0100 return peak; 0101 } 0102 0103 // adds the converted version of the scale value if it is a valid, non-negative float 0104 static void maybeAddPeak( const TagLib::String &scaleVal, Meta::ReplayGainTag key, Meta::ReplayGainTagMap *map ) 0105 { 0106 // scale value is >= 0, and typically not much bigger than 1 0107 QString value = TStringToQString( scaleVal ); 0108 bool ok = false; 0109 qreal peak = value.toFloat( &ok ); 0110 if ( ok && peak >= 0 ) 0111 (*map)[key] = peakToDecibels( peak ); 0112 } 0113 0114 static void maybeAddGain( const TagLib::String &input, Meta::ReplayGainTag key, Meta::ReplayGainTagMap *map ) 0115 { 0116 QString value = TStringToQString( input ).remove( QStringLiteral(" dB") ); 0117 bool ok = false; 0118 qreal gain = value.toFloat( &ok ); 0119 if (ok) 0120 (*map)[key] = gain; 0121 } 0122 0123 static Meta::ReplayGainTagMap readID3v2Tags( TagLib::ID3v2::Tag *tag ) 0124 { 0125 Meta::ReplayGainTagMap map; 0126 { // ID3v2.4.0 native replay gain tag support (as written by Quod Libet, for example). 0127 TagLib::ID3v2::FrameList frames = tag->frameListMap()["RVA2"]; 0128 frames.append(tag->frameListMap()["XRVA"]); 0129 if ( !frames.isEmpty() ) 0130 { 0131 for ( unsigned int i = 0; i < frames.size(); ++i ) 0132 { 0133 // we have to parse this frame ourselves 0134 // ID3v2 frame header is 10 bytes, so skip that 0135 TagLib::ByteVector data = frames[i]->render().mid( 10 ); 0136 unsigned int offset = 0; 0137 QString desc( QString::fromLatin1(data.data()) ); 0138 offset += desc.count() + 1; 0139 unsigned int channel = data.mid( offset, 1 ).toUInt( true ); 0140 // channel 1 is the main volume - the only one we care about 0141 if ( channel == 1 ) 0142 { 0143 ++offset; 0144 qint16 adjustment512 = data.mid( offset, 2 ).toShort( true ); 0145 qreal adjustment = ( (qreal)adjustment512 ) / 512.0; 0146 offset += 2; 0147 unsigned int peakBits = data.mid( offset, 1 ).toUInt( true ); 0148 ++offset; 0149 bool ok = false; 0150 qreal peak = readRVA2PeakValue( data.mid( offset ), peakBits, &ok ); 0151 if ( ok ) 0152 { 0153 if ( desc.toLower() == QLatin1String("album") ) 0154 { 0155 map[Meta::ReplayGain_Album_Gain] = adjustment; 0156 map[Meta::ReplayGain_Album_Peak] = peakToDecibels( peak ); 0157 } 0158 else if ( desc.toLower() == QLatin1String("track") || !map.contains( Meta::ReplayGain_Track_Gain ) ) 0159 { 0160 map[Meta::ReplayGain_Track_Gain] = adjustment; 0161 map[Meta::ReplayGain_Track_Peak] = peakToDecibels( peak ); 0162 } 0163 } 0164 } 0165 } 0166 if ( !map.isEmpty() ) 0167 return map; 0168 } 0169 } 0170 0171 { // Foobar2000-style ID3v2.3.0 tags 0172 TagLib::ID3v2::FrameList frames = tag->frameListMap()["TXXX"]; 0173 for ( TagLib::ID3v2::FrameList::Iterator it = frames.begin(); it != frames.end(); ++it ) { 0174 TagLib::ID3v2::UserTextIdentificationFrame* frame = 0175 dynamic_cast<TagLib::ID3v2::UserTextIdentificationFrame*>( *it ); 0176 if ( frame && frame->fieldList().size() >= 2 ) 0177 { 0178 QString desc = TStringToQString( frame->description() ).toLower(); 0179 if ( desc == QLatin1String("replaygain_album_gain") ) 0180 maybeAddGain( frame->fieldList()[1], Meta::ReplayGain_Album_Gain, &map ); 0181 if ( desc == QLatin1String("replaygain_album_peak") ) 0182 maybeAddPeak( frame->fieldList()[1], Meta::ReplayGain_Album_Peak, &map ); 0183 if ( desc == QLatin1String("replaygain_track_gain") ) 0184 maybeAddGain( frame->fieldList()[1], Meta::ReplayGain_Track_Gain, &map ); 0185 if ( desc == QLatin1String("replaygain_track_peak") ) 0186 maybeAddPeak( frame->fieldList()[1], Meta::ReplayGain_Track_Peak, &map ); 0187 } 0188 } 0189 } 0190 return map; 0191 } 0192 0193 static Meta::ReplayGainTagMap readAPETags( TagLib::APE::Tag *tag ) 0194 { 0195 Meta::ReplayGainTagMap map; 0196 const TagLib::APE::ItemListMap &items = tag->itemListMap(); 0197 if ( items.contains("REPLAYGAIN_TRACK_GAIN") ) 0198 { 0199 maybeAddGain( items["REPLAYGAIN_TRACK_GAIN"].values()[0], Meta::ReplayGain_Track_Gain, &map ); 0200 if ( items.contains("REPLAYGAIN_TRACK_PEAK") ) 0201 maybeAddPeak( items["REPLAYGAIN_TRACK_PEAK"].values()[0], Meta::ReplayGain_Track_Peak, &map ); 0202 } 0203 if ( items.contains("REPLAYGAIN_ALBUM_GAIN") ) 0204 { 0205 maybeAddGain( items["REPLAYGAIN_ALBUM_GAIN"].values()[0], Meta::ReplayGain_Album_Gain, &map ); 0206 if ( items.contains("REPLAYGAIN_ALBUM_PEAK") ) 0207 maybeAddPeak( items["REPLAYGAIN_ALBUM_PEAK"].values()[0], Meta::ReplayGain_Album_Peak, &map ); 0208 } 0209 return map; 0210 } 0211 0212 static Meta::ReplayGainTagMap readXiphTags( TagLib::Ogg::XiphComment *tag ) 0213 { 0214 const TagLib::Ogg::FieldListMap &tagMap = tag->fieldListMap(); 0215 Meta::ReplayGainTagMap outputMap; 0216 0217 if ( !tagMap["REPLAYGAIN_TRACK_GAIN"].isEmpty() ) 0218 { 0219 maybeAddGain( tagMap["REPLAYGAIN_TRACK_GAIN"].front(), Meta::ReplayGain_Track_Gain, &outputMap ); 0220 if ( !tagMap["REPLAYGAIN_TRACK_PEAK"].isEmpty() ) 0221 maybeAddPeak( tagMap["REPLAYGAIN_TRACK_PEAK"].front(), Meta::ReplayGain_Track_Peak, &outputMap ); 0222 } 0223 0224 if ( !tagMap["REPLAYGAIN_ALBUM_GAIN"].isEmpty() ) 0225 { 0226 maybeAddGain( tagMap["REPLAYGAIN_ALBUM_GAIN"].front(), Meta::ReplayGain_Album_Gain, &outputMap ); 0227 if ( !tagMap["REPLAYGAIN_ALBUM_PEAK"].isEmpty() ) 0228 maybeAddPeak( tagMap["REPLAYGAIN_ALBUM_PEAK"].front(), Meta::ReplayGain_Album_Peak, &outputMap ); 0229 } 0230 0231 return outputMap; 0232 } 0233 0234 static Meta::ReplayGainTagMap readASFTags( TagLib::ASF::Tag *tag ) 0235 { 0236 const TagLib::ASF::AttributeListMap &tagMap = tag->attributeListMap(); 0237 Meta::ReplayGainTagMap outputMap; 0238 0239 if ( !tagMap["REPLAYGAIN_TRACK_GAIN"].isEmpty() ) 0240 { 0241 maybeAddGain( tagMap["REPLAYGAIN_TRACK_GAIN"].front().toString(), Meta::ReplayGain_Track_Gain, &outputMap ); 0242 if ( !tagMap["REPLAYGAIN_TRACK_PEAK"].isEmpty() ) 0243 maybeAddPeak( tagMap["REPLAYGAIN_TRACK_PEAK"].front().toString(), Meta::ReplayGain_Track_Peak, &outputMap ); 0244 } 0245 0246 if ( !tagMap["REPLAYGAIN_ALBUM_GAIN"].isEmpty() ) 0247 { 0248 maybeAddGain( tagMap["REPLAYGAIN_ALBUM_GAIN"].front().toString(), Meta::ReplayGain_Album_Gain, &outputMap ); 0249 if ( !tagMap["REPLAYGAIN_ALBUM_PEAK"].isEmpty() ) 0250 maybeAddPeak( tagMap["REPLAYGAIN_ALBUM_PEAK"].front().toString(), Meta::ReplayGain_Album_Peak, &outputMap ); 0251 } 0252 0253 return outputMap; 0254 } 0255 // Bad news: ReplayGain in MP4 is not actually standardized in any way. Maybe reimplement at some point...maybe. See 0256 // http://www.hydrogenaudio.org/forums/lofiversion/index.php/t14322.html 0257 #ifdef DO_NOT_USE_THIS_UNTIL_FIXED 0258 static Meta::ReplayGainTagMap readMP4Tags( TagLib::MP4::Tag *tag ) 0259 { 0260 Meta::ReplayGainTagMap outputMap; 0261 0262 if ( !tag->trackReplayGain().isNull() ) { 0263 maybeAddGain( tag->trackReplayGain(), Meta::ReplayGain_Track_Gain, &outputMap ); 0264 if ( !tag->trackReplayGainPeak().isNull() ) 0265 maybeAddPeak( tag->trackReplayGainPeak(), Meta::ReplayGain_Track_Peak, &outputMap ); 0266 } 0267 0268 if ( !tag->albumReplayGain().isNull() ) { 0269 maybeAddGain( tag->albumReplayGain(), Meta::ReplayGain_Album_Gain, &outputMap ); 0270 if ( !tag->albumReplayGainPeak().isNull() ) 0271 maybeAddPeak( tag->albumReplayGainPeak(), Meta::ReplayGain_Album_Peak, &outputMap ); 0272 } 0273 0274 return outputMap; 0275 } 0276 #endif 0277 0278 Meta::ReplayGainTagMap 0279 Meta::readReplayGainTags( const TagLib::FileRef &fileref ) 0280 { 0281 Meta::ReplayGainTagMap map; 0282 // NB: we can't get replay gain info from MPC files, since it's stored in some magic place 0283 // and not in the APE tags, and taglib doesn't let us access the information (unless 0284 // we want to parse the file ourselves). 0285 // FIXME: should we try getting the info from the MPC APE tag just in case? 0286 0287 if ( TagLib::MPEG::File *file = dynamic_cast<TagLib::MPEG::File *>( fileref.file() ) ) 0288 { 0289 if ( file->ID3v2Tag() ) 0290 map = readID3v2Tags( file->ID3v2Tag() ); 0291 if ( map.isEmpty() && file->APETag() ) 0292 map = readAPETags( file->APETag() ); 0293 } 0294 else if ( TagLib::Ogg::Vorbis::File *file = dynamic_cast<TagLib::Ogg::Vorbis::File *>( fileref.file() ) ) 0295 { 0296 if ( file->tag() ) 0297 map = readXiphTags( file->tag() ); 0298 } 0299 else if ( TagLib::FLAC::File *file = dynamic_cast<TagLib::FLAC::File *>( fileref.file() ) ) 0300 { 0301 if ( file->xiphComment() ) 0302 map = readXiphTags( file->xiphComment() ); 0303 if ( map.isEmpty() && file->ID3v2Tag() ) 0304 map = readID3v2Tags( file->ID3v2Tag() ); 0305 } 0306 else if ( TagLib::Ogg::FLAC::File *file = dynamic_cast<TagLib::Ogg::FLAC::File *>( fileref.file() ) ) 0307 { 0308 if ( file->tag() ) 0309 map = readXiphTags( file->tag() ); 0310 } 0311 else if ( TagLib::WavPack::File *file = dynamic_cast<TagLib::WavPack::File *>( fileref.file() ) ) 0312 { 0313 if ( file->APETag() ) 0314 map = readAPETags( file->APETag() ); 0315 } 0316 else if ( TagLib::TrueAudio::File *file = dynamic_cast<TagLib::TrueAudio::File *>( fileref.file() ) ) 0317 { 0318 if ( file->ID3v2Tag() ) 0319 map = readID3v2Tags( file->ID3v2Tag() ); 0320 } 0321 else if ( TagLib::Ogg::Speex::File *file = dynamic_cast<TagLib::Ogg::Speex::File *>( fileref.file() ) ) 0322 { 0323 if ( file->tag() ) 0324 map = readXiphTags( file->tag() ); 0325 } 0326 else if ( TagLib::MPC::File *file = dynamic_cast<TagLib::MPC::File *>( fileref.file() ) ) 0327 { 0328 // This is NOT the correct way to get replay gain tags from MPC files, but 0329 // taglib doesn't allow us access to the real information. 0330 // This allows people to work around this issue by copying their replay gain 0331 // information to the APE tag. 0332 if ( file->APETag() ) 0333 map = readAPETags( file->APETag() ); 0334 } 0335 else if ( TagLib::ASF::File *file = dynamic_cast<TagLib::ASF::File *>( fileref.file() ) ) 0336 { 0337 if ( file->tag() ) 0338 map = readASFTags( file->tag() ); 0339 } 0340 // See comment above 0341 #ifdef DO_NOT_USE_THIS_UNTIL_FIXED 0342 else if ( TagLib::MP4::File *file = dynamic_cast<TagLib::MP4::File *>( fileref.file() ) ) 0343 { 0344 if ( file->tag() ) 0345 map = readMP4Tags( file->getMP4Tag() ); 0346 } 0347 #endif 0348 return map; 0349 } 0350