File indexing completed on 2024-04-21 08:35:10

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