added last.fm chart playlists

added front end support for library tracks
This commit is contained in:
aj 2019-11-01 23:46:49 +00:00
parent 71fdb6620d
commit f5eb07ca21
5 changed files with 280 additions and 31 deletions

View File

@ -95,9 +95,14 @@ def playlist(username=None):
playlist_add_this_month = request_json.get('add_this_month', None) playlist_add_this_month = request_json.get('add_this_month', None)
playlist_add_last_month = request_json.get('add_last_month', None) playlist_add_last_month = request_json.get('add_last_month', None)
playlist_library_tracks = request_json.get('include_library_tracks', None)
playlist_recommendation = request_json.get('include_recommendations', None) playlist_recommendation = request_json.get('include_recommendations', None)
playlist_recommendation_sample = request_json.get('recommendation_sample', None) playlist_recommendation_sample = request_json.get('recommendation_sample', None)
playlist_chart_range = request_json.get('chart_range', None)
playlist_chart_limit = request_json.get('chart_limit', None)
queried_playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()] queried_playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()]
if request.method == 'PUT': if request.method == 'PUT':
@ -111,6 +116,7 @@ def playlist(username=None):
'name': playlist_name, 'name': playlist_name,
'parts': playlist_parts if playlist_parts is not None else [], 'parts': playlist_parts if playlist_parts is not None else [],
'playlist_references': playlist_references if playlist_references is not None else [], 'playlist_references': playlist_references if playlist_references is not None else [],
'include_library_tracks': playlist_library_tracks if playlist_library_tracks is not None else False,
'include_recommendations': playlist_recommendation if playlist_recommendation is not None else False, 'include_recommendations': playlist_recommendation if playlist_recommendation is not None else False,
'recommendation_sample': playlist_recommendation_sample if playlist_recommendation_sample is not None else 10, 'recommendation_sample': playlist_recommendation_sample if playlist_recommendation_sample is not None else 10,
'uri': None, 'uri': None,
@ -127,6 +133,10 @@ def playlist(username=None):
to_add['add_this_month'] = playlist_add_this_month if playlist_add_this_month is not None else False to_add['add_this_month'] = playlist_add_this_month if playlist_add_this_month is not None else False
to_add['add_last_month'] = playlist_add_last_month if playlist_add_last_month is not None else False to_add['add_last_month'] = playlist_add_last_month if playlist_add_last_month is not None else False
if playlist_type == 'fmchart':
to_add['chart_range'] = playlist_chart_range
to_add['chart_limit'] = playlist_chart_limit if playlist_chart_limit is not None else 50
playlists.document().set(to_add) playlists.document().set(to_add)
logger.info(f'added {username} / {playlist_name}') logger.info(f'added {username} / {playlist_name}')
@ -171,15 +181,28 @@ def playlist(username=None):
if playlist_add_last_month is not None: if playlist_add_last_month is not None:
dic['add_last_month'] = playlist_add_last_month dic['add_last_month'] = playlist_add_last_month
if playlist_library_tracks is not None:
dic['include_library_tracks'] = playlist_library_tracks
if playlist_recommendation is not None: if playlist_recommendation is not None:
dic['include_recommendations'] = playlist_recommendation dic['include_recommendations'] = playlist_recommendation
if playlist_recommendation_sample is not None: if playlist_recommendation_sample is not None:
dic['recommendation_sample'] = playlist_recommendation_sample dic['recommendation_sample'] = playlist_recommendation_sample
if playlist_chart_range is not None:
dic['chart_range'] = playlist_chart_range
if playlist_chart_limit is not None:
dic['chart_limit'] = playlist_chart_limit
if playlist_type is not None: if playlist_type is not None:
dic['type'] = playlist_type dic['type'] = playlist_type
if playlist_type == 'fmchart':
dic['chart_range'] = 'YEAR'
dic['chart_limit'] = 50
if len(dic) == 0: if len(dic) == 0:
logger.warning(f'no changes to make for {username} / {playlist_name}') logger.warning(f'no changes to make for {username} / {playlist_name}')
return jsonify({"message": 'no changes to make', "status": "error"}), 400 return jsonify({"message": 'no changes to make', "status": "error"}), 400

View File

@ -8,7 +8,7 @@ from spotframework.net.network import Network as SpotifyNetwork
from fmframework.net.network import Network as FmNetwork from fmframework.net.network import Network as FmNetwork
from music.db.user import DatabaseUser from music.db.user import DatabaseUser
from music.model.user import User from music.model.user import User
from music.model.playlist import Playlist, RecentsPlaylist, Sort from music.model.playlist import Playlist, RecentsPlaylist, LastFMChartPlaylist, Sort
db = firestore.Client() db = firestore.Client()
@ -260,6 +260,39 @@ def parse_playlist_reference(username, playlist_ref=None, playlist_snapshot=None
add_this_month=playlist_dict.get('add_this_month'), add_this_month=playlist_dict.get('add_this_month'),
day_boundary=playlist_dict.get('day_boundary')) day_boundary=playlist_dict.get('day_boundary'))
elif playlist_dict.get('type') == 'fmchart':
return LastFMChartPlaylist(uri=playlist_dict.get('uri'),
name=playlist_dict.get('name'),
username=username,
db_ref=playlist_ref,
include_recommendations=playlist_dict.get('include_recommendations', False),
recommendation_sample=playlist_dict.get('recommendation_sample', 0),
include_library_tracks=playlist_dict.get('include_library_tracks', False),
parts=playlist_dict.get('parts'),
playlist_references=playlist_dict.get('playlist_references'),
shuffle=playlist_dict.get('shuffle'),
sort=Sort[playlist_dict.get('sort', 'release_date')],
description_overwrite=playlist_dict.get('description_overwrite'),
description_suffix=playlist_dict.get('description_suffix'),
lastfm_stat_count=playlist_dict.get('lastfm_stat_count', 0),
lastfm_stat_album_count=playlist_dict.get('lastfm_stat_album_count', 0),
lastfm_stat_artist_count=playlist_dict.get('lastfm_stat_artist_count', 0),
lastfm_stat_percent=playlist_dict.get('lastfm_stat_percent', 0),
lastfm_stat_album_percent=playlist_dict.get('lastfm_stat_album_percent', 0),
lastfm_stat_artist_percent=playlist_dict.get('lastfm_stat_artist_percent', 0),
lastfm_stat_last_refresh=playlist_dict.get('lastfm_stat_last_refresh'),
chart_limit=playlist_dict.get('chart_limit'),
chart_range=FmNetwork.Range[playlist_dict.get('chart_range')])
def update_playlist(username: str, name: str, updates: dict) -> None: def update_playlist(username: str, name: str, updates: dict) -> None:
if len(updates) > 0: if len(updates) > 0:

View File

@ -3,6 +3,8 @@ from enum import Enum
from datetime import datetime from datetime import datetime
from google.cloud.firestore import DocumentReference from google.cloud.firestore import DocumentReference
from fmframework.net.network import Network
import music.db.database as database import music.db.database as database
@ -73,6 +75,7 @@ class Playlist:
return { return {
'uri': self.uri, 'uri': self.uri,
'name': self.name, 'name': self.name,
'type': 'default',
'include_recommendations': self.include_recommendations, 'include_recommendations': self.include_recommendations,
'recommendation_sample': self.recommendation_sample, 'recommendation_sample': self.recommendation_sample,
@ -325,7 +328,8 @@ class RecentsPlaylist(Playlist):
response.update({ response.update({
'add_last_month': self.add_last_month, 'add_last_month': self.add_last_month,
'add_this_month': self.add_this_month, 'add_this_month': self.add_this_month,
'day_boundary': self.day_boundary 'day_boundary': self.day_boundary,
'type': 'recents'
}) })
return response return response
@ -355,3 +359,97 @@ class RecentsPlaylist(Playlist):
def day_boundary(self, value): def day_boundary(self, value):
database.update_playlist(self.username, self.name, {'day_boundary': value}) database.update_playlist(self.username, self.name, {'day_boundary': value})
self._day_boundary = value self._day_boundary = value
class LastFMChartPlaylist(Playlist):
def __init__(self,
uri: str,
name: str,
username: str,
chart_range: Network.Range,
db_ref: DocumentReference,
include_recommendations: bool,
recommendation_sample: int,
include_library_tracks: bool,
parts: List[str],
playlist_references: List[DocumentReference],
shuffle: bool,
chart_limit: int = 50,
sort: Sort = None,
description_overwrite: str = None,
description_suffix: str = None,
lastfm_stat_count: int = None,
lastfm_stat_album_count: int = None,
lastfm_stat_artist_count: int = None,
lastfm_stat_percent: int = None,
lastfm_stat_album_percent: int = None,
lastfm_stat_artist_percent: int = None,
lastfm_stat_last_refresh: datetime = None):
super().__init__(uri=uri,
name=name,
username=username,
db_ref=db_ref,
include_recommendations=include_recommendations,
recommendation_sample=recommendation_sample,
include_library_tracks=include_library_tracks,
parts=parts,
playlist_references=playlist_references,
shuffle=shuffle,
sort=sort,
description_overwrite=description_overwrite,
description_suffix=description_suffix,
lastfm_stat_count=lastfm_stat_count,
lastfm_stat_album_count=lastfm_stat_album_count,
lastfm_stat_artist_count=lastfm_stat_artist_count,
lastfm_stat_percent=lastfm_stat_percent,
lastfm_stat_album_percent=lastfm_stat_album_percent,
lastfm_stat_artist_percent=lastfm_stat_artist_percent,
lastfm_stat_last_refresh=lastfm_stat_last_refresh)
self._chart_range = chart_range
self._chart_limit = chart_limit
def to_dict(self):
response = super().to_dict()
response.update({
'chart_limit': self.chart_limit,
'chart_range': self.chart_range.name,
'type': 'fmchart'
})
return response
@property
def chart_range(self):
return self._chart_range
@chart_range.setter
def chart_range(self, value):
database.update_playlist(self.username, self.name, {'chart_range': value.name})
self._chart_range = value
@property
def chart_limit(self):
return self._chart_limit
@chart_limit.setter
def chart_limit(self, value):
database.update_playlist(self.username, self.name, {'chart_limit': value})
self._chart_limit = value

View File

@ -10,9 +10,11 @@ from spotframework.engine.processor.deduplicate import DeduplicateByID
from spotframework.model.uri import Uri from spotframework.model.uri import Uri
from spotfm.engine.chart_source import ChartSource
import music.db.database as database import music.db.database as database
from music.db.part_generator import PartGenerator from music.db.part_generator import PartGenerator
from music.model.playlist import RecentsPlaylist from music.model.playlist import RecentsPlaylist, LastFMChartPlaylist
db = firestore.Client() db = firestore.Client()
@ -34,20 +36,20 @@ def run_user_playlist(username, playlist_name):
logger.critical(f'no playlist id to populate ({username}/{playlist_name})') logger.critical(f'no playlist id to populate ({username}/{playlist_name})')
return None return None
if len(playlist.parts) == 0 and len(playlist.playlist_references) == 0:
logger.critical(f'no playlists to use for creation ({username}/{playlist_name})')
return None
net = database.get_authed_spotify_network(username) net = database.get_authed_spotify_network(username)
engine = PlaylistEngine(net) engine = PlaylistEngine(net)
if isinstance(playlist, LastFMChartPlaylist) and user.lastfm_username is not None:
engine.sources.append(ChartSource(spotnet=net, fmnet=database.get_authed_lastfm_network(user.username)))
processors = [DeduplicateByID()] processors = [DeduplicateByID()]
if playlist.shuffle is True: if not isinstance(playlist, LastFMChartPlaylist):
processors.append(Shuffle()) if playlist.shuffle is True:
else: processors.append(Shuffle())
processors.append(SortReleaseDate(reverse=True)) else:
processors.append(SortReleaseDate(reverse=True))
part_generator = PartGenerator(user=user) part_generator = PartGenerator(user=user)
submit_parts = part_generator.get_recursive_parts(playlist.name) submit_parts = part_generator.get_recursive_parts(playlist.name)
@ -62,6 +64,9 @@ def run_user_playlist(username, playlist_name):
if playlist.include_library_tracks: if playlist.include_library_tracks:
params.append(LibraryTrackSource.Params()) params.append(LibraryTrackSource.Params())
if isinstance(playlist, LastFMChartPlaylist):
params.append(ChartSource.Params(chart_range=playlist.chart_range, limit=playlist.chart_limit))
if isinstance(playlist, RecentsPlaylist): if isinstance(playlist, RecentsPlaylist):
boundary_date = datetime.datetime.now(datetime.timezone.utc) - \ boundary_date = datetime.datetime.now(datetime.timezone.utc) - \
datetime.timedelta(days=int(playlist.day_boundary)) datetime.timedelta(days=int(playlist.day_boundary))

View File

@ -45,6 +45,9 @@ class Edit extends Component{
playlist_references: [], playlist_references: [],
type: 'default', type: 'default',
chart_limit: '',
chart_range: '',
day_boundary: '', day_boundary: '',
recommendation_sample: '', recommendation_sample: '',
newPlaylistName: '', newPlaylistName: '',
@ -66,9 +69,15 @@ class Edit extends Component{
this.handleRun = this.handleRun.bind(this); this.handleRun = this.handleRun.bind(this);
this.handleShuffleChange = this.handleShuffleChange.bind(this); this.handleShuffleChange = this.handleShuffleChange.bind(this);
this.handleIncludeLibraryTracksChange = this.handleIncludeLibraryTracksChange.bind(this);
this.handleIncludeRecommendationsChange = this.handleIncludeRecommendationsChange.bind(this); this.handleIncludeRecommendationsChange = this.handleIncludeRecommendationsChange.bind(this);
this.handleThisMonthChange = this.handleThisMonthChange.bind(this); this.handleThisMonthChange = this.handleThisMonthChange.bind(this);
this.handleLastMonthChange = this.handleLastMonthChange.bind(this); this.handleLastMonthChange = this.handleLastMonthChange.bind(this);
this.handleChartLimitChange = this.handleChartLimitChange.bind(this);
this.handleChartRangeChange = this.handleChartRangeChange.bind(this);
} }
componentDidMount(){ componentDidMount(){
@ -124,6 +133,12 @@ class Edit extends Component{
if(event.target.name == 'type'){ if(event.target.name == 'type'){
this.handleTypeChange(event.target.value); this.handleTypeChange(event.target.value);
} }
if(event.target.name == 'chart_range'){
this.handleChartRangeChange(event.target.value);
}
if(event.target.name == 'chart_limit'){
this.handleChartLimitChange(event.target.value);
}
} }
handleDayBoundaryChange(boundary) { handleDayBoundaryChange(boundary) {
@ -213,6 +228,36 @@ class Edit extends Component{
}); });
} }
handleIncludeLibraryTracksChange(event) {
this.setState({
include_library_tracks: event.target.checked
});
axios.post('/api/playlist', {
name: this.state.name,
include_library_tracks: event.target.checked
}).catch((error) => {
showMessage(`error updating library tracks (${error.response.status})`);
});
}
handleChartRangeChange(value) {
axios.post('/api/playlist', {
name: this.state.name,
chart_range: value
}).catch((error) => {
showMessage(`error updating chart range (${error.response.status})`);
});
}
handleChartLimitChange(value) {
axios.post('/api/playlist', {
name: this.state.name,
chart_limit: parseInt(value)
}).catch((error) => {
showMessage(`error updating limit (${error.response.status})`);
});
}
handleAddPart(event){ handleAddPart(event){
if(this.state.newPlaylistName.length != 0){ if(this.state.newPlaylistName.length != 0){
@ -325,26 +370,22 @@ class Edit extends Component{
} }
handleRun(event){ handleRun(event){
if(this.state.playlist_references.length > 0 || this.state.parts.length > 0){ axios.get('/api/user')
axios.get('/api/user') .then((response) => {
.then((response) => { if(response.data.spotify_linked == true){
if(response.data.spotify_linked == true){ axios.get('/api/playlist/run', {params: {name: this.state.name}})
axios.get('/api/playlist/run', {params: {name: this.state.name}}) .then((reponse) => {
.then((reponse) => { showMessage(`${this.state.name} ran`);
showMessage(`${this.state.name} ran`); })
}) .catch((error) => {
.catch((error) => { showMessage(`error running ${this.state.name} (${error.response.status})`);
showMessage(`error running ${this.state.name} (${error.response.status})`); });
}); }else{
}else{ showMessage(`link spotify before running`);
showMessage(`link spotify before running`); }
} }).catch((error) => {
}).catch((error) => { showMessage(`error running ${this.state.name} (${error.response.status})`);
showMessage(`error running ${this.state.name} (${error.response.status})`); });
});
}else{
showMessage(`add either playlists or parts`);
}
} }
render(){ render(){
@ -411,6 +452,16 @@ class Edit extends Component{
onChange={this.handleIncludeRecommendationsChange}></input> onChange={this.handleIncludeRecommendationsChange}></input>
</td> </td>
</tr> </tr>
<tr>
<td className="center-text ui-text text-no-select">
include library tracks?
</td>
<td>
<input type="checkbox"
checked={this.state.include_library_tracks}
onChange={this.handleIncludeLibraryTracksChange}></input>
</td>
</tr>
<tr> <tr>
<td className="center-text ui-text text-no-select"> <td className="center-text ui-text text-no-select">
number of recommendations number of recommendations
@ -423,6 +474,42 @@ class Edit extends Component{
onChange={this.handleInputChange}></input> onChange={this.handleInputChange}></input>
</td> </td>
</tr> </tr>
{ this.state.type == 'fmchart' &&
<tr>
<td className="center-text ui-text text-no-select">
limit
</td>
<td>
<input type="number"
name="chart_limit"
className="full-width"
value={this.state.chart_limit}
onChange={this.handleInputChange}></input>
</td>
</tr>
}
{ this.state.type == 'fmchart' &&
<tr>
<td className="center-text ui-text text-no-select">
chart range
</td>
<td>
<select className="full-width"
name="chart_range"
onChange={this.handleInputChange}
value={this.state.chart_range}>
<option value="WEEK">7 day</option>
<option value="MONTH">30 day</option>
<option value="QUARTER">90 day</option>
<option value="HALFYEAR">180 day</option>
<option value="YEAR">365 day</option>
<option value="OVERALL">overall</option>
</select>
</td>
</tr>
}
{ this.state.type == 'recents' && { this.state.type == 'recents' &&
<tr> <tr>
<td className="center-text ui-text text-no-select"> <td className="center-text ui-text text-no-select">
@ -461,6 +548,8 @@ class Edit extends Component{
</td> </td>
</tr> </tr>
} }
<tr> <tr>
<td className="center-text ui-text text-no-select"> <td className="center-text ui-text text-no-select">
playlist type playlist type
@ -472,6 +561,7 @@ class Edit extends Component{
value={this.state.type}> value={this.state.type}>
<option value="default">default</option> <option value="default">default</option>
<option value="recents">recents</option> <option value="recents">recents</option>
<option value="fmchart">last.fm chart</option>
</select> </select>
</td> </td>
</tr> </tr>