Files
whisper-local-transcribe/app.py
2026-03-02 17:02:16 +01:00

197 lines
8.8 KiB
Python

import os
import sys
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox
from src._LocalTranscribe import transcribe, get_path
import customtkinter
import threading
# ── Helper: redirect stdout/stderr into a CTkTextbox ──────────────────────
import re
_ANSI_RE = re.compile(r'\x1b\[[0-9;]*m') # strip colour codes
class _ConsoleRedirector:
"""Redirects output exclusively to the in-app console panel."""
def __init__(self, text_widget):
self.widget = text_widget
def write(self, text):
clean = _ANSI_RE.sub('', text) # strip ANSI colours
if clean.strip() == '':
return
# Schedule UI update on the main thread
try:
self.widget.after(0, self._append, clean)
except Exception:
pass
def _append(self, text):
self.widget.configure(state='normal')
self.widget.insert('end', text + ('\n' if not text.endswith('\n') else ''))
self.widget.see('end')
self.widget.configure(state='disabled')
def flush(self):
pass
# HuggingFace model IDs for non-standard models
HF_MODEL_MAP = {
'KB Swedish (tiny)': 'KBLab/kb-whisper-tiny',
'KB Swedish (base)': 'KBLab/kb-whisper-base',
'KB Swedish (small)': 'KBLab/kb-whisper-small',
'KB Swedish (medium)': 'KBLab/kb-whisper-medium',
'KB Swedish (large)': 'KBLab/kb-whisper-large',
}
customtkinter.set_appearance_mode("System")
customtkinter.set_default_color_theme("blue") # Themes: blue (default), dark-blue, green
firstclick = True
class App:
def __init__(self, master):
self.master = master
# Change font
font = ('Roboto', 13, 'bold') # Change the font and size here
font_b = ('Roboto', 12) # Change the font and size here
# Folder Path
path_frame = customtkinter.CTkFrame(master)
path_frame.pack(fill=tk.BOTH, padx=10, pady=10)
customtkinter.CTkLabel(path_frame, text="Folder:", font=font).pack(side=tk.LEFT, padx=5)
self.path_entry = customtkinter.CTkEntry(path_frame, width=50, font=font_b)
self.path_entry.insert(0, os.path.join(os.getcwd(), 'sample_audio'))
self.path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
customtkinter.CTkButton(path_frame, text="Browse", command=self.browse, font=font).pack(side=tk.LEFT, padx=5)
# Language frame
#thanks to pommicket from Stackoverflow for this fix
def on_entry_click(event):
"""function that gets called whenever entry is clicked"""
global firstclick
if firstclick: # if this is the first time they clicked it
firstclick = False
self.language_entry.delete(0, "end") # delete all the text in the entry
language_frame = customtkinter.CTkFrame(master)
language_frame.pack(fill=tk.BOTH, padx=10, pady=10)
customtkinter.CTkLabel(language_frame, text="Language:", font=font).pack(side=tk.LEFT, padx=5)
self.language_entry = customtkinter.CTkEntry(language_frame, width=50, font=('Roboto', 12, 'italic'))
self.default_language_text = "Enter language (or ignore to auto-detect)"
self.language_entry.insert(0, self.default_language_text)
self.language_entry.bind('<FocusIn>', on_entry_click)
self.language_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
# Model frame
models = ['tiny', 'tiny.en', 'base', 'base.en',
'small', 'small.en', 'medium', 'medium.en',
'large-v2', 'large-v3',
'───────────────',
'KB Swedish (tiny)', 'KB Swedish (base)',
'KB Swedish (small)', 'KB Swedish (medium)',
'KB Swedish (large)']
model_frame = customtkinter.CTkFrame(master)
model_frame.pack(fill=tk.BOTH, padx=10, pady=10)
customtkinter.CTkLabel(model_frame, text="Model:", font=font).pack(side=tk.LEFT, padx=5)
# ComboBox frame
self.model_combobox = customtkinter.CTkComboBox(
model_frame, width=50, state="readonly",
values=models, font=font_b)
self.model_combobox.set('medium') # Set the default value
self.model_combobox.pack(side=tk.LEFT, fill=tk.X, expand=True)
# Progress Bar
self.progress_bar = ttk.Progressbar(master, length=200, mode='indeterminate')
# Button actions frame
button_frame = customtkinter.CTkFrame(master)
button_frame.pack(fill=tk.BOTH, padx=10, pady=10)
self.transcribe_button = customtkinter.CTkButton(button_frame, text="Transcribe", command=self.start_transcription, font=font)
self.transcribe_button.pack(side=tk.LEFT, padx=5, pady=10, fill=tk.X, expand=True)
customtkinter.CTkButton(button_frame, text="Quit", command=master.quit, font=font).pack(side=tk.RIGHT, padx=5, pady=10, fill=tk.X, expand=True)
# ── Embedded console / log panel ──────────────────────────────────
log_label = customtkinter.CTkLabel(master, text="Console output", font=font, anchor='w')
log_label.pack(fill=tk.X, padx=12, pady=(8, 0))
self.log_box = customtkinter.CTkTextbox(master, height=220, font=('Consolas', 14),
wrap='word', state='disabled',
fg_color='#1e1e1e', text_color='#e0e0e0')
self.log_box.pack(fill=tk.BOTH, expand=True, padx=10, pady=(2, 10))
# Redirect stdout & stderr into the log panel (no backend console)
sys.stdout = _ConsoleRedirector(self.log_box)
sys.stderr = _ConsoleRedirector(self.log_box)
# Welcome message (shown after redirect so it appears in the panel)
print("Welcome to Local Transcribe with Whisper! \U0001f600")
print("Transcriptions will be saved automatically.")
print("" * 46)
# Helper functions
# Browsing
def browse(self):
initial_dir = os.getcwd()
folder_path = filedialog.askdirectory(initialdir=initial_dir)
self.path_entry.delete(0, tk.END)
self.path_entry.insert(0, folder_path)
# Start transcription
def start_transcription(self):
# Disable transcribe button
self.transcribe_button.configure(state=tk.DISABLED)
# Start a new thread for the transcription process
threading.Thread(target=self.transcribe_thread).start()
# Threading
def transcribe_thread(self):
path = self.path_entry.get()
model_display = self.model_combobox.get()
# Ignore the visual separator
if model_display.startswith(''):
messagebox.showinfo("Invalid selection", "Please select a model, not the separator line.")
self.transcribe_button.configure(state=tk.NORMAL)
return
model = HF_MODEL_MAP.get(model_display, model_display)
language = self.language_entry.get()
# Auto-set Swedish for KB models
is_kb_model = model_display.startswith('KB Swedish')
# Check if the language field has the default text or is empty
if is_kb_model:
language = 'sv'
elif language == self.default_language_text or not language.strip():
language = None # This is the same as passing nothing
verbose = True # always show transcription progress in the console panel
# Show progress bar
self.progress_bar.pack(fill=tk.X, padx=5, pady=5)
self.progress_bar.start()
# Setting path and files
glob_file = get_path(path)
#messagebox.showinfo("Message", "Starting transcription!")
# Start transcription
try:
output_text = transcribe(path, glob_file, model, language, verbose)
except UnboundLocalError:
messagebox.showinfo("Files not found error!", 'Nothing found, choose another folder.')
pass
except ValueError:
messagebox.showinfo("Invalid language name, you might have to clear the default text to continue!")
# Hide progress bar
self.progress_bar.stop()
self.progress_bar.pack_forget()
# Enable transcribe button
self.transcribe_button.configure(state=tk.NORMAL)
# Recover output text
try:
messagebox.showinfo("Finished!", output_text)
except UnboundLocalError:
pass
if __name__ == "__main__":
# Setting custom themes
root = customtkinter.CTk()
root.title("Local Transcribe with Whisper")
# Geometry — taller to accommodate the embedded console panel
width, height = 550, 560
root.geometry('{}x{}'.format(width, height))
root.minsize(450, 480)
# Icon
root.iconbitmap('images/icon.ico')
# Run
app = App(root)
root.mainloop()