File indexing completed on 2024-05-19 04:49:56
0001 /**************************************************************************************** 0002 * Copyright (c) 2008-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::Checkpoint" 0018 0019 #include "Checkpoint.h" 0020 0021 #include "playlistgenerator/Constraint.h" 0022 #include "playlistgenerator/ConstraintFactory.h" 0023 0024 #include "core/meta/Meta.h" 0025 #include "core/support/Debug.h" 0026 #include "core-impl/collections/support/CollectionManager.h" 0027 0028 #include <QUrl> 0029 0030 #include <algorithm> 0031 #include <climits> 0032 #include <cmath> 0033 0034 Constraint* 0035 ConstraintTypes::Checkpoint::createFromXml( QDomElement& xmlelem, ConstraintNode* p ) 0036 { 0037 if ( p ) { 0038 return new Checkpoint( xmlelem, p ); 0039 } else { 0040 return nullptr; 0041 } 0042 } 0043 0044 Constraint* 0045 ConstraintTypes::Checkpoint::createNew( ConstraintNode* p ) 0046 { 0047 if ( p ) { 0048 return new Checkpoint( p ); 0049 } else { 0050 return nullptr; 0051 } 0052 } 0053 0054 ConstraintFactoryEntry* 0055 ConstraintTypes::Checkpoint::registerMe() 0056 { 0057 return new ConstraintFactoryEntry( QStringLiteral("Checkpoint"), 0058 i18n("Checkpoint"), 0059 i18n("Fixes a track, album, or artist to a certain position in the playlist"), 0060 &Checkpoint::createFromXml, &Checkpoint::createNew ); 0061 } 0062 0063 ConstraintTypes::Checkpoint::Checkpoint( QDomElement& xmlelem, ConstraintNode* p ) 0064 : Constraint( p ) 0065 , m_position( 0 ) 0066 , m_strictness( 1.0 ) 0067 , m_checkpointType( CheckpointTrack ) 0068 , m_matcher( nullptr ) 0069 { 0070 QDomAttr a; 0071 0072 a = xmlelem.attributeNode( QStringLiteral("position") ); 0073 if ( !a.isNull() ) 0074 m_position = a.value().toInt(); 0075 0076 0077 a = xmlelem.attributeNode( QStringLiteral("checkpointtype") ); 0078 if ( !a.isNull() ) 0079 m_checkpointType = static_cast<CheckpointType>( a.value().toInt() ); 0080 0081 a = xmlelem.attributeNode( QStringLiteral("trackurl") ); 0082 if ( !a.isNull() ) { 0083 Meta::TrackPtr trk = CollectionManager::instance()->trackForUrl( QUrl( a.value() ) ); 0084 if ( trk ) { 0085 if ( m_checkpointType == CheckpointAlbum ) { 0086 m_checkpointObject = Meta::DataPtr::dynamicCast( trk->album() ); 0087 } else if ( m_checkpointType == CheckpointArtist ) { 0088 m_checkpointObject = Meta::DataPtr::dynamicCast( trk->artist() ); 0089 } else { 0090 m_checkpointObject = Meta::DataPtr::dynamicCast( trk ); 0091 } 0092 } 0093 } 0094 0095 setCheckpoint( m_checkpointObject ); 0096 0097 a = xmlelem.attributeNode( QStringLiteral("strictness") ); 0098 if ( !a.isNull() ) 0099 m_strictness = a.value().toDouble(); 0100 debug() << getName(); 0101 } 0102 0103 ConstraintTypes::Checkpoint::Checkpoint( ConstraintNode* p ) 0104 : Constraint( p ) 0105 , m_position( 0 ) 0106 , m_strictness( 1.0 ) 0107 , m_checkpointType( CheckpointTrack ) 0108 , m_matcher( nullptr ) 0109 { 0110 } 0111 0112 ConstraintTypes::Checkpoint::~Checkpoint() 0113 { 0114 delete m_matcher; 0115 } 0116 0117 0118 QWidget* 0119 ConstraintTypes::Checkpoint::editWidget() const 0120 { 0121 CheckpointEditWidget* e = new CheckpointEditWidget( m_position, static_cast<int>( 10*m_strictness ), m_checkpointObject ); 0122 connect( e, &CheckpointEditWidget::positionChanged, this, &Checkpoint::setPosition ); 0123 connect( e, &CheckpointEditWidget::strictnessChanged, this, &Checkpoint::setStrictness ); 0124 connect( e, &CheckpointEditWidget::checkpointChanged, this, &Checkpoint::setCheckpoint ); 0125 return e; 0126 } 0127 0128 void 0129 ConstraintTypes::Checkpoint::toXml( QDomDocument& doc, QDomElement& elem ) const 0130 { 0131 if( !m_checkpointObject ) 0132 return; 0133 0134 QDomElement c = doc.createElement( QStringLiteral("constraint") ); 0135 QDomText t = doc.createTextNode( getName() ); 0136 c.appendChild( t ); 0137 c.setAttribute( QStringLiteral("type"), QStringLiteral("Checkpoint") ); 0138 c.setAttribute( QStringLiteral("position"), m_position ); 0139 c.setAttribute( QStringLiteral("checkpointtype"), m_checkpointType ); 0140 Meta::TrackPtr r; 0141 Meta::ArtistPtr a; 0142 Meta::AlbumPtr l; 0143 switch ( m_checkpointType ) { 0144 case CheckpointTrack: 0145 r = Meta::TrackPtr::dynamicCast( m_checkpointObject ); 0146 c.setAttribute( QStringLiteral("trackurl"), r->uidUrl() ); 0147 break; 0148 case CheckpointAlbum: 0149 l = Meta::AlbumPtr::dynamicCast( m_checkpointObject ); 0150 if ( l->tracks().length() > 0 ) { 0151 r = l->tracks().first(); 0152 c.setAttribute( QStringLiteral("trackurl"), r->uidUrl() ); 0153 } 0154 break; 0155 case CheckpointArtist: 0156 a = Meta::ArtistPtr::dynamicCast( m_checkpointObject ); 0157 if ( a->tracks().length() > 0 ) { 0158 r = a->tracks().first(); 0159 c.setAttribute( QStringLiteral("trackurl"), r->uidUrl() ); 0160 } 0161 break; 0162 } 0163 c.setAttribute( QStringLiteral("strictness"), QString::number( m_strictness ) ); 0164 elem.appendChild( c ); 0165 } 0166 0167 QString 0168 ConstraintTypes::Checkpoint::getName() const 0169 { 0170 KLocalizedString name( ki18n("Checkpoint: %1") ); 0171 Meta::TrackPtr t; 0172 Meta::AlbumPtr l; 0173 Meta::ArtistPtr r; 0174 switch ( m_checkpointType ) { 0175 case CheckpointTrack: 0176 t = Meta::TrackPtr::dynamicCast( m_checkpointObject ); 0177 if ( t == Meta::TrackPtr() ) { 0178 name = name.subs( i18n("unassigned") ); 0179 } else { 0180 name = name.subs( i18n("\"%1\" (track) by %2", t->prettyName(), t->artist()->prettyName() ) ); 0181 } 0182 break; 0183 case CheckpointAlbum: 0184 l = Meta::AlbumPtr::dynamicCast( m_checkpointObject ); 0185 if ( l == Meta::AlbumPtr() ) { 0186 name = name.subs( i18n("unassigned") ); 0187 } else { 0188 if ( l->hasAlbumArtist() ) { 0189 name = name.subs( i18n("\"%1\" (album) by %2", l->prettyName(), l->albumArtist()->prettyName() ) ); 0190 } else { 0191 name = name.subs( i18n("\"%1\" (album)", l->prettyName() ) ); 0192 } 0193 } 0194 break; 0195 case CheckpointArtist: 0196 r = Meta::ArtistPtr::dynamicCast( m_checkpointObject ); 0197 if ( r == Meta::ArtistPtr() ) { 0198 name = name.subs( i18n("unassigned") ); 0199 } else { 0200 name = name.subs( i18n("\"%1\" (artist)", r->prettyName() ) ); 0201 } 0202 break; 0203 } 0204 0205 return name.toString(); 0206 } 0207 0208 double 0209 ConstraintTypes::Checkpoint::satisfaction( const Meta::TrackList& tl ) const 0210 { 0211 // What are the ending time boundaries of each track in this playlist? 0212 qint64 start = 0; 0213 QList< qint64 > boundaries; 0214 foreach ( const Meta::TrackPtr t, tl ) { 0215 boundaries << ( start += t->length() ); 0216 } 0217 0218 // Is the playlist long enough to contain the checkpoint? 0219 if ( boundaries.last() < m_position ) { 0220 return 0.0; // no, it does not 0221 } 0222 0223 // Where are the appropriate tracks in this playlist? 0224 QList<int> locs = m_matcher->find( tl ); 0225 if ( locs.size() < 1 ) { 0226 return 0.0; // none found 0227 } else { 0228 qint64 best = boundaries.last(); // the length of the playlist is the upper bound for distances 0229 foreach ( int i, locs ) { 0230 qint64 start = ( i>0 )?boundaries.at( i-1 ):0; 0231 qint64 end = boundaries.at( i ); 0232 if ( (start <= m_position) && ( end >= m_position ) ) { 0233 // checkpoint position has a match flanking it 0234 return 1.0; 0235 } else if ( end < m_position ) { 0236 // appropriate track is before the checkpoint 0237 best = (best < (m_position - end))?best:(m_position - end); 0238 } else if ( start > m_position ) { 0239 // appropriate track is after the checkpoint 0240 best = (best < (start - m_position))?best:(start - m_position); 0241 } else { 0242 warning() << "WTF JUST HAPPENED?" << m_position << "(" << start << "," << end << ")"; 0243 } 0244 } 0245 return penalty( best ); 0246 } 0247 0248 warning() << "Improper exit condition"; 0249 return 0.0; 0250 } 0251 0252 double 0253 ConstraintTypes::Checkpoint::penalty( const qint64 d ) const 0254 { 0255 return exp( d / ( -( 120000.0 * ( 1.0 + ( 8.0 * m_strictness ) ) ) ) ); 0256 } 0257 0258 quint32 0259 ConstraintTypes::Checkpoint::suggestPlaylistSize() const 0260 { 0261 return static_cast<quint32>( m_position / 300000 ) + 1; 0262 } 0263 0264 void 0265 ConstraintTypes::Checkpoint::setPosition( const int v ) 0266 { 0267 m_position = static_cast<qint64>( v ); 0268 } 0269 0270 void 0271 ConstraintTypes::Checkpoint::setStrictness( const int sv ) 0272 { 0273 m_strictness = static_cast<double>(sv)/10.0; 0274 } 0275 0276 void 0277 ConstraintTypes::Checkpoint::setCheckpoint( const Meta::DataPtr& data ) 0278 { 0279 if ( data == Meta::DataPtr() ) 0280 return; 0281 0282 delete m_matcher; 0283 if ( Meta::TrackPtr track = Meta::TrackPtr::dynamicCast( data ) ) { 0284 m_checkpointType = CheckpointTrack; 0285 m_matcher = new TrackMatcher( track ); 0286 debug() << "setting checkpoint track:" << track->prettyName(); 0287 } else if ( Meta::AlbumPtr album = Meta::AlbumPtr::dynamicCast( data ) ) { 0288 m_checkpointType = CheckpointAlbum; 0289 m_matcher = new AlbumMatcher( album ); 0290 debug() << "setting checkpoint album:" << album->prettyName(); 0291 } else if ( Meta::ArtistPtr artist = Meta::ArtistPtr::dynamicCast( data ) ) { 0292 debug() << "setting checkpoint artist:" << artist->prettyName(); 0293 m_matcher = new ArtistMatcher( artist ); 0294 m_checkpointType = CheckpointArtist; 0295 } 0296 0297 m_checkpointObject = data; 0298 Q_EMIT dataChanged(); 0299 } 0300 0301 /****************************** 0302 * Track Matcher * 0303 ******************************/ 0304 ConstraintTypes::Checkpoint::TrackMatcher::TrackMatcher( const Meta::TrackPtr& t ) 0305 : m_trackToMatch( t ) 0306 { 0307 } 0308 0309 ConstraintTypes::Checkpoint::TrackMatcher::~TrackMatcher() 0310 { 0311 } 0312 0313 QList<int> 0314 ConstraintTypes::Checkpoint::TrackMatcher::find( const Meta::TrackList& tl ) const 0315 { 0316 QList<int> positions; 0317 for ( int i = 0; i < tl.length(); i++ ) { 0318 if ( tl.at( i ) == m_trackToMatch ) { 0319 positions << i; 0320 } 0321 } 0322 0323 return positions; 0324 } 0325 0326 bool 0327 ConstraintTypes::Checkpoint::TrackMatcher::match( const Meta::TrackPtr& t ) const 0328 { 0329 return ( t == m_trackToMatch ); 0330 } 0331 0332 /****************************** 0333 * Artist Matcher * 0334 ******************************/ 0335 ConstraintTypes::Checkpoint::ArtistMatcher::ArtistMatcher( const Meta::ArtistPtr& a ) 0336 : m_artistToMatch( a ) 0337 { 0338 } 0339 0340 ConstraintTypes::Checkpoint::ArtistMatcher::~ArtistMatcher() 0341 { 0342 } 0343 0344 QList<int> 0345 ConstraintTypes::Checkpoint::ArtistMatcher::find( const Meta::TrackList& tl ) const 0346 { 0347 QList<int> positions; 0348 for ( int i = 0; i < tl.length(); i++ ) { 0349 if ( tl.at( i )->artist() == m_artistToMatch ) { 0350 positions << i; 0351 } 0352 } 0353 0354 return positions; 0355 } 0356 0357 bool 0358 ConstraintTypes::Checkpoint::ArtistMatcher::match( const Meta::TrackPtr& t ) const 0359 { 0360 return ( t->artist() == m_artistToMatch ); 0361 } 0362 0363 /****************************** 0364 * Album Matcher * 0365 ******************************/ 0366 ConstraintTypes::Checkpoint::AlbumMatcher::AlbumMatcher( const Meta::AlbumPtr& l ) 0367 : m_albumToMatch( l ) 0368 { 0369 } 0370 0371 ConstraintTypes::Checkpoint::AlbumMatcher::~AlbumMatcher() 0372 { 0373 } 0374 0375 QList<int> 0376 ConstraintTypes::Checkpoint::AlbumMatcher::find( const Meta::TrackList& tl ) const 0377 { 0378 QList<int> positions; 0379 for ( int i = 0; i < tl.length(); i++ ) { 0380 if ( tl.at( i )->album() == m_albumToMatch ) { 0381 positions << i; 0382 } 0383 } 0384 0385 return positions; 0386 } 0387 0388 bool 0389 ConstraintTypes::Checkpoint::AlbumMatcher::match( const Meta::TrackPtr& t ) const 0390 { 0391 return ( t->album() == m_albumToMatch ); 0392 } 0393 0394 /****************************** 0395 * Edit Widget * 0396 ******************************/ 0397 0398 ConstraintTypes::CheckpointEditWidget::CheckpointEditWidget( const qint64 length, 0399 const int strictness, 0400 const Meta::DataPtr& data ) : QWidget( nullptr ) 0401 { 0402 ui.setupUi( this ); 0403 0404 ui.timeEdit_Position->setTime( QTime(0, 0, 0).addMSecs( length ) ); 0405 ui.slider_Strictness->setValue( strictness ); 0406 ui.trackSelector->setData( data ); 0407 } 0408 0409 void 0410 ConstraintTypes::CheckpointEditWidget::on_timeEdit_Position_timeChanged( const QTime& t ) 0411 { 0412 Q_EMIT positionChanged( QTime(0, 0, 0).msecsTo( t ) ); 0413 Q_EMIT updated(); 0414 } 0415 0416 void 0417 ConstraintTypes::CheckpointEditWidget::on_slider_Strictness_valueChanged( const int v ) 0418 { 0419 Q_EMIT strictnessChanged( v ); 0420 Q_EMIT updated(); 0421 } 0422 0423 void 0424 ConstraintTypes::CheckpointEditWidget::on_trackSelector_selectionChanged( const Meta::DataPtr& data ) 0425 { 0426 Q_EMIT checkpointChanged( data ); 0427 Q_EMIT updated(); 0428 }