支持使用命名管道实时混流

This commit is contained in:
nilaoda 2022-12-10 15:53:50 +08:00
parent 38fb339f82
commit d8ead32d3e
6 changed files with 159 additions and 13 deletions

View File

@ -68,6 +68,7 @@ namespace N_m3u8DL_RE.Common.Resource
public static string cmd_useSystemProxy { get => GetText("cmd_useSystemProxy"); }
public static string cmd_customProxy { get => GetText("cmd_customProxy"); }
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_taskStartAt { get => GetText("cmd_taskStartAt"); }
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 saveName { get => GetText("saveName"); }
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 fetch { get => GetText("fetch"); }
public static string ffmpegMerge { get => GetText("ffmpegMerge"); }

View File

@ -22,6 +22,18 @@ namespace N_m3u8DL_RE.Common.Resource
zhTW: "檢測到新版本,請盡快升級!",
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
(
zhCN: "程序将等待,直到:",
@ -310,6 +322,12 @@ namespace N_m3u8DL_RE.Common.Resource
zhTW: "指定HLS解密IV. 可以是文件, HEX或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
(
zhCN: "录制直播并开启实时合并时依然保留分片",

View File

@ -18,7 +18,7 @@ namespace N_m3u8DL_RE.CommandLine
{
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)")]
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> 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> 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<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),
TaskStartAt = bindingContext.ParseResult.GetValueForOption(TaskStartAt),
LivePerformAsVod = bindingContext.ParseResult.GetValueForOption(LivePerformAsVod),
LivePipeMux = bindingContext.ParseResult.GetValueForOption(LivePipeMux),
UseSystemProxy = bindingContext.ParseResult.GetValueForOption(UseSystemProxy),
CustomProxy = bindingContext.ParseResult.GetValueForOption(CustomProxy),
LiveWaitTime = bindingContext.ParseResult.GetValueForOption(LiveWaitTime),
@ -485,7 +487,7 @@ namespace N_m3u8DL_RE.CommandLine
LogLevel, UILanguage, UrlProcessorArgs, Keys, KeyTextFile, DecryptionBinaryPath, UseShakaPackager, MP4RealTimeDecryption,
MuxAfterDone,
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
};

View File

@ -222,5 +222,9 @@ namespace N_m3u8DL_RE.CommandLine
public int? LiveWaitTime { get; set; }
public bool MuxKeepFiles { get; set; }
//public bool LiveWriteHLS { get; set; } = true;
/// <summary>
/// See: <see cref="CommandInvoker.LivePipeMux"/>.
/// </summary>
public bool LivePipeMux { get; set; }
}
}

View File

@ -14,6 +14,9 @@ using N_m3u8DL_RE.Util;
using Spectre.Console;
using Spectre.Console.Rendering;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
@ -27,6 +30,7 @@ namespace N_m3u8DL_RE.DownloadManager
DownloaderConfig DownloaderConfig;
StreamExtractor StreamExtractor;
List<StreamSpec> SelectedSteams;
ConcurrentDictionary<int, string> PipeSteamNamesDic = new();
List<OutputFile> OutputFiles = new();
DateTime NowDateTime;
DateTime? PublishDateTime;
@ -183,7 +187,7 @@ namespace N_m3u8DL_RE.DownloadManager
bool initDownloaded = false; //是否下载过init文件
ConcurrentDictionary<MediaSegment, DownloadResult?> FileDic = new();
List<Mediainfo> mediaInfos = new();
FileStream? fileOutputStream = null;
Stream? fileOutputStream = null;
WebVttSub currentVtt = new(); //字幕流始终维护一个实例
bool firstSub = true;
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)))}");
}
if (!DownloaderConfig.MyOptions.LivePipeMux || streamSpec.MediaType == MediaType.SUBTITLES)
{
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)
{
@ -612,17 +639,20 @@ namespace N_m3u8DL_RE.DownloadManager
}
if (fileOutputStream != null)
{
if (!DownloaderConfig.MyOptions.LivePipeMux)
{
//记录所有文件信息
OutputFiles.Add(new OutputFile()
{
Index = task.Id,
FilePath = fileOutputStream.Name,
FilePath = (fileOutputStream as FileStream)!.Name,
LangCode = streamSpec.Language,
Description = streamSpec.Name,
Mediainfos = mediaInfos,
MediaType = streamSpec.MediaType,
});
}
fileOutputStream.Close();
fileOutputStream.Dispose();
}

View File

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