예제 #1
0
        public static async Task <List <Song> > GetSongsFromPageAsync(string url, bool useDateLimit = false)
        {
            string      pageText = string.Empty;
            List <Song> songs    = new List <Song>();;

            try
            {
                using (var response = await WebClient.GetAsync(url).ConfigureAwait(false))
                {
                    pageText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                }

                Logger.Debug($"Successful got pageText from {url}");
                foreach (var song in ParseSongsFromPage(pageText))
                {
                    songs.Add(ScrapedDataProvider.GetOrCreateSong(song));
                }
            }
            catch (Exception)
            {
                Logger.Error($"Error getting page text from {url}");
            }

            return(songs);
        }
        public static SongInfo GetSongByHash(string hash)
        {
            string        url      = BEATSAVER_GETBYHASH_BASE_URL + hash.ToLowerInvariant();
            string        pageText = "";
            BeatSaverSong song;

            try
            {
                var pageTask = WebUtils.TryGetStringAsync(url);
                pageTask.Wait();
                pageText = pageTask.Result;
                if (string.IsNullOrEmpty(pageText))
                {
                    Logger.Warning($"Unable to get web page at {url}");
                    return(null);
                }
            }
            catch (HttpRequestException)
            {
                Logger.Error($"HttpRequestException while trying to populate fields for {hash}");
                return(null);
            }
            catch (AggregateException ae)
            {
                ae.WriteExceptions($"Exception while trying to get details for {hash}");
            }
            catch (Exception ex)
            {
                Logger.Exception("Exception getting page", ex);
            }
            song           = ParseSongsFromPage(pageText).FirstOrDefault();
            song.ScrapedAt = DateTime.Now;
            return(ScrapedDataProvider.GetOrCreateSong(song));
        }
        /// <summary>
        /// Creates a SongInfo from a JObject. Sets the ScrapedAt time for the song.
        /// </summary>
        /// <param name="song"></param>
        /// <returns></returns>
        public static BeatSaverSong ParseSongFromJson(JObject song)
        {
            //JSONObject song = (JSONObject) aKeyValue;
            string songIndex = song["key"]?.Value <string>();
            string songName  = song["name"]?.Value <string>();
            string author    = song["uploader"]?["username"]?.Value <string>();
            string songUrl   = "https://beatsaver.com/download/" + songIndex;

            if (BeatSaverSong.TryParseBeatSaver(song, out BeatSaverSong newSong))
            {
                newSong.ScrapedAt = DateTime.Now;
                SongInfo songInfo = ScrapedDataProvider.GetOrCreateSong(newSong);

                songInfo.BeatSaverInfo = newSong;
                return(newSong);
            }
            else
            {
                if (!(string.IsNullOrEmpty(songIndex)))
                {
                    // TODO: look at this
                    Logger.Warning($"Couldn't parse song {songIndex}, skipping.");// using sparse definition.");
                    //return new SongInfo(songIndex, songName, songUrl, author);
                }
                else
                {
                    Logger.Error("Unable to identify song, skipping");
                }
            }
            return(null);
        }
예제 #4
0
        public static async Task <Song> GetSongByHashAsync(string hash, CancellationToken cancellationToken)
        {
            Uri    uri      = new Uri(BEATSAVER_GETBYHASH_BASE_URL + hash);
            string pageText = "";

            try
            {
                using (var response = await WebUtils.GetBeatSaverAsync(uri, cancellationToken).ConfigureAwait(false))
                {
                    pageText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                }
                if (string.IsNullOrEmpty(pageText))
                {
                    Logger.Warning($"Unable to get web page at {uri}");
                    return(null);
                }
            }
            catch (HttpRequestException)
            {
                Logger.Error($"HttpRequestException while trying to populate fields for {hash}");
                return(null);
            }
            catch (AggregateException ae)
            {
                ae.WriteExceptions($"Exception while trying to get details for {hash}");
            }
            catch (Exception ex)
            {
                Logger.Exception("Exception getting page", ex);
            }
            Song song = ParseSongsFromPage(pageText).FirstOrDefault();

            song.ScrapedAt = DateTime.Now;
            return(ScrapedDataProvider.GetOrCreateSong(song));
        }
예제 #5
0
        public static ScoreSaberSong GetScoreSaberSongFromJson(JToken song)
        {
            //JSONObject song = (JSONObject) aKeyValue;
            //string songIndex = song["key"]?.Value<string>();
            string songHash = song["id"]?.Value <string>();
            string songName = song["name"]?.Value <string>();
            string author   = song["author"]?.Value <string>();
            //string songUrl = "https://beatsaver.com/download/" + songIndex;
            ScoreSaberSong newSong = null;

            if (ScoreSaberSong.TryParseScoreSaberSong(song, ref newSong))
            {
                //newSong.Feed = "followings";
                newSong.ScrapedAt = DateTime.Now;
                ScrapedDataProvider.GetOrCreateSong(newSong, true);
                return(newSong);
            }
            else
            {
                if (!(string.IsNullOrEmpty(songName)))
                {
                    Logger.Warning($"Couldn't parse song {songName}, using sparse definition.");
                    //songs.Add(new ScoreSaberSong("", songName, "", author));
                }
                else
                {
                    Logger.Error("Unable to identify song, skipping");
                }
            }
            return(null);
        }
예제 #6
0
        public static List <SongInfo> GetSongsFromPage(string pageText)
        {
            var sssongs = GetSSSongsFromPage(pageText);

            List <SongInfo> songs = new List <SongInfo>();
            SongInfo        tempSong;

            //sssongs.AsParallel().WithDegreeOfParallelism(Config.MaxConcurrentPageChecks).ForAll(s => s.PopulateFields());
            //Parallel.ForEach(sssongs, new ParallelOptions { MaxDegreeOfParallelism = Config.MaxConcurrentPageChecks }, s => s.PopulateFields());
            foreach (var song in sssongs)
            {
                tempSong = ScrapedDataProvider.GetSong(song, false); // Don't search online because creating the ScoreSaberSong already tried
                if (tempSong != null && !string.IsNullOrEmpty(tempSong.key))
                {
                    //tempSong.ScoreSaberInfo.AddOrUpdate(song.uid, song);
                    songs.Add(tempSong);
                }
                else
                {
                    Logger.Warning($"Could not find song {song.name} with hash {song.hash} on Beat Saver, skipping...");
                }
            }

            return(songs);
        }
예제 #7
0
        //[TestMethod]
        public void BuildDatabaseFromJsonTest()
        {
            var bsScrape = @"TestData\BeatSaverScrape.json";
            var ssScrape = @"TestData\ScoreSaberScrape.json";
            var dbPath   = "songsTest.db";

            ScrapedDataProvider.BuildDatabaseFromJson(dbPath, bsScrape, ssScrape);
        }
예제 #8
0
        private static void Tests()
        {
            //ScrapedDataProvider.Initialize();

            WebUtils.Initialize(5);

            ScrapedDataProvider.BeatSaverSongs.WriteFile();
            var bsReader = new BeatSaverReader();
            //var bsSongs = bsReader.GetSongsFromFeed(new BeatSaverFeedSettings((int)BeatSaverFeeds.LATEST) { MaxPages = 10, searchOnline = true });
            var authorSongs = BeatSaverReader.GetSongsByAuthor("ruckasdfus");

            var song = ScrapedDataProvider.Songs.Values.Where(s => s.ScoreSaberInfo.Any(d => d.Value.uid == 155732)).FirstOrDefault();

            var found           = ScrapedDataProvider.TryGetSongByKey("3f57", out SongInfo badSong, false);
            var CustomSongsPath = Path.Combine(OldConfig.BeatSaberPath, "Beat Saber_Data", "CustomLevels");
            var tempFolder      = new DirectoryInfo(Path.Combine(Path.GetTempPath(), badSong.key + ".zip"));
            var outputFolder    = new DirectoryInfo(Path.Combine(CustomSongsPath, $"{badSong.key} ({Utilities.MakeSafeFilename(badSong.songName)} - {Utilities.MakeSafeFilename(badSong.authorName)})"));
            var job             = new DownloadJob(badSong, tempFolder.FullName, outputFolder.FullName);

            job.RunJobAsync().Wait();
            var br     = new BeastSaberReader("Zingabopp", 3);
            var text   = WebUtils.GetPageText("https://bsaber.com/wp-json/bsaber-api/songs/?bookmarked_by=Zingabopp&page=1");
            var bSongs = br.GetSongsFromPage(text);


            //ScrapedDataProvider.Initialize();


            //ScrapedDataProvider.Initialize();
            var bsScrape = ScrapedDataProvider.BeatSaverSongs;
            var ssScrape = ScrapedDataProvider.ScoreSaberSongs;

            ScrapedDataProvider.TryGetSongByHash("501f6b1bddb2af72abda0f1e6b7b89cb1eb3db67", out SongInfo deletedSong);
            //var job = new DownloadJob(deletedSong, "test.zip", @"ScrapedData\test");
            //var jobTask = job.RunJobAsync();
            //jobTask.Wait();
            bsScrape.AddOrUpdate(null);
            var resp = WebUtils.HttpClient.GetAsync("https://beatsaver.com/api/maps/detail/b");

            Task.WaitAll(resp);

            var rateHeaders = resp.Result.Headers.Where(h => h.Key.StartsWith("Rate-Limit")).ToDictionary(x => x.Key, x => x.Value.FirstOrDefault());
            var remoteTime  = resp.Result.Headers.Date;
            var rateInfo    = WebUtils.ParseRateLimit(rateHeaders);

            Console.WriteLine("Reset Timespan: " + rateInfo.TimeToReset.ToString());
            foreach (var item in resp.Result.Headers)
            {
                Console.WriteLine($"{item.Key}: {string.Join("|", item.Value)}");
            }

            var trending    = ScrapedDataProvider.Songs.Values.Where(s => s.ScoreSaberInfo.Count > 0).OrderByDescending(s => s.ScoreSaberInfo.Values.Select(ss => ss.scores).Aggregate((a, b) => a + b)).Take(100);
            var detTrending = trending.Select(s => (s.ScoreSaberInfo.Values.Select(ss => ss.scores).Aggregate((a, b) => a + b), s)).ToList();
        }
        public static List <SongInfo> GetSongsFromPage(string url)
        {
            string pageText = GetPageText(url);
            var    songs    = new List <SongInfo>();

            foreach (var song in ParseSongsFromPage(pageText))
            {
                songs.Add(ScrapedDataProvider.GetOrCreateSong(song));
            }
            return(songs);
        }
        public static async Task <List <SongInfo> > GetSongsFromPageAsync(string url, bool useDateLimit = false)
        {
            string          pageText = string.Empty;
            List <SongInfo> songs    = new List <SongInfo>();;

            try
            {
                pageText = await GetPageTextAsync(url).ConfigureAwait(false);

                Logger.Debug($"Successful got pageText from {url}");
                foreach (var song in ParseSongsFromPage(pageText))
                {
                    songs.Add(ScrapedDataProvider.GetOrCreateSong(song));
                }
            }
            catch (Exception ex)
            {
                Logger.Error($"Error getting page text from {url}");
            }

            return(songs);
        }
        public static List <SongInfo> Search(string criteria, SearchType type)
        {
            if (type == SearchType.key)
            {
                return(new List <SongInfo>()
                {
                    GetSongByKey(criteria)
                });
            }

            if (type == SearchType.user)
            {
                return(GetSongsByUploaderId(criteria));
            }

            if (type == SearchType.hash)
            {
                return(new List <SongInfo>()
                {
                    GetSongByHash(criteria)
                });
            }
            StringBuilder url;

            url = new StringBuilder(Feeds[BeatSaverFeeds.SEARCH].BaseUrl);
            url.Replace(SEARCHTYPEKEY, type.ToString());
            url.Replace(SEARCHKEY, criteria);

            string pageText = GetPageText(url.ToString());
            var    songs    = new List <SongInfo>();

            foreach (var song in ParseSongsFromPage(pageText))
            {
                songs.Add(ScrapedDataProvider.GetOrCreateSong(song));
            }
            return(songs);
        }
예제 #12
0
        static void Main(string[] args)
        {
            try
            {
                Logger.ShortenSourceName = true;
                try
                {
                    OldConfig.Initialize();
                    Logger.LogLevel = OldConfig.StrToLogLevel(OldConfig.LoggingLevel);
                    if (Logger.LogLevel < LogLevel.Info)
                    {
                        Logger.ShortenSourceName = false;
                    }
                }
                catch (FileNotFoundException ex)
                {
                    Logger.Exception("Error initializing Config", ex);
                }
                Logger.Info($"Using Beat Saber directory: {OldConfig.BeatSaberPath}");
                ScrapedDataProvider.Initialize();
                Logger.Info($"Scrapes loaded, {ScrapedDataProvider.BeatSaverSongs.Data.Count} BeatSaverSongs and {ScrapedDataProvider.ScoreSaberSongs.Data.Count} ScoreSaber difficulties loaded");
                //DoFullScrape();
                //var scoreSaberSongs = ScrapedDataProvider.ScoreSaberSongs.Data.Select(ss => ss.hash).Distinct().Count();
                //var activeSongs = ScrapedDataProvider.Songs.Values.Where(s => s.ScoreSaberInfo?.Values.Count > 0).Count();
                //Tests();
                try
                {
                    if (args.Length > 0)
                    {
                        var bsDir = new DirectoryInfo(args[0]);
                        if (bsDir.Exists)
                        {
                            if (bsDir.GetFiles("Beat Saber.exe").Length > 0)
                            {
                                Logger.Info("Found Beat Saber.exe");
                                OldConfig.BeatSaberPath = bsDir.FullName;
                                Logger.Info($"Updated Beat Saber directory path to {OldConfig.BeatSaberPath}");
                                Console.WriteLine("Press any key to continue...");
                                Console.ReadKey();
                            }
                            else
                            {
                                Logger.Warning($"Provided directory does not appear to be Beat Saber's root folder, ignoring it");
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.Exception($"Error parsing command line arguments", ex);
                }


                if (!OldConfig.CriticalError)
                {
                    WebUtils.Initialize(OldConfig.MaxConcurrentPageChecks);
                    Stopwatch sw = new Stopwatch();
                    sw.Start();
                    SyncSaber ss = new SyncSaber();
                    ss.ScrapeNewSongs();
                    Console.WriteLine();
                    if (OldConfig.SyncFavoriteMappersFeed && OldConfig.FavoriteMappers.Count > 0)
                    {
                        Logger.Info($"Downloading songs from FavoriteMappers.ini...");
                        try
                        {
                            ss.DownloadSongsFromFeed(BeatSaverReader.NameKey, new BeatSaverFeedSettings(0)
                            {
                                Authors = OldConfig.FavoriteMappers.ToArray()
                            });
                        }
                        catch (AggregateException ae)
                        {
                            ae.WriteExceptions($"Exceptions downloading songs from FavoriteMappers.ini.");
                        }
                        catch (Exception ex)
                        {
                            Logger.Exception("Exception downloading songs from FavoriteMappers.ini.", ex);
                        }
                    }
                    else
                    {
                        if (OldConfig.SyncFavoriteMappersFeed)
                        {
                            Logger.Warning($"Skipping FavoriteMappers.ini feed, no authors found in {Path.Combine(OldConfig.BeatSaberPath, "UserData", "FavoriteMappers.ini")}");
                        }
                    }

                    if (OldConfig.SyncFollowingsFeed)
                    {
                        // Followings
                        Console.WriteLine();
                        Logger.Info($"Downloading songs from {BeastSaberReader.Feeds[BeastSaberFeeds.FOLLOWING].Name} feed...");
                        try
                        {
                            //ss.DownloadBeastSaberFeed(0, Web.BeastSaberReader.GetMaxBeastSaberPages(0));
                            ss.DownloadSongsFromFeed(BeastSaberReader.NameKey, new BeastSaberFeedSettings(0)
                            {
                                MaxPages = OldConfig.MaxFollowingsPages
                            });
                        }
                        catch (AggregateException ae)
                        {
                            ae.WriteExceptions($"Exceptions downloading songs from BeastSaberFeed: Following.");
                        }
                        catch (Exception ex)
                        {
                            Logger.Exception($"Exception downloading BeastSaberFeed: Following", ex);
                        }
                    }
                    // Bookmarks
                    if (OldConfig.SyncBookmarksFeed)
                    {
                        Console.WriteLine();
                        Logger.Info($"Downloading songs from {BeastSaberReader.Feeds[BeastSaberFeeds.BOOKMARKS].Name} feed...");
                        try
                        {
                            //ss.DownloadBeastSaberFeed(1, Web.BeastSaberReader.GetMaxBeastSaberPages(1));
                            ss.DownloadSongsFromFeed(BeastSaberReader.NameKey, new BeastSaberFeedSettings(1)
                            {
                                MaxPages = OldConfig.MaxBookmarksPages
                            });
                        }
                        catch (AggregateException ae)
                        {
                            ae.WriteExceptions($"Exceptions downloading songs from BeastSaberFeed: Bookmarks.");
                        }
                        catch (Exception ex)
                        {
                            Logger.Exception($"Exception downloading BeastSaberFeed: Bookmarks", ex);
                        }
                    }
                    if (OldConfig.SyncCuratorRecommendedFeed)
                    {
                        // Curator Recommended
                        Console.WriteLine();
                        Logger.Info($"Downloading songs from {BeastSaberReader.Feeds[BeastSaberFeeds.CURATOR_RECOMMENDED].Name} feed...");
                        try
                        {
                            //ss.DownloadBeastSaberFeed(2, Web.BeastSaberReader.GetMaxBeastSaberPages(2));
                            ss.DownloadSongsFromFeed(BeastSaberReader.NameKey, new BeastSaberFeedSettings(2)
                            {
                                MaxPages = OldConfig.MaxCuratorRecommendedPages
                            });
                        }
                        catch (AggregateException ae)
                        {
                            ae.WriteExceptions($"Exceptions downloading songs from BeastSaberFeed: Curator Recommended.");
                        }
                        catch (Exception ex)
                        {
                            Logger.Exception($"Exception downloading BeastSaberFeed: Curator Recommended", ex);
                        }
                    }

                    if (OldConfig.SyncTopPPFeed)
                    {
                        // ScoreSaber Top PP
                        Console.WriteLine();
                        Logger.Info($"Downloading songs from {ScoreSaberReader.Feeds[ScoreSaberFeeds.TOP_RANKED].Name} feed...");
                        try
                        {
                            //ss.DownloadBeastSaberFeed(2, Web.BeastSaberReader.GetMaxBeastSaberPages(2));
                            ss.DownloadSongsFromFeed(ScoreSaberReader.NameKey, new ScoreSaberFeedSettings((int)ScoreSaberFeeds.TOP_RANKED)
                            {
                                MaxSongs     = 1000,//Config.MaxScoreSaberSongs,
                                SongsPerPage = 10,
                                searchOnline = true
                            });
                        }
                        catch (AggregateException ae)
                        {
                            ae.WriteExceptions($"Exceptions downloading songs from ScoreSaberFeed: Top Ranked.");
                        }
                        catch (Exception ex)
                        {
                            Logger.Exception($"Exception downloading ScoreSaberFeed: Top Ranked.", ex);
                        }
                    }

                    /*
                     * Console.WriteLine();
                     * Logger.Info($"Downloading newest songs on Beat Saver...");
                     * try
                     * {
                     *  ss.DownloadSongsFromFeed(BeatSaverReader.NameKey, new BeatSaverFeedSettings(1) {
                     *      MaxPages = Config.MaxBeatSaverPages
                     *  });
                     * }
                     *
                     * catch (Exception ex)
                     * {
                     *  Logger.Exception("Exception downloading BeatSaver newest feed.", ex);
                     * }
                     */

                    sw.Stop();
                    var processingTime = new TimeSpan(sw.ElapsedTicks);
                    Console.WriteLine();
                    Logger.Info($"Finished downloading songs in {(int)processingTime.TotalMinutes} min {processingTime.Seconds} sec");
                }
                else
                {
                    foreach (string e in OldConfig.Errors)
                    {
                        Logger.Error($"Invalid setting: {e} = {OldConfig.Setting[e]}");
                    }
                }


                ScrapedDataProvider.BeatSaverSongs.WriteFile();
                ScrapedDataProvider.ScoreSaberSongs.WriteFile();
            }
            catch (OutOfDateException ex)
            {
                Logger.Error(ex.Message);
            }
            catch (AggregateException ae)
            {
                ae.WriteExceptions($"Uncaught exceptions in Main()");
            }
            catch (Exception ex)
            {
                Logger.Exception("Uncaught exception in Main()", ex);
            }
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }
        /// <summary>
        /// Parses the page text and returns all the songs it can find.
        /// </summary>
        /// <param name="pageText"></param>
        /// <exception cref="XmlException">Invalid XML in pageText</exception>
        /// <returns></returns>
        public List <SongInfo> GetSongsFromPage(string pageText)
        {
            List <SongInfo>   songsOnPage = new List <SongInfo>();
            List <BSaberSong> bSongs      = new List <BSaberSong>();
            int totalSongsForPage         = 0;

            if (pageText.ToLower().StartsWith(@"<?xml"))
            {
                bool        retry       = false;
                XmlDocument xmlDocument = new XmlDocument();
                do
                {
                    try
                    {
                        xmlDocument.LoadXml(pageText);
                        retry = false;
                    }
                    catch (XmlException ex)
                    {
                        if (retry == true)
                        {
                            Logger.Exception("Exception parsing XML.", ex);
                            retry = false;
                        }
                        else
                        {
                            Logger.Warning("Invalid XML formatting detected, attempting to fix...");
                            pageText = pageText.Replace(" & ", " &amp; ");
                            retry    = true;
                        }
                        //File.WriteAllText("ErrorText.xml", pageText);
                    }
                } while (retry == true);
                List <Task> populateTasks = new List <Task>();
                XmlNodeList xmlNodeList   = xmlDocument.DocumentElement.SelectNodes("/rss/channel/item");
                foreach (object obj in xmlNodeList)
                {
                    XmlNode node = (XmlNode)obj;
                    if (node["DownloadURL"] == null || node["SongTitle"] == null)
                    {
                        Logger.Debug("Not a song! Skipping!");
                    }
                    else
                    {
                        // TODO: Not really using any of this except the hash.
                        string songName    = node["SongTitle"].InnerText;
                        string downloadUrl = node["DownloadURL"]?.InnerText;
                        string hash        = node["Hash"]?.InnerText?.ToUpper();
                        string authorName  = node["LevelAuthorName"]?.InnerText;
                        string songKey     = node["SongKey"]?.InnerText;
                        if (downloadUrl.Contains("dl.php"))
                        {
                            Logger.Warning("Skipping BeastSaber download with old url format!");
                            totalSongsForPage++;
                        }
                        else
                        {
                            string songIndex = !string.IsNullOrEmpty(songKey) ? songKey : downloadUrl.Substring(downloadUrl.LastIndexOf('/') + 1);
                            //string mapper = !string.IsNullOrEmpty(authorName) ? authorName : GetMapperFromBsaber(node.InnerText);
                            //string songUrl = !string.IsNullOrEmpty(downloadUrl) ? downloadUrl : BeatSaverDownloadURL_Base + songIndex;

                            if (ScrapedDataProvider.TryGetSongByKey(songIndex, out SongInfo currentSong))
                            {
                                songsOnPage.Add(currentSong);
                            }
                        }
                    }
                }
            }
            else // Page is JSON (hopefully)
            {
                JObject result = new JObject();
                try
                {
                    result = JObject.Parse(pageText);
                }
                catch (Exception ex)
                {
                    Logger.Exception("Unable to parse JSON from text", ex);
                }

                var songs = result["songs"];
                foreach (var bSong in songs)
                {
                    // Try to get the song hash from BeastSaber
                    string songHash = bSong["hash"]?.Value <string>();
                    if (!string.IsNullOrEmpty(songHash))
                    {
                        if (ScrapedDataProvider.TryGetSongByHash(songHash, out SongInfo currentSong))
                        {
                            songsOnPage.Add(currentSong);
                        }
                    }
                    else
                    {
                        // Unable to get song hash, try getting song_key from BeastSaber
                        string songKey = bSong["song_key"]?.Value <string>();
                        if (!string.IsNullOrEmpty(songKey))
                        {
                            if (ScrapedDataProvider.TryGetSongByKey(songKey, out SongInfo currentSong))
                            {
                                songsOnPage.Add(currentSong);
                            }
                            else
                            {
                                Logger.Debug($"ScrapedDataProvider could not find song: {bSong.Value<string>()}");
                            }
                        }
                        else
                        {
                            Logger.Debug($"Not a song, skipping: {bSong.ToString()}");
                        }
                    }
                }
            }
            //Task.WaitAll(populateTasks.ToArray());
            Logger.Debug($"{songsOnPage.Count} songs on the page");
            return(songsOnPage);
        }