This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
openclaw-skills/extract_frames.py
T
OpenClaw Agent e0a2b323d9 Update obsidian-kb skill and add find-skills
- obsidian-kb: Add OCR processors (easyocr, tesseract)
- obsidian-kb: Fix subtitle detection with test files
- obsidian-kb: Add cookie verification script
- Add find-skills skill for discovering capabilities
- Add video processing scripts
- Update clawhub lock file
2026-02-09 11:53:42 +08:00

229 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
下载视频片段用于 OCR 提取硬字幕
策略:每隔 N 秒截取一帧,而不是下载整个视频
"""
import asyncio
import json
import os
import cv2
import numpy as np
from playwright.async_api import async_playwright
from urllib.request import Request, urlopen
async def extract_frames_for_ocr(bvid, output_dir="/tmp/bilibili_frames", interval_seconds=10, max_frames=200):
"""
提取视频帧用于 OCR
Args:
bvid: B站视频 BV 号
output_dir: 输出目录
interval_seconds: 截帧间隔(秒)
max_frames: 最大截取帧数
Returns:
str: 输出目录路径
"""
url = f"https://www.bilibili.com/video/{bvid}"
# 创建输出目录
os.makedirs(output_dir, exist_ok=True)
print(f"正在提取视频帧: {url}")
print(f"间隔: {interval_seconds}秒,最大帧数: {max_frames}")
print("=" * 80)
async with async_playwright() as playwright:
browser = await playwright.chromium.launch(headless=True)
context = await browser.new_context()
page = await context.new_page()
# 获取视频信息
print("\n1. 获取视频信息...")
try:
api_url = "https://api.bilibili.com/x/web-interface/view"
response = await page.request.get(api_url, params={'bvid': bvid})
data = await response.json()
if data.get('code') != 0:
print(f" ❌ 失败: {data.get('message')}")
await browser.close()
return None
video_data = data.get('data', {})
title = video_data.get('title')
aid = video_data.get('aid')
cid = video_data.get('cid')
duration = video_data.get('duration')
print(f" 标题: {title}")
print(f" 时长: {duration}秒")
except Exception as e:
print(f" ❌ 异常: {e}")
await browser.close()
return None
# 获取视频流 URL
print("\n2. 获取视频流 URL...")
try:
playurl_api = "https://api.bilibili.com/x/player/playurl"
params = {
'bvid': bvid,
'cid': cid,
'qn': 80, # 1080P
'otype': 'json',
'fnver': 0,
'fnval': 0, # FLV 格式
}
playurl_response = await page.request.get(playurl_api, params=params)
if not playurl_response.ok or playurl_response.status != 200:
print(f" ❌ HTTP 错误: {playurl_response.status}")
await browser.close()
return None
playurl_data = await playurl_response.json()
if playurl_data.get('code') != 0:
print(f" ❌ API 错误: {playurl_data.get('message')}")
await browser.close()
return None
play_data = playurl_data.get('data', {})
durl = play_data.get('durl', [])
if not durl:
print(f" ❌ 没有找到视频流")
await browser.close()
return None
# 获取第一个分段的 URL
first_segment_url = durl[0].get('url')
print(f" ✅ 获取到视频流 URL")
except Exception as e:
print(f" ❌ 异常: {e}")
import traceback
traceback.print_exc()
await browser.close()
return None
# 使用 OpenCV 直接从 URL 读取视频并提取帧
print(f"\n3. 提取视频帧...")
try:
# 添加请求头
headers = {
'Referer': 'https://www.bilibili.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
}
# 使用 urllib 下载视频到临时文件(只下载前 50MB)
print(f" 正在下载视频(前 50MB)...")
req = Request(first_segment_url, headers=headers)
# 下载视频数据(限制 50MB)
chunk_size = 8192
max_bytes = 50 * 1024 * 1024 # 50MB
video_data = bytearray()
with urlopen(req, timeout=60) as response:
while True:
chunk = response.read(chunk_size)
if not chunk or len(video_data) >= max_bytes:
break
video_data.extend(chunk)
print(f" 已下载 {len(video_data) / 1024 / 1024:.2f}MB")
# 保存为临时文件
temp_video_path = os.path.join(output_dir, "temp_video.flv")
with open(temp_video_path, 'wb') as f:
f.write(bytes(video_data))
# 使用 OpenCV 打开视频
cap = cv2.VideoCapture(temp_video_path)
if not cap.isOpened():
print(f" ❌ 无法打开视频文件")
await browser.close()
return None
# 获取视频帧率
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
print(f" 帧率: {fps:.2f} fps")
print(f" 总帧数: {total_frames}")
# 计算需要提取的帧
interval_frames = int(fps * interval_seconds)
frame_count = 0
extracted_count = 0
print(f"\n 开始提取帧...")
while True:
# 跳到目标帧
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_count)
# 读取帧
ret, frame = cap.read()
if not ret:
break
# 保存帧
frame_path = os.path.join(output_dir, f"frame_{extracted_count:04d}_{frame_count}.jpg")
cv2.imwrite(frame_path, frame)
extracted_count += 1
print(f" 提取第 {extracted_count} 帧 (时间: {frame_count / fps:.1f}s)")
# 检查是否达到最大帧数
if extracted_count >= max_frames:
print(f" 已达到最大帧数限制")
break
# 跳到下一帧
frame_count += interval_frames
cap.release()
# 删除临时视频文件
os.remove(temp_video_path)
print(f"\n4. 完成!")
print(f" 成功提取 {extracted_count} 帧图像")
print(f" 输出目录: {output_dir}")
await browser.close()
return output_dir
except Exception as e:
print(f" ❌ 提取帧失败: {e}")
import traceback
traceback.print_exc()
await browser.close()
return None
if __name__ == '__main__':
import sys
bvid = "BV1KtFzzqEUG"
result = asyncio.run(extract_frames_for_ocr(
bvid,
interval_seconds=10, # 每 10 秒提取一帧
max_frames=100 # 最多提取 100 帧
))
if result:
print(f"\n✅ 提取完成!")
print(f"输出目录: {result}")
print(f"\n下一步:使用 OCR 工具识别字幕")
else:
print(f"\n❌ 提取失败")
sys.exit(1)