Exemplo n.º 1
0
        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);
            }
        }
Exemplo n.º 2
0
        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;
                DEBUG_LOG = myOption.Debug;
                string input      = myOption.Url;
                string selectPage = myOption.SelectPage;
                string aidOri     = ""; //原始aid
                COOKIE = myOption.Cookie;
                TOKEN  = myOption.AccessToken != null?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 (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 (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"));
                }
                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 (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();
                }
                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;

                if (!infoMode)
                {
                }

                //打印分P信息
                foreach (Page p in pagesInfo)
                {
                    if (more && p.index != pagesInfo.Count)
                    {
                        continue;
                    }
                    if (!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(可使用参数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> videoInfo = new List <Video>();
                    List <Audio> audioInfo = new List <Audio>();
                    string       videoPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.mp4";
                    string       audioPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.m4a";
                    string       outPath   = title + (pagesInfo.Count > 1 ? $"/[P{p.index}]{p.title}.mp4" : $"[P{p.index}]{p.title}.mp4");
                    //调用解析
                    string webJson = GetPlayJson(aidOri, p.aid, p.cid, p.epid, tvApi);
                    //File.WriteAllText($"debug.json", JObject.Parse(webJson).ToString());

                    JArray audio = null;
                    JArray video = null;

                    //此处代码简直灾难,后续优化吧
                    if (webJson.Contains("\"dash\":{")) //dash
                    {
                        string nodeName = "data";
                        if (webJson.Contains("\"result\":{"))
                        {
                            nodeName = "result";
                        }

                        int pDur = p.dur;
                        //处理未获取到视频时长的情况
                        if (p.dur == 0)
                        {
                            try { pDur = !tvApi?JObject.Parse(webJson)[nodeName]["dash"]["duration"].Value <int>() : JObject.Parse(webJson)["dash"]["duration"].Value <int>(); } catch { }
                        }
                        if (pDur == 0)
                        {
                            try { pDur = !tvApi?JObject.Parse(webJson)[nodeName]["timelength"].Value <int>() / 1000 : JObject.Parse(webJson)["timelength"].Value <int>() / 1000; } catch { }
                        }

                        bool reParse = false;
reParse:
                        if (reParse)
                        {
                            webJson = GetPlayJson(aidOri, p.aid, p.cid, p.epid, tvApi, "125");
                        }
                        try { video = JArray.Parse(!tvApi ? JObject.Parse(webJson)[nodeName]["dash"]["video"].ToString() : JObject.Parse(webJson)["dash"]["video"].ToString()); } catch { }
                        try { audio = JArray.Parse(!tvApi ? JObject.Parse(webJson)[nodeName]["dash"]["audio"].ToString() : JObject.Parse(webJson)["dash"]["audio"].ToString()); } catch { }
                        if (video != null)
                        {
                            foreach (JObject node in video)
                            {
                                Video v = new Video();
                                v.id       = node["id"].ToString();
                                v.dfn      = qualitys[node["id"].ToString()];
                                v.bandwith = Convert.ToInt64(node["bandwidth"].ToString()) / 1000;
                                v.baseUrl  = node["base_url"].ToString();
                                v.codecs   = node["codecid"].ToString() == "12" ? "HEVC" : "AVC";
                                if (!tvApi)
                                {
                                    v.res = node["width"].ToString() + "x" + node["height"].ToString();
                                    v.fps = node["frame_rate"].ToString();
                                }
                                if (hevc && v.codecs == "AVC")
                                {
                                    continue;
                                }
                                if (!videoInfo.Contains(v))
                                {
                                    videoInfo.Add(v);
                                }
                            }
                        }

                        //此处处理免二压视频,需要单独再请求一次
                        if (!reParse)
                        {
                            reParse = true;
                            goto reParse;
                        }

                        if (audio != null)
                        {
                            foreach (JObject node in audio)
                            {
                                Audio a = new Audio();
                                a.id       = node["id"].ToString();
                                a.dfn      = node["id"].ToString();
                                a.bandwith = Convert.ToInt64(node["bandwidth"].ToString()) / 1000;
                                a.baseUrl  = node["base_url"].ToString();
                                a.codecs   = "M4A";
                                audioInfo.Add(a);
                            }
                        }

                        if (video != null && videoInfo.Count == 0)
                        {
                            LogError("没有找到符合要求的视频流");
                            continue;
                        }
                        if (audio != null && audioInfo.Count == 0)
                        {
                            LogError("没有找到符合要求的音频流");
                            continue;
                        }
                        //降序
                        videoInfo.Sort(Compare);
                        audioInfo.Sort(Compare);
                        int vIndex = 0;
                        int aIndex = 0;
                        if (!hideStreams)
                        {
                            //展示所有的音视频流信息
                            Log($"共计{videoInfo.Count}条视频流.");
                            int index = 0;
                            foreach (var v in videoInfo)
                            {
                                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);
                                }
                            }
                            Log($"共计{audioInfo.Count}条音频流.");
                            index = 0;
                            foreach (var a in audioInfo)
                            {
                                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)
                        {
                            Log("请选择一条视频流(输入序号): ", false);
                            Console.ForegroundColor = ConsoleColor.Cyan;
                            vIndex = Convert.ToInt32(Console.ReadLine());
                            if (vIndex > videoInfo.Count || vIndex < 0)
                            {
                                vIndex = 0;
                            }
                            Console.ResetColor();
                            Log("请选择一条音频流(输入序号): ", false);
                            Console.ForegroundColor = ConsoleColor.Cyan;
                            aIndex = Convert.ToInt32(Console.ReadLine());
                            if (aIndex > audioInfo.Count || aIndex < 0)
                            {
                                aIndex = 0;
                            }
                            Console.ResetColor();
                        }
                        if (File.Exists(outPath) && new FileInfo(outPath).Length != 0)
                        {
                            Log($"{outPath}已存在, 跳过下载...");
                            continue;
                        }

                        Log($"已选择的流:");
                        if (video != null)
                        {
                            LogColor($"[视频] [{videoInfo[vIndex].dfn}] [{videoInfo[vIndex].res}] [{videoInfo[vIndex].codecs}] [{videoInfo[vIndex].fps}] [{videoInfo[vIndex].bandwith} kbps] [~{FormatFileSize(pDur * videoInfo[vIndex].bandwith * 1024 / 8)}]".Replace("[] ", ""), false);
                        }
                        if (audio != null)
                        {
                            LogColor($"[音频] [{audioInfo[aIndex].codecs}] [{audioInfo[aIndex].bandwith} kbps] [~{FormatFileSize(pDur * audioInfo[aIndex].bandwith * 1024 / 8)}]", false);
                        }

                        if (multiThread && !videoInfo[vIndex].baseUrl.Contains("-cmcc-"))
                        {
                            if (video != null)
                            {
                                Log($"开始多线程下载P{p.index}视频...");
                                await MultiThreadDownloadFileAsync(videoInfo[vIndex].baseUrl, videoPath);

                                Log("合并视频分片...");
                                CombineMultipleFilesIntoSingleFile(GetFiles(Path.GetDirectoryName(videoPath), ".vclip"), videoPath);
                            }
                            if (audio != null)
                            {
                                Log($"开始多线程下载P{p.index}音频...");
                                await MultiThreadDownloadFileAsync(audioInfo[aIndex].baseUrl, audioPath);

                                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 && videoInfo[vIndex].baseUrl.Contains("-cmcc-"))
                            {
                                LogError("检测到cmcc域名cdn, 已经禁用多线程");
                            }
                            if (video != null)
                            {
                                Log($"开始下载P{p.index}视频...");
                                await DownloadFile(videoInfo[vIndex].baseUrl, videoPath);
                            }
                            if (audio != null)
                            {
                                Log($"开始下载P{p.index}音频...");
                                await DownloadFile(audioInfo[aIndex].baseUrl, audioPath);
                            }
                        }
                        Log($"下载P{p.index}完毕");
                        if (video == null)
                        {
                            videoPath = "";
                        }
                        if (audio == null)
                        {
                            audioPath = "";
                        }
                        Log("开始合并音视频" + (subtitleInfo.Count > 0 ? "和字幕" : "") + "...");
                        int code = MuxAV(videoPath, audioPath, outPath,
                                         desc.Replace("\"", ""),
                                         title.Replace("\"", ""),
                                         pagesInfo.Count > 1 ? ($"P{p.index}.{p.title}") : "",
                                         File.Exists($"{p.aid}/{p.aid}.jpg") ? $"{p.aid}/{p.aid}.jpg" : "",
                                         subtitleInfo);
                        if (code != 0 || !File.Exists(outPath) || new FileInfo(outPath).Length == 0)
                        {
                            LogError("合并失败"); continue;
                        }
                        Log("清理临时文件...");
                        if (video != null)
                        {
                            File.Delete(videoPath);
                        }
                        if (audio != null)
                        {
                            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 (webJson.Contains("\"durl\":["))  //flv
                    {
                        bool flag = false;
                        //默认以最高清晰度解析
                        webJson = GetPlayJson(aidOri, p.aid, p.cid, p.epid, tvApi, "125");
reParse:
                        List <string> clips        = new List <string>();
                        List <string> dfns         = new List <string>();
                        string        quality      = "";
                        string        videoCodecid = "";
                        string        url          = "";
                        string        format       = "";
                        double        size         = 0;
                        double        length       = 0;
                        if (webJson.Contains("\"data\":{"))
                        {
                            format       = JObject.Parse(webJson)["data"]["format"].ToString();
                            quality      = JObject.Parse(webJson)["data"]["quality"].ToString();
                            videoCodecid = JObject.Parse(webJson)["data"]["video_codecid"].ToString();
                            //获取所有分段
                            foreach (JObject node in JArray.Parse(JObject.Parse(webJson)["data"]["durl"].ToString()))
                            {
                                clips.Add(node["url"].ToString());
                                size   += node["size"].Value <double>();
                                length += node["length"].Value <double>();
                            }
                            //获取可用清晰度
                            foreach (JObject node in JArray.Parse(JObject.Parse(webJson)["data"]["qn_extras"].ToString()))
                            {
                                dfns.Add(node["qn"].ToString());
                            }
                        }
                        else
                        {
                            format       = JObject.Parse(webJson)["format"].ToString();
                            quality      = JObject.Parse(webJson)["quality"].ToString();
                            videoCodecid = JObject.Parse(webJson)["video_codecid"].ToString();
                            //获取所有分段
                            foreach (JObject node in JArray.Parse(JObject.Parse(webJson)["durl"].ToString()))
                            {
                                clips.Add(node["url"].ToString());
                                size   += node["size"].Value <double>();
                                length += node["length"].Value <double>();
                            }
                            //获取可用清晰度
                            foreach (JObject node in JArray.Parse(JObject.Parse(webJson)["qn_extras"].ToString()))
                            {
                                dfns.Add(node["qn"].ToString());
                            }
                        }
                        Video v1 = new Video();
                        v1.id      = quality;
                        v1.dfn     = qualitys[quality];
                        v1.baseUrl = url;
                        v1.codecs  = videoCodecid == "12" ? "HEVC" : "AVC";
                        if (hevc && v1.codecs == "AVC")
                        {
                        }
                        else
                        {
                            videoInfo.Add(v1);
                        }

                        //降序
                        videoInfo.Sort(Compare);

                        if (interactMode && !flag)
                        {
                            int i = 0;
                            dfns.ForEach(delegate(string 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();
                            //重新解析
                            webJson = GetPlayJson(aidOri, p.aid, p.cid, p.epid, tvApi, dfns[vIndex]);
                            flag    = true;
                            videoInfo.Clear();
                            goto reParse;
                        }

                        Log($"共计{videoInfo.Count}条流({format}, 共有{clips.Count}个分段).");
                        int index = 0;
                        foreach (var v in videoInfo)
                        {
                            LogColor($"{index++}. [{v.dfn}] [{v.res}] [{v.codecs}] [{v.fps}] [~{(size / 1024 / (length / 1000) * 8).ToString("00")} kbps] [{FormatFileSize(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{p.index}.{p.cid}.{i.ToString(pad)}.mp4";
                            if (multiThread && !link.Contains("-cmcc-"))
                            {
                                if (videoInfo.Count != 0)
                                {
                                    Log($"开始多线程下载P{p.index}视频, 片段({(i + 1).ToString(pad)}/{clips.Count})...");
                                    await MultiThreadDownloadFileAsync(link, videoPath);

                                    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 (videoInfo.Count != 0)
                                {
                                    Log($"开始下载P{p.index}视频, 片段({(i + 1).ToString(pad)}/{clips.Count})...");
                                    await DownloadFile(link, videoPath);
                                }
                            }
                        }
                        Log($"下载P{p.index}完毕");
                        Log("开始合并分段...");
                        var files = GetFiles(Path.GetDirectoryName(videoPath), ".mp4");
                        videoPath = $"{p.aid}/{p.aid}.P{p.index}.{p.cid}.mp4";
                        MergeFLV(files, videoPath);
                        Log("开始混流视频" + (subtitleInfo.Count > 0 ? "和字幕" : "") + "...");
                        int code = MuxAV(videoPath, "", outPath,
                                         desc.Replace("\"", ""),
                                         title.Replace("\"", ""),
                                         pagesInfo.Count > 1 ? ($"P{p.index}.{p.title}") : "",
                                         File.Exists($"{p.aid}/{p.aid}.jpg") ? $"{p.aid}/{p.aid}.jpg" : "",
                                         subtitleInfo);
                        if (code != 0 || !File.Exists(outPath) || new FileInfo(outPath).Length == 0)
                        {
                            LogError("合并失败"); continue;
                        }
                        Log("清理临时文件...");
                        if (videoInfo.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 (webJson.Contains("平台不可观看"))
                        {
                            throw new Exception("当前(WEB)平台不可观看,请尝试使用TV API解析。");
                        }
                        else if (webJson.Contains("地区不可观看") || 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);
            }
        }
Exemplo n.º 3
0
        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);
            }
        }
Exemplo n.º 4
0
        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("BBDown Server Edition");
            Console.WriteLine();
            //检测更新
            new Thread(async() =>
            {
                await CheckUpdateAsync();
            }).Start();
            try
            {
                //Read cookie from cookie.txt
                string cookiePath = Directory.GetCurrentDirectory();
                if (!File.Exists($"cookie.txt"))
                {
                    File.Create($"{cookiePath}/cookie.txt");
                }
                string cookieString = File.ReadAllText($"{cookiePath}/cookie.txt");
                if (cookieString != null)
                {
                    cookieString = "";
                }
                LogDebug(cookieString);
                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 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 = cookieString;
                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" && selectPage != "LATEST")
                {
                    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();
                }

                var    vInfo   = fetcher.Fetch(aidOri);
                string title   = vInfo.Title;
                string desc    = vInfo.Desc;
                string pic     = vInfo.Pic;
                string pubTime = vInfo.PubTime;
                LogColor("視頻標題: " + title);
                LogDebug("發佈時間: " + 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
                    {
                        LogDebug($"P{p.index}: [{p.cid}] [{p.title}] [{FormatTime(p.dur)}]");
                    }
                }
                if (selectPage == "LATEST")
                {
                    selectedPages = new List <string>();
                    selectedPages.Add(pagesInfo.Count.ToString());
                    LogDebug(pagesInfo.Count.ToString());
                }
                //如果用户没有选择分P,根据epid来确定某一集
                if (selectedPages == null && selectPage != "ALL" && !string.IsNullOrEmpty(vInfo.Index) && selectPage != "LATEST")
                {
                    selectedPages = new List <string> {
                        vInfo.Index
                    };
                    Log("程序已自動選擇你輸入的集數,如果要下載其他集數請自行指定分P(可使用參數-p ALL代表全部 -p LATEST代表最新一集)");
                }

                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($"temp/{p.aid}"))
                        {
                            Directory.CreateDirectory($"temp/{p.aid}");
                        }
                        if (!File.Exists($"temp/{p.aid}/{p.aid}.jpg"))
                        {
                            Log("下載封面...");
                            LogDebug("下載:{0}", pic);
                            new WebClient().DownloadFile(pic, $"temp/{p.aid}/{p.aid}.jpg");
                        }
                        string[] files = System.IO.Directory.GetFiles(Directory.GetCurrentDirectory(), $"temp/{p.aid}/{p.aid}.{p.cid}.*.srt");
                        if (files.Length > 0)
                        {
                            Log("字幕已經獲取...");
                            for (int i = 0; i < files.Length; i++)
                            {
                                LogDebug(files[i]);
                            }
                        }
                        else
                        {
                            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);
                            }
                        }
                    }
                    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   = $"temp/{p.aid}/{p.aid}.P{indexStr}.{p.cid}.mp4";
                    string        audioPath   = $"temp/{p.aid}/{p.aid}.P{indexStr}.{p.cid}.m4a";
                    //处理文件夹以.结尾导致的异常情况
                    if (title.EndsWith("."))
                    {
                        title += "_fix";
                    }
                    var converter = new OpenChineseConverter();
                    title = converter.ToTaiwanFromSimplified(title);
                    Log(title);
                    string ep = p.index.ToString("D2");
                    //讀取JSON存放
                    string  jsonpath = Directory.GetCurrentDirectory();
                    string  jsonfile = File.ReadAllText($"{jsonpath}/config.json");
                    dynamic json     = JValue.Parse(jsonfile);
                    string  dirname  = json.dir;
                    title = Regex.Replace(title, @"[<>:""/\\|?*]", "-");


                    //调用解析
                    (webJsonStr, videoTracks, audioTracks, clips, dfns) = ExtractTracks(hevc, aidOri, p.aid, p.cid, p.epid, tvApi, intlApi);
                    //File.WriteAllText($"debug.json", JObject.Parse(webJsonStr).ToString());
                    JObject respJson = JObject.Parse(webJsonStr);
                    string  outPath  = dirname + (pagesInfo.Count > 1 ? $"/{json.prefix}{title}[{ep}][0000P]{json.suffix}" +
                                                  $".mp4" : $"/{json.prefix}{title}[{ep}][0000P]{json.suffix}.mp4");
                    //此处代码简直灾难,后续优化吧
                    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();
                            }
                        }

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

                        outPath = dirname + (pagesInfo.Count > 1 ? $"/{json.prefix}{title}[{ep}][{videoTracks[vIndex].dfn}]{json.suffix}" +
                                             $".mp4" : $"/{json.prefix}{title}[{ep}][{videoTracks[vIndex].dfn}]{json.suffix}.mp4");

                        if (File.Exists(outPath) && new FileInfo(outPath).Length != 0)
                        {
                            Log($"{outPath}已存在, 跳過下載...");
                            continue;
                        }

                        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($"temp/{p.aid}/{p.aid}.jpg") ? $"temp/{p.aid}/{p.aid}.jpg" : "",
                                         lang,
                                         subtitleInfo, audioOnly, videoOnly, p.aid, p.cid);
                        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);
                        }
                    }
                    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 = $"temp/{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 = $"temp/{p.aid}/{p.aid}.P{indexStr}.{p.cid}.mp4";
                        MergeFLV(files, videoPath);
                        Log("開始混流視頻" + (subtitleInfo.Count > 0 ? "和字幕" : "") + "...");
                        int code = MuxAV(videoPath, "", outPath,
                                         desc,
                                         title,
                                         vInfo.PagesInfo.Count > 1 ? ($"P{indexStr}.{p.title}") : "",
                                         File.Exists($"temp/{p.aid}/{p.aid}.jpg") ? $"{p.aid}/{p.aid}.jpg" : "",
                                         lang,
                                         subtitleInfo, audioOnly, videoOnly, p.aid, p.cid);
                        if (code != 0 || !File.Exists(outPath) || new FileInfo(outPath).Length == 0)
                        {
                            LogError("合併失敗"); continue;
                        }
                        Log("清理臨時文件...");
                        if (videoTracks.Count != 0)
                        {
                            File.Delete(videoPath);
                        }
                    }
                    else
                    {
                        if (webJsonStr.Contains("平台不可观看"))
                        {
                            throw new Exception("當前(WEB)平台不可觀看,請嘗試使用TV API解析。");
                        }
                        else if (webJsonStr.Contains("购买后才能观看"))
                        {
                            throw new Exception("購買後才能觀看");
                        }
                        else if (webJsonStr.Contains("大会员专享限制"))
                        {
                            throw new Exception("大會員專享限制");
                        }
                        else if (webJsonStr.Contains("地区不可观看") || 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);
            }
        }