diff --git a/skills/kb-ocr-processor/SKILL.md b/skills/kb-ocr-processor/SKILL.md index a2ebdc5..cfd9ef5 100644 --- a/skills/kb-ocr-processor/SKILL.md +++ b/skills/kb-ocr-processor/SKILL.md @@ -4,19 +4,36 @@ ## Features -- Batch frame processing -- PaddleOCR/Tesseract support -- Confidence scoring -- Text merging +- **视频截帧**: 直接从视频文件截取帧图片 +- **批量处理**: 支持处理帧目录 +- **双引擎**: EasyOCR / Tesseract +- **置信度评分**: 每帧识别结果带置信度 +- **文本合并**: 自动合并所有帧的识别结果 ## Commands ```bash -# Process frames +# 方式1: 直接从视频截帧并OCR(推荐) +python ocr_processor.py --video ./video.flv --output ocr.json + +# 方式2: 处理已截取的帧目录 python ocr_processor.py --input ./frames/ --output ocr.json -# With custom FPS -python ocr_processor.py --input ./frames/ --fps 0.1 --output ocr.json +# 指定截帧帧率(默认1帧/秒) +python ocr_processor.py --video ./video.flv --fps 2 --output ocr.json + +# 同时生成合并的文本文件 +python ocr_processor.py --video ./video.flv --output ocr.json --merge + +# 使用 Tesseract 引擎 +python ocr_processor.py --input ./frames/ --engine tesseract --output ocr.json +``` + +## Workflow 中的使用 + +```bash +# 步骤5: OCR处理(视频无字幕时) +python ocr_processor.py --video ${CONTENT_VIDEO_PATH} --output ${CONTENT_DIR}/ocr.json --merge ``` ## Output @@ -24,14 +41,15 @@ python ocr_processor.py --input ./frames/ --fps 0.1 --output ocr.json ```json { "success": true, - "data": { - "frames": { - "frame_0001.jpg": { - "texts": [{"text": "字幕", "confidence": 0.95}] - } - }, - "merged_text": "合并的字幕文本..." - } + "engine": "easyocr", + "total_frames": 619, + "results": [ + { + "source": "0001.jpg", + "text": "字幕文字", + "confidence": 0.95 + } + ] } ``` @@ -40,4 +58,9 @@ python ocr_processor.py --input ./frames/ --fps 0.1 --output ocr.json ```bash cp -r kb-ocr-processor/ ~/skills/ pip install -r requirements.txt + +# 依赖说明 +# - easyocr: 主要OCR引擎(自动下载模型) +# - pytesseract: Tesseract引擎 +# - ffmpeg: 视频截帧(apt install ffmpeg) ``` diff --git a/skills/kb-ocr-processor/ocr_processor.py b/skills/kb-ocr-processor/ocr_processor.py index d905a05..9e5ec82 100644 --- a/skills/kb-ocr-processor/ocr_processor.py +++ b/skills/kb-ocr-processor/ocr_processor.py @@ -216,6 +216,50 @@ class OCRProcessor: print(f"Merged text saved to {text_path}") +def extract_frames(video_path: str, output_dir: str, fps: float = 1.0) -> str: + """ + 使用 ffmpeg 从视频中截取帧图片 + + Args: + video_path: 视频文件路径 + output_dir: 输出目录 + fps: 每秒截取帧数 (默认: 1) + + Returns: + 帧图片目录路径 + """ + import subprocess + + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + # ffmpeg 命令: -i 输入 -vf fps=fps 输出格式 + cmd = [ + "ffmpeg", "-y", + "-i", video_path, + "-vf", f"fps={fps}", + "-q:v", "2", # 质量等级 (2=高质量) + str(output_dir / "%04d.jpg") + ] + + print(f"🔄 截取视频帧: {video_path}") + print(f" 输出目录: {output_dir}") + + try: + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0: + # 统计截取的帧数 + frame_count = len(list(output_dir.glob("*.jpg"))) + print(f"✅ 截取完成: {frame_count} 帧") + return str(output_dir) + else: + print(f"❌ ffmpeg 错误: {result.stderr}") + return "" + except FileNotFoundError: + print("❌ 错误: 未找到 ffmpeg,请先安装: apt install ffmpeg") + return "" + + def main(): """命令行入口""" parser = argparse.ArgumentParser( @@ -225,35 +269,56 @@ def main(): 示例: # 使用 EasyOCR 处理帧目录 python ocr_processor.py --input ./frames/ --output ./ocr.json - + + # 直接从视频截帧并 OCR + python ocr_processor.py --video ./video.flv --output ./ocr.json + # 使用 Tesseract 处理 python ocr_processor.py --input ./frames/ --output ./ocr.json --engine tesseract - - # 指定采样帧率 - python ocr_processor.py --input ./frames/ --output ./ocr.json --fps 0.5 - + + # 指定截帧帧率 + python ocr_processor.py --video ./video.flv --output ./ocr.json --fps 2 + # 同时生成合并的文本文件 python ocr_processor.py --input ./frames/ --output ./ocr.json --merge """ ) - - parser.add_argument("--input", "-i", required=True, help="输入帧图片目录") + + # 输入源:帧目录 或 视频文件(二选一) + input_group = parser.add_mutually_exclusive_group(required=True) + input_group.add_argument("--input", "-i", help="输入帧图片目录") + input_group.add_argument("--video", "-v", help="输入视频文件(会自动截帧)") + parser.add_argument("--output", "-o", required=True, help="输出 JSON 文件路径") - parser.add_argument("--fps", "-f", type=float, default=0.1, help="采样帧率 (默认: 0.1)") + parser.add_argument("--fps", "-f", type=float, default=1.0, help="截帧帧率 (默认: 1帧/秒)") parser.add_argument("--engine", "-e", choices=["easyocr", "tesseract"], default="easyocr", help="OCR 引擎 (默认: easyocr)") parser.add_argument("--merge", action="store_true", help="同时保存合并的文本文件") parser.add_argument("--languages", "-l", nargs="+", default=["ch_sim", "en"], help="语言列表") parser.add_argument("--progress", "-p", help="可选:进度文件路径(会实时更新)") - + args = parser.parse_args() - + + # 如果指定了视频文件,先截帧 + input_dir = args.input + if args.video: + if not os.path.exists(args.video): + print(f"❌ 视频文件不存在: {args.video}") + return 1 + + # 创建临时帧目录 + frames_dir = Path(args.video).stem + "_frames" + input_dir = extract_frames(args.video, frames_dir, fps=args.fps) + + if not input_dir: + return 1 + # 初始化处理器 processor = OCRProcessor(engine=args.engine, languages=args.languages) - + # 处理帧 - results = processor.process_frames(args.input, fps=args.fps, progress_file=args.progress) + results = processor.process_frames(input_dir, fps=args.fps, progress_file=args.progress) if results: # 保存结果 diff --git a/skills/kb-workflow/kb_workflow.py b/skills/kb-workflow/kb_workflow.py index b19c979..944ceb0 100644 --- a/skills/kb-workflow/kb_workflow.py +++ b/skills/kb-workflow/kb_workflow.py @@ -243,16 +243,16 @@ def main(): parser.add_argument('--check', action='store_true', help='查重') parser.add_argument('--url', type=str, help='要检查的URL') - parser.add_argument('--archive', type=str, help='归档的文件路径') + parser.add_argument('--archive-file', type=str, help='归档的文件路径') parser.add_argument('--base-path', type=str, help='获取知识库根目录') - parser.add_argument('--pending', action='store_true', help='url解析目录') + parser.add_argument('--pending', action='store_true', help='获取url解析目录') parser.add_argument('--metadata', action='store_true', help='获取元数据目录') parser.add_argument('--content', action='store_true', help='获取内容目录') parser.add_argument('--content_downloads', action='store_true', help='获取主内容下载目录') parser.add_argument('--content_subtitles', action='store_true', help='获取字幕目录') parser.add_argument('--analyzed', action='store_true', help='获取深度分析目录') - parser.add_argument('--archive', action='store_true', help='获取归档目录') + parser.add_argument('--archive_dir', action='store_true', help='获取归档目录') parser.add_argument('--archive_subscriptions', action='store_true', help='获取订阅信息目录') parser.add_argument('--extract_platform', type=str, help='要提取平台名称的文件名') args = parser.parse_args() @@ -263,10 +263,16 @@ def main(): result = check_duplicate(base_path, args.url) print(json.dumps(result, ensure_ascii=False, indent=2)) - elif args.archive: - result = archive_and_git(args.archive, base_path) + elif args.archive_file: + result = archive_and_git(args.archive_file, base_path) print(json.dumps(result, ensure_ascii=False, indent=2)) + elif args.archive_dir: + path = dirs.get("archive") + if path: + print(json.dumps({"path": str(path)}, ensure_ascii=False, indent=2)) + return + else: parser.print_help()