diff --git a/README.md b/README.md index 2477c2b..9bef7a9 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,14 @@ will be ignored. ## Searching +### Search Query +The search query is determined as follows: + +- For album downloads: If the album field is non-empty, search for 'Artist Album' + Otherwise, search for 'Artist Title' +- For all other download types: If the title field is non-empty, search for 'Artist Title' + Otherwise, search for 'Artist Album' + ### Soulseek's rate limits The server will ban you for 30 minutes if too many searches are performed within a short timespan. The program has a search limiter which can be adjusted with --searches-per-time @@ -492,13 +500,20 @@ sldl test.csv ```
-Download spotify likes while skipping songs that already exist in the output folder: +Download spotify likes: ``` -sldl spotify-likes --skip-existing +sldl spotify-likes ```
-Download from a youtube playlist with fallback to yt-dlp in case it is not found on soulseek, and retrieve deleted video titles from wayback machine: +Download albums for every song in a spotify playlist: +``` +sldl https://spotify/playlist/url --album --skip-existing +``` + +
+ +Retrieve deleted video names, then download from a youtube playlist with fallback to yt-dlp: ``` sldl "https://www.youtube.com/playlist?list=PLI_eFW8NAFzYAXZ5DrU6E6mQ_XfhaLBUX" --get-deleted --yt-dlp ``` @@ -522,7 +537,7 @@ sldl "artist=MC MENTAL" --aggregate --skip-existing --music-dir "path/to/music" ```
-Download all albums: +Download all albums by an artist that are on soulseek: ``` sldl "artist=MC MENTAL" --aggregate --album ``` diff --git a/slsk-batchdl/Config.cs b/slsk-batchdl/Config.cs index fee2d51..b8613f2 100644 --- a/slsk-batchdl/Config.cs +++ b/slsk-batchdl/Config.cs @@ -225,10 +225,16 @@ static class Config if (DoNotDownload) m3uOption = M3uOption.None; - else if (!hasConfiguredM3uMode && inputType == InputType.String) - m3uOption = M3uOption.None; - else if (!hasConfiguredM3uMode && Program.trackLists != null && !aggregate &&!Program.trackLists.Flattened(true, false, true).Skip(1).Any()) - m3uOption = M3uOption.None; + else if (!hasConfiguredM3uMode) + { + if (inputType == InputType.String) + m3uOption = M3uOption.None; + else if (!aggregate && !(skipExisting && (skipMode == SkipMode.M3u || skipMode == SkipMode.M3uCond)) + && Program.trackLists != null && !Program.trackLists.Flattened(true, false, true).Skip(1).Any()) + { + m3uOption = M3uOption.None; + } + } parentFolder = Utils.ExpandUser(parentFolder); m3uFilePath = Utils.ExpandUser(m3uFilePath); diff --git a/slsk-batchdl/Data.cs b/slsk-batchdl/Data.cs index fe6d716..298283c 100644 --- a/slsk-batchdl/Data.cs +++ b/slsk-batchdl/Data.cs @@ -51,7 +51,10 @@ namespace Data public string ToKey() { - return $"{Artist};{Album};{Title};{Length};{(int)Type}"; + if (Type == TrackType.Album) + return $"{Artist};{Album};{(int)Type}"; + else + return $"{Artist};{Album};{Title};{Length};{(int)Type}"; } public override string ToString() @@ -103,7 +106,7 @@ namespace Data public bool needSkipExistingAfterSearch = false; public bool gotoNextAfterSearch = false; public bool placeInSubdir = false; - public string? subdirOverride; + public bool useRemoteDirname = false; public TrackListEntry() { @@ -138,12 +141,13 @@ namespace Data } public TrackListEntry(List> list, Track source, bool needSearch, bool placeInSubdir, - bool canBeSkipped, bool needSkipExistingAfterSearch, bool gotoNextAfterSearch) + bool useRemoteDirname, bool canBeSkipped, bool needSkipExistingAfterSearch, bool gotoNextAfterSearch) { this.list = list; this.source = source; this.needSourceSearch = needSearch; this.placeInSubdir = placeInSubdir; + this.useRemoteDirname = useRemoteDirname; this.sourceCanBeSkipped = canBeSkipped; this.needSkipExistingAfterSearch = needSkipExistingAfterSearch; this.gotoNextAfterSearch = gotoNextAfterSearch; @@ -183,10 +187,9 @@ namespace Data res.AddTrackToLast(enumerator.Current); } - if (hasNext && enumerator.Current.Type != TrackType.Normal) - res.AddEntry(new TrackListEntry(track)); - else if (!hasNext) - break; + if (hasNext) + res.AddEntry(new TrackListEntry(enumerator.Current)); + else break; } } @@ -280,11 +283,26 @@ namespace Data lists = newLists; } + public void SetListEntryOptions() + { + // place downloads in subdirs if there is more than one special (album/aggregate) download + bool placeInSubdirs = Flattened(true, false, true).Skip(1).Any(); + + if (placeInSubdirs) + { + foreach(var tle in lists) + { + if (tle.source.Type != TrackType.Normal) + tle.placeInSubdir = true; + } + } + } + public IEnumerable Flattened(bool addSources, bool addSpecialSourceTracks, bool sourcesOnly = false) { foreach (var tle in lists) { - if ((addSources || sourcesOnly) && tle.source != null) + if ((addSources || sourcesOnly) && tle.source != null && tle.source.Type != TrackType.Normal) yield return tle.source; if (!sourcesOnly && tle.list.Count > 0 && (tle.source.Type == TrackType.Normal || addSpecialSourceTracks)) { diff --git a/slsk-batchdl/Extractors/Bandcamp.cs b/slsk-batchdl/Extractors/Bandcamp.cs index 675401d..a24d914 100644 --- a/slsk-batchdl/Extractors/Bandcamp.cs +++ b/slsk-batchdl/Extractors/Bandcamp.cs @@ -25,6 +25,7 @@ namespace Extractors if (isArtist) { + Console.WriteLine("Retrieving bandcamp artist discography.."); string artistUrl = Config.input.TrimEnd('/'); if (!artistUrl.EndsWith("/music")) @@ -60,13 +61,13 @@ namespace Extractors { source = t, placeInSubdir = true, - subdirOverride = t.ToString(true) }; trackLists.AddEntry(tle); } } else { + Console.WriteLine("Retrieving bandcamp item.."); var web = new HtmlWeb(); var doc = await web.LoadFromWebAsync(Config.input); diff --git a/slsk-batchdl/Extractors/Csv.cs b/slsk-batchdl/Extractors/Csv.cs index 634bd31..65061c7 100644 --- a/slsk-batchdl/Extractors/Csv.cs +++ b/slsk-batchdl/Extractors/Csv.cs @@ -17,25 +17,22 @@ namespace Extractors public async Task GetTracks(int maxTracks, int offset, bool reverse) { - int max = reverse ? int.MaxValue : maxTracks; - int off = reverse ? 0 : offset; - if (!File.Exists(Config.input)) throw new FileNotFoundException("CSV file not found"); - var tracks = await ParseCsvIntoTrackInfo(Config.input, Config.artistCol, Config.trackCol, Config.lengthCol, Config.albumCol, Config.descCol, Config.ytIdCol, Config.trackCountCol, Config.timeUnit, Config.ytParse); + var tracks = await ParseCsvIntoTrackInfo(Config.input, Config.artistCol, Config.trackCol, Config.lengthCol, + Config.albumCol, Config.descCol, Config.ytIdCol, Config.trackCountCol, Config.timeUnit, Config.ytParse); if (reverse) tracks.Reverse(); - var trackLists = TrackLists.FromFlattened(tracks.Skip(off).Take(max)); + var trackLists = TrackLists.FromFlattened(tracks.Skip(offset).Take(maxTracks)); foreach (var tle in trackLists.lists) { if (tle.source.Type != TrackType.Normal) { tle.placeInSubdir = true; - tle.subdirOverride = tle.source.ToString(true); } } diff --git a/slsk-batchdl/Extractors/Spotify.cs b/slsk-batchdl/Extractors/Spotify.cs index 2958ca0..702cd19 100644 --- a/slsk-batchdl/Extractors/Spotify.cs +++ b/slsk-batchdl/Extractors/Spotify.cs @@ -110,6 +110,8 @@ namespace Extractors Config.defaultFolderName = playlistName.ReplaceInvalidChars(Config.invalidReplaceStr); + trackLists.AddEntry(tle); + if (reverse) { trackLists.Reverse(); diff --git a/slsk-batchdl/Extractors/String.cs b/slsk-batchdl/Extractors/String.cs index 2bae45e..75c9e75 100644 --- a/slsk-batchdl/Extractors/String.cs +++ b/slsk-batchdl/Extractors/String.cs @@ -16,7 +16,7 @@ namespace Extractors var trackLists = new TrackLists(); var music = ParseTrackArg(Config.input, Config.album); - if (music.Title.Length == 0 && music.Album.Length > 0) + if (Config.album || (music.Title.Length == 0 && music.Album.Length > 0)) { music.Type = TrackType.Album; trackLists.AddEntry(new TrackListEntry(music)); @@ -35,7 +35,7 @@ namespace Extractors return trackLists; } - public Track ParseTrackArg(string input, bool isAlbum) + static public Track ParseTrackArg(string input, bool isAlbum) { input = input.Trim(); var track = new Track(); diff --git a/slsk-batchdl/Help.cs b/slsk-batchdl/Help.cs index c7847c7..ea399ef 100644 --- a/slsk-batchdl/Help.cs +++ b/slsk-batchdl/Help.cs @@ -283,6 +283,14 @@ public static class Help const string searchHelp = @" Searching + Search Query + The search query is determined as follows: + + - For album downloads: If the album field is non-empty, search for 'Artist Album' + Otherwise, search for 'Artist Title' + - For all other download types: If the title field is non-empty, search for 'Artist Title' + Otherwise, search for 'Artist Album' + Soulseek's rate limits The server will ban you for 30 minutes if too many searches are performed within a short timespan. The program has a search limiter which can be adjusted with --searches-per-time diff --git a/slsk-batchdl/M3uEditor.cs b/slsk-batchdl/M3uEditor.cs index 8e2e4ed..72ee255 100644 --- a/slsk-batchdl/M3uEditor.cs +++ b/slsk-batchdl/M3uEditor.cs @@ -289,6 +289,7 @@ public class M3uEditor public Track? PreviousRunResult(Track track) { + var key = track.ToKey(); previousRunData.TryGetValue(track.ToKey(), out var t); return t; } diff --git a/slsk-batchdl/Program.cs b/slsk-batchdl/Program.cs index 1702f8e..33778ae 100644 --- a/slsk-batchdl/Program.cs +++ b/slsk-batchdl/Program.cs @@ -68,10 +68,12 @@ static partial class Program WriteLine("Got tracks", debugOnly: true); - Config.PostProcessArgs(); - trackLists.UpgradeListTypes(Config.aggregate, Config.album); + trackLists.SetListEntryOptions(); + + Config.PostProcessArgs(); + m3uEditor = new M3uEditor(Config.m3uFilePath, trackLists, Config.m3uOption, Config.offset); InitExistingChecker(); @@ -262,7 +264,7 @@ static partial class Program foreach (var item in res) { var newSource = new Track(tle.source) { Type = TrackType.Album }; - trackLists.AddEntry(new TrackListEntry(item, newSource, false, true, true, false, false)); + trackLists.AddEntry(new TrackListEntry(item, newSource, false, true, true, true, false, false)); } } @@ -680,9 +682,9 @@ static partial class Program soulseekFolderPathPrefix = GetCommonPathPrefix(tracks); - if (tle.placeInSubdir && Config.nameFormat.Length == 0 && (!downloadingImages || idx == 0)) + if (tle.placeInSubdir && Config.nameFormat.Length == 0 && (idx == 0 || !downloadingImages)) { - string name = tle.subdirOverride ?? Utils.GetBaseNameSlsk(soulseekFolderPathPrefix); + string name = tle.useRemoteDirname ? Utils.GetBaseNameSlsk(soulseekFolderPathPrefix) : tle.source.ToString(true); Config.outputFolder = Path.Join(savedOutputFolder, name); } diff --git a/slsk-batchdl/SearchAndDownload.cs b/slsk-batchdl/SearchAndDownload.cs index d115c18..d46072b 100644 --- a/slsk-batchdl/SearchAndDownload.cs +++ b/slsk-batchdl/SearchAndDownload.cs @@ -919,11 +919,22 @@ static partial class Program static string GetSearchString(Track track) { - if (track.Title.Length > 0) - return (track.Artist + " " + track.Title).Trim(); - else if (track.Album.Length > 0) - return (track.Artist + " " + track.Album).Trim(); - return track.Artist.Trim(); + if (track.Type == TrackType.Album) + { + if (track.Album.Length > 0) + return (track.Artist + " " + track.Album).Trim(); + if (track.Title.Length > 0) + return (track.Artist + " " + track.Title).Trim(); + return track.Artist.Trim(); + } + else + { + if (track.Title.Length > 0) + return (track.Artist + " " + track.Title).Trim(); + else if (track.Album.Length > 0) + return (track.Artist + " " + track.Album).Trim(); + return track.Artist.Trim(); + } } diff --git a/slsk-batchdl/Test.cs b/slsk-batchdl/Test.cs index 150450d..f6054be 100644 --- a/slsk-batchdl/Test.cs +++ b/slsk-batchdl/Test.cs @@ -1,14 +1,8 @@ using Data; using Enums; using ExistingCheckers; -using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; using static Test.Helpers; @@ -238,17 +232,17 @@ namespace Test var albums = new List() { - new Track() { Album="Some Title" }, - new Track() { Album="Some, Title" }, - new Track() { Title = "some title", Artist = "Some artist" }, - new Track() { Album = "Title", Artist = "Artist", Length = 42 }, - new Track() { Title="Some, Title", Artist = "Some, Artist", Album = "Some, Album", Length = 42 }, - new Track() { Artist = "Some, Artist = a", Album = "Some, Album", Length = 42 }, + new Track() { Album="Some Title", Type = TrackType.Album }, + new Track() { Album="Some, Title", Type = TrackType.Album }, + new Track() { Title = "some title", Artist = "Some artist", Type = TrackType.Album }, + new Track() { Album = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album }, + new Track() { Title="Some, Title", Artist = "Some, Artist", Album = "Some, Album", Length = 42, Type = TrackType.Album }, + new Track() { Artist = "Some, Artist = a", Album = "Some, Album", Length = 42, Type = TrackType.Album }, - new Track() { Album = "Foo Bar" }, - new Track() { Album = "Bar", Artist = "Foo" }, - new Track() { Album = "Title", Artist = "Artist", Length = 42 }, - new Track() { Title = "Title", Artist = "Artist", Length = 42 }, + new Track() { Album = "Foo Bar", Type = TrackType.Album }, + new Track() { Album = "Bar", Artist = "Foo", Type = TrackType.Album }, + new Track() { Album = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album }, + new Track() { Title = "Title", Artist = "Artist", Length = 42, Type = TrackType.Album }, }; var extractor = new Extractors.StringExtractor();