一、主方法
public void downloadBzVideo(String videoUrl, String recordId) {
try {
if (StringUtil.isEmpty(videoUrl)) {
log.error("B站 no video url!");
}
//创建视频目录
File videoDir = makeFileDir(BZ_VIDEO_FILE_DIR, recordId);
//创建ffmpeg日志目录
File logDir = makeFileDir(BZ_LOG_FILE_DIR, recordId);
//声明音频文件、视频文件和最后合成视频文件,方便后面操作。也可以后面直接指定路径
File audioFile = new File(videoDir.getAbsolutePath() + "/" + recordId + ".mp3");
File videoFile = new File(videoDir.getAbsolutePath() + "/" + recordId + ".mp4");
File finalVideoFile = new File(videoDir.getAbsolutePath() + "/" + recordId + "_final.mp4");
downloadBzVideoToFile(videoUrl, audioFile, videoFile, finalVideoFile, logDir);
//删除创建的目录,避免线上频繁创建文件使用内存过大。(视情况是否删除文件)
deleteFileDir(videoDir, logDir);
} catch (Exception e) {
log.error("B站视频下载失败,URL为:={},详细信息:", videoUrl, e);
}
}
二、下载视频到本地
private void downloadBzVideoToFile(String url, File audioFile, File videoFile, File finalVideoFile, File logDir)
throws Exception {
String referer = "https://www.bilibili.com";
String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36";
String playInfo = "";
Pattern playInfoPattern = Pattern.compile("<script>window.__playinfo__=(.*?)</script>");
HttpHeaders headers = new HttpHeaders();
headers.set("Referer", referer);
headers.set("User-Agent", userAgent);
headers.set(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN_VALUE);
// Accept-Encoding 头,表示客户端接收gzip格式的压缩
headers.set(HttpHeaders.ACCEPT_ENCODING, "gzip");
HttpEntity<?> requestEntity = new HttpEntity<>(headers);
RestTemplate template = new RestTemplate();
ResponseEntity<byte[]> response = template.exchange(url,
HttpMethod.GET, requestEntity, byte[].class);
if (!response.getStatusCode().is2xxSuccessful()) {
// TODO 非200响应
log.error("请求失败,url:{},status:{}", url, response.getStatusCode());
return;
}
String contentEncoding = response.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING);
if ("gzip".equals(contentEncoding)) { // gzip编码
// gzip解压服务器的响应体
byte[] data = unGZip(new ByteArrayInputStream(response.getBody()));
String responseBody = new String(data, StandardCharsets.UTF_8);
//解析音频、视频文件
Matcher playInfoMatcher = playInfoPattern.matcher(responseBody);
if (playInfoMatcher.find()) {
playInfo = playInfoMatcher.group(1);
}
JSONObject jsonMap = JSONObject.parseObject(playInfo);
String audioUrl = jsonMap.getJSONObject("data").getJSONObject("dash").getJSONArray("audio")
.getJSONObject(0)
.getString("base_url");
String videoUrl = jsonMap.getJSONObject("data").getJSONObject("dash").getJSONArray("video")
.getJSONObject(0)
.getString("base_url");
//将音频、视频内容写入文件
byte[] audioContent = downloadBytes(audioUrl, referer, userAgent);
byte[] videoContent = downloadBytes(videoUrl, referer, userAgent);
saveContentToFile(audioFile, audioContent);
saveContentToFile(videoFile, videoContent);
//执行ffmpeg命令,将音视频文件合并
String cmd = String.format(
ffmpegPath + " -i " + videoFile.getAbsolutePath() + " -y -i "
+ audioFile.getAbsolutePath() + " -c:v copy -c:a copy -strict experimental "
+ finalVideoFile.getAbsolutePath());
execCommand(cmd, logDir);
}
}
三、ffmpeg相关调用方法
private void execCommand(String cmd, File logFileDir) {
StringBuilder stdoutContent = new StringBuilder();
StringBuilder stderrContent = new StringBuilder();
execCommand(cmd, stdoutContent, stderrContent, logFileDir);
}
private void execCommand(String cmd, StringBuilder stdoutContent, StringBuilder stderrContent, File logFileDir) {
java.lang.Process process = null;
try {
String osName = System.getProperty("os.name").toLowerCase();
String[] cmds = new String[]{"cmd", "/c", cmd};
if (osName.indexOf("linux") >= 0) {
cmds = new String[]{"/bin/sh", "-c", cmd};
}
process = Runtime.getRuntime().exec(cmds, null, logFileDir);
ByteArrayOutputStream stdoutOutStream = new ByteArrayOutputStream();
ByteArrayOutputStream stderrOutStream = new ByteArrayOutputStream();
InputStream errorInStream = new BufferedInputStream(process.getErrorStream());
InputStream processInStream = new BufferedInputStream(process.getInputStream());
int num = 0;
byte[] bs = new byte[1024];
while ((num = errorInStream.read(bs)) != -1) {
stderrOutStream.write(bs, 0, num);
}
while ((num = processInStream.read(bs)) != -1) {
stdoutOutStream.write(bs, 0, num);
}
process.waitFor();
stdoutContent.append(new String(stdoutOutStream.toByteArray()));
stderrContent.append(new String(stderrOutStream.toByteArray()));
errorInStream.close();
processInStream.close();
stdoutOutStream.close();
stderrOutStream.close();
} catch (IOException e) {
// log.error("failed to exec cmd: {}", cmd, e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
try {
if (process != null) {
process.destroy();
}
} catch (Exception ex) {
//ignore
}
}
}
四、相关重构方法
/**
* 删除目录
*/
private void deleteFileDir(File... fileDirs) {
for (File fileDir : fileDirs) {
File[] listFiles = fileDir.listFiles();
if (listFiles != null) {
for (File file : listFiles) {
file.delete();
}
}
fileDir.delete();
}
}
/**
* 保存信息到指定文件
*/
private void saveContentToFile(File file, byte[] content) throws Exception {
BufferedOutputStream audioStream = new BufferedOutputStream(
new FileOutputStream(file));
audioStream.write(content);
audioStream.close();
}
/**
* 创建目录
*/
public File makeFileDir(String newFileDir, String recordId) {
File file = new File(newFileDir + "/" + recordId + "/");
if (!file.exists()) {
file.mkdirs();
}
return file;
}
/**
* 下载url为字节码
*/
private byte[] downloadBytes(String url, String referer, String userAgent) {
HttpResponse response = HttpRequest.get(url)
.header("Referer", referer)
.header("User-Agent", userAgent)
.setFollowRedirects(true).executeAsync();
if (!response.isOk()) {
throw new HttpException("Server response error with status code: [{}]",
response.getStatus());
} else {
return response.bodyBytes();
}
}
/**
* Gzip解压缩
*/
private byte[] unGZip(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream)) {
byte[] buf = new byte[4096];
int len = -1;
while ((len = gzipInputStream.read(buf, 0, buf.length)) != -1) {
byteArrayOutputStream.write(buf, 0, len);
}
return byteArrayOutputStream.toByteArray();
} finally {
byteArrayOutputStream.close();
}
}```