# Easy Study 后端（FastAPI）

自然拼读 / 牛津树绘本学习系统的后端服务，提供绘本与单词/拼读查询、OCR 文本纠错、
页面音频时间标记、按需 TTS，以及一组离线数据处理工具（OCR → 结构化 → 拼读 → 音素音频）。

## 目录结构

```
backend/
├── app/
│   ├── main.py            # FastAPI 入口（CORS、/images 静态挂载、路由注册）
│   ├── config.py          # 全局配置：$workhome / $host / 各类路径 / resource_url
│   └── routers/
│       ├── books.py       # 绘本接口 /api/books
│       └── words.py       # 单词/拼读接口 /api/words
├── tools/                 # 离线数据处理脚本（独立运行）
│   ├── extract_book_text.py     # EasyOCR 提取绘本文字 → book-ocr/
│   ├── process_book_data.py     # OCR → 结构化课程数据 book-structured/
│   ├── generate_phonics.py      # CMU 词典补全单词拼读
│   └── extract_phoneme_audio.py # 录音 → 音素发音音频（静音切分+剪辑）
└── requirements.txt
```

## 启动

```bash
cd backend
python -m venv .venv && .venv\Scripts\activate   # Windows
# source .venv/bin/activate                       # Linux/Mac
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
```

- API 文档（Swagger）：http://localhost:8000/docs
- 健康检查：http://localhost:8000/api/health
- 工具脚本另需：`pip install edge-tts`（TTS）、`easyocr`、`nltk`、`faster-whisper`（按需）

> 生产/联调建议通过 Nginx 统一入口访问（见根目录 `deploy/nginx/`），前端、API、静态资源同源。

## 配置（环境变量）

所有路径与资源地址集中在 `app/config.py`，均可用环境变量覆盖。核心两个：

| 变量 | 含义 | 默认值 |
| --- | --- | --- |
| `EASYSTUDY_WORKHOME` | **$workhome**：本地文件读写基准（= Nginx 静态汇总根） | Win `E:\nginx-1.8.0\html`；Linux `/data/easystudy/static` |
| `EASYSTUDY_HOST` | **$host**：资源返回的 URL 前缀（空=同源相对路径） | 空 |

设计约定：
- **后端处理类**（读写 JSON / 写 TTS / 数据流水线）→ 用基于 `$workhome` 的本地路径。
- **资源返回类**（图片、音频地址）→ 用 `$host + uri`（`config.resource_url/image_url/tts_url`），
  uri 与 Nginx location 对齐（如 `/images/...`、`/audio/tts-words/...`）。
- `$workhome` 下各子目录通过软链接汇总（`deploy/nginx/win-link.bat` / `linux-link.sh`），
  因此「本地子目录」与「Nginx URI」一一对应。

细粒度覆盖（按需）：`EASYSTUDY_DATA_DIR`、`EASYSTUDY_IMAGES_DIR`、`EASYSTUDY_TTS_DIR`、
`EASYSTUDY_PHONICS_FILE`、`EASYSTUDY_PHONEMES_DIR`、`EASYSTUDY_PHONEME_MAP`、`EASYSTUDY_CORS_ORIGINS`。

## API 概览

绘本 `/api/books`：列表/详情/单页/单词、单词编辑·删除·插入（OCR 纠错）、页面音频时间标记。
单词/拼读 `/api/words`：音素映射、拼读拆分 lookup、搜索、绘本引用、按需 TTS。
详见 Swagger `/docs`。

## 离线数据工具

按顺序构建绘本学习数据：

```bash
python tools/extract_book_text.py     # ① EasyOCR：绘本图片 → book-ocr/*.json
python tools/process_book_data.py     # ② 结构化：过滤非正文、拆句分词、关联拼读 → book-structured/
python tools/generate_phonics.py      # ③ 补拼读：CMU 词典 + 字母-音素对齐
python tools/extract_phoneme_audio.py # ④ 音素音频：见下
```

---

## 音素发音自动提取方案（extract_phoneme_audio.py）

### 背景
录音内容结构：开头一段"测试朗读"，之后每个单元为「音标(IPA) + 样例词」朗读。
旧工具 `251228-words/video-cut` 是人工在波形上逐个标记起止再截取；本工具自动化该过程。

### 流水线
1. **静音切分**：`ffmpeg silencedetect` 检出静音区间，反推"说话片段"`(start,end)`。
2. **跳过开头**：`--skip-intro-sec` 秒 或 `--skip-segments` N 段，去掉测试朗读。
3. **顺序映射**：剩余片段按音素顺序（`--order-file`，默认内置教学顺序）依次对应音素；
   `--unit-utterances 2` 时每个音素含两段（音标段 + 样例词段）。
4. **（可选）ASR 核对**：`--asr` 用 faster-whisper 转写每段文本，写入 review 供人工校验/排错。
5. **dry-run 先行**：默认只产出 `review.json`（片段时间 + 映射 + 转写），确认无误再 `--apply` 落盘。
6. **落盘与命名**：按 `phoneme.json`（IPA → `NN.mp3`）命名输出到 `config.PHONEMES_AUDIO_DIR`
   （默认 `251228-words/audio/pho-v1`，对应 Nginx `/audio/phonemes/`）；新音素自动追加编号并回写
   `phoneme.json`，同时输出 `metadata.json`。

### 文件/路径规则（与现有系统一致）
- 输出目录：`config.PHONEMES_AUDIO_DIR` → Nginx `/audio/phonemes/`
- 命名映射：`config.PHONEME_MAP_FILE`（`phoneme.json`），如 `æ → 30.mp3`、`ʃ → 42.mp3`
- 剪辑范围：`--clip unit`（音标到样例词整体，默认）或 `--clip ipa`（仅音标段）

### 用法
```bash
# 1) 先 dry-run，检查分段与映射
python tools/extract_phoneme_audio.py --input 发音.mp3 --skip-intro-sec 8
# 2) 片段数与音素数不匹配时，调静音参数（注意 --noise 以 - 开头，须用 = 号写法）
python tools/extract_phoneme_audio.py --input 发音.mp3 --noise=-32dB --min-silence 0.45
# 3) 可选：开 ASR 辅助核对
python tools/extract_phoneme_audio.py --input 发音.mp3 --asr --asr-model small
# 4) 确认后落盘
python tools/extract_phoneme_audio.py --input 发音.mp3 --skip-intro-sec 8 --apply
```

### 依赖与调参
- 必需：`ffmpeg` / `ffprobe`（PATH 中，或 `--ffmpeg/--ffprobe` 指定）
- 可选：`pip install faster-whisper`（仅 `--asr`）
- 调参要点：环境噪声大→调低 `--noise`（如 `-35dB`）；词与词粘连→增大 `--min-silence`；
  漏掉短音→减小 `--min-seg`。以 `review.json` 的片段数==音素数为对齐目标。

### 后续可增强
- 用 ASR 转写匹配"样例词表"自动定位/跳过开头与漏段（当前靠顺序映射 + 人工 review）。
- 音素与样例词分别落两份音频（IPA 音 + 整词音），丰富学习页素材。
