private static async Task DoWorkAsync(MyOption myOption) { Console.BackgroundColor = ConsoleColor.DarkBlue; Console.ForegroundColor = ConsoleColor.White; var ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; Console.Write($"BBDown version {ver.Major}.{ver.Minor}.{ver.Build}, Bilibili Downloader.\r\n"); Console.ResetColor(); Console.Write("欢迎到讨论区交流:\r\n" + "https://github.com/nilaoda/BBDown/discussions\r\n"); Console.WriteLine(); //检测更新 new Thread(async() => { await CheckUpdateAsync(); }).Start(); try { bool interactMode = myOption.Interactive; bool infoMode = myOption.OnlyShowInfo; bool tvApi = myOption.UseTvApi; bool intlApi = myOption.UseIntlApi; bool hevc = myOption.OnlyHevc; bool hideStreams = myOption.HideStreams; bool multiThread = myOption.MultiThread; bool audioOnly = myOption.AudioOnly; bool videoOnly = myOption.VideoOnly; bool subOnly = myOption.SubOnly; bool skipMux = myOption.SkipMux; bool showAll = myOption.ShowAll; bool useAria2c = myOption.UseAria2c; DEBUG_LOG = myOption.Debug; string input = myOption.Url; string lang = myOption.Language; string selectPage = myOption.SelectPage.ToUpper(); string aidOri = ""; //原始aid COOKIE = myOption.Cookie; TOKEN = myOption.AccessToken.Replace("access_token=", ""); //audioOnly和videoOnly同时开启则全部忽视 if (audioOnly && videoOnly) { audioOnly = false; videoOnly = false; } List <string> selectedPages = null; if (!string.IsNullOrEmpty(GetQueryString("p", input))) { selectedPages = new List <string>(); selectedPages.Add(GetQueryString("p", input)); } LogDebug("运行参数:{0}", myOption); if (string.IsNullOrEmpty(COOKIE) && File.Exists(Path.Combine(AppContext.BaseDirectory, "BBDown.data")) && !tvApi) { Log("加载本地cookie..."); LogDebug("文件路径:{0}", Path.Combine(AppContext.BaseDirectory, "BBDown.data")); COOKIE = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "BBDown.data")); } if (string.IsNullOrEmpty(TOKEN) && File.Exists(Path.Combine(AppContext.BaseDirectory, "BBDownTV.data")) && tvApi) { Log("加载本地token..."); LogDebug("文件路径:{0}", Path.Combine(AppContext.BaseDirectory, "BBDownTV.data")); TOKEN = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "BBDownTV.data")); TOKEN = TOKEN.Replace("access_token=", ""); } Log("获取aid..."); aidOri = await GetAvIdAsync(input); Log("获取aid结束: " + aidOri); //-p的优先级大于URL中的自带p参数,所以先清空selectedPages if (!string.IsNullOrEmpty(selectPage) && selectPage != "ALL") { selectedPages = new List <string>(); try { string tmp = selectPage; tmp = tmp.Trim().Trim(','); if (tmp.Contains("-")) { int start = int.Parse(tmp.Split('-')[0]); int end = int.Parse(tmp.Split('-')[1]); for (int i = start; i <= end; i++) { selectedPages.Add(i.ToString()); } } else { foreach (var s in tmp.Split(',')) { selectedPages.Add(s); } } } catch { LogError("解析分P参数时失败了~"); selectedPages = null; }; } if (selectPage == "ALL") { selectedPages = null; } if (string.IsNullOrEmpty(aidOri)) { throw new Exception("输入有误"); } Log("获取视频信息..."); IFetcher fetcher = new BBDownNormalInfoFetcher(); if (aidOri.StartsWith("cheese")) { fetcher = new BBDownCheeseInfoFetcher(); } else if (aidOri.StartsWith("ep")) { if (intlApi) { fetcher = new BBDownIntlBangumiInfoFetcher(); } else { fetcher = new BBDownBangumiInfoFetcher(); } } else if (aidOri.StartsWith("mid")) { fetcher = new BBDownSpaceVideoFetcher(); } var vInfo = fetcher.Fetch(aidOri); string title = vInfo.Title; string desc = vInfo.Desc; string pic = vInfo.Pic; string pubTime = vInfo.PubTime; LogColor("视频标题: " + title); Log("发布时间: " + pubTime); List <Page> pagesInfo = vInfo.PagesInfo; List <Subtitle> subtitleInfo = new List <Subtitle>(); bool more = false; bool bangumi = vInfo.IsBangumi; bool cheese = vInfo.IsCheese; //打印分P信息 foreach (Page p in pagesInfo) { if (!showAll && more && p.index != pagesInfo.Count) { continue; } if (!showAll && !more && p.index > 5) { Log("......"); more = true; } else { Log($"P{p.index}: [{p.cid}] [{p.title}] [{FormatTime(p.dur)}]"); } } //如果用户没有选择分P,根据epid来确定某一集 if (selectedPages == null && selectPage != "ALL" && !string.IsNullOrEmpty(vInfo.Index)) { selectedPages = new List <string> { vInfo.Index }; Log("程序已自动选择你输入的集数,如果要下载其他集数请自行指定分P(如可使用-p ALL代表全部)"); } Log($"共计 {pagesInfo.Count} 个分P, 已选择:" + (selectedPages == null ? "ALL" : string.Join(",", selectedPages))); //过滤不需要的分P if (selectedPages != null) { pagesInfo = pagesInfo.Where(p => selectedPages.Contains(p.index.ToString())).ToList(); } foreach (Page p in pagesInfo) { Log($"开始解析P{p.index}..."); if (!infoMode) { if (!Directory.Exists(p.aid)) { Directory.CreateDirectory(p.aid); } if (!subOnly && !File.Exists($"{p.aid}/{p.aid}.jpg")) { Log("下载封面..."); LogDebug("下载:{0}", pic); new WebClient().DownloadFile(pic, $"{p.aid}/{p.aid}.jpg"); } LogDebug("获取字幕..."); subtitleInfo = BBDownSubUtil.GetSubtitles(p.aid, p.cid, p.epid, intlApi); foreach (Subtitle s in subtitleInfo) { Log($"下载字幕 {s.lan} => {BBDownSubUtil.SubDescDic[s.lan]}..."); LogDebug("下载:{0}", s.url); BBDownSubUtil.SaveSubtitle(s.url, s.path); if (subOnly && File.Exists(s.path) && File.ReadAllText(s.path) != "") { string _indexStr = p.index.ToString("0".PadRight(pagesInfo.OrderByDescending(_p => _p.index).First().index.ToString().Length, '0')); //处理文件夹以.结尾导致的异常情况 if (title.EndsWith(".")) { title += "_fix"; } string _outSubPath = GetValidFileName(title) + (pagesInfo.Count > 1 ? $"/[P{_indexStr}]{GetValidFileName(p.title)}" : (vInfo.PagesInfo.Count > 1 ? $"[P{_indexStr}]{GetValidFileName(p.title)}" : "")) + $"_{BBDownSubUtil.SubDescDic[s.lan]}.srt"; if (_outSubPath.Contains("/")) { if (!Directory.Exists(Path.GetDirectoryName(_outSubPath))) { Directory.CreateDirectory(Path.GetDirectoryName(_outSubPath)); } } File.Move(s.path, _outSubPath); } } if (subOnly) { if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) { Directory.Delete(p.aid, true); } continue; } } string webJsonStr = ""; List <Video> videoTracks = new List <Video>(); List <Audio> audioTracks = new List <Audio>(); List <string> clips = new List <string>(); List <string> dfns = new List <string>(); string indexStr = p.index.ToString("0".PadRight(pagesInfo.OrderByDescending(_p => _p.index).First().index.ToString().Length, '0')); string videoPath = $"{p.aid}/{p.aid}.P{indexStr}.{p.cid}.mp4"; string audioPath = $"{p.aid}/{p.aid}.P{indexStr}.{p.cid}.m4a"; //处理文件夹以.结尾导致的异常情况 if (title.EndsWith(".")) { title += "_fix"; } string outPath = GetValidFileName(title) + (pagesInfo.Count > 1 ? $"/[P{indexStr}]{GetValidFileName(p.title)}" : (vInfo.PagesInfo.Count > 1 ? $"[P{indexStr}]{GetValidFileName(p.title)}" : "")) + ".mp4"; //调用解析 (webJsonStr, videoTracks, audioTracks, clips, dfns) = ExtractTracks(hevc, aidOri, p.aid, p.cid, p.epid, tvApi, intlApi); //File.WriteAllText($"debug.json", JObject.Parse(webJson).ToString()); JObject respJson = JObject.Parse(webJsonStr); //此处代码简直灾难,后续优化吧 if ((videoTracks.Count != 0 || audioTracks.Count != 0) && clips.Count == 0) //dash { if (webJsonStr.Contains("\"video\":[") && videoTracks.Count == 0) { LogError("没有找到符合要求的视频流"); if (!audioOnly) { continue; } } if (webJsonStr.Contains("\"audio\":[") && audioTracks.Count == 0) { LogError("没有找到符合要求的音频流"); if (!videoOnly) { continue; } } //降序 videoTracks.Sort(Compare); audioTracks.Sort(Compare); if (audioOnly) { videoTracks.Clear(); } if (videoOnly) { audioTracks.Clear(); } int vIndex = 0; int aIndex = 0; if (!hideStreams) { //展示所有的音视频流信息 if (videoTracks.Count > 0) { Log($"共计{videoTracks.Count}条视频流."); int index = 0; foreach (var v in videoTracks) { int pDur = p.dur == 0 ? v.dur : p.dur; LogColor($"{index++}. [{v.dfn}] [{v.res}] [{v.codecs}] [{v.fps}] [{v.bandwith} kbps] [~{FormatFileSize(pDur * v.bandwith * 1024 / 8)}]".Replace("[] ", ""), false); if (infoMode) { Console.WriteLine(v.baseUrl); } } } if (audioTracks.Count > 0) { Log($"共计{audioTracks.Count}条音频流."); int index = 0; foreach (var a in audioTracks) { int pDur = p.dur == 0 ? a.dur : p.dur; LogColor($"{index++}. [{a.codecs}] [{a.bandwith} kbps] [~{FormatFileSize(pDur * a.bandwith * 1024 / 8)}]", false); if (infoMode) { Console.WriteLine(a.baseUrl); } } } } if (infoMode) { continue; } if (interactMode && !hideStreams) { if (videoTracks.Count > 0) { Log("请选择一条视频流(输入序号): ", false); Console.ForegroundColor = ConsoleColor.Cyan; vIndex = Convert.ToInt32(Console.ReadLine()); if (vIndex > videoTracks.Count || vIndex < 0) { vIndex = 0; } Console.ResetColor(); } if (audioTracks.Count > 0) { Log("请选择一条音频流(输入序号): ", false); Console.ForegroundColor = ConsoleColor.Cyan; aIndex = Convert.ToInt32(Console.ReadLine()); if (aIndex > audioTracks.Count || aIndex < 0) { aIndex = 0; } Console.ResetColor(); } } if (File.Exists(outPath) && new FileInfo(outPath).Length != 0) { Log($"{outPath}已存在, 跳过下载..."); continue; } Log($"已选择的流:"); if (videoTracks.Count > 0) { LogColor($"[视频] [{videoTracks[vIndex].dfn}] [{videoTracks[vIndex].res}] [{videoTracks[vIndex].codecs}] [{videoTracks[vIndex].fps}] [{videoTracks[vIndex].bandwith} kbps] [~{FormatFileSize(videoTracks[vIndex].dur * videoTracks[vIndex].bandwith * 1024 / 8)}]".Replace("[] ", ""), false); } if (audioTracks.Count > 0) { LogColor($"[音频] [{audioTracks[aIndex].codecs}] [{audioTracks[aIndex].bandwith} kbps] [~{FormatFileSize(audioTracks[aIndex].dur * audioTracks[aIndex].bandwith * 1024 / 8)}]", false); } if (videoTracks.Count > 0) { if (multiThread && !videoTracks[vIndex].baseUrl.Contains("-cmcc-")) { Log($"开始多线程下载P{p.index}视频..."); await MultiThreadDownloadFileAsync(videoTracks[vIndex].baseUrl, videoPath, useAria2c); Log("合并视频分片..."); CombineMultipleFilesIntoSingleFile(GetFiles(Path.GetDirectoryName(videoPath), ".vclip"), videoPath); Log("清理分片..."); foreach (var file in new DirectoryInfo(Path.GetDirectoryName(videoPath)).EnumerateFiles("*.?clip")) { file.Delete(); } } else { if (multiThread && videoTracks[vIndex].baseUrl.Contains("-cmcc-")) { LogError("检测到cmcc域名cdn, 已经禁用多线程"); } Log($"开始下载P{p.index}视频..."); await DownloadFile(videoTracks[vIndex].baseUrl, videoPath, useAria2c); } } if (audioTracks.Count > 0) { if (multiThread && !audioTracks[aIndex].baseUrl.Contains("-cmcc-")) { Log($"开始多线程下载P{p.index}音频..."); await MultiThreadDownloadFileAsync(audioTracks[aIndex].baseUrl, audioPath, useAria2c); Log("合并音频分片..."); CombineMultipleFilesIntoSingleFile(GetFiles(Path.GetDirectoryName(audioPath), ".aclip"), audioPath); Log("清理分片..."); foreach (var file in new DirectoryInfo(Path.GetDirectoryName(videoPath)).EnumerateFiles("*.?clip")) { file.Delete(); } } else { if (multiThread && audioTracks[aIndex].baseUrl.Contains("-cmcc-")) { LogError("检测到cmcc域名cdn, 已经禁用多线程"); } Log($"开始下载P{p.index}音频..."); await DownloadFile(audioTracks[aIndex].baseUrl, audioPath, useAria2c); } } Log($"下载P{p.index}完毕"); if (videoTracks.Count == 0) { videoPath = ""; } if (audioTracks.Count == 0) { audioPath = ""; } if (skipMux) { continue; } Log("开始合并音视频" + (subtitleInfo.Count > 0 ? "和字幕" : "") + "..."); int code = MuxAV(videoPath, audioPath, outPath, desc, title, vInfo.PagesInfo.Count > 1 ? ($"P{indexStr}.{p.title}") : "", File.Exists($"{p.aid}/{p.aid}.jpg") ? $"{p.aid}/{p.aid}.jpg" : "", lang, subtitleInfo, audioOnly, videoOnly); if (code != 0 || !File.Exists(outPath) || new FileInfo(outPath).Length == 0) { LogError("合并失败"); continue; } Log("清理临时文件..."); if (videoTracks.Count > 0) { File.Delete(videoPath); } if (audioTracks.Count > 0) { File.Delete(audioPath); } foreach (var s in subtitleInfo) { File.Delete(s.path); } if (pagesInfo.Count == 1 || p.index == pagesInfo.Last().index || p.aid != pagesInfo.Last().aid) { File.Delete($"{p.aid}/{p.aid}.jpg"); } if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) { Directory.Delete(p.aid, true); } } else if (clips.Count > 0 && dfns.Count > 0) //flv { bool flag = false; reParse: //降序 videoTracks.Sort(Compare); if (interactMode && !flag) { int i = 0; dfns.ForEach(key => LogColor($"{i++}.{qualitys[key]}")); Log("请选择最想要的清晰度(输入序号): ", false); Console.ForegroundColor = ConsoleColor.Cyan; var vIndex = Convert.ToInt32(Console.ReadLine()); if (vIndex > dfns.Count || vIndex < 0) { vIndex = 0; } Console.ResetColor(); //重新解析 (webJsonStr, videoTracks, audioTracks, clips, dfns) = ExtractTracks(hevc, aidOri, p.aid, p.cid, p.epid, tvApi, intlApi, dfns[vIndex]); flag = true; videoTracks.Clear(); goto reParse; } Log($"共计{videoTracks.Count}条流(共有{clips.Count}个分段)."); int index = 0; foreach (var v in videoTracks) { LogColor($"{index++}. [{v.dfn}] [{v.res}] [{v.codecs}] [{v.fps}] [~{(v.size / 1024 / v.dur * 8).ToString("00")} kbps] [{FormatFileSize(v.size)}]".Replace("[] ", ""), false); if (infoMode) { clips.ForEach(delegate(string c) { Console.WriteLine(c); }); } } if (infoMode) { continue; } if (File.Exists(outPath) && new FileInfo(outPath).Length != 0) { Log($"{outPath}已存在, 跳过下载..."); 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{indexStr}.{p.cid}.{i.ToString(pad)}.mp4"; if (multiThread && !link.Contains("-cmcc-")) { if (videoTracks.Count != 0) { Log($"开始多线程下载P{p.index}视频, 片段({(i + 1).ToString(pad)}/{clips.Count})..."); await MultiThreadDownloadFileAsync(link, videoPath, useAria2c); Log("合并视频分片..."); CombineMultipleFilesIntoSingleFile(GetFiles(Path.GetDirectoryName(videoPath), ".vclip"), videoPath); } Log("清理分片..."); foreach (var file in new DirectoryInfo(Path.GetDirectoryName(videoPath)).EnumerateFiles("*.?clip")) { file.Delete(); } } else { if (multiThread && link.Contains("-cmcc-")) { LogError("检测到cmcc域名cdn, 已经禁用多线程"); } if (videoTracks.Count != 0) { Log($"开始下载P{p.index}视频, 片段({(i + 1).ToString(pad)}/{clips.Count})..."); await DownloadFile(link, videoPath, useAria2c); } } } Log($"下载P{p.index}完毕"); Log("开始合并分段..."); var files = GetFiles(Path.GetDirectoryName(videoPath), ".mp4"); videoPath = $"{p.aid}/{p.aid}.P{indexStr}.{p.cid}.mp4"; MergeFLV(files, videoPath); if (skipMux) { continue; } Log("开始混流视频" + (subtitleInfo.Count > 0 ? "和字幕" : "") + "..."); int code = MuxAV(videoPath, "", outPath, desc, title, vInfo.PagesInfo.Count > 1 ? ($"P{indexStr}.{p.title}") : "", File.Exists($"{p.aid}/{p.aid}.jpg") ? $"{p.aid}/{p.aid}.jpg" : "", lang, subtitleInfo, audioOnly, videoOnly); if (code != 0 || !File.Exists(outPath) || new FileInfo(outPath).Length == 0) { LogError("合并失败"); continue; } Log("清理临时文件..."); if (videoTracks.Count != 0) { File.Delete(videoPath); } foreach (var s in subtitleInfo) { File.Delete(s.path); } if (pagesInfo.Count == 1 || p.index == pagesInfo.Last().index || p.aid != pagesInfo.Last().aid) { File.Delete($"{p.aid}/{p.aid}.jpg"); } if (Directory.Exists(p.aid) && Directory.GetFiles(p.aid).Length == 0) { Directory.Delete(p.aid, true); } } else { if (webJsonStr.Contains("平台不可观看")) { throw new Exception("当前(WEB)平台不可观看,请尝试使用TV API解析。"); } else if (webJsonStr.Contains("地区不可观看") || webJsonStr.Contains("地区不支持")) { throw new Exception("当前地区不可观看,尝试设置系统代理后解析。"); } else if (webJsonStr.Contains("购买后才能观看")) { throw new Exception("购买后才能观看哦"); } LogError("解析此分P失败(使用--debug查看详细信息)"); LogDebug("{0}", webJsonStr); continue; } } Log("任务完成"); } catch (Exception e) { Console.BackgroundColor = ConsoleColor.Red; Console.ForegroundColor = ConsoleColor.White; Console.Write(e.Message); Console.ResetColor(); Console.WriteLine(); Thread.Sleep(1); } }
private static async Task DoWorkAsync(MyOption myOption) { Console.BackgroundColor = ConsoleColor.DarkBlue; Console.ForegroundColor = ConsoleColor.White; var ver = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version; Console.Write($"BBDown version {ver.Major}.{ver.Minor}.{ver.Build}, Bilibili Downloader.\r\n"); Console.ResetColor(); Console.Write("请注意:任何BUG请前往以下网址反馈:\r\n" + "https://github.com/nilaoda/BBDown/issues\r\n"); Console.WriteLine(); //检测更新 new Thread(async() => { await CheckUpdateAsync(); }).Start(); try { bool interactMode = myOption.Interactive; bool infoMode = myOption.OnlyShowInfo; bool tvApi = myOption.UseTvApi; bool hevc = myOption.OnlyHevc; bool hideStreams = myOption.HideStreams; bool multiThread = myOption.MultiThread; bool audioOnly = myOption.AudioOnly; bool videoOnly = myOption.VideoOnly; bool skipMux = myOption.SkipMux; bool showAll = myOption.ShowAll; bool useAria2c = myOption.UseAria2c; DEBUG_LOG = myOption.Debug; string input = myOption.Url; string selectPage = myOption.SelectPage.ToUpper(); string aidOri = ""; //原始aid COOKIE = myOption.Cookie; TOKEN = myOption.AccessToken.Replace("access_token=", ""); //audioOnly和videoOnly同时开启则全部忽视 if (audioOnly && videoOnly) { myOption.AudioOnly = false; myOption.VideoOnly = false; } List <string> selectedPages = null; if (!string.IsNullOrEmpty(GetQueryString("p", input))) { selectedPages = new List <string>(); selectedPages.Add(GetQueryString("p", input)); } LogDebug("运行参数:{0}", myOption); if (string.IsNullOrEmpty(COOKIE) && File.Exists(Path.Combine(AppContext.BaseDirectory, "BBDown.data")) && !tvApi) { Log("加载本地cookie..."); LogDebug("文件路径:{0}", Path.Combine(AppContext.BaseDirectory, "BBDown.data")); COOKIE = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "BBDown.data")); } if (string.IsNullOrEmpty(TOKEN) && File.Exists(Path.Combine(AppContext.BaseDirectory, "BBDownTV.data")) && tvApi) { Log("加载本地token..."); LogDebug("文件路径:{0}", Path.Combine(AppContext.BaseDirectory, "BBDownTV.data")); TOKEN = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "BBDownTV.data")); TOKEN = TOKEN.Replace("access_token=", ""); } Log("获取aid..."); aidOri = await GetAvIdAsync(input); Log("获取aid结束: " + aidOri); //-p的优先级大于URL中的自带p参数,所以先清空selectedPages if (!string.IsNullOrEmpty(selectPage) && selectPage != "ALL") { selectedPages = new List <string>(); try { string tmp = selectPage; tmp = tmp.Trim().Trim(','); if (tmp.Contains("-")) { int start = int.Parse(tmp.Split('-')[0]); int end = int.Parse(tmp.Split('-')[1]); for (int i = start; i <= end; i++) { selectedPages.Add(i.ToString()); } } else { foreach (var s in tmp.Split(',')) { selectedPages.Add(s); } } } catch { LogError("解析分P参数时失败了~"); selectedPages = null; }; } if (selectPage == "ALL") { selectedPages = null; } if (string.IsNullOrEmpty(aidOri)) { throw new Exception("输入有误"); } Log("获取视频信息..."); IFetcher fetcher = new BBDownNormalInfoFetcher(); if (aidOri.StartsWith("cheese")) { fetcher = new BBDownCheeseInfoFetcher(); } else if (aidOri.StartsWith("ep")) { fetcher = new BBDownBangumiInfoFetcher(); } else if (aidOri.StartsWith("mid")) { fetcher = new BBDownSpaceVideoFetcher(); } var vInfo = fetcher.Fetch(aidOri); string title = vInfo.Title; string desc = vInfo.Desc; string pic = vInfo.Pic; string pubTime = vInfo.PubTime; LogColor("视频标题: " + title); Log("发布时间: " + pubTime); List <Page> pagesInfo = vInfo.PagesInfo; List <Subtitle> subtitleInfo = new List <Subtitle>(); bool more = false; bool bangumi = vInfo.IsBangumi; bool cheese = vInfo.IsCheese; //打印分P信息 foreach (Page p in pagesInfo) { if (!showAll && more && p.index != pagesInfo.Count) { continue; } if (!showAll && !more && p.index > 5) { Log("......"); more = true; } else { Log($"P{p.index}: [{p.cid}] [{p.title}] [{FormatTime(p.dur)}]"); } } //如果用户没有选择分P,根据epid来确定某一集 if (selectedPages == null && selectPage != "ALL" && !string.IsNullOrEmpty(vInfo.Index)) { selectedPages = new List <string> { vInfo.Index }; Log("程序已自动选择你输入的集数,如果要下载其他集数请自行指定分P(如可使用-p ALL代表全部)"); } Log($"共计 {pagesInfo.Count} 个分P, 已选择:" + (selectedPages == null ? "ALL" : string.Join(",", selectedPages))); //过滤不需要的分P if (selectedPages != null) { pagesInfo = pagesInfo.Where(p => selectedPages.Contains(p.index.ToString())).ToList(); } foreach (Page p in pagesInfo) { Log($"开始解析P{p.index}..."); if (!infoMode) { if (!Directory.Exists(p.aid)) { Directory.CreateDirectory(p.aid); } if (!File.Exists($"{p.aid}/{p.aid}.jpg")) { Log("下载封面..."); LogDebug("下载:{0}", pic); new WebClient().DownloadFile(pic, $"{p.aid}/{p.aid}.jpg"); } LogDebug("获取字幕..."); subtitleInfo = BBDownSubUtil.GetSubtitles(p.aid, p.cid); foreach (Subtitle s in subtitleInfo) { Log($"下载字幕 {s.lan} => {BBDownSubUtil.SubDescDic[s.lan]}..."); LogDebug("下载:{0}", s.url); BBDownSubUtil.SaveSubtitle(s.url, s.path); } } List <Video> videoTracks = new List <Video>(); List <Audio> audioTracks = new List <Audio>(); string indexStr = p.index.ToString("0".PadRight(pagesInfo.OrderByDescending(_p => _p.index).First().index.ToString().Length, '0')); string videoPath = $"{p.aid}/{p.aid}.P{indexStr}.{p.cid}.mp4"; string audioPath = $"{p.aid}/{p.aid}.P{indexStr}.{p.cid}.m4a"; //处理文件夹以.结尾导致的异常情况 if (title.EndsWith(".")) { title += "_fix"; } string outPath = GetValidFileName(title) + (pagesInfo.Count > 1 ? $"/[P{indexStr}]{GetValidFileName(p.title)}" : (vInfo.PagesInfo.Count > 1 ? $"[P{indexStr}]{GetValidFileName(p.title)}" : "")) + ".mp4"; //调用解析 string webJson = GetPlayJson(aidOri, p.aid, p.cid, p.epid, tvApi); //File.WriteAllText($"debug.json", JObject.Parse(webJson).ToString()); IBBDownParse webResp; if (tvApi) { webResp = JsonConvert.DeserializeObject <TVResponse>(webJson); } else { webResp = JsonConvert.DeserializeObject <PlayInfoResp>(webJson); } int pDur; //此处代码简直灾难,后续优化吧 if (webJson.Contains("\"dash\":{")) //dash { List <Video> videoTrackstmp; List <Audio> audioTrackstmp; (pDur, videoTrackstmp, audioTrackstmp) = webResp.GetVideoInfos(p, myOption); #region 此处处理免二压视频,需要单独再请求一次 webJson = GetPlayJson(aidOri, p.aid, p.cid, p.epid, tvApi, "125"); //File.WriteAllText($"debug.json", JObject.Parse(webJson).ToString()); if (tvApi) { webResp = JsonConvert.DeserializeObject <TVResponse>(webJson); } else { webResp = JsonConvert.DeserializeObject <PlayInfoResp>(webJson); } int tmpdata; (tmpdata, videoTracks, audioTracks) = webResp.GetVideoInfos(p, myOption); //合并两次解析video结果 foreach (var item in videoTracks) { if (!videoTracks.Contains(item)) { videoTracks.Add(item); } } #endregion if (videoTracks.Count == 0) { LogError("没有找到符合要求的视频流"); continue; } if (audioTracks.Count == 0) { LogError("没有找到符合要求的音频流"); continue; } //选择音视频 (Video dvideo, Audio daudio, bool isSelected) = VideoSelector(videoTracks, audioTracks, myOption, outPath, pDur); if (!isSelected) { continue; } #region 载 await DownLoadData(vInfo, subtitleInfo, p, videoTracks, audioTracks, dvideo, daudio, myOption, indexStr); #endregion } else if (webJson.Contains("\"durl\":[")) //flv { Video v1; (videoTracks, v1) = webResp.GetVideoInfo(myOption); //选择画质,如果和当前不一样,进行下载 string quality = SingleVideoSelector(v1, myOption); if (v1.id != quality) { videoTracks.Clear(); webJson = GetPlayJson(aidOri, p.aid, p.cid, p.epid, tvApi, quality); if (tvApi) { webResp = JsonConvert.DeserializeObject <TVResponse>(webJson); } else { webResp = JsonConvert.DeserializeObject <PlayInfoResp>(webJson); } (videoTracks, v1) = webResp.GetVideoInfo(myOption); } //下载 await DownloadFlvFile(vInfo, subtitleInfo, p, videoTracks, v1, myOption, indexStr); } else { if (webJson.Contains("平台不可观看")) { throw new Exception("当前(WEB)平台不可观看,请尝试使用TV API解析。"); } else if (webJson.Contains("地区不可观看") || webJson.Contains("地区不支持")) { throw new Exception("当前地区不可观看,请尝试使用代理解析。"); } else if (webJson.Contains("购买后才能观看")) { throw new Exception("购买后才能观看哦"); } LogError("解析此分P失败(使用--debug查看详细信息)"); LogDebug("{0}", webJson); continue; } } Log("任务完成"); } catch (Exception e) { Console.BackgroundColor = ConsoleColor.Red; Console.ForegroundColor = ConsoleColor.White; Console.Write(e.Message); Console.ResetColor(); Console.WriteLine(); Thread.Sleep(1); } }