新增B站视频下载功能
新增弹幕转ass功能 优化B站网络请求头代码
This commit is contained in:
parent
edfd663743
commit
84c583ad38
11
pom.xml
11
pom.xml
@ -120,6 +120,17 @@
|
|||||||
<version>3.4.1</version>
|
<version>3.4.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.protobuf</groupId>
|
||||||
|
<artifactId>protobuf-java</artifactId>
|
||||||
|
<version>3.20.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.protobuf</groupId>
|
||||||
|
<artifactId>protobuf-java-util</artifactId>
|
||||||
|
<version>3.20.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
170
src/main/java/com/yutou/qqbot/bilibili/AssTools.java
Normal file
170
src/main/java/com/yutou/qqbot/bilibili/AssTools.java
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
package com.yutou.qqbot.bilibili;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class AssTools {
|
||||||
|
private final StringBuilder builder;
|
||||||
|
private String title;
|
||||||
|
private int y = 0;
|
||||||
|
private List<String> filters = new ArrayList<>();
|
||||||
|
private String alpha="80";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹幕转换ass
|
||||||
|
* @param title 标题
|
||||||
|
*/
|
||||||
|
public AssTools(String title) {
|
||||||
|
builder = new StringBuilder();
|
||||||
|
this.title=title;
|
||||||
|
initAssHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹幕过滤器
|
||||||
|
* @param filter 过滤词
|
||||||
|
*/
|
||||||
|
public void addFilter(String... filter) {
|
||||||
|
filters.addAll(Arrays.asList(filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 弹幕透明度
|
||||||
|
* @param alpha 0 完全不透明 255 完全透明
|
||||||
|
*/
|
||||||
|
public void setAlpha(int alpha){
|
||||||
|
this.alpha=Integer.toHexString(alpha);
|
||||||
|
}
|
||||||
|
private void addBuilder(String txt) {
|
||||||
|
builder.append(txt).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initAssHeader() {
|
||||||
|
addBuilder("[Script Info]");
|
||||||
|
addBuilder("Title: " + title);
|
||||||
|
addBuilder("Original Script: 本字幕由@yutou生成");
|
||||||
|
addBuilder("ScriptType: v4.00+");
|
||||||
|
addBuilder("Collisions: Normal");
|
||||||
|
addBuilder("PlayResX: 560");
|
||||||
|
addBuilder("PlayResY: 420");
|
||||||
|
addBuilder("Timer: 10.0000");
|
||||||
|
addBuilder("");
|
||||||
|
addBuilder("[V4+ Styles]");
|
||||||
|
addBuilder("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, ");
|
||||||
|
addBuilder("MarginL, MarginR, MarginV, Encoding");
|
||||||
|
addBuilder("Style: Fix,Microsoft YaHei UI,25,&H66FFFFFF,&H66FFFFFF,&H66000000,&H66000000,1,0,0,0,100,100,0,0,1,2,0,2,20,20,2,0");
|
||||||
|
addBuilder("Style: R2L,Microsoft YaHei UI,14,&H00FFFFFF,&H000000FF,&H00161616,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,20,20,20,1");
|
||||||
|
addBuilder("");
|
||||||
|
addBuilder("[Events]");
|
||||||
|
addBuilder("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存弹幕文件
|
||||||
|
* @param savePath 存储路径
|
||||||
|
* @return 存储结果
|
||||||
|
*/
|
||||||
|
public boolean saveDanmu(String savePath) {
|
||||||
|
System.out.println("savePath = " + savePath);
|
||||||
|
File file = new File(savePath+File.separator+title+".ass");
|
||||||
|
FileWriter writer;
|
||||||
|
try {
|
||||||
|
if (file.exists()) {
|
||||||
|
if (!file.delete()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean mkdirs = file.mkdirs();
|
||||||
|
boolean delete = file.delete();
|
||||||
|
if (!mkdirs || !delete) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!file.createNewFile()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
writer = new FileWriter(file);
|
||||||
|
writer.write(builder.toString());
|
||||||
|
writer.flush();
|
||||||
|
writer.close();
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}finally {
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 添加弹幕
|
||||||
|
* @param list 弹幕
|
||||||
|
*/
|
||||||
|
public void addDanmu(List<DanmuData> list) {
|
||||||
|
list.sort((o1, o2) -> {
|
||||||
|
if (o1.getTime() == o2.getTime()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return o1.getTime() < o2.getTime() ? -1 : 1;
|
||||||
|
});
|
||||||
|
for (DanmuData danmuData : list) {
|
||||||
|
addDanmu(danmuData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 添加弹幕
|
||||||
|
* @param danmuData 弹幕
|
||||||
|
*/
|
||||||
|
public void addDanmu(DanmuData danmuData) {
|
||||||
|
if (filters.contains(danmuData.getDanmu())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addY();
|
||||||
|
long _time = danmuData.getTime();
|
||||||
|
long h = TimeUnit.MILLISECONDS.toHours(_time);
|
||||||
|
long m = TimeUnit.MILLISECONDS.toMinutes(_time) % 60;
|
||||||
|
long s = TimeUnit.MILLISECONDS.toSeconds(_time) % 60;
|
||||||
|
String sTime = String.format("%s:%s:%s.0",
|
||||||
|
new DecimalFormat("00").format(h),
|
||||||
|
new DecimalFormat("00").format(m),
|
||||||
|
new DecimalFormat("00").format(s));
|
||||||
|
if (s >= 52) {
|
||||||
|
s = (s + 8) - 60;
|
||||||
|
m++;
|
||||||
|
} else {
|
||||||
|
s += 8;
|
||||||
|
}
|
||||||
|
String eTime = String.format("%s:%s:%s.0",
|
||||||
|
new DecimalFormat("00").format(h),
|
||||||
|
new DecimalFormat("00").format(m),
|
||||||
|
new DecimalFormat("00").format(s));
|
||||||
|
float x1, x2;
|
||||||
|
x1 = 560 + (danmuData.getDanmu().length() * 12.5f);
|
||||||
|
x2 = 0 - (danmuData.getDanmu().length() * 12.5f);
|
||||||
|
|
||||||
|
String ass = String.format("Dialogue: 0,%s,%s,R2L,,20,20,2,,{\\move(%.1f,%d,%.1f,%d)\\c&%s\\alpha&H%s}%s",
|
||||||
|
sTime,
|
||||||
|
eTime,
|
||||||
|
x1,
|
||||||
|
y,
|
||||||
|
x2,
|
||||||
|
y,
|
||||||
|
danmuData.getFontColorHex(),
|
||||||
|
alpha,
|
||||||
|
danmuData.getDanmu()
|
||||||
|
);
|
||||||
|
addBuilder(ass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void addY() {
|
||||||
|
y += 25;
|
||||||
|
if (y >= 420) {
|
||||||
|
y = 25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -2,18 +2,29 @@ package com.yutou.qqbot.bilibili;
|
|||||||
|
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.yutou.qqbot.utlis.ConfigTools;
|
import com.yutou.qqbot.interfaces.ObjectInterface;
|
||||||
import com.yutou.qqbot.utlis.StringUtils;
|
import com.yutou.qqbot.utlis.*;
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class BiliBiliUtils {
|
public class BiliBiliUtils {
|
||||||
private static long oldBiliBiliHttpTime = 0;
|
private static long oldBiliBiliHttpTime = 0;
|
||||||
|
|
||||||
|
public enum HTTP {
|
||||||
|
POST, GET
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RET_MODEL {
|
||||||
|
BYTE, JSON
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized static JSONObject http_get(String url) {
|
public synchronized static JSONObject http_get(String url) {
|
||||||
try {
|
try {
|
||||||
// Log.i("调用url = "+url);
|
// Log.i("调用url = "+url);
|
||||||
@ -49,7 +60,15 @@ public class BiliBiliUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static JSONObject http_post(String url, String body) {
|
public static JSONObject http_post(String url, String body) {
|
||||||
|
return http(url, HTTP.POST, body, RET_MODEL.JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T http(String url, HTTP model, String body, RET_MODEL ret_model) {
|
||||||
JSONObject json = null;
|
JSONObject json = null;
|
||||||
|
BufferedInputStream stream = null;
|
||||||
|
ByteArrayOutputStream outputStream = null;
|
||||||
|
OutputStream connectionOutputStream = null;
|
||||||
|
HttpURLConnection connection = null;
|
||||||
try {
|
try {
|
||||||
if (System.currentTimeMillis() - oldBiliBiliHttpTime < 1000) {
|
if (System.currentTimeMillis() - oldBiliBiliHttpTime < 1000) {
|
||||||
try {
|
try {
|
||||||
@ -59,49 +78,68 @@ public class BiliBiliUtils {
|
|||||||
}
|
}
|
||||||
oldBiliBiliHttpTime = System.currentTimeMillis();
|
oldBiliBiliHttpTime = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
HttpURLConnection connection = getBiliHttpPost(url, getCookie());
|
if (model == HTTP.POST) {
|
||||||
|
connection = getBiliHttpPost(url, getCookie());
|
||||||
|
} else {
|
||||||
|
connection = getBiliHttpGet(url, getCookie());
|
||||||
|
}
|
||||||
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||||
OutputStream connectionOutputStream = null;
|
|
||||||
if (!StringUtils.isEmpty(body)) {
|
if (!StringUtils.isEmpty(body)) {
|
||||||
connectionOutputStream = connection.getOutputStream();
|
connectionOutputStream = connection.getOutputStream();
|
||||||
connectionOutputStream.write(body.getBytes(StandardCharsets.UTF_8));
|
connectionOutputStream.write(body.getBytes(StandardCharsets.UTF_8));
|
||||||
connectionOutputStream.flush();
|
connectionOutputStream.flush();
|
||||||
}
|
}
|
||||||
connection.connect();
|
connection.connect();
|
||||||
if(connection.getResponseCode()==400){
|
if (connection.getResponseCode() == 400) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
BufferedInputStream stream = new BufferedInputStream(connection.getInputStream());
|
stream = new BufferedInputStream(connection.getInputStream());
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
outputStream = new ByteArrayOutputStream();
|
||||||
byte[] bytes = new byte[1024];
|
byte[] bytes = new byte[1024];
|
||||||
int len = 0, size;
|
int len = 0, size;
|
||||||
while ((len = stream.read(bytes)) != -1) {
|
while ((len = stream.read(bytes)) != -1) {
|
||||||
outputStream.write(bytes, 0, len);
|
outputStream.write(bytes, 0, len);
|
||||||
outputStream.flush();
|
outputStream.flush();
|
||||||
}
|
}
|
||||||
|
if (ret_model == RET_MODEL.BYTE) {
|
||||||
|
return (T) outputStream.toByteArray();
|
||||||
|
}
|
||||||
String str = outputStream.toString(StandardCharsets.UTF_8);
|
String str = outputStream.toString(StandardCharsets.UTF_8);
|
||||||
outputStream.close();
|
|
||||||
try {
|
try {
|
||||||
json = JSON.parseObject(str);
|
json = JSON.parseObject(str);
|
||||||
json.put("cookie", connection.getHeaderField("Set-Cookie"));
|
json.put("cookie", connection.getHeaderField("Set-Cookie"));
|
||||||
return json;
|
return (T) json;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
json = new JSONObject();
|
json = new JSONObject();
|
||||||
json.put("html", str);
|
json.put("html", str);
|
||||||
json.put("cookie", connection.getHeaderField("Set-Cookie"));
|
json.put("cookie", connection.getHeaderField("Set-Cookie"));
|
||||||
return json;
|
return (T) json;
|
||||||
} finally {
|
|
||||||
stream.close();
|
|
||||||
if (connectionOutputStream != null) {
|
|
||||||
connectionOutputStream.close();
|
|
||||||
}
|
|
||||||
connection.disconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (stream != null) {
|
||||||
|
stream.close();
|
||||||
}
|
}
|
||||||
return json;
|
if (outputStream != null) {
|
||||||
|
outputStream.close();
|
||||||
|
}
|
||||||
|
if (connectionOutputStream != null) {
|
||||||
|
connectionOutputStream.close();
|
||||||
|
}
|
||||||
|
if (connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getCookie() {
|
public static String getCookie() {
|
||||||
@ -150,15 +188,101 @@ public class BiliBiliUtils {
|
|||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static File download(final String url, final String saveName, boolean isProxy) {
|
||||||
|
File jar = null;
|
||||||
|
try {
|
||||||
|
File savePath = new File(HttpTools.downloadPath+saveName);
|
||||||
|
Proxy proxy = null;
|
||||||
|
if (!savePath.exists()) {
|
||||||
|
savePath.mkdirs();
|
||||||
|
}
|
||||||
|
savePath.delete();
|
||||||
|
Log.i("DOWNLOAD", "下载文件:" + url + " 保存文件:" + saveName);
|
||||||
|
if (isProxy) {
|
||||||
|
proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
|
||||||
|
}
|
||||||
|
HttpsURLConnection connection;
|
||||||
|
if (isProxy) {
|
||||||
|
connection = (HttpsURLConnection) new URL(url).openConnection(proxy);
|
||||||
|
} else {
|
||||||
|
connection = (HttpsURLConnection) new URL(url).openConnection();
|
||||||
|
}
|
||||||
|
setConnection(getCookie(), connection);
|
||||||
|
|
||||||
|
InputStream inputStream = connection.getInputStream();
|
||||||
|
jar = new File(HttpTools.downloadPath + saveName + "_tmp.tmp");
|
||||||
|
jar.createNewFile();
|
||||||
|
Log.i("DOWNLOAD", "临时保存文件:" + jar.getAbsolutePath());
|
||||||
|
OutputStream outputStream = new FileOutputStream(jar);
|
||||||
|
byte[] bytes = new byte[1024];
|
||||||
|
double size = connection.getContentLength();
|
||||||
|
double downSize = 0;
|
||||||
|
int len;
|
||||||
|
while ((len = inputStream.read(bytes)) > 0) {
|
||||||
|
outputStream.write(bytes, 0, len);
|
||||||
|
downSize += len;
|
||||||
|
}
|
||||||
|
outputStream.close();
|
||||||
|
inputStream.close();
|
||||||
|
File oldJar = new File(HttpTools.downloadPath + saveName);
|
||||||
|
if (oldJar.exists()) {
|
||||||
|
oldJar.delete();
|
||||||
|
}
|
||||||
|
jar.renameTo(oldJar);
|
||||||
|
Log.i("DOWNLOAD", "实际保存:" + oldJar.getAbsolutePath() + " " + oldJar.getName());
|
||||||
|
return oldJar;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
if (jar != null) {
|
||||||
|
jar.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void download_ffmpeg(final List<String> url, final String saveName) {
|
||||||
|
new Thread(() -> {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append(ConfigTools.load(ConfigTools.CONFIG, "ffmpeg", String.class)).append(" ");
|
||||||
|
/* builder.append("-user_agent").append(" ");
|
||||||
|
builder.append("\"").append("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36 Referer:https://live.bilibili.com").append("\"").append(" ");
|
||||||
|
builder.append("-cookies").append(" ");
|
||||||
|
builder.append("\"").append(getCookie()).append("\"").append(" ");
|
||||||
|
builder.append("-headers").append(" ");
|
||||||
|
builder.append("\"").append("Referer:https://live.bilibili.com").append("\"").append(" ");*/
|
||||||
|
for (String _url : url) {
|
||||||
|
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(new File(HttpTools.downloadPath + saveName + ".mp4").getAbsolutePath()).append(" ");
|
||||||
|
System.out.println(builder);
|
||||||
|
AppTools.exec(builder.toString(), new ObjectInterface() {
|
||||||
|
@Override
|
||||||
|
public void out(String data) {
|
||||||
|
super.out(data);
|
||||||
|
System.out.println("data = " + data);
|
||||||
|
}
|
||||||
|
}, false, false);
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void setConnection(String cookie, HttpURLConnection connection) {
|
private static void setConnection(String cookie, HttpURLConnection connection) {
|
||||||
connection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
connection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
||||||
connection.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.8");
|
connection.addRequestProperty("Accept-Language", "zh-CN,zh;q=0.8");
|
||||||
connection.setRequestProperty("Cache-Control", "max-age=0");
|
connection.addRequestProperty("Cache-Control", "max-age=0");
|
||||||
//connection.setRequestProperty("Referer", ".bilibili.com");
|
connection.setRequestProperty("Referer", "https://www.bilibili.com");
|
||||||
connection.setRequestProperty("Connection", "keep-alive");
|
connection.addRequestProperty("Connection", "keep-alive");
|
||||||
connection.setRequestProperty("Upgrade-Insecure-Requests", "1");
|
connection.addRequestProperty("Upgrade-Insecure-Requests", "1");
|
||||||
connection.setRequestProperty("Cookie", cookie);
|
connection.addRequestProperty("Cookie", cookie);
|
||||||
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36");
|
connection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JSONObject getLoginInfo() {
|
public static JSONObject getLoginInfo() {
|
||||||
|
26
src/main/java/com/yutou/qqbot/bilibili/DanmuData.java
Normal file
26
src/main/java/com/yutou/qqbot/bilibili/DanmuData.java
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package com.yutou.qqbot.bilibili;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DanmuData {
|
||||||
|
private int id;
|
||||||
|
private int model;//1~3 滚动弹幕 4 底端弹幕 5 顶端弹幕 6 逆向弹幕 7 精准定位 8 高级弹幕
|
||||||
|
private int fontSize;
|
||||||
|
private int fontColor;
|
||||||
|
private long time;
|
||||||
|
private String uCode;
|
||||||
|
private String danmu;
|
||||||
|
private long uid;
|
||||||
|
private String uname;
|
||||||
|
|
||||||
|
public Date getTimeDate() {
|
||||||
|
return new Date(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFontColorHex() {
|
||||||
|
return Integer.toHexString(fontColor);
|
||||||
|
}
|
||||||
|
}
|
57620
src/main/java/com/yutou/qqbot/bilibili/VideoDanMu.java
Normal file
57620
src/main/java/com/yutou/qqbot/bilibili/VideoDanMu.java
Normal file
File diff suppressed because it is too large
Load Diff
271
src/main/java/com/yutou/qqbot/models/BiliBili/BiliVideo.java
Normal file
271
src/main/java/com/yutou/qqbot/models/BiliBili/BiliVideo.java
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
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.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;
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
public class BiliVideo extends Model {
|
||||||
|
public String downloadPath = "tmp";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUserPublic() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getUsePowers() {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModelName() {
|
||||||
|
return "B站视频下载";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downVideo(String url) {
|
||||||
|
if (!new BiliLogin().testLogin()) {
|
||||||
|
System.err.println("未登录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!url.contains("?")) {
|
||||||
|
url += "?";
|
||||||
|
}
|
||||||
|
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("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 {
|
||||||
|
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"));
|
||||||
|
downVideo(json, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (tmp.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
JSONObject http = BiliBiliUtils.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 = BiliBiliUtils.download(video.getString("baseUrl"), eps.getString("title") + "_video.mp4", false);
|
||||||
|
if (videoFile == null) {
|
||||||
|
downVideo(json, eps);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File audioFile = BiliBiliUtils.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(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);
|
||||||
|
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);
|
||||||
|
try {
|
||||||
|
VideoDanMu.DmSegMobileReply parse = VideoDanMu.DmSegMobileReply.parseFrom(http);
|
||||||
|
AssTools tools = new AssTools(title);
|
||||||
|
List<DanmuData> list = new ArrayList<>();
|
||||||
|
for (VideoDanMu.DanmakuElem elem : parse.getElemsList()) {
|
||||||
|
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);
|
||||||
|
tools.saveDanmu("tmp");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
System.out.println("data = " + data);
|
||||||
|
videoFile.delete();
|
||||||
|
audioFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
}, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject getVideoInfo(String url) {
|
||||||
|
if (!new BiliLogin().testLogin()) {
|
||||||
|
System.err.println("未登录");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JSONObject json = buildJSON(url);
|
||||||
|
if (json != null) {
|
||||||
|
return BiliBiliUtils.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();
|
||||||
|
//岚少 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.337.search-card.all.click");
|
||||||
|
video.downVideo("https://www.bilibili.com/video/BV18L4y1H7rz?spm_id_from=333.999.0.0");
|
||||||
|
// int a=16|2048;
|
||||||
|
// System.out.println("a = " + a);
|
||||||
|
//video.downDanmu(428855000L,976216102L,"【都市_情感】《唐可可的诱惑》第一集",1);
|
||||||
|
}
|
||||||
|
}
|
@ -68,10 +68,10 @@ public class AppTools {
|
|||||||
}
|
}
|
||||||
if (isInput) {
|
if (isInput) {
|
||||||
processOut(process.getInputStream(), objectInterface, isOutQQBot);
|
processOut(process.getInputStream(), objectInterface, isOutQQBot);
|
||||||
processOut(process.getErrorStream());
|
processOut(process.getErrorStream(),null,isOutQQBot);
|
||||||
} else {
|
} else {
|
||||||
processOut(process.getErrorStream(), objectInterface, isOutQQBot);
|
processOut(process.getErrorStream(), objectInterface, isOutQQBot);
|
||||||
processOut(process.getInputStream());
|
processOut(process.getInputStream(),null,isOutQQBot);
|
||||||
}
|
}
|
||||||
process.destroy();
|
process.destroy();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -1,7 +1,21 @@
|
|||||||
package com.yutou.qqbot.utlis;
|
package com.yutou.qqbot.utlis;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
public class StringUtils {
|
public class StringUtils {
|
||||||
public static boolean isEmpty(String str){
|
public static boolean isEmpty(String str){
|
||||||
return str == null || str.trim().length() == 0;
|
return str == null || str.trim().length() == 0;
|
||||||
}
|
}
|
||||||
|
public static String toSaveFileName(String str){
|
||||||
|
return str.replace("\\","_")
|
||||||
|
.replace("/","_")
|
||||||
|
.replace(":","_")
|
||||||
|
.replace("*","_")
|
||||||
|
.replace("?","_")
|
||||||
|
.replace("\"","_")
|
||||||
|
.replace("<","_")
|
||||||
|
.replace(">","_")
|
||||||
|
.replace("|","_")
|
||||||
|
.replace("$(File.separator)", File.separator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
549
src/main/resources/proto/dm.proto
Normal file
549
src/main/resources/proto/dm.proto
Normal file
@ -0,0 +1,549 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package com.yutou.qqbot.bilibili;
|
||||||
|
option java_package= "com.yutou.qqbot.bilibili";
|
||||||
|
option java_outer_classname="VideoDanMu";
|
||||||
|
|
||||||
|
//弹幕
|
||||||
|
service DM {
|
||||||
|
// 获取分段弹幕
|
||||||
|
rpc DmSegMobile (DmSegMobileReq) returns (DmSegMobileReply);
|
||||||
|
// 客户端弹幕元数据 字幕、分段、防挡蒙版等
|
||||||
|
rpc DmView (DmViewReq) returns (DmViewReply);
|
||||||
|
// 修改弹幕配置
|
||||||
|
rpc DmPlayerConfig (DmPlayerConfigReq) returns (Response);
|
||||||
|
// ott弹幕列表
|
||||||
|
rpc DmSegOtt(DmSegOttReq) returns(DmSegOttReply);
|
||||||
|
// SDK弹幕列表
|
||||||
|
rpc DmSegSDK(DmSegSDKReq) returns(DmSegSDKReply);
|
||||||
|
//
|
||||||
|
rpc DmExpoReport (DmExpoReportReq) returns (DmExpoReportRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message BuzzwordConfig {
|
||||||
|
//
|
||||||
|
repeated BuzzwordShowConfig keywords = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message BuzzwordShowConfig {
|
||||||
|
//
|
||||||
|
string name = 1;
|
||||||
|
//
|
||||||
|
string schema = 2;
|
||||||
|
//
|
||||||
|
int32 source = 3;
|
||||||
|
//
|
||||||
|
int64 id = 4;
|
||||||
|
//
|
||||||
|
int64 buzzword_id = 5;
|
||||||
|
//
|
||||||
|
int32 schema_type = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 互动弹幕条目信息
|
||||||
|
message CommandDm {
|
||||||
|
// 弹幕id
|
||||||
|
int64 id = 1;
|
||||||
|
// 对象视频cid
|
||||||
|
int64 oid = 2;
|
||||||
|
// 发送者mid
|
||||||
|
string mid = 3;
|
||||||
|
// 互动弹幕指令
|
||||||
|
string command = 4;
|
||||||
|
// 互动弹幕正文
|
||||||
|
string content = 5;
|
||||||
|
// 出现时间
|
||||||
|
int32 progress = 6;
|
||||||
|
// 创建时间
|
||||||
|
string ctime = 7;
|
||||||
|
// 发布时间
|
||||||
|
string mtime = 8;
|
||||||
|
// 扩展json数据
|
||||||
|
string extra = 9;
|
||||||
|
// 弹幕id str类型
|
||||||
|
string idStr = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕属性位值
|
||||||
|
enum DMAttrBit {
|
||||||
|
DMAttrBitProtect = 0; // 保护弹幕
|
||||||
|
DMAttrBitFromLive = 1; // 直播弹幕
|
||||||
|
DMAttrHighLike = 2; // 高赞弹幕
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕ai云屏蔽列表
|
||||||
|
message DanmakuAIFlag {
|
||||||
|
// 弹幕ai云屏蔽条目
|
||||||
|
repeated DanmakuFlag dm_flags = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕条目
|
||||||
|
message DanmakuElem {
|
||||||
|
// 弹幕dmid
|
||||||
|
int64 id = 1;
|
||||||
|
// 弹幕出现位置(单位ms)
|
||||||
|
int32 progress = 2;
|
||||||
|
// 弹幕类型
|
||||||
|
int32 mode = 3;
|
||||||
|
// 弹幕字号
|
||||||
|
int32 fontsize = 4;
|
||||||
|
// 弹幕颜色
|
||||||
|
uint32 color = 5;
|
||||||
|
// 发送着mid hash
|
||||||
|
string midHash = 6;
|
||||||
|
// 弹幕正文
|
||||||
|
string content = 7;
|
||||||
|
// 发送时间
|
||||||
|
int64 ctime = 8;
|
||||||
|
// 权重 区间:[1,10]
|
||||||
|
int32 weight = 9;
|
||||||
|
// 动作
|
||||||
|
string action = 10;
|
||||||
|
// 弹幕池
|
||||||
|
int32 pool = 11;
|
||||||
|
// 弹幕dmid str
|
||||||
|
string idStr = 12;
|
||||||
|
// 弹幕属性位(bin求AND)
|
||||||
|
// bit0:保护 bit1:直播 bit2:高赞
|
||||||
|
int32 attr = 13;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕ai云屏蔽条目
|
||||||
|
message DanmakuFlag {
|
||||||
|
int64 dmid = 1; // 弹幕dmid
|
||||||
|
uint32 flag = 2; // 评分
|
||||||
|
}
|
||||||
|
|
||||||
|
// 云屏蔽配置信息
|
||||||
|
message DanmakuFlagConfig {
|
||||||
|
// 云屏蔽等级
|
||||||
|
int32 rec_flag = 1;
|
||||||
|
// 云屏蔽文案
|
||||||
|
string rec_text = 2;
|
||||||
|
// 云屏蔽开关
|
||||||
|
int32 rec_switch = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕默认配置
|
||||||
|
message DanmuDefaultPlayerConfig {
|
||||||
|
bool player_danmaku_use_default_config = 1; // 是否使用推荐弹幕设置
|
||||||
|
bool player_danmaku_ai_recommended_switch = 4; // 是否开启智能云屏蔽
|
||||||
|
int32 player_danmaku_ai_recommended_level = 5; // 智能云屏蔽等级
|
||||||
|
bool player_danmaku_blocktop = 6; // 是否屏蔽顶端弹幕
|
||||||
|
bool player_danmaku_blockscroll = 7; // 是否屏蔽滚动弹幕
|
||||||
|
bool player_danmaku_blockbottom = 8; // 是否屏蔽底端弹幕
|
||||||
|
bool player_danmaku_blockcolorful = 9; // 是否屏蔽彩色弹幕
|
||||||
|
bool player_danmaku_blockrepeat = 10; // 是否屏蔽重复弹幕
|
||||||
|
bool player_danmaku_blockspecial = 11; // 是否屏蔽高级弹幕
|
||||||
|
float player_danmaku_opacity = 12; // 弹幕不透明度
|
||||||
|
float player_danmaku_scalingfactor = 13; // 弹幕缩放比例
|
||||||
|
float player_danmaku_domain = 14; // 弹幕显示区域
|
||||||
|
int32 player_danmaku_speed = 15; // 弹幕速度
|
||||||
|
bool inline_player_danmaku_switch = 16; // 是否开启弹幕
|
||||||
|
int32 player_danmaku_senior_mode_switch = 17; //
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕配置
|
||||||
|
message DanmuPlayerConfig {
|
||||||
|
bool player_danmaku_switch = 1; // 是否开启弹幕
|
||||||
|
bool player_danmaku_switch_save = 2; // 是否记录弹幕开关设置
|
||||||
|
bool player_danmaku_use_default_config = 3; // 是否使用推荐弹幕设置
|
||||||
|
bool player_danmaku_ai_recommended_switch = 4; // 是否开启智能云屏蔽
|
||||||
|
int32 player_danmaku_ai_recommended_level = 5; // 智能云屏蔽等级
|
||||||
|
bool player_danmaku_blocktop = 6; // 是否屏蔽顶端弹幕
|
||||||
|
bool player_danmaku_blockscroll = 7; // 是否屏蔽滚动弹幕
|
||||||
|
bool player_danmaku_blockbottom = 8; // 是否屏蔽底端弹幕
|
||||||
|
bool player_danmaku_blockcolorful = 9; // 是否屏蔽彩色弹幕
|
||||||
|
bool player_danmaku_blockrepeat = 10; // 是否屏蔽重复弹幕
|
||||||
|
bool player_danmaku_blockspecial = 11; // 是否屏蔽高级弹幕
|
||||||
|
float player_danmaku_opacity = 12; // 弹幕不透明度
|
||||||
|
float player_danmaku_scalingfactor = 13; // 弹幕缩放比例
|
||||||
|
float player_danmaku_domain = 14; // 弹幕显示区域
|
||||||
|
int32 player_danmaku_speed = 15; // 弹幕速度
|
||||||
|
bool player_danmaku_enableblocklist = 16; // 是否开启屏蔽列表
|
||||||
|
bool inline_player_danmaku_switch = 17; // 是否开启弹幕
|
||||||
|
int32 inline_player_danmaku_config = 18; //
|
||||||
|
int32 player_danmaku_ios_switch_save = 19; //
|
||||||
|
int32 player_danmaku_senior_mode_switch = 20; //
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕显示区域自动配置
|
||||||
|
message DanmuPlayerDynamicConfig {
|
||||||
|
// 时间
|
||||||
|
int32 progress = 1;
|
||||||
|
// 弹幕显示区域
|
||||||
|
float player_danmaku_domain = 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕配置信息
|
||||||
|
message DanmuPlayerViewConfig {
|
||||||
|
// 弹幕默认配置
|
||||||
|
DanmuDefaultPlayerConfig danmuku_default_player_config = 1;
|
||||||
|
// 弹幕用户配置
|
||||||
|
DanmuPlayerConfig danmuku_player_config = 2;
|
||||||
|
// 弹幕显示区域自动配置列表
|
||||||
|
repeated DanmuPlayerDynamicConfig danmuku_player_dynamic_config = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// web端用户弹幕配置
|
||||||
|
message DanmuWebPlayerConfig {
|
||||||
|
bool dm_switch = 1; // 是否开启弹幕
|
||||||
|
bool ai_switch = 2; // 是否开启智能云屏蔽
|
||||||
|
int32 ai_level = 3; // 智能云屏蔽等级
|
||||||
|
bool blocktop = 4; // 是否屏蔽顶端弹幕
|
||||||
|
bool blockscroll = 5; // 是否屏蔽滚动弹幕
|
||||||
|
bool blockbottom = 6; // 是否屏蔽底端弹幕
|
||||||
|
bool blockcolor = 7; // 是否屏蔽彩色弹幕
|
||||||
|
bool blockspecial = 8; // 是否屏蔽重复弹幕
|
||||||
|
bool preventshade = 9; //
|
||||||
|
bool dmask = 10; //
|
||||||
|
float opacity = 11; //
|
||||||
|
int32 dmarea = 12; //
|
||||||
|
float speedplus = 13; //
|
||||||
|
float fontsize = 14; // 弹幕字号
|
||||||
|
bool screensync = 15; //
|
||||||
|
bool speedsync = 16; //
|
||||||
|
string fontfamily = 17; //
|
||||||
|
bool bold = 18; // 是否使用加粗
|
||||||
|
int32 fontborder = 19; //
|
||||||
|
string draw_type = 20; // 弹幕渲染类型
|
||||||
|
int32 senior_mode_switch = 21; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message DmExpoReportReq {
|
||||||
|
//
|
||||||
|
string session_id = 1;
|
||||||
|
//
|
||||||
|
int64 oid = 2;
|
||||||
|
//
|
||||||
|
string spmid = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message DmExpoReportRes {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改弹幕配置-请求
|
||||||
|
message DmPlayerConfigReq {
|
||||||
|
int64 ts = 1; //
|
||||||
|
PlayerDanmakuSwitch switch = 2; // 是否开启弹幕
|
||||||
|
PlayerDanmakuSwitchSave switch_save = 3; // 是否记录弹幕开关设置
|
||||||
|
PlayerDanmakuUseDefaultConfig use_default_config = 4; // 是否使用推荐弹幕设置
|
||||||
|
PlayerDanmakuAiRecommendedSwitch ai_recommended_switch = 5; // 是否开启智能云屏蔽
|
||||||
|
PlayerDanmakuAiRecommendedLevel ai_recommended_level = 6; // 智能云屏蔽等级
|
||||||
|
PlayerDanmakuBlocktop blocktop = 7; // 是否屏蔽顶端弹幕
|
||||||
|
PlayerDanmakuBlockscroll blockscroll = 8; // 是否屏蔽滚动弹幕
|
||||||
|
PlayerDanmakuBlockbottom blockbottom = 9; // 是否屏蔽底端弹幕
|
||||||
|
PlayerDanmakuBlockcolorful blockcolorful = 10; // 是否屏蔽彩色弹幕
|
||||||
|
PlayerDanmakuBlockrepeat blockrepeat = 11; // 是否屏蔽重复弹幕
|
||||||
|
PlayerDanmakuBlockspecial blockspecial = 12; // 是否屏蔽高级弹幕
|
||||||
|
PlayerDanmakuOpacity opacity = 13; // 弹幕不透明度
|
||||||
|
PlayerDanmakuScalingfactor scalingfactor = 14; // 弹幕缩放比例
|
||||||
|
PlayerDanmakuDomain domain = 15; // 弹幕显示区域
|
||||||
|
PlayerDanmakuSpeed speed = 16; // 弹幕速度
|
||||||
|
PlayerDanmakuEnableblocklist enableblocklist = 17; // 是否开启屏蔽列表
|
||||||
|
InlinePlayerDanmakuSwitch inlinePlayerDanmakuSwitch = 18; // 是否开启弹幕
|
||||||
|
PlayerDanmakuSeniorModeSwitch senior_mode_switch = 19; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message DmSegConfig {
|
||||||
|
//
|
||||||
|
int64 page_size = 1;
|
||||||
|
//
|
||||||
|
int64 total = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取弹幕-响应
|
||||||
|
message DmSegMobileReply {
|
||||||
|
// 弹幕列表
|
||||||
|
repeated DanmakuElem elems = 1;
|
||||||
|
// 是否已关闭弹幕
|
||||||
|
// 0:未关闭 1:已关闭
|
||||||
|
int32 state = 2;
|
||||||
|
// 弹幕云屏蔽ai评分值
|
||||||
|
DanmakuAIFlag ai_flag = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取弹幕-请求
|
||||||
|
message DmSegMobileReq {
|
||||||
|
// 稿件avid/漫画epid
|
||||||
|
int64 pid = 1;
|
||||||
|
// 视频cid/漫画cid
|
||||||
|
int64 oid = 2;
|
||||||
|
// 弹幕类型
|
||||||
|
// 1:视频 2:漫画
|
||||||
|
int32 type = 3;
|
||||||
|
// 分段(6min)
|
||||||
|
int64 segment_index = 4;
|
||||||
|
// 是否青少年模式
|
||||||
|
int32 teenagers_mode = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ott弹幕列表-响应
|
||||||
|
message DmSegOttReply {
|
||||||
|
// 是否已关闭弹幕
|
||||||
|
// 0:未关闭 1:已关闭
|
||||||
|
bool closed = 1;
|
||||||
|
// 弹幕列表
|
||||||
|
repeated DanmakuElem elems = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ott弹幕列表-请求
|
||||||
|
message DmSegOttReq {
|
||||||
|
// 稿件avid/漫画epid
|
||||||
|
int64 pid = 1;
|
||||||
|
// 视频cid/漫画cid
|
||||||
|
int64 oid = 2;
|
||||||
|
// 弹幕类型
|
||||||
|
// 1:视频 2:漫画
|
||||||
|
int32 type = 3;
|
||||||
|
// 分段(6min)
|
||||||
|
int64 segment_index = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕SDK-响应
|
||||||
|
message DmSegSDKReply {
|
||||||
|
// 是否已关闭弹幕
|
||||||
|
// 0:未关闭 1:已关闭
|
||||||
|
bool closed = 1;
|
||||||
|
// 弹幕列表
|
||||||
|
repeated DanmakuElem elems = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕SDK-请求
|
||||||
|
message DmSegSDKReq {
|
||||||
|
// 稿件avid/漫画epid
|
||||||
|
int64 pid = 1;
|
||||||
|
// 视频cid/漫画cid
|
||||||
|
int64 oid = 2;
|
||||||
|
// 弹幕类型
|
||||||
|
// 1:视频 2:漫画
|
||||||
|
int32 type = 3;
|
||||||
|
// 分段(6min)
|
||||||
|
int64 segment_index = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端弹幕元数据-响应
|
||||||
|
message DmViewReply {
|
||||||
|
// 是否已关闭弹幕
|
||||||
|
// 0:未关闭 1:已关闭
|
||||||
|
bool closed = 1;
|
||||||
|
// 智能防挡弹幕蒙版信息
|
||||||
|
VideoMask mask = 2;
|
||||||
|
// 视频字幕
|
||||||
|
VideoSubtitle subtitle = 3;
|
||||||
|
// 高级弹幕专包url(bfs)
|
||||||
|
repeated string special_dms = 4;
|
||||||
|
// 云屏蔽配置信息
|
||||||
|
DanmakuFlagConfig ai_flag = 5;
|
||||||
|
// 弹幕配置信息
|
||||||
|
DanmuPlayerViewConfig player_config = 6;
|
||||||
|
// 弹幕发送框样式
|
||||||
|
int32 send_box_style = 7;
|
||||||
|
// 是否允许
|
||||||
|
bool allow = 8;
|
||||||
|
// check box 是否展示
|
||||||
|
string check_box = 9;
|
||||||
|
// check box 展示文本
|
||||||
|
string check_box_show_msg = 10;
|
||||||
|
// 展示文案
|
||||||
|
string text_placeholder = 11;
|
||||||
|
// 弹幕输入框文案
|
||||||
|
string input_placeholder = 12;
|
||||||
|
// 用户举报弹幕 cid维度屏蔽的正则规则
|
||||||
|
repeated string report_filter_content = 13;
|
||||||
|
//
|
||||||
|
ExpoReport expo_report = 14;
|
||||||
|
//
|
||||||
|
BuzzwordConfig buzzword_config = 15;
|
||||||
|
//
|
||||||
|
repeated Expressions expressions = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端弹幕元数据-请求
|
||||||
|
message DmViewReq {
|
||||||
|
// 稿件avid/漫画epid
|
||||||
|
int64 pid = 1;
|
||||||
|
// 视频cid/漫画cid
|
||||||
|
int64 oid = 2;
|
||||||
|
// 弹幕类型
|
||||||
|
// 1:视频 2:漫画
|
||||||
|
int32 type = 3;
|
||||||
|
// 页面spm
|
||||||
|
string spmid = 4;
|
||||||
|
// 是否冷启
|
||||||
|
int32 is_hard_boot = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// web端弹幕元数据-响应
|
||||||
|
message DmWebViewReply {
|
||||||
|
// 是否已关闭弹幕
|
||||||
|
// 0:未关闭 1:已关闭
|
||||||
|
int32 state = 1;
|
||||||
|
//
|
||||||
|
string text = 2;
|
||||||
|
//
|
||||||
|
string text_side = 3;
|
||||||
|
// 分段弹幕配置
|
||||||
|
DmSegConfig dm_sge = 4;
|
||||||
|
// 云屏蔽配置信息
|
||||||
|
DanmakuFlagConfig flag = 5;
|
||||||
|
// 高级弹幕专包url(bfs)
|
||||||
|
repeated string special_dms = 6;
|
||||||
|
// check box 是否展示
|
||||||
|
bool check_box = 7;
|
||||||
|
// 弹幕数
|
||||||
|
int64 count = 8;
|
||||||
|
// 互动弹幕
|
||||||
|
repeated CommandDm commandDms = 9;
|
||||||
|
// 用户弹幕配置
|
||||||
|
DanmuWebPlayerConfig player_config = 10;
|
||||||
|
// 用户举报弹幕 cid维度屏蔽
|
||||||
|
repeated string report_filter_content = 11;
|
||||||
|
//
|
||||||
|
repeated Expressions expressions = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message ExpoReport {
|
||||||
|
//
|
||||||
|
bool should_report_at_end = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message Expression {
|
||||||
|
//
|
||||||
|
repeated string keyword = 1;
|
||||||
|
//
|
||||||
|
string url = 2;
|
||||||
|
//
|
||||||
|
repeated Period period = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message Expressions {
|
||||||
|
//
|
||||||
|
repeated Expression data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message Period {
|
||||||
|
//
|
||||||
|
int64 start = 1;
|
||||||
|
//
|
||||||
|
int64 end = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否开启弹幕
|
||||||
|
message InlinePlayerDanmakuSwitch {bool value = 1;}
|
||||||
|
// 智能云屏蔽等级
|
||||||
|
message PlayerDanmakuAiRecommendedLevel {bool value = 1;}
|
||||||
|
// 是否开启智能云屏蔽
|
||||||
|
message PlayerDanmakuAiRecommendedSwitch {bool value = 1;}
|
||||||
|
// 是否屏蔽底端弹幕
|
||||||
|
message PlayerDanmakuBlockbottom {bool value = 1;}
|
||||||
|
// 是否屏蔽彩色弹幕
|
||||||
|
message PlayerDanmakuBlockcolorful {bool value = 1;}
|
||||||
|
// 是否屏蔽重复弹幕
|
||||||
|
message PlayerDanmakuBlockrepeat {bool value = 1;}
|
||||||
|
// 是否屏蔽滚动弹幕
|
||||||
|
message PlayerDanmakuBlockscroll {bool value = 1;}
|
||||||
|
// 是否屏蔽高级弹幕
|
||||||
|
message PlayerDanmakuBlockspecial {bool value = 1;}
|
||||||
|
// 是否屏蔽顶端弹幕
|
||||||
|
message PlayerDanmakuBlocktop {bool value = 1;}
|
||||||
|
// 弹幕显示区域
|
||||||
|
message PlayerDanmakuDomain {float value = 1;}
|
||||||
|
// 是否开启屏蔽列表
|
||||||
|
message PlayerDanmakuEnableblocklist {bool value = 1;}
|
||||||
|
// 弹幕不透明度
|
||||||
|
message PlayerDanmakuOpacity {float value = 1;}
|
||||||
|
// 弹幕缩放比例
|
||||||
|
message PlayerDanmakuScalingfactor {float value = 1;}
|
||||||
|
//
|
||||||
|
message PlayerDanmakuSeniorModeSwitch {int32 value = 1;}
|
||||||
|
// 弹幕速度
|
||||||
|
message PlayerDanmakuSpeed {int32 value = 1;}
|
||||||
|
// 是否开启弹幕
|
||||||
|
message PlayerDanmakuSwitch {bool value = 1;bool canIgnore = 2;}
|
||||||
|
// 是否记录弹幕开关设置
|
||||||
|
message PlayerDanmakuSwitchSave {bool value = 1;}
|
||||||
|
// 是否使用推荐弹幕设置
|
||||||
|
message PlayerDanmakuUseDefaultConfig {bool value = 1;}
|
||||||
|
|
||||||
|
// 修改弹幕配置-响应
|
||||||
|
message Response {
|
||||||
|
//
|
||||||
|
int32 code = 1;
|
||||||
|
//
|
||||||
|
string message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单个字幕信息
|
||||||
|
message SubtitleItem {
|
||||||
|
// 字幕id
|
||||||
|
int64 id = 1;
|
||||||
|
// 字幕id str
|
||||||
|
string id_str = 2;
|
||||||
|
// 字幕语言代码
|
||||||
|
string lan = 3;
|
||||||
|
// 字幕语言
|
||||||
|
string lan_doc = 4;
|
||||||
|
// 字幕文件url
|
||||||
|
string subtitle_url = 5;
|
||||||
|
// 字幕作者信息
|
||||||
|
UserInfo author = 6;
|
||||||
|
// 字幕类型
|
||||||
|
SubtitleType type = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SubtitleType {
|
||||||
|
CC = 0; // CC字幕
|
||||||
|
AI = 1; // AI生成字幕
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字幕作者信息
|
||||||
|
message UserInfo {
|
||||||
|
// 用户mid
|
||||||
|
int64 mid = 1;
|
||||||
|
// 用户昵称
|
||||||
|
string name = 2;
|
||||||
|
// 用户性别
|
||||||
|
string sex = 3;
|
||||||
|
// 用户头像url
|
||||||
|
string face = 4;
|
||||||
|
// 用户签名
|
||||||
|
string sign = 5;
|
||||||
|
// 用户等级
|
||||||
|
int32 rank = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 智能防挡弹幕蒙版信息
|
||||||
|
message VideoMask {
|
||||||
|
// 视频cid
|
||||||
|
int64 cid = 1;
|
||||||
|
// 平台
|
||||||
|
// 0:web端 1:客户端
|
||||||
|
int32 plat = 2;
|
||||||
|
// 帧率
|
||||||
|
int32 fps = 3;
|
||||||
|
// 间隔时间
|
||||||
|
int64 time = 4;
|
||||||
|
// 蒙版url
|
||||||
|
string mask_url = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 视频字幕信息
|
||||||
|
message VideoSubtitle {
|
||||||
|
// 视频原语言代码
|
||||||
|
string lan = 1;
|
||||||
|
// 视频原语言
|
||||||
|
string lanDoc = 2;
|
||||||
|
// 视频字幕列表
|
||||||
|
repeated SubtitleItem subtitles = 3;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user