初步支持直播录制
This commit is contained in:
parent
e3bbdde016
commit
1429f3c14b
|
@ -52,8 +52,15 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
public static string cmd_urlProcessorArgs { get => GetText("cmd_urlProcessorArgs"); }
|
||||
public static string cmd_useShakaPackager { get => GetText("cmd_useShakaPackager"); }
|
||||
public static string cmd_concurrentDownload { get => GetText("cmd_concurrentDownload"); }
|
||||
public static string cmd_liveKeepSegments { get => GetText("cmd_liveKeepSegments"); }
|
||||
public static string cmd_liveRecordLimit { get => GetText("cmd_liveRecordLimit"); }
|
||||
public static string cmd_liveRealTimeMerge { get => GetText("cmd_liveRealTimeMerge"); }
|
||||
public static string cmd_livePerformAsVod { get => GetText("cmd_livePerformAsVod"); }
|
||||
public static string cmd_muxAfterDone { get => GetText("cmd_muxAfterDone"); }
|
||||
public static string cmd_writeMetaJson { get => GetText("cmd_writeMetaJson"); }
|
||||
public static string liveLimit { get => GetText("liveLimit"); }
|
||||
public static string liveLimitReached { get => GetText("liveLimitReached"); }
|
||||
public static string saveName { get => GetText("saveName"); }
|
||||
public static string fetch { get => GetText("fetch"); }
|
||||
public static string ffmpegMerge { get => GetText("ffmpegMerge"); }
|
||||
public static string ffmpegNotFound { get => GetText("ffmpegNotFound"); }
|
||||
|
|
|
@ -232,6 +232,30 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
zhTW: "此字符串將直接傳遞給URL Processor",
|
||||
enUS: "Give these arguments to the URL Processors."
|
||||
),
|
||||
["cmd_liveRealTimeMerge"] = new TextContainer
|
||||
(
|
||||
zhCN: "录制直播时实时合并",
|
||||
zhTW: "錄製直播時即時合併",
|
||||
enUS: "Real-time merge into file when recording live"
|
||||
),
|
||||
["cmd_livePerformAsVod"] = new TextContainer
|
||||
(
|
||||
zhCN: "以点播方式下载直播流",
|
||||
zhTW: "以點播方式下載直播流",
|
||||
enUS: "Download live streams as vod"
|
||||
),
|
||||
["cmd_liveKeepSegments"] = new TextContainer
|
||||
(
|
||||
zhCN: "录制直播并开启实时合并时依然保留分片",
|
||||
zhTW: "錄製直播並開啟即時合併時依然保留分片",
|
||||
enUS: "Keep segments when recording a live broadcast and enable liveRealTimeMerge"
|
||||
),
|
||||
["cmd_liveRecordLimit"] = new TextContainer
|
||||
(
|
||||
zhCN: "录制直播时的录制时长限制",
|
||||
zhTW: "錄製直播時的錄製時長限制",
|
||||
enUS: "Recording time limit when recording live"
|
||||
),
|
||||
["cmd_useShakaPackager"] = new TextContainer
|
||||
(
|
||||
zhCN: "解密时使用shaka-packager替代mp4decrypt",
|
||||
|
@ -397,6 +421,24 @@ namespace N_m3u8DL_RE.Common.Resource
|
|||
zhTW: "解析後的訊息是否輸出json文件",
|
||||
enUS: "Write meta json after parsed"
|
||||
),
|
||||
["liveLimit"] = new TextContainer
|
||||
(
|
||||
zhCN: "本次直播录制时长上限: ",
|
||||
zhTW: "本次直播錄製時長上限: ",
|
||||
enUS: "Live recording duration limit: "
|
||||
),
|
||||
["liveLimitReached"] = new TextContainer
|
||||
(
|
||||
zhCN: "到达直播录制上限,即将停止录制",
|
||||
zhTW: "到達直播錄製上限,即將停止錄製",
|
||||
enUS: "Live recording limit reached, will stop recording soon"
|
||||
),
|
||||
["saveName"] = new TextContainer
|
||||
(
|
||||
zhCN: "保存文件名: ",
|
||||
zhTW: "保存檔案名: ",
|
||||
enUS: "Save Name: "
|
||||
),
|
||||
["fetch"] = new TextContainer
|
||||
(
|
||||
zhCN: "获取: ",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using N_m3u8DL_RE.Common.Entity;
|
||||
using N_m3u8DL_RE.Common.Enum;
|
||||
using N_m3u8DL_RE.Common.Util;
|
||||
using N_m3u8DL_RE.Parser.Config;
|
||||
using N_m3u8DL_RE.Parser.Constants;
|
||||
using N_m3u8DL_RE.Parser.Util;
|
||||
|
@ -471,10 +472,23 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
return streamList;
|
||||
}
|
||||
|
||||
|
||||
public async Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
||||
public async Task RefreshPlayListAsync(List<StreamSpec> streamSpecs)
|
||||
{
|
||||
if (streamSpecs.Count == 0) return;
|
||||
var rawText = await HTTPUtil.GetWebSourceAsync(ParserConfig.Url);
|
||||
var newStreams = await ExtractStreamsAsync(rawText);
|
||||
foreach (var streamSpec in streamSpecs)
|
||||
{
|
||||
var match = newStreams.Where(n => n.ToShortString() == streamSpec.ToShortString());
|
||||
if (match.Any())
|
||||
streamSpec.Playlist = match.First().Playlist;
|
||||
}
|
||||
//这里才调用URL预处理器,节省开销
|
||||
await ProcessUrlAsync(streamSpecs);
|
||||
}
|
||||
|
||||
private async Task ProcessUrlAsync(List<StreamSpec> streamSpecs)
|
||||
{
|
||||
for (int i = 0; i < streamSpecs.Count; i++)
|
||||
{
|
||||
var playlist = streamSpecs[i].Playlist;
|
||||
|
@ -496,6 +510,12 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
}
|
||||
}
|
||||
|
||||
public async Task FetchPlayListAsync(List<StreamSpec> streamSpecs)
|
||||
{
|
||||
//这里才调用URL预处理器,节省开销
|
||||
await ProcessUrlAsync(streamSpecs);
|
||||
}
|
||||
|
||||
public string PreProcessUrl(string url)
|
||||
{
|
||||
foreach (var p in ParserConfig.UrlProcessors)
|
||||
|
|
|
@ -536,5 +536,10 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshPlayListAsync(List<StreamSpec> streamSpecs)
|
||||
{
|
||||
await FetchPlayListAsync(streamSpecs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace N_m3u8DL_RE.Parser.Extractor
|
|||
Task<List<StreamSpec>> ExtractStreamsAsync(string rawText);
|
||||
|
||||
Task FetchPlayListAsync(List<StreamSpec> streamSpecs);
|
||||
Task RefreshPlayListAsync(List<StreamSpec> streamSpecs);
|
||||
|
||||
string PreProcessUrl(string url);
|
||||
|
||||
|
|
|
@ -5,12 +5,14 @@ using N_m3u8DL_RE.Common.Resource;
|
|||
using N_m3u8DL_RE.Parser.Constants;
|
||||
using N_m3u8DL_RE.Parser.Extractor;
|
||||
using N_m3u8DL_RE.Common.Util;
|
||||
using N_m3u8DL_RE.Common.Enum;
|
||||
|
||||
namespace N_m3u8DL_RE.Parser
|
||||
{
|
||||
public class StreamExtractor
|
||||
{
|
||||
public IExtractor Extractor { get; private set; }
|
||||
public ExtractorType ExtractorType { get => extractor.ExtractorType; }
|
||||
private IExtractor extractor;
|
||||
private ParserConfig parserConfig = new ParserConfig();
|
||||
private string rawText;
|
||||
private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
|
||||
|
@ -56,13 +58,13 @@ namespace N_m3u8DL_RE.Parser
|
|||
if (rawText.StartsWith(HLSTags.ext_m3u))
|
||||
{
|
||||
Logger.InfoMarkUp(ResString.matchHLS);
|
||||
Extractor = new HLSExtractor(parserConfig);
|
||||
extractor = new HLSExtractor(parserConfig);
|
||||
}
|
||||
else if (rawText.Contains("</MPD>") && rawText.Contains("<MPD"))
|
||||
{
|
||||
Logger.InfoMarkUp(ResString.matchDASH);
|
||||
//extractor = new DASHExtractor(parserConfig);
|
||||
Extractor = new DASHExtractor2(parserConfig);
|
||||
extractor = new DASHExtractor2(parserConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -80,7 +82,7 @@ namespace N_m3u8DL_RE.Parser
|
|||
{
|
||||
await semaphore.WaitAsync();
|
||||
Logger.Info(ResString.parsingStream);
|
||||
return await Extractor.ExtractStreamsAsync(rawText);
|
||||
return await extractor.ExtractStreamsAsync(rawText);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -98,7 +100,20 @@ namespace N_m3u8DL_RE.Parser
|
|||
{
|
||||
await semaphore.WaitAsync();
|
||||
Logger.Info(ResString.parsingStream);
|
||||
await Extractor.FetchPlayListAsync(streamSpecs);
|
||||
await extractor.FetchPlayListAsync(streamSpecs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RefreshPlayListAsync(List<StreamSpec> streamSpecs)
|
||||
{
|
||||
try
|
||||
{
|
||||
await semaphore.WaitAsync();
|
||||
await extractor.RefreshPlayListAsync(streamSpecs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
using N_m3u8DL_RE.Common.Util;
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Rendering;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_RE.Column
|
||||
{
|
||||
internal class RecordingDurationColumn : ProgressColumn
|
||||
{
|
||||
protected override bool NoWrap => true;
|
||||
private ConcurrentDictionary<int, int> _recodingDurDic;
|
||||
public Style MyStyle { get; set; } = new Style(foreground: Color.Grey);
|
||||
public RecordingDurationColumn(ConcurrentDictionary<int, int> recodingDurDic)
|
||||
{
|
||||
_recodingDurDic = recodingDurDic;
|
||||
}
|
||||
public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
|
||||
{
|
||||
return new Text(GlobalUtil.FormatTime(_recodingDurDic[task.Id]), MyStyle).LeftAligned();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using Spectre.Console;
|
||||
using Spectre.Console.Rendering;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace N_m3u8DL_RE.Column
|
||||
{
|
||||
internal class RecordingStatusColumn : ProgressColumn
|
||||
{
|
||||
protected override bool NoWrap => true;
|
||||
public Style MyStyle { get; set; } = new Style(foreground: Color.Default);
|
||||
public Style FinishedStyle { get; set; } = new Style(foreground: Color.Yellow);
|
||||
public override IRenderable Render(RenderContext context, ProgressTask task, TimeSpan deltaTime)
|
||||
{
|
||||
if (task.IsFinished)
|
||||
return new Text($"{task.Value}/{task.MaxValue} Waiting ", FinishedStyle).LeftAligned();
|
||||
return new Text($"{task.Value}/{task.MaxValue} Recording", MyStyle).LeftAligned();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ using N_m3u8DL_RE.Common.Resource;
|
|||
using N_m3u8DL_RE.Entity;
|
||||
using N_m3u8DL_RE.Enum;
|
||||
using N_m3u8DL_RE.Util;
|
||||
using NiL.JS.Expressions;
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Binding;
|
||||
using System.CommandLine.Parsing;
|
||||
|
@ -47,6 +48,13 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
private readonly static Option<string?> BaseUrl = new(new string[] { "--base-url" }, description: ResString.cmd_baseUrl);
|
||||
private readonly static Option<bool> ConcurrentDownload = new(new string[] { "-mt", "--concurrent-download" }, description: ResString.cmd_concurrentDownload, getDefaultValue: () => false);
|
||||
|
||||
//直播相关
|
||||
private readonly static Option<bool> LivePerformAsVod = new(new string[] { "--live-perform-as-vod" }, description: ResString.cmd_livePerformAsVod, getDefaultValue: () => false);
|
||||
private readonly static Option<bool> LiveRealTimeMerge = new(new string[] { "--live-real-time-merge" }, description: ResString.cmd_liveRealTimeMerge, getDefaultValue: () => false);
|
||||
private readonly static Option<bool> LiveKeepSegments = new(new string[] { "--live-keep-segments" }, description: ResString.cmd_liveKeepSegments, getDefaultValue: () => true);
|
||||
private readonly static Option<TimeSpan?> LiveRecordLimit = new(new string[] { "--live-record-limit" }, description: ResString.cmd_liveRecordLimit, parseArgument: ParseLiveLimit) { ArgumentHelpName = "HH:mm:ss" };
|
||||
|
||||
|
||||
//复杂命令行如下
|
||||
private readonly static Option<MuxOptions?> MuxAfterDone = new(new string[] { "-M", "--mux-after-done" }, description: ResString.cmd_muxAfterDone, parseArgument: ParseMuxAfterDone) { ArgumentHelpName = "OPTIONS" };
|
||||
private readonly static Option<List<OutputFile>> MuxImports = new("--mux-import", description: ResString.cmd_muxImport, parseArgument: ParseImports) { Arity = ArgumentArity.OneOrMore, AllowMultipleArgumentsPerToken = false, ArgumentHelpName = "OPTIONS" };
|
||||
|
@ -54,11 +62,29 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
private readonly static Option<StreamFilter?> AudioFilter = new(new string[] { "-sa", "--select-audio" }, description: ResString.cmd_selectAudio, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||
private readonly static Option<StreamFilter?> SubtitleFilter = new(new string[] { "-ss", "--select-subtitle" }, description: ResString.cmd_selectSubtitle, parseArgument: ParseStreamFilter) { ArgumentHelpName = "OPTIONS" };
|
||||
|
||||
/// <summary>
|
||||
/// 解析录制直播时长限制
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <returns></returns>
|
||||
private static TimeSpan? ParseLiveLimit(ArgumentResult result)
|
||||
{
|
||||
var input = result.Tokens.First().Value;
|
||||
try
|
||||
{
|
||||
return OtherUtil.ParseDur(input);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
result.ErrorMessage = "error in parse LiveRecordLimit: " + input;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? ParseSaveName(ArgumentResult result)
|
||||
{
|
||||
var input = result.Tokens.First().Value;
|
||||
var newName = ConvertUtil.GetValidFileName(input);
|
||||
var newName = OtherUtil.GetValidFileName(input);
|
||||
if (string.IsNullOrEmpty(newName))
|
||||
{
|
||||
result.ErrorMessage = "Invalid save name!";
|
||||
|
@ -143,7 +169,7 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
private static Dictionary<string, string> ParseHeaders(ArgumentResult result)
|
||||
{
|
||||
var array = result.Tokens.Select(t => t.Value).ToArray();
|
||||
return ConvertUtil.SplitHeaderArrayToDic(array);
|
||||
return OtherUtil.SplitHeaderArrayToDic(array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -269,6 +295,10 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
VideoFilter = bindingContext.ParseResult.GetValueForOption(VideoFilter),
|
||||
AudioFilter = bindingContext.ParseResult.GetValueForOption(AudioFilter),
|
||||
SubtitleFilter = bindingContext.ParseResult.GetValueForOption(SubtitleFilter),
|
||||
LiveRealTimeMerge = bindingContext.ParseResult.GetValueForOption(LiveRealTimeMerge),
|
||||
LiveKeepSegments = bindingContext.ParseResult.GetValueForOption(LiveKeepSegments),
|
||||
LiveRecordLimit = bindingContext.ParseResult.GetValueForOption(LiveRecordLimit),
|
||||
LivePerformAsVod = bindingContext.ParseResult.GetValueForOption(LivePerformAsVod),
|
||||
};
|
||||
|
||||
var parsedHeaders = bindingContext.ParseResult.GetValueForOption(Headers);
|
||||
|
@ -304,13 +334,15 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
|
||||
public static async Task<int> InvokeArgs(string[] args, Func<MyOption, Task> action)
|
||||
{
|
||||
var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220914")
|
||||
var rootCommand = new RootCommand("N_m3u8DL-RE (Beta version) 20220917")
|
||||
{
|
||||
Input, TmpDir, SaveDir, SaveName, BaseUrl, ThreadCount, DownloadRetryCount, AutoSelect, SkipMerge, SkipDownload, CheckSegmentsCount,
|
||||
BinaryMerge, DelAfterDone, WriteMetaJson, AppendUrlParams, ConcurrentDownload, Headers, /**SavePattern,**/ SubOnly, SubtitleFormat, AutoSubtitleFix,
|
||||
FFmpegBinaryPath,
|
||||
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
|
||||
MuxAfterDone, MuxImports, VideoFilter, AudioFilter, SubtitleFilter
|
||||
MuxAfterDone,
|
||||
LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LiveRecordLimit,
|
||||
MuxImports, VideoFilter, AudioFilter, SubtitleFilter
|
||||
};
|
||||
rootCommand.TreatUnmatchedTokensAsErrors = true;
|
||||
rootCommand.SetHandler(async (myOption) => await action(myOption), new MyOptionBinder());
|
||||
|
|
|
@ -51,6 +51,10 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
/// </summary>
|
||||
public int DownloadRetryCount { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.LiveRecordLimit"/>.
|
||||
/// </summary>
|
||||
public TimeSpan? LiveRecordLimit { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.SkipMerge"/>.
|
||||
/// </summary>
|
||||
public bool SkipMerge { get; set; }
|
||||
|
@ -107,6 +111,18 @@ namespace N_m3u8DL_RE.CommandLine
|
|||
/// </summary>
|
||||
public bool ConcurrentDownload { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.LiveRealTimeMerge"/>.
|
||||
/// </summary>
|
||||
public bool LiveRealTimeMerge { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.LiveKeepSegments"/>.
|
||||
/// </summary>
|
||||
public bool LiveKeepSegments { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.LivePerformAsVod"/>.
|
||||
/// </summary>
|
||||
public bool LivePerformAsVod { get; set; }
|
||||
/// <summary>
|
||||
/// See: <see cref="CommandInvoker.SubtitleFormat"/>.
|
||||
/// </summary>
|
||||
public SubtitleFormat SubtitleFormat { get; set; }
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
<IlcTrimMetadata>true</IlcTrimMetadata>
|
||||
<IlcGenerateStackTraceData>true</IlcGenerateStackTraceData>
|
||||
<SatelliteResourceLanguages>zh-CN;zh-TW;en-US</SatelliteResourceLanguages>
|
||||
<NativeAotCompilerVersion>7.0.0-*</NativeAotCompilerVersion>
|
||||
<PublishAot>true</PublishAot>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
|
@ -367,7 +367,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
subContentFixed = OtherUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
output = Path.ChangeExtension(output, ".srt");
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
|
||||
|
@ -402,7 +402,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
subContentFixed = OtherUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
output = Path.ChangeExtension(output, ".srt");
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
|
||||
|
@ -435,7 +435,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
subContentFixed = OtherUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
output = Path.ChangeExtension(output, ".srt");
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
|
||||
|
@ -472,7 +472,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
|||
if (DownloaderConfig.MyOptions.SubtitleFormat != Enum.SubtitleFormat.VTT)
|
||||
{
|
||||
path = Path.ChangeExtension(path, ".srt");
|
||||
subContentFixed = ConvertUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
subContentFixed = OtherUtil.WebVtt2Other(finalVtt, DownloaderConfig.MyOptions.SubtitleFormat);
|
||||
output = Path.ChangeExtension(output, ".srt");
|
||||
}
|
||||
await File.WriteAllTextAsync(path, subContentFixed, new UTF8Encoding(false));
|
||||
|
|
|
@ -186,7 +186,7 @@ namespace N_m3u8DL_RE
|
|||
var streams = await extractor.ExtractStreamsAsync();
|
||||
|
||||
//直播检测
|
||||
var livingFlag = streams.Any(s => s.Playlist?.IsLive == true);
|
||||
var livingFlag = streams.Any(s => s.Playlist?.IsLive == true) && !option.LivePerformAsVod;
|
||||
if (livingFlag)
|
||||
{
|
||||
Logger.WarnMarkUp($"[white on darkorange3_1]{ResString.liveFound}[/]");
|
||||
|
@ -248,7 +248,7 @@ namespace N_m3u8DL_RE
|
|||
|
||||
//HLS: 选中流中若有没加载出playlist的,加载playlist
|
||||
//DASH: 加载playlist (调用url预处理器)
|
||||
if (selectedStreams.Any(s => s.Playlist == null) || extractor.Extractor.ExtractorType == ExtractorType.MPEG_DASH)
|
||||
if (selectedStreams.Any(s => s.Playlist == null) || extractor.ExtractorType == ExtractorType.MPEG_DASH)
|
||||
await extractor.FetchPlayListAsync(selectedStreams);
|
||||
|
||||
//无法识别的加密方式,自动开启二进制合并
|
||||
|
@ -281,19 +281,11 @@ namespace N_m3u8DL_RE
|
|||
//尝试从URL或文件读取文件名
|
||||
if (string.IsNullOrEmpty(option.SaveName))
|
||||
{
|
||||
if (File.Exists(option.Input))
|
||||
{
|
||||
option.SaveName = Path.GetFileNameWithoutExtension(option.Input) + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
}
|
||||
else
|
||||
{
|
||||
var uri = new Uri(option.Input);
|
||||
var name = uri.GetLeftPart(UriPartial.Path).Split('/').Last();
|
||||
name = string.Join(".", name.Split('.').SkipLast(1)).Trim('.');
|
||||
option.SaveName = ConvertUtil.GetValidFileName(name) + "_" + DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
}
|
||||
option.SaveName = OtherUtil.GetFileNameFromInput(option.Input);
|
||||
}
|
||||
|
||||
Logger.InfoMarkUp(ResString.saveName + $"[deepskyblue1]{option.SaveName.EscapeMarkup()}[/]");
|
||||
|
||||
//下载配置
|
||||
var downloadConfig = new DownloaderConfig()
|
||||
{
|
||||
|
@ -313,7 +305,12 @@ namespace N_m3u8DL_RE
|
|||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Live not supported yet.");
|
||||
var sldm = new SimpleLiveRecordManager(downloadConfig, selectedStreams, extractor);
|
||||
var result = await sldm.StartRecordAsync();
|
||||
if (result)
|
||||
Logger.InfoMarkUp("[white on green]Done[/]");
|
||||
else
|
||||
Logger.ErrorMarkUp("[white on red]Faild[/]");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
@ -193,7 +193,7 @@ namespace N_m3u8DL_RE.Util
|
|||
|
||||
command.Append($" -metadata date=\"{dateString}\" -ignore_unknown -copy_unknown ");
|
||||
command.Append($" \"{outputPath}.{ext}\"");
|
||||
|
||||
|
||||
InvokeFFmpeg(binary, command.ToString(), Environment.CurrentDirectory);
|
||||
|
||||
if (File.Exists($"{outputPath}.{ext}") && new FileInfo($"{outputPath}.{ext}").Length > 1024)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
using N_m3u8DL_RE.Common.Entity;
|
||||
using N_m3u8DL_RE.Common.Log;
|
||||
using N_m3u8DL_RE.Enum;
|
||||
using System.CommandLine;
|
||||
using System.Text;
|
||||
|
||||
namespace N_m3u8DL_RE.Util
|
||||
{
|
||||
internal class ConvertUtil
|
||||
internal class OtherUtil
|
||||
{
|
||||
public static Dictionary<string,string> SplitHeaderArrayToDic(string[]? headers)
|
||||
{
|
||||
|
@ -66,5 +67,55 @@ namespace N_m3u8DL_RE.Util
|
|||
}
|
||||
return title.Trim('.');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从输入自动获取文件名
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetFileNameFromInput(string input, bool addSuffix = true)
|
||||
{
|
||||
var saveName = addSuffix ? DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") : string.Empty;
|
||||
if (File.Exists(input))
|
||||
{
|
||||
saveName = Path.GetFileNameWithoutExtension(input) + "_" + saveName;
|
||||
}
|
||||
else
|
||||
{
|
||||
var uri = new Uri(input);
|
||||
var name = uri.GetLeftPart(UriPartial.Path).Split('/').Last();
|
||||
name = string.Join(".", name.Split('.').SkipLast(1)).Trim('.');
|
||||
saveName = GetValidFileName(name) + "_" + saveName;
|
||||
}
|
||||
return saveName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 hh:mm:ss 解析TimeSpan
|
||||
/// </summary>
|
||||
/// <param name="timeStr"></param>
|
||||
/// <returns></returns>
|
||||
public static TimeSpan ParseDur(string timeStr)
|
||||
{
|
||||
var arr = timeStr.Replace(":", ":").Split(':');
|
||||
var days = -1;
|
||||
var hours = -1;
|
||||
var mins = -1;
|
||||
var secs = -1;
|
||||
arr.Reverse().Select(i => Convert.ToInt32(i)).ToList().ForEach(item =>
|
||||
{
|
||||
if (secs == -1) secs = item;
|
||||
else if (mins == -1) mins = item;
|
||||
else if (hours == -1) hours = item;
|
||||
else if (days == -1) days = item;
|
||||
});
|
||||
|
||||
if (days == -1) days = 0;
|
||||
if (hours == -1) hours = 0;
|
||||
if (mins == -1) mins = 0;
|
||||
if (secs == -1) secs = 0;
|
||||
|
||||
return new TimeSpan(days, hours, mins, secs);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue