using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using static BBDown.Core.Entity.Entity; using static BBDown.BBDownUtil; using static BBDown.Core.Logger; using System.Linq; using System.Text.RegularExpressions; using BBDown.Core; using BBDown.Core.Entity; using static BBDown.BBDownDownloadUtil; namespace BBDown; internal partial class Program { /// /// 兼容旧版本命令行参数并给出警告 /// /// private static void HandleDeprecatedOptions(MyOption myOption) { if (myOption.AddDfnSubfix) { LogWarn("--add-dfn-subfix 已被弃用, 建议使用 --file-pattern/-F 或 --multi-file-pattern/-M 来自定义输出文件名格式"); if (string.IsNullOrEmpty(myOption.FilePattern) && string.IsNullOrEmpty(myOption.MultiFilePattern)) { SinglePageDefaultSavePath += "[]"; MultiPageDefaultSavePath += "[]"; LogWarn($"已切换至 -F \"{SinglePageDefaultSavePath}\" -M \"{MultiPageDefaultSavePath}\""); } } if (myOption.Aria2cProxy != "") { LogWarn("--aria2c-proxy 已被弃用, 请使用 --aria2c-args 来设置aria2c代理, 本次执行已添加该代理"); myOption.Aria2cArgs += $" --all-proxy=\"{myOption.Aria2cProxy}\""; } if (myOption.OnlyHevc) { LogWarn("--only-hevc/-hevc 已被弃用, 请使用 --encoding-priority 来设置编码优先级, 本次执行已将hevc设置为最高优先级"); myOption.EncodingPriority = "hevc"; } if (myOption.OnlyAvc) { LogWarn("--only-avc/-avc 已被弃用, 请使用 --encoding-priority 来设置编码优先级, 本次执行已将avc设置为最高优先级"); myOption.EncodingPriority = "avc"; } if (myOption.OnlyAv1) { LogWarn("--only-av1/-av1 已被弃用, 请使用 --encoding-priority 来设置编码优先级, 本次执行已将av1设置为最高优先级"); myOption.EncodingPriority = "av1"; } if (myOption.NoPaddingPageNum) { LogWarn("--no-padding-page-num 已被弃用, 建议使用 --file-pattern/-F 或 --multi-file-pattern/-M 来自定义输出文件名格式"); if (string.IsNullOrEmpty(myOption.FilePattern) && string.IsNullOrEmpty(myOption.MultiFilePattern)) { MultiPageDefaultSavePath = MultiPageDefaultSavePath.Replace("", ""); LogWarn($"已切换至 -M \"{MultiPageDefaultSavePath}\""); } } if (myOption.BandwithAscending) { LogWarn("--bandwith-ascending 已被弃用, 建议使用 --video-ascending 与 --audio-ascending 来指定视频或音频是否升序, 本次执行已将视频与音频均设为升序"); myOption.VideoAscending = true; myOption.AudioAscending = true; } } /// /// 解析用户指定的编码优先级 /// /// /// private static Dictionary ParseEncodingPriority(MyOption myOption, out string firstEncoding) { var encodingPriority = new Dictionary(); firstEncoding = ""; if (myOption.EncodingPriority != null) { var encodingPriorityTemp = myOption.EncodingPriority .ToUpper() .Replace(',', ',') .Replace("-", string.Empty) .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Where(s => !string.IsNullOrEmpty(s)).ToList(); byte index = 0; firstEncoding = encodingPriorityTemp.First(); foreach (string encoding in encodingPriorityTemp) { if (encodingPriority.ContainsKey(encoding)) continue; encodingPriority[encoding] = index; index++; } } return encodingPriority; } private static BBDownDanmakuFormat[] ParseDownloadDanmakuFormats(MyOption myOption) { if (string.IsNullOrEmpty(myOption.DownloadDanmakuFormats)) return BBDownDanmakuFormatInfo.DefaultFormats; var formats = myOption.DownloadDanmakuFormats.Replace(",", ",").ToLower().Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); if (formats.Any(format => !BBDownDanmakuFormatInfo.AllFormatNames.Contains(format))) { LogError($"包含不支持的下载弹幕格式:{myOption.DownloadDanmakuFormats}"); return BBDownDanmakuFormatInfo.DefaultFormats; } return formats.Select(BBDownDanmakuFormatInfo.FromFormatName).ToArray(); } /// /// 解析用户输入的清晰度规格优先级 /// /// /// private static Dictionary ParseDfnPriority(MyOption myOption) { var dfnPriority = new Dictionary(); if (myOption.DfnPriority != null) { var dfnPriorityTemp = myOption.DfnPriority.Replace(",", ",").Split(',').Select(s => s.ToUpper().Trim()).Where(s => !string.IsNullOrEmpty(s)); int index = 0; foreach (string dfn in dfnPriorityTemp) { if (dfnPriority.ContainsKey(dfn)) { continue; } dfnPriority[dfn] = index; index++; } } return dfnPriority; } /// /// 寻找并设置所需的二进制文件 /// /// /// private static void FindBinaries(MyOption myOption) { if (!string.IsNullOrEmpty(myOption.FFmpegPath) && File.Exists(myOption.FFmpegPath)) { BBDownMuxer.FFMPEG = myOption.FFmpegPath; } if (!string.IsNullOrEmpty(myOption.Mp4boxPath) && File.Exists(myOption.Mp4boxPath)) { BBDownMuxer.MP4BOX = myOption.Mp4boxPath; } if (!string.IsNullOrEmpty(myOption.Aria2cPath) && File.Exists(myOption.Aria2cPath)) { BBDownAria2c.ARIA2C = myOption.Aria2cPath; } //寻找ffmpeg或mp4box if (!myOption.SkipMux) { if (myOption.UseMP4box) { if (string.IsNullOrEmpty(BBDownMuxer.MP4BOX) || !File.Exists(BBDownMuxer.MP4BOX)) { var binPath = FindExecutable("mp4box") ?? FindExecutable("MP4box"); if (string.IsNullOrEmpty(binPath)) throw new Exception("找不到可执行的mp4box文件"); BBDownMuxer.MP4BOX = binPath; } } else if (string.IsNullOrEmpty(BBDownMuxer.FFMPEG) || !File.Exists(BBDownMuxer.FFMPEG)) { var binPath = FindExecutable("ffmpeg"); if (string.IsNullOrEmpty(binPath)) throw new Exception("找不到可执行的ffmpeg文件"); BBDownMuxer.FFMPEG = binPath; } } //寻找aria2c if (myOption.UseAria2c) { if (string.IsNullOrEmpty(BBDownAria2c.ARIA2C) || !File.Exists(BBDownAria2c.ARIA2C)) { var binPath = FindExecutable("aria2c"); if (string.IsNullOrEmpty(binPath)) throw new Exception("找不到可执行的aria2c文件"); BBDownAria2c.ARIA2C = binPath; } } } /// /// 处理有冲突的选项 /// /// private static void HandleConflictingOptions(MyOption myOption) { //手动选择时不能隐藏流 if (myOption.Interactive) { myOption.HideStreams = false; } //audioOnly和videoOnly同时开启则全部忽视 if (myOption.AudioOnly && myOption.VideoOnly) { myOption.AudioOnly = false; myOption.VideoOnly = false; } if (myOption.SkipSubtitle) { myOption.SubOnly = false; } } /// /// 设置用户输入的自定义工作目录 /// /// private static void ChangeWorkingDir(MyOption myOption) { if (!string.IsNullOrEmpty(myOption.WorkDir)) { //解释环境变量 myOption.WorkDir = Environment.ExpandEnvironmentVariables(myOption.WorkDir); var dir = Path.GetFullPath(myOption.WorkDir); if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } //设置工作目录 Environment.CurrentDirectory = dir; LogDebug("切换工作目录至:{0}", dir); } } /// /// 加载用户的认证信息(cookie或token) /// /// private static void LoadCredentials(MyOption myOption) { if (string.IsNullOrEmpty(Config.COOKIE) && File.Exists(Path.Combine(APP_DIR, "BBDown.data"))) { Log("加载本地cookie..."); LogDebug("文件路径:{0}", Path.Combine(APP_DIR, "BBDown.data")); Config.COOKIE = File.ReadAllText(Path.Combine(APP_DIR, "BBDown.data")); } if (string.IsNullOrEmpty(Config.TOKEN) && File.Exists(Path.Combine(APP_DIR, "BBDownTV.data")) && myOption.UseTvApi) { Log("加载本地token..."); LogDebug("文件路径:{0}", Path.Combine(APP_DIR, "BBDownTV.data")); Config.TOKEN = File.ReadAllText(Path.Combine(APP_DIR, "BBDownTV.data")); Config.TOKEN = Config.TOKEN.Replace("access_token=", ""); } if (string.IsNullOrEmpty(Config.TOKEN) && File.Exists(Path.Combine(APP_DIR, "BBDownApp.data")) && myOption.UseAppApi) { Log("加载本地token..."); LogDebug("文件路径:{0}", Path.Combine(APP_DIR, "BBDownApp.data")); Config.TOKEN = File.ReadAllText(Path.Combine(APP_DIR, "BBDownApp.data")); Config.TOKEN = Config.TOKEN.Replace("access_token=", ""); } } 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列表 /// /// /// /// /// private static List? GetSelectedPages(MyOption myOption, VInfo vInfo, string input) { List? selectedPages = null; List pagesInfo = vInfo.PagesInfo; string selectPage = myOption.SelectPage.ToUpper().Trim().Trim(','); if (string.IsNullOrEmpty(selectPage)) { //如果用户没有选择分P, 根据epid或query param来确定某一集 if (!string.IsNullOrEmpty(vInfo.Index)) { selectedPages = [vInfo.Index]; Log("程序已自动选择你输入的集数, 如果要下载其他集数请自行指定分P(如可使用-p ALL代表全部)"); } else if (!string.IsNullOrEmpty(GetQueryString("p", input))) { selectedPages = [GetQueryString("p", input)]; Log("程序已自动选择你输入的集数, 如果要下载其他集数请自行指定分P(如可使用-p ALL代表全部)"); } } else if (selectPage != "ALL") { selectedPages = new List(); //选择最新分P string lastPage = pagesInfo.Count.ToString(); foreach (string key in new[] { "LAST", "NEW", "LATEST" }) { selectPage = selectPage.Replace(key, lastPage); } try { if (selectPage.Contains('-')) { string[] tmp = selectPage.Split('-'); int start = int.Parse(tmp[0]); int end = int.Parse(tmp[1]); for (int i = start; i <= end; i++) { selectedPages.Add(i.ToString()); } } else { foreach (var s in selectPage.Split(',')) { selectedPages.Add(s); } } } catch { LogError("解析分P参数时失败了~"); selectedPages = null; }; } return selectedPages; } /// /// 处理CDN域名 /// /// /// /// private static void HandlePcdn(MyOption myOption, Video? selectedVideo, Audio? selectedAudio) { if (myOption.UposHost == "") { //处理PCDN if (!myOption.AllowPcdn) { var pcdnReg = PcdnRegex(); if (selectedVideo != null && pcdnReg.IsMatch(selectedVideo.baseUrl)) { LogWarn($"检测到视频流为PCDN, 尝试强制替换为{BACKUP_HOST}……"); selectedVideo.baseUrl = pcdnReg.Replace(selectedVideo.baseUrl, $"://{BACKUP_HOST}/"); } if (selectedAudio != null && pcdnReg.IsMatch(selectedAudio.baseUrl)) { LogWarn($"检测到音频流为PCDN, 尝试强制替换为{BACKUP_HOST}……"); selectedAudio.baseUrl = pcdnReg.Replace(selectedAudio.baseUrl, $"://{BACKUP_HOST}/"); } } var akamReg = AkamRegex(); if (selectedVideo != null && Config.AREA != "" && selectedVideo.baseUrl.Contains("akamaized.net")) { LogWarn($"检测到视频流为外国源, 尝试强制替换为{BACKUP_HOST}……"); selectedVideo.baseUrl = akamReg.Replace(selectedVideo.baseUrl, $"://{BACKUP_HOST}/"); } if (selectedAudio != null && Config.AREA != "" && selectedAudio.baseUrl.Contains("akamaized.net")) { LogWarn($"检测到音频流为外国源, 尝试强制替换为{BACKUP_HOST}……"); selectedAudio.baseUrl = akamReg.Replace(selectedAudio.baseUrl, $"://{BACKUP_HOST}/"); } } else { if (selectedVideo != null) { LogWarn($"尝试将视频流强制替换为{myOption.UposHost}……"); selectedVideo.baseUrl = UposRegex().Replace(selectedVideo.baseUrl, $"://{myOption.UposHost}/"); } if (selectedAudio != null) { LogWarn($"尝试将音频流强制替换为{myOption.UposHost}……"); selectedAudio.baseUrl = UposRegex().Replace(selectedAudio.baseUrl, $"://{myOption.UposHost}/"); } } } /// /// 打印解析到的各个轨道信息 /// /// /// private static void PrintAllTracksInfo(ParsedResult parsedResult, int pageDur, bool onlyShowInfo) { if (parsedResult.BackgroundAudioTracks.Any() && parsedResult.RoleAudioList.Any()) { Log($"共计{parsedResult.BackgroundAudioTracks.Count}条背景音频流."); int index = 0; foreach (var a in parsedResult.BackgroundAudioTracks) { int pDur = pageDur == 0 ? a.dur : pageDur; LogColor($"{index++}. [{a.codecs}] [{a.bandwith} kbps] [~{FormatFileSize(pDur * a.bandwith * 1024 / 8)}]", false); } Log($"共计{parsedResult.RoleAudioList.Count}条配音, 每条包含{parsedResult.RoleAudioList[0].audio.Count}条配音流."); index = 0; foreach (var a in parsedResult.RoleAudioList[0].audio) { int pDur = pageDur == 0 ? a.dur : pageDur; LogColor($"{index++}. [{a.codecs}] [{a.bandwith} kbps] [~{FormatFileSize(pDur * a.bandwith * 1024 / 8)}]", false); } } //展示所有的音视频流信息 if (parsedResult.VideoTracks.Any()) { Log($"共计{parsedResult.VideoTracks.Count}条视频流."); int index = 0; foreach (var v in parsedResult.VideoTracks) { int pDur = pageDur == 0 ? v.dur : pageDur; var size = v.size > 0 ? v.size : pDur * v.bandwith * 1024 / 8; LogColor($"{index++}. [{v.dfn}] [{v.res}] [{v.codecs}] [{v.fps}] [{v.bandwith} kbps] [~{FormatFileSize(size)}]".Replace("[] ", ""), false); if (onlyShowInfo) Console.WriteLine(v.baseUrl); } } if (parsedResult.AudioTracks.Any()) { Log($"共计{parsedResult.AudioTracks.Count}条音频流."); int index = 0; foreach (var a in parsedResult.AudioTracks) { int pDur = pageDur == 0 ? a.dur : pageDur; LogColor($"{index++}. [{a.codecs}] [{a.bandwith} kbps] [~{FormatFileSize(pDur * a.bandwith * 1024 / 8)}]", false); if (onlyShowInfo) Console.WriteLine(a.baseUrl); } } } private static void PrintSelectedTrackInfo(Video? selectedVideo, Audio? selectedAudio, int pageDur) { if (selectedVideo != null) { int pDur = pageDur == 0 ? selectedVideo.dur : pageDur; var size = selectedVideo.size > 0 ? selectedVideo.size : pDur * selectedVideo.bandwith * 1024 / 8; LogColor($"[视频] [{selectedVideo.dfn}] [{selectedVideo.res}] [{selectedVideo.codecs}] [{selectedVideo.fps}] [{selectedVideo.bandwith} kbps] [~{FormatFileSize(size)}]".Replace("[] ", ""), false); } if (selectedAudio != null) { int pDur = pageDur == 0 ? selectedAudio.dur : pageDur; LogColor($"[音频] [{selectedAudio.codecs}] [{selectedAudio.bandwith} kbps] [~{FormatFileSize(pDur * selectedAudio.bandwith * 1024 / 8)}]", false); } } /// /// 引导用户进行手动选择轨道 /// /// /// /// private static void SelectTrackManually(ParsedResult parsedResult, ref int vIndex, ref int aIndex) { if (parsedResult.VideoTracks.Any()) { Log("请选择一条视频流(输入序号): ", false); Console.ForegroundColor = ConsoleColor.Cyan; vIndex = Convert.ToInt32(Console.ReadLine()); if (vIndex > parsedResult.VideoTracks.Count || vIndex < 0) vIndex = 0; Console.ResetColor(); } if (parsedResult.AudioTracks.Any()) { Log("请选择一条音频流(输入序号): ", false); Console.ForegroundColor = ConsoleColor.Cyan; aIndex = Convert.ToInt32(Console.ReadLine()); if (aIndex > parsedResult.AudioTracks.Count || aIndex < 0) aIndex = 0; Console.ResetColor(); } } /// /// 下载轨道 /// /// private static async Task DownloadTrackAsync(string url, string destPath, DownloadConfig downloadConfig, bool video) { if (downloadConfig.MultiThread && !url.Contains("-cmcc-")) { await MultiThreadDownloadFileAsync(url, destPath, downloadConfig); Log($"合并{(video ? "视频" : "音频")}分片..."); CombineMultipleFilesIntoSingleFile(GetFiles(Path.GetDirectoryName(destPath)!, $".{(video ? "v" : "a")}clip"), destPath); Log("清理分片..."); foreach (var file in new DirectoryInfo(Path.GetDirectoryName(destPath)!).EnumerateFiles("*.?clip")) file.Delete(); } else { if (downloadConfig.MultiThread && url.Contains("-cmcc-")) { LogWarn("检测到cmcc域名cdn, 已经禁用多线程"); downloadConfig.ForceHttp = false; } await DownloadFileAsync(url, destPath, downloadConfig); } } [GeneratedRegex("://.*:\\d+/")] private static partial Regex PcdnRegex(); [GeneratedRegex("://.*akamaized\\.net/")] private static partial Regex AkamRegex(); [GeneratedRegex("://[^/]+/")] private static partial Regex UposRegex(); }