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 logging
import random
from spotframework.engine.playlistengine import PlaylistEngine, PlaylistSource, RecommendationSource, LibraryTrackSource
from spotframework.engine.processor.shuffle import Shuffle
from spotframework.engine.processor.sort import SortReleaseDate
from spotframework.engine.processor.deduplicate import DeduplicateByName
import spotframework.util.monthstrings as monthstrings
from spotframework.model.uri import Uri
from spotfm.engine.chart_source import ChartSource
from spotframework.filter import remove_local
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 spotfm.charts.chart import get_chart_of_spotify_tracks
import music.db.database as database
from music.db.part_generator import PartGenerator
from music.model.user import User
from music.model.playlist import Playlist
db = firestore.Client()
logger = logging.getLogger(__name__)
@ -29,7 +26,7 @@ def run_user_playlist(username, playlist_name):
# PRE-RUN CHECKS
if user is None:
logger.error(f'user {username} not found')
logger.error(f'user not found {username} / {playlist_name}')
return
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()
if playlist is None:
logger.critical(f'playlist not found ({username}/{playlist_name})')
logger.critical(f'playlist not found {username} / {playlist_name}')
return
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
# END CHECKS
@ -49,71 +46,124 @@ def run_user_playlist(username, playlist_name):
net = database.get_authed_spotify_network(user)
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
engine = PlaylistEngine(net)
user_playlists = net.get_user_playlists()
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()]
params = [
PlaylistSource.Params(names=spotify_playlist_names)
]
if playlist.add_last_month:
part_names.append(monthstrings.get_last_month())
if playlist.add_this_month:
part_names.append(monthstrings.get_this_month())
# OPTIONS
if playlist.include_recommendations:
params.append(RecommendationSource.Params(recommendation_limit=playlist.recommendation_sample))
# LOAD PLAYLIST TRACKS
for part_name in part_names:
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:
params.append(LibraryTrackSource.Params())
# END OPTIONS
if playlist.type == 'fmchart':
if user.lastfm_username is None:
logger.error(f'{username} has no associated last.fm username, chart source skipped')
library_tracks = net.get_library_tracks()
if library_tracks and len(library_tracks) > 0:
playlist_tracks += library_tracks
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
try:
chart_range = Network.Range[playlist.chart_range]
except KeyError:
logger.error(f'invalid last.fm chart range found for '
f'{playlist_name}/{username} {playlist.chart_range}, defaulting to 1 month')
logger.error(f'invalid last.fm chart range found {playlist.chart_range}, '
f'defaulting to 1 month {username} / {playlist_name}')
engine.sources.append(ChartSource(spotnet=net, fmnet=database.get_authed_lastfm_network(user)))
params.append(ChartSource.Params(chart_range=chart_range, limit=playlist.chart_limit))
fmnet = database.get_authed_lastfm_network(user)
if fmnet is not None:
chart_tracks = get_chart_of_spotify_tracks(spotnet=net,
fmnet=fmnet,
period=chart_range,
limit=playlist.chart_limit)
else:
# INCLUDE SORT METHOD (no sorting for last.fm chart playlist)
if playlist.shuffle is True:
processors.append(Shuffle())
if chart_tracks is not None and len(chart_tracks) > 0:
playlist_tracks += chart_tracks
else:
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:
processors.append(SortReleaseDate(reverse=True))
logger.error(f'error getting recommendations {username} / {playlist_name}')
# GENERATE TRACKS
if playlist.type == 'recents':
boundary_date = datetime.datetime.now(datetime.timezone.utc) - \
datetime.timedelta(days=int(playlist.day_boundary))
tracks = engine.get_recent_playlist(params=params,
processors=processors,
boundary_date=boundary_date,
add_this_month=playlist.add_this_month,
add_last_month=playlist.add_last_month)
# DEDUPLICATE
playlist_tracks = deduplicate_by_name(playlist_tracks)
# EXECUTE
resp = net.replace_playlist_tracks(uri_string=playlist.uri, uris=[i.uri for i in playlist_tracks])
if resp:
if playlist.description_overwrite:
string = playlist.description_overwrite
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:
tracks = engine.make_playlist(params=params,
processors=processors)
logger.error(f'error executing {username} / {playlist_name}')
# 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.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",
"dependencies": {
"@material-ui/core": "^4.9.5",
"@material-ui/core": "^4.10.2",
"@material-ui/icons": "^4.9.1",
"axios": "^0.19.2",
"chart.js": "^2.9.3",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-router-dom": "^5.1.2"
"react-router-dom": "^5.2.0"
},
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.0",
"@babel/preset-react": "^7.9.4",
"@babel/cli": "^7.10.1",
"@babel/core": "^7.10.2",
"@babel/preset-env": "^7.10.2",
"@babel/preset-react": "^7.10.1",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.2",
"css-loader": "^3.6.0",
"style-loader": "^0.23.1",
"webpack": "^4.42.1",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"webpack-merge": "^4.2.2"
}

View File

@ -1,37 +1,37 @@
astroid==2.4.1
astroid==2.4.2
cachetools==4.1.0
certifi==2020.4.5.1
certifi==2020.4.5.2
chardet==3.0.4
click==7.1.2
fireo==1.2.8
fireo==1.3.3
Flask==1.1.2
google-api-core==1.17.0
google-auth==1.14.3
google-api-core==1.20.1
google-auth==1.17.2
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-pubsub==1.5.0
google-cloud-pubsub==1.6.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
grpcio==1.29.0
idna==2.9
isort==4.3.21
itsdangerous==1.1.0
Jinja2==2.11.2
lazy-object-proxy==1.4.3
lazy-object-proxy==1.5.0
MarkupSafe==1.1.1
mccabe==0.6.1
numpy==1.18.4
numpy==1.18.5
opencv-python==4.2.0.34
protobuf==3.11.3
protobuf==3.12.2
pyasn1==0.4.8
pyasn1-modules==0.2.8
pylint==2.5.2
pylint==2.5.3
pytz==2020.1
requests==2.23.0
rsa==4.0
six==1.14.0
rsa==4.6
six==1.15.0
tabulate==0.8.7
toml==0.10.1
urllib3==1.25.9

View File

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