使用FFmpeg提取小尺寸测试视频帧时出现「Invalid data found when processing input」错误的排查求助
问题分析与修复方案
我看到你遇到的问题是小尺寸测试视频提取帧时FFmpeg报错,但大尺寸视频能正常运行,这主要有两个关键问题需要修复:
- 临时文件写入后未重置读取指针:写入视频字节到临时文件后,文件指针停在末尾,FFmpeg读取时会从这个位置开始,自然读不到有效数据。
- 测试视频生成的兼容性不足:小尺寸视频用默认参数生成时,编码格式的兼容性较差,导致FFmpeg解析异常。
下面是修复后的完整代码及细节说明:
修复后的创建测试视频代码
import subprocess import shlex import os from dataclasses import dataclass def create_dummy_mp4_video(w: int, h: int, fps: int, sec: int, filename: str) -> None: cmd = ( f"ffmpeg -y " # 覆盖已有文件 f"-f lavfi -i color=size={w}x{h}:rate={fps}:color=black " # 生成纯色视频源 f"-f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 " # 生成静音音频源 f"-t {sec} " # 视频时长(秒) f"-c:v libx264 -profile:v baseline -pix_fmt yuv420p " # 指定兼容的H.264编码参数 f"-movflags +faststart " # 将moov原子移到文件开头,提升读取兼容性 f"{filename}" ) proc = subprocess.run( shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, ) if proc.returncode != 0: raise Exception(f"FFmpeg生成视频失败: {proc.stderr.decode()}") @dataclass(frozen=True) class FakeVideo: body: bytes width: int height: int fps: int size: int frames: int length_s: int def video() -> FakeVideo: w, h, fps, sec, filename = 100, 100, 10, 1, "output.mp4" create_dummy_mp4_video(w, h, fps, sec, filename) video_path = os.path.join(os.getcwd(), filename) with open(video_path, "rb") as file: body = file.read() size = len(body) frames = fps * sec # 修复帧数量计算错误(原代码为fps//sec,逻辑错误) return FakeVideo( body=body, width=w, height=h, fps=fps, size=size, frames=frames, length_s=sec, )
修复后的异步提取帧代码
import asyncio import aiofiles import logging import imghdr LOGGER = logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) async def run_shell_command(frame_millisecond, data: bytes) -> bytes: async with aiofiles.tempfile.NamedTemporaryFile("wb") as file: await file.write(data) await file.seek(0) # 关键修复:将文件指针重置到开头,确保FFmpeg能读取完整内容 proc = await asyncio.create_subprocess_exec( "ffmpeg", "-i", file.name, "-ss", f"{frame_millisecond}ms", # 定位到指定毫秒位置 "-vframes", "1", # 只提取一帧 "-c:v", "png", # 指定输出为PNG格式 "-f", "image2pipe", "-", # 将输出定向到标准输出 stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await proc.communicate() level = logging.DEBUG if proc.returncode == 0 else logging.WARN LOGGER.log(level, f"[命令退出码] {proc.returncode}") if stderr: LOGGER.log(level, f"[错误输出] {stderr.decode()}") return stdout async def runner(): v = video() time = int(v.length_s / 2 * 1000) res = await run_shell_command(time, v.body) assert isinstance(res, bytes) assert imghdr.what(h=res, file=None) == "png" print("帧提取成功!") if __name__ == "__main__": asyncio.run(runner()) # 使用Python 3.7+推荐的异步运行方式
关键修复点说明
临时文件指针重置:
在写入视频字节后添加await file.seek(0),确保FFmpeg读取文件时从开头开始,而不是从写入后的末尾位置。这是导致你报错的最直接原因。测试视频编码兼容性优化:
- 明确指定
libx264编码器、baseline编码 profile 和yuv420p像素格式,这些参数确保生成的MP4具有广泛的兼容性,尤其是小尺寸视频场景。 - 添加
-movflags +faststart将视频的moov原子(存储元数据的部分)移到文件开头,让FFmpeg无需读取到文件末尾就能解析视频信息。
- 明确指定
其他细节修复:
- 修复了帧数量计算错误:原代码
frames = fps // sec逻辑错误,正确应为frames = fps * sec(总帧数=帧率×时长)。 - 使用
asyncio.run()替代旧的get_event_loop().run_until_complete(),符合Python 3.7+的异步代码规范。
- 修复了帧数量计算错误:原代码
现在你可以测试任意尺寸的视频(包括100x100),都能正常提取帧了。
内容的提问来源于stack exchange,提问作者indigo153
相关产品推荐
相关产品推荐

