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/skills/obsidian-kb/src/pipeline.py
T
OpenClaw Agent f26d4d8f7f feat: implement obsidian-kb skill with long video chunking
- Multi-source content aggregation (Bilibili/YouTube/Zhihu)
- Smart inbox management with AI quality scoring
- Long video chunking analysis (30min+ auto-segmentation)
- Topic-based organization
- Git auto-sync to Obsidian
- Template system with Jinja2 and fallback rendering
- CLI commands: subscribe, inbox, process, skip, status, search
- Comprehensive test suite with unit and integration tests
- Documentation: README.md and SKILL.md

Checkpoint 7-8 completed by Agent E
2026-02-06 18:15:30 +08:00

274 lines
9.3 KiB
Python

"""Obsidian KB - 内容处理管道
C4.3: 实现完整处理流程
"""
import os
from datetime import datetime
from typing import Optional
from src.models import Content, ContentStatus, AnalysisResult
from src.light_scanner import LightScanner
from src.deep_analyzer import DeepAnalyzer
from src.config import get_config
class ProcessingPipeline:
"""内容处理管道 - 协调轻量扫描、深度分析、笔记生成和同步"""
def __init__(self, ai_client=None, git_client=None):
self.config = get_config()
self.ai_client = ai_client
self.git_client = git_client
# 初始化处理器
self.light_scanner = LightScanner(ai_client=ai_client)
self.deep_analyzer = DeepAnalyzer(ai_client=ai_client)
def light_scan(self, content: Content, content_text: str) -> Content:
"""
轻量扫描阶段
Args:
content: Content对象
content_text: 内容文本
Returns:
更新后的Content对象
"""
print(f"[Pipeline] 开始轻量扫描: {content.id}")
try:
content = self.light_scanner.scan(content, content_text)
print(f"[Pipeline] 轻量扫描完成: 摘要长度={len(content.summary)}, 关键词={content.key_points}, 质量分={content.quality_metrics.overall_score:.2f}")
return content
except Exception as e:
print(f"[Pipeline] 轻量扫描失败: {e}")
raise
def deep_analyze(self, content: Content, content_text: str) -> Content:
"""
深度分析阶段
Args:
content: Content对象
content_text: 内容文本
Returns:
更新后的Content对象
"""
print(f"[Pipeline] 开始深度分析: {content.id}")
try:
analysis_result = self.deep_analyzer.analyze(content, content_text)
print(f"[Pipeline] 深度分析完成: 主题数={len(analysis_result.main_topics)}, 时间轴段数={len(analysis_result.timeline)}")
return content
except Exception as e:
print(f"[Pipeline] 深度分析失败: {e}")
content.update_status(ContentStatus.ERROR)
raise
def process_full(self, content: Content, content_text: str) -> Content:
"""
完整处理流程:
轻量扫描 → 入库 → 深度分析 → 生成笔记 → Git同步
Args:
content: Content对象
content_text: 内容文本
Returns:
完全处理后的Content对象
"""
print(f"\n{'='*60}")
print(f"[Pipeline] 开始完整处理流程: {content.id}")
print(f"{'='*60}\n")
try:
# Step 1: 轻量扫描
print("\n📋 Step 1: 轻量扫描")
content = self.light_scan(content, content_text)
# Step 2: 入库(保存到数据库/文件系统)
print("\n💾 Step 2: 保存内容")
self._save_content(content)
# Step 3: 深度分析
print("\n🔍 Step 3: 深度分析")
content = self.deep_analyze(content, content_text)
# Step 4: 生成笔记
print("\n📝 Step 4: 生成Obsidian笔记")
content = self._generate_note(content)
# Step 5: Git同步
if self.config.git.enabled:
print("\n🔄 Step 5: Git同步")
self._sync_to_git(content)
else:
print("\n⏭️ Step 5: Git同步已禁用,跳过")
content.update_status(ContentStatus.GENERATED)
print(f"\n{'='*60}")
print(f"[Pipeline] 处理完成: {content.id}")
print(f"{'='*60}\n")
return content
except Exception as e:
print(f"\n❌ [Pipeline] 处理流程失败: {e}")
raise
def _save_content(self, content: Content) -> None:
"""保存内容到存储"""
# 创建存储目录
storage_dir = os.path.expanduser("~/.obsidian-kb/storage")
os.makedirs(storage_dir, exist_ok=True)
# 保存JSON文件
import json
file_path = os.path.join(storage_dir, f"{content.id}.json")
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(content.to_dict(), f, ensure_ascii=False, indent=2)
print(f"[Pipeline] 内容已保存: {file_path}")
def _generate_note(self, content: Content) -> Content:
"""生成Obsidian笔记"""
# 构建笔记内容
note_content = self._build_note_content(content)
# 确定笔记路径
vault_path = os.path.expanduser(self.config.obsidian.vault_path)
notes_folder = self.config.obsidian.notes_folder
note_dir = os.path.join(vault_path, notes_folder)
os.makedirs(note_dir, exist_ok=True)
# 生成文件名
safe_title = self._sanitize_filename(content.title or content.id)
timestamp = datetime.now().strftime("%Y%m%d")
note_filename = f"{timestamp}_{safe_title}.md"
note_path = os.path.join(note_dir, note_filename)
# 写入文件
with open(note_path, 'w', encoding='utf-8') as f:
f.write(note_content)
content.output_path = note_path
content.update_status(ContentStatus.GENERATED)
print(f"[Pipeline] 笔记已生成: {note_path}")
return content
def _build_note_content(self, content: Content) -> str:
"""构建Obsidian笔记内容"""
lines = [
f"# {content.title or '未命名内容'}",
"",
"## 📊 元数据",
f"- **来源**: {content.source}",
f"- **URL**: {content.url}",
f"- **作者**: {content.author or '未知'}",
f"- **类型**: {content.content_type.value}",
]
if content.duration:
duration_str = self._format_duration(content.duration)
lines.append(f"- **时长**: {duration_str}")
lines.extend([
f"- **质量评分**: {content.quality_metrics.overall_score:.1f}/1.0",
f"- **处理时间**: {datetime.now().strftime('%Y-%m-%d %H:%M')}",
"",
"## 📝 摘要",
content.summary or "暂无摘要",
"",
"## 🏷️ 关键词",
])
# 添加关键词标签
if content.key_points:
keyword_tags = ' '.join([f"#{kw.replace(' ', '_')}" for kw in content.key_points[:5]])
lines.append(keyword_tags)
lines.append('')
# 添加时间轴
if content.timeline:
lines.extend([
"",
"## ⏱️ 时间轴",
"",
])
for item in content.timeline:
lines.append(f"### {item.timestamp} - {item.title}")
lines.append(item.description)
lines.append('')
# 添加原始内容(折叠)
lines.extend([
"",
"## 📄 原始内容",
"",
"```",
content.transcript[:2000] + "..." if len(content.transcript) > 2000 else content.transcript,
"```",
"",
"---",
f"*由 Obsidian KB 自动生成 - {datetime.now().strftime('%Y-%m-%d %H:%M')}*"
])
return '\n'.join(lines)
def _sync_to_git(self, content: Content) -> None:
"""同步到Git仓库"""
if not self.git_client:
print("[Pipeline] Git客户端未配置,跳过同步")
return
try:
vault_path = os.path.expanduser(self.config.obsidian.vault_path)
# 添加文件
relative_path = os.path.relpath(content.output_path, vault_path)
self.git_client.add(relative_path)
# 提交
commit_msg = self.config.git.commit_message_template.format(
title=content.title or content.id
)
self.git_client.commit(commit_msg)
# 推送(如果启用)
if self.config.git.auto_push:
self.git_client.push()
print(f"[Pipeline] Git同步完成")
except Exception as e:
print(f"[Pipeline] Git同步失败: {e}")
# Git同步失败不阻断主流程
def _sanitize_filename(self, filename: str) -> str:
"""清理文件名"""
import re
# 替换非法字符
filename = re.sub(r'[<>:"/\\|?*]', '_', filename)
# 限制长度
if len(filename) > 50:
filename = filename[:50]
return filename.strip()
def _format_duration(self, seconds: int) -> str:
"""格式化时长"""
hours = seconds // 3600
minutes = (seconds % 3600) // 60
secs = seconds % 60
if hours > 0:
return f"{hours}小时{minutes}分钟"
elif minutes > 0:
return f"{minutes}分钟{secs}秒"
else:
return f"{secs}秒"