mirror of
https://github.com/fiso64/slsk-batchdl.git
synced 2024-12-22 14:32:40 +00:00
commit
This commit is contained in:
parent
e1859ad899
commit
2c4ee4309d
3 changed files with 170 additions and 125 deletions
|
@ -121,7 +121,7 @@ Options:
|
||||||
-r --reverse Download tracks in reverse order
|
-r --reverse Download tracks in reverse order
|
||||||
--name-format <format> Name format for downloaded tracks, e.g "{artist} - {title}"
|
--name-format <format> Name format for downloaded tracks, e.g "{artist} - {title}"
|
||||||
--fast-search Begin downloading as soon as a file satisfying the preferred
|
--fast-search Begin downloading as soon as a file satisfying the preferred
|
||||||
conditions is found. Increases chance to download bad files.
|
conditions is found. Higher chance to download wrong files.
|
||||||
--m3u <option> Create an m3u8 playlist file
|
--m3u <option> Create an m3u8 playlist file
|
||||||
'none': Do not create a playlist file
|
'none': Do not create a playlist file
|
||||||
'fails' (default): Write only failed downloads to the m3u
|
'fails' (default): Write only failed downloads to the m3u
|
||||||
|
|
|
@ -25,7 +25,7 @@ using SlDictionary = System.Collections.Concurrent.ConcurrentDictionary<string,
|
||||||
// --on-complete
|
// --on-complete
|
||||||
// --artist-col, --title-col, --album-col, --length-col, --yt-desc-col, --yt-id-col, --album-track-count-col
|
// --artist-col, --title-col, --album-col, --length-col, --yt-desc-col, --yt-id-col, --album-track-count-col
|
||||||
// --input-type, --login, --random-login, --no-modify-share-count --fast-search-delay,
|
// --input-type, --login, --random-login, --no-modify-share-count --fast-search-delay,
|
||||||
// --fails-to-deprioritize (=1), --fails-to-ignore (=2)
|
// --fails-to-deprioritize (=1), --fails-to-ignore (=2), --invalid-replace-str
|
||||||
// --cond, --pref, --danger-words, --pref-danger-words, --strict-title, --strict-artist, --strict-album
|
// --cond, --pref, --danger-words, --pref-danger-words, --strict-title, --strict-artist, --strict-album
|
||||||
// --fast-search-delay, --fast-search-min-up-speed
|
// --fast-search-delay, --fast-search-min-up-speed
|
||||||
// --min-album-track-count, --max-album-track-count, --extract-max-track-count
|
// --min-album-track-count, --max-album-track-count, --extract-max-track-count
|
||||||
|
@ -97,6 +97,7 @@ static class Program
|
||||||
static string input = "";
|
static string input = "";
|
||||||
static bool preciseSkip = true;
|
static bool preciseSkip = true;
|
||||||
static string nameFormat = "";
|
static string nameFormat = "";
|
||||||
|
static string invalidReplaceStr = " ";
|
||||||
static bool skipNotFound = false;
|
static bool skipNotFound = false;
|
||||||
static bool desperateSearch = false;
|
static bool desperateSearch = false;
|
||||||
static bool noRemoveSpecialChars = false;
|
static bool noRemoveSpecialChars = false;
|
||||||
|
@ -168,7 +169,7 @@ static class Program
|
||||||
"\n Provide a --youtube-key to include unavailabe uploads." +
|
"\n Provide a --youtube-key to include unavailabe uploads." +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\n Path to a local CSV file: Use a csv file containing track" +
|
"\n Path to a local CSV file: Use a csv file containing track" +
|
||||||
"\n info to download. The names of the columns should be Artist, " +
|
"\n info to download. The names of the columns should be Artist," +
|
||||||
"\n Title, Album, Length. Only the title or album column is" +
|
"\n Title, Album, Length. Only the title or album column is" +
|
||||||
"\n required, but extra info may improve search results." +
|
"\n required, but extra info may improve search results." +
|
||||||
"\n" +
|
"\n" +
|
||||||
|
@ -190,7 +191,7 @@ static class Program
|
||||||
"\n -r --reverse Download tracks in reverse order" +
|
"\n -r --reverse Download tracks in reverse order" +
|
||||||
"\n --name-format <format> Name format for downloaded tracks, e.g \"{artist} - {title}\"" +
|
"\n --name-format <format> Name format for downloaded tracks, e.g \"{artist} - {title}\"" +
|
||||||
"\n --fast-search Begin downloading as soon as a file satisfying the preferred" +
|
"\n --fast-search Begin downloading as soon as a file satisfying the preferred" +
|
||||||
"\n conditions is found. Increases chance to download bad files." +
|
"\n conditions is found. Higher chance to download wrong files." +
|
||||||
"\n --m3u <option> Create an m3u8 playlist file" +
|
"\n --m3u <option> Create an m3u8 playlist file" +
|
||||||
"\n 'none': Do not create a playlist file" +
|
"\n 'none': Do not create a playlist file" +
|
||||||
"\n 'fails' (default): Write only failed downloads to the m3u" +
|
"\n 'fails' (default): Write only failed downloads to the m3u" +
|
||||||
|
@ -489,6 +490,9 @@ static class Program
|
||||||
case "--name-format":
|
case "--name-format":
|
||||||
nameFormat = args[++i];
|
nameFormat = args[++i];
|
||||||
break;
|
break;
|
||||||
|
case "--invalid-replace-str":
|
||||||
|
invalidReplaceStr = args[++i];
|
||||||
|
break;
|
||||||
case "--p":
|
case "--p":
|
||||||
case "--print":
|
case "--print":
|
||||||
string opt = args[++i];
|
string opt = args[++i];
|
||||||
|
@ -705,7 +709,9 @@ static class Program
|
||||||
preferredCond.Formats = args[++i].Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
preferredCond.Formats = args[++i].Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||||
break;
|
break;
|
||||||
case "--plt":
|
case "--plt":
|
||||||
|
case "--pref-tolerance":
|
||||||
case "--pref-length-tol":
|
case "--pref-length-tol":
|
||||||
|
case "--pref-length-tolerance":
|
||||||
preferredCond.LengthTolerance = int.Parse(args[++i]);
|
preferredCond.LengthTolerance = int.Parse(args[++i]);
|
||||||
break;
|
break;
|
||||||
case "--pmbr":
|
case "--pmbr":
|
||||||
|
@ -758,7 +764,9 @@ static class Program
|
||||||
necessaryCond.Formats = args[++i].Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
necessaryCond.Formats = args[++i].Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||||
break;
|
break;
|
||||||
case "--lt":
|
case "--lt":
|
||||||
|
case "--tolerance":
|
||||||
case "--length-tol":
|
case "--length-tol":
|
||||||
|
case "--length-tolerance":
|
||||||
necessaryCond.LengthTolerance = int.Parse(args[++i]);
|
necessaryCond.LengthTolerance = int.Parse(args[++i]);
|
||||||
break;
|
break;
|
||||||
case "--mbr":
|
case "--mbr":
|
||||||
|
@ -973,7 +981,7 @@ static class Program
|
||||||
if (folderName == ".")
|
if (folderName == ".")
|
||||||
folderName = "";
|
folderName = "";
|
||||||
folderName = folderName.Replace("\\", "/");
|
folderName = folderName.Replace("\\", "/");
|
||||||
folderName = String.Join('/', folderName.Split("/").Select(x => ReplaceInvalidChars(x, " ").Trim()));
|
folderName = String.Join('/', folderName.Split("/").Select(x => x.ReplaceInvalidChars(invalidReplaceStr).Trim()));
|
||||||
folderName = folderName.Replace('/', Path.DirectorySeparatorChar);
|
folderName = folderName.Replace('/', Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
outputFolder = Path.Combine(parentFolder, folderName);
|
outputFolder = Path.Combine(parentFolder, folderName);
|
||||||
|
@ -1049,7 +1057,7 @@ static class Program
|
||||||
if (album || aggregate)
|
if (album || aggregate)
|
||||||
trackLists = TrackLists.FromFlatList(trackLists.Flattened().ToList(), aggregate, album);
|
trackLists = TrackLists.FromFlatList(trackLists.Flattened().ToList(), aggregate, album);
|
||||||
|
|
||||||
defaultFolderName = ReplaceInvalidChars(name, " ");
|
defaultFolderName = name.ReplaceInvalidChars(invalidReplaceStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1144,7 +1152,7 @@ static class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
defaultFolderName = ReplaceInvalidChars(playlistName, " ");
|
defaultFolderName = playlistName.ReplaceInvalidChars(invalidReplaceStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1179,7 +1187,7 @@ static class Program
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultFolderName = ReplaceInvalidChars(track.ToString(true), " ").Trim();
|
defaultFolderName = track.ToString(true).ReplaceInvalidChars(invalidReplaceStr).Trim();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -1243,7 +1251,7 @@ static class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aggregate || isAlbum || album)
|
if (aggregate || isAlbum || album)
|
||||||
defaultFolderName = ReplaceInvalidChars(music.ToString(true), " ").Trim();
|
defaultFolderName = music.ToString(true).ReplaceInvalidChars(invalidReplaceStr).Trim();
|
||||||
else
|
else
|
||||||
defaultFolderName = ".";
|
defaultFolderName = ".";
|
||||||
}
|
}
|
||||||
|
@ -1255,8 +1263,8 @@ static class Program
|
||||||
{
|
{
|
||||||
var (list, type, source) = trackLists.lists[i];
|
var (list, type, source) = trackLists.lists[i];
|
||||||
|
|
||||||
List<Track> existing = new List<Track>();
|
var existing = new List<Track>();
|
||||||
List<Track> notFound = new List<Track>();
|
var notFound = new List<Track>();
|
||||||
|
|
||||||
if (skipNotFound)
|
if (skipNotFound)
|
||||||
{
|
{
|
||||||
|
@ -1382,6 +1390,11 @@ static class Program
|
||||||
{
|
{
|
||||||
track.ArtistMaybeWrong = true;
|
track.ArtistMaybeWrong = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
track.Artist = track.Artist.Trim();
|
||||||
|
track.Album = track.Album.Trim();
|
||||||
|
track.Title = track.Title.Trim();
|
||||||
|
|
||||||
return track;
|
return track;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1936,7 +1949,7 @@ static class Program
|
||||||
if (downloading == 0 && !searchEnded)
|
if (downloading == 0 && !searchEnded)
|
||||||
{
|
{
|
||||||
downloading = 1;
|
downloading = 1;
|
||||||
var (r, f) = fsResults.ArgMax(x => x.Value.Item1.UploadSpeed).Value;
|
var (r, f) = fsResults.MaxBy(x => x.Value.Item1.UploadSpeed).Value;
|
||||||
saveFilePath = GetSavePath(f.Filename);
|
saveFilePath = GetSavePath(f.Filename);
|
||||||
fsUser = r.Username;
|
fsUser = r.Username;
|
||||||
fsFile = f.Filename;
|
fsFile = f.Filename;
|
||||||
|
@ -1955,7 +1968,9 @@ static class Program
|
||||||
if (fastSearch && !debugDisableDownload && userSuccessCount.GetValueOrDefault(r.Username, 0) > deprioritizeOn)
|
if (fastSearch && !debugDisableDownload && userSuccessCount.GetValueOrDefault(r.Username, 0) > deprioritizeOn)
|
||||||
{
|
{
|
||||||
var f = r.Files.First();
|
var f = r.Files.First();
|
||||||
if (r.HasFreeUploadSlot && r.UploadSpeed/1024.0/1024.0 >= fastSearchMinUpSpeed && preferredCond.FileSatisfies(f, track, r))
|
|
||||||
|
if (r.HasFreeUploadSlot && r.UploadSpeed/1024.0/1024.0 >= fastSearchMinUpSpeed
|
||||||
|
&& BracketCheck(track, InferTrack(f.Filename, track)) && preferredCond.FileSatisfies(f, track, r))
|
||||||
{
|
{
|
||||||
fsResults.TryAdd(r.Username + "\\" + f.Filename, (r, f));
|
fsResults.TryAdd(r.Username + "\\" + f.Filename, (r, f));
|
||||||
if (Interlocked.Exchange(ref fsResultsStarted, 1) == 0)
|
if (Interlocked.Exchange(ref fsResultsStarted, 1) == 0)
|
||||||
|
@ -2030,9 +2045,11 @@ static class Program
|
||||||
if (debugDisableDownload)
|
if (debugDisableDownload)
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
Console.WriteLine();
|
||||||
foreach (var (response, file) in orderedResults) {
|
foreach (var (response, file) in orderedResults) {
|
||||||
Console.WriteLine(DisplayString(track, file, response,
|
Console.WriteLine(DisplayString(track, file, response,
|
||||||
(printResultsFull ? necessaryCond : null), (printResultsFull ? preferredCond : null), printResultsFull, infoFirst: true));
|
printResultsFull ? necessaryCond : null, printResultsFull ? preferredCond : null,
|
||||||
|
fullpath: printResultsFull, infoFirst: true, showSpeed: printResultsFull));
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
WriteLine($"Total: {count}\n", ConsoleColor.Yellow);
|
WriteLine($"Total: {count}\n", ConsoleColor.Yellow);
|
||||||
|
@ -2137,7 +2154,7 @@ static class Program
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nameFormat != "" && !useYtdlp)
|
if (nameFormat != "")
|
||||||
saveFilePath = ApplyNamingFormat(saveFilePath, track);
|
saveFilePath = ApplyNamingFormat(saveFilePath, track);
|
||||||
|
|
||||||
return saveFilePath;
|
return saveFilePath;
|
||||||
|
@ -2186,10 +2203,12 @@ static class Program
|
||||||
|
|
||||||
if (debugDisableDownload && !debugPrintTracks)
|
if (debugDisableDownload && !debugPrintTracks)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine();
|
||||||
foreach (var (response, file) in orderedResults)
|
foreach (var (response, file) in orderedResults)
|
||||||
{
|
{
|
||||||
Console.WriteLine(DisplayString(track, file, response,
|
Console.WriteLine(DisplayString(track, file, response,
|
||||||
(printResultsFull ? necessaryCond : null), (printResultsFull ? preferredCond : null), printResultsFull, infoFirst: true));
|
printResultsFull ? necessaryCond : null, printResultsFull ? preferredCond : null,
|
||||||
|
fullpath: printResultsFull, infoFirst: true, showSpeed: printResultsFull));
|
||||||
}
|
}
|
||||||
WriteLine($"Total: {orderedResults.Count()}\n", ConsoleColor.Yellow);
|
WriteLine($"Total: {orderedResults.Count()}\n", ConsoleColor.Yellow);
|
||||||
return default;
|
return default;
|
||||||
|
@ -2419,38 +2438,28 @@ static class Program
|
||||||
useInfer = false;
|
useInfer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<string, (Track, int)>? result = null;
|
Dictionary<string, (Track, int)>? infTracksAndCounts = null;
|
||||||
if (useInfer)
|
if (useInfer)
|
||||||
{
|
{
|
||||||
var equivalentFiles = EquivalentFiles(track, results.Select(x => x.Value), 1);
|
var equivalentFiles = EquivalentFiles(track, results.Select(x => x.Value), 1);
|
||||||
result = 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(
|
.ToSafeDictionary(x => $"{x.Username}\\{x.Filename}", y => (y.Item1, y.Count));
|
||||||
x => $"{x.Username}\\{x.Filename}",
|
|
||||||
x => (x.Item1, x.Count));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(Track, int) infTrack((SearchResponse response, Soulseek.File file) x)
|
(Track, int) inferredTrack((SearchResponse response, Soulseek.File file) x)
|
||||||
{
|
{
|
||||||
string key = $"{x.response.Username}\\{x.file.Filename}";
|
string key = $"{x.response.Username}\\{x.file.Filename}";
|
||||||
if (result != null && result.ContainsKey(key))
|
if (infTracksAndCounts != null && infTracksAndCounts.ContainsKey(key))
|
||||||
return result[key];
|
return infTracksAndCounts[key];
|
||||||
return (new Track(), 0);
|
return (new Track(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bracketCheck((SearchResponse response, Soulseek.File file) x)
|
|
||||||
{
|
|
||||||
Track inferredTrack = infTrack(x).Item1;
|
|
||||||
string t1 = track.Title.RemoveFt().Replace('[', '(');
|
|
||||||
string t2 = inferredTrack.Title.RemoveFt().Replace('[', '(');
|
|
||||||
return track.ArtistMaybeWrong || t1.Contains('(') || !t2.Contains('(');
|
|
||||||
}
|
|
||||||
|
|
||||||
int levenshtein((SearchResponse response, Soulseek.File file) x)
|
int levenshtein((SearchResponse response, Soulseek.File file) x)
|
||||||
{
|
{
|
||||||
Track inferredTrack = infTrack(x).Item1;
|
Track t = inferredTrack(x).Item1;
|
||||||
string t1 = track.Title.ReplaceInvalidChars("").Replace(" ", "").Replace("_", "").RemoveFt().ToLower();
|
string t1 = track.Title.RemoveFt().ReplaceSpecialChars("").Replace(" ", "").Replace("_", "").ToLower();
|
||||||
string t2 = inferredTrack.Title.ReplaceInvalidChars("").Replace(" ", "").Replace("_", "").RemoveFt().ToLower();
|
string t2 = t.Title.RemoveFt().ReplaceSpecialChars("").Replace(" ", "").Replace("_", "").ToLower();
|
||||||
return Utils.Levenshtein(t1, t2);
|
return Utils.Levenshtein(t1, t2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2461,7 +2470,7 @@ static class Program
|
||||||
.ThenByDescending(x => necessaryCond.FileSatisfies(x.file, track, x.response))
|
.ThenByDescending(x => necessaryCond.FileSatisfies(x.file, track, x.response))
|
||||||
.ThenByDescending(x => preferredCond.BannedUsersSatisfies(x.response))
|
.ThenByDescending(x => preferredCond.BannedUsersSatisfies(x.response))
|
||||||
.ThenByDescending(x => (x.file.Length != null && x.file.Length > 0) || preferredCond.AcceptNoLength)
|
.ThenByDescending(x => (x.file.Length != null && x.file.Length > 0) || preferredCond.AcceptNoLength)
|
||||||
.ThenByDescending(x => !useBracketCheck || bracketCheck(x)) // deprioritize result if it contains '(' or '[' and the title does not (avoid remixes)
|
.ThenByDescending(x => !useBracketCheck || BracketCheck(track, inferredTrack(x).Item1)) // deprioritize result if it contains '(' or '[' and the title does not (avoid remixes)
|
||||||
.ThenByDescending(x => preferredCond.StrictTitleSatisfies(x.file.Filename, track.Title))
|
.ThenByDescending(x => preferredCond.StrictTitleSatisfies(x.file.Filename, track.Title))
|
||||||
.ThenByDescending(x => preferredCond.LengthToleranceSatisfies(x.file, track.Length))
|
.ThenByDescending(x => preferredCond.LengthToleranceSatisfies(x.file, track.Length))
|
||||||
.ThenByDescending(x => preferredCond.FormatSatisfies(x.file.Filename))
|
.ThenByDescending(x => preferredCond.FormatSatisfies(x.file.Filename))
|
||||||
|
@ -2473,14 +2482,36 @@ static class Program
|
||||||
.ThenByDescending(x => albumMode || FileConditions.StrictString(x.file.Filename, track.Title))
|
.ThenByDescending(x => albumMode || FileConditions.StrictString(x.file.Filename, track.Title))
|
||||||
.ThenByDescending(x => !albumMode || FileConditions.StrictString(GetDirectoryNameSlsk(x.file.Filename), track.Album))
|
.ThenByDescending(x => !albumMode || FileConditions.StrictString(GetDirectoryNameSlsk(x.file.Filename), track.Album))
|
||||||
.ThenByDescending(x => FileConditions.StrictString(x.file.Filename, track.Artist, boundarySkipWs: false))
|
.ThenByDescending(x => FileConditions.StrictString(x.file.Filename, track.Artist, boundarySkipWs: false))
|
||||||
.ThenByDescending(x => !useLevenshtein || levenshtein(x) <= 5) // sorts by the distance between the track title and the inferred title of the search result
|
.ThenByDescending(x => useInfer ? inferredTrack(x).Item2 : 0) // sorts by the number of occurences of this track
|
||||||
.ThenByDescending(x => x.response.UploadSpeed / 1024 / 300)
|
.ThenByDescending(x => x.response.UploadSpeed / 1024 / 350)
|
||||||
.ThenByDescending(x => (x.file.BitRate ?? 0) / 70)
|
.ThenByDescending(x => (x.file.BitRate ?? 0) / 80)
|
||||||
.ThenByDescending(x => useInfer ? infTrack(x).Item2 : 0) // sorts by the number of occurences of this track
|
.ThenByDescending(x => useLevenshtein ? levenshtein(x) / 5 : 0) // sorts by the distance between the track title and the inferred title of the search result
|
||||||
.ThenByDescending(x => random.Next());
|
.ThenByDescending(x => random.Next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool BracketCheck(Track track, Track other)
|
||||||
|
{
|
||||||
|
string t1 = track.Title.RemoveFt().Replace('[', '(');
|
||||||
|
if (t1.Contains('('))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
string t2 = other.Title.RemoveFt().Replace('[', '(');
|
||||||
|
if (!t2.Contains('('))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
string ar = track.Artist.Replace('[', '(');
|
||||||
|
if (ar.Contains('(') && !t2.Replace(ar, "").Contains('('))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
string al = track.Album.Replace('[', '(');
|
||||||
|
if (al.Contains('(') && !t2.Replace(al, "").Contains('('))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static async Task RunSearches(Track track, SlDictionary results, Func<int, FileConditions, FileConditions, SearchOptions> getSearchOptions,
|
static async Task RunSearches(Track track, SlDictionary results, Func<int, FileConditions, FileConditions, SearchOptions> getSearchOptions,
|
||||||
Action<SearchResponse> responseHandler, CancellationToken ct, Action? onSearch = null)
|
Action<SearchResponse> responseHandler, CancellationToken ct, Action? onSearch = null)
|
||||||
{
|
{
|
||||||
|
@ -2697,13 +2728,7 @@ static class Program
|
||||||
if (!parts[0].ContainsIgnoreCase(aname) || !parts[1].ContainsIgnoreCase(tname))
|
if (!parts[0].ContainsIgnoreCase(aname) || !parts[1].ContainsIgnoreCase(tname))
|
||||||
{
|
{
|
||||||
t.ArtistMaybeWrong = true;
|
t.ArtistMaybeWrong = true;
|
||||||
//if (!maybeRemix && parts[0].ContainsIgnoreCase(tname) && parts[1].ContainsIgnoreCase(aname))
|
|
||||||
//{
|
|
||||||
// t.ArtistName = realParts[1];
|
|
||||||
// t.TrackTitle = realParts[0];
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (parts.Length == 3)
|
else if (parts.Length == 3)
|
||||||
{
|
{
|
||||||
|
@ -2752,12 +2777,57 @@ static class Program
|
||||||
|
|
||||||
t.Title = parts[2];
|
t.Title = parts[2];
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int artistPos = -1, titlePos = -1;
|
||||||
|
|
||||||
|
if (aname != "")
|
||||||
|
{
|
||||||
|
var s = parts.Select((p, i) => (p, i)).Where(x => x.p.ContainsIgnoreCase(aname));
|
||||||
|
if (s.Any())
|
||||||
|
{
|
||||||
|
artistPos = s.MinBy(x => Math.Abs(x.p.Length - aname.Length)).i;
|
||||||
|
if (artistPos != -1)
|
||||||
|
t.Artist = parts[artistPos];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tname != "")
|
||||||
|
{
|
||||||
|
var ss = parts.Select((p, i) => (p, i)).Where(x => x.i != artistPos && x.p.ContainsIgnoreCase(tname));
|
||||||
|
if (ss.Any())
|
||||||
|
{
|
||||||
|
titlePos = ss.MinBy(x => Math.Abs(x.p.Length - tname.Length)).i;
|
||||||
|
if (titlePos != -1)
|
||||||
|
t.Title = parts[titlePos];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (t.Title == "")
|
if (t.Title == "")
|
||||||
{
|
{
|
||||||
t.Title = fname;
|
t.Title = fname;
|
||||||
t.ArtistMaybeWrong = true;
|
t.ArtistMaybeWrong = true;
|
||||||
}
|
}
|
||||||
|
else if (t.Artist != "" && !t.Title.ContainsIgnoreCase(defaultTrack.Title) && !t.Artist.ContainsIgnoreCase(defaultTrack.Artist))
|
||||||
|
{
|
||||||
|
string[] x = { t.Artist, t.Album, t.Title };
|
||||||
|
|
||||||
|
var perm = (0, 1, 2);
|
||||||
|
(int, int, int)[] permutations = { (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0) };
|
||||||
|
|
||||||
|
foreach (var p in permutations)
|
||||||
|
{
|
||||||
|
if (x[p.Item1].ContainsIgnoreCase(defaultTrack.Artist) && x[p.Item3].ContainsIgnoreCase(defaultTrack.Title))
|
||||||
|
{
|
||||||
|
perm = p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Artist = x[perm.Item1];
|
||||||
|
t.Album = x[perm.Item2];
|
||||||
|
t.Title = x[perm.Item3];
|
||||||
|
}
|
||||||
|
|
||||||
t.Title = t.Title.RemoveFt();
|
t.Title = t.Title.RemoveFt();
|
||||||
t.Artist = t.Artist.RemoveFt();
|
t.Artist = t.Artist.RemoveFt();
|
||||||
|
@ -3172,7 +3242,7 @@ static class Program
|
||||||
fname = regexRemove != "" ? Regex.Replace(fname, regexRemove, "") : fname;
|
fname = regexRemove != "" ? Regex.Replace(fname, regexRemove, "") : fname;
|
||||||
fname = diacrRemove ? fname.RemoveDiacritics() : fname;
|
fname = diacrRemove ? fname.RemoveDiacritics() : fname;
|
||||||
fname = fname.Trim().RemoveConsecutiveWs();
|
fname = fname.Trim().RemoveConsecutiveWs();
|
||||||
tname = tname.Replace("_", " ").ReplaceInvalidChars(" ", true, false);
|
tname = tname.Replace("_", " ").ReplaceInvalidChars(" ", true, true);
|
||||||
tname = regexRemove != "" ? Regex.Replace(tname, regexRemove, "") : tname;
|
tname = regexRemove != "" ? Regex.Replace(tname, regexRemove, "") : tname;
|
||||||
tname = diacrRemove ? tname.RemoveDiacritics() : tname;
|
tname = diacrRemove ? tname.RemoveDiacritics() : tname;
|
||||||
tname = tname.Trim().RemoveConsecutiveWs();
|
tname = tname.Trim().RemoveConsecutiveWs();
|
||||||
|
@ -3451,7 +3521,7 @@ static class Program
|
||||||
static string GetSaveName(string sourceFname)
|
static string GetSaveName(string sourceFname)
|
||||||
{
|
{
|
||||||
string name = GetFileNameWithoutExtSlsk(sourceFname);
|
string name = GetFileNameWithoutExtSlsk(sourceFname);
|
||||||
return ReplaceInvalidChars(name, " ");
|
return name.ReplaceInvalidChars(invalidReplaceStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
static string GetAsPathSlsk(string fname)
|
static string GetAsPathSlsk(string fname)
|
||||||
|
@ -3541,7 +3611,7 @@ static class Program
|
||||||
TagLib.File? file = null;
|
TagLib.File? file = null;
|
||||||
|
|
||||||
try { file = TagLib.File.Create(filepath); }
|
try { file = TagLib.File.Create(filepath); }
|
||||||
catch { return filepath; }
|
catch { }
|
||||||
|
|
||||||
Regex regex = new Regex(@"(\{(?:\{??[^\{]*?\}))");
|
Regex regex = new Regex(@"(\{(?:\{??[^\{]*?\}))");
|
||||||
MatchCollection matches = regex.Matches(newName);
|
MatchCollection matches = regex.Matches(newName);
|
||||||
|
@ -3550,7 +3620,8 @@ static class Program
|
||||||
{
|
{
|
||||||
foreach (Match match in matches.Cast<Match>())
|
foreach (Match match in matches.Cast<Match>())
|
||||||
{
|
{
|
||||||
string inner = match.Groups[1].Value.Trim('{').Trim('}');
|
string inner = match.Groups[1].Value;
|
||||||
|
inner = inner.Substring(1, inner.Length - 2);
|
||||||
|
|
||||||
var options = inner.Split('|');
|
var options = inner.Split('|');
|
||||||
string chosenOpt = "";
|
string chosenOpt = "";
|
||||||
|
@ -3559,7 +3630,7 @@ static class Program
|
||||||
{
|
{
|
||||||
string[] parts = Regex.Split(opt, @"\([^\)]*\)");
|
string[] parts = Regex.Split(opt, @"\([^\)]*\)");
|
||||||
string[] result = parts.Where(part => !string.IsNullOrWhiteSpace(part)).ToArray();
|
string[] result = parts.Where(part => !string.IsNullOrWhiteSpace(part)).ToArray();
|
||||||
if (result.All(x => GetVarValue(x, file, track) != "")) {
|
if (result.All(x => GetVarValue(x, file, filepath, track) != "")) {
|
||||||
chosenOpt = opt;
|
chosenOpt = opt;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3568,10 +3639,11 @@ static class Program
|
||||||
chosenOpt = Regex.Replace(chosenOpt, @"\([^()]*\)|[^()]+", match =>
|
chosenOpt = Regex.Replace(chosenOpt, @"\([^()]*\)|[^()]+", match =>
|
||||||
{
|
{
|
||||||
if (match.Value.StartsWith("(") && match.Value.EndsWith(")"))
|
if (match.Value.StartsWith("(") && match.Value.EndsWith(")"))
|
||||||
return match.Value.Substring(1, match.Value.Length-2);
|
return match.Value.Substring(1, match.Value.Length-2).ReplaceInvalidChars(invalidReplaceStr, removeSlash: false);
|
||||||
else
|
else
|
||||||
return GetVarValue(match.Value, file, track);
|
return GetVarValue(match.Value, file, filepath, track).ReplaceInvalidChars(invalidReplaceStr);
|
||||||
});
|
});
|
||||||
|
|
||||||
string old = match.Groups[1].Value;
|
string old = match.Groups[1].Value;
|
||||||
old = old.StartsWith("{{") ? old.Substring(1) : old;
|
old = old.StartsWith("{{") ? old.Substring(1) : old;
|
||||||
newName = newName.Replace(old, chosenOpt);
|
newName = newName.Replace(old, chosenOpt);
|
||||||
|
@ -3580,15 +3652,14 @@ static class Program
|
||||||
matches = regex.Matches(newName);
|
matches = regex.Matches(newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (newName != format)
|
if (newName != format)
|
||||||
{
|
{
|
||||||
string directory = Path.GetDirectoryName(filepath);
|
string directory = Path.GetDirectoryName(filepath) ?? "";
|
||||||
string dirsep = Path.DirectorySeparatorChar.ToString();
|
|
||||||
string extension = Path.GetExtension(filepath);
|
string extension = Path.GetExtension(filepath);
|
||||||
newName = newName.Replace(new string[] { "/", "\\" }, dirsep);
|
char dirsep = Path.DirectorySeparatorChar;
|
||||||
|
newName = newName.Replace('/', dirsep);
|
||||||
var x = newName.Split(dirsep, StringSplitOptions.RemoveEmptyEntries);
|
var x = newName.Split(dirsep, StringSplitOptions.RemoveEmptyEntries);
|
||||||
newName = string.Join(dirsep, x.Select(x => ReplaceInvalidChars(x, " ")));
|
newName = string.Join(dirsep, x.Select(x => x.ReplaceInvalidChars(invalidReplaceStr)));
|
||||||
string newFilePath = Path.Combine(directory, newName + extension);
|
string newFilePath = Path.Combine(directory, newName + extension);
|
||||||
return newFilePath;
|
return newFilePath;
|
||||||
}
|
}
|
||||||
|
@ -3596,22 +3667,22 @@ static class Program
|
||||||
return filepath;
|
return filepath;
|
||||||
}
|
}
|
||||||
|
|
||||||
static string GetVarValue(string x, TagLib.File file, Track track)
|
static string GetVarValue(string x, TagLib.File? file, string filepath, Track track)
|
||||||
{
|
{
|
||||||
switch (x)
|
switch (x)
|
||||||
{
|
{
|
||||||
case "artist":
|
case "artist":
|
||||||
return file.Tag.FirstPerformer ?? "";
|
return file?.Tag.FirstPerformer ?? "";
|
||||||
case "artists":
|
case "artists":
|
||||||
return string.Join(" & ", file.Tag.Performers);
|
return file != null ? string.Join(" & ", file.Tag.Performers) : "";
|
||||||
case "albumartist":
|
case "albumartist":
|
||||||
return file.Tag.FirstAlbumArtist ?? "";
|
return file?.Tag.FirstAlbumArtist ?? "";
|
||||||
case "albumartists":
|
case "albumartists":
|
||||||
return string.Join(" & ", file.Tag.AlbumArtists);
|
return file != null ? string.Join(" & ", file.Tag.AlbumArtists) : "";
|
||||||
case "title":
|
case "title":
|
||||||
return file.Tag.Title ?? "";
|
return file?.Tag.Title ?? "";
|
||||||
case "album":
|
case "album":
|
||||||
return file.Tag.Album ?? "";
|
return file?.Tag.Album ?? "";
|
||||||
case "sartist":
|
case "sartist":
|
||||||
case "sartists":
|
case "sartists":
|
||||||
return track.Artist;
|
return track.Artist;
|
||||||
|
@ -3620,13 +3691,13 @@ static class Program
|
||||||
case "salbum":
|
case "salbum":
|
||||||
return track.Album;
|
return track.Album;
|
||||||
case "year":
|
case "year":
|
||||||
return file.Tag.Year.ToString() ?? "";
|
return file?.Tag.Year.ToString() ?? "";
|
||||||
case "track":
|
case "track":
|
||||||
return file.Tag.Track.ToString("D2") ?? "";
|
return file?.Tag.Track.ToString("D2") ?? "";
|
||||||
case "disc":
|
case "disc":
|
||||||
return file.Tag.Disc.ToString() ?? "";
|
return file?.Tag.Disc.ToString() ?? "";
|
||||||
case "filename":
|
case "filename":
|
||||||
return Path.GetFileNameWithoutExtension(file.Name);
|
return Path.GetFileNameWithoutExtension(filepath);
|
||||||
case "foldername":
|
case "foldername":
|
||||||
return defaultFolderName;
|
return defaultFolderName;
|
||||||
default:
|
default:
|
||||||
|
@ -4078,28 +4149,8 @@ static class Program
|
||||||
return totalSeconds;
|
return totalSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
static string ReplaceInvalidChars(this string str, string replaceStr, bool windows = false, bool removeSlash = true)
|
|
||||||
{
|
|
||||||
char[] invalidChars = Path.GetInvalidFileNameChars();
|
|
||||||
if (windows)
|
|
||||||
invalidChars = new char[] { ':', '|', '?', '>', '<', '*', '"', '/', '\\' };
|
|
||||||
if (!removeSlash)
|
|
||||||
invalidChars = invalidChars.Where(c => c != '/' && c != '\\').ToArray();
|
|
||||||
foreach (char c in invalidChars)
|
|
||||||
str = str.Replace(c.ToString(), replaceStr);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
static string ReplaceSpecialChars(this string str, string replaceStr)
|
|
||||||
{
|
|
||||||
string special = ";:'\"|?!<>*/\\[]{}()-–—&%^$#@+=`~_";
|
|
||||||
foreach (char c in special)
|
|
||||||
str = str.Replace(c.ToString(), replaceStr);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
static string DisplayString(Track t, Soulseek.File? file=null, SearchResponse? response=null, FileConditions? nec=null,
|
static string DisplayString(Track t, Soulseek.File? file=null, SearchResponse? response=null, FileConditions? nec=null,
|
||||||
FileConditions? pref=null, bool fullpath=false, string customPath="", bool infoFirst=false, bool showUser=true)
|
FileConditions? pref=null, bool fullpath=false, string customPath="", bool infoFirst=false, bool showUser=true, bool showSpeed=false)
|
||||||
{
|
{
|
||||||
if (file == null)
|
if (file == null)
|
||||||
return t.ToString();
|
return t.ToString();
|
||||||
|
@ -4108,18 +4159,19 @@ static class Program
|
||||||
string bitRate = file.BitRate.HasValue ? $"{file.BitRate}kbps" : "";
|
string bitRate = file.BitRate.HasValue ? $"{file.BitRate}kbps" : "";
|
||||||
string fileSize = $"{file.Size / (float)(1024 * 1024):F1}MB";
|
string fileSize = $"{file.Size / (float)(1024 * 1024):F1}MB";
|
||||||
string user = showUser && response?.Username != null ? response.Username + "\\" : "";
|
string user = showUser && response?.Username != null ? response.Username + "\\" : "";
|
||||||
|
string speed = showSpeed && response?.Username != null ? $"({response.UploadSpeed / 1024.0 / 1024.0:F2}MB/s) " : "";
|
||||||
string fname = fullpath ? file.Filename : (showUser ? "..\\" : "") + (customPath == "" ? GetFileNameSlsk(file.Filename) : customPath);
|
string fname = fullpath ? file.Filename : (showUser ? "..\\" : "") + (customPath == "" ? GetFileNameSlsk(file.Filename) : customPath);
|
||||||
string length = Utils.IsMusicFile(file.Filename) ? (file.Length ?? -1).ToString() + "s" : "";
|
string length = Utils.IsMusicFile(file.Filename) ? (file.Length ?? -1).ToString() + "s" : "";
|
||||||
string displayText;
|
string displayText;
|
||||||
if (!infoFirst)
|
if (!infoFirst)
|
||||||
{
|
{
|
||||||
string info = string.Join('/', new string[] { length, sampleRate+bitRate, fileSize }.Where(value => value!=""));
|
string info = string.Join('/', new string[] { length, sampleRate+bitRate, fileSize }.Where(value => value!=""));
|
||||||
displayText = $"{user}{fname} [{info}]";
|
displayText = $"{speed}{user}{fname} [{info}]";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string info = string.Join('/', new string[] { length.PadRight(4), (sampleRate+bitRate).PadRight(8), fileSize.PadLeft(6) });
|
string info = string.Join('/', new string[] { length.PadRight(4), (sampleRate+bitRate).PadRight(8), fileSize.PadLeft(6) });
|
||||||
displayText = $"[{info}] {user}{fname}";
|
displayText = $"[{info}] {speed}{user}{fname}";
|
||||||
}
|
}
|
||||||
|
|
||||||
string necStr = nec != null ? $"nec:{nec.GetNotSatisfiedName(file, t, response)}, " : "";
|
string necStr = nec != null ? $"nec:{nec.GetNotSatisfiedName(file, t, response)}, " : "";
|
||||||
|
|
|
@ -76,6 +76,32 @@ public static class Utils
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ReplaceInvalidChars(this string str, string replaceStr, bool windows = false, bool removeSlash = true, bool alwaysRemoveSlash = false)
|
||||||
|
{
|
||||||
|
char[] invalidChars = Path.GetInvalidFileNameChars();
|
||||||
|
if (windows)
|
||||||
|
invalidChars = new char[] { ':', '|', '?', '>', '<', '*', '"', '/', '\\' };
|
||||||
|
if (!removeSlash && !alwaysRemoveSlash)
|
||||||
|
invalidChars = invalidChars.Where(c => c != '/' && c != '\\').ToArray();
|
||||||
|
if (alwaysRemoveSlash)
|
||||||
|
{
|
||||||
|
var x = invalidChars.ToList();
|
||||||
|
x.AddRange(new char[] { '/', '\\' });
|
||||||
|
invalidChars = x.ToArray();
|
||||||
|
}
|
||||||
|
foreach (char c in invalidChars)
|
||||||
|
str = str.Replace(c.ToString(), replaceStr);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ReplaceSpecialChars(this string str, string replaceStr)
|
||||||
|
{
|
||||||
|
string special = ";:'\"|?!<>*/\\[]{}()-–—&%^$#@+=`~_";
|
||||||
|
foreach (char c in special)
|
||||||
|
str = str.Replace(c.ToString(), replaceStr);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
public static string RemoveFt(this string str, bool removeParentheses = true, bool onlyIfNonempty = true)
|
public static string RemoveFt(this string str, bool removeParentheses = true, bool onlyIfNonempty = true)
|
||||||
{
|
{
|
||||||
string[] ftStrings = { "feat.", "ft." };
|
string[] ftStrings = { "feat.", "ft." };
|
||||||
|
@ -201,39 +227,6 @@ public static class Utils
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/35462350/13157140
|
|
||||||
public static T ArgMax<T, K>(this IEnumerable<T> source, Func<T, K> map, IComparer<K> comparer = null)
|
|
||||||
{
|
|
||||||
if (Object.ReferenceEquals(null, source))
|
|
||||||
throw new ArgumentNullException("source");
|
|
||||||
else if (Object.ReferenceEquals(null, map))
|
|
||||||
throw new ArgumentNullException("map");
|
|
||||||
|
|
||||||
T result = default(T);
|
|
||||||
K maxKey = default(K);
|
|
||||||
Boolean first = true;
|
|
||||||
|
|
||||||
if (null == comparer)
|
|
||||||
comparer = Comparer<K>.Default;
|
|
||||||
|
|
||||||
foreach (var item in source)
|
|
||||||
{
|
|
||||||
K key = map(item);
|
|
||||||
|
|
||||||
if (first || comparer.Compare(key, maxKey) > 0)
|
|
||||||
{
|
|
||||||
first = false;
|
|
||||||
maxKey = key;
|
|
||||||
result = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!first)
|
|
||||||
return result;
|
|
||||||
else
|
|
||||||
throw new ArgumentException("Can't compute ArgMax on empty sequence.", "source");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int Levenshtein(string source, string target)
|
public static int Levenshtein(string source, string target)
|
||||||
{
|
{
|
||||||
if (source.Length == 0)
|
if (source.Length == 0)
|
||||||
|
|
Loading…
Reference in a new issue