added last.fm chart playlists
added front end support for library tracks
This commit is contained in:
parent
71fdb6620d
commit
f5eb07ca21
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user