DEV Community

Mate Technologies
Mate Technologies

Posted on

Step-by-Step Guide: Build Audio Extended – Extend Audio Seamlessly

In this tutorial, we’ll build Audio Extended, a Python desktop application to extend audio files from minutes to hours with seamless looping and optional fade-in/out.

GitHub Source: https://github.com/rogers-cyber/AudioExtended

1️⃣ Setup Python Environment

Make sure you have Python 3.10+ installed. Then install the required libraries:

pip install pydub ttkbootstrap
Enter fullscreen mode Exit fullscreen mode

You also need FFmpeg installed. Download it from https://ffmpeg.org/download.html
and note the bin folder path (e.g., C:\ffmpeg\bin).

2️⃣ Import Libraries and Setup FFmpeg

We need Python libraries for audio processing and GUI:

import sys
import os
from pathlib import Path
from pydub import AudioSegment
import subprocess
import tkinter as tk
from tkinter import filedialog, messagebox
import ttkbootstrap as tb
from ttkbootstrap.widgets.scrolled import ScrolledText
import threading
import math
import datetime
Enter fullscreen mode Exit fullscreen mode

Then configure FFmpeg:

FFMPEG_DIR = r"C:\ffmpeg\bin"  # Update with your FFmpeg path
os.environ["PATH"] = FFMPEG_DIR + os.pathsep + os.environ.get("PATH", "")

AudioSegment.converter = os.path.join(FFMPEG_DIR, "ffmpeg.exe")
AudioSegment.ffprobe = os.path.join(FFMPEG_DIR, "ffprobe.exe")
Enter fullscreen mode Exit fullscreen mode

3️⃣ Silence Console Windows for Subprocess

Windows often pops up extra console windows when using FFmpeg. We can prevent this:

_original_popen = subprocess.Popen

def silent_popen(*args, **kwargs):
    kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW
    return _original_popen(*args, **kwargs)
Enter fullscreen mode Exit fullscreen mode

subprocess.Popen = silent_popen
4️⃣ Initialize Tkinter App

Set up the main GUI window:

app = tk.Tk()
style = tb.Style(theme="superhero")
app.title("Audio Extended v1.1.0")
app.geometry("950x600")
Enter fullscreen mode Exit fullscreen mode

stop_event = threading.Event() # For safely stopping audio extension
5️⃣ Helpers and Logging

We’ll create small helper functions for UI updates and logging:

def ui(func, *args, **kwargs):
    app.after(0, lambda: func(*args, **kwargs))

def log_line(text):
    def _log():
        log.text.config(state="normal")
        log.text.insert("end", text + "\n")
        log.text.see("end")
        log.text.config(state="disabled")
    ui(_log)
Enter fullscreen mode Exit fullscreen mode

6️⃣ Menu Bar with About & Help

Add File and Help menus:

menubar = tb.Menu(app)
file_menu = tb.Menu(menubar, tearoff=0)
file_menu.add_command(label="Exit", command=app.quit)

help_menu = tb.Menu(menubar, tearoff=0)
help_menu.add_command(label="User Guide", command=show_help)
help_menu.add_command(label="About", command=show_about)

menubar.add_cascade(label="File", menu=file_menu)
menubar.add_cascade(label="Help", menu=help_menu)
app.config(menu=menubar)
Enter fullscreen mode Exit fullscreen mode

You can define show_about() and show_help() to display info dialogs with instructions.

7️⃣ Input and Output Panels

Create GUI elements to select audio input and output folder:

audio_path = tk.StringVar()
output_dir = tk.StringVar()
file_prefix = tk.StringVar(value="extended")

input_card = tb.Labelframe(app, text="Audio Input", padding=15)
input_card.pack(fill="x", padx=10, pady=10)
tb.Entry(input_card, textvariable=audio_path).pack(side="left", fill="x", expand=True, padx=5)
tb.Button(input_card, text="Browse", bootstyle="info",
          command=lambda: audio_path.set(filedialog.askopenfilename(filetypes=[("Audio","*.mp3 *.wav *.flac *.ogg")]))).pack(side="left", padx=5)
Enter fullscreen mode Exit fullscreen mode

Similarly, create an output folder selection:

output_card = tb.Labelframe(app, text="Output Settings", padding=15)
output_card.pack(fill="x", padx=10, pady=10)
tb.Entry(output_card, textvariable=output_dir).pack(side="left", fill="x", expand=True, padx=5)
tb.Button(output_card, text="Browse", bootstyle="info", command=lambda: output_dir.set(filedialog.askdirectory())).pack(side="left", padx=5)
Enter fullscreen mode Exit fullscreen mode

8️⃣ Duration Presets and Fade

Add preset durations and fade settings:

dur_presets = {"10 minutes": 10*60, "30 minutes": 30*60, "1 hour": 60*60, "2 hours": 2*60*60, "12 hours": 12*60*60}
target_option = tk.StringVar(value="Select Duration")
target_duration = tk.IntVar(value=0)

fade_var = tk.IntVar(value=3)  # default fade seconds
Enter fullscreen mode Exit fullscreen mode

9️⃣ Preview Audio Duration

Show original audio length and target duration:

preview_label = tb.Label(output_card, text="Audio Duration: -- | Target: --")
preview_label.pack(side="left", padx=10)

def update_preview(*args):
    try:
        audio = AudioSegment.from_file(audio_path.get())
        duration = len(audio)/1000
        preview_label.config(text=f"Audio: {format_time(duration)} | Target: {format_time(target_duration.get())}")
    except:
        preview_label.config(text="Audio Duration: -- | Target: --")

audio_path.trace_add("write", lambda *args: update_preview())
target_duration.trace_add("write", lambda *args: update_preview())
Enter fullscreen mode Exit fullscreen mode

🔟 Extend Audio Function

The main function loops and applies fade to extend audio:

def extend_audio():
    if not audio_path.get() or not output_dir.get():
        messagebox.showerror("Error", "Select input and output")
        return
    audio = AudioSegment.from_file(audio_path.get())
    target_ms = target_duration.get() * 1000
    fade_ms = fade_var.get() * 1000
    orig_len = len(audio)

    if fade_ms * 2 < orig_len:
        audio = audio.fade_in(fade_ms).fade_out(fade_ms)

    extended_audio = AudioSegment.empty()
    current_ms = 0
    chunk_ms = 1000

    while current_ms < target_ms:
        if stop_event.is_set():
            log_line("⏹ Stopped by user")
            break
        start = current_ms % orig_len
        end = min(start + chunk_ms, orig_len)
        extended_audio += audio[start:end]
        current_ms += (end - start)
        ui(progress.configure, value=min(current_ms / target_ms * 100, 100))

    extended_audio = extended_audio[:target_ms]
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    out_file = os.path.join(output_dir.get(), f"{file_prefix.get()}_{timestamp}.mp3")
    extended_audio.export(out_file, format="mp3")
    log_line(f"Saved extended audio: {out_file}")
Enter fullscreen mode Exit fullscreen mode

11️⃣ Add Start/Stop Buttons

bar = tb.Frame(app)
bar.pack(fill="x", padx=10, pady=5)
tb.Button(bar, text="Start", bootstyle="success", command=lambda: threading.Thread(target=extend_audio, daemon=True).start()).pack(side="left", padx=5)
tb.Button(bar, text="Stop", bootstyle="danger", command=lambda: stop_event.set()).pack(side="left", padx=5)
Enter fullscreen mode Exit fullscreen mode

12️⃣ Run the App

Finally, start the Tkinter main loop:

app.mainloop()
Enter fullscreen mode Exit fullscreen mode

✅ You’re Done!

You now have a fully working Audio Extended application that can extend audio seamlessly with fade-in/out and live progress updates.

For full source code and prebuilt EXE: https://github.com/rogers-cyber/AudioExtended

Top comments (0)