如何在Django项目中将用户上传的视频转换并保存为M3U8格式?
如何在Django项目中将用户上传的视频转换并保存为M3U8格式?
嘿,我来帮你搞定这个视频转M3U8的需求!要实现这个功能,我们主要依赖FFmpeg(处理视频转换的业界标准工具),再结合Django的文件处理机制来完成。下面是一步步的实现方案,完全适配你现有的代码结构:
1. 先搞定依赖安装
首先得确保你的环境里有FFmpeg:
- 如果你用的是Ubuntu/Debian,直接在终端跑
sudo apt-get install ffmpeg - Windows的话,去FFmpeg官网下载安装包,记得把它的bin目录加到系统环境变量里
- macOS可以用Homebrew:
brew install ffmpeg
另外,我们可以用Python的ffmpeg-python库来简化调用(当然直接用subprocess调用FFmpeg命令也没问题),跑这个命令安装:
pip install ffmpeg-python
2. 修改你的视频模型(models.py)
我们需要给Video模型加个字段来保存转换后的M3U8文件,还要在保存视频时自动触发转换逻辑。修改后的代码如下:
from django.db import models from django.core.validators import FileExtensionValidator, MinValueValidator, MaxValueValidator from django.core.files.storage import default_storage from django.core.files.base import ContentFile import os import shutil from .utils import convert_to_m3u8 # 后面要写的转换工具函数 class Video(AdvertisementModelMixin): text = models.CharField(max_length=255) description = models.TextField() image = WEBPField( verbose_name='Image', upload_to=video_folder, ) video = models.FileField( upload_to='video/', validators=[FileExtensionValidator( allowed_extensions=['MOV', 'avi', 'mp4', 'webm', 'mkv'] # 这里把你原来的'm'修正了,应该是笔误吧? )] ) # 新增字段:保存转换后的M3U8文件 m3u8_file = models.FileField(upload_to='video/m3u8/', blank=True, null=True) skip_duration = models.PositiveSmallIntegerField( null=True, blank=True, validators=[ MinValueValidator(3), MaxValueValidator(20) ] ) def __str__(self): return f"{self.id}. {self.text}" def save(self, *args, **kwargs): # 只有当新上传了视频且还没生成M3U8时,才触发转换 if self.video and not self.m3u8_file: # 调用转换函数生成M3U8和TS分片 m3u8_temp_path = convert_to_m3u8(self.video.path) if m3u8_temp_path: # 处理M3U8文件上传到媒体存储 m3u8_filename = os.path.splitext(os.path.basename(self.video.name))[0] + '.m3u8' with open(m3u8_temp_path, 'rb') as f: self.m3u8_file.save(m3u8_filename, ContentFile(f.read()), save=False) # 上传所有TS分片文件(M3U8依赖这些分片才能播放) temp_dir = os.path.dirname(m3u8_temp_path) for ts_file in os.listdir(temp_dir): if ts_file.endswith('.ts'): ts_path = os.path.join(temp_dir, ts_file) with open(ts_path, 'rb') as f: default_storage.save(os.path.join('video/m3u8/', ts_file), ContentFile(f.read())) # 清理临时文件,避免占用空间 shutil.rmtree(temp_dir) # 调用父类的save方法完成最终保存 super().save(*args, **kwargs) class Meta: verbose_name = "video"
3. 写视频转换的工具函数
在你的项目里新建一个utils.py文件,放这个转换函数:
import subprocess import os import tempfile def convert_to_m3u8(input_video_path): # 创建临时目录存放转换后的文件,避免污染项目目录 with tempfile.TemporaryDirectory() as temp_dir: output_filename = os.path.splitext(os.path.basename(input_video_path))[0] + '.m3u8' output_m3u8_path = os.path.join(temp_dir, output_filename) # FFmpeg核心命令:把原视频转成HLS格式(M3U8),每个分片10秒,用H.264编码视频、AAC编码音频 cmd = [ 'ffmpeg', '-i', input_video_path, '-c:v', 'libx264', # 视频编码用H.264,兼容性最好 '-c:a', 'aac', # 音频编码用AAC '-hls_time', '10', # 每个TS分片的时长(秒) '-hls_list_size', '0', # 保留所有分片,不自动删除旧的 '-hls_segment_filename', os.path.join(temp_dir, 'segment_%03d.ts'), # 分片文件名格式 output_m3u8_path ] try: # 执行转换命令,捕获输出用于调试 result = subprocess.run(cmd, check=True, capture_output=True, text=True) print(f"视频转换成功:{result.stdout}") return output_m3u8_path except subprocess.CalledProcessError as e: print(f"视频转换失败:{e.stderr}") return None
4. 更新序列化器和视图
在你的VideoSerializer里加上m3u8_file字段,这样前端就能拿到M3U8的访问URL了:
# serializers.py from rest_framework import serializers from .models import Video class VideoSerializer(serializers.ModelSerializer): class Meta: model = Video fields = ['id', 'text', 'description', 'image', 'video', 'm3u8_file', 'skip_duration']
你的VideoViewSet不用改太多,只要确保序列化器用的是更新后的VideoSerializer就行,现在它会返回M3U8文件的链接了。
5. 重要注意事项
- 异步处理:视频转换是很耗时的操作,如果用户上传大视频,同步转换会导致请求超时。建议用Celery把转换逻辑改成异步任务,当视频上传完成后,触发Celery任务去转码,转完再更新
m3u8_file字段。 - 媒体存储配置:如果你的项目用云存储(比如AWS S3、阿里云OSS),要确保
default_storage配置正确,这样分片文件和M3U8才能正常上传到云端。 - 权限和安全:要限制上传视频的大小,避免用户上传超大文件拖垮服务器;另外,M3U8和TS文件的访问权限也要做好控制,比如只允许登录用户访问。
- 测试:先上传几个小视频测试转换逻辑,看看M3U8能不能正常播放,有问题的话看终端输出的FFmpeg错误信息排查。
备注:内容来源于stack exchange,提问作者rowsen




