1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-03-21 00:09:47 +01:00

add a Song search type to dynamic playlists.

This commit is contained in:
Leo Franchi 2011-05-23 20:41:19 -04:00
parent 03c7c0e35f
commit 2f5dd43e5b
3 changed files with 163 additions and 43 deletions

View File

@ -172,6 +172,24 @@ Tomahawk::EchonestControl::updateWidgets()
connect( input, SIGNAL( textEdited( QString ) ), &m_editingTimer, SLOT( stop() ) );
connect( input, SIGNAL( textEdited( QString ) ), &m_delayedEditTimer, SLOT( start() ) );
match->hide();
input->hide();
m_match = QWeakPointer< QWidget >( match );
m_input = QWeakPointer< QWidget >( input );
} else if( selectedType() == "Song" ) {
m_currentType = Echonest::DynamicPlaylist::SongId;
QLabel* match = new QLabel( tr( "similar to" ) );
QLineEdit* input = new QLineEdit();
m_matchString = QString();
m_matchData = QString::number( (int)Echonest::DynamicPlaylist::SongRadioType );
connect( input, SIGNAL( textChanged(QString) ), this, SLOT( updateData() ) );
connect( input, SIGNAL( editingFinished() ), this, SLOT( editingFinished() ) );
connect( input, SIGNAL( textEdited( QString ) ), &m_editingTimer, SLOT( stop() ) );
connect( input, SIGNAL( textEdited( QString ) ), &m_delayedEditTimer, SLOT( start() ) );
match->hide();
input->hide();
m_match = QWeakPointer< QWidget >( match );
@ -394,7 +412,7 @@ Tomahawk::EchonestControl::updateData()
m_data.first = m_currentType;
m_data.second = edit->text();
}
} else if( selectedType() == "Artist Description" ) {
} else if( selectedType() == "Artist Description" || selectedType() == "Song" ) {
QLineEdit* edit = qobject_cast<QLineEdit*>( m_input.data() );
if( edit && !edit->text().isEmpty() ) {
m_data.first = m_currentType;
@ -467,7 +485,7 @@ Tomahawk::EchonestControl::updateWidgetsFromData()
QLineEdit* edit = qobject_cast<QLineEdit*>( m_input.data() );
if( edit )
edit->setText( m_data.second.toString() );
} else if( selectedType() == "Artist Description" ) {
} else if( selectedType() == "Artist Description" || selectedType() == "Song" ) {
QLineEdit* edit = qobject_cast<QLineEdit*>( m_input.data() );
if( edit )
edit->setText( m_data.second.toString() );
@ -547,6 +565,8 @@ Tomahawk::EchonestControl::calculateSummary()
summary = QString( "similar to ~%1" ).arg( m_data.second.toString() );
} else if( selectedType() == "Artist Description" ) {
summary = QString( "with genre ~%1" ).arg( m_data.second.toString() );
} else if( selectedType() == "Artist Description" ) {
summary = QString( "similar to ~%1" ).arg( m_data.second.toString() );
} else if( selectedType() == "Variety" || selectedType() == "Danceability" || selectedType() == "Artist Hotttnesss" || selectedType() == "Energy" || selectedType() == "Artist Familiarity" || selectedType() == "Song Hotttnesss" ) {
QString modifier;
qreal sliderVal = m_data.second.toReal();

View File

@ -90,19 +90,18 @@ QPixmap EchonestGenerator::logo()
void
EchonestGenerator::generate( int number )
{
// convert to an echonest query, and fire it off
qDebug() << Q_FUNC_INFO;
qDebug() << "Generating playlist with" << m_controls.size();
foreach( const dyncontrol_ptr& ctrl, m_controls )
qDebug() << ctrl->selectedType() << ctrl->match() << ctrl->input();
// convert to an echonest query, and fire it off
qDebug() << Q_FUNC_INFO;
qDebug() << "Generating playlist with" << m_controls.size();
foreach( const dyncontrol_ptr& ctrl, m_controls )
qDebug() << ctrl->selectedType() << ctrl->match() << ctrl->input();
setProperty( "number", number ); //HACK
connect( this, SIGNAL( paramsGenerated( Echonest::DynamicPlaylist::PlaylistParams ) ), this, SLOT( doGenerate(Echonest::DynamicPlaylist::PlaylistParams ) ) );
try {
Echonest::DynamicPlaylist::PlaylistParams params = getParams();
params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Results, number ) );
QNetworkReply* reply = Echonest::DynamicPlaylist::staticPlaylist( params );
qDebug() << "Generating a static playlist from echonest!" << reply->url().toString();
connect( reply, SIGNAL( finished() ), this, SLOT( staticFinished() ) );
getParams();
} catch( std::runtime_error& e ) {
qWarning() << "Got invalid controls!" << e.what();
emit error( "Filters are not valid", e.what() );
@ -112,18 +111,41 @@ EchonestGenerator::generate( int number )
void
EchonestGenerator::startOnDemand()
{
connect( this, SIGNAL( paramsGenerated( Echonest::DynamicPlaylist::PlaylistParams ) ), this, SLOT( doStartOnDemand( Echonest::DynamicPlaylist::PlaylistParams ) ) );
try {
Echonest::DynamicPlaylist::PlaylistParams params = getParams();
QNetworkReply* reply = m_dynPlaylist->start( params );
qDebug() << "starting a dynamic playlist from echonest!" << reply->url().toString();
connect( reply, SIGNAL( finished() ), this, SLOT( dynamicStarted() ) );
getParams();
} catch( std::runtime_error& e ) {
qWarning() << "Got invalid controls!" << e.what();
emit error( "Filters are not valid", e.what() );
}
}
void
EchonestGenerator::doGenerate( const Echonest::DynamicPlaylist::PlaylistParams& paramsIn )
{
disconnect( this, SIGNAL( paramsGenerated( Echonest::DynamicPlaylist::PlaylistParams ) ), this, SLOT( doGenerate(Echonest::DynamicPlaylist::PlaylistParams ) ) );
int number = property( "number" ).toInt();
setProperty( "number", QVariant() );
Echonest::DynamicPlaylist::PlaylistParams params = paramsIn;
params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Results, number ) );
QNetworkReply* reply = Echonest::DynamicPlaylist::staticPlaylist( params );
qDebug() << "Generating a static playlist from echonest!" << reply->url().toString();
connect( reply, SIGNAL( finished() ), this, SLOT( staticFinished() ) );
}
void
EchonestGenerator::doStartOnDemand( const Echonest::DynamicPlaylist::PlaylistParams& params )
{
disconnect( this, SIGNAL( paramsGenerated( Echonest::DynamicPlaylist::PlaylistParams ) ), this, SLOT( doStartOnDemand( Echonest::DynamicPlaylist::PlaylistParams ) ) );
QNetworkReply* reply = m_dynPlaylist->start( params );
qDebug() << "starting a dynamic playlist from echonest!" << reply->url().toString();
connect( reply, SIGNAL( finished() ), this, SLOT( dynamicStarted() ) );
}
void
EchonestGenerator::fetchNext( int rating )
{
@ -173,17 +195,78 @@ EchonestGenerator::staticFinished()
emit generated( queries );
}
Echonest::DynamicPlaylist::PlaylistParams
EchonestGenerator::getParams() const throw( std::runtime_error )
void
EchonestGenerator::getParams() throw( std::runtime_error )
{
Echonest::DynamicPlaylist::PlaylistParams params;
foreach( const dyncontrol_ptr& control, m_controls ) {
params.append( control.dynamicCast<EchonestControl>()->toENParam() );
}
appendRadioType( params );
return params;
if( appendRadioType( params ) == Echonest::DynamicPlaylist::SongRadioType ) {
// we need to do another pass, converting all song queries to song-ids.
m_storedParams = params;
qDeleteAll( m_waiting );
m_waiting.clear();
// one query per track
for( int i = 0; i < params.count(); i++ ) {
const Echonest::DynamicPlaylist::PlaylistParamData param = params.value( i );
if( param.first == Echonest::DynamicPlaylist::SongId ) { // this is a song type enum
QString text = param.second.toString();
Echonest::Song::SearchParams q;
q.append( Echonest::Song::SearchParamData( Echonest::Song::Combined, text ) ); // search with the free text "combined" parameter
QNetworkReply* r = Echonest::Song::search( q );
r->setProperty( "index", i );
r->setProperty( "search", text );
m_waiting.insert( r );
connect( r, SIGNAL( finished() ), this, SLOT( songLookupFinished() ) );
}
}
} else {
emit paramsGenerated( params );
}
}
void
EchonestGenerator::songLookupFinished()
{
QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() );
if( !m_waiting.contains( r ) ) // another generate/start was begun meanwhile, we're out of date
return;
Q_ASSERT( r );
m_waiting.remove( r );
QString search = r->property( "search" ).toString();
QByteArray id;
try {
Echonest::SongList songs = Echonest::Song::parseSearch( r );
if( songs.size() > 0 ) {
id = songs.first().id();
qDebug() << "Got ID for song:" << songs.first() << "from search:" << search;;
} else {
qDebug() << "Got no songs from our song id lookup.. :(. We looked for:" << search;
}
} catch( Echonest::ParseError& e ) {
qWarning() << "Failed to parse song/search result:" << e.errorType() << e.what();
}
int idx = r->property( "index" ).toInt();
Q_ASSERT( m_storedParams.count() >= idx );
// replace the song text with the song id in-place
m_storedParams[ idx ].second = id;
if( m_waiting.isEmpty() ) { // we're done!
emit paramsGenerated( m_storedParams );
}
}
void
EchonestGenerator::dynamicStarted()
{
@ -257,9 +340,9 @@ EchonestGenerator::onlyThisArtistType( Echonest::DynamicPlaylist::ArtistTypeEnum
bool some = false;
foreach( const dyncontrol_ptr& control, m_controls ) {
if( ( control->selectedType() == "Artist" || control->selectedType() == "Artist Description" ) && static_cast<Echonest::DynamicPlaylist::ArtistTypeEnum>( control->match().toInt() ) != type ) {
if( ( control->selectedType() == "Artist" || control->selectedType() == "Artist Description" || control->selectedType() == "Song" ) && static_cast<Echonest::DynamicPlaylist::ArtistTypeEnum>( control->match().toInt() ) != type ) {
only = false;
} else if( ( control->selectedType() == "Artist" || control->selectedType() == "Artist Description" ) && static_cast<Echonest::DynamicPlaylist::ArtistTypeEnum>( control->match().toInt() ) == type ) {
} else if( ( control->selectedType() == "Artist" || control->selectedType() == "Artist Description" || control->selectedType() == "Song" ) && static_cast<Echonest::DynamicPlaylist::ArtistTypeEnum>( control->match().toInt() ) == type ) {
some = true;
}
}
@ -272,26 +355,29 @@ EchonestGenerator::onlyThisArtistType( Echonest::DynamicPlaylist::ArtistTypeEnum
return false;
}
void
Echonest::DynamicPlaylist::ArtistTypeEnum
EchonestGenerator::appendRadioType( Echonest::DynamicPlaylist::PlaylistParams& params ) const throw( std::runtime_error )
{
/**
* So we try to match the best type of echonest playlist, based on the controls
* the types are artist, artist-radio, artist-description, catalog, catalog-radio, song-radio. we don't care about the catalog ones, and
* we can't use the song ones since for the moment EN only accepts Song IDs, not names, and we don't want to insert an extra song.search
* call first.
* the types are artist, artist-radio, artist-description, catalog, catalog-radio, song-radio. we don't care about the catalog ones
*
*/
/// 1. artist: If all the artist controls are Limit-To. If some were but not all, error out.
/// 2. artist-description: If all the artist entries are Description. If some were but not all, error out.
/// 3. artist-radio: If all the artist entries are Similar To. If some were but not all, error out.
/// 4. song-radio: If all the artist entries are Similar To. If some were but not all, error out.
if( onlyThisArtistType( Echonest::DynamicPlaylist::ArtistType ) )
params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::ArtistType ) );
else if( onlyThisArtistType( Echonest::DynamicPlaylist::ArtistDescriptionType ) )
params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::ArtistDescriptionType ) );
else if( onlyThisArtistType( Echonest::DynamicPlaylist::ArtistRadioType ) )
params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::ArtistRadioType ) );
else if( onlyThisArtistType( Echonest::DynamicPlaylist::SongRadioType ) )
params.append( Echonest::DynamicPlaylist::PlaylistParamData( Echonest::DynamicPlaylist::Type, Echonest::DynamicPlaylist::SongRadioType ) );
return static_cast< Echonest::DynamicPlaylist::ArtistTypeEnum >( params.last().second.toInt() );
}
query_ptr
@ -336,13 +422,13 @@ EchonestGenerator::sentenceSummary()
QList< dyncontrol_ptr > allcontrols = m_controls;
QString sentence = "Songs ";
/// 1. Collect all artist filters
/// 1. Collect all required filters
/// 2. Get the sorted by filter if it exists.
QList< dyncontrol_ptr > artists;
QList< dyncontrol_ptr > required;
dyncontrol_ptr sorting;
foreach( const dyncontrol_ptr& control, allcontrols ) {
if( control->selectedType() == "Artist" || control->selectedType() == "Artist Description" )
artists << control;
if( control->selectedType() == "Artist" || control->selectedType() == "Artist Description" || control->selectedType() == "Song" )
required << control;
else if( control->selectedType() == "Sorting" )
sorting = control;
}
@ -351,23 +437,23 @@ EchonestGenerator::sentenceSummary()
/// Skip empty artists
QList< dyncontrol_ptr > empty;
foreach( const dyncontrol_ptr& artist, artists ) {
foreach( const dyncontrol_ptr& artist, required ) {
QString summary = artist.dynamicCast< EchonestControl >()->summary();
if( summary.lastIndexOf( "~" ) == summary.length() - 1 )
empty << artist;
}
foreach( const dyncontrol_ptr& toremove, empty ) {
artists.removeAll( toremove );
required.removeAll( toremove );
allcontrols.removeAll( toremove );
}
/// If there are no artists and no filters, show some help text
if( artists.isEmpty() && allcontrols.isEmpty() )
if( required.isEmpty() && allcontrols.isEmpty() )
sentence = "No configured filters!";
/// Do the assembling. Start with the artists if there are any, then do all the rest.
for( int i = 0; i < artists.size(); i++ ) {
dyncontrol_ptr artist = artists.value( i );
for( int i = 0; i < required.size(); i++ ) {
dyncontrol_ptr artist = required.value( i );
allcontrols.removeAll( artist ); // remove from pool while we're here
/// Collapse artist lists
@ -376,9 +462,9 @@ EchonestGenerator::sentenceSummary()
if( i == 0 ) { // if it's the first.. special casez
center = summary.remove( "~" );
if( artists.size() == 2 ) // special case for 2, no comma. ( X and Y )
if( required.size() == 2 ) // special case for 2, no comma. ( X and Y )
suffix = " and ";
else if( artists.size() > 2 ) // in a list with more after
else if( required.size() > 2 ) // in a list with more after
suffix = ", ";
else if( allcontrols.isEmpty() && sorting.isNull() ) // the last one, and no more controls, so put a period
suffix = ".";
@ -386,7 +472,7 @@ EchonestGenerator::sentenceSummary()
suffix = " ";
} else {
center = summary.mid( summary.indexOf( "~" ) + 1 );
if( i == artists.size() - 1 ) { // if there are more, add an " and "
if( i == required.size() - 1 ) { // if there are more, add an " and "
if( !( allcontrols.isEmpty() && sorting.isNull() ) )
suffix = ", ";
else
@ -402,7 +488,7 @@ EchonestGenerator::sentenceSummary()
const bool last = ( i == allcontrols.size() - 1 && sorting.isNull() );
QString prefix, suffix;
if( last ) { // only if there is not just 1
if( !( artists.isEmpty() && allcontrols.size() == 1 ) )
if( !( required.isEmpty() && allcontrols.size() == 1 ) )
prefix = "and ";
suffix = ".";
} else

View File

@ -61,6 +61,10 @@ public:
static QVector< QString > styles();
static QVector< QString > moods();
signals:
void paramsGenerated( const Echonest::DynamicPlaylist::PlaylistParams& );
private slots:
void staticFinished();
void dynamicStarted();
@ -71,13 +75,19 @@ private slots:
void steerDescription( const QString& desc );
void resetSteering();
void doGenerate( const Echonest::DynamicPlaylist::PlaylistParams& params );
void doStartOnDemand( const Echonest::DynamicPlaylist::PlaylistParams& params );
void stylesReceived();
void moodsReceived();
void songLookupFinished();
private:
Echonest::DynamicPlaylist::PlaylistParams getParams() const throw( std::runtime_error );
// get result from signal paramsGenerated
void getParams() throw( std::runtime_error );
query_ptr queryFromSong( const Echonest::Song& song );
void appendRadioType( Echonest::DynamicPlaylist::PlaylistParams& params ) const throw( std::runtime_error );
Echonest::DynamicPlaylist::ArtistTypeEnum appendRadioType( Echonest::DynamicPlaylist::PlaylistParams& params ) const throw( std::runtime_error );
bool onlyThisArtistType( Echonest::DynamicPlaylist::ArtistTypeEnum type ) const throw( std::runtime_error );
Echonest::DynamicPlaylist* m_dynPlaylist;
@ -86,6 +96,10 @@ private:
static QVector< QString > s_styles;
static QVector< QString > s_moods;
// used for the intermediary song id lookup
QSet< QNetworkReply* > m_waiting;
Echonest::DynamicPlaylist::PlaylistParams m_storedParams;
QWeakPointer<EchonestSteerer> m_steerer;
bool m_steeredSinceLastTrack;
Echonest::DynamicPlaylist::DynamicControl m_steerData;