更新弹幕、直播的下载,以及弹幕的转换还有部分接口
This commit is contained in:
parent
ac7f076721
commit
590c54b777
@ -21,6 +21,8 @@ import retrofit2.Response;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
|
@ -1,6 +1,9 @@
|
||||
package com.yutou.biliapi.bean.live;
|
||||
|
||||
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
|
||||
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
|
||||
import lombok.Data;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Objects;
|
||||
@ -11,6 +14,7 @@ public class LiveRoomConfig {
|
||||
BigInteger roomId;
|
||||
String anchorName;
|
||||
boolean isLogin;
|
||||
String rootPath="live";
|
||||
LiveDanmuInfo liveInfo;
|
||||
LiveRoomInfo roomInfo;
|
||||
|
||||
@ -37,4 +41,17 @@ public class LiveRoomConfig {
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(roomId);
|
||||
}
|
||||
|
||||
public static LiveRoomConfig buildConfig(String roomId){
|
||||
BiliLiveConfigDatabase database = new BiliLiveConfigDatabase();
|
||||
LiveConfigDatabaseBean bean = database.getConfig(new BigInteger(roomId));
|
||||
|
||||
LiveRoomConfig config = new LiveRoomConfig();
|
||||
config.setLoginUid(bean.getRecordUid());
|
||||
config.setRoomId(bean.getRoomId());
|
||||
config.setAnchorName(bean.getAnchorName());
|
||||
config.setLogin(StringUtils.hasText(bean.getRecordUid()));
|
||||
config.setRootPath(bean.getRecordPath());
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,12 @@ public class LiveConfigDatabaseBean extends AbsDatabasesBean {
|
||||
private boolean isRecordDanmu;
|
||||
@JSONField(name = "keyword")
|
||||
private List<String> keywordList;
|
||||
@JSONField(name = "recordPath")
|
||||
private String recordPath="live";
|
||||
@JSONField(name = "recordUid")
|
||||
private String recordUid;
|
||||
@JSONField(name = "recordLiveModel")
|
||||
private int recordLiveModel;//0 - ffmpeg 1 - java
|
||||
@JSONField(name = "recordDanmuDate")
|
||||
private String recordDanmuDate="* * *";// * * * 分 时 星期 | 周日是1
|
||||
@JSONField(name = "recordLiveDate")
|
||||
|
@ -2,6 +2,7 @@ package com.yutou.biliapi.bean.live.database;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.yutou.biliapi.bean.websocket.live.WSDanmuData;
|
||||
import com.yutou.bilibili.datas.DanmuData;
|
||||
import com.yutou.common.databases.AbsDatabasesBean;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@ -29,7 +30,6 @@ public class LiveDanmuDatabaseBean extends AbsDatabasesBean {
|
||||
private String uname;
|
||||
|
||||
|
||||
|
||||
public LiveDanmuDatabaseBean() {
|
||||
super("danmu", System.currentTimeMillis());
|
||||
}
|
||||
@ -44,4 +44,15 @@ public class LiveDanmuDatabaseBean extends AbsDatabasesBean {
|
||||
uid = danmu.getUid();
|
||||
uname = danmu.getUname();
|
||||
}
|
||||
|
||||
public DanmuData createDanmuData() {
|
||||
DanmuData data = new DanmuData();
|
||||
data.setId(id);
|
||||
data.setDanmu(danmu);
|
||||
data.setModel(model);
|
||||
data.setFontSize(fontSize);
|
||||
data.setFontColor(fontColor);
|
||||
data.setTime(time);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
package com.yutou.biliapi.bean.live.database;
|
||||
|
||||
import com.alibaba.fastjson2.annotation.JSONField;
|
||||
import com.yutou.common.databases.AbsDatabasesBean;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class LiveVideoDatabaseBean extends AbsDatabasesBean {
|
||||
@JSONField(name = "info")
|
||||
String roomInfoJson;
|
||||
@JSONField(name = "start_time")
|
||||
Date startTime;
|
||||
@JSONField(name = "path")
|
||||
String path;
|
||||
|
||||
public LiveVideoDatabaseBean() {
|
||||
super("live_video", System.currentTimeMillis());
|
||||
}
|
||||
}
|
@ -31,4 +31,8 @@ public class LoginCookieDatabaseBean extends AbsDatabasesBean {
|
||||
public LoginCookieDatabaseBean() {
|
||||
super("login_cookie", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public String toCookieString() {
|
||||
return "SESSDATA=" + sessdta + "; Path=" + path + "; DedeUserID=" + dedeUserID + "; DedeUserID__ckMd5=" + dedeUserIDCkMd5 + "; bili_jct=" + biliJct + "; Expires=" + expires + "; Domain=" + domain + "; sid=" + sid + "; gourl=" + gourl;
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public class BiliBiliLoginDatabase extends SQLiteManager {
|
||||
return list.getFirst();
|
||||
}
|
||||
for (LoginCookieDatabaseBean bean : list) {
|
||||
if (bean.getSid().equals(userId)) {
|
||||
if (bean.getDedeUserID().equals(userId)) {
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
@ -66,7 +66,7 @@ public class BiliBiliLoginDatabase extends SQLiteManager {
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return "bilibili_login.db";
|
||||
return "old_bilibili_login.db";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,13 +2,16 @@ package com.yutou.biliapi.databases;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.yutou.biliapi.bean.live.LiveRoomConfig;
|
||||
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
|
||||
import com.yutou.common.databases.AbsDatabasesBean;
|
||||
import com.yutou.common.databases.SQLiteManager;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class BiliLiveConfigDatabase extends SQLiteManager {
|
||||
String fileName;
|
||||
@ -44,12 +47,13 @@ public class BiliLiveConfigDatabase extends SQLiteManager {
|
||||
return null;
|
||||
}
|
||||
for (LiveConfigDatabaseBean bean : list) {
|
||||
if (bean.getRoomId() == roomId) {
|
||||
if (Objects.equals(bean.getRoomId(), roomId)) {
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean deleteConfig(BigInteger roomId) {
|
||||
LiveConfigDatabaseBean config = getConfig(roomId);
|
||||
if (config == null) {
|
||||
@ -57,6 +61,7 @@ public class BiliLiveConfigDatabase extends SQLiteManager {
|
||||
}
|
||||
return delete(config);
|
||||
}
|
||||
|
||||
public List<LiveConfigDatabaseBean> getAllConfig() {
|
||||
List<LiveConfigDatabaseBean> list = get(getDataBean().get(0).getTableName(), LiveConfigDatabaseBean.class);
|
||||
if (list.isEmpty()) {
|
||||
|
@ -4,24 +4,35 @@ import com.alibaba.fastjson2.util.DateUtils;
|
||||
import com.yutou.biliapi.bean.live.*;
|
||||
import com.yutou.biliapi.bean.live.database.*;
|
||||
import com.yutou.biliapi.bean.websocket.live.*;
|
||||
import com.yutou.bilibili.Tools.DateFormatUtils;
|
||||
import com.yutou.common.databases.AbsDatabasesBean;
|
||||
import com.yutou.common.databases.SQLiteManager;
|
||||
import com.yutou.common.okhttp.HttpDownloadUtils;
|
||||
import org.apache.poi.ss.usermodel.DataFormat;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static com.alibaba.fastjson2.util.DateUtils.DateTimeFormatPattern.DATE_FORMAT_10_DASH;
|
||||
|
||||
public class BiliLiveDatabase extends SQLiteManager {
|
||||
LiveInfoDatabaseBean bean;
|
||||
LiveRoomConfig config;
|
||||
String fileName;
|
||||
File rootPath;
|
||||
|
||||
public BiliLiveDatabase(LiveRoomConfig roomConfig, String path) {
|
||||
this.config = roomConfig;
|
||||
rootPath = new File(path).getParentFile();
|
||||
fileName =path;
|
||||
init();
|
||||
}
|
||||
|
||||
public BiliLiveDatabase(LiveRoomConfig roomConfig) {
|
||||
String time = DateUtils.format(new Date().getTime(), DATE_FORMAT_10_DASH);
|
||||
rootPath = new File(roomConfig.getAnchorName() + File.separator + time + roomConfig.getRoomInfo().getTitle());
|
||||
rootPath = new File(roomConfig.getRootPath() + File.separator + roomConfig.getAnchorName() + File.separator + time + File.separator + roomConfig.getRoomInfo().getTitle());
|
||||
config = roomConfig;
|
||||
if (!rootPath.exists()) {
|
||||
rootPath.mkdirs();
|
||||
@ -33,7 +44,11 @@ public class BiliLiveDatabase extends SQLiteManager {
|
||||
@Override
|
||||
public void init() {
|
||||
super.init();
|
||||
HttpDownloadUtils.download(config.getRoomInfo().getUserCover(),rootPath.getAbsolutePath(),"poster.jpg");
|
||||
if(config.getRoomInfo()!=null) {
|
||||
HttpDownloadUtils.download(new HttpDownloadUtils.Builder().setUrl(config.getRoomInfo().getUserCover())
|
||||
.setPath(rootPath.getAbsolutePath())
|
||||
.setFileName("poster.jpg"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -49,17 +64,22 @@ public class BiliLiveDatabase extends SQLiteManager {
|
||||
new LiveGiftDatabaseBean(),
|
||||
new LiveInteractWordDatabaseBean(),
|
||||
new LiveSuperChatDatabaseBean(),
|
||||
new LiveSourceDatabaseBean()
|
||||
new LiveSourceDatabaseBean(),
|
||||
new LiveVideoDatabaseBean()
|
||||
);
|
||||
}
|
||||
|
||||
public void addLiveInfo(LiveRoomInfo info) {
|
||||
this.bean = new LiveInfoDatabaseBean(info);
|
||||
|
||||
List<LiveInfoDatabaseBean> infos = get(bean.getTableName(), LiveInfoDatabaseBean.class);
|
||||
if (infos.isEmpty()) {
|
||||
createInfo(bean);
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
}
|
||||
|
||||
public void addLiveInfo(LiveVideoDatabaseBean info) {
|
||||
createInfo(info);
|
||||
}
|
||||
|
||||
public List<LiveVideoDatabaseBean> getLiveInfos() {
|
||||
return get(new LiveVideoDatabaseBean().getTableName(), LiveVideoDatabaseBean.class);
|
||||
}
|
||||
|
||||
private void addData(WSData bean) {
|
||||
@ -75,30 +95,32 @@ public class BiliLiveDatabase extends SQLiteManager {
|
||||
}
|
||||
|
||||
public void addSource(WSData bean) {
|
||||
System.out.println("BiliLiveDatabase.addSource");
|
||||
add(new LiveSourceDatabaseBean(bean));
|
||||
addData(bean);
|
||||
}
|
||||
|
||||
private void createInfo(LiveInfoDatabaseBean bean) {
|
||||
private void createInfo(LiveVideoDatabaseBean bean) {
|
||||
String format = DateFormatUtils.format(bean.getSql_time());
|
||||
if (get(bean.getTableName(), " `sql_time` = '" + format + "'", LiveVideoDatabaseBean.class).isEmpty()) {
|
||||
add(bean);
|
||||
} else {
|
||||
update(bean);
|
||||
}
|
||||
}
|
||||
|
||||
public List<LiveSourceDatabaseBean> getSource(long startTime, long entTime) {
|
||||
return get(startTime, entTime, LiveSourceDatabaseBean.class);
|
||||
}
|
||||
|
||||
public <T extends AbsDatabasesBean> List<T> get(long startTime, long entTime, Class<T> clazz) {
|
||||
public <T extends AbsDatabasesBean> List<T> getOfTime(String startTime, String entTime, Class<T> clazz) {
|
||||
String tableName = null;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String where = null;
|
||||
if (startTime != -1) {
|
||||
sb.append(" `sql_time` >= ").append(startTime);
|
||||
if (startTime != null) {
|
||||
sb.append(" `sql_time` >= ").append("\"").append(startTime).append("\"");
|
||||
}
|
||||
if (entTime != -1) {
|
||||
if (entTime != null) {
|
||||
if (!sb.isEmpty()) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
sb.append(" `sql_time` <= ").append(entTime);
|
||||
sb.append(" `sql_time` <= ").append("\"").append(entTime).append("\"");
|
||||
}
|
||||
if (!sb.isEmpty()) {
|
||||
where = sb.toString();
|
||||
|
@ -4,8 +4,8 @@ import com.aayushatharva.brotli4j.Brotli4jLoader;
|
||||
import com.aayushatharva.brotli4j.decoder.Decoder;
|
||||
import com.aayushatharva.brotli4j.decoder.DecoderJNI;
|
||||
import com.aayushatharva.brotli4j.decoder.DirectDecompress;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.fastjson2.util.DateUtils;
|
||||
import com.yutou.biliapi.api.LiveApi;
|
||||
import com.yutou.biliapi.bean.live.LiveDanmuInfo;
|
||||
import com.yutou.biliapi.bean.live.LiveRoomConfig;
|
||||
@ -26,7 +26,6 @@ import org.java_websocket.handshake.ServerHandshake;
|
||||
import retrofit2.Response;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
@ -34,15 +33,19 @@ import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
import static com.alibaba.fastjson2.util.DateUtils.DateTimeFormatPattern.DATE_FORMAT_10_DASH;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class WebSocketManager {
|
||||
ThreadPoolExecutor executor;
|
||||
private static WebSocketManager instance;
|
||||
Map<LiveRoomConfig, WebSocketClientTh> roomMap;
|
||||
Map<LiveRoomConfig, DanmuTask> roomMap;
|
||||
private final List<String> userStopList = new ArrayList<>();//手动停止列表
|
||||
|
||||
private WebSocketManager() {
|
||||
roomMap = new HashMap<>();
|
||||
executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
|
||||
}
|
||||
|
||||
public static WebSocketManager getInstance() {
|
||||
@ -56,10 +59,51 @@ public class WebSocketManager {
|
||||
return roomMap.containsKey(roomConfig);
|
||||
}
|
||||
|
||||
public void addRoom(LiveRoomConfig roomConfig) {
|
||||
public JSONArray getLiveRoomList() {
|
||||
JSONArray array = new JSONArray();
|
||||
array.addAll(roomMap.keySet());
|
||||
return array;
|
||||
}
|
||||
|
||||
public void addRoom(LiveRoomConfig roomConfig, boolean isUser) {
|
||||
if (!isUser && userStopList.contains(roomConfig.getRoomId().toString())) {
|
||||
return;
|
||||
}
|
||||
if (checkRoom(roomConfig)) {
|
||||
return;
|
||||
}
|
||||
if (isUser) {
|
||||
userStopList.remove(roomConfig.getRoomId().toString());
|
||||
}
|
||||
DanmuTask task = new DanmuTask(roomConfig);
|
||||
roomMap.put(roomConfig, task);
|
||||
System.out.println("添加websocket任务");
|
||||
executor.execute(task);
|
||||
}
|
||||
|
||||
public void stopRoom(String roomId, boolean isUser) {
|
||||
LiveRoomConfig roomConfig=new LiveRoomConfig();
|
||||
roomConfig.setRoomId(new BigInteger(roomId));
|
||||
if (checkRoom(roomConfig)) {
|
||||
roomMap.get(roomConfig).close();
|
||||
roomMap.remove(roomConfig);
|
||||
}
|
||||
if (isUser) {
|
||||
userStopList.add(roomConfig.getRoomId().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class DanmuTask implements Runnable {
|
||||
LiveRoomConfig roomConfig;
|
||||
WebSocketClientTh client;
|
||||
|
||||
public DanmuTask(LiveRoomConfig config) {
|
||||
this.roomConfig = config;
|
||||
WebSocketManager.getInstance().roomMap.put(roomConfig, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LiveApi api = BiliLiveNetApiManager.getInstance().getApi(roomConfig.getLoginUid());
|
||||
Response<HttpBody<LiveRoomInfo>> execute = null;
|
||||
try {
|
||||
@ -67,11 +111,10 @@ public class WebSocketManager {
|
||||
if (execute.isSuccessful()) {
|
||||
roomConfig.setRoomInfo(execute.body() != null ? execute.body().getData() : null);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
api.getLiveRoomDanmuInfo(String.valueOf(roomConfig.getLoginUid())).enqueue(new HttpCallback<LiveDanmuInfo>() {
|
||||
api.getLiveRoomDanmuInfo(String.valueOf(roomConfig.getRoomId())).enqueue(new HttpCallback<LiveDanmuInfo>() {
|
||||
@Override
|
||||
public void onResponse(Headers headers, int code, String status, LiveDanmuInfo response, String rawResponse) {
|
||||
if (!response.getHostList().isEmpty()) {
|
||||
@ -80,7 +123,7 @@ public class WebSocketManager {
|
||||
// url="ws://127.0.0.1:8765";
|
||||
try {
|
||||
roomConfig.setLiveInfo(response);
|
||||
new WebSocketClientTh(new URI(url), roomConfig);
|
||||
client = new WebSocketClientTh(new URI(url), roomConfig);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -94,10 +137,8 @@ public class WebSocketManager {
|
||||
});
|
||||
}
|
||||
|
||||
public void stopRoom(LiveRoomConfig roomConfig) {
|
||||
if (checkRoom(roomConfig)) {
|
||||
roomMap.get(roomConfig).close();
|
||||
roomMap.remove(roomConfig);
|
||||
public void close() {
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,19 +146,20 @@ public class WebSocketManager {
|
||||
private LiveRoomConfig roomConfig;
|
||||
private HeartbeatTask heartbeatTask;
|
||||
BiliLiveDatabase liveDatabase;
|
||||
private boolean itTmp = true;
|
||||
|
||||
public WebSocketClientTh(URI serverUri, LiveRoomConfig roomId) {
|
||||
super(serverUri);
|
||||
System.out.println("WebSocketClientTh.WebSocketClientTh : " + serverUri);
|
||||
this.roomConfig = roomId;
|
||||
heartbeatTask = new HeartbeatTask();
|
||||
Brotli4jLoader.ensureAvailability();
|
||||
liveDatabase = new BiliLiveDatabase(roomConfig);
|
||||
Brotli4jLoader.ensureAvailability();
|
||||
heartbeatTask = new HeartbeatTask();
|
||||
connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(ServerHandshake serverHandshake) {
|
||||
WebSocketManager.getInstance().roomMap.put(roomConfig, this);
|
||||
heartbeatTask.setSocket(this);
|
||||
heartbeatTask.sendInitAuthData();
|
||||
new Timer().schedule(heartbeatTask, 1000, 30000);
|
||||
@ -131,7 +173,7 @@ public class WebSocketManager {
|
||||
|
||||
@Override
|
||||
public void onMessage(ByteBuffer bytes) {
|
||||
System.out.println("WebSocketClientTh.onMessage");
|
||||
System.out.println("WebSocketClientTh.onMessage: " + roomConfig.getAnchorName());
|
||||
super.onMessage(bytes);
|
||||
decompress(bytes.array());
|
||||
}
|
||||
@ -161,7 +203,7 @@ public class WebSocketManager {
|
||||
byte[] bytes = new byte[data.length - 16];
|
||||
WebSocketHeader header = new WebSocketHeader(data);
|
||||
System.arraycopy(data, header.getHeaderSize(), bytes, 0, data.length - header.getHeaderSize());
|
||||
System.out.println("数据大小:" + header.getDataSize() + " 协议:" + header.getAgree() + " 头部大小:" + header.getHeaderSize() + " 命令:" + header.getCmdData());
|
||||
// System.out.println("数据大小:" + header.getDataSize() + " 协议:" + header.getAgree() + " 头部大小:" + header.getHeaderSize() + " 命令:" + header.getCmdData());
|
||||
switch (header.getAgree()) {
|
||||
case 0:
|
||||
case 1:
|
||||
@ -183,14 +225,14 @@ public class WebSocketManager {
|
||||
DirectDecompress directDecompress = Decoder.decompress(bytes);
|
||||
if (directDecompress.getResultStatus() == DecoderJNI.Status.DONE) {
|
||||
WebSocketBody body = new WebSocketBody(directDecompress.getDecompressedData());
|
||||
Log.i("3协议:" + useHeader + " 命令数:" + body.getBodyList().size());
|
||||
// Log.i("协议:" + useHeader + " 命令数:" + body.getBodyList().size());
|
||||
for (JSONObject json : body.getBodyList()) {
|
||||
WSData parse = WSData.parse(json);
|
||||
liveDatabase.addSource(parse);
|
||||
Log.i("解压:" + parse);
|
||||
// Log.i("解压:" + parse);
|
||||
}
|
||||
System.out.println();
|
||||
System.out.println();
|
||||
// System.out.println();
|
||||
// System.out.println();
|
||||
} else {
|
||||
Log.e(new RuntimeException("解压失败"));
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ public class LiveConfigController {
|
||||
|
||||
@RequestMapping(value = "set", method = RequestMethod.POST)
|
||||
@ResponseBody
|
||||
public ResultData<JSONObject> setConfig(String url, LiveConfigDatabaseBean bean) {
|
||||
public JSONObject setConfig(String url, LiveConfigDatabaseBean bean) {
|
||||
LiveConfigDatabaseBean config = configService.addConfig(url, bean);
|
||||
if (config != null) {
|
||||
return ResultData.success(config.toJson());
|
||||
@ -34,7 +34,7 @@ public class LiveConfigController {
|
||||
|
||||
@RequestMapping(value = "update", method = RequestMethod.POST)
|
||||
@ResponseBody
|
||||
public ResultData<JSONObject> updateConfig(String roomId, LiveConfigDatabaseBean bean) {
|
||||
public JSONObject updateConfig(String roomId, LiveConfigDatabaseBean bean) {
|
||||
LiveConfigDatabaseBean config = configService.updateConfig(new BigInteger(roomId), bean);
|
||||
if (config != null) {
|
||||
return ResultData.success(config.toJson());
|
||||
@ -44,7 +44,7 @@ public class LiveConfigController {
|
||||
|
||||
@RequestMapping(value = "get", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public ResultData<JSONObject> getConfig(String roomId) {
|
||||
public JSONObject getConfig(String roomId) {
|
||||
if ("0".equals(roomId) || !StringUtils.hasText(roomId)) {
|
||||
return ResultData.fail(ReturnCode.RC999);
|
||||
}
|
||||
@ -57,7 +57,7 @@ public class LiveConfigController {
|
||||
|
||||
@RequestMapping(value = "all", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public ResultData<JSONArray> getAllConfig() {
|
||||
public JSONObject getAllConfig() {
|
||||
List<LiveConfigDatabaseBean> config = configService.getAllConfig();
|
||||
if (config != null) {
|
||||
return ResultData.success(JSONArray.parseArray(JSONArray.toJSONString(config)));
|
||||
@ -67,7 +67,7 @@ public class LiveConfigController {
|
||||
|
||||
@RequestMapping(value = "delete", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public ResultData<JSONObject> deleteConfig(BigInteger roomId) {
|
||||
public JSONObject deleteConfig(BigInteger roomId) {
|
||||
if (roomId.equals(BigInteger.ZERO)) {
|
||||
return ResultData.fail(ReturnCode.RC999);
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
package com.yutou.bilibili.Controllers;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.yutou.biliapi.bean.live.LiveRoomConfig;
|
||||
import com.yutou.biliapi.net.WebSocketManager;
|
||||
import com.yutou.bilibili.datas.ResultData;
|
||||
import com.yutou.bilibili.datas.ReturnCode;
|
||||
import com.yutou.bilibili.services.LiveDanmuService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
public class LiveDanmuController {
|
||||
@Resource
|
||||
LiveDanmuService service;
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping("/live/danmu/list")
|
||||
public JSONObject getLiveDanmuList() {
|
||||
return ResultData.success(service.getLiveRoomList());
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping("/live/danmu/stop")
|
||||
public JSONObject stopLiveDanmu(String roomId) {
|
||||
service.stop(roomId);
|
||||
return ResultData.success(ReturnCode.RC100);
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping("/live/danmu/start")
|
||||
public JSONObject startLiveDanmu(String roomId) {
|
||||
service.start(roomId);
|
||||
return ResultData.success(ReturnCode.RC100);
|
||||
}
|
||||
@ResponseBody
|
||||
@RequestMapping("/live/danmu/file/list")
|
||||
public JSONObject getDanmuList(String roomId) {
|
||||
return ResultData.success(service.getDanmuFileList(roomId));
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.yutou.bilibili.Controllers;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
|
||||
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
|
||||
import com.yutou.biliapi.net.WebSocketManager;
|
||||
import com.yutou.bilibili.datas.ResultData;
|
||||
import com.yutou.bilibili.datas.ReturnCode;
|
||||
import com.yutou.bilibili.services.LiveVideoService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
public class LiveVideoController {
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
|
||||
@RequestMapping("/live/video/list")
|
||||
@ResponseBody
|
||||
public JSONObject getLiveVideoList() {
|
||||
return ResultData.success(videoService.getDownloadTasks());
|
||||
}
|
||||
|
||||
@RequestMapping("/live/video/stop")
|
||||
@ResponseBody
|
||||
public JSONObject stopDownload(String roomId) {
|
||||
videoService.stop(roomId, true);
|
||||
return ResultData.success(true);
|
||||
}
|
||||
|
||||
@RequestMapping("/live/video/start")
|
||||
@ResponseBody
|
||||
public JSONObject startDownload(String roomId) {
|
||||
BiliLiveConfigDatabase liveConfigDatabase = new BiliLiveConfigDatabase();
|
||||
List<LiveConfigDatabaseBean> list = liveConfigDatabase.getAllConfig();
|
||||
for (LiveConfigDatabaseBean bean : list) {
|
||||
if (bean.getRoomId().toString().equals(roomId)) {
|
||||
videoService.start(bean, true);
|
||||
return ResultData.success(true);
|
||||
}
|
||||
}
|
||||
return ResultData.fail(ReturnCode.RC999);
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package com.yutou.bilibili.Controllers;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.yutou.bilibili.Tools.FileServerUtils;
|
||||
import com.yutou.bilibili.Tools.Tools;
|
||||
import com.yutou.bilibili.datas.ResultData;
|
||||
import com.yutou.bilibili.datas.VideoFilePath;
|
||||
import com.yutou.bilibili.services.LiveVideoService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
public class VideoFileController {
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping("/file/list")
|
||||
public JSONObject getFileList(String roomId) {
|
||||
List<VideoFilePath> list;
|
||||
if (StringUtils.hasText(roomId)) {
|
||||
list = videoService.getVideoPath(roomId);
|
||||
} else {
|
||||
list = videoService.getAllVideoPath();
|
||||
}
|
||||
return ResultData.success(list);
|
||||
}
|
||||
@RequestMapping("/file/{base64}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<FileSystemResource> getFile(@PathVariable String base64) {
|
||||
File file = FileServerUtils.toFile(base64);
|
||||
System.out.println(file.getAbsolutePath());
|
||||
return Tools.getFile(file);
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +1,19 @@
|
||||
package com.yutou.bilibili.Tools;
|
||||
|
||||
import com.yutou.bilibili.services.SystemService;
|
||||
import com.yutou.common.utils.Log;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ApplicationClose implements ApplicationListener<ContextClosedEvent> {
|
||||
@Resource
|
||||
SystemService systemConfigService;
|
||||
@Override
|
||||
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
|
||||
Log.i("服务结束");
|
||||
systemConfigService.stop();
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ public class AssTools {
|
||||
private final Date startTime;
|
||||
private int y = 0;
|
||||
private List<String> filters = new ArrayList<>();
|
||||
private String alpha="80";
|
||||
private String alpha="100";
|
||||
|
||||
/**
|
||||
* 弹幕转换ass
|
||||
@ -140,7 +140,7 @@ public class AssTools {
|
||||
y,
|
||||
x2,
|
||||
y,
|
||||
danmuData.getFontColorHex(),
|
||||
danmuData.getFontColor(),
|
||||
alpha,
|
||||
danmuData.getDanmu()
|
||||
);
|
||||
|
22
src/main/java/com/yutou/bilibili/Tools/DateFormatUtils.java
Normal file
22
src/main/java/com/yutou/bilibili/Tools/DateFormatUtils.java
Normal file
@ -0,0 +1,22 @@
|
||||
package com.yutou.bilibili.Tools;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class DateFormatUtils {
|
||||
public static String format(Date date,String format){
|
||||
return new SimpleDateFormat(format).format(date);
|
||||
}
|
||||
public static String format(long time,String format){
|
||||
return new SimpleDateFormat(format).format(new Date(time));
|
||||
}
|
||||
public static String format(long time){
|
||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(time));
|
||||
}
|
||||
public static String format(Date date){
|
||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(date);
|
||||
}
|
||||
public static String format(){
|
||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
|
||||
}
|
||||
}
|
18
src/main/java/com/yutou/bilibili/Tools/FileServerUtils.java
Normal file
18
src/main/java/com/yutou/bilibili/Tools/FileServerUtils.java
Normal file
@ -0,0 +1,18 @@
|
||||
package com.yutou.bilibili.Tools;
|
||||
|
||||
import com.yutou.common.utils.Base64Tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class FileServerUtils {
|
||||
public static String toUrl(String file) {
|
||||
return "/file/" + URLEncoder.encode(Base64Tools.encode(file), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static File toFile(String base64) {
|
||||
return new File(URLDecoder.decode(Base64Tools.decode(base64), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
@ -13,11 +13,11 @@ import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.MessageDigest;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
import java.util.*;
|
||||
|
||||
public class Tools {
|
||||
|
||||
@ -203,4 +203,24 @@ public class Tools {
|
||||
public static String getToDayTime() {
|
||||
return new SimpleDateFormat("yyyy-MM-dd").format(new Date());
|
||||
}
|
||||
|
||||
//扫描文件夹
|
||||
public static List<File> scanFile(File file){
|
||||
List<File> list = new ArrayList<>();
|
||||
FileVisitor<Path> visitor = new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if ("live.db".equals(file.toFile().getName())) {
|
||||
list.add(file.toFile());
|
||||
}
|
||||
return super.visitFile(file, attrs);
|
||||
}
|
||||
};
|
||||
try {
|
||||
Files.walkFileTree(Paths.get(file.getAbsolutePath()), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, visitor);
|
||||
return list;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ public class DanmuData {
|
||||
private int id;
|
||||
private int model;//1~3 滚动弹幕 4 底端弹幕 5 顶端弹幕 6 逆向弹幕 7 精准定位 8 高级弹幕
|
||||
private int fontSize;
|
||||
private int fontColor;
|
||||
private String fontColor;
|
||||
private long time;
|
||||
private String uCode;
|
||||
private String danmu;
|
||||
@ -20,7 +20,4 @@ public class DanmuData {
|
||||
return new Date(time);
|
||||
}
|
||||
|
||||
public String getFontColorHex() {
|
||||
return Integer.toHexString(fontColor);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.yutou.bilibili.datas;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@ -14,28 +15,28 @@ public class ResultData<T> {
|
||||
}
|
||||
|
||||
|
||||
public static <T> ResultData<T> success(T data) {
|
||||
public static <T> JSONObject success(T data) {
|
||||
ResultData<T> resultData = new ResultData<>();
|
||||
resultData.setStatus(ReturnCode.RC100.getCode());
|
||||
resultData.setMessage(ReturnCode.RC100.getMessage());
|
||||
resultData.setData(data);
|
||||
return resultData;
|
||||
return JSONObject.parseObject(JSONObject.toJSONString(resultData));
|
||||
}
|
||||
|
||||
public static <T> ResultData<T> fail(int code, String message) {
|
||||
public static <T> JSONObject fail(int code, String message) {
|
||||
ResultData<T> resultData = new ResultData<>();
|
||||
resultData.setStatus(code);
|
||||
resultData.setMessage(message);
|
||||
return resultData;
|
||||
return JSONObject.parseObject(JSONObject.toJSONString(resultData));
|
||||
}
|
||||
public static <T> ResultData<T> success(ReturnCode code) {
|
||||
public static JSONObject success(ReturnCode code) {
|
||||
return fail(code);
|
||||
}
|
||||
public static <T> ResultData<T> fail(ReturnCode code) {
|
||||
public static <T> JSONObject fail(ReturnCode code) {
|
||||
ResultData<T> resultData = new ResultData<>();
|
||||
resultData.setStatus(code.getCode());
|
||||
resultData.setMessage(code.getMessage());
|
||||
return resultData;
|
||||
return JSONObject.parseObject(JSONObject.toJSONString(resultData));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import lombok.EqualsAndHashCode;
|
||||
@Data
|
||||
public class SystemConfigDatabaseBean extends AbsDatabasesBean {
|
||||
@JSONField(name = "timer_loop")
|
||||
private long timerLoop = 5000;
|
||||
private long timerLoop = 30000;
|
||||
|
||||
|
||||
public SystemConfigDatabaseBean() {
|
||||
|
15
src/main/java/com/yutou/bilibili/datas/VideoFilePath.java
Normal file
15
src/main/java/com/yutou/bilibili/datas/VideoFilePath.java
Normal file
@ -0,0 +1,15 @@
|
||||
package com.yutou.bilibili.datas;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class VideoFilePath {
|
||||
private String name;
|
||||
private String roomId;
|
||||
private String path;
|
||||
private String cover;
|
||||
private boolean isParent;
|
||||
private List<VideoFilePath> children;
|
||||
}
|
@ -3,7 +3,8 @@ package com.yutou.bilibili.interfaces;
|
||||
import java.io.File;
|
||||
|
||||
public abstract class DownloadInterface {
|
||||
public void onDownloading(double soFarBytes, double totalBytes){};
|
||||
public void onDownload(File file){};
|
||||
public void onError(Exception e){};
|
||||
public void onDownloadStart(){}
|
||||
public boolean onDownloading(double soFarBytes, double totalBytes){return true;}
|
||||
public void onDownload(File file){}
|
||||
public void onError(Exception e){}
|
||||
}
|
||||
|
@ -1,7 +1,83 @@
|
||||
package com.yutou.bilibili.services;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.yutou.biliapi.bean.live.LiveRoomConfig;
|
||||
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
|
||||
import com.yutou.biliapi.bean.live.database.LiveDanmuDatabaseBean;
|
||||
import com.yutou.biliapi.bean.live.database.LiveVideoDatabaseBean;
|
||||
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
|
||||
import com.yutou.biliapi.databases.BiliLiveDatabase;
|
||||
import com.yutou.biliapi.net.WebSocketManager;
|
||||
import com.yutou.bilibili.Tools.AssTools;
|
||||
import com.yutou.bilibili.Tools.DateFormatUtils;
|
||||
import com.yutou.bilibili.Tools.Tools;
|
||||
import com.yutou.common.utils.FFmpegUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class LiveDanmuService {
|
||||
|
||||
public void start(String roomId) {
|
||||
WebSocketManager.getInstance().addRoom(LiveRoomConfig.buildConfig(roomId), true);
|
||||
}
|
||||
|
||||
public void stop(String roomId) {
|
||||
WebSocketManager.getInstance().stopRoom(roomId, true);
|
||||
}
|
||||
|
||||
public JSONArray getLiveRoomList() {
|
||||
return WebSocketManager.getInstance().getLiveRoomList();
|
||||
}
|
||||
|
||||
|
||||
private BiliLiveDatabase getDatabase(String roomId, File file) {
|
||||
return new BiliLiveDatabase(LiveRoomConfig.buildConfig(roomId), file.getParent() + File.separator + "live.db");
|
||||
}
|
||||
public List<File> getDanmuFileList(String roomId) {
|
||||
BiliLiveConfigDatabase configDatabase=new BiliLiveConfigDatabase();
|
||||
LiveConfigDatabaseBean bean = configDatabase.getConfig(new BigInteger(roomId));
|
||||
configDatabase.close();
|
||||
return Tools.scanFile(new File(bean.getRecordPath() + File.separator + bean.getAnchorName()));
|
||||
}
|
||||
|
||||
public void saveDanmuXML(LiveVideoDatabaseBean videoDatabaseBean, BiliLiveDatabase database) {
|
||||
File videoFile = new File(videoDatabaseBean.getPath());
|
||||
long videoTime = FFmpegUtils.getVideoTime(videoFile) + videoDatabaseBean.getStartTime().getTime();
|
||||
System.out.println("开始时间:" + videoDatabaseBean.getStartTime().getTime());
|
||||
System.out.println("结束时间:" + videoTime);
|
||||
String startTime = DateFormatUtils.format(videoDatabaseBean.getStartTime());
|
||||
String endTime = DateFormatUtils.format(videoTime);
|
||||
List<LiveDanmuDatabaseBean> danmus = database.getOfTime(startTime, endTime, LiveDanmuDatabaseBean.class);
|
||||
System.out.println("弹幕数量:" + danmus.size());
|
||||
AssTools assTools = new AssTools(videoFile.getName().replace(".flv", ""), videoDatabaseBean.getStartTime());
|
||||
for (LiveDanmuDatabaseBean dm : danmus) {
|
||||
assTools.addDanmu(dm.createDanmuData());
|
||||
}
|
||||
assTools.saveDanmu(videoFile.getAbsolutePath().replace(".flv", ".ass"));
|
||||
}
|
||||
|
||||
public String toTimeString(long videoTime) {
|
||||
long seconds = videoTime / 1000;
|
||||
long hours = seconds / 3600;
|
||||
long remainingSecondsAfterHours = seconds % 3600;
|
||||
long minutes = remainingSecondsAfterHours / 60;
|
||||
long finalRemainingSeconds = remainingSecondsAfterHours % 60;
|
||||
// long finalRemainingMilliseconds = videoTime % 1000;
|
||||
return String.format("%d小时%d分钟%d秒", hours, minutes, finalRemainingSeconds);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
LiveDanmuService service = new LiveDanmuService();
|
||||
List<File> files = service.getDanmuFileList("22047448");
|
||||
for (File file : files) {
|
||||
System.out.println(file);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
377
src/main/java/com/yutou/bilibili/services/LiveVideoService.java
Normal file
377
src/main/java/com/yutou/bilibili/services/LiveVideoService.java
Normal file
@ -0,0 +1,377 @@
|
||||
package com.yutou.bilibili.services;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.fastjson2.util.DateUtils;
|
||||
import com.yutou.biliapi.api.LiveApi;
|
||||
import com.yutou.biliapi.bean.live.LiveRoomConfig;
|
||||
import com.yutou.biliapi.bean.live.LiveRoomInfo;
|
||||
import com.yutou.biliapi.bean.live.LiveRoomPlayInfo;
|
||||
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
|
||||
import com.yutou.biliapi.bean.live.database.LiveVideoDatabaseBean;
|
||||
import com.yutou.biliapi.bean.login.LoginCookieDatabaseBean;
|
||||
import com.yutou.biliapi.databases.BiliBiliLoginDatabase;
|
||||
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
|
||||
import com.yutou.biliapi.databases.BiliLiveDatabase;
|
||||
import com.yutou.biliapi.enums.LiveProtocol;
|
||||
import com.yutou.biliapi.enums.LiveVideoCodec;
|
||||
import com.yutou.biliapi.enums.LiveVideoDefinition;
|
||||
import com.yutou.biliapi.enums.LiveVideoFormat;
|
||||
import com.yutou.biliapi.net.BiliLiveNetApiManager;
|
||||
import com.yutou.biliapi.net.WebSocketManager;
|
||||
import com.yutou.bilibili.Tools.DateFormatUtils;
|
||||
import com.yutou.bilibili.Tools.FileServerUtils;
|
||||
import com.yutou.bilibili.Tools.Tools;
|
||||
import com.yutou.bilibili.datas.VideoFilePath;
|
||||
import com.yutou.bilibili.interfaces.DownloadInterface;
|
||||
import com.yutou.common.okhttp.HttpCallback;
|
||||
import com.yutou.common.okhttp.HttpDownloadUtils;
|
||||
import com.yutou.common.utils.ConfigTools;
|
||||
import com.yutou.common.utils.FFmpegUtils;
|
||||
import okhttp3.Headers;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.alibaba.fastjson2.util.DateUtils.DateTimeFormatPattern.DATE_FORMAT_10_DASH;
|
||||
|
||||
@Service
|
||||
public class LiveVideoService {
|
||||
ThreadPoolExecutor executor;
|
||||
private final Map<LiveConfigDatabaseBean, VideoTask> liveVideoMap = new HashMap<>();
|
||||
private final List<String> userStopList = new ArrayList<>();//手动停止列表
|
||||
|
||||
|
||||
public LiveVideoService() {
|
||||
System.out.println("初始化下载服务");
|
||||
executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
|
||||
}
|
||||
|
||||
public void start(LiveConfigDatabaseBean bean, boolean isUser) {
|
||||
if (!isUser && userStopList.contains(bean.getRoomId().toString())) {
|
||||
return;
|
||||
}
|
||||
if (isUser) {
|
||||
userStopList.remove(bean.getRoomId().toString());
|
||||
}
|
||||
if (liveVideoMap.containsKey(bean)) {
|
||||
return;
|
||||
}
|
||||
System.out.println("添加下载任务:" + liveVideoMap.keySet().size());
|
||||
liveVideoMap.put(bean, null);
|
||||
BiliLiveNetApiManager.getInstance().getApi(bean.getRecordUid()).getRoomInfo(bean.getRoomId().toString()).enqueue(new HttpCallback<LiveRoomInfo>() {
|
||||
@Override
|
||||
public void onResponse(Headers headers, int code, String status, LiveRoomInfo response, String rawResponse) {
|
||||
if (response.getLiveStatus() == 1) {
|
||||
VideoTask task = new VideoTask(bean, response);
|
||||
executor.execute(task);
|
||||
liveVideoMap.put(bean, task);
|
||||
} else {
|
||||
liveVideoMap.remove(bean);
|
||||
System.out.println("移除下载");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
liveVideoMap.remove(bean);
|
||||
System.out.println("移除下载");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void stop(String roomId, boolean isUser) {
|
||||
if (isUser) {
|
||||
userStopList.add(roomId);
|
||||
}
|
||||
for (Map.Entry<LiveConfigDatabaseBean, VideoTask> entry : liveVideoMap.entrySet()) {
|
||||
if (entry.getKey().getRoomId().toString().equals(roomId)) {
|
||||
entry.getValue().isDownload = false;
|
||||
liveVideoMap.remove(entry.getKey());
|
||||
System.out.println("移除下载");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean check(String roomId) {
|
||||
for (LiveConfigDatabaseBean bean : liveVideoMap.keySet()) {
|
||||
if (bean.getRoomId().toString().equals(roomId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public JSONArray getDownloadTasks() {
|
||||
JSONArray array = new JSONArray();
|
||||
array.addAll(liveVideoMap.keySet());
|
||||
return array;
|
||||
}
|
||||
|
||||
public void stopAll() {
|
||||
for (VideoTask task : liveVideoMap.values()) {
|
||||
task.isDownload = false;
|
||||
}
|
||||
liveVideoMap.clear();
|
||||
}
|
||||
|
||||
private class VideoTask implements Runnable {
|
||||
LiveConfigDatabaseBean bean;
|
||||
boolean isDownload = true;
|
||||
LiveApi api;
|
||||
String savePath;
|
||||
LiveConfigDatabaseBean config;
|
||||
BiliLiveDatabase database;
|
||||
LiveVideoDatabaseBean videoDatabaseBean;
|
||||
LiveRoomInfo roomInfo;
|
||||
|
||||
public VideoTask(LiveConfigDatabaseBean bean, LiveRoomInfo roomInfo) {
|
||||
this.bean = bean;
|
||||
this.roomInfo = roomInfo;
|
||||
api = BiliLiveNetApiManager.getInstance().getApi(bean.getRecordUid());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (roomInfo.getLiveStatus() == 1) {
|
||||
String time = DateUtils.format(new Date().getTime(), DATE_FORMAT_10_DASH);
|
||||
savePath = new File(bean.getRecordPath() + File.separator + bean.getAnchorName() + File.separator + time + File.separator + roomInfo.getTitle()).getAbsolutePath();
|
||||
savePath += File.separator + "[" +
|
||||
DateUtils.format(new Date(),
|
||||
"yyyy-MM-dd HH-mm-ss") + "]" + roomInfo.getTitle() + ".flv";
|
||||
record(bean, roomInfo);
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
api.getRoomInfo(config.getRoomId().toString()).enqueue(new HttpCallback<LiveRoomInfo>() {
|
||||
@Override
|
||||
public void onResponse(Headers headers, int code, String status, LiveRoomInfo response, String rawResponse) {
|
||||
if (response.getLiveStatus() == 1) {
|
||||
LiveVideoService.this.start(bean, false);
|
||||
} else {
|
||||
LiveVideoService.this.stop(bean.getRoomId().toString(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void record(LiveConfigDatabaseBean bean, LiveRoomInfo roomInfo) {
|
||||
this.config = bean;
|
||||
isDownload = true;
|
||||
LiveRoomConfig config = new LiveRoomConfig();
|
||||
config.setLoginUid(bean.getRecordUid());
|
||||
config.setRoomId(bean.getRoomId());
|
||||
config.setAnchorName(bean.getAnchorName());
|
||||
config.setLogin(StringUtils.hasText(bean.getRecordUid()));
|
||||
config.setRoomInfo(roomInfo);
|
||||
config.setRootPath(bean.getRecordPath());
|
||||
database = new BiliLiveDatabase(config);
|
||||
api.getLiveRoomPlayInfo(
|
||||
bean.getRoomId().toString(),
|
||||
LiveProtocol.getAll(),
|
||||
LiveVideoFormat.getAll(),
|
||||
LiveVideoCodec.getAll(),
|
||||
LiveVideoDefinition.ORIGINAL.getValue()).enqueue(new HttpCallback<LiveRoomPlayInfo>() {
|
||||
@Override
|
||||
public void onResponse(Headers headers, int code, String status, LiveRoomPlayInfo response, String rawResponse) {
|
||||
LiveRoomPlayInfo.Codec codec = response.getPlayurlInfo().getPlayurl().getStream().get(0).getFormat().get(0).getCodec().get(0);
|
||||
String url = codec.getUrlInfo().get(0).getHost() + codec.getBaseUrl() + codec.getUrlInfo().get(0).getExtra();
|
||||
|
||||
if (bean.getRecordLiveModel() == 1) {
|
||||
javaRecord(url, response);
|
||||
} else {
|
||||
ffmpeg(url, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void javaRecord(String url, LiveRoomPlayInfo playInfo) {
|
||||
|
||||
HttpDownloadUtils.download(new HttpDownloadUtils.Builder()
|
||||
.setUrl(url)
|
||||
.setPath(savePath)
|
||||
.setDownloadInterface(new DownloadInterface() {
|
||||
@Override
|
||||
public void onDownloadStart() {
|
||||
super.onDownloadStart();
|
||||
VideoTask.this.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDownloading(double soFarBytes, double totalBytes) {
|
||||
return isDownload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownload(File file) {
|
||||
super.onDownload(file);
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
super.onError(e);
|
||||
stop();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void ffmpeg(String url, LiveRoomPlayInfo playInfo) {
|
||||
String ffmpegPath = ConfigTools.load(ConfigTools.CONFIG, "ffmpeg", String.class);
|
||||
String cookie = "";
|
||||
LoginCookieDatabaseBean ck = BiliBiliLoginDatabase.getInstance().getCookie(config.getRecordUid());
|
||||
if (ck != null) {
|
||||
cookie = ck.toCookieString();
|
||||
}
|
||||
|
||||
FFmpegUtils command = new FFmpegUtils.Builder()
|
||||
.withParam("-user_agent", ConfigTools.getUserAgent())
|
||||
.withParam("-cookies", cookie)
|
||||
.withParam("-headers", "Referer: https://live.bilibili.com")
|
||||
// .withNotSymbolParam("-progress", "-")
|
||||
.withNotSymbolParam("-threads", "8")
|
||||
.withNotSymbolParam("-c:v", "copy")
|
||||
.withNotSymbolParam("-y", "")
|
||||
.build(ffmpegPath, url, savePath);
|
||||
System.out.println(command);
|
||||
try {
|
||||
command.start(new DownloadInterface() {
|
||||
@Override
|
||||
public void onDownloadStart() {
|
||||
super.onDownloadStart();
|
||||
VideoTask.this.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDownloading(double soFarBytes, double totalBytes) {
|
||||
if (!isDownload) {
|
||||
command.stop();
|
||||
}
|
||||
return super.onDownloading(soFarBytes, totalBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownload(File file) {
|
||||
super.onDownload(file);
|
||||
System.err.println("下载完成 ");
|
||||
stop();
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void onStart() {
|
||||
videoDatabaseBean = new LiveVideoDatabaseBean();
|
||||
videoDatabaseBean.setPath(savePath);
|
||||
videoDatabaseBean.setRoomInfoJson(JSONObject.toJSONString(roomInfo));
|
||||
videoDatabaseBean.setStartTime(new Date());
|
||||
database.addLiveInfo(videoDatabaseBean);
|
||||
}
|
||||
}
|
||||
|
||||
public List<VideoFilePath> getAllVideoPath() {
|
||||
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase();
|
||||
List<LiveConfigDatabaseBean> list = configDatabase.getAllConfig();
|
||||
configDatabase.close();
|
||||
List<VideoFilePath> filePathList = new ArrayList<>();
|
||||
for (LiveConfigDatabaseBean bean : list) {
|
||||
filePathList.addAll(getVideoFilePath(bean));
|
||||
}
|
||||
return filePathList;
|
||||
}
|
||||
|
||||
public List<VideoFilePath> getVideoPath(String roomId) {
|
||||
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase();
|
||||
LiveConfigDatabaseBean bean = configDatabase.getConfig(new BigInteger(roomId));
|
||||
configDatabase.close();
|
||||
return new ArrayList<>(getVideoFilePath(bean));
|
||||
}
|
||||
|
||||
private List<VideoFilePath> getVideoFilePath(LiveConfigDatabaseBean configBean) {
|
||||
List<VideoFilePath> filePathList = new ArrayList<>();
|
||||
String recordPath = configBean.getRecordPath() + File.separator + configBean.getAnchorName();
|
||||
File recordDir = new File(recordPath);
|
||||
if (recordDir.exists()) {
|
||||
|
||||
List<File> files = Tools.scanFile(recordDir).stream()
|
||||
.filter(file -> "live.db".equals(file.getName()))
|
||||
.toList();
|
||||
|
||||
if (!files.isEmpty()) {
|
||||
for (File db : files) {
|
||||
VideoFilePath path = createVideoRootFilePath(configBean, db);
|
||||
BiliLiveDatabase database = new BiliLiveDatabase(LiveRoomConfig.buildConfig(configBean.getRoomId().toString()), db.getAbsolutePath());
|
||||
List<LiveVideoDatabaseBean> infos = database.getLiveInfos();
|
||||
database.close();
|
||||
path.setChildren(getVideoInfo(infos));
|
||||
filePathList.add(path);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return filePathList;
|
||||
}
|
||||
|
||||
private VideoFilePath createVideoRootFilePath(LiveConfigDatabaseBean config, File db) {
|
||||
VideoFilePath path = new VideoFilePath();
|
||||
path.setRoomId(config.getRoomId().toString());
|
||||
path.setCover(config.getAnchorFace());
|
||||
path.setName(config.getAnchorName());
|
||||
path.setParent(true);
|
||||
path.setPath(FileServerUtils.toUrl(db.getParent()));
|
||||
return path;
|
||||
}
|
||||
|
||||
private List<VideoFilePath> getVideoInfo(List<LiveVideoDatabaseBean> videoList) {
|
||||
List<VideoFilePath> filePathList = new ArrayList<>();
|
||||
for (LiveVideoDatabaseBean bean : videoList) {
|
||||
VideoFilePath path = new VideoFilePath();
|
||||
LiveRoomInfo roomInfo = JSONObject.parseObject(bean.getRoomInfoJson(), LiveRoomInfo.class);
|
||||
path.setRoomId(roomInfo.getRoomId().toString());
|
||||
path.setName(roomInfo.getTitle());
|
||||
path.setPath(DateFormatUtils.format(bean.getSql_time()));
|
||||
path.setCover(FileServerUtils.toUrl(new File(bean.getPath()).getParent() + File.separator + "poster.jpg"));
|
||||
path.setParent(false);
|
||||
path.setChildren(null);
|
||||
filePathList.add(path);
|
||||
}
|
||||
return filePathList;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
LiveVideoService service = new LiveVideoService();
|
||||
List<VideoFilePath> path = service.getAllVideoPath();
|
||||
System.out.println("path.size() = " + path.size());
|
||||
System.out.println(JSONArray.toJSONString(path));
|
||||
}
|
||||
}
|
@ -27,6 +27,8 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class SystemService {
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
SystemConfigDatabases databases = new SystemConfigDatabases();
|
||||
private ScheduledExecutorService timer;
|
||||
private ScheduledFuture<?> scheduled;
|
||||
@ -50,6 +52,7 @@ public class SystemService {
|
||||
}
|
||||
scheduled = timer.scheduleAtFixedRate(() -> {
|
||||
List<LiveConfigDatabaseBean> list = liveConfigDatabase.getAllConfig();
|
||||
System.out.println("循环任务:"+list.size());
|
||||
for (LiveConfigDatabaseBean bean : list) {
|
||||
if (bean.isRecordDanmu() && bean.checkRecordDanmuTime()) {
|
||||
recordDanmu(bean);
|
||||
@ -60,7 +63,10 @@ public class SystemService {
|
||||
}
|
||||
}, 0, getLoopTimer(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
scheduled.cancel(true);
|
||||
videoService.stopAll();
|
||||
}
|
||||
// 录制弹幕
|
||||
private void recordDanmu(LiveConfigDatabaseBean bean) {
|
||||
LiveRoomConfig config = new LiveRoomConfig();
|
||||
@ -68,12 +74,13 @@ public class SystemService {
|
||||
config.setRoomId(bean.getRoomId());
|
||||
config.setAnchorName(bean.getAnchorName());
|
||||
config.setLogin(StringUtils.hasText(bean.getRecordUid()));
|
||||
WebSocketManager.getInstance().addRoom(config);
|
||||
config.setRootPath(bean.getRecordPath());
|
||||
WebSocketManager.getInstance().addRoom(config,false);
|
||||
}
|
||||
|
||||
// 录制视频
|
||||
private void recordVideo(LiveConfigDatabaseBean bean) {
|
||||
|
||||
videoService.start(bean, false);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
@ -85,4 +92,5 @@ public class SystemService {
|
||||
service.start();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ public class BiliBiliLiveDatabasesManager extends SQLiteManager {
|
||||
"values (%d,%d,%d,%d,'%s','%s',%d,'%s')"
|
||||
,data.getModel()
|
||||
,data.getFontSize()
|
||||
,data.getFontColor()
|
||||
// ,data.getFontColor()
|
||||
,data.getTime()
|
||||
,data.getUCode()
|
||||
,data.getDanmu()
|
||||
@ -87,7 +87,7 @@ public class BiliBiliLiveDatabasesManager extends SQLiteManager {
|
||||
data.setId(set.getInt("id"));
|
||||
data.setModel(set.getInt("model"));
|
||||
data.setFontSize(set.getInt("fontSize"));
|
||||
data.setFontColor(set.getInt("fontColor"));
|
||||
// data.setFontColor(set.getInt("fontColor"));
|
||||
data.setTime(set.getLong("time"));
|
||||
data.setUCode(set.getString("uCode"));
|
||||
data.setDanmu(set.getString("danmu"));
|
||||
|
@ -40,7 +40,7 @@ public class AbsDatabasesBean {
|
||||
json.put(key,"");
|
||||
}
|
||||
}
|
||||
System.out.println("创建" + tableName + "表 json:" + json);
|
||||
//System.out.println("创建" + tableName + "表 json:" + json);
|
||||
return json;
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import com.yutou.common.inter.ISqlDatabaseBean;
|
||||
import com.yutou.common.utils.Log;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import org.sqlite.SQLiteConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
@ -36,7 +37,9 @@ public abstract class SQLiteManager {
|
||||
items = new ArrayList<>();
|
||||
for (Field field : fields) {
|
||||
String name = field.getAnnotation(JSONField.class).name();
|
||||
if (name.equals("tableName")) continue;
|
||||
if ("tableName".equals(name)) {
|
||||
continue;
|
||||
}
|
||||
String type = BuildSqlItem.TYPE_STRING;
|
||||
if (field.getType() == int.class) {
|
||||
type = BuildSqlItem.TYPE_INT;
|
||||
@ -97,10 +100,10 @@ public abstract class SQLiteManager {
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends AbsDatabasesBean> void add(T t) {
|
||||
try {
|
||||
Statement statement = conn.createStatement();
|
||||
protected <T extends AbsDatabasesBean> void add(T t) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try {
|
||||
|
||||
StringBuilder value = new StringBuilder();
|
||||
sb.append("INSERT INTO `").append(t.getTableName()).append("` ");
|
||||
sb.append("(");
|
||||
@ -112,22 +115,44 @@ public abstract class SQLiteManager {
|
||||
continue;
|
||||
}
|
||||
sb.append("`").append(key).append("`,");
|
||||
value.append("'").append(json.get(key)).append("',");
|
||||
//value.append("'").append(json.get(key)).append("',");
|
||||
value.append("?").append(",");
|
||||
}
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
value.deleteCharAt(value.length() - 1);
|
||||
value.append(")");
|
||||
sb.append(") VALUES ");
|
||||
sb.append(value);
|
||||
statement.executeUpdate(sb.toString());
|
||||
PreparedStatement statement = conn.prepareStatement(sb.toString());
|
||||
int i = 1;
|
||||
for (String key : keySet) {
|
||||
if ("id".equals(key)) {
|
||||
continue;
|
||||
}
|
||||
if (json.get(key) instanceof String) {
|
||||
statement.setString(i++, json.get(key).toString());
|
||||
} else if (json.get(key) instanceof Integer) {
|
||||
statement.setInt(i++, json.getInteger(key));
|
||||
} else if (json.get(key) instanceof Long) {
|
||||
statement.setLong(i++, json.getLong(key));
|
||||
} else if (json.get(key) instanceof BigInteger) {
|
||||
statement.setObject(i++, json.get(key));
|
||||
} else if (json.get(key) instanceof Boolean) {
|
||||
statement.setBoolean(i++, json.getBoolean(key));
|
||||
} else {
|
||||
statement.setObject(i++, json.get(key));
|
||||
}
|
||||
}
|
||||
statement.execute();
|
||||
statement.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
System.err.println(sb);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends AbsDatabasesBean> void update(T t) {
|
||||
protected <T extends AbsDatabasesBean> void update(T t) {
|
||||
try {
|
||||
String id = DateUtils.format(t.getSql_time(), "yyyy-MM-dd HH:mm:ss.SSS");
|
||||
Statement statement = conn.createStatement();
|
||||
@ -158,11 +183,11 @@ public abstract class SQLiteManager {
|
||||
|
||||
}
|
||||
|
||||
public <T extends AbsDatabasesBean> List<T> get(String table, Class<T> tClass) {
|
||||
protected <T extends AbsDatabasesBean> List<T> get(String table, Class<T> tClass) {
|
||||
return get(table, null, tClass);
|
||||
}
|
||||
|
||||
public <T extends AbsDatabasesBean> List<T> get(String table, String where, Class<T> tClass) {
|
||||
protected <T extends AbsDatabasesBean> List<T> get(String table, String where, Class<T> tClass) {
|
||||
List<T> list = new ArrayList<>();
|
||||
try {
|
||||
Statement statement = conn.createStatement();
|
||||
@ -177,9 +202,16 @@ public abstract class SQLiteManager {
|
||||
String name = resultSet.getMetaData().getColumnName(i + 1);
|
||||
Object value = resultSet.getObject(name);
|
||||
if (value instanceof String && ((String) value).startsWith("[")) {
|
||||
try {
|
||||
json.put(name, JSON.parseArray((String) value));
|
||||
} catch (Exception e) {
|
||||
json.put(name, value);
|
||||
}
|
||||
} else {
|
||||
json.put(name, resultSet.getObject(name));
|
||||
json.put(name, value);
|
||||
if (json.get(name) == null || "null".equals(json.get(name).toString())) {
|
||||
json.put(name, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
list.add(json.to(tClass));
|
||||
@ -214,7 +246,7 @@ public abstract class SQLiteManager {
|
||||
try {
|
||||
sql.mkdirs();
|
||||
sql.delete();
|
||||
conn = DriverManager.getConnection(url + sql.getAbsolutePath());
|
||||
linkDB();
|
||||
} catch (Exception e) {
|
||||
Log.e(e);
|
||||
}
|
||||
@ -273,28 +305,17 @@ public abstract class SQLiteManager {
|
||||
if (!sql.exists()) {
|
||||
createSql(buildSql);
|
||||
} else {
|
||||
conn = DriverManager.getConnection(url + sql.getAbsolutePath());
|
||||
linkDB();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setDB(String fileName) {
|
||||
try {
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
sql = new File("db" + File.separator + fileName);
|
||||
if (sql.exists()) {
|
||||
if (conn != null && !conn.isClosed()) {
|
||||
conn.close();
|
||||
}
|
||||
conn = DriverManager.getConnection(url + sql.getAbsolutePath());
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(e);
|
||||
}
|
||||
return false;
|
||||
private void linkDB() throws SQLException {
|
||||
SQLiteConfig config = new SQLiteConfig();
|
||||
config.enforceForeignKeys(true);
|
||||
conn = config.createConnection(url + sql.getAbsolutePath());
|
||||
}
|
||||
|
||||
|
||||
|
@ -18,10 +18,11 @@ public abstract class FileCallback<T> implements Callback<FileBody<T>> {
|
||||
|
||||
private static ThreadPoolExecutor executor;
|
||||
private final T bean;
|
||||
private String savePath;
|
||||
|
||||
|
||||
public FileCallback(T bean) {
|
||||
public FileCallback(T bean, String savePath) {
|
||||
this.bean = bean;
|
||||
this.savePath = savePath;
|
||||
if (executor == null) {
|
||||
executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
|
||||
}
|
||||
@ -45,7 +46,7 @@ public abstract class FileCallback<T> implements Callback<FileBody<T>> {
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("开始下载");
|
||||
File file = new File("download" + File.separator + System.currentTimeMillis() + ".flv");
|
||||
File file = new File(savePath);
|
||||
onStart(bean);
|
||||
if (!file.exists()) {
|
||||
boolean mkdirs = file.getParentFile().mkdirs();
|
||||
@ -67,6 +68,7 @@ public abstract class FileCallback<T> implements Callback<FileBody<T>> {
|
||||
outputStream.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
onFailure(bean,e);
|
||||
} finally {
|
||||
onFinish(bean);
|
||||
try {
|
||||
@ -91,7 +93,13 @@ public abstract class FileCallback<T> implements Callback<FileBody<T>> {
|
||||
|
||||
@Override
|
||||
public void onResponse(Call<FileBody<T>> call, Response<FileBody<T>> response) {
|
||||
try {
|
||||
executor.execute(new DownloadTask(bean, response.headers(), call.request().url(), response.body().getInputStream()));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
onFailure(bean,e);
|
||||
call.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,48 +1,115 @@
|
||||
package com.yutou.common.okhttp;
|
||||
|
||||
import com.yutou.bilibili.interfaces.DownloadInterface;
|
||||
import com.yutou.common.utils.ConfigTools;
|
||||
import com.yutou.common.utils.Log;
|
||||
import lombok.Data;
|
||||
import okhttp3.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class HttpDownloadUtils {
|
||||
public static void download(String url, String path,String fileName) {
|
||||
OkHttpClient okHttpClient = new OkHttpClient();
|
||||
Request request = new Request.Builder()
|
||||
.get()
|
||||
.url(url)
|
||||
public static void download(Builder builder) {
|
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||
.connectTimeout(2, TimeUnit.MINUTES)
|
||||
.readTimeout(2, TimeUnit.MINUTES)
|
||||
.build();
|
||||
Request.Builder rb = new Request.Builder()
|
||||
.get()
|
||||
.addHeader("User-Agent", ConfigTools.getUserAgent())
|
||||
.url(builder.url);
|
||||
if (StringUtils.hasText(builder.cookie)) {
|
||||
rb.addHeader("Set-Cookie", builder.cookie);
|
||||
}
|
||||
Request request = rb.build();
|
||||
Call call = okHttpClient.newCall(request);
|
||||
call.enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NotNull Call call, @NotNull IOException e) {
|
||||
|
||||
if (builder.downloadInterface != null) {
|
||||
builder.downloadInterface.onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
|
||||
InputStream inputStream = Objects.requireNonNull(response.body()).byteStream();
|
||||
File target = new File(path, fileName);
|
||||
File target;
|
||||
if (StringUtils.hasText(builder.fileName)) {
|
||||
target = new File(builder.path, builder.fileName);
|
||||
} else {
|
||||
target = new File(builder.path);
|
||||
}
|
||||
FileOutputStream fileOutputStream = new FileOutputStream(target);
|
||||
|
||||
if (builder.downloadInterface != null) {
|
||||
builder.downloadInterface.onDownloadStart();
|
||||
}
|
||||
try {
|
||||
byte[] buffer = new byte[2048];
|
||||
int len;
|
||||
long soFarBytes = 0;
|
||||
long totalBytes = inputStream.available();
|
||||
while ((len = inputStream.read(buffer)) != -1) {
|
||||
fileOutputStream.write(buffer, 0, len);
|
||||
soFarBytes += len;
|
||||
if (builder.downloadInterface != null) {
|
||||
if (!builder.downloadInterface.onDownloading(soFarBytes, totalBytes)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fileOutputStream.flush();
|
||||
} catch (IOException e) {
|
||||
Log.getLogger("download").log(Level.FINE, "download error:"+url, e);
|
||||
Log.getLogger("download").log(Level.FINE, "download error:" + builder.url, e);
|
||||
} finally {
|
||||
if (builder.downloadInterface != null) {
|
||||
builder.downloadInterface.onDownload(target);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Builder {
|
||||
String url;
|
||||
String path;
|
||||
String fileName;
|
||||
DownloadInterface downloadInterface;
|
||||
String cookie;
|
||||
|
||||
public Builder setUrl(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPath(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDownloadInterface(DownloadInterface downloadInterface) {
|
||||
this.downloadInterface = downloadInterface;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCookie(String cookie) {
|
||||
this.cookie = cookie;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.yutou.common.okhttp;
|
||||
|
||||
import com.yutou.common.utils.ConfigTools;
|
||||
import okhttp3.Request;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -38,7 +39,7 @@ public class ParamsContext {
|
||||
break;
|
||||
}
|
||||
headerMap.remove("tableName");
|
||||
headerMap.put("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36");
|
||||
headerMap.put("User-Agent", ConfigTools.getUserAgent());
|
||||
return iRequestParam.getRequest(headerMap, map, request);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package com.yutou.common.utils;
|
||||
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
@ -93,4 +94,16 @@ public class ConfigTools {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getUserAgent() {
|
||||
String ua=load(CONFIG,"userAgent",String.class);
|
||||
if(!StringUtils.hasText(ua)){
|
||||
ua="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36";
|
||||
}
|
||||
return ua;
|
||||
}
|
||||
public static boolean saveUserAgent(String ua) {
|
||||
return save(CONFIG,"userAgent",ua);
|
||||
|
||||
}
|
||||
}
|
||||
|
128
src/main/java/com/yutou/common/utils/FFmpegUtils.java
Normal file
128
src/main/java/com/yutou/common/utils/FFmpegUtils.java
Normal file
@ -0,0 +1,128 @@
|
||||
package com.yutou.common.utils;
|
||||
|
||||
import com.yutou.bilibili.interfaces.DownloadInterface;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FFmpegUtils {
|
||||
|
||||
private String command;
|
||||
InputStream inputStream;
|
||||
OutputStream outputStream;
|
||||
|
||||
private FFmpegUtils() {
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final Map<String, String> params = new LinkedHashMap<>();
|
||||
private final Map<String, String> paramsNotSymbol = new LinkedHashMap<>();
|
||||
|
||||
public Builder withParam(String key, String value) {
|
||||
if (value != null) {
|
||||
params.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withNotSymbolParam(String key, String value) {
|
||||
if (value != null) {
|
||||
paramsNotSymbol.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private String getCommand(String ffmpegPath, String inputUrl, String outputPath) {
|
||||
StringBuilder command = new StringBuilder(ffmpegPath);
|
||||
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
command.append(" ").append(entry.getKey());
|
||||
if (entry.getValue() != null && !entry.getValue().isEmpty()) {
|
||||
command.append(" ").append("\"").append(entry.getValue()).append("\"");
|
||||
}
|
||||
}
|
||||
command.append(" -i ").append("\"").append(inputUrl).append("\"");
|
||||
for (Map.Entry<String, String> entry : paramsNotSymbol.entrySet()) {
|
||||
command.append(" ").append(entry.getKey());
|
||||
if (entry.getValue() != null && !entry.getValue().isEmpty()) {
|
||||
command.append(" ").append(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
command.append(" ").append("\"").append(outputPath).append("\"");
|
||||
|
||||
return command.toString();
|
||||
|
||||
}
|
||||
|
||||
public FFmpegUtils build(String ffmpegPath, String inputUrl, String outputPath) {
|
||||
FFmpegUtils ffmpeg = new FFmpegUtils();
|
||||
ffmpeg.command = getCommand(ffmpegPath, inputUrl, outputPath);
|
||||
return ffmpeg;
|
||||
}
|
||||
}
|
||||
|
||||
public void start(DownloadInterface downloadInterface) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Process process = AppTools.exec(command);
|
||||
inputStream = process.getErrorStream();
|
||||
outputStream = process.getOutputStream();
|
||||
byte[] bytes = new byte[2048];
|
||||
if (downloadInterface != null) {
|
||||
downloadInterface.onDownloadStart();
|
||||
}
|
||||
while (inputStream.read(bytes) > -1) {
|
||||
if (downloadInterface != null) {
|
||||
downloadInterface.onDownloading(0, 0);
|
||||
}
|
||||
}
|
||||
//获取视频时长:ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4
|
||||
//获取到结果:5372.432000
|
||||
if (downloadInterface != null) {
|
||||
System.out.println("触发下载完成");
|
||||
downloadInterface.onDownload(null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
outputStream.write("q".getBytes());
|
||||
outputStream.flush();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频时长
|
||||
* @param video 视频文件
|
||||
* @return 毫秒
|
||||
*/
|
||||
public static long getVideoTime(File video) {
|
||||
String ffprobe = ConfigTools.load(ConfigTools.CONFIG, "ffprobe", String.class);
|
||||
String exec = ffprobe + " -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 \"" + video.getAbsolutePath() + "\"";
|
||||
try {
|
||||
System.out.println(exec);
|
||||
Process process = AppTools.exec(exec);
|
||||
InputStream inputStream = process.getInputStream();
|
||||
byte[] bytes = new byte[2048];
|
||||
String data;
|
||||
inputStream.read(bytes);
|
||||
data = new String(bytes);
|
||||
inputStream.close();
|
||||
process.destroy();
|
||||
process.exitValue();
|
||||
return (long) (Double.parseDouble(data)*1000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user