支持使用命名管道实时混流
This commit is contained in:
parent
38fb339f82
commit
d8ead32d3e
|
@ -68,6 +68,7 @@ namespace N_m3u8DL_RE.Common.Resource
|
||||||
public static string cmd_useSystemProxy { get => GetText("cmd_useSystemProxy"); }
|
public static string cmd_useSystemProxy { get => GetText("cmd_useSystemProxy"); }
|
||||||
public static string cmd_customProxy { get => GetText("cmd_customProxy"); }
|
public static string cmd_customProxy { get => GetText("cmd_customProxy"); }
|
||||||
public static string cmd_liveKeepSegments { get => GetText("cmd_liveKeepSegments"); }
|
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"); }
|
public static string cmd_liveRecordLimit { get => GetText("cmd_liveRecordLimit"); }
|
||||||
public static string cmd_taskStartAt { get => GetText("cmd_taskStartAt"); }
|
public static string cmd_taskStartAt { get => GetText("cmd_taskStartAt"); }
|
||||||
public static string cmd_liveWaitTime { get => GetText("cmd_liveWaitTime"); }
|
public static string cmd_liveWaitTime { get => GetText("cmd_liveWaitTime"); }
|
||||||
|
@ -81,6 +82,8 @@ namespace N_m3u8DL_RE.Common.Resource
|
||||||
public static string liveLimitReached { get => GetText("liveLimitReached"); }
|
public static string liveLimitReached { get => GetText("liveLimitReached"); }
|
||||||
public static string saveName { get => GetText("saveName"); }
|
public static string saveName { get => GetText("saveName"); }
|
||||||
public static string taskStartAt { get => GetText("taskStartAt"); }
|
public static string taskStartAt { get => GetText("taskStartAt"); }
|
||||||
|
public static string namedPipeCreated { get => GetText("namedPipeCreated"); }
|
||||||
|
public static string namedPipeMux { get => GetText("namedPipeMux"); }
|
||||||
public static string partMerge { get => GetText("partMerge"); }
|
public static string partMerge { get => GetText("partMerge"); }
|
||||||
public static string fetch { get => GetText("fetch"); }
|
public static string fetch { get => GetText("fetch"); }
|
||||||
public static string ffmpegMerge { get => GetText("ffmpegMerge"); }
|
public static string ffmpegMerge { get => GetText("ffmpegMerge"); }
|
||||||
|
|
|
@ -22,6 +22,18 @@ namespace N_m3u8DL_RE.Common.Resource
|
||||||
zhTW: "檢測到新版本,請盡快升級!",
|
zhTW: "檢測到新版本,請盡快升級!",
|
||||||
enUS: "New version detected!"
|
enUS: "New version detected!"
|
||||||
),
|
),
|
||||||
|
["namedPipeCreated"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "已创建命名管道:",
|
||||||
|
zhTW: "已創建命名管道:",
|
||||||
|
enUS: "Named pipe created: "
|
||||||
|
),
|
||||||
|
["namedPipeMux"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "通过命名管道混流到",
|
||||||
|
zhTW: "通過命名管道混流到",
|
||||||
|
enUS: "Mux with named pipe, to"
|
||||||
|
),
|
||||||
["taskStartAt"] = new TextContainer
|
["taskStartAt"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "程序将等待,直到:",
|
zhCN: "程序将等待,直到:",
|
||||||
|
@ -310,6 +322,12 @@ namespace N_m3u8DL_RE.Common.Resource
|
||||||
zhTW: "指定HLS解密IV. 可以是文件, HEX或Base64",
|
zhTW: "指定HLS解密IV. 可以是文件, HEX或Base64",
|
||||||
enUS: "Set the HLS decryption iv. Can be file, HEX or Base64"
|
enUS: "Set the HLS decryption iv. Can be file, HEX or Base64"
|
||||||
),
|
),
|
||||||
|
["cmd_livePipeMux"] = new TextContainer
|
||||||
|
(
|
||||||
|
zhCN: "录制直播并开启实时合并时通过管道+ffmpeg实时混流到TS文件",
|
||||||
|
zhTW: "錄製直播並開啟即時合併時通過管道+ffmpeg即時混流到TS文件",
|
||||||
|
enUS: "Real-time muxing to TS file through pipeline + ffmpeg (liveRealTimeMerge enabled)"
|
||||||
|
),
|
||||||
["cmd_liveKeepSegments"] = new TextContainer
|
["cmd_liveKeepSegments"] = new TextContainer
|
||||||
(
|
(
|
||||||
zhCN: "录制直播并开启实时合并时依然保留分片",
|
zhCN: "录制直播并开启实时合并时依然保留分片",
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
{
|
{
|
||||||
internal partial class CommandInvoker
|
internal partial class CommandInvoker
|
||||||
{
|
{
|
||||||
public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20221208";
|
public const string VERSION_INFO = "N_m3u8DL-RE (Beta version) 20221210";
|
||||||
|
|
||||||
[GeneratedRegex("((best|worst)\\d*|all)")]
|
[GeneratedRegex("((best|worst)\\d*|all)")]
|
||||||
private static partial Regex ForStrRegex();
|
private static partial Regex ForStrRegex();
|
||||||
|
@ -76,6 +76,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
private readonly static Option<bool> LivePerformAsVod = new(new string[] { "--live-perform-as-vod" }, description: ResString.cmd_livePerformAsVod, 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> 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<bool> LiveKeepSegments = new(new string[] { "--live-keep-segments" }, description: ResString.cmd_liveKeepSegments, getDefaultValue: () => true);
|
||||||
|
private readonly static Option<bool> LivePipeMux = new(new string[] { "--live-pipe-mux" }, description: ResString.cmd_livePipeMux, getDefaultValue: () => false);
|
||||||
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<TimeSpan?> LiveRecordLimit = new(new string[] { "--live-record-limit" }, description: ResString.cmd_liveRecordLimit, parseArgument: ParseLiveLimit) { ArgumentHelpName = "HH:mm:ss" };
|
||||||
private readonly static Option<int?> LiveWaitTime = new(new string[] { "--live-wait-time" }, description: ResString.cmd_liveWaitTime) { ArgumentHelpName = "SEC" };
|
private readonly static Option<int?> LiveWaitTime = new(new string[] { "--live-wait-time" }, description: ResString.cmd_liveWaitTime) { ArgumentHelpName = "SEC" };
|
||||||
|
|
||||||
|
@ -416,6 +417,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
LiveRecordLimit = bindingContext.ParseResult.GetValueForOption(LiveRecordLimit),
|
LiveRecordLimit = bindingContext.ParseResult.GetValueForOption(LiveRecordLimit),
|
||||||
TaskStartAt = bindingContext.ParseResult.GetValueForOption(TaskStartAt),
|
TaskStartAt = bindingContext.ParseResult.GetValueForOption(TaskStartAt),
|
||||||
LivePerformAsVod = bindingContext.ParseResult.GetValueForOption(LivePerformAsVod),
|
LivePerformAsVod = bindingContext.ParseResult.GetValueForOption(LivePerformAsVod),
|
||||||
|
LivePipeMux = bindingContext.ParseResult.GetValueForOption(LivePipeMux),
|
||||||
UseSystemProxy = bindingContext.ParseResult.GetValueForOption(UseSystemProxy),
|
UseSystemProxy = bindingContext.ParseResult.GetValueForOption(UseSystemProxy),
|
||||||
CustomProxy = bindingContext.ParseResult.GetValueForOption(CustomProxy),
|
CustomProxy = bindingContext.ParseResult.GetValueForOption(CustomProxy),
|
||||||
LiveWaitTime = bindingContext.ParseResult.GetValueForOption(LiveWaitTime),
|
LiveWaitTime = bindingContext.ParseResult.GetValueForOption(LiveWaitTime),
|
||||||
|
@ -485,7 +487,7 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
|
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
|
||||||
MuxAfterDone,
|
MuxAfterDone,
|
||||||
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, TaskStartAt,
|
CustomHLSMethod, CustomHLSKey, CustomHLSIv, UseSystemProxy, CustomProxy, TaskStartAt,
|
||||||
LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LiveRecordLimit, LiveWaitTime,
|
LivePerformAsVod, LiveRealTimeMerge, LiveKeepSegments, LivePipeMux, LiveRecordLimit, LiveWaitTime,
|
||||||
MuxImports, VideoFilter, AudioFilter, SubtitleFilter, DropVideoFilter, DropAudioFilter, DropSubtitleFilter, MoreHelp
|
MuxImports, VideoFilter, AudioFilter, SubtitleFilter, DropVideoFilter, DropAudioFilter, DropSubtitleFilter, MoreHelp
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -222,5 +222,9 @@ namespace N_m3u8DL_RE.CommandLine
|
||||||
public int? LiveWaitTime { get; set; }
|
public int? LiveWaitTime { get; set; }
|
||||||
public bool MuxKeepFiles { get; set; }
|
public bool MuxKeepFiles { get; set; }
|
||||||
//public bool LiveWriteHLS { get; set; } = true;
|
//public bool LiveWriteHLS { get; set; } = true;
|
||||||
|
/// <summary>
|
||||||
|
/// See: <see cref="CommandInvoker.LivePipeMux"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool LivePipeMux { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,6 +14,9 @@ using N_m3u8DL_RE.Util;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading.Tasks.Dataflow;
|
using System.Threading.Tasks.Dataflow;
|
||||||
|
@ -27,6 +30,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
DownloaderConfig DownloaderConfig;
|
DownloaderConfig DownloaderConfig;
|
||||||
StreamExtractor StreamExtractor;
|
StreamExtractor StreamExtractor;
|
||||||
List<StreamSpec> SelectedSteams;
|
List<StreamSpec> SelectedSteams;
|
||||||
|
ConcurrentDictionary<int, string> PipeSteamNamesDic = new();
|
||||||
List<OutputFile> OutputFiles = new();
|
List<OutputFile> OutputFiles = new();
|
||||||
DateTime NowDateTime;
|
DateTime NowDateTime;
|
||||||
DateTime? PublishDateTime;
|
DateTime? PublishDateTime;
|
||||||
|
@ -183,7 +187,7 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
bool initDownloaded = false; //是否下载过init文件
|
bool initDownloaded = false; //是否下载过init文件
|
||||||
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
|
||||||
List<Mediainfo> mediaInfos = new();
|
List<Mediainfo> mediaInfos = new();
|
||||||
FileStream? fileOutputStream = null;
|
Stream? fileOutputStream = null;
|
||||||
WebVttSub currentVtt = new(); //字幕流始终维护一个实例
|
WebVttSub currentVtt = new(); //字幕流始终维护一个实例
|
||||||
bool firstSub = true;
|
bool firstSub = true;
|
||||||
task.StartTask();
|
task.StartTask();
|
||||||
|
@ -537,8 +541,31 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
{
|
{
|
||||||
Logger.WarnMarkUp($"{Path.GetFileName(output)} => {Path.GetFileName(output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output)))}");
|
Logger.WarnMarkUp($"{Path.GetFileName(output)} => {Path.GetFileName(output = Path.ChangeExtension(output, $"copy" + Path.GetExtension(output)))}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!DownloaderConfig.MyOptions.LivePipeMux || streamSpec.MediaType == MediaType.SUBTITLES)
|
||||||
|
{
|
||||||
fileOutputStream = new FileStream(output, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
|
fileOutputStream = new FileStream(output, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//创建管道
|
||||||
|
output = Path.ChangeExtension(output, ".ts");
|
||||||
|
var pipeName = $"RE_pipe_{Guid.NewGuid()}";
|
||||||
|
fileOutputStream = PipeUtil.CreatePipe(pipeName);
|
||||||
|
Logger.InfoMarkUp($"{ResString.namedPipeCreated} [cyan]{pipeName.EscapeMarkup()}[/]");
|
||||||
|
PipeSteamNamesDic[task.Id] = pipeName;
|
||||||
|
if (PipeSteamNamesDic.Count == SelectedSteams.Where(x => x.MediaType != MediaType.SUBTITLES).Count())
|
||||||
|
{
|
||||||
|
var names = PipeSteamNamesDic.OrderBy(i => i.Key).Select(k => k.Value).ToArray();
|
||||||
|
Logger.WarnMarkUp($"{ResString.namedPipeMux} [deepskyblue1]{Path.GetFileName(output).EscapeMarkup()}[/]");
|
||||||
|
var t = PipeUtil.StartPipeMuxAsync(DownloaderConfig.MyOptions.FFmpegBinaryPath!, names, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Windows only
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
await (fileOutputStream as NamedPipeServerStream)!.WaitForConnectionAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (streamSpec.MediaType != MediaType.SUBTITLES)
|
if (streamSpec.MediaType != MediaType.SUBTITLES)
|
||||||
{
|
{
|
||||||
|
@ -612,17 +639,20 @@ namespace N_m3u8DL_RE.DownloadManager
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileOutputStream != null)
|
if (fileOutputStream != null)
|
||||||
|
{
|
||||||
|
if (!DownloaderConfig.MyOptions.LivePipeMux)
|
||||||
{
|
{
|
||||||
//记录所有文件信息
|
//记录所有文件信息
|
||||||
OutputFiles.Add(new OutputFile()
|
OutputFiles.Add(new OutputFile()
|
||||||
{
|
{
|
||||||
Index = task.Id,
|
Index = task.Id,
|
||||||
FilePath = fileOutputStream.Name,
|
FilePath = (fileOutputStream as FileStream)!.Name,
|
||||||
LangCode = streamSpec.Language,
|
LangCode = streamSpec.Language,
|
||||||
Description = streamSpec.Name,
|
Description = streamSpec.Name,
|
||||||
Mediainfos = mediaInfos,
|
Mediainfos = mediaInfos,
|
||||||
MediaType = streamSpec.MediaType,
|
MediaType = streamSpec.MediaType,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
fileOutputStream.Close();
|
fileOutputStream.Close();
|
||||||
fileOutputStream.Dispose();
|
fileOutputStream.Dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.CommandLine;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace N_m3u8DL_RE.Util
|
||||||
|
{
|
||||||
|
internal class PipeUtil
|
||||||
|
{
|
||||||
|
public static Stream CreatePipe(string pipeName)
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return new NamedPipeServerStream(pipeName, PipeDirection.InOut);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var path = Path.Combine(Path.GetTempPath(), pipeName);
|
||||||
|
using var p = new Process();
|
||||||
|
p.StartInfo = new ProcessStartInfo()
|
||||||
|
{
|
||||||
|
FileName = "mkfifo",
|
||||||
|
Arguments = path,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
};
|
||||||
|
p.Start();
|
||||||
|
p.WaitForExit();
|
||||||
|
Thread.Sleep(200);
|
||||||
|
return new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<bool> StartPipeMuxAsync(string binary, string[] pipeNames, string outputPath)
|
||||||
|
{
|
||||||
|
return await Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
return StartPipeMux(binary, pipeNames, outputPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool StartPipeMux(string binary, string[] pipeNames, string outputPath)
|
||||||
|
{
|
||||||
|
string dateString = DateTime.Now.ToString("o");
|
||||||
|
StringBuilder command = new StringBuilder("-y -fflags +genpts -loglevel quiet ");
|
||||||
|
|
||||||
|
foreach (var item in pipeNames)
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
command.Append($" -i \"\\\\.\\pipe\\{item}\" ");
|
||||||
|
else
|
||||||
|
//command.Append($" -i \"unix://{Path.Combine(Path.GetTempPath(), $"CoreFxPipe_{item}")}\" ");
|
||||||
|
command.Append($" -i \"{Path.Combine(Path.GetTempPath(), item)}\" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < pipeNames.Length; i++)
|
||||||
|
{
|
||||||
|
command.Append($" -map {i} ");
|
||||||
|
}
|
||||||
|
|
||||||
|
command.Append(" -strict unofficial -c copy ");
|
||||||
|
command.Append($" -metadata date=\"{dateString}\" ");
|
||||||
|
command.Append($" -ignore_unknown -copy_unknown ");
|
||||||
|
command.Append($" -f mpegts -shortest \"{outputPath}\"");
|
||||||
|
|
||||||
|
using var p = new Process();
|
||||||
|
p.StartInfo = new ProcessStartInfo()
|
||||||
|
{
|
||||||
|
WorkingDirectory = Environment.CurrentDirectory,
|
||||||
|
FileName = binary,
|
||||||
|
Arguments = command.ToString(),
|
||||||
|
CreateNoWindow = true,
|
||||||
|
UseShellExecute = false
|
||||||
|
};
|
||||||
|
p.StartInfo.Environment.Add("FFREPORT", "file=ffreport.log:level=42");
|
||||||
|
p.Start();
|
||||||
|
p.WaitForExit();
|
||||||
|
|
||||||
|
return p.ExitCode == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue