Django Rest上传视频时实时转码为H264格式实现方案咨询
实现实时流式转码上传的方案
1. 绕过Django Rest默认的文件接收机制
Django Rest默认的MultipartParser会把上传文件完整写入临时文件后再交给视图处理,这不符合实时转码需求。需要自定义流式解析器,直接从请求数据流中读取视频片段:
from rest_framework.parsers import MultipartParser from rest_framework.exceptions import ParseError import io from django.http.multipartparser import MultiPartParser as DjangoMultiPartParser from django.conf import settings class StreamingMultipartParser(MultipartParser): def parse(self, stream, media_type=None, parser_context=None): parser_context = parser_context or {} request = parser_context.get('request') encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET) boundary = self.get_boundary(media_type, request) if not boundary: raise ParseError("Missing boundary in multipart request") # 自定义上传处理器,仅流式读取数据不生成临时文件 class StreamingUploadHandler: def __init__(self): self.stream = io.BytesIO() self.file_name = None self.content_type = None def receive_data_chunk(self, raw_data, start): self.stream.write(raw_data) def file_complete(self, file_size): self.stream.seek(0) return self request.upload_handlers = [StreamingUploadHandler()] try: parser = DjangoMultiPartParser(request.META, stream, boundary, encoding) data, files = parser.parse() except Exception as e: raise ParseError(f"Failed to parse multipart request: {str(e)}") video_file = next(iter(files.values()), None) if not video_file: raise ParseError("No video file found in request") return {'data': data, 'video_stream': video_file.stream, 'file_name': video_file.name}
2. 用FFmpeg管道实现实时转码
在视图中启动FFmpeg子进程,将流式读取的视频数据直接传入FFmpeg标准输入,同时将转码后的输出写入目标文件:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status import subprocess import os from django.conf import settings class VideoUploadView(APIView): parser_classes = [StreamingMultipartParser] def post(self, request, *args, **kwargs): video_stream = request.data.get('video_stream') original_name = request.data.get('file_name') if not video_stream: return Response({"error": "No video stream provided"}, status=status.HTTP_400_BAD_REQUEST) # 生成H264编码的输出文件名 output_filename = f"{os.path.splitext(original_name)[0]}_h264.mp4" output_path = os.path.join(settings.MEDIA_ROOT, output_filename) # FFmpeg核心命令:从标准输入读取,转码为H264格式MP4 ffmpeg_cmd = [ 'ffmpeg', '-i', 'pipe:0', # 从标准输入读取原始视频流 '-c:v', 'libx264', # 指定视频编码器为H264 '-crf', '23', # 质量控制参数(值越小质量越高,18-28为常用范围) '-preset', 'medium', # 编码速度与质量平衡(fast/medium/slow) '-f', 'mp4', # 输出格式为MP4 '-movflags', '+faststart', # 优化MP4的Web播放兼容性 '-y', # 覆盖已存在的输出文件 output_path ] try: # 启动FFmpeg子进程,绑定标准输入/错误流 process = subprocess.Popen( ffmpeg_cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL ) # 分块读取上传流并写入FFmpeg进程 chunk_size = 1024 * 1024 # 按1MB块读取,可根据服务器性能调整 while True: chunk = video_stream.read(chunk_size) if not chunk: break process.stdin.write(chunk) process.stdin.flush() # 关闭输入流,等待转码完成 process.stdin.close() process.wait() # 检查转码是否成功 if process.returncode != 0: stderr = process.stderr.read().decode('utf-8') if os.path.exists(output_path): os.remove(output_path) return Response({"error": f"Transcoding failed: {stderr}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response({ "status": "success", "output_file": output_filename, "file_path": output_path }, status=status.HTTP_201_CREATED) except Exception as e: # 异常时清理进程与未完成文件 if 'process' in locals() and process.poll() is None: process.kill() if os.path.exists(output_path): os.remove(output_path) return Response({"error": f"Upload failed: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
3. 关键注意事项
- 资源占用优化:转码是CPU密集型操作,高并发场景下建议将转码逻辑放到Celery异步队列中,避免阻塞Web服务器。
- 错误处理:需处理上传中断、FFmpeg崩溃等异常,确保及时清理进程和未完成的文件。
- 参数调整:根据业务需求调整
-crf(质量)、-preset(速度)参数,若需要保留音频,添加-c:a aac参数指定音频编码器。 - 格式校验:可在读取初始流片段后,用FFmpeg的
-probesize参数检测输入格式,避免无效文件进入转码流程。
内容的提问来源于stack exchange,提问作者Illucious
相关产品推荐
相关产品推荐

