支持自定义下载范围; 支持混流时忽略字幕
This commit is contained in:
parent
d930f10aff
commit
be07844762
|
@ -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; }
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"); }
|
||||
|
|
|
@ -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" +
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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"/>.
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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后自动使用二进制版
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue