本文为您介绍 Python 多模态函数库中视频函数的使用方法。通过内置 python 模块,您可以在 Flink SQL 作业中读取视频元数据、生成视频片段引用,以及按全部帧、关键帧或固定时间间隔抽取视频帧。
使用限制
仅实时计算引擎 VVR 11.8 及以上版本支持。
使用方法
使用说明
在平台 SQL 作业中,视频函数由内置 python 模块提供,无需在作业中引入额外依赖。使用由 python 模块提供的函数前,需要先加载该模块,再启用 python 和 core 模块:
LOAD MODULE python;
USE MODULES python, core;基本调用
LOAD MODULE python;
USE MODULES python, core;
-- 读取视频元数据。
SELECT id, VIDEO_METADATA(uri, 'null') AS metadata
FROM videos;
-- 每隔 1 秒抽取一帧,并展开为一帧一行。
SELECT v.id, f.frame, f.metadata
FROM videos AS v
CROSS JOIN LATERAL TABLE(
VIDEO_EXPLODE_FRAMES(v.uri, 'sample', 1000)
) AS f(frame, metadata);参数说明
SQL 函数按签名中的顺序传参,只支持位置参数。函数签名中的 [ ] 表示可选参数。例如 VIDEO_METADATA 的签名如下:
VIDEO_METADATA(
uri
[, on_error
[, read_chunk_size
[, max_cached_blocks
[, read_ahead_blocks]]]]
)调用时遵循以下规则:
省略的可选参数会使用参数默认值进行填充。
如果需要显式设置靠后的可选参数,前面的可选参数也需要明确写出。
NULL会作为参数值传入,不等同于省略参数时的默认值。多态数据参数需要使用NULL时,可以通过CAST(NULL AS <精确类型>)明确类型。除
video_input、VIDEO_METADATA的uri以及VIDEO_SPLIT的metadata外,其他参数都是配置参数,必须使用 SQL 字面量(包括NULL),不能使用表字段或CAST表达式。VIDEO_SPLIT和VIDEO_EXPLODE_FRAMES是表值函数(UDTF),需要通过LATERAL TABLE调用。
数据类型说明
视频数据形态
数据形态 | SQL 类型 | 说明 |
视频 URI |
| Flink 文件系统可访问的视频 URI。 |
| 指向原视频中的一个时间片段,不复制视频内容。 |
VIDEO_METADATA 只接受视频 URI,其他三个视频函数同时接受 URI 和视频片段引用。读取视频时只选择第一个视频流。
视频片段引用
ROW<uri STRING, start_time_ms BIGINT, end_time_ms BIGINT>字段 | 类型 | 说明 |
|
| 原始视频文件路径或对象存储 URI。 |
|
| 片段的开始时间,单位毫秒; |
|
| 片段的结束时间,单位毫秒; |
VIDEO_SPLIT 的返回数据结构。视频片段采用闭区间 [start_time_ms, end_time_ms]。例如 [0, 999] 后一个相邻片段从 1000 开始。
视频元数据
VIDEO_METADATA 返回以下结构:
ROW<
width INT,
height INT,
fps DOUBLE,
duration_ms BIGINT,
frame_count BIGINT,
time_base DOUBLE,
codec_name STRING,
video_stream_index INT
>字段 | 类型 | 说明 |
|
| 视频宽度,单位像素。 |
|
| 视频高度,单位像素。 |
|
| 视频帧率。 |
|
| 视频时长,单位毫秒。 |
|
| 视频帧数。 |
|
| 视频流时间基准,用于计算帧时间。 |
|
| 视频编码格式名称,例如 |
|
| 选中的视频流编号。 |
底层视频流或容器未提供对应信息时,元数据字段可能为 NULL。
视频帧元数据
VIDEO_EXPLODE_FRAMES 为每个输出帧返回以下元数据:
ROW<
uri STRING,
video_stream_index INT,
frame_index BIGINT,
pts BIGINT,
time_ms BIGINT,
key_frame BOOLEAN,
start_time_ms BIGINT,
end_time_ms BIGINT
>字段 | 类型 | 说明 |
|
| 原始视频文件路径或对象存储 URI。 |
|
| 输出帧所属的视频流编号。 |
|
| 输出帧在当前抽帧结果中的序号,从 |
|
| 视频文件中的原始帧时间戳,通常用于和视频处理工具对齐。无法确定时为 |
|
| 帧对应的视频时间,单位毫秒。无法确定时为 |
|
| 是否为关键帧。 |
|
| 当前抽帧输入片段的开始时间,单位毫秒;输入为完整 URI 时为 |
|
| 当前抽帧输入片段的结束时间,单位毫秒;输入为完整 URI 时为 |
视频元数据
VIDEO_METADATA
读取视频基本信息,例如分辨率、帧率、时长和编码格式。该函数只读取容器和视频流元数据,不解码视频帧。
VIDEO_METADATA(
uri
[, on_error
[, read_chunk_size
[, max_cached_blocks
[, read_ahead_blocks]]]]
)参数 | 类型 | 默认值 | 说明 |
|
| 必填 | 视频 URI。 |
|
|
| 错误处理策略: |
|
|
| 单次从 Java 文件系统桥读取的字节数,必须大于 0,且不能超过 16 MiB。 |
|
|
| 每个文件最多缓存的块数,必须大于或等于 0; |
|
|
| 缓存未命中后预读的块数,必须大于或等于 0; |
返回类型: 视频元数据 ROW。输入 URI 为 NULL 时返回 NULL。
WITH source_with_metadata AS (
SELECT id, VIDEO_METADATA(uri, 'null') AS metadata
FROM videos
)
SELECT id, metadata.width AS width, metadata.duration_ms AS duration_ms
FROM source_with_metadata;视频切分与抽帧
VIDEO_EXTRACT_FRAMES
从完整视频或视频片段中抽帧,并把同一个输入视频的结果收集到同一行的数组中。若视频较长或抽帧数量较多,推荐使用 VIDEO_EXPLODE_FRAMES,避免单行持有过多图像。
VIDEO_EXTRACT_FRAMES(
video_input
[, frame_selector
[, sample_interval_ms
[, max_frames
[, image_height, image_width
[, on_error
[, read_chunk_size
[, max_cached_blocks
[, read_ahead_blocks]]]]]]]]]
)参数 | 类型 | 默认值 | 说明 |
|
| 必填 | 视频 URI 或 |
|
|
| 抽帧策略,支持 |
|
|
| 当 |
|
|
| 保留在同一输出行中的帧数安全上限,必须大于 0; |
|
|
| 输出图像高度,单位为像素,必须与 |
|
|
| 输出图像宽度,单位为像素,必须与 |
|
|
| 错误处理策略: |
|
|
| 单次从 Java 文件系统桥读取的字节数,必须大于 0,且不能超过 16 MiB。 |
|
|
| 每个文件最多缓存的块数,必须大于或等于 0; |
|
|
| 缓存未命中后预读的块数,必须大于或等于 0; |
返回类型: ARRAY<IMAGE>。
image_height和image_width指定的是精确输出尺寸,不保持原始宽高比。输入为视频片段引用时,使用闭区间
[start_time_ms, end_time_ms]筛选能够确定time_ms的帧。'all_frames'和'keyframe'策略下,无法确定time_ms的帧仍可能输出;'sample'策略会跳过这类帧。frame_selector='sample'从片段起点开始建立采样时间点,输出时间不早于各采样点的第一帧;无法确定time_ms的帧不会作为采样结果。输入为
NULL或在'null'策略下处理失败时返回NULL;处理成功但没有选中帧时返回空数组。
-- 每隔 1 秒抽一帧,每个视频最多返回 10 帧,并缩放为 224 × 224。
SELECT VIDEO_EXTRACT_FRAMES(
uri, 'sample', 1000, 10, 224, 224, 'null'
) AS frames
FROM videos;VIDEO_SPLIT
将完整视频或已有视频片段切分为多个片段引用,展开为一行一个片段引用。该函数本身不复制视频内容,也不读取视频帧,只计算时间窗口。
VIDEO_SPLIT(
video_input
[, metadata
[, segment_duration_ms
[, num_segments
[, video_duration_ms
[, max_segments
[, on_error
[, read_chunk_size
[, max_cached_blocks
[, read_ahead_blocks]]]]]]]]]
)参数 | 类型 | 默认值 | 说明 |
|
| 必填 | 视频 URI 或已有视频片段引用。 |
| 视频元数据 |
| 包含有效 |
|
|
| 固定片段长度,单位毫秒,必须大于 0。与 |
|
|
| 切分成近似等长的片段数量,必须大于 0。与 |
|
|
| 显式视频时长,单位毫秒,必须大于 0;在输入没有结束时间且 |
|
|
| 单行最多输出的片段数,必须大于 0。达到限制后停止输出剩余片段。 |
|
|
| 错误处理策略: |
|
|
| 单次从 Java 文件系统桥读取的字节数,必须大于 0,且不能超过 16 MiB。 |
|
|
| 每个文件最多缓存的块数,必须大于或等于 0; |
|
|
| 缓存未命中后预读的块数,必须大于或等于 0; |
返回列: segment ROW<uri STRING, start_time_ms BIGINT, end_time_ms BIGINT>,一行一个片段。
read_chunk_size、max_cached_blocks 和 read_ahead_blocks 仅在函数自行探测元数据时使用。
输入引用已经包含
end_time_ms时,直接使用该片段边界。输入没有
end_time_ms时,依次使用metadata中的时长和video_duration_ms;二者都没有有效值时,函数会探测视频时长。按固定时长切分时,每段是闭区间。例如 2 秒视频按 1000 毫秒切分为
[0, 999]和[1000, 1999]。按片段数切分时,函数使用向上取整的片段时长,因此实际片段数可能少于
num_segments。
-- metadata 位置使用有类型的 NULL 占位,按 1 秒切分。
SELECT v.id, s.segment
FROM videos AS v
CROSS JOIN LATERAL TABLE(
VIDEO_SPLIT(v.uri, CAST(NULL AS BIGINT), 1000)
) AS s(segment);
-- 复用已经读取的元数据,按目标数量切分。
WITH source_with_metadata AS (
SELECT id, uri, VIDEO_METADATA(uri) AS metadata
FROM videos
)
SELECT v.id, s.segment
FROM source_with_metadata AS v
CROSS JOIN LATERAL TABLE(
VIDEO_SPLIT(
v.uri,
v.metadata,
NULL,
4
)
) AS s(segment);VIDEO_EXPLODE_FRAMES
从完整视频或视频片段中抽帧,并将结果展开为一帧一行。
VIDEO_EXPLODE_FRAMES(
video_input
[, frame_selector
[, sample_interval_ms
[, max_frames
[, image_height, image_width
[, on_error
[, read_chunk_size
[, max_cached_blocks
[, read_ahead_blocks]]]]]]]]]
)参数 | 类型 | 默认值 | 说明 |
|
| 必填 | 视频 URI 或 |
|
|
| 抽帧策略,支持 |
|
|
| 当 |
|
|
| 每个输入最多输出的帧数,必须大于 0; |
|
|
| 输出帧高度,单位像素,必须与 |
|
|
| 输出帧宽度,单位像素,必须与 |
|
|
| 错误处理策略: |
|
|
| 单次从 Java 文件系统桥读取的字节数,必须大于 0,且不能超过 16 MiB。 |
|
|
| 每个文件最多缓存的块数,必须大于或等于 0; |
|
|
| 缓存未命中后预读的块数,必须大于或等于 0; |
返回列: frame IMAGE, metadata ROW<...>,一行一个视频帧。
选帧、缩放和片段边界规则与 VIDEO_EXTRACT_FRAMES 相同。使用 on_error='skip' 时,如果解码在第一帧输出前失败,则不输出任何行;如果输出部分帧后发生错误,已经输出的行会保留。
SELECT v.id, f.frame, f.metadata.time_ms AS frame_time_ms
FROM videos AS v
CROSS JOIN LATERAL TABLE(
VIDEO_EXPLODE_FRAMES(
v.uri, 'sample', 1000, 10, 224, 224, 'skip'
)
) AS f(frame, metadata);完整 Pipeline 示例
以下示例先读取视频元数据,再把视频按 1 秒切分,最后从每个片段抽取一帧:
LOAD MODULE python;
USE MODULES python, core;
-- 步骤 1:读取每个视频 URI,并提取对应的视频元数据。
WITH source_with_metadata AS (
SELECT
id,
uri,
VIDEO_METADATA(uri, 'null') AS metadata
FROM videos
),
-- 步骤 2:结合视频 URI 和元数据,将每个视频拆分为多个视频片段。
video_segments AS (
SELECT
v.id,
s.segment
FROM source_with_metadata AS v
CROSS JOIN LATERAL TABLE(
VIDEO_SPLIT(v.uri, v.metadata, 1000, NULL, NULL, 1024, 'skip')
) AS s(segment)
)
-- 步骤 3:以采样模式从每个视频片段中提取视频帧,并输出帧时间及其所属片段的起止时间。
SELECT
s.id,
f.frame,
f.metadata.time_ms AS frame_time_ms,
f.metadata.start_time_ms AS segment_start_ms,
f.metadata.end_time_ms AS segment_end_ms
FROM video_segments AS s
CROSS JOIN LATERAL TABLE(
VIDEO_EXPLODE_FRAMES(s.segment, 'sample', 1000, 1)
) AS f(frame, metadata);