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 }