diff --git a/src/N_m3u8DL-RE.Common/Log/CustomAnsiConsole.cs b/src/N_m3u8DL-RE.Common/Log/CustomAnsiConsole.cs new file mode 100644 index 0000000..417bcf8 --- /dev/null +++ b/src/N_m3u8DL-RE.Common/Log/CustomAnsiConsole.cs @@ -0,0 +1,92 @@ +using System.Text; +using System.Text.RegularExpressions; +using Spectre.Console; + +namespace N_m3u8DL_RE.Common.Log; + +public class NonAnsiWriter : TextWriter +{ + public override Encoding Encoding => Console.OutputEncoding; + + private string lastOut = ""; + + public override void Write(char value) + { + Console.Write(value); + } + + public override void Write(string value) + { + if (lastOut == value) + { + return; + } + lastOut = value; + RemoveAnsiEscapeSequences(value); + } + + private void RemoveAnsiEscapeSequences(string input) + { + // Use regular expression to remove ANSI escape sequences + string output = Regex.Replace(input, @"\x1B\[(\d+;?)+m", ""); + output = Regex.Replace(output, @"\[\??\d+[AKlh]", ""); + output = Regex.Replace(output,"[\r\n] +",""); + if (string.IsNullOrWhiteSpace(output)) + { + return; + } + Console.Write(output); + } +} + +/// +/// A console capable of writing ANSI escape sequences. +/// +public static class CustomAnsiConsole +{ + public static IAnsiConsole Console { get; set; } = AnsiConsole.Console; + + public static void InitConsole(bool forceAnsi, bool noAnsiColor) + { + if (forceAnsi) + { + var ansiConsoleSettings = new AnsiConsoleSettings(); + if (noAnsiColor) + { + ansiConsoleSettings.Out = new AnsiConsoleOutput(new NonAnsiWriter()); + } + + ansiConsoleSettings.Interactive = InteractionSupport.Yes; + ansiConsoleSettings.Ansi = AnsiSupport.Yes; + Console = AnsiConsole.Create(ansiConsoleSettings); + Console.Profile.Width = int.MaxValue; + } + else + { + var ansiConsoleSettings = new AnsiConsoleSettings(); + if (noAnsiColor) + { + ansiConsoleSettings.Out = new AnsiConsoleOutput(new NonAnsiWriter()); + } + Console = AnsiConsole.Create(ansiConsoleSettings); + } + } + + /// + /// Writes the specified markup to the console. + /// + /// The value to write. + public static void Markup(string value) + { + Console.Markup(value); + } + + /// + /// Writes the specified markup, followed by the current line terminator, to the console. + /// + /// The value to write. + public static void MarkupLine(string value) + { + Console.MarkupLine(value); + } +} \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Common/Log/Logger.cs b/src/N_m3u8DL-RE.Common/Log/Logger.cs index 897f086..eafbdc4 100644 --- a/src/N_m3u8DL-RE.Common/Log/Logger.cs +++ b/src/N_m3u8DL-RE.Common/Log/Logger.cs @@ -40,7 +40,11 @@ namespace N_m3u8DL_RE.Common.Log try { var logDir = Path.GetDirectoryName(Environment.ProcessPath) + "/Logs"; - if (!Directory.Exists(logDir)) { Directory.CreateDirectory(logDir); } + if (!Directory.Exists(logDir)) + { + Directory.CreateDirectory(logDir); + } + LogFilePath = Path.Combine(logDir, DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss-fff") + ".log"); //若文件存在则加序号 int index = 1; @@ -49,11 +53,12 @@ namespace N_m3u8DL_RE.Common.Log { LogFilePath = Path.Combine(Path.GetDirectoryName(LogFilePath)!, $"{fileName}-{index++}.log"); } + string now = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); string init = "LOG " + DateTime.Now.ToString("yyyy/MM/dd") + Environment.NewLine - + "Save Path: " + Path.GetDirectoryName(LogFilePath) + Environment.NewLine - + "Task Start: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + Environment.NewLine - + "Task CommandLine: " + Environment.CommandLine; + + "Save Path: " + Path.GetDirectoryName(LogFilePath) + Environment.NewLine + + "Task Start: " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + Environment.NewLine + + "Task CommandLine: " + Environment.CommandLine; init += $"{Environment.NewLine}{Environment.NewLine}"; File.WriteAllText(LogFilePath, init, Encoding.UTF8); } @@ -74,13 +79,14 @@ namespace N_m3u8DL_RE.Common.Log { if (subWrite == "") { - AnsiConsole.MarkupLine(write); + CustomAnsiConsole.MarkupLine(write); } else { - AnsiConsole.Markup(write); + CustomAnsiConsole.Markup(write); Console.WriteLine(subWrite); } + if (IsWriteFile && File.Exists(LogFilePath)) { var plain = write.RemoveMarkup() + subWrite.RemoveMarkup(); @@ -112,6 +118,7 @@ namespace N_m3u8DL_RE.Common.Log { data = VarsRepRegex().Replace(data, $"{ps[i]}", 1); } + return data; } @@ -202,6 +209,7 @@ namespace N_m3u8DL_RE.Common.Log { data = exception.ToString().EscapeMarkup(); } + ErrorMarkUp(data); } @@ -233,4 +241,4 @@ namespace N_m3u8DL_RE.Common.Log } } } -} +} \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.cs index dd50078..a97f0d4 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.cs +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.cs @@ -16,6 +16,7 @@ namespace N_m3u8DL_RE.Common.Resource public static string customRangeFound { get => GetText("customRangeFound"); } public static string customAdKeywordsFound { get => GetText("customAdKeywordsFound"); } public static string customRangeInvalid { get => GetText("customRangeInvalid"); } + public static string consoleRedirected { get => GetText("consoleRedirected"); } public static string autoBinaryMerge { get => GetText("autoBinaryMerge"); } public static string autoBinaryMerge2 { get => GetText("autoBinaryMerge2"); } public static string autoBinaryMerge3 { get => GetText("autoBinaryMerge3"); } @@ -55,6 +56,8 @@ namespace N_m3u8DL_RE.Common.Resource public static string cmd_customHLSKey { get => GetText("cmd_customHLSKey"); } public static string cmd_customHLSIv { get => GetText("cmd_customHLSIv"); } public static string cmd_Input { get => GetText("cmd_Input"); } + public static string cmd_forceAnsiConsole { get => GetText("cmd_forceAnsiConsole"); } + public static string cmd_noAnsiColor { get => GetText("cmd_noAnsiColor"); } public static string cmd_keys { get => GetText("cmd_keys"); } public static string cmd_keyText { get => GetText("cmd_keyText"); } public static string cmd_loadKeyFailed { get => GetText("cmd_loadKeyFailed"); } diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs index df1732f..5a8e3a8 100644 --- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs +++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs @@ -22,6 +22,18 @@ namespace N_m3u8DL_RE.Common.Resource zhTW: "即時解密已被強制關閉", enUS: "Real-time decryption has been disabled" ), + ["cmd_forceAnsiConsole"] = new TextContainer + ( + zhCN: "强制认定终端为支持ANSI且可交互的终端", + zhTW: "強制認定終端為支援ANSI且可交往的終端", + enUS: "Force assuming the terminal is ANSI-compatible and interactive" + ), + ["cmd_noAnsiColor"] = new TextContainer + ( + zhCN: "去除ANSI颜色", + zhTW: "關閉ANSI顏色", + enUS: "Remove ANSI colors" + ), ["customRangeWarn"] = new TextContainer ( zhCN: "请注意,自定义下载范围有时会导致音画不同步", @@ -46,6 +58,12 @@ namespace N_m3u8DL_RE.Common.Resource zhTW: "用戶自定義下載範圍:", enUS: "User customed range: " ), + ["consoleRedirected"] = new TextContainer + ( + zhCN: "输出被重定向, 将清除ANSI颜色", + zhTW: "輸出被重定向, 將清除ANSI顏色", + enUS: "Output is redirected, ANSI colors are cleared." + ), ["processImageSub"] = new TextContainer ( zhCN: "正在处理图形字幕", diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index 8654576..f9875e7 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -53,6 +53,8 @@ namespace N_m3u8DL_RE.CommandLine private readonly static Option AppendUrlParams = new(new string[] { "--append-url-params" }, description: ResString.cmd_appendUrlParams, getDefaultValue: () => false); private readonly static Option MP4RealTimeDecryption = new (new string[] { "--mp4-real-time-decryption" }, description: ResString.cmd_MP4RealTimeDecryption, getDefaultValue: () => false); private readonly static Option UseShakaPackager = new (new string[] { "--use-shaka-packager" }, description: ResString.cmd_useShakaPackager, getDefaultValue: () => false); + private readonly static Option ForceAnsiConsole = new(new string[] { "--force-ansi-console" }, description: ResString.cmd_forceAnsiConsole); + private readonly static Option NoAnsiColor = new(new string[] { "--no-ansi-color" }, description: ResString.cmd_noAnsiColor); private readonly static Option DecryptionBinaryPath = new(new string[] { "--decryption-binary-path" }, description: ResString.cmd_decryptionBinaryPath) { ArgumentHelpName = "PATH" }; private readonly static Option FFmpegBinaryPath = new(new string[] { "--ffmpeg-binary-path" }, description: ResString.cmd_ffmpegBinaryPath) { ArgumentHelpName = "PATH" }; private readonly static Option BaseUrl = new(new string[] { "--base-url" }, description: ResString.cmd_baseUrl); @@ -484,6 +486,8 @@ namespace N_m3u8DL_RE.CommandLine var option = new MyOption { Input = bindingContext.ParseResult.GetValueForArgument(Input), + ForceAnsiConsole = bindingContext.ParseResult.GetValueForOption(ForceAnsiConsole), + NoAnsiColor = bindingContext.ParseResult.GetValueForOption(NoAnsiColor), LogLevel = bindingContext.ParseResult.GetValueForOption(LogLevel), AutoSelect = bindingContext.ParseResult.GetValueForOption(AutoSelect), SkipMerge = bindingContext.ParseResult.GetValueForOption(SkipMerge), @@ -594,7 +598,7 @@ namespace N_m3u8DL_RE.CommandLine var rootCommand = new RootCommand(VERSION_INFO) { - Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount, + Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, ForceAnsiConsole, NoAnsiColor,AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount, BinaryMerge, UseFFmpegConcatDemuxer, DelAfterDone, NoDateInfo, NoLog, WriteMetaJson, AppendUrlParams, ConcurrentDownload, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix, FFmpegBinaryPath, LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption, diff --git a/src/N_m3u8DL-RE/CommandLine/MyOption.cs b/src/N_m3u8DL-RE/CommandLine/MyOption.cs index 1bb4478..9b926ce 100644 --- a/src/N_m3u8DL-RE/CommandLine/MyOption.cs +++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs @@ -85,6 +85,14 @@ namespace N_m3u8DL_RE.CommandLine /// public bool BinaryMerge { get; set; } /// + /// See: . + /// + public bool ForceAnsiConsole { get; set; } + /// + /// See: . + /// + public bool NoAnsiColor { get; set; } + /// /// See: . /// public bool UseFFmpegConcatDemuxer { get; set; } diff --git a/src/N_m3u8DL-RE/DownloadManager/HTTPLiveRecordManager.cs b/src/N_m3u8DL-RE/DownloadManager/HTTPLiveRecordManager.cs index 8bcab13..be6c39d 100644 --- a/src/N_m3u8DL-RE/DownloadManager/HTTPLiveRecordManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/HTTPLiveRecordManager.cs @@ -197,11 +197,11 @@ namespace N_m3u8DL_RE.DownloadManager ConcurrentDictionary SpeedContainerDic = new(); //速度计算 ConcurrentDictionary Results = new(); - var progress = AnsiConsole.Progress().AutoClear(true); + var progress = CustomAnsiConsole.Console.Progress().AutoClear(true); progress.AutoRefresh = DownloaderConfig.MyOptions.LogLevel != LogLevel.OFF; //进度条的列定义 - progress.Columns(new ProgressColumn[] + var progressColumns = new ProgressColumn[] { new TaskDescriptionColumn() { Alignment = Justify.Left }, new RecordingDurationColumn(RecordingDurDic), //时长显示 @@ -209,7 +209,12 @@ namespace N_m3u8DL_RE.DownloadManager new RecordingStatusColumn(), new DownloadSpeedColumn(SpeedContainerDic), //速度计算 new SpinnerColumn(), - }); + }; + if (DownloaderConfig.MyOptions.NoAnsiColor) + { + progressColumns = progressColumns.SkipLast(1).ToArray(); + } + progress.Columns(progressColumns); await progress.StartAsync(async ctx => { diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs index ed182b8..8ad42f4 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs @@ -628,12 +628,12 @@ namespace N_m3u8DL_RE.DownloadManager { ConcurrentDictionary SpeedContainerDic = new(); //速度计算 ConcurrentDictionary Results = new(); - - var progress = AnsiConsole.Progress().AutoClear(true); + + var progress = CustomAnsiConsole.Console.Progress().AutoClear(true); progress.AutoRefresh = DownloaderConfig.MyOptions.LogLevel != LogLevel.OFF; //进度条的列定义 - progress.Columns(new ProgressColumn[] + var progressColumns = new ProgressColumn[] { new TaskDescriptionColumn() { Alignment = Justify.Left }, new ProgressBarColumn(){ Width = 30 }, @@ -642,7 +642,12 @@ namespace N_m3u8DL_RE.DownloadManager new DownloadSpeedColumn(SpeedContainerDic), //速度计算 new RemainingTimeColumn(), new SpinnerColumn(), - }); + }; + if (DownloaderConfig.MyOptions.NoAnsiColor) + { + progressColumns = progressColumns.SkipLast(1).ToArray(); + } + progress.Columns(progressColumns); if (DownloaderConfig.MyOptions.MP4RealTimeDecryption && !DownloaderConfig.MyOptions.UseShakaPackager && DownloaderConfig.MyOptions.Keys != null && DownloaderConfig.MyOptions.Keys.Length > 0) diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs index 2458923..4ab0b66 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs @@ -801,11 +801,11 @@ namespace N_m3u8DL_RE.DownloadManager await StreamingUtil.WriteMasterListAsync(SelectedSteams, saveName, saveDir); }*/ - var progress = AnsiConsole.Progress().AutoClear(true); + var progress = CustomAnsiConsole.Console.Progress().AutoClear(true); progress.AutoRefresh = DownloaderConfig.MyOptions.LogLevel != LogLevel.OFF; - + //进度条的列定义 - progress.Columns(new ProgressColumn[] + var progressColumns = new ProgressColumn[] { new TaskDescriptionColumn() { Alignment = Justify.Left }, new RecordingDurationColumn(RecordedDurDic, RefreshedDurDic), //时长显示 @@ -813,7 +813,12 @@ namespace N_m3u8DL_RE.DownloadManager new PercentageColumn(), new DownloadSpeedColumn(SpeedContainerDic), //速度计算 new SpinnerColumn(), - }); + }; + if (DownloaderConfig.MyOptions.NoAnsiColor) + { + progressColumns = progressColumns.SkipLast(1).ToArray(); + } + progress.Columns(progressColumns); await progress.StartAsync(async ctx => { diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs index b522fb5..b0dac1e 100644 --- a/src/N_m3u8DL-RE/Program.cs +++ b/src/N_m3u8DL-RE/Program.cs @@ -25,6 +25,7 @@ namespace N_m3u8DL_RE Console.CancelKeyPress += Console_CancelKeyPress; ServicePointManager.DefaultConnectionLimit = 1024; try { Console.CursorVisible = true; } catch { } + string loc = "en-US"; string currLoc = Thread.CurrentThread.CurrentUICulture.Name; if (currLoc == "zh-CN" || currLoc == "zh-SG") loc = "zh-CN"; @@ -70,6 +71,14 @@ namespace N_m3u8DL_RE static async Task DoWorkAsync(MyOption option) { + + if (Console.IsOutputRedirected || Console.IsErrorRedirected) + { + option.ForceAnsiConsole = true; + option.NoAnsiColor = true; + Logger.Info(ResString.consoleRedirected); + } + CustomAnsiConsole.InitConsole(option.ForceAnsiConsole, option.NoAnsiColor); //检测更新 CheckUpdateAsync(); diff --git a/src/N_m3u8DL-RE/Util/FilterUtil.cs b/src/N_m3u8DL-RE/Util/FilterUtil.cs index f89a595..80a1a6b 100644 --- a/src/N_m3u8DL-RE/Util/FilterUtil.cs +++ b/src/N_m3u8DL-RE/Util/FilterUtil.cs @@ -135,7 +135,7 @@ namespace N_m3u8DL_RE.Util prompt.Select(basicStreams.Concat(audios).Concat(subs).First()); //多选 - var selectedStreams = AnsiConsole.Prompt(prompt); + var selectedStreams = CustomAnsiConsole.Console.Prompt(prompt); return selectedStreams; }