mirror of
https://github.com/fiso64/slsk-batchdl.git
synced 2024-12-31 18:52:41 +00:00
commit
This commit is contained in:
parent
c5d11b995d
commit
8cf3016e6b
7 changed files with 79 additions and 39 deletions
10
README.md
10
README.md
|
@ -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
8
changelog.md
Normal 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
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue