http://ffmpeg.org/
選擇須要的版本
在這個網址下載ffmpeg,https://github.com/BtbN/FFmpeg-Builds/releases
將解壓後獲得的如下幾個文件放置在E:\FFmpeg
下
python
此電腦--屬性--高級系統設置--環境變量
在系統變量(也就是下面那一半)處找到新建,按以下所示的方法填寫
再將%FFMPEG_HOME%
以及%FFMPEG_HOME%\bin
寫入系統變量的Path中
而後一路肯定便可git
win+R,cmd
輸入ffmpeg -version
github
對於我將B站PC端緩存的音頻mp4和視頻mp4文件合併的需求,須要用到的命令爲:
ffmpeg.exe -i audio1.mp4 -i video.mp4 -acodec copy -vcodec copy output.mp4
能夠把mp4的文件設置成絕對路徑,這樣就能夠轉換指定路徑的文件以及保存到指定路徑了,好比這樣:
ffmpeg.exe -i "E:\嗶哩嗶哩視頻\ss27993\77413703\1\audio1.mp4" -i "E:\嗶哩嗶哩視頻\ss27993\77413703\1\video.mp4" -acodec copy -vcodec copy "E:\B站導出視頻\Dr.STONE石紀元\第22話寶物.mp4
經過PC端緩存的未合併的視頻和音頻,全都是命名爲video.mp4和audio.mp4
PS:有些兄弟是導出的手機端緩存的視頻和音頻,是m4s格式的,方法也同樣
但光有這條命令還不夠,須要本身手動一個個操做,太麻煩了
所以我還須要使用Python來自動幫我完成工做編程
雖然Python能夠實現自動化,減小時間的浪費,但最快的仍是之後記得緩存時勾選自動合併
json
PC端緩存的視頻保存的文件結構有不少種,我只是根據我碰到的狀況寫的,但大同小異,修改起來也不麻煩,只是再加個if和else罷了緩存
緩存的番劇是這種結構:
上面舉的例子是Dr.stone石紀元,我緩存的鬼滅之刃也是如此
特色是在一個以視頻ID號名稱的文件夾(ss27993)後,跟着許多子文件夾(57983089等),而後在這些子文件夾中又有一個或多個子文件夾(好比1),而後緩存的視頻保存在這個文件夾裏,裏面有一個info文件(就是json格式),還有audio1.mp4和video.mp4。
PS:
還有一個xml文件,是彈幕信息,暫時我不知道怎麼處理
app
除了番劇,通常的視頻緩存的結構是這樣的
不難看出,這比番劇要少一個層級ide
文件信息主要由info文件和dvi文件來記錄
這兩種文件均可以直接以json文件來處理,也就是,首先open函數打開文件,而後用json.load轉成字典。。。
而後我還發現了一個特色是,視頻和音頻所在的目錄下是info文件,而它的上一層目錄下是dvi文件
雖然文件格式基本是一致的,可是裏面的鍵-值關係卻不一致,單集視頻的名稱在番劇中對應的是鍵Description
,在其餘視頻中對應的是PartName
視頻總的名稱保存在外層的目錄下的info文件或dvi文件中,番劇中對應的鍵是SeasonTitle
,在其餘視頻中對應的是Title
具體問題具體分析,首先因爲我實踐得較少,這樣的總結不必定對,而後之後也許也會有新的格式、新的變化模塊化
如下是個人Python代碼,你能夠先試試能不能用,用不了的話,能夠在理解的基礎上修改。理解不了的話能夠看個人後面的解釋,以及代碼中的註釋,對於運行過程當中一些變量的值,我都把它放在註釋中了,方便你理解。函數
# -*- coding = utf-8 -*- # @time:2020/10/17/017 23:09 # Author:cyx # @File:main.py.py # @Software:PyCharm # 從.info文件中得到了Title信息,可是若是其中有某些特殊字符,保存時可能出現問題 def get_correct_title(title): error_set = ['/', '\\', ':', '*', '?', '"', '|', '<', '>', '\b', ' ', '.'] correct_title = title # print(title) for c in correct_title: if c in error_set: correct_title = correct_title.replace(c, '') return correct_title def popen(cmd): # https://blog.csdn.net/qq_41451161/article/details/82901235 try: popen = subprocess.Popen(cmd, stdout=subprocess.PIPE) popen.wait() lines = popen.stdout.readlines() return [line.decode('gbk') for line in lines] except BaseException as e: return -1 if __name__ == '__main__': import os import json import subprocess # ffmpeg -i video.m4s -i audio.m4s -c:v copy -c:a aac -strict experimental output.mp4 # ffmpeg.exe -i audio1.mp4 -i video.mp4 -acodec copy -vcodec copy output.mp4 AVhao = input("請輸入視頻AV號:") superPath = "E:\\嗶哩嗶哩視頻" + "\\" + AVhao partDirs = [] # 保存每P視頻所在的文件夾路徑 paths = os.listdir(superPath) # 獲取當前路徑下全部的文件(包括文件夾)名稱 # paths = ['8','9'] # 有時候,會莫名其妙的少了幾個視頻,能夠經過重載來從新加載缺失的視頻 # print(paths) # paths # ['27993.info', '57983089', '58612211', '59811008', '60862133', '61898240', '62925012', '64005445', '65020725', '66013155', '66808912', '67587875', '68398229', '69175748', '70021307', '70873680', '71617211', '73379440', '74051851', '74974157', '75746600', '76619409', '77413703', '78266594', '79070874', 'cover.jpg', 'desktop.ini'] # 獲取每P視頻所在的文件夾路徑 savePos = '' seq = [] # 鑑於有些up主命名時毫無規律,導出後沒法正常排序,只能手動排序了 # 根據AV號名文件夾下的子文件夾的名稱進行排序,可是番劇的話不是這樣排序,不過番劇單集的名稱很規範,不須要這樣 for p in paths: if '.' not in p: seq.append(p) if 'info' in p: # print(p) # p: # 27993.info info = superPath + "\\" + p with open(info, 'r', encoding='utf-8') as load_f: load_dict = json.load(load_f) projectTitle = load_dict['SeasonTitle'] projectTitle = get_correct_title(projectTitle) savePos = 'E:\\B站導出視頻\\' + projectTitle print('savePos: ', savePos) if 'dvi' in p: # print(p) # P: # 328738595.dvi dvi = superPath + "\\" + p with open(dvi, 'r', encoding='utf-8') as load_f: load_dict = json.load(load_f) projectTitle = load_dict['Title'] projectTitle = get_correct_title(projectTitle) savePos = 'E:\\B站導出視頻\\' + projectTitle print('savePos: ', savePos) # 防止文件存在時再次生成該文件夾出現錯誤 try: os.mkdir(savePos) break except: pass subDir = superPath + "\\" + p if os.path.isdir(subDir): # print(subDir) # 全部子文件夾的路徑保存在partDirs中 partDirs.append(subDir) # print("partDirs: ",partDirs) # partDirs: ['E:\\嗶哩嗶哩視頻\\ss27993\\57983089', 'E:\\嗶哩嗶哩視頻\\ss27993\\58612211', 'E:\\嗶哩嗶哩視頻\\ss27993\\59811008', 'E:\\嗶哩嗶哩視頻\\ss27993\\60862133', 'E:\\嗶哩嗶哩視頻\\ss27993\\61898240', 'E:\\嗶哩嗶哩視頻\\ss27993\\62925012', 'E:\\嗶哩嗶哩視頻\\ss27993\\64005445', 'E:\\嗶哩嗶哩視頻\\ss27993\\65020725', 'E:\\嗶哩嗶哩視頻\\ss27993\\66013155', 'E:\\嗶哩嗶哩視頻\\ss27993\\66808912', 'E:\\嗶哩嗶哩視頻\\ss27993\\67587875', 'E:\\嗶哩嗶哩視頻\\ss27993\\68398229', 'E:\\嗶哩嗶哩視頻\\ss27993\\69175748', 'E:\\嗶哩嗶哩視頻\\ss27993\\70021307', 'E:\\嗶哩嗶哩視頻\\ss27993\\70873680', 'E:\\嗶哩嗶哩視頻\\ss27993\\71617211', 'E:\\嗶哩嗶哩視頻\\ss27993\\73379440', 'E:\\嗶哩嗶哩視頻\\ss27993\\74051851', 'E:\\嗶哩嗶哩視頻\\ss27993\\74974157', 'E:\\嗶哩嗶哩視頻\\ss27993\\75746600', 'E:\\嗶哩嗶哩視頻\\ss27993\\76619409', 'E:\\嗶哩嗶哩視頻\\ss27993\\77413703', 'E:\\嗶哩嗶哩視頻\\ss27993\\78266594', 'E:\\嗶哩嗶哩視頻\\ss27993\\79070874'] videoPos = '' i = 0 for p in partDirs: # print(p) # 列出子文件夾中的全部文件 sublist = os.listdir(p) # 檢查info文件是否在當前子文件夾中 for file in sublist: # print(file) # file: # 1 # 57983089. # dvi # cover.jpg # desktop.ini if 'info' in file: infoPos = p + "\\" + file videoPos = p else: subsubDir = p + "\\" + file if os.path.isdir(subsubDir): # print(subsubDir) # subsubDir: E:\嗶哩嗶哩視頻\ss27993\57983089\1 subsubList = os.listdir(subsubDir) for subsubFile in subsubList: if 'info' in subsubFile: infoPos = subsubDir + "\\" + subsubFile videoPos = subsubDir break with open(infoPos, 'r', encoding='utf-8') as load_f: load_dict = json.load(load_f) if 'ss' in AVhao: videoTitle = load_dict['Description'] else: videoTitle = load_dict['PartName'] videoTitle = get_correct_title(videoTitle) print('videoTitle: ', videoTitle) videoDir = videoPos + "\\" + 'video.mp4' audioDir = videoPos + "\\" + 'audio1.mp4' # print('videoDir: ', videoDir) # print('audioDir: ', audioDir) # videoDir: E:\嗶哩嗶哩視頻\ss27993\74051851\1\video.mp4 # audioDir: E:\嗶哩嗶哩視頻\ss27993\74051851\1\audio1.mp4 if 'ss' in AVhao: outDir = savePos + "\\" + videoTitle + '.mp4' else: outDir = savePos + "\\" + seq[i] + '_' + videoTitle + '.mp4' i += 1 # 對於那些命名很規範的視頻,能夠不用本身再排序,進行一下重載,不規範的視頻再把這句註釋掉就好 outDir = savePos + "\\" + videoTitle + '.mp4' # command = 'cd ' + superPath + '\\64 && ' # && 多名命令 # command = 'cd ' + 'E:\\ProgramFiles\\ffmpeg' + ' && ' command = 'E:\\FFmpeg\\bin\\ffmpeg.exe -i ' + '"' + audioDir + '"' ' -i ' + '"' + videoDir + '"'+ ' -acodec copy -vcodec copy ' + '"' + outDir + '"' # print("保存地址",outDir) # 保存地址 E:\B站導出視頻\[Lynda視頻]音頻錄製錄音技巧教程(中英雙語字幕)全集130課時AudioRecordingTechniques混音錄音棚音樂工做室歌曲調音\98錄製獨奏薩克斯演奏技巧二.mp4 # print(command) # command = 'E:\\FFmpeg\\bin\\ffmpeg.exe -i "E:\嗶哩嗶哩視頻\ss27993\77413703\1\audio1.mp4" -i "E:\嗶哩嗶哩視頻\ss27993\77413703\1\video.mp4" -acodec copy -vcodec copy "E:\B站導出視頻\Dr.STONE石紀元\第22話寶物.mp4' # os.system(command) popen(command) # ffmpeg.exe -i audio1.mp4 -i video.mp4 -acodec copy -vcodec copy output.mp4 break
如你所見個人編程水平不高,模塊化作的不好,不便於理解,因此有必要進行說明。
直接從main開始看起吧。
AVhao = input("請輸入視頻AV號:") superPath = "E:\\嗶哩嗶哩視頻" + "\\" + AVhao partDirs = [] # 保存每P視頻所在的文件夾路徑 paths = os.listdir(superPath) # 獲取當前路徑下全部的文件(包括文件夾)名稱
首先是用input接收AV號或BV號的輸入,放入AVhao變量中。
而後用superPath變量存放你須要合併的視頻的根目錄。好比我在B站緩存的全部視頻存放在E:\嗶哩嗶哩視頻
下,注意程序中要有兩條\,而後superPath就是E:\嗶哩嗶哩視頻\AVhao
。
os.listdir(),括號中的參數必須是一個真實的路徑,這個函數能夠獲得這個路徑下全部的文件和文件夾的名稱。
我用paths來存放E:\嗶哩嗶哩視頻\AVhao
路徑下全部的文件名稱和文件夾名稱。
爲了防止個人表達能力有限帶來的理解上的不便,你能夠看圖,paths對應的是下圖中的內容:
這個變量的類型是列表,因此能夠用for循環來遍歷。
savePos = '' seq = [] for p in paths: if '.' not in p: seq.append(p) if 'info' in p: # print(p) # p: # 27993.info info = superPath + "\\" + p with open(info, 'r', encoding='utf-8') as load_f: load_dict = json.load(load_f) projectTitle = load_dict['SeasonTitle'] projectTitle = get_correct_title(projectTitle) savePos = 'E:\\B站導出視頻\\' + projectTitle print('savePos: ', savePos) if 'dvi' in p: # print(p) # P: # 328738595.dvi dvi = superPath + "\\" + p with open(dvi, 'r', encoding='utf-8') as load_f: load_dict = json.load(load_f) projectTitle = load_dict['Title'] projectTitle = get_correct_title(projectTitle) savePos = 'E:\\B站導出視頻\\' + projectTitle print('savePos: ', savePos) # 防止文件存在時再次生成該文件夾出現錯誤 try: os.mkdir(savePos) break except: pass subDir = superPath + "\\" + p if os.path.isdir(subDir): # print(subDir) # 全部子文件夾的路徑保存在partDirs中 partDirs.append(subDir)
我設置了一個savePos變量,用來表示合併後的視頻保存的位置,由於我但願將視頻保存在一個我本身指定的文件夾下,同時這個文件夾的名稱是這個視頻的名稱,好比Dr.stone石紀元。
由於想自動化操做,因此我經過緩存文件夾中的info文件和dvi文件來找到視頻的名稱。
使用open函數打開這兩個文件中的一個,由於不肯定文件結構是什麼樣的,因此我用了兩個if語句。
而後再用json.load函數將其加載爲字典,並根據對應的鍵讀取對應的值,從而能夠拼接處對應的保存地址savePos。
因爲建立已經存在的同名文件夾會發生錯誤,爲避免這種可能,我將建立目錄的操做放在了try語句下。
建立目錄用的是os.mkdir()函數,括號中是一個絕對路徑。
而後我用subDir來表示子文件夾的名稱,注意!是子文件夾,而不是文件。
我用os.path.isdir(subDir)來進行判斷,若是是文件夾而不是文件的話,就加到partDirs列表中,partDirs.append(subDir)
這個列表中的每一個元素都是一個子文件夾的絕對路徑,好比:E:\嗶哩嗶哩視頻\ss27993\57983089
而這個名爲seq的顯得很突兀,這個其實我也是後來加的,這個列表的做用在於記錄當前子文件夾的名稱,也就是在AVhao文件夾的下一層,若是不是番劇的話,應當有許多個文件夾分別是一、二、3等等,這些其實對應的是播放列表中的順序。
而之因此使用if '.' not in p
,由於這一層的文件夾全都是用來表示播放順序的,所以不存在後綴名,從而也就沒有「.」。
videoPos = '' i = 0 for p in partDirs: # print(p) # 列出子文件夾中的全部文件 sublist = os.listdir(p) # 檢查info文件是否在當前子文件夾中 for file in sublist: # print(file) # file: # 1 # 57983089. # dvi # cover.jpg # desktop.ini if 'info' in file: infoPos = p + "\\" + file videoPos = p else: subsubDir = p + "\\" + file if os.path.isdir(subsubDir): # print(subsubDir) # subsubDir: E:\嗶哩嗶哩視頻\ss27993\57983089\1 subsubList = os.listdir(subsubDir) for subsubFile in subsubList: if 'info' in subsubFile: infoPos = subsubDir + "\\" + subsubFile videoPos = subsubDir break with open(infoPos, 'r', encoding='utf-8') as load_f: load_dict = json.load(load_f) if 'ss' in AVhao: videoTitle = load_dict['Description'] else: videoTitle = load_dict['PartName'] videoTitle = get_correct_title(videoTitle) print('videoTitle: ', videoTitle) videoDir = videoPos + "\\" + 'video.mp4' audioDir = videoPos + "\\" + 'audio1.mp4' # print('videoDir: ', videoDir) # print('audioDir: ', audioDir) # videoDir: E:\嗶哩嗶哩視頻\ss27993\74051851\1\video.mp4 # audioDir: E:\嗶哩嗶哩視頻\ss27993\74051851\1\audio1.mp4 if 'ss' in AVhao: outDir = savePos + "\\" + videoTitle + '.mp4' else: outDir = savePos + "\\" + seq[i] + '_' + videoTitle + '.mp4' i += 1 # 對於那些命名很規範的視頻,能夠不用本身再排序,進行一下重載,不規範的視頻再把這句註釋掉就好 outDir = savePos + "\\" + videoTitle + '.mp4' # command = 'cd ' + superPath + '\\64 && ' # && 多名命令 # command = 'cd ' + 'E:\\ProgramFiles\\ffmpeg' + ' && ' command = 'E:\\FFmpeg\\bin\\ffmpeg.exe -i ' + '"' + audioDir + '"' ' -i ' + '"' + videoDir + '"'+ ' -acodec copy -vcodec copy ' + '"' + outDir + '"' # print("保存地址",outDir) # 保存地址 E:\B站導出視頻\[Lynda視頻]音頻錄製錄音技巧教程(中英雙語字幕)全集130課時AudioRecordingTechniques混音錄音棚音樂工做室歌曲調音\98錄製獨奏薩克斯演奏技巧二.mp4 # print(command) # command = 'E:\\FFmpeg\\bin\\ffmpeg.exe -i "E:\嗶哩嗶哩視頻\ss27993\77413703\1\audio1.mp4" -i "E:\嗶哩嗶哩視頻\ss27993\77413703\1\video.mp4" -acodec copy -vcodec copy "E:\B站導出視頻\Dr.STONE石紀元\第22話寶物.mp4' # os.system(command) popen(command) # ffmpeg.exe -i audio1.mp4 -i video.mp4 -acodec copy -vcodec copy output.mp4 break
這一部分是我用來實現合併特定路徑的視頻和音頻,並最終導出到指定目錄的。
videoPos是待合併的視頻的路徑,音頻文件也在這一目錄下。
for p in partDirs:
,在這個for循環中遍歷的是partDirs列表,這個列表由前面的步驟獲得,其中的每一個元素都是一個路徑。
接下來的這個if-else是用來區別番劇和普通視頻合集的,由於它們有不一樣的目錄結構。
infoPos用來記錄包含單集視頻名稱的info文件的路徑,而後用open函數打開這個info文件,根據AVhao中是否有ss,判斷是不是番劇,若是有ss,則代表是番劇,對應的視頻名稱爲Description鍵對應的值。不然的話,對應的視頻名稱爲PartName鍵對應的值,可是若是是要保存爲文件的話,固然不能直接以這個名稱命名,不然極有可能發生錯誤,所以我用了一個get_correct_title函數來對標題進行重載,以確保格式正確。
videoDir和audioDir分別是待合併的視頻和音頻的絕對路徑。
而後我根據AVhao中是否有ss,來判斷是不是番劇。由於番劇的單集視頻名稱一般會有序號,因此我能夠將輸出視頻的保存路徑設置爲保存目錄加視頻名.mp4。
許多up主的視頻名稱沒有體現視頻的前後順序,這樣帶來的問題是導出後順序播放時產生跳集現象。所以我用seq加下劃線的方式來爲視頻排序。好比「1_簡介.mp4」。
而對於視頻名稱自己有排序的狀況1_1簡介.mp4,這樣有些奇怪,因此咱們碰到這種狀況時,能夠直接在下面添加一個outDir = savePos + "\\" + videoTitle + '.mp4'
,其餘狀況不用時註釋掉就好。
最後是用Python程序執行Dos命令,我將命令設爲command變量,經過for循環,會自動生成不一樣的命令,而後執行命令行有兩種方法,一種是導入os庫,使用os.system(command),另外一種是我在https://blog.csdn.net/qq_41451161/article/details/82901235
借用的popen函數,這個也能夠用,但須要導入subprocess庫。 使用命令行上,有兩個比較坑的地方,一是前面必須給出ffmpeg.exe的絕對路徑,也就是E:\FFmpeg\bin\ffmpeg.exe,在Python中用是這樣的,但直接在命令行中,只要輸入ffmpeg.exe便可(前提是你設置好了環境變量)。第二個坑是,command賦值時,必定給路徑加上引號,不然的話識別命令時會發生錯誤。