mirror of
https://github.com/fiso64/slsk-batchdl.git
synced 2024-12-22 14:32:40 +00:00
362 lines
15 KiB
C#
362 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Konsole;
|
|
using Soulseek;
|
|
using System.Text.RegularExpressions;
|
|
|
|
using Data;
|
|
using Enums;
|
|
|
|
using Directory = System.IO.Directory;
|
|
using File = System.IO.File;
|
|
using ProgressBar = Konsole.ProgressBar;
|
|
using SearchResponse = Soulseek.SearchResponse;
|
|
using SlFile = Soulseek.File;
|
|
using SlResponse = Soulseek.SearchResponse;
|
|
|
|
public static class Printing
|
|
{
|
|
static readonly object consoleLock = new();
|
|
|
|
public 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, bool showSpeed = false)
|
|
{
|
|
if (file == null)
|
|
return t.ToString();
|
|
|
|
string sampleRate = file.SampleRate.HasValue ? $"{(file.SampleRate.Value / 1000.0).Normalize()}kHz" : "";
|
|
string bitRate = file.BitRate.HasValue ? $"{file.BitRate}kbps" : "";
|
|
string fileSize = $"{file.Size / (float)(1024 * 1024):F1}MB";
|
|
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.Length == 0 ? Utils.GetFileNameSlsk(file.Filename) : customPath);
|
|
string length = Utils.IsMusicFile(file.Filename) ? (file.Length ?? -1).ToString() + "s" : "";
|
|
string displayText;
|
|
if (!infoFirst)
|
|
{
|
|
string info = string.Join('/', new string[] { length, sampleRate + bitRate, fileSize }.Where(value => value.Length > 0));
|
|
displayText = $"{speed}{user}{fname} [{info}]";
|
|
}
|
|
else
|
|
{
|
|
string info = string.Join('/', new string[] { length.PadRight(4), (sampleRate + bitRate).PadRight(8), fileSize.PadLeft(6) });
|
|
displayText = $"[{info}] {speed}{user}{fname}";
|
|
}
|
|
|
|
string necStr = nec != null ? $"nec:{nec.GetNotSatisfiedName(file, t, response)}, " : "";
|
|
string prefStr = pref != null ? $"prf:{pref.GetNotSatisfiedName(file, t, response)}" : "";
|
|
string cond = "";
|
|
if (nec != null || pref != null)
|
|
cond = $" ({(necStr + prefStr).TrimEnd(' ', ',')})";
|
|
|
|
return displayText + cond;
|
|
}
|
|
|
|
|
|
public static void PrintTracks(List<Track> tracks, int number = int.MaxValue, bool fullInfo = false, bool pathsOnly = false, bool showAncestors = true, bool infoFirst = false, bool showUser = true)
|
|
{
|
|
if (tracks.Count == 0)
|
|
return;
|
|
|
|
number = Math.Min(tracks.Count, number);
|
|
|
|
string ancestor = "";
|
|
|
|
if (!showAncestors)
|
|
ancestor = Utils.GreatestCommonDirectorySlsk(tracks.SelectMany(x => x.Downloads.Select(y => y.Item2.Filename)));
|
|
|
|
if (pathsOnly)
|
|
{
|
|
for (int i = 0; i < number; i++)
|
|
{
|
|
foreach (var x in tracks[i].Downloads)
|
|
{
|
|
if (ancestor.Length == 0)
|
|
Console.WriteLine(" " + DisplayString(tracks[i], x.Item2, x.Item1, infoFirst: infoFirst, showUser: showUser));
|
|
else
|
|
Console.WriteLine(" " + DisplayString(tracks[i], x.Item2, x.Item1, customPath: x.Item2.Filename.Replace(ancestor, "").TrimStart('\\'), infoFirst: infoFirst, showUser: showUser));
|
|
}
|
|
}
|
|
}
|
|
else if (!fullInfo)
|
|
{
|
|
for (int i = 0; i < number; i++)
|
|
{
|
|
Console.WriteLine($" {tracks[i]}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < number; i++)
|
|
{
|
|
if (!tracks[i].IsNotAudio)
|
|
{
|
|
Console.WriteLine($" Artist: {tracks[i].Artist}");
|
|
if (!string.IsNullOrEmpty(tracks[i].Title) || tracks[i].Type == TrackType.Normal)
|
|
Console.WriteLine($" Title: {tracks[i].Title}");
|
|
if (!string.IsNullOrEmpty(tracks[i].Album) || tracks[i].Type == TrackType.Album)
|
|
Console.WriteLine($" Album: {tracks[i].Album}");
|
|
if (tracks[i].Length > -1 || tracks[i].Type == TrackType.Normal)
|
|
Console.WriteLine($" Length: {tracks[i].Length}s");
|
|
if (!string.IsNullOrEmpty(tracks[i].DownloadPath))
|
|
Console.WriteLine($" Local path: {tracks[i].DownloadPath}");
|
|
if (!string.IsNullOrEmpty(tracks[i].URI))
|
|
Console.WriteLine($" URL/ID: {tracks[i].URI}");
|
|
if (tracks[i].Type != TrackType.Normal)
|
|
Console.WriteLine($" Type: {tracks[i].Type}");
|
|
if (!string.IsNullOrEmpty(tracks[i].Other))
|
|
Console.WriteLine($" Other: {tracks[i].Other}");
|
|
if (tracks[i].ArtistMaybeWrong)
|
|
Console.WriteLine($" Artist maybe wrong: {tracks[i].ArtistMaybeWrong}");
|
|
if (tracks[i].Downloads != null)
|
|
{
|
|
Console.WriteLine($" Shares: {tracks[i].Downloads.Count}");
|
|
foreach (var x in tracks[i].Downloads)
|
|
{
|
|
if (ancestor.Length == 0)
|
|
Console.WriteLine(" " + DisplayString(tracks[i], x.Item2, x.Item1, infoFirst: infoFirst, showUser: showUser));
|
|
else
|
|
Console.WriteLine(" " + DisplayString(tracks[i], x.Item2, x.Item1, customPath: x.Item2.Filename.Replace(ancestor, "").TrimStart('\\'), infoFirst: infoFirst, showUser: showUser));
|
|
}
|
|
if (tracks[i].Downloads?.Count > 0) Console.WriteLine();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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.Item2, x.Item1, infoFirst: infoFirst, showUser: showUser));
|
|
else
|
|
Console.WriteLine(" " + DisplayString(tracks[i], x.Item2, x.Item1, customPath: x.Item2.Filename.Replace(ancestor, "").TrimStart('\\'), infoFirst: infoFirst, showUser: showUser));
|
|
}
|
|
Console.WriteLine();
|
|
}
|
|
Console.WriteLine();
|
|
}
|
|
}
|
|
|
|
if (number < tracks.Count)
|
|
Console.WriteLine($" ... (etc)");
|
|
}
|
|
|
|
|
|
public static async Task PrintResults(TrackListEntry tle, List<Track> existing, List<Track> notFound)
|
|
{
|
|
await Program.InitClientAndUpdateIfNeeded();
|
|
|
|
if (tle.source.Type == TrackType.Normal)
|
|
{
|
|
await Search.SearchAndPrintResults(tle.list[0]);
|
|
}
|
|
else if (tle.source.Type == TrackType.Aggregate)
|
|
{
|
|
Console.WriteLine(new string('-', 60));
|
|
Console.WriteLine($"Results for aggregate {tle.source.ToString(true)}:");
|
|
PrintTracksTbd(tle.list[0].Where(t => t.State == TrackState.Initial).ToList(), existing, notFound, tle.source.Type);
|
|
}
|
|
else if (tle.source.Type == TrackType.Album)
|
|
{
|
|
Console.WriteLine(new string('-', 60));
|
|
|
|
if (!Config.I.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.I.noBrowseFolder)
|
|
Console.WriteLine("[Skipping full folder retrieval]");
|
|
|
|
foreach (var ls in tle.list)
|
|
{
|
|
PrintAlbum(ls);
|
|
|
|
if (!Config.I.printOption.HasFlag(PrintOption.Full))
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("No results.");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public static void PrintComplete(TrackLists trackLists)
|
|
{
|
|
var ls = trackLists.Flattened(true, true);
|
|
int successes = 0, fails = 0;
|
|
foreach (var x in ls)
|
|
{
|
|
if (x.State == TrackState.Downloaded)
|
|
successes++;
|
|
else if (x.State == TrackState.Failed)
|
|
fails++;
|
|
}
|
|
if (successes + fails > 1)
|
|
Console.WriteLine($"\nCompleted: {successes} succeeded, {fails} failed.");
|
|
}
|
|
|
|
|
|
public static void PrintTracksTbd(List<Track> toBeDownloaded, List<Track> existing, List<Track> notFound, TrackType type, bool summary = true)
|
|
{
|
|
if (type == TrackType.Normal && !Config.I.PrintTracks && toBeDownloaded.Count == 1 && existing.Count + notFound.Count == 0)
|
|
return;
|
|
|
|
string notFoundLastTime = notFound.Count > 0 ? $"{notFound.Count} not found" : "";
|
|
string alreadyExist = existing.Count > 0 ? $"{existing.Count} already exist" : "";
|
|
notFoundLastTime = alreadyExist.Length > 0 && notFoundLastTime.Length > 0 ? ", " + notFoundLastTime : notFoundLastTime;
|
|
string skippedTracks = alreadyExist.Length + notFoundLastTime.Length > 0 ? $" ({alreadyExist}{notFoundLastTime})" : "";
|
|
bool full = Config.I.printOption.HasFlag(PrintOption.Full);
|
|
bool allSkipped = existing.Count + notFound.Count > toBeDownloaded.Count;
|
|
|
|
if (summary && (type == TrackType.Normal || skippedTracks.Length > 0))
|
|
Console.WriteLine($"Downloading {toBeDownloaded.Count(x => !x.IsNotAudio)} tracks{skippedTracks}{(allSkipped ? '.' : ':')}");
|
|
|
|
if (toBeDownloaded.Count > 0)
|
|
{
|
|
bool showAll = type != TrackType.Normal || Config.I.PrintTracks || Config.I.PrintResults;
|
|
PrintTracks(toBeDownloaded, showAll ? int.MaxValue : 10, full, infoFirst: Config.I.PrintTracks);
|
|
|
|
if (full && (existing.Count > 0 || notFound.Count > 0))
|
|
Console.WriteLine("\n-----------------------------------------------\n");
|
|
}
|
|
|
|
if (Config.I.PrintTracks || Config.I.PrintResults)
|
|
{
|
|
if (existing.Count > 0)
|
|
{
|
|
Console.WriteLine($"\nThe following tracks already exist:");
|
|
PrintTracks(existing, fullInfo: full, infoFirst: Config.I.PrintTracks);
|
|
}
|
|
if (notFound.Count > 0)
|
|
{
|
|
Console.WriteLine($"\nThe following tracks were not found during a prior run:");
|
|
PrintTracks(notFound, fullInfo: full, infoFirst: Config.I.PrintTracks);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public static void PrintAlbum(List<Track> albumTracks)
|
|
{
|
|
if (albumTracks.Count == 0 && albumTracks[0].Downloads.Count == 0)
|
|
return;
|
|
|
|
var response = albumTracks[0].FirstResponse;
|
|
string userInfo = $"{response.Username} ({((float)response.UploadSpeed / (1024 * 1024)):F3}MB/s)";
|
|
var (parents, props) = FolderInfo(albumTracks.Select(x => x.FirstDownload));
|
|
|
|
WriteLine($"User : {userInfo}\nFolder: {parents}\nProps : {props}", ConsoleColor.White);
|
|
PrintTracks(albumTracks.ToList(), pathsOnly: true, showAncestors: false, showUser: false);
|
|
}
|
|
|
|
|
|
static (string parents, string props) FolderInfo(IEnumerable<SlFile> files)
|
|
{
|
|
string res = "";
|
|
int totalLengthInSeconds = files.Sum(f => f.Length ?? 0);
|
|
var sampleRates = files.Where(f => f.SampleRate.HasValue).Select(f => f.SampleRate.Value).OrderBy(r => r).ToList();
|
|
|
|
int? modeSampleRate = sampleRates.GroupBy(rate => rate).OrderByDescending(g => g.Count()).Select(g => (int?)g.Key).FirstOrDefault();
|
|
|
|
var bitRates = files.Where(f => f.BitRate.HasValue).Select(f => f.BitRate.Value).ToList();
|
|
double? meanBitrate = bitRates.Count > 0 ? (double?)bitRates.Average() : null;
|
|
|
|
double totalFileSizeInMB = files.Sum(f => f.Size) / (1024.0 * 1024.0);
|
|
|
|
TimeSpan totalTimeSpan = TimeSpan.FromSeconds(totalLengthInSeconds);
|
|
string totalLengthFormatted;
|
|
if (totalTimeSpan.TotalHours >= 1)
|
|
totalLengthFormatted = string.Format("{0}:{1:D2}:{2:D2}", (int)totalTimeSpan.TotalHours, totalTimeSpan.Minutes, totalTimeSpan.Seconds);
|
|
else
|
|
totalLengthFormatted = string.Format("{0:D2}:{1:D2}", totalTimeSpan.Minutes, totalTimeSpan.Seconds);
|
|
|
|
var mostCommonExtension = files.GroupBy(f => Utils.GetExtensionSlsk(f.Filename))
|
|
.OrderByDescending(g => Utils.IsMusicExtension(g.Key)).ThenByDescending(g => g.Count()).First().Key.TrimStart('.');
|
|
|
|
res = $"[{mostCommonExtension.ToUpper()} / {totalLengthFormatted}";
|
|
|
|
if (modeSampleRate.HasValue)
|
|
res += $" / {(modeSampleRate.Value / 1000.0).Normalize()} kHz";
|
|
|
|
if (meanBitrate.HasValue)
|
|
res += $" / {(int)meanBitrate.Value} kbps";
|
|
|
|
res += $" / {totalFileSizeInMB:F2} MB]";
|
|
|
|
string gcp = Utils.GreatestCommonDirectorySlsk(files.Select(x => x.Filename)).TrimEnd('\\');
|
|
int lastIndex = gcp.LastIndexOf('\\');
|
|
if (lastIndex != -1)
|
|
{
|
|
int secondLastIndex = gcp.LastIndexOf('\\', lastIndex - 1);
|
|
gcp = secondLastIndex == -1 ? gcp : gcp[(secondLastIndex + 1)..];
|
|
}
|
|
|
|
return (gcp, res);
|
|
}
|
|
|
|
|
|
public static void RefreshOrPrint(ProgressBar? progress, int current, string item, bool print = false, bool refreshIfOffscreen = false)
|
|
{
|
|
if (progress != null && !Console.IsOutputRedirected && (refreshIfOffscreen || progress.Y >= Console.WindowTop))
|
|
{
|
|
try { progress.Refresh(current, item); }
|
|
catch { }
|
|
}
|
|
else if ((Config.I.noProgress || Console.IsOutputRedirected) && print)
|
|
{
|
|
Console.WriteLine(item);
|
|
}
|
|
}
|
|
|
|
|
|
public static void WriteLine(string value, ConsoleColor color = ConsoleColor.Gray, bool safe = false, bool debugOnly = false)
|
|
{
|
|
if (debugOnly && !Config.I.debugInfo)
|
|
return;
|
|
if (!safe)
|
|
{
|
|
Console.ForegroundColor = color;
|
|
Console.WriteLine(value);
|
|
Console.ResetColor();
|
|
}
|
|
else
|
|
{
|
|
Program.skipUpdate = true;
|
|
lock (consoleLock)
|
|
{
|
|
Console.ForegroundColor = color;
|
|
Console.WriteLine(value);
|
|
Console.ResetColor();
|
|
}
|
|
|
|
Program.skipUpdate = false;
|
|
}
|
|
}
|
|
|
|
|
|
public static ProgressBar? GetProgressBar()
|
|
{
|
|
lock (consoleLock)
|
|
{
|
|
if (!Config.I.noProgress)
|
|
{
|
|
return new ProgressBar(PbStyle.SingleLine, 100, Console.WindowWidth - 10, character: ' ');
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|