- Published on
m3u8转mp4的几种方法
- Authors
- Name
- Adain
m3u8转mp4的几种方法
m3u8是一种常见的流媒体播放列表格式,通常用于HLS(HTTP Live Streaming)视频传输。在某些情况下,我们需要将m3u8文件转换为mp4格式以便本地保存或进一步处理。本文将介绍几种实用的转换方法。
什么是m3u8?
m3u8是一种基于文本的播放列表文件格式,它包含了一系列媒体文件的URL列表。HLS协议将视频分割成多个小片段(通常是.ts文件),然后通过m3u8文件来组织这些片段的播放顺序。
典型的m3u8文件内容:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXTINF:10.0,
segment1.ts
#EXTINF:10.0,
segment2.ts
#EXTINF:10.0,
segment3.ts
#EXT-X-ENDLIST
方法一:使用FFmpeg(推荐)
FFmpeg是最强大和最可靠的视频处理工具,支持几乎所有视频格式的转换。
安装FFmpeg
Windows:
# 使用Chocolatey
choco install ffmpeg
# 或下载预编译版本
# https://ffmpeg.org/download.html
macOS:
# 使用Homebrew
brew install ffmpeg
Linux (Ubuntu/Debian):
sudo apt update
sudo apt install ffmpeg
基本转换命令
# 简单转换
ffmpeg -i "https://example.com/playlist.m3u8" -c copy output.mp4
# 指定编解码器
ffmpeg -i "https://example.com/playlist.m3u8" -c:v libx264 -c:a aac output.mp4
# 重新编码并设置质量
ffmpeg -i "https://example.com/playlist.m3u8" -c:v libx264 -crf 23 -c:a aac -b:a 128k output.mp4
高级参数说明
# 完整的转换命令示例
ffmpeg -i "playlist.m3u8" \
-c:v libx264 \ # 视频编码器
-c:a aac \ # 音频编码器
-crf 23 \ # 视频质量 (18-28, 越小质量越好)
-preset medium \ # 编码速度预设
-movflags +faststart \ # 优化网络播放
-y \ # 覆盖输出文件
output.mp4
批量转换脚本
Bash脚本 (Linux/macOS):
#!/bin/bash
# batch_convert.sh
for file in *.m3u8; do
if [ -f "$file" ]; then
output="${file%.m3u8}.mp4"
echo "转换 $file 到 $output"
ffmpeg -i "$file" -c copy "$output"
fi
done
批处理脚本 (Windows):
@echo off
for %%f in (*.m3u8) do (
echo 转换 %%f
ffmpeg -i "%%f" -c copy "%%~nf.mp4"
)
pause
方法二:使用Python脚本
对于需要更多控制或批量处理的场景,可以使用Python编写自定义脚本。
安装依赖
pip install requests m3u8 ffmpeg-python
基础转换脚本
import m3u8
import requests
import os
import subprocess
from urllib.parse import urljoin, urlparse
def download_m3u8_to_mp4(m3u8_url, output_file):
"""
下载m3u8流并转换为mp4
"""
try:
# 解析m3u8文件
playlist = m3u8.load(m3u8_url)
if playlist.is_variant:
# 如果是多码率播放列表,选择最高质量
best_playlist = max(playlist.playlists,
key=lambda p: p.stream_info.bandwidth or 0)
m3u8_url = urljoin(m3u8_url, best_playlist.uri)
playlist = m3u8.load(m3u8_url)
# 获取基础URL
base_url = m3u8_url.rsplit('/', 1)[0] + '/'
# 创建临时目录
temp_dir = "temp_segments"
os.makedirs(temp_dir, exist_ok=True)
# 下载所有片段
segment_files = []
for i, segment in enumerate(playlist.segments):
segment_url = urljoin(base_url, segment.uri)
segment_file = f"{temp_dir}/segment_{i:04d}.ts"
print(f"下载片段 {i+1}/{len(playlist.segments)}")
response = requests.get(segment_url, stream=True)
response.raise_for_status()
with open(segment_file, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
segment_files.append(segment_file)
# 使用FFmpeg合并片段
concat_file = f"{temp_dir}/concat_list.txt"
with open(concat_file, 'w') as f:
for segment_file in segment_files:
f.write(f"file '{os.path.abspath(segment_file)}'\n")
# FFmpeg命令
cmd = [
'ffmpeg',
'-f', 'concat',
'-safe', '0',
'-i', concat_file,
'-c', 'copy',
'-y',
output_file
]
print("合并视频片段...")
subprocess.run(cmd, check=True)
# 清理临时文件
for file in segment_files:
os.remove(file)
os.remove(concat_file)
os.rmdir(temp_dir)
print(f"转换完成: {output_file}")
except Exception as e:
print(f"转换失败: {str(e)}")
# 使用示例
if __name__ == "__main__":
m3u8_url = "https://example.com/playlist.m3u8"
output_file = "output_video.mp4"
download_m3u8_to_mp4(m3u8_url, output_file)
增强版Python脚本
import m3u8
import requests
import os
import subprocess
import argparse
from concurrent.futures import ThreadPoolExecutor, as_completed
from urllib.parse import urljoin
import time
class m3u8Downloader:
def __init__(self, max_workers=5, timeout=30):
self.max_workers = max_workers
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
def download_segment(self, segment_info):
"""下载单个视频片段"""
url, filepath, index, total = segment_info
for attempt in range(3): # 重试3次
try:
response = self.session.get(url, timeout=self.timeout)
response.raise_for_status()
with open(filepath, 'wb') as f:
f.write(response.content)
print(f"\r下载进度: {index+1}/{total} ({(index+1)/total*100:.1f}%)", end='', flush=True)
return True
except Exception as e:
if attempt == 2: # 最后一次尝试
print(f"\n下载失败 {filepath}: {str(e)}")
return False
time.sleep(1) # 重试前等待
def convert_m3u8_to_mp4(self, m3u8_url, output_file, quality='best'):
"""
转换m3u8到mp4
quality: 'best', 'worst', 或指定带宽
"""
try:
print(f"解析m3u8: {m3u8_url}")
playlist = m3u8.load(m3u8_url)
# 处理多码率播放列表
if playlist.is_variant:
if quality == 'best':
selected = max(playlist.playlists,
key=lambda p: p.stream_info.bandwidth or 0)
elif quality == 'worst':
selected = min(playlist.playlists,
key=lambda p: p.stream_info.bandwidth or float('inf'))
else:
# 按带宽选择
target_bandwidth = int(quality)
selected = min(playlist.playlists,
key=lambda p: abs((p.stream_info.bandwidth or 0) - target_bandwidth))
print(f"选择码率: {selected.stream_info.bandwidth} bps")
m3u8_url = urljoin(m3u8_url, selected.uri)
playlist = m3u8.load(m3u8_url)
# 准备下载
base_url = m3u8_url.rsplit('/', 1)[0] + '/'
temp_dir = f"temp_{int(time.time())}"
os.makedirs(temp_dir, exist_ok=True)
# 准备片段下载任务
download_tasks = []
for i, segment in enumerate(playlist.segments):
segment_url = urljoin(base_url, segment.uri)
segment_file = f"{temp_dir}/segment_{i:04d}.ts"
download_tasks.append((segment_url, segment_file, i, len(playlist.segments)))
# 并发下载片段
print(f"\n开始下载 {len(download_tasks)} 个片段...")
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
futures = [executor.submit(self.download_segment, task) for task in download_tasks]
success_count = 0
for future in as_completed(futures):
if future.result():
success_count += 1
print(f"\n下载完成: {success_count}/{len(download_tasks)} 个片段")
if success_count == 0:
raise Exception("没有成功下载任何片段")
# 合并片段
print("合并视频片段...")
self.merge_segments(temp_dir, output_file)
# 清理临时文件
import shutil
shutil.rmtree(temp_dir)
print(f"转换完成: {output_file}")
except Exception as e:
print(f"转换失败: {str(e)}")
raise
def merge_segments(self, temp_dir, output_file):
"""使用FFmpeg合并视频片段"""
# 创建文件列表
concat_file = f"{temp_dir}/concat_list.txt"
segment_files = sorted([f for f in os.listdir(temp_dir) if f.endswith('.ts')])
with open(concat_file, 'w', encoding='utf-8') as f:
for segment_file in segment_files:
f.write(f"file '{os.path.abspath(os.path.join(temp_dir, segment_file))}'\n")
# FFmpeg命令
cmd = [
'ffmpeg',
'-f', 'concat',
'-safe', '0',
'-i', concat_file,
'-c', 'copy',
'-movflags', '+faststart',
'-y',
output_file
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise Exception(f"FFmpeg错误: {result.stderr}")
def main():
parser = argparse.ArgumentParser(description='m3u8转mp4工具')
parser.add_argument('m3u8_url', help='m3u8文件URL')
parser.add_argument('-o', '--output', default='output.mp4', help='输出文件名')
parser.add_argument('-q', '--quality', default='best',
help='视频质量: best, worst, 或指定带宽')
parser.add_argument('-w', '--workers', type=int, default=5,
help='并发下载线程数')
args = parser.parse_args()
downloader = m3u8Downloader(max_workers=args.workers)
downloader.convert_m3u8_to_mp4(args.m3u8_url, args.output, args.quality)
if __name__ == "__main__":
main()
方法三:使用在线工具
对于不想安装软件的用户,可以使用在线转换工具:
推荐的在线工具
CloudConvert
- 支持m3u8到mp4转换
- 免费用户有使用限制
- 网址: https://cloudconvert.com
Online Video Converter
- 简单易用的界面
- 支持多种格式
- 网址: https://www.onlinevideoconverter.com
Convertio
- 支持大文件转换
- 提供API接口
- 网址: https://convertio.co
注意事项
- 在线工具的处理速度取决于网络和服务器负载
- 大文件可能有上传限制
- 注意隐私安全,避免上传敏感内容
方法四:使用专门的下载工具
you-get
# 安装
pip install you-get
# 使用
you-get -o /path/to/output "https://example.com/playlist.m3u8"
yt-dlp
# 安装
pip install yt-dlp
# 使用
yt-dlp -o "%(title)s.%(ext)s" "https://example.com/playlist.m3u8"
# 指定格式
yt-dlp -f "best[ext=mp4]" "https://example.com/playlist.m3u8"
N_m3u8DL-CLI
Windows专用的m3u8下载工具:
# 下载并解压工具
# https://github.com/nilaoda/N_m3u8DL-CLI
# 使用
N_m3u8DL-CLI.exe "https://example.com/playlist.m3u8" --workDir "C:\Downloads"
常见问题和解决方案
1. 网络超时问题
# FFmpeg增加网络缓冲
ffmpeg -i "playlist.m3u8" -timeout 30000000 -c copy output.mp4
# 使用代理
ffmpeg -http_proxy "http://proxy:port" -i "playlist.m3u8" -c copy output.mp4
2. 权限验证问题
某些m3u8需要特定的请求头:
# 添加User-Agent
ffmpeg -user_agent "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" -i "playlist.m3u8" -c copy output.mp4
# 添加Referer
ffmpeg -headers "Referer: https://example.com" -i "playlist.m3u8" -c copy output.mp4
3. 加密的m3u8流
对于AES加密的HLS流:
import m3u8
from Crypto.Cipher import AES
import requests
def decrypt_segment(encrypted_data, key, iv):
"""解密AES加密的片段"""
cipher = AES.new(key, AES.MODE_CBC, iv)
return cipher.decrypt(encrypted_data)
# 在下载脚本中处理加密
def download_encrypted_segment(segment, key_uri, base_url):
# 获取密钥
key_response = requests.get(urljoin(base_url, key_uri))
key = key_response.content
# 下载加密片段
segment_url = urljoin(base_url, segment.uri)
response = requests.get(segment_url)
# 解密
iv = bytes.fromhex(segment.key.iv.replace('0x', '')) if segment.key.iv else b'\x00' * 16
decrypted_data = decrypt_segment(response.content, key, iv)
return decrypted_data
4. 大文件处理
对于特别大的m3u8文件:
# 使用流复制模式,减少内存占用
ffmpeg -i "playlist.m3u8" -c copy -avoid_negative_ts make_zero output.mp4
# 分段处理
ffmpeg -i "playlist.m3u8" -ss 00:00:00 -t 01:00:00 -c copy part1.mp4
ffmpeg -i "playlist.m3u8" -ss 01:00:00 -t 01:00:00 -c copy part2.mp4
性能优化建议
1. 并发下载优化
# 调整并发数量
max_workers = min(32, (os.cpu_count() or 1) + 4)
# 使用连接池
session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
pool_connections=100,
pool_maxsize=100
)
session.mount('http://', adapter)
session.mount('https://', adapter)
2. 内存优化
# 流式下载大文件
def download_large_segment(url, filepath):
with requests.get(url, stream=True) as response:
response.raise_for_status()
with open(filepath, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
3. 错误处理和重试
import time
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_robust_session():
session = requests.Session()
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
总结
m3u8转mp4的方法各有优劣:
- FFmpeg: 功能最强大,支持最全面,推荐用于技术用户
- Python脚本: 灵活性最高,可定制性强,适合开发者
- 在线工具: 最简单易用,适合偶尔使用的用户
- 专门工具: 针对性强,通常有更好的用户体验
选择哪种方法取决于你的具体需求、技术水平和使用频率。对于大多数用户,我推荐先尝试FFmpeg的简单命令,如果需要更多控制,再考虑使用Python脚本或专门的下载工具。
记住要遵守相关的版权法律,只下载你有权限访问的内容。