Preparation for the recording script
For training-purposes, I let Claude create a text-file of 200 sentences related to amateur-radio. This is what it created.

There are other scripts around and in development. Initially I trained it with Kari’s, OH2XX, script, but based on an earlier post by Tom, N1MM and a few e-mail exchanges with John, K3CT, I decided to make it a bit more hamradio orientated. John has created a much shorter training script, of 7 carefully selected sentences which is a lot less work to record. I always think, the more specific data I provide, the better the result will be, hence I’m going for 200 sentences. You can download the file here. I created a separate folder in the piper-recording-studio/prompts folder, called N1MM_en-GB and placed it there. Why en-GB? Because it is related to the language of the training-file I will use later on.
Start the recording
Open the Piper Recording Studio directory in the terminal, and make sure you have the virtual environment activated with .\.venv\scripts\activate.ps1
In the PowerShell window enter python -m piper_recording_studio
and the program starts in the background. Now go to your browser and open the user-interface with http://127.0.0.1:8000. Select N1MM and start recording your clips. They will be stored in the output folder. You can always resume recording later on, so don’t wipe the audio-files yet.
Exporting
Close the browser and press Ctrl+C in the terminal window to stop the recording program and the cursor will appear again in the prompt. We need to install a few more programs and dependencies to convert the audio to export it in the required format. So enter the following commands in the prompt:
winget install ffmpeg
python -m pip install -r requirements_export.txt
And finally the audio and data to a separate folder. I created a folder called dataset in the basefolder to export to:
python -m export_dataset output\en-GB\ ..\dataset
Cleaning metadata-file
The metadata-file created by Piper Recording Studio contains empty lines which need to be removed, otherwise the piper-training will abort.
import tkinter as tk
from tkinter import filedialog, messagebox
import shutil
import os
def is_empty_line(line):
"""Check if a line is truly empty (only whitespace or completely empty)"""
return line.strip() == ""
def remove_empty_lines():
"""Main processing function with file picker"""
# Hide the root window
root = tk.Tk()
root.withdraw()
# Select input CSV file
print("Please select the CSV file to process...")
input_file = filedialog.askopenfilename(
title="Select CSV File",
filetypes=[("CSV files", "*.csv"), ("All files", "*.*")]
)
if not input_file:
print("No file selected. Exiting.")
return
# Generate backup filename
backup_file = f"{input_file}.bak"
print(f"\n{'='*60}")
print(f"Input file: {input_file}")
print(f"Backup file: {backup_file}")
print(f"{'='*60}\n")
try:
# Step 1: Read the file
print("Step 1: Reading CSV file...")
with open(input_file, 'r', encoding='utf-8') as f:
lines = f.readlines()
total_lines = len(lines)
print(f"✓ Read {total_lines} lines from file")
# Step 2: Check for empty lines
print("\nStep 2: Checking for empty lines...")
non_empty_lines = []
empty_count = 0
for i, line in enumerate(lines, 1):
if is_empty_line(line):
empty_count += 1
print(f" - Found empty line {i}")
else:
non_empty_lines.append(line)
remaining_lines = len(non_empty_lines)
if empty_count == 0:
print("✓ No empty lines found - file is already clean")
print("\nNo processing needed - file will not be modified")
else:
print(f"✓ Found {empty_count} empty line(s) to remove")
print(f"✓ {remaining_lines} line(s) will remain")
# Step 3: Create backup only if we're going to modify the file
print("\nStep 3: Creating backup of original file...")
shutil.copy2(input_file, backup_file)
print(f"✓ Backup created: {backup_file}")
# Step 4: Write the cleaned data back
print("\nStep 4: Writing cleaned data to original file...")
with open(input_file, 'w', encoding='utf-8') as f:
f.writelines(non_empty_lines)
print(f"✓ Cleaned data written to {input_file}")
# Summary
print(f"\n{'='*60}")
if empty_count > 0:
print("✓ PROCESSING COMPLETE!")
print(f"Original lines: {total_lines}")
print(f"Empty lines removed: {empty_count}")
print(f"Remaining lines: {remaining_lines}")
print(f"\nOriginal file backed up to:")
print(f"{backup_file}")
else:
print("✓ CHECK COMPLETE!")
print(f"Total lines: {total_lines}")
print(f"Empty lines found: 0")
print(f"\nNo backup created - file was not modified")
print(f"{'='*60}\n")
# Show success message
if empty_count > 0:
messagebox.showinfo(
"Success",
f"CSV processed successfully!\n\n"
f"Original lines: {total_lines}\n"
f"Empty lines removed: {empty_count}\n"
f"Remaining lines: {remaining_lines}\n\n"
f"Backup saved to:\n{backup_file}"
)
else:
messagebox.showinfo(
"No Changes Needed",
f"The CSV file is already clean!\n\n"
f"No empty lines were found.\n"
f"No backup created - file was not modified."
)
except Exception as e:
print(f"\n✗ Error: {e}")
import traceback
traceback.print_exc()
messagebox.showerror("Error", f"An error occurred:\n\n{str(e)}")
finally:
root.destroy()
if __name__ == "__main__":
print("CSV Empty Line Remover")
print("="*60)
remove_empty_lines()
To fix this, download the file from myGithub-page, or copy and paste the above code in a text-editor and save it in the piper-recording-studio-folder. The script let’s you pick the metadata.csv file (which is stored in the dataset-folder), and removes the empty lines. The original file is kept as BAK, in case you want to revert. If there are no empty lines, there won’t be no BAcK-up made.
With the export finished, leave the virtual environment with the deactivate
command, and let’s process the audio and generate our voice-model file for N1MM.