Have you ever wished you could easily record your thoughts, meetings, or any sound that catches your ear, exactly when you want and for how long you desire? Today, we’re turning that wish into reality!
In today’s tutorial, we’re going to create a voice recorder app in Python, we will use a timer for the recording in which the user can control its duration and when to stop the recording.
Let’s get started!
Table of Contents
- Necessary Libraries
- Importing Libraries
- VoiceRecorder Class
- Initializer Method and GUI Setup
- Recording Variables
- Title Label
- Duration Label and Entry
- Buttons
- Counter Label
- Start Recording Function
- Stop Recording Function
- Wait For Countdown Function
- Update Button States
- Update Counter
- Get Actual Recorded Frames
- Save Recording
- Main Part of the Code
- Example
- Full Code
Necessary Libraries
To make this code run as intended, it’s necessary to install these libraries using your terminal or command prompt:
$ pip install tk
$ pip install sounddevice
$ pip install scipy
The sounddevice and scipy libraries for recording, playing, and saving audio.
Importing Libraries
from tkinter import *
from tkinter import messagebox
import sounddevice as sound
from scipy.io.wavfile import write
import threading
import time
We start by importing the tkinter library, which creates a graphical user interface (GUI), then from tkinter
we import messagebox
which displays pop-up message boxes.
After that, we import sounddevice
to record and play audio, and then scipy.io.wavfile
that saves audio recordings to a WAV file.
Lastly, we import threading
which allows running multiple threads or parallel tasks.
VoiceRecorder Class
Once we completed importing our libraries we start by defining our new class which we will name VoiceRecorder
.
class VoiceRecorder:
Initializer Method and GUI Setup
def __init__(self, master):
self.master = master
master.title("Voice Recorder - The Pycodes")
master.geometry("600x500")
master.configure(bg="black")
master.resizable(False, False)
Next, we set up our VoiceRecorder
application, by defining the title size (600x500
) and the color of the background (black
) along with disabling the resizing of the main window.
Recording Variables
self.recording = None
self.freq = 44100
self.stop_recording_flag = False # Variable to signal the countdown to stop
self.countdown_thread = None # Variable to store the countdown thread
After that, we create an empty container (self.recording
) So we can store the sound that we are going to record, then we set our Recorder to take 44100
sound snapshots per second, after that we have set the self.stop_recording_flag
to False
which tells our Recorder to not stop recording, lastly, we put a placeholder for a special helper that will count down the time while we’re recording.
Title Label
self.label = Label(master, text="Voice Recorder", font="arial 30 bold", bg="black", fg="red")
self.label.pack()
Here, we created a label named Voice Recorder and then packed it into the GUI.
Duration Label and Entry
self.duration_label = Label(master, text="Enter recording time in seconds:", font="arial 15", bg="black", fg="red")
self.duration_label.pack()
self.duration_var = StringVar()
self.entry = Entry(master, textvariable=self.duration_var, font="arial 30", width=15)
self.entry.pack(pady=10)
In this section, we create a label that asks the user to enter the recording time and the entry where to put the answer and then pack it into the GUI.
Buttons
self.start_button = Button(master, font="arial 20", text="Start Recording", bg="green", fg="white", bd=0, command=self.start_recording)
self.start_button.pack(pady=10)
self.stop_button = Button(master, font="arial 20", text="Stop Recording", bg="red", fg="white", bd=0, command=self.stop_recording, state=DISABLED)
self.stop_button.pack(pady=10)
self.save_button = Button(master, font="arial 20", text="Save Recording", bg="blue", fg="white", bd=0, command=self.save_recording, state=DISABLED)
self.save_button.pack(pady=10)
Now, we create three buttons initially disabled and each has a different color but the same font, the buttons are “Start Recording“, “Stop Recording” and “Save Recording“.
Counter Label
self.counter_label = Label(master, text="", font="arial 40", width=4, background="black", fg="white")
self.counter_label.pack()
Following that, we create the countdown label.
Start Recording Function
def start_recording(self):
try:
recording_time = int(self.duration_var.get())
except ValueError:
messagebox.showerror("Error", "Please enter a valid duration.")
return
self.recording = sound.rec(recording_time * self.freq, samplerate=self.freq, channels=2)
self.stop_recording_flag = False # Reset the flag
self.countdown_thread = threading.Thread(target=self.update_counter, args=(recording_time,))
self.countdown_thread.start()
self.start_button.config(state=DISABLED)
self.stop_button.config(state=NORMAL)
self.save_button.config(state=DISABLED)
This part of the code is initiated when the user clicks the “Start Recording” button, if the user doesn’t put an integer before clicking the button an error message appears. However, if he puts an integer then that integer is converted to recording time and the recording starts Thanks to the sounddevice
library, and the audio is stored in the self.recording
which was initially empty with the self.stop_recording_flag
reset to False
to indicate that the recording is ongoing and the creation of the self.countdown_thread
which updates the countdown label during the countdown.
Lastly, once the “Start recording” button is clicked it gets disabled and only the Stop Recording button is enabled.
Stop Recording Function
def stop_recording(self):
sound.stop()
self.stop_recording_flag = True # Signal the countdown to stop
# Wait for the countdown thread to complete before proceeding
threading.Thread(target=self.wait_for_countdown).start()
The code presented here starts once the user clicks the “Stop Recording” button which stops the ongoing audio recording and changes the self.stop_recording_flag
from False
to True
making the countdown thread stop, and starting the self.wait_for_countdown
thread.
Wait For Countdown Function
def wait_for_countdown(self):
# This function is used to join the countdown thread
self.countdown_thread.join()
# Update button states after the countdown thread completes
self.master.after(0, self.update_button_states)
# Trim the recorded data based on the actual recording time
if self.recording is not None:
self.recording = self.recording[:self.get_actual_recorded_frames()]
# Save the recording automatically when the countdown finishes
if not self.stop_recording_flag:
self.master.after(0, self.save_recording)
This part of the code waits until the countdown finishes so that it can update the button states and make sure that the audio is recorded if the audio is recorded then any extra of it is removed, and it checks if the recording is stopped automatically or manually.
If it is stopped after the countdown finishes (automatically), then the audio is saved by automatically calling the save_recording
function without the user’s intervention.
Update Button States
def update_button_states(self):
# Update button states after the countdown thread completes
self.start_button.config(state=NORMAL)
self.stop_button.config(state=DISABLED)
self.save_button.config(state=NORMAL)
This section starts once the countdown is over by enabling the “Start Recording” and “Save Recording” buttons while disabling the “Stop Recording” button.
Update Counter
def update_counter(self, recording_time):
while recording_time > 0 and not self.stop_recording_flag:
self.master.update()
time.sleep(1)
recording_time -= 1
if recording_time == 0:
messagebox.showinfo("Time Countdown", "Time's up")
self.master.after(0, self.save_recording) # Save the recording automatically when the countdown finishes
self.counter_label.config(text=str(recording_time))
sound.wait()
These lines are for managing the countdown during the recording. In other words, if the countdown is not zero or manually stopped it gets updated constantly by reducing the time until it reaches zero, once that happens a message pops up indicating that the time is up and the audio recording is automatically saved.
Get Actual Recorded Frames
def get_actual_recorded_frames(self):
# Calculate the actual number of frames recorded based on the time elapsed
if self.recording is not None:
elapsed_time = int(self.duration_var.get()) - int(self.counter_label.cget("text"))
actual_frames = int(elapsed_time * self.freq)
return min(actual_frames, len(self.recording))
return 0
What this part of the code does is determine how much of the recorded audio is valid based on the time that has passed, making sure we don’t include any extra or unnecessary parts in our recording.
Save Recording
def save_recording(self):
try:
write("voice-recording.wav", self.freq, self.recording)
messagebox.showinfo("Save", "Recording saved successfully!")
except Exception as e:
messagebox.showerror("Error", f"An error occurred while saving the recording: {e}")
This one saves the recorded audio to a file named voice-recording.wav
if that is successful a message indicating that pops up otherwise a message indicating the failure shows up instead.
Main Part of the Code
if __name__ == "__main__":
root = Tk()
app = VoiceRecorder(root)
root.mainloop()
Lastly, this part creates the tkinter
window while initializing the VoiceRecorder
class and starts the mainloop
.
Example
Full Code
from tkinter import *
from tkinter import messagebox
import sounddevice as sound
from scipy.io.wavfile import write
import threading
import time
class VoiceRecorder:
def __init__(self, master):
self.master = master
master.title("Voice Recorder - The Pycodes")
master.geometry("600x500")
master.configure(bg="black")
master.resizable(False, False)
self.recording = None
self.freq = 44100
self.stop_recording_flag = False # Variable to signal the countdown to stop
self.countdown_thread = None # Variable to store the countdown thread
self.label = Label(master, text="Voice Recorder", font="arial 30 bold", bg="black", fg="red")
self.label.pack()
self.duration_label = Label(master, text="Enter recording time in seconds:", font="arial 15", bg="black", fg="red")
self.duration_label.pack()
self.duration_var = StringVar()
self.entry = Entry(master, textvariable=self.duration_var, font="arial 30", width=15)
self.entry.pack(pady=10)
self.start_button = Button(master, font="arial 20", text="Start Recording", bg="green", fg="white", bd=0, command=self.start_recording)
self.start_button.pack(pady=10)
self.stop_button = Button(master, font="arial 20", text="Stop Recording", bg="red", fg="white", bd=0, command=self.stop_recording, state=DISABLED)
self.stop_button.pack(pady=10)
self.save_button = Button(master, font="arial 20", text="Save Recording", bg="blue", fg="white", bd=0, command=self.save_recording, state=DISABLED)
self.save_button.pack(pady=10)
self.counter_label = Label(master, text="", font="arial 40", width=4, background="black", fg="white")
self.counter_label.pack()
def start_recording(self):
try:
recording_time = int(self.duration_var.get())
except ValueError:
messagebox.showerror("Error", "Please enter a valid duration.")
return
self.recording = sound.rec(recording_time * self.freq, samplerate=self.freq, channels=2)
self.stop_recording_flag = False # Reset the flag
self.countdown_thread = threading.Thread(target=self.update_counter, args=(recording_time,))
self.countdown_thread.start()
self.start_button.config(state=DISABLED)
self.stop_button.config(state=NORMAL)
self.save_button.config(state=DISABLED)
def stop_recording(self):
sound.stop()
self.stop_recording_flag = True # Signal the countdown to stop
# Wait for the countdown thread to complete before proceeding
threading.Thread(target=self.wait_for_countdown).start()
def wait_for_countdown(self):
# This function is used to join the countdown thread
self.countdown_thread.join()
# Update button states after the countdown thread completes
self.master.after(0, self.update_button_states)
# Trim the recorded data based on the actual recording time
if self.recording is not None:
self.recording = self.recording[:self.get_actual_recorded_frames()]
# Save the recording automatically when the countdown finishes
if not self.stop_recording_flag:
self.master.after(0, self.save_recording)
def update_button_states(self):
# Update button states after the countdown thread completes
self.start_button.config(state=NORMAL)
self.stop_button.config(state=DISABLED)
self.save_button.config(state=NORMAL)
def update_counter(self, recording_time):
while recording_time > 0 and not self.stop_recording_flag:
self.master.update()
time.sleep(1)
recording_time -= 1
if recording_time == 0:
messagebox.showinfo("Time Countdown", "Time's up")
self.master.after(0, self.save_recording) # Save the recording automatically when the countdown finishes
self.counter_label.config(text=str(recording_time))
sound.wait()
def get_actual_recorded_frames(self):
# Calculate the actual number of frames recorded based on the time elapsed
if self.recording is not None:
elapsed_time = int(self.duration_var.get()) - int(self.counter_label.cget("text"))
actual_frames = int(elapsed_time * self.freq)
return min(actual_frames, len(self.recording))
return 0
def save_recording(self):
try:
write("voice-recording.wav", self.freq, self.recording)
messagebox.showinfo("Save", "Recording saved successfully!")
except Exception as e:
messagebox.showerror("Error", f"An error occurred while saving the recording: {e}")
if __name__ == "__main__":
root = Tk()
app = VoiceRecorder(root)
root.mainloop()
Happy Coding!