From 42ea5ca0bd726ffaabcb1c6c19af8cb08e9655c3 Mon Sep 17 00:00:00 2001 From: Klein <11241686+Klein422@user.noreply.gitee.com> Date: Mon, 18 Sep 2023 21:16:22 +0800 Subject: [PATCH] =?UTF-8?q?B=E7=AB=99=E8=A7=86=E9=A2=91=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/example/utils/HttpClientUtil.java | 8 + chopperbot-publish/pom.xml | 14 ++ .../org/example/api/BilibiliPublishApi.java | 22 ++ .../publisher/PlatformVideoPublisher.java | 16 ++ .../impl/BilibiliVideoPublisher.java | 196 ++++++++++++++++++ .../main/java/org/example/utils/FileUtil.java | 30 +++ .../org/example/utils/HttpClientUtil.java | 117 +++++++++++ .../org/example/utils/VideoDeviceUtil.java | 42 ++++ 8 files changed, 445 insertions(+) create mode 100644 chopperbot-publish/src/main/java/org/example/api/BilibiliPublishApi.java create mode 100644 chopperbot-publish/src/main/java/org/example/core/publisher/PlatformVideoPublisher.java create mode 100644 chopperbot-publish/src/main/java/org/example/core/publisher/impl/BilibiliVideoPublisher.java create mode 100644 chopperbot-publish/src/main/java/org/example/utils/FileUtil.java create mode 100644 chopperbot-publish/src/main/java/org/example/utils/HttpClientUtil.java create mode 100644 chopperbot-publish/src/main/java/org/example/utils/VideoDeviceUtil.java diff --git a/chopperbot-live/src/main/java/org/example/utils/HttpClientUtil.java b/chopperbot-live/src/main/java/org/example/utils/HttpClientUtil.java index e6093ee..e60da08 100644 --- a/chopperbot-live/src/main/java/org/example/utils/HttpClientUtil.java +++ b/chopperbot-live/src/main/java/org/example/utils/HttpClientUtil.java @@ -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 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)); } diff --git a/chopperbot-publish/pom.xml b/chopperbot-publish/pom.xml index 8bda647..b0b00af 100644 --- a/chopperbot-publish/pom.xml +++ b/chopperbot-publish/pom.xml @@ -29,5 +29,19 @@ edgegrid-signer-core 5.0.0 + + org.apache.httpcomponents + httpcore + + + org.apache.httpcomponents + httpclient + + + org.example + chopperbot-common + 1.0-SNAPSHOT + compile + diff --git a/chopperbot-publish/src/main/java/org/example/api/BilibiliPublishApi.java b/chopperbot-publish/src/main/java/org/example/api/BilibiliPublishApi.java new file mode 100644 index 0000000..04def7b --- /dev/null +++ b/chopperbot-publish/src/main/java/org/example/api/BilibiliPublishApi.java @@ -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"); //封面路径 + } +} diff --git a/chopperbot-publish/src/main/java/org/example/core/publisher/PlatformVideoPublisher.java b/chopperbot-publish/src/main/java/org/example/core/publisher/PlatformVideoPublisher.java new file mode 100644 index 0000000..8bca5f3 --- /dev/null +++ b/chopperbot-publish/src/main/java/org/example/core/publisher/PlatformVideoPublisher.java @@ -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); +} diff --git a/chopperbot-publish/src/main/java/org/example/core/publisher/impl/BilibiliVideoPublisher.java b/chopperbot-publish/src/main/java/org/example/core/publisher/impl/BilibiliVideoPublisher.java new file mode 100644 index 0000000..f6c019d --- /dev/null +++ b/chopperbot-publish/src/main/java/org/example/core/publisher/impl/BilibiliVideoPublisher.java @@ -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 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 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 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("发布失败,请手动发布"); + } + } +} diff --git a/chopperbot-publish/src/main/java/org/example/utils/FileUtil.java b/chopperbot-publish/src/main/java/org/example/utils/FileUtil.java new file mode 100644 index 0000000..3ac4f16 --- /dev/null +++ b/chopperbot-publish/src/main/java/org/example/utils/FileUtil.java @@ -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; + } +} diff --git a/chopperbot-publish/src/main/java/org/example/utils/HttpClientUtil.java b/chopperbot-publish/src/main/java/org/example/utils/HttpClientUtil.java new file mode 100644 index 0000000..e60da08 --- /dev/null +++ b/chopperbot-publish/src/main/java/org/example/utils/HttpClientUtil.java @@ -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 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 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 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 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 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 headers) { + if (headers != null && !headers.isEmpty()) { + for (Map.Entry 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"); + } + } + +} diff --git a/chopperbot-publish/src/main/java/org/example/utils/VideoDeviceUtil.java b/chopperbot-publish/src/main/java/org/example/utils/VideoDeviceUtil.java new file mode 100644 index 0000000..79bdba9 --- /dev/null +++ b/chopperbot-publish/src/main/java/org/example/utils/VideoDeviceUtil.java @@ -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; + } +}