支持自定义下载范围; 支持混流时忽略字幕

This commit is contained in:
nilaoda 2023-06-28 23:19:43 +08:00
parent d930f10aff
commit be07844762
12 changed files with 266 additions and 29 deletions

View File

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

View File

@ -225,6 +225,22 @@ namespace N_m3u8DL_RE.Common.Entity
return sb.ToString();
}
/// <summary>
/// 字幕向前平移指定时间
/// </summary>
/// <param name="time"></param>
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();

View File

@ -10,6 +10,9 @@ namespace N_m3u8DL_RE.Common.Resource
public class ResString
{
public readonly static string ReLiveTs = "<RE_LIVE_TS>";
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"); }

View File

@ -10,6 +10,24 @@ namespace N_m3u8DL_RE.Common.Resource
{
public static Dictionary<string, TextContainer> 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" +

View File

@ -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<string> Input = new(name: "input", description: ResString.cmd_Input);
private readonly static Option<string?> TmpDir = new(new string[] { "--tmp-dir" }, description: ResString.cmd_tmpDir);
@ -60,6 +62,9 @@ namespace N_m3u8DL_RE.CommandLine
private readonly static Option<bool> UseSystemProxy = new(new string[] { "--use-system-proxy" }, description: ResString.cmd_useSystemProxy, getDefaultValue: () => true);
private readonly static Option<WebProxy?> CustomProxy = new(new string[] { "--custom-proxy" }, description: ResString.cmd_customProxy, parseArgument: ParseProxy) { ArgumentHelpName = "URL" };
//只下载部分分片
private readonly static Option<CustomRange?> CustomRange = new(new string[] { "--custom-range" }, description: ResString.cmd_customRange, parseArgument: ParseCustomRange) { ArgumentHelpName = "RANGE" };
//morehelp
private readonly static Option<string?> MoreHelp = new(new string[] { "--morehelp" }, description: ResString.cmd_moreHelp) { ArgumentHelpName = "OPTION" };
@ -94,6 +99,55 @@ namespace N_m3u8DL_RE.CommandLine
private readonly static Option<StreamFilter?> DropAudioFilter = new(new string[] { "-da", "--drop-audio" }, description: ResString.cmd_dropAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
private readonly static Option<StreamFilter?> DropSubtitleFilter = new(new string[] { "-ds", "--drop-subtitle" }, description: ResString.cmd_dropSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
/// <summary>
/// 解析用户定义的下载范围
/// </summary>
/// <param name="result"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
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;
}
}
/// <summary>
/// 解析用户代理
/// </summary>
@ -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
};

View File

@ -113,14 +113,6 @@ namespace N_m3u8DL_RE.CommandLine
/// </summary>
public bool MuxAfterDone { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.MuxToMp4"/>.
/// </summary>
public bool MuxToMp4 { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.UseMkvmerge"/>.
/// </summary>
public bool UseMkvmerge { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.ConcurrentDownload"/>.
/// </summary>
public bool ConcurrentDownload { get; set; }
@ -221,10 +213,14 @@ namespace N_m3u8DL_RE.CommandLine
/// </summary>
public WebProxy? CustomProxy { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.CustomRange"/>.
/// </summary>
public CustomRange? CustomRange { get; set; }
/// <summary>
/// See: <see cref="CommandInvoker.LiveWaitTime"/>.
/// </summary>
public int? LiveWaitTime { get; set; }
public bool MuxKeepFiles { get; set; }
public MuxOptions MuxOptions { get; set; }
//public bool LiveWriteHLS { get; set; } = true;
/// <summary>
/// See: <see cref="CommandInvoker.LivePipeMux"/>.

View File

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

View File

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

View File

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

View File

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

View File

@ -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后自动使用二进制版

View File

@ -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
}
}
}
/// <summary>
/// 应用用户自定义的分片范围
/// </summary>
/// <param name="selectedSteams"></param>
/// <param name="customRange"></param>
public static void ApplyCustomRange(List<StreamSpec> 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<MediaSegment>();
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;
}
}
}
}