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_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_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()]
if request.method == 'PUT':
@ -111,6 +116,7 @@ def playlist(username=None):
'name': playlist_name,
'parts': playlist_parts if playlist_parts 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,
'recommendation_sample': playlist_recommendation_sample if playlist_recommendation_sample is not None else 10,
'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_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)
logger.info(f'added {username} / {playlist_name}')
@ -171,15 +181,28 @@ def playlist(username=None):
if playlist_add_last_month is not None:
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:
dic['include_recommendations'] = playlist_recommendation
if playlist_recommendation_sample is not None:
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:
dic['type'] = playlist_type
if playlist_type == 'fmchart':
dic['chart_range'] = 'YEAR'
dic['chart_limit'] = 50
if len(dic) == 0:
logger.warning(f'no changes to make for {username} / {playlist_name}')
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 music.db.user import DatabaseUser
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()
@ -260,6 +260,39 @@ def parse_playlist_reference(username, playlist_ref=None, playlist_snapshot=None
add_this_month=playlist_dict.get('add_this_month'),
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:
if len(updates) > 0:

View File

@ -3,6 +3,8 @@ from enum import Enum
from datetime import datetime
from google.cloud.firestore import DocumentReference
from fmframework.net.network import Network
import music.db.database as database
@ -73,6 +75,7 @@ class Playlist:
return {
'uri': self.uri,
'name': self.name,
'type': 'default',
'include_recommendations': self.include_recommendations,
'recommendation_sample': self.recommendation_sample,
@ -325,7 +328,8 @@ class RecentsPlaylist(Playlist):
response.update({
'add_last_month': self.add_last_month,
'add_this_month': self.add_this_month,
'day_boundary': self.day_boundary
'day_boundary': self.day_boundary,
'type': 'recents'
})
return response
@ -355,3 +359,97 @@ class RecentsPlaylist(Playlist):
def day_boundary(self, value):
database.update_playlist(self.username, self.name, {'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 spotfm.engine.chart_source import ChartSource
import music.db.database as database
from music.db.part_generator import PartGenerator
from music.model.playlist import RecentsPlaylist
from music.model.playlist import RecentsPlaylist, LastFMChartPlaylist
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})')
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)
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()]
if playlist.shuffle is True:
processors.append(Shuffle())
else:
processors.append(SortReleaseDate(reverse=True))
if not isinstance(playlist, LastFMChartPlaylist):
if playlist.shuffle is True:
processors.append(Shuffle())
else:
processors.append(SortReleaseDate(reverse=True))
part_generator = PartGenerator(user=user)
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:
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):
boundary_date = datetime.datetime.now(datetime.timezone.utc) - \
datetime.timedelta(days=int(playlist.day_boundary))

View File

@ -45,6 +45,9 @@ class Edit extends Component{
playlist_references: [],
type: 'default',
chart_limit: '',
chart_range: '',
day_boundary: '',
recommendation_sample: '',
newPlaylistName: '',
@ -66,9 +69,15 @@ class Edit extends Component{
this.handleRun = this.handleRun.bind(this);
this.handleShuffleChange = this.handleShuffleChange.bind(this);
this.handleIncludeLibraryTracksChange = this.handleIncludeLibraryTracksChange.bind(this);
this.handleIncludeRecommendationsChange = this.handleIncludeRecommendationsChange.bind(this);
this.handleThisMonthChange = this.handleThisMonthChange.bind(this);
this.handleLastMonthChange = this.handleLastMonthChange.bind(this);
this.handleChartLimitChange = this.handleChartLimitChange.bind(this);
this.handleChartRangeChange = this.handleChartRangeChange.bind(this);
}
componentDidMount(){
@ -124,6 +133,12 @@ class Edit extends Component{
if(event.target.name == 'type'){
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) {
@ -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){
if(this.state.newPlaylistName.length != 0){
@ -325,26 +370,22 @@ class Edit extends Component{
}
handleRun(event){
if(this.state.playlist_references.length > 0 || this.state.parts.length > 0){
axios.get('/api/user')
.then((response) => {
if(response.data.spotify_linked == true){
axios.get('/api/playlist/run', {params: {name: this.state.name}})
.then((reponse) => {
showMessage(`${this.state.name} ran`);
})
.catch((error) => {
showMessage(`error running ${this.state.name} (${error.response.status})`);
});
}else{
showMessage(`link spotify before running`);
}
}).catch((error) => {
showMessage(`error running ${this.state.name} (${error.response.status})`);
});
}else{
showMessage(`add either playlists or parts`);
}
axios.get('/api/user')
.then((response) => {
if(response.data.spotify_linked == true){
axios.get('/api/playlist/run', {params: {name: this.state.name}})
.then((reponse) => {
showMessage(`${this.state.name} ran`);
})
.catch((error) => {
showMessage(`error running ${this.state.name} (${error.response.status})`);
});
}else{
showMessage(`link spotify before running`);
}
}).catch((error) => {
showMessage(`error running ${this.state.name} (${error.response.status})`);
});
}
render(){
@ -411,6 +452,16 @@ class Edit extends Component{
onChange={this.handleIncludeRecommendationsChange}></input>
</td>
</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>
<td className="center-text ui-text text-no-select">
number of recommendations
@ -423,6 +474,42 @@ class Edit extends Component{
onChange={this.handleInputChange}></input>
</td>
</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' &&
<tr>
<td className="center-text ui-text text-no-select">
@ -461,6 +548,8 @@ class Edit extends Component{
</td>
</tr>
}
<tr>
<td className="center-text ui-text text-no-select">
playlist type
@ -472,6 +561,7 @@ class Edit extends Component{
value={this.state.type}>
<option value="default">default</option>
<option value="recents">recents</option>
<option value="fmchart">last.fm chart</option>
</select>
</td>
</tr>