前言
使用哔哩哔哩手机客户端下载的视频在电脑上播放,无奈视频是分段的,每次都只好手动的合并再播放。而且客户端下载的视频不会按网页文件名命名,而是以av号–全数字命名。最可怕的是,每次打开一集的时候,进入的目录层级得吓死人。
最最可怕的是,新版客户端默认文件后缀是 .blv 。难道我们要一个一个重命名然后再合并吗?
NO!这种重复的事情交给计算机就好了。
自己动手丰衣足食,我们就动手写个JAVA版的哔哩哔哩视频合并小程序。
完整项目地址: http://git.oschina.net/chengww5217/BiliBiliMerge
直接下载使用:
http://git.oschina.net/chengww5217/BiliBiliMerge/raw/master/run/BilibiliMeroV1.2.7z
使用帮助:
http://git.oschina.net/chengww5217/BiliBiliMerge/blob/master/README.md
实现功能
- 1.自动识别文件夹下视频文件并进行合并
- 2.合并后以视频播放页视频名称+视频分 P 名称命名
F:(日剧)夺爱之冬\第一话.flv - 3.合并完成删除源文件
前期准备
将哔哩哔哩手机客户端下载的视频移出手机的 Android 目录,如移动到根目录
因 Android MTP 限制,电脑无法访问 Android 目录。此目录是 Android 应用缓存目录。
视频位于 Android–data–tv.danmaku.bili(最下面)–download 下。如图显示的数字目录即为需求目录。请将数字目录移出Android目录外。
手机连上电脑后,将上述数字目录复制或移动到电脑。
8896746\1\entry.json 这个json包含了整个播放目录的名称和每一P的名称
8896746\1\lua.flv.bili2api.3 .blv 这个文件夹就是各分段视频文件了。
注意:视频文件命名逻辑是:0.blv,1.blv…9.blv,10.blv…
也就是说,一旦视频文件超过 10 个,如 0-10,合并的时候会出现这样的合并顺序:0.blv–1.blv–10.blv–2.blv… 所以说,我们需要先把 0.blv-9.blv 重命名为 00.blv-09.blv
FLV是一个二进制文件,由文件头(FLV header)和很多tag组成。tag又可以分成三类:audio,video,script,分别代表音频流,视频流,脚本流(关键字或者文件信息之类)。
FLV文件=FLV头文件+ tag1+tag内容1 + tag2+tag内容2 + …+… + tagN+tag内容N。
也就是说合并FLV分段视频的时候不能简单粗暴的将多个flv视频片段按字节流的方式写到一个文件中。
这时候来看FLV合并的原理:
(1) flv 文件由1个header和若干个tag组成;
(2) header记录了视频的元数据;
(3) tag 是有时间戳的数据;
(4) flv合并的原理就是把多个文件里的tag组装起来,调整各tag的时间戳。
(5)判断是否为第一个文件,是则安装头部。
- 了解了这些就可以动手撰写我们的合并程序了。Let’s go.
流程逻辑
提示输入哔哩哔哩下载的视频文件夹(输入文件夹),输入输出的文件夹。
因最后合并完成后要删除源文件,故要求输出文件夹不能和输入文件夹相同。
一次输入多个输入文件夹以英文逗号隔开。
然后进入输入文件夹下– entry.json 得到视频名称,和输入文件夹拼接创建目录。
如:输出到 F:\视频名称 文件夹
执行合并
listFiles()执行两次进入到这个文件夹
entry.json 得到视频每一P的名称,拼接输出如 F:\视频名称\第一话.flv
判断进入 lua.flv.bili2api.3 文件夹即可得到所有视频文件
判断对 0.flv-9.flv 进行重命名—> 00.flv-09.flv
进行合并操作
程序
- 2.输入输出文件夹
包含main方法的Bilibili.java
输入输出文件夹
File out; File[] in = null; while(true){ boolean isBreak = true; Scanner scanner = new Scanner(System.in); String line = scanner.nextLine(); if(line == null || line.length() == 0){ System.out.println("输入不为空,请重试:"); isBreak = false; }else{ String[] lines = line.split(","); in = new File[lines.length]; for(int i = 0;i < lines.length;i++){ in[i] = new File(lines[i]); if(!in[i].exists()){ System.out.println(in[i].getAbsolutePath() + "文件夹不存在,请重试:"); isBreak = false; break; } } } if(isBreak){ break; } }
System.out.println("请输入输出路径:"); while(true){ Scanner scanner = new Scanner(System.in); String line = scanner.nextLine(); out = new File(line); if(!out.exists()){ System.out.println("文件夹不存在,请重试:"); }else{ boolean isEquals = true; for(int i = 0;i < in.length;i++){ if(out.getAbsolutePath().equals(in[i].getAbsolutePath())){ isEquals = false; System.out.println("输出路径和某个输入路径相同,请重试:"); break; } } if(isEquals){ break; } } }
|
for(int i = 0;i < in.length;i++){ String path = in[i].getAbsolutePath() +separator+ "1"+separator+"entry.json"; String line = null; try { BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), Charset.forName("utf-8"))); line = reader.readLine(); reader.close(); System.out.println("json="+line); } catch (Exception e) { e.printStackTrace(); } String[] names = tool.json_getName(line); String episode_path = out.getAbsolutePath() + separator + names[0]; File episode = new File(episode_path); if(!episode.exists()){ episode.mkdirs(); } System.out.println("输出:"+episode_path); tool.doMerge(in[i], episode_path); }
|
- 4.判断对 0.flv-9.flv 进行重命名—> 00.flv-09.flv 后合并
public void doMerge(File in,String episode_path){ //1、2、3、4... File[] files = in.listFiles(); //循环 for(File f : files){ //文件名,如第一话 String name = null; //获得所有名为.blv的文件 File[] ffs = null; File[] fs = f.listFiles(); for(final File ff : fs){ if(ff.getName().equals("entry.json")){ String json_name = null; try { BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(ff), Charset.forName("utf-8"))); json_name = reader.readLine(); reader.close(); } catch (Exception e) { e.printStackTrace(); } name = json_getName(json_name)[1]; } if(ff.isDirectory() && ff.getName().startsWith("lua.")){ //重命名 for(int i = 0; i < ff.list().length;i++){ File pathname = ff.listFiles()[i]; //0.blv -- 00.blv if(pathname.getName().endsWith(".blv") && pathname.getName().length() == 5){ pathname.renameTo(new File(pathname.getParentFile().getAbsolutePath() + File.separator + "0" + i + ".blv")); } if(pathname.getName().endsWith(".flv") && pathname.getName().length() == 5){ pathname.renameTo(new File(pathname.getParentFile().getAbsolutePath() + File.separator + "0" + i + ".flv")); } //0.blv.bdl -- 00.blv.bdl if(pathname.getName().endsWith(".blv.bdl") && pathname.getName().length() == 9){ pathname.renameTo(new File(pathname.getParentFile().getAbsolutePath() + File.separator + "0" + i + ".blv.bdl")); } } ffs = ff.listFiles(new FileFilter() { public boolean accept(File pathname) { for(int i = 0;i < ff.list().length;i++){ if(pathname.getName().endsWith(".blv") || pathname.getName().endsWith(".flv") || pathname.getName().endsWith(".blv.bdl")){ return true; } } return false; } }); //合并 System.out.println("开始合并..."); FlvMerge mFlvMerge = new FlvMerge(); try { mFlvMerge.merge(ffs, new File(episode_path + File.separator + name + ".flv")); } catch (IOException e) { e.printStackTrace(); } } } } }
|
public boolean deleteFolder(File file){ if(!file.exists()){ return false; } if(file.isFile() || file.listFiles().length == 0){ file.delete(); return true; }else{ File[] files = file.listFiles(); for(int i=0;i<files.length;i++){ deleteFolder(files[i]); } file.delete(); return true; } }
|
- 6.具体怎么对FLV视频进行合并的,请点击这里 ,注释比较清晰。