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 }