You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Python中基于帧时间戳写入可变帧率视频?

解决Python中用自定义时间戳生成可变帧率(VFR)视频的问题

我之前也踩过这个坑!OpenCV和scikit-video确实不太支持直接写入带自定义时间戳的可变帧率视频——它们默认都是按你初始化时设置的固定帧率来计算每帧的显示时间,完全忽略你自己的时间戳数据。不过有两个非常靠谱的方案可以实现你的需求,都是基于FFmpeg生态的(毕竟FFmpeg才是处理视频格式的天花板):

方案一:用FFmpeg命令行 + 临时帧文件

这个方法简单直接,不需要额外的Python库(除了OpenCV用来存帧),适合快速实现:

步骤说明

  1. 把你的每一帧和对应的时间戳保存成两个部分:
    • 按顺序命名的帧图片(比如frame_000001.pngframe_000002.png
    • 一个时间戳文本文件,每行对应一帧的时间戳(转成微秒,FFmpeg默认用这个单位)
  2. 调用FFmpeg命令,通过-timestamps_file参数指定时间戳文件,让FFmpeg按照我们的时间戳来生成VFR视频。

代码示例

import cv2
import subprocess
import os

# 假设你已经有了frames列表,每个元素是(OpenCV格式的帧, 相对时间戳(秒))
frames = [...]  # 替换成你的实际帧数据

# 创建临时文件夹存放帧和时间戳文件
temp_dir = "temp_vfr_frames"
os.makedirs(temp_dir, exist_ok=True)
timestamp_path = os.path.join(temp_dir, "timestamps.txt")

# 保存帧和时间戳
with open(timestamp_path, "w", encoding="utf-8") as ts_file:
    for idx, (frame, ts_sec) in enumerate(frames):
        # 保存帧为PNG(画质无损,避免压缩失真)
        frame_filename = os.path.join(temp_dir, f"frame_{idx:06d}.png")
        cv2.imwrite(frame_filename, frame)
        # 写入时间戳(转成微秒)
        ts_us = int(ts_sec * 1_000_000)
        ts_file.write(f"{ts_us}\n")

# 调用FFmpeg生成VFR视频
output_path = "vfr_output.mp4"
ffmpeg_cmd = [
    "ffmpeg",
    "-f", "image2",
    "-i", os.path.join(temp_dir, "frame_%06d.png"),  # 匹配帧文件名
    "-timestamps_file", timestamp_path,
    "-c:v", "libx264",  # H.264编码,兼容性好
    "-crf", "23",  # 画质参数,数值越小画质越好(18-28是常用范围)
    "-pix_fmt", "yuv420p",  # 兼容大部分播放器
    "-y",  # 覆盖已有输出文件
    output_path
]

# 执行命令
subprocess.run(ffmpeg_cmd, check=True, capture_output=True)

# 清理临时文件
for file in os.listdir(temp_dir):
    os.remove(os.path.join(temp_dir, file))
os.rmdir(temp_dir)

方案二:用PyAV(FFmpeg的Python绑定)

如果不想折腾命令行和临时文件,可以用PyAV——它是FFmpeg的官方Python包装,能直接在代码里处理帧和时间戳,集成度更高:

步骤说明

  1. 先安装PyAV:pip install av
  2. 创建视频容器和流,设置时间基为微秒(方便计算时间戳)
  3. 遍历帧,将每个帧的pts(显示时间戳)设置为我们的自定义时间戳,然后编码写入容器。

代码示例

import av
import numpy as np

# 假设frames是[(numpy格式的帧, 相对时间戳(秒)), ...]
frames = [...]  # 替换成你的实际帧数据

output_path = "vfr_output.mp4"

# 打开输出容器
with av.open(output_path, mode="w") as container:
    # 创建视频流,这里rate设为None表示可变帧率
    stream = container.add_stream("h264", rate=None)
    # 设置视频分辨率(从第一帧获取)
    first_frame = frames[0][0]
    stream.width = first_frame.shape[1]
    stream.height = first_frame.shape[0]
    stream.pix_fmt = "yuv420p"  # 兼容播放器
    stream.time_base = av.time_base.US  # 设置时间基为微秒

    for frame_np, ts_sec in frames:
        # 将numpy帧转换为AVFrame(注意OpenCV是BGR,PyAV默认用RGB,所以转格式)
        av_frame = av.VideoFrame.from_ndarray(frame_np, format="bgr24")
        # 设置显示时间戳:把秒转成微秒,作为pts值
        av_frame.pts = int(ts_sec * 1_000_000)
        # 编码帧并写入容器
        for packet in stream.encode(av_frame):
            container.mux(packet)
    
    # 刷新编码器,确保所有帧都写入
    for packet in stream.encode():
        container.mux(packet)

为什么OpenCV/scikit-video不行?

简单来说,这两个库的视频写入模块都是为**固定帧率(CFR)**设计的:

  • OpenCV的VideoWriter会根据你初始化时传入的fps参数,自动计算每帧的时间(比如fps=30的话,每帧间隔1/30秒),完全忽略你自己的时间戳。
  • scikit-video底层依赖的是OpenCV或者FFmpeg的简化接口,同样没有暴露设置自定义时间戳的API。

所以想做VFR视频,还是得靠FFmpeg生态的工具~

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

火山引擎 最新活动