使用JAVA合并嗶哩嗶哩手機(jī)客戶端下載的視頻

前言

使用嗶哩嗶哩手機(jī)客戶端下載的視頻在電腦上播放,無奈視頻是分段的,每次都只好手動(dòng)的合并再播放。而且客戶端下載的視頻不會(huì)按網(wǎng)頁文件名命名,而是以av號(hào)--全數(shù)字命名。最可怕的是,每次打開一集的時(shí)候,進(jìn)入的目錄層級(jí)得嚇?biāo)廊恕?/p>

視頻層級(jí)

最最可怕的是,新版客戶端默認(rèn)文件后綴是.blv難道我們要一個(gè)一個(gè)重命名然后再合并嗎?
NO!這種重復(fù)的事情交給計(jì)算機(jī)就好了。自己動(dòng)手豐衣足食,我們就動(dòng)手寫個(gè)JAVA版的嗶哩嗶哩視頻合并小程序。

完整項(xiàng)目地址: 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

實(shí)現(xiàn)功能

  • 1.自動(dòng)識(shí)別文件夾下視頻文件并進(jìn)行合并
  • 2.合并后以視頻播放頁視頻名稱+視頻分P名稱命名
    F:\(日劇)奪愛之冬\第一話.flv
  • 3.合并完成刪除源文件

前期準(zhǔn)備

  • 1.得到嗶哩嗶哩客戶端下載的視頻目錄

將嗶哩嗶哩手機(jī)客戶端下載的視頻移出手機(jī)的Android目錄,如移動(dòng)到根目錄
因android MTP限制,電腦無法訪問Android目錄。此目錄是Android應(yīng)用緩存目錄。
視頻位于 Android--data--tv.danmaku.bili(最下面)--download下。如圖顯示的數(shù)字目錄即為需求目錄。請(qǐng)將數(shù)字目錄移出Android目錄外。

手機(jī)連上電腦后,將上述數(shù)字目錄復(fù)制或移動(dòng)到電腦。

  • 2.分析視頻目錄結(jié)構(gòu)

8896746\1\entry.json 這個(gè)json包含了整個(gè)播放目錄的名稱和每一P的名稱
8896746\1\lua.flv.bili2api.3\0.blv 這個(gè)文件夾就是各分段視頻文件了。
注意:視頻文件命名邏輯是:0.blv,1.blv...9.blv,10.blv...
也就是說,一旦視頻文件超過10個(gè),如 0-10,合并的時(shí)候會(huì)出現(xiàn)這樣的合并順序:0.blv--1.blv--10.blv--2.blv...
所以說,我們需要先把 0.blv-9.blv 重命名為 00.blv-09.blv

  • 3.FLV科普

FLV是一個(gè)二進(jìn)制文件,由文件頭(FLV header)和很多tag組成。tag又可以分成三類:audio,video,script,分別代表音頻流,視頻流,腳本流(關(guān)鍵字或者文件信息之類)。
FLV文件=FLV頭文件+ tag1+tag內(nèi)容1 + tag2+tag內(nèi)容2 + ...+... + tagN+tag內(nèi)容N。

也就是說合并FLV分段視頻的時(shí)候不能簡(jiǎn)單粗暴的將多個(gè)flv視頻片段按字節(jié)流的方式寫到一個(gè)文件中。
這時(shí)候來看FLV合并的原理:

(1) flv 文件由1個(gè)header和若干個(gè)tag組成;
(2) header記錄了視頻的元數(shù)據(jù);
(3) tag 是有時(shí)間戳的數(shù)據(jù);
(4) flv合并的原理就是把多個(gè)文件里的tag組裝起來,調(diào)整各tag的時(shí)間戳。
(5)判斷是否為第一個(gè)文件,是則安裝頭部。

  • 了解了這些就可以動(dòng)手撰寫我們的合并程序了。Let's go.

流程邏輯

  • 提示輸入嗶哩嗶哩下載的視頻文件夾(輸入文件夾),輸入輸出的文件夾。
    因最后合并完成后要?jiǎng)h除源文件,故要求輸出文件夾不能和輸入文件夾相同。
    一次輸入多個(gè)輸入文件夾以英文逗號(hào)隔開。

  • 然后進(jìn)入輸入文件夾下-- entry.json 得到視頻名稱,和輸入文件夾拼接創(chuàng)建目錄。
    如:輸出到 F:\\視頻名稱 文件夾

  • 執(zhí)行合并
    listFiles()執(zhí)行兩次進(jìn)入到這個(gè)文件夾

entry.json得到視頻每一P的名稱,拼接輸出如F:\\視頻名稱\第一話.flv
判斷進(jìn)入lua.flv.bili2api.3文件夾即可得到所有視頻文件
判斷對(duì) 0.flv-9.flv 進(jìn)行重命名---> 00.flv-09.flv
進(jìn)行合并操作

  • 刪除源文件

程序

  • 1.首先eclipse建項(xiàng)目
    包結(jié)構(gòu)很簡(jiǎn)單
包結(jié)構(gòu)
  • 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("輸入不為空,請(qǐng)重試:");
                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() + "文件夾不存在,請(qǐng)重試:");
                        isBreak = false;
                        break;
                    }
                }
            }
            if(isBreak){
                break;
            }
        }
        
        System.out.println("請(qǐng)輸入輸出路徑:");
        while(true){
            Scanner scanner = new Scanner(System.in);
            String line = scanner.nextLine();
            out = new File(line);
            if(!out.exists()){
                System.out.println("文件夾不存在,請(qǐng)重試:");
            }else{
                boolean isEquals = true;
                for(int i = 0;i < in.length;i++){
                    if(out.getAbsolutePath().equals(in[i].getAbsolutePath())){
                        isEquals = false;
                        System.out.println("輸出路徑和某個(gè)輸入路徑相同,請(qǐng)重試:");
                        break;
                    }
                }
                if(isEquals){
                    break;
                }
            }
        }
  • 3.循環(huán)讀取多個(gè)輸入目錄的視頻名稱
//循環(huán)
        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.判斷對(duì) 0.flv-9.flv 進(jìn)行重命名---> 00.flv-09.flv 后合并
public void doMerge(File in,String episode_path){
        //1、2、3、4...
        File[] files = in.listFiles();
                
        //循環(huán)
        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();
                        }
                    }
                }
            }
    }
  • 5.遞歸刪除操作
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;
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,692評(píng)論 18 399
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評(píng)論 19 139
  • 1 IONo18 1.1IO框架 【 IO:Input Output 在程序運(yùn)行的過程中,可能需要對(duì)一些設(shè)備進(jìn)...
    征程_Journey閱讀 1,030評(píng)論 0 1
  • File類 File類用來操作文件路徑或文件夾路徑 絕對(duì)路徑從根目錄開始 相對(duì)路徑在eclipse中代表當(dāng)前項(xiàng)目根...
    JerichoPH閱讀 602評(píng)論 0 3
  • 小子凌晨做噩夢(mèng),嚷著“媽媽不要去玉環(huán)”。 醒來還強(qiáng)調(diào)一次,懶媽安慰:“媽媽和寶貝一起,不去玉環(huán)”。 小子想了下,又...
    歐元小姨閱讀 165評(píng)論 1 1

友情鏈接更多精彩內(nèi)容