bilinote-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.
- bilinote_cli-0.1.0.dist-info/METADATA +205 -0
- bilinote_cli-0.1.0.dist-info/RECORD +89 -0
- bilinote_cli-0.1.0.dist-info/WHEEL +4 -0
- bilinote_cli-0.1.0.dist-info/entry_points.txt +2 -0
- bilinote_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- src/__init__.py +0 -0
- src/app/config_manager.py +287 -0
- src/app/core/__init__.py +0 -0
- src/app/decorators/__init__.py +0 -0
- src/app/decorators/timeit.py +16 -0
- src/app/downloaders/__init__.py +0 -0
- src/app/downloaders/base.py +52 -0
- src/app/downloaders/bilibili_downloader.py +365 -0
- src/app/downloaders/common.py +1 -0
- src/app/downloaders/douyin_downloader.py +294 -0
- src/app/downloaders/douyin_helper/abogus.py +635 -0
- src/app/downloaders/kuaishou_downloader.py +93 -0
- src/app/downloaders/kuaishou_helper/__init__.py +0 -0
- src/app/downloaders/kuaishou_helper/kuaishou.py +96 -0
- src/app/downloaders/local_downloader.py +137 -0
- src/app/downloaders/xiaoyuzhoufm_download.py +25 -0
- src/app/downloaders/youtube_downloader.py +121 -0
- src/app/downloaders/youtube_subtitle.py +98 -0
- src/app/enmus/exception.py +21 -0
- src/app/enmus/note_enums.py +7 -0
- src/app/enmus/task_status_enums.py +28 -0
- src/app/exceptions/__init__.py +0 -0
- src/app/exceptions/biz_exception.py +6 -0
- src/app/exceptions/note.py +9 -0
- src/app/exceptions/provider.py +12 -0
- src/app/gpt/__init__.py +0 -0
- src/app/gpt/base.py +17 -0
- src/app/gpt/gpt_factory.py +13 -0
- src/app/gpt/prompt.py +77 -0
- src/app/gpt/prompt_builder.py +114 -0
- src/app/gpt/provider/OpenAI_compatible_provider.py +31 -0
- src/app/gpt/request_chunker.py +161 -0
- src/app/gpt/universal_gpt.py +348 -0
- src/app/gpt/utils.py +4 -0
- src/app/models/__init__.py +0 -0
- src/app/models/audio_model.py +15 -0
- src/app/models/gpt_model.py +19 -0
- src/app/models/model_config.py +16 -0
- src/app/models/notes_model.py +12 -0
- src/app/models/pipeline_model.py +27 -0
- src/app/models/provide_model.py +16 -0
- src/app/models/transcriber_model.py +16 -0
- src/app/models/video_record.py +0 -0
- src/app/secret_manager.py +116 -0
- src/app/services/__init__.py +0 -0
- src/app/services/batch_processor.py +343 -0
- src/app/services/cache/__init__.py +0 -0
- src/app/services/cache/task_cache.py +122 -0
- src/app/services/constant.py +17 -0
- src/app/services/note.py +141 -0
- src/app/services/pipeline/__init__.py +0 -0
- src/app/services/pipeline/ai_processor.py +98 -0
- src/app/services/pipeline/preparer.py +264 -0
- src/app/services/postprocessing.py +54 -0
- src/app/services/searcher.py +113 -0
- src/app/services/serial_executor.py +17 -0
- src/app/transcriber/__init__.py +0 -0
- src/app/transcriber/base.py +14 -0
- src/app/transcriber/bcut.py +245 -0
- src/app/transcriber/groq.py +76 -0
- src/app/transcriber/kuaishou.py +109 -0
- src/app/transcriber/transcriber_provider.py +134 -0
- src/app/transcriber/whisper_cpp.py +120 -0
- src/app/utils/cookie_helper.py +18 -0
- src/app/utils/env_checker.py +0 -0
- src/app/utils/file_cleanup.py +36 -0
- src/app/utils/logger.py +37 -0
- src/app/utils/note_helper.py +66 -0
- src/app/utils/path_helper.py +150 -0
- src/app/utils/screenshot_marker.py +13 -0
- src/app/utils/status_code.py +12 -0
- src/app/utils/url_parser.py +81 -0
- src/app/utils/video_helper.py +70 -0
- src/app/utils/video_reader.py +188 -0
- src/app/validators/__init__.py +0 -0
- src/app/validators/video_url_validator.py +38 -0
- src/cli.py +613 -0
- src/config/__init__.py +0 -0
- src/config/config.yaml.example +37 -0
- src/config/dev_config.json +6 -0
- src/config/model_config_manager.py +218 -0
- src/config/models.json +84 -0
- src/config/transcriber.json +29 -0
- src/ffmpeg_helper.py +49 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bilinote-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI 视频笔记生成 CLI 工具
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: bilibili-api-python>=17.4.1
|
|
8
|
+
Requires-Dist: browser-cookie3>=0.20.1
|
|
9
|
+
Requires-Dist: ffmpeg-python>=0.2.0
|
|
10
|
+
Requires-Dist: gmssl>=3.2.2
|
|
11
|
+
Requires-Dist: httpx>=0.28.1
|
|
12
|
+
Requires-Dist: keyring>=25.7.0
|
|
13
|
+
Requires-Dist: openai>=2.32.0
|
|
14
|
+
Requires-Dist: pillow>=12.2.0
|
|
15
|
+
Requires-Dist: pydantic>=2.13.2
|
|
16
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
17
|
+
Requires-Dist: requests>=2.33.1
|
|
18
|
+
Requires-Dist: youtube-transcript-api>=1.2.4
|
|
19
|
+
Requires-Dist: yt-dlp>=2026.3.17
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# BiliNote-CLI
|
|
23
|
+
[English](README.en.md) | 简体中文
|
|
24
|
+
|
|
25
|
+
AI 视频笔记生成工具 - 让 AI 为你的视频做笔记
|
|
26
|
+
|
|
27
|
+
> **项目说明**:本项目基于 [BiliNote](https://github.com/JefferyHcool/BiliNote) fork 并进行大量重构。
|
|
28
|
+
> 原项目采用 MIT 许可证,感谢原作者 Jeffery Huang 的杰出工作。
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## 功能
|
|
32
|
+
|
|
33
|
+
让 ai 自动把视频转换为 markdown 格式笔记
|
|
34
|
+
- 多平台支持:Bilibili、YouTube、抖音、快手、本地视频
|
|
35
|
+
- AI 笔记生成:支持 DeepSeek、OpenAI、Qwen 等
|
|
36
|
+
- 截图插入、视频跳转链接、多模态理解
|
|
37
|
+
- 批量执行、关键词搜索
|
|
38
|
+
|
|
39
|
+
## 安装
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# 安装 CLI
|
|
43
|
+
cd BiliNote-cli
|
|
44
|
+
uv tool install .
|
|
45
|
+
|
|
46
|
+
# 安装 FFmpeg(必需)
|
|
47
|
+
brew install ffmpeg # macOS
|
|
48
|
+
winget install ffmpeg # Windows
|
|
49
|
+
# Windows还可以:从 ffmpeg.org 下载,解压后,再将 bin 文件夹路径添加到系统环境变量 PATH 中
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 配置
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# 设置 API Key(必需)
|
|
56
|
+
bilinote config set DEEPSEEK_API_KEY sk-your-api-key
|
|
57
|
+
|
|
58
|
+
# 设置 Cookie(推荐,用于获取平台字幕、避免被反爬虫)
|
|
59
|
+
bilinote config set BILIBILI_COOKIE "SESSDATA=xxx; ..."
|
|
60
|
+
|
|
61
|
+
# 查看配置状态
|
|
62
|
+
bilinote config list
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
非敏感配置在 `~/.bilinote/config.yaml`:
|
|
67
|
+
```yaml
|
|
68
|
+
transcriber:
|
|
69
|
+
default_type: "bcut" # 默认音频转写器
|
|
70
|
+
whisper_model_size: "base"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 【教程】提取 Safari Cookie
|
|
74
|
+
|
|
75
|
+
1. 打开 Safari 的开发者模式。
|
|
76
|
+
2. 在浏览器中打开目标网站,按 F12(或 Cmd + Opt + I)打开开发者工具。
|
|
77
|
+
3. 切换到 **Network (网络)** 标签页。
|
|
78
|
+
4. 刷新页面,随便找一个请求,**右键**点击该请求。
|
|
79
|
+
5. 选择 **Copy** -> **Copy as cURL**。
|
|
80
|
+
6. 在你的剪贴板里,你会看到一串完整的命令,其中 `-H 'cookie: ...'` 后面就是该网站的所有 Cookie。
|
|
81
|
+
|
|
82
|
+
## 使用
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# 基础用法
|
|
86
|
+
bilinote process "https://www.bilibili.com/video/BV1mQ9jBcEf4"
|
|
87
|
+
|
|
88
|
+
# 带截图和链接
|
|
89
|
+
bilinote process "<url>" --screenshot --link --style 学术风
|
|
90
|
+
|
|
91
|
+
# 多模态理解(AI 看画面)
|
|
92
|
+
bilinote process "<url>" --video-understanding --model gpt-4o
|
|
93
|
+
|
|
94
|
+
# 自定义输出目录
|
|
95
|
+
bilinote process "<url>" --output-dir ./notes/
|
|
96
|
+
|
|
97
|
+
# 搜索视频
|
|
98
|
+
bilinote search "Python 教程" --platform bilibili
|
|
99
|
+
|
|
100
|
+
# 其他命令
|
|
101
|
+
bilinote model-list # 列出可用模型
|
|
102
|
+
bilinote model-set-default deepseek # 设置默认模型
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 命令说明
|
|
106
|
+
|
|
107
|
+
| 命令 | 说明 |
|
|
108
|
+
|------|------|
|
|
109
|
+
| `process <url>` | 处理视频生成笔记 |
|
|
110
|
+
| `search <keyword>` | 搜索视频并批量生成 |
|
|
111
|
+
| `model-list` | 列出可用模型 |
|
|
112
|
+
| `model-set-default <model>` | 设置默认模型 |
|
|
113
|
+
| `config set <key> <value>` | 设置密钥 |
|
|
114
|
+
| `config list` | 查看配置状态 |
|
|
115
|
+
|
|
116
|
+
### process 参数
|
|
117
|
+
|
|
118
|
+
| 参数 | 说明 |
|
|
119
|
+
|------|------|
|
|
120
|
+
| `--model` | AI 模型,如 `deepseek-chat`, `gpt-4o` |
|
|
121
|
+
| `--quality` | 音频质量:`fast`, `medium`, `slow` |
|
|
122
|
+
| `--screenshot` | 插入视频截图 |
|
|
123
|
+
| `--link` | 插入视频跳转链接 |
|
|
124
|
+
| `--style` | 笔记风格:学术风、口语风等 |
|
|
125
|
+
| `--video-understanding` | 启用多模态理解 |
|
|
126
|
+
| `--output` | 输出文件路径 |
|
|
127
|
+
|
|
128
|
+
## 目录结构
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
~/.bilinote/ #windows: C:\Users\<user_name>\.bilinote\
|
|
132
|
+
├── data/
|
|
133
|
+
│ ├── downloads/ # 下载的音频
|
|
134
|
+
│ ├── cache/ # 转写缓存
|
|
135
|
+
│ ├── output/notes/ # 生成的笔记★
|
|
136
|
+
│ └── state/ # 任务状态
|
|
137
|
+
├── config.yaml # 用户配置★
|
|
138
|
+
└── logs/
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 【指南】配置 whisper-cpp 作为本地音频转写器
|
|
142
|
+
|
|
143
|
+
### macOS 安装 whisper.cpp 指南
|
|
144
|
+
|
|
145
|
+
#### 1. 安装 whisper-cpp
|
|
146
|
+
```bash
|
|
147
|
+
brew install whisper-cpp
|
|
148
|
+
```
|
|
149
|
+
#### 2. 下载模型
|
|
150
|
+
```bash
|
|
151
|
+
# 创建目录并进入
|
|
152
|
+
mkdir -p ~/whisper-models && cd ~/whisper-models
|
|
153
|
+
# 下载模型(以 base 模型为例)
|
|
154
|
+
curl -L -o ggml-base.bin 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin?download=true'
|
|
155
|
+
```
|
|
156
|
+
- 或:到 [huggingface 网站](https://huggingface.co/ggerganov/whisper.cpp/tree/main) 下载 whisper 模型。手动移动到模型存储目录。
|
|
157
|
+
- ⚠️ **注意**:如果你选择把模型放入其他目录,那么需在 `config.yaml` 内修改模型地址。
|
|
158
|
+
|
|
159
|
+
### Windows 安装 whisper.cpp 指南
|
|
160
|
+
|
|
161
|
+
#### 1. 下载程序
|
|
162
|
+
|
|
163
|
+
- 访问 [whisper.cpp Github Releases 页面](https://github.com/ggerganov/whisper.cpp/releases)
|
|
164
|
+
- 下载软件压缩包:
|
|
165
|
+
- CPU 版:`whisper-bin-x64.zip`
|
|
166
|
+
- NVIDIA 显卡加速版:`whisper-cublas-12.4.0-bin-x64.zip`
|
|
167
|
+
- 解压到固定目录,如 `D:\ProgramFiles\whispercpp`
|
|
168
|
+
|
|
169
|
+
#### 2. 下载模型
|
|
170
|
+
|
|
171
|
+
- 访问 [Huggingface 模型页面](https://huggingface.co/ggerganov/whisper.cpp/tree/main)
|
|
172
|
+
- 下载 `ggml-base.bin`(或其他模型)
|
|
173
|
+
- 建议放入 `D:\ProgramFiles\whispercpp\models\`
|
|
174
|
+
|
|
175
|
+
#### 3. 添加到 PATH
|
|
176
|
+
|
|
177
|
+
以**管理员**身份打开命令提示符,执行:
|
|
178
|
+
```cmd
|
|
179
|
+
setx /M PATH "D:\ProgramFiles\whispercpp;%PATH%"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- ⚠️ **注意**:上面的 `D:\ProgramFiles\whispercpp` 需替换为 `whisper-cli.exe` 所在的目录。
|
|
183
|
+
- **重启命令提示符**后生效。
|
|
184
|
+
- 验证是否安装成功:`whisper-cli --help`
|
|
185
|
+
- 如果安装遇到问题,建议手动在系统环境变量中添加。
|
|
186
|
+
|
|
187
|
+
### 配置 whisper-cpp(macOS / Windows 通用)
|
|
188
|
+
|
|
189
|
+
在 `config.yaml` 中填写模型文件(如`ggml-base.bin`)的路径:
|
|
190
|
+
|
|
191
|
+
```yaml
|
|
192
|
+
|
|
193
|
+
transcriber:
|
|
194
|
+
default_type: "whisper-cpp"
|
|
195
|
+
whisper-cpp:
|
|
196
|
+
model_path: "D:/ProgramFiles/whispercpp/models/ggml-base.bin" # Win
|
|
197
|
+
# model_path: "~/whisper-models/ggml-base.bin" # macOS
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- ⚠️ **注意**:地址应该使用 `/` 而不是 `\`
|
|
201
|
+
- ⚠️ **注意**:如果模型放在其他位置,`model_path` 需相应修改。
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
MIT
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
src/cli.py,sha256=ntGHEGqU8wGg9OP6x3eAdjv79Kxcl7u7zkzlg3Ve0kw,22538
|
|
3
|
+
src/ffmpeg_helper.py,sha256=G8HG5vKjcP47WojunOeHZX9fVHiFuvIQ5j7MVwwEJ3o,2097
|
|
4
|
+
src/app/config_manager.py,sha256=WGstP1m6ZUvPVgtXr2Oc-ifAWK23ah8uAGNdrQBC11M,10212
|
|
5
|
+
src/app/secret_manager.py,sha256=JgZ9TMuMTjx8tia2BjnhiEMWhc3NdM9I57X-nwaD5Zg,3370
|
|
6
|
+
src/app/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
src/app/decorators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
src/app/decorators/timeit.py,sha256=xCxkqASysjs8XfTJRp5O4PRsv-v3ubd8Ilw8qOh30vE,418
|
|
9
|
+
src/app/downloaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
src/app/downloaders/base.py,sha256=b63JEpNTXFe-YKNaRsjL-qdeBrCSa_5cKhwhqsZ-Y_Y,1663
|
|
11
|
+
src/app/downloaders/bilibili_downloader.py,sha256=mZO79Mu2VbX-CB2rIIiJq8fKA2p2fWwg_WQkc_s6QBQ,13349
|
|
12
|
+
src/app/downloaders/common.py,sha256=3PZFCaeZbClO1Nd92A3dsaloBf1NR7ED5k3SorexaaU,18
|
|
13
|
+
src/app/downloaders/douyin_downloader.py,sha256=rN2rtWncEz0ThS6Y3sWEBqlFTJsEyFpTt2C8IrwY9Dc,14627
|
|
14
|
+
src/app/downloaders/kuaishou_downloader.py,sha256=c-aBOMhwiT6F6yucg_sbhQLS2LZm96cvoDBvnLOpxtg,3377
|
|
15
|
+
src/app/downloaders/local_downloader.py,sha256=SMbdZYJvoa6XeGObjJoDiQBL8ziYIQKSk4Qzi3iuC-8,4973
|
|
16
|
+
src/app/downloaders/xiaoyuzhoufm_download.py,sha256=f4cezJ1S2KhCo18D559CjXqQTwsWECxaUCoyhiBO6aI,822
|
|
17
|
+
src/app/downloaders/youtube_downloader.py,sha256=NPWOO6KQH1coHh3Em-OyljPKNBu1ugUuZ6ja_OJ9PFw,4132
|
|
18
|
+
src/app/downloaders/youtube_subtitle.py,sha256=OpMzi8CuXikx5kABnxiy9esZHVeDkn9c_vw_Cr1j8dY,3784
|
|
19
|
+
src/app/downloaders/douyin_helper/abogus.py,sha256=hIczkIx66XuR-VaXhM8iJVz_kKjEJnh87jUMaSAR_k4,17980
|
|
20
|
+
src/app/downloaders/kuaishou_helper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
src/app/downloaders/kuaishou_helper/kuaishou.py,sha256=G5XwYDwOWaSNgHsw_itYFSr9vkQCnwULoKYOg1uSg_k,4914
|
|
22
|
+
src/app/enmus/exception.py,sha256=K2-0TKu2wW5w0yrzIWCQ2sw9bFYkL9UJTiPQ4TD3hTY,679
|
|
23
|
+
src/app/enmus/note_enums.py,sha256=B2TXZyfjyK9OR-a3ymkF-1MfifxXLBVg1dT4S2ksrus,111
|
|
24
|
+
src/app/enmus/task_status_enums.py,sha256=yRgcBmZj3NzibVbsJWU4rDKsW2bumG9boZklVGZkxWQ,783
|
|
25
|
+
src/app/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
src/app/exceptions/biz_exception.py,sha256=kroC6RlhauYcoFrca_-u5GUZiA3xmm_bkdnojBn8cgA,183
|
|
27
|
+
src/app/exceptions/note.py,sha256=MGBg6JMobfaLdo42nzEWCjofD1du1lns7oFCTSDLHCQ,253
|
|
28
|
+
src/app/exceptions/provider.py,sha256=MUqP4eaJhoCNOTl0kusBS4rYStV1zQnIe9rJG0ib7aw,277
|
|
29
|
+
src/app/gpt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
src/app/gpt/base.py,sha256=O3ruJfFJ3qQaPsHViMTSApW2v4fRBS2L5LB4MV-xlTs,338
|
|
31
|
+
src/app/gpt/gpt_factory.py,sha256=bc8x0ECE1WsgP9rlcrCid5xDyIvph-3rT456wQcT-qc,489
|
|
32
|
+
src/app/gpt/prompt.py,sha256=XIeSsbsCWCXQ1TKeD0WxDqFziA8i6gkzAJ-6d1qoeTE,2946
|
|
33
|
+
src/app/gpt/prompt_builder.py,sha256=2YOI73E2F2GlBDF2BH7XBMaXNU91uKdpOkQ3n4ZEO_0,5418
|
|
34
|
+
src/app/gpt/request_chunker.py,sha256=3Mxn7EZqhBoz-GX56Srbf74aOV0mWGGj8Y3xhsP3x7A,6034
|
|
35
|
+
src/app/gpt/universal_gpt.py,sha256=5IKvHRw4NM5qBJXs2fG2e9dVThlzT-ykHCrAPzICIXI,13534
|
|
36
|
+
src/app/gpt/utils.py,sha256=EMw4iPrRJZc0XTnwuiO66E2AdbjSiHafYWkWiMYKF8c,107
|
|
37
|
+
src/app/gpt/provider/OpenAI_compatible_provider.py,sha256=_yaJ7VIRFD8GXxGvumDZ27VJ8gbJ_k9dcZaNbIQ0sLU,951
|
|
38
|
+
src/app/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
+
src/app/models/audio_model.py,sha256=I_U9F6zKTbsr4HUWA23p7a_GQEmjmhbFKQg5rWKKq9s,567
|
|
40
|
+
src/app/models/gpt_model.py,sha256=f_rgUKQJX5hIcFqZUgG-Mk9uwdGB0OfmvY8qS8_za9E,500
|
|
41
|
+
src/app/models/model_config.py,sha256=ZP8QO4GkyQreBFW0op4Lj70GmH9hkBUzH2Swfbcap-4,757
|
|
42
|
+
src/app/models/notes_model.py,sha256=nJDqBS7FzWnmeuVtnN67UhswXrgj0-tZd6PavGv3Jtc,442
|
|
43
|
+
src/app/models/pipeline_model.py,sha256=s8tBFcHTqvbFY6YtNrddgee5AIO-sBfC680TrTauQL0,812
|
|
44
|
+
src/app/models/provide_model.py,sha256=74r5Anoq2YgOKLqNjdkL7J86u3-OF5fb_-IynoljAXY,708
|
|
45
|
+
src/app/models/transcriber_model.py,sha256=YasqQlXKVZU0n3kR9GfrkvMvDoYRFespnJGcytr9RBo,636
|
|
46
|
+
src/app/models/video_record.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
|
+
src/app/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
|
+
src/app/services/batch_processor.py,sha256=cKH-OmLJA_r9qfxdFnw2iQZwAAaFFyQpkXj4RgN9Xuc,12985
|
|
49
|
+
src/app/services/constant.py,sha256=qqsay72goXNDdFrd9S4k8vZGm30-72iIx5oiiu-rA64,716
|
|
50
|
+
src/app/services/note.py,sha256=Ztdh3AcG3xSVYeZafXHq0kvsPw5NPWdK4jt6DEgRjqs,5045
|
|
51
|
+
src/app/services/postprocessing.py,sha256=PGBYWAam0z1OcI_5KgF50wUhtlIH_SAOaAvuP5vAbQo,2048
|
|
52
|
+
src/app/services/searcher.py,sha256=yNsxiUNgm43eKlb8Ygm5bKxoRET8IZSBaTBzk5_Jbfs,3703
|
|
53
|
+
src/app/services/serial_executor.py,sha256=BmdUWc9075Pcv9-70L9teFGq4Wok-Riq28ESWUzplzA,440
|
|
54
|
+
src/app/services/cache/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
55
|
+
src/app/services/cache/task_cache.py,sha256=Onx95-gsxecUCIJlCnrG-hn6Of7re7_xu7CoKeskz7U,4814
|
|
56
|
+
src/app/services/pipeline/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
|
+
src/app/services/pipeline/ai_processor.py,sha256=EmRcl42PLUGIY68ahz1buvc_CbpauHjR-TvsZBJ8Td0,3866
|
|
58
|
+
src/app/services/pipeline/preparer.py,sha256=xU3wvo75TJA198Vn-ZuSyb053lOujlaP4uaKOIpIH9A,10610
|
|
59
|
+
src/app/transcriber/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
+
src/app/transcriber/base.py,sha256=7-7mnXW7QWZi-hznS6rw__K0pzKfyCQnxfgrlCpROEg,325
|
|
61
|
+
src/app/transcriber/bcut.py,sha256=l4G7yhP3KbW4bTyDAdhTlB8s-9vFpc2ZRqdjfjWhzdc,8575
|
|
62
|
+
src/app/transcriber/groq.py,sha256=rn0MURUGDelSokFKnlr-gfRu83QRG2j8ZG60O2LpkpQ,2721
|
|
63
|
+
src/app/transcriber/kuaishou.py,sha256=JpeucAnQ0kzp5uLJyZxiPmSFplbyzWL0sa5V9Rk3Sv0,3806
|
|
64
|
+
src/app/transcriber/transcriber_provider.py,sha256=xldB30Cdf-qbPy9_RZ7zg7C7yzMwzKf-SquY2--o8p8,4243
|
|
65
|
+
src/app/transcriber/whisper_cpp.py,sha256=bJGC5qK-TKUv1W2f3PsG7DqVkDqRngg0Kx-4sfXaw7Y,4218
|
|
66
|
+
src/app/utils/cookie_helper.py,sha256=rnkNTV5MfAI3M03IyDVLHNZucrlLbl8BFb2tfHnO4sk,434
|
|
67
|
+
src/app/utils/env_checker.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
|
+
src/app/utils/file_cleanup.py,sha256=bJPxvz_5nOux4-UfSAug3Km7wAr_4MV8XTu2UYT75Lo,1142
|
|
69
|
+
src/app/utils/logger.py,sha256=qOg1f5RMHW-bv5Aklb7FLAKlLklpASIeQFAqf-0fsxw,1095
|
|
70
|
+
src/app/utils/note_helper.py,sha256=XakHdblX4dyXFbPqp7qMYW07PZFcs1973FHxrp22RdY,2407
|
|
71
|
+
src/app/utils/path_helper.py,sha256=S0qRkVa_aFr3atL8ujic3wiGyj86B9HWk0uajp0Qq00,5798
|
|
72
|
+
src/app/utils/screenshot_marker.py,sha256=ucR4B0xsHl3I-zGl23Z8T6HAqrUUg_AAy_UWYIYugb8,494
|
|
73
|
+
src/app/utils/status_code.py,sha256=_g9QNLamsBKfab2ivBp4ia-D_lwbT2tlYK_-EW1Uvik,209
|
|
74
|
+
src/app/utils/url_parser.py,sha256=YWSve0g4mH0_25pxBoaaAeUN7orXUTUciXuwRhJi_Pk,2253
|
|
75
|
+
src/app/utils/video_helper.py,sha256=n6PnK-kqqI8Usd9v0fd1iJZhxX6ZxmagcNWnoPoH_Fc,2207
|
|
76
|
+
src/app/utils/video_reader.py,sha256=xpZps6o34_DzCP_Zr9MWdkO4wG2R8EUR0RI_oa8sMDk,8111
|
|
77
|
+
src/app/validators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
78
|
+
src/app/validators/video_url_validator.py,sha256=syKDVAHDUDC_h62_NvGNZ8Tq1DTXKRKQecsi7R5A43Y,1051
|
|
79
|
+
src/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
80
|
+
src/config/config.yaml.example,sha256=7Qh1DSDg4M1eQu1sP1n1xZ5HFocaqzWHSUW1kWryZm0,1392
|
|
81
|
+
src/config/dev_config.json,sha256=Qm28vNH8i1SVTNbpte9L5yQ0RZVZbfAwx3DcKpFcu-w,79
|
|
82
|
+
src/config/model_config_manager.py,sha256=gOv6UFIoiocl8O-hNcq1MP2V9-f9Z2iyLOFi1jcOr-Y,6884
|
|
83
|
+
src/config/models.json,sha256=cBr4Rd99Ctvlw7TV1Wowr6N3wqaFLT4A8wAWuF_tzTk,2471
|
|
84
|
+
src/config/transcriber.json,sha256=ULE5bkegXwTxOM5QBVy3jkrUjIvSWLZyLsRQgsUpYdQ,862
|
|
85
|
+
bilinote_cli-0.1.0.dist-info/METADATA,sha256=yn5c5eBLzQNHlbyuAiYbqGbTILw58o0TBQSV3vhaipw,6240
|
|
86
|
+
bilinote_cli-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
87
|
+
bilinote_cli-0.1.0.dist-info/entry_points.txt,sha256=jUTT2VYH131wUA3I179HSGGASG_pgNsWntm-r_QrrC0,42
|
|
88
|
+
bilinote_cli-0.1.0.dist-info/licenses/LICENSE,sha256=6v2Mq4O6Bfqb0qIs4w6Q4-xbV5bWzaSYozkI7cxO58g,1071
|
|
89
|
+
bilinote_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Jeffery Huang
|
|
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.
|
src/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
配置管理器
|
|
3
|
+
使用 YAML 文件管理非敏感配置,存储在 ~/.bilinote/config.yaml
|
|
4
|
+
同时加载 transcriber.json 作为转写器默认配置
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Optional, Dict, List
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
def _get_config_example_path() -> Path:
|
|
18
|
+
"""获取 config.yaml.example 模板文件路径"""
|
|
19
|
+
# 从当前文件位置推导: app/config_manager.py -> src/config/config.yaml.example
|
|
20
|
+
base_dir = Path(__file__).parent.parent
|
|
21
|
+
return base_dir / "config" / "config.yaml.example"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_default_config() -> str:
|
|
25
|
+
"""根据操作系统读取配置模板并填充占位符"""
|
|
26
|
+
example_path = _get_config_example_path()
|
|
27
|
+
if not example_path.exists():
|
|
28
|
+
raise FileNotFoundError(f"配置文件模板不存在: {example_path}")
|
|
29
|
+
|
|
30
|
+
template = example_path.read_text(encoding="utf-8")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# macOS 默认 whisper 模型路径
|
|
35
|
+
if platform.system() == "Darwin":
|
|
36
|
+
default_whisper_model_path = str(Path.home() / "whisper-models" / "ggml-base.bin")
|
|
37
|
+
else:
|
|
38
|
+
default_whisper_model_path = ""
|
|
39
|
+
|
|
40
|
+
# 填充占位符
|
|
41
|
+
return template.format(
|
|
42
|
+
default_whisper_model_path=default_whisper_model_path,
|
|
43
|
+
example_whisper_model_path="~/whisper-models/ggml-base.bin",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _get_config_path() -> Path:
|
|
48
|
+
"""获取配置文件路径"""
|
|
49
|
+
bilinote_home = Path.home() / ".bilinote"
|
|
50
|
+
return bilinote_home / "config.yaml"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _deep_merge(base: dict, override: dict) -> dict:
|
|
54
|
+
"""深度合并字典,override 覆盖 base"""
|
|
55
|
+
result = dict(base)
|
|
56
|
+
for key, value in override.items():
|
|
57
|
+
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
58
|
+
result[key] = _deep_merge(result[key], value)
|
|
59
|
+
else:
|
|
60
|
+
result[key] = value
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _get_by_path(data: dict, path: str, default: Any = None) -> Any:
|
|
65
|
+
"""通过点号路径获取嵌套字典值,如 'models.openai.base_url'"""
|
|
66
|
+
keys = path.split(".")
|
|
67
|
+
current = data
|
|
68
|
+
for key in keys:
|
|
69
|
+
if not isinstance(current, dict) or key not in current:
|
|
70
|
+
return default
|
|
71
|
+
current = current[key]
|
|
72
|
+
return current
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _set_by_path(data: dict, path: str, value: Any) -> dict:
|
|
76
|
+
"""通过点号路径设置嵌套字典值"""
|
|
77
|
+
keys = path.split(".")
|
|
78
|
+
current = data
|
|
79
|
+
for key in keys[:-1]:
|
|
80
|
+
if key not in current or not isinstance(current[key], dict):
|
|
81
|
+
current[key] = {}
|
|
82
|
+
current = current[key]
|
|
83
|
+
current[keys[-1]] = value
|
|
84
|
+
return data
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _load_default_config() -> dict:
|
|
88
|
+
"""加载默认配置"""
|
|
89
|
+
return yaml.safe_load(_get_default_config())
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _get_transcriber_json_path() -> Path:
|
|
93
|
+
"""获取 transcriber.json 路径"""
|
|
94
|
+
# 从当前文件位置推导: app/config_manager.py -> src/config/transcriber.json
|
|
95
|
+
base_dir = Path(__file__).parent.parent
|
|
96
|
+
return base_dir / "config" / "transcriber.json"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _load_transcriber_defaults() -> dict:
|
|
100
|
+
"""加载 transcriber.json 作为默认配置"""
|
|
101
|
+
path = _get_transcriber_json_path()
|
|
102
|
+
if not path.exists():
|
|
103
|
+
return {}
|
|
104
|
+
try:
|
|
105
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
106
|
+
return json.load(f)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.warning(f"加载 transcriber.json 失败: {e}")
|
|
109
|
+
return {}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _get_dev_config_json_path() -> Path:
|
|
113
|
+
"""获取 dev_config.json 路径"""
|
|
114
|
+
base_dir = Path(__file__).parent.parent
|
|
115
|
+
return base_dir / "config" / "dev_config.json"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _load_dev_config_defaults() -> dict:
|
|
119
|
+
"""加载 dev_config.json 作为开发者默认配置"""
|
|
120
|
+
path = _get_dev_config_json_path()
|
|
121
|
+
if not path.exists():
|
|
122
|
+
return {}
|
|
123
|
+
try:
|
|
124
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
125
|
+
return json.load(f)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.warning(f"加载 dev_config.json 失败: {e}")
|
|
128
|
+
return {}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class ConfigManager:
|
|
132
|
+
"""YAML 配置管理器,单例模式"""
|
|
133
|
+
|
|
134
|
+
_instance: Optional["ConfigManager"] = None
|
|
135
|
+
|
|
136
|
+
def __init__(self):
|
|
137
|
+
self._config_path = _get_config_path()
|
|
138
|
+
self._config: Optional[dict] = None
|
|
139
|
+
|
|
140
|
+
@classmethod
|
|
141
|
+
def get_instance(cls) -> "ConfigManager":
|
|
142
|
+
if cls._instance is None:
|
|
143
|
+
cls._instance = cls()
|
|
144
|
+
return cls._instance
|
|
145
|
+
|
|
146
|
+
def _load(self) -> dict:
|
|
147
|
+
"""加载配置文件,与默认配置合并(优先级:config.yaml > dev_config.json > transcriber.json > 代码默认值)"""
|
|
148
|
+
# 1. 代码默认配置
|
|
149
|
+
defaults = _load_default_config()
|
|
150
|
+
# 2. transcriber.json 覆盖
|
|
151
|
+
transcriber_defaults = _load_transcriber_defaults()
|
|
152
|
+
defaults = _deep_merge(defaults, transcriber_defaults)
|
|
153
|
+
# 3. dev_config.json 开发者配置覆盖
|
|
154
|
+
dev_defaults = _load_dev_config_defaults()
|
|
155
|
+
defaults = _deep_merge(defaults, dev_defaults)
|
|
156
|
+
# 4. 用户 config.yaml 覆盖
|
|
157
|
+
if not self._config_path.exists():
|
|
158
|
+
return defaults
|
|
159
|
+
try:
|
|
160
|
+
with open(self._config_path, "r", encoding="utf-8") as f:
|
|
161
|
+
user_config = yaml.safe_load(f) or {}
|
|
162
|
+
return _deep_merge(defaults, user_config)
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.error(f"加载配置文件失败 {self._config_path}: {e}")
|
|
165
|
+
return defaults
|
|
166
|
+
|
|
167
|
+
def get_config(self) -> dict:
|
|
168
|
+
"""获取完整配置"""
|
|
169
|
+
if self._config is None:
|
|
170
|
+
self._config = self._load()
|
|
171
|
+
return dict(self._config)
|
|
172
|
+
|
|
173
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
174
|
+
"""
|
|
175
|
+
获取配置项,优先级:
|
|
176
|
+
1. 系统环境变量(最高优先级,兼容 CI/CD)
|
|
177
|
+
2. config.yaml 用户配置
|
|
178
|
+
3. 默认值
|
|
179
|
+
"""
|
|
180
|
+
# 系统环境变量优先
|
|
181
|
+
env_key = key.upper().replace(".", "_")
|
|
182
|
+
env_val = os.getenv(env_key)
|
|
183
|
+
if env_val is not None:
|
|
184
|
+
# 尝试类型转换
|
|
185
|
+
if default is not None:
|
|
186
|
+
try:
|
|
187
|
+
if isinstance(default, int):
|
|
188
|
+
return int(env_val)
|
|
189
|
+
elif isinstance(default, float):
|
|
190
|
+
return float(env_val)
|
|
191
|
+
elif isinstance(default, bool):
|
|
192
|
+
return env_val.lower() in ("true", "1", "yes")
|
|
193
|
+
except (ValueError, TypeError):
|
|
194
|
+
pass
|
|
195
|
+
return env_val
|
|
196
|
+
|
|
197
|
+
# config.yaml 回退
|
|
198
|
+
config = self.get_config()
|
|
199
|
+
return _get_by_path(config, key, default)
|
|
200
|
+
|
|
201
|
+
def set(self, key: str, value: Any) -> None:
|
|
202
|
+
"""设置配置项并保存到文件"""
|
|
203
|
+
# 加载当前文件内容(不含默认值)
|
|
204
|
+
file_config: dict = {}
|
|
205
|
+
if self._config_path.exists():
|
|
206
|
+
try:
|
|
207
|
+
with open(self._config_path, "r", encoding="utf-8") as f:
|
|
208
|
+
file_config = yaml.safe_load(f) or {}
|
|
209
|
+
except Exception:
|
|
210
|
+
file_config = {}
|
|
211
|
+
|
|
212
|
+
# 设置值
|
|
213
|
+
_set_by_path(file_config, key, value)
|
|
214
|
+
|
|
215
|
+
# 保存
|
|
216
|
+
self._config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
217
|
+
with open(self._config_path, "w", encoding="utf-8") as f:
|
|
218
|
+
yaml.dump(file_config, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
|
|
219
|
+
|
|
220
|
+
# 清除缓存
|
|
221
|
+
self._config = None
|
|
222
|
+
logger.info(f"已设置配置项: {key}")
|
|
223
|
+
|
|
224
|
+
def ensure_default_config(self) -> None:
|
|
225
|
+
"""首次运行时创建默认配置文件(根据操作系统选择不同配置)"""
|
|
226
|
+
if not self._config_path.exists():
|
|
227
|
+
self._config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
228
|
+
with open(self._config_path, "w", encoding="utf-8") as f:
|
|
229
|
+
f.write(_get_default_config())
|
|
230
|
+
system = platform.system()
|
|
231
|
+
logger.info(f"已创建默认配置文件 ({system}): {self._config_path}")
|
|
232
|
+
|
|
233
|
+
def reload(self) -> None:
|
|
234
|
+
"""重新加载配置"""
|
|
235
|
+
self._config = None
|
|
236
|
+
|
|
237
|
+
# ========== 转写器配置相关方法(从 TranscriberConfigManager 合并)==========
|
|
238
|
+
|
|
239
|
+
def get_transcriber_config(self, transcriber_type: str) -> Dict[str, Any]:
|
|
240
|
+
"""获取指定转写器的配置,支持环境变量覆盖"""
|
|
241
|
+
config = self.get_config()
|
|
242
|
+
transcriber_config = config.get("transcribers", {}).get(transcriber_type, {})
|
|
243
|
+
|
|
244
|
+
# 环境变量覆盖 → config.yaml 覆盖
|
|
245
|
+
if transcriber_type == "groq":
|
|
246
|
+
from app.secret_manager import get_secret
|
|
247
|
+
transcriber_config["api_key"] = get_secret("GROQ_API_KEY") or transcriber_config.get("api_key")
|
|
248
|
+
transcriber_config["base_url"] = self.get(
|
|
249
|
+
"models.groq.base_url",
|
|
250
|
+
transcriber_config.get("base_url")
|
|
251
|
+
)
|
|
252
|
+
transcriber_config["model"] = self.get(
|
|
253
|
+
"transcriber.groq_model",
|
|
254
|
+
transcriber_config.get("model")
|
|
255
|
+
)
|
|
256
|
+
elif transcriber_type == "whisper-cpp":
|
|
257
|
+
cli_path = self.get(
|
|
258
|
+
"transcriber.whisper-cpp.cli_path",
|
|
259
|
+
transcriber_config.get("cli_path", "whisper-cli")
|
|
260
|
+
)
|
|
261
|
+
# cli_path 为空字符串时回退到 whisper-cli
|
|
262
|
+
transcriber_config["cli_path"] = cli_path or "whisper-cli"
|
|
263
|
+
transcriber_config["model_path"] = self.get(
|
|
264
|
+
"transcriber.whisper-cpp.model_path",
|
|
265
|
+
transcriber_config.get("model_path", "")
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return transcriber_config
|
|
269
|
+
|
|
270
|
+
def get_fallback_priority(self) -> List[str]:
|
|
271
|
+
"""获取 fallback 优先级列表"""
|
|
272
|
+
config = self.get_config()
|
|
273
|
+
return config.get("fallback_priority", ["bcut", "kuaishou","whisper-cpp","groq"])
|
|
274
|
+
|
|
275
|
+
def get_default_transcriber(self) -> str:
|
|
276
|
+
"""获取默认转写器类型"""
|
|
277
|
+
return self.get("transcriber.default_type", "bcut")
|
|
278
|
+
|
|
279
|
+
def is_transcriber_enabled(self, transcriber_type: str) -> bool:
|
|
280
|
+
"""检查转写器是否启用"""
|
|
281
|
+
config = self.get_transcriber_config(transcriber_type)
|
|
282
|
+
return config.get("enabled", True)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def get_config_manager() -> ConfigManager:
|
|
286
|
+
"""获取全局 ConfigManager 单例"""
|
|
287
|
+
return ConfigManager.get_instance()
|
src/app/core/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import functools
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
def timeit(func):
|
|
8
|
+
@functools.wraps(func)
|
|
9
|
+
def wrapper(*args, **kwargs):
|
|
10
|
+
start = time.perf_counter()
|
|
11
|
+
result = func(*args, **kwargs)
|
|
12
|
+
end = time.perf_counter()
|
|
13
|
+
duration = end - start
|
|
14
|
+
logger.info(f"{func.__name__} executed in {duration:.4f} seconds")
|
|
15
|
+
return result
|
|
16
|
+
return wrapper
|
|
File without changes
|