Petal learns French, and the pack that shipped was misspelling itself
Phase 24, the fr half: langpack, Hunspell dictionary, Piper voice, and the lexicon coverage that turned out to have been measured already (63.1%, better than pt-PT's 62.1%). No migration; not deployed. The plan recorded that build_ptpt_dictionary.py "generalizes" to French. It did not. It handled single-character flags and plain PFX/SFX and stopped on everything else, and fr.aff uses four of the things it stopped on. FLAG long is the dangerous one: French flags are two characters, so the old reader's set(flagstr) yields a bag of unrelated letters and expands every entry through the wrong paradigm without ever erroring. Plus continuation flags (French really does affix an affixed form), NEEDAFFIX on 68,075 of 84,140 stems, and FULLSTRIP. Renamed build_hunspell_dictionary.py with a per-language profile, asserting that CIRCUMFIX and FORBIDDENWORD are still unused rather than assuming it — and it rebuilds pt-PT byte-identical to the shipped asset, which is the only thing that makes "generalized" a claim rather than a hope. Elision was decided by building both halves and measuring. Keeping l'arbre and its thirty-three siblings: 3,159,832 forms, 8.25 MB gzipped. Dropping them: 473,326 and 1.19 MB. They are not new words, but the tokenizer keeps internal apostrophes, so they genuinely would have been underlined — so they moved out of the dictionary into withElision, which splits at a known clitic and still requires the remainder to be a word (l'zzzz stays flagged). Real nspell: 369 ms and 74 MB, against pt-PT's 842 ms and 139 MB, on the larger language. Where the regional trap lives is the mirror image of Portuguese's: every fr_* Piper voice is fr_FR and Debian's fr_FR/fr_CA/fr_BE dictionaries are one shared word list, so nothing can be quietly wrong about the country and the whole decision sits in the copy. What French has instead is the 1990 reform, packaged three ways; comprehensive ships, because Petal never corrects her French and coût and cout are both correct. Then the interim review pass, at the user's suggestion and explicitly "for now": four models read each Latin pack independently, and only findings at least two of them reached on their own were applied — five per pack. It earned its keep on the pack that was already live. pt-PT was carrying pre-Acordo spellings (adjectivos, actualmente) in a file whose own header commits to post-Acordo, plus Brazilian decepção, because the Phase 21 greps checked for Brazilian vocabulary and never checked the pack against its own spelling policy. That grep now exists and was confirmed to fail on the old text before being kept. Where reviewers agreed a line was wrong but split on the fix, the wording is mine and the reasoning is in BUILD_PLAN rather than averaged away. Still owed, and both packs now say so precisely: a quorum of models agreeing is agreement, not authority. No native speaker has read either pack, and none of this has been seen in a browser. go build/vet/test clean, tsc, vite build, vitest 190/190. Claude-Session: https://claude.ai/code/session_016y6gyuHkQXPiEuW8RGQyua
This commit is contained in:
+16
-7
@@ -23,11 +23,13 @@ runs them.
|
||||
|
||||
- **petal** — the single Go binary with the frontend embedded. Publishes no host
|
||||
port; Traefik is the only way in.
|
||||
- **piper-en** / **piper-zh** — read-aloud. Each Piper HTTP server loads exactly
|
||||
one voice, so English and Chinese are separate containers off one image, with
|
||||
the models cached in a shared volume. They sit on an internal network with no
|
||||
published ports, so only Petal can reach them. Adding pt-PT in Phase 21 is a
|
||||
fourth service, not a new image.
|
||||
- **piper-en** / **piper-zh** / **piper-pt** / **piper-fr** — read-aloud. Each
|
||||
Piper HTTP server loads exactly one voice, so every language is its own
|
||||
container off one image, with the models cached in a shared volume. They sit
|
||||
on an internal network with no published ports, so only Petal can reach them.
|
||||
Adding pt-PT in Phase 21 was a third service and fr in Phase 24 a fourth —
|
||||
never a new image, and since Phase 21 never any Go either (the languages are
|
||||
discovered from `TTS_ENDPOINT_<LANG>`/`TTS_VOICE_<LANG>`).
|
||||
|
||||
They run as containers rather than the host systemd units millenia uses because
|
||||
Piper was never actually installed on the VPS, and the `reala` account has no
|
||||
@@ -332,8 +334,8 @@ commonest English words; ECDICT covers essentially all of them and is in daily
|
||||
use by a real writer. `lexicon.Set.For` is where that decision lives — one
|
||||
`switch`, changed the day a comparison on her actual lookups says otherwise.
|
||||
|
||||
For pt-PT and French the same measurement reads 62%, which is why they use
|
||||
DreamDict: there is no alternative source for them at all.
|
||||
For pt-PT and French the same measurement reads 62% and 63%, which is why they
|
||||
use DreamDict: there is no alternative source for them at all.
|
||||
|
||||
### Rebuilding it
|
||||
|
||||
@@ -604,6 +606,13 @@ d=json.load(urllib.request.urlopen('https://huggingface.co/rhasspy/piper-voices/
|
||||
print([k for k in d if k.startswith('pt')])"
|
||||
```
|
||||
|
||||
**French: the opposite situation, and worth knowing it is.** Every `fr_*` voice
|
||||
in the catalogue is `fr_FR`, so there is no wrong country to land on by default
|
||||
and no Québec voice to choose instead; `fr_FR-siwis-medium` is picked to match
|
||||
the register of the other three rather than to avoid anything. The name is also
|
||||
plain ASCII, so the entrypoint's percent-encoded download fallback — which
|
||||
exists only because `tugão` broke `piper.download_voices` — never fires here.
|
||||
|
||||
**Slow replay.** `POST /api/tts` takes `slow: true`, which raises Piper's
|
||||
`length_scale` to about 4/3 (≈0.75× pace). It is a separate cache entry, not a
|
||||
playback-rate trick, so the slow clip is synthesized once and then instant.
|
||||
|
||||
Reference in New Issue
Block a user