视频抽帧算子 las_video_frame_extract 用于按指定频率从视频中均匀抽取图片帧,并将结果写入您指定的 TOS 路径。您可以利用该算子快速为视频理解、检测审核、封面生成和低频采样分析等场景准备结构化帧数据。
视频抽帧算子支持对输入视频按指定帧率进行抽帧,将抽取的图片帧上传至指定 TOS 存储路径。任务完成后,您可以获取视频元信息以及每一帧的访问地址。算子支持可配置的抽帧频率、最大帧数限制、输出图片格式和缩放策略,适用于视频理解、检测、审核、摘要、封面生成等场景。
http、https 和 tos:// 协议。jpg 和 png。output_path_template 参数设置输出路径,必须以 tos:// 开头,且必须包含 {index} 占位符。例如 tos://bucket/frames/frame_{index}.{autoext}。Tos.AccessFailed 错误。fps 的取值范围为 0.1 ~ 10.0。resize_short_side 与 resize_hw 互斥,不能同时设置。resize_hw 为长度固定为 2 的数组 [width, height]。当某一项为 null 时,表示按另一边等比缩放。max_frames 参数控制输出帧数上限,避免长视频产生过多帧。jpg 和 png 两种图片格式。resize_short_side)或指定目标分辨率(resize_hw),满足不同场景需求。max_frames 控制输出规模。计费标准
细分项 | 计费标准说明 |
|---|---|
计费项 | 对输入视频采样后的图片张数统计用量,并进行计费。 |
计费类型 | 按量计费,单位: |
单价 | 0.1 元/千张 |
计费详情
计费公式:总费用 = 单价 * 用量
细分项 | 注意与前提 |
|---|---|
开通 LAS |
|
费用 | 调用算子前,您需先了解使用算子时的模型调用费用,详情请参见大模型调用计费。 |
鉴权(API Key) | 调用算子前,您需要先生成算子调用的API Key,并建议将API Key配置为环境变量,便于更安全地调用算子,详情请参见获取 API Key 并配置。 |
BaseURL | 调用算子前,您需要先根据您当前使用的LAS服务所在地域,了解算子调用的BaseURL,用于配置算子调用路径参数取值。 |
调用 las_video_frame_extract 时,您需要先通过 Submit 提交异步任务,待任务进入完成状态后,再通过 Poll 获取抽帧结果。
调用视频抽帧算子提交异步任务,对输入视频按指定帧率进行抽帧,并将抽取的图片帧上传至指定 TOS 路径。提交成功后会返回任务 ID,您可以基于该 ID 通过 Poll 接口继续查询处理进度和结果。
参数 | 类型 | 是否必填 | 示例值 | 描述 |
|---|---|---|---|---|
operator_id | string | 是 | las_video_frame_extract | 算子 ID。 |
operator_version | string | 是 | v1 | 算子版本。 |
data | VideoFrameExtractData | 是 | 视频抽帧请求参数。 | |
video_url | string | 是 | tos://bucket/path/video.mp4 | 输入视频地址。支持 http/https 和 tos://bucket/key。 |
output_path_template | string | 是 | tos://bucket/frames/frame_{index}.{autoext} | 输出路径模板。必须以 tos:// 开头,且包含 {index} 占位符。可选占位符:{autoext}(自动匹配输出格式后缀,如 jpg、png)。 |
fps | float | 否 | 1.0 | 抽帧帧率(帧/秒)。取值范围 0.1 ~ 10.0,默认 1.0。 |
max_frames | integer | 否 | 200 | 最大抽帧数量。大于 0,不传则不限制。 |
image_format | string | 否 | jpg | 输出图片格式。可选值: jpg、png,默认 jpg。 |
resize_short_side | integer | 否 | 720 | 按短边缩放到指定像素,保持宽高比。与 resize_hw 互斥,不可同时设置。 |
resize_hw | array of integer | 否 | [720, 1080] | 指定输出分辨率 [width, height]。数组长度为 2,元素可为 null(表示按另一边等比缩放,如 [720, null])。与 resize_short_side 互斥,不可同时设置。 |
参数 | 类型 | 示例值 | 描述 |
|---|---|---|---|
metadata | metadata | 请求元信息。 | |
task_id | string | task-xxx | 异步模式下的任务 ID,用于作业状态查询。 |
task_status | string | PENDING | 异步模式下的任务状态。
|
business_code | string | 0 | 业务码。 |
error_msg | string | 如有异常,会返回详细错误信息。 |
请求示例
curl --location "https://operator.las.cn-beijing.volces.com/api/v1/submit" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer $LAS_API_KEY" \ --data '{ "operator_id": "las_video_frame_extract", "operator_version": "v1", "data": { "video_url": "tos://my-bucket/videos/sample.mp4", "output_path_template": "tos://my-bucket/frames/frame_{index}.{autoext}", "fps": 1.0, "max_frames": 200, "image_format": "jpg", "resize_short_side": 720 } }'
返回示例
{ "metadata": { "task_id": "task-20260420120000-abc123", "task_status": "PENDING", "business_code": "0", "error_msg": "" } }
HttpStatusCode | 错误码 | 错误信息 | 描述 |
|---|---|---|---|
|
|
| 请求参数不合法。 |
|
|
| 缺少必填参数。 |
|
|
| 输入视频 URL 不合法。 |
|
|
| 视频文件无效或无法解析。 |
|
|
| 视频下载失败。 |
|
|
| 视频格式不受支持。 |
|
|
| 视频处理超时。 |
|
|
| 视频抽帧失败。 |
|
|
| TOS 访问失败,请检查凭证和路径。 |
|
|
| 缺少鉴权信息。 |
|
|
| API Key 不合法。 |
调用 Poll 接口查询视频抽帧任务的执行状态和结果。当任务状态为 COMPLETED 时,接口会返回视频元信息和抽取后的帧列表。
名称 | 类型 | 是否必选 | 示例值 | 描述 |
|---|---|---|---|---|
| string | 是 |
| 算子 ID。 |
| string | 是 |
| 算子版本。 |
| string | 是 |
| 提交任务后返回的任务 ID。 |
参数 | 类型 | 示例值 | 描述 |
|---|---|---|---|
metadata | metadata | 请求元信息。 | |
task_id | string | task-xxx | 任务 ID。 |
task_status | string | COMPLETED | 任务状态。
|
business_code | string | 0 | 业务码。 |
error_msg | string | 如有异常,会返回详细错误信息。 | |
data | VideoFrameExtractResponse | 任务完成后返回的数据。 | |
video_duration | float | 11.234 | 视频总时长(秒)。 |
video_resolution | string | 1920x1080 | 视频原始分辨率。 |
frame_count | integer | 11 | 抽取的总帧数。 |
frames | array of ExtractedFrame | 抽取的帧列表。 | |
index | integer | 0 | 帧序号,从 0 开始。 |
timestamp_seconds | float | 0.5 | 帧对应的视频时间戳(秒)。 |
width | integer | 720 | 帧图片宽度(像素)。 |
height | integer | 405 | 帧图片高度(像素)。 |
tos_path | string | tos://bucket/frames/frame_000.jpg | 帧图片在 TOS 上的存储路径。 |
请求示例
curl --location "https://operator.las.cn-beijing.volces.com/api/v1/poll" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer $LAS_API_KEY" \ --data '{ "operator_id": "las_video_frame_extract", "operator_version": "v1", "task_id": "task-20260420120000-abc123" }'
返回示例
{ "metadata": { "task_id": "task-20260420120000-abc123", "task_status": "COMPLETED", "business_code": "0", "error_msg": "" }, "data": { "video_duration": 11.234, "video_resolution": "1920x1080", "frame_count": 3, "frames": [ { "index": 0, "timestamp_seconds": 0.5, "width": 720, "height": 405, "http_url": "https://example.com/frame_000.jpg", "tos_path": "tos://my-bucket/frames/frame_000.jpg" }, { "index": 1, "timestamp_seconds": 1.5, "width": 720, "height": 405, "http_url": "https://example.com/frame_001.jpg", "tos_path": "tos://my-bucket/frames/frame_001.jpg" }, { "index": 2, "timestamp_seconds": 2.5, "width": 720, "height": 405, "http_url": "https://example.com/frame_002.jpg", "tos_path": "tos://my-bucket/frames/frame_002.jpg" } ] } }
HttpCode | 错误码 | 错误信息 | 说明 |
|---|---|---|---|
400 | Parameter.Invalid | invalid parameter | 请求参数不合法。 |
400 | Parameter.Missing | Missing required parameter | 缺少必填参数。 |
400 | Url.Invalid | invalid url | 输入视频 URL 不合法。 |
400 | Video.Invalid | invalid video file | 视频文件无效或无法解析。 |
400 | Video.DownloadFailed | Video download failed | 视频下载失败。 |
400 | Video.FormatUnsupported | Video format not supported | 视频格式不受支持。 |
400 | Video.Timeout | Video processing timeout | 视频处理超时。 |
400 | Video.FrameExtractionFailed | Video frame extraction failed | 视频抽帧失败。 |
400 | Tos.AccessFailed | Failed to access TOS path with provided credentials | TOS 访问失败,请检查凭证和路径。 |
400 | Task.NotFound | Task not found | 任务不存在。 |
401 | Authorization.Missing | Missing Authorization | 缺少鉴权信息。 |
401 | ApiKey.InValid | The api key is invalid | API Key 不合法。 |
500 | Internal.ServerError | Internal server error | 服务内部错误。 |
输入列名 | 说明 |
|---|---|
video_paths | 输入视频路径列(本地或远程URI),类型 string,默认 None |
video_binaries | 输入视频二进制列,类型 binary,默认 None |
video_formats | 对应二进制输入的格式(如"mp4","mov"),类型 string,默认 None |
video_durations | 视频时长列(秒),类型 float,默认 None |
结构体数组,字段包括:
如参数没有默认值,则为必填参数
参数名称 | 类型 | 默认值 | 描述 |
|---|---|---|---|
sample_mode | str | by_count_uniform | 采样模式 可选值: ["by_count_uniform", "by_interval_time", "by_interval_frames", "by_fps", "by_timestamps"] |
start_time_sec | float | 0.0 | 起始时间(秒) |
end_time_sec | float or None | 结束时间(秒),None 表示视频末尾 | |
count_k | int or None | 均匀/随机采样的帧数(by_count_uniform使用) | |
interval_sec | float or None | 时间间隔Δt(秒,by_interval_time使用) | |
interval_frames | int or None | 解码帧间隔N(by_interval_frames使用) | |
target_fps | float or None | 目标FPS(by_fps使用) | |
timestamps_sec | list or None | 目标时间戳列表(秒,by_timestamps使用) | |
img_type | str | .jpg | 输出图片格式(用于base64与可选TOS落盘),可选[".jpg", ".png", ".webp"],默认 ".jpg" |
output_tos_dir | str | 若非空则将采样帧落盘本地并上传至 TOS 的目标目录(目录下每个视频单独子目录) | |
max_frames | int or None | 返回帧上限(防御性限制),None 表示不限制 | |
seed | int | 42 | 当无法提前获知视频总时长时,算子会采用 reservoir sampling(水塘抽样)算法,从视频流中均匀随机采样指定数量的帧。此参数用于设置随机数种子,保证采样结果可复现,默认值为 42。 |
output_frames | bool | True | 是否输出原始帧数组(较大,默认 True)。注意:即使设置为 False,返回结果仍包含 frames 字段,但内容为空列表。 |
output_base64 | bool | True | 是否输出 base64 编码(较大,默认 True)。注意:即使设置为 False,返回结果仍包含 base64 字段,但内容为空列表。 |
下面的代码展示了如何使用 Daft(适用于分布式)从视频中按帧间隔采样若干图像帧。本示例设置 interval_frames=100,实际产出 6 张图片。
from __future__ import annotations import os import daft from daft import col from daft.las.functions.udf import las_udf from daft.las.functions.video.video_frame_sampler import VideoFrameSampler if __name__ == "__main__": # 更改完采样的帧会保存到指定的TOS路径下,因此,需要设置好环境变量以保证有权限写入TOS,包括:ACCESS_KEY,SECRET_KEY,TOS_ENDPOINT,TOS_REGION,TOS_TEST_DIR TOS_DIR = os.getenv("TOS_TEST_DIR", "tos_bucket") output_tos_dir = f"tos://{TOS_DIR}/video/video_frame_sampler" if os.getenv("DAFT_RUNNER", "native") == "ray": import logging import ray def configure_logging(): logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S.%s".format(), ) logging.getLogger("tracing.span").setLevel(logging.WARNING) logging.getLogger("daft_io.stats").setLevel(logging.WARNING) logging.getLogger("DaftStatisticsManager").setLevel(logging.WARNING) logging.getLogger("DaftFlotillaScheduler").setLevel(logging.WARNING) logging.getLogger("DaftFlotillaDispatcher").setLevel(logging.WARNING) ray.init(dashboard_host="0.0.0.0", runtime_env={"worker_process_setup_hook": configure_logging}) daft.set_runner_ray() daft.set_execution_config(actor_udf_ready_timeout=600) daft.set_execution_config(min_cpu_per_task=0) # 使用环境变量构建URL tos_dir_url = os.getenv("TOS_DIR_URL", "las-cn-beijing-public-online.tos-cn-beijing.volces.com") samples = { "video_path": [ f"https://{tos_dir_url}/public/shared_video_dataset/sample.mp4" ] } ds = daft.from_pydict(samples) sampler = las_udf( VideoFrameSampler, construct_args={ "sample_mode": "by_interval_frames", "interval_frames": 100, "output_tos_dir": output_tos_dir, "img_type": ".jpg", }, ) # 使用 Daft 进行分布式处理 ds = ds.with_column("results", sampler(col("video_path"))) ds = ds.select( "video_path", col("results")["frames"].alias("frames"), col("results")["base64"].alias("base64"), col("results")["timestamps"].alias("timestamps"), col("results")["frame_indices"].alias("frame_indices"), col("results")["tos_paths"].alias("tos_paths"), ) ds.show() # ╭────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ # │ video_path ┆ results │ # │ --- ┆ --- │ # │ Utf8 ┆ Struct[frames: List[Binary], base64: List[Utf8], timestamps: List[Float64], frame_indices: List[Int64], tos_paths: List[Utf8]] │ # ╞════════════════════════════════╪══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╡ # │ https://las-cn-beijing-publi-… ┆ {frames: […], base64: […], timestamps: […], frame_indices: […], tos_paths: ["tos://tos_bucket/video/video_frame_sampler/frame_0.jpg", …]} │ # ╰────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯