环境jdk14
maven构建

接口文档地址:https://github.com/1015770492/bilibili-download/blob/master/doc/bilibili-Api文档.md

原理是下载视频文件(不带声音)和音频文件,然后通过第三方工具ffmpeg合并视频文件和音频文件为一个文件
ffmpeg官网地址

自定义的http请求工具类部分代码展示;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class BiliBiliHttpUtils {

    /**
     * 没有就创建目录,有则返沪true
     * @param destDirName
     * @return
     */
    public static boolean createDirect(String destDirName) {
        File file = new File(destDirName);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        return true;
    }



    /**
     * 下载文件的作用,需要3个参数 资源原路径,真正下载的url,保存本地的路径
     *
     * @param refererUrl 视频的参考路径,
     * @param url        真正下载的url链接,可以是m4s文件和audio文件,下载后需要合并
     * @param savePath   保存的路径
     * @return 下载成功则返回true,中间报异常退出则返回false
     */
    public static boolean downloadFile(String refererUrl, String url, String savePath) {
        System.out.print("下载文件:");
        System.out.println(savePath);//打印保存的文件路径
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.add("referer", refererUrl);//设置请求头
        HttpEntity<String> entity = new HttpEntity<String>("", headers);
        System.out.println("发送请求获取文件的输入流");
        ResponseEntity<Resource> in = restTemplate.exchange(url, HttpMethod.GET, entity, Resource.class);
        if (in.getStatusCode() == HttpStatus.OK) {
            try (InputStream is = in.getBody().getInputStream()) {//java9新特性自动关闭流
                boolean flag = createDirect(savePath);
                if (flag) {
                    try (FileOutputStream fos = new FileOutputStream(savePath);) {
                        System.out.println("正在写入文件...");
                        is.transferTo(fos);//写入输出流
                        return true;//写入成功返回true
                    } catch (IOException e) {
                        e.printStackTrace();
                        System.out.println("写入中断");
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("创建文件夹失败");
            }
            System.out.println("文件写入完成");
        } else {
            System.out.println("状态码" + in.getStatusCode());
            System.out.println(in.toString());
        }


        return false;//默认返回失败
    }


    /**
     * 获取url中的 bvid
     *
     * @param url
     * @return
     * @throws Exception
     */
    public static String getBVID(String url) throws Exception {
        Pattern BVIDPartPattern = Pattern.compile("BV[a-z|0-9|A-Z]*");
        Matcher matcher = BVIDPartPattern.matcher(url);
        String bvid;
        if (matcher.find()) {
            bvid = matcher.group();
        } else {
            throw new Exception("传入的url中不包含bvid");
        }
        return bvid;
    }

    /**
     * 传入视频的播放地址,例如url: "https://www.bilibili.com/video/BV1m4411H7pi"  获取其中包含BV的这串字符串
     *
     * @param url
     * @return
     * @throws Exception
     */
    public static JSONObject getCidJSON(String url) throws Exception {
        String bvid = getBVID(url);
        //从url中转换成最终请求的url接口
        String requestUrl = new StringBuilder("http://api.bilibili.com/x/web-interface/view?bvid=").append(bvid).toString();
        String jsonString = new RestTemplate().getForEntity(requestUrl, String.class).getBody();
        JSONObject parse = JSONObject.parseObject(jsonString);
        return parse.getJSONObject("data");//返回data部分
    }


    /**
     * 传入cid的json数组,获取所有下载链接--》一个cid对应一个视频,一个视频有多个清晰度的播放源
     *
     * @param cidJSONObject
     * @return
     */
    public static List<JSONObject> getPlayUrlList(JSONObject cidJSONObject) {
        String bvid = cidJSONObject.getString("bvid");//视频的id
        String title = cidJSONObject.getString("title");//标题--》用来做文件夹
        String picUrl = cidJSONObject.getString("pic");//封面图片


        JSONArray pages = cidJSONObject.getJSONArray("pages");//包含这一系列视频的cid内容
        //如果传入的cid过多并行流会导致http请求过大,容易被服务器拒绝
        try (Stream<JSONObject> jsonObjectStream = pages.stream()
                .map(o -> {
                    JSONObject obj = (JSONObject) o;
                    String cid = obj.getString("cid");//获取cid
                    String part = obj.getString("part");//获取视频名  前缀
                    System.out.println(part);//映射

                    //获取播放地址的api
                    String requestUrl = new StringBuilder("https://api.bilibili.com/x/player/playurl?cid=").append(cid)
                            .append("&bvid=").append(bvid).append("&qn=80&fnver=0&fnval=16&fourk=1").toString();

                    String jsonString = new RestTemplate().getForEntity(requestUrl, String.class).getBody();//执行太快了
                    JSONObject data = JSONObject.parseObject(jsonString).getJSONObject("data");//获取api返回的jaon中的data对象
                    /**
                     * 添加自定义的属性,方便保存
                     */
                    data.put("cid", cid);//将cid添加到返回的数据中
                    data.put("directName", title);//添加视频的标题作为文件夹
                    data.put("videoName", part);//添加视频的名称
                    return data;//将添加了自定义属性的json返回,作为新的流
                })) {
//        System.out.println(cidJSONObject);

            return jsonObjectStream.collect(Collectors.toList());//将收集到的url封装起来
        }
    }

}

合并图像和音频文件代码展示

import java.io.File;
import java.io.IOException;
import java.util.concurrent.*;

public class MergeVideoAndAudioUtils {
    static ExecutorService executorService = Executors.newFixedThreadPool(20);

    public static boolean merge(String videoPath, String audioPath, String savePath) throws ExecutionException, InterruptedException {

        File directory = new File("");//设定为当前文件夹
        String currentAbsolutePath = directory.getAbsolutePath();
//        System.out.println(currentAbsolutePath);

        String relativePath = "src/main/java/top/huashengshu/bilibili/utils/merge/util/ffmpeg.exe";
        String ffmpegAbsolutePath = new StringBuilder(currentAbsolutePath).append("/").append(relativePath)
                .append(" -i \"").append(videoPath)
                .append("\" -i \"").append(audioPath)
                .append("\" -c copy \"").append(savePath).append("\"")
                .toString();//合并音视频文件得命令
        CompletableFuture<Process> processFuture = CompletableFuture.supplyAsync(() -> {
            Process process = null;
            try {
                Runtime runtime = Runtime.getRuntime();//获取cmd窗口
                System.out.println(ffmpegAbsolutePath);//打印一下命令
                process = runtime.exec(ffmpegAbsolutePath);//执行合并命令
            } catch (IOException e) {
                e.printStackTrace();
                return process;//异常返回
            }
            return process;
        }, executorService);

        Process process = processFuture.get();//获取上一个的process对象

        CompletableFuture.runAsync(() -> {
            while (process.isAlive()) {
            }//判断合并命令是否执行完成,如果完成则会跳出循环
            clearGarbageFile(videoPath,savePath);//清理垃圾文件
            clearGarbageFile(audioPath,savePath);//清理垃圾文件
        },executorService);
        return true;//将合并结果返回
    }

    public static boolean clearGarbageFile(String filePath,String mp4Path) {
        File mp4File = new File(mp4Path);
        if (mp4File.exists()) {
            File tempFile = new File(filePath);
            if (tempFile.exists()){
                tempFile.delete();//文件存在,删除文件
            }else {
                System.out.println("文件不存在:"+filePath);
            }
        }else {
            System.out.println("没有合并成:"+mp4Path);
        }
        return true;
    }

}

使用方式

调用BiliBiliUtilspatchDownload方法即可

/**
 * 测试案例
 *
 * @param args
 */
public static void main(String[] args) {
    String referUrl = "https://www.bilibili.com/video/BV1oi4y1u7Bq";//测试用的url
    BiliBiliUtils.patchDownload(referUrl, "D:/");//将referUrl系列的所有视频存到D:/盘
}

在这里插入图片描述
项目没有完全完成,在改进中,并且会有后期版本,采用vue+ElementUI的静态页面,调用工具类实现批量下载视频,暂时没有实现登录功能

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐