1
0
Fork 0
mirror of https://github.com/fiso64/slsk-batchdl.git synced 2024-12-22 22:42:41 +00:00
This commit is contained in:
fiso64 2024-05-07 14:40:46 +02:00
parent 27ec0c30b3
commit 6c70442449
3 changed files with 52 additions and 29 deletions

View file

@ -242,4 +242,4 @@ Create a file named `slsk-batchdl.conf` in the same directory as the executable
## Notes ## Notes
- For macOS builds you can use publish.sh to build the app. Download dotnet from https://dotnet.microsoft.com/en-us/download/dotnet/6.0, then run `chmod +x publish.sh && sh publish.sh` - For macOS builds you can use publish.sh to build the app. Download dotnet from https://dotnet.microsoft.com/en-us/download/dotnet/6.0, then run `chmod +x publish.sh && sh publish.sh`
- `--display single` and especially `double` can cause the printed lines to be duplicated or overwritten on some configurations. Use `simple` if that's an issue. - `--display single` and especially `double` can cause the printed lines to be duplicated or overwritten on some configurations. Use `simple` if that's an issue.
- 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` and `--searches-renew-time` (when limit is reached, the status of the downloads will be "Waiting"). By default it is configured to allow up to 34 searches every 220 seconds. These values were determined through experimentation as unfortunately I couldn't find any information regarding soulseek's rate limits, so they may be incorrect. You can also use `--random-login` to re-login with a random username and password automatically. - 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` and `--searches-renew-time` (when limit is reached, the status of the downloads will be "Waiting"). By default it is configured to allow up to 34 searches every 220 seconds. These values were determined through experimentation as unfortunately I couldn't find any information regarding soulseek's rate limits, so they may be incorrect.

View file

@ -559,6 +559,8 @@ static class Program
case "--max-stale-time": case "--max-stale-time":
downloadMaxStaleTime = int.Parse(args[++i]); downloadMaxStaleTime = int.Parse(args[++i]);
break; break;
case "--processes":
case "--concurrent-processes":
case "--concurrent-downloads": case "--concurrent-downloads":
maxConcurrentProcesses = int.Parse(args[++i]); maxConcurrentProcesses = int.Parse(args[++i]);
break; break;
@ -1167,7 +1169,7 @@ static class Program
if (tracks.Count > 0) if (tracks.Count > 0)
{ {
bool showAll = type != TrackLists.ListType.Normal || debugPrintTracks; bool showAll = type != TrackLists.ListType.Normal || debugPrintTracks;
PrintTracks(tracks, showAll ? int.MaxValue : 10, debugPrintTracksFull); PrintTracks(tracks, showAll ? int.MaxValue : 10, debugPrintTracksFull, infoFirst: debugPrintTracks);
if (debugPrintTracksFull && (existing.Count > 0 || notFound.Count > 0)) if (debugPrintTracksFull && (existing.Count > 0 || notFound.Count > 0))
Console.WriteLine("\n-----------------------------------------------\n"); Console.WriteLine("\n-----------------------------------------------\n");
} }
@ -1177,12 +1179,12 @@ static class Program
if (existing.Count > 0) if (existing.Count > 0)
{ {
Console.WriteLine($"\nThe following tracks already exist:"); Console.WriteLine($"\nThe following tracks already exist:");
PrintTracks(existing, fullInfo: debugPrintTracksFull); PrintTracks(existing, fullInfo: debugPrintTracksFull, infoFirst: debugPrintTracks);
} }
if (notFound.Count > 0) if (notFound.Count > 0)
{ {
Console.WriteLine($"\nThe following tracks were not found during the last run:"); Console.WriteLine($"\nThe following tracks were not found during the last run:");
PrintTracks(notFound, fullInfo: debugPrintTracksFull); PrintTracks(notFound, fullInfo: debugPrintTracksFull, infoFirst: debugPrintTracks);
} }
} }
} }
@ -1203,7 +1205,7 @@ static class Program
d.ToList().ForEach(x => existing.TryAdd(x.Key, x.Value)); d.ToList().ForEach(x => existing.TryAdd(x.Key, x.Value));
} }
else if (musicDir != "" && !System.IO.Directory.Exists(musicDir)) else if (musicDir != "" && !System.IO.Directory.Exists(musicDir))
if (print) Console.WriteLine($"Path does not exist: {musicDir}"); if (print) Console.WriteLine($"Musid dir does not exist: {musicDir}");
return existing.Select(x => x.Key).ToList(); return existing.Select(x => x.Key).ToList();
} }
@ -1233,7 +1235,8 @@ static class Program
{ {
SemaphoreSlim semaphore = new SemaphoreSlim(maxConcurrentProcesses); SemaphoreSlim semaphore = new SemaphoreSlim(maxConcurrentProcesses);
var downloadTasks = tracks.Select(async (track, index) => var copy = new List<Track>(tracks);
var downloadTasks = copy.Select(async (track, index) =>
{ {
if (track.TrackState == Track.State.Exists || track.TrackState == Track.State.NotFoundLastTime) if (track.TrackState == Track.State.Exists || track.TrackState == Track.State.NotFoundLastTime)
return; return;
@ -1294,10 +1297,11 @@ static class Program
mainLoopCts = new CancellationTokenSource(); mainLoopCts = new CancellationTokenSource();
albumCommonPath = Utils.GreatestCommonPath(tracks.SelectMany(x => x.Downloads.Select(y => y.Value.Item2.Filename)), dirsep: '\\'); albumCommonPath = Utils.GreatestCommonPath(tracks.SelectMany(x => x.Downloads.Select(y => y.Value.Item2.Filename)), dirsep: '\\');
SemaphoreSlim semaphore = new SemaphoreSlim(maxConcurrentProcesses); SemaphoreSlim semaphore = new SemaphoreSlim(maxConcurrentProcesses);
var copy = new List<Track>(tracks);
try try
{ {
var downloadTasks = tracks.Select(async (track, index) => var downloadTasks = copy.Select(async (track, index) =>
{ {
if (track.TrackState == Track.State.Exists || track.TrackState == Track.State.NotFoundLastTime) if (track.TrackState == Track.State.Exists || track.TrackState == Track.State.NotFoundLastTime)
return; return;
@ -1627,7 +1631,7 @@ static class Program
{ {
foreach (var (response, file) in fileResponses) { foreach (var (response, file) in fileResponses) {
Console.WriteLine(DisplayString(track, file, response, Console.WriteLine(DisplayString(track, file, response,
(printResultsFull ? necessaryCond : null), (printResultsFull ? preferredCond : null), printResultsFull)); (printResultsFull ? necessaryCond : null), (printResultsFull ? preferredCond : null), printResultsFull, infoFirst: true));
} }
WriteLine($"Total: {fileResponses.Count()}\n", ConsoleColor.Yellow); WriteLine($"Total: {fileResponses.Count()}\n", ConsoleColor.Yellow);
return ""; return "";
@ -2200,7 +2204,9 @@ static class Program
filename = GetFileNameWithoutExtSlsk(filename).Replace(" — ", " - ").Replace("_", " ").RemoveConsecutiveWs().Trim(); filename = GetFileNameWithoutExtSlsk(filename).Replace(" — ", " - ").Replace("_", " ").RemoveConsecutiveWs().Trim();
var trackNumStart = new Regex(@"^(?:(?:[0-9][-\.])?\d{2,3}[. -]|\b\d\.\s|\b\d\s-\s)(?=.+\S)"); var trackNumStart = new Regex(@"^(?:(?:[0-9][-\.])?\d{2,3}[. -]|\b\d\.\s|\b\d\s-\s)(?=.+\S)");
var trackNumMiddle = new Regex(@"(?<=- )((\d-)?\d{2,3}|\d{2,3}\.?)\s+"); //var trackNumMiddle = new Regex(@"\s+-\s+(\d{2,3})(?: -|\.|)\s+|\s+-(\d{2,3})-\s+");
var trackNumMiddle = new Regex(@"(?<= - )((\d-)?\d{2,3}|\d{2,3}\.?)\s+");
var trackNumMiddleAlt = new Regex(@"\s+-(\d{2,3})-\s+");
if (trackNumStart.IsMatch(filename)) if (trackNumStart.IsMatch(filename))
{ {
@ -2210,9 +2216,13 @@ static class Program
} }
else else
{ {
filename = trackNumMiddle.Replace(filename, "<<tracknum>>", 1).Trim(); var reg = trackNumMiddle.IsMatch(filename) ? trackNumMiddle : (trackNumMiddleAlt.IsMatch(filename) ? trackNumMiddleAlt : null);
filename = Regex.Replace(filename, @"-\s*<<tracknum>>\s*-", "-"); if (reg != null && !reg.IsMatch(defaultTrack.ToString(noInfo: true)))
filename = filename.Replace("<<tracknum>>", ""); {
filename = reg.Replace(filename, "<<tracknum>>", 1).Trim();
filename = Regex.Replace(filename, @"-\s*<<tracknum>>\s*-", "-");
filename = filename.Replace("<<tracknum>>", "");
}
} }
string aname = t.ArtistName.Trim(); string aname = t.ArtistName.Trim();
@ -2376,7 +2386,7 @@ static class Program
{ {
if (client.State.HasFlag(SoulseekClientStates.LoggedIn)) if (client.State.HasFlag(SoulseekClientStates.LoggedIn))
{ {
foreach (var (key, val) in searches) foreach (var (key, val) in searches) // shouldn't this give "collection was modified" errors? whatever..
{ {
if (val == null) if (val == null)
searches.TryRemove(key, out _); searches.TryRemove(key, out _);
@ -3465,17 +3475,27 @@ static class Program
} }
static string DisplayString(Track t, Soulseek.File? file=null, SearchResponse? response=null, FileConditions? nec=null, static string DisplayString(Track t, Soulseek.File? file=null, SearchResponse? response=null, FileConditions? nec=null,
FileConditions? pref=null, bool fullpath=false, string customPath="") FileConditions? pref=null, bool fullpath=false, string customPath="", bool infoFirst=false)
{ {
if (file == null) if (file == null)
return t.ToString(); return t.ToString();
string sampleRate = file.SampleRate.HasValue ? $"{file.SampleRate}Hz/" : ""; string sampleRate = file.SampleRate.HasValue ? $"{file.SampleRate}Hz" : "";
string bitRate = file.BitRate.HasValue ? $"{file.BitRate}kbps/" : ""; string bitRate = file.BitRate.HasValue ? $"{file.BitRate}kbps" : "";
string fileSize = $"{file.Size / (float)(1024 * 1024):F1}MB"; string fileSize = $"{file.Size / (float)(1024 * 1024):F1}MB";
string fname = fullpath ? "\\" + file.Filename : "\\..\\" + (customPath == "" ? GetFileNameSlsk(file.Filename) : customPath); string fname = fullpath ? "\\" + file.Filename : "\\..\\" + (customPath == "" ? GetFileNameSlsk(file.Filename) : customPath);
string length = Utils.IsMusicFile(file.Filename) ? (file.Length ?? -1).ToString() + "s/" : ""; string length = Utils.IsMusicFile(file.Filename) ? (file.Length ?? -1).ToString() + "s" : "";
string displayText = $"{response?.Username ?? ""}{fname} [{length}{sampleRate}{bitRate}{fileSize}]"; string displayText;
if (!infoFirst)
{
string info = string.Join('/', new string[] { length, sampleRate+bitRate, fileSize }.Where(value => value!=""));
displayText = $"{response?.Username ?? ""}{fname} [{info}]";
}
else
{
string info = string.Join('/', new string[] { length.PadRight(4), (sampleRate+bitRate).PadRight(8), fileSize.PadLeft(6) });
displayText = $"[{info}] {response?.Username ?? ""}{fname}";
}
string necStr = nec != null ? $"nec:{nec.GetNotSatisfiedName(file, t, response)}, " : ""; string necStr = nec != null ? $"nec:{nec.GetNotSatisfiedName(file, t, response)}, " : "";
string prefStr = pref != null ? $"prf:{pref.GetNotSatisfiedName(file, t, response)}" : ""; string prefStr = pref != null ? $"prf:{pref.GetNotSatisfiedName(file, t, response)}" : "";
@ -3486,7 +3506,7 @@ static class Program
return displayText + cond; return displayText + cond;
} }
static void PrintTracks(List<Track> tracks, int number = int.MaxValue, bool fullInfo=false, bool pathsOnly=false, bool showAncestors=false) static void PrintTracks(List<Track> tracks, int number = int.MaxValue, bool fullInfo=false, bool pathsOnly=false, bool showAncestors=false, bool infoFirst=false)
{ {
number = Math.Min(tracks.Count, number); number = Math.Min(tracks.Count, number);
@ -3502,9 +3522,9 @@ static class Program
foreach (var x in tracks[i].Downloads) foreach (var x in tracks[i].Downloads)
{ {
if (ancestor == "") if (ancestor == "")
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1)); Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, infoFirst: infoFirst));
else else
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, customPath: x.Value.Item2.Filename.Replace(ancestor, ""))); Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, customPath: x.Value.Item2.Filename.Replace(ancestor, ""), infoFirst: infoFirst));
} }
} }
} }
@ -3535,9 +3555,9 @@ static class Program
Console.WriteLine($" Shares: {tracks[i].Downloads.Count}"); Console.WriteLine($" Shares: {tracks[i].Downloads.Count}");
foreach (var x in tracks[i].Downloads) { foreach (var x in tracks[i].Downloads) {
if (ancestor == "") if (ancestor == "")
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1)); Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, infoFirst: infoFirst));
else else
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, customPath: x.Value.Item2.Filename.Replace(ancestor, ""))); Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, customPath: x.Value.Item2.Filename.Replace(ancestor, ""), infoFirst: infoFirst));
} }
if (tracks[i].Downloads?.Count > 0) Console.WriteLine(); if (tracks[i].Downloads?.Count > 0) Console.WriteLine();
} }
@ -3548,9 +3568,9 @@ static class Program
Console.WriteLine($" Shares: {tracks[i].Downloads.Count}"); Console.WriteLine($" Shares: {tracks[i].Downloads.Count}");
foreach (var x in tracks[i].Downloads) { foreach (var x in tracks[i].Downloads) {
if (ancestor == "") if (ancestor == "")
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1)); Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, infoFirst: infoFirst));
else else
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, customPath: x.Value.Item2.Filename.Replace(ancestor, ""))); Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, customPath: x.Value.Item2.Filename.Replace(ancestor, ""), infoFirst: infoFirst));
} }
Console.WriteLine(); Console.WriteLine();
} }
@ -3941,7 +3961,7 @@ public class M3UEditor
var value = lastBracketIndex != line.Length ? line.Substring(lastBracketIndex + 1).Trim().TrimEnd(']') : ""; var value = lastBracketIndex != line.Length ? line.Substring(lastBracketIndex + 1).Trim().TrimEnd(']') : "";
return new { Key = key, Value = value }; return new { Key = key, Value = value };
}) })
.ToDictionary(pair => pair.Key, pair => pair.Value); .ToSafeDictionary(pair => pair.Key, pair => pair.Value);
} }
public void Update() public void Update()

View file

@ -29,9 +29,12 @@ public static class Utils
public static void Move(string sourceFilePath, string destinationFilePath) public static void Move(string sourceFilePath, string destinationFilePath)
{ {
if (File.Exists(destinationFilePath)) if (File.Exists(sourceFilePath))
File.Delete(destinationFilePath); {
File.Move(sourceFilePath, destinationFilePath); if (File.Exists(destinationFilePath))
File.Delete(destinationFilePath);
File.Move(sourceFilePath, destinationFilePath);
}
} }
public static bool EqualsAny(this string input, string[] values, StringComparison comparison = StringComparison.Ordinal) public static bool EqualsAny(this string input, string[] values, StringComparison comparison = StringComparison.Ordinal)