B站视频发布

This commit is contained in:
Klein
2023-09-18 21:16:22 +08:00
parent 8f241a0e90
commit 42ea5ca0bd
8 changed files with 445 additions and 0 deletions

View File

@@ -3,6 +3,7 @@ package org.example.utils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
@@ -50,6 +51,13 @@ public class HttpClientUtil {
return executeRequest(httpPost);
}
public static String post(String url, UrlEncodedFormEntity urlEncodedFormEntity, Map<String, String> headers) {
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(urlEncodedFormEntity);
addHeaders(httpPost, headers);
return executeRequest(httpPost);
}
public static String put(String url) {
return executeRequest(new HttpPut(url));
}

View File

@@ -29,5 +29,19 @@
<artifactId>edgegrid-signer-core</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>chopperbot-common</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,22 @@
package org.example.api;
import org.example.core.publisher.impl.BilibiliVideoPublisher;
/**
* @author dhx
* @date 2023/9/18 20:42
*/
public class BilibiliPublishApi {
public static void PublishVideo(String videoPath,String devicePath,String Cookie,String coverPath){
BilibiliVideoPublisher bilibiliVideoPublisher = new BilibiliVideoPublisher();
bilibiliVideoPublisher.publishVideo(videoPath,devicePath,Cookie,coverPath);
}
public static void main(String[] args) {
PublishVideo(
"C:\\Users\\admin\\Downloads\\test.mp4", //视频路径
"C:\\Users\\admin\\Desktop\\video_bin\\", //分割后视频存放路径
"", //b站Cookie
"C:\\Users\\admin\\Desktop\\OIP-C.jpg"); //封面路径
}
}

View File

@@ -0,0 +1,16 @@
package org.example.core.publisher;
/**
* @author dhx
* @date 2023/9/18 20:49
*/
public interface PlatformVideoPublisher {
/**
*
* @param videoPath 视频本地路径
* @param devicePath 视频分片后存放路径
* @param Cookie B站用户Cookie
* @param coverPath 视频封面本地路径
*/
void publishVideo(String videoPath,String devicePath,String Cookie,String coverPath);
}

View File

@@ -0,0 +1,196 @@
package org.example.core.publisher.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.example.core.publisher.PlatformVideoPublisher;
import org.example.exception.ChopperBotException;
import org.example.utils.FileUtil;
import org.example.utils.HttpClientUtil;
import org.example.utils.VideoDeviceUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.time.LocalTime;
import java.util.*;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* @author dhx
* @date 2023/9/18 20:49
*/
public class BilibiliVideoPublisher implements PlatformVideoPublisher {
@Override
public void publishVideo(String videoPath, String devicePath, String Cookie, String coverPath) {
String jctPart = Cookie.substring(Cookie.indexOf("bili_jct"));
String csrf;
try {
csrf = jctPart.substring(jctPart.indexOf("=") + 1, jctPart.indexOf(";"));
System.out.println(csrf);
} catch (Exception e) {
throw new ChopperBotException("Cookie 错误");
}
int filesize = FileUtil.getFilesize(videoPath);
if (filesize == -1) {
throw new ChopperBotException("获取文件大小失败");
}
//申请上传
Map<String, String> header = new HashMap<>();
header.put("Cookie", Cookie);
header.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69");
header.put("Referer", "https://member.bilibili.com/platform/upload/video/frame?spm_id_from=333.1007.top_bar.upload");
String res = HttpClientUtil.get("https://member.bilibili.com/preupload?probe_version=20221109&upcdn=bda2&zone=cs&name=test.mp4&r=upos&profile=ugcfx%2Fbup&ssl=0&version=2.14.0.0&build=2140000&size=" + filesize + "&webVersion=2.14.0"
, header);
JSONObject mp4Obj = JSONObject.parseObject(res);
String mp4_upos_uri = mp4Obj.getString("upos_uri");
String put_query = mp4Obj.getString("put_query");
String endpoint = mp4Obj.getString("endpoint");
Long chunk_size = mp4Obj.getLong("chunk_size");
Long biz_id = mp4Obj.getLong("biz_id");
String auth = mp4Obj.getString("auth");
String res2 = HttpClientUtil.get("https://member.bilibili.com/preupload?name=file_meta.txt&size=2000&r=upos&profile=fxmeta%2Fbup&ssl=0&version=2.14.0.0&build=2140000&webVersion=2.14.0", header);
JSONObject txtObj = JSONObject.parseObject(res2);
String meta_upos_uri = txtObj.getString("upos_uri");
String preUploadUrl = String.format("https:%s%s?uploads&output=json%s&filesize=%s&partsize=%s&meta_upos_uri=%s&biz_id=%s", endpoint, mp4_upos_uri.substring(mp4_upos_uri.indexOf('/') + 1), put_query.substring(put_query.indexOf('&')), String.valueOf(filesize), chunk_size, meta_upos_uri, biz_id);
Map<String, String> header2 = new HashMap<>();
header2.put("X-Upos-Auth", auth);
String res3 = HttpClientUtil.post(preUploadUrl, "{}", header2);
JSONObject uploadObj = JSONObject.parseObject(res3);
String upload_id = uploadObj.getString("upload_id");
//视频分片
int chunkNumber = VideoDeviceUtil.device(videoPath, devicePath, chunk_size);
if (chunkNumber != -1) {
chunkNumber--;
} else {
throw new ChopperBotException("分割视频失败");
}
//上传视频
for (int i = 1; i <= chunkNumber; i++) {
long end = i == chunkNumber ? filesize : i * chunk_size;
long start = (i - 1) * chunk_size;
File file = new File(devicePath + "chunk_" + i + ".bin");
if (!file.exists()) {
throw new ChopperBotException(String.format("未找到视频片段%s", i));
}
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPut httpPut = new HttpPut(String.format("https:%s%s?partNumber=%s&uploadId=%s&chunk=%s&chunks=%s&size=%s&start=%s&end=%s&total=%s",
endpoint, mp4_upos_uri.substring(mp4_upos_uri.indexOf('/') + 1), i, upload_id, i - 1, chunkNumber, end - start, start, end, filesize)); // 替换为实际的目标URL
byte[] binaryData = new byte[(int) file.length()];
FileInputStream fileInputStream = new FileInputStream(file);
fileInputStream.read(binaryData);
fileInputStream.close();
ByteArrayEntity entity = new ByteArrayEntity(binaryData);
httpPut.setEntity(entity);
httpPut.setHeader("Content-Type", "application/octet-stream");
httpPut.setHeader("X-Upos-Auth", auth);
CloseableHttpResponse response = httpClient.execute(httpPut);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
System.out.println("上传中:" + i * 100 / chunkNumber + "%");
}
} catch (IOException e) {
e.printStackTrace();
}
}
//通知上传完成
JSONObject body = new JSONObject();
JSONArray parts = new JSONArray();
for (int i = 1; i <= chunkNumber; i++) {
JSONObject item = new JSONObject();
item.put("partNumber", i);
item.put("eTag", "etag");
parts.add(item);
}
body.put("parts", parts);
String res4 = HttpClientUtil.post(
String.format(
"https:%s%s?output=json&name=test.mp4%s&uploadId=%s&biz_id=%s",
endpoint, mp4_upos_uri.substring(mp4_upos_uri.indexOf('/') + 1), put_query.substring(put_query.indexOf('&')), upload_id, biz_id
), body.toString(), header2
);
//上传封面
String base64Image = "";
try {
File imageFile = new File(coverPath);
FileInputStream fileInputStream = new FileInputStream(imageFile);
byte[] imageData = new byte[(int) imageFile.length()];
fileInputStream.read(imageData);
fileInputStream.close();
base64Image = Base64.getEncoder().encodeToString(imageData);
base64Image = "data:image/jpeg;base64," + base64Image;
} catch (IOException e) {
e.printStackTrace();
}
List<NameValuePair> list = new ArrayList<>();
list.add(new BasicNameValuePair("cover", base64Image));
list.add(new BasicNameValuePair("csrf", csrf));
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(list, UTF_8);
String res5 = HttpClientUtil.post("https://member.bilibili.com/x/vu/web/cover/up?t=" + LocalTime.now(), urlEncodedFormEntity, header);
JSONObject cover = JSONObject.parseObject(res5);
JSONObject coverData = cover.getJSONObject("data");
if (coverData == null) return;
String coverUrl = coverData.getString("url");
//发布视频
JSONObject body1 = new JSONObject();
JSONObject subtitle = new JSONObject();
JSONArray videos = new JSONArray();
subtitle.put("open", 0);
subtitle.put("lan", " ");
JSONObject video = new JSONObject();
video.put("filename", mp4_upos_uri.substring(mp4_upos_uri.lastIndexOf('/') + 1, mp4_upos_uri.indexOf('.')));
video.put("title", "test");
video.put("desc", "");
video.put("cid", biz_id);
videos.add(video);
body1.put("act_reserve_create", 0);
body1.put("copyright", 1);
body1.put("cover", coverUrl); //封面
body1.put("csrf", csrf);
body1.put("desc", "");
body1.put("desc_format_id", 0);
body1.put("dolby", 0);
body1.put("dynamic", "");
body1.put("interactive", 0);
body1.put("lossless_music", 0);
body1.put("no_disturbance", 0);
body1.put("no_reprint", 1);
body1.put("recreate", -1);
body1.put("tag", "助眠,音乐"); //视频标签
body1.put("tid", 130); //视频分区 130音乐综合
body1.put("title", "test");
body1.put("up_close_danmu", false);
body1.put("up_close_reply", false);
body1.put("up_selection_reply", false);
body1.put("web_os", 1);
body1.put("subtitle", subtitle);
body1.put("videos", videos);
String res6 = HttpClientUtil.post(String.format("https://member.bilibili.com/x/vu/web/add/v3?t=%s&csrf=%s", LocalTime.now(), csrf), body1.toString(), header);
JSONObject res6Obj = JSONObject.parseObject(res6);
int code = res6Obj.getInteger("code");
if (code == 0) {
System.out.println("发布成功");
} else {
throw new ChopperBotException("发布失败,请手动发布");
}
}
}

View File

@@ -0,0 +1,30 @@
package org.example.utils;
import java.io.File;
import java.io.FileInputStream;
/**
* @author dhx
* @date 2023/9/18 15:30
*/
public class FileUtil {
/**
* 获取文件大小
*/
public static int getFilesize(String filePath){
int filesize = 0;
FileInputStream fileInputStream = null;
try {
File file = new File(filePath);
if (file.exists() && file.isFile()) {
fileInputStream = new FileInputStream(file);
filesize = fileInputStream.available();
fileInputStream.close();
}
} catch (Exception e) {
e.printStackTrace();
return -1;
}
return filesize;
}
}

View File

@@ -0,0 +1,117 @@
package org.example.utils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* 简单的get,post请求工具类
* @author 燧枫
* @date 2023/5/16 19:24
*/
public class HttpClientUtil {
private static final CloseableHttpClient httpClient = HttpClients.createDefault();
public static String get(String url) {
return executeRequest(new HttpGet(url));
}
public static String get(String url, Map<String, String> headers) {
HttpGet httpGet = new HttpGet(url);
addHeaders(httpGet, headers);
return executeRequest(httpGet);
}
public static String post(String url){
return executeRequest(new HttpPost(url));
}
public static String post(String url, String json) {
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
return executeRequest(httpPost);
}
public static String post(String url, String json, Map<String, String> headers) {
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
addHeaders(httpPost, headers);
return executeRequest(httpPost);
}
public static String post(String url, UrlEncodedFormEntity urlEncodedFormEntity, Map<String, String> headers) {
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(urlEncodedFormEntity);
addHeaders(httpPost, headers);
return executeRequest(httpPost);
}
public static String put(String url) {
return executeRequest(new HttpPut(url));
}
public static String put(String url, String json) {
HttpPut httpPut = new HttpPut(url);
httpPut.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
return executeRequest(httpPut);
}
public static String put(String url, String json, Map<String, String> headers) {
HttpPut httpPut = new HttpPut(url);
httpPut.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
addHeaders(httpPut, headers);
return executeRequest(httpPut);
}
public static String delete(String url) {
return executeRequest(new HttpDelete(url));
}
public static String delete(String url, Map<String, String> headers) {
HttpDelete httpDelete = new HttpDelete(url);
addHeaders(httpDelete, headers);
return executeRequest(httpDelete);
}
private static String executeRequest(HttpUriRequest request) {
try (CloseableHttpResponse response = httpClient.execute(request)) {
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void addHeaders(HttpRequest httpRequest, Map<String, String> headers) {
if (headers != null && !headers.isEmpty()) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
httpRequest.addHeader(entry.getKey(), entry.getValue());
}
}
}
private static String handleResponse(HttpResponse response) {
HttpEntity entity = response.getEntity();
if (entity != null) {
try {
return EntityUtils.toString(entity, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException("Response entity is null");
}
}
}

View File

@@ -0,0 +1,42 @@
package org.example.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 视频分段工具类
* @author dhx
* @date 2023/9/18 15:08
*/
public class VideoDeviceUtil {
/**
* videoPath: 视频存放路径
* savePath: 分割后视频存放路径
* chunk_size: 每一段大小
* 返回值: 分为多少块
*/
public static int device(String videoPath,String savePath,Long chunk_size){
int chunkNumber = 1;
try {
File inputFile = new File(videoPath);
FileInputStream fileInputStream = new FileInputStream(inputFile);
byte[] buffer = new byte[Math.toIntExact(chunk_size)];
int bytesRead;
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
String outputFilePath = savePath + "chunk_" + chunkNumber + ".bin";
File outputFile = new File(outputFilePath);
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
fileOutputStream.write(buffer, 0, bytesRead);
fileOutputStream.close();
chunkNumber++;
}
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
return -1;
}
return chunkNumber;
}
}