Run Text-to-Speech Offline
Build a Python narrator that turns text files into local WAV audio.
Introduction
30 Second Summary
Online voice tools can turn written words into audio. They can also introduce usage fees or send your text beyond your laptop.
In this project, you will run the English Phonon model entirely on your laptop CPU. You will write a Python script that turns a text file into WAV audio files.
What You'll Build
Your finished offline narrator takes a text file, gives every paragraph a voice, saves the results as playable audio, and reports how long each recording took.
By the end of this project, you'll have:
- A working browser preview where you can hear Phonon generate speech from your own sentence.
- A playable local voice sample saved as phonon.wav without an API key or cloud speech service.
- An offline narrator that converts each paragraph in a text file into a separate WAV file with its generation time.
- Secret Mission: An optional challenge to push your offline speech generation skills further.
Are there any prerequisites?
You need Python and basic familiarity with a terminal. You do not need a GPU or an API key because the model runs locally on your laptop CPU.
Before We Start
This opening checkpoint locks in what you are building before any hands-on work begins. Your offline narrator will turn text files into WAV audio while keeping the text on your laptop.
Try the Browser Demo
Your finished narrator will turn private text into audio on your laptop. Before setting up Python, hearing the model gives you a benchmark for the voice you are about to run locally.
The official Phonon demo runs the text-to-speech model inside a browser tab. This gives you the project's first audible result without installing anything locally.
In this step, get ready to:
- Open the official Phonon blog post to find its browser demo.
- Generate a sample sentence in the demo.
- Listen to the generated speech in your browser tab.
Open the official demo
The official blog post links directly to the browser experience. Starting there confirms that you are testing the demo released for Phonon.
- Open the official Phonon blog post in your browser.
- Scroll to the What we released section.
- Select the linked words hear it right now to open the browser demo.
The first visit can take longer while your browser fetches the model files. A pause during this initial load is expected.
- Keep the demo tab open until its controls become usable.
You should see the Phonon demo ready to accept a sentence. Nothing has been installed on your laptop.
What runs in the browser?
The demo loads Phonon into your browser tab. Speech generation then uses your device instead of sending each sentence to a hosted speech API.
This is the same on-device idea that your Python script uses later. The demo lets you hear the result before you build that local workflow.
Demo still loading?
- Confirm that your internet connection is active for the initial model download.
- Refresh the demo tab if its controls remain unavailable.
- Return to the blog post if the demo link opened an unexpected page.
Ask for help if the page still does not become interactive: Help me troubleshoot why the Phonon browser demo is not loading.
Generate and hear a sample
A successful demo turns written text into audible speech inside the tab. Your browser's audio player provides the visible proof that generation finished.
- Click inside the demo's text box.
- Replace the existing sample with Hello from Phonon..
Before you start generation, do you expect the sentence to become playable without any local Python setup?
- Start speech generation using the demo control beside the text box.
- Play the generated speech using the demo's audio player.
You should hear the voice say your sample sentence. The playable audio confirms that Phonon generated speech inside your browser tab.
You have your first audible result. Phonon can now turn a sentence into speech on your device.
Can't hear the generated speech?
- Check that your laptop volume is audible.
- Confirm that the demo tab is not muted.
- Allow browser audio playback if your browser requests permission.
- Generate the sentence again if the audio player has no playable result.
Ask for help if the audio remains silent: Help me troubleshoot silent audio in the Phonon browser demo.
At this checkpoint, you have opened the demo from the Phonon blog post. You have also generated and heard a sample sentence in the browser tab.
You now have a sound benchmark for Phonon. Next, you will prepare a local environment so the model can run from your terminal.
Set Up Python and Transformers
You have already heard Phonon generate speech in your browser. Your next goal is to prepare the local foundation for an offline narrator.
A virtual environment gives this project an isolated place for its Python packages. This protects the rest of your laptop from dependency changes.
Transformers 5.21 is the first release with Phonon support. An older release cannot load the Phonon model class.
In this step, get ready to:
- Confirm that Python 3.11 or newer is available.
- Prepare an isolated virtual environment.
- Verify a Transformers installation at version 5.21 or later.
Check your Python version
The current Transformers installation guide supports Python 3.11 or newer. This gives the Phonon integration a compatible runtime.
- Leave the Phonon demo tab open in your browser.
macOS
- Press Cmd+Space to open Spotlight.
- Type Terminal into the search bar.
- Press Enter to open Terminal.
- Print your installed Python version by running this command:
python3 --version
What does this command do?
The --version option prints the Python release connected to python3.
Windows
- Press the Windows key to open Windows Search.
- Type Command Prompt into the search bar.
- Press Enter to open Command Prompt.
- Print your installed Python version by running this command:
python --version
What does this command do?
The --version option prints the Python release connected to python.
✔️ I see Python 3.11 or higher
Your Python runtime meets the requirement for this project.
- Keep the terminal available for the next substep.
ⓧ I see an older Python version
Your existing interpreter needs an upgrade before it can support the current Transformers release.
- Open the official Python downloads page.
- Download a stable Python release at version 3.11 or higher for your operating system.
- Run the downloaded installer.
- Complete the installer using its default options.
- Return to the terminal from earlier.
- Repeat the version check for your operating system.
You should now see Python 3.11 or a higher version number.
ⓧ Command not found
Your terminal cannot find a Python installation. The official installer adds the runtime needed for this project.
- Open the official Python downloads page.
- Download a stable Python release at version 3.11 or higher for your operating system.
- Run the downloaded installer.
- Complete the installer using its default options.
- Return to the terminal from earlier.
- Repeat the version check for your operating system.
You should now see Python 3.11 or a higher version number.
Create and activate a virtual environment
The phonon-offline-tts directory gives your later scripts a predictable home. Its .env directory holds the isolated Python installation.
macOS
- Return to Terminal from the previous substep.
- Create the local project directory plus its virtual environment by running these commands:
cd ~/Desktop
mkdir phonon-offline-tts
cd phonon-offline-tts
python3 -m venv .env
source .env/bin/activate
What do these commands do?
- The first command moves your terminal to the Desktop.
- The mkdir command creates the phonon-offline-tts directory.
- The next cd command enters that directory.
- The venv module creates the isolated environment inside .env.
- The final command activates that environment for this terminal session.
Your terminal prompt should now begin with (.env).
Windows
- Return to Command Prompt from the previous substep.
- Create the local project directory plus its virtual environment by running these commands:
cd %USERPROFILE%\Desktop
mkdir phonon-offline-tts
cd phonon-offline-tts
python -m venv .env
.env\Scripts\activate.bat
What do these commands do?
- The first command moves your terminal to the Desktop.
- The mkdir command creates the phonon-offline-tts directory.
- The next cd command enters that directory.
- The venv module creates the isolated environment inside .env.
- The activation script connects Command Prompt to that environment.
Your Command Prompt should now begin with (.env).
Don't see the environment name?
Confirm that you used the activation command for your operating system. The command must run from inside the phonon-offline-tts directory.
If the directory already existed from an earlier attempt, enter it before creating the environment again.
Help me activate my Python virtual environment.
Install and verify Transformers
The version constraint asks the package installer for Transformers 5.21 or a newer stable release. This keeps the required Phonon support inside your activated environment.
The package download can sit quietly while dependencies are resolved. A short pause here is expected.
- Install a compatible Transformers release by running this command:
python -m pip install "transformers>=5.21"
What does this command do?
- The python -m pip portion uses the package installer from your active environment.
- The >=5.21 constraint rejects releases that predate Phonon support.
- The installed package stays inside .env.
The command finishes after the package plus its dependencies have been installed.
Installation stopped?
Confirm that your terminal prompt still begins with (.env). A missing prefix means the environment is no longer active.
Check that your laptop has an internet connection for this one-time package download.
Help me troubleshoot the Transformers installation.
Before you check, do you think the installed version meets Phonon's minimum?
- Print the installed Transformers version by running this command:
python -c "import transformers; print(transformers.__version__)"
What does this check prove?
- The import confirms that Transformers is available to the active Python interpreter.
- The __version__ value reveals the installed release.
You should see 5.21 or a higher version number.
Your local foundation is ready: the activated environment now has the Transformers release that supports Phonon.
Seeing an older version?
Confirm that the terminal prompt begins with (.env). Then rerun the installation command from this substep.
If the import fails, activate the environment again before repeating the version check.
Help me verify my Transformers version.
Your Python environment can now use Phonon's Transformers integration. Next, you will load the English model and inspect its parameter counts.
Load and Inspect Phonon
The browser demo proved that Phonon can generate speech in your tab. Your activated Python environment now gives you a local path to the same model family.
Before you can generate a WAV locally, you need to confirm that both model components load. Separate parameter counts make that confirmation visible.
In this step, get ready to:
- Start a Python session inside your activated virtual environment.
- Load the English Phonon checkpoint with its audio tokenizer.
- Print separate parameter counts for both components.
Use your prepared environment
Python resolves packages from the active virtual environment. Starting the interpreter in the terminal from earlier keeps this check tied to your installed Transformers version.
- Start an interactive Python session in the activated terminal by running this command:
python
What does this command do?
The python command starts the interpreter from your active virtual environment. The session can access Transformers 5.21 or later from that environment.
Load and inspect Phonon
The gradium/phonon-31m-en checkpoint is the English Phonon model. Its Apache 2.0 licence allows commercial use.
The processor includes an audio tokenizer. It maps audio to the latent values that Phonon uses.
Where do the model files go?
The first load downloads the model files into your local Hugging Face cache. Later sessions can reuse those downloaded files.
- Keep your internet connection active while this first load downloads the checkpoint.
- Consider whether the model or its audio tokenizer has more parameters.
- Paste this inspection code into the Python prompt:
from transformers import AutoProcessor, GradiumPhononTTSForConditionalGeneration
model_id = "gradium/phonon-31m-en"
processor = AutoProcessor.from_pretrained(model_id, device_map="auto")
model = GradiumPhononTTSForConditionalGeneration.from_pretrained(model_id, device_map="auto")
print(f"{model.num_parameters():,}") # 30,788,354
print(f"{processor.audio_tokenizer.num_parameters():,}") # 10,305,313
What does this code do?
- The model_id value identifies the English Phonon checkpoint.
- The AutoProcessor.from_pretrained() call loads the processor that holds the audio tokenizer.
- The GradiumPhononTTSForConditionalGeneration.from_pretrained() call loads the speech model.
- The device_map="auto" setting selects the available local device. This project uses your laptop CPU.
- Each num_parameters() call prints the size of one loaded component.
- Wait for both from_pretrained() calls to finish.
- Compare the two lines printed at the bottom of your terminal.
You will see 30,788,354 for the Phonon model. The next line shows 10,305,313 for the audio tokenizer.
Great. Both components are now present in your Hugging Face cache and loaded on your CPU.
Counts missing or load stopped?
- Confirm that the terminal still uses the activated virtual environment from earlier.
- Check that the environment contains Transformers 5.21 or later.
- Retry the first load after restoring your network connection.
Get targeted help with the loading problem by asking: Help me troubleshoot why the Phonon model or audio tokenizer is not loading in my activated Python environment.
✔️ Awesome, I've got everything!
Your model and audio tokenizer counts match the published Phonon values. The local checkpoint is ready for speech generation.
ⓧ I'd like to double check the full code
- Compare your pasted code with this complete inspection snippet:
from transformers import AutoProcessor, GradiumPhononTTSForConditionalGeneration
model_id = "gradium/phonon-31m-en"
processor = AutoProcessor.from_pretrained(model_id, device_map="auto")
model = GradiumPhononTTSForConditionalGeneration.from_pretrained(model_id, device_map="auto")
print(f"{model.num_parameters():,}") # 30,788,354
print(f"{processor.audio_tokenizer.num_parameters():,}") # 10,305,313
Your local model is ready. Next, you will give it a voice and turn a sentence into playable audio.
Generate Your First WAV
Your parameter counts confirmed that the Phonon model is loaded on your CPU. The next proof is something you can hear.
Speech synthesis needs a voice prompt to shape the sound. In this step, you will load the Easton voice before turning one sentence into a WAV file.
In this step, get ready to:
- Load a compatible voice from the Phonon voices set.
- Synthesize one sample sentence with the local model.
- Play the generated phonon.wav file.
Load a compatible voice
A Phonon voice is a tensor of precomputed speaker latents. The Easton voice lives in the Hugging Face repository named gradium/phonon-voices.
- Switch back to the terminal with your activated virtual environment.
- Load the Easton voice into the Python session from your model inspection by running the code below:
from huggingface_hub import hf_hub_download
from safetensors.torch import load_file
# Download Easton's precomputed speaker latents.
path = hf_hub_download("gradium/phonon-voices", "voices/LMDIYE0Oa377Qh6Z.safetensors")
# Reorient the stored frames for the model input.
voice = load_file(path)["speaker_wavs"][0].T
print(voice.shape)
What does this code do?
- The hf_hub_download() function downloads Easton's voice file into your local cache.
- The speaker_wavs tensor stores the precomputed voice information.
- The transpose operation arranges each frame into the shape expected by Phonon.
- Check the shape printed beneath the code.
You should see torch.Size([125, 512]). This confirms that Easton's voice latents are ready for synthesis.
Voice did not load?
- Confirm that the virtual environment from earlier is still activated.
- Check your internet connection if the voice download stalls.
- Compare the repository name with gradium/phonon-voices.
Ask for help with the voice-loading step.
Synthesize one sentence
The processor combines the selected voice with the sentence inside a chat template. The empty audio item reserves the position where Easton's voice latents belong.
- Prepare the voice prompt and sample sentence in the current Python session by running the code below:
# Mark where the selected voice belongs in the prompt.
conversation = [
{"role": "user", "content": [{"type": "audio"}, {"type": "text", "text": "Hello from Phonon."}]}
]
# Format the voice and text for local generation.
inputs = processor.apply_chat_template(
conversation,
tokenize=True,
return_dict=True,
add_generation_prompt=True,
processor_kwargs={"audio_prompt_latents": [voice]},
).to(model.device)
print(inputs.input_ids.shape)
How is the prompt prepared?
- The conversation value pairs Easton's voice with the sentence Hello from Phonon..
- The empty audio item reserves the voice position.
- The audio_prompt_latents value places the selected voice into that position.
- The apply_chat_template() method prepares the final model input.
- Check the shape printed on the final line.
You should see a torch.Size value containing two dimensions. This confirms that the prompt has been tokenized as a batch.
Prompt preparation failed?
- Confirm that processor still refers to the processor loaded during model inspection.
- Confirm that voice printed the expected two-dimensional shape.
- Check the spelling of audio_prompt_latents.
Ask for help with the prompt template.
Before you run the generation code, what proof do you expect to find in your local project directory?
This is the CPU-heavy moment in the step. The duration depends on your laptop, so a busy terminal still means the model is working.
- Generate the audio latents and save the decoded waveform by running the code below:
# Generate speech latents from the prepared prompt.
outputs = model.generate(**inputs)
# Decode the latents into a waveform.
audio = processor.decode(outputs.audio_latents, outputs.audio_latents_mask)
processor.save_audio(audio, "phonon.wav")
print("Saved phonon.wav")
How does the WAV get created?
- The generate() method produces audio latents from the prepared prompt.
- The decode() method converts those latents into waveform samples.
- The save_audio() method writes the waveform to phonon.wav.
- Check the final terminal message after generation finishes.
You should see Saved phonon.wav. Your local project directory now contains the generated audio file.
WAV file missing?
- Confirm that the terminal returned to the prompt after generation.
- Check that the filename in save_audio() is exactly phonon.wav.
- Confirm that outputs contains both audio latents and an audio latents mask.
Ask for help with the WAV generation step.
✔️ Awesome, I've got everything!
Great. Confirm that your Python session is still open before playing the generated file.
ⓧ I'd like to double check the full code
from huggingface_hub import hf_hub_download
from safetensors.torch import load_file
# Download Easton's precomputed speaker latents.
path = hf_hub_download("gradium/phonon-voices", "voices/LMDIYE0Oa377Qh6Z.safetensors")
# Reorient the stored frames for the model input.
voice = load_file(path)["speaker_wavs"][0].T
print(voice.shape)
# Mark where the selected voice belongs in the prompt.
conversation = [
{"role": "user", "content": [{"type": "audio"}, {"type": "text", "text": "Hello from Phonon."}]}
]
# Format the voice and text for local generation.
inputs = processor.apply_chat_template(
conversation,
tokenize=True,
return_dict=True,
add_generation_prompt=True,
processor_kwargs={"audio_prompt_latents": [voice]},
).to(model.device)
print(inputs.input_ids.shape)
# Generate speech latents from the prepared prompt.
outputs = model.generate(**inputs)
# Decode the latents into a waveform.
audio = processor.decode(outputs.audio_latents, outputs.audio_latents_mask)
processor.save_audio(audio, "phonon.wav")
print("Saved phonon.wav")
Play and verify the WAV
The saved file is the end-to-end test for local synthesis. Playing it confirms that the voice prompt reached the model before the generated latents passed through the audio decoder.
- Press Cmd+Space on macOS or the Windows key on Windows to open your search bar.
- Type Finder on macOS or File Explorer on Windows.
- Press Enter to open the file browser.
- Return to the local project directory from earlier.
- Double-click phonon.wav to play it in your default audio player.
You should hear Hello from Phonon. spoken in Easton's voice. The audio player should show phonon.wav as the active file.
You have your first audible result. Your laptop has synthesized speech locally with the selected Phonon voice.
Cannot hear the generated speech?
- Raise your system volume before replaying the file.
- Select a different installed audio player if the default player cannot open the file.
- Regenerate phonon.wav if the file reports a size of zero bytes.
Ask for help playing the local WAV file.
Your laptop can now turn one sentence into playable speech. Next up, you will extend this working pipeline to read text from a file and create one WAV per paragraph.
Build the Offline Narrator
Your first local WAV proves that Phonon can speak on your laptop. A one-off sentence still leaves you copying text into code.
This step turns a plain-text file into one WAV file per paragraph. It also prints the time spent generating each file.
In this step, get ready to:
- Supply multiple paragraphs through a plain-text file.
- Build a narrator that creates a separate WAV file for every paragraph.
- Measure the generation time for every paragraph.
Create the narration input
A text file separates your narration from the speech code. A blank line gives the script a clear paragraph boundary.
- Return to the local project directory from earlier in your text editor.
- Use your text editor's new-file control to create narration.txt inside that directory.
- Add these two paragraphs to narration.txt:
Phonon turns this first paragraph into speech on my laptop.
Each paragraph becomes its own audio file without sending my text to a speech service.
Why Use a Blank Line?
The empty line marks the end of the first paragraph. The narrator uses that boundary to create two independent audio files.
- Save narration.txt.
- Confirm that your editor shows two non-empty paragraphs separated by one blank line.
Don't See Two Paragraphs?
Check that the two sentences have a completely empty line between them. A normal wrapped line remains part of the same paragraph.
Ask for help checking the paragraph boundary in your input file.
Build the narrator script
The narrator uses Python to prepare each paragraph for your existing Transformers setup. A reusable function keeps the speech-generation sequence identical for every paragraph.
- Use your text editor's new-file control to create narrator.py inside the same local project directory.
- Add the imports and narration function below to narrator.py:
# Import file handling, timing, and local model helpers.
import argparse
import re
import time
from pathlib import Path
from huggingface_hub import hf_hub_download
from safetensors.torch import load_file
from transformers import AutoProcessor, GradiumPhononTTSForConditionalGeneration
def narrate_paragraph(paragraph, output_path, processor, model, voice):
# Pair the selected voice with the paragraph text.
conversation = [
{"role": "user", "content": [{"type": "audio"}, {"type": "text", "text": paragraph}]}
]
inputs = processor.apply_chat_template(
conversation,
tokenize=True,
return_dict=True,
add_generation_prompt=True,
processor_kwargs={"audio_prompt_latents": [voice]},
).to(model.device)
# Measure synthesis through the completed WAV file.
started_at = time.perf_counter()
outputs = model.generate(**inputs)
audio = processor.decode(outputs.audio_latents, outputs.audio_latents_mask)
processor.save_audio(audio, str(output_path))
return time.perf_counter() - started_at
print("Narration function is ready.")
What Does This Code Do?
- The conversation pairs one paragraph with the compatible voice loaded by the script.
- The chat template converts that pair into the inputs expected by Phonon.
- The model generates audio latents before the processor decodes them into playable audio.
- The timer measures the work that ends with a saved WAV file.
- Save narrator.py.
- Check that the narration function loads by running this command in the activated virtual environment:
python narrator.py
What Should You See?
You should see Narration function is ready. in the terminal. This confirms that Python can import the local speech dependencies and parse the function.
Seeing an Import or Syntax Error?
Confirm that the virtual environment from earlier is still activated. Compare the import names and indentation against the code block.
Help me diagnose the error from this first narrator check.
The function can now synthesize one paragraph. The main program supplies the file input plus the cached model resources.
- Add this main program below the readiness message in narrator.py:
def main():
# Read the supplied file and keep only non-empty paragraphs.
parser = argparse.ArgumentParser(description="Generate one WAV file per paragraph.")
parser.add_argument("input_file", type=Path)
args = parser.parse_args()
text = args.input_file.read_text(encoding="utf-8")
paragraphs = [
paragraph.strip()
for paragraph in re.split(r"\n\s*\n", text)
if paragraph.strip()
]
if not paragraphs:
raise ValueError("The input file has no non-empty paragraphs.")
# Load the cached English model and the compatible voice.
model_id = "gradium/phonon-31m-en"
processor = AutoProcessor.from_pretrained(model_id, device_map="auto")
model = GradiumPhononTTSForConditionalGeneration.from_pretrained(model_id, device_map="auto")
voice_path = hf_hub_download("gradium/phonon-voices", "voices/LMDIYE0Oa377Qh6Z.safetensors")
voice = load_file(voice_path)["speaker_wavs"][0].T
# Generate one numbered WAV file and timing line per paragraph.
for index, paragraph in enumerate(paragraphs, start=1):
output_path = Path(f"paragraph_{index:02d}.wav")
duration = narrate_paragraph(paragraph, output_path, processor, model, voice)
print(f"Paragraph {index}: {output_path} in {duration:.2f} seconds.")
if __name__ == "__main__":
main()
How Does the Main Program Work?
- The positional input accepts the path to the supplied text file.
- The blank-line pattern removes empty sections while preserving each complete paragraph.
- The cached English model and compatible voice provide the local speech resources.
- The loop numbers every WAV file and prints its measured generation duration.
- Save the completed narrator.py file.
Before this run, predict how many timing lines the two input paragraphs should produce.
- Generate the paragraph audio by running this command in the activated virtual environment:
python narrator.py narration.txt
What Should You See?
You should see one timing line for paragraph_01.wav. You should see a second timing line for paragraph_02.wav.
Each line includes a generation duration in seconds. Those measurements show how quickly your laptop turns each paragraph into saved audio.
This first full run is the slowest because it loads the cached model before speaking. A quiet terminal during that load is expected.
Missing a WAV File or Timing Line?
Confirm that narration.txt is saved beside narrator.py. Check that the input still contains one blank line between its two paragraphs.
Help me find why my narrator did not create one WAV file per paragraph.
✔️ Awesome, I've got everything!
Your narrator code is complete. Double-check that both files are saved before playing the generated audio.
ⓧ I'd like to double check the full code
# Import file handling, timing, and local model helpers.
import argparse
import re
import time
from pathlib import Path
from huggingface_hub import hf_hub_download
from safetensors.torch import load_file
from transformers import AutoProcessor, GradiumPhononTTSForConditionalGeneration
def narrate_paragraph(paragraph, output_path, processor, model, voice):
# Pair the selected voice with the paragraph text.
conversation = [
{"role": "user", "content": [{"type": "audio"}, {"type": "text", "text": paragraph}]}
]
inputs = processor.apply_chat_template(
conversation,
tokenize=True,
return_dict=True,
add_generation_prompt=True,
processor_kwargs={"audio_prompt_latents": [voice]},
).to(model.device)
# Measure synthesis through the completed WAV file.
started_at = time.perf_counter()
outputs = model.generate(**inputs)
audio = processor.decode(outputs.audio_latents, outputs.audio_latents_mask)
processor.save_audio(audio, str(output_path))
return time.perf_counter() - started_at
print("Narration function is ready.")
def main():
# Read the supplied file and keep only non-empty paragraphs.
parser = argparse.ArgumentParser(description="Generate one WAV file per paragraph.")
parser.add_argument("input_file", type=Path)
args = parser.parse_args()
text = args.input_file.read_text(encoding="utf-8")
paragraphs = [
paragraph.strip()
for paragraph in re.split(r"\n\s*\n", text)
if paragraph.strip()
]
if not paragraphs:
raise ValueError("The input file has no non-empty paragraphs.")
# Load the cached English model and the compatible voice.
model_id = "gradium/phonon-31m-en"
processor = AutoProcessor.from_pretrained(model_id, device_map="auto")
model = GradiumPhononTTSForConditionalGeneration.from_pretrained(model_id, device_map="auto")
voice_path = hf_hub_download("gradium/phonon-voices", "voices/LMDIYE0Oa377Qh6Z.safetensors")
voice = load_file(voice_path)["speaker_wavs"][0].T
# Generate one numbered WAV file and timing line per paragraph.
for index, paragraph in enumerate(paragraphs, start=1):
output_path = Path(f"paragraph_{index:02d}.wav")
duration = narrate_paragraph(paragraph, output_path, processor, model, voice)
print(f"Paragraph {index}: {output_path} in {duration:.2f} seconds.")
if __name__ == "__main__":
main()
Generate and play the paragraph audio
The terminal proves that both files were written. Listening confirms that each paragraph became usable local speech.
Before you press play, predict whether each file contains one paragraph or the whole document.
- Return to the local audio player from earlier.
- Use its file control to select paragraph_01.wav from the local project directory.
- Play paragraph_01.wav.
- Use the same file control to select paragraph_02.wav.
- Play paragraph_02.wav.
You should hear the first input paragraph in paragraph_01.wav. You should hear the second input paragraph in paragraph_02.wav.
Can't Play a Paragraph File?
Confirm that you selected the generated WAV file instead of narration.txt. Check that the file size is greater than zero in your local project directory.
Help me troubleshoot a generated paragraph WAV that will not play.
That is the reusable loop complete. Your laptop now turns a supplied text file into timed paragraph audio without an API key.
Secret mission
Benchmark Your Offline Narrator
Measure how paragraph length affects generation time on your own CPU. You will run a controlled three-paragraph benchmark. Then you will compare total time with time per word.
Clean Up Your Resources
Clean Up Your Resources
Your offline narrator uses local files with no paid cloud resources. Choose how much of the local setup you want to keep.
Resources you used:
- The Phonon browser demo tab.
- The local project directory containing your offline narrator script, narration input text file, Python virtual environment, WAV outputs, and phonon.wav.
- The Hugging Face cache files for gradium/phonon-31m-en and its audio tokenizer.
- The cached compatible voice from gradium/phonon-voices.
Keep everything running
Keep the full setup in place if you plan to generate more narration soon. Your local files do not create ongoing service charges.
- Keep the Phonon browser demo tab available for quick sentence tests.
- Keep the local project directory so your narrator script, input text, WAV files, and Python virtual environment remain available.
- Keep the cached model, audio tokenizer, and voice so future narration runs can stay offline after the downloads are complete.
Pause - I'll come back to this later
A paused local project has no active browser or terminal session. Your project files and cached downloads stay intact for your return.
- Close the Phonon browser demo tab.
- Close the terminal window containing the activated Python virtual environment.
- Leave the local project directory in place.
- Leave the Hugging Face cache unchanged.
- Return to the local project directory when you are ready to continue.
- Activate the existing Python virtual environment using the same method from earlier.
Delete - I don't want to use this again
A full cleanup removes the project files and cached downloads from your laptop. This recovers the disk space used by the narrator and Phonon resources.
Deletion removes your local work
Emptying Trash or Recycle Bin makes this cleanup permanent. Your narrator can be rebuilt later by repeating the project.
- Close the Phonon browser demo tab.
- Close the terminal window containing the activated Python virtual environment.
- Use Finder on macOS or File Explorer on Windows to locate the local project directory from earlier.
- Move the local project directory to Trash on macOS or Recycle Bin on Windows.
- Return to the Hugging Face cache from earlier using Finder on macOS or File Explorer on Windows.
- Move the cached entries for gradium/phonon-31m-en and gradium/phonon-voices to Trash on macOS or Recycle Bin on Windows.
- Empty Trash on macOS or Recycle Bin on Windows.
- Confirm that the local project directory and both Phonon cache entries are no longer listed.
Nice Work!
Nice Work!
You did it! You built an offline narrator with the Phonon model that turns a text file into playable WAV files on your laptop CPU.
You've learned how to:
- Load gradium/phonon-31m-en on your CPU. Print the model's parameter count. Print the audio tokenizer parameter count separately.
- Generate speech with a compatible Phonon voice. Save it as phonon.wav. Play the file locally. Your text stays on your laptop throughout generation.
- Build an offline narrator that reads a supplied text file. Create one WAV file per paragraph. Print the generation time for every paragraph.
- Complete the optional Secret Mission to push your offline narration skills further.
Ready to quiz yourself?