We now have all the parts together to build our own little speech-to-text application. We record the speech into an audio file with sounddevice and then use Whisper to extract the text from it. Let us see how we can do that in one combined script.
Installation
If you followed along the last few posts, you can skip this part. You only need to install the packages if they are not already installed:
uvpipinstall-Usounddeviceopenai-whisper
We also need to install FFmpeg, that we can do on Windows with the pre-build binaries or on Linux with this command:
sudoaptupdate&&sudoaptinstallffmpeg
Record speech and get the text
To make this script a bit more useful, we use a loop that continues until we say e (for exit). We start the loop with s and record until we press the Enter key. If that happens, we stop the recording, create an audio file, extract the text, print it to the command line, and append it to a file:
importwarningswarnings.filterwarnings("ignore",category=UserWarning)importsounddeviceassdimportnumpyasnpimportwaveimportthreadingimportsysimportwhisperfromdatetimeimportdatetime# Parameterssample_rate=44100# 44.1 kHzchannels=1# 1 = Mono, 2 = Stereofile_name="output_sounddevice.wav"dtype='int16'now=datetime.now()transcription_file=f"transcription_{now.strftime("%Y%m%d_%H%M")}.txt"# Load Whisper modelmodel=whisper.load_model("medium",device="cpu")defcallback(indata,frames,time,status):ifstatus:print(status,file=sys.stderr)recorded_frames.append(indata.copy())defwait_for_enter():input("Recording... Press Enter to stop.\n")globalrecordingrecording=Falseprint("Welcome to the audio recording application!")print("You can record audio and transcribe it using Whisper.")print(f"The transcription will be saved to the file {transcription_file}.")print()whileTrue:choice=input("Type 's' to start recording or 'e' to end the application: ").strip().lower()ifchoice=='s':# Reset recording state and bufferglobalrecorded_frames,recordingrecorded_frames=[]recording=True# Start enter-listening threadstop_thread=threading.Thread(target=wait_for_enter)stop_thread.start()print("Recording started... (hit Enter to stop)")# Start recording streamwithsd.InputStream(samplerate=sample_rate,channels=channels,dtype=dtype,callback=callback):whilerecording:sd.sleep(100)# Combine and save to fileaudio_data=np.concatenate(recorded_frames)withwave.open(file_name,'wb')aswf:wf.setnchannels(channels)wf.setsampwidth(np.dtype(dtype).itemsize)wf.setframerate(sample_rate)wf.writeframes(audio_data.tobytes())print(f"Start with the transcription of the recorded audio...")result=model.transcribe(file_name)print(result["text"])print("\n\n\n*********")withopen(transcription_file,"a",encoding="utf-8")asf:f.write(result["text"]+"\n")elifchoice=='e':print("Exiting program.")breakelse:print("Invalid input. Please type 'record' or 'stop'.")
The smaller the increments we record, the less we need to record again if we notice a problem with the transcription. That way we can learn on the fly what pronunciation works better for our script so that we end up with the correct text in the output.
I tried it first with the tiny model, that I used in the last post. But with a different microphone that produced only gibberish. With the medium model I got a good balance of transcription quality to wait time.
The first few lines in the script disable this warning message:
FP16 is not supported on CPU; using FP32 instead
While it is a good reminder, we do not need to see it with every loop we do.
Conclusion
Python is great when it comes to scripts like the one we created in this post. We can leverage great libraries, combine them with a bit of glue code and we end up with a nice solution to solve a problem.
If you prefer different tools, you are free to take the script and modify it. Let me know if you come up with something that works better.