bilisum 1.13.2 → 1.13.3

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 (125) hide show
  1. package/README.md +4 -4
  2. package/bin/bilisum.js +157 -76
  3. package/package.json +7 -2
  4. package/runtime/VERSION +1 -0
  5. package/runtime/apps/service/pyproject.toml +29 -0
  6. package/runtime/apps/service/src/video_sum_service/__init__.py +1 -0
  7. package/runtime/apps/service/src/video_sum_service/__main__.py +5 -0
  8. package/runtime/apps/service/src/video_sum_service/app.py +480 -0
  9. package/runtime/apps/service/src/video_sum_service/context.py +24 -0
  10. package/runtime/apps/service/src/video_sum_service/integrations.py +264 -0
  11. package/runtime/apps/service/src/video_sum_service/knowledge/__init__.py +5 -0
  12. package/runtime/apps/service/src/video_sum_service/knowledge/index_service.py +408 -0
  13. package/runtime/apps/service/src/video_sum_service/knowledge/local_llm.py +319 -0
  14. package/runtime/apps/service/src/video_sum_service/knowledge/rag_service.py +492 -0
  15. package/runtime/apps/service/src/video_sum_service/knowledge/tag_service.py +242 -0
  16. package/runtime/apps/service/src/video_sum_service/main.py +31 -0
  17. package/runtime/apps/service/src/video_sum_service/repository.py +942 -0
  18. package/runtime/apps/service/src/video_sum_service/routers/__init__.py +1 -0
  19. package/runtime/apps/service/src/video_sum_service/routers/knowledge.py +272 -0
  20. package/runtime/apps/service/src/video_sum_service/routers/system.py +280 -0
  21. package/runtime/apps/service/src/video_sum_service/routers/tasks.py +287 -0
  22. package/runtime/apps/service/src/video_sum_service/routers/videos.py +766 -0
  23. package/runtime/apps/service/src/video_sum_service/runtime_support.py +1007 -0
  24. package/runtime/apps/service/src/video_sum_service/schemas.py +418 -0
  25. package/runtime/apps/service/src/video_sum_service/settings_manager.py +130 -0
  26. package/runtime/apps/service/src/video_sum_service/task_artifacts.py +102 -0
  27. package/runtime/apps/service/src/video_sum_service/task_exports.py +108 -0
  28. package/runtime/apps/service/src/video_sum_service/transcribe_worker.py +9 -0
  29. package/runtime/apps/service/src/video_sum_service/video_assets.py +623 -0
  30. package/runtime/apps/service/src/video_sum_service/worker.py +381 -0
  31. package/runtime/apps/web/static/apple-touch-icon.png +0 -0
  32. package/runtime/apps/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  33. package/runtime/apps/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  34. package/runtime/apps/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  35. package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  36. package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  37. package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  38. package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  39. package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  40. package/runtime/apps/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  41. package/runtime/apps/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  42. package/runtime/apps/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  43. package/runtime/apps/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  44. package/runtime/apps/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  45. package/runtime/apps/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  46. package/runtime/apps/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  47. package/runtime/apps/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  48. package/runtime/apps/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  49. package/runtime/apps/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  50. package/runtime/apps/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  51. package/runtime/apps/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  52. package/runtime/apps/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  53. package/runtime/apps/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  54. package/runtime/apps/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  55. package/runtime/apps/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  56. package/runtime/apps/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  57. package/runtime/apps/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  58. package/runtime/apps/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  59. package/runtime/apps/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  60. package/runtime/apps/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  61. package/runtime/apps/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  62. package/runtime/apps/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  63. package/runtime/apps/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  64. package/runtime/apps/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  65. package/runtime/apps/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  66. package/runtime/apps/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  67. package/runtime/apps/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  68. package/runtime/apps/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  69. package/runtime/apps/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  70. package/runtime/apps/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  71. package/runtime/apps/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  72. package/runtime/apps/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  73. package/runtime/apps/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  74. package/runtime/apps/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  75. package/runtime/apps/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  76. package/runtime/apps/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  77. package/runtime/apps/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  78. package/runtime/apps/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  79. package/runtime/apps/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  80. package/runtime/apps/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  81. package/runtime/apps/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  82. package/runtime/apps/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  83. package/runtime/apps/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  84. package/runtime/apps/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  85. package/runtime/apps/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  86. package/runtime/apps/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  87. package/runtime/apps/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  88. package/runtime/apps/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  89. package/runtime/apps/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  90. package/runtime/apps/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  91. package/runtime/apps/web/static/assets/icons/icon-180.png +0 -0
  92. package/runtime/apps/web/static/assets/icons/icon-512.png +0 -0
  93. package/runtime/apps/web/static/assets/icons/icon.svg +17 -0
  94. package/runtime/apps/web/static/assets/index-Cj5PVjQg.js +388 -0
  95. package/runtime/apps/web/static/assets/index-DU2t4_7s.css +1 -0
  96. package/runtime/apps/web/static/favicon-32x32.png +0 -0
  97. package/runtime/apps/web/static/favicon.ico +0 -0
  98. package/runtime/apps/web/static/favicon.svg +17 -0
  99. package/runtime/apps/web/static/index.html +19 -0
  100. package/runtime/apps/web/static/js/api.js +123 -0
  101. package/runtime/apps/web/static/js/main.js +932 -0
  102. package/runtime/apps/web/static/js/state.js +28 -0
  103. package/runtime/apps/web/static/js/utils.js +81 -0
  104. package/runtime/apps/web/static/js/views/home.js +604 -0
  105. package/runtime/apps/web/static/js/views/settings.js +477 -0
  106. package/runtime/apps/web/static/styles.css +2206 -0
  107. package/runtime/packages/core/pyproject.toml +18 -0
  108. package/runtime/packages/core/src/video_sum_core/__init__.py +1 -0
  109. package/runtime/packages/core/src/video_sum_core/errors.py +22 -0
  110. package/runtime/packages/core/src/video_sum_core/markdown_exports.py +154 -0
  111. package/runtime/packages/core/src/video_sum_core/models/__init__.py +1 -0
  112. package/runtime/packages/core/src/video_sum_core/models/tasks.py +70 -0
  113. package/runtime/packages/core/src/video_sum_core/pipeline/__init__.py +1 -0
  114. package/runtime/packages/core/src/video_sum_core/pipeline/base.py +46 -0
  115. package/runtime/packages/core/src/video_sum_core/pipeline/real.py +3467 -0
  116. package/runtime/packages/core/src/video_sum_core/transcribe_subprocess.py +173 -0
  117. package/runtime/packages/core/src/video_sum_core/utils.py +127 -0
  118. package/runtime/packages/infra/pyproject.toml +16 -0
  119. package/runtime/packages/infra/src/video_sum_infra/__init__.py +1 -0
  120. package/runtime/packages/infra/src/video_sum_infra/app.py +39 -0
  121. package/runtime/packages/infra/src/video_sum_infra/config.py +393 -0
  122. package/runtime/packages/infra/src/video_sum_infra/db.py +34 -0
  123. package/runtime/packages/infra/src/video_sum_infra/logging.py +54 -0
  124. package/runtime/packages/infra/src/video_sum_infra/paths.py +6 -0
  125. package/runtime/packages/infra/src/video_sum_infra/runtime.py +485 -0
@@ -0,0 +1,18 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27.0"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "video-sum-core"
7
+ version = "1.13.3"
8
+ description = "Core domain logic for the local video summarizer."
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "pydantic>=2.10,<3.0",
12
+ "yt-dlp>=2025.3.31",
13
+ "httpx>=0.28,<1.0",
14
+ "video-sum-infra",
15
+ ]
16
+
17
+ [tool.hatch.build.targets.wheel]
18
+ packages = ["src/video_sum_core"]
@@ -0,0 +1 @@
1
+ """Core package for local video summarization."""
@@ -0,0 +1,22 @@
1
+ class VideoSumError(Exception):
2
+ """Base application error for core domain logic."""
3
+
4
+
5
+ class LLMConfigurationError(VideoSumError):
6
+ """Raised when LLM settings are incomplete or invalid for the current request."""
7
+
8
+
9
+ class LLMAuthenticationError(VideoSumError):
10
+ """Raised when the upstream LLM provider rejects the supplied credentials."""
11
+
12
+
13
+ class TranscriptionConfigurationError(VideoSumError):
14
+ """Raised when transcription provider settings are incomplete or invalid."""
15
+
16
+
17
+ class TranscriptionAuthenticationError(VideoSumError):
18
+ """Raised when the upstream transcription provider rejects the supplied credentials."""
19
+
20
+
21
+ class UnsupportedInputError(VideoSumError):
22
+ """Raised when an input type is not supported by the current pipeline."""
@@ -0,0 +1,154 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ import json
5
+ import re
6
+ from pathlib import Path
7
+
8
+ from video_sum_core.utils import sanitize_filename
9
+
10
+
11
+ def build_export_filename(title: str, export_time: datetime) -> str:
12
+ safe_title = re.sub(r"\s+", " ", sanitize_filename(str(title or "").strip())).strip(" .")
13
+ if not safe_title:
14
+ safe_title = "BiliSum Note"
15
+ return f"{safe_title} {export_time.date().isoformat()}.md"
16
+
17
+
18
+ def build_task_markdown_export(
19
+ *,
20
+ title: str,
21
+ overview: str,
22
+ knowledge_note_markdown: str,
23
+ key_points: list[str],
24
+ timeline: list[dict[str, object]],
25
+ source_url: str,
26
+ platform: str,
27
+ video_id: str | None,
28
+ canonical_id: str | None,
29
+ task_id: str,
30
+ created_at: datetime,
31
+ exported_at: datetime,
32
+ tags: list[str],
33
+ target: str = "obsidian",
34
+ mindmap_path: str | None = None,
35
+ ) -> str:
36
+ normalized_title = str(title or "").strip() or "BiliSum 知识笔记"
37
+ body = _build_markdown_body(
38
+ title=normalized_title,
39
+ overview=overview,
40
+ knowledge_note_markdown=knowledge_note_markdown,
41
+ key_points=key_points,
42
+ timeline=timeline,
43
+ mindmap_path=mindmap_path,
44
+ )
45
+ if str(target or "obsidian").strip().lower() != "obsidian":
46
+ return body
47
+
48
+ frontmatter = _build_frontmatter(
49
+ {
50
+ "title": normalized_title,
51
+ "source_url": str(source_url or ""),
52
+ "platform": str(platform or ""),
53
+ "video_id": str(video_id or ""),
54
+ "canonical_id": str(canonical_id or ""),
55
+ "task_id": str(task_id or ""),
56
+ "created_at": created_at.isoformat(),
57
+ "exported_at": exported_at.isoformat(),
58
+ "tags": [str(tag).strip() for tag in tags if str(tag).strip()],
59
+ }
60
+ )
61
+ return f"{frontmatter}\n\n{body}".strip()
62
+
63
+
64
+ def _build_frontmatter(payload: dict[str, object]) -> str:
65
+ lines = ["---"]
66
+ for key, value in payload.items():
67
+ if isinstance(value, list):
68
+ lines.append(f"{key}:")
69
+ for item in value:
70
+ lines.append(f" - {json.dumps(str(item), ensure_ascii=False)}")
71
+ continue
72
+ lines.append(f"{key}: {json.dumps(str(value or ''), ensure_ascii=False)}")
73
+ lines.append("---")
74
+ return "\n".join(lines)
75
+
76
+
77
+ def _build_markdown_body(
78
+ *,
79
+ title: str,
80
+ overview: str,
81
+ knowledge_note_markdown: str,
82
+ key_points: list[str],
83
+ timeline: list[dict[str, object]],
84
+ mindmap_path: str | None,
85
+ ) -> str:
86
+ sections = [
87
+ f"# {title}",
88
+ "## 核心概览",
89
+ str(overview or "").strip() or "暂无核心概览。",
90
+ "## 关键要点",
91
+ _format_key_points(key_points),
92
+ "## 章节时间线",
93
+ _format_timeline(timeline),
94
+ "## 知识笔记",
95
+ _normalize_embedded_note(knowledge_note_markdown, title),
96
+ ]
97
+ if str(mindmap_path or "").strip():
98
+ sections.extend(
99
+ [
100
+ "## 思维导图引用信息",
101
+ f"- 导图文件:`{Path(str(mindmap_path)).as_posix()}`",
102
+ ]
103
+ )
104
+ return "\n\n".join(section.strip() for section in sections if str(section).strip())
105
+
106
+
107
+ def _format_key_points(key_points: list[str]) -> str:
108
+ items = [str(item).strip() for item in key_points if str(item).strip()]
109
+ if not items:
110
+ return "- 暂无关键要点。"
111
+ return "\n".join(f"- {item}" for item in items)
112
+
113
+
114
+ def _format_timeline(timeline: list[dict[str, object]]) -> str:
115
+ items: list[str] = []
116
+ for entry in timeline:
117
+ title = str(entry.get("title") or "").strip() or "未命名章节"
118
+ summary = str(entry.get("summary") or "").strip()
119
+ start = _format_timestamp(entry.get("start"))
120
+ line = f"- `{start}` **{title}**"
121
+ if summary:
122
+ line = f"{line}: {summary}"
123
+ items.append(line)
124
+ if not items:
125
+ return "- 暂无章节时间线。"
126
+ return "\n".join(items)
127
+
128
+
129
+ def _format_timestamp(value: object) -> str:
130
+ try:
131
+ total_seconds = max(0, int(float(value)))
132
+ except (TypeError, ValueError):
133
+ total_seconds = 0
134
+ hours, remainder = divmod(total_seconds, 3600)
135
+ minutes, seconds = divmod(remainder, 60)
136
+ if hours > 0:
137
+ return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
138
+ return f"{minutes:02d}:{seconds:02d}"
139
+
140
+
141
+ def _normalize_embedded_note(markdown: str, title: str) -> str:
142
+ text = str(markdown or "").strip()
143
+ if not text:
144
+ return "暂无知识笔记正文。"
145
+
146
+ lines = text.splitlines()
147
+ if lines and lines[0].lstrip().startswith("#"):
148
+ heading_text = re.sub(r"^#+\s*", "", lines[0]).strip()
149
+ if heading_text == title or heading_text:
150
+ lines = lines[1:]
151
+ while lines and not lines[0].strip():
152
+ lines = lines[1:]
153
+ normalized = "\n".join(lines).strip()
154
+ return normalized or "暂无知识笔记正文。"
@@ -0,0 +1 @@
1
+ """Domain models for core package."""
@@ -0,0 +1,70 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class InputType(str, Enum):
8
+ URL = "url"
9
+ VIDEO_FILE = "video_file"
10
+ AUDIO_FILE = "audio_file"
11
+ TRANSCRIPT_TEXT = "transcript_text"
12
+
13
+
14
+ class TaskStatus(str, Enum):
15
+ QUEUED = "queued"
16
+ RUNNING = "running"
17
+ COMPLETED = "completed"
18
+ FAILED = "failed"
19
+ CANCELLED = "cancelled"
20
+
21
+
22
+ class TaskOptions(BaseModel):
23
+ language: str = "zh"
24
+ summary_mode: str = "auto"
25
+ prefer_subtitles: bool = True
26
+ export_formats: list[str] = Field(default_factory=lambda: ["md", "json"])
27
+
28
+
29
+ class TaskInput(BaseModel):
30
+ input_type: InputType
31
+ source: str
32
+ title: str | None = None
33
+ platform_hint: str | None = None
34
+ options: TaskOptions = Field(default_factory=TaskOptions)
35
+
36
+
37
+ class TaskResult(BaseModel):
38
+ overview: str = ""
39
+ knowledge_note_markdown: str = ""
40
+ transcript_text: str = ""
41
+ segment_summaries: list[str] = Field(default_factory=list)
42
+ key_points: list[str] = Field(default_factory=list)
43
+ timeline: list[dict[str, object]] = Field(default_factory=list)
44
+ chapter_groups: list[dict[str, object]] = Field(default_factory=list)
45
+ artifacts: dict[str, str] = Field(default_factory=dict)
46
+ llm_prompt_tokens: int | None = None
47
+ llm_completion_tokens: int | None = None
48
+ llm_total_tokens: int | None = None
49
+ mindmap_status: str = "idle"
50
+ mindmap_error_message: str | None = None
51
+ mindmap_artifact_path: str | None = None
52
+ mindmap_updated_at: datetime | None = None
53
+
54
+
55
+ class MindMapNode(BaseModel):
56
+ id: str
57
+ label: str
58
+ type: str
59
+ summary: str = ""
60
+ children: list["MindMapNode"] = Field(default_factory=list)
61
+ time_anchor: float | None = None
62
+ source_chapter_titles: list[str] = Field(default_factory=list)
63
+ source_chapter_starts: list[float] = Field(default_factory=list)
64
+
65
+
66
+ class TaskMindMap(BaseModel):
67
+ version: int = 1
68
+ title: str
69
+ root: str
70
+ nodes: list[MindMapNode] = Field(default_factory=list)
@@ -0,0 +1 @@
1
+ """Pipeline contracts and orchestration."""
@@ -0,0 +1,46 @@
1
+ from typing import Callable
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from video_sum_core.models.tasks import TaskInput, TaskResult
6
+
7
+
8
+ class PipelineEvent(BaseModel):
9
+ stage: str
10
+ progress: int = 0
11
+ message: str
12
+ payload: dict[str, object] = Field(default_factory=dict)
13
+
14
+
15
+ class PipelineContext(BaseModel):
16
+ task_id: str
17
+ task_input: TaskInput
18
+
19
+
20
+ PipelineEventReporter = Callable[[PipelineEvent], None]
21
+
22
+
23
+ class PipelineRunner:
24
+ """Minimal orchestration contract for future pipeline implementations."""
25
+
26
+ def preflight(
27
+ self,
28
+ context: PipelineContext,
29
+ on_event: PipelineEventReporter | None = None,
30
+ ) -> None:
31
+ return None
32
+
33
+ def run(
34
+ self,
35
+ context: PipelineContext,
36
+ on_event: PipelineEventReporter | None = None,
37
+ ) -> tuple[list[PipelineEvent], TaskResult]:
38
+ event = PipelineEvent(
39
+ stage="accepted",
40
+ progress=0,
41
+ message="Pipeline scaffold is ready. Processing implementation is pending.",
42
+ )
43
+ if on_event is not None:
44
+ on_event(event)
45
+ result = TaskResult()
46
+ return [event], result