This project turns the New HSK Course 3 textbook and its Workbook into study sheets — pinyin over every character, and a tap-to-play shadow drill on every sentence.
Look at what you’re building before you build it.
Open Lesson 1 →Lesson 1 is finished and on the web, which means your Claude Code can read it — the page, the way the pinyin sits over each character, the play buttons, all of it. That is far more use to it than any description, so do this before you ask it to build anything.
fetch https://hsk3-yu-h2ltb07459.pages.dev/lesson-01/ and tell me how a lesson page is put together
It can read the finished PDF the same way, at
/lesson-01/New-HSK-3-Lesson-1-pinyin.pdf, and listen to how a drill is
assembled at /lesson-01/audio/text-1-drill.mp3.
Better still, have it look at the pages as pictures. Reading the HTML tells it what is on a sheet; a picture tells it how the sheet is spaced — which is most of the work.
render the Lesson 1 PDF to page images and look at them
The lesson page is 25 A4 sheets tall. A single full-page capture comes out enormous and unreadable, and costs a lot for nothing. Shoot one sheet at a time, or render the PDF — the build already does exactly that when it counts the pages, so the images are a by-product you get for free.
When something you build later looks wrong, the quickest fix is usually
compare this with Lesson 1 on the site
.
What has to be on your machine
| Thing | Why | Check it |
|---|---|---|
| Claude Code | runs everything | you’re reading this in it |
| Python 3 | the build scripts | python --version |
| pypinyin, PyMuPDF, Pillow | pinyin, PDF pages, image crops | pip install pypinyin pymupdf pillow |
| ffmpeg & ffprobe | cutting and joining audio | ffmpeg -version |
| yt-dlp | fetching the publisher’s lesson audio | pip install -U yt-dlp |
| Google Chrome | printing the sheets to PDF | already there, most likely |
check I have everything the hanzi-to-pinyin skill needs
The accounts that have to be yours
ElevenLabs is the only paid thing. The slow narration is generated there. The transcription used to cut the book’s own audio is free.
- Sign up at elevenlabs.io. Starter is about $6/month for 40,000 characters.
- A whole lesson costs roughly 500–800 characters of that. One month covers many lessons.
- Make an API key in your account settings.
Run this in your own terminal, then restart Claude Code. The scripts look for this first.
setx ELEVENLABS_API_KEY "your-key-here"
Cloudflare — only if you want your own web copy. Alex’s copy
already exists and you can simply use it. If you do want your own it’s free: sign up,
run npx wrangler login, and tell Claude the project name to create.
The books, and getting the text out
The sheets are built from text, not from pictures of text. This is the one stage that is not a single command, and the one worth understanding before you start.
Ask Alex for the two PDFs — the Course 3 textbook and the Workbook.
The Course 3 PDF is a scan. Every page is one flat photograph, and the text layer inside it is a machine’s guess. On Lesson 1 that guess silently dropped the entire 生词 New Words box and turned two of seven dialogue bubbles into unreadable characters. A missing box looks exactly like a page that never had one — which is why this cannot be spot-checked afterwards.
MinerU reads that same scan properly. It is free, open source, and runs on your own machine: github.com/opendatalab/MinerU. Installing it is four commands.
An RTX 5080 is more than enough — comfortably faster than the laptop card these timings were measured on, so a whole book will take well under an hour. Nothing here needs adjusting for it.
If you ever run this on a machine without an NVIDIA card, MinerU falls back to the processor and the same book takes many hours. The sensible answer there is to ask Alex to convert it once and send you the output folder — a few megabytes of JSON.
3a — install it. The order matters, and step three is the one people get wrong.
# 1 uv, which installs an isolated Python pip install -U uv # 2 Python 3.12 in its own environment. # Do not use a newer Python - the ML stack does not support it yet. uv python install 3.12 uv venv --python 3.12 # 3 MinerU itself uv pip install -U "mineru[core]" # 4 Replace the CPU build of torch with the GPU one. # MinerU installs a CPU-only torch, and it wins unless you overwrite it # afterwards. cu128 below is right for an RTX 50-series card, which # is what you have - copy this line exactly as it stands. uv pip install torch torchvision --index-url https://download.pytorch.org/whl/cu128
Then check it worked. If cuda available says False, step 4
did not take and everything will run on the processor:
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"
python -c "import mineru; print('mineru OK')"
A few gigabytes land in ~/.cache/huggingface the first time you convert
anything. That is normal and happens once.
3b — convert a book. Ask Claude to do it; it takes roughly 20 seconds a page, so a 200-page book is over an hour. Start it and go and do something else.
convert this PDF with MinerU and tell me where the JSON landed
What you want out of it is <name>_content_list.json. Each entry has
the page number, the type, the text, and a bounding box — the box is what the build uses
to crop the book’s own photographs out of the page.
Use MinerU’s in-process API, not its command line. The CLI starts a web server that deadlocks when run in the background. Claude knows this if you point it at this page.
Keep the filename short. Windows stops at 260 characters and MinerU writes the document’s name into the output path twice. A file named over about 70 characters fails with an error that does not mention the length. One PDF sat in a folder for nine days being retried 1,768 times because of this.
One conversion at a time. The models fill the graphics card’s memory; a second run started alongside will fail.
3c — the audio. The publisher’s recordings are on YouTube, one video
per lesson, and yt-dlp fetches them. The playlist is in
Make HSK 3 like TestDaF for KIRILL.md. Nothing to set up beyond having
yt-dlp installed and up to date — an old copy gets
HTTP 403 from YouTube, and updating it is the fix.
Making a lesson
In Claude Code, in the project folder:
use the hanzi-to-pinyin skill and make lesson 2
The skill won’t start on its own — you have to name it. It walks six steps: find the text, read every page image, crop the book’s own pictures, build the sheets, print and count the pages, build the audio.
The commands, if you ever want to run one yourself:
python skills/hanzi-to-pinyin/scripts/build.py \
--lesson lessons/lesson-02 --out . \
--stem "Lesson-2-pinyin" --lesson-no 2
python skills/hanzi-to-pinyin/scripts/build_audio.py \
--lesson lessons/lesson-02 \
--book lessons/lesson-02/book-audio.mp3 \
--voices lessons/lesson-02/beijing_male.json
.\skills\hanzi-to-pinyin\scripts\make_pdf.ps1 `
-Html ".\Lesson-2-pinyin.html" `
-Pdf ".\New-HSK-3-Lesson-2-pinyin.pdf"
Two checks before you trust a lesson
When the audio is built, every dialogue line prints a score for how well it matched the book’s recording. Anything below 90% also prints the text it matched. Listen to those.
Two differences are known and harmless: the transcriber hears the name 白家月 as 白嘉悦, and the recording says 一点 where the book prints 一点儿.
Printing the PDF reports spill-only pages — a page holding
nothing but a footer means a sheet ran past its A4 box. Zero is the
target. If there are spills,
references/page-layout.md lists what to try, in order.
What’s fixed, and why
Each of these was decided after hearing the alternative. Don’t change one without saying so.
| Setting | What it is |
|---|---|
| Voice | ElevenLabs Evan Zhao — Warm, Calm and Trustworthy. Male, Beijing Mandarin. |
| Speed | 0.75, using the model’s own speed control. Never slow audio by stretching it — that shifts pitch, and in Chinese pitch is the tone. |
| Drill, with the book | slow → pause 1.5× → slow again → pause 1.3× → the book’s own take → pause 1.7× |
| Drill, without it | slow → pause 1.5× → slow again → pause 1.3× |
| Vocabulary | per word: slow → pause 1.5× → again → pause 1.5× |
| New Words | the book’s own recording, not synthesised — it already reads each word twice |
| Pinyin | shows the tone changes as spoken, including the third-tone change — 有点儿 is yóudiǎnr. The dictionary tone stays in the New Words box. |
A voice sold as soft or emotional gets its character by bending pitch — and that is the same thing tones are made of. A third tone stops dipping cleanly, and you copy a blurred version.
What you can’t do from your side
- Deploy to Alex’s web address. That project lives on his Cloudflare account. Build your own if you want your own copy.
- Spend his ElevenLabs credits. Yours is a separate account and a separate bill.
Where the reasoning lives
| File | What it answers |
|---|---|
CLAUDE.md | the standing rules, and the live address |
SKILL.md | the six steps |
pinyin-conventions.md | why a reading is printed the way it is |
page-layout.md | how to make a sheet fit |
lesson-01-worked-example.md | the shape to copy, and what went wrong the first time |
SESSION-LOG.md | what was tried, what it cost, what is still unverified |
This is a copyrighted textbook, reproduced for your own study. It does not go into anything sold, published or posted.