效果展示:
FFmpeg 用来合成音频和视频
代码如下
import os
import pprint
import re
import requests
from bs4 import BeautifulSoup
from lxml import etree
import json
class bilibili:
def __init__(self,url):
self.head = {
## 此处设置防盗链:指明连接的请求来源于B站,合法
#https://www.bilibili.com/video/BV17w4m1e7PT/
'referer':
'https://www.bilibili.com/',
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0'
}
self.response = requests.get(url=url, headers=self.head)
def askURL(self,url):
self.url = url
response = requests.get(url=self.url, headers=self.head)
return response
def download(self):
VideoInfo = re.findall('<script>window.__playinfo__=(.*?)</script>', self.response.text)[0]
VideoName = re.findall('<title data-vue-meta="true">(.*?)</title>', self.response.text)[0].translate(str.maketrans('','',' !:/'))
#re.findall('<title>(.*?)</title>',
# pprint.pprint(json.loads(VideoInfo))
jsonData = json.loads(VideoInfo)
audioURL = jsonData['data']['dash']['audio'][0]['baseUrl']
#videoURL = jsonData['data']['dash']['video'][0]['baseUrl']
videoURL = jsonData['data']['dash']['video'][4]['baseUrl']
if not os.path.exists(rf"download_video/"):
os.mkdir(rf"download_video/")
if not os.path.exists(rf"download_video/{VideoName}"):
os.mkdir(rf"download_video/{VideoName}")
audioContent = self.askURL(audioURL).content
with open(f'download_video/{VideoName}/' + VideoName + '.mp3', mode='wb') as f:
f.write(audioContent)
print(f"正在下载视频:{VideoName}")
videoContent = self.askURL(videoURL).content
with open(f'download_video/{VideoName}/' + VideoName + '.mp4', mode='wb') as f:
print(f"正在下载音频:{VideoName}")
f.write(videoContent)
return VideoName
def ffpmeg(self,VideoName,Ffmpeg_path):
self.Ffmpeg_path = Ffmpeg_path
root = os.getcwd()
audioFile = rf'{root}\download_video\{VideoName}/' + VideoName + '.mp3'
videoFile = rf'{root}\download_video\{VideoName}/' + VideoName + '.mp4'
outfile_name = rf'{root}\download_video\{VideoName}/' + VideoName + 'synthesis.mp4'
cmd = f'{self.Ffmpeg_path} -i {audioFile} -i {videoFile} -acodec copy -vcodec copy {outfile_name}'
#
text = os.popen(cmd).read()
print(text)
print(f"视频下载路径为:{outfile_name}")
if __name__ == '__main__':
#视频地址
#url = 'https://www.bilibili.com/video/BV1Qy411H7Ne/'
url = input("请输入视频地址:")
#ffmpeg路径
Ffmpeg_path = r'F:\pycharm\project\ffmpeg\bin\ffmpeg'
if url.strip() == '':
pass
else:
b = bilibili(url)
b.askURL(url)
VideoName = b.download()
#合成音视频,需要自行下载ffpmeg
#b.ffpmeg(VideoName,Ffmpeg_path)
更新
import json
import os
import re
import time
import requests
import subprocess
from urllib.parse import quote
from typing import Optional
class BilibiliDownloader:
def __init__(self):
self.headers = {
'referer': 'https://www.bilibili.com/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
}
self.session = requests.Session()
self.session.headers.update(self.headers)
def sanitize_filename(self, filename: str) -> str:
"""清理非法文件名字符"""
return re.sub(r'[\\/:*?"<>|!]', '', filename).strip()
def get_video_info(self, url: str) -> tuple:
"""获取视频元数据"""
try:
response = self.session.get(url, timeout=10)
response.raise_for_status()
except requests.RequestException as e:
raise RuntimeError(f"请求失败: {str(e)}")
# 提取视频标题
title_match = re.search(r'<title>(.*?)</title>', response.text)
if not title_match:
raise ValueError("无法提取视频标题")
video_title = self.sanitize_filename(title_match.group(1))
# 提取视频信息
play_info_match = re.search(r'<script>window.__playinfo__=(.*?)</script>', response.text)
if not play_info_match:
raise ValueError("无法提取视频播放信息")
try:
play_info = json.loads(play_info_match.group(1))
audio_url = play_info['data']['dash']['audio'][0]['baseUrl']
video_url = play_info['data']['dash']['video'][0]['baseUrl']
except (KeyError, IndexError, json.JSONDecodeError) as e:
raise ValueError(f"解析视频信息失败: {str(e)}")
return video_title, audio_url, video_url
def download_file(self, url: str, filepath: str, chunk_size: int = 8192) -> None:
"""通用下载方法(流式下载)"""
try:
with self.session.get(url, stream=True, timeout=30) as response:
response.raise_for_status()
total_size = int(response.headers.get('content-length', 0))
with open(filepath, 'wb') as f:
downloaded = 0
for chunk in response.iter_content(chunk_size=chunk_size):
if chunk:
f.write(chunk)
downloaded += len(chunk)
print(f"\r下载进度: {downloaded}/{total_size} bytes", end='')
print() # 换行
except Exception as e:
if os.path.exists(filepath):
os.remove(filepath)
raise RuntimeError(f"下载失败: {str(e)}")
def process_video(self, url: str, output_dir: str = "download_video") -> Optional[str]:
"""主处理流程"""
try:
# 获取视频信息
video_title, audio_url, video_url = self.get_video_info(url)
# 创建输出目录
output_path = os.path.join(output_dir, video_title)
os.makedirs(output_path, exist_ok=True)
# 下载文件
print(f"开始下载视频: {video_title}")
audio_path = os.path.join(output_path, f"{video_title}.mp3")
video_path = os.path.join(output_path, f"{video_title}.mp4")
self.download_file(audio_url, audio_path)
self.download_file(video_url, video_path)
return output_path
except Exception as e:
print(f"\n处理失败: {str(e)}")
return None
@staticmethod
def merge_media(output_path: str, ffmpeg_path: str = "ffmpeg") -> bool:
"""合并音视频"""
video_title = os.path.basename(output_path)
audio_file = os.path.join(output_path, f"{video_title}.mp3")
video_file = os.path.join(output_path, f"{video_title}.mp4")
output_file = os.path.join(output_path, f"{video_title}_merged.mp4")
if not os.path.exists(ffmpeg_path):
raise FileNotFoundError("未找到FFmpeg可执行文件")
cmd = [
ffmpeg_path,
'-y', # 覆盖已有文件
'-i', audio_file,
'-i', video_file,
'-c', 'copy',
output_file
]
try:
subprocess.run(
cmd,
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
# 清理临时文件
os.remove(audio_file)
os.remove(video_file)
return True
except subprocess.CalledProcessError as e:
print(f"合并失败: {str(e)}")
return False
if __name__ == '__main__':
downloader = BilibiliDownloader()
# 用户输入
video_url = input("请输入B站视频地址(留空退出): ").strip()
if not video_url:
exit()
# FFmpeg路径配置(默认在环境变量中)
ffmpeg_path = r'D:\pycharm\demo\pythonProject1\study\video\download_video\ffmpeg\bin\ffmpeg.exe' # 可配置为环境变量
try:
# 下载处理
output_path = downloader.process_video(video_url)
if not output_path:
exit()
# 合并文件
print("开始合并音视频...")
if downloader.merge_media(output_path, ffmpeg_path):
print("处理完成!最终文件:", os.path.join(output_path, f"{os.path.basename(output_path)}_merged.mp4"))
except KeyboardInterrupt:
print("\n用户中断操作")
except Exception as e:
print(f"发生未知错误: {str(e)}")