File indexing completed on 2024-05-19 04:49:57

0001 /****************************************************************************************
0002  * Copyright (c) 2010-2012 Soren Harward <stharward@gmail.com>                          *
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 #define DEBUG_PREFIX "Constraint::TagMatchSupport"
0018 
0019 #include "TagMatch.h"
0020 
0021 #include "core/meta/Meta.h"
0022 #include "core/meta/support/MetaConstants.h"
0023 
0024 #include <cmath>
0025 
0026 ConstraintTypes::TagMatchFieldsModel::TagMatchFieldsModel()
0027 {
0028     m_fieldNames << QStringLiteral("url")
0029                  << QStringLiteral("title")
0030                  << QStringLiteral("artist name")
0031                  << QStringLiteral("album name")
0032                  << QStringLiteral("genre")
0033                  << QStringLiteral("composer")
0034                  << QStringLiteral("year")
0035                  << QStringLiteral("comment")
0036                  << QStringLiteral("track number")
0037                  << QStringLiteral("disc number")
0038                  << QStringLiteral("length")
0039                  << QStringLiteral("score")
0040                  << QStringLiteral("rating")
0041                  << QStringLiteral("create date")
0042                  << QStringLiteral("first played")
0043                  << QStringLiteral("last played")
0044                  << QStringLiteral("play count")
0045                  << QStringLiteral("label");
0046 
0047     m_fieldTypes.insert( QStringLiteral("url"), TagMatch::FieldTypeString );
0048     m_fieldTypes.insert( QStringLiteral("title"), TagMatch::FieldTypeString );
0049     m_fieldTypes.insert( QStringLiteral("artist name"), TagMatch::FieldTypeString );
0050     m_fieldTypes.insert( QStringLiteral("album name"), TagMatch::FieldTypeString );
0051     m_fieldTypes.insert( QStringLiteral("genre"), TagMatch::FieldTypeString );
0052     m_fieldTypes.insert( QStringLiteral("composer"), TagMatch::FieldTypeString );
0053     m_fieldTypes.insert( QStringLiteral("year"), TagMatch::FieldTypeInt );
0054     m_fieldTypes.insert( QStringLiteral("comment"), TagMatch::FieldTypeString );
0055     m_fieldTypes.insert( QStringLiteral("track number"), TagMatch::FieldTypeInt );
0056     m_fieldTypes.insert( QStringLiteral("disc number"), TagMatch::FieldTypeInt );
0057     m_fieldTypes.insert( QStringLiteral("length"), TagMatch::FieldTypeInt );
0058     m_fieldTypes.insert( QStringLiteral("create date"), TagMatch::FieldTypeDate);
0059     m_fieldTypes.insert( QStringLiteral("score"), TagMatch::FieldTypeInt );
0060     m_fieldTypes.insert( QStringLiteral("rating"), TagMatch::FieldTypeInt );
0061     m_fieldTypes.insert( QStringLiteral("first played"), TagMatch::FieldTypeDate );
0062     m_fieldTypes.insert( QStringLiteral("last played"), TagMatch::FieldTypeDate );
0063     m_fieldTypes.insert( QStringLiteral("play count"), TagMatch::FieldTypeInt );
0064     m_fieldTypes.insert( QStringLiteral("label"), TagMatch::FieldTypeString );
0065 
0066     m_fieldMetaValues.insert( QStringLiteral("url"), Meta::valUrl );
0067     m_fieldMetaValues.insert( QStringLiteral("title"), Meta::valTitle );
0068     m_fieldMetaValues.insert( QStringLiteral("artist name"), Meta::valArtist );
0069     m_fieldMetaValues.insert( QStringLiteral("album name"), Meta::valAlbum );
0070     m_fieldMetaValues.insert( QStringLiteral("genre"), Meta::valGenre );
0071     m_fieldMetaValues.insert( QStringLiteral("composer"), Meta::valComposer );
0072     m_fieldMetaValues.insert( QStringLiteral("year"), Meta::valYear );
0073     m_fieldMetaValues.insert( QStringLiteral("comment"), Meta::valComment );
0074     m_fieldMetaValues.insert( QStringLiteral("track number"), Meta::valTrackNr );
0075     m_fieldMetaValues.insert( QStringLiteral("disc number"), Meta::valDiscNr );
0076     m_fieldMetaValues.insert( QStringLiteral("length"), Meta::valLength );
0077     m_fieldMetaValues.insert( QStringLiteral("create date"), Meta::valCreateDate);
0078     m_fieldMetaValues.insert( QStringLiteral("score"), Meta::valScore );
0079     m_fieldMetaValues.insert( QStringLiteral("rating"), Meta::valRating );
0080     m_fieldMetaValues.insert( QStringLiteral("first played"), Meta::valFirstPlayed );
0081     m_fieldMetaValues.insert( QStringLiteral("last played"), Meta::valLastPlayed );
0082     m_fieldMetaValues.insert( QStringLiteral("play count"), Meta::valPlaycount );
0083     m_fieldMetaValues.insert( QStringLiteral("label"), Meta::valLabel );
0084 
0085     m_fieldPrettyNames.insert( QStringLiteral("url"), i18n("url") );
0086     m_fieldPrettyNames.insert( QStringLiteral("title"), i18n("title") );
0087     m_fieldPrettyNames.insert( QStringLiteral("artist name"), i18n("artist name") );
0088     m_fieldPrettyNames.insert( QStringLiteral("album name"), i18n("album name") );
0089     m_fieldPrettyNames.insert( QStringLiteral("genre"), i18n("genre") );
0090     m_fieldPrettyNames.insert( QStringLiteral("composer"), i18n("composer") );
0091     m_fieldPrettyNames.insert( QStringLiteral("year"), i18nc("Field name", "year") );
0092     m_fieldPrettyNames.insert( QStringLiteral("comment"), i18n("comment") );
0093     m_fieldPrettyNames.insert( QStringLiteral("track number"), i18n("track number") );
0094     m_fieldPrettyNames.insert( QStringLiteral("disc number"), i18n("disc number") );
0095     m_fieldPrettyNames.insert( QStringLiteral("length"), i18n("length") );
0096     m_fieldPrettyNames.insert( QStringLiteral("create date"), i18n("added to collection") );
0097     m_fieldPrettyNames.insert( QStringLiteral("score"), i18n("score") );
0098     m_fieldPrettyNames.insert( QStringLiteral("rating"), i18n("rating") );
0099     m_fieldPrettyNames.insert( QStringLiteral("first played"), i18n("first played") );
0100     m_fieldPrettyNames.insert( QStringLiteral("last played"), i18n("last played") );
0101     m_fieldPrettyNames.insert( QStringLiteral("play count"), i18n("play count") );
0102     m_fieldPrettyNames.insert( QStringLiteral("label"), i18n("label") );
0103 }
0104 
0105 ConstraintTypes::TagMatchFieldsModel::~TagMatchFieldsModel()
0106 {
0107 }
0108 
0109 int
0110 ConstraintTypes::TagMatchFieldsModel::rowCount( const QModelIndex& parent ) const
0111 {
0112     Q_UNUSED( parent )
0113     return m_fieldNames.length();
0114 }
0115 
0116 QVariant
0117 ConstraintTypes::TagMatchFieldsModel::data( const QModelIndex& idx, int role ) const
0118 {
0119     QString s = m_fieldNames.at( idx.row() );
0120 
0121     switch ( role ) {
0122         case Qt::DisplayRole:
0123         case Qt::EditRole:
0124             return QVariant( m_fieldPrettyNames.value( s ) );
0125             break;
0126         default:
0127             return QVariant();
0128     }
0129     return QVariant();
0130 }
0131 
0132 bool
0133 ConstraintTypes::TagMatchFieldsModel::contains( const QString& s ) const
0134 {
0135     return m_fieldNames.contains( s );
0136 }
0137 
0138 int
0139 ConstraintTypes::TagMatchFieldsModel::index_of( const QString& s ) const
0140 {
0141     return m_fieldNames.indexOf( s );
0142 }
0143 
0144 QString
0145 ConstraintTypes::TagMatchFieldsModel::field_at( int idx ) const
0146 {
0147     if ( ( idx >= 0 ) && ( idx < m_fieldNames.length() ) )
0148         return m_fieldNames.at( idx );
0149     else
0150         return QString();
0151 }
0152 
0153 qint64
0154 ConstraintTypes::TagMatchFieldsModel::meta_value_of( const QString& f ) const
0155 {
0156     return m_fieldMetaValues.value( f );
0157 }
0158 
0159 QString
0160 ConstraintTypes::TagMatchFieldsModel::pretty_name_of( const QString& f ) const
0161 {
0162     return m_fieldPrettyNames.value( f );
0163 }
0164 
0165 ConstraintTypes::TagMatch::FieldTypes
0166 ConstraintTypes::TagMatchFieldsModel::type_of( const QString& f ) const
0167 {
0168     return m_fieldTypes.value( f );
0169 }
0170 
0171 /*************************************
0172 **************************************/
0173 
0174 ConstraintTypes::TagMatch::Comparer::Comparer() : m_dateWeight( 1209600.0 )
0175 {
0176     m_numFieldWeight.insert( Meta::valYear, 8.0 );
0177     m_numFieldWeight.insert( Meta::valTrackNr, 5.0 );
0178     m_numFieldWeight.insert( Meta::valDiscNr, 0.75 );
0179     m_numFieldWeight.insert( Meta::valLength, 100000.0 );
0180     m_numFieldWeight.insert( Meta::valScore, 20.0 );
0181     m_numFieldWeight.insert( Meta::valRating, 3.0 );
0182     m_numFieldWeight.insert( Meta::valPlaycount, 4.0 );
0183 }
0184 
0185 ConstraintTypes::TagMatch::Comparer::~Comparer()
0186 {
0187 }
0188 
0189 double
0190 ConstraintTypes::TagMatch::Comparer::compareNum( const double test,
0191                                                  const int comparison,
0192                                                  const double target,
0193                                                  const double strictness,
0194                                                  const qint64 field ) const
0195 {
0196     const double weight = m_numFieldWeight.value( field );
0197 
0198     if ( comparison == CompareNumEquals ) {
0199         // fuzzy equals -- within 1%, or within 0.001
0200         if ( ( abs( test - target ) < ( abs( test + target ) / 200.0 ) ) || ( abs( test - target ) < 0.001 ) ) {
0201             return 1.0;
0202         } else {
0203             return fuzzyProb( test, target, strictness, weight );
0204         }
0205     } else if ( comparison == CompareNumGreaterThan ) {
0206         return ( test > target ) ? 1.0 : fuzzyProb( test, target, strictness, weight );
0207     } else if ( comparison == CompareNumLessThan ) {
0208         return ( test < target ) ? 1.0 : fuzzyProb( test, target, strictness, weight );
0209     } else {
0210         return 0.0;
0211     }
0212     return 0.0;
0213 }
0214 
0215 double
0216 ConstraintTypes::TagMatch::Comparer::compareStr( const QString& test,
0217                                                  const int comparison,
0218                                                  const QString& target ) const
0219 {
0220     if ( comparison == CompareStrEquals ) {
0221         if ( test.compare( target, Qt::CaseInsensitive ) == 0 )
0222             return 1.0;
0223     } else if ( comparison == CompareStrStartsWith ) {
0224         if ( test.startsWith( target, Qt::CaseInsensitive ) )
0225             return 1.0;
0226     } else if ( comparison == CompareStrEndsWith ) {
0227         if ( test.endsWith( target, Qt::CaseInsensitive ) )
0228             return 1.0;
0229     } else if ( comparison == CompareStrContains ) {
0230         if ( test.contains( target, Qt::CaseInsensitive ) )
0231             return 1.0;
0232     } else if ( comparison == CompareStrRegExp ) {
0233         QRegExp rx( target );
0234         if ( rx.indexIn( test ) >= 0 )
0235             return 1.0;
0236     } else {
0237         return 0.0;
0238     }
0239     return 0.0;
0240 }
0241 
0242 
0243 double
0244 ConstraintTypes::TagMatch::Comparer::compareDate( const uint test,
0245                                                   const int comparison,
0246                                                   const QVariant& targetVar,
0247                                                   const double strictness ) const
0248 {
0249     const double weight = m_dateWeight;
0250 
0251     int comp = comparison;
0252     uint target = 0;
0253     if ( comparison == CompareDateWithin ) {
0254         comp = CompareDateAfter;
0255         QDateTime now = QDateTime::currentDateTime();
0256         DateRange r = targetVar.value<DateRange>();
0257         switch ( r.second ) {
0258             case 0:
0259                 target = now.addDays( -1 * r.first ).toSecsSinceEpoch();
0260                 break;
0261             case 1:
0262                 target = now.addMonths( -1 * r.first ).toSecsSinceEpoch();
0263                 break;
0264             case 2:
0265                 target = now.addYears( -1 * r.first ).toSecsSinceEpoch();
0266                 break;
0267             default:
0268                 break;
0269         }
0270     } else {
0271         target = targetVar.value<uint>();
0272     }
0273 
0274     const double dte = static_cast<double>( test );
0275     const double dta = static_cast<double>( target );
0276     if ( comp == CompareDateOn ) {
0277         // fuzzy equals -- within 1%, or within 10.0
0278         if ( ( abs( dte - dta ) < ( abs( dte + dta ) / 200.0 ) ) || ( abs( dte - dta ) < 10.0 ) ) {
0279             return 1.0;
0280         } else {
0281             return fuzzyProb( dte, dta, strictness, weight );
0282         }
0283     } else if ( comp == CompareDateAfter ) {
0284         return ( test > target ) ? 1.0 : fuzzyProb( dte, dta, strictness, weight );
0285     } else if ( comp == CompareDateBefore ) {
0286         return ( test < target ) ? 1.0 : fuzzyProb( dte, dta, strictness, weight );
0287     } else {
0288         return 0.0;
0289     }
0290     return 0.0;
0291 }
0292 
0293 double
0294 ConstraintTypes::TagMatch::Comparer::compareLabels( const Meta::TrackPtr &t,
0295                                                     const int comparison,
0296                                                     const QString& target ) const
0297 {
0298     Meta::LabelList labelList = t->labels();
0299 
0300     double v = 0.0;
0301     foreach ( Meta::LabelPtr label, labelList ) {
0302         // this is technically more correct ...
0303         // v = qMax( compare( label->prettyName(), comparison, target ), v );
0304 
0305         // ... but as long as compareStr() returns only 0.0 or 1.0, the following is faster:
0306         v = compareStr( label->prettyName(), comparison, target );
0307         if ( v > 0.99 ) {
0308             return 1.0;
0309         }
0310     }
0311 
0312     return v;
0313 }
0314 
0315 uint
0316 ConstraintTypes::TagMatch::Comparer::rangeDate( const double strictness ) const
0317 {
0318     if ( strictness > 0.99 ) return 0;
0319     const double s = strictness * strictness;
0320     return static_cast<uint>( ceil( 0.460517 * m_dateWeight / ( 0.1 + s ) ) );
0321 }
0322 
0323 int
0324 ConstraintTypes::TagMatch::Comparer::rangeNum( const double strictness, const qint64 field ) const
0325 {
0326     if ( strictness > 0.99 ) return 0;
0327     const double s = strictness * strictness;
0328     const double w = m_numFieldWeight.value( field );
0329     return static_cast<int>( ceil( 0.460517 * w / ( 0.1 + s ) ) );
0330 }
0331 
0332 double
0333 ConstraintTypes::TagMatch::Comparer::fuzzyProb( const double a, const double b, const double strictness, const double w ) const
0334 {
0335     const double s = strictness * strictness;
0336     return exp( -10.0 * ( 0.1 + s ) / w * ( 1 + abs( a - b ) ) );
0337 }