diff --git a/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs b/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs index fd90b78..20b29a0 100644 --- a/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs +++ b/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs @@ -31,6 +31,9 @@ namespace N_m3u8DL_RE.Common.Entity public string? Channels { get; set; } public string? Extension { get; set; } + //Dash + public RoleType? Role { get; set; } + //补充信息-色域 public string? VideoRange { get; set; } //补充信息-特征 @@ -74,19 +77,19 @@ namespace N_m3u8DL_RE.Common.Entity if (MediaType == Enum.MediaType.AUDIO) { prefixStr = $"[deepskyblue3]Aud[/] {encStr}"; - var d = $"{GroupId} | {(Bandwidth != null ? (Bandwidth / 1000) + " Kbps" : "")} | {Name} | {Codecs} | {Language} | {(Channels != null ? Channels + "CH" : "")}"; + var d = $"{GroupId} | {(Bandwidth != null ? (Bandwidth / 1000) + " Kbps" : "")} | {Name} | {Codecs} | {Language} | {(Channels != null ? Channels + "CH" : "")} | {Role}"; returnStr = d.EscapeMarkup(); } else if (MediaType == Enum.MediaType.SUBTITLES) { prefixStr = $"[deepskyblue3_1]Sub[/] {encStr}"; - var d = $"{GroupId} | {Language} | {Name} | {Codecs}"; + var d = $"{GroupId} | {Language} | {Name} | {Codecs} | {Role}"; returnStr = d.EscapeMarkup(); } else { prefixStr = $"[aqua]Vid[/] {encStr}"; - var d = $"{Resolution} | {Bandwidth / 1000} Kbps | {GroupId} | {FrameRate} | {Codecs} | {VideoRange}"; + var d = $"{Resolution} | {Bandwidth / 1000} Kbps | {GroupId} | {FrameRate} | {Codecs} | {VideoRange} | {Role}"; returnStr = d.EscapeMarkup(); } @@ -108,19 +111,19 @@ namespace N_m3u8DL_RE.Common.Entity if (MediaType == Enum.MediaType.AUDIO) { prefixStr = $"[deepskyblue3]Aud[/] {encStr}"; - var d = $"{(Bandwidth != null ? (Bandwidth / 1000) + " Kbps" : "")} | {Name} | {Language} | {(Channels != null ? Channels + "CH" : "")}"; + var d = $"{(Bandwidth != null ? (Bandwidth / 1000) + " Kbps" : "")} | {Name} | {Language} | {(Channels != null ? Channels + "CH" : "")} | {Role}"; returnStr = d.EscapeMarkup(); } else if (MediaType == Enum.MediaType.SUBTITLES) { prefixStr = $"[deepskyblue3_1]Sub[/] {encStr}"; - var d = $"{Language} | {Name} | {Codecs}"; + var d = $"{Language} | {Name} | {Codecs} | {Role}"; returnStr = d.EscapeMarkup(); } else { prefixStr = $"[aqua]Vid[/] {encStr}"; - var d = $"{Resolution} | {Bandwidth / 1000} Kbps | {FrameRate} | {VideoRange}"; + var d = $"{Resolution} | {Bandwidth / 1000} Kbps | {FrameRate} | {VideoRange} | {Role}"; returnStr = d.EscapeMarkup(); } @@ -150,19 +153,19 @@ namespace N_m3u8DL_RE.Common.Entity if (MediaType == Enum.MediaType.AUDIO) { prefixStr = $"[deepskyblue3]Aud[/] {encStr}"; - var d = $"{GroupId} | {(Bandwidth != null ? (Bandwidth / 1000) + " Kbps" : "")} | {Name} | {Codecs} | {Language} | {(Channels != null ? Channels + "CH" : "")} | {segmentsCountStr}"; + var d = $"{GroupId} | {(Bandwidth != null ? (Bandwidth / 1000) + " Kbps" : "")} | {Name} | {Codecs} | {Language} | {(Channels != null ? Channels + "CH" : "")} | {segmentsCountStr} | {Role}"; returnStr = d.EscapeMarkup(); } else if (MediaType == Enum.MediaType.SUBTITLES) { prefixStr = $"[deepskyblue3_1]Sub[/] {encStr}"; - var d = $"{GroupId} | {Language} | {Name} | {Codecs} | {Characteristics} | {segmentsCountStr}"; + var d = $"{GroupId} | {Language} | {Name} | {Codecs} | {Characteristics} | {segmentsCountStr} | {Role}"; returnStr = d.EscapeMarkup(); } else { prefixStr = $"[aqua]Vid[/] {encStr}"; - var d = $"{Resolution} | {Bandwidth / 1000} Kbps | {GroupId} | {FrameRate} | {Codecs} | {VideoRange} | {segmentsCountStr}"; + var d = $"{Resolution} | {Bandwidth / 1000} Kbps | {GroupId} | {FrameRate} | {Codecs} | {VideoRange} | {segmentsCountStr} | {Role}"; returnStr = d.EscapeMarkup(); } diff --git a/src/N_m3u8DL-RE.Common/Enum/RoleType.cs b/src/N_m3u8DL-RE.Common/Enum/RoleType.cs new file mode 100644 index 0000000..787801f --- /dev/null +++ b/src/N_m3u8DL-RE.Common/Enum/RoleType.cs @@ -0,0 +1,15 @@ +namespace N_m3u8DL_RE.Common.Enum +{ + public enum RoleType + { + Subtitle = 0, + Main = 1, + Alternate = 2, + Supplementary = 3, + Commentary = 4, + Dub = 5, + Description = 6, + Sign = 7, + Metadata = 8, + } +} diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs index 1de99b4..df1732f 100644 --- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs +++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs @@ -459,7 +459,7 @@ namespace N_m3u8DL_RE.Common.Resource zhCN: "通过正则表达式选择符合要求的视频流. 你能够以:分隔形式指定如下参数:\r\n\r\n" + "id=REGEX:lang=REGEX:name=REGEX:codec=REGEX:res=REGEX:frame=REGEX\r\n" + "segsMin=number:segsMax=number:ch=REGEX:range=REGEX:url=REGEX\r\n" + - "plistDurMin=hms:plistDurMax=hms:for=FOR\r\n\r\n" + + "plistDurMin=hms:plistDurMax=hms:role=string:for=FOR\r\n\r\n" + "* for=FOR: 选择方式. best[number], worst[number], all (默认: best)\r\n\r\n" + "例如: \r\n" + "# 选择最佳视频\r\n" + @@ -467,11 +467,12 @@ namespace N_m3u8DL_RE.Common.Resource "# 选择4K+HEVC视频\r\n" + "-sv res=\"3840*\":codec=hvc1:for=best\r\n" + "# 选择长度大于1小时20分钟30秒的视频\r\n" + - "-sv plistDurMin=\"1h20m30s\":for=best\r\n", + "-sv plistDurMin=\"1h20m30s\":for=best\r\n" + + "-sv role=\"main\":for:best\r\n", zhTW: "通過正則表達式選擇符合要求的影片軌. 你能夠以:分隔形式指定如下參數:\r\n\r\n" + "id=REGEX:lang=REGEX:name=REGEX:codec=REGEX:res=REGEX:frame=REGEX\r\n" + "segsMin=number:segsMax=number:ch=REGEX:range=REGEX:url=REGEX\r\n" + - "plistDurMin=hms:plistDurMax=hms:for=FOR\r\n\r\n" + + "plistDurMin=hms:plistDurMax=hms:role=string:for=FOR\r\n\r\n" + "* for=FOR: 選擇方式. best[number], worst[number], all (默認: best)\r\n\r\n" + "例如: \r\n" + "# 選擇最佳影片\r\n" + @@ -479,11 +480,12 @@ namespace N_m3u8DL_RE.Common.Resource "# 選擇4K+HEVC影片\r\n" + "-sv res=\"3840*\":codec=hvc1:for=best\r\n" + "# 選擇長度大於1小時20分鐘30秒的影片\r\n" + - "-sv plistDurMin=\"1h20m30s\":for=best\r\n", + "-sv plistDurMin=\"1h20m30s\":for=best\r\n" + + "-sv role=\"main\":for:best\r\n", enUS: "Select video streams by regular expressions. OPTIONS is a colon separated list of:\r\n\r\n" + "id=REGEX:lang=REGEX:name=REGEX:codec=REGEX:res=REGEX:frame=REGEX\r\n" + "segsMin=number:segsMax=number:ch=REGEX:range=REGEX:url=REGEX\r\n" + - "plistDurMin=hms:plistDurMax=hms:for=FOR\r\n\r\n" + + "plistDurMin=hms:plistDurMax=hms:role=string:for=FOR\r\n\r\n" + "* for=FOR: Select type. best[number], worst[number], all (Default: best)\r\n\r\n" + "Examples: \r\n" + "# select best video\r\n" + @@ -491,7 +493,8 @@ namespace N_m3u8DL_RE.Common.Resource "# select 4K+HEVC video\r\n" + "-sv res=\"3840*\":codec=hvc1:for=best\r\n" + "# Select best video with duration longer than 1 hour 20 minutes 30 seconds\r\n" + - "-sv plistDurMin=\"1h20m30s\":for=best\r\n" + "-sv plistDurMin=\"1h20m30s\":for=best\r\n" + + "-sv role=\"main\":for:best\r\n" ), ["cmd_selectAudio"] = new TextContainer ( @@ -514,7 +517,8 @@ namespace N_m3u8DL_RE.Common.Resource "# 选择最佳英语音轨\r\n" + "-sa lang=en:for=best\r\n" + "# 选择最佳的2条英语(或日语)音轨\r\n" + - "-sa lang=\"ja|en\":for=best2\r\n", + "-sa lang=\"ja|en\":for=best2\r\n" + + "-sa role=\"main\":for:best\r\n", zhTW: "通過正則表達式選擇符合要求的音軌. 參考 --select-video\r\n\r\n" + "例如: \r\n" + "# 選擇所有音訊\r\n" + @@ -522,7 +526,8 @@ namespace N_m3u8DL_RE.Common.Resource "# 選擇最佳英語音軌\r\n" + "-sa lang=en:for=best\r\n" + "# 選擇最佳的2條英語(或日語)音軌\r\n" + - "-sa lang=\"ja|en\":for=best2\r\n", + "-sa lang=\"ja|en\":for=best2\r\n" + + "-sa role=\"main\":for:best\r\n", enUS: "Select audio streams by regular expressions. ref --select-video\r\n\r\n" + "Examples: \r\n" + "# select all\r\n" + @@ -530,7 +535,8 @@ namespace N_m3u8DL_RE.Common.Resource "# select best eng audio\r\n" + "-sa lang=en:for=best\r\n" + "# select best 2, and language is ja or en\r\n" + - "-sa lang=\"ja|en\":for=best2\r\n" + "-sa lang=\"ja|en\":for=best2\r\n" + + "-sa role=\"main\":for:best\r\n" ), ["cmd_selectSubtitle"] = new TextContainer ( diff --git a/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs b/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs index c2b9045..b84021d 100644 --- a/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs +++ b/src/N_m3u8DL-RE.Parser/Extractor/DASHExtractor2.cs @@ -1,4 +1,4 @@ -using N_m3u8DL_RE.Common.Entity; +using N_m3u8DL_RE.Common.Entity; using N_m3u8DL_RE.Common.Enum; using N_m3u8DL_RE.Common.Util; using N_m3u8DL_RE.Parser.Config; @@ -189,10 +189,17 @@ namespace N_m3u8DL_RE.Parser.Extractor if (role != null) { var v = role.Attribute("value")?.Value; - if (v == "subtitle") - streamSpec.MediaType = MediaType.SUBTITLES; - if (mType != null && mType.Contains("ttml")) - streamSpec.Extension = "ttml"; + if (Enum.TryParse(v, true, out RoleType roleType)) + { + streamSpec.Role = roleType; + + if (roleType == RoleType.Subtitle) + { + streamSpec.MediaType = MediaType.SUBTITLES; + if (mType != null && mType.Contains("ttml")) + streamSpec.Extension = "ttml"; + } + } } streamSpec.Playlist.IsLive = isLive; //设置刷新间隔 timeShiftBufferDepth / 2 @@ -213,7 +220,7 @@ namespace N_m3u8DL_RE.Parser.Extractor { streamSpec.PublishTime = DateTime.Parse(publishTime); } - + //第一种形式 SegmentBase var segmentBaseElement = representation.Elements().Where(e => e.Name.LocalName == "SegmentBase").FirstOrDefault(); @@ -439,7 +446,7 @@ namespace N_m3u8DL_RE.Parser.Extractor } //判断加密情况 - if (adaptationSet.Elements().Concat(representation.Elements()).Any(e => e.Name.LocalName == "ContentProtection")) + if (adaptationSet.Elements().Concat(representation.Elements()).Any(e => e.Name.LocalName == "ContentProtection")) { if (streamSpec.Playlist.MediaInit != null) { @@ -453,7 +460,7 @@ namespace N_m3u8DL_RE.Parser.Extractor //处理同一ID分散在不同Period的情况 var _index = streamList.FindIndex(_f => _f.PeriodId != streamSpec.PeriodId && _f.GroupId == streamSpec.GroupId && _f.Resolution == streamSpec.Resolution && _f.MediaType == streamSpec.MediaType); - if (_index > -1) + if (_index > -1) { if (isLive) { diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs index 2a45538..3a59b52 100644 --- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs +++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs @@ -1,4 +1,4 @@ -using N_m3u8DL_RE.Common.Enum; +using N_m3u8DL_RE.Common.Enum; using N_m3u8DL_RE.Common.Log; using N_m3u8DL_RE.Common.Resource; using N_m3u8DL_RE.Common.Util; @@ -99,7 +99,7 @@ namespace N_m3u8DL_RE.CommandLine private readonly static Option VideoFilter = new(new string[] { "-sv", "--select-video" }, description: ResString.cmd_selectVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" }; private readonly static Option AudioFilter = new(new string[] { "-sa", "--select-audio" }, description: ResString.cmd_selectAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" }; private readonly static Option SubtitleFilter = new(new string[] { "-ss", "--select-subtitle" }, description: ResString.cmd_selectSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" }; - + private readonly static Option DropVideoFilter = new(new string[] { "-dv", "--drop-video" }, description: ResString.cmd_dropVideo, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" }; private readonly static Option DropAudioFilter = new(new string[] { "-da", "--drop-audio" }, description: ResString.cmd_dropAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" }; private readonly static Option DropSubtitleFilter = new(new string[] { "-ds", "--drop-subtitle" }, description: ResString.cmd_dropSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" }; @@ -178,7 +178,7 @@ namespace N_m3u8DL_RE.CommandLine return null; } } - + /// /// 解析用户代理 /// @@ -367,6 +367,10 @@ namespace N_m3u8DL_RE.CommandLine if (!string.IsNullOrEmpty(plistDurMax)) streamFilter.PlaylistMaxDur = OtherUtil.ParseSeconds(plistDurMax); + var role = p.GetValue("role"); + if (System.Enum.TryParse(role, true, out RoleType roleType)) + streamFilter.Role = roleType; + return streamFilter; } @@ -553,7 +557,7 @@ namespace N_m3u8DL_RE.CommandLine //混流设置 var muxAfterDoneValue = bindingContext.ParseResult.GetValueForOption(MuxAfterDone); - if (muxAfterDoneValue != null) + if (muxAfterDoneValue != null) { option.MuxAfterDone = true; option.MuxOptions = muxAfterDoneValue; @@ -571,7 +575,7 @@ namespace N_m3u8DL_RE.CommandLine { var argList = new List(args); var index = -1; - if ((index = argList.IndexOf("--morehelp")) >= 0 && argList.Count > index + 1) + if ((index = argList.IndexOf("--morehelp")) >= 0 && argList.Count > index + 1) { var option = argList[index + 1]; var msg = option switch diff --git a/src/N_m3u8DL-RE/Entity/StreamFilter.cs b/src/N_m3u8DL-RE/Entity/StreamFilter.cs index f260cda..41191b3 100644 --- a/src/N_m3u8DL-RE/Entity/StreamFilter.cs +++ b/src/N_m3u8DL-RE/Entity/StreamFilter.cs @@ -23,6 +23,7 @@ namespace N_m3u8DL_RE.Entity public long? SegmentsMaxCount { get; set; } public double? PlaylistMinDur { get; set; } public double? PlaylistMaxDur { get; set; } + public RoleType? Role { get; set; } public string For { get; set; } = "best"; @@ -43,6 +44,7 @@ namespace N_m3u8DL_RE.Entity if (SegmentsMaxCount != null) sb.Append($"SegmentsMaxCount: {SegmentsMaxCount} "); if (PlaylistMinDur != null) sb.Append($"PlaylistMinDur: {PlaylistMinDur} "); if (PlaylistMaxDur != null) sb.Append($"PlaylistMaxDur: {PlaylistMaxDur} "); + if (Role.HasValue) sb.Append($"Role: {Role} "); return sb.ToString() + $"For: {For}"; } diff --git a/src/N_m3u8DL-RE/Util/FilterUtil.cs b/src/N_m3u8DL-RE/Util/FilterUtil.cs index e71b36c..f89a595 100644 --- a/src/N_m3u8DL-RE/Util/FilterUtil.cs +++ b/src/N_m3u8DL-RE/Util/FilterUtil.cs @@ -46,6 +46,8 @@ namespace N_m3u8DL_RE.Util inputs = inputs.Where(i => i.Playlist?.TotalDuration > filter.PlaylistMinDur); if (filter.PlaylistMaxDur != null) inputs = inputs.Where(i => i.Playlist?.TotalDuration < filter.PlaylistMaxDur); + if (filter.Role.HasValue) + inputs = inputs.Where(i => i.Role == filter.Role); var bestNumberStr = filter.For.Replace("best", ""); var worstNumberStr = filter.For.Replace("worst", "");