From aade5a5d7c835db8dc7b671307c1c094de7630ae Mon Sep 17 00:00:00 2001 From: nilaoda Date: Tue, 19 Jul 2022 22:53:36 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=80=E7=B3=BB=E5=88=97?= =?UTF-8?q?=E5=85=A8=E6=96=B0=E7=9A=84=E5=91=BD=E4=BB=A4=E8=A1=8C=E9=80=89?= =?UTF-8?q?=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../N_m3u8DL-RE.Common.csproj | 15 +- .../Resource/ResString.Designer.cs | 202 +++++++++++++-- .../Resource/ResString.en-US.resx | 172 ------------ .../Resource/ResString.resx | 100 +++++-- .../Resource/ResString.zh-Hans.resx | 244 ++++++++++++++++++ ...ring.zh-TW.resx => ResString.zh-Hant.resx} | 52 ++++ src/N_m3u8DL-RE.sln | 1 + src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs | 89 +++++++ src/N_m3u8DL-RE/CommandLine/MyOption.cs | 77 ++++++ src/N_m3u8DL-RE/Config/DownloaderConfig.cs | 25 +- src/N_m3u8DL-RE/Directory.Build.props | 5 + .../DownloadManager/SimpleDownloadManager.cs | 63 +++-- src/N_m3u8DL-RE/Enum/SubtitleFormat.cs | 14 + src/N_m3u8DL-RE/N_m3u8DL-RE.csproj | 7 +- src/N_m3u8DL-RE/Program.cs | 84 ++++-- src/N_m3u8DL-RE/Util/ConvertUtil.cs | 55 ++++ src/N_m3u8DL-RE/rd.xml | 6 +- 17 files changed, 937 insertions(+), 274 deletions(-) delete mode 100644 src/N_m3u8DL-RE.Common/Resource/ResString.en-US.resx create mode 100644 src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hans.resx rename src/N_m3u8DL-RE.Common/Resource/{ResString.zh-TW.resx => ResString.zh-Hant.resx} (79%) create mode 100644 src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs create mode 100644 src/N_m3u8DL-RE/CommandLine/MyOption.cs create mode 100644 src/N_m3u8DL-RE/Enum/SubtitleFormat.cs create mode 100644 src/N_m3u8DL-RE/Util/ConvertUtil.cs diff --git a/src/N_m3u8DL-RE.Common/N_m3u8DL-RE.Common.csproj b/src/N_m3u8DL-RE.Common/N_m3u8DL-RE.Common.csproj index 6360901..bbd9d1d 100644 --- a/src/N_m3u8DL-RE.Common/N_m3u8DL-RE.Common.csproj +++ b/src/N_m3u8DL-RE.Common/N_m3u8DL-RE.Common.csproj @@ -22,16 +22,19 @@ - - PublicResXFileCodeGenerator - PublicResXFileCodeGenerator - ResString.Designer.cs + true + ResString.Designer.cs - + PublicResXFileCodeGenerator + true + + + PublicResXFileCodeGenerator + true - + diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.Designer.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.Designer.cs index d7e568b..e491cb7 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.Designer.cs +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.Designer.cs @@ -61,7 +61,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 错误的m3u8 的本地化字符串。 + /// 查找类似 Bad m3u8 的本地化字符串。 /// public static string badM3u8 { get { @@ -70,7 +70,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 二进制合并中... 的本地化字符串。 + /// 查找类似 Binary merging... 的本地化字符串。 /// public static string binaryMerge { get { @@ -79,7 +79,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 验证最后一个分片有效性 的本地化字符串。 + /// 查找类似 Verifying the validity of the last segment 的本地化字符串。 /// public static string checkingLast { get { @@ -88,7 +88,161 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 获取: 的本地化字符串。 + /// 查找类似 Automatically selects the best tracks of all types 的本地化字符串。 + /// + public static string cmd_autoSelect { + get { + return ResourceManager.GetString("cmd_autoSelect", resourceCulture); + } + } + + /// + /// 查找类似 Binary merge 的本地化字符串。 + /// + public static string cmd_binaryMerge { + get { + return ResourceManager.GetString("cmd_binaryMerge", resourceCulture); + } + } + + /// + /// 查找类似 Check if the actual number of segments downloaded matches the expected number 的本地化字符串。 + /// + public static string cmd_checkSegmentsCount { + get { + return ResourceManager.GetString("cmd_checkSegmentsCount", resourceCulture); + } + } + + /// + /// 查找类似 Delete temporary files when done 的本地化字符串。 + /// + public static string cmd_delAfterDone { + get { + return ResourceManager.GetString("cmd_delAfterDone", resourceCulture); + } + } + + /// + /// 查找类似 Pass custom header(s) to server, Example: + /// -H "Cookie: mycookie" -H "User-Agent: iOS" 的本地化字符串。 + /// + public static string cmd_header { + get { + return ResourceManager.GetString("cmd_header", resourceCulture); + } + } + + /// + /// 查找类似 Input Url or File 的本地化字符串。 + /// + public static string cmd_Input { + get { + return ResourceManager.GetString("cmd_Input", resourceCulture); + } + } + + /// + /// 查找类似 Set log level 的本地化字符串。 + /// + public static string cmd_logLevel { + get { + return ResourceManager.GetString("cmd_logLevel", resourceCulture); + } + } + + /// + /// 查找类似 Set output directory 的本地化字符串。 + /// + public static string cmd_saveDir { + get { + return ResourceManager.GetString("cmd_saveDir", resourceCulture); + } + } + + /// + /// 查找类似 Set output filename 的本地化字符串。 + /// + public static string cmd_saveName { + get { + return ResourceManager.GetString("cmd_saveName", resourceCulture); + } + } + + /// + /// 查找类似 Skip download 的本地化字符串。 + /// + public static string cmd_skipDownload { + get { + return ResourceManager.GetString("cmd_skipDownload", resourceCulture); + } + } + + /// + /// 查找类似 Skip segments merge 的本地化字符串。 + /// + public static string cmd_skipMerge { + get { + return ResourceManager.GetString("cmd_skipMerge", resourceCulture); + } + } + + /// + /// 查找类似 Subtitle output format 的本地化字符串。 + /// + public static string cmd_subFormat { + get { + return ResourceManager.GetString("cmd_subFormat", resourceCulture); + } + } + + /// + /// 查找类似 Select only subtitle tracks 的本地化字符串。 + /// + public static string cmd_subOnly { + get { + return ResourceManager.GetString("cmd_subOnly", resourceCulture); + } + } + + /// + /// 查找类似 Automatically fix subtitles 的本地化字符串。 + /// + public static string cmd_subtitleFix { + get { + return ResourceManager.GetString("cmd_subtitleFix", resourceCulture); + } + } + + /// + /// 查找类似 Set download thread count 的本地化字符串。 + /// + public static string cmd_threadCount { + get { + return ResourceManager.GetString("cmd_threadCount", resourceCulture); + } + } + + /// + /// 查找类似 Set temporary file directory 的本地化字符串。 + /// + public static string cmd_tmpDir { + get { + return ResourceManager.GetString("cmd_tmpDir", resourceCulture); + } + } + + /// + /// 查找类似 Set UI language 的本地化字符串。 + /// + public static string cmd_uiLanguage { + get { + return ResourceManager.GetString("cmd_uiLanguage", resourceCulture); + } + } + + /// + /// 查找类似 Fetch: 的本地化字符串。 /// public static string fetch { get { @@ -97,7 +251,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 正在提取TTML(raw)字幕... 的本地化字符串。 + /// 查找类似 Extracting TTML(raw) subtitle... 的本地化字符串。 /// public static string fixingTTML { get { @@ -106,7 +260,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 正在提取TTML(mp4)字幕... 的本地化字符串。 + /// 查找类似 Extracting TTML(mp4) subtitle... 的本地化字符串。 /// public static string fixingTTMLmp4 { get { @@ -115,7 +269,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 正在提取VTT(raw)字幕... 的本地化字符串。 + /// 查找类似 Extracting VTT(raw) subtitle... 的本地化字符串。 /// public static string fixingVTT { get { @@ -124,7 +278,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 正在提取VTT(mp4)字幕... 的本地化字符串。 + /// 查找类似 Extracting VTT(mp4) subtitle... 的本地化字符串。 /// public static string fixingVTTmp4 { get { @@ -133,7 +287,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 找不到支持的Processor 的本地化字符串。 + /// 查找类似 No Processor matched 的本地化字符串。 /// public static string keyProcessorNotFound { get { @@ -142,7 +296,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 检测到直播流 的本地化字符串。 + /// 查找类似 Live stream found 的本地化字符串。 /// public static string liveFound { get { @@ -151,7 +305,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 加载URL: 的本地化字符串。 + /// 查找类似 Loading URL: 的本地化字符串。 /// public static string loadingUrl { get { @@ -160,7 +314,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 检测到Master列表,开始解析全部流信息 的本地化字符串。 + /// 查找类似 Master List detected, try parse all streams 的本地化字符串。 /// public static string masterM3u8Found { get { @@ -169,7 +323,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 内容匹配: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/] 的本地化字符串。 + /// 查找类似 Content Matched: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/] 的本地化字符串。 /// public static string matchDASH { get { @@ -178,7 +332,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 内容匹配: [white on deepskyblue1]HTTP Live Streaming[/] 的本地化字符串。 + /// 查找类似 Content Matched: [white on deepskyblue1]HTTP Live Streaming[/] 的本地化字符串。 /// public static string matchHLS { get { @@ -187,7 +341,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 当前输入不受支持: 的本地化字符串。 + /// 查找类似 Input not supported: 的本地化字符串。 /// public static string notSupported { get { @@ -196,7 +350,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 正在解析媒体信息... 的本地化字符串。 + /// 查找类似 Parsing streams... 的本地化字符串。 /// public static string parsingStream { get { @@ -205,7 +359,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 [grey](按键盘上下键以浏览更多内容)[/] 的本地化字符串。 + /// 查找类似 [grey](Move up and down to reveal more streams)[/] 的本地化字符串。 /// public static string promptChoiceText { get { @@ -214,7 +368,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 (按 [blue]空格键[/] 选择流, [green]回车键[/] 完成选择) 的本地化字符串。 + /// 查找类似 (Press [blue]<space>[/] to toggle a stream, [green]<enter>[/] to accept) 的本地化字符串。 /// public static string promptInfo { get { @@ -223,7 +377,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 请选择 [green]你要下载的内容[/]: 的本地化字符串。 + /// 查找类似 Please select [green]what you want to download[/]: 的本地化字符串。 /// public static string promptTitle { get { @@ -232,7 +386,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 分片数量校验不通过, 共{}个,已下载{}. 的本地化字符串。 + /// 查找类似 Segment count check not pass, total: {}, downloaded: {}. 的本地化字符串。 /// public static string segmentCountCheckNotPass { get { @@ -241,7 +395,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 已选择的流: 的本地化字符串。 + /// 查找类似 Selected streams: 的本地化字符串。 /// public static string selectedStream { get { @@ -250,7 +404,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 开始下载... 的本地化字符串。 + /// 查找类似 Start downloading... 的本地化字符串。 /// public static string startDownloading { get { @@ -259,7 +413,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 已解析, 共计 {} 条媒体流, 基本流 {} 条, 可选音频流 {} 条, 可选字幕流 {} 条 的本地化字符串。 + /// 查找类似 Extracted, there are {} streams, with {} basic streams, {} audio streams, {} subtitle streams 的本地化字符串。 /// public static string streamsInfo { get { @@ -268,7 +422,7 @@ namespace N_m3u8DL_RE.Common.Resource { } /// - /// 查找类似 写出meta.json 的本地化字符串。 + /// 查找类似 Writing meta.json 的本地化字符串。 /// public static string writeJson { get { diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.en-US.resx b/src/N_m3u8DL-RE.Common/Resource/ResString.en-US.resx deleted file mode 100644 index 68cb5fb..0000000 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.en-US.resx +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Bad m3u8 - - - Input not supported: - - - Loading URL: - - - Content Matched: [white on deepskyblue1]HTTP Live Streaming[/] - - - Master List detected, try parse all streams - - - Fetch: - - - Please select [green]what you want to download[/]: - - - [grey](Move up and down to reveal more streams)[/] - - - (Press [blue]<space>[/] to toggle a stream, [green]<enter>[/] to accept) - - - Extracted, there are {} streams, with {} basic streams, {} audio streams, {} subtitle streams - - - Live stream found - - - Selected streams: - - - Writing meta.json - - - Content Matched: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/] - - - Verifying the validity of the last segment - - - Parsing streams... - - - No Processor matched - - - Start downloading... - - - Segment count check not pass, total: {}, downloaded: {}. - - - Extracting VTT(raw) subtitle... - - - Extracting VTT(mp4) subtitle... - - - Binary merging... - - - Extracting TTML(mp4) subtitle... - - - Extracting TTML(raw) subtitle... - - \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.resx b/src/N_m3u8DL-RE.Common/Resource/ResString.resx index 9b4e647..69ef495 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.resx +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.resx @@ -98,75 +98,127 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 错误的m3u8 + Bad m3u8 - 当前输入不受支持: + Input not supported: - 加载URL: + Loading URL: - 内容匹配: [white on deepskyblue1]HTTP Live Streaming[/] + Content Matched: [white on deepskyblue1]HTTP Live Streaming[/] - 检测到Master列表,开始解析全部流信息 + Master List detected, try parse all streams - 获取: + Fetch: - 请选择 [green]你要下载的内容[/]: + Please select [green]what you want to download[/]: - [grey](按键盘上下键以浏览更多内容)[/] + [grey](Move up and down to reveal more streams)[/] - (按 [blue]空格键[/] 选择流, [green]回车键[/] 完成选择) + (Press [blue]<space>[/] to toggle a stream, [green]<enter>[/] to accept) - 已解析, 共计 {} 条媒体流, 基本流 {} 条, 可选音频流 {} 条, 可选字幕流 {} 条 + Extracted, there are {} streams, with {} basic streams, {} audio streams, {} subtitle streams - 检测到直播流 + Live stream found - 已选择的流: + Selected streams: - 写出meta.json + Writing meta.json - 内容匹配: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/] + Content Matched: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/] - 验证最后一个分片有效性 + Verifying the validity of the last segment - 正在解析媒体信息... + Parsing streams... - 找不到支持的Processor + No Processor matched - 开始下载... + Start downloading... - 分片数量校验不通过, 共{}个,已下载{}. + Segment count check not pass, total: {}, downloaded: {}. - 正在提取VTT(raw)字幕... + Extracting VTT(raw) subtitle... - 正在提取VTT(mp4)字幕... + Extracting VTT(mp4) subtitle... - 二进制合并中... + Binary merging... - 正在提取TTML(mp4)字幕... + Extracting TTML(mp4) subtitle... - 正在提取TTML(raw)字幕... + Extracting TTML(raw) subtitle... + + + Input Url or File + + + Pass custom header(s) to server, Example: + -H "Cookie: mycookie" -H "User-Agent: iOS" + + + Set log level + + + Automatically selects the best tracks of all types + + + Select only subtitle tracks + + + Set download thread count + + + Skip segments merge + + + Binary merge + + + Delete temporary files when done + + + Automatically fix subtitles + + + Check if the actual number of segments downloaded matches the expected number + + + Subtitle output format + + + Set temporary file directory + + + Set output directory + + + Set output filename + + + Set UI language + + + Skip download \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hans.resx b/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hans.resx new file mode 100644 index 0000000..52094e4 --- /dev/null +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hans.resx @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 错误的m3u8 + + + 当前输入不受支持: + + + 加载URL: + + + 内容匹配: [white on deepskyblue1]HTTP Live Streaming[/] + + + 检测到Master列表,开始解析全部流信息 + + + 获取: + + + 请选择 [green]你要下载的内容[/]: + + + [grey](按键盘上下键以浏览更多内容)[/] + + + (按 [blue]空格键[/] 选择流, [green]回车键[/] 完成选择) + + + 已解析, 共计 {} 条媒体流, 基本流 {} 条, 可选音频流 {} 条, 可选字幕流 {} 条 + + + 检测到直播流 + + + 已选择的流: + + + 写出meta.json + + + 内容匹配: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/] + + + 验证最后一个分片有效性 + + + 正在解析媒体信息... + + + 找不到支持的Processor + + + 开始下载... + + + 分片数量校验不通过, 共{}个,已下载{}. + + + 正在提取VTT(raw)字幕... + + + 正在提取VTT(mp4)字幕... + + + 二进制合并中... + + + 正在提取TTML(mp4)字幕... + + + 正在提取TTML(raw)字幕... + + + 链接或文件 + + + 为HTTP请求设置特定的请求头, 例如: + -H "Cookie: mycookie" -H "User-Agent: iOS" + + + 设置日志级别 + + + 自动选择所有类型的最佳轨道 + + + 只选取字幕轨道 + + + 设置下载线程数 + + + 跳过合并分片 + + + 二进制合并 + + + 完成后删除临时文件 + + + 自动修正字幕 + + + 检测实际下载的分片数量和预期数量是否匹配 + + + 字幕输出类型 + + + 设置临时文件存储目录 + + + 设置输出目录 + + + 设置保存文件名 + + + 设置UI语言 + + + 跳过下载 + + \ No newline at end of file diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.zh-TW.resx b/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hant.resx similarity index 79% rename from src/N_m3u8DL-RE.Common/Resource/ResString.zh-TW.resx rename to src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hant.resx index 2cda1c5..7952de7 100644 --- a/src/N_m3u8DL-RE.Common/Resource/ResString.zh-TW.resx +++ b/src/N_m3u8DL-RE.Common/Resource/ResString.zh-Hant.resx @@ -169,4 +169,56 @@ 正在提取TTML(raw)字幕... + + 鏈接或文件 + + + 為HTTP請求設置特定的請求頭, 例如: + -H "Cookie: mycookie" -H "User-Agent: iOS" + + + 設置日誌級別 + + + 自動選擇所有類型的最佳軌道 + + + 只選取字幕軌道 + + + 設置下載線程數 + + + 跳過合併分片 + + + 二進制合併 + + + 完成後刪除臨時文件 + + + 自動修正字幕 + + + 檢測實際下載的分片數量和預期數量是否匹配 + + + 字幕輸出類型 + + + 設置臨時文件存儲目錄 + + + 設置輸出目錄 + + + 設置保存文件名 + + + 設置UI語言 + + + 跳過下載 + \ No newline at end of file diff --git a/src/N_m3u8DL-RE.sln b/src/N_m3u8DL-RE.sln index 7f69da1..8954741 100644 --- a/src/N_m3u8DL-RE.sln +++ b/src/N_m3u8DL-RE.sln @@ -32,6 +32,7 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution + RESX_NeutralResourcesLanguage = en-US SolutionGuid = {87F963D4-EA06-413D-9372-C726711C32B5} EndGlobalSection EndGlobal diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs new file mode 100644 index 0000000..65ae774 --- /dev/null +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -0,0 +1,89 @@ +using N_m3u8DL_RE.Common.Log; +using N_m3u8DL_RE.Common.Resource; +using N_m3u8DL_RE.Enum; +using System.CommandLine; +using System.CommandLine.Binding; +using System.Globalization; + +namespace N_m3u8DL_RE.CommandLine +{ + + internal class CommandInvoker + { + private readonly static Argument Input = new(name: "input", description: ResString.cmd_Input); + private readonly static Option TmpDir = new(new string[] { "--tmp-dir" }, description: ResString.cmd_tmpDir); + private readonly static Option SaveDir = new(new string[] { "--save-dir" }, description: ResString.cmd_saveDir); + private readonly static Option SaveName = new(new string[] { "--save-name" }, description: ResString.cmd_saveName); + private readonly static Option UILanguage = new(new string[] { "--ui-language" }, description: ResString.cmd_uiLanguage); + private readonly static Option Headers = new(new string[] { "--header", "-H" }, description: ResString.cmd_header) { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = false }; + private readonly static Option LogLevel = new(name: "--log-level", description: ResString.cmd_logLevel, getDefaultValue: () => Common.Log.LogLevel.INFO); + private readonly static Option SubtitleFormat = new(name: "--sub-format", description: ResString.cmd_subFormat, getDefaultValue: () => Enum.SubtitleFormat.VTT); + private readonly static Option AutoSelect = new(new string[] { "--auto-select" }, description: ResString.cmd_autoSelect, getDefaultValue: () => false); + private readonly static Option SubOnly = new(new string[] { "--sub-only" }, description: ResString.cmd_subOnly, getDefaultValue: () => false); + private readonly static Option ThreadCount = new(new string[] { "--thread-count" }, description: ResString.cmd_threadCount, getDefaultValue: () => 8); + private readonly static Option SkipMerge = new(new string[] { "--skip-merge" }, description: ResString.cmd_skipMerge, getDefaultValue: () => false); + private readonly static Option SkipDownload = new(new string[] { "--skip-download" }, description: ResString.cmd_skipDownload, getDefaultValue: () => false); + private readonly static Option BinaryMerge = new(new string[] { "--binary-merge" }, description: ResString.cmd_binaryMerge, getDefaultValue: () => true); + private readonly static Option DelAfterDone = new(new string[] { "--del-after-done" }, description: ResString.cmd_delAfterDone, getDefaultValue: () => true); + private readonly static Option AutoSubtitleFix = new(new string[] { "--auto-subtitle-fix" }, description: ResString.cmd_subtitleFix, getDefaultValue: () => true); + private readonly static Option CheckSegmentsCount = new(new string[] { "--check-segments-count" }, description: ResString.cmd_checkSegmentsCount, getDefaultValue: () => true); + + class MyOptionBinder : BinderBase + { + protected override MyOption GetBoundValue(BindingContext bindingContext) + { + var option = new MyOption + { + Input = bindingContext.ParseResult.GetValueForArgument(Input), + Headers = bindingContext.ParseResult.GetValueForOption(Headers), + LogLevel = bindingContext.ParseResult.GetValueForOption(LogLevel), + AutoSelect = bindingContext.ParseResult.GetValueForOption(AutoSelect), + SkipMerge = bindingContext.ParseResult.GetValueForOption(SkipMerge), + BinaryMerge = bindingContext.ParseResult.GetValueForOption(BinaryMerge), + DelAfterDone = bindingContext.ParseResult.GetValueForOption(DelAfterDone), + AutoSubtitleFix = bindingContext.ParseResult.GetValueForOption(AutoSubtitleFix), + CheckSegmentsCount = bindingContext.ParseResult.GetValueForOption(CheckSegmentsCount), + SubtitleFormat = bindingContext.ParseResult.GetValueForOption(SubtitleFormat), + SubOnly = bindingContext.ParseResult.GetValueForOption(SubOnly), + TmpDir = bindingContext.ParseResult.GetValueForOption(TmpDir), + SaveDir = bindingContext.ParseResult.GetValueForOption(SaveDir), + SaveName = bindingContext.ParseResult.GetValueForOption(SaveName), + ThreadCount = bindingContext.ParseResult.GetValueForOption(ThreadCount), + UILanguage = bindingContext.ParseResult.GetValueForOption(UILanguage), + SkipDownload = bindingContext.ParseResult.GetValueForOption(SkipDownload), + }; + + //在这里设置语言 + + string loc = "en-US"; + string currLoc = Thread.CurrentThread.CurrentUICulture.Name; + if (currLoc == "zh-CN" || currLoc == "zh-SG") loc = "zh-Hans"; + else if (currLoc.StartsWith("zh-")) loc = "zh-Hant"; + + //以用户选择优先 + loc = option.UILanguage ?? loc; + + CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(loc); + Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(loc); + Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(loc); + ResString.Culture = CultureInfo.GetCultureInfo(loc); + + return option; + } + } + + + public static async Task InvokeArgs(string[] args, Func action) + { + var rootCommand = new RootCommand("N_m3u8DL-RE Beta version: 20220719") + { + Input, TmpDir, SaveDir, SaveName, ThreadCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount, + BinaryMerge, DelAfterDone, Headers, SubOnly, SubtitleFormat, AutoSubtitleFix, LogLevel, UILanguage + }; + rootCommand.TreatUnmatchedTokensAsErrors = true; + rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder()); + + return await rootCommand.InvokeAsync(args); + } + } +} diff --git a/src/N_m3u8DL-RE/CommandLine/MyOption.cs b/src/N_m3u8DL-RE/CommandLine/MyOption.cs new file mode 100644 index 0000000..e02bdda --- /dev/null +++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs @@ -0,0 +1,77 @@ +using N_m3u8DL_RE.Common.Log; +using N_m3u8DL_RE.Enum; + +namespace N_m3u8DL_RE.CommandLine +{ + internal class MyOption + { + /// + /// See: . + /// + public string Input { get; set; } = default!; + /// + /// See: . + /// + public string[]? Headers { get; set; } + /// + /// See: . + /// + public LogLevel LogLevel { get; set; } + /// + /// See: . + /// + public bool AutoSelect { get; set; } + /// + /// See: . + /// + public bool SubOnly { get; set; } + /// + /// See: . + /// + public int ThreadCount { get; set; } + /// + /// See: . + /// + public bool SkipMerge { get; set; } + /// + /// See: . + /// + public bool BinaryMerge { get; set; } + /// + /// See: . + /// + public bool DelAfterDone { get; set; } + /// + /// See: . + /// + public bool AutoSubtitleFix { get; set; } + /// + /// See: . + /// + public bool CheckSegmentsCount { get; set; } + /// + /// See: . + /// + public bool SkipDownload { get; set; } + /// + /// See: . + /// + public SubtitleFormat SubtitleFormat { get; set; } + /// + /// See: . + /// + public string? TmpDir { get; set; } + /// + /// See: . + /// + public string? SaveDir { get; set; } + /// + /// See: . + /// + public string? SaveName { get; set; } + /// + /// See: . + /// + public string? UILanguage { get; set; } + } +} \ No newline at end of file diff --git a/src/N_m3u8DL-RE/Config/DownloaderConfig.cs b/src/N_m3u8DL-RE/Config/DownloaderConfig.cs index 4f23a9d..7b368f0 100644 --- a/src/N_m3u8DL-RE/Config/DownloaderConfig.cs +++ b/src/N_m3u8DL-RE/Config/DownloaderConfig.cs @@ -1,4 +1,7 @@ -using System; +using N_m3u8DL_RE.CommandLine; +using N_m3u8DL_RE.Enum; +using N_m3u8DL_RE.Parser.Config; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,6 +11,22 @@ namespace N_m3u8DL_RE.Config { internal class DownloaderConfig { + public DownloaderConfig() { } + + public DownloaderConfig(MyOption option) + { + AutoSubtitleFix = option.AutoSubtitleFix; + SkipMerge = option.SkipMerge; + BinaryMerge = option.BinaryMerge; + DelAfterDone = option.DelAfterDone; + CheckSegmentsCount = option.CheckSegmentsCount; + SubtitleFormat = option.SubtitleFormat; + TmpDir = option.TmpDir; + SaveName = option.SaveName; + SaveDir = option.SaveDir; + ThreadCount = option.ThreadCount; + } + /// /// 临时文件存储目录 /// @@ -49,6 +68,10 @@ namespace N_m3u8DL_RE.Config /// public bool AutoSubtitleFix { get; set; } = true; /// + /// 字幕格式 + /// + public SubtitleFormat SubtitleFormat { get; set; } = SubtitleFormat.VTT; + /// /// 请求头 /// public Dictionary Headers { get; set; } = new Dictionary() diff --git a/src/N_m3u8DL-RE/Directory.Build.props b/src/N_m3u8DL-RE/Directory.Build.props index ad71932..630e98d 100644 --- a/src/N_m3u8DL-RE/Directory.Build.props +++ b/src/N_m3u8DL-RE/Directory.Build.props @@ -5,6 +5,11 @@ true true full + link + true + false + true + zh-Hans;zh-Hant;en-US 7.0.0-* diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs index 2d85386..32955f8 100644 --- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs +++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs @@ -40,7 +40,7 @@ namespace N_m3u8DL_RE.DownloadManager if (segments == null) return false; var dirName = $"{NowDateTime:yyyy-MM-dd_HH-mm-ss}_{streamSpec.GroupId}_{streamSpec.Codecs}_{streamSpec.Language}"; - var tmpDir = DownloaderConfig.TmpDir ?? Path.Combine(Environment.CurrentDirectory, dirName); + var tmpDir = Path.Combine(DownloaderConfig.TmpDir ?? Environment.CurrentDirectory, dirName); var saveDir = DownloaderConfig.SaveDir ?? Environment.CurrentDirectory; var saveName = DownloaderConfig.SaveName ?? dirName; var headers = DownloaderConfig.Headers; @@ -152,12 +152,19 @@ namespace N_m3u8DL_RE.DownloadManager foreach (var item in files) File.Delete(item); FileDic.Clear(); var index = 0; - var path = Path.Combine(tmpDir, index.ToString(pad) + $".fix.{streamSpec.Extension ?? "clip"}"); - var vttContentFixed = finalVtt.ToStringWithHeader(); - await File.WriteAllTextAsync(path, vttContentFixed, new UTF8Encoding(false)); + var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt"); + var subContentFixed = finalVtt.ToStringWithHeader(); + //转换字幕格式 + if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT) + { + path = Path.ChangeExtension(path, ".srt"); + subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat); + output = Path.ChangeExtension(output, ".srt"); + } + await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false)); FileDic[keys.First()] = new DownloadResult() { - ActualContentLength = vttContentFixed.Length, + ActualContentLength = subContentFixed.Length, ActualFilePath = path }; } @@ -181,15 +188,22 @@ namespace N_m3u8DL_RE.DownloadManager FileDic.Clear(); var index = 0; var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt"); - var vttContentFixed = finalVtt.ToStringWithHeader(); - await File.WriteAllTextAsync(path, vttContentFixed, new UTF8Encoding(false)); + var subContentFixed = finalVtt.ToStringWithHeader(); + //转换字幕格式 + if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT) + { + path = Path.ChangeExtension(path, ".srt"); + subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat); + output = Path.ChangeExtension(output, ".srt"); + } + await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false)); FileDic[firstKey] = new DownloadResult() { - ActualContentLength = vttContentFixed.Length, + ActualContentLength = subContentFixed.Length, ActualFilePath = path }; //修改输出后缀 - output = Path.ChangeExtension(output, ".vtt"); + output = Path.ChangeExtension(output, Path.GetExtension(path)); } } @@ -207,15 +221,22 @@ namespace N_m3u8DL_RE.DownloadManager FileDic.Clear(); var index = 0; var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt"); - var vttContentFixed = finalVtt.ToStringWithHeader(); - await File.WriteAllTextAsync(path, vttContentFixed, new UTF8Encoding(false)); + var subContentFixed = finalVtt.ToStringWithHeader(); + //转换字幕格式 + if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT) + { + path = Path.ChangeExtension(path, ".srt"); + subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat); + output = Path.ChangeExtension(output, ".srt"); + } + await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false)); FileDic[firstKey] = new DownloadResult() { - ActualContentLength = vttContentFixed.Length, + ActualContentLength = subContentFixed.Length, ActualFilePath = path }; //修改输出后缀 - output = Path.ChangeExtension(output, ".vtt"); + output = Path.ChangeExtension(output, Path.GetExtension(path)); } //自动修复TTML mp4字幕 @@ -237,15 +258,22 @@ namespace N_m3u8DL_RE.DownloadManager FileDic.Clear(); var index = 0; var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt"); - var vttContentFixed = finalVtt.ToStringWithHeader(); - await File.WriteAllTextAsync(path, vttContentFixed, new UTF8Encoding(false)); + var subContentFixed = finalVtt.ToStringWithHeader(); + //转换字幕格式 + if (DownloaderConfig.SubtitleFormat != Enum.SubtitleFormat.VTT) + { + path = Path.ChangeExtension(path, ".srt"); + subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.SubtitleFormat); + output = Path.ChangeExtension(output, ".srt"); + } + await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false)); FileDic[firstKey] = new DownloadResult() { - ActualContentLength = vttContentFixed.Length, + ActualContentLength = subContentFixed.Length, ActualFilePath = path }; //修改输出后缀 - output = Path.ChangeExtension(output, ".vtt"); + output = Path.ChangeExtension(output, Path.GetExtension(path)); } //合并 @@ -262,6 +290,7 @@ namespace N_m3u8DL_RE.DownloadManager throw new NotImplementedException(); } } + //删除临时文件夹 if (DownloaderConfig.DelAfterDone) { diff --git a/src/N_m3u8DL-RE/Enum/SubtitleFormat.cs b/src/N_m3u8DL-RE/Enum/SubtitleFormat.cs new file mode 100644 index 0000000..e1c75e7 --- /dev/null +++ b/src/N_m3u8DL-RE/Enum/SubtitleFormat.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace N_m3u8DL_RE.Enum +{ + internal enum SubtitleFormat + { + VTT, + SRT + } +} diff --git a/src/N_m3u8DL-RE/N_m3u8DL-RE.csproj b/src/N_m3u8DL-RE/N_m3u8DL-RE.csproj index bd6cbb6..924f5c5 100644 --- a/src/N_m3u8DL-RE/N_m3u8DL-RE.csproj +++ b/src/N_m3u8DL-RE/N_m3u8DL-RE.csproj @@ -7,18 +7,15 @@ enable preview enable + 0.0.1 - - - - - + diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs index a4c53a9..b722547 100644 --- a/src/N_m3u8DL-RE/Program.cs +++ b/src/N_m3u8DL-RE/Program.cs @@ -19,26 +19,32 @@ using N_m3u8DL_RE.Config; using N_m3u8DL_RE.Util; using System.Diagnostics; using N_m3u8DL_RE.DownloadManager; +using N_m3u8DL_RE.CommandLine; +using System.Reflection; namespace N_m3u8DL_RE { internal class Program { - static async Task Main(string[] args) { - string loc = "en-US"; - string currLoc = Thread.CurrentThread.CurrentUICulture.Name; - if (currLoc == "zh-TW" || currLoc == "zh-HK" || currLoc == "zh-MO") loc = "zh-TW"; - else if (currLoc == "zh-CN" || currLoc == "zh-SG") loc = "zh-CN"; - //设置语言 - CultureInfo.DefaultThreadCurrentCulture = new CultureInfo(loc); - Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(loc); - //Logger.LogLevel = LogLevel.DEBUG; + await CommandInvoker.InvokeArgs(args, DoWorkAsync); + } + + static async Task DoWorkAsync(MyOption option) + { + Logger.LogLevel = option.LogLevel; try { var parserConfig = new ParserConfig(); + + //设置Headers + foreach (var item in ConvertUtil.SplitHeaderArrayToDic(option.Headers)) + { + parserConfig.Headers[item.Key] = item.Value; + } + //demo1 parserConfig.ContentProcessors.Insert(0, new DemoProcessor()); //demo2 @@ -53,19 +59,29 @@ namespace N_m3u8DL_RE //url = "https://rest-as.ott.kaltura.com/api_v3/service/assetFile/action/playManifest/partnerId/147/assetId/1304099/assetType/media/assetFileId/16136929/contextType/PLAYBACK/isAltUrl/False/ks/djJ8MTQ3fMusTFH6PCZpcrfKLQwI-pPm9ex6b6r49wioe32WH2udXeM4reyWIkSDpi7HhvhxBHAHAKiHrcnkmIJQpyAt4MuDBG0ywGQ-jOeqQFcTRQ8BGJGw6g-smSBLwSbo4CCx9M9vWNJX3GkOfhoMAY4yRU-ur3okHiVq1mUJ82XBd_iVqLuzodnc9sJEtcHH0zc5CoPiTq2xor-dq3yDURnZm3isfSN3t9uLIJEW09oE-SJ84DM5GUuFUdbnIV8bdcWUsPicUg-Top1G2D3WcWXq4EvPnwvD8jrC_vsiOpLHf5akAwtdGsJ6__cXUmT7a-QlfjdvaZ5T8UhDLnttHmsxYs2E5c0lh4uOvvJou8dD8iYxUexlPI2j4QUkBRxqOEVLSNV3Y82-5TTRqgnK_uGYXHwk7EAmDws7hbLj2-DJ1heXDcye3OJYdunJgAS-9ma5zmQQNiY_HYh6wj2N1HpCTNAtWWga6R9fC0VgBTZbidW-YwMSGzIvMQfIfWKe15X7Oc_hCs-zGfW9XeRJZrutcWKK_D_HlzpQVBF2vIF3XgaI/a.mpd"; //url = "https://dash.akamaized.net/dash264/TestCases/2c/qualcomm/1/MultiResMPEG2.mpd"; //url = "http://playertest.longtailvideo.com/adaptive/oceans_aes/oceans_aes.m3u8"; - //url = "https://cmaf.lln.latam.hbomaxcdn.com/videos/GYPGKMQjoDkVLBQEAAAAo/1/1b5ad5/1_single_J8sExA_1080hi.mpd"; + url = "https://cmaf.lln.latam.hbomaxcdn.com/videos/GYPGKMQjoDkVLBQEAAAAo/1/1b5ad5/1_single_J8sExA_1080hi.mpd"; //url = "https://livesim.dashif.org/dash/vod/testpic_2s/multi_subs.mpd"; //ttml + mp4 //url = "http://media.axprod.net/TestVectors/v6-Clear/Manifest_1080p.mpd"; //vtt + mp4 - url = "https://livesim.dashif.org/dash/vod/testpic_2s/xml_subs.mpd"; //ttml + //url = "https://livesim.dashif.org/dash/vod/testpic_2s/xml_subs.mpd"; //ttml + //url = "https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8"; //HLS vtt + //url = "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_adv_example_hevc/master.m3u8"; //高级HLS fMP4+VTT + //url = "https://events-delivery.apple.com/0205eyyhwbbqexozkwmgccegwnjyrktg/m3u8/vod_index-dpyfrsVksFWjneFiptbXnAMYBtGYbXeZ.m3u8"; //高级HLS fMP4+VTT + //url = "https://apionvod5.seezntv.com/ktmain1/cold/CP/55521/202207/media/MIAM61RPSGL150000100_DRM/MIAM61RPSGL150000100_H.m3u8?sid=0000000F50000040000A700000020000"; + //url = "https://ewcdn12.nowe.com/session/16-5-72579e3-2103014898783810281/Content/DASH_VOS3/VOD/6908/19585/d2afa5fe-e9c8-40f0-8d18-648aaaf292b6/f677841a-9d8f-2ff5-3517-674ba49ef192/manifest.mpd?token=894db5d69931835f82dd8e393974ef9f_1658146180"; + //url = "https://ols-ww100-cp.akamaized.net/manifest/master/06ee6f68-ee80-11ea-9bc5-02b68fb543c4/65794a72596d6c30496a6f7a4e6a67324e4441774d444173496e42735958526d62334a74496a6f695a47567a6133527663434973496d526c646d6c6a5a565235634755694f694a335a5749694c434a746232526c62434936496e6470626d527664334d694c434a7663315235634755694f694a6a61484a76625755694c434a7663794936496a45774d6934774c6a41694c434a68634841694f69497a4c6a416966513d3d/dash.mpd?cpatoken=exp=1658223027~acl=/manifest/master/06ee6f68-ee80-11ea-9bc5-02b68fb543c4/*~hmac=644c608aac361f688e9b24b0f345c801d0f2d335819431d1873ff7aeac46d6b2&access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXZpY2VfaWQiOm51bGwsIndhdGNoX3R5cGUiOiJQUkVNSVVNIiwicHJvZ3JhbV9pZCI6ImUwMWRmYjAyLTM1YmItMTFlOS1hNDI3LTA2YTA0MTdjMWQxZSIsImFkX3RhZyI6ZmFsc2UsInBhcmVudF9wcm9ncmFtX2lkIjoiZmJmMDc2MDYtMzNmYi0xMWU5LWE0MjctMDZhMDQxN2MxZDFlIiwiY2xpZW50X2lkIjoiNGQ3MDViZTQtYTQ5ZS0xMWVhLWJiMzctMDI0MmFjMTMwMDAyIiwidmlkZW9fdHlwZSI6InZvZCIsImdyYW50X3R5cGUiOiJwbGF5X3ZpZGVvIiwidXNlcl9pZCI6ImFhNTMxZWQ2LWM2NTMtNDliYS04NGI1LWFkZDRmNGIzNGMyNyIsImN1cnJlbnRfc2Vjb25kIjowLCJyZXBvcnRfaWQiOiJOU1RHIiwic2NvcGUiOlsicHVibGljOi4qIiwibWU6LioiXSwiZXhwIjoxNjU4Mzk1ODI2LCJkZXRlY3Rpb25faWQiOm51bGwsInZpZGVvX2lkIjoiODc0Yjk0ZDItNzZiYi00YzliLTgzODQtNzJlMTA0NWVjOGMxIiwiaXNzIjoiQXNpYXBsYXktT0F1dGgtU2VydmVyIiwiaWF0IjoxNjU4MTM2NjI2LCJ0ZXJyaXRvcnkiOiJUVyJ9.1juciYIyMNzykXKu-nGLR_cYWvPMEAE9ub-ny7RzFnM"; + //url = "https://a38avoddashs3ww-a.akamaihd.net/ondemand/iad_2/8e91/f2f2/ec5a/430f-bd7a-0779f4a0189d/685cda75-609c-41c1-86bb-688f4cdb5521_corrected.mpd"; + url = "https://dcs-vod.mp.lura.live/vod/p/session/manifest.mpd?i=i177610817-nb45239a2-e962-4137-bc70-1790359619e6"; + //url = "https://theater.kktv.com.tw/98/04000198010001_584b26392f7f7f11fc62299214a55fb7/16113081449d8d5e9960_sub_dash.mpd"; //MPD+VTT + //url = ""; - if (args.Length > 0) + if (!string.IsNullOrEmpty(option.Input)) { - url = args[0]; + url = option.Input; } if (string.IsNullOrEmpty(url)) { - url = AnsiConsole.Ask("请输入 [green]URL[/]: "); + url = AnsiConsole.Ask("Input [green]URL[/]: "); } //流提取器配置 @@ -95,7 +111,27 @@ namespace N_m3u8DL_RE } //展示交互式选择框 - var selectedStreams = PromptUtil.SelectStreams(lists); + //var selectedStreams = PromptUtil.SelectStreams(lists); + var selectedStreams = new List(); + if (option.AutoSelect) + { + if (basicStreams.Any()) + selectedStreams.Add(basicStreams.First()); + var langs = audios.DistinctBy(a => a.Language).Select(a => a.Language); + foreach (var lang in langs) + { + selectedStreams.Add(audios.Where(a => a.Language == lang).OrderByDescending(a => a.Bandwidth).First()); + } + selectedStreams.AddRange(subs); + } + else if (option.SubOnly) + { + selectedStreams.AddRange(subs); + } + else + { + selectedStreams = PromptUtil.SelectStreams(lists); + } //一个以上的话,需要手动重新加载playlist if (lists.Count() > 1) await extractor.FetchPlayListAsync(selectedStreams); @@ -107,29 +143,33 @@ namespace N_m3u8DL_RE Logger.InfoMarkUp(item.ToString()); } + if (option.SkipDownload) + { + return; + } + +#if DEBUG Console.ReadKey(); +#endif //下载配置 - var downloadConfig = new DownloaderConfig() + var downloadConfig = new DownloaderConfig(option) { Headers = parserConfig.Headers, - BinaryMerge = true, - DelAfterDone = true, - CheckSegmentsCount = true }; //开始下载 var sdm = new SimpleDownloadManager(downloadConfig); var result = await sdm.StartDownloadAsync(selectedStreams); if (result) - Logger.InfoMarkUp("[white on green]成功[/]"); + Logger.InfoMarkUp("[white on green]Done[/]"); else - Logger.ErrorMarkUp("[white on red]失败[/]"); + Logger.ErrorMarkUp("[white on red]Faild[/]"); } catch (Exception ex) { Logger.Error(ex.ToString()); + await Task.Delay(3000); } - //Console.ReadKey(); } } } \ No newline at end of file diff --git a/src/N_m3u8DL-RE/Util/ConvertUtil.cs b/src/N_m3u8DL-RE/Util/ConvertUtil.cs new file mode 100644 index 0000000..02076c3 --- /dev/null +++ b/src/N_m3u8DL-RE/Util/ConvertUtil.cs @@ -0,0 +1,55 @@ +using N_m3u8DL_RE.Common.Entity; +using N_m3u8DL_RE.Common.Log; +using N_m3u8DL_RE.Enum; +using System.Text; + +namespace N_m3u8DL_RE.Util +{ + internal class ConvertUtil + { + public static Dictionary SplitHeaderArrayToDic(string[]? headers) + { + Dictionary dic = new(); + + if (headers != null) + { + foreach (string header in headers) + { + var index = header.IndexOf(':'); + if (index != -1) + { + dic[header[..index].Trim()] = header[(index + 1)..].Trim(); + } + } + } + + return dic; + } + + private static string WebVtt2Srt(WebVttSub vtt) + { + StringBuilder sb = new StringBuilder(); + int index = 1; + foreach (var c in vtt.Cues) + { + sb.AppendLine($"{index++}"); + sb.AppendLine(c.StartTime.ToString(@"hh\:mm\:ss\,fff") + " --> " + c.EndTime.ToString(@"hh\:mm\:ss\,fff")); + sb.AppendLine(c.Payload); + sb.AppendLine(); + } + sb.AppendLine(); + return sb.ToString(); + } + + public static string WebVtt2Other(WebVttSub vtt, SubtitleFormat toFormat) + { + Logger.Debug($"Convert {SubtitleFormat.VTT} ==> {toFormat}"); + return toFormat switch + { + SubtitleFormat.VTT => vtt.ToStringWithHeader(), + SubtitleFormat.SRT => WebVtt2Srt(vtt), + _ => throw new NotSupportedException($"{toFormat} not supported!") + }; + } + } +} diff --git a/src/N_m3u8DL-RE/rd.xml b/src/N_m3u8DL-RE/rd.xml index ef7a25e..a9352dc 100644 --- a/src/N_m3u8DL-RE/rd.xml +++ b/src/N_m3u8DL-RE/rd.xml @@ -1,7 +1,7 @@ - + + \ No newline at end of file