1
0
Fork 0
mirror of https://github.com/fiso64/slsk-batchdl.git synced 2024-12-22 14:32:40 +00:00

improve result ordering code

This commit is contained in:
fiso64 2024-08-28 13:49:39 +02:00
parent d1228110a2
commit 4e3ed2ec46
7 changed files with 131 additions and 197 deletions

View file

@ -105,8 +105,8 @@ Usage: sldl <input> [OPTIONS]
on a per-track basis, so it is best kept off in that case.
-d, --desperate Tries harder to find the desired track by searching for the
artist/album/title only, then filtering. (slower search)
--fails-to-downrank <num> Number of fails to downrank a user's uploads (default: 1)
--fails-to-ignore <num> Number of fails to ban/ignore a user's uploads (default: 2)
--fails-to-downrank <num> Number of fails to downrank a user's shares (default: 1)
--fails-to-ignore <num> Number of fails to ban/ignore a user's shares (default: 2)
--yt-dlp Use yt-dlp to download tracks that weren't found on
Soulseek. yt-dlp must be available from the command line.
@ -367,8 +367,12 @@ accept-no-length = false
```
sldl will therefore prefer mp3 files with bitrate between 200 and 2500 kbps, and whose length
differs from the supplied length by no more than 3 seconds. It will also prefer files whose
paths contain the supplied artist and album (ignoring case, and bounded by boundary characters)
and which have a non-null length. Changing the last three preferred conditions is not recommended.
paths contain the supplied title and album (ignoring case, and bounded by boundary characters)
and which have non-null length. Changing the last three preferred conditions is not recommended.
Note that files satisfying a subset of the preferred conditions will still be preferred over files
that don't satisfy any condition, but some conditions have precedence over others. For instance,
a file that only satisfies strict-title (if enabled) will always be preferred over a file that
only satisfies the format condition. Run with --print "results-full" to reveal the sorting logic.
### Important note
Some info may be unavailable depending on the client used by the peer. For example, the standard

View file

@ -537,9 +537,6 @@ static class Config
case "bannedusers":
cond.BannedUsers = value.Split(',', tr);
break;
case "dangerwords":
cond.DangerWords = value.Split(',', tr);
break;
case "stricttitle":
cond.StrictTitle = bool.Parse(value);
break;
@ -967,10 +964,6 @@ static class Config
case "--pref-max-bitdepth":
preferredCond.MaxBitDepth = int.Parse(args[++i]);
break;
case "--pdw":
case "--pref-danger-words":
preferredCond.DangerWords = args[++i].Split(',');
break;
case "--pst":
case "--pstt":
case "--pref-strict-title":
@ -1022,10 +1015,6 @@ static class Config
case "--max-bitdepth":
necessaryCond.MaxBitDepth = int.Parse(args[++i]);
break;
case "--dw":
case "--danger-words":
necessaryCond.DangerWords = args[++i].Split(',');
break;
case "--stt":
case "--strict-title":
setFlag(ref necessaryCond.StrictTitle, ref i);

View file

@ -1,6 +1,6 @@
using Enums;
using Soulseek;
using SlDictionary = System.Collections.Concurrent.ConcurrentDictionary<string, (Soulseek.SearchResponse, Soulseek.File)>;
namespace Data
{
@ -21,11 +21,11 @@ namespace Data
public TrackType Type = TrackType.Normal;
public FailureReason FailureReason = FailureReason.None;
public TrackState State = TrackState.Initial;
public SlDictionary? Downloads = null;
public List<(SearchResponse, Soulseek.File)>? Downloads = null;
public bool OutputsDirectory => Type != TrackType.Normal;
public Soulseek.File? FirstDownload => Downloads?.FirstOrDefault().Value.Item2;
public string? FirstUsername => Downloads?.FirstOrDefault().Value.Item1.Username;
public Soulseek.File? FirstDownload => Downloads?.FirstOrDefault().Item2;
public string? FirstUsername => Downloads?.FirstOrDefault().Item1?.Username;
public Track() { }
@ -64,13 +64,13 @@ namespace Data
public string ToString(bool noInfo = false)
{
if (IsNotAudio && Downloads != null && !Downloads.IsEmpty)
return $"{Utils.GetFileNameSlsk(Downloads.First().Value.Item2.Filename)}";
if (IsNotAudio && Downloads != null && Downloads.Count > 0)
return $"{Utils.GetFileNameSlsk(Downloads[0].Item2.Filename)}";
string str = Artist;
if (Type == TrackType.Normal && Title.Length == 0 && Downloads != null && !Downloads.IsEmpty)
if (Type == TrackType.Normal && Title.Length == 0 && Downloads != null && Downloads.Count > 0)
{
str = $"{Utils.GetFileNameSlsk(Downloads.First().Value.Item2.Filename)}";
str = $"{Utils.GetFileNameSlsk(Downloads[0].Item2.Filename)}";
}
else if (Title.Length > 0 || Album.Length > 0)
{
@ -313,12 +313,14 @@ namespace Data
}
}
public class TrackStringComparer : IEqualityComparer<Track>
public class TrackComparer : IEqualityComparer<Track>
{
private bool _ignoreCase = false;
public TrackStringComparer(bool ignoreCase = false)
private int _lenTol = -1;
public TrackComparer(bool ignoreCase = false, int lenTol = -1)
{
_ignoreCase = ignoreCase;
_lenTol = lenTol;
}
public bool Equals(Track a, Track b)
@ -330,7 +332,8 @@ namespace Data
return string.Equals(a.Title, b.Title, comparer)
&& string.Equals(a.Artist, b.Artist, comparer)
&& string.Equals(a.Album, b.Album, comparer);
&& string.Equals(a.Album, b.Album, comparer)
&& _lenTol == -1 || (a.Length == -1 && b.Length == -1) || (a.Length != -1 && b.Length != -1 && Math.Abs(a.Length - b.Length) <= _lenTol);
}
public int GetHashCode(Track a)

View file

@ -17,10 +17,8 @@ public class FileConditions
public bool StrictTitle = false;
public bool StrictArtist = false;
public bool StrictAlbum = false;
public string[] DangerWords = Array.Empty<string>();
public string[] Formats = Array.Empty<string>();
public string[] BannedUsers = Array.Empty<string>();
public string StrictStringRegexRemove = string.Empty;
public bool StrictStringDiacrRemove = true;
public bool AcceptNoLength = true;
public bool AcceptMissingProps = true;
@ -40,7 +38,6 @@ public class FileConditions
MinBitDepth = other.MinBitDepth;
MaxBitDepth = other.MaxBitDepth;
Formats = other.Formats.ToArray();
DangerWords = other.DangerWords.ToArray();
BannedUsers = other.BannedUsers.ToArray();
}
@ -58,12 +55,10 @@ public class FileConditions
StrictTitle == other.StrictTitle &&
StrictArtist == other.StrictArtist &&
StrictAlbum == other.StrictAlbum &&
StrictStringRegexRemove == other.StrictStringRegexRemove &&
StrictStringDiacrRemove == other.StrictStringDiacrRemove &&
AcceptNoLength == other.AcceptNoLength &&
AcceptMissingProps == other.AcceptMissingProps &&
Formats.SequenceEqual(other.Formats) &&
DangerWords.SequenceEqual(other.DangerWords) &&
BannedUsers.SequenceEqual(other.BannedUsers);
}
return false;
@ -81,7 +76,7 @@ public class FileConditions
public bool FileSatisfies(Soulseek.File file, Track track, SearchResponse? response)
{
return DangerWordSatisfies(file.Filename, track.Title, track.Artist) && FormatSatisfies(file.Filename)
return FormatSatisfies(file.Filename)
&& LengthToleranceSatisfies(file, track.Length) && BitrateSatisfies(file) && SampleRateSatisfies(file)
&& StrictTitleSatisfies(file.Filename, track.Title) && StrictArtistSatisfies(file.Filename, track.Artist)
&& StrictAlbumSatisfies(file.Filename, track.Album) && BannedUsersSatisfies(response) && BitDepthSatisfies(file);
@ -89,7 +84,7 @@ public class FileConditions
public bool FileSatisfies(TagLib.File file, Track track, bool filenameChecks = false)
{
return DangerWordSatisfies(file.Name, track.Title, track.Artist) && FormatSatisfies(file.Name)
return FormatSatisfies(file.Name)
&& LengthToleranceSatisfies(file, track.Length) && BitrateSatisfies(file) && SampleRateSatisfies(file)
&& BitDepthSatisfies(file) && (!filenameChecks || StrictTitleSatisfies(file.Name, track.Title)
&& StrictArtistSatisfies(file.Name, track.Artist) && StrictAlbumSatisfies(file.Name, track.Album));
@ -97,44 +92,19 @@ public class FileConditions
public bool FileSatisfies(SimpleFile file, Track track, bool filenameChecks = false)
{
return DangerWordSatisfies(file.Path, track.Title, track.Artist) && FormatSatisfies(file.Path)
return FormatSatisfies(file.Path)
&& LengthToleranceSatisfies(file, track.Length) && BitrateSatisfies(file) && SampleRateSatisfies(file)
&& BitDepthSatisfies(file) && (!filenameChecks || StrictTitleSatisfies(file.Path, track.Title)
&& StrictArtistSatisfies(file.Path, track.Artist) && StrictAlbumSatisfies(file.Path, track.Album));
}
public bool DangerWordSatisfies(string fname, string tname, string aname)
{
if (tname.Length == 0)
return true;
fname = Utils.GetFileNameWithoutExtSlsk(fname).Replace(" — ", " - ");
tname = tname.Replace(" — ", " - ");
foreach (var word in DangerWords)
{
if (fname.ContainsIgnoreCase(word) ^ tname.ContainsIgnoreCase(word))
{
if (!(fname.Contains(" - ") && fname.ContainsIgnoreCase(word) && aname.ContainsIgnoreCase(word)))
{
if (word == "mix")
return fname.ContainsIgnoreCase("original mix") || tname.ContainsIgnoreCase("original mix");
else
return false;
}
}
}
return true;
}
public bool StrictTitleSatisfies(string fname, string tname, bool noPath = true)
{
if (!StrictTitle || tname.Length == 0)
return true;
fname = noPath ? Utils.GetFileNameWithoutExtSlsk(fname) : fname;
return StrictString(fname, tname, StrictStringRegexRemove, StrictStringDiacrRemove, ignoreCase: true);
return StrictString(fname, tname, StrictStringDiacrRemove, ignoreCase: true);
}
public bool StrictArtistSatisfies(string fname, string aname)
@ -142,7 +112,7 @@ public class FileConditions
if (!StrictArtist || aname.Length == 0)
return true;
return StrictString(fname, aname, StrictStringRegexRemove, StrictStringDiacrRemove, ignoreCase: true, boundarySkipWs: false);
return StrictString(fname, aname, StrictStringDiacrRemove, ignoreCase: true, boundarySkipWs: false);
}
public bool StrictAlbumSatisfies(string fname, string alname)
@ -150,25 +120,24 @@ public class FileConditions
if (!StrictAlbum || alname.Length == 0)
return true;
return StrictString(Utils.GetDirectoryNameSlsk(fname), alname, StrictStringRegexRemove, StrictStringDiacrRemove, ignoreCase: true);
return StrictString(Utils.GetDirectoryNameSlsk(fname), alname, StrictStringDiacrRemove, ignoreCase: true);
}
public static string StrictStringPreprocess(string str, string regexRemove = "", bool diacrRemove = true)
public static string StrictStringPreprocess(string str, bool diacrRemove = true)
{
str = str.Replace('_', ' ').ReplaceInvalidChars(' ', true, false);
str = regexRemove.Length > 0 ? Regex.Replace(str, regexRemove, "") : str;
str = diacrRemove ? str.RemoveDiacritics() : str;
str = str.Trim().RemoveConsecutiveWs();
return str;
}
public static bool StrictString(string fname, string tname, string regexRemove = "", bool diacrRemove = true, bool ignoreCase = true, bool boundarySkipWs = true)
public static bool StrictString(string fname, string tname, bool diacrRemove = true, bool ignoreCase = true, bool boundarySkipWs = true)
{
if (tname.Length == 0)
return true;
fname = StrictStringPreprocess(fname, regexRemove, diacrRemove);
tname = StrictStringPreprocess(tname, regexRemove, diacrRemove);
fname = StrictStringPreprocess(fname, diacrRemove);
tname = StrictStringPreprocess(tname, diacrRemove);
if (boundarySkipWs)
return fname.ContainsWithBoundaryIgnoreWs(tname, ignoreCase, acceptLeftDigit: true);
@ -252,43 +221,24 @@ public class FileConditions
public string GetNotSatisfiedName(Soulseek.File file, Track track, SearchResponse? response)
{
if (!DangerWordSatisfies(file.Filename, track.Title, track.Artist))
return "DangerWord fails";
if (!FormatSatisfies(file.Filename))
return "Format fails";
if (!LengthToleranceSatisfies(file, track.Length))
return "Length fails";
if (!BitrateSatisfies(file))
return "Bitrate fails";
if (!SampleRateSatisfies(file))
return "SampleRate fails";
if (!StrictTitleSatisfies(file.Filename, track.Title))
return "StrictTitle fails";
if (!StrictArtistSatisfies(file.Filename, track.Artist))
return "StrictArtist fails";
if (!BitDepthSatisfies(file))
return "BitDepth fails";
if (!BannedUsersSatisfies(response))
return "BannedUsers fails";
return "Satisfied";
}
public string GetNotSatisfiedName(TagLib.File file, Track track)
{
if (!DangerWordSatisfies(file.Name, track.Title, track.Artist))
return "DangerWord fails";
if (!FormatSatisfies(file.Name))
return "Format fails";
if (!StrictTitleSatisfies(file.Filename, track.Title))
return "StrictTitle fails";
if (track.Type == Enums.TrackType.Album && !StrictAlbumSatisfies(file.Filename, track.Artist))
return "StrictAlbum fails";
if (!StrictArtistSatisfies(file.Filename, track.Artist))
return "StrictArtist fails";
if (!LengthToleranceSatisfies(file, track.Length))
return "Length fails";
return "LengthTolerance fails";
if (!FormatSatisfies(file.Filename))
return "Format fails";
if (track.Type != Enums.TrackType.Album && !StrictAlbumSatisfies(file.Filename, track.Artist))
return "StrictAlbum fails";
if (!BitrateSatisfies(file))
return "Bitrate fails";
if (!SampleRateSatisfies(file))
return "SampleRate fails";
if (!StrictTitleSatisfies(file.Name, track.Title))
return "StrictTitle fails";
if (!StrictArtistSatisfies(file.Name, track.Artist))
return "StrictArtist fails";
if (!BitDepthSatisfies(file))
return "BitDepth fails";
return "Satisfied";

View file

@ -83,8 +83,8 @@ public static class Help
on a per-track basis, so it is best kept off in that case.
-d, --desperate Tries harder to find the desired track by searching for the
artist/album/title only, then filtering. (slower search)
--fails-to-downrank <num> Number of fails to downrank a user's uploads (default: 1)
--fails-to-ignore <num> Number of fails to ban/ignore a user's uploads (default: 2)
--fails-to-downrank <num> Number of fails to downrank a user's shares (default: 1)
--fails-to-ignore <num> Number of fails to ban/ignore a user's shares (default: 2)
--yt-dlp Use yt-dlp to download tracks that weren't found on
Soulseek. yt-dlp must be available from the command line.
@ -339,8 +339,12 @@ public static class Help
sldl will therefore prefer mp3 files with bitrate between 200 and 2500 kbps, and whose length
differs from the supplied length by no more than 3 seconds. It will also prefer files whose
paths contain the supplied artist and album (ignoring case, and bounded by boundary characters)
and which have a non-null length. Changing the last three preferred conditions is not recommended.
paths contain the supplied title and album (ignoring case, and bounded by boundary characters)
and which have non-null length. Changing the last three preferred conditions is not recommended.
Note that files satisfying a subset of the preferred conditions will still be preferred over files
that don't satisfy any condition, but some conditions have precedence over others. For instance,
a file that only satisfies strict-title (if enabled) will always be preferred over a file that
only satisfies the format condition. Run with --print ""results-full"" to reveal the sorting logic.
Important note
Some info may be unavailable depending on the client used by the peer. For example, the standard

View file

@ -350,12 +350,16 @@ static partial class Program
else if (tle.source.Type == TrackType.Album)
{
Console.WriteLine(new string('-', 60));
Console.WriteLine($"Results for album {tle.source.ToString(true)}:");
if (!Config.printOption.HasFlag(PrintOption.Full))
Console.WriteLine($"Result 1 of {tle.list.Count} for album {tle.source.ToString(true)}:");
else
Console.WriteLine($"Results ({tle.list.Count}) for album {tle.source.ToString(true)}:");
if (tle.list.Count > 0 && tle.list[0].Count > 0)
{
if (!Config.noBrowseFolder)
Console.WriteLine("[Skipped full folder retrieval]");
Console.WriteLine("[Skipping full folder retrieval]");
foreach (var ls in tle.list)
{
@ -430,12 +434,12 @@ static partial class Program
static void PrintAlbum(List<Track> albumTracks, bool retrieveAll = false)
{
if (albumTracks.Count == 0 && albumTracks[0].Downloads.IsEmpty)
if (albumTracks.Count == 0 && albumTracks[0].Downloads.Count == 0)
return;
var response = albumTracks[0].Downloads.First().Value.Item1;
var response = albumTracks[0].Downloads[0].Item1;
string userInfo = $"{response.Username} ({((float)response.UploadSpeed / (1024 * 1024)):F3}MB/s)";
var (parents, props) = FolderInfo(albumTracks.SelectMany(x => x.Downloads.Select(d => d.Value.Item2)));
var (parents, props) = FolderInfo(albumTracks.SelectMany(x => x.Downloads.Select(d => d.Item2)));
Console.WriteLine();
WriteLine($"User : {userInfo}\nFolder: {parents}\nProps : {props}", ConsoleColor.White);
@ -623,23 +627,23 @@ static partial class Program
{
var albumArtList = list
//.Where(tracks => tracks)
.Select(tracks => tracks.Where(t => Utils.IsImageFile(t.Downloads.First().Value.Item2.Filename)))
.Select(tracks => tracks.Where(t => Utils.IsImageFile(t.Downloads[0].Item2.Filename)))
.Where(tracks => tracks.Any());
if (option == AlbumArtOption.Largest)
{
list = albumArtList
.OrderByDescending(tracks => tracks.Select(t => t.Downloads.First().Value.Item2.Size).Max() / 1024 / 100)
.ThenByDescending(tracks => tracks.First().Downloads.First().Value.Item1.UploadSpeed / 1024 / 300)
.ThenByDescending(tracks => tracks.Select(t => t.Downloads.First().Value.Item2.Size).Sum() / 1024 / 100)
.OrderByDescending(tracks => tracks.Select(t => t.Downloads[0].Item2.Size).Max() / 1024 / 100)
.ThenByDescending(tracks => tracks.First().Downloads[0].Item1.UploadSpeed / 1024 / 300)
.ThenByDescending(tracks => tracks.Select(t => t.Downloads[0].Item2.Size).Sum() / 1024 / 100)
.Select(x => x.ToList()).ToList();
}
else if (option == AlbumArtOption.Most)
{
list = albumArtList
.OrderByDescending(tracks => tracks.Count())
.ThenByDescending(tracks => tracks.First().Downloads.First().Value.Item1.UploadSpeed / 1024 / 300)
.ThenByDescending(tracks => tracks.Select(t => t.Downloads.First().Value.Item2.Size).Sum() / 1024 / 100)
.ThenByDescending(tracks => tracks.First().Downloads[0].Item1.UploadSpeed / 1024 / 300)
.ThenByDescending(tracks => tracks.Select(t => t.Downloads[0].Item2.Size).Sum() / 1024 / 100)
.Select(x => x.ToList()).ToList();
}
}
@ -655,7 +659,7 @@ static partial class Program
else if (option == AlbumArtOption.Largest)
{
long curMax = dlFiles.Keys.Where(x => Utils.IsImageFile(x) && File.Exists(x)).Max(x => new FileInfo(x).Length);
need = curMax < list[0].Max(t => t.Downloads.First().Value.Item2.Size) - 1024 * 50;
need = curMax < list[0].Max(t => t.Downloads[0].Item2.Size) - 1024 * 50;
}
return need;
@ -693,7 +697,7 @@ static partial class Program
if (!Config.noBrowseFolder && !Config.interactiveMode && !retrievedFolders.Contains(soulseekFolderPathPrefix))
{
Console.WriteLine("Getting all files in folder...");
var response = tracks[0].Downloads.First().Value.Item1;
var response = tracks[0].Downloads[0].Item1;
await CompleteFolder(tracks, response, soulseekFolderPathPrefix);
retrievedFolders.Add(soulseekFolderPathPrefix);
}
@ -865,9 +869,9 @@ static partial class Program
static string GetCommonPathPrefix(List<Track> tracks)
{
if (tracks.Count == 1)
return Utils.GetDirectoryNameSlsk(tracks.First().Downloads.First().Value.Item2.Filename);
return Utils.GetDirectoryNameSlsk(tracks.First().Downloads[0].Item2.Filename);
else
return Utils.GreatestCommonPath(tracks.SelectMany(x => x.Downloads.Select(y => y.Value.Item2.Filename)), dirsep: '\\');
return Utils.GreatestCommonPath(tracks.SelectMany(x => x.Downloads.Select(y => y.Item2.Filename)), dirsep: '\\');
}
@ -897,7 +901,7 @@ static partial class Program
{
Console.WriteLine();
var tracks = list[aidx];
var response = tracks[0].Downloads.First().Value.Item1;
var response = tracks[0].Downloads[0].Item1;
var folder = GetCommonPathPrefix(tracks);
if (retrieveFolder && !Config.noBrowseFolder && !retrievedFolders.Contains(folder))
@ -908,7 +912,7 @@ static partial class Program
}
string userInfo = $"{response.Username} ({((float)response.UploadSpeed / (1024 * 1024)):F3}MB/s)";
var (parents, props) = FolderInfo(tracks.SelectMany(x => x.Downloads.Select(d => d.Value.Item2)));
var (parents, props) = FolderInfo(tracks.SelectMany(x => x.Downloads.Select(d => d.Item2)));
WriteLine($"[{aidx + 1} / {list.Count}]", ConsoleColor.DarkGray);
WriteLine($"User : {userInfo}\nFolder: {parents}\nProps : {props}", ConsoleColor.White);
@ -1396,7 +1400,7 @@ static partial class Program
string ancestor = "";
if (showAncestors)
ancestor = Utils.GreatestCommonPath(tracks.SelectMany(x => x.Downloads.Select(y => y.Value.Item2.Filename)), Path.DirectorySeparatorChar);
ancestor = Utils.GreatestCommonPath(tracks.SelectMany(x => x.Downloads.Select(y => y.Item2.Filename)), Path.DirectorySeparatorChar);
if (pathsOnly)
{
@ -1405,9 +1409,9 @@ static partial class Program
foreach (var x in tracks[i].Downloads)
{
if (ancestor.Length == 0)
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, infoFirst: infoFirst, showUser: showUser));
Console.WriteLine(" " + DisplayString(tracks[i], x.Item2, x.Item1, infoFirst: infoFirst, showUser: showUser));
else
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, customPath: x.Value.Item2.Filename.Replace(ancestor, ""), infoFirst: infoFirst, showUser: showUser));
Console.WriteLine(" " + DisplayString(tracks[i], x.Item2, x.Item1, customPath: x.Item2.Filename.Replace(ancestor, ""), infoFirst: infoFirst, showUser: showUser));
}
}
}
@ -1447,23 +1451,23 @@ static partial class Program
foreach (var x in tracks[i].Downloads)
{
if (ancestor.Length == 0)
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, infoFirst: infoFirst, showUser: showUser));
Console.WriteLine(" " + DisplayString(tracks[i], x.Item2, x.Item1, infoFirst: infoFirst, showUser: showUser));
else
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, customPath: x.Value.Item2.Filename.Replace(ancestor, ""), infoFirst: infoFirst, showUser: showUser));
Console.WriteLine(" " + DisplayString(tracks[i], x.Item2, x.Item1, customPath: x.Item2.Filename.Replace(ancestor, ""), infoFirst: infoFirst, showUser: showUser));
}
if (tracks[i].Downloads?.Count > 0) Console.WriteLine();
}
}
else
{
Console.WriteLine($" File: {Utils.GetFileNameSlsk(tracks[i].Downloads.First().Value.Item2.Filename)}");
Console.WriteLine($" File: {Utils.GetFileNameSlsk(tracks[i].Downloads[0].Item2.Filename)}");
Console.WriteLine($" Shares: {tracks[i].Downloads.Count}");
foreach (var x in tracks[i].Downloads)
{
if (ancestor.Length == 0)
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, infoFirst: infoFirst, showUser: showUser));
Console.WriteLine(" " + DisplayString(tracks[i], x.Item2, x.Item1, infoFirst: infoFirst, showUser: showUser));
else
Console.WriteLine(" " + DisplayString(tracks[i], x.Value.Item2, x.Value.Item1, customPath: x.Value.Item2.Filename.Replace(ancestor, ""), infoFirst: infoFirst, showUser: showUser));
Console.WriteLine(" " + DisplayString(tracks[i], x.Item2, x.Item1, customPath: x.Item2.Filename.Replace(ancestor, ""), infoFirst: infoFirst, showUser: showUser));
}
Console.WriteLine();
}

View file

@ -22,6 +22,7 @@ static partial class Program
throw new Exception();
responseData ??= new ResponseData();
IEnumerable<(SlResponse response, SlFile file)>? orderedResults = null;
var progress = GetProgressBar(Config.displayMode);
var results = new SlDictionary();
var fsResults = new SlDictionary();
@ -38,7 +39,7 @@ static partial class Program
if (track.Downloads != null)
{
results = track.Downloads;
orderedResults = track.Downloads;
goto downloads;
}
@ -141,9 +142,10 @@ static partial class Program
downloads:
if (downloading == 0 && !results.IsEmpty)
if (downloading == 0 && (!results.IsEmpty || orderedResults != null))
{
var orderedResults = OrderedResults(results, track, true);
if (orderedResults == null)
orderedResults = OrderedResults(results, track, useInfer: true);
int trackTries = Config.maxRetriesPerTrack;
async Task<bool> process(SlResponse response, SlFile file)
@ -376,13 +378,12 @@ static partial class Program
Album = track.Album,
Length = x.file.Length ?? -1,
IsNotAudio = !Utils.IsMusicFile(x.file.Filename),
Downloads = new ConcurrentDictionary<string, (SlResponse, SlFile file)>(
new Dictionary<string, (SlResponse response, SlFile file)> { { x.response.Username + '\\' + x.file.Filename, x } })
Downloads = new() { x },
};
ls.Add(t);
}
ls = ls.OrderBy(t => t.IsNotAudio).ThenBy(t => t.Downloads.First().Value.Item2.Filename).ToList();
ls = ls.OrderBy(t => t.IsNotAudio).ThenBy(t => t.Downloads[0].Item2.Filename).ToList();
result.Add(ls);
}
@ -396,9 +397,9 @@ static partial class Program
static async Task<List<Track>> GetAggregateTracks(Track track, ResponseData responseData)
{
var results = new ConcurrentDictionary<string, (SearchResponse, Soulseek.File)>();
var results = new SlDictionary();
SearchOptions getSearchOptions(int timeout, FileConditions nec, FileConditions prf) =>
new SearchOptions(
new (
minimumResponseFileCount: 1,
minimumPeerUploadSpeed: 1,
removeSingleCharacterSearchTerms: Config.removeSingleCharacterSearchTerms,
@ -410,9 +411,6 @@ static partial class Program
fileFilter: (file) =>
{
return Utils.IsMusicFile(file.Filename) && nec.FileSatisfies(file, track, null);
//&& FileConditions.StrictString(file.Filename, track.ArtistName, ignoreCase: true)
//&& FileConditions.StrictString(file.Filename, track.TrackTitle, ignoreCase: true)
//&& FileConditions.StrictString(file.Filename, track.Album, ignoreCase: true);
}
);
void handler(SlResponse r)
@ -433,11 +431,8 @@ static partial class Program
string trackName = track.Title.Trim();
string albumName = track.Album.Trim();
//var orderedResults = OrderedResults(results, track, false, false, false);
var fileResponses = results.Select(x => x.Value);
var equivalentFiles = EquivalentFiles(track, fileResponses).ToList();
var equivalentFiles = EquivalentFiles(track, results.Select(x => x.Value))
.Select(x => (x.Item1, OrderedResults(x.Item2, track, false, false, false))).ToList();
if (!Config.relax)
{
@ -452,8 +447,7 @@ static partial class Program
var tracks = equivalentFiles
.Select(kvp =>
{
kvp.Item1.Downloads = new SlDictionary(
kvp.Item2.ToDictionary(item => { return item.response.Username + "\\" + item.file.Filename; }, item => item));
kvp.Item1.Downloads = kvp.Item2.ToList();
return kvp.Item1;
}).ToList();
@ -498,6 +492,7 @@ static partial class Program
sortedLengthLists.Add((sortedLengths, album, user));
}
var usernamesList = new List<HashSet<string>>();
var lengthsList = new List<int[]>();
var res = new List<List<List<Track>>>();
@ -511,8 +506,8 @@ static partial class Program
{
if (lengths.Length == 1 && lengthsList[i].Length == 1)
{
var t1 = InferTrack(album[0].Downloads.First().Value.Item2.Filename, new Track());
var t2 = InferTrack(res[i][0][0].Downloads.First().Value.Item2.Filename, new Track());
var t1 = InferTrack(album[0].Downloads[0].Item2.Filename, new Track());
var t2 = InferTrack(res[i][0][0].Downloads[0].Item2.Filename, new Track());
if ((t2.Artist.ContainsIgnoreCase(t1.Artist) || t1.Artist.ContainsIgnoreCase(t2.Artist))
&& (t2.Title.ContainsIgnoreCase(t1.Title) || t1.Title.ContainsIgnoreCase(t2.Title)))
@ -527,6 +522,7 @@ static partial class Program
if (found)
{
usernamesList[i].Add(user);
res[i].Add(album);
break;
}
@ -539,12 +535,17 @@ static partial class Program
}
else
{
usernamesList.Add(new() { user });
lengthsList.Add(lengths);
res.Add(new List<List<Track>> { album });
}
}
res = res.Where(x => x.Count >= Config.minSharesAggregate).OrderByDescending(x => x.Count).ToList();
res = res.Select((x, i) => (x, i))
.Where(x => usernamesList[x.i].Count >= Config.minSharesAggregate)
.OrderByDescending(x => usernamesList[x.i].Count)
.Select(x => x.x)
.ToList();
return res; // Note: The nested lists are still ordered according to OrderedResults
}
@ -586,7 +587,7 @@ static partial class Program
if (allFiles.Count > tracks.Count)
{
var paths = tracks.Select(x => x.Downloads.First().Value.Item2.Filename).ToHashSet();
var paths = tracks.Select(x => x.Downloads[0].Item2.Filename).ToHashSet();
var first = tracks[0];
foreach ((var dir, var file) in allFiles)
@ -600,8 +601,7 @@ static partial class Program
Artist = first.Artist,
Album = first.Album,
IsNotAudio = !Utils.IsMusicFile(file.Filename),
Downloads = new ConcurrentDictionary<string, (SlResponse, SlFile file)>(
new Dictionary<string, (SlResponse response, SlFile file)> { { response.Username + '\\' + fullPath, (response, newFile) } })
Downloads = new() { (response, newFile) }
};
tracks.Add(t);
}
@ -615,7 +615,7 @@ static partial class Program
}
static IOrderedEnumerable<(Track, IEnumerable<(SlResponse response, SlFile file)>)> EquivalentFiles(Track track,
static IEnumerable<(Track, IEnumerable<(SlResponse response, SlFile file)>)> EquivalentFiles(Track track,
IEnumerable<(SlResponse, SlFile)> fileResponses, int minShares = -1)
{
if (minShares == -1)
@ -628,53 +628,32 @@ static partial class Program
return t;
}
var res = fileResponses
.GroupBy(inferTrack, new TrackStringComparer(ignoreCase: true))
.Where(group => group.Select(x => x.Item1.Username).Distinct().Count() >= minShares)
.SelectMany(group =>
var groups = fileResponses
.GroupBy(inferTrack, new TrackComparer(ignoreCase: true, Config.necessaryCond.LengthTolerance))
.Select(x => (x, x.Select(y => y.Item1.Username).Distinct().Count()))
.Where(x => x.Item2 >= minShares)
.OrderByDescending(x => x.Item2)
.Select(x => x.x)
.Select(x =>
{
var sortedTracks = group.OrderBy(t => t.Item2.Length).Where(x => x.Item2.Length != null).ToList();
var groups = new List<(Track, List<(SearchResponse, Soulseek.File)>)>();
var noLengthGroup = group.Where(x => x.Item2.Length == null);
for (int i = 0; i < sortedTracks.Count;)
{
var subGroup = new List<(SearchResponse, Soulseek.File)> { sortedTracks[i] };
int j = i + 1;
while (j < sortedTracks.Count)
{
int l1 = (int)sortedTracks[j].Item2.Length;
int l2 = (int)sortedTracks[i].Item2.Length;
if (Config.necessaryCond.LengthTolerance == -1 || Math.Abs(l1 - l2) <= Config.necessaryCond.LengthTolerance)
{
subGroup.Add(sortedTracks[j]);
j++;
}
else break;
}
var t = new Track(group.Key);
t.Length = (int)sortedTracks[i].Item2.Length;
groups.Add((t, subGroup));
i = j;
}
if (x.Key.Length == -1)
x.Key.Length = x.FirstOrDefault(y => y.Item2.Length != null).Item2?.Length ?? -1;
return (x.Key, x.AsEnumerable());
});
if (noLengthGroup.Any())
{
if (groups.Count > 0 && !Config.preferredCond.AcceptNoLength)
groups.First().Item2.AddRange(noLengthGroup);
else
groups.Add((group.Key, noLengthGroup.ToList()));
}
return groups.Where(subGroup => subGroup.Item2.Select(x => x.Item1.Username).Distinct().Count() >= minShares)
.Select(subGroup => (subGroup.Item1, subGroup.Item2.AsEnumerable()));
}).OrderByDescending(x => x.Item2.Count());
return res;
return groups;
}
static IOrderedEnumerable<(SlResponse response, SlFile file)> OrderedResults(IEnumerable<KeyValuePair<string, (SlResponse, SlFile)>> results,
Track track, bool useInfer = false, bool useLevenshtein = true, bool albumMode = false)
{
return OrderedResults(results.Select(x => x.Value), track, useInfer, useLevenshtein, albumMode);
}
static IOrderedEnumerable<(SlResponse response, SlFile file)> OrderedResults(IEnumerable<(SlResponse, SlFile)> results,
Track track, bool useInfer = false, bool useLevenshtein = true, bool albumMode = false)
{
bool useBracketCheck = true;
if (albumMode)
@ -685,9 +664,10 @@ static partial class Program
}
Dictionary<string, (Track, int)>? infTracksAndCounts = null;
if (useInfer)
if (useInfer) // this is very slow
{
var equivalentFiles = EquivalentFiles(track, results.Select(x => x.Value), 1);
var equivalentFiles = EquivalentFiles(track, results, 1);
infTracksAndCounts = equivalentFiles
.SelectMany(t => t.Item2, (t, f) => new { t.Item1, f.response.Username, f.file.Filename, Count = t.Item2.Count() })
.ToSafeDictionary(x => $"{x.Username}\\{x.Filename}", y => (y.Item1, y.Count));
@ -710,7 +690,7 @@ static partial class Program
}
var random = new Random();
return results.Select(kvp => (response: kvp.Value.Item1, file: kvp.Value.Item2))
return results.Select(x => (response: x.Item1, file: x.Item2))
.Where(x => userSuccessCount.GetValueOrDefault(x.response.Username, 0) > Config.ignoreOn)
.OrderByDescending(x => userSuccessCount.GetValueOrDefault(x.response.Username, 0) > Config.downrankOn)
.ThenByDescending(x => Config.necessaryCond.FileSatisfies(x.file, track, x.response))
@ -718,10 +698,11 @@ static partial class Program
.ThenByDescending(x => (x.file.Length != null && x.file.Length > 0) || Config.preferredCond.AcceptNoLength)
.ThenByDescending(x => !useBracketCheck || FileConditions.BracketCheck(track, inferredTrack(x).Item1)) // downrank result if it contains '(' or '[' and the title does not (avoid remixes)
.ThenByDescending(x => Config.preferredCond.StrictTitleSatisfies(x.file.Filename, track.Title))
.ThenByDescending(x => !albumMode || Config.preferredCond.StrictAlbumSatisfies(x.file.Filename, track.Album))
.ThenByDescending(x => Config.preferredCond.StrictArtistSatisfies(x.file.Filename, track.Title))
.ThenByDescending(x => Config.preferredCond.LengthToleranceSatisfies(x.file, track.Length))
.ThenByDescending(x => Config.preferredCond.FormatSatisfies(x.file.Filename))
.ThenByDescending(x => Config.preferredCond.StrictAlbumSatisfies(x.file.Filename, track.Album))
.ThenByDescending(x => albumMode || Config.preferredCond.StrictAlbumSatisfies(x.file.Filename, track.Album))
.ThenByDescending(x => Config.preferredCond.BitrateSatisfies(x.file))
.ThenByDescending(x => Config.preferredCond.SampleRateSatisfies(x.file))
.ThenByDescending(x => Config.preferredCond.BitDepthSatisfies(x.file))
@ -901,7 +882,7 @@ static partial class Program
}
else
{
var orderedResults = OrderedResults(results, track, true);
var orderedResults = OrderedResults(results, track, useInfer: true);
int count = 0;
Console.WriteLine();
foreach (var (response, file) in orderedResults)
@ -971,7 +952,6 @@ static partial class Program
filename = Utils.GetFileNameWithoutExtSlsk(filename).Replace(" — ", " - ").Replace('_', ' ').Trim().RemoveConsecutiveWs();
var trackNumStart = new Regex(@"^(?:(?:[0-9][-\.])?\d{2,3}[. -]|\b\d\.\s|\b\d\s-\s)(?=.+\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+");