In this tutorial, we'll walk through setting up QuickAudio, a Windows desktop tool for batch audio speed adjustment with pitch preservation. You can either run the EXE or clone the repository to run from Python.
Download the EXE:
https://github.com/rogers-cyber/QuickAudio/releases
Clone the repository (optional):
git clone https://github.com/rogers-cyber/QuickAudio.git
cd QuickAudio
- Import Required Libraries
QuickAudio uses Python libraries for GUI, drag & drop, and audio processing.
import os
import sys
import threading
import time
import traceback
from queue import Queue, Empty
from tkinter import filedialog, messagebox
import ttkbootstrap as tb
import tkinter as tk
from tkinterdnd2 import DND_FILES, TkinterDnD
import subprocess
tkinter & ttkbootstrap: GUI creation and styling
tkinterdnd2: Drag & drop support
subprocess: Call FFmpeg/FFplay for audio processing
queue & threading: Handle background processing without freezing UI
- Configuration and Paths
Set your application name, version, and paths to FFmpeg and FFplay.
APP_NAME = "QuickAudio – Change Audio Speed"
APP_VERSION = "2.0.0"
FFMPEG_PATH = r"C:\ffmpeg\bin\ffmpeg.exe"
FFPLAY_PATH = r"C:\ffmpeg\bin\ffplay.exe"
SUPPORTED_FORMATS = ["mp3", "wav", "ogg", "m4a", "flac"]
⚠ Make sure FFmpeg/FFplay is installed and the path is correct.
- Initialize the GUI Application
app = TkinterDnD.Tk()
app.title(f"{APP_NAME} v{APP_VERSION}")
app.geometry("1150x740")
tb.Style("darkly")
We use TkinterDnD.Tk() to allow drag & drop.
ttkbootstrap provides a modern dark theme.
- Application State & Utility Functions
audio_list = []
ui_queue = Queue()
stop_flag = False
pause_flag = False
preview_process = None
def log_error():
with open("error.log", "a", encoding="utf-8") as f:
f.write(traceback.format_exc() + "\n")
def ffmpeg_exists():
return os.path.isfile(FFMPEG_PATH)
audio_list stores selected files.
ui_queue communicates UI updates from background threads.
stop_flag & pause_flag control audio processing flow.
- Adding Audio Files
def add_audio():
files = filedialog.askopenfilenames(
filetypes=[("Audio Files", "*.mp3 *.wav *.ogg *.m4a *.flac")]
)
for f in files:
if f not in audio_list:
audio_list.append(f)
ui_queue.put(("add", f))
def clear_audio():
audio_list.clear()
ui_queue.put(("clear", None))
Use askopenfilenames() to select multiple audio files.
The UI queue updates the listbox dynamically.
- GUI Panels: Files, Settings, Progress, Log
files_card = tb.Labelframe(app, text="🎵 Audio Files", padding=12)
files_card.pack(fill="x", padx=12, pady=6)
Files Card: List and manage audio files.
Settings Card: Control speed, pitch, output folder, format.
Progress Card: Shows processing percentage.
Log Card: Real-time messages and errors.
- Speed and Pitch Controls
speed_var = tb.DoubleVar(value=1.0)
pitch_preserve = tb.BooleanVar(value=True)
output_format = tb.StringVar(value="mp3")
output_dir = tb.StringVar()
Slider and presets adjust playback speed (0.5x – 3x).
Checkbox toggles pitch preservation.
Combobox selects export format.
- Audio Processing Logic
def process_audio():
global stop_flag, pause_flag
stop_flag = pause_flag = False
if not ffmpeg_exists():
messagebox.showerror("Error", "FFmpeg not found at configured path.")
return
if not audio_list:
messagebox.showerror("Error", "No audio files selected.")
return
out_dir = output_dir.get() or os.path.dirname(audio_list[0])
total = len(audio_list)
Runs in a separate thread to keep the UI responsive.
Processes each file using FFmpeg with the chosen speed and pitch options.
- Background UI Updates
def process_ui():
try:
while True:
cmd, data = ui_queue.get_nowait()
if cmd == "add":
listbox.insert("end", data)
elif cmd == "clear":
listbox.delete(0, "end")
elif cmd == "progress":
progress_var.set(data)
elif cmd == "log":
log_text.config(state="normal")
log_text.insert("end", data + "\n")
log_text.see("end")
log_text.config(state="disabled")
elif cmd == "complete":
status_lbl.config(text=data)
progress_var.set(100)
except Empty:
pass
app.after(100, process_ui)
UI updates (progress, log, listbox) are safely handled from the background thread.
- Live Audio Preview
def play_preview():
global preview_process
selection = listbox.curselection()
audio = listbox.get(selection[0])
cmd = [FFPLAY_PATH, "-nodisp", "-autoexit", "-loglevel", "quiet", "-af", f"atempo={speed_var.get()}", audio]
preview_process = subprocess.Popen(cmd, creationflags=CREATE_NO_WINDOW)
Preview audio at the chosen speed before exporting.
Can stop preview with a dedicated button.
- Drag & Drop Support
def drop(event):
files = app.tk.splitlist(event.data)
for f in files:
if os.path.isfile(f) and f not in audio_list:
audio_list.append(f)
ui_queue.put(("add", f))
app.drop_target_register(DND_FILES)
app.dnd_bind("<<Drop>>", drop)
Users can drag files directly into the app window.
Automatically adds them to the processing list.
- Starting the Application
app.after(100, process_ui)
app.mainloop()
Starts the UI loop and schedules the first process_ui call.
Application is now ready to use.
✅ Summary
QuickAudio allows batch speed adjustment with pitch preservation.
Supports drag & drop, live preview, pause/resume, stop, and multi-format export.
Fully built with Python, Tkinter, ttkbootstrap, and FFmpeg for Windows.
Run the EXE or clone the repo and execute with Python to get started.

Top comments (0)