Skip to content

Commit

Permalink
Merge pull request #244 from nICEnnnnnnnLee/dev
Browse files Browse the repository at this point in the history
V6.38 Update
  • Loading branch information
nICEnnnnnnnLee authored Dec 22, 2024
2 parents 34bd3a7 + 7044387 commit 59e64aa
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 39 deletions.
15 changes: 12 additions & 3 deletions .github/release.info
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
* 修复:纠正一键下载时,以日期作为条件判断不准确的错误 #235
* 修复:更新字幕api的解析 #232
* 其它详见[V6.36...V6.37](https://github.com/nICEnnnnnnnLee/BilibiliDown/compare/V6.36...V6.37)
* 修复:在查询清晰度时带上cookie,这是因为某些视频必须登录才能查看,比如`BV1fx411x7QS` #240
* 优化:对查询清晰度的API进行了升级,并增强了鲁棒性(虽然旧的也还能用)
* 新增:提供多种清晰度查询策略,目的是减少不必要的网络请求次数
+ `tryNormalTypeFirst` 先尝试普通视频,报错后尝试其它类型; 绝大多数情况1次网络请求,少数2次。
+ `judgeTypeFirst` 先判断视频类型,再进行查询; 2次网络请求。**这是旧版本的查询策略**。
+ `returnFixedValue` 不查询,直接返回固定值; 无网络请求。**这是新版本的默认查询策略**。
+ 无论是何策略,若单个BV下子视频数量多于5,总会返回固定列表。
+ 引入该功能的主要原因是`BV1g5pqeBEXP`,这个互动视频有上百个片段,查询清晰度会“卡死”在那,实际上后台一直在获取每个视频的清晰度。但这是不必要的。
+ 不建议在配置文件中修改该值。若实在有需要,可以在菜单栏临时变更策略,程序关闭后失效。
* 新增:当`clipTitle`和视频标题`avTitle`一致时,允许将`clipTitle`置空 #237
+ `bilibili.name.format.clipTitle.allowNull`为`true`时功能启用(默认关闭),此时可以配合条件判断进行使用,避免文件名出现冗余的重复信息。
* 其它详见[V6.37...V6.38](https://github.com/nICEnnnnnnnLee/BilibiliDown/compare/V6.37...V6.38)
<hr/>

如果你是Win64用户,且没有java环境,请下载附件`*.win_x64_jre11.release.zip`或 `*.win_x64.msi`
13 changes: 13 additions & 0 deletions UPDATE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
## UPDATE
* V6.38 `2024-12-22`
* 修复:在查询清晰度时带上cookie,这是因为某些视频必须登录才能查看,比如`BV1fx411x7QS` #240
* 优化:对查询清晰度的API进行了升级,并增强了鲁棒性(虽然旧的也还能用)
* 新增:提供多种清晰度查询策略,目的是减少不必要的网络请求次数
+ `tryNormalTypeFirst` 先尝试普通视频,报错后尝试其它类型; 绝大多数情况1次网络请求,少数2次。
+ `judgeTypeFirst` 先判断视频类型,再进行查询; 2次网络请求。**这是旧版本的查询策略**
+ `returnFixedValue` 不查询,直接返回固定值; 无网络请求。**这是新版本的默认查询策略**
+ 无论是何策略,若单个BV下子视频数量多于5,总会返回固定列表。
+ 引入该功能的主要原因是`BV1g5pqeBEXP`,这个互动视频有上百个片段,查询清晰度会“卡死”在那,实际上后台一直在获取每个视频的清晰度。但这是不必要的。
+ 不建议在配置文件中修改该值。若实在有需要,可以在菜单栏临时变更策略,程序关闭后失效。
* 新增:当`clipTitle`和视频标题`avTitle`一致时,允许将`clipTitle`置空 #237
+ `bilibili.name.format.clipTitle.allowNull``true`时功能启用(默认关闭),此时可以配合条件判断进行使用,避免文件名出现冗余的重复信息。
* 其它详见[V6.37...V6.38](https://github.com/nICEnnnnnnnLee/BilibiliDown/compare/V6.37...V6.38)
* V6.37 `2024-11-24`
* 修复:纠正一键下载时,以日期作为条件判断不准确的错误 #235
* 修复:更新字幕api的解析 #232
Expand Down
140 changes: 108 additions & 32 deletions src/nicelee/bilibili/parsers/impl/AbstractBaseParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import nicelee.bilibili.util.HttpHeaders;
import nicelee.bilibili.util.HttpRequestUtil;
import nicelee.bilibili.util.Logger;
import nicelee.bilibili.util.convert.ConvertUtil;
import nicelee.ui.Global;

public abstract class AbstractBaseParser implements IInputParser {
Expand Down Expand Up @@ -72,22 +73,45 @@ public VideoInfo getAVDetail(String bvId, int videoFormat, boolean getVideoLink)
// 根据第一个获取总体大致信息
JSONObject jObj = array.getJSONObject(0);
long cid = jObj.getLong("cid");
String detailUrl = String.format("https://api.bilibili.com/x/web-interface/view?cid=%d&bvid=%s", cid, bvId);
// String detailUrl = String.format("https://api.bilibili.com/x/web-interface/view?cid=%d&bvid=%s", cid, bvId);
// String detailJson = util.getContent(detailUrl, headers_json, HttpCookies.globalCookiesWithFingerprint());
// JSONObject detailObj = new JSONObject(detailJson).getJSONObject("data");
String detailUrl = "https://api.bilibili.com/x/web-interface/wbi/view/detail?platform=web&page_no=1&p=1&need_operation_card=1&web_rm_repeat=1&need_elec=1&bvid="
+ bvId;
detailUrl += API.genDmImgParams();
detailUrl = API.encWbi(detailUrl);
String detailJson = util.getContent(detailUrl, headers_json, HttpCookies.globalCookiesWithFingerprint());
Logger.println(detailUrl);
Logger.println(detailJson);
JSONObject detailObj = new JSONObject(detailJson).getJSONObject("data");

long aid = detailObj.getLong("aid");
long ctime = detailObj.optLong("ctime") * 1000;
viInfo.setVideoName(detailObj.getString("title"));
viInfo.setBrief(detailObj.getString("desc"));
viInfo.setAuthor(detailObj.getJSONObject("owner").getString("name"));
viInfo.setAuthorId(String.valueOf(detailObj.getJSONObject("owner").getLong("mid")));
viInfo.setVideoPreview(detailObj.getString("pic"));
JSONObject detailRaw = new JSONObject(detailJson);
long aid = ConvertUtil.Bv2Av(bvId);
int videoCnt; long ctime;
if(detailRaw.optInt("code") == -403) {
detailUrl = String.format("https://api.bilibili.com/x/v3/fav/resource/infos?resources=%d:2&platform=web&folder_mid=&folder_id=", aid);
detailJson = util.getContent(detailUrl, headers_json, HttpCookies.globalCookiesWithFingerprint());
Logger.println(detailUrl);
Logger.println(detailJson);
JSONObject detailObj = new JSONObject(detailJson).getJSONArray("data").getJSONObject(0);
ctime = detailObj.optLong("ctime") * 1000;
videoCnt = detailObj.optInt("page");
viInfo.setVideoName(detailObj.getString("title"));
viInfo.setBrief(detailObj.getString("intro"));
viInfo.setAuthor(detailObj.getJSONObject("upper").getString("name"));
viInfo.setAuthorId(String.valueOf(detailObj.getJSONObject("upper").getLong("mid")));
viInfo.setVideoPreview(detailObj.getString("cover"));
} else {
JSONObject detailObj = detailRaw.getJSONObject("data").getJSONObject("View");
ctime = detailObj.optLong("ctime") * 1000;
videoCnt = detailObj.optInt("videos");
viInfo.setVideoName(detailObj.getString("title"));
viInfo.setBrief(detailObj.getString("desc"));
viInfo.setAuthor(detailObj.getJSONObject("owner").getString("name"));
viInfo.setAuthorId(String.valueOf(detailObj.getJSONObject("owner").getLong("mid")));
viInfo.setVideoPreview(detailObj.getString("pic"));
}

// 判断是否是互动视频
if (detailObj.optInt("videos") > 1 && array.length() == 1) {
if (videoCnt > 1 && array.length() == 1) {
// 查询graph_version版本
String url_graph_version = String.format("https://api.bilibili.com/x/player/wbi/v2?aid=%d&cid=%d&isGaiaAvoided=false", aid, cid);
url_graph_version += API.genDmImgParams();
Expand Down Expand Up @@ -142,6 +166,7 @@ public VideoInfo getAVDetail(String bvId, int videoFormat, boolean getVideoLink)
}
clip.setLinks(links);
} catch (Exception e) {
e.printStackTrace();
clip.setLinks(links);
}

Expand All @@ -153,23 +178,30 @@ public VideoInfo getAVDetail(String bvId, int videoFormat, boolean getVideoLink)
return viInfo;
}

/**
* 使用https://api.bilibili.com/pgc/player/web/playurl
*
* @external input HttpRequestUtil util
* @param bvId
* @param cid
* @return
*/
public int[] getVideoQNList(String bvId, String cid) {
switch (Global.infoQueryStrategy) {
case "tryNormalTypeFirst":
return getVideoQNList_TryNormalTypeFirst(bvId, cid);
case "judgeTypeFirst":
return getVideoQNList_JudgeTypeFirst(bvId, cid);
default:
return new int[] { 120, 116, 112, 80, 74, 64, 32, 16 };
}
}

private int[] getVideoQNList_JudgeTypeFirst(String bvId, String cid) {
HttpHeaders headers = new HttpHeaders();
JSONArray jArr = null;
// 先判断类型
String url = "https://api.bilibili.com/x/web-interface/view/detail?aid=&jsonp=jsonp&callback=__jp0&bvid="
// https://api.bilibili.com/x/web-interface/wbi/view/detail?platform=web&bvid=%s&&need_operation_card=1&web_rm_repeat=1&need_elec=1
String url = "https://api.bilibili.com/x/web-interface/wbi/view/detail?platform=web&bvid="
+ bvId;
url += API.genDmImgParams();
url = API.encWbi(url);
HashMap<String, String> header = headers.getBiliJsonAPIHeaders(bvId);
String callBack = util.getContent(url, header);
JSONObject infoObj = new JSONObject(callBack.substring(6, callBack.length() - 1)).getJSONObject("data")
String callBack = util.getContent(url, header, HttpCookies.globalCookiesWithFingerprint());
Logger.println(callBack);
JSONObject infoObj = new JSONObject(callBack).getJSONObject("data")
.getJSONObject("View");
Long aid = infoObj.optLong("aid");

Expand All @@ -179,7 +211,7 @@ public int[] getVideoQNList(String bvId, String cid) {
url = String.format(url, cid, bvId, 32);
Logger.println(url);
String json = util.getContent(url, headers.getBiliJsonAPIHeaders(bvId), HttpCookies.globalCookiesWithFingerprint());
System.out.println(json);
Logger.println(json);
jArr = new JSONObject(json).getJSONObject("data").getJSONArray("accept_quality");
} else {
// 非普通类型
Expand All @@ -188,7 +220,37 @@ public int[] getVideoQNList(String bvId, String cid) {
Logger.println(url);
String json = util.getContent(url, headers.getBiliJsonAPIHeaders("av" + aid),
HttpCookies.globalCookiesWithFingerprint());
System.out.println(json);
Logger.println(json);
jArr = new JSONObject(json).getJSONObject("result").getJSONArray("accept_quality");
}
int qnList[] = new int[jArr.length()];
for (int i = 0; i < qnList.length; i++) {
qnList[i] = jArr.getInt(i);
// Logger.println(qnList[i]);
}
return qnList;
}

private int[] getVideoQNList_TryNormalTypeFirst(String bvId, String cid) {
HttpHeaders headers = new HttpHeaders();
JSONArray jArr = null;
try {
// 普通类型
String url = "https://api.bilibili.com/x/player/playurl?cid=%s&bvid=%s&qn=%d&type=&otype=json&fnver=0&fnval=4048&fourk=1";
url = String.format(url, cid, bvId, 32);
Logger.println(url);
String json = util.getContent(url, headers.getBiliJsonAPIHeaders(bvId), HttpCookies.globalCookiesWithFingerprint());
Logger.println(json);
jArr = new JSONObject(json).getJSONObject("data").getJSONArray("accept_quality");
} catch (Exception e) {
// 非普通类型
long aid = ConvertUtil.Bv2Av(bvId);
String url = "https://api.bilibili.com/pgc/player/web/playurl?fnval=4048&fnver=0&fourk=1&otype=json&avid=%d&cid=%s&qn=%s";
url = String.format(url, aid, cid, 32);
Logger.println(url);
String json = util.getContent(url, headers.getBiliJsonAPIHeaders("av" + aid),
HttpCookies.globalCookiesWithFingerprint());
Logger.println(json);
jArr = new JSONObject(json).getJSONObject("result").getJSONArray("accept_quality");
}
int qnList[] = new int[jArr.length()];
Expand Down Expand Up @@ -290,15 +352,29 @@ String getVideoLinkByFormat(String bvId, String cid, int qn, int downloadFormat)
// 根据downloadFormat确定fnval
String fnval = (downloadFormat & 0x01) == Global.MP4? "4048" : "2";
// 先判断类型
String url = "https://api.bilibili.com/x/web-interface/view/detail?aid=&jsonp=jsonp&callback=__jp0&bvid="
Long aid; boolean isNormalType;
String url = "https://api.bilibili.com/x/web-interface/wbi/view/detail?platform=web&bvid="
+ bvId;
url += API.genDmImgParams();
url = API.encWbi(url);
HashMap<String, String> header = headers.getBiliJsonAPIHeaders(bvId);
String callBack = util.getContent(url, header);
JSONObject infoObj = new JSONObject(callBack.substring(6, callBack.length() - 1)).getJSONObject("data")
.getJSONObject("View");
Long aid = infoObj.optLong("aid");

if (infoObj.optString("redirect_url").isEmpty()) {
String callBack = util.getContent(url, header, HttpCookies.globalCookiesWithFingerprint());
JSONObject detailRaw = new JSONObject(callBack);
if(detailRaw.optInt("code") == -403) {
aid = ConvertUtil.Bv2Av(bvId);
String detailUrl = String.format("https://api.bilibili.com/x/v3/fav/resource/infos?resources=%d:2&platform=web&folder_mid=&folder_id=", aid);
String detailJson = util.getContent(detailUrl, header, HttpCookies.globalCookiesWithFingerprint());
Logger.println(detailUrl);
Logger.println(detailJson);
JSONObject detailObj = new JSONObject(detailJson).getJSONArray("data").getJSONObject(0);
isNormalType = detailObj.getInt("attr") != 2; // 0 普通 16 互动视频
} else {
JSONObject infoObj = detailRaw.getJSONObject("data")
.getJSONObject("View");
aid = infoObj.optLong("aid");
isNormalType = infoObj.optString("redirect_url").isEmpty();
}
if (isNormalType) {
String trylookTail = Global.isLogin ? "" : "&try_look=1";
// 普通类型
url = downloadFormat == 2 ?
Expand All @@ -313,7 +389,7 @@ String getVideoLinkByFormat(String bvId, String cid, int qn, int downloadFormat)
// List cookie = downloadFormat == 2 ? null : HttpCookies.globalCookiesWithFingerprint();
List<HttpCookie> cookie = HttpCookies.globalCookiesWithFingerprint();
String json = util.getContent(url, headers.getBiliJsonAPIHeaders(bvId), cookie);
System.out.println(json);
Logger.println(json);
jObj = new JSONObject(json).getJSONObject("data");
} else {
// 非普通类型
Expand Down
8 changes: 6 additions & 2 deletions src/nicelee/bilibili/util/CmdUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,12 @@ public static String genFormatedName(VideoInfo avInfo, ClipInfo clip, int realQN
paramMap.put("pAv", "" + clip.getPage());
paramMap.put("pDisplay", "" + clip.getRemark());
paramMap.put("qn", "" + realQN);
paramMap.put("avTitle", clip.getAvTitle().replaceAll("[/\\\\]", "_"));
paramMap.put("clipTitle", clip.getTitle().replaceAll("[/\\\\]", "_"));
String avTitle = clip.getAvTitle().replaceAll("[/\\\\]", "_");
String clipTitle = clip.getTitle().replaceAll("[/\\\\]", "_");
paramMap.put("avTitle", avTitle);
if( !(Global.ctFormatAllowNull && avTitle.equals(clipTitle)) ) {
paramMap.put("clipTitle", clipTitle);
}
paramMap.put("listName", clip.getListName()); // 已确保没有路径分隔符
paramMap.put("listOwnerName", clip.getListOwnerName()); // 已确保没有路径分隔符
paramMap.put("UpName", clip.getUpName().replaceAll("[/\\\\]", "_"));
Expand Down
6 changes: 5 additions & 1 deletion src/nicelee/ui/Global.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

public class Global {
// 界面显示相关
@Config(key = "bilibili.version", defaultValue = "v6.37", warning = false)
@Config(key = "bilibili.version", defaultValue = "v6.38", warning = false)
public static String version; // 一般情况下,我们不会设置这个标签,这个用于测试
@Config(key = "bilibili.time.syncServer", note = "同步服务器的时间", defaultValue = "false", valids = { "true", "false" })
public static boolean syncServerTime;
Expand Down Expand Up @@ -116,6 +116,8 @@ public class Global {
public static boolean saveToRepo; // 使用仓库保存下载成功的记录
@Config(key = "bilibili.name.format", note = "自定义下载文件名称", defaultValue = "(:listName listName-)avTitle-pDisplay-clipTitle-qn")
public static String formatStr;
@Config(key = "bilibili.name.format.clipTitle.allowNull", note = "自定义下载名称时,若avTitle等于clipTitle,将clipTitle置空", defaultValue = "false", eq_true = "true", valids = { "true", "false" })
public static boolean ctFormatAllowNull;
@Config(key = "bilibili.name.date.favTime.pattern", note = "收藏时间格式化", defaultValue = "yyMMdd")
public static String favTimeFormat;
@Config(key = "bilibili.name.date.cTime.pattern", note = "发布/更新时间格式化", defaultValue = "yyMMdd")
Expand Down Expand Up @@ -168,6 +170,8 @@ public class Global {
public static int pageSize = 5; // 当有分页时,每页显示个数
@Config(key = "bilibili.pageDisplay", defaultValue = "listAll")
public static String pageDisplay = "listAll"; // 分页查询时,结果展示方式 listAll/promptAll(promptAll 逐渐弃用)
@Config(key = "bilibili.info.query.strategy", note = "查询可用清晰度时,使用的策略", defaultValue = "returnFixedValue", valids = { "tryNormalTypeFirst", "judgeTypeFirst", "returnFixedValue" })
public static String infoQueryStrategy;
// 临时文件相关
@Config(key = "bilibili.restrictTempMode", defaultValue = "true", eq_true = "on", valids = { "on", "off" })
public static boolean restrictTempMode;
Expand Down
22 changes: 22 additions & 0 deletions src/nicelee/ui/item/MJMenuBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,27 @@ public void init(JRadioButtonMenuItem[] menuItems) {
}
}
}.build();

String[] straOptions = { "tryNormalTypeFirst", "judgeTypeFirst", "returnFixedValue" };
String[] straOptionTips = { "先尝试普通类型,报错再尝试其它", "先判断类型再查询", "返回固定值" };
JMenu dQNQueryStrategyMenuItem = new MJMenuWithRadioGroupBuilder("可用清晰度查询策略", straOptionTips) {

@Override
public void onItemSelected(int itemIndex, JRadioButtonMenuItem item) {
Global.infoQueryStrategy = straOptions[itemIndex];
Logger.println("可用清晰度查询策略为: " + straOptionTips[itemIndex]);
}

@Override
public void init(JRadioButtonMenuItem[] menuItems) {
for(int i = 0; i < menuItems.length; i++) {
if(straOptions[i].equals(Global.infoQueryStrategy)) {
menuItems[i].setSelected(true);
}
}
}
}.build();

File configDir = new File(ResourcesUtil.baseDirectory(), "config");
List<String> configFiles = new ArrayList<>();
configFiles.add(Global.batchDownloadConfigName);
Expand Down Expand Up @@ -282,6 +303,7 @@ public void init(JRadioButtonMenuItem[] menuItems) {
configMenu.add(dTypeReDownloadMenuItem);
configMenu.add(dForceReplaceHostMenuItem);
configMenu.add(dQNMenuItem);
configMenu.add(dQNQueryStrategyMenuItem);
configMenu.add(dBatchDownMenuItem);
configMenu.add(dUpdateMenuItem);
configMenu.add(dFFmpegMenuItem);
Expand Down
8 changes: 7 additions & 1 deletion src/nicelee/ui/thread/BatchDownloadThread.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,13 @@ public void run() {
if(!isPageable && page >= 2)
break;
String sp = validStr + " p=" + page;
VideoInfo avInfo = ina.getVideoDetail(sp, Global.downloadFormat, false);
VideoInfo avInfo = null;
try {
avInfo = ina.getVideoDetail(sp, Global.downloadFormat, false);
} catch (Exception e) {
e.printStackTrace();
break;
}
Collection<ClipInfo> clips = avInfo.getClips().values();
if (clips.size() == 0)
break;
Expand Down
Loading

0 comments on commit 59e64aa

Please sign in to comment.