java:哔哩哔哩 UWP 下载视频合并、重命名

背景

最近电脑磁盘空间告急,翻查发现b站下载的视频占了快200G,所以就想转移备份到云盘上去,这样还可以在线看。

可是找到存储的下载文件,却发现可视化太低了,文件名都是一串数字,需要通过b站客户端才能解析出视频名称和分P标题这些。

所以在上传云盘之前就得手动处理一下。

回忆

2019年,b站网页版是不提供下载的,当然现在依然不行。

官方下载渠道就是客户端,手机APP或者pc客户端。

PC客户端那个时候只能从win10应用商店下载,哔哩哔哩 UWP。

  • UWP即[Windows 10](https://baike.baidu.com/item/Windows 10?fromModule=lemma_inlink)中的Universal Windows Platform简称,即Windows通用应用平台。
  • 从腾讯到B站,UWP应用为何被大家弃之如敝履
  • 今天登上去看看,挂着停止维护的通知,而且不能登陆,不过功能都还能正常使用。

image-20230923121741799

按着惯例,先百度,淘淘有没有造好的轮子。

有一些博客,但不多。

下载FFmpeg

UWP 下载的视频格式一直在变化,分段flv–>音画分离–>完整视频MP4。

涉及到视频合并(分段合并、音频视频合并)和格式转换,需要使用 FFmpeg

进入后点击下载按钮

选择windows后,点击第一个

进入后点击ffmpeg-git-full.7z版下载压缩包

绿色版,解压即可使用

image-20230923124241148

配置环境变量PATH:bin目录

  • 如果不配环境变量,就需要写绝对路径调用

image-20230923124120547

检查是否成功

cmd窗口,键入ffmpeg -version命令,如果弹出ffmpeg的版本即为安装成功

分析3种视频格式

  • .dvi json格式文件,Title 值即视频名称
  • .info json格式文件,Title 值即分P名称
  • .xml xml格式文件,弹幕信息

根目录

image-20230922000515669

1.分段flv

  • 一般6分钟一段

image-20230922230038457

2.音画分离

image-20230922230004453

3.完整的视频

image-20230922000540834

java代码

package Bilibili;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import util.PythonUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static json.ParseJsonUtil.readFileContent;

public class BilibiliUtilVideoUWP {
    
    

    public static void main(String[] args) throws Exception {
    
    
        // 根目录下 967612226.dvi cover.jpg desktop.ini
        // String filepath = "D:\\bilibiliload\\333024338"; // 完整视频MP4
        // String filepath = "D:\\bilibiliload\\967612226"; // 音画分开MP4
        // String filepath = "D:\\bilibiliload\\17222411"; // 分段flv 一般6分钟一段
        File file = new File(filepath);
        if (!file.exists() || !file.isDirectory()) {
    
    
            System.out.println("请指定正确的目录");
            return;
        }
        File[] files = file.listFiles();

        // 根目录下获取视频名称 .dvi
        File[] dviFiles = file.listFiles((dir, name) -> name.endsWith(".dvi"));
        if (dviFiles == null || dviFiles.length == 0) {
    
    
            System.out.println("不存在指定后缀的文件");
            return;
        }
        File dviFile = dviFiles[0];
        if (!dviFile.isFile()) {
    
    
            System.out.println("不存在指定后缀的文件");
            return;
        }
        String jsonString = readFileContent(dviFile);
        JSONObject jsonObject = JSON.parseObject(jsonString);

        String title = jsonObject.getString("Title");
        if (StringUtils.isBlank(title)) {
    
    
            System.out.println("视频名称为null");
            return;
        }

        // 复制到一个新的目录下
        // File parentFile = file.getParentFile();
        File targetFile = new File(file, title);
        // 已存在同名目录处理,在文件夹名称后面+副本
        while (true) {
    
    
            if (!targetFile.exists() || !targetFile.isDirectory()) {
    
    
                // 创建目录
                // targetFile.mkdir(); // 只能创建一层目录
                targetFile.mkdirs(); // 创建多层目录,包括创建必需但不存在的父目录
                break;
            }
            title += "-副本";
            targetFile = new File(file, title);
        }
        // stream流排序 https://blog.csdn.net/qq_36763236/article/details/111469653
        List<File> fileList = Arrays.stream(files)
                .filter(f -> isP(f))
                .sorted(Comparator.comparing(f -> Integer.parseInt(f.getName())))
                .collect(Collectors.toList());

        if (CollectionUtils.isNotEmpty(fileList)) {
    
    
            for (File dir : fileList) {
    
    
                // 获取分P标题
                // 333024338.info 333024338_1.xml 333024338_1_0.mp4
                File[] pFiles = dir.listFiles();
                // 视频文件 mp4 flv
                List<File> videoFileList = new ArrayList<>();
                // 视频信息文件 .info
                File partNameFile = null;

                for (File pFile : pFiles) {
    
    
                    if (!pFile.isFile()) {
    
    
                        continue;
                    }
                    if (pFile.getName().endsWith(".info")) {
    
    
                        partNameFile = pFile;
                    }
                    if (pFile.getName().endsWith(".mp4") || pFile.getName().endsWith(".flv")) {
    
    
                        // audio1.mp4 video.mp4 音频和视频都是MP4
                        videoFileList.add(pFile);
                    }
                }

                if (partNameFile != null && CollectionUtils.isNotEmpty(videoFileList)) {
    
    
                    String partNameJsonString = readFileContent(partNameFile);
                    JSONObject partNameJsonObject = JSON.parseObject(partNameJsonString);

                    // 分p名称
                    String partName = partNameJsonObject.getString("PartName");
                    // 视频格式 1:flv 2:MP4
                    int format = partNameJsonObject.getIntValue("Format");
                    // 视频文件个数 flv:未合并  MP4:音频视频分离
                    int totalParts = partNameJsonObject.getIntValue("TotalParts");
                    // 是否合并
                    Boolean isMerged = partNameJsonObject.getBoolean("IsMerged");

                    if (format == 2 && isMerged != null && isMerged) {
    
    
                        // 完整视频 Mp4
                        if (StringUtils.isNotBlank(partName)) {
    
    
                            File videoFile = videoFileList.get(0);
                            // copy文件
                            // 4种Java文件复制的方法 https://blog.csdn.net/qq_30436011/article/details/127490081
                            File dest = new File(targetFile, partName + ".mp4");
                            Files.copy(videoFile.toPath(), dest.toPath());

                            // 测试时你,可以先只处理一个
                            // break;
                        }
                    }

                    if (format == 2 && isMerged != null && !isMerged) {
    
    
                        // 音频视频分离
                        if (StringUtils.isNotBlank(partName)) {
    
    
                            if (videoFileList.size() > 2) {
    
    
                                System.out.println("视频文件存在多个: " + dir.getAbsolutePath());
                                continue;
                            }
                            // audio1.mp4 video.mp4
                            String audioPath = null; // 音频文件路径
                            String videoPath = null; // 视频文件路径
                            File file1 = videoFileList.get(0);
                            File file2 = videoFileList.get(1);
                            if (file1.getName().startsWith("audio")) {
    
    
                                audioPath = file1.getAbsolutePath();
                                videoPath = file2.getAbsolutePath();
                            } else if (file1.getName().startsWith("video")) {
    
    
                                audioPath = file2.getAbsolutePath();
                                videoPath = file1.getAbsolutePath();
                            }

                            File dest = new File(targetFile, partName + ".mp4");
                            // 合并音视频
                            ffmpegMerge(videoPath, audioPath, dest.getAbsolutePath());

                            // 测试时你,可以先只处理一个
                            // break;
                        }
                    }

                    // ffmpeg将多个flv文件合成为mp4
                    if (format == 1) {
    
    
                        // 分段flv
                        if (StringUtils.isNotBlank(partName)) {
    
    
                            // 17222411_2_0.flv 17222411_2_1.flv 17222411_2_2.flv
                            videoFileList = videoFileList.stream()
                                    .sorted(Comparator.comparing(f -> Integer.parseInt(f.getName().substring(f.getName().indexOf("_") + 1, f.getName().lastIndexOf(".")).replace("_", ""))))
                                    .collect(Collectors.toList());

                            String str = "";
                            for (File flvFile : videoFileList) {
    
    
                                if (StringUtils.isNotBlank(str)) {
    
    
                                    str += "\n";
                                }
                                str += "file '" + flvFile.getAbsolutePath() + "'";
                            }

                            System.out.println("flv个数:" + videoFileList.size());
                            // 保存文件名到目标文件 给ffmpeg调用
                            File listFile = new File(file, "list.txt");
                            String listFilePath = listFile.getAbsolutePath();
                            // 是覆盖,还是追加??? 是覆盖
                            writeFile(str, listFilePath);

                            File dest = new File(targetFile, partName + ".mp4");

                            ffmpegMergeFlv(listFilePath, dest.getAbsolutePath());

                            // 测试时你,可以先只处理一个
                            // break;
                        }
                    }

                }

            }
        }

    }

    /**
     * 是否为分P目录
     */
    private static boolean isP(File file) {
    
    
        if (file == null || !file.isDirectory()) {
    
    
            return false;
        }
        String name = file.getName();
        // java判断字符串是否为纯数字 https://jingyan.baidu.com/article/c74d6000d721f50f6b595d5e.html
        return name.matches("[0-9]+");
    }

    /**
     * 将内容写到目标文件
     */
    private static void writeFile(String newContent, String targetFile) throws Exception {
    
    
        File file = new File(targetFile);
        FileOutputStream fos = new FileOutputStream(file);
        OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
        osw.write(newContent);
        osw.flush();
        osw.close();
        fos.close();
    }

    /**
     * java实现音视频的合并
     * https://blog.csdn.net/maziaotong/article/details/127863727
     * 使用 ffmpeg 来合并音频和视频文件 https://www.cnblogs.com/dragona/p/17206097.html
     *
     * @param videoFilePath 原视频路径
     * @param audioFilePath 原视频路径
     * @param desFilePath   合并后视频存放路径+视频名称
     */
    public static void ffmpegMerge(String videoFilePath, String audioFilePath, String desFilePath) {
    
    
        // ffmpeg -i 原视频路径 -i 原音频路径 -codec copy 合并后视频存放路径+视频名称

        // 解释器
        String exe = "ffmpeg";
        // 组合成一个字符串数组
        String[] cmdArr = new String[]{
    
    exe, "-i", videoFilePath, "-i", audioFilePath, "-codec", "copy", desFilePath};

        PythonUtil.executePythonScript(cmdArr);
    }

    /**
     * 使用ffmpeg批量合并flv文件
     * ffmpeg -f concat -i mylist.txt -c copy output.flv
     * https://www.likecs.com/show-379618.html
     *
     * @param desFilePath 合并后视频存放路径+视频名称
     */
    public static void ffmpegMergeFlv(String listFilePath, String desFilePath) {
    
    
        // 解决报错:-safe 0
        // [concat @ 000001bbe578d3c0] Unsafe file name 'D:bilibiliload17222411117222411_1_0.flv'
        // [in#0 @ 000001bbe5777500] Error opening input: Operation not permitted

        // 解决报错:文件路径加单引号
        // Impossible to open 'D:bilibiliload17222411117222411_1_0.flv'

        // -loglevel quiet 如果报错可以删掉,放出日志
        // ffmpeg设置终端不显示日志 https://www.cnblogs.com/chentiao/p/17174382.html
        String cmdStr = MessageFormat.format("ffmpeg -loglevel quiet -f concat -safe 0 -i {0} -codec copy {1}", listFilePath, desFilePath);
        System.out.println("cmdStr: " + cmdStr);
        PythonUtil.executeCmd(cmdStr);

    }

}

最新版

老版的处理完,顺便研究下新版的客户端

pc客户端

下载

image-20230923130644378

使用

image-20230923130407881

下载完,客户端是显示的合集,本地文件夹却是一个分P一个文件夹,没有归类,全在根目录

image-20230923130757692

image-20230923130821731

另外,视频格式换成了m4s,音画分离,而且你还无法判断谁是音频、谁是视频

image-20230923131102105

java代码

andriod

缓存的文件在 Android/data/tv.danmaku.bili/download

文件有按合集归类,也是音画分离,不过视频类别一目了然

image-20230925214146065

image-20230925214206611

image-20230925214219397

java代码

猜你喜欢

转载自blog.csdn.net/weixin_44174211/article/details/133281689