public async Task <List <IPlaylistSyncError> > SyncToSpotify(MusicBeeSyncHelper mb, List <MusicBeePlaylist> mbPlaylistsToSync, bool includeFoldersInPlaylistName = false, bool includeZAtStartOfDatePlaylistName = true) { List <IPlaylistSyncError> errors = new List <IPlaylistSyncError>(); foreach (MusicBeePlaylist playlist in mbPlaylistsToSync) { // Use LINQ to check for a playlist with the same name // If there is one, clear it's contents, otherwise create one // Unless it's been deleted, in which case pretend it doesn't exist. // I'm not sure how to undelete a playlist, or even if you can string spotifyPlaylistName = null; if (includeFoldersInPlaylistName) { spotifyPlaylistName = playlist.Name; } else { spotifyPlaylistName = playlist.Name.Split('\\').Last(); } if (includeZAtStartOfDatePlaylistName) { // if it starts with a 2, it's a date playlist if (spotifyPlaylistName.StartsWith("2")) { spotifyPlaylistName = $"Z {spotifyPlaylistName}"; } } // If Spotify playlist with same name already exists, clear it. // Otherwise create one SimplePlaylist thisPlaylist = Playlists.FirstOrDefault(p => p.Name == spotifyPlaylistName); string thisPlaylistId; if (thisPlaylist != null) { var request = new PlaylistReplaceItemsRequest(new List <string>() { }); var success = await Spotify.Playlists.ReplaceItems(thisPlaylist.Id, request); if (!success) { Log("Error while trying to clear playlist before syncing new tracks"); return(errors); } thisPlaylistId = thisPlaylist.Id; } else { var request = new PlaylistCreateRequest(spotifyPlaylistName); FullPlaylist newPlaylist = await Spotify.Playlists.Create(Profile.Id, request); thisPlaylistId = newPlaylist.Id; } List <FullTrack> songsToAdd = new List <FullTrack>(); // And get the title and artist of each file, and add it to the GMusic playlist foreach (var song in playlist.Songs) { string title = song.Title; string artist = song.Artist; string album = song.Album; string artistEsc = EscapeChar(artist.ToLower()); string titleEsc = EscapeChar(title.ToLower()); string searchStr = $"artist:{artistEsc} track:{titleEsc}"; var request = new SearchRequest(SearchRequest.Types.Track, searchStr); SearchResponse search = await Spotify.Search.Item(request); if (search.Tracks == null || search.Tracks.Items == null) { Log($"Could not find track on Spotify '{searchStr}' for '{title}' by '{artist}'"); continue; } if (search.Tracks.Items.Count == 0) { Log($"Found 0 results on Spotify for: {searchStr} for '{title}' by '{artist}'"); continue; } // try to find track matching artist and title FullTrack trackToAdd = null; foreach (FullTrack track in search.Tracks.Items) { bool titleMatches = (track.Name.ToLower() == title.ToLower()); bool artistMatches = (track.Artists.Exists(a => a.Name.ToLower() == artist.ToLower())); bool albumMatches = (track.Album.Name.ToLower() == album.ToLower()); if (titleMatches && artistMatches && albumMatches) { trackToAdd = track; break; } else if ((titleMatches && artistMatches) || (titleMatches && albumMatches) || (artistMatches && albumMatches)) { // if two of them match, guessing this track is correct is // probably better than just using the firstordefault, but keep looping hoping for a better track trackToAdd = track; } else if (artistMatches && trackToAdd == null) { // if just the artist matches and we haven't found anything yet... this might be our best guess trackToAdd = track; } } if (trackToAdd == null) { trackToAdd = search.Tracks.Items.FirstOrDefault(); Log($"Didn't find a perfect match for {searchStr} for '{title}' by '{artist}', so using '{trackToAdd.Name}' by '{trackToAdd.Artists.FirstOrDefault().Name}' instead"); } songsToAdd.Add(trackToAdd); } List <string> uris = songsToAdd.ConvertAll(x => x.Uri); while (uris.Count > 0) { List <string> currUris = uris.Take(75).ToList(); if (currUris.Count == 0) { break; } uris.RemoveRange(0, currUris.Count); var request = new PlaylistAddItemsRequest(currUris); var resp = await Spotify.Playlists.AddItems(thisPlaylistId, request); if (resp == null) { Log("Error while trying to update playlist with track uris"); return(errors); } } } return(errors); }
public async Task <List <IPlaylistSyncError> > SyncToMusicBee(MusicBeeSyncHelper mb, List <SimplePlaylist> playlists) { List <IPlaylistSyncError> errors = new List <IPlaylistSyncError>(); // Go through each playlist we want to sync in turn foreach (SimplePlaylist playlist in playlists) { // Create an empty list for this playlist's local songs List <MusicBeeSong> mbPlaylistSongs = new List <MusicBeeSong>(); // Get all tracks for playlist var fp = await Spotify.Playlists.Get(playlist.Id); var allTracks = await Spotify.PaginateAll(fp.Tracks); var tracks = new List <FullTrack>(); foreach (var t in allTracks) { if (t.Track is FullTrack track) { tracks.Add(track); } } foreach (FullTrack track in tracks) { string artistStr = track.Artists.FirstOrDefault().Name.ToLower(); string titleStr = track.Name.ToLower(); MusicBeeSong thisMbSong = mb.Songs.FirstOrDefault(s => s.Artist.ToLower() == artistStr && s.Title.ToLower() == titleStr); if (thisMbSong != null) { mbPlaylistSongs.Add(thisMbSong); } else { errors.Add(new UnableToFindSpotifyTrackError() { AlbumName = track.Album.Name, ArtistName = track.Artists.FirstOrDefault().Name, PlaylistName = playlist.Name, TrackName = track.Name, SearchedService = false }); } } //mbAPI expects a string array of song filenames to create a playlist string[] mbPlaylistSongFiles = new string[mbPlaylistSongs.Count]; int i = 0; foreach (MusicBeeSong song in mbPlaylistSongs) { mbPlaylistSongFiles[i] = song.Filename; i++; } // Now we need to delete any existing playlist with matching name MusicBeePlaylist localPlaylist = mb.Playlists.FirstOrDefault(p => p.Name == playlist.Name); if (localPlaylist != null) { mb.MbApiInterface.Playlist_DeletePlaylist(localPlaylist.mbName); } // Create the playlist locally string playlistRelativeDir = ""; string playlistName = playlist.Name; // if it's a date playlist, remove first Z if (playlistName.StartsWith("Z ")) { playlistName = playlistName.Skip(2).ToString(); } string[] itemsInPath = playlist.Name.Split('\\'); if (itemsInPath.Length > 1) { // Creates a playlist at top level directory mb.MbApiInterface.Playlist_CreatePlaylist("", playlistName, mbPlaylistSongFiles); } mb.MbApiInterface.Playlist_CreatePlaylist(playlistRelativeDir, playlistName, mbPlaylistSongFiles); } // Get the local playlists again mb.RefreshMusicBeePlaylists(); return(errors); }
public async Task <List <IPlaylistSyncError> > SyncToGoogle(MusicBeeSyncHelper mb, List <MusicBeePlaylist> mbPlaylistsToSync, bool includeFoldersInPlaylistName = false, bool includeZAtStartOfDatePlaylistName = true) { List <IPlaylistSyncError> errors = new List <IPlaylistSyncError>(); foreach (MusicBeePlaylist playlist in mbPlaylistsToSync) { // Use LINQ to check for a playlist with the same name // If there is one, clear it's contents, otherwise create one // Unless it's been deleted, in which case pretend it doesn't exist. // I'm not sure how to undelete a playlist, or even if you can string gpmPlaylistName = null; if (includeFoldersInPlaylistName) { gpmPlaylistName = playlist.Name; } else { gpmPlaylistName = playlist.Name.Split('\\').Last(); } if (includeZAtStartOfDatePlaylistName) { // if it starts with a 2, it's a date playlist if (gpmPlaylistName.StartsWith("2")) { gpmPlaylistName = $"Z {gpmPlaylistName}"; } } Playlist thisPlaylist = GooglePlaylists.FirstOrDefault(p => p.Name == gpmPlaylistName && p.Deleted == false); String thisPlaylistID = ""; if (thisPlaylist != null) { List <PlaylistEntry> allPlsSongs = thisPlaylist.Songs; if (allPlsSongs.Count > 0) { MutatePlaylistResponse response = await api.RemoveFromPlaylistAsync(allPlsSongs); } thisPlaylistID = thisPlaylist.Id; } else { MutatePlaylistResponse response = await api.CreatePlaylistAsync(gpmPlaylistName); thisPlaylistID = response.MutateResponses.First().ID; } List <Track> songsToAdd = new List <Track>(); // And get the title and artist of each file, and add it to the GMusic playlist foreach (var song in playlist.Songs) { string title = song.Title; string artist = song.Artist; string album = song.Album; // First check for matching title, artist, album, if we find nothing, then check for matching title/artist Track gSong = GoogleSongsFetched.FirstOrDefault(item => (item.Artist.ToLower() == artist.ToLower() && item.Title.ToLower() == title.ToLower() && item.Album.ToLower() == album.ToLower())); if (gSong == null) { gSong = GoogleSongsFetched.FirstOrDefault(item => (item.Artist.ToLower() == artist.ToLower() && item.Title.ToLower() == title.ToLower())); if (gSong != null) { songsToAdd.Add(gSong); } else { // Didn't find it in cached library, so query for it Track result = await TryGetTrackAsync(artist, title, album); if (result != null) { GoogleSongsFetched.Add(result); songsToAdd.Add(result); } else { // didn't find it even via querying errors.Add(new UnableToFindGPMTrackError() { PlaylistName = gpmPlaylistName, AlbumName = album, ArtistName = artist, TrackName = title, SearchedService = true, }); } } } else { songsToAdd.Add(gSong); } } await api.AddToPlaylistAsync(thisPlaylistID, songsToAdd); } return(errors); }
public async Task <List <IPlaylistSyncError> > SyncToMusicBee(MusicBeeSyncHelper mb, List <Playlist> playlists) { List <IPlaylistSyncError> errors = new List <IPlaylistSyncError>(); // Go through each playlist we want to sync in turn foreach (Playlist playlist in playlists) { // Create an empty list for this playlist's local songs List <MusicBeeSong> mbPlaylistSongs = new List <MusicBeeSong>(); // For each entry in the playlist we're syncing, get the song from the GMusic library we've downloaded, // Get the song Title and Artist and then look it up in the list of local songs. // If we find it, add it to the list of local songs foreach (PlaylistEntry entry in playlist.Songs) { Track thisSong = GoogleSongsFetched.FirstOrDefault(s => s.Id == entry.TrackID || s.NID == entry.TrackID); // if we couldn't find it, attempt to fetch it by trackID and cache it if (IsTrackInvalid(thisSong)) { thisSong = await api.GetTrackAsync(entry.TrackID); if (IsTrackInvalid(thisSong)) { errors.Add(new UnableToFindGooglePlaylistEntryError() { GpmPlaylistName = playlist.Name, GpmPlaylistPosition = entry.AbsolutePosition, GpmTrackId = entry.TrackID, }); } else { GoogleSongsFetched.Add(thisSong); } } if (!IsTrackInvalid(thisSong)) { MusicBeeSong thisMbSong = mb.Songs.FirstOrDefault(s => s.Artist == thisSong.Artist && s.Title == thisSong.Title && s.Album == thisSong.Album); if (thisMbSong != null) { mbPlaylistSongs.Add(thisMbSong); } else { errors.Add(new UnableToFindGPMTrackError() { AlbumName = thisSong.Album, ArtistName = thisSong.Artist, PlaylistName = playlist.Name, TrackName = thisSong.Title, SearchedService = false, }); } } } //mbAPI expects a string array of song filenames to create a playlist string[] mbPlaylistSongFiles = new string[mbPlaylistSongs.Count]; int i = 0; foreach (MusicBeeSong song in mbPlaylistSongs) { mbPlaylistSongFiles[i] = song.Filename; i++; } // Now we need to delete any existing playlist with matching name MusicBeePlaylist localPlaylist = mb.Playlists.FirstOrDefault(p => p.Name == playlist.Name); if (localPlaylist != null) { mb.MbApiInterface.Playlist_DeletePlaylist(localPlaylist.mbName); } // Create the playlist locally string playlistRelativeDir = ""; string playlistName = playlist.Name; string[] itemsInPath = playlist.Name.Split('\\'); if (itemsInPath.Length > 1) { // Creates a playlist at top level directory mb.MbApiInterface.Playlist_CreatePlaylist("", playlistName, mbPlaylistSongFiles); } mb.MbApiInterface.Playlist_CreatePlaylist(playlistRelativeDir, playlistName, mbPlaylistSongFiles); } // Get the local playlists again mb.RefreshMusicBeePlaylists(); return(errors); }
public async Task <List <IPlaylistSyncError> > SyncToYoutubeMusic(MusicBeeSyncHelper mb, List <MusicBeePlaylist> mbPlaylistsToSync, bool includeFoldersInPlaylistName = false, bool includeZAtStartOfDatePlaylistName = true) { List <IPlaylistSyncError> errors = new List <IPlaylistSyncError>(); foreach (var playlist in mbPlaylistsToSync) { string newPlaylistName = null; if (includeFoldersInPlaylistName) { newPlaylistName = playlist.Name; } else { newPlaylistName = playlist.Name.Split('\\').Last(); } if (includeZAtStartOfDatePlaylistName) { // if it starts with a 2, it's a date playlist if (newPlaylistName.StartsWith("2")) { newPlaylistName = $"Z {newPlaylistName}"; } } List <string> videoIds = new List <string>(); foreach (var song in playlist.Songs) { string searchStr = $"{song.Title} {song.Artist} {song.Album}"; var response = await Ytm.Search(searchStr); var video = FindMatchInSearchResult(searchStr, song, response); if (video == null || !IsPerfectMatch(song, video)) { // if we couldn't find it, try searching uploads response = await Ytm.SearchUploads(searchStr); var otherVideo = FindMatchInSearchResult(searchStr, song, response); video = FindBestMatchBetweenTwo(song, video, otherVideo); } if (video == null) { errors.Add(new UnableToFindYTMTrackError() { AlbumName = song.Album, ArtistName = song.Artist, PlaylistName = playlist.Name, SearchedService = true, TrackName = song.Title, }); } else { videoIds.Add(video.VideoId); } } // If YTM playlist with same name already exists, clear it. // Otherwise create one var thisPlaylist = Playlists.FirstOrDefault(p => p.Title == newPlaylistName); if (thisPlaylist != null) { var playlistWithSongs = await Ytm.GetPlaylist(thisPlaylist.PlaylistId, authRequired : true); if (playlistWithSongs.Tracks.Count != 0) { var res = await Ytm.RemovePlaylistItems(playlistWithSongs.PlaylistId, playlistWithSongs.Tracks); if (!res) { Log("Error while trying to clear playlist before syncing new tracks"); return(errors); } } await Ytm.AddPlaylistItems(playlistWithSongs.PlaylistId, videoIds); } else { var res = await Ytm.CreatePlaylist(newPlaylistName, "", videoIds : videoIds); } } return(errors); }
public async Task <List <IPlaylistSyncError> > SyncToMusicBee(MusicBeeSyncHelper mb, List <Playlist> playlists) { List <IPlaylistSyncError> errors = new List <IPlaylistSyncError>(); return(errors); }