biliob/src/main/java/com/yutou/bilibili/services/LiveVideoDownloadService.java
2024-11-02 18:24:16 +08:00

389 lines
16 KiB
Java

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.bilibili.Tools.DateFormatUtils;
import com.yutou.bilibili.Tools.FileServerUtils;
import com.yutou.bilibili.Tools.LiveInfoNfoTools;
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.record.AbsVideoRecord;
import com.yutou.common.utils.ConfigTools;
import com.yutou.common.utils.FFmpegUtils;
import com.yutou.common.utils.Log;
import okhttp3.Headers;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.File;
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 LiveVideoDownloadService {
private final ThreadPoolExecutor executor;
private final List<String> userStopList = new ArrayList<>();//手动停止列表
private final AbsVideoRecord videoRecord;
public LiveVideoDownloadService() {
Log.i("初始化下载服务");
videoRecord = new FFmpegUtils();
executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
}
public boolean checkDownload(String roomId) {
return videoRecord.check(roomId);
}
public void clearUserStopList() {
userStopList.clear();
}
public void start(LiveConfigDatabaseBean bean, boolean isUser) {
if (!isUser && userStopList.contains(bean.getRoomId().toString())) {
return;
}
if (isUser) {
userStopList.remove(bean.getRoomId().toString());
}
if (videoRecord.check(bean.getRoomId().toString())) {
return;
}
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);
} else {
Log.i("移除下载");
}
}
@Override
public void onFailure(Throwable throwable) {
Log.i("移除下载");
}
});
}
public void stop(String roomId, boolean isUser) {
if (isUser) {
userStopList.add(roomId);
}
videoRecord.kill(roomId);
}
public JSONArray getDownloadTasks() {
JSONArray array = new JSONArray();
array.addAll(videoRecord.getRoomIds());
return array;
}
public void stopAll() {
videoRecord.killAll();
}
private class VideoTask implements Runnable {
LiveConfigDatabaseBean bean;
boolean isDownload = true;
LiveApi api;
String savePath;
File rootPath;
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);
rootPath = new File(bean.getRecordPath() + File.separator + bean.getAnchorName() + File.separator + time + File.separator + roomInfo.getTitle());
savePath = rootPath.getAbsolutePath() + File.separator + "[" +
DateUtils.format(new Date(),
"yyyy-MM-dd HH-mm-ss") + "]" + roomInfo.getTitle() + ".flv";
if (!rootPath.exists()) {
rootPath.mkdirs();
}
record(bean, roomInfo);
} else {
stop();
}
}
private void stop() {
videoRecord.kill(bean.getRoomId().toString());
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) {
LiveVideoDownloadService.this.start(bean, false);
} else {
LiveVideoDownloadService.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);
HttpDownloadUtils.download(new HttpDownloadUtils.Builder().setUrl(roomInfo.getKeyframe())
.setPath(rootPath.getAbsolutePath())
.setFileName("poster.jpg"));
LiveInfoNfoTools.saveLiveInfoNfo(roomInfo, rootPath.getAbsolutePath(), new File(savePath).getName().replace(".flv", ".nfo"));
saveLiveInfo(roomInfo);
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) {
Log.e(throwable);
}
});
}
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.Builder builder = new FFmpegUtils.Builder()
.withParam("-user_agent", ConfigTools.getUserAgent())
.withParam("-headers", "Referer: https://live.bilibili.com")
// .withNotSymbolParam("-progress", "-")
.withNotSymbolParam("-threads", "8")
.withNotSymbolParam("-c:v", "copy")
.withNotSymbolParam("-y", "");
if (ck != null) {
builder = builder.withParam("-cookies", cookie);
}
FFmpegUtils command = builder.build(config.getRoomId(), ffmpegPath, url, savePath);
Log.i(command.getCommand());
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);
Log.e("下载完成 ");
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);
}
}
private void saveLiveInfo(LiveRoomInfo roomInfo) {
}
public VideoFilePath getVideoPath(String roomId) {
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase();
LiveConfigDatabaseBean bean = configDatabase.getConfig(roomId);
configDatabase.close();
return getVideoFilePath(bean);
}
private VideoFilePath getVideoFilePath(LiveConfigDatabaseBean configBean) {
String recordPath = configBean.getRecordPath() + File.separator + configBean.getAnchorName();
File recordDir = new File(recordPath);
BiliLiveDatabase database = new BiliLiveDatabase(LiveRoomConfig.buildConfig(configBean.getRoomId()), recordDir.getAbsolutePath());
VideoFilePath path = createVideoRootFilePath(configBean, recordDir);
if (recordDir.exists()) {
List<LiveVideoDatabaseBean> infos = database.getLiveInfos();
database.close();
path.setChildren(getVideoInfo(infos));
}
return path;
}
private VideoFilePath createVideoRootFilePath(LiveConfigDatabaseBean config, File db) {
VideoFilePath path = new VideoFilePath();
path.setRoomId(config.getRoomId());
path.setCover(config.getAnchorFace());
path.setName(config.getAnchorName());
path.setUid(config.getAnchorUid());
path.setParent(true);
path.setPath(FileServerUtils.toUrl(db.getParent()));
return path;
}
private Map<String, List<VideoFilePath>> getVideoInfo(List<LiveVideoDatabaseBean> videoList) {
Map<String, List<VideoFilePath>> map = new HashMap<>();
for (LiveVideoDatabaseBean bean : videoList) {
String date = DateFormatUtils.format(bean.getSql_time(), "yyyy-MM-dd");
if (!map.containsKey(date)) {
map.put(date, new ArrayList<>());
}
VideoFilePath path = new VideoFilePath();
LiveRoomInfo roomInfo = JSONObject.parseObject(bean.getRoomInfoJson(), LiveRoomInfo.class);
path.setRoomId(roomInfo.getRoomId());
path.setName(roomInfo.getTitle());
path.setUid(roomInfo.getUid());
path.setPath(String.valueOf(bean.getSql_time().getTime()));
path.setCover(roomInfo.getKeyframe());
path.setParent(false);
path.setChildren(null);
map.get(date).add(path);
}
return map;
}
public String getVideoPlay(String roomId, String videoId) {
String ffmpegPath = ConfigTools.load(ConfigTools.CONFIG, "ffmpeg", String.class);
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase();
LiveConfigDatabaseBean config = configDatabase.getConfig(roomId);
String recordPath = config.getRecordPath() + File.separator + config.getAnchorName();
configDatabase.close();
LiveVideoDatabaseBean videoInfo = null;
BiliLiveDatabase liveDatabase = new BiliLiveDatabase(LiveRoomConfig.buildConfig(roomId), new File(recordPath).getAbsolutePath());
for (LiveVideoDatabaseBean info : liveDatabase.getLiveInfos()) {
if (videoId.trim().equals(String.valueOf(info.getSql_time().getTime()))) {
videoInfo = info;
break;
}
}
if(videoInfo != null) {
FFmpegUtils ffmpeg = FFmpegUtils.segment(videoId, ffmpegPath, new File(videoInfo.getPath()), ConfigTools.load(ConfigTools.CONFIG, "outVideoPath", String.class));
System.out.println(ffmpeg.getCommand());
ffmpeg.start(new DownloadInterface() {
@Override
public void onDownload(File file) {
super.onDownload(file);
}
});
return ffmpeg.getOutputFilePath();
}
return null;
}
public static void main(String[] args) {
LiveVideoDownloadService service=new LiveVideoDownloadService();
String play = service.getVideoPlay("17961", "1730363029293");
System.out.println(play);
}
}