Updated run_playlist to use function playlist engine

This commit is contained in:
aj 2020-06-16 20:45:26 +01:00
parent 731913c8c8
commit 2b009eb6df
6 changed files with 1246 additions and 1486 deletions

View File

@ -1,25 +1,22 @@
from google.cloud import firestore
import datetime import datetime
import logging import logging
import random
from spotframework.engine.playlistengine import PlaylistEngine, PlaylistSource, RecommendationSource, LibraryTrackSource import spotframework.util.monthstrings as monthstrings
from spotframework.engine.processor.shuffle import Shuffle
from spotframework.engine.processor.sort import SortReleaseDate
from spotframework.engine.processor.deduplicate import DeduplicateByName
from spotframework.model.uri import Uri from spotframework.model.uri import Uri
from spotframework.filter import remove_local
from spotfm.engine.chart_source import ChartSource from spotframework.filter.added import added_after
from spotframework.filter.sort import sort_by_release_date
from spotframework.filter.deduplicate import deduplicate_by_name
from fmframework.net.network import Network from fmframework.net.network import Network
from spotfm.charts.chart import get_chart_of_spotify_tracks
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.user import User from music.model.user import User
from music.model.playlist import Playlist from music.model.playlist import Playlist
db = firestore.Client()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -29,7 +26,7 @@ def run_user_playlist(username, playlist_name):
# PRE-RUN CHECKS # PRE-RUN CHECKS
if user is None: if user is None:
logger.error(f'user {username} not found') logger.error(f'user not found {username} / {playlist_name}')
return return
logger.info(f'running {username} / {playlist_name}') logger.info(f'running {username} / {playlist_name}')
@ -37,11 +34,11 @@ def run_user_playlist(username, playlist_name):
playlist = Playlist.collection.parent(user.key).filter('name', '==', playlist_name).get() playlist = Playlist.collection.parent(user.key).filter('name', '==', playlist_name).get()
if playlist is None: if playlist is None:
logger.critical(f'playlist not found ({username}/{playlist_name})') logger.critical(f'playlist not found {username} / {playlist_name}')
return return
if playlist.uri is None: if playlist.uri is None:
logger.critical(f'no playlist id to populate ({username}/{playlist_name})') logger.critical(f'no playlist id to populate {username} / {playlist_name}')
return return
# END CHECKS # END CHECKS
@ -49,71 +46,124 @@ def run_user_playlist(username, playlist_name):
net = database.get_authed_spotify_network(user) net = database.get_authed_spotify_network(user)
if net is None: if net is None:
logger.error(f'no spotify network returned for {username}') logger.error(f'no spotify network returned for {username} / {playlist_name}')
return return
engine = PlaylistEngine(net) user_playlists = net.get_user_playlists()
part_generator = PartGenerator(user=user) part_generator = PartGenerator(user=user)
part_names = part_generator.get_recursive_parts(playlist.name)
spotify_playlist_names = part_generator.get_recursive_parts(playlist.name) playlist_tracks = []
processors = [DeduplicateByName()] if playlist.add_last_month:
params = [ part_names.append(monthstrings.get_last_month())
PlaylistSource.Params(names=spotify_playlist_names) if playlist.add_this_month:
] part_names.append(monthstrings.get_this_month())
# OPTIONS # LOAD PLAYLIST TRACKS
if playlist.include_recommendations: for part_name in part_names:
params.append(RecommendationSource.Params(recommendation_limit=playlist.recommendation_sample)) try: # attempt to cast to uri
uri = Uri(part_name)
_tracks = net.get_playlist_tracks(uri=uri)
if _tracks and len(_tracks) > 0:
playlist_tracks += _tracks
else:
logger.warning(f'no tracks returned for {uri} {username} / {playlist_name}')
except ValueError: # is a playlist name
part_playlist = next((i for i in user_playlists if i.name == part_name), None)
if part_playlist is None:
logger.warning(f'playlist {part_name} not found {username} / {playlist_name}')
continue
part_playlist_tracks = net.get_playlist_tracks(uri=part_playlist.uri)
if part_playlist_tracks and len(part_playlist_tracks) > 0:
playlist_tracks += part_playlist_tracks
else:
logger.warning(f'no tracks returned for {part_playlist.name} {username} / {playlist_name}')
playlist_tracks = remove_local(playlist_tracks)
# LIBRARY
if playlist.include_library_tracks: if playlist.include_library_tracks:
params.append(LibraryTrackSource.Params()) library_tracks = net.get_library_tracks()
# END OPTIONS if library_tracks and len(library_tracks) > 0:
playlist_tracks += library_tracks
if playlist.type == 'fmchart':
if user.lastfm_username is None:
logger.error(f'{username} has no associated last.fm username, chart source skipped')
else: else:
logger.error(f'error getting library tracks {username} / {playlist_name}')
# PLAYLIST TYPE SPECIFIC
if playlist.type == 'recents':
boundary_date = datetime.datetime.now(datetime.timezone.utc) - \
datetime.timedelta(days=int(playlist.day_boundary))
playlist_tracks = added_after(playlist_tracks, boundary_date)
elif playlist.type == 'fmchart':
if user.lastfm_username is None:
logger.error(f'no associated last.fm username, chart source skipped {username} / {playlist_name}')
else:
chart_range = Network.Range.MONTH chart_range = Network.Range.MONTH
try: try:
chart_range = Network.Range[playlist.chart_range] chart_range = Network.Range[playlist.chart_range]
except KeyError: except KeyError:
logger.error(f'invalid last.fm chart range found for ' logger.error(f'invalid last.fm chart range found {playlist.chart_range}, '
f'{playlist_name}/{username} {playlist.chart_range}, defaulting to 1 month') f'defaulting to 1 month {username} / {playlist_name}')
engine.sources.append(ChartSource(spotnet=net, fmnet=database.get_authed_lastfm_network(user))) fmnet = database.get_authed_lastfm_network(user)
params.append(ChartSource.Params(chart_range=chart_range, limit=playlist.chart_limit)) if fmnet is not None:
chart_tracks = get_chart_of_spotify_tracks(spotnet=net,
fmnet=fmnet,
period=chart_range,
limit=playlist.chart_limit)
else: if chart_tracks is not None and len(chart_tracks) > 0:
# INCLUDE SORT METHOD (no sorting for last.fm chart playlist) playlist_tracks += chart_tracks
if playlist.shuffle is True: else:
processors.append(Shuffle()) logger.error(f'no tracks returned {username} / {playlist_name}')
else:
logger.error(f'no last.fm network returned {username} / {playlist_name}')
# SORT METHOD
if playlist.shuffle:
random.shuffle(playlist_tracks)
elif playlist.type != 'fmchart':
playlist_tracks = sort_by_release_date(tracks=playlist_tracks, reverse=True)
# RECOMMENDATIONS
if playlist.include_recommendations:
recommendations = net.get_recommendations(tracks=[i.uri.object_id for i
in random.sample(playlist_tracks, k=min(5, len(playlist_tracks)))
if i.uri.object_type == Uri.ObjectType.track],
response_limit=playlist.recommendation_sample)
if recommendations and len(recommendations) > 0:
playlist_tracks += recommendations
else: else:
processors.append(SortReleaseDate(reverse=True)) logger.error(f'error getting recommendations {username} / {playlist_name}')
# GENERATE TRACKS # DEDUPLICATE
if playlist.type == 'recents': playlist_tracks = deduplicate_by_name(playlist_tracks)
boundary_date = datetime.datetime.now(datetime.timezone.utc) - \
datetime.timedelta(days=int(playlist.day_boundary)) # EXECUTE
tracks = engine.get_recent_playlist(params=params, resp = net.replace_playlist_tracks(uri_string=playlist.uri, uris=[i.uri for i in playlist_tracks])
processors=processors, if resp:
boundary_date=boundary_date, if playlist.description_overwrite:
add_this_month=playlist.add_this_month, string = playlist.description_overwrite
add_last_month=playlist.add_last_month) else:
string = ' / '.join(sorted(part_names))
if playlist.description_suffix:
string += f' - {str(playlist.description_suffix)}'
if string is None or len(string) == 0:
logger.error(f'no string generated {username} / {playlist_name}')
return None
resp = net.change_playlist_details(Uri(playlist.uri), description=string)
if resp is None:
logger.error(f'error changing description {username} / {playlist_name}')
else: else:
tracks = engine.make_playlist(params=params, logger.error(f'error executing {username} / {playlist_name}')
processors=processors)
# NET OPS
engine.execute_playlist(tracks, Uri(playlist.uri))
overwrite = playlist.description_overwrite
suffix = playlist.description_suffix
engine.change_description(sorted(spotify_playlist_names),
uri=Uri(playlist.uri),
overwrite=overwrite,
suffix=suffix)
playlist.last_updated = datetime.datetime.utcnow() playlist.last_updated = datetime.datetime.utcnow()
playlist.update() playlist.update()

2516
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -19,24 +19,24 @@
}, },
"homepage": "https://github.com/Sarsoo/Music-Tools#readme", "homepage": "https://github.com/Sarsoo/Music-Tools#readme",
"dependencies": { "dependencies": {
"@material-ui/core": "^4.9.5", "@material-ui/core": "^4.10.2",
"@material-ui/icons": "^4.9.1", "@material-ui/icons": "^4.9.1",
"axios": "^0.19.2", "axios": "^0.19.2",
"chart.js": "^2.9.3", "chart.js": "^2.9.3",
"react": "^16.13.0", "react": "^16.13.0",
"react-dom": "^16.13.0", "react-dom": "^16.13.0",
"react-router-dom": "^5.1.2" "react-router-dom": "^5.2.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.8.4", "@babel/cli": "^7.10.1",
"@babel/core": "^7.9.0", "@babel/core": "^7.10.2",
"@babel/preset-env": "^7.9.0", "@babel/preset-env": "^7.10.2",
"@babel/preset-react": "^7.9.4", "@babel/preset-react": "^7.10.1",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0", "clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.2", "css-loader": "^3.6.0",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"webpack": "^4.42.1", "webpack": "^4.43.0",
"webpack-cli": "^3.3.11", "webpack-cli": "^3.3.11",
"webpack-merge": "^4.2.2" "webpack-merge": "^4.2.2"
} }

View File

@ -1,37 +1,37 @@
astroid==2.4.1 astroid==2.4.2
cachetools==4.1.0 cachetools==4.1.0
certifi==2020.4.5.1 certifi==2020.4.5.2
chardet==3.0.4 chardet==3.0.4
click==7.1.2 click==7.1.2
fireo==1.2.8 fireo==1.3.3
Flask==1.1.2 Flask==1.1.2
google-api-core==1.17.0 google-api-core==1.20.1
google-auth==1.14.3 google-auth==1.17.2
google-cloud-core==1.3.0 google-cloud-core==1.3.0
google-cloud-firestore==1.6.2 google-cloud-firestore==1.7.0
google-cloud-logging==1.15.0 google-cloud-logging==1.15.0
google-cloud-pubsub==1.5.0 google-cloud-pubsub==1.6.0
google-cloud-tasks==1.5.0 google-cloud-tasks==1.5.0
googleapis-common-protos==1.51.0 googleapis-common-protos==1.52.0
grpc-google-iam-v1==0.12.3 grpc-google-iam-v1==0.12.3
grpcio==1.29.0 grpcio==1.29.0
idna==2.9 idna==2.9
isort==4.3.21 isort==4.3.21
itsdangerous==1.1.0 itsdangerous==1.1.0
Jinja2==2.11.2 Jinja2==2.11.2
lazy-object-proxy==1.4.3 lazy-object-proxy==1.5.0
MarkupSafe==1.1.1 MarkupSafe==1.1.1
mccabe==0.6.1 mccabe==0.6.1
numpy==1.18.4 numpy==1.18.5
opencv-python==4.2.0.34 opencv-python==4.2.0.34
protobuf==3.11.3 protobuf==3.12.2
pyasn1==0.4.8 pyasn1==0.4.8
pyasn1-modules==0.2.8 pyasn1-modules==0.2.8
pylint==2.5.2 pylint==2.5.3
pytz==2020.1 pytz==2020.1
requests==2.23.0 requests==2.23.0
rsa==4.0 rsa==4.6
six==1.14.0 six==1.15.0
tabulate==0.8.7 tabulate==0.8.7
toml==0.10.1 toml==0.10.1
urllib3==1.25.9 urllib3==1.25.9

View File

@ -530,7 +530,6 @@ export class Edit extends Component{
onChange={this.handleInputChange} /> onChange={this.handleInputChange} />
</Grid> </Grid>
} }
{ this.state.type == 'recents' &&
<Grid item xs={12}> <Grid item xs={12}>
<FormControlLabel <FormControlLabel
control={ control={
@ -547,7 +546,6 @@ export class Edit extends Component{
labelPlacement="bottom" labelPlacement="bottom"
/> />
</Grid> </Grid>
}
<Grid item xs={12}> <Grid item xs={12}>
<FormControl variant="filled"> <FormControl variant="filled">
<InputLabel htmlFor="type-select">Type</InputLabel> <InputLabel htmlFor="type-select">Type</InputLabel>

View File

@ -378,7 +378,7 @@ function StatsCard (props) {
<Typography variant="h1" color="textPrimary" className={classes.root}>= { props.count }</Typography> <Typography variant="h1" color="textPrimary" className={classes.root}>= { props.count }</Typography>
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<Typography variant="h4" color="textSecondary" className={classes.root}>{ Math.round(props.proportion) }%</Typography> <Typography variant="h4" color="textSecondary" className={classes.root}>{ props.proportion.toFixed(2) }%</Typography>
</Grid> </Grid>
</Grid> </Grid>
</CardContent> </CardContent>