430 lines
18 KiB
Java

package com.yutou.qqbot.models.BiliBili;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.yutou.qqbot.Annotations.UseModel;
import com.yutou.qqbot.QQBotManager;
import com.yutou.qqbot.QQNumberManager;
import com.yutou.qqbot.bilibili.*;
import com.yutou.qqbot.interfaces.ObjectInterface;
import com.yutou.qqbot.models.Model;
import com.yutou.qqbot.utlis.*;
import net.mamoe.mirai.event.events.MessageEvent;
import net.mamoe.mirai.message.data.QuoteReply;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
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.getMessage().serializeToMiraiCode().contains("mirai:app")) {
int id = event.getSource().getIds()[0];
String json = event.getMessage().get(1).contentToString();
System.out.println("id = " + id);
System.out.println("json = " + json);
RedisTools.set("qq_msg_id_" + id, json, 60 * 60 * 2);
} else if (isAt()) {
if (event.getMessage().get(1) instanceof QuoteReply) {
int id = ((QuoteReply) event.getMessage().get(1)).getSource().getIds()[0];
if (msg.contains("省流") || msg.contains("总结")) {
String value = onAIVideo(id);
if(!StringUtils.isEmpty(value)){
QQBotManager.getInstance().sendMessage(qq,value);
}
}
}
}
}
private String onAIVideo(int id) {
String string = RedisTools.get("qq_msg_id_" + id);
//RedisTools.remove("qq_msg_id_"+id);
String url = null;
if (StringUtils.isEmpty(string)) {
url = ((QuoteReply) event.getMessage().get(1)).getSource().getOriginalMessage().contentToString();
} else {
JSONObject json = JSONObject.parseObject(string);
if (json.containsKey("ver")) {
url = json.getJSONObject("meta").getJSONObject("detail_1").getString("qqdocurl");
}
}
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;
}
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("事件结束");
}
}