Multimodal_RAG-AX650N / src /preprocessing /video_processor.py
H022329's picture
Upload folder using huggingface_hub
5316124 verified
Raw
History Blame Contribute Delete
5.44 kB
"""
VideoProcessor — 视频预处理。
将视频缩放到指定分辨率、控制帧率,输出单个处理后视频文件用于嵌入。
"""
import logging
import subprocess
import tempfile
import uuid
from pathlib import Path
from typing import Dict, List, Optional
from config.settings import settings
logger = logging.getLogger(__name__)
SUPPORTED_VIDEO_FORMATS = {
".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".wmv",
}
class VideoProcessor:
"""视频预处理器 — 单视频单向量。
将视频缩放到指定分辨率、控制帧率,输出一个 MP4 文件,
整个视频作为一个 VIDEO 向量嵌入。
Usage:
proc = VideoProcessor(target_size=256, max_frames=32)
result = proc.process("video.mp4")
# result["video_bytes"] → 处理后的视频数据
"""
def __init__(
self,
target_size: int = 256,
max_frames: int = 32,
):
self.target_size = target_size # 输出视频分辨率 (短边)
self.max_frames = max_frames # 输出视频最大帧数
def process(self, video_path: str) -> dict:
"""处理视频文件。
缩放 + 帧率控制 → 单个 MP4 文件。
Returns:
{chunk_id, video_bytes, metadata}
"""
path = Path(video_path)
if not path.exists():
raise FileNotFoundError(f"视频文件不存在: {video_path}")
suffix = path.suffix.lower()
if suffix not in SUPPORTED_VIDEO_FORMATS:
raise ValueError(f"不支持的视频格式: {suffix}")
# 获取元数据
metadata = self._get_video_metadata(video_path)
duration = metadata.get("duration", 0)
with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmp:
out_path = tmp.name
try:
# 计算目标帧率: 总帧数 / 时长
target_fps = min(self.max_frames / duration, 30) if duration > 0 else 10
cmd = [
"ffmpeg", "-y",
"-i", video_path,
"-vf", (
f"scale='min({self.target_size},iw)':min'({self.target_size},ih)':force_original_aspect_ratio=1,"
f"fps={target_fps},"
f"pad={self.target_size}:{self.target_size}:(ow-iw)/2:(oh-ih)/2:black"
),
"-c:v", "libx264",
"-preset", "ultrafast",
"-crf", "28",
"-an", # 去掉音频
"-movflags", "+faststart",
out_path,
]
subprocess.run(cmd, capture_output=True, timeout=300)
out_file = Path(out_path)
if out_file.exists() and out_file.stat().st_size > 0:
video_bytes = out_file.read_bytes()
chunk_id = str(uuid.uuid4())
return {
"chunk_id": chunk_id,
"video_bytes": video_bytes,
"text": f"[视频: {path.name}, {duration:.0f}s, {target_fps:.1f}fps]",
"metadata": {
"source_file": str(path.resolve()),
"source_file_name": path.name,
"duration_sec": round(duration, 2),
"target_size": self.target_size,
"target_fps": round(target_fps, 1),
"max_frames": self.max_frames,
"file_size_mb": round(out_file.stat().st_size / (1024 * 1024), 2),
},
}
else:
raise RuntimeError("ffmpeg 输出文件为空")
finally:
Path(out_path).unlink(missing_ok=True)
def _get_video_metadata(self, video_path: str) -> dict:
"""获取视频元数据 (ffprobe)。"""
try:
import json
result = subprocess.run(
["ffprobe", "-v", "quiet", "-print_format", "json",
"-show_format", "-show_streams", video_path],
capture_output=True, text=True, timeout=30,
)
data = json.loads(result.stdout)
fmt = data.get("format", {})
video_stream = None
for s in data.get("streams", []):
if s.get("codec_type") == "video":
video_stream = s
break
duration = float(fmt.get("duration", 0))
return {
"duration": round(duration, 2),
"fps": self._parse_fps(video_stream),
"resolution": (
f"{video_stream.get('width','?')}x{video_stream.get('height','?')}"
if video_stream else "unknown"
),
"codec": video_stream.get("codec_name", "unknown") if video_stream else "unknown",
}
except Exception:
return {"duration": 0, "fps": 0, "resolution": "unknown", "codec": "unknown"}
@staticmethod
def _parse_fps(video_stream: Optional[dict]) -> float:
if not video_stream:
return 0
r = video_stream.get("r_frame_rate", "0/1")
if "/" in r:
parts = r.split("/")
return round(float(parts[0]) / float(parts[1]), 2) if float(parts[1]) > 0 else 0
try:
return float(r)
except (ValueError, TypeError):
return 0