openmd-cli 0.1.0__py3-none-any.whl

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 (177) hide show
  1. openmd/__init__.py +3 -0
  2. openmd/__main__.py +5 -0
  3. openmd/adapters/__init__.py +1 -0
  4. openmd/adapters/base.py +95 -0
  5. openmd/adapters/bilibili/__init__.py +11 -0
  6. openmd/adapters/bilibili/adapter.py +104 -0
  7. openmd/adapters/bilibili/discovery.py +33 -0
  8. openmd/adapters/douyin/__init__.py +11 -0
  9. openmd/adapters/douyin/adapter.py +117 -0
  10. openmd/adapters/douyin/discovery.py +36 -0
  11. openmd/adapters/douyin/sidecar.py +77 -0
  12. openmd/adapters/generic_media/__init__.py +14 -0
  13. openmd/adapters/high_privacy.py +77 -0
  14. openmd/adapters/local_audio/__init__.py +14 -0
  15. openmd/adapters/local_audio/adapter.py +169 -0
  16. openmd/adapters/local_audio/asr.py +58 -0
  17. openmd/adapters/local_audio/backends/__init__.py +1 -0
  18. openmd/adapters/local_audio/backends/auto.py +177 -0
  19. openmd/adapters/local_audio/backends/faster_whisper.py +285 -0
  20. openmd/adapters/local_audio/backends/funasr_sense_voice.py +14 -0
  21. openmd/adapters/local_audio/backends/qwen3_asr.py +254 -0
  22. openmd/adapters/opencli.py +770 -0
  23. openmd/adapters/pdf/__init__.py +11 -0
  24. openmd/adapters/pdf/adapter.py +153 -0
  25. openmd/adapters/pdf/backends/__init__.py +1 -0
  26. openmd/adapters/pdf/backends/markitdown.py +165 -0
  27. openmd/adapters/pdf/parser.py +56 -0
  28. openmd/adapters/qq_group/__init__.py +11 -0
  29. openmd/adapters/qq_group/adapter.py +289 -0
  30. openmd/adapters/qq_group/parsers/__init__.py +1 -0
  31. openmd/adapters/qq_group/parsers/html.py +60 -0
  32. openmd/adapters/qq_group/parsers/json.py +71 -0
  33. openmd/adapters/registry.py +149 -0
  34. openmd/adapters/screenshot/__init__.py +11 -0
  35. openmd/adapters/screenshot/adapter.py +154 -0
  36. openmd/adapters/screenshot/backends/__init__.py +1 -0
  37. openmd/adapters/screenshot/backends/paddle.py +15 -0
  38. openmd/adapters/screenshot/backends/rapidocr.py +176 -0
  39. openmd/adapters/screenshot/backends/tesseract.py +15 -0
  40. openmd/adapters/screenshot/ocr.py +84 -0
  41. openmd/adapters/wechat_group/__init__.py +11 -0
  42. openmd/adapters/wechat_group/adapter.py +160 -0
  43. openmd/adapters/wechat_group/wx_cli.py +268 -0
  44. openmd/adapters/wechat_moments/__init__.py +11 -0
  45. openmd/adapters/wechat_moments/adapter.py +167 -0
  46. openmd/adapters/wechat_moments/wx_cli.py +265 -0
  47. openmd/adapters/weixin/__init__.py +11 -0
  48. openmd/adapters/weixin/adapter.py +148 -0
  49. openmd/adapters/weixin/discovery.py +510 -0
  50. openmd/adapters/weixin/report.py +278 -0
  51. openmd/adapters/xiaohongshu.py +158 -0
  52. openmd/backends/__init__.py +1 -0
  53. openmd/backends/base.py +204 -0
  54. openmd/backends/bilibili.py +1480 -0
  55. openmd/backends/bilibili_wbi.py +277 -0
  56. openmd/backends/discourse.py +426 -0
  57. openmd/backends/douyin.py +868 -0
  58. openmd/backends/douyin_detail.py +124 -0
  59. openmd/backends/douyin_providers.py +602 -0
  60. openmd/backends/executor.py +546 -0
  61. openmd/backends/feishu.py +861 -0
  62. openmd/backends/generic_media.py +77 -0
  63. openmd/backends/interactive.py +60 -0
  64. openmd/backends/ocr.py +41 -0
  65. openmd/backends/opencli.py +82 -0
  66. openmd/backends/pdf.py +122 -0
  67. openmd/backends/registry.py +234 -0
  68. openmd/backends/screenshot.py +79 -0
  69. openmd/backends/video_metadata.py +167 -0
  70. openmd/backends/web_article.py +363 -0
  71. openmd/backends/wechat_group.py +35 -0
  72. openmd/backends/wechat_moments.py +35 -0
  73. openmd/backends/weixin.py +767 -0
  74. openmd/backends/x.py +455 -0
  75. openmd/backends/xiaoe.py +940 -0
  76. openmd/backends/xiaohongshu.py +911 -0
  77. openmd/backends/xiaohongshu_providers.py +876 -0
  78. openmd/backends/xiaoyuzhou.py +1017 -0
  79. openmd/backends/youtube.py +280 -0
  80. openmd/backends/youtube_discovery.py +1245 -0
  81. openmd/backends/youtube_transcript.py +213 -0
  82. openmd/browser_extension/background.js +231 -0
  83. openmd/browser_extension/icons/icon-128.png +0 -0
  84. openmd/browser_extension/icons/icon-16.png +0 -0
  85. openmd/browser_extension/icons/icon-32.png +0 -0
  86. openmd/browser_extension/icons/icon-48.png +0 -0
  87. openmd/browser_extension/icons/icon-512.png +0 -0
  88. openmd/browser_extension/idle.html +6 -0
  89. openmd/browser_extension/manifest.json +43 -0
  90. openmd/browser_extension/options.html +42 -0
  91. openmd/browser_extension/options.js +117 -0
  92. openmd/browser_extension/popup.css +71 -0
  93. openmd/browser_extension/popup.html +17 -0
  94. openmd/browser_extension/popup.js +59 -0
  95. openmd/browser_extension/vendor/opencli-bridge/LICENSE +190 -0
  96. openmd/browser_extension/vendor/opencli-bridge/VENDOR.md +31 -0
  97. openmd/browser_extension/vendor/opencli-bridge/background.js +2742 -0
  98. openmd/browser_host.py +503 -0
  99. openmd/browser_setup.py +106 -0
  100. openmd/cli.py +2012 -0
  101. openmd/console.py +32 -0
  102. openmd/core/__init__.py +1 -0
  103. openmd/core/capabilities.py +162 -0
  104. openmd/core/content_validation.py +323 -0
  105. openmd/core/coverage.py +163 -0
  106. openmd/core/detector.py +247 -0
  107. openmd/core/doctor.py +104 -0
  108. openmd/core/execution_policy.py +164 -0
  109. openmd/core/filename.py +64 -0
  110. openmd/core/input_validation.py +91 -0
  111. openmd/core/local_config.py +96 -0
  112. openmd/core/manifest.py +156 -0
  113. openmd/core/models.py +168 -0
  114. openmd/core/opencli_output.py +34 -0
  115. openmd/core/operation_contract.py +60 -0
  116. openmd/core/output.py +194 -0
  117. openmd/core/package_contract.py +335 -0
  118. openmd/core/package_validator.py +489 -0
  119. openmd/core/package_writer.py +666 -0
  120. openmd/core/planner.py +345 -0
  121. openmd/core/preferences.py +61 -0
  122. openmd/core/readable.py +129 -0
  123. openmd/core/receipt.py +285 -0
  124. openmd/core/renderer.py +185 -0
  125. openmd/core/runlog.py +208 -0
  126. openmd/core/secrets.py +313 -0
  127. openmd/doctor_view.py +68 -0
  128. openmd/interactive.py +453 -0
  129. openmd/ocr/__init__.py +1 -0
  130. openmd/ocr/breakers.py +84 -0
  131. openmd/ocr/config.py +54 -0
  132. openmd/ocr/documents.py +522 -0
  133. openmd/ocr/endpoint.py +72 -0
  134. openmd/ocr/layout.py +92 -0
  135. openmd/ocr/layout_filter.py +118 -0
  136. openmd/ocr/layout_worker.py +89 -0
  137. openmd/ocr/native_math.py +220 -0
  138. openmd/ocr/office.py +305 -0
  139. openmd/ocr/ollama.py +200 -0
  140. openmd/ocr/otsl.py +51 -0
  141. openmd/ocr/ovis-prompt.txt +1 -0
  142. openmd/ocr/pipeline.py +384 -0
  143. openmd/ocr/settings.json +25 -0
  144. openmd/ocr/tiling.py +631 -0
  145. openmd/post_video.py +154 -0
  146. openmd/probe.py +397 -0
  147. openmd/routing/__init__.py +1 -0
  148. openmd/routing/cards/builtin.json +620 -0
  149. openmd/routing/cards/defuddle.json +70 -0
  150. openmd/routing/cards/x-tweet-fetcher.json +71 -0
  151. openmd/routing/cards/xyz-dl.json +60 -0
  152. openmd/routing/cards/yutto.json +68 -0
  153. openmd/routing/entry_shim.py +71 -0
  154. openmd/routing/lifecycle.py +459 -0
  155. openmd/routing/loader.py +138 -0
  156. openmd/routing/providers.py +158 -0
  157. openmd/routing/readers.py +108 -0
  158. openmd/routing/routes.json +504 -0
  159. openmd/routing/runner.py +192 -0
  160. openmd/routing/view.py +40 -0
  161. openmd/runtime/__init__.py +1 -0
  162. openmd/runtime/_windows_job.py +234 -0
  163. openmd/runtime/asr.py +217 -0
  164. openmd/runtime/asr_worker.py +119 -0
  165. openmd/runtime/gpu_lock.py +81 -0
  166. openmd/runtime/gpu_quarantine.py +85 -0
  167. openmd/runtime/interpreters.py +407 -0
  168. openmd/runtime/managed_tools.py +204 -0
  169. openmd/runtime/models.py +202 -0
  170. openmd/runtime/services.py +175 -0
  171. openmd/runtime/tempdirs.py +227 -0
  172. openmd_cli-0.1.0.dist-info/METADATA +168 -0
  173. openmd_cli-0.1.0.dist-info/RECORD +177 -0
  174. openmd_cli-0.1.0.dist-info/WHEEL +5 -0
  175. openmd_cli-0.1.0.dist-info/entry_points.txt +2 -0
  176. openmd_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  177. openmd_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,227 @@
1
+ """Owned, recoverable temp directories; sweep only direct Openmd children of system temp."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ctypes
6
+ import json
7
+ import os
8
+ import shutil
9
+ import stat
10
+ import tempfile
11
+ import threading
12
+ import time
13
+ from pathlib import Path
14
+
15
+ OWNER_FILE = ".openmd-owner.json"
16
+ MAX_AGE_S = 24 * 60 * 60
17
+ MAX_OWNER_PIDS = 256
18
+ MAX_OWNER_BYTES = 4096
19
+ _startup_report = None
20
+ _owned_directories: set[Path] = set()
21
+ _owner_lock = threading.RLock()
22
+
23
+
24
+ def pid_alive(pid: int) -> bool:
25
+ """Treat inaccessible process state as live. Never use Windows os.kill(pid, 0)."""
26
+ if pid <= 0:
27
+ return False
28
+ if os.name == "nt":
29
+ from ctypes import wintypes
30
+
31
+ kernel = ctypes.WinDLL("kernel32", use_last_error=True)
32
+ kernel.OpenProcess.argtypes = (wintypes.DWORD, wintypes.BOOL, wintypes.DWORD)
33
+ kernel.OpenProcess.restype = wintypes.HANDLE
34
+ kernel.GetExitCodeProcess.argtypes = (wintypes.HANDLE, ctypes.POINTER(wintypes.DWORD))
35
+ kernel.CloseHandle.argtypes = (wintypes.HANDLE,)
36
+ handle = kernel.OpenProcess(0x1000, False, pid)
37
+ if not handle:
38
+ return ctypes.get_last_error() != 87 # invalid parameter means no such PID
39
+ try:
40
+ code = wintypes.DWORD()
41
+ return not kernel.GetExitCodeProcess(handle, ctypes.byref(code)) or code.value == 259
42
+ finally:
43
+ kernel.CloseHandle(handle)
44
+ try:
45
+ os.kill(pid, 0)
46
+ except ProcessLookupError:
47
+ return False
48
+ except PermissionError:
49
+ return True
50
+ # A killed-but-unreaped (zombie) process still answers signal 0, yet holds
51
+ # no files and never runs again; orphans wait for init to reap them.
52
+ try:
53
+ with open(f"/proc/{pid}/stat", encoding="ascii", errors="replace") as stat:
54
+ state = stat.read().rsplit(")", 1)[1].split()[0]
55
+ except (OSError, IndexError):
56
+ return True # no /proc (macOS) or vanished mid-read: keep the safe answer
57
+ return state not in {"Z", "X"}
58
+
59
+
60
+ def _read_owner(marker: Path) -> dict:
61
+ """One bounded schema/size contract for readers and writers."""
62
+ if _linked(marker):
63
+ raise ValueError("linked owner marker")
64
+ with marker.open("rb") as source:
65
+ data = source.read(MAX_OWNER_BYTES + 1)
66
+ if len(data) > MAX_OWNER_BYTES:
67
+ raise ValueError("oversized owner marker")
68
+ owner = json.loads(data)
69
+ pid, pids = owner.get("pid"), owner.get("pids", [])
70
+ if not isinstance(pids, list) or len(pids) > MAX_OWNER_PIDS or not all(
71
+ isinstance(p, int) and not isinstance(p, bool) and 0 < p <= 0xFFFFFFFF
72
+ for p in [pid, *pids]
73
+ ) or not isinstance(owner.get("incomplete", False), bool):
74
+ raise ValueError("invalid owner marker")
75
+ return owner
76
+
77
+
78
+ def _write_owner(marker: Path, owner: dict) -> None:
79
+ data = json.dumps(owner, separators=(",", ":")).encode("utf-8")
80
+ if len(data) > MAX_OWNER_BYTES or len(owner.get("pids", [])) > MAX_OWNER_PIDS:
81
+ raise ValueError("owner marker exceeds shared contract")
82
+ # Readers see the old or the new complete record, never a truncated write.
83
+ fd, name = tempfile.mkstemp(prefix=OWNER_FILE + ".", suffix=".tmp", dir=marker.parent)
84
+ try:
85
+ with os.fdopen(fd, "wb") as destination:
86
+ destination.write(data)
87
+ os.replace(name, marker)
88
+ finally:
89
+ try:
90
+ os.unlink(name)
91
+ except FileNotFoundError:
92
+ pass
93
+
94
+
95
+ def _mark(directory) -> None:
96
+ try:
97
+ _write_owner(Path(directory) / OWNER_FILE, {"pid": os.getpid()})
98
+ with _owner_lock:
99
+ _owned_directories.add(Path(directory).resolve())
100
+ except OSError:
101
+ # Failure to establish ownership must never leave an unprotected live directory.
102
+ shutil.rmtree(directory, ignore_errors=True)
103
+ raise
104
+
105
+
106
+ def owned_mkdtemp(*args, **kwargs):
107
+ directory = tempfile.mkdtemp(*args, **kwargs)
108
+ _mark(directory)
109
+ return directory
110
+
111
+
112
+ def owned_temporary_directory(*args, **kwargs):
113
+ directory = tempfile.TemporaryDirectory(*args, **kwargs)
114
+ _mark(directory.name)
115
+ return directory
116
+
117
+
118
+ def note_process_owner(process) -> None:
119
+ """Protect active workspaces even if the controller dies before its workers.
120
+
121
+ A conservative association with all active workspaces in this controller is
122
+ intentional: retain an extra directory rather than remove a live worker's
123
+ input. Prune exited owners on EVERY write, including duplicate/dead inputs.
124
+ Inaccessible PIDs count as alive; true simultaneous overflow is unknown.
125
+ """
126
+ pid = getattr(process, "pid", None)
127
+ if not isinstance(pid, int) or pid <= 0:
128
+ return
129
+ with _owner_lock:
130
+ for directory in tuple(_owned_directories):
131
+ marker = directory / OWNER_FILE
132
+ try:
133
+ if not marker.is_file():
134
+ _owned_directories.discard(directory)
135
+ continue
136
+ owner = _read_owner(marker)
137
+ candidates = dict.fromkeys([owner["pid"], *owner.get("pids", []), os.getpid(), pid])
138
+ pids = [p for p in candidates if pid_alive(p)]
139
+ incomplete = owner.get("incomplete", False) or len(pids) > MAX_OWNER_PIDS
140
+ updated = {"pid": os.getpid(), "pids": pids[:MAX_OWNER_PIDS]}
141
+ if incomplete:
142
+ # Never silently drop untracked live ownership. Such rare
143
+ # overflow stays unknown until an explicit manual cleanup.
144
+ updated["incomplete"] = True
145
+ _write_owner(marker, updated)
146
+ except (OSError, ValueError, AttributeError, TypeError):
147
+ pass # Unreadable/malformed markers are retained by the sweeper.
148
+
149
+
150
+ def _linked(path: Path) -> bool:
151
+ info = path.lstat()
152
+ return stat.S_ISLNK(info.st_mode) or bool(getattr(info, "st_file_attributes", 0) & 0x400)
153
+
154
+
155
+ def _size(path: Path) -> int:
156
+ total = 0
157
+ try:
158
+ for child in path.iterdir():
159
+ if not _linked(child):
160
+ total += _size(child) if child.is_dir() else child.stat().st_size
161
+ except OSError:
162
+ pass
163
+ return total
164
+
165
+
166
+ def sweep_tempdirs(*, include_sizes: bool = True) -> dict:
167
+ root = Path(tempfile.gettempdir()).resolve()
168
+ report = {"root": str(root), "deleted": 0, "retained": 0, "failed": 0, "deleted_bytes": 0, "retained_bytes": 0, "sizes_measured": include_sizes, "errors": []}
169
+ now = time.time()
170
+ try:
171
+ with os.scandir(root) as entries:
172
+ for entry in entries:
173
+ if not entry.name.startswith("openmd-"):
174
+ continue
175
+ path = Path(entry.path)
176
+ try:
177
+ # Check lexical and resolved parent immediately before any removal.
178
+ if _linked(path) or not entry.is_dir(follow_symlinks=False) or path.resolve().parent != root:
179
+ continue
180
+ age = now - entry.stat(follow_symlinks=False).st_mtime
181
+ size = _size(path) if include_sizes else 0
182
+ retain = age <= MAX_AGE_S
183
+ marker = path / OWNER_FILE
184
+ if not retain and marker.exists():
185
+ # Invalid/unreadable markers are unknown ownership, so retain.
186
+ try:
187
+ owner = _read_owner(marker)
188
+ retain = owner.get("incomplete", False) or any(pid_alive(p) for p in [owner["pid"], *owner.get("pids", [])])
189
+ except (OSError, ValueError, AttributeError):
190
+ retain = True
191
+ if retain:
192
+ report["retained"] += 1
193
+ report["retained_bytes"] += size
194
+ continue
195
+ # Revalidate after ownership and size inspection; never follow a swapped link.
196
+ if _linked(path) or path.resolve().parent != root:
197
+ continue
198
+ try:
199
+ shutil.rmtree(path)
200
+ except OSError:
201
+ report["retained"] += 1
202
+ report["retained_bytes"] += size
203
+ raise
204
+ report["deleted"] += 1
205
+ report["deleted_bytes"] += size
206
+ except OSError as exc:
207
+ report["failed"] += 1
208
+ report["errors"].append({"directory": entry.name, "error": type(exc).__name__})
209
+ except OSError as exc:
210
+ report["failed"] += 1
211
+ report["errors"].append({"error": type(exc).__name__})
212
+ return report
213
+
214
+
215
+ def sweep_at_startup() -> dict:
216
+ global _startup_report
217
+ if _startup_report is None:
218
+ # Never delay the CLI for directory removal or size scans. An interrupted
219
+ # sweep is safe to retry on the next startup; doctor runs synchronously.
220
+ report = {"started": True}
221
+ _startup_report = report
222
+
223
+ def sweep():
224
+ report.update(sweep_tempdirs(include_sizes=False))
225
+
226
+ threading.Thread(target=sweep, name="openmd-temp-sweep", daemon=True).start()
227
+ return _startup_report
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.4
2
+ Name: openmd-cli
3
+ Version: 0.1.0
4
+ Summary: 把网页文章、公众号、视频字幕等链接保存成干净的 Markdown 原文 / 逐字稿。
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/baigao417/openmd
7
+ Project-URL: Issues, https://github.com/baigao417/openmd/issues
8
+ Project-URL: Changelog, https://github.com/baigao417/openmd/commits/main
9
+ Keywords: markdown,transcript,subtitle,wechat,bilibili,youtube,obsidian,clipper
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Environment :: Console
18
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
19
+ Classifier: Natural Language :: Chinese (Simplified)
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: trafilatura<3,>=2
24
+ Requires-Dist: youtube-transcript-api<2,>=1.2
25
+ Requires-Dist: beautifulsoup4>=4.12
26
+ Requires-Dist: markdownify>=0.13
27
+ Requires-Dist: nodejs-wheel-binaries<23,>=22.12
28
+ Provides-Extra: asr
29
+ Requires-Dist: faster-whisper>=1.1.0; extra == "asr"
30
+ Requires-Dist: opencc-python-reimplemented>=0.1.7; extra == "asr"
31
+ Provides-Extra: web
32
+ Provides-Extra: pdf
33
+ Requires-Dist: markitdown[pdf]>=0.1.6; extra == "pdf"
34
+ Provides-Extra: ocr
35
+ Requires-Dist: rapidocr-onnxruntime>=1.4.4; extra == "ocr"
36
+ Requires-Dist: Pillow>=10; extra == "ocr"
37
+ Provides-Extra: ocr-router
38
+ Requires-Dist: Pillow>=10; extra == "ocr-router"
39
+ Requires-Dist: numpy>=1.24; extra == "ocr-router"
40
+ Requires-Dist: PyMuPDF>=1.24; extra == "ocr-router"
41
+ Provides-Extra: document
42
+ Requires-Dist: markitdown[docx,pdf,pptx,xlsx]>=0.1.6; extra == "document"
43
+ Requires-Dist: rapidocr-onnxruntime>=1.4.4; extra == "document"
44
+ Requires-Dist: Pillow>=10; extra == "document"
45
+ Provides-Extra: dev
46
+ Requires-Dist: pytest>=8; extra == "dev"
47
+ Requires-Dist: Pillow>=10; extra == "dev"
48
+ Requires-Dist: numpy>=1.24; extra == "dev"
49
+ Requires-Dist: PyMuPDF>=1.24; extra == "dev"
50
+ Dynamic: license-file
51
+
52
+ # Openmd
53
+
54
+ `Openmd` 是一个多平台内容摄取工具:把平台 URL、作者主页、合集、课程目录、playlist、网页文章或通用可访问视频转成统一 Markdown。
55
+
56
+ 它不是漂亮笔记生成器,也不是完整 Agent 工作站。它只是 Markdown-first 工作流中的内容摄取节点,目标是 **高效、可靠地取得原始正文 / 逐字稿**。
57
+
58
+ ```text
59
+ platform url / author / playlist / catalog / web page / generic media
60
+ -> router
61
+ -> backend capability registry
62
+ -> platform adapter
63
+ -> native text / transcript / media fallback / ASR
64
+ -> normalized raw Markdown
65
+ -> local archive / truth-source review / downstream agent workflow
66
+ ```
67
+
68
+ ## 30 秒上手
69
+
70
+ ```powershell
71
+ pip install openmd-cli # 或 pipx install openmd-cli;需要 Python 3.10+
72
+ openmd "https://sspai.com/post/73145" # 粘贴链接 → 回车选保存位置 → 得到 标题.md
73
+ openmd "https://..." -o D:\notes # 脚本 / AI Agent:给了目录就直接跑完
74
+ openmd doctor # 看看现在能处理哪些内容、缺什么怎么装
75
+ ```
76
+
77
+ | 内容 | 装完即可用 | 需要额外安装 |
78
+ |---|---|---|
79
+ | 网页文章、YouTube 有字幕的视频 | ✓ | — |
80
+ | B 站字幕、公众号、X 等需要浏览器读取的平台 | 首次自动准备浏览器读取组件(约 20MB,一次) | Openmd 浏览器插件(`openmd browser-setup`),借用你已登录的浏览器在后台读取,窗口自动最小化 |
81
+ | 无字幕视频、播客(本机转录) | | `pip install "openmd-cli[asr]"` + 模型,见 [工具接入规范](https://github.com/baigao417/openmd/blob/main/docs/tool-integration.md) |
82
+ | 图片、PDF、Office | | `pip install "openmd-cli[document]"` |
83
+ | 浏览器一键保存 | | `openmd browser-setup`(目前仅 Windows,见下文) |
84
+
85
+ 所需运行环境随 `pip install openmd-cli` 一起装好,不需要另装 Node.js 或其他工具。不想自动下载可设 `OPENMD_AUTO_INSTALL_TOOLS=0`,npm 慢可设 `OPENMD_NPM_REGISTRY` 指向镜像;想看到后台读取窗口(排查问题时)可设 `OPENMD_SHOW_BROWSER=1`。第三方组件与许可证见 [THIRD_PARTY_NOTICES](https://github.com/baigao417/openmd/blob/main/THIRD_PARTY_NOTICES.md)。
86
+
87
+ 取不到时 Openmd 不会存一份垃圾文件:它会说明原因,并打印一行 `提 issue 请附上:…`,复制到 [链接提取失败](https://github.com/baigao417/openmd/blob/main/.github/ISSUE_TEMPLATE/link-failure.yml) 即可。想参与开发见 [CONTRIBUTING](https://github.com/baigao417/openmd/blob/main/CONTRIBUTING.md)。
88
+
89
+ ## 输入链接后保存
90
+
91
+ 在终端执行 `openmd` 后粘贴链接,或直接执行 `openmd "https://example.com/article"`。粘贴后直接开始提取,不做任何确认:公开读取、浏览器读取(可能使用现有登录会话)、必要时下载媒体和本机转录按路线需要自动开启;云服务从不自动开启。非交互运行(脚本 / Agent)只要给出保存目录(`-o` / `--save-to`)就按同一档自动开启;显式传入能力开关时只开这些,`--yes` 仅为兼容保留。提取成功后选择保存目录,回车沿用上次成功保存的位置,输入 `n` 取消。默认直接保存 `标题.md`,同名时才加后缀;运行日志放在 `~/.openmd/runs/`,不混入所选文件夹。终端显示“已取得正文”和“已保存”,`--verbose` 显示工具路线,`--json` 输出机器回执。
92
+
93
+ 带视频的帖子(目前 X):默认只查视频时长,在正文后注明“本帖含视频(约 N 分钟),未转文字”;`--video text` 临时下载视频、本机转写并把逐字稿接在正文后(下载文件用完即删);`--video file` 另把视频文件存进同名 assets 文件夹并在 Markdown 里嵌入。视频这一步失败不影响正文保存,只在文件里写明原因。
94
+
95
+ 默认 Markdown 按说话人、章节、停顿或约 30 秒合并字幕段落,保留原字词和错误,不补写标点或总结。`--raw` 保留原字幕逐行/表格排版。YouTube/B 站标题作者缺失时,在公开网络许可内补取元数据,失败仍保存正文并说明缺失;取得正文不等于已核对全文准确性与完整性。
96
+
97
+ ```powershell
98
+ openmd
99
+ openmd go "https://example.com/article"
100
+ openmd "https://example.com/video" --raw
101
+ # 自动化:给了目录即可;只想开公开网络时显式写 --enable-network-backends 收窄
102
+ openmd "https://example.com/article" -o "D:\notes" --json
103
+ openmd doctor --profile media
104
+ openmd doctor --profile tools
105
+ openmd probe --enable-network-backends --enable-browser-backends # 本机 out/probe-config.json 配样本,模板见 docs/examples/probe-config.example.json;--platform bilibili 只测一个平台,报告在 out/probe/<date>/,不下载媒体或跑 ASR
106
+ openmd routes --format markdown
107
+ ```
108
+
109
+ `openmd --help` 只列日常命令(`go`、`batch`),`openmd go --help` 只列 `--raw / --save-to / --video / --json / --verbose`;其余子命令和参数照常可用,只是不在帮助里显示(`openmd <命令> -h` 查看)。
110
+
111
+ ### 浏览器插件(Chrome / Edge)
112
+
113
+ 1. 终端运行一次 `openmd browser-setup`(只写当前用户注册表和 `~/.openmd/browser/`;`--uninstall` 撤销)。
114
+ 2. 打开 `chrome://extensions`,开启“开发者模式”,“加载已解压的扩展程序”,选 `browser-setup` 打印并自动打开的文件夹(`~/.openmd/browser/extension`,升级 openmd 后重跑一次 `browser-setup` 即同步)。插件 ID 固定为 `lceckiadcfloncbklcojdiimbpollngh`。
115
+ 3. 点工具栏的 Openmd(仓鼠)图标:直接保存当前页到默认位置,图标出现 ✓ 即完成;失败才弹系统通知说明原因。
116
+ - 多选标签页:按住 Ctrl / Shift 点选多个标签,再点图标,一次全部保存。
117
+ - 保存一片链接:在课程目录、合集列表等页面拖选一段内容,右键“用 Openmd 保存选中的链接”。
118
+ - 右键“用 Openmd 保存此页 / 此链接”同样可用。
119
+ - X 帖子带视频:直接点图标只注明有视频;在 X 页面(或帖子链接、视频上)右键“用 Openmd 保存(含视频文字版)”附上逐字稿,“(含视频文字版和视频文件)”再留下视频。
120
+ 4. 设置页(右键图标 → 选项):添加多个保存位置(名字 + 文件夹,可用“选择…”弹出文件夹对话框),选默认位置;“点击图标时”可切换为“先选择保存位置”;“帖子里带视频时”可改成默认附上逐字稿。
121
+
122
+ 借用浏览器读取(B 站、公众号、X 等)时,Chrome 可能在顶部显示“Openmd 已开始调试此浏览器”:这是 Chrome 对使用调试接口的插件统一显示的提示,插件自身无法隐藏;读取只在 Openmd 的后台窗口里进行。
123
+
124
+ 插件通过 Chrome Native Messaging 调用本机 `openmd`,不开本地端口;只有上面这个插件 ID 能连。所有保存进入后台队列,一次只跑一个(避免本机转录同时占满显卡),后台不弹任何窗口。关机时没跑完的任务,开机后插件一联系本机就自动重新排队(最多重试一次)。重启电脑后无需任何操作。任务记录在 `~/.openmd/browser-jobs/`。
125
+
126
+ ASR 步骤自动选择已有 CUDA worker;主 CLI 保留自己的环境。本地模型目录或缓存不存在时明确报错,不自动从 HF 下载。工具位置、ASR Python 和模型根目录统一放在本机配置(`OPENMD_CONFIG` 指定 JSON,源码 checkout 默认 `out/local-config.json`,安装版默认 `~/.openmd/local.json`);只存安装位置,不存凭证。配置示例与安装边界见 [工具接入规范](https://github.com/baigao417/openmd/blob/main/docs/tool-integration.md),实际路由由打包 JSON 生成:[当前路由](https://github.com/baigao417/openmd/blob/main/docs/routes.generated.md)。架构图和各平台工具一览:[Openmd 全景](https://github.com/baigao417/openmd/blob/main/docs/architecture-map.html)(浏览器打开)。
127
+
128
+ ## 输出格式
129
+
130
+ ```md
131
+ ---
132
+ title: ...
133
+ author: ...
134
+ url: ...
135
+ published: "2026-11-07"
136
+ platform: bilibili
137
+ ---
138
+
139
+ # 标题
140
+
141
+ **00:00:01**
142
+
143
+ 合并成段的原始逐字稿……
144
+
145
+ *来源:自动字幕;识别错误保持原样。*
146
+ ```
147
+
148
+ ### 状态口径
149
+
150
+ - `verified`:已成功产出可用正文 / 逐字稿,包括原生正文、官方/作者字幕、自动字幕、第三方 transcript、ASR 转写、飞书原生 Markdown 导出。
151
+ - `partial`:产出文本但明显不完整,例如只抓到预览、部分章节缺失、视频只转写一段、collection 部分失败。
152
+ - `failed`:没有产出可用文字稿 / 正文。
153
+
154
+ `source_level` 表示来源等级,不等同于 `status`。这些字段保留在运行记录和显式 package 中,不在普通 Markdown 中重复展开。
155
+
156
+ ## 文档
157
+
158
+ - [子系统地图](https://github.com/baigao417/openmd/blob/main/docs/subsystems/README.md):每个模块的职责、入口和验证命令
159
+ - [接入新平台](https://github.com/baigao417/openmd/blob/main/docs/adding-a-platform.md):大多数平台只需加一张工具卡
160
+ - [工具接入规范](https://github.com/baigao417/openmd/blob/main/docs/tool-integration.md)、[当前路由](https://github.com/baigao417/openmd/blob/main/docs/routes.generated.md)、[全景图](https://github.com/baigao417/openmd/blob/main/docs/architecture-map.html)
161
+
162
+ ## 参与开发
163
+
164
+ Windows / macOS / Linux 都可以开发:`python scripts/dev_setup.py` 一条命令装好环境。流程、issue 与 PR 要求见 [CONTRIBUTING](https://github.com/baigao417/openmd/blob/main/CONTRIBUTING.md),硬规则见 [code-standards](https://github.com/baigao417/openmd/blob/main/docs/code-standards.md)。
165
+
166
+ ## 许可证
167
+
168
+ [MIT](https://github.com/baigao417/openmd/blob/main/LICENSE)。第三方组件见 [THIRD_PARTY_NOTICES](https://github.com/baigao417/openmd/blob/main/THIRD_PARTY_NOTICES.md)。
@@ -0,0 +1,177 @@
1
+ openmd/__init__.py,sha256=GtdieGClGQfkKP6QZ3ODKVBY5V4ha2fPPun1i_EtiNY,45
2
+ openmd/__main__.py,sha256=PSQ4rpL0dG6f-qH4N7H-gD9igQkdHzH4yVZDcW8lfZo,80
3
+ openmd/browser_host.py,sha256=tmydV9DFbtEW9oAQIuWijuyZdta-VRAmtHyDop0jErY,19683
4
+ openmd/browser_setup.py,sha256=Uj3Lp6zoJ5BSpdz15EBiY0ix-Bp0IGB3yI7sWMa3Tp8,4328
5
+ openmd/cli.py,sha256=_xp0Jmx2mbkCDdmwgDTo-_tp7v8Z1q4V9387cI1Mwxw,85265
6
+ openmd/console.py,sha256=sYYrOjM2_2N1tsRinphWXlnvFbhcSI5_dqf91eHDvMA,1241
7
+ openmd/doctor_view.py,sha256=SehE5pl_oYfU8mkv90pjQ-6xhNFv7vrkalK72dBpDxc,3275
8
+ openmd/interactive.py,sha256=Zqkf7cjMT9kfstqWWucRPoK6WO-4tI5HtUxwYG4tb6I,23800
9
+ openmd/post_video.py,sha256=SYHyq7WTEXXp_DbyiUcjrCygniV_nhsrQb4uax1PZU8,6648
10
+ openmd/probe.py,sha256=3gwyGKFWEBQkPf7ilL_yYUa28kakiPcyH6s462Vyljc,19901
11
+ openmd/adapters/__init__.py,sha256=srLL8U-cyZzFFb-pkNPAW6BlvenF_KcLRNXgzPWYFTI,48
12
+ openmd/adapters/base.py,sha256=CtTDQAiE0WK1va9Qvik7Clbn5c4POxwuknsROe3HChs,3225
13
+ openmd/adapters/high_privacy.py,sha256=WBbZ_vwOA_vgRSnzN81qChybM5d0a-xR37e-ZTe12zk,2218
14
+ openmd/adapters/opencli.py,sha256=PL72iZN0RxIpI9O2aEnFehC8hmYfBkxwlJj7OsQLvXk,28173
15
+ openmd/adapters/registry.py,sha256=rTnKav5ONZjOvn4L_6UYzNt__aVJRL3onM-OzD9nDQE,4547
16
+ openmd/adapters/xiaohongshu.py,sha256=iSvvrOyeWovt7QhpItIOlB33MzrHsSqCeqYg-QXiEYk,6400
17
+ openmd/adapters/bilibili/__init__.py,sha256=E8OpuGZ5c2cQ6PtefPTdYLXGdFsNsTx8ThwjFKoj4ms,298
18
+ openmd/adapters/bilibili/adapter.py,sha256=y2nRG6dLjFtbSqgLkQ7KI1vG9AYxf_bnwUllPYJVXEk,3805
19
+ openmd/adapters/bilibili/discovery.py,sha256=CLMlpSqkpiZl5NTnh6ReV4X6dpUnMn4mlInoJ4ViV5U,1046
20
+ openmd/adapters/douyin/__init__.py,sha256=QR_rgG8cEgsKooVP3W9-NN8wZTy1dRVa8bH8CPs5bgw,292
21
+ openmd/adapters/douyin/adapter.py,sha256=grIdY6M6Lq3dwngqI__y0e46GaEG4w3xqZUkjFAU4_s,4581
22
+ openmd/adapters/douyin/discovery.py,sha256=t653ZmfyYKQDiHKfvVE_o1IdOG0IM2wicw7-yVItp3A,1184
23
+ openmd/adapters/douyin/sidecar.py,sha256=ZvQ3UYylkeKuIhfl8fI3J6qQB5qLRwiG1P3pLgEF0SM,2340
24
+ openmd/adapters/generic_media/__init__.py,sha256=ldkiXT2OBKmAUj1sEUtJ1OyhSB8xTfLhZhQiayOw2Wg,470
25
+ openmd/adapters/local_audio/__init__.py,sha256=IAXusBo56nM_1vsII0HGL5NAw-hT09FqxmoXp-OkOIw,470
26
+ openmd/adapters/local_audio/adapter.py,sha256=bslG4Ls24pTw3s6jmNfOH_W8UmsE2o_DdCnNjXRv9_E,5268
27
+ openmd/adapters/local_audio/asr.py,sha256=VQ7DzAVqiFTpVfWbQfJNSC9pcp-UZZ92khkfJC9RUyw,1858
28
+ openmd/adapters/local_audio/backends/__init__.py,sha256=wUbtKlx2PWPpvWqz62iO6WOuEisL5MY-aXn2RCcRLvU,50
29
+ openmd/adapters/local_audio/backends/auto.py,sha256=38SsF6L5ZREHtiZQzlzPhg937XI0YVndTMKtDkOAfPg,7728
30
+ openmd/adapters/local_audio/backends/faster_whisper.py,sha256=wgBR71xv3QVBnGV40s2kh2eMv0Jwu9tywiRgGrX9yWY,11767
31
+ openmd/adapters/local_audio/backends/funasr_sense_voice.py,sha256=5Z9WL6g1Y8gFw4droFICBls4gCu4bueMxwTgJ2gW5Nc,432
32
+ openmd/adapters/local_audio/backends/qwen3_asr.py,sha256=he-icfcXmcPrkbgNsIUUaPUq74SLZ4A83SJMY55c2Ws,10892
33
+ openmd/adapters/pdf/__init__.py,sha256=z7XmeBR0pp3W63WpWRquJfjkc92yIC7lXmlxLQiWl2w,307
34
+ openmd/adapters/pdf/adapter.py,sha256=G6W0OzY0Ai8kgfNPN7Qj8A7c9PRNtpkBwZJO38eadKA,5340
35
+ openmd/adapters/pdf/parser.py,sha256=bmdOo2AKq05yt4itLxZEwlKI3vOWeLyAh68a2QIQ33E,1919
36
+ openmd/adapters/pdf/backends/__init__.py,sha256=lB0BWP_c4ZyOMMuK0u179HY2qSPwnaQyQ3XWL2QQsIY,41
37
+ openmd/adapters/pdf/backends/markitdown.py,sha256=XELi2jlYCGHjSbjqm_1dfrVpEK843XSH4gDW3oIOu4s,6537
38
+ openmd/adapters/qq_group/__init__.py,sha256=KVftIlwwTzOwBeV3xYdaNyq0Tv7nGWrvIR4Skxb7lGc,305
39
+ openmd/adapters/qq_group/adapter.py,sha256=r1VivgA0PUBnZKngBY-ussl4EJNrNzl2IMWfOm0agP8,11475
40
+ openmd/adapters/qq_group/parsers/__init__.py,sha256=dvJEdqQHWLogWL2ZPEigfhoWUgLyUqj-ssv9nGzE5LA,55
41
+ openmd/adapters/qq_group/parsers/html.py,sha256=-gQXHA1l2BstL4YnLl1-MN1D3LPgmpFKGs_25w3pGKQ,1932
42
+ openmd/adapters/qq_group/parsers/json.py,sha256=jKGHPjkgJHCDNcIpSoQcy9U_kXJuLux307nyMJ9vNBc,2410
43
+ openmd/adapters/screenshot/__init__.py,sha256=fXX_29-lF-9tPoKlY_wYUWx4VHnw0J1CI-peQKvoGvM,318
44
+ openmd/adapters/screenshot/adapter.py,sha256=YCGQB_Q5v8dvHEH8vCC7dcKYdtMj7hVrEwHANn_eAMc,5401
45
+ openmd/adapters/screenshot/ocr.py,sha256=S9ZT9XWalGLvzjrD8qNfBM2IO4CTT6yi4mVBq5Z5B-I,2766
46
+ openmd/adapters/screenshot/backends/__init__.py,sha256=EUf5R3VT_cDSBgF84w_HTkoOh_-IzX_gwNN1RVrLMkA,52
47
+ openmd/adapters/screenshot/backends/paddle.py,sha256=u36VP8Z4IRE2vIAyJCz0rxbCmJAOR-1SFr9uZEl_Ddg,461
48
+ openmd/adapters/screenshot/backends/rapidocr.py,sha256=DI0CFQ3aHOMHEISblZuhsTgSFSfudhfebakEfqGHBuU,6511
49
+ openmd/adapters/screenshot/backends/tesseract.py,sha256=E_4vbWk4FkyB8x1l93D0Hx85beRxHf5S5_IHQN_Ojtk,467
50
+ openmd/adapters/wechat_group/__init__.py,sha256=rcH9ubJMSiUTOfS_ik82NVRMiBVtoy7M6yHpSTqJxLA,317
51
+ openmd/adapters/wechat_group/adapter.py,sha256=JRPUR2447opk0QU87MdTM6CwJypo_IbDYLJCvGuw0tY,6296
52
+ openmd/adapters/wechat_group/wx_cli.py,sha256=Y-clCuMC4ilB-1HC9v8ZW6nsDc600iyScSDaX_r0j50,9746
53
+ openmd/adapters/wechat_moments/__init__.py,sha256=ymH9Y2j_9VEOWeNoJuH-GqgG-IeESSz0jord4nJ_MjI,323
54
+ openmd/adapters/wechat_moments/adapter.py,sha256=GYkclGAcaxr4QogpPomoaN2OV7eU3wLRXepyXivnRFI,6449
55
+ openmd/adapters/wechat_moments/wx_cli.py,sha256=qchAHryfmO4884CF2zi9w9qHJsItqkLvQ1iLxkpvDk0,9510
56
+ openmd/adapters/weixin/__init__.py,sha256=MYrxXFxhoWH-iVWmI9auTcqUYuJk9MNyEVc4RDryJGk,346
57
+ openmd/adapters/weixin/adapter.py,sha256=GhjPD1beALdSf8roZVViJCnj7BpNtfQ5_j4yD7-RD3Y,5189
58
+ openmd/adapters/weixin/discovery.py,sha256=XUqahJ9fqFdw4Q44IJLKZoXZqbYznB42p_mxcDn8jZ0,20539
59
+ openmd/adapters/weixin/report.py,sha256=3BPPJtzGx7Jb2eAV1QLAfYVkjcdCRC8Ba8VjkyRk5EI,11459
60
+ openmd/backends/__init__.py,sha256=n4X9gekJnqkPgV10YKRoIO0cFwq9_WsYtGa2egJRQlA,34
61
+ openmd/backends/base.py,sha256=gN7Rbah7xG1AMnwZbbjJbpHfvbA8rCauCpD48wkyZnA,5194
62
+ openmd/backends/bilibili.py,sha256=wuGcErvxbgIbP8KHD43nwwmxeDszGRFxAsaR5gzjnWQ,55129
63
+ openmd/backends/bilibili_wbi.py,sha256=odIOHxh-15yNjiVWfSqrv5OF1qN8Rrhm4bSmQOS_988,7983
64
+ openmd/backends/discourse.py,sha256=dQj-AKc4Da7gKfeta2_xBQNTQlfT2iqTE44BOff9Ql8,20742
65
+ openmd/backends/douyin.py,sha256=e3lNOdkOgY_okoQdlLDlVfqjy8e3V95n3l6PYk7v5ig,32158
66
+ openmd/backends/douyin_detail.py,sha256=9LwdaAjPSGnhZ5pmCdd1tZZ4UIPuHulLSNScoMFj9Sw,6575
67
+ openmd/backends/douyin_providers.py,sha256=klb_97kduPq23JlDLfm3pOXTWv_vWEDGuOExpgxwKI8,30369
68
+ openmd/backends/executor.py,sha256=S_p4Nc5-Ejbe_9njzQZ4jMS7sP-ngxiASL1fSXbP9KE,20813
69
+ openmd/backends/feishu.py,sha256=QR2JgFNVeatbtzuOZb0ug37vVCTaxNJ5NOyDHZjVMFs,30995
70
+ openmd/backends/generic_media.py,sha256=LoD6-tremmgdEVme58qqpedJVb729NtYIE2NalWtjhI,3102
71
+ openmd/backends/interactive.py,sha256=J6HDGKHAaYE29lKxNFShFY4bWdX1HHsxagGNeupccA0,2417
72
+ openmd/backends/ocr.py,sha256=djKktgx2UDJNqwTdypCQ9m3TdeM-cayoXt_JumrmFeI,2298
73
+ openmd/backends/opencli.py,sha256=GB2IASAtt-0p3A5u0zTlb0wg7KNXA7OOMZd04VpHf2c,2903
74
+ openmd/backends/pdf.py,sha256=9mTYAdWkClwEkUuApRM6RHwD6h2De_TTDPcbEAVGRJc,5989
75
+ openmd/backends/registry.py,sha256=YYKR5EXRRhb4Ra1Cb4u0K-m0SmEVyVSBkc7gne3bxQM,8577
76
+ openmd/backends/screenshot.py,sha256=d7mGoOciAjz85ckr5O571KelqnUUTLeossm_XBhDNJY,3130
77
+ openmd/backends/video_metadata.py,sha256=bP-xwuwdTytbkxcTX5CE-hMPC2n3GVExGvD9bGVvrpw,7300
78
+ openmd/backends/web_article.py,sha256=j0TOrh0QDoSLpcBxZFYj0koLtO56mRjmQmgOHCYCrU0,13816
79
+ openmd/backends/wechat_group.py,sha256=tYifX0hQ73futYv8im9qBQZ3Ud9tgxuuQIH9j7JxFeQ,1217
80
+ openmd/backends/wechat_moments.py,sha256=zy6egFOLAIfjM7bd_PCUuIpxjM6Qe13Znq1ekxhA21E,1215
81
+ openmd/backends/weixin.py,sha256=nUmQaIulIlSy88x7Mqem5EP83rMCh4wcWyP4EdVpH_w,29468
82
+ openmd/backends/x.py,sha256=GIg43uyj0FS9azioTASUVdDog88XDmCSsATILHfiYcM,15628
83
+ openmd/backends/xiaoe.py,sha256=pgV66AwvYehc7Zd3mGkPEQGGBS6zp5d2c7ux_4GQwxY,33481
84
+ openmd/backends/xiaohongshu.py,sha256=8lkQ9UVdfFsed01HmIw9YuFP0rPKvw8RrdLowcfbvfo,34129
85
+ openmd/backends/xiaohongshu_providers.py,sha256=v3Z52Mn1GPAQKmKkbU63lTUDBZ4_oLu43VTv_TPKYAs,37978
86
+ openmd/backends/xiaoyuzhou.py,sha256=EGkt-dPmaWTlsd6yMLNenLsIb870kZxWNcqMsF5ylag,37922
87
+ openmd/backends/youtube.py,sha256=yP8-bfk_185ASCMvcxDmHuYcPfmw0mQVJuPcc7F_JSQ,10860
88
+ openmd/backends/youtube_discovery.py,sha256=-sfFI9JMMmRch-P6fOuH0uK9Xdua99yNuUDUjBztXqU,52639
89
+ openmd/backends/youtube_transcript.py,sha256=HIZTdcygWDGo0R_t_OmEPS_EomYV_k1FetDIIjRUvM0,7747
90
+ openmd/browser_extension/background.js,sha256=KcbN9IiRiEpcJ2FNcb1QdTJ5sikIE8Q-YnVuv-pMVVs,10450
91
+ openmd/browser_extension/idle.html,sha256=1CvUITKM8g8kItVeb5q1lrz7glpusWQwzoq1ehA0RB0,342
92
+ openmd/browser_extension/manifest.json,sha256=RiP212Uf3znt2nvALzGmD--ZIgAygQq1HeoAZOrl4i0,1427
93
+ openmd/browser_extension/options.html,sha256=Al451FfqvDwsC_HDmMuyME0FheN67nyzf98lky_mWbo,1600
94
+ openmd/browser_extension/options.js,sha256=Q0YpmkpGAdYuVzqNYLG835gI-zOpW-G5GRMPW-MG5PY,4277
95
+ openmd/browser_extension/popup.css,sha256=MX2OWf9s7Lv-MMFqFYlzuo2rrU75bv6EcNATOLkbCU4,3700
96
+ openmd/browser_extension/popup.html,sha256=XoKs9uQeqPFJcBLBL7C71Yky_ruCO7mtsgMbKColEUo,451
97
+ openmd/browser_extension/popup.js,sha256=RcNbRBxLdsu7O52ZdWdnWNJIapfOGkSS3YYGEOFQ21U,2287
98
+ openmd/browser_extension/icons/icon-128.png,sha256=bQR0pGTN3fY8jTYDi43-7s_ZIDb5ezq-3aGLuyGGDZQ,9045
99
+ openmd/browser_extension/icons/icon-16.png,sha256=Vl6U3sSjnkDVgd0doi5TB_O9pQ6ufn0o4s859LKTWGM,789
100
+ openmd/browser_extension/icons/icon-32.png,sha256=fgJuA5hN8irHrqtPghuG9XrUkGgt7_EXBHuxpSeWsEc,1772
101
+ openmd/browser_extension/icons/icon-48.png,sha256=mgQG6nOHXGVtoRWE96P2LBB8p-D2sjnxYTpmGqBnCZY,2880
102
+ openmd/browser_extension/icons/icon-512.png,sha256=NkWGPF7GaV5TEqvy-mcuBJ-u-Tg2nVoDGah2uNNolKI,38855
103
+ openmd/browser_extension/vendor/opencli-bridge/LICENSE,sha256=AhC4tmzwA1gkLLkhuivjpG3-AZAVmxuVI4ijiAzh_1Q,10760
104
+ openmd/browser_extension/vendor/opencli-bridge/VENDOR.md,sha256=5dzJ0zEre3siK8_XdO7vAP4xVcGvVc7HtAi0Se5WG_U,1801
105
+ openmd/browser_extension/vendor/opencli-bridge/background.js,sha256=H2nAYExIeEOBAd4bD8ljRdXXG6WwVg1hfDsRW8kG43c,107283
106
+ openmd/core/__init__.py,sha256=sK0yIGpTRdnFrLdAc_-sHdYvorthOHFe70VKOusN_Jk,40
107
+ openmd/core/capabilities.py,sha256=YXBrAT-gzZ4WObYgOKATDYq1QhFewRSppNXNu8rCrY0,6553
108
+ openmd/core/content_validation.py,sha256=7YhBoKjo3HRP3Oy0cgkd7k7uXrvmEVsvOfR8hzwDKdA,16605
109
+ openmd/core/coverage.py,sha256=NhaTE2Q4uY3Kp0IXLqycdv6h24MMJILOVT0p_p77L1k,6409
110
+ openmd/core/detector.py,sha256=YzM_-V7cO9abib6J-q-Z_uR4q7qRs19HNc3spGuBgYw,7583
111
+ openmd/core/doctor.py,sha256=wYq6Bi6R_0nNhUyGNy9ptBU_TVN3sM02EgffohoawEM,5274
112
+ openmd/core/execution_policy.py,sha256=YKamVsUqoHS0LVJUB51UFdTTDyx8P-jwoRJ9z-BRVb4,6470
113
+ openmd/core/filename.py,sha256=9_LuK7uo5ovKcrWxt-QE_72bpTWuT13M8cJE7l7GU-g,1757
114
+ openmd/core/input_validation.py,sha256=olt-6zz6MpF0JI3bCWxUERXYG6I4aFQSFTqAkGvIZ0o,3634
115
+ openmd/core/local_config.py,sha256=KWzq5Uoxcy0zK-foxVbtQq7iS3gJRousCkUwllDYYeM,4352
116
+ openmd/core/manifest.py,sha256=4cRFpAre_HYSCpZy0fGfZgfHYH3js86D4Zh81orDWKk,5459
117
+ openmd/core/models.py,sha256=NqHHkd__6fM8cEXqDxAMf04pRl4pgclepbczpFZJNoQ,4260
118
+ openmd/core/opencli_output.py,sha256=3bzSYhAENdRwQhKU15z765lBttT52Vii3W8Lk7XlWNo,1148
119
+ openmd/core/operation_contract.py,sha256=zFGw6fINqL-uIpkmg1r5uXgTuqG9-BARkQlBBIEM35I,2869
120
+ openmd/core/output.py,sha256=aLUflF5zcU6wvVRlkliIGjtpDZEi-fJY5X8_P6OiH5s,6672
121
+ openmd/core/package_contract.py,sha256=u81eRX9NNvOkMutv5uVTLH0ihQQPuHn7BxYtCrhrV1o,11066
122
+ openmd/core/package_validator.py,sha256=epSEEgv0Nc_j-PNnfSpbIiF9DO0wXOmfGtPYTSuWRYo,19212
123
+ openmd/core/package_writer.py,sha256=4L52u7_VwM2dmcl-oGSAlPVwk6fNFTAAA_zAQwRIzBY,22815
124
+ openmd/core/planner.py,sha256=Ah-NgJNGauH74GEZl_k4sVsDrQpKyVzlk9YyLlMUkhE,16339
125
+ openmd/core/preferences.py,sha256=Xvi6zrkzaIfle3Elz4lRguIsRGoKR6rpJQkP6H_Ames,2187
126
+ openmd/core/readable.py,sha256=cIHiygC4OrHvd_GxtRq7WRab-xT9Kmeg70EYuQSPzoc,4662
127
+ openmd/core/receipt.py,sha256=ng_t_jg6LjNsl4sJlUBAY_3NUWjxPy8ocIjnDNaaJTo,11841
128
+ openmd/core/renderer.py,sha256=wYz4yGWgb4HyCTP-5GdDc4PENJ4E1Tx5HB-jjGjGiNA,7584
129
+ openmd/core/runlog.py,sha256=YCpMC8MKHXiwYQv0DaJIMXj8mxtIPp7FxKCPwuKmfzY,7387
130
+ openmd/core/secrets.py,sha256=SdOyqViLqkiT1JMJdP_8080golWzP36cHHQjkIuwTFc,11042
131
+ openmd/ocr/__init__.py,sha256=uTvJedcA338NurFnA_ea5rNcdB7ZWx0FNdqd72JoocM,78
132
+ openmd/ocr/breakers.py,sha256=HnG8kYvTrymX3Uvz1VK3JDf1GENkcG8ZjjhhGj6fJ3E,3746
133
+ openmd/ocr/config.py,sha256=clKinBw6vE-n5wq_Xjh8E9fEVrMp7IKlbWZcjNDRM54,2942
134
+ openmd/ocr/documents.py,sha256=R32yxsFjkwzu5UcyN3x2wTVEr0IEiLXilNBlAOo90eI,29617
135
+ openmd/ocr/endpoint.py,sha256=F_D86hAAsJbTjXpvdR1alKF_W1OQXdOi4RWidelbBDk,3635
136
+ openmd/ocr/layout.py,sha256=P2aojdMel57rzYNRJwyL_J8N7OE-NJOsPe3cLvrv-P4,4768
137
+ openmd/ocr/layout_filter.py,sha256=4Xvu7E1SY7-vbq2b6_JNQDxmCtzZ-OJk97qt3porfs0,4545
138
+ openmd/ocr/layout_worker.py,sha256=5AURXUDsM9wXGWSMS2fA7h_hQR9WFk0ffTtrNTRu8M4,4389
139
+ openmd/ocr/native_math.py,sha256=wjvAEhs8tRC_OOIknGbm1ZsAXOw4kWgNd3owJRJECW0,11075
140
+ openmd/ocr/office.py,sha256=6Cf7wZy9vDvcbpdyIfwH6NUohkLGyWS3-CsI9yvDnBw,15730
141
+ openmd/ocr/ollama.py,sha256=djhdavrK54XNTyyscCp2MOKTdn-oXbuZYaV8N5o2gVg,8688
142
+ openmd/ocr/otsl.py,sha256=zWH-Lt_llCtRmofQpyopw2oLR2waDP_qHD9uwxA2uqk,2042
143
+ openmd/ocr/ovis-prompt.txt,sha256=2_MdWJmoYEfODNTQ0SzcVhbfG-pdmmALgjCYRCwymXw,512
144
+ openmd/ocr/pipeline.py,sha256=8GtEyikEsfVkAcWqI5YfUxL9fa7Ka2r-ON2MHSGOnQM,24196
145
+ openmd/ocr/settings.json,sha256=hEW0ylpo_IRDw85BoM3yZhJ1pSa-5Oe5ZiuqHbkUWkA,637
146
+ openmd/ocr/tiling.py,sha256=lHewkeE62L1e-7ICJ8icDLNHhK3hXrddjB-PeVqLRWA,28109
147
+ openmd/routing/__init__.py,sha256=g4EqPf1Lt8nOdVDzQI-sBDBk01AgO7phTdDQx0QqjDc,60
148
+ openmd/routing/entry_shim.py,sha256=eE4uZItwt2D3TIDqlC1OiXWVH-bP2PgoZQoeFeNVoFY,2530
149
+ openmd/routing/lifecycle.py,sha256=5DWTiV6cclzWRkSmSLzlvr_w2ITxoyUzu5_1-LSkxLY,25128
150
+ openmd/routing/loader.py,sha256=HeTI3kdtSLX_1eBvQsOqXnFupEevxhO4o10JeIGoN7M,7252
151
+ openmd/routing/providers.py,sha256=v7W3AZ7Wn7E1paOy68AYvtDVLrEKshfTdZ4-o5O9hEM,10267
152
+ openmd/routing/readers.py,sha256=1aPOBsXOCiDBAV7-kTauISgg1AG7wU2yh9_5lLEWqpQ,5227
153
+ openmd/routing/routes.json,sha256=88vHh7Ujh9GLLhOXScJ3avjsbO0-F6Wf0E0dGvr2PEM,10095
154
+ openmd/routing/runner.py,sha256=86XCpUbg7myOaPAPESZRQOy84-YB7b51MIQql4MM_MU,10229
155
+ openmd/routing/view.py,sha256=120Moeh5wDVaerN7uQ6cjObojANQizUKVvTKgB4zmsY,1735
156
+ openmd/routing/cards/builtin.json,sha256=HaOFuKZ3YbfoC2nmkrGvg2LFm0G59zFjs7DcAikiNTE,14741
157
+ openmd/routing/cards/defuddle.json,sha256=vDzPYfDx6-Muc7I1LDD7vsPu4J6-7jkeU7YBjY5PsAE,1727
158
+ openmd/routing/cards/x-tweet-fetcher.json,sha256=9M9gQqd4q4sClkihfrxDtmrkWHYpPzZQokEig4hslOg,1827
159
+ openmd/routing/cards/xyz-dl.json,sha256=RV0mQ5uznCWsFQC1hpxTqqVK62hX4Yeu1fuL8d1uQUY,1513
160
+ openmd/routing/cards/yutto.json,sha256=PXd6jN9ocpwqth3o0p-O35O6sbHIxOAu5a2MB1f5jqk,1633
161
+ openmd/runtime/__init__.py,sha256=mYCHaOY_HoKDXBZCaaCONlJP3MzfGC4960S761oKdME,78
162
+ openmd/runtime/_windows_job.py,sha256=7KIGe55pJegJiXbRYH6oNARF6QNW13b7YCXIyFuE3OQ,11103
163
+ openmd/runtime/asr.py,sha256=4bqK44i5vXI1G-hIq_mEB9vaNy3QBmxoUiABx80qSNI,12387
164
+ openmd/runtime/asr_worker.py,sha256=MaU8RQOc0Fziv-yad-gnM3ofLYdVUjffp0NrrX37qZ0,5897
165
+ openmd/runtime/gpu_lock.py,sha256=8TyBHOj7DFpqyjM6E3bSJGOffJxxITKm6wvkiAfz1tA,2962
166
+ openmd/runtime/gpu_quarantine.py,sha256=JUKkXVXVtHTirHGOrzUP7s4SjYsYsNkhcxA85Fnc-48,3724
167
+ openmd/runtime/interpreters.py,sha256=p7612gApD-C0tmBXuidlM8bvGo-jvUUTfBoktk24CmM,20172
168
+ openmd/runtime/managed_tools.py,sha256=dggp5RMjBK-Ub-1_kgVnqh9VS7wvZg8KBIdJKVgY_94,8209
169
+ openmd/runtime/models.py,sha256=-_t_tLTyu4tLaP62FXEI9MUwTryY-kZX8ZSzBH78Pwk,9517
170
+ openmd/runtime/services.py,sha256=g2qa8SMEinkvMgaDUaXBhid4tPciT1TFJImGEu_akSw,7867
171
+ openmd/runtime/tempdirs.py,sha256=mzhmPJ9fjYK5rvvaXOHUJICNLcy9Q-1f1FNfd4SM-vo,9139
172
+ openmd_cli-0.1.0.dist-info/licenses/LICENSE,sha256=7Bw0bCz-dhYgU-WqpWRZZCx4l-5q5auk7QiAweAeMoY,1066
173
+ openmd_cli-0.1.0.dist-info/METADATA,sha256=-kb5EQKcQhPM1vtSWNcqrMsSNGMTPSrMEDs4X5GtnCQ,12092
174
+ openmd_cli-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
175
+ openmd_cli-0.1.0.dist-info/entry_points.txt,sha256=XM7lITA5SJNLa4Wxv-MbU-CFXWT5RxtqsEhkB6asmJo,43
176
+ openmd_cli-0.1.0.dist-info/top_level.txt,sha256=C_6VqPIAKSjzb2LCEE5pNh9fPWURfeBMDB_94-BjyXA,7
177
+ openmd_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ openmd = openmd.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 baigao417
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.
@@ -0,0 +1 @@
1
+ openmd