支持使用命名管道实时混流
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_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"); }
|
||||
|
|
|
@ -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: "录制直播并开启实时合并时依然保留分片",
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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