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:
parent
d1228110a2
commit
4e3ed2ec46
7 changed files with 131 additions and 197 deletions
12
README.md
12
README.md
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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+");
|
||||
|
||||
|
|
Loading…
Reference in a new issue