436 lines
18 KiB
Java
436 lines
18 KiB
Java
package com.yutou.qqbot.models.BiliBili;
|
|
|
|
import com.alibaba.fastjson2.JSONArray;
|
|
import com.alibaba.fastjson2.JSONObject;
|
|
import com.yutou.napcat.enums.MessageEnum;
|
|
import com.yutou.napcat.event.MessageEvent;
|
|
import com.yutou.napcat.handle.OtherHandle;
|
|
import com.yutou.napcat.handle.Reply;
|
|
import com.yutou.napcat.http.NapCatApi;
|
|
import com.yutou.napcat.model.MessageBean;
|
|
import com.yutou.okhttp.HttpBody;
|
|
import com.yutou.qqbot.Annotations.UseModel;
|
|
import com.yutou.qqbot.QQBotManager;
|
|
import com.yutou.qqbot.bilibili.*;
|
|
import com.yutou.qqbot.interfaces.ObjectInterface;
|
|
import com.yutou.qqbot.models.Model;
|
|
import com.yutou.qqbot.utlis.AppTools;
|
|
import com.yutou.qqbot.utlis.ConfigTools;
|
|
import com.yutou.qqbot.utlis.HttpTools;
|
|
import com.yutou.qqbot.utlis.StringUtils;
|
|
import retrofit2.Response;
|
|
|
|
import java.io.File;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
@UseModel
|
|
public class BiliVideo extends Model {
|
|
public String downloadPath = "tmp";
|
|
List<DanmuData> danmuDatas = new ArrayList<>();
|
|
long danmuNextTime = 0;
|
|
private BiliBiliUtils biliUtils;
|
|
private long qq;
|
|
|
|
public BiliVideo(long qq) {
|
|
this.qq = qq;
|
|
biliUtils = BiliBiliUtils.getInstance(qq);
|
|
}
|
|
|
|
public BiliVideo() {
|
|
this.qq = QQBotManager.defQQ;
|
|
biliUtils = BiliBiliUtils.getInstance(qq);
|
|
}
|
|
|
|
@Override
|
|
public boolean isUserPublic() {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public String[] getUsePowers() {
|
|
return new String[0];
|
|
}
|
|
|
|
@Override
|
|
public String getModelName() {
|
|
return "B站视频下载";
|
|
}
|
|
|
|
@Override
|
|
public void onMessage(Long qq, MessageEvent event, boolean isGroup) {
|
|
super.onMessage(qq, event, isGroup);
|
|
if (event.isAtMe() && event.hasType(MessageEnum.REPLY)) {
|
|
Reply reply = event.findType(Reply.class);
|
|
long id = reply.getData().getId();
|
|
if (msg.contains("省流") || msg.contains("总结")) {
|
|
String value = onAIVideo(id);
|
|
if (!StringUtils.isEmpty(value)) {
|
|
QQBotManager.getInstance().sendMessage(qq, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private String onAIVideo(long id) {
|
|
try {
|
|
Response<HttpBody<MessageBean>> execute = NapCatApi.getMessageApi().getMessage(id).execute();
|
|
if(execute.body()==null){
|
|
return "省流失败";
|
|
}
|
|
MessageEvent handle = MessageEvent.parseHandleHttp(execute.body().getSrc());
|
|
if (handle.hasType(MessageEnum.JSON)) {
|
|
OtherHandle type = handle.findType(OtherHandle.class);
|
|
String url = type.getData().getMeta().getDetail1().getQqdocurl();
|
|
if (StringUtils.isEmpty(url)) {
|
|
return "地址不正确";
|
|
}
|
|
if (url.startsWith("BV")) {
|
|
url = "https://www.bilibili.com/video/" + url.trim();
|
|
}
|
|
if (!url.startsWith("https://www.bilibili.com/video/") && !url.startsWith("https://b23.tv")) {
|
|
return "这是B站吗?";
|
|
}
|
|
String ai = BiliBiliAppUtils.getVideoAI(url.trim());
|
|
if (!StringUtils.isEmpty(ai)) {
|
|
return ai;
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
|
|
|
|
return "省流失败";
|
|
}
|
|
|
|
public void downVideo(String url) {
|
|
downVideo(url, true, false);
|
|
}
|
|
|
|
public void downVideo(String url, boolean downDanmu, boolean merge) {
|
|
if (biliUtils == null || !new BiliLogin(qq).testLogin()) {
|
|
System.err.println("未登录");
|
|
return;
|
|
}
|
|
if (!url.contains("?")) {
|
|
url += "?";
|
|
}
|
|
danmuDatas.clear();
|
|
JSONObject info = getVideoInfo(url);
|
|
if (info.getInteger("code") == 0) {
|
|
JSONObject infoData = info.getJSONObject("data");
|
|
JSONObject eps = new JSONObject();
|
|
if (infoData.containsKey("ugc_season")) {
|
|
JSONObject ugc = infoData.getJSONObject("ugc_season");
|
|
eps.put("title", ugc.getString("title"));
|
|
JSONArray ep = new JSONArray();
|
|
for (Object o : ugc.getJSONArray("sections")) {
|
|
JSONObject season = (JSONObject) o;
|
|
for (Object episodes : season.getJSONArray("episodes")) {
|
|
JSONObject _epi = (JSONObject) episodes;
|
|
JSONObject _item = new JSONObject();
|
|
_item.put("title", season.getString("title") + "-" + _epi.getString("title"));
|
|
_item.put("cid", _epi.getLong("cid"));
|
|
_item.put("aid", _epi.getLong("aid"));
|
|
ep.add(_item);
|
|
}
|
|
}
|
|
eps.put("eps", ep);
|
|
} else if (infoData.getInteger("videos") != 1) {
|
|
JSONArray pages = infoData.getJSONArray("pages");
|
|
JSONArray ep = new JSONArray();
|
|
for (Object o : pages) {
|
|
JSONObject page = (JSONObject) o;
|
|
JSONObject _item = new JSONObject();
|
|
_item.put("title", infoData.getString("title") + "-" + page.getString("part"));
|
|
_item.put("cid", page.getLong("cid"));
|
|
_item.put("aid", infoData.getLong("aid"));
|
|
ep.add(_item);
|
|
}
|
|
eps.put("title", infoData.getString("title"));
|
|
eps.put("eps", ep);
|
|
} else {
|
|
eps.put("title", infoData.getString("title"));
|
|
eps.put("cid", infoData.getLong("cid"));
|
|
}
|
|
JSONObject json = new JSONObject();
|
|
json.put("danmu", downDanmu);
|
|
json.put("merge", merge);
|
|
json.put("avid", infoData.getLong("aid"));
|
|
if (eps.containsKey("cid")) {
|
|
json.put("cid", eps.getLong("cid"));
|
|
json.put("qn", 127);
|
|
json.put("fnval", 80);
|
|
json.put("fourk", 1);
|
|
downVideo(json, eps);
|
|
} else {
|
|
System.out.println("json = " + json);
|
|
System.out.println("eps = " + eps);
|
|
List<File> list = new ArrayList<>();
|
|
|
|
String root = new File("tmp").getAbsolutePath() + File.separator;
|
|
for (Object o : eps.getJSONArray("eps")) {
|
|
JSONObject item = (JSONObject) o;
|
|
json.put("avid", item.getLong("aid"));
|
|
json.put("cid", item.getLong("cid"));
|
|
json.put("qn", 127);
|
|
json.put("fnval", 80);
|
|
json.put("fourk", 1);
|
|
item.put("title", eps.getString("title") + "$(File.separator)" + item.getString("title"));
|
|
list.add(new File(root + StringUtils.toSaveFileName(item.getString("title") + ".mp4")));
|
|
downVideo(json, item);
|
|
if (downDanmu && merge) {
|
|
long tmp = 0;
|
|
for (VideoDanMu.DanmakuElem elem : buildDanmuHttp(json.getLong("cid"), json.getLong("avid"), 1)) {
|
|
DanmuData danmuData = new DanmuData();
|
|
danmuData.setDanmu(elem.getContent());
|
|
danmuData.setFontSize(elem.getFontsize());
|
|
danmuData.setTime(elem.getProgress() + danmuNextTime);
|
|
danmuData.setFontColor(elem.getColor());
|
|
danmuData.setModel(elem.getMode());
|
|
if (elem.getProgress() > tmp) {
|
|
tmp = elem.getProgress();
|
|
}
|
|
danmuDatas.add(danmuData);
|
|
}
|
|
danmuNextTime = tmp;
|
|
}
|
|
}
|
|
if (merge) {
|
|
merge(root, StringUtils.toSaveFileName(eps.getString("title")), list);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
private void merge(String root, String name, List<File> files) {
|
|
String saveName = root + name;
|
|
File fileList = new File(saveName + File.separator + "tmp.txt");
|
|
System.out.println("fileList.getAbsolutePath() = " + fileList.getAbsolutePath());
|
|
StringBuilder builder = new StringBuilder();
|
|
int i = 0;
|
|
List<File> tmp = new ArrayList<>();
|
|
for (File file : files) {
|
|
System.out.println("file.getName() = " + file.getName());
|
|
file.renameTo(new File(file.getParentFile(), i + ".mp4"));
|
|
tmp.add(new File(file.getParentFile(), i + ".mp4"));
|
|
builder.append("file '").append(i++).append(".mp4'");
|
|
builder.append("\n");
|
|
}
|
|
try {
|
|
// boolean b = fileList.createNewFile();
|
|
// System.out.println("b = " + b);
|
|
FileWriter fw = new FileWriter(fileList);
|
|
fw.write(builder.toString());
|
|
fw.flush();
|
|
fw.close();
|
|
|
|
String exec = String.format("cd \"%s\" && ffmpeg -f concat -i \"%s\" -c copy %s.mp4", saveName, "tmp.txt", name);
|
|
System.out.println("exec = " + exec);
|
|
AppTools.exec(exec, new ObjectInterface() {
|
|
@Override
|
|
public void out(String data) {
|
|
super.out(data);
|
|
// System.out.println(data);
|
|
System.out.println("over");
|
|
fileList.delete();
|
|
for (File file : tmp) {
|
|
file.delete();
|
|
}
|
|
AssTools tools = new AssTools(name);
|
|
tools.addDanmu(danmuDatas);
|
|
boolean saveDanmu = tools.saveDanmu(saveName);
|
|
System.out.println("弹幕保存:" + saveDanmu);
|
|
}
|
|
}, false, false);
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
|
|
}
|
|
|
|
private void downVideo(JSONObject json, JSONObject eps) {
|
|
eps.put("title", StringUtils.toSaveFileName(eps.getString("title")));
|
|
File tmp = new File(HttpTools.downloadPath + eps.getString("title") + ".mp4");
|
|
if (json.getBooleanValue("danmu") && !json.getBooleanValue("merge")) {
|
|
List<VideoDanMu.DanmakuElem> elems = buildDanmuHttp(json.getLong("cid"), json.getLong("avid"), 1);
|
|
downDanmu("tmp", eps.getString("title"), elems);
|
|
}
|
|
if (tmp.exists()) {
|
|
return;
|
|
}
|
|
JSONObject http = biliUtils.http("https://api.bilibili.com/x/player/playurl?" + HttpTools.toUrlParams(json), BiliBiliUtils.HTTP.GET, null, BiliBiliUtils.RET_MODEL.JSON);
|
|
if (http.getInteger("code") == 0) {
|
|
JSONObject data = http.getJSONObject("data");
|
|
JSONObject dash = data.getJSONObject("dash");
|
|
JSONObject video = dash.getJSONArray("video").getJSONObject(0);
|
|
JSONObject audio = dash.getJSONArray("audio").getJSONObject(0);
|
|
|
|
File videoFile = biliUtils.download(video.getString("baseUrl"), eps.getString("title") + "_video.mp4", false);
|
|
if (videoFile == null) {
|
|
downVideo(json, eps);
|
|
return;
|
|
}
|
|
File audioFile = biliUtils.download(audio.getString("baseUrl"), eps.getString("title") + "_audio.mp3", false);
|
|
if (audioFile == null) {
|
|
videoFile.delete();
|
|
downVideo(json, eps);
|
|
return;
|
|
}
|
|
save(eps.getString("title"), videoFile, audioFile);
|
|
}
|
|
}
|
|
|
|
private void downDanmu(String savePath, String title, List<VideoDanMu.DanmakuElem> danmuList) {
|
|
try {
|
|
AssTools tools = new AssTools(title);
|
|
List<DanmuData> list = new ArrayList<>();
|
|
for (VideoDanMu.DanmakuElem elem : danmuList) {
|
|
DanmuData danmuData = new DanmuData();
|
|
danmuData.setDanmu(elem.getContent());
|
|
danmuData.setFontSize(elem.getFontsize());
|
|
danmuData.setTime(elem.getProgress());
|
|
danmuData.setFontColor(elem.getColor());
|
|
danmuData.setModel(elem.getMode());
|
|
list.add(danmuData);
|
|
}
|
|
tools.addDanmu(list);
|
|
boolean saveDanmu = tools.saveDanmu(savePath);
|
|
System.out.println("弹幕保存:" + saveDanmu);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
private List<VideoDanMu.DanmakuElem> buildDanmuHttp(long cid, long avid, int segment_index) {
|
|
List<VideoDanMu.DanmakuElem> tmp, danmuList = new ArrayList<>();
|
|
JSONObject json = new JSONObject();
|
|
json.put("type", 1);
|
|
json.put("oid", cid);
|
|
json.put("avid", avid);
|
|
json.put("segment_index", segment_index);
|
|
while (!(tmp = getDanmu(json)).isEmpty()) {
|
|
danmuList.addAll(tmp);
|
|
json.put("segment_index", ++segment_index);
|
|
}
|
|
return danmuList;
|
|
}
|
|
|
|
private List<VideoDanMu.DanmakuElem> getDanmu(JSONObject json) {
|
|
try {
|
|
byte[] http = biliUtils.http("https://api.bilibili.com/x/v2/dm/web/seg.so?" + HttpTools.toUrlParams(json), BiliBiliUtils.HTTP.GET, null, BiliBiliUtils.RET_MODEL.BYTE);
|
|
VideoDanMu.DmSegMobileReply parse = VideoDanMu.DmSegMobileReply.parseFrom(http);
|
|
return parse.getElemsList();
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
return new ArrayList<>();
|
|
|
|
}
|
|
|
|
|
|
private void save(String name, File videoFile, File audioFile) {
|
|
List<String> urls = new ArrayList<>();
|
|
urls.add(videoFile.getAbsolutePath());
|
|
urls.add(audioFile.getAbsolutePath());
|
|
|
|
StringBuilder builder = new StringBuilder();
|
|
builder.append(ConfigTools.load(ConfigTools.CONFIG, "ffmpeg", String.class)).append(" ");
|
|
for (String _url : urls) {
|
|
builder.append("-i").append(" ");
|
|
builder.append("\"").append(_url).append("\"").append(" ");
|
|
}
|
|
builder.append("-vcodec").append(" ");
|
|
builder.append("copy").append(" ");
|
|
builder.append("-acodec").append(" ");
|
|
builder.append("copy").append(" ");
|
|
builder.append("-threads").append(" ");
|
|
builder.append("8").append(" ");
|
|
// builder.append("-y").append(" ");
|
|
builder.append("\"").append(new File(HttpTools.downloadPath + name + ".mp4").getAbsolutePath()).append("\"").append(" ");
|
|
System.out.println(builder);
|
|
AppTools.exec(builder.toString(), new ObjectInterface() {
|
|
@Override
|
|
public void out(String data) {
|
|
super.out(data);
|
|
videoFile.delete();
|
|
audioFile.delete();
|
|
}
|
|
|
|
}, false, false);
|
|
}
|
|
|
|
public JSONObject getVideoInfo(String url) {
|
|
if (!new BiliLogin(qq).testLogin()) {
|
|
System.err.println("未登录");
|
|
return null;
|
|
}
|
|
JSONObject json = buildJSON(url);
|
|
if (json != null) {
|
|
return biliUtils.http("https://api.bilibili.com/x/web-interface/view?" + HttpTools.toUrlParams(json), BiliBiliUtils.HTTP.GET, null, BiliBiliUtils.RET_MODEL.JSON);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private JSONObject buildJSON(String url) {
|
|
if (!url.contains("?")) {
|
|
url += "?";
|
|
}
|
|
Pattern pattern = Pattern.compile("(?<=video/).*?(?=\\?)");
|
|
Matcher matcher = pattern.matcher(url);
|
|
String id = null;
|
|
if (matcher.find()) {
|
|
id = matcher.group();
|
|
}
|
|
if (id == null) {
|
|
return null;
|
|
}
|
|
if (id.contains("/")) {
|
|
id = id.replace("/", "");
|
|
}
|
|
JSONObject json = new JSONObject();
|
|
if (id.startsWith("BV")) {
|
|
json.put("bvid", id);
|
|
} else {
|
|
json.put("avid", id.toLowerCase().replace("av", ""));
|
|
}
|
|
return json;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
BiliVideo video = new BiliVideo(QQBotManager.defQQ);
|
|
JSONObject login = new BiliLogin(QQBotManager.defQQ).login();
|
|
System.out.println(login);
|
|
//岚少 480
|
|
//video.downVideo("https://www.bilibili.com/video/BV1Ps411m7pt?spm_id_from=333.999.0.0");
|
|
//唐诱 合集
|
|
//video.downVideo("https://www.bilibili.com/video/BV1Vv4y1K7ox?spm_id_from=444.41.top_right_bar_window_default_collection.content.click");
|
|
//邦邦 长视频
|
|
// video.downVideo("https://www.bilibili.com/video/BV1w5411271A?spm_id_from=444.41.list.card_archive.click");
|
|
//LK 超清4k hdr
|
|
//video.downVideo("https://www.bilibili.com/video/BV1uZ4y1U7h8/?spm_id_from=333.788.recommend_more_video.-1");
|
|
// hdr
|
|
// video.downVideo("https://www.bilibili.com/video/BV1rp4y1e745/?spm_id_from=333.788.recommend_more_video.-1");
|
|
// 1080+ 60fps
|
|
//video.downVideo("https://www.bilibili.com/video/BV1qF411T7Vf?spm_id_from=444.41.list.card_archive.click");
|
|
//唐诱正片
|
|
//video.downVideo("https://www.bilibili.com/video/BV1L44y147zR?spm_id_from=333.999.0.0");// ep1
|
|
//video.downVideo("https://www.bilibili`.com/video/BV1Zu4y1B7DU/?spm_id_from=333.337.search-card.all.click", true, false);// ep5
|
|
// video.downVideo("https://www.bilibili.com/video/BV1SL411g7FS/?spm_id_from=333.788.recommend_more_video.0"); //all ig 1\5
|
|
|
|
// video.downVideo("https://www.bilibili.com/video/BV18L4y1H7rz?spm_id_from=333.999.0.0");
|
|
// video.downVideo("https://www.bilibili.com/video/BV1Pe4y1Q7MX?spm_id_from=444.41.top_right_bar_window_history.content.click");
|
|
// int a=16|2048;
|
|
// System.out.println("a = " + a);
|
|
//video.downDanmu(428855000L,976216102L,"【都市_情感】《唐可可的诱惑》第一集",1);
|
|
video.downVideo("https://www.bilibili.com/bangumi/play/ep776259", false, false);// ep5
|
|
System.out.println("事件结束");
|
|
}
|
|
}
|