新增一系列全新的命令行选项

This commit is contained in:
nilaoda 2022-07-19 22:53:36 +08:00
parent 79b4b68469
commit aade5a5d7c
17 changed files with 937 additions and 274 deletions

View File

@ -22,15 +22,18 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resource\ResString.en-US.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resource\ResString.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<PublicClass>true</PublicClass>
<LastGenOutput>ResString.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Resource\ResString.zh-TW.resx">
<EmbeddedResource Update="Resource\ResString.zh-Hans.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<PublicClass>true</PublicClass>
</EmbeddedResource>
<EmbeddedResource Update="Resource\ResString.zh-Hant.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<PublicClass>true</PublicClass>
</EmbeddedResource>
</ItemGroup>

View File

@ -61,7 +61,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 错误的m3u8 的本地化字符串。
/// 查找类似 Bad m3u8 的本地化字符串。
/// </summary>
public static string badM3u8 {
get {
@ -70,7 +70,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 二进制合并中... 的本地化字符串。
/// 查找类似 Binary merging... 的本地化字符串。
/// </summary>
public static string binaryMerge {
get {
@ -79,7 +79,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 验证最后一个分片有效性 的本地化字符串。
/// 查找类似 Verifying the validity of the last segment 的本地化字符串。
/// </summary>
public static string checkingLast {
get {
@ -88,7 +88,161 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 获取: 的本地化字符串。
/// 查找类似 Automatically selects the best tracks of all types 的本地化字符串。
/// </summary>
public static string cmd_autoSelect {
get {
return ResourceManager.GetString("cmd_autoSelect", resourceCulture);
}
}
/// <summary>
/// 查找类似 Binary merge 的本地化字符串。
/// </summary>
public static string cmd_binaryMerge {
get {
return ResourceManager.GetString("cmd_binaryMerge", resourceCulture);
}
}
/// <summary>
/// 查找类似 Check if the actual number of segments downloaded matches the expected number 的本地化字符串。
/// </summary>
public static string cmd_checkSegmentsCount {
get {
return ResourceManager.GetString("cmd_checkSegmentsCount", resourceCulture);
}
}
/// <summary>
/// 查找类似 Delete temporary files when done 的本地化字符串。
/// </summary>
public static string cmd_delAfterDone {
get {
return ResourceManager.GetString("cmd_delAfterDone", resourceCulture);
}
}
/// <summary>
/// 查找类似 Pass custom header(s) to server, Example:
/// -H &quot;Cookie: mycookie&quot; -H &quot;User-Agent: iOS&quot; 的本地化字符串。
/// </summary>
public static string cmd_header {
get {
return ResourceManager.GetString("cmd_header", resourceCulture);
}
}
/// <summary>
/// 查找类似 Input Url or File 的本地化字符串。
/// </summary>
public static string cmd_Input {
get {
return ResourceManager.GetString("cmd_Input", resourceCulture);
}
}
/// <summary>
/// 查找类似 Set log level 的本地化字符串。
/// </summary>
public static string cmd_logLevel {
get {
return ResourceManager.GetString("cmd_logLevel", resourceCulture);
}
}
/// <summary>
/// 查找类似 Set output directory 的本地化字符串。
/// </summary>
public static string cmd_saveDir {
get {
return ResourceManager.GetString("cmd_saveDir", resourceCulture);
}
}
/// <summary>
/// 查找类似 Set output filename 的本地化字符串。
/// </summary>
public static string cmd_saveName {
get {
return ResourceManager.GetString("cmd_saveName", resourceCulture);
}
}
/// <summary>
/// 查找类似 Skip download 的本地化字符串。
/// </summary>
public static string cmd_skipDownload {
get {
return ResourceManager.GetString("cmd_skipDownload", resourceCulture);
}
}
/// <summary>
/// 查找类似 Skip segments merge 的本地化字符串。
/// </summary>
public static string cmd_skipMerge {
get {
return ResourceManager.GetString("cmd_skipMerge", resourceCulture);
}
}
/// <summary>
/// 查找类似 Subtitle output format 的本地化字符串。
/// </summary>
public static string cmd_subFormat {
get {
return ResourceManager.GetString("cmd_subFormat", resourceCulture);
}
}
/// <summary>
/// 查找类似 Select only subtitle tracks 的本地化字符串。
/// </summary>
public static string cmd_subOnly {
get {
return ResourceManager.GetString("cmd_subOnly", resourceCulture);
}
}
/// <summary>
/// 查找类似 Automatically fix subtitles 的本地化字符串。
/// </summary>
public static string cmd_subtitleFix {
get {
return ResourceManager.GetString("cmd_subtitleFix", resourceCulture);
}
}
/// <summary>
/// 查找类似 Set download thread count 的本地化字符串。
/// </summary>
public static string cmd_threadCount {
get {
return ResourceManager.GetString("cmd_threadCount", resourceCulture);
}
}
/// <summary>
/// 查找类似 Set temporary file directory 的本地化字符串。
/// </summary>
public static string cmd_tmpDir {
get {
return ResourceManager.GetString("cmd_tmpDir", resourceCulture);
}
}
/// <summary>
/// 查找类似 Set UI language 的本地化字符串。
/// </summary>
public static string cmd_uiLanguage {
get {
return ResourceManager.GetString("cmd_uiLanguage", resourceCulture);
}
}
/// <summary>
/// 查找类似 Fetch: 的本地化字符串。
/// </summary>
public static string fetch {
get {
@ -97,7 +251,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 正在提取TTML(raw)字幕... 的本地化字符串。
/// 查找类似 Extracting TTML(raw) subtitle... 的本地化字符串。
/// </summary>
public static string fixingTTML {
get {
@ -106,7 +260,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 正在提取TTML(mp4)字幕... 的本地化字符串。
/// 查找类似 Extracting TTML(mp4) subtitle... 的本地化字符串。
/// </summary>
public static string fixingTTMLmp4 {
get {
@ -115,7 +269,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 正在提取VTT(raw)字幕... 的本地化字符串。
/// 查找类似 Extracting VTT(raw) subtitle... 的本地化字符串。
/// </summary>
public static string fixingVTT {
get {
@ -124,7 +278,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 正在提取VTT(mp4)字幕... 的本地化字符串。
/// 查找类似 Extracting VTT(mp4) subtitle... 的本地化字符串。
/// </summary>
public static string fixingVTTmp4 {
get {
@ -133,7 +287,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 找不到支持的Processor 的本地化字符串。
/// 查找类似 No Processor matched 的本地化字符串。
/// </summary>
public static string keyProcessorNotFound {
get {
@ -142,7 +296,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 检测到直播流 的本地化字符串。
/// 查找类似 Live stream found 的本地化字符串。
/// </summary>
public static string liveFound {
get {
@ -151,7 +305,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 加载URL: 的本地化字符串。
/// 查找类似 Loading URL: 的本地化字符串。
/// </summary>
public static string loadingUrl {
get {
@ -160,7 +314,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 检测到Master列表开始解析全部流信息 的本地化字符串。
/// 查找类似 Master List detected, try parse all streams 的本地化字符串。
/// </summary>
public static string masterM3u8Found {
get {
@ -169,7 +323,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 内容匹配: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/] 的本地化字符串。
/// 查找类似 Content Matched: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/] 的本地化字符串。
/// </summary>
public static string matchDASH {
get {
@ -178,7 +332,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 内容匹配: [white on deepskyblue1]HTTP Live Streaming[/] 的本地化字符串。
/// 查找类似 Content Matched: [white on deepskyblue1]HTTP Live Streaming[/] 的本地化字符串。
/// </summary>
public static string matchHLS {
get {
@ -187,7 +341,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 当前输入不受支持: 的本地化字符串。
/// 查找类似 Input not supported: 的本地化字符串。
/// </summary>
public static string notSupported {
get {
@ -196,7 +350,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 正在解析媒体信息... 的本地化字符串。
/// 查找类似 Parsing streams... 的本地化字符串。
/// </summary>
public static string parsingStream {
get {
@ -205,7 +359,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 [grey](按键盘上下键以浏览更多内容)[/] 的本地化字符串。
/// 查找类似 [grey](Move up and down to reveal more streams)[/] 的本地化字符串。
/// </summary>
public static string promptChoiceText {
get {
@ -214,7 +368,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 (按 [blue]空格键[/] 选择流, [green]回车键[/] 完成选择) 的本地化字符串。
/// 查找类似 (Press [blue]&lt;space&gt;[/] to toggle a stream, [green]&lt;enter&gt;[/] to accept) 的本地化字符串。
/// </summary>
public static string promptInfo {
get {
@ -223,7 +377,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 请选择 [green]你要下载的内容[/]: 的本地化字符串。
/// 查找类似 Please select [green]what you want to download[/]: 的本地化字符串。
/// </summary>
public static string promptTitle {
get {
@ -232,7 +386,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 分片数量校验不通过, 共{}个,已下载{}. 的本地化字符串。
/// 查找类似 Segment count check not pass, total: {}, downloaded: {}. 的本地化字符串。
/// </summary>
public static string segmentCountCheckNotPass {
get {
@ -241,7 +395,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 已选择的流: 的本地化字符串。
/// 查找类似 Selected streams: 的本地化字符串。
/// </summary>
public static string selectedStream {
get {
@ -250,7 +404,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 开始下载... 的本地化字符串。
/// 查找类似 Start downloading... 的本地化字符串。
/// </summary>
public static string startDownloading {
get {
@ -259,7 +413,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 已解析, 共计 {} 条媒体流, 基本流 {} 条, 可选音频流 {} 条, 可选字幕流 {} 条 的本地化字符串。
/// 查找类似 Extracted, there are {} streams, with {} basic streams, {} audio streams, {} subtitle streams 的本地化字符串。
/// </summary>
public static string streamsInfo {
get {
@ -268,7 +422,7 @@ namespace N_m3u8DL_RE.Common.Resource {
}
/// <summary>
/// 查找类似 写出meta.json 的本地化字符串。
/// 查找类似 Writing meta.json 的本地化字符串。
/// </summary>
public static string writeJson {
get {

View File

@ -1,172 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="badM3u8" xml:space="preserve">
<value>Bad m3u8</value>
</data>
<data name="notSupported" xml:space="preserve">
<value>Input not supported: </value>
</data>
<data name="loadingUrl" xml:space="preserve">
<value>Loading URL: </value>
</data>
<data name="matchHLS" xml:space="preserve">
<value>Content Matched: [white on deepskyblue1]HTTP Live Streaming[/]</value>
</data>
<data name="masterM3u8Found" xml:space="preserve">
<value>Master List detected, try parse all streams</value>
</data>
<data name="fetch" xml:space="preserve">
<value>Fetch: </value>
</data>
<data name="promptTitle" xml:space="preserve">
<value>Please select [green]what you want to download[/]:</value>
</data>
<data name="promptChoiceText" xml:space="preserve">
<value>[grey](Move up and down to reveal more streams)[/]</value>
</data>
<data name="promptInfo" xml:space="preserve">
<value>(Press [blue]&lt;space&gt;[/] to toggle a stream, [green]&lt;enter&gt;[/] to accept)</value>
</data>
<data name="streamsInfo" xml:space="preserve">
<value>Extracted, there are {} streams, with {} basic streams, {} audio streams, {} subtitle streams</value>
</data>
<data name="liveFound" xml:space="preserve">
<value>Live stream found</value>
</data>
<data name="selectedStream" xml:space="preserve">
<value>Selected streams:</value>
</data>
<data name="writeJson" xml:space="preserve">
<value>Writing meta.json</value>
</data>
<data name="matchDASH" xml:space="preserve">
<value>Content Matched: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/]</value>
</data>
<data name="checkingLast" xml:space="preserve">
<value>Verifying the validity of the last segment</value>
</data>
<data name="parsingStream" xml:space="preserve">
<value>Parsing streams...</value>
</data>
<data name="keyProcessorNotFound" xml:space="preserve">
<value>No Processor matched</value>
</data>
<data name="startDownloading" xml:space="preserve">
<value>Start downloading...</value>
</data>
<data name="segmentCountCheckNotPass" xml:space="preserve">
<value>Segment count check not pass, total: {}, downloaded: {}.</value>
</data>
<data name="fixingVTT" xml:space="preserve">
<value>Extracting VTT(raw) subtitle...</value>
</data>
<data name="fixingVTTmp4" xml:space="preserve">
<value>Extracting VTT(mp4) subtitle...</value>
</data>
<data name="binaryMerge" xml:space="preserve">
<value>Binary merging...</value>
</data>
<data name="fixingTTMLmp4" xml:space="preserve">
<value>Extracting TTML(mp4) subtitle...</value>
</data>
<data name="fixingTTML" xml:space="preserve">
<value>Extracting TTML(raw) subtitle...</value>
</data>
</root>

View File

@ -98,75 +98,127 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="badM3u8" xml:space="preserve">
<value>错误的m3u8</value>
<value>Bad m3u8</value>
</data>
<data name="notSupported" xml:space="preserve">
<value>当前输入不受支持: </value>
<value>Input not supported: </value>
</data>
<data name="loadingUrl" xml:space="preserve">
<value>加载URL: </value>
<value>Loading URL: </value>
</data>
<data name="matchHLS" xml:space="preserve">
<value>内容匹配: [white on deepskyblue1]HTTP Live Streaming[/]</value>
<value>Content Matched: [white on deepskyblue1]HTTP Live Streaming[/]</value>
</data>
<data name="masterM3u8Found" xml:space="preserve">
<value>检测到Master列表开始解析全部流信息</value>
<value>Master List detected, try parse all streams</value>
</data>
<data name="fetch" xml:space="preserve">
<value>获取: </value>
<value>Fetch: </value>
</data>
<data name="promptTitle" xml:space="preserve">
<value>请选择 [green]你要下载的内容[/]:</value>
<value>Please select [green]what you want to download[/]:</value>
</data>
<data name="promptChoiceText" xml:space="preserve">
<value>[grey](按键盘上下键以浏览更多内容)[/]</value>
<value>[grey](Move up and down to reveal more streams)[/]</value>
</data>
<data name="promptInfo" xml:space="preserve">
<value>(按 [blue]空格键[/] 选择流, [green]回车键[/] 完成选择)</value>
<value>(Press [blue]&lt;space&gt;[/] to toggle a stream, [green]&lt;enter&gt;[/] to accept)</value>
</data>
<data name="streamsInfo" xml:space="preserve">
<value>已解析, 共计 {} 条媒体流, 基本流 {} 条, 可选音频流 {} 条, 可选字幕流 {} 条</value>
<value>Extracted, there are {} streams, with {} basic streams, {} audio streams, {} subtitle streams</value>
</data>
<data name="liveFound" xml:space="preserve">
<value>检测到直播流</value>
<value>Live stream found</value>
</data>
<data name="selectedStream" xml:space="preserve">
<value>已选择的流:</value>
<value>Selected streams:</value>
</data>
<data name="writeJson" xml:space="preserve">
<value>写出meta.json</value>
<value>Writing meta.json</value>
</data>
<data name="matchDASH" xml:space="preserve">
<value>内容匹配: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/]</value>
<value>Content Matched: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/]</value>
</data>
<data name="checkingLast" xml:space="preserve">
<value>验证最后一个分片有效性</value>
<value>Verifying the validity of the last segment</value>
</data>
<data name="parsingStream" xml:space="preserve">
<value>正在解析媒体信息...</value>
<value>Parsing streams...</value>
</data>
<data name="keyProcessorNotFound" xml:space="preserve">
<value>找不到支持的Processor</value>
<value>No Processor matched</value>
</data>
<data name="startDownloading" xml:space="preserve">
<value>开始下载...</value>
<value>Start downloading...</value>
</data>
<data name="segmentCountCheckNotPass" xml:space="preserve">
<value>分片数量校验不通过, 共{}个,已下载{}.</value>
<value>Segment count check not pass, total: {}, downloaded: {}.</value>
</data>
<data name="fixingVTT" xml:space="preserve">
<value>正在提取VTT(raw)字幕...</value>
<value>Extracting VTT(raw) subtitle...</value>
</data>
<data name="fixingVTTmp4" xml:space="preserve">
<value>正在提取VTT(mp4)字幕...</value>
<value>Extracting VTT(mp4) subtitle...</value>
</data>
<data name="binaryMerge" xml:space="preserve">
<value>二进制合并中...</value>
<value>Binary merging...</value>
</data>
<data name="fixingTTMLmp4" xml:space="preserve">
<value>正在提取TTML(mp4)字幕...</value>
<value>Extracting TTML(mp4) subtitle...</value>
</data>
<data name="fixingTTML" xml:space="preserve">
<value>正在提取TTML(raw)字幕...</value>
<value>Extracting TTML(raw) subtitle...</value>
</data>
<data name="cmd_Input" xml:space="preserve">
<value>Input Url or File</value>
</data>
<data name="cmd_header" xml:space="preserve">
<value>Pass custom header(s) to server, Example:
-H "Cookie: mycookie" -H "User-Agent: iOS"</value>
</data>
<data name="cmd_logLevel" xml:space="preserve">
<value>Set log level</value>
</data>
<data name="cmd_autoSelect" xml:space="preserve">
<value>Automatically selects the best tracks of all types</value>
</data>
<data name="cmd_subOnly" xml:space="preserve">
<value>Select only subtitle tracks</value>
</data>
<data name="cmd_threadCount" xml:space="preserve">
<value>Set download thread count</value>
</data>
<data name="cmd_skipMerge" xml:space="preserve">
<value>Skip segments merge</value>
</data>
<data name="cmd_binaryMerge" xml:space="preserve">
<value>Binary merge</value>
</data>
<data name="cmd_delAfterDone" xml:space="preserve">
<value>Delete temporary files when done</value>
</data>
<data name="cmd_subtitleFix" xml:space="preserve">
<value>Automatically fix subtitles</value>
</data>
<data name="cmd_checkSegmentsCount" xml:space="preserve">
<value>Check if the actual number of segments downloaded matches the expected number</value>
</data>
<data name="cmd_subFormat" xml:space="preserve">
<value>Subtitle output format</value>
</data>
<data name="cmd_tmpDir" xml:space="preserve">
<value>Set temporary file directory</value>
</data>
<data name="cmd_saveDir" xml:space="preserve">
<value>Set output directory</value>
</data>
<data name="cmd_saveName" xml:space="preserve">
<value>Set output filename</value>
</data>
<data name="cmd_uiLanguage" xml:space="preserve">
<value>Set UI language</value>
</data>
<data name="cmd_skipDownload" xml:space="preserve">
<value>Skip download</value>
</data>
</root>

View File

@ -0,0 +1,244 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="badM3u8" xml:space="preserve">
<value>错误的m3u8</value>
</data>
<data name="notSupported" xml:space="preserve">
<value>当前输入不受支持: </value>
</data>
<data name="loadingUrl" xml:space="preserve">
<value>加载URL: </value>
</data>
<data name="matchHLS" xml:space="preserve">
<value>内容匹配: [white on deepskyblue1]HTTP Live Streaming[/]</value>
</data>
<data name="masterM3u8Found" xml:space="preserve">
<value>检测到Master列表开始解析全部流信息</value>
</data>
<data name="fetch" xml:space="preserve">
<value>获取: </value>
</data>
<data name="promptTitle" xml:space="preserve">
<value>请选择 [green]你要下载的内容[/]:</value>
</data>
<data name="promptChoiceText" xml:space="preserve">
<value>[grey](按键盘上下键以浏览更多内容)[/]</value>
</data>
<data name="promptInfo" xml:space="preserve">
<value>(按 [blue]空格键[/] 选择流, [green]回车键[/] 完成选择)</value>
</data>
<data name="streamsInfo" xml:space="preserve">
<value>已解析, 共计 {} 条媒体流, 基本流 {} 条, 可选音频流 {} 条, 可选字幕流 {} 条</value>
</data>
<data name="liveFound" xml:space="preserve">
<value>检测到直播流</value>
</data>
<data name="selectedStream" xml:space="preserve">
<value>已选择的流:</value>
</data>
<data name="writeJson" xml:space="preserve">
<value>写出meta.json</value>
</data>
<data name="matchDASH" xml:space="preserve">
<value>内容匹配: [white on mediumorchid1]Dynamic Adaptive Streaming over HTTP[/]</value>
</data>
<data name="checkingLast" xml:space="preserve">
<value>验证最后一个分片有效性</value>
</data>
<data name="parsingStream" xml:space="preserve">
<value>正在解析媒体信息...</value>
</data>
<data name="keyProcessorNotFound" xml:space="preserve">
<value>找不到支持的Processor</value>
</data>
<data name="startDownloading" xml:space="preserve">
<value>开始下载...</value>
</data>
<data name="segmentCountCheckNotPass" xml:space="preserve">
<value>分片数量校验不通过, 共{}个,已下载{}.</value>
</data>
<data name="fixingVTT" xml:space="preserve">
<value>正在提取VTT(raw)字幕...</value>
</data>
<data name="fixingVTTmp4" xml:space="preserve">
<value>正在提取VTT(mp4)字幕...</value>
</data>
<data name="binaryMerge" xml:space="preserve">
<value>二进制合并中...</value>
</data>
<data name="fixingTTMLmp4" xml:space="preserve">
<value>正在提取TTML(mp4)字幕...</value>
</data>
<data name="fixingTTML" xml:space="preserve">
<value>正在提取TTML(raw)字幕...</value>
</data>
<data name="cmd_Input" xml:space="preserve">
<value>链接或文件</value>
</data>
<data name="cmd_header" xml:space="preserve">
<value>为HTTP请求设置特定的请求头, 例如:
-H "Cookie: mycookie" -H "User-Agent: iOS"</value>
</data>
<data name="cmd_logLevel" xml:space="preserve">
<value>设置日志级别</value>
</data>
<data name="cmd_autoSelect" xml:space="preserve">
<value>自动选择所有类型的最佳轨道</value>
</data>
<data name="cmd_subOnly" xml:space="preserve">
<value>只选取字幕轨道</value>
</data>
<data name="cmd_threadCount" xml:space="preserve">
<value>设置下载线程数</value>
</data>
<data name="cmd_skipMerge" xml:space="preserve">
<value>跳过合并分片</value>
</data>
<data name="cmd_binaryMerge" xml:space="preserve">
<value>二进制合并</value>
</data>
<data name="cmd_delAfterDone" xml:space="preserve">
<value>完成后删除临时文件</value>
</data>
<data name="cmd_subtitleFix" xml:space="preserve">
<value>自动修正字幕</value>
</data>
<data name="cmd_checkSegmentsCount" xml:space="preserve">
<value>检测实际下载的分片数量和预期数量是否匹配</value>
</data>
<data name="cmd_subFormat" xml:space="preserve">
<value>字幕输出类型</value>
</data>
<data name="cmd_tmpDir" xml:space="preserve">
<value>设置临时文件存储目录</value>
</data>
<data name="cmd_saveDir" xml:space="preserve">
<value>设置输出目录</value>
</data>
<data name="cmd_saveName" xml:space="preserve">
<value>设置保存文件名</value>
</data>
<data name="cmd_uiLanguage" xml:space="preserve">
<value>设置UI语言</value>
</data>
<data name="cmd_skipDownload" xml:space="preserve">
<value>跳过下载</value>
</data>
</root>

View File

@ -169,4 +169,56 @@
<data name="fixingTTML" xml:space="preserve">
<value>正在提取TTML(raw)字幕...</value>
</data>
<data name="cmd_Input" xml:space="preserve">
<value>鏈接或文件</value>
</data>
<data name="cmd_header" xml:space="preserve">
<value>為HTTP請求設置特定的請求頭, 例如:
-H "Cookie: mycookie" -H "User-Agent: iOS"</value>
</data>
<data name="cmd_logLevel" xml:space="preserve">
<value>設置日誌級別</value>
</data>
<data name="cmd_autoSelect" xml:space="preserve">
<value>自動選擇所有類型的最佳軌道</value>
</data>
<data name="cmd_subOnly" xml:space="preserve">
<value>只選取字幕軌道</value>
</data>
<data name="cmd_threadCount" xml:space="preserve">
<value>設置下載線程數</value>
</data>
<data name="cmd_skipMerge" xml:space="preserve">
<value>跳過合併分片</value>
</data>
<data name="cmd_binaryMerge" xml:space="preserve">
<value>二進制合併</value>
</data>
<data name="cmd_delAfterDone" xml:space="preserve">
<value>完成後刪除臨時文件</value>
</data>
<data name="cmd_subtitleFix" xml:space="preserve">
<value>自動修正字幕</value>
</data>
<data name="cmd_checkSegmentsCount" xml:space="preserve">
<value>檢測實際下載的分片數量和預期數量是否匹配</value>
</data>
<data name="cmd_subFormat" xml:space="preserve">
<value>字幕輸出類型</value>
</data>
<data name="cmd_tmpDir" xml:space="preserve">
<value>設置臨時文件存儲目錄</value>
</data>
<data name="cmd_saveDir" xml:space="preserve">
<value>設置輸出目錄</value>
</data>
<data name="cmd_saveName" xml:space="preserve">
<value>設置保存文件名</value>
</data>
<data name="cmd_uiLanguage" xml:space="preserve">
<value>設置UI語言</value>
</data>
<data name="cmd_skipDownload" xml:space="preserve">
<value>跳過下載</value>
</data>
</root>

View File

@ -32,6 +32,7 @@ Global
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
RESX_NeutralResourcesLanguage = en-US
SolutionGuid = {87F963D4-EA06-413D-9372-C726711C32B5}
EndGlobalSection
EndGlobal

View File

@ -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<string> Input = new(name: "input", description: ResString.cmd_Input);
private readonly static Option<string?> TmpDir = new(new string[] { "--tmp-dir" }, description: ResString.cmd_tmpDir);
private readonly static Option<string?> SaveDir = new(new string[] { "--save-dir" }, description: ResString.cmd_saveDir);
private readonly static Option<string?> SaveName = new(new string[] { "--save-name" }, description: ResString.cmd_saveName);
private readonly static Option<string?> UILanguage = new(new string[] { "--ui-language" }, description: ResString.cmd_uiLanguage);
private readonly static Option<string[]?> Headers = new(new string[] { "--header", "-H" }, description: ResString.cmd_header) { Arity = ArgumentArity.ZeroOrMore, AllowMultipleArgumentsPerToken = false };
private readonly static Option<LogLevel> LogLevel = new(name: "--log-level", description: ResString.cmd_logLevel, getDefaultValue: () => Common.Log.LogLevel.INFO);
private readonly static Option<SubtitleFormat> SubtitleFormat = new(name: "--sub-format", description: ResString.cmd_subFormat, getDefaultValue: () => Enum.SubtitleFormat.VTT);
private readonly static Option<bool> AutoSelect = new(new string[] { "--auto-select" }, description: ResString.cmd_autoSelect, getDefaultValue: () => false);
private readonly static Option<bool> SubOnly = new(new string[] { "--sub-only" }, description: ResString.cmd_subOnly, getDefaultValue: () => false);
private readonly static Option<int> ThreadCount = new(new string[] { "--thread-count" }, description: ResString.cmd_threadCount, getDefaultValue: () => 8);
private readonly static Option<bool> SkipMerge = new(new string[] { "--skip-merge" }, description: ResString.cmd_skipMerge, getDefaultValue: () => false);
private readonly static Option<bool> SkipDownload = new(new string[] { "--skip-download" }, description: ResString.cmd_skipDownload, getDefaultValue: () => false);
private readonly static Option<bool> BinaryMerge = new(new string[] { "--binary-merge" }, description: ResString.cmd_binaryMerge, getDefaultValue: () => true);
private readonly static Option<bool> DelAfterDone = new(new string[] { "--del-after-done" }, description: ResString.cmd_delAfterDone, getDefaultValue: () => true);
private readonly static Option<bool> AutoSubtitleFix = new(new string[] { "--auto-subtitle-fix" }, description: ResString.cmd_subtitleFix, getDefaultValue: () => true);
private readonly static Option<bool> CheckSegmentsCount = new(new string[] { "--check-segments-count" }, description: ResString.cmd_checkSegmentsCount, getDefaultValue: () => true);
class MyOptionBinder : BinderBase<MyOption>
{
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<int> InvokeArgs(string[] args, Func<MyOption, Task> 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);
}
}
}

View File

@ -0,0 +1,77 @@
using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Enum;
namespace N_m3u8DL_RE.CommandLine
{
internal class MyOption
{
/// <summary>
/// See: <see cref="CommandInvoker.Input"/>.
/// </summary>
public string Input { get; set; } = default!;
/// <summary>
/// See: <see cref="CommandInvoker.Headers"/>.
/// </summary>
public string[]? Headers { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.LogLevel"/>.
/// </summary>
public LogLevel LogLevel { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.AutoSelect"/>.
/// </summary>
public bool AutoSelect { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.SubOnly"/>.
/// </summary>
public bool SubOnly { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.ThreadCount"/>.
/// </summary>
public int ThreadCount { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.SkipMerge"/>.
/// </summary>
public bool SkipMerge { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.BinaryMerge"/>.
/// </summary>
public bool BinaryMerge { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.DelAfterDone"/>.
/// </summary>
public bool DelAfterDone { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.AutoSubtitleFix"/>.
/// </summary>
public bool AutoSubtitleFix { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.CheckSegmentsCount"/>.
/// </summary>
public bool CheckSegmentsCount { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.SkipDownload"/>.
/// </summary>
public bool SkipDownload { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.SubtitleFormat"/>.
/// </summary>
public SubtitleFormat SubtitleFormat { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.TmpDir"/>.
/// </summary>
public string? TmpDir { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.SaveDir"/>.
/// </summary>
public string? SaveDir { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.SaveName"/>.
/// </summary>
public string? SaveName { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.UILanguage"/>.
/// </summary>
public string? UILanguage { get; set; }
}
}

View File

@ -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;
}
/// <summary>
/// 临时文件存储目录
/// </summary>
@ -49,6 +68,10 @@ namespace N_m3u8DL_RE.Config
/// </summary>
public bool AutoSubtitleFix { get; set; } = true;
/// <summary>
/// 字幕格式
/// </summary>
public SubtitleFormat SubtitleFormat { get; set; } = SubtitleFormat.VTT;
/// <summary>
/// 请求头
/// </summary>
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>()

View File

@ -5,6 +5,11 @@
<IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
<StaticallyLinked Condition="$(RuntimeIdentifier.StartsWith('win'))">true</StaticallyLinked>
<TrimMode>full</TrimMode>
<TrimmerDefaultAction>link</TrimmerDefaultAction>
<IlcTrimMetadata>true</IlcTrimMetadata>
<IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
<UseSystemResourceKeys>true</UseSystemResourceKeys>
<SatelliteResourceLanguages>zh-Hans;zh-Hant;en-US</SatelliteResourceLanguages>
<NativeAotCompilerVersion>7.0.0-*</NativeAotCompilerVersion>
</PropertyGroup>

View File

@ -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)
{

View File

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

View File

@ -7,18 +7,15 @@
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<Version>0.0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\N_m3u8DL-RE.Parser\N_m3u8DL-RE.Parser.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Subtitle\" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
</Project>

View File

@ -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<string>("请输入 [green]URL[/]: ");
url = AnsiConsole.Ask<string>("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<StreamSpec>();
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();
}
}
}

View File

@ -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<string,string> SplitHeaderArrayToDic(string[]? headers)
{
Dictionary<string,string> 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!")
};
}
}
}

View File

@ -1,7 +1,7 @@
<Directives>
<Application>
<Assembly Name="N_m3u8DL-RE" Dynamic="Required All"/>
<!--<Assembly Name="N_m3u8DL-RE" Dynamic="Required All"/>
<Assembly Name="N_m3u8DL-RE.Common" Dynamic="Required All"/>
<Assembly Name="N_m3u8DL-RE.Parser" Dynamic="Required All"/>
<Assembly Name="N_m3u8DL-RE.Parser" Dynamic="Required All"/>-->
</Application>
</Directives>