新增FFmpeg录制方法,但如果意外中断会存储失败

This commit is contained in:
yutou 2021-04-15 18:34:19 +08:00
parent e94033b3fd
commit 5666582641
9 changed files with 222 additions and 67 deletions

View File

@ -50,7 +50,7 @@
</div>
</form>
</div>
<div id="config" class="layui-card" style="margin-top: 5%;height: 100%;display: none">
<div id="config" class="layui-card" style="margin-top: 5%;height: 120%;display: none">
<div class="layui-card-header">系统设置</div>
<div class="layui-card-body">
<form class="layui-form" action="" style="margin-top: 2%;width: 80%">
@ -68,6 +68,15 @@
lay-text="开启|关闭">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">录制器</label>
<div class="layui-input-block">
<input id="savemodel" type="checkbox" lay-filter="saveLiveModel" lay-skin="switch"
lay-text="FFMPEG| Java ">
<i class="layui-icon" id="htitle">&#xe60b;</i>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">FFmpeg</label>
<div class="layui-input-block">
@ -121,6 +130,15 @@
form.render();
}
})
$.post('/system/get/savelive.do',function (json){
if (json.code === undefined || json.code !== 0) {
return;
}
if (json.data.save_live_model) {
$('#savemodel').prop('checked', true);
}
form.render();
})
form.on('switch(userReg)', function (data) {
let flag = data.elem.checked ? '1' : '0';
$.post("/system/set/config.do", {key: "userReg", value: flag}, function (json) {
@ -137,6 +155,14 @@
}
})
})
form.on('switch(saveLiveModel)',function (data){
let flag = data.elem.checked ? 'ffmpeg' : 'java';
$.post("/system/set/savelive.do", {model: flag}, function (json) {
if (json.code !== undefined) {
layer.msg(json.msg)
}
})
})
});
$.post('/bili/login/get/test.do', function (json) {
if (json.code === undefined || json.code !== 0) {
@ -153,6 +179,7 @@
$('#ffmpeg').val(json.data.ffmpeg_path);
})
$('#login').click(function () {
$.post('/bili/login/set/login.do', function (json) {
if (json.code === undefined || json.code !== 0) {
@ -184,7 +211,12 @@
})
$('#header').load("/html/header.html");
$('#footer').load("/html/footer.html");
$('#htitle').on('click', function () {
layer.open({
title:"录制模式",
content:'FFmpeg录制能保留视频信息如时间戳之类的但对性能要求比较高。<br>Java录制使用javaApi录制性能要求较低但无法记录视频信息如没有时间戳且有可能因为超时而断开录制。<br>建议性能足够的情况下使用FFmpeg。<br>需要设置FFmpeg的路径'
})
});
</script>
</body>
<style>

View File

@ -0,0 +1,7 @@
package com.yutou.bilibili.BiliBili.Datas;
public class AppData {
public static String FFMPEG="";
public static String BILIBILI_HEADERS = "User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36 Referer:https://live.bilibili.com";
public static boolean LIVE_SAVE_FFMPEG=false;
}

View File

@ -1,6 +1,7 @@
package com.yutou.bilibili.BiliBili.Tools;
import com.alibaba.fastjson.JSONObject;
import com.yutou.bilibili.BiliBili.Datas.AppData;
import com.yutou.bilibili.BiliBili.Live;
import com.yutou.bilibili.BiliBili.LiveUtils;
import com.yutou.bilibili.QQBot.QQBotManager;
@ -122,38 +123,11 @@ public class SaveLive {
super.run();
String url = getLiveUrl(roomId);
try {
com.yutou.bilibili.Tools.Log.i("开始录制:" + roomId);
QQBotManager.getInstance().sendMessage(roomId + " 已启动录制");
HttpURLConnection connection = LiveUtils.getBiliHttpGet(url, LiveUtils.getCookie());
connection.setReadTimeout(5000);
connection.setConnectTimeout(5000);
heartbeat = new Timer();
//Heartbeat beat = new Heartbeat();
heartbeat.schedule(new Heartbeat(), 0, 30000);
heartbeats.put(roomId, heartbeat);
//heartbeats.add(beat);
InputStream inputStream = connection.getInputStream();
liveFile = new File(String.format("live%s%s%s[%s]%d.mp4",
File.separator,
AppTools.getToDayTime(),
File.separator,
AppTools.getToDayNowTimeToString().replace(":", ""),
roomId));
if (!liveFile.exists()) {
liveFile.mkdirs();
liveFile.delete();
if(AppData.LIVE_SAVE_FFMPEG){
ffmpegDownload(url);
}else {
httpDownload(url);
}
FileOutputStream outputStream = new FileOutputStream(liveFile);
int len;
byte[] bytes = new byte[1024];
while ((len = inputStream.read(bytes)) != -1 && isSave) {
outputStream.write(bytes, 0, len);
outputStream.flush();
}
outputStream.close();
inputStream.close();
com.yutou.bilibili.Tools.Log.i("录制完成:" + roomId + " save = " + isSave + " len = " + len);
QQBotManager.getInstance().sendMessage("录制完成:" + roomId + " save = " + isSave + " len = " + len);
} catch (Exception e) {
com.yutou.bilibili.Tools.Log.e(e);
Log.i("录制发生意外:" + e.getMessage());
@ -164,6 +138,74 @@ public class SaveLive {
}
private void ffmpegDownload(String url) throws Exception {
liveFile = new File(String.format("live%s%s%s[%s]%d.mp4",
File.separator,
AppTools.getToDayTime(),
File.separator,
AppTools.getToDayNowTimeToString().replace(":", ""),
roomId));
if (!liveFile.exists()) {
liveFile.mkdirs();
liveFile.delete();
}
String exec = String.format("%s -user_agent \"%s\" -cookies \"%s\" -headers \"%s\" -i \"%s\" -threads 8 -c:v copy -y \"%s\" %s ",
AppData.FFMPEG,
AppData.BILIBILI_HEADERS,
LiveUtils.getCookie(),
"Referer:https://live.bilibili.com",
url,
liveFile.getAbsolutePath(),
""
);
System.out.println(exec);
Process process=AppTools.exec(exec);
InputStream inputStream = process.getErrorStream();
byte[] bytes = new byte[2048];
while (inputStream.read(bytes) > -1) {
System.out.println(new String(bytes,StandardCharsets.UTF_8));
}
System.out.println("----------------stop ffmpeg");
inputStream.close();
com.yutou.bilibili.Tools.Log.i("录制完成:" + roomId);
QQBotManager.getInstance().sendMessage("录制完成:" + roomId);
}
private void httpDownload(String url) throws Exception {
com.yutou.bilibili.Tools.Log.i("开始录制:" + roomId);
QQBotManager.getInstance().sendMessage(roomId + " 已启动录制");
HttpURLConnection connection = LiveUtils.getBiliHttpGet(url, LiveUtils.getCookie());
connection.setReadTimeout(5000);
connection.setConnectTimeout(5000);
heartbeat = new Timer();
//Heartbeat beat = new Heartbeat();
heartbeat.schedule(new Heartbeat(), 0, 30000);
heartbeats.put(roomId, heartbeat);
//heartbeats.add(beat);
InputStream inputStream = connection.getInputStream();
liveFile = new File(String.format("live%s%s%s[%s]%d.mp4",
File.separator,
AppTools.getToDayTime(),
File.separator,
AppTools.getToDayNowTimeToString().replace(":", ""),
roomId));
if (!liveFile.exists()) {
liveFile.mkdirs();
liveFile.delete();
}
FileOutputStream outputStream = new FileOutputStream(liveFile);
int len;
byte[] bytes = new byte[1024];
while ((len = inputStream.read(bytes)) != -1 && isSave) {
outputStream.write(bytes, 0, len);
outputStream.flush();
}
outputStream.close();
inputStream.close();
com.yutou.bilibili.Tools.Log.i("录制完成:" + roomId + " save = " + isSave + " len = " + len);
QQBotManager.getInstance().sendMessage("录制完成:" + roomId + " save = " + isSave + " len = " + len);
}
private class Heartbeat extends TimerTask {
int nextInterval = 1;

View File

@ -1,10 +1,12 @@
package com.yutou.bilibili.Controllers;
import com.alibaba.fastjson.JSONObject;
import com.yutou.bilibili.BiliBili.Datas.AppData;
import com.yutou.bilibili.QQBot.QQBotManager;
import com.yutou.bilibili.Services.ISystemConfigService;
import com.yutou.bilibili.Tools.Config;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@ -21,7 +23,7 @@ public class SystemConfigController {
@ResponseBody
public JSONObject getRegUser() {
JSONObject json = new JSONObject();
JSONObject data=new JSONObject();
JSONObject data = new JSONObject();
String reg = configService.getConfig(Config.USER_REG);
String bLive = configService.getConfig(Config.BILI_LIVE_FLAG);
if (reg == null) {
@ -31,68 +33,103 @@ public class SystemConfigController {
bLive = "0";
}
data.put(Config.USER_REG, reg);
data.put(Config.BILI_LIVE_FLAG,bLive);
json.put("code",0);
json.put("data",data);
data.put(Config.BILI_LIVE_FLAG, bLive);
json.put("code", 0);
json.put("data", data);
return json;
}
@RequestMapping("/system/set/config.do")
@ResponseBody
public JSONObject setConfig(String key,String value){
public JSONObject setConfig(String key, String value) {
configService.setConfig(key, value);
JSONObject json=new JSONObject();
json.put("code",0);
json.put("msg","ok");
JSONObject json = new JSONObject();
json.put("code", 0);
json.put("msg", "ok");
return json;
}
@RequestMapping("/system/public/reg.do")
@ResponseBody
public JSONObject getRegModel(){
public JSONObject getRegModel() {
JSONObject json = new JSONObject();
JSONObject data=new JSONObject();
JSONObject data = new JSONObject();
String reg = configService.getConfig(Config.USER_REG);
boolean model=false;
boolean model = false;
if (reg == null) {
reg = "0";
}
if(reg.equals("1")){
model=true;
if (reg.equals("1")) {
model = true;
}
data.put(Config.USER_REG, model);
json.put("code",0);
json.put("data",data);
json.put("code", 0);
json.put("data", data);
return json;
}
@ResponseBody
@RequestMapping("/system/set/ffmpeg.do")
public JSONObject setFFmpeg(String ffmpeg) throws UnsupportedEncodingException {
ffmpeg= URLDecoder.decode(ffmpeg,"UTF-8");
configService.setConfig(Config.SYSTEM_VIDEO_FFMPEG,ffmpeg);
JSONObject json=new JSONObject();
json.put("code",0);
json.put("msg","ok");
ffmpeg = URLDecoder.decode(ffmpeg, "UTF-8");
configService.setConfig(Config.SYSTEM_VIDEO_FFMPEG, ffmpeg);
AppData.FFMPEG = ffmpeg;
JSONObject json = new JSONObject();
json.put("code", 0);
json.put("msg", "ok");
return json;
}
@ResponseBody
@RequestMapping("/system/get/ffmpeg.do")
public JSONObject getFFmpeg(){
public JSONObject getFFmpeg() {
JSONObject json = new JSONObject();
JSONObject data=new JSONObject();
JSONObject data = new JSONObject();
String reg = configService.getConfig(Config.SYSTEM_VIDEO_FFMPEG);
data.put(Config.SYSTEM_VIDEO_FFMPEG, reg);
json.put("code",0);
json.put("data",data);
json.put("code", 0);
json.put("data", data);
return json;
}
@ResponseBody
@RequestMapping("/system/set/savelive.do")
public JSONObject setSaveLive(String model) throws UnsupportedEncodingException {
JSONObject json = new JSONObject();
if (StringUtils.isEmpty(configService.getConfig(Config.SYSTEM_VIDEO_FFMPEG))) {
json.put("code", 404);
json.put("msg", "请先设置FFmpeg路径");
return json;
}
model = URLDecoder.decode(model, "UTF-8");
configService.setConfig(Config.SYSTEM_VIDEO_SAVE_MODEL, model);
AppData.LIVE_SAVE_FFMPEG = model.equals("ffmpeg");
json.put("code", 0);
json.put("msg", "ok");
return json;
}
@ResponseBody
@RequestMapping("/system/get/savelive.do")
public JSONObject getSaveLiveModel() {
JSONObject json = new JSONObject();
JSONObject data = new JSONObject();
String reg = configService.getConfig(Config.SYSTEM_VIDEO_SAVE_MODEL);
System.out.println(reg);
data.put(Config.SYSTEM_VIDEO_SAVE_MODEL, (!StringUtils.isEmpty(reg) && reg.equals("ffmpeg")));
json.put("code", 0);
json.put("data", data);
return json;
}
@ResponseBody
@RequestMapping("/system/qq/login.do")
public JSONObject loginQQ(){
public JSONObject loginQQ() {
QQBotManager.getInstance().stop();
QQBotManager.getInstance().init();
JSONObject json=new JSONObject();
json.put("code",0);
json.put("msg","ok");
JSONObject json = new JSONObject();
json.put("code", 0);
json.put("msg", "ok");
return json;
}
}

View File

@ -307,4 +307,26 @@ public class AppTools {
return false;
}
}
public static boolean isRuntimeSystemOfWindow(){
return System.getProperty ("os.name").contains("Windows");
}
public static Process exec(String exec)throws Exception{
if(AppTools.isRuntimeSystemOfWindow()) {
return Runtime.getRuntime().exec(new String[]{
"cmd",
"/c",
exec
}
);
}else{
return Runtime.getRuntime().exec(new String[]{
"sh",
"-c",
exec
}
);
}
}
}

View File

@ -0,0 +1,13 @@
package com.yutou.bilibili.Tools;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;
@Component
public class ApplicationClose implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
Log.i("服务结束");
}
}

View File

@ -1,9 +1,11 @@
package com.yutou.bilibili.Tools;
import com.yutou.bilibili.BiliBili.Datas.AppData;
import com.yutou.bilibili.BiliBili.Datas.LiveData;
import com.yutou.bilibili.BiliBili.Live;
import com.yutou.bilibili.BiliBili.LiveUtils;
import com.yutou.bilibili.BiliBili.Services.IBiliBiliLiveService;
import com.yutou.bilibili.Services.ISystemConfigService;
import com.yutou.bilibili.mybatis.Bili.mybatis.model.BilibiliLiveInfo;
import com.yutou.bilibili.mybatis.Bili.mybatis.model.BilibiliUpInfo;
import org.springframework.boot.ApplicationArguments;
@ -27,12 +29,16 @@ import java.util.TimerTask;
public class ApplicationInit implements ApplicationRunner {
@Resource
IBiliBiliLiveService service;
@Resource
ISystemConfigService configService;
private Timer timer;
@Override
public void run(ApplicationArguments args) throws Exception {
LiveUtils.LiveGiftConfig.getInstance();
AppData.FFMPEG=configService.getConfig(Config.SYSTEM_VIDEO_FFMPEG);
AppData.LIVE_SAVE_FFMPEG= configService.getConfig(Config.SYSTEM_VIDEO_SAVE_MODEL) != null && configService.getConfig(Config.SYSTEM_VIDEO_SAVE_MODEL).equals("ffmpeg");
startTimer();
}

View File

@ -4,4 +4,5 @@ public class Config {
public static final String USER_REG="userReg";
public static final String BILI_LIVE_FLAG="biliLive";
public static final String SYSTEM_VIDEO_FFMPEG="ffmpeg_path";
public static final String SYSTEM_VIDEO_SAVE_MODEL="save_live_model";
}

View File

@ -39,12 +39,7 @@ public class FFmpegUtils {
file.getAbsolutePath(),
out.getAbsolutePath() + File.separator + file.getName());
com.yutou.bilibili.Tools.Log.i(exec);
Process process = Runtime.getRuntime().exec(new String[]{
"cmd",
"/c",
exec
}
);
Process process=AppTools.exec(exec);
InputStream inputStream = process.getErrorStream();
byte[] bytes = new byte[1024];
while (inputStream.read(bytes) > -1) {