diff --git a/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs b/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs
index c1c2442..fd90b78 100644
--- a/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs
+++ b/src/N_m3u8DL-RE.Common/Entity/StreamSpec.cs
@@ -17,6 +17,9 @@ namespace N_m3u8DL_RE.Common.Entity
public string? Name { get; set; }
public Choise? Default { get; set; }
+ //由于用户选择 被跳过的分片总时长
+ public double? SkippedDuration { get; set; }
+
//MSS信息
public MSSData? MSSData { get; set; }
diff --git a/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs b/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs
index 9a637c7..a0f4979 100644
--- a/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs
+++ b/src/N_m3u8DL-RE.Common/Entity/WebVttSub.cs
@@ -225,6 +225,22 @@ namespace N_m3u8DL_RE.Common.Entity
return sb.ToString();
}
+ ///
+ /// 字幕向前平移指定时间
+ ///
+ ///
+ public void LeftShiftTime(TimeSpan time)
+ {
+ foreach (var cue in this.Cues)
+ {
+ if (cue.StartTime.TotalSeconds - time.TotalSeconds > 0) cue.StartTime -= time;
+ else cue.StartTime = TimeSpan.FromSeconds(0);
+
+ if (cue.EndTime.TotalSeconds - time.TotalSeconds > 0) cue.EndTime -= time;
+ else cue.EndTime = TimeSpan.FromSeconds(0);
+ }
+ }
+
public string ToVtt()
{
return "WEBVTT" + Environment.NewLine + Environment.NewLine + ToString();
diff --git a/src/N_m3u8DL-RE.Common/Resource/ResString.cs b/src/N_m3u8DL-RE.Common/Resource/ResString.cs
index 9e2c6e5..f88cd00 100644
--- a/src/N_m3u8DL-RE.Common/Resource/ResString.cs
+++ b/src/N_m3u8DL-RE.Common/Resource/ResString.cs
@@ -10,6 +10,9 @@ namespace N_m3u8DL_RE.Common.Resource
public class ResString
{
public readonly static string ReLiveTs = "";
+ public static string customRangeWarn { get => GetText("customRangeWarn"); }
+ public static string customRangeFound { get => GetText("customRangeFound"); }
+ public static string customRangeInvalid { get => GetText("customRangeInvalid"); }
public static string autoBinaryMerge { get => GetText("autoBinaryMerge"); }
public static string autoBinaryMerge2 { get => GetText("autoBinaryMerge2"); }
public static string autoBinaryMerge3 { get => GetText("autoBinaryMerge3"); }
@@ -41,6 +44,7 @@ namespace N_m3u8DL_RE.Common.Resource
public static string cmd_selectSubtitle { get => GetText("cmd_selectSubtitle"); }
public static string cmd_dropSubtitle { get => GetText("cmd_dropSubtitle"); }
public static string cmd_selectSubtitle_more { get => GetText("cmd_selectSubtitle_more"); }
+ public static string cmd_custom_range { get => GetText("cmd_custom_range"); }
public static string cmd_customHLSMethod { get => GetText("cmd_customHLSMethod"); }
public static string cmd_customHLSKey { get => GetText("cmd_customHLSKey"); }
public static string cmd_customHLSIv { get => GetText("cmd_customHLSIv"); }
@@ -69,6 +73,7 @@ namespace N_m3u8DL_RE.Common.Resource
public static string cmd_concurrentDownload { get => GetText("cmd_concurrentDownload"); }
public static string cmd_useSystemProxy { get => GetText("cmd_useSystemProxy"); }
public static string cmd_customProxy { get => GetText("cmd_customProxy"); }
+ public static string cmd_customRange { get => GetText("cmd_customRange"); }
public static string cmd_liveKeepSegments { get => GetText("cmd_liveKeepSegments"); }
public static string cmd_livePipeMux { get => GetText("cmd_livePipeMux"); }
public static string cmd_liveRecordLimit { get => GetText("cmd_liveRecordLimit"); }
diff --git a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs
index ff63767..8581ebf 100644
--- a/src/N_m3u8DL-RE.Common/Resource/StaticText.cs
+++ b/src/N_m3u8DL-RE.Common/Resource/StaticText.cs
@@ -10,6 +10,24 @@ namespace N_m3u8DL_RE.Common.Resource
{
public static Dictionary LANG_DIC = new()
{
+ ["customRangeWarn"] = new TextContainer
+ (
+ zhCN: "请注意,自定义下载范围有时会导致音画不同步",
+ zhTW: "請注意,自定義下載範圍有時會導致音畫不同步",
+ enUS: "Please note that custom range may sometimes result in audio and video being out of sync"
+ ),
+ ["customRangeInvalid"] = new TextContainer
+ (
+ zhCN: "自定义下载范围无效",
+ zhTW: "自定義下載範圍無效",
+ enUS: "User customed range invalid"
+ ),
+ ["customRangeFound"] = new TextContainer
+ (
+ zhCN: "用户自定义下载范围:",
+ zhTW: "用戶自定義下載範圍:",
+ enUS: "User customed range: "
+ ),
["processImageSub"] = new TextContainer
(
zhCN: "正在处理图形字幕",
@@ -304,6 +322,12 @@ namespace N_m3u8DL_RE.Common.Resource
zhTW: "設置請求代理, 如 http://127.0.0.1:8888",
enUS: "Set web request proxy, like http://127.0.0.1:8888"
),
+ ["cmd_customRange"] = new TextContainer
+ (
+ zhCN: "仅下载部分分片. 输入 \"--morehelp custom-range\" 以查看详细信息",
+ zhTW: "僅下載部分分片. 輸入 \"--morehelp custom-range\" 以查看詳細訊息",
+ enUS: "Download only part of the segments. Use \"--morehelp custom-range\" for more details"
+ ),
["cmd_useSystemProxy"] = new TextContainer
(
zhCN: "使用系统默认代理",
@@ -478,6 +502,39 @@ namespace N_m3u8DL_RE.Common.Resource
zhTW: "通過正則表達式去除符合要求的字幕流.",
enUS: "Drop subtitle streams by regular expressions."
),
+ ["cmd_custom_range"] = new TextContainer
+ (
+ zhCN: "下载点播内容时, 仅下载部分分片.\r\n\r\n" +
+ "例如: \r\n" +
+ "# 下载[0,10]共11个分片\r\n" +
+ "--custom-range 0-10\r\n" +
+ "# 下载从序号10开始的后续分片\r\n" +
+ "--custom-range 10-\r\n" +
+ "# 下载前100个分片\r\n" +
+ "--custom-range -99\r\n" +
+ "# 下载第5分钟到20分钟的内容\r\n" +
+ "--custom-range 05:00-20:00\r\n",
+ zhTW: "下載點播內容時, 僅下載部分分片.\r\n\r\n" +
+ "例如: \r\n" +
+ "# 下載[0,10]共11個分片\r\n" +
+ "--custom-range 0-10\r\n" +
+ "# 下載從序號10開始的後續分片\r\n" +
+ "--custom-range 10-\r\n" +
+ "# 下載前100個分片\r\n" +
+ "--custom-range -99\r\n" +
+ "# 下載第5分鐘到20分鐘的內容\r\n" +
+ "--custom-range 05:00-20:00\r\n",
+ enUS: "Download only part of the segments when downloading vod content.\r\n\r\n" +
+ "Examples: \r\n" +
+ "# Download [0,10], a total of 11 segments\r\n" +
+ "--custom-range 0-10\r\n" +
+ "# Download subsequent segments starting from index 10\r\n" +
+ "--custom-range 10-\r\n" +
+ "# Download the first 100 segments\r\n" +
+ "--custom-range -99\r\n" +
+ "# Download content from the 05:00 to 20:00\r\n" +
+ "--custom-range 05:00-20:00\r\n"
+ ),
["cmd_selectSubtitle_more"] = new TextContainer
(
zhCN: "通过正则表达式选择符合要求的字幕流. 参考 --select-video\r\n\r\n" +
@@ -505,6 +562,7 @@ namespace N_m3u8DL_RE.Common.Resource
"* format=FORMAT: 指定混流容器 mkv, mp4\r\n" +
"* muxer=MUXER: 指定混流程序 ffmpeg, mkvmerge (默认: ffmpeg)\r\n" +
"* bin_path=PATH: 指定程序路径 (默认: 自动寻找)\r\n" +
+ "* skip_sub=BOOL: 是否忽略字幕文件 (默认: false)\r\n" +
"* keep=BOOL: 混流完成是否保留文件 true, false (默认: false)\r\n\r\n" +
"例如: \r\n" +
"# 混流为mp4容器\r\n" +
@@ -517,6 +575,7 @@ namespace N_m3u8DL_RE.Common.Resource
"* format=FORMAT: 指定混流容器 mkv, mp4\r\n" +
"* muxer=MUXER: 指定混流程序 ffmpeg, mkvmerge (默認: ffmpeg)\r\n" +
"* bin_path=PATH: 指定程序路徑 (默認: 自動尋找)\r\n" +
+ "* skip_sub=BOOL: 是否忽略字幕文件 (默認: false)\r\n" +
"* keep=BOOL: 混流完成是否保留文件 true, false (默認: false)\r\n\r\n" +
"例如: \r\n" +
"# 混流為mp4容器\r\n" +
@@ -529,6 +588,7 @@ namespace N_m3u8DL_RE.Common.Resource
"* format=FORMAT: set container. mkv, mp4\r\n" +
"* muxer=MUXER: set muxer. ffmpeg, mkvmerge (Default: ffmpeg)\r\n" +
"* bin_path=PATH: set binary file path. (Default: auto)\r\n" +
+ "* skip_sub=BOOL: set whether or not skip subtitle files (Default: false)\r\n" +
"* keep=BOOL: set whether or not keep files. true, false (Default: false)\r\n\r\n" +
"Examples: \r\n" +
"# mux to mp4\r\n" +
diff --git a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs
index 1d36b4b..ed0511e 100644
--- a/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs
+++ b/src/N_m3u8DL-RE/CommandLine/CommandInvoker.cs
@@ -18,10 +18,12 @@ namespace N_m3u8DL_RE.CommandLine
{
internal partial class CommandInvoker
{
- public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20230618";
+ public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20230628";
[GeneratedRegex("((best|worst)\\d*|all)")]
private static partial Regex ForStrRegex();
+ [GeneratedRegex("(\\d*)-(\\d*)")]
+ private static partial Regex RangeRegex();
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);
@@ -60,6 +62,9 @@ namespace N_m3u8DL_RE.CommandLine
private readonly static Option UseSystemProxy = new(new string[] { "--use-system-proxy" }, description: ResString.cmd_useSystemProxy, getDefaultValue: () => true);
private readonly static Option CustomProxy = new(new string[] { "--custom-proxy" }, description: ResString.cmd_customProxy, parseArgument: ParseProxy) { ArgumentHelpName = "URL" };
+ //只下载部分分片
+ private readonly static Option CustomRange = new(new string[] { "--custom-range" }, description: ResString.cmd_customRange, parseArgument: ParseCustomRange) { ArgumentHelpName = "RANGE" };
+
//morehelp
private readonly static Option MoreHelp = new(new string[] { "--morehelp" }, description: ResString.cmd_moreHelp) { ArgumentHelpName = "OPTION" };
@@ -94,6 +99,55 @@ namespace N_m3u8DL_RE.CommandLine
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" };
+ ///
+ /// 解析用户定义的下载范围
+ ///
+ ///
+ ///
+ ///
+ private static CustomRange? ParseCustomRange(ArgumentResult result)
+ {
+ var input = result.Tokens.First().Value;
+ //支持的种类 0-100; 01:00:00-02:30:00; -300; 300-; 05:00-; -03:00;
+ try
+ {
+ if (string.IsNullOrEmpty(input))
+ return null;
+
+ var arr = input.Split('-');
+ if (arr.Length != 2)
+ throw new ArgumentException("Bad format!");
+
+ if (input.Contains(":"))
+ {
+ return new CustomRange()
+ {
+ InputStr = input,
+ StartSec = arr[0] == "" ? 0 : OtherUtil.ParseDur(arr[0]).TotalSeconds,
+ EndSec = arr[1] == "" ? double.MaxValue : OtherUtil.ParseDur(arr[1]).TotalSeconds,
+ };
+ }
+ else if (RangeRegex().IsMatch(input))
+ {
+ var left = RangeRegex().Match(input).Groups[1].Value;
+ var right = RangeRegex().Match(input).Groups[2].Value;
+ return new CustomRange()
+ {
+ InputStr = input,
+ StartSegIndex = left == "" ? 0 : long.Parse(left),
+ EndSegIndex = right == "" ? long.MaxValue : long.Parse(right),
+ };
+ }
+
+ throw new ArgumentException("Bad format!");
+ }
+ catch (Exception ex)
+ {
+ result.ErrorMessage = $"error in parse CustomRange: " + ex.Message;
+ return null;
+ }
+ }
+
///
/// 解析用户代理
///
@@ -365,6 +419,13 @@ namespace N_m3u8DL_RE.CommandLine
result.ErrorMessage = $"keep={keep} not valid";
return null;
}
+ //是否忽略字幕
+ var skipSub = p.GetValue("skip_sub") ?? "false";
+ if (skipSub != "true" && skipSub != "false")
+ {
+ result.ErrorMessage = $"skip_sub={keep} not valid";
+ return null;
+ }
//冲突检测
if (muxer == "mkvmerge" && format == "mp4")
{
@@ -376,6 +437,7 @@ namespace N_m3u8DL_RE.CommandLine
UseMkvmerge = muxer == "mkvmerge",
MuxToMp4 = format == "mp4",
KeepFiles = keep == "true",
+ SkipSubtitle = skipSub == "true",
BinPath = bin_path == "auto" ? null : bin_path
};
}
@@ -431,6 +493,7 @@ namespace N_m3u8DL_RE.CommandLine
LiveFixVttByAudio = bindingContext.ParseResult.GetValueForOption(LiveFixVttByAudio),
UseSystemProxy = bindingContext.ParseResult.GetValueForOption(UseSystemProxy),
CustomProxy = bindingContext.ParseResult.GetValueForOption(CustomProxy),
+ CustomRange = bindingContext.ParseResult.GetValueForOption(CustomRange),
LiveWaitTime = bindingContext.ParseResult.GetValueForOption(LiveWaitTime),
NoDateInfo = bindingContext.ParseResult.GetValueForOption(NoDateInfo),
NoLog = bindingContext.ParseResult.GetValueForOption(NoLog),
@@ -458,10 +521,8 @@ namespace N_m3u8DL_RE.CommandLine
if (muxAfterDoneValue != null)
{
option.MuxAfterDone = true;
- option.MuxKeepFiles = muxAfterDoneValue.KeepFiles;
- option.MuxToMp4 = muxAfterDoneValue.MuxToMp4;
- option.UseMkvmerge = muxAfterDoneValue.UseMkvmerge;
- if (option.UseMkvmerge) option.MkvmergeBinaryPath = muxAfterDoneValue.BinPath;
+ option.MuxOptions = muxAfterDoneValue;
+ if (muxAfterDoneValue.UseMkvmerge) option.MkvmergeBinaryPath = muxAfterDoneValue.BinPath;
else option.FFmpegBinaryPath = muxAfterDoneValue.BinPath;
}
@@ -485,6 +546,7 @@ namespace N_m3u8DL_RE.CommandLine
"select-video" => ResString.cmd_selectVideo_more,
"select-audio" => ResString.cmd_selectAudio_more,
"select-subtitle" => ResString.cmd_selectSubtitle_more,
+ "custom-range" => ResString.cmd_custom_range,
_ => $"Option=\"{option}\" not found"
};
Console.WriteLine($"More Help:\r\n\r\n --{option}\r\n\r\n" + msg);
@@ -498,7 +560,7 @@ namespace N_m3u8DL_RE.CommandLine
FFmpegBinaryPath,
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
MuxAfterDone,
- CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, TaskStartAt,
+ CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, CustomRange, TaskStartAt,
LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LivePipeMux, LiveFixVttByAudio, LiveRecordLimit, LiveWaitTime,
MuxImports, VideoFilter, AudioFilter, SubtitleFilter, DropVideoFilter, DropAudioFilter, DropSubtitleFilter, MoreHelp
};
diff --git a/src/N_m3u8DL-RE/CommandLine/MyOption.cs b/src/N_m3u8DL-RE/CommandLine/MyOption.cs
index 7716010..ec2369a 100644
--- a/src/N_m3u8DL-RE/CommandLine/MyOption.cs
+++ b/src/N_m3u8DL-RE/CommandLine/MyOption.cs
@@ -113,14 +113,6 @@ namespace N_m3u8DL_RE.CommandLine
///
public bool MuxAfterDone { get; set; }
///
- /// See: .
- ///
- public bool MuxToMp4 { get; set; }
- ///
- /// See: .
- ///
- public bool UseMkvmerge { get; set; }
- ///
/// See: .
///
public bool ConcurrentDownload { get; set; }
@@ -221,10 +213,14 @@ namespace N_m3u8DL_RE.CommandLine
///
public WebProxy? CustomProxy { get; set; }
///
+ /// See: .
+ ///
+ public CustomRange? CustomRange { get; set; }
+ ///
/// See: .
///
public int? LiveWaitTime { get; set; }
- public bool MuxKeepFiles { get; set; }
+ public MuxOptions MuxOptions { get; set; }
//public bool LiveWriteHLS { get; set; } = true;
///
/// See: .
diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs
index 760148a..c2225ee 100644
--- a/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs
+++ b/src/N_m3u8DL-RE/DownloadManager/SimpleDownloadManager.cs
@@ -126,6 +126,9 @@ namespace N_m3u8DL_RE.DownloadManager
var currentKID = "";
var readInfo = false; //是否读取过
+ //用户自定义范围导致被跳过的时长 计算字幕偏移使用
+ var skippedDur = streamSpec.SkippedDuration ?? 0d;
+
Logger.Debug($"dirName: {dirName}; tmpDir: {tmpDir}; saveDir: {saveDir}; saveName: {saveName}");
//创建文件夹
@@ -360,7 +363,7 @@ namespace N_m3u8DL_RE.DownloadManager
//手动计算MPEGTS
if (finalVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
{
- vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
+ vtt.MpegtsTimestamp = 90000 * (long)(skippedDur + keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
}
if (first) { finalVtt = vtt; first = false; }
else finalVtt.AddCuesFromOne(vtt);
@@ -371,6 +374,8 @@ namespace N_m3u8DL_RE.DownloadManager
FileDic.Clear();
var index = 0;
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
+ //设置字幕偏移
+ finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
var subContentFixed = finalVtt.ToVtt();
//转换字幕格式
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
@@ -405,6 +410,8 @@ namespace N_m3u8DL_RE.DownloadManager
FileDic.Clear();
var index = 0;
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
+ //设置字幕偏移
+ finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
var subContentFixed = finalVtt.ToVtt();
//转换字幕格式
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
@@ -435,7 +442,7 @@ namespace N_m3u8DL_RE.DownloadManager
//手动计算MPEGTS
if (finalVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
{
- vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
+ vtt.MpegtsTimestamp = 90000 * (long)(skippedDur + keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
}
if (first) { finalVtt = vtt; first = false; }
else finalVtt.AddCuesFromOne(vtt);
@@ -451,6 +458,8 @@ namespace N_m3u8DL_RE.DownloadManager
FileDic.Clear();
var index = 0;
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
+ //设置字幕偏移
+ finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
var subContentFixed = finalVtt.ToVtt();
//转换字幕格式
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
@@ -485,7 +494,7 @@ namespace N_m3u8DL_RE.DownloadManager
//手动计算MPEGTS
if (finalVtt.MpegtsTimestamp == 0 && vtt.MpegtsTimestamp == 0)
{
- vtt.MpegtsTimestamp = 90000 * (long)keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration);
+ vtt.MpegtsTimestamp = 90000 * (long)(skippedDur + keys.Where(s => s.Index < seg.Index).Sum(s => s.Duration));
}
if (first) { finalVtt = vtt; first = false; }
else finalVtt.AddCuesFromOne(vtt);
@@ -502,6 +511,8 @@ namespace N_m3u8DL_RE.DownloadManager
FileDic.Clear();
var index = 0;
var path = Path.Combine(tmpDir, index.ToString(pad) + ".fix.vtt");
+ //设置字幕偏移
+ finalVtt.LeftShiftTime(TimeSpan.FromSeconds(skippedDur));
var subContentFixed = finalVtt.ToVtt();
//转换字幕格式
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
@@ -595,6 +606,7 @@ namespace N_m3u8DL_RE.DownloadManager
//记录所有文件信息
if (File.Exists(output))
+ {
OutputFiles.Add(new OutputFile()
{
Index = task.Id,
@@ -604,6 +616,7 @@ namespace N_m3u8DL_RE.DownloadManager
Mediainfos = mediaInfos,
MediaType = streamSpec.MediaType,
});
+ }
return true;
}
@@ -683,24 +696,29 @@ namespace N_m3u8DL_RE.DownloadManager
if (success && DownloaderConfig.MyOptions.MuxAfterDone && OutputFiles.Count > 0)
{
OutputFiles = OutputFiles.OrderBy(o => o.Index).ToList();
+ //是否跳过字幕
+ if (DownloaderConfig.MyOptions.MuxOptions.SkipSubtitle)
+ {
+ OutputFiles = OutputFiles.Where(o => o.MediaType != MediaType.SUBTITLES).ToList();
+ }
if (DownloaderConfig.MyOptions.MuxImports != null)
{
OutputFiles.AddRange(DownloaderConfig.MyOptions.MuxImports);
}
OutputFiles.ForEach(f => Logger.WarnMarkUp($"[grey]{Path.GetFileName(f.FilePath).EscapeMarkup()}[/]"));
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
- var ext = DownloaderConfig.MyOptions.MuxToMp4 ? ".mp4" : ".mkv";
+ var ext = DownloaderConfig.MyOptions.MuxOptions.MuxToMp4 ? ".mp4" : ".mkv";
var dirName = Path.GetFileName(DownloaderConfig.DirPrefix);
var outName = $"{dirName}.MUX";
var outPath = Path.Combine(saveDir, outName);
Logger.WarnMarkUp($"Muxing to [grey]{outName.EscapeMarkup()}{ext}[/]");
var result = false;
- if (DownloaderConfig.MyOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
- else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxToMp4, !DownloaderConfig.MyOptions.NoDateInfo);
+ if (DownloaderConfig.MyOptions.MuxOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
+ else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxOptions.MuxToMp4, !DownloaderConfig.MyOptions.NoDateInfo);
//完成后删除各轨道文件
if (result)
{
- if (!DownloaderConfig.MyOptions.MuxKeepFiles)
+ if (!DownloaderConfig.MyOptions.MuxOptions.KeepFiles)
{
Logger.WarnMarkUp("[grey]Cleaning files...[/]");
OutputFiles.ForEach(f => File.Delete(f.FilePath));
diff --git a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs
index 229d3cf..0f531aa 100644
--- a/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs
+++ b/src/N_m3u8DL-RE/DownloadManager/SimpleLiveRecordManager2.cs
@@ -892,24 +892,29 @@ namespace N_m3u8DL_RE.DownloadManager
if (success && DownloaderConfig.MyOptions.MuxAfterDone && OutputFiles.Count > 0)
{
OutputFiles = OutputFiles.OrderBy(o => o.Index).ToList();
+ //是否跳过字幕
+ if (DownloaderConfig.MyOptions.MuxOptions.SkipSubtitle)
+ {
+ OutputFiles = OutputFiles.Where(o => o.MediaType != MediaType.SUBTITLES).ToList();
+ }
if (DownloaderConfig.MyOptions.MuxImports != null)
{
OutputFiles.AddRange(DownloaderConfig.MyOptions.MuxImports);
}
OutputFiles.ForEach(f => Logger.WarnMarkUp($"[grey]{Path.GetFileName(f.FilePath).EscapeMarkup()}[/]"));
var saveDir = DownloaderConfig.MyOptions.SaveDir ?? Environment.CurrentDirectory;
- var ext = DownloaderConfig.MyOptions.MuxToMp4 ? ".mp4" : ".mkv";
+ var ext = DownloaderConfig.MyOptions.MuxOptions.MuxToMp4 ? ".mp4" : ".mkv";
var dirName = Path.GetFileName(DownloaderConfig.DirPrefix);
var outName = $"{dirName}.MUX";
var outPath = Path.Combine(saveDir, outName);
Logger.WarnMarkUp($"Muxing to [grey]{outName.EscapeMarkup()}{ext}[/]");
var result = false;
- if (DownloaderConfig.MyOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
- else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxToMp4, !DownloaderConfig.MyOptions.NoDateInfo);
+ if (DownloaderConfig.MyOptions.MuxOptions.UseMkvmerge) result = MergeUtil.MuxInputsByMkvmerge(DownloaderConfig.MyOptions.MkvmergeBinaryPath!, OutputFiles.ToArray(), outPath);
+ else result = MergeUtil.MuxInputsByFFmpeg(DownloaderConfig.MyOptions.FFmpegBinaryPath!, OutputFiles.ToArray(), outPath, DownloaderConfig.MyOptions.MuxOptions.MuxToMp4, !DownloaderConfig.MyOptions.NoDateInfo);
//完成后删除各轨道文件
if (result)
{
- if (!DownloaderConfig.MyOptions.MuxKeepFiles)
+ if (!DownloaderConfig.MyOptions.MuxOptions.KeepFiles)
{
Logger.WarnMarkUp("[grey]Cleaning files...[/]");
OutputFiles.ForEach(f => File.Delete(f.FilePath));
diff --git a/src/N_m3u8DL-RE/Entity/CustomRange.cs b/src/N_m3u8DL-RE/Entity/CustomRange.cs
new file mode 100644
index 0000000..e59b4c1
--- /dev/null
+++ b/src/N_m3u8DL-RE/Entity/CustomRange.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace N_m3u8DL_RE.Entity
+{
+ public class CustomRange
+ {
+ public required string InputStr { get; set; }
+ public double? StartSec { get; set; }
+ public double? EndSec { get; set; }
+
+ public long? StartSegIndex { get; set; }
+ public long? EndSegIndex { get; set;}
+
+ public override string? ToString()
+ {
+ return $"StartSec: {StartSec}, EndSec: {EndSec}, StartSegIndex: {StartSegIndex}, EndSegIndex: {EndSegIndex}";
+ }
+ }
+}
diff --git a/src/N_m3u8DL-RE/Entity/MuxOptions.cs b/src/N_m3u8DL-RE/Entity/MuxOptions.cs
index 2761832..7d43757 100644
--- a/src/N_m3u8DL-RE/Entity/MuxOptions.cs
+++ b/src/N_m3u8DL-RE/Entity/MuxOptions.cs
@@ -11,6 +11,7 @@ namespace N_m3u8DL_RE.Entity
public bool UseMkvmerge { get; set; } = false;
public bool MuxToMp4 { get; set; } = false;
public bool KeepFiles { get; set; } = false;
+ public bool SkipSubtitle { get; set; } = false;
public string? BinPath { get; set; }
}
}
diff --git a/src/N_m3u8DL-RE/Program.cs b/src/N_m3u8DL-RE/Program.cs
index 4f42c8a..10f8ed6 100644
--- a/src/N_m3u8DL-RE/Program.cs
+++ b/src/N_m3u8DL-RE/Program.cs
@@ -96,6 +96,7 @@ namespace N_m3u8DL_RE
throw new ArgumentException("MuxAfterDone disabled, MuxImports not allowed!");
}
+ //LivePipeMux开启时 LiveRealTimeMerge必须开启
if (option.LivePipeMux && !option.LiveRealTimeMerge)
{
Logger.WarnMarkUp("LivePipeMux detected, forced enable LiveRealTimeMerge");
@@ -114,7 +115,7 @@ namespace N_m3u8DL_RE
Logger.Extra($"ffmpeg => {option.FFmpegBinaryPath}");
//预先检查mkvmerge
- if (option.UseMkvmerge && option.MuxAfterDone)
+ if (option.MuxOptions != null && option.MuxOptions.UseMkvmerge && option.MuxAfterDone)
{
if (option.MkvmergeBinaryPath == null)
option.MkvmergeBinaryPath = GlobalUtil.FindExecutable("mkvmerge");
@@ -299,10 +300,13 @@ namespace N_m3u8DL_RE
option.BinaryMerge = true;
}
+ //应用用户自定义的分片范围
+ if (!livingFlag)
+ FilterUtil.ApplyCustomRange(selectedStreams, option.CustomRange);
+
//记录文件
extractor.RawFiles["meta_selected.json"] = GlobalUtil.ConvertToJson(selectedStreams);
-
Logger.Info(ResString.selectedStream);
foreach (var item in selectedStreams)
{
@@ -322,7 +326,6 @@ namespace N_m3u8DL_RE
Console.ReadKey();
#endif
-
Logger.InfoMarkUp(ResString.saveName + $"[deepskyblue1]{option.SaveName.EscapeMarkup()}[/]");
//开始MuxAfterDone后自动使用二进制版
diff --git a/src/N_m3u8DL-RE/Util/FilterUtil.cs b/src/N_m3u8DL-RE/Util/FilterUtil.cs
index b9f3629..eb77a9a 100644
--- a/src/N_m3u8DL-RE/Util/FilterUtil.cs
+++ b/src/N_m3u8DL-RE/Util/FilterUtil.cs
@@ -1,5 +1,6 @@
using N_m3u8DL_RE.Common.Entity;
using N_m3u8DL_RE.Common.Enum;
+using N_m3u8DL_RE.Common.Log;
using N_m3u8DL_RE.Common.Resource;
using N_m3u8DL_RE.Entity;
using Spectre.Console;
@@ -182,5 +183,49 @@ namespace N_m3u8DL_RE.Util
}
}
}
+
+ ///
+ /// 应用用户自定义的分片范围
+ ///
+ ///
+ ///
+ public static void ApplyCustomRange(List selectedSteams, CustomRange? customRange)
+ {
+ var resultList = selectedSteams.Select(x => 0d).ToList();
+
+ if (customRange == null) return;
+
+ Logger.InfoMarkUp($"{ResString.customRangeFound}[Cyan underline]{customRange.InputStr}[/]");
+ Logger.WarnMarkUp($"[darkorange3_1]{ResString.customRangeWarn}[/]");
+
+ var filteByIndex = customRange.StartSegIndex != null && customRange.EndSegIndex != null;
+ var filteByTime = customRange.StartSec != null && customRange.EndSec != null;
+
+ if (!filteByIndex && !filteByTime)
+ {
+ Logger.ErrorMarkUp(ResString.customRangeInvalid);
+ return;
+ }
+
+ foreach (var stream in selectedSteams)
+ {
+ var skippedDur = 0d;
+ if (stream.Playlist == null) continue;
+ foreach (var part in stream.Playlist.MediaParts)
+ {
+ var newSegments = new List();
+ if (filteByIndex)
+ newSegments = part.MediaSegments.Where(seg => seg.Index >= customRange.StartSegIndex && seg.Index <= customRange.EndSegIndex).ToList();
+ else
+ newSegments = part.MediaSegments.Where(seg => stream.Playlist.MediaParts.SelectMany(p => p.MediaSegments).Where(x => x.Index < seg.Index).Sum(x => x.Duration) >= customRange.StartSec
+ && stream.Playlist.MediaParts.SelectMany(p => p.MediaSegments).Where(x => x.Index < seg.Index).Sum(x => x.Duration) <= customRange.EndSec).ToList();
+
+ if (newSegments.Count > 0)
+ skippedDur += part.MediaSegments.Where(seg => seg.Index < newSegments.First().Index).Sum(x => x.Duration);
+ part.MediaSegments = newSegments;
+ }
+ stream.SkippedDuration = skippedDur;
+ }
+ }
}
}