playlist generating working without recommendations
This commit is contained in:
parent
e1094b131f
commit
97cf1839c0
@ -16,17 +16,13 @@ class Program
|
|||||||
{
|
{
|
||||||
var repo = new UserRepo(projectId: System.Environment.GetEnvironmentVariable("GOOGLE_CLOUD_PROJECT"));
|
var repo = new UserRepo(projectId: System.Environment.GetEnvironmentVariable("GOOGLE_CLOUD_PROJECT"));
|
||||||
|
|
||||||
var userContext = await repo.GetUserContext("andy");
|
|
||||||
|
|
||||||
Console.WriteLine(userContext.User);
|
|
||||||
|
|
||||||
var walker = new PartTreeWalker(repo);
|
var walker = new PartTreeWalker(repo);
|
||||||
var partPlaylists = await walker.GetPlaylistParts("andy", "RAP");
|
// var partPlaylists = await walker.GetPlaylistParts("andy", "RAP");
|
||||||
|
|
||||||
var spotifyNetwork = new SpotifyNetworkProvider(repo, null, NullLogger<SpotifyNetworkProvider>.Instance);
|
var spotifyNetwork = new SpotifyNetworkProvider(repo, null, NullLogger<SpotifyNetworkProvider>.Instance);
|
||||||
|
|
||||||
var generator = new PlaylistGenerator(repo, spotifyNetwork, walker, NullLogger<PlaylistGenerator>.Instance);
|
var generator = new PlaylistGenerator(repo, spotifyNetwork, walker, NullLogger<PlaylistGenerator>.Instance);
|
||||||
|
|
||||||
await generator.GeneratePlaylist("RAP", "andy");
|
await generator.GeneratePlaylist("POP", "andy");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
Mixonomer/Extensions/StringExtensions.cs
Normal file
6
Mixonomer/Extensions/StringExtensions.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Mixonomer.Extensions;
|
||||||
|
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
public static string UriToId(this string uri) => uri.Split(':')[2];
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections;
|
||||||
using SpotifyAPI.Web;
|
using SpotifyAPI.Web;
|
||||||
|
|
||||||
namespace Mixonomer.Playlist;
|
namespace Mixonomer.Playlist;
|
||||||
@ -6,4 +7,7 @@ public class PlaylistGeneratingContext
|
|||||||
{
|
{
|
||||||
public IList<PlaylistTrack<IPlayableItem>> PartTracks { get; set; }
|
public IList<PlaylistTrack<IPlayableItem>> PartTracks { get; set; }
|
||||||
public IList<SavedTrack> LibraryTracks { get; set; }
|
public IList<SavedTrack> LibraryTracks { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<CommonTrack> ToCommonTracks() => PartTracks.Select(x => (CommonTrack)x)
|
||||||
|
.Concat(LibraryTracks.Select(x => (CommonTrack)x));
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
|
using Google.Cloud.Firestore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Mixonomer.Exceptions;
|
using Mixonomer.Exceptions;
|
||||||
|
using Mixonomer.Extensions;
|
||||||
using Mixonomer.Fire;
|
using Mixonomer.Fire;
|
||||||
using Mixonomer.Fire.Extensions;
|
using Mixonomer.Fire.Extensions;
|
||||||
using Mixonomer.Playlist.Sort;
|
using Mixonomer.Playlist.Sort;
|
||||||
@ -26,6 +28,8 @@ public class PlaylistGenerator
|
|||||||
{
|
{
|
||||||
using var logScope = _logger.BeginScope(new Dictionary<string, string> { {"username", username}, {"playlist", playlistName} });
|
using var logScope = _logger.BeginScope(new Dictionary<string, string> { {"username", username}, {"playlist", playlistName} });
|
||||||
|
|
||||||
|
ArgumentException.ThrowIfNullOrWhiteSpace(username);
|
||||||
|
|
||||||
var user = await _userRepo.GetUser(username);
|
var user = await _userRepo.GetUser(username);
|
||||||
var dbPlaylist = await _userRepo.GetPlaylists(user).FirstOrDefaultAsync(x => x.name == playlistName);
|
var dbPlaylist = await _userRepo.GetPlaylists(user).FirstOrDefaultAsync(x => x.name == playlistName);
|
||||||
|
|
||||||
@ -45,14 +49,31 @@ public class PlaylistGenerator
|
|||||||
|
|
||||||
var context = new PlaylistGeneratingContext
|
var context = new PlaylistGeneratingContext
|
||||||
{
|
{
|
||||||
PartTracks = await GetPlatlistTracks(spotifyClient, partPlaylists).ToListAsync(),
|
PartTracks = await GetPlaylistTracks(spotifyClient, partPlaylists).ToListAsync(),
|
||||||
LibraryTracks = await GetLibraryTracks(spotifyClient, dbPlaylist).ToListAsync()
|
LibraryTracks = await GetLibraryTracks(spotifyClient, dbPlaylist).ToListAsync()
|
||||||
};
|
};
|
||||||
|
|
||||||
context = DoPlaylistTypeProcessing(context, user, dbPlaylist);
|
context = DoPlaylistTypeProcessing(context, user, dbPlaylist);
|
||||||
|
|
||||||
var combinedTracks = CollapseContextToCommonTracks(context);
|
var combinedTracks = CollapseContextToCommonTracks(context);
|
||||||
|
|
||||||
|
// var recommender = new SpotifyRecommender(spotifyClient);
|
||||||
|
// var recommendations = await recommender.GetRecommendations(dbPlaylist, combinedTracks);
|
||||||
|
//
|
||||||
|
// combinedTracks = combinedTracks.Concat(recommendations);
|
||||||
|
|
||||||
|
// combinedTracks = combinedTracks.DistinctBy(x => (x.TrackName, string.Join(':', x.ArtistNames.Order())));
|
||||||
|
// combinedTracks = combinedTracks.DistinctBy(x => x.TrackUri);
|
||||||
|
combinedTracks = combinedTracks.DistinctBy(x => (x.TrackName.ToLower(), string.Concat(x.ArtistNames.Order())));
|
||||||
|
|
||||||
combinedTracks = SortTracks(combinedTracks, dbPlaylist);
|
combinedTracks = SortTracks(combinedTracks, dbPlaylist);
|
||||||
|
|
||||||
|
await ExecutePlaylist(spotifyClient, dbPlaylist, user, combinedTracks, parts);
|
||||||
|
|
||||||
|
await dbPlaylist.Reference.SetAsync(new
|
||||||
|
{
|
||||||
|
last_updated = DateTime.UtcNow
|
||||||
|
}, SetOptions.MergeAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IEnumerable<string>> GetFullPartList(User user, Fire.Playlist playlist)
|
private async Task<IEnumerable<string>> GetFullPartList(User user, Fire.Playlist playlist)
|
||||||
@ -79,14 +100,15 @@ public class PlaylistGenerator
|
|||||||
|
|
||||||
private IEnumerable<FullPlaylist> GetPartPlaylists(Fire.Playlist subjectPlaylist, IEnumerable<FullPlaylist> allPlaylists, IEnumerable<string> parts)
|
private IEnumerable<FullPlaylist> GetPartPlaylists(Fire.Playlist subjectPlaylist, IEnumerable<FullPlaylist> allPlaylists, IEnumerable<string> parts)
|
||||||
{
|
{
|
||||||
var allPlaylistDict = allPlaylists.ToDictionary(p => p.Name ?? "no name");
|
foreach (var playlist in allPlaylists)
|
||||||
|
{
|
||||||
foreach (var part in parts)
|
foreach (var part in parts)
|
||||||
{
|
{
|
||||||
if (allPlaylistDict.TryGetValue(part, out var playlist))
|
if (playlist.Name?.Equals(part, StringComparison.Ordinal) ?? false)
|
||||||
{
|
{
|
||||||
if (!subjectPlaylist.include_spotify_owned &&
|
if (!subjectPlaylist.include_spotify_owned &&
|
||||||
(playlist.Owner?.DisplayName.Contains("spotify", StringComparison.InvariantCultureIgnoreCase) ?? false))
|
(playlist.Owner?.DisplayName.Contains("spotify", StringComparison.InvariantCultureIgnoreCase) ??
|
||||||
|
false))
|
||||||
{
|
{
|
||||||
// skip
|
// skip
|
||||||
}
|
}
|
||||||
@ -97,14 +119,15 @@ public class PlaylistGenerator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async IAsyncEnumerable<PlaylistTrack<IPlayableItem>> GetPlatlistTracks(SpotifyClient client, IEnumerable<FullPlaylist> playlists)
|
private async IAsyncEnumerable<PlaylistTrack<IPlayableItem>> GetPlaylistTracks(SpotifyClient client, IEnumerable<FullPlaylist> playlists)
|
||||||
{
|
{
|
||||||
foreach (var playlist in playlists)
|
foreach (var playlist in playlists)
|
||||||
{
|
{
|
||||||
if (playlist.Tracks is { } tracks)
|
if (playlist.Tracks is { } tracks)
|
||||||
{
|
{
|
||||||
foreach (var track in await client.PaginateAll(tracks))
|
foreach (var track in await client.PaginateAll(await client.Playlists.GetItems(playlist.Id)))
|
||||||
{
|
{
|
||||||
yield return track;
|
yield return track;
|
||||||
}
|
}
|
||||||
@ -128,11 +151,8 @@ public class PlaylistGenerator
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual IEnumerable<CommonTrack> CollapseContextToCommonTracks(PlaylistGeneratingContext context)
|
protected virtual IEnumerable<CommonTrack> CollapseContextToCommonTracks(PlaylistGeneratingContext context) =>
|
||||||
{
|
context.ToCommonTracks();
|
||||||
return context.PartTracks.Select(x => (CommonTrack)x)
|
|
||||||
.Concat(context.LibraryTracks.Select(x => (CommonTrack)x));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual IEnumerable<CommonTrack> SortTracks(IEnumerable<CommonTrack> tracks, Fire.Playlist playlist)
|
protected virtual IEnumerable<CommonTrack> SortTracks(IEnumerable<CommonTrack> tracks, Fire.Playlist playlist)
|
||||||
{
|
{
|
||||||
@ -145,4 +165,21 @@ public class PlaylistGenerator
|
|||||||
return tracks.OrderByReleaseDate();
|
return tracks.OrderByReleaseDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual async Task ExecutePlaylist(SpotifyClient client, Fire.Playlist playlist, User user, IEnumerable<CommonTrack> tracks, IEnumerable<string> partList)
|
||||||
|
{
|
||||||
|
var chunks = tracks.Select(x => x.TrackUri).Chunk(100);
|
||||||
|
var playlistId = playlist.uri.UriToId();
|
||||||
|
|
||||||
|
if (chunks.FirstOrDefault() is { } chunk)
|
||||||
|
{
|
||||||
|
await client.Playlists.ReplaceItems(playlist.uri.UriToId(),
|
||||||
|
new PlaylistReplaceItemsRequest(chunk.ToList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var remainingChunk in chunks.Skip(1))
|
||||||
|
{
|
||||||
|
await client.Playlists.AddItems(playlistId, new PlaylistAddItemsRequest(remainingChunk));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -11,8 +11,11 @@ public static class SortExtensions
|
|||||||
.ThenBy(x => x.TrackNumber);
|
.ThenBy(x => x.TrackNumber);
|
||||||
|
|
||||||
public static IOrderedEnumerable<CommonTrack> OrderByReleaseDate(this IEnumerable<CommonTrack> input) =>
|
public static IOrderedEnumerable<CommonTrack> OrderByReleaseDate(this IEnumerable<CommonTrack> input) =>
|
||||||
input.OrderByArtistAlbumTrackNumber()
|
input.OrderByDescending(x => x.ReleaseDate)
|
||||||
.ThenByDescending(x => x.ReleaseDate);
|
.ThenBy(x => x.AlbumArtistNames.First())
|
||||||
|
.ThenBy(x => x.AlbumName)
|
||||||
|
.ThenBy(x => x.DiscNumber)
|
||||||
|
.ThenBy(x => x.TrackNumber);
|
||||||
|
|
||||||
public static IOrderedEnumerable<CommonTrack> Shuffle(this IEnumerable<CommonTrack> input) =>
|
public static IOrderedEnumerable<CommonTrack> Shuffle(this IEnumerable<CommonTrack> input) =>
|
||||||
input.OrderBy(x => _rng.Next());
|
input.OrderBy(x => _rng.Next());
|
||||||
|
@ -38,7 +38,7 @@ public class SpotifyNetworkProvider
|
|||||||
await WriteUserTokenUpdate(user, new
|
await WriteUserTokenUpdate(user, new
|
||||||
{
|
{
|
||||||
access_token = refreshed.AccessToken,
|
access_token = refreshed.AccessToken,
|
||||||
refresh_token = refreshed.RefreshToken,
|
refresh_token = refreshed.RefreshToken ?? user.refresh_token,
|
||||||
last_refreshed = refreshed.CreatedAt,
|
last_refreshed = refreshed.CreatedAt,
|
||||||
token_expiry = refreshed.ExpiresIn
|
token_expiry = refreshed.ExpiresIn
|
||||||
});
|
});
|
||||||
@ -58,7 +58,7 @@ public class SpotifyNetworkProvider
|
|||||||
await WriteUserTokenUpdate(user, new
|
await WriteUserTokenUpdate(user, new
|
||||||
{
|
{
|
||||||
access_token = resp.AccessToken,
|
access_token = resp.AccessToken,
|
||||||
refresh_token = resp.RefreshToken,
|
refresh_token = resp.RefreshToken ?? user.refresh_token,
|
||||||
last_refreshed = resp.CreatedAt,
|
last_refreshed = resp.CreatedAt,
|
||||||
token_expiry = resp.ExpiresIn
|
token_expiry = resp.ExpiresIn
|
||||||
});
|
});
|
||||||
|
@ -18,7 +18,10 @@ public class SpotifyRecommender: IRecommend
|
|||||||
{
|
{
|
||||||
if (playlist.include_recommendations)
|
if (playlist.include_recommendations)
|
||||||
{
|
{
|
||||||
var request = new RecommendationsRequest();
|
var request = new RecommendationsRequest()
|
||||||
|
{
|
||||||
|
Limit = playlist.recommendation_sample
|
||||||
|
};
|
||||||
|
|
||||||
var response = await _client.Browse.GetRecommendations(request);
|
var response = await _client.Browse.GetRecommendations(request);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user