You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Content-Range请求字节拼接为可播放MP4?代码问题排查

问题分析与修复方案

你的实现存在几个关键问题,直接导致MP4文件拼接后无法播放:

1. 临时文件排序逻辑错误

当前代码通过字符串排序临时文件名,比如temporary_file_1000_1999会排在temporary_file_0_999前面(字符串比较中"1" > "0"),导致文件片段拼接顺序完全错乱。MP4是结构严格的二进制文件,顺序错误会直接破坏其解析结构。

2. 临时文件写入模式错误

save_to_temporary_file里用了ab追加模式,如果同一个片段因网络重试重复上传,会导致该片段数据被多次写入,最终拼接后的文件数据冗余,破坏MP4格式。

3. 缺少基础错误校验

比如parse_content_range返回None时未处理、uploaded_file为空的情况未校验,这些都会引发后续逻辑异常。


修正后的代码

核心修改点:

  • 按片段起始字节的数值排序临时文件,确保拼接顺序正确
  • 临时文件改用wb模式写入,避免重复数据
  • 添加必要的参数校验,拦截非法请求
@app.route('/admin/upload', methods=['POST'])
@app_key
def admin_dashboard_upload_endpoint():
    # Headers
    file_name = request.headers.get('Filename')
    title = request.headers.get('Title')
    path = f'something/path'

    # 校验必要参数
    if not file_name:
        return "Missing Filename header", 400

    # Get the Content-Range header
    content_range = request.headers.get('Content-Range')
    if not content_range:
        return "Missing Content-Range header", 400

    # Parse the Content-Range header to get start and end bytes
    start_byte, end_byte, total_size = parse_content_range(content_range)
    if start_byte is None or end_byte is None or total_size is None:
        return "Invalid Content-Range format", 400

    # Get the uploaded file from the request
    uploaded_file = request.files.get('File')
    if not uploaded_file:
        return "Missing File part", 400

    # Save the received bytes to a temporary file
    os.makedirs('uploads', exist_ok=True)
    save_to_temporary_file(uploaded_file, start_byte, end_byte)

    # Check if all parts have been received
    if end_byte == total_size - 1:
        # If all parts are received, combine the temporary files into one
        combine_temporary_files(file_name)

        with open(f'uploads/{file_name}', 'rb') as combined_file:
            return upload_to_storage_dashboard(combined_file, title=title, path=path, file_remove=f'uploads/{file_name}')

    return "OK", 206

def parse_content_range(content_range):
    # Example of content_range: 'bytes 0-999/2000'
    match = re.match(r'bytes (\d+)-(\d+)/(\d+)', content_range)
    if match:
        start_byte, end_byte, total_size = map(int, match.groups())
        return start_byte, end_byte, total_size
    else:
        # Handle invalid Content-Range header
        return None, None, None

def save_to_temporary_file(uploaded_file, start_byte, end_byte):
    temp_file_path = f'uploads/temporary_file_{start_byte}_{end_byte}.mp4'
    # 用wb模式覆盖写入,确保每个片段数据唯一
    with open(temp_file_path, 'wb') as temp_file:
        temp_file.write(uploaded_file.read())

def combine_temporary_files(file_name):
    combined_file_path = f'uploads/{file_name}'
    # 从文件名中解析start_byte,按数值排序
    temp_files = []
    for f in os.listdir('uploads'):
        if f.startswith('temporary_file_'):
            # 提取start_byte数值
            parts = f.split('_')
            if len(parts) >=3:
                try:
                    start_byte = int(parts[2])
                    temp_files.append((start_byte, f))
                except ValueError:
                    continue
    # 按start_byte从小到大排序,确保拼接顺序正确
    temp_files.sort(key=lambda x: x[0])

    with open(combined_file_path, 'wb') as combined_file:
        for _, temp_file_name in temp_files:
            temp_file_path = os.path.join('uploads', temp_file_name)
            with open(temp_file_path, 'rb') as temp_file:
                combined_file.write(temp_file.read())
            os.remove(temp_file_path)

额外检查项

如果修正后仍无法播放,需确认Flutter端上传逻辑:

  • 确保每个片段的Content-Range与实际发送的字节范围完全匹配(比如发送0-999字节,对应原文件的前1000字节)
  • 确认没有重复发送或遗漏片段
  • 检查上传过程中是否存在数据截断(发送的字节数与声明的范围不一致)

内容的提问来源于stack exchange,提问作者Jiehfeng

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 17:22:55