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. 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 -d, --desperate Tries harder to find the desired track by searching for the
artist/album/title only, then filtering. (slower search) 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-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 uploads (default: 2) --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 --yt-dlp Use yt-dlp to download tracks that weren't found on
Soulseek. yt-dlp must be available from the command line. 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 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 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) paths contain the supplied title 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. 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 ### Important note
Some info may be unavailable depending on the client used by the peer. For example, the standard 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": case "bannedusers":
cond.BannedUsers = value.Split(',', tr); cond.BannedUsers = value.Split(',', tr);
break; break;
case "dangerwords":
cond.DangerWords = value.Split(',', tr);
break;
case "stricttitle": case "stricttitle":
cond.StrictTitle = bool.Parse(value); cond.StrictTitle = bool.Parse(value);
break; break;
@ -967,10 +964,6 @@ static class Config
case "--pref-max-bitdepth": case "--pref-max-bitdepth":
preferredCond.MaxBitDepth = int.Parse(args[++i]); preferredCond.MaxBitDepth = int.Parse(args[++i]);
break; break;
case "--pdw":
case "--pref-danger-words":
preferredCond.DangerWords = args[++i].Split(',');
break;
case "--pst": case "--pst":
case "--pstt": case "--pstt":
case "--pref-strict-title": case "--pref-strict-title":
@ -1022,10 +1015,6 @@ static class Config
case "--max-bitdepth": case "--max-bitdepth":
necessaryCond.MaxBitDepth = int.Parse(args[++i]); necessaryCond.MaxBitDepth = int.Parse(args[++i]);
break; break;
case "--dw":
case "--danger-words":
necessaryCond.DangerWords = args[++i].Split(',');
break;
case "--stt": case "--stt":
case "--strict-title": case "--strict-title":
setFlag(ref necessaryCond.StrictTitle, ref i); setFlag(ref necessaryCond.StrictTitle, ref i);

View file

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

View file

@ -17,10 +17,8 @@ public class FileConditions
public bool StrictTitle = false; public bool StrictTitle = false;
public bool StrictArtist = false; public bool StrictArtist = false;
public bool StrictAlbum = false; public bool StrictAlbum = false;
public string[] DangerWords = Array.Empty<string>();
public string[] Formats = Array.Empty<string>(); public string[] Formats = Array.Empty<string>();
public string[] BannedUsers = Array.Empty<string>(); public string[] BannedUsers = Array.Empty<string>();
public string StrictStringRegexRemove = string.Empty;
public bool StrictStringDiacrRemove = true; public bool StrictStringDiacrRemove = true;
public bool AcceptNoLength = true; public bool AcceptNoLength = true;
public bool AcceptMissingProps = true; public bool AcceptMissingProps = true;
@ -40,7 +38,6 @@ public class FileConditions
MinBitDepth = other.MinBitDepth; MinBitDepth = other.MinBitDepth;
MaxBitDepth = other.MaxBitDepth; MaxBitDepth = other.MaxBitDepth;
Formats = other.Formats.ToArray(); Formats = other.Formats.ToArray();
DangerWords = other.DangerWords.ToArray();
BannedUsers = other.BannedUsers.ToArray(); BannedUsers = other.BannedUsers.ToArray();
} }
@ -58,12 +55,10 @@ public class FileConditions
StrictTitle == other.StrictTitle && StrictTitle == other.StrictTitle &&
StrictArtist == other.StrictArtist && StrictArtist == other.StrictArtist &&
StrictAlbum == other.StrictAlbum && StrictAlbum == other.StrictAlbum &&
StrictStringRegexRemove == other.StrictStringRegexRemove &&
StrictStringDiacrRemove == other.StrictStringDiacrRemove && StrictStringDiacrRemove == other.StrictStringDiacrRemove &&
AcceptNoLength == other.AcceptNoLength && AcceptNoLength == other.AcceptNoLength &&
AcceptMissingProps == other.AcceptMissingProps && AcceptMissingProps == other.AcceptMissingProps &&
Formats.SequenceEqual(other.Formats) && Formats.SequenceEqual(other.Formats) &&
DangerWords.SequenceEqual(other.DangerWords) &&
BannedUsers.SequenceEqual(other.BannedUsers); BannedUsers.SequenceEqual(other.BannedUsers);
} }
return false; return false;
@ -81,7 +76,7 @@ public class FileConditions
public bool FileSatisfies(Soulseek.File file, Track track, SearchResponse? response) 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) && LengthToleranceSatisfies(file, track.Length) && BitrateSatisfies(file) && SampleRateSatisfies(file)
&& StrictTitleSatisfies(file.Filename, track.Title) && StrictArtistSatisfies(file.Filename, track.Artist) && StrictTitleSatisfies(file.Filename, track.Title) && StrictArtistSatisfies(file.Filename, track.Artist)
&& StrictAlbumSatisfies(file.Filename, track.Album) && BannedUsersSatisfies(response) && BitDepthSatisfies(file); && 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) 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) && LengthToleranceSatisfies(file, track.Length) && BitrateSatisfies(file) && SampleRateSatisfies(file)
&& BitDepthSatisfies(file) && (!filenameChecks || StrictTitleSatisfies(file.Name, track.Title) && BitDepthSatisfies(file) && (!filenameChecks || StrictTitleSatisfies(file.Name, track.Title)
&& StrictArtistSatisfies(file.Name, track.Artist) && StrictAlbumSatisfies(file.Name, track.Album)); && 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) 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) && LengthToleranceSatisfies(file, track.Length) && BitrateSatisfies(file) && SampleRateSatisfies(file)
&& BitDepthSatisfies(file) && (!filenameChecks || StrictTitleSatisfies(file.Path, track.Title) && BitDepthSatisfies(file) && (!filenameChecks || StrictTitleSatisfies(file.Path, track.Title)
&& StrictArtistSatisfies(file.Path, track.Artist) && StrictAlbumSatisfies(file.Path, track.Album)); && 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) public bool StrictTitleSatisfies(string fname, string tname, bool noPath = true)
{ {
if (!StrictTitle || tname.Length == 0) if (!StrictTitle || tname.Length == 0)
return true; return true;
fname = noPath ? Utils.GetFileNameWithoutExtSlsk(fname) : fname; 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) public bool StrictArtistSatisfies(string fname, string aname)
@ -142,7 +112,7 @@ public class FileConditions
if (!StrictArtist || aname.Length == 0) if (!StrictArtist || aname.Length == 0)
return true; 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) public bool StrictAlbumSatisfies(string fname, string alname)
@ -150,25 +120,24 @@ public class FileConditions
if (!StrictAlbum || alname.Length == 0) if (!StrictAlbum || alname.Length == 0)
return true; 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 = str.Replace('_', ' ').ReplaceInvalidChars(' ', true, false);
str = regexRemove.Length > 0 ? Regex.Replace(str, regexRemove, "") : str;
str = diacrRemove ? str.RemoveDiacritics() : str; str = diacrRemove ? str.RemoveDiacritics() : str;
str = str.Trim().RemoveConsecutiveWs(); str = str.Trim().RemoveConsecutiveWs();
return str; 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) if (tname.Length == 0)
return true; return true;
fname = StrictStringPreprocess(fname, regexRemove, diacrRemove); fname = StrictStringPreprocess(fname, diacrRemove);
tname = StrictStringPreprocess(tname, regexRemove, diacrRemove); tname = StrictStringPreprocess(tname, diacrRemove);
if (boundarySkipWs) if (boundarySkipWs)
return fname.ContainsWithBoundaryIgnoreWs(tname, ignoreCase, acceptLeftDigit: true); return fname.ContainsWithBoundaryIgnoreWs(tname, ignoreCase, acceptLeftDigit: true);
@ -252,43 +221,24 @@ public class FileConditions
public string GetNotSatisfiedName(Soulseek.File file, Track track, SearchResponse? response) 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)) if (!BannedUsersSatisfies(response))
return "BannedUsers fails"; return "BannedUsers fails";
return "Satisfied"; if (!StrictTitleSatisfies(file.Filename, track.Title))
} return "StrictTitle fails";
if (track.Type == Enums.TrackType.Album && !StrictAlbumSatisfies(file.Filename, track.Artist))
public string GetNotSatisfiedName(TagLib.File file, Track track) return "StrictAlbum fails";
{ if (!StrictArtistSatisfies(file.Filename, track.Artist))
if (!DangerWordSatisfies(file.Name, track.Title, track.Artist)) return "StrictArtist fails";
return "DangerWord fails";
if (!FormatSatisfies(file.Name))
return "Format fails";
if (!LengthToleranceSatisfies(file, track.Length)) 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)) if (!BitrateSatisfies(file))
return "Bitrate fails"; return "Bitrate fails";
if (!SampleRateSatisfies(file)) if (!SampleRateSatisfies(file))
return "SampleRate fails"; return "SampleRate fails";
if (!StrictTitleSatisfies(file.Name, track.Title))
return "StrictTitle fails";
if (!StrictArtistSatisfies(file.Name, track.Artist))
return "StrictArtist fails";
if (!BitDepthSatisfies(file)) if (!BitDepthSatisfies(file))
return "BitDepth fails"; return "BitDepth fails";
return "Satisfied"; 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. 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 -d, --desperate Tries harder to find the desired track by searching for the
artist/album/title only, then filtering. (slower search) 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-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 uploads (default: 2) --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 --yt-dlp Use yt-dlp to download tracks that weren't found on
Soulseek. yt-dlp must be available from the command line. 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 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 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) paths contain the supplied title 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. 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 Important note
Some info may be unavailable depending on the client used by the peer. For example, the standard 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) else if (tle.source.Type == TrackType.Album)
{ {
Console.WriteLine(new string('-', 60)); 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 (tle.list.Count > 0 && tle.list[0].Count > 0)
{ {
if (!Config.noBrowseFolder) if (!Config.noBrowseFolder)
Console.WriteLine("[Skipped full folder retrieval]"); Console.WriteLine("[Skipping full folder retrieval]");
foreach (var ls in tle.list) foreach (var ls in tle.list)
{ {
@ -430,12 +434,12 @@ static partial class Program
static void PrintAlbum(List<Track> albumTracks, bool retrieveAll = false) 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; 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)"; 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(); Console.WriteLine();
WriteLine($"User : {userInfo}\nFolder: {parents}\nProps : {props}", ConsoleColor.White); WriteLine($"User : {userInfo}\nFolder: {parents}\nProps : {props}", ConsoleColor.White);
@ -623,23 +627,23 @@ static partial class Program
{ {
var albumArtList = list var albumArtList = list
//.Where(tracks => tracks) //.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()); .Where(tracks => tracks.Any());
if (option == AlbumArtOption.Largest) if (option == AlbumArtOption.Largest)
{ {
list = albumArtList list = albumArtList
.OrderByDescending(tracks => tracks.Select(t => t.Downloads.First().Value.Item2.Size).Max() / 1024 / 100) .OrderByDescending(tracks => tracks.Select(t => t.Downloads[0].Item2.Size).Max() / 1024 / 100)
.ThenByDescending(tracks => tracks.First().Downloads.First().Value.Item1.UploadSpeed / 1024 / 300) .ThenByDescending(tracks => tracks.First().Downloads[0].Item1.UploadSpeed / 1024 / 300)
.ThenByDescending(tracks => tracks.Select(t => t.Downloads.First().Value.Item2.Size).Sum() / 1024 / 100) .ThenByDescending(tracks => tracks.Select(t => t.Downloads[0].Item2.Size).Sum() / 1024 / 100)
.Select(x => x.ToList()).ToList(); .Select(x => x.ToList()).ToList();
} }
else if (option == AlbumArtOption.Most) else if (option == AlbumArtOption.Most)
{ {
list = albumArtList list = albumArtList
.OrderByDescending(tracks => tracks.Count()) .OrderByDescending(tracks => tracks.Count())
.ThenByDescending(tracks => tracks.First().Downloads.First().Value.Item1.UploadSpeed / 1024 / 300) .ThenByDescending(tracks => tracks.First().Downloads[0].Item1.UploadSpeed / 1024 / 300)
.ThenByDescending(tracks => tracks.Select(t => t.Downloads.First().Value.Item2.Size).Sum() / 1024 / 100) .ThenByDescending(tracks => tracks.Select(t => t.Downloads[0].Item2.Size).Sum() / 1024 / 100)
.Select(x => x.ToList()).ToList(); .Select(x => x.ToList()).ToList();
} }
} }
@ -655,7 +659,7 @@ static partial class Program
else if (option == AlbumArtOption.Largest) else if (option == AlbumArtOption.Largest)
{ {
long curMax = dlFiles.Keys.Where(x => Utils.IsImageFile(x) && File.Exists(x)).Max(x => new FileInfo(x).Length); 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; return need;
@ -693,7 +697,7 @@ static partial class Program
if (!Config.noBrowseFolder && !Config.interactiveMode && !retrievedFolders.Contains(soulseekFolderPathPrefix)) if (!Config.noBrowseFolder && !Config.interactiveMode && !retrievedFolders.Contains(soulseekFolderPathPrefix))
{ {
Console.WriteLine("Getting all files in folder..."); 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); await CompleteFolder(tracks, response, soulseekFolderPathPrefix);
retrievedFolders.Add(soulseekFolderPathPrefix); retrievedFolders.Add(soulseekFolderPathPrefix);
} }
@ -865,9 +869,9 @@ static partial class Program
static string GetCommonPathPrefix(List<Track> tracks) static string GetCommonPathPrefix(List<Track> tracks)
{ {
if (tracks.Count == 1) if (tracks.Count == 1)
return Utils.GetDirectoryNameSlsk(tracks.First().Downloads.First().Value.Item2.Filename); return Utils.GetDirectoryNameSlsk(tracks.First().Downloads[0].Item2.Filename);
else 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(); Console.WriteLine();
var tracks = list[aidx]; var tracks = list[aidx];
var response = tracks[0].Downloads.First().Value.Item1; var response = tracks[0].Downloads[0].Item1;
var folder = GetCommonPathPrefix(tracks); var folder = GetCommonPathPrefix(tracks);
if (retrieveFolder && !Config.noBrowseFolder && !retrievedFolders.Contains(folder)) 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)"; 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($"[{aidx + 1} / {list.Count}]", ConsoleColor.DarkGray);
WriteLine($"User : {userInfo}\nFolder: {parents}\nProps : {props}", ConsoleColor.White); WriteLine($"User : {userInfo}\nFolder: {parents}\nProps : {props}", ConsoleColor.White);
@ -1396,7 +1400,7 @@ static partial class Program
string ancestor = ""; string ancestor = "";
if (showAncestors) 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) if (pathsOnly)
{ {
@ -1405,9 +1409,9 @@ static partial class Program
foreach (var x in tracks[i].Downloads) foreach (var x in tracks[i].Downloads)
{ {
if (ancestor.Length == 0) 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 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) foreach (var x in tracks[i].Downloads)
{ {
if (ancestor.Length == 0) 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 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(); if (tracks[i].Downloads?.Count > 0) Console.WriteLine();
} }
} }
else 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}"); Console.WriteLine($" Shares: {tracks[i].Downloads.Count}");
foreach (var x in tracks[i].Downloads) foreach (var x in tracks[i].Downloads)
{ {
if (ancestor.Length == 0) 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 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(); Console.WriteLine();
} }

View file

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