Listening While Your Hands Are Busy — Long Articles as Audio With Local TTS

Peeling supports off a print. Sanding a surface flat. Soldering. The hands are busy and the eyes cannot be on a screen. Count how many hours a day that adds up to and it is more than you would guess. Push text into that time and you read more.
Trying it, though, is not simple. Turning a seven-thousand-character article into audio is a different problem from reading one sentence aloud. What follows is the design I actually landed on for long-form local TTS, and the places it did not work. It is more a record of constraints than a success story.
- Why full text rather than a summary
- Do not read written language aloud as written
- You cannot hand it the whole thing at once
- Designing the chunking
- Half a second at the seams
- Fifty-six chunks, measured
- It cannot resume
- Behaviour changes with model size
- Guaranteeing completeness
- Where it earns its place at a 3D printing bench
- Summary — running and being usable
- Sources
Why full text rather than a summary
The first decision is which one you are producing.
Summaries are short and easy to make. The trouble is that a summarised audio track mostly produces the feeling of having listened. Figures and conditions drop out, so when you want to check something you end up back in the text — in which case you may as well have read it.
Full text loses nothing and is long. For a seven-thousand-character article the reading takes a while, but the hands-busy window was long to begin with, so length is rarely the problem. Too short is worse: thirty minutes of work against five minutes of audio leaves twenty-five minutes of silence.
The dividing question is what the audio replaces. Replacing reading time means full text. Deciding whether to read at all means a summary. One fills a work session, the other filters, and they want opposite lengths. This site uses full text because the intent is that you listen instead of reading the article.
The pipeline also checks by machine that the narration script lands between 70% and 130% of the original article length, and refuses to generate outside that band. Too summarised or too inflated and it stops. Leave that judgement to a human eye and it drifts toward summary over time.
Do not read written language aloud as written
Even with the policy settled, feeding article text straight to a synthesiser produces something unlistenable, because written and spoken language are built differently.
Concretely: tables cannot be read — a comparison table comes out as bar, item, bar, value, bar. Heading punctuation gets spoken; hashes and interpuncts become sounds. So the script is rewritten. Tables are opened out into sentences. Heading markers are dropped. URLs are removed.
One line to hold while rewriting: do not lose information. Trying to make it flow, you drift toward summarising. The length check from the previous section acts as the brake — if trimming for readability pushes you outside the band, you trimmed too much.
You cannot hand it the whole thing at once
The first implementation wall: pass a long passage in one call and what comes back is not usable. Longer input breaks down more readily — it skips, it repeats, the tail gets cut. There is a ceiling on what generates safely.
So splitting is mandatory, and the question becomes where to cut.
Designing the chunking
My implementation splits on paragraphs. A block separated by blank lines becomes one chunk. The ceiling is 400 characters, and any paragraph over that is split further at Japanese sentence boundaries.
Paragraphs, because they keep a unit of meaning intact. Cut mid-sentence and the intonation does not join up across the seam. At a paragraph break there was a pause anyway, so it sounds natural.
The 400-character ceiling has a reason too. Longer breaks more often, so the value sits comfortably below where trouble starts. Raise it and you get fewer chunks and a shorter run, at a higher failure probability per chunk. Think of it as a dial between speed and stability. On a long job, failure is expensive, so the dial goes toward stability.
This design has a side effect that shows up later: chunk count depends on paragraph count, not character count. The same seven thousand characters written in short paragraphs produces far more chunks. More on that below.
Short adjacent paragraphs are not merged, which is deliberate simplicity. Merging would shorten the run but requires a judgement about how far to merge. I chose the simple rule that works reliably, and left the optimisation on the table.
Half a second at the seams
Concatenating the audio files directly leaves no gap at all at the chunk boundaries. So the implementation inserts 500 ms of silence between chunks.
The value was chosen to read as a paragraph break without dragging. One abrupt seam is not noticeable; fifty-six of them are fatiguing.
That property — unnoticeable once, wearing when repeated — is oddly similar to layer lines in 3D printing. A single step is invisible; a hundred stacked steps decide how the surface reads.
Fifty-six chunks, measured
Running the design on a real article: 7,708 characters with 11 top-level headings, narration body 7,447 characters. Splitting produced 56 chunks — 54 body, plus one for the title and one for the closing.
I had expected 18 to 20. The real figure was nearly three times that. Same time per chunk, three times the chunks, three times the run.
The cause is the paragraph-based rule above. Short paragraphs make for readable prose and multiply chunk count. The writing style feeds directly into the processing time, which is not a connection you anticipate.
Time per chunk, on an NVIDIA GeForce RTX 5070 Ti with the 1.7B model, measured on real chunks:
| Chunk length | Elapsed | Per 100 characters |
|---|---|---|
| 127 characters | 52.5 s | 41.4 s |
| 241 characters | 102.0 s | 42.3 s |
| 322 characters | 124.5 s | 38.6 s |
About 40 seconds per 100 characters, near enough proportional to length. Which means the estimate is simple, and also that a full article is a job measured in hours, not minutes.
It cannot resume
This is the heaviest constraint. There is no per-chunk resume.
The implementation generates every chunk into a temporary directory and concatenates once they are all present. Fail or interrupt partway and all progress is lost; the next run starts from the beginning — even at 50 of 56.
On a job of this length that hurts. Run for hours, fail, run for hours again. I have it recorded as a known limitation with room to improve.
How I absorbed it operationally: run one article at a time, isolated from other work. Once the body text and images for a piece are final, the audio goes to a batch stage on its own. Failure then rolls back one article rather than a set. A design problem absorbed by ordering rather than by code.
Why not just fix it? Priority. Keeping intermediate files so it can resume is not hard to implement — but a mechanism to guarantee script quality had to come first. The order in which things start working and the order in which they should be finished are not the same. Record the limitation, and if operations can carry it, let them. That is a normal state of affairs for a personal toolchain.
On making it faster: there is a setting that keeps the model resident between chunks instead of releasing it, and I assumed that was the win — load and unload round trips looked like the dominant cost.
Measured, it made no difference. Same chunk: 32.0 s releasing every time, 33.7 s resident. Within noise, and if anything slightly slower. The time is going into generation itself, not into moving the model.
I want to keep that as an example of a hypothesis failing. Loading is expensive is a plausible story, and there is material out there that says so. Planning the optimisation without measuring would have spent effort on a change that does nothing. To go faster you either cut characters or use a smaller model — and the smaller model has the side effect described next.
This is the same shape as batch image generation in covering your article images locally. For a long job, decide up front how far a failure rolls you back.
Behaviour changes with model size
About the model itself. Qwen3-TTS was released on 22 January 2026 in 0.6B and 1.7B sizes under Apache 2.0. It covers ten languages — Chinese, English, Japanese, Korean, German, French, Russian, Portuguese, Spanish and Italian — with Japanese officially among them.
Variants are Base, CustomVoice and VoiceDesign. Base is described as capable of a 3-second rapid voice clone from user audio, taking a reference clip and its transcript.
The distinctive part is that you can steer delivery with natural-language instructions. The project describes flexible control over multi-dimensional acoustic attributes such as timbre, emotion, and prosody. You can write ask for a calm register as a sentence.
One observation from my own environment. On 0.6B, those instructions were silently ignored. No error — the same result as passing none. Switching to 1.7B, they took effect. I could not find documentation covering this, so I will not call it specified behaviour, but failing without an error makes the cause much harder to find. Writing it down in case someone hits the same wall.
This kind of quiet no-op is a general risk with small models. The instruction format is accepted identically; the capacity to act on it is not there, so it passes through. Returning an error would require the model to determine that it cannot do the thing, and that is usually not built.
The remedy is to confirm once, at the start, that output changes when the instruction changes. Give it two extreme instructions and listen for a difference. Ten seconds of checking against hours of doubt.
Guaranteeing completeness
The frightening failure mode in machine-processing long text is a part going missing quietly. You believe it all generated, and thirty percent is absent. Audio cannot be eyeballed, so you find out by listening to the whole thing.
My defence is the pre-generation length check: is the narration body within 70% to 130% of the source article? Outside that, generation does not start.
The band exists rather than an exact match because converting to spoken form changes the volume — opening a table into prose adds, condensing a list subtracts. Exactness is not required; being off by an order of magnitude should stop the run.
The script is also checked for stray titles, URLs and punctuation marks, which are nonsense when spoken. If you are putting local TTS into a real workflow, this kind of upstream check is what earns its keep.
The check runs before generation for a reason: discovering a mistake after starting a multi-hour run is expensive, and the check takes seconds. Seconds against hours is not a difficult trade. The principle generalises — do everything you can verify before the run, and for what only shows up at runtime, try it small first. The longer the job, the more an upstream check is worth.
Where it earns its place at a 3D printing bench
Waiting on a print is the first case. A job can run for hours; you do not need to watch it, but you cannot really leave. Technical reading through the ears fits exactly there.
Post-processing works too. Support removal, sanding, painting — hands and eyes occupied, hearing free. Background knowledge suits this better than reading out a procedure.
Reading assembly steps aloud is possible but wants care. Instructions are dense with numbers, and mishearing one means redoing the work. Where a figure decides the outcome — a dimension, a torque — do not rely on ears alone. Audio is a comprehension aid, not a verification method.
The reason numbers are hard to catch is less about synthesis quality than about the structure of spoken Japanese: zero point two millimetres and two millimetres are acoustically close, and with thin surrounding context they blur. Units do the same — millimetre and centimetre differ by one syllable.
So the split is: knowledge through the ears, numbers through the eyes. The costs of running any of this locally rather than on a metered service are worked through in reading your cloud AI bill before you buy a GPU.
Summary — running and being usable
- Full text over summary when the audio replaces reading time; a summary is for deciding whether to read.
- Rewrite written language into spoken form without losing information, and check the result lands within 70% to 130% of the original.
- Split into chunks — paragraphs, with a 400-character ceiling. Chunk count follows paragraph count, not character count.
- Insert 500 ms of silence at the seams. One seam is invisible, fifty-six are tiring.
- A 7,447-character script became 56 chunks against an expected 18 to 20, at about 40 seconds per 100 characters.
- There is no per-chunk resume, so run one article at a time and keep the rollback small.
- Keeping the model resident made no measurable difference. Measure before optimising.
- On 0.6B, style instructions were silently ignored; 1.7B honoured them. Verify once that instructions change the output.
Sources
- Qwen3-TTS on GitHub (0.6B and 1.7B, Apache 2.0, ten languages, Base / CustomVoice / VoiceDesign)
- Qwen — Qwen3-TTS-12Hz-0.6B-CustomVoice model card




