@rookiestar/eng-lang-tutor 1.0.16 → 1.0.18
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.
Potentially problematic release.
This version of @rookiestar/eng-lang-tutor might be problematic. Click here for more details.
- package/CLAUDE.md +31 -14
- package/SKILL.md +79 -57
- package/package.json +1 -1
- package/scripts/__init__.py +28 -0
- package/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/__pycache__/audio_composer.cpython-313.pyc +0 -0
- package/scripts/__pycache__/audio_converter.cpython-313.pyc +0 -0
- package/scripts/__pycache__/audio_enhancer.cpython-313.pyc +0 -0
- package/scripts/__pycache__/audio_utils.cpython-313.pyc +0 -0
- package/scripts/__pycache__/command_parser.cpython-313.pyc +0 -0
- package/scripts/__pycache__/constants.cpython-313.pyc +0 -0
- package/scripts/__pycache__/cron_push.cpython-313.pyc +0 -0
- package/scripts/__pycache__/dedup.cpython-313.pyc +0 -0
- package/scripts/__pycache__/error_notebook.cpython-313.pyc +0 -0
- package/scripts/__pycache__/feishu_voice.cpython-313.pyc +0 -0
- package/scripts/__pycache__/gamification.cpython-313.pyc +0 -0
- package/scripts/__pycache__/scorer.cpython-313.pyc +0 -0
- package/scripts/__pycache__/state_manager.cpython-313.pyc +0 -0
- package/scripts/__pycache__/utils.cpython-313.pyc +0 -0
- package/scripts/audio/__init__.py +23 -0
- package/scripts/audio/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/audio/__pycache__/composer.cpython-313.pyc +0 -0
- package/scripts/audio/__pycache__/converter.cpython-313.pyc +0 -0
- package/scripts/audio/__pycache__/feishu_voice.cpython-313.pyc +0 -0
- package/scripts/audio/__pycache__/utils.cpython-313.pyc +0 -0
- package/scripts/{audio_composer.py → audio/composer.py} +4 -39
- package/scripts/{audio_converter.py → audio/converter.py} +4 -28
- package/scripts/{feishu_voice.py → audio/feishu_voice.py} +1 -18
- package/scripts/audio/tts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/audio/tts/__pycache__/base.cpython-313.pyc +0 -0
- package/scripts/audio/tts/__pycache__/manager.cpython-313.pyc +0 -0
- package/scripts/{tts → audio/tts}/manager.py +6 -2
- package/scripts/audio/tts/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/audio/tts/providers/__pycache__/xunfei.cpython-313.pyc +0 -0
- package/scripts/audio/utils.py +63 -0
- package/scripts/cli/__init__.py +7 -0
- package/scripts/cli/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/cli/__pycache__/cli.cpython-313.pyc +0 -0
- package/scripts/cli/__pycache__/command_parser.cpython-313.pyc +0 -0
- package/scripts/{cli.py → cli/cli.py} +8 -2
- package/scripts/core/__init__.py +30 -0
- package/scripts/core/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/core/__pycache__/constants.cpython-313.pyc +0 -0
- package/scripts/core/__pycache__/error_notebook.cpython-313.pyc +0 -0
- package/scripts/core/__pycache__/gamification.cpython-313.pyc +0 -0
- package/scripts/core/__pycache__/scorer.cpython-313.pyc +0 -0
- package/scripts/core/__pycache__/state_manager.cpython-313.pyc +0 -0
- package/scripts/{constants.py → core/constants.py} +18 -0
- package/scripts/core/error_notebook.py +305 -0
- package/scripts/{gamification.py → core/gamification.py} +11 -43
- package/scripts/{scorer.py → core/scorer.py} +2 -21
- package/scripts/{state_manager.py → core/state_manager.py} +33 -270
- package/scripts/scheduling/__init__.py +6 -0
- package/scripts/scheduling/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/scheduling/__pycache__/cron_push.cpython-313.pyc +0 -0
- package/scripts/{cron_push.py → scheduling/cron_push.py} +5 -2
- package/scripts/utils/__init__.py +12 -0
- package/scripts/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/utils/__pycache__/dedup.cpython-313.pyc +0 -0
- package/scripts/utils/__pycache__/helpers.cpython-313.pyc +0 -0
- package/scripts/{dedup.py → utils/dedup.py} +6 -0
- package/scripts/{utils.py → utils/helpers.py} +6 -0
- /package/scripts/{tts → audio/tts}/__init__.py +0 -0
- /package/scripts/{tts → audio/tts}/base.py +0 -0
- /package/scripts/{tts → audio/tts}/providers/__init__.py +0 -0
- /package/scripts/{tts → audio/tts}/providers/xunfei.py +0 -0
- /package/scripts/{command_parser.py → cli/command_parser.py} +0 -0
package/CLAUDE.md
CHANGED
|
@@ -117,7 +117,7 @@ Agent结合难度与偏好的设置,以及用户的历史学习进度动态生
|
|
|
117
117
|
|
|
118
118
|
#### 2.3.3 去重策略(14天窗口)
|
|
119
119
|
|
|
120
|
-
使用 `scripts/dedup.py` 实现三层去重:
|
|
120
|
+
使用 `scripts/utils/dedup.py` 实现三层去重:
|
|
121
121
|
|
|
122
122
|
| 层级 | 方法 | 描述 |
|
|
123
123
|
|-----|------|-----|
|
|
@@ -145,10 +145,27 @@ eng-lang-tutor/
|
|
|
145
145
|
├── SKILL.md # 核心技能文档
|
|
146
146
|
├── CLAUDE.md # 项目说明(本文件)
|
|
147
147
|
├── scripts/
|
|
148
|
-
│ ├──
|
|
149
|
-
│ ├──
|
|
150
|
-
│ ├──
|
|
151
|
-
│
|
|
148
|
+
│ ├── __init__.py # 包入口,重导出核心类
|
|
149
|
+
│ ├── core/
|
|
150
|
+
│ │ ├── state_manager.py # 状态持久化与事件日志
|
|
151
|
+
│ │ ├── error_notebook.py # 错题本管理
|
|
152
|
+
│ │ ├── scorer.py # 答案评估与XP计算
|
|
153
|
+
│ │ ├── gamification.py # 连胜/等级/徽章
|
|
154
|
+
│ │ └── constants.py # 共享常量和工具函数
|
|
155
|
+
│ ├── cli/
|
|
156
|
+
│ │ ├── cli.py # CLI入口点
|
|
157
|
+
│ │ └── command_parser.py # 命令解析
|
|
158
|
+
│ ├── audio/
|
|
159
|
+
│ │ ├── tts/ # TTS引擎(Coqui/Azure/Edge)
|
|
160
|
+
│ │ ├── composer.py # 音频合成
|
|
161
|
+
│ │ ├── converter.py # 格式转换
|
|
162
|
+
│ │ ├── feishu_voice.py # 飞书语音发送
|
|
163
|
+
│ │ └── utils.py # 音频工具函数
|
|
164
|
+
│ ├── scheduling/
|
|
165
|
+
│ │ └── cron_push.py # 定时推送
|
|
166
|
+
│ └── utils/
|
|
167
|
+
│ ├── dedup.py # 14天去重逻辑
|
|
168
|
+
│ └── helpers.py # 通用工具函数
|
|
152
169
|
├── templates/
|
|
153
170
|
│ ├── state_schema.json # 状态 JSON Schema
|
|
154
171
|
│ ├── keypoint_schema.json # 知识点 JSON Schema
|
|
@@ -179,15 +196,15 @@ eng-lang-tutor/
|
|
|
179
196
|
|
|
180
197
|
| 切片 | 对应脚本 | 输入 | 输出 |
|
|
181
198
|
|-----|---------|------|-----|
|
|
182
|
-
| 状态加载/保存 | state_manager.py | - | state.json |
|
|
183
|
-
| 事件日志 | state_manager.py | event_type, data | events_YYYY-MM.jsonl |
|
|
184
|
-
| 每日内容保存 | state_manager.py | content_type, content | daily/YYYY-MM-DD/*.json |
|
|
185
|
-
| 答案评估 | scorer.py | quiz.json + user_answers | results + updated state |
|
|
186
|
-
| XP计算 | scorer.py | correct_count, streak | XP with multiplier |
|
|
187
|
-
| 连胜更新 | gamification.py | study_date | new_streak |
|
|
188
|
-
| 等级更新 | gamification.py | XP | level |
|
|
189
|
-
| 徽章检查 | gamification.py | progress | new_badges |
|
|
190
|
-
| 去重检查 | dedup.py | new_content, recent_content | is_duplicate |
|
|
199
|
+
| 状态加载/保存 | core/state_manager.py | - | state.json |
|
|
200
|
+
| 事件日志 | core/state_manager.py | event_type, data | events_YYYY-MM.jsonl |
|
|
201
|
+
| 每日内容保存 | core/state_manager.py | content_type, content | daily/YYYY-MM-DD/*.json |
|
|
202
|
+
| 答案评估 | core/scorer.py | quiz.json + user_answers | results + updated state |
|
|
203
|
+
| XP计算 | core/scorer.py | correct_count, streak | XP with multiplier |
|
|
204
|
+
| 连胜更新 | core/gamification.py | study_date | new_streak |
|
|
205
|
+
| 等级更新 | core/gamification.py | XP | level |
|
|
206
|
+
| 徽章检查 | core/gamification.py | progress | new_badges |
|
|
207
|
+
| 去重检查 | utils/dedup.py | new_content, recent_content | is_duplicate |
|
|
191
208
|
|
|
192
209
|
### 4.2 核心工作流
|
|
193
210
|
|
package/SKILL.md
CHANGED
|
@@ -90,15 +90,17 @@ This system has two independent level systems:
|
|
|
90
90
|
|
|
91
91
|
| Script | Purpose |
|
|
92
92
|
|--------|---------|
|
|
93
|
-
| state_manager.py | State persistence, event logging, error notebook |
|
|
94
|
-
|
|
|
95
|
-
| scorer.py | Answer evaluation, XP calculation |
|
|
96
|
-
| gamification.py | Streak/level/badge logic |
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
|
|
|
100
|
-
|
|
|
101
|
-
|
|
|
93
|
+
| core/state_manager.py | State persistence, event logging, error notebook |
|
|
94
|
+
| core/error_notebook.py | Error notebook management |
|
|
95
|
+
| core/scorer.py | Answer evaluation, XP calculation |
|
|
96
|
+
| core/gamification.py | Streak/level/badge logic |
|
|
97
|
+
| core/constants.py | Shared constants (level thresholds, level names) |
|
|
98
|
+
| cli/cli.py | CLI entry point for state management |
|
|
99
|
+
| cli/command_parser.py | Natural language command parsing |
|
|
100
|
+
| scheduling/cron_push.py | Scheduled content push (keypoint/quiz placeholders) |
|
|
101
|
+
| utils/dedup.py | 14-day content deduplication |
|
|
102
|
+
| utils/helpers.py | Utility functions (safe divide, deep merge) |
|
|
103
|
+
| audio/ | Audio generation (TTS, composition, conversion, Feishu) |
|
|
102
104
|
|
|
103
105
|
## CLI Commands
|
|
104
106
|
|
|
@@ -107,48 +109,48 @@ This system has two independent level systems:
|
|
|
107
109
|
### Content Management
|
|
108
110
|
```bash
|
|
109
111
|
# Save daily content (keypoint/quiz)
|
|
110
|
-
python3 scripts
|
|
112
|
+
python3 -m scripts.cli.cli save_daily --content-type keypoint --content '<JSON>'
|
|
111
113
|
|
|
112
114
|
# Record keypoint view
|
|
113
|
-
python3 scripts
|
|
115
|
+
python3 -m scripts.cli.cli record_view [--date YYYY-MM-DD]
|
|
114
116
|
```
|
|
115
117
|
|
|
116
118
|
### Stats & Config
|
|
117
119
|
```bash
|
|
118
120
|
# Display learning progress
|
|
119
|
-
python3 scripts
|
|
121
|
+
python3 -m scripts.cli.cli stats
|
|
120
122
|
|
|
121
123
|
# Display current configuration
|
|
122
|
-
python3 scripts
|
|
124
|
+
python3 -m scripts.cli.cli config
|
|
123
125
|
|
|
124
126
|
# Update configuration
|
|
125
|
-
python3 scripts
|
|
126
|
-
python3 scripts
|
|
127
|
-
python3 scripts
|
|
127
|
+
python3 -m scripts.cli.cli config --cefr B2
|
|
128
|
+
python3 -m scripts.cli.cli config --style professional
|
|
129
|
+
python3 -m scripts.cli.cli config --oral-ratio 80
|
|
128
130
|
```
|
|
129
131
|
|
|
130
132
|
### Error Notebook
|
|
131
133
|
```bash
|
|
132
134
|
# List errors (paginated)
|
|
133
|
-
python3 scripts
|
|
135
|
+
python3 -m scripts.cli.cli errors [--page 1] [--per-page 5] [--month YYYY-MM]
|
|
134
136
|
|
|
135
137
|
# Get random errors for review
|
|
136
|
-
python3 scripts
|
|
138
|
+
python3 -m scripts.cli.cli errors --random 5
|
|
137
139
|
|
|
138
140
|
# Get error statistics
|
|
139
|
-
python3 scripts
|
|
141
|
+
python3 -m scripts.cli.cli errors --stats
|
|
140
142
|
|
|
141
143
|
# Get errors for review session
|
|
142
|
-
python3 scripts
|
|
144
|
+
python3 -m scripts.cli.cli errors --review 5
|
|
143
145
|
```
|
|
144
146
|
|
|
145
147
|
### Schedule
|
|
146
148
|
```bash
|
|
147
149
|
# Display current schedule
|
|
148
|
-
python3 scripts
|
|
150
|
+
python3 -m scripts.cli.cli schedule
|
|
149
151
|
|
|
150
152
|
# Update schedule (quiz_time must be later than keypoint_time)
|
|
151
|
-
python3 scripts
|
|
153
|
+
python3 -m scripts.cli.cli schedule --keypoint-time 07:00 --quiz-time 21:00
|
|
152
154
|
```
|
|
153
155
|
|
|
154
156
|
## Core Principles
|
|
@@ -274,10 +276,10 @@ The bot recognizes these natural language commands:
|
|
|
274
276
|
```
|
|
275
277
|
Step 1: LLM generates keypoint JSON
|
|
276
278
|
Step 2: ⛔ EXECUTE THIS BASH COMMAND (do NOT skip):
|
|
277
|
-
python3 scripts
|
|
279
|
+
python3 -m scripts.cli.cli save_daily --content-type keypoint --content '<ESCAPED_JSON>'
|
|
278
280
|
(This auto-generates audio and saves to audio/YYYY-MM-DD/keypoint_full.mp3)
|
|
279
281
|
Step 3: ⛔ EXECUTE THIS BASH COMMAND (do NOT skip):
|
|
280
|
-
python3 scripts
|
|
282
|
+
python3 -m scripts.cli.cli record_view
|
|
281
283
|
Step 4: Send audio file via message tool (if audio exists):
|
|
282
284
|
- Read keypoint.json and check if audio.composed field exists
|
|
283
285
|
- If exists, send audio file using message tool with media parameter
|
|
@@ -287,46 +289,66 @@ Step 5: Display formatted content to user
|
|
|
287
289
|
|
|
288
290
|
### Audio File Sending (After Keypoint Save)
|
|
289
291
|
|
|
290
|
-
When keypoint is saved, audio is auto-generated
|
|
292
|
+
When keypoint is saved, audio is auto-generated. Send it to user with backward compatibility:
|
|
291
293
|
|
|
294
|
+
**Step 1: Check voice bubble capability**
|
|
295
|
+
```bash
|
|
296
|
+
echo $FEISHU_VOICE_BUBBLE_ENABLED
|
|
292
297
|
```
|
|
293
|
-
1. Check keypoint.json for audio field:
|
|
294
|
-
cat ~/.openclaw/state/eng-lang-tutor/daily/YYYY-MM-DD/keypoint.json | grep -o '"audio":{[^}]*}'
|
|
295
298
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
299
|
+
**Step 2a: If FEISHU_VOICE_BUBBLE_ENABLED=true (enhanced mode)**
|
|
300
|
+
|
|
301
|
+
Send BOTH voice bubble AND file attachment for best compatibility:
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
// Voice bubble (handled by enhanced OpenClaw gateway)
|
|
305
|
+
{
|
|
306
|
+
"action": "send",
|
|
307
|
+
"media": "~/.openclaw/media/{audio.composed}",
|
|
308
|
+
"asVoice": true
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// File attachment (fallback for progress bar control)
|
|
312
|
+
{
|
|
313
|
+
"action": "send",
|
|
314
|
+
"media": "~/.openclaw/media/{audio.composed}",
|
|
315
|
+
"caption": "🔊 今日知识点语音版"
|
|
316
|
+
}
|
|
317
|
+
```
|
|
302
318
|
|
|
303
|
-
|
|
304
|
-
{
|
|
305
|
-
"action": "send",
|
|
306
|
-
"media": "~/.openclaw/media/eng-lang-tutor/2026-02-23/keypoint_full.mp3",
|
|
307
|
-
"caption": "🔊 今日知识点语音版"
|
|
308
|
-
}
|
|
319
|
+
**Step 2b: If FEISHU_VOICE_BUBBLE_ENABLED not set or false (standard mode)**
|
|
309
320
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
321
|
+
Only send file attachment:
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"action": "send",
|
|
325
|
+
"media": "~/.openclaw/media/{audio.composed}",
|
|
326
|
+
"caption": "🔊 今日知识点语音版"
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Audio file info is stored in keypoint.json:**
|
|
331
|
+
```json
|
|
332
|
+
{
|
|
333
|
+
"audio": {
|
|
334
|
+
"composed": "eng-lang-tutor/2026-02-23/keypoint_full.mp3",
|
|
335
|
+
"duration_seconds": 37.7,
|
|
336
|
+
"generated_at": "2026-02-23T02:20:14"
|
|
337
|
+
}
|
|
338
|
+
}
|
|
318
339
|
```
|
|
319
340
|
|
|
320
341
|
**IMPORTANT:**
|
|
321
342
|
- Audio is saved to `~/.openclaw/media/` (OpenClaw's allowed media directory)
|
|
322
343
|
- Always send audio file BEFORE displaying text content
|
|
323
344
|
- The `audio.composed` field contains the path relative to `~/.openclaw/media/`
|
|
345
|
+
- Enhanced mode (voice bubble) requires OpenClaw gateway patch for Feishu
|
|
324
346
|
|
|
325
347
|
### After Quiz Generation (MANDATORY)
|
|
326
348
|
```
|
|
327
349
|
Step 1: LLM generates quiz JSON
|
|
328
350
|
Step 2: ⛔ EXECUTE THIS BASH COMMAND (do NOT skip):
|
|
329
|
-
python3 scripts
|
|
351
|
+
python3 -m scripts.cli.cli save_daily --content-type quiz --content '<ESCAPED_JSON>'
|
|
330
352
|
Step 3: Present quiz questions to user
|
|
331
353
|
```
|
|
332
354
|
|
|
@@ -349,10 +371,10 @@ Bot checks: completion_status.quiz_completed_date == today?
|
|
|
349
371
|
→ NO:
|
|
350
372
|
1. Check if keypoint.json exists for today
|
|
351
373
|
→ If NO: Generate keypoint via LLM first
|
|
352
|
-
→ ⛔ EXECUTE: python3 scripts
|
|
374
|
+
→ ⛔ EXECUTE: python3 -m scripts.cli.cli save_daily --content-type keypoint --content '<ESCAPED_JSON>'
|
|
353
375
|
2. Generate quiz via LLM
|
|
354
376
|
3. Set generated=true in the JSON content
|
|
355
|
-
4. ⛔ EXECUTE: python3 scripts
|
|
377
|
+
4. ⛔ EXECUTE: python3 -m scripts.cli.cli save_daily --content-type quiz --content '<ESCAPED_JSON>'
|
|
356
378
|
5. Present questions to user
|
|
357
379
|
```
|
|
358
380
|
|
|
@@ -364,9 +386,9 @@ User manually requests quiz before scheduled keypoint push time
|
|
|
364
386
|
Bot checks: Does keypoint.json exist for today?
|
|
365
387
|
→ NO:
|
|
366
388
|
1. IMMEDIATELY generate keypoint via LLM (do NOT say "will notify later")
|
|
367
|
-
2. ⛔ EXECUTE: python3 scripts
|
|
389
|
+
2. ⛔ EXECUTE: python3 -m scripts.cli.cli save_daily --content-type keypoint --content '<ESCAPED_JSON>'
|
|
368
390
|
3. Generate quiz via LLM
|
|
369
|
-
4. ⛔ EXECUTE: python3 scripts
|
|
391
|
+
4. ⛔ EXECUTE: python3 -m scripts.cli.cli save_daily --content-type quiz --content '<ESCAPED_JSON>'
|
|
370
392
|
5. Present quiz questions to user
|
|
371
393
|
→ All in ONE response - user should receive quiz immediately
|
|
372
394
|
→ YES: Proceed with quiz generation normally (see Quiz Already Completed section)
|
|
@@ -384,12 +406,12 @@ Bot checks: Does keypoint.json exist for today (~/.openclaw/state/eng-lang-tutor
|
|
|
384
406
|
→ NO:
|
|
385
407
|
1. Generate new keypoint via LLM
|
|
386
408
|
2. Set generated=true in the JSON content
|
|
387
|
-
3. ⛔ EXECUTE: python3 scripts
|
|
388
|
-
4. ⛔ EXECUTE: python3 scripts
|
|
409
|
+
3. ⛔ EXECUTE: python3 -m scripts.cli.cli save_daily --content-type keypoint --content '<ESCAPED_JSON>'
|
|
410
|
+
4. ⛔ EXECUTE: python3 -m scripts.cli.cli record_view
|
|
389
411
|
5. Send audio file via message tool (if audio.composed exists)
|
|
390
412
|
6. Display formatted content to user (REMOVE any [AUDIO:...] tags from text)
|
|
391
413
|
→ YES: Check keypoint.generated
|
|
392
|
-
→ TRUE: ⛔ EXECUTE: python3 scripts
|
|
414
|
+
→ TRUE: ⛔ EXECUTE: python3 -m scripts.cli.cli record_view, then send audio and display
|
|
393
415
|
→ FALSE: Follow steps 1-6 above
|
|
394
416
|
```
|
|
395
417
|
|
|
@@ -560,6 +582,6 @@ Stored in `state.json`:
|
|
|
560
582
|
|
|
561
583
|
| Script | Purpose |
|
|
562
584
|
|--------|---------|
|
|
563
|
-
| command_parser.py | Parse user messages to determine intent |
|
|
564
|
-
| cron_push.py | Handle scheduled content generation |
|
|
585
|
+
| cli/command_parser.py | Parse user messages to determine intent |
|
|
586
|
+
| scheduling/cron_push.py | Handle scheduled content generation |
|
|
565
587
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
eng-lang-tutor scripts package
|
|
4
|
+
|
|
5
|
+
Main entry points:
|
|
6
|
+
- StateManager: Core state persistence
|
|
7
|
+
- Scorer: Quiz evaluation
|
|
8
|
+
- GamificationManager: XP/levels/streaks/badges
|
|
9
|
+
- CommandParser: User command parsing
|
|
10
|
+
- AudioComposer: Audio generation
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .core.state_manager import StateManager
|
|
14
|
+
from .core.scorer import Scorer
|
|
15
|
+
from .core.gamification import GamificationManager
|
|
16
|
+
from .core.constants import LEVEL_THRESHOLDS, calculate_level, get_level_name, get_streak_multiplier
|
|
17
|
+
from .core.error_notebook import ErrorNotebookManager
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
'StateManager',
|
|
21
|
+
'Scorer',
|
|
22
|
+
'GamificationManager',
|
|
23
|
+
'ErrorNotebookManager',
|
|
24
|
+
'LEVEL_THRESHOLDS',
|
|
25
|
+
'calculate_level',
|
|
26
|
+
'get_level_name',
|
|
27
|
+
'get_streak_multiplier',
|
|
28
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Audio functionality: TTS, composition, conversion, Feishu integration."""
|
|
3
|
+
|
|
4
|
+
from .composer import AudioComposer, CompositionResult
|
|
5
|
+
from .converter import AudioConverter, ConversionResult, convert_mp3_to_opus
|
|
6
|
+
from .utils import get_ffmpeg_path, get_audio_duration
|
|
7
|
+
from .feishu_voice import FeishuVoiceSender, VoiceSendResult
|
|
8
|
+
from .tts import TTSManager, TTSProvider, TTSResult
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'AudioComposer',
|
|
12
|
+
'CompositionResult',
|
|
13
|
+
'AudioConverter',
|
|
14
|
+
'ConversionResult',
|
|
15
|
+
'convert_mp3_to_opus',
|
|
16
|
+
'get_ffmpeg_path',
|
|
17
|
+
'get_audio_duration',
|
|
18
|
+
'FeishuVoiceSender',
|
|
19
|
+
'VoiceSendResult',
|
|
20
|
+
'TTSManager',
|
|
21
|
+
'TTSProvider',
|
|
22
|
+
'TTSResult',
|
|
23
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -24,10 +24,8 @@ from pathlib import Path
|
|
|
24
24
|
from typing import Optional, List
|
|
25
25
|
from dataclasses import dataclass
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
except ImportError:
|
|
30
|
-
from tts import TTSManager
|
|
27
|
+
from .tts import TTSManager
|
|
28
|
+
from .utils import get_ffmpeg_path, get_audio_duration
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
@dataclass
|
|
@@ -59,12 +57,7 @@ class AudioComposer:
|
|
|
59
57
|
ffmpeg_path: ffmpeg 可执行文件路径(默认自动检测)
|
|
60
58
|
"""
|
|
61
59
|
self.tts = tts_manager
|
|
62
|
-
self.ffmpeg_path = ffmpeg_path or
|
|
63
|
-
if not self.ffmpeg_path:
|
|
64
|
-
raise RuntimeError(
|
|
65
|
-
"ffmpeg not found. Install it with: brew install ffmpeg (macOS) "
|
|
66
|
-
"or apt-get install ffmpeg (Ubuntu)"
|
|
67
|
-
)
|
|
60
|
+
self.ffmpeg_path = ffmpeg_path or get_ffmpeg_path()
|
|
68
61
|
|
|
69
62
|
# 创建临时目录用于存放中间文件
|
|
70
63
|
self.temp_dir = Path(tempfile.mkdtemp(prefix="audio_composer_"))
|
|
@@ -232,7 +225,7 @@ class AudioComposer:
|
|
|
232
225
|
final_audio = self._concatenate_segments(segments, output_path)
|
|
233
226
|
|
|
234
227
|
# 5. 获取时长
|
|
235
|
-
duration = self.
|
|
228
|
+
duration = get_audio_duration(final_audio, self.ffmpeg_path)
|
|
236
229
|
|
|
237
230
|
return CompositionResult(
|
|
238
231
|
success=True,
|
|
@@ -359,31 +352,3 @@ class AudioComposer:
|
|
|
359
352
|
raise RuntimeError(f"Failed to concatenate audio: {result.stderr}")
|
|
360
353
|
|
|
361
354
|
return output_path
|
|
362
|
-
|
|
363
|
-
def _get_duration(self, audio_path: Path) -> float:
|
|
364
|
-
"""
|
|
365
|
-
获取音频时长
|
|
366
|
-
|
|
367
|
-
Args:
|
|
368
|
-
audio_path: 音频文件路径
|
|
369
|
-
|
|
370
|
-
Returns:
|
|
371
|
-
时长(秒)
|
|
372
|
-
"""
|
|
373
|
-
cmd = [
|
|
374
|
-
self.ffmpeg_path,
|
|
375
|
-
"-i", str(audio_path),
|
|
376
|
-
"-hide_banner",
|
|
377
|
-
"-f", "null",
|
|
378
|
-
"-"
|
|
379
|
-
]
|
|
380
|
-
|
|
381
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
382
|
-
|
|
383
|
-
# 从 stderr 中解析时长,格式: " Duration: 00:00:03.45, ..."
|
|
384
|
-
import re
|
|
385
|
-
match = re.search(r"Duration: (\d+):(\d+):(\d+\.?\d*)", result.stderr)
|
|
386
|
-
if match:
|
|
387
|
-
hours, minutes, seconds = match.groups()
|
|
388
|
-
return int(hours) * 3600 + int(minutes) * 60 + float(seconds)
|
|
389
|
-
return 0.0
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import subprocess
|
|
12
|
-
import shutil
|
|
13
12
|
from pathlib import Path
|
|
14
13
|
from typing import Optional
|
|
15
14
|
from dataclasses import dataclass
|
|
16
15
|
|
|
16
|
+
from .utils import get_ffmpeg_path, get_audio_duration
|
|
17
|
+
|
|
17
18
|
|
|
18
19
|
@dataclass
|
|
19
20
|
class ConversionResult:
|
|
@@ -38,12 +39,7 @@ class AudioConverter:
|
|
|
38
39
|
Args:
|
|
39
40
|
ffmpeg_path: ffmpeg 可执行文件路径(默认自动检测)
|
|
40
41
|
"""
|
|
41
|
-
self.ffmpeg_path = ffmpeg_path or
|
|
42
|
-
if not self.ffmpeg_path:
|
|
43
|
-
raise RuntimeError(
|
|
44
|
-
"ffmpeg not found. Install it with: brew install ffmpeg (macOS) "
|
|
45
|
-
"or apt-get install ffmpeg (Ubuntu)"
|
|
46
|
-
)
|
|
42
|
+
self.ffmpeg_path = ffmpeg_path or get_ffmpeg_path()
|
|
47
43
|
|
|
48
44
|
def convert_to_voice(
|
|
49
45
|
self,
|
|
@@ -137,7 +133,7 @@ class AudioConverter:
|
|
|
137
133
|
)
|
|
138
134
|
|
|
139
135
|
# 获取音频时长
|
|
140
|
-
duration = self.
|
|
136
|
+
duration = get_audio_duration(output_path, self.ffmpeg_path)
|
|
141
137
|
|
|
142
138
|
return ConversionResult(
|
|
143
139
|
success=True,
|
|
@@ -156,26 +152,6 @@ class AudioConverter:
|
|
|
156
152
|
error_message=str(e)
|
|
157
153
|
)
|
|
158
154
|
|
|
159
|
-
def _get_duration(self, audio_path: Path) -> float:
|
|
160
|
-
"""获取音频时长(秒)"""
|
|
161
|
-
cmd = [
|
|
162
|
-
self.ffmpeg_path,
|
|
163
|
-
"-i", str(audio_path),
|
|
164
|
-
"-hide_banner",
|
|
165
|
-
"-f", "null",
|
|
166
|
-
"-"
|
|
167
|
-
]
|
|
168
|
-
|
|
169
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
170
|
-
|
|
171
|
-
# 从 stderr 中解析时长,格式: " Duration: 00:00:03.45, ..."
|
|
172
|
-
import re
|
|
173
|
-
match = re.search(r"Duration: (\d+):(\d+):(\d+\.?\d*)", result.stderr)
|
|
174
|
-
if match:
|
|
175
|
-
hours, minutes, seconds = match.groups()
|
|
176
|
-
return int(hours) * 3600 + int(minutes) * 60 + float(seconds)
|
|
177
|
-
return 0.0
|
|
178
|
-
|
|
179
155
|
def batch_convert(
|
|
180
156
|
self,
|
|
181
157
|
input_dir: Path,
|
|
@@ -30,7 +30,7 @@ from pathlib import Path
|
|
|
30
30
|
from typing import Optional, Dict, Any, List
|
|
31
31
|
from dataclasses import dataclass
|
|
32
32
|
|
|
33
|
-
from .
|
|
33
|
+
from .converter import AudioConverter, ConversionResult
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
@dataclass
|
|
@@ -402,20 +402,3 @@ class FeishuVoiceSender:
|
|
|
402
402
|
results.append(result)
|
|
403
403
|
|
|
404
404
|
return results
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
# 同步包装器(用于非异步环境)
|
|
408
|
-
class FeishuVoiceSenderSync:
|
|
409
|
-
"""飞书语音发送器的同步包装"""
|
|
410
|
-
|
|
411
|
-
def __init__(self, *args, **kwargs):
|
|
412
|
-
self._async_sender = FeishuVoiceSender(*args, **kwargs)
|
|
413
|
-
|
|
414
|
-
def send_voice(self, *args, **kwargs) -> VoiceSendResult:
|
|
415
|
-
return asyncio.run(self._async_sender.send_voice(*args, **kwargs))
|
|
416
|
-
|
|
417
|
-
def send_voice_from_text(self, *args, **kwargs) -> VoiceSendResult:
|
|
418
|
-
return asyncio.run(self._async_sender.send_voice_from_text(*args, **kwargs))
|
|
419
|
-
|
|
420
|
-
def send_keypoint_voices(self, *args, **kwargs) -> List[VoiceSendResult]:
|
|
421
|
-
return asyncio.run(self._async_sender.send_keypoint_voices(*args, **kwargs))
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -30,11 +30,15 @@ import os
|
|
|
30
30
|
import sys
|
|
31
31
|
|
|
32
32
|
# 添加 scripts 目录到路径以导入 state_manager
|
|
33
|
-
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
33
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
34
34
|
|
|
35
35
|
from .base import TTSProvider, TTSConfig, TTSResult
|
|
36
36
|
from .providers.xunfei import XunFeiProvider
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from ...core.state_manager import get_default_state_dir
|
|
40
|
+
except ImportError:
|
|
41
|
+
from scripts.core.state_manager import get_default_state_dir
|
|
38
42
|
|
|
39
43
|
|
|
40
44
|
# Provider 注册表
|
|
Binary file
|
|
Binary file
|