1
0
Fork 0
mirror of https://github.com/fiso64/slsk-batchdl.git synced 2024-12-31 18:52:41 +00:00
This commit is contained in:
fiso64 2024-08-30 18:35:44 +02:00
parent c5d11b995d
commit 8cf3016e6b
7 changed files with 79 additions and 39 deletions

View file

@ -23,6 +23,7 @@ See the [examples](#examples-1).
- [Configuration](#configuration) - [Configuration](#configuration)
- [Examples](#examples-1) - [Examples](#examples-1)
- [Notes](#notes) - [Notes](#notes)
- [Docker](#docker)
## Options ## Options
@ -48,7 +49,7 @@ Usage: sldl <input> [OPTIONS]
-o, --offset <offset> Skip a specified number of tracks -o, --offset <offset> Skip a specified number of tracks
-r, --reverse Download tracks in reverse order -r, --reverse Download tracks in reverse order
-c, --config <path> Set config file location. Set to 'none' to ignore config -c, --config <path> Set config file location. Set to 'none' to ignore config
--profile <name> Configuration profile to use. See --help "config". --profile <names> Configuration profile(s) to use. See --help "config".
--concurrent-downloads <num> Max concurrent downloads (default: 2) --concurrent-downloads <num> Max concurrent downloads (default: 2)
--m3u <option> Create an m3u8 playlist file in the output directory --m3u <option> Create an m3u8 playlist file in the output directory
'none' (default for single inputs): Do not create 'none' (default for single inputs): Do not create
@ -78,14 +79,15 @@ Usage: sldl <input> [OPTIONS]
--debug Print extra debug info --debug Print extra debug info
--listen-port <port> Port for incoming connections (default: 49998) --listen-port <port> Port for incoming connections (default: 49998)
--on-complete <command> Run a specified command whenever a file is downloaded. --on-complete <command> Run a command whenever a file is downloaded.
Available placeholders: {path} (local save path), {title}, Available placeholders: {path} (local save path), {title},
{artist},{album},{uri},{length},{failure-reason},{state}. {artist},{album},{uri},{length},{failure-reason},{state}.
Prepend a state number to only download in specific cases: Prepend a state number to only download in specific cases:
1:, 2:, 3:, 4: for the Downloaded, Failed, Exists, and 1:, 2:, 3:, 4: for the Downloaded, Failed, Exists, and
NotFoundLastTime states respectively. NotFoundLastTime states respectively.
E.g: '1:<cmd>' will only run the command if the file is E.g: '1:<cmd>' will only run the command if the file is
downloaded successfully. downloaded successfully. Prepend 's:' to use the system
shell to execute the command.
``` ```
``` ```
Searching Searching
@ -420,7 +422,7 @@ tag1 is null, use tag2. String literals enclosed in parentheses are ignored in t
- "{artist( - )title|filename}" - "{artist( - )title|filename}"
If artist and title are not null, name it 'Artist - Title', otherwise use the original If artist and title are not null, name it 'Artist - Title', otherwise use the original
filename. filename.
- "{artist(/)album(/)track(. )title|(missing-tags/)filename}" - "{albumartist(/)album(/)track(. )title|(missing-tags/)filename}"
Sort files into artist/album folders if all tags are present, otherwise put them in Sort files into artist/album folders if all tags are present, otherwise put them in
the 'missing-tags' folder. the 'missing-tags' folder.

8
changelog.md Normal file
View file

@ -0,0 +1,8 @@
- configuration profiles, auto-profiles
- album aggregate download mode
- automatically browse user shares during album downloads to guarantee that all files from the parent folder are downloaded
- skip-existing is now much faster. Default mode is now m3u as it is more reliable. Changed how m3u skip-existing works (not backward compatible). Added `m3u-cond` skip mode. m3u modes can now skip album downloads.
- can now download entire bandcamp artist discographies
- added `--on-complete` to run a command whenever a file is downloaded
- now also looks in the user config directories for `sldl.conf`
- many bug fixes

View file

@ -173,7 +173,7 @@ static class Config
ApplyAutoProfiles(); ApplyAutoProfiles();
} }
ApplyProfile(profile); ApplyProfiles(profile);
ProcessArgs(args); ProcessArgs(args);
@ -326,14 +326,16 @@ static class Config
{ {
//appliedProfiles.Clear(); //appliedProfiles.Clear();
appliedProfiles.UnionWith(newProfiles); appliedProfiles.UnionWith(newProfiles);
ApplyProfile(profile); ApplyProfiles(profile);
ProcessArgs(arguments); ProcessArgs(arguments);
PostProcessArgs(); PostProcessArgs();
} }
} }
static void ApplyProfile(string name) static void ApplyProfiles(string names)
{
foreach (var name in names.Split(','))
{ {
if (name.Length > 0 && name != "default") if (name.Length > 0 && name != "default")
{ {
@ -346,6 +348,7 @@ static class Config
Console.WriteLine($"Error: No profile '{name}' found in config"); Console.WriteLine($"Error: No profile '{name}' found in config");
} }
} }
}
static HashSet<string> ApplyAutoProfiles(TrackListEntry? tle = null) static HashSet<string> ApplyAutoProfiles(TrackListEntry? tle = null)
@ -359,7 +362,7 @@ static class Config
{ {
if (key == "default" || appliedProfiles.Contains(key)) if (key == "default" || appliedProfiles.Contains(key))
continue; continue;
if (key != profile && val.cond != null && ProfileConditionSatisfied(val.cond, tle)) if (val.cond != null && ProfileConditionSatisfied(val.cond, tle))
{ {
Console.WriteLine($"Applying auto profile: {key}"); Console.WriteLine($"Applying auto profile: {key}");
ProcessArgs(val.args); ProcessArgs(val.args);

View file

@ -40,14 +40,14 @@ namespace Extractors
if (Config.input == "spotify-likes") if (Config.input == "spotify-likes")
{ {
Console.WriteLine("Loading Spotify likes"); Console.WriteLine("Loading Spotify likes..");
var tracks = await spotifyClient.GetLikes(max, off); var tracks = await spotifyClient.GetLikes(max, off);
playlistName = "Spotify Likes"; playlistName = "Spotify Likes";
tle.list.Add(tracks); tle.list.Add(tracks);
} }
else if (Config.input.Contains("/album/")) else if (Config.input.Contains("/album/"))
{ {
Console.WriteLine("Loading Spotify album"); Console.WriteLine("Loading Spotify album..");
(var source, var tracks) = await spotifyClient.GetAlbum(Config.input); (var source, var tracks) = await spotifyClient.GetAlbum(Config.input);
playlistName = source.ToString(noInfo: true); playlistName = source.ToString(noInfo: true);
tle.source = source; tle.source = source;
@ -60,7 +60,7 @@ namespace Extractors
} }
else if (Config.input.Contains("/artist/")) else if (Config.input.Contains("/artist/"))
{ {
Console.WriteLine("Loading spotify artist"); Console.WriteLine("Loading spotify artist..");
Console.WriteLine("Error: Spotify artist download currently not supported."); Console.WriteLine("Error: Spotify artist download currently not supported.");
Environment.Exit(0); Environment.Exit(0);
} }
@ -203,12 +203,12 @@ namespace Extractors
{ {
if (_clientToken.Length != 0) if (_clientToken.Length != 0)
{ {
Console.WriteLine("Testing Spotify access with existing token..."); //Console.WriteLine("Testing Spotify access with existing token...");
var client = new SpotifyClient(_clientToken); var client = new SpotifyClient(_clientToken);
try try
{ {
var me = await client.UserProfile.Current(); var me = await client.UserProfile.Current();
Console.WriteLine("Spotify access is good!"); //Console.WriteLine("Spotify access is good!");
_client = client; _client = client;
return true; return true;
} }
@ -229,7 +229,7 @@ namespace Extractors
{ {
var oauthClient = new OAuthClient(); var oauthClient = new OAuthClient();
var refreshResponse = await oauthClient.RequestToken(refreshRequest); var refreshResponse = await oauthClient.RequestToken(refreshRequest);
Console.WriteLine($"We got a new refreshed access token from server: {refreshResponse.AccessToken}"); //Console.WriteLine($"We got a new refreshed access token from server: {refreshResponse.AccessToken}");
_clientToken = refreshResponse.AccessToken; _clientToken = refreshResponse.AccessToken;
_client = new SpotifyClient(_clientToken); _client = new SpotifyClient(_clientToken);
return true; return true;

View file

@ -27,7 +27,7 @@ public static class Help
-o, --offset <offset> Skip a specified number of tracks -o, --offset <offset> Skip a specified number of tracks
-r, --reverse Download tracks in reverse order -r, --reverse Download tracks in reverse order
-c, --config <path> Set config file location. Set to 'none' to ignore config -c, --config <path> Set config file location. Set to 'none' to ignore config
--profile <name> Configuration profile to use. See --help ""config"". --profile <names> Configuration profile(s) to use. See --help ""config"".
--concurrent-downloads <num> Max concurrent downloads (default: 2) --concurrent-downloads <num> Max concurrent downloads (default: 2)
--m3u <option> Create an m3u8 playlist file in the output directory --m3u <option> Create an m3u8 playlist file in the output directory
'none' (default for single inputs): Do not create 'none' (default for single inputs): Do not create
@ -57,14 +57,15 @@ public static class Help
--debug Print extra debug info --debug Print extra debug info
--listen-port <port> Port for incoming connections (default: 49998) --listen-port <port> Port for incoming connections (default: 49998)
--on-complete <command> Run a specified command whenever a file is downloaded. --on-complete <command> Run a command whenever a file is downloaded.
Available placeholders: {path} (local save path), {title}, Available placeholders: {path} (local save path), {title},
{artist},{album},{uri},{length},{failure-reason},{state}. {artist},{album},{uri},{length},{failure-reason},{state}.
Prepend a state number to only download in specific cases: Prepend a state number to only download in specific cases:
1:, 2:, 3:, 4: for the Downloaded, Failed, Exists, and 1:, 2:, 3:, 4: for the Downloaded, Failed, Exists, and
NotFoundLastTime states respectively. NotFoundLastTime states respectively.
E.g: '1:<cmd>' will only run the command if the file is E.g: '1:<cmd>' will only run the command if the file is
downloaded successfully. downloaded successfully. Prepend 's:' to use the system
shell to execute the command.
Searching Searching
--fast-search Begin downloading as soon as a file satisfying the preferred --fast-search Begin downloading as soon as a file satisfying the preferred
@ -393,7 +394,7 @@ public static class Help
""{artist( - )title|filename}"" ""{artist( - )title|filename}""
If artist and title are not null, name it 'Artist - Title', otherwise use the original If artist and title are not null, name it 'Artist - Title', otherwise use the original
filename. filename.
""{artist(/)album(/)track(. )title|(missing-tags/)filename}"" ""{albumartist(/)album(/)track(. )title|(missing-tags/)filename}""
Sort files into artist/album folders if all tags are present, otherwise put them in Sort files into artist/album folders if all tags are present, otherwise put them in
the 'missing-tags' folder. the 'missing-tags' folder.

View file

@ -258,7 +258,6 @@ public static class Printing
WriteLine($"User : {userInfo}\nFolder: {parents}\nProps : {props}", ConsoleColor.White); WriteLine($"User : {userInfo}\nFolder: {parents}\nProps : {props}", ConsoleColor.White);
PrintTracks(albumTracks.ToList(), pathsOnly: true, showAncestors: false, showUser: false); PrintTracks(albumTracks.ToList(), pathsOnly: true, showAncestors: false, showUser: false);
Console.WriteLine();
} }

View file

@ -432,7 +432,7 @@ static partial class Program
bool succeeded = false; bool succeeded = false;
string? soulseekDir = null; string? soulseekDir = null;
while (tle.list.Count > 0) while (tle.list.Count > 0 && !Config.albumArtOnly)
{ {
int index = 0; int index = 0;
bool wasInteractive = Config.interactiveMode; bool wasInteractive = Config.interactiveMode;
@ -531,7 +531,7 @@ static partial class Program
int[]? sortedLengths = null; int[]? sortedLengths = null;
if (chosenAlbum != null && chosenAlbum.Count(t => !t.IsNotAudio) > 0) if (chosenAlbum != null && chosenAlbum.Any(t => !t.IsNotAudio))
sortedLengths = chosenAlbum.Where(t => !t.IsNotAudio).Select(t => t.Length).OrderBy(x => x).ToArray(); sortedLengths = chosenAlbum.Where(t => !t.IsNotAudio).Select(t => t.Length).OrderBy(x => x).ToArray();
var albumArts = downloads var albumArts = downloads
@ -556,7 +556,9 @@ static partial class Program
{ {
mSize = chosenAlbum mSize = chosenAlbum
.Where(t => t.State == TrackState.Downloaded && Utils.IsImageFile(t.DownloadPath)) .Where(t => t.State == TrackState.Downloaded && Utils.IsImageFile(t.DownloadPath))
.Max(t => t.FirstDownload.Size); .Select(t => t.FirstDownload.Size)
.DefaultIfEmpty(0)
.Max();
} }
} }
else if (option == AlbumArtOption.Most) else if (option == AlbumArtOption.Most)
@ -772,6 +774,7 @@ static partial class Program
} }
PrintAlbum(tracks); PrintAlbum(tracks);
Console.WriteLine();
string userInput = interactiveModeLoop().Trim(); string userInput = interactiveModeLoop().Trim();
switch (userInput) switch (userInput)
@ -913,15 +916,30 @@ static partial class Program
{ {
if (onComplete.Length == 0) if (onComplete.Length == 0)
return; return;
else if (onComplete.Length > 2 && onComplete[0].IsDigit() && onComplete[1] == ':')
bool useShellExecute = false;
int count = 0;
while (onComplete.Length > 2 && count++ < 2)
{
if (onComplete[0] == 's' && onComplete[1] == ':')
{
useShellExecute = true;
}
else if (onComplete[0].IsDigit() && onComplete[1] == ':')
{ {
if ((int)track.State != int.Parse(onComplete[0].ToString())) if ((int)track.State != int.Parse(onComplete[0].ToString()))
return; return;
}
else
{
break;
}
onComplete = onComplete[2..]; onComplete = onComplete[2..];
} }
Process process = new Process(); var process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo(); var startInfo = new ProcessStartInfo();
onComplete = onComplete.Replace("{title}", track.Title) onComplete = onComplete.Replace("{title}", track.Title)
.Replace("{artist}", track.Artist) .Replace("{artist}", track.Artist)
@ -957,16 +975,25 @@ static partial class Program
startInfo.Arguments = parts.Length > 1 ? parts[1] : ""; startInfo.Arguments = parts.Length > 1 ? parts[1] : "";
} }
if (!useShellExecute)
{
startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true; startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false; }
startInfo.UseShellExecute = useShellExecute;
process.StartInfo = startInfo; process.StartInfo = startInfo;
WriteLine($"on-complete: FileName={startInfo.FileName}, Arguments={startInfo.Arguments}", debugOnly: true); WriteLine($"on-complete: FileName={startInfo.FileName}, Arguments={startInfo.Arguments}", debugOnly: true);
process.Start(); process.Start();
if (!useShellExecute)
{
process.BeginOutputReadLine(); process.BeginOutputReadLine();
process.BeginErrorReadLine(); process.BeginErrorReadLine();
}
process.WaitForExit(); process.WaitForExit();
} }