B站緩存視頻數據m4s轉mp4
- 結構分析
結構分析
在沒有改變數據存儲目錄的情況下,b站默認數據保存目錄為:
`Android->data->tv.danmaku.bili->download`
每個文件夾代表一個集合的視頻,比如,我下載的”java從入門到精通“,那么就會保存到一個目錄里面:
每個以c_開頭的都是一個小章節。
每個小章節包含entry.json(視頻標題及章節名稱等信息),danmaku.xml(彈幕),64或80等(視頻文件及音頻文件等)
將audio.m4s以及video.m4s合成后就是一個完整的視頻。本地得安裝ffmpeg或者使用python-ffmpeg。
關鍵部分代碼:
command = ["ffmpeg","-i", video_m4s, # 輸入視頻文件"-i", audio_m4s, # 輸入音頻文件'-c:v', 'copy', # 不重新編碼視頻'-c:a', 'copy', # 不重新編碼音頻"-y", # 覆蓋已存在的文件output_mp4 # 輸出文件]# 使用 subprocess.run 執行命令result = subprocess.run(command)
將整個標題的視頻轉換出來。
#!/usr/bin/python3
from tkinter import *
from tkinter import messagebox
from tkinter import filedialog
import subprocess
import os
import json
import ffmpegdef select_source():# 選擇視頻源目錄dir_path = filedialog.askdirectory(title="選擇視頻源目錄")if dir_path:source_entry.delete(0, END)source_entry.insert(0, dir_path)def select_target():# 選擇保存目錄dir_path = filedialog.askdirectory(title="選擇視頻保存目錄")if dir_path:target_entry.delete(0, END)target_entry.insert(0, dir_path)def convert_with_ffmpeg(video_m4s, audio_m4s, output_mp4):if os.path.getsize(video_m4s) < 0 or os.path.getsize(audio_m4s) <= 0:print("非法的音視頻文件,{audio_m4s},視頻文件:{video_m4s}")return Falsetry:# 輸入視頻和音頻video = ffmpeg.input(video_m4s)audio = ffmpeg.input(audio_m4s)# 構造 FFmpeg 命令command = ["ffmpeg","-i", video_m4s, # 輸入視頻文件"-i", audio_m4s, # 輸入音頻文件'-c:v', 'copy', # 不重新編碼視頻'-c:a', 'copy', # 不重新編碼音頻"-y", # 覆蓋已存在的文件output_mp4 # 輸出文件]# 使用 subprocess.run 執行命令result = subprocess.run(command)# 檢查命令是否成功執行if result.returncode == 0:print(f"{output_mp4} -> 合并成功!")return Trueelse:print(f"合并失敗, 視頻文件:{video_m4s}, 音頻文件:{audio_m4s}")print(f"錯誤信息: {result.stderr}")return Falseexcept ffmpeg.Error as e:print(f"合并失敗,音頻文件:{audio_m4s},視頻文件:{video_m4s}")return Falsedef batch_convert_vedio(source_path, target_path):for dir in os.listdir(source_path):dir_path = os.path.join(source_path, dir)entry_file = os.path.join(dir_path, "entry.json")data_dir = os.listdir(dir_path).pop(0)audio_path = os.path.join(dir_path, data_dir, "audio.m4s")vedio_path = os.path.join(dir_path, data_dir, "video.m4s")with open(entry_file, 'r', encoding='utf-8') as f:entry_sjon = f.read()json_data = json.loads(entry_sjon)part_content = json_data.get("page_data", {}).get("part")title = json_data.get("title")if not os.path.exists(os.path.join(target_path, f'{title}')):os.makedirs(os.path.join(target_path, f'{title}'))target_vedio_path = os.path.join(target_path, f'{title}/{part_content}.mp4')convert_with_ffmpeg(vedio_path, audio_path, target_vedio_path)print(target_vedio_path)def convert_video():source_path = source_entry.get()target_path = target_entry.get()if not source_path or not target_path:messagebox.showerror("錯誤", "請選擇源目錄和目標目錄!")else:batch_convert_vedio(source_path, target_path)messagebox.showinfo("成功", f"視頻將從 {source_path} 轉換保存到 {target_path}")root = Tk()
root.title("bili視頻轉換工具")# 設置窗口大小并居中
window_width = 500 # 稍微加寬窗口
window_height = 200
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
x = (screen_width - window_width) // 2
y = (screen_height - window_height) // 2
root.geometry(f"{window_width}x{window_height}+{x}+{y}")# 配置網格布局權重
for i in range(5):root.grid_columnconfigure(i, weight=1)
for i in range(4):root.grid_rowconfigure(i, weight=1)# 視頻源目錄選擇
source_label = Label(root, text="視頻源目錄:")
source_label.grid(row=0, column=0, sticky="e", padx=5, pady=5)source_entry = Entry(root)
source_entry.grid(row=0, column=1, columnspan=3, sticky="ew", padx=5, pady=5)source_button = Button(root, text="瀏覽...", command=select_source)
source_button.grid(row=0, column=4, sticky="ew", padx=5, pady=5)# 視頻保存目錄選擇
target_label = Label(root, text="視頻保存目錄:")
target_label.grid(row=1, column=0, sticky="e", padx=5, pady=5)target_entry = Entry(root)
target_entry.grid(row=1, column=1, columnspan=3, sticky="ew", padx=5, pady=5)target_button = Button(root, text="瀏覽...", command=select_target)
target_button.grid(row=1, column=4, sticky="ew", padx=5, pady=5)# 轉換按鈕
button_convert = Button(root, text="立即轉換", command=convert_video)
button_convert.grid(row=2, column=1, columnspan=3, sticky="ew", padx=50, pady=20)# 進入消息循環
root.mainloop()
具體效果如下: