新增B站视频下载功能 #7
@ -0,0 +1,29 @@
|
||||
package com.yutou.qqbot.Controllers;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.yutou.qqbot.models.BiliBili.BiliVideo;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
public class BiliBiliController {
|
||||
@RequestMapping("/bilibili/down.do")
|
||||
@ResponseBody
|
||||
public JSONObject downloadBili(String data) {
|
||||
new Thread(() -> {
|
||||
JSONObject json = JSONObject.parseObject(data);
|
||||
System.out.println("json = " + json);
|
||||
String url = json.getString("url");
|
||||
boolean downDanmu = json.containsKey("danmu") && "on".equals(json.getString("danmu"));
|
||||
boolean merge = json.containsKey("merge") && "on".equals(json.getString("merge"));
|
||||
BiliVideo video = new BiliVideo();
|
||||
video.downVideo(url, downDanmu, merge);
|
||||
}
|
||||
).start();
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("msg", "ok");
|
||||
json.put("code", 0);
|
||||
return json;
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class QQBotApplication {
|
||||
public static final String version="QQBot v.1.3.4";
|
||||
public static final String version="QQBot v.1.3.5";
|
||||
public static void main(String[] args) {
|
||||
System.out.println("version = " + version);
|
||||
SpringApplication.run(QQBotApplication.class, args);
|
||||
|
@ -1,16 +1,9 @@
|
||||
package com.yutou.qqbot.models.BiliBili;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import com.google.protobuf.TextFormat;
|
||||
import com.google.protobuf.UnknownFieldSet;
|
||||
import com.google.protobuf.util.JsonFormat;
|
||||
import com.yutou.qqbot.Annotations.UseModel;
|
||||
import com.yutou.qqbot.bilibili.*;
|
||||
import com.yutou.qqbot.interfaces.DownloadInterface;
|
||||
import com.yutou.qqbot.interfaces.ObjectInterface;
|
||||
import com.yutou.qqbot.models.Model;
|
||||
import com.yutou.qqbot.utlis.AppTools;
|
||||
@ -18,22 +11,18 @@ import com.yutou.qqbot.utlis.ConfigTools;
|
||||
import com.yutou.qqbot.utlis.HttpTools;
|
||||
import com.yutou.qqbot.utlis.StringUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
@UseModel
|
||||
public class BiliVideo extends Model {
|
||||
public String downloadPath = "tmp";
|
||||
List<DanmuData> danmuDatas = new ArrayList<>();
|
||||
long danmuNextTime = 0;
|
||||
|
||||
@Override
|
||||
public boolean isUserPublic() {
|
||||
@ -50,7 +39,11 @@ public class BiliVideo extends Model {
|
||||
return "B站视频下载";
|
||||
}
|
||||
|
||||
private void downVideo(String url) {
|
||||
public void downVideo(String url) {
|
||||
downVideo(url, true, false);
|
||||
}
|
||||
|
||||
public void downVideo(String url, boolean downDanmu, boolean merge) {
|
||||
if (!new BiliLogin().testLogin()) {
|
||||
System.err.println("未登录");
|
||||
return;
|
||||
@ -58,6 +51,7 @@ public class BiliVideo extends Model {
|
||||
if (!url.contains("?")) {
|
||||
url += "?";
|
||||
}
|
||||
danmuDatas.clear();
|
||||
JSONObject info = getVideoInfo(url);
|
||||
if (info.getInteger("code") == 0) {
|
||||
JSONObject infoData = info.getJSONObject("data");
|
||||
@ -96,6 +90,8 @@ public class BiliVideo extends Model {
|
||||
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"));
|
||||
@ -104,6 +100,11 @@ public class BiliVideo extends Model {
|
||||
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"));
|
||||
@ -112,17 +113,86 @@ public class BiliVideo extends Model {
|
||||
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");
|
||||
downDanmu(json.getLong("cid"), json.getLong("avid"), eps.getString("title"), 1);
|
||||
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;
|
||||
}
|
||||
@ -148,19 +218,8 @@ public class BiliVideo extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
private void downDanmu(long cid, long avid, String title, int segment_index) {
|
||||
System.out.println("cid = " + cid + ", avid = " + avid + ", title = " + title + ", segment_index = " + segment_index);
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("type", 1);
|
||||
json.put("oid", cid);
|
||||
json.put("avid", avid);
|
||||
json.put("segment_index", segment_index);
|
||||
private void downDanmu(String savePath, String title, List<VideoDanMu.DanmakuElem> danmuList) {
|
||||
try {
|
||||
List<VideoDanMu.DanmakuElem> tmp, danmuList = new ArrayList<>();
|
||||
while (!(tmp = getDanmu(json)).isEmpty()) {
|
||||
danmuList.addAll(tmp);
|
||||
json.put("segment_index",++segment_index);
|
||||
}
|
||||
AssTools tools = new AssTools(title);
|
||||
List<DanmuData> list = new ArrayList<>();
|
||||
for (VideoDanMu.DanmakuElem elem : danmuList) {
|
||||
@ -173,13 +232,27 @@ public class BiliVideo extends Model {
|
||||
list.add(danmuData);
|
||||
}
|
||||
tools.addDanmu(list);
|
||||
tools.saveDanmu("tmp");
|
||||
|
||||
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 = BiliBiliUtils.http("https://api.bilibili.com/x/v2/dm/web/seg.so?" + HttpTools.toUrlParams(json), BiliBiliUtils.HTTP.GET, null, BiliBiliUtils.RET_MODEL.BYTE);
|
||||
@ -217,7 +290,6 @@ public class BiliVideo extends Model {
|
||||
@Override
|
||||
public void out(String data) {
|
||||
super.out(data);
|
||||
System.out.println("data = " + data);
|
||||
videoFile.delete();
|
||||
audioFile.delete();
|
||||
}
|
||||
@ -265,6 +337,8 @@ public class BiliVideo extends Model {
|
||||
|
||||
public static void main(String[] args) {
|
||||
BiliVideo video = new BiliVideo();
|
||||
JSONObject login = new BiliLogin().login();
|
||||
System.out.println(login);
|
||||
//岚少 480
|
||||
//video.downVideo("https://www.bilibili.com/video/BV1Ps411m7pt?spm_id_from=333.999.0.0");
|
||||
//唐诱 合集
|
||||
@ -279,12 +353,14 @@ public class BiliVideo extends Model {
|
||||
//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/BV18L4y1H7rz?spm_id_from=333.999.0.0");// ep5
|
||||
video.downVideo("https://www.bilibili.com/video/BV18L4y1H7rz?spm_id_from=333.999.0.0", 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);
|
||||
System.out.println("事件结束");
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ public class AppTools {
|
||||
printWriter.close();
|
||||
return writer.toString();
|
||||
}
|
||||
public static void exec(String exec, ObjectInterface objectInterface, boolean isOutQQBot, boolean isInput) {
|
||||
public static String exec(String exec, ObjectInterface objectInterface, boolean isOutQQBot, boolean isInput) {
|
||||
try {
|
||||
Process process;
|
||||
if (AppTools.isRuntimeSystemOfWindow()) {
|
||||
@ -69,17 +69,20 @@ public class AppTools {
|
||||
}
|
||||
);
|
||||
}
|
||||
String ret;
|
||||
if (isInput) {
|
||||
processOut(process.getInputStream(), objectInterface, isOutQQBot);
|
||||
ret=processOut(process.getInputStream(), objectInterface, isOutQQBot);
|
||||
processOut(process.getErrorStream(),null,isOutQQBot);
|
||||
} else {
|
||||
processOut(process.getErrorStream(), objectInterface, isOutQQBot);
|
||||
ret=processOut(process.getErrorStream(), objectInterface, isOutQQBot);
|
||||
processOut(process.getInputStream(),null,isOutQQBot);
|
||||
}
|
||||
process.destroy();
|
||||
return ret;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static boolean isRuntimeSystemOfWindow() {
|
||||
@ -121,7 +124,7 @@ public class AppTools {
|
||||
public static void processOut(InputStream inputStream) {
|
||||
processOut(inputStream,null,true);
|
||||
}
|
||||
public static void processOut(InputStream inputStream, ObjectInterface objectInterface, boolean isOutQQBot){
|
||||
public static String processOut(InputStream inputStream, ObjectInterface objectInterface, boolean isOutQQBot){
|
||||
String tmp;
|
||||
StringBuilder str = new StringBuilder();
|
||||
try {
|
||||
@ -141,6 +144,7 @@ public class AppTools {
|
||||
if(isOutQQBot) {
|
||||
QQBotManager.getInstance().sendMessage(str.toString());
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
public static void sendServer(String title, String msg) {
|
||||
try {
|
||||
|
81
web/bilibili.html
Normal file
81
web/bilibili.html
Normal file
@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>BiliBili下载器</title>
|
||||
<link rel="stylesheet" href="layui/css/layui.css">
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="myDiy">
|
||||
<blockquote class="layui-elem-quote">BiliBili下载器
|
||||
</blockquote>
|
||||
|
||||
<br/><br/><br/>
|
||||
|
||||
<div class="layui-bg-gray layui-row layui-col-space15" id="card" style="padding: 30px;">
|
||||
|
||||
<form class="layui-form" action="">
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">视频地址</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="url" name="url" required lay-verify="required" placeholder="请输入URL" autocomplete="off" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label class="layui-form-label">复选框</label>
|
||||
<div class="layui-input-block">
|
||||
<input type="checkbox" name="danmu" title="下载弹幕" checked>
|
||||
<input type="checkbox" name="merge" title="合并合集">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button class="layui-btn" lay-submit lay-filter="formDemo">立即提交</button>
|
||||
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<script src="layui/layui.js"></script>
|
||||
<script src="layui/jquery-3.2.1.js"></script>
|
||||
<script>
|
||||
layui.use('form', function(){
|
||||
let form = layui.form;
|
||||
|
||||
//监听提交
|
||||
form.on('submit(formDemo)', function(_data){
|
||||
//layer.msg(JSON.stringify(data.field));
|
||||
$.post("/bilibili/down.do",{data:JSON.stringify(_data.field)},function (json) {
|
||||
layer.msg(json.msg)
|
||||
})
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.myDiy {
|
||||
width: 40%;
|
||||
height: 300px;
|
||||
margin-top: 10px;
|
||||
margin-left: 25%;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user