lizhengwei

jira:NYJ-1540 desc: add video_event_for_contents func

... ... @@ -122,7 +122,7 @@ class Video2Frame:
if not cap.isOpened():
raise ValueError(f"无法打开视频片段: {clip_path}")
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
video_fps = cap.get(cv2.CAP_PROP_FPS)
if video_fps <= 0:
video_fps = 25.0
... ... @@ -132,10 +132,9 @@ class Video2Frame:
if frame_interval < 1:
frame_interval = 1
frame_paths = []
frames = []
saved_count = 0
frame_count = 0
while True:
ret, frame = cap.read()
if not ret:
... ... @@ -145,27 +144,26 @@ class Video2Frame:
if roi is not None:
x, y, w, h = roi
frame = frame[y:y + h, x:x + w]
if max_px_area is not None:
h, w = frame.shape[:2]
area = h * w
if area > max_px_area:
scale = (max_px_area / area) ** 0.5
new_w = int(w * scale)
new_h = int(h * scale)
frame = cv2.resize(
frame, (new_w, new_h), interpolation=cv2.INTER_AREA
)
frame = self.reisize_frame(frame, max_px_area)
frames.append(frame)
frame_path = frame_output_dir / f"frame_{saved_count:06d}.jpg"
cv2.imwrite(str(frame_path), frame)
frame_paths.append(str(frame_path))
saved_count += 1
frame_count += 1
cap.release()
return frame_paths
return frames
def reisize_frame(self, frame, max_px_area):
if max_px_area is None:
return frame
h, w = frame.shape[:2]
area = h * w
if area > max_px_area:
scale = (max_px_area / area) ** 0.5
new_w = int(w * scale)
new_h = int(h * scale)
frame = cv2.resize(frame, (new_w, new_h), interpolation=cv2.INTER_AREA)
return frame
@staticmethod
def _find_frame_idx(utc_list, target_utc, find_last_not_greater=True):
... ... @@ -241,28 +239,34 @@ def frames2content(frames):
将帧列表(图片路径列表)转为 LLM content 列表。
格式与 llm_video_content.py 中的 contents 返回的 content 一致。
"""
content = []
contents = []
video_prompt = (
f"以下是从视频中按时间顺序提取的 {len(frames)} 帧画面,"
f"请将它们视为一个连续的视频进行分析。"
)
content.append({"type": "text", "text": video_prompt})
contents.append({"type": "text", "text": video_prompt})
for frame_path in frames:
with open(frame_path, "rb") as f:
img_bytes = f.read()
b64_str = base64.b64encode(img_bytes).decode("utf-8")
content.append({
for frame in frames:
_, buffer = cv2.imencode('.jpg', frame)
b64_str = base64.b64encode(buffer).decode('utf-8')
# with open(frame_path, "rb") as f:
# img_bytes = f.read()
# b64_str = base64.b64encode(img_bytes).decode("utf-8")
contents.append({
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{b64_str}"
}
})
return content
return contents
if __name__ == "__main__":
# pass
import argparse
import sys
import tempfile
... ... @@ -275,7 +279,7 @@ if __name__ == "__main__":
)
parser.add_argument(
"--cache-dir",
default=None,
default=r"C:\Users\lzw\AppData\Local\Temp\video2frame_test_mn26tcy_my",
help="缓存目录(默认自动创建临时目录)",
)
parser.add_argument(
... ... @@ -293,7 +297,7 @@ if __name__ == "__main__":
parser.add_argument(
"--fps",
type=float,
default=1.0,
default=2.0,
help="抽帧帧率(默认 1.0)",
)
args = parser.parse_args()
... ... @@ -314,11 +318,13 @@ if __name__ == "__main__":
start_utc=args.start,
end_utc=args.end,
fps=args.fps,
roi=None,
max_px_area=200_000,
)
print(f" 成功提取 {len(frames)} 帧")
if frames:
print(f" 首帧: {frames[0]}")
print(f" 末帧: {frames[-1]}")
print(f" 首帧: {frames[0].shape}")
print(f" 末帧: {frames[-1].shape}")
except Exception as e:
print(f" 失败: {e}")
import traceback
... ... @@ -340,27 +346,46 @@ if __name__ == "__main__":
if len(content) > 3:
print(f" ... 还有 {len(content) - 3} 个元素")
# TEST 3: ROI + max_px_area
print("\n[TEST 3] to_frames with roi + max_px_area")
try:
end_crop = min(args.start + 5, args.end)
frames_cropped = v2f.to_frames(
url=args.url,
start_utc=args.start,
end_utc=end_crop,
fps=1.0,
roi=None,
max_px_area=200_000,
)
print(f" 成功提取 {len(frames_cropped)} 帧(带 ROI 裁剪和面积缩放)")
except Exception as e:
print(f" 跳过/失败: {e}")
print("\n[TEST 3] FootballReplayVideoEvent 进球识别(可选)")
# TEST 4: SEI UTC 模式说明(仅演示,需真实含 SEI 的流才能运行)
print("\n[TEST 4] SEI UTC 绝对时间戳模式(示例代码,需替换为含 SEI 的流)")
print(
" # 示例:假设视频第一帧 UTC 为 1715600000,提取 10 秒片段\n"
" # frames = v2f.to_frames(url, start_utc=1715600000, end_utc=1715600010, fps=1)"
try:
from ..util.football_replay_video_event_by_llm import FootballReplayVideoEvent
except ImportError:
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from util.football_replay_video_event_by_llm import FootballReplayVideoEvent
fbrv = FootballReplayVideoEvent(
base_url="http://192.168.1.59:11434",
model="qwen3.6:35b-a3b-q8_0",
temperature=0.7,
)
print("\n[INFO] 所有测试完成")
event_json = fbrv.video_event_for_contents(content, None)
print(f" 识别结果: {event_json}")
# # TEST 3: ROI + max_px_area
# print("\n[TEST 3] to_frames with roi + max_px_area")
# try:
# end_crop = min(args.start + 5, args.end)
# frames_cropped = v2f.to_frames(
# url=args.url,
# start_utc=args.start,
# end_utc=end_crop,
# fps=1.0,
# roi=None,
# max_px_area=200_000,
# )
# print(f" 成功提取 {len(frames_cropped)} 帧(带 ROI 裁剪和面积缩放)")
# except Exception as e:
# print(f" 跳过/失败: {e}")
#
# # TEST 4: SEI UTC 模式说明(仅演示,需真实含 SEI 的流才能运行)
# print("\n[TEST 4] SEI UTC 绝对时间戳模式(示例代码,需替换为含 SEI 的流)")
# print(
# " # 示例:假设视频第一帧 UTC 为 1715600000,提取 10 秒片段\n"
# " # frames = v2f.to_frames(url, start_utc=1715600000, end_utc=1715600010, fps=1)"
# )
#
# print("\n[INFO] 所有测试完成")
... ...
... ... @@ -96,3 +96,21 @@ class FootballReplayVideoEvent:
with open(cache_path, 'w', encoding='utf-8') as f:
f.write(result)
return result_json
def video_event_for_contents(self, contents: list, asr_text: str = '无', cache_path=None):
if cache_path is not None and os.path.exists(cache_path):
with open(cache_path, 'r', encoding='utf-8') as f:
return json.loads(f.read())
system_message = SystemMessage(content=req_prompt)
video_message = HumanMessage(content=contents)
asr_message = HumanMessage(content=f"解说内容:{asr_text}")
result = self.model.invoke([system_message, video_message, asr_message]).content
try:
result_json = json.loads(result)
except json.JSONDecodeError:
result_json = json.loads(result.replace("```json", "").replace("```", ""))
if cache_path is not None:
os.makedirs(Path(cache_path).parent, exist_ok=True)
with open(cache_path, 'w', encoding='utf-8') as f:
f.write(result)
return result_json
... ...