Updated run_playlist to use function playlist engine
This commit is contained in:
parent
731913c8c8
commit
2b009eb6df
@ -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)
|
||||||
|
|
||||||
if playlist.include_library_tracks:
|
_tracks = net.get_playlist_tracks(uri=uri)
|
||||||
params.append(LibraryTrackSource.Params())
|
if _tracks and len(_tracks) > 0:
|
||||||
# END OPTIONS
|
playlist_tracks += _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.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:
|
||||||
|
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
|
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)
|
||||||
|
|
||||||
|
if chart_tracks is not None and len(chart_tracks) > 0:
|
||||||
|
playlist_tracks += chart_tracks
|
||||||
else:
|
else:
|
||||||
# INCLUDE SORT METHOD (no sorting for last.fm chart playlist)
|
logger.error(f'no tracks returned {username} / {playlist_name}')
|
||||||
if playlist.shuffle is True:
|
|
||||||
processors.append(Shuffle())
|
|
||||||
else:
|
else:
|
||||||
processors.append(SortReleaseDate(reverse=True))
|
logger.error(f'no last.fm network returned {username} / {playlist_name}')
|
||||||
|
|
||||||
# GENERATE TRACKS
|
# SORT METHOD
|
||||||
if playlist.type == 'recents':
|
if playlist.shuffle:
|
||||||
boundary_date = datetime.datetime.now(datetime.timezone.utc) - \
|
random.shuffle(playlist_tracks)
|
||||||
datetime.timedelta(days=int(playlist.day_boundary))
|
elif playlist.type != 'fmchart':
|
||||||
tracks = engine.get_recent_playlist(params=params,
|
playlist_tracks = sort_by_release_date(tracks=playlist_tracks, reverse=True)
|
||||||
processors=processors,
|
|
||||||
boundary_date=boundary_date,
|
# RECOMMENDATIONS
|
||||||
add_this_month=playlist.add_this_month,
|
if playlist.include_recommendations:
|
||||||
add_last_month=playlist.add_last_month)
|
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:
|
||||||
tracks = engine.make_playlist(params=params,
|
logger.error(f'error getting recommendations {username} / {playlist_name}')
|
||||||
processors=processors)
|
|
||||||
|
|
||||||
# NET OPS
|
# DEDUPLICATE
|
||||||
engine.execute_playlist(tracks, Uri(playlist.uri))
|
playlist_tracks = deduplicate_by_name(playlist_tracks)
|
||||||
|
|
||||||
overwrite = playlist.description_overwrite
|
# EXECUTE
|
||||||
suffix = playlist.description_suffix
|
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:
|
||||||
|
logger.error(f'error executing {username} / {playlist_name}')
|
||||||
|
|
||||||
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()
|
||||||
|
2514
package-lock.json
generated
2514
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -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"
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user