diff --git a/BBDown/BBDownApiServer.cs b/BBDown/BBDownApiServer.cs index b5e786048..3c8701466 100644 --- a/BBDown/BBDownApiServer.cs +++ b/BBDown/BBDownApiServer.cs @@ -109,7 +109,7 @@ private async Task AddDownloadTaskAsync(MyOption option) task.Title = vInfo.Title; task.Pic = vInfo.Pic; task.VideoPubTime = vInfo.PubTime; - await Program.DownloadPageAsync(option, vInfo, encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, + await Program.DownloadPagesAsync(option, vInfo, encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, input, savePathFormat, lang, fetchedAid, delay, apiType, task); task.IsSuccessful = true; } diff --git a/BBDown/CommandLineInvoker.cs b/BBDown/CommandLineInvoker.cs index 49b5112f4..7f0a74afb 100644 --- a/BBDown/CommandLineInvoker.cs +++ b/BBDown/CommandLineInvoker.cs @@ -52,6 +52,7 @@ internal class CommandLineInvoker private readonly static Option Aria2cPath = new(new string[] { "--aria2c-path" }, "设置aria2c的路径"); private readonly static Option UposHost = new(new string[] { "--upos-host" }, "自定义upos服务器"); private readonly static Option ForceReplaceHost = new(new string[] { "--force-replace-host" }, "强制替换下载服务器host(默认开启)"); + private readonly static Option SaveArchivesToFile = new(new string[] { "--save-archives-to-file" }, "将下载过的视频记录到本地文件中, 用于后续跳过下载同个视频"); private readonly static Option DelayPerPage = new(new string[] { "--delay-per-page" }, "设置下载合集分P之间的下载间隔时间(单位: 秒, 默认无间隔)"); private readonly static Option FilePattern = new(new string[] { "--file-pattern", "-F" }, $"使用内置变量自定义单P存储文件名:\r\n\r\n" + @@ -140,6 +141,7 @@ protected override MyOption GetBoundValue(BindingContext bindingContext) if (bindingContext.ParseResult.HasOption(Aria2cPath)) option.Aria2cPath = bindingContext.ParseResult.GetValueForOption(Aria2cPath)!; if (bindingContext.ParseResult.HasOption(UposHost)) option.UposHost = bindingContext.ParseResult.GetValueForOption(UposHost)!; if (bindingContext.ParseResult.HasOption(ForceReplaceHost)) option.ForceReplaceHost = bindingContext.ParseResult.GetValueForOption(ForceReplaceHost)!; + if (bindingContext.ParseResult.HasOption(SaveArchivesToFile)) option.SaveArchivesToFile = bindingContext.ParseResult.GetValueForOption(SaveArchivesToFile)!; if (bindingContext.ParseResult.HasOption(DelayPerPage)) option.DelayPerPage = bindingContext.ParseResult.GetValueForOption(DelayPerPage)!; if (bindingContext.ParseResult.HasOption(Host)) option.Host = bindingContext.ParseResult.GetValueForOption(Host)!; if (bindingContext.ParseResult.HasOption(EpHost)) option.EpHost = bindingContext.ParseResult.GetValueForOption(EpHost)!; @@ -202,6 +204,7 @@ public static RootCommand GetRootCommand(Func action) Aria2cPath, UposHost, ForceReplaceHost, + SaveArchivesToFile, DelayPerPage, Host, EpHost, diff --git a/BBDown/MyOption.cs b/BBDown/MyOption.cs index f47710a5a..d581c5865 100644 --- a/BBDown/MyOption.cs +++ b/BBDown/MyOption.cs @@ -38,6 +38,7 @@ internal class MyOption public bool AudioAscending { get; set; } = false; public bool AllowPcdn { get; set; } = false; public bool ForceReplaceHost { get; set; } = true; + public bool SaveArchivesToFile { get; set; } = false; public string FilePattern { get; set; } = ""; public string MultiFilePattern { get; set; } = ""; public string SelectPage { get; set; } = ""; diff --git a/BBDown/Program.Methods.cs b/BBDown/Program.Methods.cs index f93987bb3..2a54b12ae 100644 --- a/BBDown/Program.Methods.cs +++ b/BBDown/Program.Methods.cs @@ -244,6 +244,29 @@ private static void LoadCredentials(MyOption myOption) } } + private static object fileLock = new object(); + public static void SaveAidToFile(string aid) + { + lock (fileLock) + { + string filePath = Path.Combine(APP_DIR, "BBDown.archives"); + LogDebug("文件路径:{0}", filePath); + File.AppendAllText(filePath, $"{aid}|"); + } + } + + public static bool CheckAidFromFile(string aid) + { + lock (fileLock) + { + string filePath = Path.Combine(APP_DIR, "BBDown.archives"); + if (!File.Exists(filePath)) return false; + LogDebug("文件路径:{0}", filePath); + var text = File.ReadAllText(filePath); + return text.Split('|').Any(item => item == aid); + } + } + /// /// 获取选中的分P列表 /// diff --git a/BBDown/Program.cs b/BBDown/Program.cs index 615ee3e33..ce6147151 100644 --- a/BBDown/Program.cs +++ b/BBDown/Program.cs @@ -12,14 +12,11 @@ using static BBDown.BBDownDownloadUtil; using static BBDown.Core.Parser; using static BBDown.Core.Logger; -using System.Text; using System.Linq; using System.Text.Json; -using System.Net.Http; using System.Text.RegularExpressions; using BBDown.Core; using BBDown.Core.Util; -using BBDown.Core.Fetcher; using System.Text.Json.Serialization; using System.CommandLine.Builder; using BBDown.Core.Entity; @@ -186,7 +183,7 @@ private static void StartServer(string? listenUrl) server.Run(string.IsNullOrEmpty(listenUrl) ? defaultListenUrl : listenUrl); } - public static (Dictionary? encodingPriority, Dictionary? dfnPriority, string? firstEncoding, + public static (Dictionary encodingPriority, Dictionary dfnPriority, string? firstEncoding, bool downloadDanmaku, string input, string savePathFormat, string lang, string aidOri, int delay) SetUpWork(MyOption myOption) { @@ -287,14 +284,10 @@ public static (Dictionary? encodingPriority, Dictionary? encodingPriority, Dictionary? dfnPriority, + public static async Task DownloadPagesAsync(MyOption myOption, VInfo vInfo, Dictionary encodingPriority, Dictionary dfnPriority, string? firstEncoding, bool downloadDanmaku, string input, string savePathFormat, string lang, string aidOri, int delay, string apiType, DownloadTask? relatedTask = null) { - string title = vInfo.Title; - string pic = vInfo.Pic; - long pubTime = vInfo.PubTime; List pagesInfo = vInfo.PagesInfo; - List subtitleInfo = new(); bool bangumi = vInfo.IsBangumi; bool cheese = vInfo.IsCheese; //获取已选择的分P列表 @@ -319,412 +312,442 @@ public static async Task DownloadPageAsync(MyOption myOption, VInfo vInfo, Dicti foreach (Page p in pagesInfo) { - bool selected = false; //用户是否已经手动选择过了轨道 - int retryCount = 0; - downloadPage: - try + if (pagesInfo.Count > 1 && delay > 0) { - string desc = string.IsNullOrEmpty(p.desc) ? vInfo.Desc : p.desc; - if (pagesInfo.Count > 1 && delay > 0) + Log($"停顿{delay}秒..."); + await Task.Delay(delay * 1000); + } + Log($"开始解析P{p.index}: {p.aid}... ({pagesInfo.IndexOf(p) + 1} of {pagesInfo.Count})"); + + if (myOption.SaveArchivesToFile) + { + if (CheckAidFromFile(p.aid)) { - Log($"停顿{delay}秒..."); - await Task.Delay(delay * 1000); + + Log($"aid: {p.aid}已下载过, 跳过下载..."); + continue; } + } + + await DownloadPageAsync(p, myOption, vInfo, encodingPriority, dfnPriority, firstEncoding, + downloadDanmaku, input, savePathFormat, lang, aidOri, apiType, relatedTask); - Log($"开始解析P{p.index}... ({pagesInfo.IndexOf(p) + 1} of {pagesInfo.Count})"); + if (myOption.SaveArchivesToFile) + { + SaveAidToFile(p.aid); + } + } + + Log("任务完成"); + } + + private static async Task DownloadPageAsync(Page p, MyOption myOption, VInfo vInfo, Dictionary encodingPriority, Dictionary dfnPriority, + string? firstEncoding, bool downloadDanmaku, string input, string savePathFormat, string lang, string aidOri, string apiType, DownloadTask? relatedTask = null) + { + List pagesInfo = vInfo.PagesInfo; + string desc = string.IsNullOrEmpty(p.desc) ? vInfo.Desc : p.desc; + bool bangumi = vInfo.IsBangumi; + var pagesCount = pagesInfo.Count; + List subtitleInfo = new(); + string title = vInfo.Title; + string pic = vInfo.Pic; + long pubTime = vInfo.PubTime; + bool selected = false; //用户是否已经手动选择过了轨道 + int retryCount = 0; + downloadPage: + try + { + LogDebug("尝试获取章节信息..."); + p.points = await FetchPointsAsync(p.cid, p.aid); - LogDebug("尝试获取章节信息..."); - p.points = await FetchPointsAsync(p.cid, p.aid); + string videoPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.mp4"; + string audioPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.m4a"; + var coverPath = $"{p.aid}/{p.aid}.jpg"; - string videoPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.mp4"; - string audioPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.m4a"; - var coverPath = $"{p.aid}/{p.aid}.jpg"; + //处理文件夹以.结尾导致的异常情况 + if (title.EndsWith(".")) title += "_fix"; + //处理文件夹以.开头导致的异常情况 + if (title.StartsWith(".")) title = "_" + title; - //处理文件夹以.结尾导致的异常情况 - if (title.EndsWith(".")) title += "_fix"; - //处理文件夹以.开头导致的异常情况 - if (title.StartsWith(".")) title = "_" + title; + //处理封面&&字幕 + if (!myOption.OnlyShowInfo) + { + if (!Directory.Exists(p.aid)) + { + Directory.CreateDirectory(p.aid); + } + if (!myOption.SkipCover && !myOption.SubOnly && !File.Exists(coverPath) && !myOption.DanmakuOnly && !myOption.CoverOnly) + { + await DownloadFile((pic == "" ? p.cover! : pic), coverPath, new DownloadConfig()); + } - //处理封面&&字幕 - if (!myOption.OnlyShowInfo) + if (!myOption.SkipSubtitle && !myOption.DanmakuOnly && !myOption.CoverOnly) { - if (!Directory.Exists(p.aid)) + LogDebug("获取字幕..."); + subtitleInfo = await SubUtil.GetSubtitlesAsync(p.aid, p.cid, p.epid, p.index, myOption.UseIntlApi); + if (myOption.SkipAi && subtitleInfo.Any()) { - Directory.CreateDirectory(p.aid); + Log($"跳过下载AI字幕"); + subtitleInfo = subtitleInfo.Where(s => !s.lan.StartsWith("ai-")).ToList(); } - if (!myOption.SkipCover && !myOption.SubOnly && !File.Exists(coverPath) && !myOption.DanmakuOnly && !myOption.CoverOnly) + foreach (Subtitle s in subtitleInfo) { - await DownloadFile((pic == "" ? p.cover! : pic), coverPath, new DownloadConfig()); - } - - if (!myOption.SkipSubtitle && !myOption.DanmakuOnly && !myOption.CoverOnly) - { - LogDebug("获取字幕..."); - subtitleInfo = await SubUtil.GetSubtitlesAsync(p.aid, p.cid, p.epid, p.index, myOption.UseIntlApi); - if (myOption.SkipAi && subtitleInfo.Any()) - { - Log($"跳过下载AI字幕"); - subtitleInfo = subtitleInfo.Where(s => !s.lan.StartsWith("ai-")).ToList(); - } - foreach (Subtitle s in subtitleInfo) + Log($"下载字幕 {s.lan} => {SubUtil.GetSubtitleCode(s.lan).Item2}..."); + LogDebug("下载:{0}", s.url); + await SubUtil.SaveSubtitleAsync(s.url, s.path); + if (myOption.SubOnly && File.Exists(s.path) && File.ReadAllText(s.path) != "") { - Log($"下载字幕 {s.lan} => {SubUtil.GetSubtitleCode(s.lan).Item2}..."); - LogDebug("下载:{0}", s.url); - await SubUtil.SaveSubtitleAsync(s.url, s.path); - if (myOption.SubOnly && File.Exists(s.path) && File.ReadAllText(s.path) != "") + var _outSubPath = FormatSavePath(savePathFormat, title, null, null, p, pagesCount, apiType, pubTime); + if (_outSubPath.Contains('/')) { - var _outSubPath = FormatSavePath(savePathFormat, title, null, null, p, pagesCount, apiType, pubTime); - if (_outSubPath.Contains('/')) - { - if (!Directory.Exists(_outSubPath.Split('/').First())) - Directory.CreateDirectory(_outSubPath.Split('/').First()); - } - _outSubPath = Path.ChangeExtension(_outSubPath, $".{s.lan}.srt"); - File.Move(s.path, _outSubPath, true); + if (!Directory.Exists(_outSubPath.Split('/').First())) + Directory.CreateDirectory(_outSubPath.Split('/').First()); } + _outSubPath = Path.ChangeExtension(_outSubPath, $".{s.lan}.srt"); + File.Move(s.path, _outSubPath, true); } } - - if (myOption.SubOnly) - { - if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true); - continue; - } } - //调用解析 - ParsedResult parsedResult = await ExtractTracksAsync(aidOri, p.aid, p.cid, p.epid, myOption.UseTvApi, myOption.UseIntlApi, myOption.UseAppApi, firstEncoding); - List audioMaterial = new(); - if (!p.points.Any()) + if (myOption.SubOnly) { - p.points = parsedResult.ExtraPoints; + if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true); + return; } + } - if (Config.DEBUG_LOG) - { - File.WriteAllText($"debug_{DateTime.Now:yyyyMMddHHmmssfff}.json", parsedResult.WebJsonString); - } + //调用解析 + ParsedResult parsedResult = await ExtractTracksAsync(aidOri, p.aid, p.cid, p.epid, myOption.UseTvApi, myOption.UseIntlApi, myOption.UseAppApi, firstEncoding); + List audioMaterial = new(); + if (!p.points.Any()) + { + p.points = parsedResult.ExtraPoints; + } - var savePath = ""; + if (Config.DEBUG_LOG) + { + File.WriteAllText($"debug_{DateTime.Now:yyyyMMddHHmmssfff}.json", parsedResult.WebJsonString); + } + + var savePath = ""; + + var downloadConfig = new DownloadConfig() + { + UseAria2c = myOption.UseAria2c, + Aria2cArgs = myOption.Aria2cArgs, + ForceHttp = myOption.ForceHttp, + MultiThread = myOption.MultiThread, + RelatedTask = relatedTask, + }; - var downloadConfig = new DownloadConfig() + //此处代码简直灾难, 后续优化吧 + if ((parsedResult.VideoTracks.Any() || parsedResult.AudioTracks.Any()) && !parsedResult.Clips.Any()) //dash + { + if (parsedResult.VideoTracks.Count == 0) { - UseAria2c = myOption.UseAria2c, - Aria2cArgs = myOption.Aria2cArgs, - ForceHttp = myOption.ForceHttp, - MultiThread = myOption.MultiThread, - RelatedTask = relatedTask, - }; - - //此处代码简直灾难, 后续优化吧 - if ((parsedResult.VideoTracks.Any() || parsedResult.AudioTracks.Any()) && !parsedResult.Clips.Any()) //dash + LogError("没有找到符合要求的视频流"); + if (!myOption.AudioOnly) return; + } + if (parsedResult.AudioTracks.Count == 0) { - if (parsedResult.VideoTracks.Count == 0) - { - LogError("没有找到符合要求的视频流"); - if (!myOption.AudioOnly) continue; - } - if (parsedResult.AudioTracks.Count == 0) - { - LogError("没有找到符合要求的音频流"); - if (!myOption.VideoOnly) continue; - } + LogError("没有找到符合要求的音频流"); + if (!myOption.VideoOnly) return; + } - if (myOption.AudioOnly) - { - parsedResult.VideoTracks.Clear(); - } - if (myOption.VideoOnly) - { - parsedResult.AudioTracks.Clear(); - parsedResult.BackgroundAudioTracks.Clear(); - parsedResult.RoleAudioList.Clear(); - } + if (myOption.AudioOnly) + { + parsedResult.VideoTracks.Clear(); + } + if (myOption.VideoOnly) + { + parsedResult.AudioTracks.Clear(); + parsedResult.BackgroundAudioTracks.Clear(); + parsedResult.RoleAudioList.Clear(); + } - //排序 - parsedResult.VideoTracks = SortTracks(parsedResult.VideoTracks, dfnPriority, encodingPriority, myOption.VideoAscending); - parsedResult.AudioTracks.Sort(Compare); - parsedResult.BackgroundAudioTracks.Sort(Compare); + //排序 + parsedResult.VideoTracks = SortTracks(parsedResult.VideoTracks, dfnPriority, encodingPriority, myOption.VideoAscending); + parsedResult.AudioTracks.Sort(Compare); + parsedResult.BackgroundAudioTracks.Sort(Compare); + foreach (var role in parsedResult.RoleAudioList) + { + role.audio.Sort(Compare); + } + if (myOption.AudioAscending) + { + parsedResult.AudioTracks.Reverse(); + parsedResult.BackgroundAudioTracks.Reverse(); foreach (var role in parsedResult.RoleAudioList) { - role.audio.Sort(Compare); - } - if (myOption.AudioAscending) - { - parsedResult.AudioTracks.Reverse(); - parsedResult.BackgroundAudioTracks.Reverse(); - foreach (var role in parsedResult.RoleAudioList) - { - role.audio.Reverse(); - } + role.audio.Reverse(); } + } - //打印轨道信息 - if (!myOption.HideStreams) - { - PrintAllTracksInfo(parsedResult, p.dur, myOption.OnlyShowInfo); - } + //打印轨道信息 + if (!myOption.HideStreams) + { + PrintAllTracksInfo(parsedResult, p.dur, myOption.OnlyShowInfo); + } - //仅展示 跳过下载 - if (myOption.OnlyShowInfo) - { - continue; - } + //仅展示 跳过下载 + if (myOption.OnlyShowInfo) + { + return; + } - int vIndex = 0; //用户手动选择的视频序号 - int aIndex = 0; //用户手动选择的音频序号 + int vIndex = 0; //用户手动选择的视频序号 + int aIndex = 0; //用户手动选择的音频序号 - //选择轨道 - if (myOption.Interactive && !selected) - { - SelectTrackManually(parsedResult, ref vIndex, ref aIndex); - selected = true; - } + //选择轨道 + if (myOption.Interactive && !selected) + { + SelectTrackManually(parsedResult, ref vIndex, ref aIndex); + selected = true; + } - Video? selectedVideo = parsedResult.VideoTracks.ElementAtOrDefault(vIndex); - Audio? selectedAudio = parsedResult.AudioTracks.ElementAtOrDefault(aIndex); - Audio? selectedBackgroundAudio = parsedResult.BackgroundAudioTracks.ElementAtOrDefault(aIndex); + Video? selectedVideo = parsedResult.VideoTracks.ElementAtOrDefault(vIndex); + Audio? selectedAudio = parsedResult.AudioTracks.ElementAtOrDefault(aIndex); + Audio? selectedBackgroundAudio = parsedResult.BackgroundAudioTracks.ElementAtOrDefault(aIndex); - LogDebug("Format Before: " + savePathFormat); - savePath = FormatSavePath(savePathFormat, title, selectedVideo, selectedAudio, p, pagesCount, apiType, pubTime); - LogDebug("Format After: " + savePath); + LogDebug("Format Before: " + savePathFormat); + savePath = FormatSavePath(savePathFormat, title, selectedVideo, selectedAudio, p, pagesCount, apiType, pubTime); + LogDebug("Format After: " + savePath); - if (downloadDanmaku) + if (downloadDanmaku) + { + var danmakuXmlPath = Path.ChangeExtension(savePath, ".xml"); + var danmakuAssPath = Path.ChangeExtension(savePath, ".ass"); + Log("正在下载弹幕Xml文件"); + string danmakuUrl = $"https://comment.bilibili.com/{p.cid}.xml"; + await DownloadFile(danmakuUrl, danmakuXmlPath, downloadConfig); + var danmakus = DanmakuUtil.ParseXml(danmakuXmlPath); + if (danmakus != null) { - var danmakuXmlPath = Path.ChangeExtension(savePath, ".xml"); - var danmakuAssPath = Path.ChangeExtension(savePath, ".ass"); - Log("正在下载弹幕Xml文件"); - string danmakuUrl = $"https://comment.bilibili.com/{p.cid}.xml"; - await DownloadFile(danmakuUrl, danmakuXmlPath, downloadConfig); - var danmakus = DanmakuUtil.ParseXml(danmakuXmlPath); - if (danmakus != null) - { - Log("正在保存弹幕Ass文件..."); - await DanmakuUtil.SaveAsAssAsync(danmakus, danmakuAssPath); - } - else - { - Log("弹幕Xml解析失败, 删除Xml..."); - File.Delete(danmakuXmlPath); - } - if (myOption.DanmakuOnly) + Log("正在保存弹幕Ass文件..."); + await DanmakuUtil.SaveAsAssAsync(danmakus, danmakuAssPath); + } + else + { + Log("弹幕Xml解析失败, 删除Xml..."); + File.Delete(danmakuXmlPath); + } + if (myOption.DanmakuOnly) + { + if (Directory.Exists(p.aid)) { - if (Directory.Exists(p.aid)) - { - Directory.Delete(p.aid); - } - continue; + Directory.Delete(p.aid); } + return; } + } - if (myOption.CoverOnly) - { - var coverUrl = pic == "" ? p.cover! : pic; - var newCoverPath = Path.ChangeExtension(savePath, Path.GetExtension(coverUrl)); - await DownloadFile(coverUrl, newCoverPath, downloadConfig); - if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true); - continue; - } + if (myOption.CoverOnly) + { + var coverUrl = pic == "" ? p.cover! : pic; + var newCoverPath = Path.ChangeExtension(savePath, Path.GetExtension(coverUrl)); + await DownloadFile(coverUrl, newCoverPath, downloadConfig); + if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true); + return; + } - Log($"已选择的流:"); - PrintSelectedTrackInfo(selectedVideo, selectedAudio, p.dur); + Log($"已选择的流:"); + PrintSelectedTrackInfo(selectedVideo, selectedAudio, p.dur); - //用户开启了强制替换 - if (myOption.ForceReplaceHost && string.IsNullOrEmpty(myOption.UposHost)) - { - myOption.UposHost = BACKUP_HOST; - } + //用户开启了强制替换 + if (myOption.ForceReplaceHost && string.IsNullOrEmpty(myOption.UposHost)) + { + myOption.UposHost = BACKUP_HOST; + } - //处理PCDN - HandlePcdn(myOption, selectedVideo, selectedAudio); + //处理PCDN + HandlePcdn(myOption, selectedVideo, selectedAudio); - if (!myOption.OnlyShowInfo && File.Exists(savePath) && new FileInfo(savePath).Length != 0) + if (!myOption.OnlyShowInfo && File.Exists(savePath) && new FileInfo(savePath).Length != 0) + { + Log($"{savePath}已存在, 跳过下载..."); + File.Delete(coverPath); + if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) { - Log($"{savePath}已存在, 跳过下载..."); - File.Delete(coverPath); - if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) - { - Directory.Delete(p.aid, true); - } - continue; + Directory.Delete(p.aid, true); } + return; + } - if (selectedVideo != null) + if (selectedVideo != null) + { + //杜比视界, 若ffmpeg版本小于5.0, 使用mp4box封装 + if (selectedVideo.dfn == Config.qualitys["126"] && !myOption.UseMP4box && !CheckFFmpegDOVI()) { - //杜比视界, 若ffmpeg版本小于5.0, 使用mp4box封装 - if (selectedVideo.dfn == Config.qualitys["126"] && !myOption.UseMP4box && !CheckFFmpegDOVI()) - { - LogWarn($"检测到杜比视界清晰度且您的ffmpeg版本小于5.0,将使用mp4box混流..."); - myOption.UseMP4box = true; - } - Log($"开始下载P{p.index}视频..."); - await DownloadTrackAsync(selectedVideo.baseUrl, videoPath, downloadConfig, video: true); + LogWarn($"检测到杜比视界清晰度且您的ffmpeg版本小于5.0,将使用mp4box混流..."); + myOption.UseMP4box = true; } + Log($"开始下载P{p.index}视频..."); + await DownloadTrackAsync(selectedVideo.baseUrl, videoPath, downloadConfig, video: true); + } - if (selectedAudio != null) - { - Log($"开始下载P{p.index}音频..."); - await DownloadTrackAsync(selectedAudio.baseUrl, audioPath, downloadConfig, video: false); - } + if (selectedAudio != null) + { + Log($"开始下载P{p.index}音频..."); + await DownloadTrackAsync(selectedAudio.baseUrl, audioPath, downloadConfig, video: false); + } - if (selectedBackgroundAudio != null) - { - var backgroundPath = $"{p.aid}/{p.aid}.{p.cid}.P{p.index}.back_ground.m4a"; - Log($"开始下载P{p.index}背景配音..."); - await DownloadTrackAsync(selectedBackgroundAudio.baseUrl, backgroundPath, downloadConfig, video: false); - audioMaterial.Add(new AudioMaterial("背景音频", "", backgroundPath)); - } + if (selectedBackgroundAudio != null) + { + var backgroundPath = $"{p.aid}/{p.aid}.{p.cid}.P{p.index}.back_ground.m4a"; + Log($"开始下载P{p.index}背景配音..."); + await DownloadTrackAsync(selectedBackgroundAudio.baseUrl, backgroundPath, downloadConfig, video: false); + audioMaterial.Add(new AudioMaterial("背景音频", "", backgroundPath)); + } - if (parsedResult.RoleAudioList.Any()) + if (parsedResult.RoleAudioList.Any()) + { + foreach (var role in parsedResult.RoleAudioList) { - foreach (var role in parsedResult.RoleAudioList) - { - Log($"开始下载P{p.index}配音[{role.title}]..."); - await DownloadTrackAsync(role.audio[aIndex].baseUrl, role.path, downloadConfig, video: false); - audioMaterial.Add(new AudioMaterial(role)); - } + Log($"开始下载P{p.index}配音[{role.title}]..."); + await DownloadTrackAsync(role.audio[aIndex].baseUrl, role.path, downloadConfig, video: false); + audioMaterial.Add(new AudioMaterial(role)); } + } - Log($"下载P{p.index}完毕"); - if (!parsedResult.VideoTracks.Any()) videoPath = ""; - if (!parsedResult.AudioTracks.Any()) audioPath = ""; - if (myOption.SkipMux) continue; - Log($"开始合并音视频{(subtitleInfo.Any() ? "和字幕" : "")}..."); - if (myOption.AudioOnly) - savePath = savePath[..^4] + ".m4a"; - int code = BBDownMuxer.MuxAV(myOption.UseMP4box, videoPath, audioPath, audioMaterial, savePath, - desc, - title, - p.ownerName ?? "", - (pagesCount > 1 || (bangumi && !vInfo.IsBangumiEnd)) ? p.title : "", - File.Exists(coverPath) ? coverPath : "", - lang, - subtitleInfo, myOption.AudioOnly, myOption.VideoOnly, p.points, p.pubTime, myOption.SimplyMux); - if (code != 0 || !File.Exists(savePath) || new FileInfo(savePath).Length == 0) - { - LogError("合并失败"); continue; - } - Log("清理临时文件..."); - Thread.Sleep(200); - if (parsedResult.VideoTracks.Any()) File.Delete(videoPath); - if (parsedResult.AudioTracks.Any()) File.Delete(audioPath); - if (p.points.Any()) File.Delete(Path.Combine(Path.GetDirectoryName(string.IsNullOrEmpty(videoPath) ? audioPath : videoPath)!, "chapters")); - foreach (var s in subtitleInfo) File.Delete(s.path); - foreach (var a in audioMaterial) File.Delete(a.path); - if (pagesInfo.Count == 1 || p.index == pagesInfo.Last().index || p.aid != pagesInfo.Last().aid) - File.Delete(coverPath); - if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true); + Log($"下载P{p.index}完毕"); + if (!parsedResult.VideoTracks.Any()) videoPath = ""; + if (!parsedResult.AudioTracks.Any()) audioPath = ""; + if (myOption.SkipMux) return; + Log($"开始合并音视频{(subtitleInfo.Any() ? "和字幕" : "")}..."); + if (myOption.AudioOnly) + savePath = savePath[..^4] + ".m4a"; + int code = BBDownMuxer.MuxAV(myOption.UseMP4box, videoPath, audioPath, audioMaterial, savePath, + desc, + title, + p.ownerName ?? "", + (pagesCount > 1 || (bangumi && !vInfo.IsBangumiEnd)) ? p.title : "", + File.Exists(coverPath) ? coverPath : "", + lang, + subtitleInfo, myOption.AudioOnly, myOption.VideoOnly, p.points, p.pubTime, myOption.SimplyMux); + if (code != 0 || !File.Exists(savePath) || new FileInfo(savePath).Length == 0) + { + LogError("合并失败"); return; } - else if (parsedResult.Clips.Any() && parsedResult.Dfns.Any()) //flv + Log("清理临时文件..."); + Thread.Sleep(200); + if (parsedResult.VideoTracks.Any()) File.Delete(videoPath); + if (parsedResult.AudioTracks.Any()) File.Delete(audioPath); + if (p.points.Any()) File.Delete(Path.Combine(Path.GetDirectoryName(string.IsNullOrEmpty(videoPath) ? audioPath : videoPath)!, "chapters")); + foreach (var s in subtitleInfo) File.Delete(s.path); + foreach (var a in audioMaterial) File.Delete(a.path); + if (pagesInfo.Count == 1 || p.index == pagesInfo.Last().index || p.aid != pagesInfo.Last().aid) + File.Delete(coverPath); + if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true); + } + else if (parsedResult.Clips.Any() && parsedResult.Dfns.Any()) //flv + { + bool flag = false; + var clips = parsedResult.Clips; + var dfns = parsedResult.Dfns; + reParse: + //排序 + parsedResult.VideoTracks = SortTracks(parsedResult.VideoTracks, dfnPriority, encodingPriority, myOption.VideoAscending); + + int vIndex = 0; + if (myOption.Interactive && !flag && !selected) { - bool flag = false; - var clips = parsedResult.Clips; - var dfns = parsedResult.Dfns; - reParse: - //排序 - parsedResult.VideoTracks = SortTracks(parsedResult.VideoTracks, dfnPriority, encodingPriority, myOption.VideoAscending); - - int vIndex = 0; - if (myOption.Interactive && !flag && !selected) - { - int i = 0; - dfns.ForEach(key => LogColor($"{i++}.{Config.qualitys[key]}")); - Log("请选择最想要的清晰度(输入序号): ", false); - Console.ForegroundColor = ConsoleColor.Cyan; - vIndex = Convert.ToInt32(Console.ReadLine()); - if (vIndex > dfns.Count || vIndex < 0) vIndex = 0; - Console.ResetColor(); - //重新解析 - parsedResult.VideoTracks.Clear(); - parsedResult = await ExtractTracksAsync(aidOri, p.aid, p.cid, p.epid, myOption.UseTvApi, myOption.UseIntlApi, myOption.UseAppApi, firstEncoding, dfns[vIndex]); - if (!p.points.Any()) p.points = parsedResult.ExtraPoints; - flag = true; - selected = true; - goto reParse; - } + int i = 0; + dfns.ForEach(key => LogColor($"{i++}.{Config.qualitys[key]}")); + Log("请选择最想要的清晰度(输入序号): ", false); + Console.ForegroundColor = ConsoleColor.Cyan; + vIndex = Convert.ToInt32(Console.ReadLine()); + if (vIndex > dfns.Count || vIndex < 0) vIndex = 0; + Console.ResetColor(); + //重新解析 + parsedResult.VideoTracks.Clear(); + parsedResult = await ExtractTracksAsync(aidOri, p.aid, p.cid, p.epid, myOption.UseTvApi, myOption.UseIntlApi, myOption.UseAppApi, firstEncoding, dfns[vIndex]); + if (!p.points.Any()) p.points = parsedResult.ExtraPoints; + flag = true; + selected = true; + goto reParse; + } - Log($"共计{parsedResult.VideoTracks.Count}条流(共有{clips.Count}个分段)."); - int index = 0; - foreach (var v in parsedResult.VideoTracks) - { - LogColor($"{index++}. [{v.dfn}] [{v.res}] [{v.codecs}] [{v.fps}] [~{v.size / 1024 / v.dur * 8:00} kbps] [{FormatFileSize(v.size)}]".Replace("[] ", ""), false); - if (myOption.OnlyShowInfo) - { - clips.ForEach(Console.WriteLine); - } - } - if (myOption.OnlyShowInfo) continue; - savePath = FormatSavePath(savePathFormat, title, parsedResult.VideoTracks.ElementAtOrDefault(vIndex), null, p, pagesCount, apiType, pubTime); - if (File.Exists(savePath) && new FileInfo(savePath).Length != 0) - { - Log($"{savePath}已存在, 跳过下载..."); - if (pagesInfo.Count == 1 && Directory.Exists(p.aid)) - { - Directory.Delete(p.aid, true); - } - continue; - } - var pad = string.Empty.PadRight(clips.Count.ToString().Length, '0'); - for (int i = 0; i < clips.Count; i++) - { - var link = clips[i]; - videoPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.{i.ToString(pad)}.mp4"; - Log($"开始下载P{p.index}视频, 片段({(i + 1).ToString(pad)}/{clips.Count})..."); - await DownloadTrackAsync(link, videoPath, downloadConfig, video: true); - } - Log($"下载P{p.index}完毕"); - Log("开始合并分段..."); - var files = GetFiles(Path.GetDirectoryName(videoPath)!, ".mp4"); - videoPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.mp4"; - BBDownMuxer.MergeFLV(files, videoPath); - if (myOption.SkipMux) continue; - Log($"开始混流视频{(subtitleInfo.Any() ? "和字幕" : "")}..."); - if (myOption.AudioOnly) - savePath = savePath[..^4] + ".m4a"; - int code = BBDownMuxer.MuxAV(false, videoPath, "", audioMaterial, savePath, - desc, - title, - p.ownerName ?? "", - (pagesCount > 1 || (bangumi && !vInfo.IsBangumiEnd)) ? p.title : "", - File.Exists(coverPath) ? coverPath : "", - lang, - subtitleInfo, myOption.AudioOnly, myOption.VideoOnly, p.points, p.pubTime, myOption.SimplyMux); - if (code != 0 || !File.Exists(savePath) || new FileInfo(savePath).Length == 0) + Log($"共计{parsedResult.VideoTracks.Count}条流(共有{clips.Count}个分段)."); + int index = 0; + foreach (var v in parsedResult.VideoTracks) + { + LogColor($"{index++}. [{v.dfn}] [{v.res}] [{v.codecs}] [{v.fps}] [~{v.size / 1024 / v.dur * 8:00} kbps] [{FormatFileSize(v.size)}]".Replace("[] ", ""), false); + if (myOption.OnlyShowInfo) { - LogError("合并失败"); continue; + clips.ForEach(Console.WriteLine); } - Log("清理临时文件..."); - Thread.Sleep(200); - if (parsedResult.VideoTracks.Count != 0) File.Delete(videoPath); - foreach (var s in subtitleInfo) File.Delete(s.path); - foreach (var a in audioMaterial) File.Delete(a.path); - if (p.points.Any()) File.Delete(Path.Combine(Path.GetDirectoryName(string.IsNullOrEmpty(videoPath) ? audioPath : videoPath)!, "chapters")); - if (pagesInfo.Count == 1 || p.index == pagesInfo.Last().index || p.aid != pagesInfo.Last().aid) - File.Delete(coverPath); - if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true); } - else + if (myOption.OnlyShowInfo) return; + savePath = FormatSavePath(savePathFormat, title, parsedResult.VideoTracks.ElementAtOrDefault(vIndex), null, p, pagesCount, apiType, pubTime); + if (File.Exists(savePath) && new FileInfo(savePath).Length != 0) { - LogError("解析此分P失败(建议--debug查看详细信息)"); - if (parsedResult.WebJsonString.Length < 100) + Log($"{savePath}已存在, 跳过下载..."); + if (pagesInfo.Count == 1 && Directory.Exists(p.aid)) { - LogError(parsedResult.WebJsonString); + Directory.Delete(p.aid, true); } - LogDebug("{0}", parsedResult.WebJsonString); - continue; + return; + } + var pad = string.Empty.PadRight(clips.Count.ToString().Length, '0'); + for (int i = 0; i < clips.Count; i++) + { + var link = clips[i]; + videoPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.{i.ToString(pad)}.mp4"; + Log($"开始下载P{p.index}视频, 片段({(i + 1).ToString(pad)}/{clips.Count})..."); + await DownloadTrackAsync(link, videoPath, downloadConfig, video: true); } + Log($"下载P{p.index}完毕"); + Log("开始合并分段..."); + var files = GetFiles(Path.GetDirectoryName(videoPath)!, ".mp4"); + videoPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.mp4"; + BBDownMuxer.MergeFLV(files, videoPath); + if (myOption.SkipMux) return; + Log($"开始混流视频{(subtitleInfo.Any() ? "和字幕" : "")}..."); + if (myOption.AudioOnly) + savePath = savePath[..^4] + ".m4a"; + int code = BBDownMuxer.MuxAV(false, videoPath, "", audioMaterial, savePath, + desc, + title, + p.ownerName ?? "", + (pagesCount > 1 || (bangumi && !vInfo.IsBangumiEnd)) ? p.title : "", + File.Exists(coverPath) ? coverPath : "", + lang, + subtitleInfo, myOption.AudioOnly, myOption.VideoOnly, p.points, p.pubTime, myOption.SimplyMux); + if (code != 0 || !File.Exists(savePath) || new FileInfo(savePath).Length == 0) + { + LogError("合并失败"); return; + } + Log("清理临时文件..."); + Thread.Sleep(200); + if (parsedResult.VideoTracks.Count != 0) File.Delete(videoPath); + foreach (var s in subtitleInfo) File.Delete(s.path); + foreach (var a in audioMaterial) File.Delete(a.path); + if (p.points.Any()) File.Delete(Path.Combine(Path.GetDirectoryName(string.IsNullOrEmpty(videoPath) ? audioPath : videoPath)!, "chapters")); + if (pagesInfo.Count == 1 || p.index == pagesInfo.Last().index || p.aid != pagesInfo.Last().aid) + File.Delete(coverPath); + if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) Directory.Delete(p.aid, true); } - catch (Exception ex) + else { - if (++retryCount > 2) throw; - LogError(ex.Message); - LogWarn("下载出现异常, 3秒后将进行自动重试..."); - await Task.Delay(3000); - goto downloadPage; + LogError("解析此分P失败(建议--debug查看详细信息)"); + if (parsedResult.WebJsonString.Length < 100) + { + LogError(parsedResult.WebJsonString); + } + LogDebug("{0}", parsedResult.WebJsonString); + return; } } - Log("任务完成"); + catch (Exception ex) + { + if (++retryCount > 2) throw; + LogError(ex.Message); + LogWarn("下载出现异常, 3秒后将进行自动重试..."); + await Task.Delay(3000); + goto downloadPage; + } } + private static async Task DoWorkAsync(MyOption myOption) { try @@ -732,7 +755,7 @@ private static async Task DoWorkAsync(MyOption myOption) var (encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, input, savePathFormat, lang, aidOri, delay) = SetUpWork(myOption); var (fetchedAid, vInfo, apiType) = await GetVideoInfoAsync(myOption, aidOri, input); - await DownloadPageAsync(myOption, vInfo, encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, + await DownloadPagesAsync(myOption, vInfo, encodingPriority, dfnPriority, firstEncoding, downloadDanmaku, input, savePathFormat, lang, fetchedAid, delay, apiType); } catch (Exception e)