From bcf2d18ab2118a0277f97da01336952c389159da Mon Sep 17 00:00:00 2001 From: My-Responsitories Date: Fri, 18 Aug 2023 19:37:42 +0800 Subject: [PATCH 1/2] add metadata creation_time to video --- BBDown.Core/Entity/Entity.cs | 14 ++- BBDown.Core/Entity/VInfo.cs | 2 +- BBDown.Core/Fetcher/BangumiInfoFetcher.cs | 11 +- BBDown.Core/Fetcher/CheeseInfoFetcher.cs | 6 +- BBDown.Core/Fetcher/FavListFetcher.cs | 20 +-- BBDown.Core/Fetcher/IntlBangumiInfoFetcher.cs | 11 +- BBDown.Core/Fetcher/MediaListFetcher.cs | 8 +- BBDown.Core/Fetcher/NormalInfoFetcher.cs | 4 +- BBDown.Core/Fetcher/SeriesListFetcher.cs | 4 +- BBDown.Core/Util/SubUtil.cs | 14 +-- BBDown/BBDownMuxer.cs | 70 +++++++---- BBDown/BBDownUtil.cs | 9 +- BBDown/CommandLineInvoker.cs | 6 +- BBDown/Program.cs | 116 +++++++++--------- 14 files changed, 158 insertions(+), 137 deletions(-) diff --git a/BBDown.Core/Entity/Entity.cs b/BBDown.Core/Entity/Entity.cs index c036b2a5b..a020f2cc1 100644 --- a/BBDown.Core/Entity/Entity.cs +++ b/BBDown.Core/Entity/Entity.cs @@ -14,6 +14,7 @@ public class Page public required string title; public required int dur; public required string res; + public required long pubTime; public string? cover; public string? desc; public string? ownerName; @@ -25,7 +26,7 @@ public string bvid public List points = new(); [SetsRequiredMembers] - public Page(int index, string aid, string cid, string epid, string title, int dur, string res) + public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime) { this.aid = aid; this.index = index; @@ -34,10 +35,11 @@ public Page(int index, string aid, string cid, string epid, string title, int du this.title = title; this.dur = dur; this.res = res; + this.pubTime = pubTime; } [SetsRequiredMembers] - public Page(int index, string aid, string cid, string epid, string title, int dur, string res, string cover) + public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime, string cover) { this.aid = aid; this.index = index; @@ -46,11 +48,12 @@ public Page(int index, string aid, string cid, string epid, string title, int du this.title = title; this.dur = dur; this.res = res; + this.pubTime = pubTime; this.cover = cover; } [SetsRequiredMembers] - public Page(int index, string aid, string cid, string epid, string title, int dur, string res, string cover, string desc) + public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime, string cover, string desc) { this.aid = aid; this.index = index; @@ -59,12 +62,13 @@ public Page(int index, string aid, string cid, string epid, string title, int du this.title = title; this.dur = dur; this.res = res; + this.pubTime = pubTime; this.cover = cover; this.desc = desc; } [SetsRequiredMembers] - public Page(int index, string aid, string cid, string epid, string title, int dur, string res, string cover, string desc, string ownerName, string ownerMid) + public Page(int index, string aid, string cid, string epid, string title, int dur, string res, long pubTime, string cover, string desc, string ownerName, string ownerMid) { this.aid = aid; this.index = index; @@ -73,6 +77,7 @@ public Page(int index, string aid, string cid, string epid, string title, int du this.title = title; this.dur = dur; this.res = res; + this.pubTime = pubTime; this.cover = cover; this.desc = desc; this.ownerName = ownerName; @@ -89,6 +94,7 @@ public Page(int index, Page page) this.title = page.title; this.dur = page.dur; this.res = page.res; + this.pubTime = page.pubTime; this.cover = page.cover; this.ownerName = page.ownerName; this.ownerMid = page.ownerMid; diff --git a/BBDown.Core/Entity/VInfo.cs b/BBDown.Core/Entity/VInfo.cs index 63602b779..179a3cc33 100644 --- a/BBDown.Core/Entity/VInfo.cs +++ b/BBDown.Core/Entity/VInfo.cs @@ -22,7 +22,7 @@ public class VInfo /// /// 视频发布时间 /// - public required string PubTime { get; set; } + public required long PubTime { get; set; } public bool IsBangumi { get; set; } public bool IsCheese { get; set; } diff --git a/BBDown.Core/Fetcher/BangumiInfoFetcher.cs b/BBDown.Core/Fetcher/BangumiInfoFetcher.cs index bb1f501ea..0c4b8658e 100644 --- a/BBDown.Core/Fetcher/BangumiInfoFetcher.cs +++ b/BBDown.Core/Fetcher/BangumiInfoFetcher.cs @@ -18,13 +18,13 @@ public async Task FetchAsync(string id) string cover = result.GetProperty("cover").ToString(); string title = result.GetProperty("title").ToString(); string desc = result.GetProperty("evaluate").ToString(); - string pubTime = result.GetProperty("publish").GetProperty("pub_time").ToString(); - var pages = result.GetProperty("episodes").EnumerateArray().ToList(); + long pubTime = DateTimeOffset.ParseExact(result.GetProperty("publish").GetProperty("pub_time").ToString(), "yyyy-MM-dd HH:mm:ss", null).ToUnixTimeSeconds(); + var pages = result.GetProperty("episodes").EnumerateArray(); List pagesInfo = new(); int i = 1; //episodes为空; 或者未包含对应epid,番外/花絮什么的 - if (pages.Count == 0 || !result.GetProperty("episodes").ToString().Contains($"/ep{id}")) + if (!(pages.Any() && result.GetProperty("episodes").ToString().Contains($"/ep{id}"))) { if (result.TryGetProperty("section", out JsonElement sections)) { @@ -33,7 +33,7 @@ public async Task FetchAsync(string id) if (section.ToString().Contains($"/ep{id}")) { title += "[" + section.GetProperty("title").ToString() + "]"; - pages = section.GetProperty("episodes").EnumerateArray().ToList(); + pages = section.GetProperty("episodes").EnumerateArray(); break; } } @@ -57,7 +57,8 @@ public async Task FetchAsync(string id) page.GetProperty("cid").ToString(), page.GetProperty("id").ToString(), _title, - 0, res); + 0, res, + page.GetProperty("pub_time").GetInt64()); if (p.epid == id) index = p.index.ToString(); pagesInfo.Add(p); } diff --git a/BBDown.Core/Fetcher/CheeseInfoFetcher.cs b/BBDown.Core/Fetcher/CheeseInfoFetcher.cs index f3114f272..099fa5ece 100644 --- a/BBDown.Core/Fetcher/CheeseInfoFetcher.cs +++ b/BBDown.Core/Fetcher/CheeseInfoFetcher.cs @@ -20,7 +20,7 @@ public async Task FetchAsync(string id) string desc = data.GetProperty("subtitle").ToString(); string ownerName = data.GetProperty("up_info").GetProperty("uname").ToString(); string ownerMid = data.GetProperty("up_info").GetProperty("mid").ToString(); - var pages = data.GetProperty("episodes").EnumerateArray().ToList(); + var pages = data.GetProperty("episodes").EnumerateArray(); List pagesInfo = new(); foreach (var page in pages) { @@ -31,6 +31,7 @@ public async Task FetchAsync(string id) page.GetProperty("title").ToString().Trim(), page.GetProperty("duration").GetInt32(), "", + page.GetProperty("release_date").GetInt64(), "", "", ownerName, @@ -38,8 +39,7 @@ public async Task FetchAsync(string id) if (p.epid == id) index = p.index.ToString(); pagesInfo.Add(p); } - string pubTime = pagesInfo.Count > 0 ? pages[0].GetProperty("release_date").ToString() : ""; - pubTime = pubTime != "" ? new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(Convert.ToDouble(pubTime)).ToLocalTime().ToString() : ""; + long pubTime = pagesInfo.Any() ? pagesInfo[0].pubTime : 0; var info = new VInfo { diff --git a/BBDown.Core/Fetcher/FavListFetcher.cs b/BBDown.Core/Fetcher/FavListFetcher.cs index a120ed6d5..0c7b886d8 100644 --- a/BBDown.Core/Fetcher/FavListFetcher.cs +++ b/BBDown.Core/Fetcher/FavListFetcher.cs @@ -8,8 +8,8 @@ namespace BBDown.Core.Fetcher { /// /// 收藏夹解析 - /// https://space.bilibili.com/4743331/favlist?spm_id_from=333.1007.0.0 - /// + /// https://space.bilibili.com/3/favlist + /// /// public class FavListFetcher : IFetcher { @@ -21,7 +21,7 @@ public async Task FetchAsync(string id) //查找默认收藏夹 if (favId == "") { - var favListApi = $"https://api.bilibili.com/x/v3/fav/folder/created/list-all?up_mid={mid}&jsonp=jsonp"; + var favListApi = $"https://api.bilibili.com/x/v3/fav/folder/created/list-all?up_mid={mid}"; favId = JsonDocument.Parse(await GetWebSourceAsync(favListApi)).RootElement.GetProperty("data").GetProperty("list").EnumerateArray().First().GetProperty("id").ToString(); } @@ -29,7 +29,7 @@ public async Task FetchAsync(string id) int index = 1; List pagesInfo = new(); - var api = $"https://api.bilibili.com/x/v3/fav/resource/list?media_id={favId}&pn=1&ps={pageSize}&keyword=&order=mtime&type=0&tid=0&platform=web&jsonp=jsonp"; + var api = $"https://api.bilibili.com/x/v3/fav/resource/list?media_id={favId}&pn=1&ps={pageSize}&order=mtime&type=2&tid=0&platform=web"; var json = await GetWebSourceAsync(api); using var infoJson = JsonDocument.Parse(json); var data = infoJson.RootElement.GetProperty("data"); @@ -37,14 +37,13 @@ public async Task FetchAsync(string id) int totalPage = (int)Math.Ceiling((double)totalCount / pageSize); var title = data.GetProperty("info").GetProperty("title").GetString()!; var intro = data.GetProperty("info").GetProperty("intro").GetString()!; - string pubTime = data.GetProperty("info").GetProperty("ctime").ToString(); - pubTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(Convert.ToDouble(pubTime)).ToLocalTime().ToString(); + long pubTime = data.GetProperty("info").GetProperty("ctime").GetInt64(); var userName = data.GetProperty("info").GetProperty("upper").GetProperty("name").ToString(); var medias = data.GetProperty("medias").EnumerateArray().ToList(); - + for (int page = 2; page <= totalPage; page++) { - api = $"https://api.bilibili.com/x/v3/fav/resource/list?media_id={favId}&pn={page}&ps={pageSize}&keyword=&order=mtime&type=0&tid=0&platform=web&jsonp=jsonp"; + api = $"https://api.bilibili.com/x/v3/fav/resource/list?media_id={favId}&pn={page}&ps={pageSize}&order=mtime&type=2&tid=0&platform=web"; json = await GetWebSourceAsync(api); var jsonDoc = JsonDocument.Parse(json); data = jsonDoc.RootElement.GetProperty("data"); @@ -53,8 +52,8 @@ public async Task FetchAsync(string id) foreach (var m in medias) { - //只处理视频类型 - if (m.GetProperty("type").GetInt32() != 2) continue; + //只处理视频类型(可以直接在query param上指定type=2) + // if (m.GetProperty("type").GetInt32() != 2) continue; //只处理未失效视频 if (m.GetProperty("attr").GetInt32() != 0) continue; @@ -82,6 +81,7 @@ public async Task FetchAsync(string id) m.GetProperty("title").ToString(), m.GetProperty("duration").GetInt32(), "", + m.GetProperty("pubtime").GetInt64(), m.GetProperty("cover").ToString(), m.GetProperty("intro").ToString(), m.GetProperty("upper").GetProperty("name").ToString(), diff --git a/BBDown.Core/Fetcher/IntlBangumiInfoFetcher.cs b/BBDown.Core/Fetcher/IntlBangumiInfoFetcher.cs index f2059e1d7..f2b704d57 100644 --- a/BBDown.Core/Fetcher/IntlBangumiInfoFetcher.cs +++ b/BBDown.Core/Fetcher/IntlBangumiInfoFetcher.cs @@ -12,7 +12,7 @@ public async Task FetchAsync(string id) { id = id[3..]; string index = ""; - //string api = $"https://api.global.bilibili.com/intl/gateway/ogv/m/view?ep_id={id}&s_locale=ja_JP"; + //string api = $"https://api.global.bilibili.com/intl/gateway/ogv/m/view?ep_id={id}"; string api = "https://" + (Config.HOST == "api.bilibili.com" ? "api.bilibili.tv" : Config.HOST) + $"/intl/gateway/v2/ogv/view/app/season?ep_id={id}&platform=android&s_locale=zh_SG&mobi_app=bstar_a" + (Config.TOKEN != "" ? $"&access_key={Config.TOKEN}" : ""); string json = (await GetWebSourceAsync(api)).Replace("\\/", "/"); @@ -39,11 +39,11 @@ public async Task FetchAsync(string id) } } - string pubTime = result.GetProperty("publish").GetProperty("pub_time").ToString(); + long pubTime = DateTimeOffset.ParseExact(result.GetProperty("publish").GetProperty("pub_time").ToString(), "yyyy-MM-dd HH:mm:ss", null).ToUnixTimeSeconds(); var pages = new List(); - if (result.TryGetProperty("episodes", out _)) + if (result.TryGetProperty("episodes", out JsonElement episodes)) { - pages = result.GetProperty("episodes").EnumerateArray().ToList(); + pages = episodes.EnumerateArray().ToList(); } List pagesInfo = new(); int i = 1; @@ -98,7 +98,8 @@ public async Task FetchAsync(string id) page.GetProperty("cid").ToString(), page.GetProperty("id").ToString(), _title, - 0, res); + 0, res, + page.GetProperty("pub_time").GetInt64()); if (p.epid == id) index = p.index.ToString(); pagesInfo.Add(p); } diff --git a/BBDown.Core/Fetcher/MediaListFetcher.cs b/BBDown.Core/Fetcher/MediaListFetcher.cs index 6da3714f0..023a87e51 100644 --- a/BBDown.Core/Fetcher/MediaListFetcher.cs +++ b/BBDown.Core/Fetcher/MediaListFetcher.cs @@ -8,7 +8,7 @@ namespace BBDown.Core.Fetcher /// /// 合集解析 /// https://space.bilibili.com/23630128/channel/collectiondetail?sid=2045 - /// https://www.bilibili.com/medialist/play/23630128?from=space&business=space_collection&business_id=2045&desc=0 + /// https://www.bilibili.com/medialist/play/23630128?business=space_collection&business_id=2045 (无法从该链接打开合集) /// public class MediaListFetcher : IFetcher { @@ -21,8 +21,7 @@ public async Task FetchAsync(string id) var data = infoJson.RootElement.GetProperty("data"); var listTitle = data.GetProperty("title").GetString()!; var intro = data.GetProperty("intro").GetString()!; - string pubTime = data.GetProperty("ctime").ToString()!; - pubTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(Convert.ToDouble(pubTime)).ToLocalTime().ToString(); + long pubTime = data.GetProperty("ctime").GetInt64()!; List pagesInfo = new(); bool hasMore = true; @@ -30,7 +29,7 @@ public async Task FetchAsync(string id) int index = 1; while (hasMore) { - var listApi = $"https://api.bilibili.com/x/v2/medialist/resource/list?type=8&oid={oid}&otype=2&biz_id={id}&bvid=&with_current=true&mobi_app=web&ps=20&direction=false&sort_field=1&tid=0&desc=false"; + var listApi = $"https://api.bilibili.com/x/v2/medialist/resource/list?type=8&oid={oid}&otype=2&biz_id={id}&with_current=true&mobi_app=web&ps=20&direction=false&sort_field=1&tid=0&desc=false"; json = await GetWebSourceAsync(listApi); using var listJson = JsonDocument.Parse(json); data = listJson.RootElement.GetProperty("data"); @@ -50,6 +49,7 @@ public async Task FetchAsync(string id) pageCount == 1 ? m.GetProperty("title").ToString() : $"{m.GetProperty("title")}_P{page.GetProperty("page")}_{page.GetProperty("title")}", //单P使用外层标题 多P则拼接内层子标题 page.GetProperty("duration").GetInt32(), page.GetProperty("dimension").GetProperty("width").ToString() + "x" + page.GetProperty("dimension").GetProperty("height").ToString(), + m.GetProperty("pubtime").GetInt64(), m.GetProperty("cover").ToString(), desc, ownerName, diff --git a/BBDown.Core/Fetcher/NormalInfoFetcher.cs b/BBDown.Core/Fetcher/NormalInfoFetcher.cs index d7edea1c2..915ab6caa 100644 --- a/BBDown.Core/Fetcher/NormalInfoFetcher.cs +++ b/BBDown.Core/Fetcher/NormalInfoFetcher.cs @@ -20,8 +20,7 @@ public async Task FetchAsync(string id) var owner = data.GetProperty("owner"); string ownerMid = owner.GetProperty("mid").ToString(); string ownerName = owner.GetProperty("name").ToString(); - string pubTime = data.GetProperty("pubdate").ToString(); - pubTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(Convert.ToDouble(pubTime)).ToLocalTime().ToString(); + long pubTime = data.GetProperty("pubdate").GetInt64(); bool bangumi = false; var pages = data.GetProperty("pages").EnumerateArray().ToList(); @@ -35,6 +34,7 @@ public async Task FetchAsync(string id) page.GetProperty("part").ToString().Trim(), page.GetProperty("duration").GetInt32(), page.GetProperty("dimension").GetProperty("width").ToString() + "x" + page.GetProperty("dimension").GetProperty("height").ToString(), + pubTime, //分p视频没有发布时间 "", "", ownerName, diff --git a/BBDown.Core/Fetcher/SeriesListFetcher.cs b/BBDown.Core/Fetcher/SeriesListFetcher.cs index b1d61bf24..a334aa5d6 100644 --- a/BBDown.Core/Fetcher/SeriesListFetcher.cs +++ b/BBDown.Core/Fetcher/SeriesListFetcher.cs @@ -22,8 +22,7 @@ public async Task FetchAsync(string id) var data = infoJson.RootElement.GetProperty("data"); var listTitle = data.GetProperty("title").GetString()!; var intro = data.GetProperty("intro").GetString()!; - string pubTime = data.GetProperty("ctime").ToString(); - pubTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(Convert.ToDouble(pubTime)).ToLocalTime().ToString(); + long pubTime = data.GetProperty("ctime").GetInt64(); List pagesInfo = new(); bool hasMore = true; @@ -51,6 +50,7 @@ public async Task FetchAsync(string id) pageCount == 1 ? m.GetProperty("title").ToString() : $"{m.GetProperty("title")}_P{page.GetProperty("page")}_{page.GetProperty("title")}", //单P使用外层标题 多P则拼接内层子标题 page.GetProperty("duration").GetInt32(), page.GetProperty("dimension").GetProperty("width").ToString() + "x" + page.GetProperty("dimension").GetProperty("height").ToString(), + m.GetProperty("pubtime").GetInt64(), m.GetProperty("cover").ToString(), desc, ownerName, diff --git a/BBDown.Core/Util/SubUtil.cs b/BBDown.Core/Util/SubUtil.cs index 95633023e..7cb52a5e1 100644 --- a/BBDown.Core/Util/SubUtil.cs +++ b/BBDown.Core/Util/SubUtil.cs @@ -458,11 +458,11 @@ private static string ConvertSubFromJson(string jsonString) lines.AppendLine((i + 1).ToString()); if (line.TryGetProperty("from", out JsonElement from)) { - lines.AppendLine($"{FormatTime(from.ToString())} --> {FormatTime(line.GetProperty("to").ToString())}"); + lines.AppendLine($"{FormatTime(from.GetDouble())} --> {FormatTime(line.GetProperty("to").GetDouble())}"); } else { - lines.AppendLine($"{FormatTime("0")} --> {FormatTime(line.GetProperty("to").ToString())}"); + lines.AppendLine($"{FormatTime(0.0)} --> {FormatTime(line.GetProperty("to").GetDouble())}"); } //有的没有内容 if (line.TryGetProperty("content", out JsonElement content)) @@ -472,15 +472,9 @@ private static string ConvertSubFromJson(string jsonString) return lines.ToString(); } - private static string FormatTime(string sec) //64.13 + private static string FormatTime(double sec) //64.13 { - string[] v = { sec, "" }; - if (sec.Contains('.')) - v = sec.Split('.'); - v[1] = v[1].PadRight(3, '0')[..3]; - TimeSpan ts = new(0, 0, Convert.ToInt32(v[0])); - string str = ts.Hours.ToString("00") + ":" + ts.Minutes.ToString("00") + ":" + ts.Seconds.ToString("00") + "," + v[1]; - return str; + return TimeSpan.FromSeconds(sec).ToString(@"hh\:mm\:ss\,fff"); } [GeneratedRegex("-[a-z]")] diff --git a/BBDown/BBDownMuxer.cs b/BBDown/BBDownMuxer.cs index 54373c7d5..919595f9f 100644 --- a/BBDown/BBDownMuxer.cs +++ b/BBDown/BBDownMuxer.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Text; -using System.Text.RegularExpressions; using static BBDown.Core.Entity.Entity; using static BBDown.BBDownUtil; using static BBDown.Core.Util.SubUtil; @@ -90,13 +90,13 @@ public static int MuxByMp4box(string videoPath, string audioPath, string outPath //----分析完毕 var arguments = (Config.DEBUG_LOG ? " -verbose " : "") + inputArg.ToString() + (metaArg.ToString() == "" ? "" : " -itags tool=" + metaArg.ToString()) + $" -new -- \"{outPath}\""; - LogDebug("mp4box命令:{0}", arguments); + LogDebug("mp4box命令: {0}", arguments); return RunExe(MP4BOX, arguments, MP4BOX != "mp4box"); } - public static int MuxAV(bool useMp4box, string videoPath, string audioPath, string outPath, string desc = "", string title = "", string author = "", string episodeId = "", string pic = "", string lang = "", List? subs = null, bool audioOnly = false, bool videoOnly = false, List? points = null) + public static int MuxAV(bool useMp4box, string videoPath, string audioPath, string outPath, string desc = "", string title = "", string author = "", string episodeId = "", string pic = "", string lang = "", List? subs = null, bool audioOnly = false, bool videoOnly = false, List? points = null, long pubTime = 0) { - if (audioOnly && audioPath != "") + if (audioOnly && audioPath != "") videoPath = ""; if (videoOnly) audioPath = ""; @@ -114,43 +114,47 @@ public static int MuxAV(bool useMp4box, string videoPath, string audioPath, stri //----分析并生成-i参数 StringBuilder inputArg = new(); StringBuilder metaArg = new(); - if (!string.IsNullOrEmpty(videoPath)) - inputArg.Append($" -i \"{videoPath}\" "); - if (!string.IsNullOrEmpty(audioPath)) - inputArg.Append($" -i \"{audioPath}\" "); - if (!string.IsNullOrEmpty(pic)) - inputArg.Append($" -i \"{pic}\" "); + byte inputCount = 0; + foreach (string path in new string[] { videoPath, audioPath, pic }) + { + if (!string.IsNullOrEmpty(path)) + { + inputCount++; + inputArg.Append($"-i \"{path}\" "); + } + } if (subs != null) { for (int i = 0; i < subs.Count; i++) { if(File.Exists(subs[i].path) && File.ReadAllText(subs[i].path!) != "") { - inputArg.Append($" -i \"{subs[i].path}\" "); - metaArg.Append($" -metadata:s:s:{i} title=\"{GetSubtitleCode(subs[i].lan).Item2}\" -metadata:s:s:{i} language={GetSubtitleCode(subs[i].lan).Item1} "); + inputCount++; + inputArg.Append($"-i \"{subs[i].path}\" "); + metaArg.Append($"-metadata:s:s:{i} title=\"{GetSubtitleCode(subs[i].lan).Item2}\" -metadata:s:s:{i} language={GetSubtitleCode(subs[i].lan).Item1} "); } } } if (!string.IsNullOrEmpty(pic)) - metaArg.Append($" -disposition:v:{(audioOnly ? "0" : "1")} attached_pic "); - var inputCount = InputRegex().Matches(inputArg.ToString()).Count; + metaArg.Append($"-disposition:v:{(audioOnly ? "0" : "1")} attached_pic "); + // var inputCount = InputRegex().Matches(inputArg.ToString()).Count; if (points != null && points.Count > 0) { var meta = GetFFmpegMetaString(points); var metaFile = Path.Combine(Path.GetDirectoryName(string.IsNullOrEmpty(videoPath) ? audioPath : videoPath)!, "chapters"); File.WriteAllText(metaFile, meta); - inputArg.Append($" -i \"{metaFile}\" -map_chapters {inputCount} "); + inputArg.Append($"-i \"{metaFile}\" -map_chapters {inputCount} "); } - for (int i = 0; i < inputCount; i++) + for (byte i = 0; i < inputCount; i++) { - inputArg.Append($" -map {i} "); + inputArg.Append($"-map {i} "); } //----分析完毕 - var arguments = $"-loglevel {(Config.DEBUG_LOG ? "verbose" : "warning")} -y " + + /*var arguments = $"-loglevel {(Config.DEBUG_LOG ? "verbose" : "warning")} -y " + inputArg.ToString() + metaArg.ToString() + $" -metadata title=\"" + (episodeId == "" ? title : episodeId) + "\" " + (lang == "" ? "" : $"-metadata:s:a:0 language={lang} ") + $"-metadata description=\"{desc}\" " + @@ -160,7 +164,26 @@ public static int MuxAV(bool useMp4box, string videoPath, string audioPath, stri (subs != null ? " -c:s mov_text " : "") + "-movflags faststart -strict unofficial -strict -2 -f mp4 " + $"-- \"{outPath}\""; - LogDebug("ffmpeg命令:{0}", arguments); + LogDebug("ffmpeg命令:{0}", arguments);*/ + + StringBuilder argsBuilder = new StringBuilder(); + argsBuilder.Append($"-loglevel {(Config.DEBUG_LOG ? "verbose" : "warning")} -y "); + argsBuilder.Append(inputArg); + argsBuilder.Append(metaArg); + argsBuilder.Append($"-metadata title=\"{(episodeId == "" ? title : episodeId)}\" "); + if (lang != "") argsBuilder.Append($"-metadata:s:a:0 language={lang} "); + if (!string.IsNullOrWhiteSpace(desc)) argsBuilder.Append($"-metadata description=\"{desc}\" "); + if (!string.IsNullOrEmpty(author)) argsBuilder.Append($"-metadata artist=\"{author}\" "); + if (episodeId != "") argsBuilder.Append($"-metadata album=\"{title}\" "); + if (pubTime != 0) argsBuilder.Append($"-metadata creation_time=\"{(DateTimeOffset.FromUnixTimeSeconds(pubTime).ToString("yyyy-MM-ddTHH:mm:ss.ffffffZ"))}\" "); + argsBuilder.Append("-c copy "); + if (audioOnly && audioPath == "") argsBuilder.Append("-vn "); + if (subs != null) argsBuilder.Append("-c:s mov_text "); + argsBuilder.Append($"-movflags faststart -strict unofficial -strict -2 -f mp4 -- \"{outPath}\""); + + string arguments = argsBuilder.ToString(); + + LogDebug("ffmpeg命令: {0}", arguments); return RunExe(FFMPEG, arguments, FFMPEG != "ffmpeg"); } @@ -168,7 +191,7 @@ public static void MergeFLV(string[] files, string outPath) { if (files.Length == 1) { - File.Move(files[0], outPath); + File.Move(files[0], outPath); } else { @@ -176,7 +199,7 @@ public static void MergeFLV(string[] files, string outPath) { var tmpFile = Path.Combine(Path.GetDirectoryName(file)!, Path.GetFileNameWithoutExtension(file) + ".ts"); var arguments = $"-loglevel warning -y -i \"{file}\" -map 0 -c copy -f mpegts -bsf:v h264_mp4toannexb \"{tmpFile}\""; - LogDebug("ffmpeg命令:{0}", arguments); + LogDebug("ffmpeg命令: {0}", arguments); RunExe("ffmpeg", arguments); File.Delete(file); } @@ -185,8 +208,5 @@ public static void MergeFLV(string[] files, string outPath) foreach (var s in f) File.Delete(s); } } - - [GeneratedRegex("-i \"")] - private static partial Regex InputRegex(); } } diff --git a/BBDown/BBDownUtil.cs b/BBDown/BBDownUtil.cs index 1341ddae2..1f4cfbc50 100644 --- a/BBDown/BBDownUtil.cs +++ b/BBDown/BBDownUtil.cs @@ -185,11 +185,10 @@ public static string FormatFileSize(double fileSize) public static string FormatTime(int time, bool absolute = false) { - TimeSpan ts = new(0, 0, time); - string str = !absolute - ? (ts.Hours.ToString("00") == "00" ? "" : ts.Hours.ToString("00") + "h") + ts.Minutes.ToString("00") + "m" + ts.Seconds.ToString("00") + "s" - : ts.Hours.ToString("00") + ":" + ts.Minutes.ToString("00") + ":" + ts.Seconds.ToString("00"); - return str; + TimeSpan ts = TimeSpan.FromSeconds(time); + return !absolute + ? (ts.Hours == 0 ? ts.ToString(@"mm\mss\s") : ts.ToString(@"hh\hmm\mss\s")) + : ts.ToString(@"hh\:mm\:ss"); } /// diff --git a/BBDown/CommandLineInvoker.cs b/BBDown/CommandLineInvoker.cs index 2edcc36a2..7faacfbfa 100644 --- a/BBDown/CommandLineInvoker.cs +++ b/BBDown/CommandLineInvoker.cs @@ -22,10 +22,10 @@ internal class CommandLineInvoker private readonly static Option HideStreams = new(new string[] { "--hide-streams", "-hs" }, "不要显示所有可用音视频流"); private readonly static Option Interactive = new(new string[] { "--interactive", "-ia" }, "交互式选择清晰度"); private readonly static Option ShowAll = new(new string[] { "--show-all" }, "展示所有分P标题"); - private readonly static Option UseAria2c = new(new string[] { "--use-aria2c" }, "调用aria2c进行下载(你需要自行准备好二进制可执行文件)"); + private readonly static Option UseAria2c = new(new string[] { "--use-aria2c", "-aria2" }, "调用aria2c进行下载(你需要自行准备好二进制可执行文件)"); private readonly static Option Aria2cArgs = new(new string[] { "--aria2c-args" }, "调用aria2c的附加参数(默认参数包含\"-x16 -s16 -j16 -k 5M\", 使用时注意字符串转义)"); private readonly static Option MultiThread = new(new string[] { "--multi-thread", "-mt" }, "使用多线程下载(默认开启)"); - private readonly static Option SelectPage = new(new string[] { "--select-page", "-p" }, "选择指定分p或分p范围: (-p 8 或 -p 1,2 或 -p 3-5 或 -p ALL 或 -p LAST)"); + private readonly static Option SelectPage = new(new string[] { "--select-page", "-p" }, "选择指定分p或分p范围: (-p 8 或 -p 1,2 或 -p 3-5 或 -p ALL 或 -p LAST 或 -p 3,5,LATEST)"); private readonly static Option AudioOnly = new(new string[] { "--audio-only" }, "仅下载音频"); private readonly static Option VideoOnly = new(new string[] { "--video-only" }, "仅下载视频"); private readonly static Option DanmakuOnly = new(new string[] { "--danmaku-only" }, "仅下载弹幕"); @@ -52,7 +52,7 @@ internal class CommandLineInvoker private readonly static Option UposHost = new(new string[] { "--upos-host" }, "自定义upos服务器"); private readonly static Option ForceReplaceHost = new(new string[] { "--force-replace-host" }, "强制替换下载服务器host(默认开启)"); private readonly static Option DelayPerPage = new(new string[] { "--delay-per-page" }, "设置下载合集分P之间的下载间隔时间(单位: 秒, 默认无间隔)"); - private readonly static Option FilePattern = new(new string[] { "--file-pattern", "-F" }, $"使用内置变量自定义单P存储文件名:\r\n\r\n" + $": 视频主标题\r\n" + $": 视频分P序号\r\n" + $": 视频分P序号(前缀补零)\r\n" + $": 视频分P标题\r\n" + $": 视频BV号\r\n" + $": 视频aid\r\n" + $": 视频cid\r\n" + $": 视频清晰度\r\n" + $": 视频分辨率\r\n" + $": 视频帧率\r\n" + $": 视频编码\r\n" + $": 视频码率\r\n" + $": 音频编码\r\n" + $": 音频码率\r\n" + $": 上传者名称\r\n" + $": 上传者mid\r\n" + $": 发布时间\r\n" + $": API类型(TV/APP/INTL/WEB)\r\n\r\n" + $"默认为: {Program.SinglePageDefaultSavePath}\r\n"); + private readonly static Option FilePattern = new(new string[] { "--file-pattern", "-F" }, $"使用内置变量自定义单P存储文件名:\r\n\r\n" + $": 视频主标题\r\n" + $": 视频分P序号\r\n" + $": 视频分P序号(前缀补零)\r\n" + $": 视频分P标题\r\n" + $": 视频BV号\r\n" + $": 视频aid\r\n" + $": 视频cid\r\n" + $": 视频清晰度\r\n" + $": 视频分辨率\r\n" + $": 视频帧率\r\n" + $": 视频编码\r\n" + $": 视频码率\r\n" + $": 音频编码\r\n" + $": 音频码率\r\n" + $": 上传者名称\r\n" + $": 上传者mid\r\n" + $": 收藏夹/番剧/合集发布时间\r\n" + $": 视频发布时间(分p视频发布时间与相同)\r\n" + $": API类型(TV/APP/INTL/WEB)\r\n\r\n" + $"默认为: {Program.SinglePageDefaultSavePath}\r\n"); private readonly static Option MultiFilePattern = new(new string[] { "--multi-file-pattern", "-M" }, $"使用内置变量自定义多P存储文件名:\r\n\r\n" + $"默认为: {Program.MultiPageDefaultSavePath}\r\n"); private readonly static Option Host = new(new string[] { "--host" }, "指定BiliPlus host(使用BiliPlus需要access_token, 不需要cookie, 解析服务器能够获取你账号的大部分权限!)"); private readonly static Option EpHost = new(new string[] { "--ep-host" }, "指定BiliPlus EP host(用于代理api.bilibili.com/pgc/view/web/season, 大部分解析服务器不支持代理该接口)"); diff --git a/BBDown/Program.cs b/BBDown/Program.cs index 7e7e9c3d3..0eb994133 100644 --- a/BBDown/Program.cs +++ b/BBDown/Program.cs @@ -39,6 +39,11 @@ private static int Compare(Audio r1, Audio r2) return r1.bandwith - r2.bandwith > 0 ? -1 : 1; } + private static string FormatTimeStamp(long ts, string format) + { + return ts == 0 ? "null" : DateTimeOffset.FromUnixTimeSeconds(ts).ToLocalTime().ToString(format); + } + [JsonSerializable(typeof(MyOption))] partial class MyOptionJsonContext : JsonSerializerContext { } @@ -249,7 +254,7 @@ private static async Task DoWorkAsync(MyOption myOption) string input = myOption.Url; string savePathFormat = myOption.FilePattern; string lang = myOption.Language; - string selectPage = myOption.SelectPage.ToUpper(); + string selectPage = myOption.SelectPage.ToUpper().Trim().Trim(','); string uposHost = myOption.UposHost; string aidOri = ""; //原始aid int delay = Convert.ToInt32(myOption.DelayPerPage); @@ -337,15 +342,6 @@ private static async Task DoWorkAsync(MyOption myOption) if (skipSubtitle) subOnly = false; - List? selectedPages = null; - if (!string.IsNullOrEmpty(GetQueryString("p", input))) - { - selectedPages = new List - { - GetQueryString("p", input) - }; - } - LogDebug("AppDirectory: {0}", APP_DIR); LogDebug("运行参数:{0}", JsonSerializer.Serialize(myOption, MyOptionJsonContext.Default.MyOption)); if (string.IsNullOrEmpty(Config.COOKIE) && File.Exists(Path.Combine(APP_DIR, "BBDown.data"))) @@ -383,35 +379,6 @@ private static async Task DoWorkAsync(MyOption myOption) Log("获取aid..."); aidOri = await GetAvIdAsync(input); Log("获取aid结束: " + aidOri); - //-p的优先级大于URL中的自带p参数, 所以先清空selectedPages - if (!string.IsNullOrEmpty(selectPage) && selectPage != "ALL") - { - selectedPages = new List(); - 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("获取视频信息..."); @@ -443,9 +410,9 @@ private static async Task DoWorkAsync(MyOption myOption) var vInfo = await fetcher.FetchAsync(aidOri); string title = vInfo.Title; string pic = vInfo.Pic; - string pubTime = vInfo.PubTime; + long pubTime = vInfo.PubTime; LogColor("视频标题: " + title); - Log("发布时间: " + pubTime); + Log("发布时间: " + FormatTimeStamp(pubTime, "yyyy-MM-dd HH:mm:ss zzz")); List pagesInfo = vInfo.PagesInfo; List subtitleInfo = new(); bool more = false; @@ -467,22 +434,54 @@ private static async Task DoWorkAsync(MyOption myOption) } } - //选择最新分P - if (!string.IsNullOrEmpty(selectPage) && (selectPage == "LAST" || selectPage == "NEW" || selectPage == "LATEST")) + List? selectedPages = null; + + if (string.IsNullOrEmpty(selectPage)) { - try + //如果用户没有选择分P, 根据epid或query param来确定某一集 + if (!string.IsNullOrEmpty(vInfo.Index)) { - selectedPages = new List { pagesInfo.Count.ToString() }; - Log("程序已选择最新一P"); + selectedPages = new List { vInfo.Index }; + Log("程序已自动选择你输入的集数, 如果要下载其他集数请自行指定分P(如可使用-p ALL代表全部)"); + } + else if (!string.IsNullOrEmpty(GetQueryString("p", input))) + { + selectedPages = new List { GetQueryString("p", input) }; + Log("程序已自动选择你输入的集数, 如果要下载其他集数请自行指定分P(如可使用-p ALL代表全部)"); } - catch { LogError("解析分P参数时失败了~"); selectedPages = null; }; } - - //如果用户没有选择分P, 根据epid来确定某一集 - if (selectedPages == null && selectPage != "ALL" && !string.IsNullOrEmpty(vInfo.Index)) + else if (selectPage != "ALL") { - selectedPages = new List { vInfo.Index }; - Log("程序已自动选择你输入的集数, 如果要下载其他集数请自行指定分P(如可使用-p ALL代表全部)"); + selectedPages = new List(); + + //选择最新分P + string lastPage = pagesInfo.Count.ToString(); + foreach (string key in new string[] { "LAST", "NEW", "LATEST" }) + { + selectPage = selectPage.Replace(key, lastPage); + } + + try + { + if (selectPage.Contains('-')) + { + string[] tmp = selectPage.Split('-'); + int start = int.Parse(tmp[0]); + int end = int.Parse(tmp[1]); + for (int i = start; i <= end; i++) + { + selectedPages.Add(i.ToString()); + } + } + else + { + foreach (var s in selectPage.Split(',')) + { + selectedPages.Add(s); + } + } + } + catch { LogError("解析分P参数时失败了~"); selectedPages = null; }; } Log($"共计 {pagesInfo.Count} 个分P, 已选择:" + (selectedPages == null ? "ALL" : string.Join(",", selectedPages))); @@ -844,7 +843,7 @@ private static async Task DoWorkAsync(MyOption myOption) (pagesCount > 1 || (bangumi && !vInfo.IsBangumiEnd)) ? p.title : "", File.Exists(coverPath) ? coverPath : "", lang, - subtitleInfo, audioOnly, videoOnly, p.points); + subtitleInfo, audioOnly, videoOnly, p.points, p.pubTime); if (code != 0 || !File.Exists(savePath) || new FileInfo(savePath).Length == 0) { LogError("合并失败"); continue; @@ -954,7 +953,7 @@ private static async Task DoWorkAsync(MyOption myOption) (pagesCount > 1 || (bangumi && !vInfo.IsBangumiEnd)) ? p.title : "", File.Exists(coverPath) ? coverPath : "", lang, - subtitleInfo, audioOnly, videoOnly, p.points); + subtitleInfo, audioOnly, videoOnly, p.points, p.pubTime); if (code != 0 || !File.Exists(savePath) || new FileInfo(savePath).Length == 0) { LogError("合并失败"); continue; @@ -1049,7 +1048,7 @@ private static List