vidknot 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. vidknot-0.1.0/LICENSE +21 -0
  2. vidknot-0.1.0/PKG-INFO +40 -0
  3. vidknot-0.1.0/README.md +1 -0
  4. vidknot-0.1.0/pyproject.toml +68 -0
  5. vidknot-0.1.0/setup.cfg +4 -0
  6. vidknot-0.1.0/src/vidknot/__init__.py +25 -0
  7. vidknot-0.1.0/src/vidknot/__main__.py +275 -0
  8. vidknot-0.1.0/src/vidknot/adapters/__init__.py +13 -0
  9. vidknot-0.1.0/src/vidknot/adapters/agent_bridge.py +147 -0
  10. vidknot-0.1.0/src/vidknot/adapters/feishu_writer.py +444 -0
  11. vidknot-0.1.0/src/vidknot/adapters/mcp_server.py +386 -0
  12. vidknot-0.1.0/src/vidknot/adapters/notion_writer.py +239 -0
  13. vidknot-0.1.0/src/vidknot/adapters/obsidian_writer.py +276 -0
  14. vidknot-0.1.0/src/vidknot/adapters/yuque_writer.py +147 -0
  15. vidknot-0.1.0/src/vidknot/api.py +192 -0
  16. vidknot-0.1.0/src/vidknot/core/__init__.py +15 -0
  17. vidknot-0.1.0/src/vidknot/core/cookie_provider.py +346 -0
  18. vidknot-0.1.0/src/vidknot/core/douyin_parser.py +220 -0
  19. vidknot-0.1.0/src/vidknot/core/download_manager.py +319 -0
  20. vidknot-0.1.0/src/vidknot/core/downloader.py +773 -0
  21. vidknot-0.1.0/src/vidknot/core/processor.py +303 -0
  22. vidknot-0.1.0/src/vidknot/core/transcriber.py +157 -0
  23. vidknot-0.1.0/src/vidknot/pipeline/__init__.py +7 -0
  24. vidknot-0.1.0/src/vidknot/pipeline/video_knowledge_pipeline.py +249 -0
  25. vidknot-0.1.0/src/vidknot/utils/__init__.py +74 -0
  26. vidknot-0.1.0/src/vidknot/utils/cache_manager.py +165 -0
  27. vidknot-0.1.0/src/vidknot/utils/config_manager.py +304 -0
  28. vidknot-0.1.0/src/vidknot/utils/env_check.py +157 -0
  29. vidknot-0.1.0/src/vidknot/utils/exceptions.py +152 -0
  30. vidknot-0.1.0/src/vidknot/utils/logger.py +124 -0
  31. vidknot-0.1.0/src/vidknot.egg-info/PKG-INFO +40 -0
  32. vidknot-0.1.0/src/vidknot.egg-info/SOURCES.txt +37 -0
  33. vidknot-0.1.0/src/vidknot.egg-info/dependency_links.txt +1 -0
  34. vidknot-0.1.0/src/vidknot.egg-info/entry_points.txt +2 -0
  35. vidknot-0.1.0/src/vidknot.egg-info/requires.txt +18 -0
  36. vidknot-0.1.0/src/vidknot.egg-info/top_level.txt +1 -0
  37. vidknot-0.1.0/tests/test_config_manager.py +168 -0
  38. vidknot-0.1.0/tests/test_exceptions.py +124 -0
  39. vidknot-0.1.0/tests/test_logger.py +97 -0
vidknot-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VidkNot Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
vidknot-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: vidknot
3
+ Version: 0.1.0
4
+ Summary: VidkNot — Video Knowledge, Knotted. 一键将视频链接转换为结构化笔记,支持飞书、语雀、Notion、Obsidian 等多平台存储。
5
+ Author: VidkNot Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/suonian/vidknot
8
+ Project-URL: Repository, https://github.com/suonian/vidknot
9
+ Project-URL: Issues, https://github.com/suonian/vidknot/issues
10
+ Keywords: video,transcription,AI,notes,knowledge-base,feishu,yuque,notion,obsidian,markdown
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: yt-dlp>=2026.3.17
24
+ Requires-Dist: fastapi>=0.136.0
25
+ Requires-Dist: uvicorn[standard]>=0.27.0
26
+ Requires-Dist: httpx>=0.27.0
27
+ Requires-Dist: pyyaml>=6.0.1
28
+ Requires-Dist: openai>=1.0.0
29
+ Provides-Extra: feishu
30
+ Requires-Dist: feishu-docx>=0.1.0; extra == "feishu"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
33
+ Requires-Dist: ruff>=0.2.0; extra == "dev"
34
+ Provides-Extra: all
35
+ Requires-Dist: feishu-docx>=0.1.0; extra == "all"
36
+ Requires-Dist: pytest>=8.0.0; extra == "all"
37
+ Requires-Dist: ruff>=0.2.0; extra == "all"
38
+ Dynamic: license-file
39
+
40
+ - 📖 [文档改进](https://github.com/suonian/vidknot/issues)
@@ -0,0 +1 @@
1
+ - 📖 [文档改进](https://github.com/suonian/vidknot/issues)
@@ -0,0 +1,68 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vidknot"
7
+ version = "0.1.0"
8
+ description = "VidkNot — Video Knowledge, Knotted. 一键将视频链接转换为结构化笔记,支持飞书、语雀、Notion、Obsidian 等多平台存储。"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ authors = [{name = "VidkNot Team"}]
12
+ keywords = ["video", "transcription", "AI", "notes", "knowledge-base", "feishu", "yuque", "notion", "obsidian", "markdown"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Topic :: Multimedia :: Sound/Audio :: Speech",
22
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
23
+ ]
24
+ requires-python = ">=3.10"
25
+ dependencies = [
26
+ "yt-dlp>=2026.3.17",
27
+ "fastapi>=0.136.0",
28
+ "uvicorn[standard]>=0.27.0",
29
+ "httpx>=0.27.0",
30
+ "pyyaml>=6.0.1",
31
+ "openai>=1.0.0",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ feishu = ["feishu-docx>=0.1.0"]
36
+ dev = [
37
+ "pytest>=8.0.0",
38
+ "ruff>=0.2.0",
39
+ ]
40
+ all = [
41
+ "feishu-docx>=0.1.0",
42
+ "pytest>=8.0.0",
43
+ "ruff>=0.2.0",
44
+ ]
45
+
46
+ [project.scripts]
47
+ vidknot = "vidknot.__main__:main"
48
+
49
+ [project.urls]
50
+ Homepage = "https://github.com/suonian/vidknot"
51
+ Repository = "https://github.com/suonian/vidknot"
52
+ Issues = "https://github.com/suonian/vidknot/issues"
53
+
54
+ [tool.setuptools.packages.find]
55
+ where = ["src"]
56
+
57
+ [tool.setuptools.package-data]
58
+ vidknot = ["*.yaml", "*.yml"]
59
+
60
+ [tool.ruff]
61
+ line-length = 100
62
+ target-version = "py310"
63
+ select = ["E", "F", "I", "N", "W", "UP"]
64
+ ignore = ["E501"]
65
+
66
+ [tool.pytest.ini_options]
67
+ testpaths = ["tests"]
68
+ addopts = "-v --tb=short"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,25 @@
1
+ """
2
+ VidkNot - Video Knowledge, Knotted.
3
+
4
+ Tie your video knowledge together.
5
+ 视频知识,结成一网。
6
+
7
+ Core module for Agent-driven video knowledge pipeline.
8
+ """
9
+
10
+ __version__ = "0.1.0"
11
+ __author__ = "VidkNot Team"
12
+ __license__ = "MIT"
13
+
14
+ from .core.downloader import VideoDownloader
15
+ from .core.transcriber import SiliconFlowASR, get_transcriber
16
+ from .core.processor import ContentProcessor
17
+ from .pipeline.video_knowledge_pipeline import VideoKnowledgePipeline
18
+
19
+ __all__ = [
20
+ "VideoDownloader",
21
+ "SiliconFlowASR",
22
+ "get_transcriber",
23
+ "ContentProcessor",
24
+ "VideoKnowledgePipeline",
25
+ ]
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ VidkNot 统一入口
4
+
5
+ 支持三种运行模式:
6
+ - CLI: python -m vidknot <url> [--destination feishu|yuque|notion|obsidian|both|none]
7
+ - MCP: python -m vidknot --mcp
8
+ - FastAPI: uvicorn vidknot.api:app
9
+ """
10
+
11
+ import sys
12
+ import argparse
13
+ from pathlib import Path
14
+
15
+ from .utils.logger import get_logger
16
+
17
+ logger = get_logger("vidknot.cli")
18
+
19
+
20
+ def main():
21
+ """智能检测运行模式并启动对应服务"""
22
+ parser = argparse.ArgumentParser(
23
+ description="VidkNot — Video Knowledge, Knotted. 将视频链接转换为结构化笔记,自动保存到飞书文档或 Obsidian Vault。",
24
+ formatter_class=argparse.RawDescriptionHelpFormatter,
25
+ epilog="""
26
+ 运行模式:
27
+ CLI 模式: python -m vidknot <url> [--destination feishu|yuque|notion|obsidian|both|none]
28
+ MCP 模式: python -m vidknot --mcp
29
+ API 模式: uvicorn vidknot.api:app --reload
30
+
31
+ 示例:
32
+ # 生成笔记并保存到 Obsidian (默认)
33
+ python -m vidknot https://youtube.com/watch?v=xxx
34
+
35
+ # 保存到飞书文档
36
+ python -m vidknot https://bilibili.com/video/BVxxx --destination feishu
37
+
38
+ # 仅返回笔记内容,不保存
39
+ python -m vidknot https://youtube.com/watch?v=xxx --destination none
40
+
41
+ # 启动 MCP Server (用于 OpenClaw / Claude Desktop)
42
+ python -m vidknot --mcp
43
+ """
44
+ )
45
+
46
+ # 运行模式参数
47
+ parser.add_argument("--mcp", action="store_true",
48
+ help="启动 MCP Stdio 模式 (用于 OpenClaw / Claude Desktop)")
49
+ parser.add_argument("--cli", action="store_true",
50
+ help="强制使用 CLI 模式 (默认自动检测)")
51
+ parser.add_argument("--check-env", action="store_true",
52
+ help="检查运行环境依赖是否满足")
53
+
54
+ # CLI 参数
55
+ parser.add_argument("url", nargs="?",
56
+ help="视频 URL (CLI 模式)")
57
+ parser.add_argument("--raw", action="store_true",
58
+ help="仅输出原始转录文本")
59
+ parser.add_argument("--summary", action="store_true",
60
+ help="生成结构化笔记 (默认)")
61
+ parser.add_argument("--destination", "-d", default="obsidian",
62
+ choices=["feishu", "yuque", "notion", "obsidian", "both", "none"],
63
+ help="笔记保存目的地: feishu(飞书)/yuque(语雀)/notion/obsidian(本地)/both(所有)/none(仅返回)")
64
+ parser.add_argument("--language", "-l", default="auto",
65
+ help="视频语言: auto/zh/en/ja/ko (默认: auto)")
66
+ parser.add_argument("--output", "-o",
67
+ help="输出文件路径 (当 destination=none 时)")
68
+ parser.add_argument("--no-cache", action="store_true",
69
+ help="禁用缓存")
70
+ parser.add_argument("--feishu-folder",
71
+ help="飞书文档保存的文件夹名称")
72
+ parser.add_argument("--obsidian-tags", nargs="+", default=[],
73
+ help="Obsidian 笔记标签")
74
+ parser.add_argument("--notify", action="store_true", default=True,
75
+ help="处理完成后发送通知 (通过 OpenClaw 消息回复)")
76
+
77
+ args = parser.parse_args()
78
+
79
+ # 模式检测优先级: check-env > MCP > CLI (有url) > Help
80
+ if args.check_env:
81
+ from .utils.env_check import check_all_requirements, get_install_guide
82
+ all_ok, messages = check_all_requirements()
83
+ for msg in messages:
84
+ print(msg)
85
+ if not all_ok:
86
+ print("\n" + get_install_guide())
87
+ sys.exit(1)
88
+ print("\n[OK] 所有环境检查通过")
89
+ sys.exit(0)
90
+
91
+ if args.mcp:
92
+ # MCP Stdio 模式
93
+ from .adapters.mcp_server import run_mcp_server
94
+ run_mcp_server()
95
+
96
+ elif args.url or args.cli:
97
+ # CLI 模式
98
+ if not args.url and args.cli:
99
+ parser.error("CLI 模式需要提供 URL 参数")
100
+ run_cli(args)
101
+
102
+ else:
103
+ # 检查是否通过 uvicorn 调用 (FastAPI 模式)
104
+ if "uvicorn" in sys.argv[0] or any("uvicorn" in arg for arg in sys.argv):
105
+ print("启动 FastAPI 模式,请访问 http://localhost:8000/docs")
106
+ return
107
+
108
+ parser.print_help()
109
+ sys.exit(1)
110
+
111
+
112
+ def run_cli(args):
113
+ """CLI 模式主逻辑(同步)"""
114
+ from .core.downloader import VideoDownloader
115
+ from .core.transcriber import SiliconFlowASR
116
+ from .core.processor import ContentProcessor
117
+ from .utils.cache_manager import CacheManager
118
+ from .utils.env_check import check_ffmpeg, check_all_requirements
119
+ from .pipeline.video_knowledge_pipeline import VideoKnowledgePipeline
120
+
121
+ logger.info("VidkNot v0.1.0 — Video Knowledge, Knotted.")
122
+
123
+ # 环境检查
124
+ logger.info("检查运行环境...")
125
+ all_ok, messages = check_all_requirements()
126
+ if not all_ok:
127
+ for msg in messages:
128
+ if "❌" in msg:
129
+ logger.error(msg)
130
+ logger.error("请安装缺失的依赖后重试")
131
+ sys.exit(1)
132
+
133
+ ffmpeg_ok, ffmpeg_path = check_ffmpeg()
134
+ logger.info(f"FFmpeg: {ffmpeg_path}")
135
+
136
+ # 确定模式
137
+ mode = "raw" if args.raw else "summary"
138
+ destination = args.destination
139
+
140
+ url = args.url
141
+ logger.info(f"正在处理: {url}")
142
+ logger.info(f"处理模式: {'结构化笔记' if mode == 'summary' else '原始转录'}")
143
+ logger.info(f"目的地: {destination}")
144
+
145
+ # 检查缓存
146
+ cache = CacheManager()
147
+ if not args.no_cache:
148
+ cached = cache.get(url, mode)
149
+ if cached:
150
+ logger.info("命中缓存!")
151
+ result = cached
152
+ else:
153
+ result = process_video(url, mode, args.language)
154
+ cache.set(url, mode, result)
155
+ else:
156
+ result = process_video(url, mode, args.language)
157
+
158
+ # 根据目的地路由
159
+ if destination != "none":
160
+ pipeline = VideoKnowledgePipeline(
161
+ destination=destination,
162
+ format=mode,
163
+ language=args.language,
164
+ )
165
+ saved = pipeline.save(result, {
166
+ "feishu_folder": args.feishu_folder,
167
+ "obsidian_tags": args.obsidian_tags,
168
+ })
169
+ logger.info(f"已保存到: {saved}")
170
+
171
+ # 输出结果
172
+ if mode == "summary":
173
+ output = result.get("markdown", "")
174
+ else:
175
+ output = result.get("transcription", "")
176
+
177
+ if args.output:
178
+ with open(args.output, "w", encoding="utf-8") as f:
179
+ f.write(output)
180
+ logger.info(f"结果已保存: {args.output}")
181
+ else:
182
+ print("\n" + "=" * 60)
183
+ print(output)
184
+ print("=" * 60)
185
+
186
+
187
+ def generate_images_markdown(metadata: dict, image_paths: list) -> str:
188
+ """为纯图片内容生成 Markdown"""
189
+ from datetime import date
190
+
191
+ title = metadata.get("title", "小红书图片笔记")
192
+ url = metadata.get("url", "")
193
+ image_dir = metadata.get("image_paths", [])
194
+
195
+ lines = [
196
+ "---",
197
+ f"title: {title}",
198
+ f"source: {url}",
199
+ f"date: {date.today().isoformat()}",
200
+ f"platform: xiaohongshu",
201
+ f"type: images",
202
+ "---",
203
+ "",
204
+ f"# {title}",
205
+ "",
206
+ f"来源: {url}",
207
+ "",
208
+ f"图片数量: {len(image_paths)} 张",
209
+ "",
210
+ "## 图片列表",
211
+ "",
212
+ ]
213
+
214
+ for i, img_path in enumerate(image_paths, 1):
215
+ from pathlib import Path
216
+ filename = Path(img_path).name
217
+ lines.append(f"### 图片 {i}")
218
+ lines.append(f"![图片 {i}]({filename})")
219
+ lines.append("")
220
+
221
+ lines.append("---")
222
+ lines.append(f"*图片保存目录: {image_dir[0].rsplit('/', 1)[0] if image_dir else '未知'}*")
223
+
224
+ return "\n".join(lines)
225
+
226
+
227
+ def process_video(url: str, mode: str, language: str) -> dict:
228
+ """处理视频的完整流程(同步)"""
229
+ from .core.downloader import VideoDownloader
230
+ from .core.transcriber import SiliconFlowASR
231
+ from .core.processor import ContentProcessor
232
+
233
+ logger.info("正在下载...")
234
+ downloader = VideoDownloader()
235
+ file_path, metadata = downloader.download_audio_with_metadata(url)
236
+ logger.info(f"下载完成: {metadata.get('title', 'Unknown')}")
237
+
238
+ result = {
239
+ "title": metadata.get("title"),
240
+ "author": metadata.get("uploader"),
241
+ "duration": metadata.get("duration"),
242
+ "source_url": url,
243
+ "transcription": "",
244
+ "metadata": metadata,
245
+ }
246
+
247
+ if metadata.get("is_images_only"):
248
+ image_count = metadata.get("image_count", 0)
249
+ image_paths = metadata.get("image_paths", [])
250
+ result["transcription"] = ""
251
+ result["markdown"] = generate_images_markdown(metadata, image_paths)
252
+ result["is_images_only"] = True
253
+ logger.info(f"纯图片笔记: {image_count} 张图片")
254
+ return result
255
+
256
+ logger.info("正在转录...")
257
+ transcriber = SiliconFlowASR()
258
+ transcription = transcriber.transcribe(file_path, language=language)
259
+ logger.info(f"转录完成: {len(transcription)} 字符")
260
+ result["transcription"] = transcription
261
+
262
+ if mode == "summary":
263
+ logger.info("正在生成结构化笔记...")
264
+ processor = ContentProcessor()
265
+ processed = processor.summarize(transcription, metadata)
266
+ result["markdown"] = processed["markdown"]
267
+ logger.info("笔记生成完成")
268
+ else:
269
+ result["markdown"] = transcription
270
+
271
+ return result
272
+
273
+
274
+ if __name__ == "__main__":
275
+ main()
@@ -0,0 +1,13 @@
1
+ """
2
+ VidkNot 适配器模块
3
+ """
4
+
5
+ from .mcp_server import MCPServer, run_mcp_server
6
+ from .agent_bridge import get_tool_metadata, execute_tool
7
+
8
+ __all__ = [
9
+ "MCPServer",
10
+ "run_mcp_server",
11
+ "get_tool_metadata",
12
+ "execute_tool",
13
+ ]
@@ -0,0 +1,147 @@
1
+ """
2
+ VidkNot Agent 桥接模块
3
+
4
+ 提供 OpenAI Function Calling Schema 定义
5
+ 用于 OpenAI / Claude 等 AI Agent 集成
6
+ """
7
+
8
+ import asyncio
9
+ import json
10
+ from typing import Dict, Any, Optional
11
+
12
+
13
+ def get_tool_metadata() -> Dict[str, Any]:
14
+ """
15
+ 获取 VidkNot 工具的 OpenAI Function Calling Schema
16
+
17
+ Returns:
18
+ OpenAI tool schema dict
19
+ """
20
+ return {
21
+ "type": "function",
22
+ "function": {
23
+ "name": "video_knowledge",
24
+ "description": """将视频链接转换为结构化笔记,并自动保存到飞书文档或 Obsidian Vault。
25
+
26
+ 适用于以下场景:
27
+ - 用户转发视频链接后,AI Agent 自动生成学习笔记
28
+ - 将视频内容整理为可搜索的 Markdown 笔记
29
+ - 自动提取视频要点、章节、关键信息
30
+
31
+ 支持的平台:YouTube、Bilibili、抖音、小红书、微博、Twitter/X 等 20+ 平台
32
+
33
+ 输出:结构化的 Markdown 笔记,包含 YAML Frontmatter(标题、作者、时长、标签等)""",
34
+ "parameters": {
35
+ "type": "object",
36
+ "properties": {
37
+ "url": {
38
+ "type": "string",
39
+ "description": "视频链接,支持 YouTube、Bilibili、抖音、小红书、微博、Twitter/X 等平台"
40
+ },
41
+ "destination": {
42
+ "type": "string",
43
+ "enum": ["feishu", "obsidian", "both", "none"],
44
+ "description": "笔记保存目的地:feishu=飞书云文档,obsidian=本地 Obsidian Vault,both=两者同时,none=仅返回内容",
45
+ "default": "obsidian"
46
+ },
47
+ "format": {
48
+ "type": "string",
49
+ "enum": ["structured", "raw"],
50
+ "description": "structured=结构化笔记(含要点、详细内容、标签),raw=仅原始转录",
51
+ "default": "structured"
52
+ },
53
+ "language": {
54
+ "type": "string",
55
+ "description": "视频语言:auto(自动检测)/ zh / en / ja / ko",
56
+ "default": "auto"
57
+ },
58
+ "feishu_folder": {
59
+ "type": "string",
60
+ "description": "飞书文档保存的文件夹名称(如:视频笔记、学习资料)"
61
+ },
62
+ "obsidian_tags": {
63
+ "type": "array",
64
+ "items": {"type": "string"},
65
+ "description": "Obsidian 笔记标签(自动添加到 YAML Frontmatter 的 tags 字段)"
66
+ },
67
+ "notify": {
68
+ "type": "boolean",
69
+ "description": "处理完成后是否发送通知(通过 Agent 消息回复用户)",
70
+ "default": True
71
+ }
72
+ },
73
+ "required": ["url"]
74
+ }
75
+ }
76
+ }
77
+
78
+
79
+ def execute_tool(arguments: Dict[str, Any]) -> Dict[str, Any]:
80
+ """
81
+ 执行 video_knowledge 工具
82
+
83
+ Args:
84
+ arguments: 工具参数
85
+
86
+ Returns:
87
+ 工具执行结果
88
+ """
89
+ from ..pipeline.video_knowledge_pipeline import VideoKnowledgePipeline
90
+
91
+ url = arguments.get("url")
92
+ destination = arguments.get("destination", "obsidian")
93
+ format_mode = arguments.get("format", "structured")
94
+ language = arguments.get("language", "auto")
95
+ feishu_folder = arguments.get("feishu_folder")
96
+ obsidian_tags = arguments.get("obsidian_tags", [])
97
+ notify = arguments.get("notify", True)
98
+
99
+ if not url:
100
+ return {
101
+ "success": False,
102
+ "error": "URL is required"
103
+ }
104
+
105
+ try:
106
+ pipeline = VideoKnowledgePipeline(
107
+ destination=destination,
108
+ format=format_mode,
109
+ language=language,
110
+ )
111
+
112
+ result = asyncio.run(pipeline.run(url))
113
+
114
+ if destination != "none":
115
+ saved = asyncio.run(
116
+ pipeline.save(result, {
117
+ "feishu_folder": feishu_folder,
118
+ "obsidian_tags": obsidian_tags,
119
+ })
120
+ )
121
+ result["saved_to"] = saved
122
+
123
+ if notify:
124
+ dest_label = {
125
+ "feishu": "飞书文档",
126
+ "obsidian": "Obsidian Vault",
127
+ "both": "飞书文档和 Obsidian",
128
+ }.get(destination, destination)
129
+ result["notify_message"] = f"✅ 笔记已生成并保存到 {dest_label}!"
130
+
131
+ return result
132
+
133
+ except Exception as e:
134
+ # 分类异常,返回结构化错误
135
+ from ..utils.exceptions import VidkNotError
136
+ if isinstance(e, VidkNotError):
137
+ return {
138
+ "success": False,
139
+ "error": e.message,
140
+ "error_details": e.details,
141
+ "error_type": e.__class__.__name__,
142
+ }
143
+ return {
144
+ "success": False,
145
+ "error": str(e),
146
+ "error_type": e.__class__.__name__,
147
+ }