-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SpotifyPlayList-dev-019-recommend-with-playlist (#182)
* BE service add getSongFeatureByPlayList, add recommend with playList * controller add getSongFeatureWithPlayList endpoint, service fix res init * implement getRecommendationWithPlayList, move nb, update readme * update * update attr in dto, refactor RecommendationsService, add getRecommendWithPlaylist endpoint * FE add GetRecommendationWithPlaylist view, update router, app.vue * update FE view, add logging at FE, BE * FE fix view naming, placeholder name * FE fix v-model, naming * BE fix spotify client init, fix recommend reqeust set seed features * FE fix playlist hardcode, remove no need code * recommend get seed song from random song, PlayListService clean code * BE remove no need log, commented code
- Loading branch information
Showing
11 changed files
with
7,017 additions
and
584 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
...ist/src/main/java/com/yen/SpotifyPlayList/model/dto/GetRecommendationsWithFeatureDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.yen.SpotifyPlayList.model.dto; | ||
|
||
import com.neovisionaries.i18n.CountryCode; | ||
import lombok.Data; | ||
import lombok.ToString; | ||
|
||
/** | ||
* | ||
* { | ||
* acousticness: 0.359, | ||
* analysisUrl: "https://api.spotify.com/v1/audio-analysis/7FJC2pF6zMliU7Lvk0GBDV", | ||
* danceability: 0.337, | ||
* durationMs: 358587, | ||
* energy: 0.532, | ||
* id: "7FJC2pF6zMliU7Lvk0GBDV", | ||
* instrumentalness: 0, | ||
* key: 6, | ||
* liveness: 0.0827, | ||
* loudness: -6.296, | ||
* mode: "MAJOR", | ||
* speechiness: 0.0345, | ||
* tempo: 140.257, | ||
* timeSignature: 4, | ||
* trackHref: "https://api.spotify.com/v1/tracks/7FJC2pF6zMliU7Lvk0GBDV", | ||
* type: "AUDIO_FEATURES", | ||
* uri: "spotify:track:7FJC2pF6zMliU7Lvk0GBDV", | ||
* valence: 0.336 | ||
* }, | ||
* | ||
*/ | ||
|
||
@ToString | ||
@Data | ||
public class GetRecommendationsWithFeatureDto { | ||
|
||
private int amount = 10; | ||
private CountryCode market = CountryCode.JP; | ||
private int maxPopularity = 100; | ||
private int minPopularity = 0; | ||
private String seedArtistId; // e.g. : 0LcJLqbBmaGUft1e9Mm8HV | ||
private String seedGenres; | ||
private String seedTrack; // e.g. 01iyCAUm8EvOFqVWYJ3dVX | ||
private int targetPopularity = 50; | ||
private double danceability = 0; | ||
private double energy = 0; | ||
private double instrumentalness = 0; | ||
private double liveness = 0; | ||
private double loudness = 0; | ||
private double speechiness = 0; | ||
private double tempo = 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
150 changes: 123 additions & 27 deletions
150
...SpotifyPlayList/src/main/java/com/yen/SpotifyPlayList/service/RecommendationsService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,151 @@ | ||
package com.yen.SpotifyPlayList.service; | ||
|
||
import com.yen.SpotifyPlayList.model.dto.GetRecommendationsDto; | ||
import com.yen.SpotifyPlayList.model.dto.GetRecommendationsWithFeatureDto; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.hc.core5.http.ParseException; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.stereotype.Service; | ||
import se.michaelthelin.spotify.SpotifyApi; | ||
import se.michaelthelin.spotify.exceptions.SpotifyWebApiException; | ||
import se.michaelthelin.spotify.model_objects.specification.AudioFeatures; | ||
import se.michaelthelin.spotify.model_objects.specification.Recommendations; | ||
import se.michaelthelin.spotify.requests.data.browse.GetRecommendationsRequest; | ||
|
||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.Random; | ||
import java.util.stream.Collectors; | ||
|
||
@Service | ||
@Slf4j | ||
public class RecommendationsService { | ||
|
||
@Autowired | ||
private AuthService authService; | ||
@Autowired private AuthService authService; | ||
|
||
private SpotifyApi spotifyApi; | ||
@Autowired private PlayListService playListService; | ||
|
||
public RecommendationsService() { | ||
private SpotifyApi spotifyApi; | ||
|
||
public RecommendationsService() {} | ||
|
||
public Recommendations getRecommendation(GetRecommendationsDto getRecommendationsDto) | ||
throws SpotifyWebApiException { | ||
try { | ||
this.spotifyApi = authService.initializeSpotifyApi(); | ||
GetRecommendationsRequest getRecommendationsRequest = | ||
prepareRecommendationsRequest(getRecommendationsDto); | ||
Recommendations recommendations = getRecommendationsRequest.execute(); | ||
log.info("Fetched recommendations: {}", recommendations); | ||
return recommendations; | ||
} catch (IOException | SpotifyWebApiException | ParseException e) { | ||
log.error("Error fetching recommendations: {}", e.getMessage()); | ||
throw new SpotifyWebApiException("getRecommendation error: " + e.getMessage()); | ||
} | ||
} | ||
|
||
public Recommendations getRecommendationWithPlayList(String playListId) | ||
throws SpotifyWebApiException { | ||
try { | ||
this.spotifyApi = authService.initializeSpotifyApi(); | ||
List<AudioFeatures> audioFeaturesList = playListService.getSongFeatureByPlayList(playListId); | ||
log.debug(">>> audioFeaturesList = " + audioFeaturesList); | ||
|
||
// Use functional programming to calculate the averages and cast Double to float | ||
// TODO : modify GetRecommendationsWithFeatureDto with attr as "float" type, and modify below | ||
// code (e.g. : averagingDouble) | ||
double energy = | ||
audioFeaturesList.stream().collect(Collectors.averagingDouble(AudioFeatures::getEnergy)); | ||
double acousticness = | ||
audioFeaturesList.stream() | ||
.collect(Collectors.averagingDouble(AudioFeatures::getAcousticness)); | ||
double danceability = | ||
audioFeaturesList.stream() | ||
.collect(Collectors.averagingDouble(AudioFeatures::getDanceability)); | ||
double liveness = | ||
audioFeaturesList.stream() | ||
.collect(Collectors.averagingDouble(AudioFeatures::getLiveness)); | ||
double loudness = | ||
audioFeaturesList.stream() | ||
.collect(Collectors.averagingDouble(AudioFeatures::getLoudness)); | ||
double speechiness = | ||
audioFeaturesList.stream() | ||
.collect(Collectors.averagingDouble(AudioFeatures::getSpeechiness)); | ||
|
||
GetRecommendationsWithFeatureDto featureDto = new GetRecommendationsWithFeatureDto(); | ||
featureDto.setEnergy(energy); | ||
// featureDto.setAcousticness(acousticness); | ||
featureDto.setDanceability(danceability); | ||
featureDto.setLiveness(liveness); | ||
featureDto.setLoudness(loudness); | ||
featureDto.setSpeechiness(speechiness); | ||
|
||
// TODO : get seed features from playList | ||
featureDto.setSeedArtistId("4sJCsXNYmUMeumUKVz4Abm"); | ||
featureDto.setSeedTrack(getRandomSeedTrackId(audioFeaturesList)); | ||
|
||
GetRecommendationsRequest getRecommendationsRequest = | ||
prepareRecommendationsRequestWithPlayList(featureDto); | ||
Recommendations recommendations = getRecommendationsRequest.execute(); | ||
|
||
return recommendations; | ||
} catch (Exception e) { | ||
log.error("Error fetching recommendations with playlist features: {}", e.getMessage()); | ||
throw new SpotifyWebApiException("getRecommendationWithPlayList error: " + e.getMessage()); | ||
} | ||
} | ||
|
||
private GetRecommendationsRequest prepareRecommendationsRequestWithPlayList( | ||
GetRecommendationsWithFeatureDto featureDto) | ||
throws IOException, SpotifyWebApiException, ParseException { | ||
return spotifyApi | ||
.getRecommendations() | ||
.limit(featureDto.getAmount()) | ||
.market(featureDto.getMarket()) | ||
.max_popularity(featureDto.getMaxPopularity()) | ||
.min_popularity(featureDto.getMinPopularity()) | ||
.seed_artists(featureDto.getSeedArtistId()) | ||
.seed_genres(featureDto.getSeedGenres()) | ||
.seed_tracks(featureDto.getSeedTrack()) | ||
// TODO : undo float cast once modify GetRecommendationsWithFeatureDto with attr as "float" type | ||
.target_danceability((float) featureDto.getDanceability()) | ||
.target_energy((float) featureDto.getEnergy()) | ||
.target_instrumentalness((float) featureDto.getInstrumentalness()) | ||
.target_liveness((float) featureDto.getLiveness()) | ||
.target_loudness((float) featureDto.getLoudness()) | ||
.target_speechiness((float) featureDto.getSpeechiness()) | ||
.build(); | ||
} | ||
|
||
private GetRecommendationsRequest prepareRecommendationsRequest(GetRecommendationsDto dto) { | ||
return spotifyApi | ||
.getRecommendations() | ||
.limit(dto.getAmount()) | ||
.market(dto.getMarket()) | ||
.max_popularity(dto.getMaxPopularity()) | ||
.min_popularity(dto.getMinPopularity()) | ||
.seed_artists(dto.getSeedArtistId()) | ||
.seed_genres(dto.getSeedGenres()) | ||
.seed_tracks(dto.getSeedTrack()) | ||
.target_popularity(dto.getTargetPopularity()) | ||
.build(); | ||
} | ||
|
||
public Recommendations getRecommendation(GetRecommendationsDto getRecommendationsDto) throws SpotifyWebApiException { | ||
|
||
Recommendations recommendations = null; | ||
try { | ||
this.spotifyApi = authService.initializeSpotifyApi(); | ||
GetRecommendationsRequest getRecommendationsRequest = prepareRecommendationsRequest(getRecommendationsDto); | ||
recommendations = getRecommendationsRequest.execute(); | ||
log.info("recommendations = " + recommendations); | ||
} catch (IOException | SpotifyWebApiException | ParseException e) { | ||
throw new SpotifyWebApiException("getRecommendation error: " + e.getMessage()); | ||
} | ||
return recommendations; | ||
private String getRandomSeedTrackId(List<AudioFeatures> audioFeaturesList) { | ||
if (audioFeaturesList == null || audioFeaturesList.size() == 0) { | ||
throw new RuntimeException("audioFeaturesList can not be null"); | ||
} | ||
Random random = new Random(); | ||
int randomInt = random.nextInt(audioFeaturesList.size()); | ||
String trackHref = audioFeaturesList.get(randomInt).getTrackHref(); | ||
return getTrackIdFromTrackUrl(trackHref); | ||
} | ||
|
||
private GetRecommendationsRequest prepareRecommendationsRequest(GetRecommendationsDto getRecommendationsDto){ | ||
return spotifyApi.getRecommendations() | ||
.limit(getRecommendationsDto.getAmount()) | ||
.market(getRecommendationsDto.getMarket()) | ||
.max_popularity(getRecommendationsDto.getMaxPopularity()) | ||
.min_popularity(getRecommendationsDto.getMinPopularity()) | ||
.seed_artists(getRecommendationsDto.getSeedArtistId()) | ||
.seed_genres(getRecommendationsDto.getSeedGenres()) | ||
.seed_tracks(getRecommendationsDto.getSeedTrack()) | ||
.target_popularity(getRecommendationsDto.getTargetPopularity()) | ||
.build(); | ||
private String getTrackIdFromTrackUrl(String trackUrl) { | ||
if (trackUrl == null) { | ||
throw new RuntimeException("trackUrl can not be null"); | ||
} | ||
return trackUrl.split("tracks")[1].replace("/", ""); | ||
} | ||
|
||
} |
Oops, something went wrong.