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;
}