diff --git a/src/main/java/com/yutou/biliapi/Main.java b/src/main/java/com/yutou/biliapi/Main.java index b08ed4f..4d06ad1 100644 --- a/src/main/java/com/yutou/biliapi/Main.java +++ b/src/main/java/com/yutou/biliapi/Main.java @@ -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) { diff --git a/src/main/java/com/yutou/biliapi/bean/live/LiveRoomConfig.java b/src/main/java/com/yutou/biliapi/bean/live/LiveRoomConfig.java index 3a42d8c..227adfa 100644 --- a/src/main/java/com/yutou/biliapi/bean/live/LiveRoomConfig.java +++ b/src/main/java/com/yutou/biliapi/bean/live/LiveRoomConfig.java @@ -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; + } } diff --git a/src/main/java/com/yutou/biliapi/bean/live/database/LiveConfigDatabaseBean.java b/src/main/java/com/yutou/biliapi/bean/live/database/LiveConfigDatabaseBean.java index fddce05..987ef65 100644 --- a/src/main/java/com/yutou/biliapi/bean/live/database/LiveConfigDatabaseBean.java +++ b/src/main/java/com/yutou/biliapi/bean/live/database/LiveConfigDatabaseBean.java @@ -26,8 +26,12 @@ public class LiveConfigDatabaseBean extends AbsDatabasesBean { private boolean isRecordDanmu; @JSONField(name = "keyword") private List 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") diff --git a/src/main/java/com/yutou/biliapi/bean/live/database/LiveDanmuDatabaseBean.java b/src/main/java/com/yutou/biliapi/bean/live/database/LiveDanmuDatabaseBean.java index 2aa4f0f..d5aec40 100644 --- a/src/main/java/com/yutou/biliapi/bean/live/database/LiveDanmuDatabaseBean.java +++ b/src/main/java/com/yutou/biliapi/bean/live/database/LiveDanmuDatabaseBean.java @@ -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,13 +30,12 @@ public class LiveDanmuDatabaseBean extends AbsDatabasesBean { private String uname; - public LiveDanmuDatabaseBean() { - super("danmu",System.currentTimeMillis()); + super("danmu", System.currentTimeMillis()); } public LiveDanmuDatabaseBean(WSDanmuData danmu) { - super("danmu",danmu.getWs_timer()); + super("danmu", danmu.getWs_timer()); this.danmu = danmu.getDanmu(); model = danmu.getModel(); fontSize = danmu.getFontSize(); @@ -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; + } } diff --git a/src/main/java/com/yutou/biliapi/bean/live/database/LiveVideoDatabaseBean.java b/src/main/java/com/yutou/biliapi/bean/live/database/LiveVideoDatabaseBean.java new file mode 100644 index 0000000..73a6537 --- /dev/null +++ b/src/main/java/com/yutou/biliapi/bean/live/database/LiveVideoDatabaseBean.java @@ -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()); + } +} diff --git a/src/main/java/com/yutou/biliapi/bean/login/LoginCookieDatabaseBean.java b/src/main/java/com/yutou/biliapi/bean/login/LoginCookieDatabaseBean.java index 40b3e8c..dac5233 100644 --- a/src/main/java/com/yutou/biliapi/bean/login/LoginCookieDatabaseBean.java +++ b/src/main/java/com/yutou/biliapi/bean/login/LoginCookieDatabaseBean.java @@ -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; + } } diff --git a/src/main/java/com/yutou/biliapi/databases/BiliBiliLoginDatabase.java b/src/main/java/com/yutou/biliapi/databases/BiliBiliLoginDatabase.java index 01c9f23..9041393 100644 --- a/src/main/java/com/yutou/biliapi/databases/BiliBiliLoginDatabase.java +++ b/src/main/java/com/yutou/biliapi/databases/BiliBiliLoginDatabase.java @@ -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 diff --git a/src/main/java/com/yutou/biliapi/databases/BiliLiveConfigDatabase.java b/src/main/java/com/yutou/biliapi/databases/BiliLiveConfigDatabase.java index 961c62d..eacfd9e 100644 --- a/src/main/java/com/yutou/biliapi/databases/BiliLiveConfigDatabase.java +++ b/src/main/java/com/yutou/biliapi/databases/BiliLiveConfigDatabase.java @@ -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 getAllConfig() { List list = get(getDataBean().get(0).getTableName(), LiveConfigDatabaseBean.class); if (list.isEmpty()) { diff --git a/src/main/java/com/yutou/biliapi/databases/BiliLiveDatabase.java b/src/main/java/com/yutou/biliapi/databases/BiliLiveDatabase.java index ab2fa35..3efe787 100644 --- a/src/main/java/com/yutou/biliapi/databases/BiliLiveDatabase.java +++ b/src/main/java/com/yutou/biliapi/databases/BiliLiveDatabase.java @@ -4,36 +4,51 @@ 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()); - config=roomConfig; - if(!rootPath.exists()){ + rootPath = new File(roomConfig.getRootPath() + File.separator + roomConfig.getAnchorName() + File.separator + time + File.separator + roomConfig.getRoomInfo().getTitle()); + config = roomConfig; + if (!rootPath.exists()) { rootPath.mkdirs(); } - fileName=rootPath.getAbsolutePath()+File.separator+"live.db"; + fileName = rootPath.getAbsolutePath() + File.separator + "live.db"; init(); } @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); + @Override + public void close() { + super.close(); + } - List infos = get(bean.getTableName(), LiveInfoDatabaseBean.class); - if (infos.isEmpty()) { - createInfo(bean); - } + public void addLiveInfo(LiveVideoDatabaseBean info) { + createInfo(info); + } + + public List 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) { - add(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 getSource(long startTime, long entTime) { - return get(startTime, entTime, LiveSourceDatabaseBean.class); - } - - public List get(long startTime, long entTime, Class clazz) { + public List getOfTime(String startTime, String entTime, Class 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(); diff --git a/src/main/java/com/yutou/biliapi/net/WebSocketManager.java b/src/main/java/com/yutou/biliapi/net/WebSocketManager.java index 6e3bd03..baa2e4d 100644 --- a/src/main/java/com/yutou/biliapi/net/WebSocketManager.java +++ b/src/main/java/com/yutou/biliapi/net/WebSocketManager.java @@ -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 roomMap; + Map roomMap; + private final List userStopList = new ArrayList<>();//手动停止列表 private WebSocketManager() { roomMap = new HashMap<>(); + executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue(100)); } public static WebSocketManager getInstance() { @@ -56,68 +59,107 @@ 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; } - LiveApi api = BiliLiveNetApiManager.getInstance().getApi(roomConfig.getLoginUid()); - Response> execute = null; - try { - execute = api.getRoomInfo(roomConfig.getRoomId().toString()).execute(); - if (execute.isSuccessful()) { - roomConfig.setRoomInfo(execute.body() != null ? execute.body().getData() : null); - } - - } catch (IOException e) { - throw new RuntimeException(e); + if (isUser) { + userStopList.remove(roomConfig.getRoomId().toString()); } - api.getLiveRoomDanmuInfo(String.valueOf(roomConfig.getLoginUid())).enqueue(new HttpCallback() { - @Override - public void onResponse(Headers headers, int code, String status, LiveDanmuInfo response, String rawResponse) { - if (!response.getHostList().isEmpty()) { - LiveDanmuInfo.Host host = response.getHostList().get(0); - String url = "wss://" + host.getHost() + ":" + host.getWssPort() + "/sub"; - // url="ws://127.0.0.1:8765"; - try { - roomConfig.setLiveInfo(response); - new WebSocketClientTh(new URI(url), roomConfig); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - } - } - - @Override - public void onFailure(Throwable throwable) { - throwable.printStackTrace(); - } - }); + DanmuTask task = new DanmuTask(roomConfig); + roomMap.put(roomConfig, task); + System.out.println("添加websocket任务"); + executor.execute(task); } - public void stopRoom(LiveRoomConfig roomConfig) { + 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> execute = null; + try { + execute = api.getRoomInfo(roomConfig.getRoomId().toString()).execute(); + if (execute.isSuccessful()) { + roomConfig.setRoomInfo(execute.body() != null ? execute.body().getData() : null); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + api.getLiveRoomDanmuInfo(String.valueOf(roomConfig.getRoomId())).enqueue(new HttpCallback() { + @Override + public void onResponse(Headers headers, int code, String status, LiveDanmuInfo response, String rawResponse) { + if (!response.getHostList().isEmpty()) { + LiveDanmuInfo.Host host = response.getHostList().get(0); + String url = "wss://" + host.getHost() + ":" + host.getWssPort() + "/sub"; + // url="ws://127.0.0.1:8765"; + try { + roomConfig.setLiveInfo(response); + client = new WebSocketClientTh(new URI(url), roomConfig); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void onFailure(Throwable throwable) { + throwable.printStackTrace(); + } + }); + } + + public void close() { + client.close(); + } } private static class WebSocketClientTh extends WebSocketClient { 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("解压失败")); } diff --git a/src/main/java/com/yutou/bilibili/Controllers/LiveConfigController.java b/src/main/java/com/yutou/bilibili/Controllers/LiveConfigController.java index e148d37..da6d4c5 100644 --- a/src/main/java/com/yutou/bilibili/Controllers/LiveConfigController.java +++ b/src/main/java/com/yutou/bilibili/Controllers/LiveConfigController.java @@ -24,7 +24,7 @@ public class LiveConfigController { @RequestMapping(value = "set", method = RequestMethod.POST) @ResponseBody - public ResultData 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 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 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 getAllConfig() { + public JSONObject getAllConfig() { List 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 deleteConfig(BigInteger roomId) { + public JSONObject deleteConfig(BigInteger roomId) { if (roomId.equals(BigInteger.ZERO)) { return ResultData.fail(ReturnCode.RC999); } diff --git a/src/main/java/com/yutou/bilibili/Controllers/LiveDanmuController.java b/src/main/java/com/yutou/bilibili/Controllers/LiveDanmuController.java new file mode 100644 index 0000000..b91022a --- /dev/null +++ b/src/main/java/com/yutou/bilibili/Controllers/LiveDanmuController.java @@ -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)); + } +} diff --git a/src/main/java/com/yutou/bilibili/Controllers/LiveVideoController.java b/src/main/java/com/yutou/bilibili/Controllers/LiveVideoController.java new file mode 100644 index 0000000..1ad803c --- /dev/null +++ b/src/main/java/com/yutou/bilibili/Controllers/LiveVideoController.java @@ -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 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); + } +} diff --git a/src/main/java/com/yutou/bilibili/Controllers/VideoFileController.java b/src/main/java/com/yutou/bilibili/Controllers/VideoFileController.java new file mode 100644 index 0000000..21d1b21 --- /dev/null +++ b/src/main/java/com/yutou/bilibili/Controllers/VideoFileController.java @@ -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 list; + if (StringUtils.hasText(roomId)) { + list = videoService.getVideoPath(roomId); + } else { + list = videoService.getAllVideoPath(); + } + return ResultData.success(list); + } + @RequestMapping("/file/{base64}") + @ResponseBody + public ResponseEntity getFile(@PathVariable String base64) { + File file = FileServerUtils.toFile(base64); + System.out.println(file.getAbsolutePath()); + return Tools.getFile(file); + } + +} diff --git a/src/main/java/com/yutou/bilibili/Tools/ApplicationClose.java b/src/main/java/com/yutou/bilibili/Tools/ApplicationClose.java index 76efb99..9718536 100644 --- a/src/main/java/com/yutou/bilibili/Tools/ApplicationClose.java +++ b/src/main/java/com/yutou/bilibili/Tools/ApplicationClose.java @@ -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 { + @Resource + SystemService systemConfigService; @Override public void onApplicationEvent(ContextClosedEvent contextClosedEvent) { Log.i("服务结束"); + systemConfigService.stop(); } } diff --git a/src/main/java/com/yutou/bilibili/Tools/AssTools.java b/src/main/java/com/yutou/bilibili/Tools/AssTools.java index 0c7e3f2..585053c 100644 --- a/src/main/java/com/yutou/bilibili/Tools/AssTools.java +++ b/src/main/java/com/yutou/bilibili/Tools/AssTools.java @@ -17,7 +17,7 @@ public class AssTools { private final Date startTime; private int y = 0; private List 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() ); diff --git a/src/main/java/com/yutou/bilibili/Tools/DateFormatUtils.java b/src/main/java/com/yutou/bilibili/Tools/DateFormatUtils.java new file mode 100644 index 0000000..0042115 --- /dev/null +++ b/src/main/java/com/yutou/bilibili/Tools/DateFormatUtils.java @@ -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()); + } +} diff --git a/src/main/java/com/yutou/bilibili/Tools/FileServerUtils.java b/src/main/java/com/yutou/bilibili/Tools/FileServerUtils.java new file mode 100644 index 0000000..777131c --- /dev/null +++ b/src/main/java/com/yutou/bilibili/Tools/FileServerUtils.java @@ -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)); + } +} diff --git a/src/main/java/com/yutou/bilibili/Tools/Tools.java b/src/main/java/com/yutou/bilibili/Tools/Tools.java index b108a9a..b37f4b6 100644 --- a/src/main/java/com/yutou/bilibili/Tools/Tools.java +++ b/src/main/java/com/yutou/bilibili/Tools/Tools.java @@ -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 scanFile(File file){ + List list = new ArrayList<>(); + FileVisitor 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); + } + } } diff --git a/src/main/java/com/yutou/bilibili/datas/DanmuData.java b/src/main/java/com/yutou/bilibili/datas/DanmuData.java index f9376d8..c162832 100644 --- a/src/main/java/com/yutou/bilibili/datas/DanmuData.java +++ b/src/main/java/com/yutou/bilibili/datas/DanmuData.java @@ -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); - } } diff --git a/src/main/java/com/yutou/bilibili/datas/ResultData.java b/src/main/java/com/yutou/bilibili/datas/ResultData.java index 9eb8c3a..f48af0c 100644 --- a/src/main/java/com/yutou/bilibili/datas/ResultData.java +++ b/src/main/java/com/yutou/bilibili/datas/ResultData.java @@ -1,5 +1,6 @@ package com.yutou.bilibili.datas; +import com.alibaba.fastjson2.JSONObject; import lombok.Data; @Data @@ -9,33 +10,33 @@ public class ResultData { private T data; private long timestamp ; - public ResultData (){ + public ResultData() { this.timestamp = System.currentTimeMillis(); } - public static ResultData success(T data) { + public static JSONObject success(T data) { ResultData 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 ResultData fail(int code, String message) { + public static JSONObject fail(int code, String message) { ResultData resultData = new ResultData<>(); resultData.setStatus(code); resultData.setMessage(message); - return resultData; + return JSONObject.parseObject(JSONObject.toJSONString(resultData)); } - public static ResultData success(ReturnCode code) { + public static JSONObject success(ReturnCode code) { return fail(code); } - public static ResultData fail(ReturnCode code) { + public static JSONObject fail(ReturnCode code) { ResultData resultData = new ResultData<>(); resultData.setStatus(code.getCode()); resultData.setMessage(code.getMessage()); - return resultData; + return JSONObject.parseObject(JSONObject.toJSONString(resultData)); } } diff --git a/src/main/java/com/yutou/bilibili/datas/SystemConfigDatabaseBean.java b/src/main/java/com/yutou/bilibili/datas/SystemConfigDatabaseBean.java index 783974a..8a753f0 100644 --- a/src/main/java/com/yutou/bilibili/datas/SystemConfigDatabaseBean.java +++ b/src/main/java/com/yutou/bilibili/datas/SystemConfigDatabaseBean.java @@ -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() { diff --git a/src/main/java/com/yutou/bilibili/datas/VideoFilePath.java b/src/main/java/com/yutou/bilibili/datas/VideoFilePath.java new file mode 100644 index 0000000..976e9e8 --- /dev/null +++ b/src/main/java/com/yutou/bilibili/datas/VideoFilePath.java @@ -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 children; +} diff --git a/src/main/java/com/yutou/bilibili/interfaces/DownloadInterface.java b/src/main/java/com/yutou/bilibili/interfaces/DownloadInterface.java index 3cee5af..e413d6d 100644 --- a/src/main/java/com/yutou/bilibili/interfaces/DownloadInterface.java +++ b/src/main/java/com/yutou/bilibili/interfaces/DownloadInterface.java @@ -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){} } diff --git a/src/main/java/com/yutou/bilibili/services/LiveDanmuService.java b/src/main/java/com/yutou/bilibili/services/LiveDanmuService.java index 60cdb25..6395458 100644 --- a/src/main/java/com/yutou/bilibili/services/LiveDanmuService.java +++ b/src/main/java/com/yutou/bilibili/services/LiveDanmuService.java @@ -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 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 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 files = service.getDanmuFileList("22047448"); + for (File file : files) { + System.out.println(file); + } + + } + + } diff --git a/src/main/java/com/yutou/bilibili/services/LiveVideoService.java b/src/main/java/com/yutou/bilibili/services/LiveVideoService.java new file mode 100644 index 0000000..07cdca9 --- /dev/null +++ b/src/main/java/com/yutou/bilibili/services/LiveVideoService.java @@ -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 liveVideoMap = new HashMap<>(); + private final List userStopList = new ArrayList<>();//手动停止列表 + + + public LiveVideoService() { + System.out.println("初始化下载服务"); + executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue(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() { + @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 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() { + @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() { + @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 getAllVideoPath() { + BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase(); + List list = configDatabase.getAllConfig(); + configDatabase.close(); + List filePathList = new ArrayList<>(); + for (LiveConfigDatabaseBean bean : list) { + filePathList.addAll(getVideoFilePath(bean)); + } + return filePathList; + } + + public List getVideoPath(String roomId) { + BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase(); + LiveConfigDatabaseBean bean = configDatabase.getConfig(new BigInteger(roomId)); + configDatabase.close(); + return new ArrayList<>(getVideoFilePath(bean)); + } + + private List getVideoFilePath(LiveConfigDatabaseBean configBean) { + List filePathList = new ArrayList<>(); + String recordPath = configBean.getRecordPath() + File.separator + configBean.getAnchorName(); + File recordDir = new File(recordPath); + if (recordDir.exists()) { + + List 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 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 getVideoInfo(List videoList) { + List 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 path = service.getAllVideoPath(); + System.out.println("path.size() = " + path.size()); + System.out.println(JSONArray.toJSONString(path)); + } +} diff --git a/src/main/java/com/yutou/bilibili/services/SystemService.java b/src/main/java/com/yutou/bilibili/services/SystemService.java index ce565e7..cfc89a3 100644 --- a/src/main/java/com/yutou/bilibili/services/SystemService.java +++ b/src/main/java/com/yutou/bilibili/services/SystemService.java @@ -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 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(); } + } diff --git a/src/main/java/com/yutou/bilibili/sqlite/BiliBiliLiveDatabasesManager.java b/src/main/java/com/yutou/bilibili/sqlite/BiliBiliLiveDatabasesManager.java index 9ac76c5..e65bd31 100644 --- a/src/main/java/com/yutou/bilibili/sqlite/BiliBiliLiveDatabasesManager.java +++ b/src/main/java/com/yutou/bilibili/sqlite/BiliBiliLiveDatabasesManager.java @@ -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")); diff --git a/src/main/java/com/yutou/common/databases/AbsDatabasesBean.java b/src/main/java/com/yutou/common/databases/AbsDatabasesBean.java index 03b5b12..e31bfb4 100644 --- a/src/main/java/com/yutou/common/databases/AbsDatabasesBean.java +++ b/src/main/java/com/yutou/common/databases/AbsDatabasesBean.java @@ -40,7 +40,7 @@ public class AbsDatabasesBean { json.put(key,""); } } - System.out.println("创建" + tableName + "表 json:" + json); + //System.out.println("创建" + tableName + "表 json:" + json); return json; } diff --git a/src/main/java/com/yutou/common/databases/SQLiteManager.java b/src/main/java/com/yutou/common/databases/SQLiteManager.java index 9c6e7d5..2d13de6 100644 --- a/src/main/java/com/yutou/common/databases/SQLiteManager.java +++ b/src/main/java/com/yutou/common/databases/SQLiteManager.java @@ -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 void add(T t) { + protected void add(T t) { + StringBuilder sb = new StringBuilder(); try { - Statement statement = conn.createStatement(); - StringBuilder sb = new StringBuilder(); + 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 void update(T t) { + protected 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 List get(String table, Class tClass) { + protected List get(String table, Class tClass) { return get(table, null, tClass); } - public List get(String table, String where, Class tClass) { + protected List get(String table, String where, Class tClass) { List 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("[")) { - json.put(name, JSON.parseArray((String) value)); + 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()); } diff --git a/src/main/java/com/yutou/common/okhttp/FileCallback.java b/src/main/java/com/yutou/common/okhttp/FileCallback.java index ae74f24..10a879a 100644 --- a/src/main/java/com/yutou/common/okhttp/FileCallback.java +++ b/src/main/java/com/yutou/common/okhttp/FileCallback.java @@ -18,10 +18,11 @@ public abstract class FileCallback implements Callback> { 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(100)); } @@ -45,7 +46,7 @@ public abstract class FileCallback implements Callback> { 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 implements Callback> { outputStream.close(); } catch (Exception e) { e.printStackTrace(); + onFailure(bean,e); } finally { onFinish(bean); try { @@ -91,7 +93,13 @@ public abstract class FileCallback implements Callback> { @Override public void onResponse(Call> call, Response> response) { - executor.execute(new DownloadTask(bean, response.headers(), call.request().url(), response.body().getInputStream())); + 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 diff --git a/src/main/java/com/yutou/common/okhttp/HttpDownloadUtils.java b/src/main/java/com/yutou/common/okhttp/HttpDownloadUtils.java index 37e9224..6213a37 100644 --- a/src/main/java/com/yutou/common/okhttp/HttpDownloadUtils.java +++ b/src/main/java/com/yutou/common/okhttp/HttpDownloadUtils.java @@ -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; + } + } } diff --git a/src/main/java/com/yutou/common/okhttp/ParamsContext.java b/src/main/java/com/yutou/common/okhttp/ParamsContext.java index 21ef4b8..3ac1e3f 100644 --- a/src/main/java/com/yutou/common/okhttp/ParamsContext.java +++ b/src/main/java/com/yutou/common/okhttp/ParamsContext.java @@ -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); } } \ No newline at end of file diff --git a/src/main/java/com/yutou/common/utils/ConfigTools.java b/src/main/java/com/yutou/common/utils/ConfigTools.java index 039c890..bbbcf22 100644 --- a/src/main/java/com/yutou/common/utils/ConfigTools.java +++ b/src/main/java/com/yutou/common/utils/ConfigTools.java @@ -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); + + } } diff --git a/src/main/java/com/yutou/common/utils/FFmpegUtils.java b/src/main/java/com/yutou/common/utils/FFmpegUtils.java new file mode 100644 index 0000000..d817581 --- /dev/null +++ b/src/main/java/com/yutou/common/utils/FFmpegUtils.java @@ -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 params = new LinkedHashMap<>(); + private final Map 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 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 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); + } + } +}