Major updates

- UI is now on customtkinter
- new environment.yml file
- entry point is now in main.py
- minor improvements in audio_transcription
This commit is contained in:
2025-09-12 17:43:53 +03:00
parent 8f41105e4b
commit 7cf4c5259b
5 changed files with 260 additions and 315 deletions
+12 -191
View File
@@ -1,195 +1,16 @@
name: notecast
channels:
- conda-forge
- defaults
- nvidia
- pytorch
- conda-forge
dependencies:
- accelerate=1.10.1=pyhcf101f3_0
- aiohappyeyeballs=2.6.1=pyhd8ed1ab_0
- aiohttp=3.12.15=pyh7db6752_0
- aiosignal=1.4.0=pyhd8ed1ab_0
- aom=3.6.0=hd77b12b_0
- arrow-cpp=19.0.0=h82e8c66_4
- async-timeout=5.0.1=pyhd8ed1ab_1
- attrs=25.3.0=pyh71513ae_0
- aws-c-auth=0.9.0=hd490b63_15
- aws-c-cal=0.9.2=hd8a8e38_0
- aws-c-common=0.12.3=h2466b09_0
- aws-c-compression=0.3.1=h5d0e663_5
- aws-c-event-stream=0.5.5=ha416645_0
- aws-c-http=0.10.2=h81282ae_2
- aws-c-io=0.20.1=hf7624bd_1
- aws-c-mqtt=0.13.1=h5c1ae27_3
- aws-c-s3=0.8.3=h1e843c7_0
- aws-c-sdkutils=0.2.4=h5d0e663_0
- aws-checksums=0.2.7=h5d0e663_1
- aws-crt-cpp=0.32.10=h8abd1a4_2
- aws-sdk-cpp=1.11.528=hd293286_1
- blas=1.0=mkl
- bottleneck=1.4.2=py312h4b0e54e_0
- brotlicffi=1.0.9.2=py312h5da7b33_1
- bzip2=1.0.8=h2bbff1b_6
- c-ares=1.34.5=h2466b09_0
- ca-certificates=2025.8.3=h4c7d964_0
- cairo=1.18.4=he9e932c_0
- certifi=2025.8.3=py312haa95532_0
- cffi=1.17.1=py312h827c3e9_1
- charset-normalizer=3.3.2=pyhd3eb1b0_0
- colorama=0.4.6=pyhd8ed1ab_1
- cuda-cccl=13.0.50=h23517cc_1
- cuda-cccl_win-64=13.0.50=hc667259_1
- cuda-cudart=12.4.127=0
- cuda-cudart-dev=12.4.127=0
- cuda-cupti=12.4.127=0
- cuda-libraries=12.4.1=0
- cuda-libraries-dev=12.4.1=0
- cuda-nvrtc=12.4.127=0
- cuda-nvrtc-dev=12.4.127=0
- cuda-nvtx=12.4.127=0
- cuda-opencl=13.0.39=0
- cuda-opencl-dev=13.0.39=0
- cuda-profiler-api=13.0.39=0
- cuda-runtime=12.4.1=0
- cuda-version=13.0=3
- datasets=4.0.0=pyhcf101f3_0
- dav1d=1.2.1=h2bbff1b_0
- dill=0.3.8=pyhd8ed1ab_0
- expat=2.7.1=h8ddb27b_0
- ffmpeg=4.3.1=ha925a31_0
- filelock=3.17.0=py312haa95532_0
- fontconfig=2.14.1=hb33846d_3
- freeglut=3.4.0=h8a1e904_1
- freetype=2.13.3=h0620614_0
- fribidi=1.0.10=h62dcd97_0
- frozenlist=1.7.0=pyhf298e5d_0
- fsspec=2025.3.0=pyhd8ed1ab_0
- gflags=2.2.2=he0c23c2_1005
- giflib=5.2.2=h7edc060_0
- glog=0.5.0=h4797de2_0
- gmp=6.3.0=h537511b_0
- gmpy2=2.2.1=py312h827c3e9_0
- graphite2=1.3.14=hd77b12b_1
- harfbuzz=10.2.0=he2f9f60_1
- hf-xet=1.1.8=py312h79d111c_0
- huggingface_hub=0.34.4=pyhd8ed1ab_1
- icu=73.1=h6c2663c_0
- idna=3.7=py312haa95532_0
- intel-openmp=2023.1.0=h59b6b97_46320
- jinja2=3.1.6=py312haa95532_0
- jpeg=9e=h827c3e9_3
- khronos-opencl-icd-loader=2024.05.08=h8cc25b3_0
- lcms2=2.16=h62be587_1
- lerc=4.0.0=h5da7b33_0
- libabseil=20250127.0=cxx17_h4eb7d71_0
- libavif=1.1.1=h827c3e9_0
- libbrotlicommon=1.0.9=hcfcfb64_9
- libbrotlidec=1.0.9=hcfcfb64_9
- libbrotlienc=1.0.9=hcfcfb64_9
- libcublas=12.4.5.8=0
- libcublas-dev=12.4.5.8=0
- libcufft=11.2.1.3=0
- libcufft-dev=11.2.1.3=0
- libcurand=10.4.0.35=0
- libcurand-dev=10.4.0.35=0
- libcurl=8.15.0=h2300eb9_0
- libcusolver=11.6.1.9=0
- libcusolver-dev=11.6.1.9=0
- libcusparse=12.3.1.170=0
- libcusparse-dev=12.3.1.170=0
- libdeflate=1.22=h5bf469e_0
- libffi=3.4.4=hd77b12b_1
- libglib=2.84.2=h405b238_0
- libgrpc=1.71.0=hf4237ab_0
- libiconv=1.16=h2bbff1b_3
- libjpeg-turbo=2.0.0=h196d8e1_0
- libnpp=12.2.5.30=0
- libnpp-dev=12.2.5.30=0
- libnvfatbin=13.0.39=0
- libnvfatbin-dev=13.0.39=0
- libnvjitlink=12.4.127=0
- libnvjitlink-dev=12.4.127=0
- libnvjpeg=12.3.1.117=0
- libnvjpeg-dev=12.3.1.117=0
- libpng=1.6.39=h8cc25b3_0
- libprotobuf=5.29.3=h65a231f_1
- libre2-11=2024.07.02=hd248061_3
- libssh2=1.11.1=h2addb87_0
- libthrift=0.15.0=ha2884a9_3
- libtiff=4.7.0=h404307b_0
- libuv=1.48.0=h827c3e9_0
- libwebp=1.3.2=h18467be_1
- libwebp-base=1.3.2=h3d04722_1
- libxml2=2.13.8=h866ff63_0
- lz4-c=1.9.4=h2bbff1b_1
- markupsafe=3.0.2=py312h827c3e9_0
- mkl=2023.1.0=h6b88ed4_46358
- mkl-service=2.4.0=py312h827c3e9_2
- mkl_fft=1.3.11=py312h827c3e9_0
- mkl_random=1.2.8=py312h0158946_0
- mpc=1.3.1=h827c3e9_0
- mpfr=4.2.1=h56c3642_0
- mpmath=1.3.0=py312haa95532_0
- multidict=6.6.3=pyh62beb40_0
- multiprocess=0.70.15=py312haa95532_0
- networkx=3.5=py312haa95532_0
- numexpr=2.11.0=py312hdb065b2_0
- numpy=2.3.1=py312h5f75535_0
- numpy-base=2.3.1=py312h23d94f8_0
- openjpeg=2.5.2=h9b5d1b5_1
- openssl=3.5.2=h725018a_0
- orc=2.1.1=hd1c1d5c_0
- packaging=25.0=pyh29332c3_1
- pandas=2.3.2=py312ha5e6156_0
- pcre2=10.42=h0ff8eda_1
- pillow=11.3.0=py312hb328d1f_0
- pip=25.2=pyhc872135_0
- pixman=0.46.4=h4043f72_0
- propcache=0.3.1=pyhe1237c8_0
- psutil=5.9.0=py312h827c3e9_1
- pyarrow=19.0.0=py312h5da7b33_1
- pycparser=2.21=pyhd3eb1b0_0
- pysocks=1.7.1=py312haa95532_0
- python=3.12.11=h716150d_0
- python-dateutil=2.9.0.post0=pyhe01879c_2
- python-tzdata=2025.2=pyhd8ed1ab_0
- python-xxhash=3.5.0=py312h827c3e9_0
- pytorch=2.5.1=py3.12_cuda12.4_cudnn9_0
- pytorch-cuda=12.4=h3fd98bf_7
- pytorch-mutex=1.0=cuda
- pytz=2025.2=pyhd8ed1ab_0
- pyyaml=6.0.2=py312h827c3e9_0
- re2=2024.07.02=haf4117d_3
- regex=2024.11.6=py312h827c3e9_0
- requests=2.32.5=py312haa95532_0
- safetensors=0.5.3=py312h44068b5_0
- setuptools=72.1.0=py312haa95532_0
- six=1.17.0=pyhe01879c_1
- snappy=1.2.2=h7fa0ca8_0
- sqlite=3.50.2=hda9a48d_1
- sympy=1.14.0=py312haa95532_0
- tbb=2021.8.0=h59b6b97_0
- tk=8.6.15=hf199647_0
- tokenizers=0.21.0=py312h482ea96_0
- tqdm=4.67.1=pyhd8ed1ab_1
- transformers=4.55.4=pyhd8ed1ab_0
- typing-extensions=4.15.0=py312haa95532_0
- typing_extensions=4.15.0=py312haa95532_0
- tzdata=2025b=h04d1e81_0
- ucrt=10.0.22621.0=haa95532_0
- urllib3=2.5.0=py312haa95532_0
- utf8proc=2.6.1=h2bbff1b_1
- vc=14.42=haa95532_5
- vc14_runtime=14.44.35208=h4927774_10
- vs2015_runtime=14.44.35208=ha6b5a95_10
- wheel=0.45.1=py312haa95532_0
- win_inet_pton=1.1.0=py312haa95532_0
- xxhash=0.8.0=h8ffe710_3
- xz=5.6.4=h4754444_1
- yaml=0.2.5=he774522_0
- yarl=1.20.1=pyhe1237c8_0
- zlib=1.2.13=h8cc25b3_1
- zstd=1.5.6=h8880b57_0
- pip:
- torchaudio==2.5.1
- torchvision==0.20.1
- torchvision
- pytorch
- torchaudio
- transformers
- accelerate
- ffmpeg
- pyinstaller
- customtkinter
- isort
- mypy
- black
+4 -12
View File
@@ -1,13 +1,5 @@
from transcription.audio_transcription import AudioTranscription
from transcription.torch_checker import check_torch
from utils.logger import setup_logger
from ui.ui import TranscriberApp
logger = setup_logger("main")
check_torch()
try:
track = AudioTranscription("sample.mp3")
print(track.transcribe_audio())
except Exception as e:
logger.error(f"Execution error: {e}")
if __name__ == "__main__":
app = TranscriberApp()
app.mainloop()
+13 -12
View File
@@ -8,7 +8,7 @@ import time
import math
from tqdm import tqdm
# TODO: rename naming
# TODO: implement transcription with shift
class AudioTranscription:
model_name = "openai/whisper-large-v2"
@@ -53,6 +53,7 @@ class AudioTranscription:
self.chunks: list = []
self.batches: list = []
self.all_transcription: list = []
try:
logger.info("Loading model WhisperProcessor...")
self.processor = WhisperProcessor.from_pretrained(self.model_name)
@@ -71,15 +72,15 @@ class AudioTranscription:
logger.error(f"Unable to load file {self.filepath}: {e}")
raise
def resample(self) -> None:
def _resample(self) -> None:
self.waveform = torchaudio.functional.resample(self.waveform, self.sampling_rate, 16000)
def to_mono(self):
def _to_mono(self):
if self.waveform.shape[0] > 1:
self.waveform = self.waveform.mean(dim=0, keepdim=True)
self.waveform = self.waveform.squeeze(0)
def split_to_chunks(self, chunk_length_s: int = 30) -> None:
def _split_to_chunks(self, chunk_length_s: int = 30) -> None:
self.logger.info(f"Splitting audio on chunks...")
self.chunk_size = chunk_length_s * 16000 # 16kHz after resampling
@@ -95,15 +96,14 @@ class AudioTranscription:
chunk = self.waveform[start:end].cpu().numpy().astype("float32")
self.chunks.append(chunk)
def resplit_to_batches(self) -> None:
def _resplit_to_batches(self) -> None:
self.logger.info(f"Splitting chunks into batches...")
self.batches = []
for i in range(0, len(self.chunks), self.custom_batch_length):
batch = self.chunks[i:i + self.custom_batch_length]
self.batches.append(batch)
def process_all_batches(self) -> None:
def _process_all_batches(self) -> None:
start_time = time.time()
try:
self.all_transcription = []
@@ -136,9 +136,10 @@ class AudioTranscription:
def transcribe_audio(self) -> str:
self.resample()
self.to_mono()
self.split_to_chunks()
self.resplit_to_batches()
self.process_all_batches()
# TODO: maybe something else, not str?
self._resample()
self._to_mono()
self._split_to_chunks()
self._resplit_to_batches()
self._process_all_batches()
return " ".join(self.all_transcription)
+9
View File
@@ -0,0 +1,9 @@
from transcription.device_configuration import DeviceConfiguration
# TODO: implement saving & removing configuration
def save_configuration(cfg: DeviceConfiguration):
config = {
"Model": cfg.model_name,
"Batch Size": cfg.batch_size,
"Data Type": cfg.data_type
}
+220 -98
View File
@@ -1,123 +1,245 @@
import os
import threading
import queue
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
from ui.ui_log_handler import UILogHandler, setup_ui_logger
from tkinter import scrolledtext, filedialog, messagebox
import customtkinter as ctk
import torch
from ui.ui_log_handler import setup_ui_logger
from transcription.torch_checker import check_torch
from transcription.device_configuration import DeviceConfiguration
from transcription.audio_transcription import AudioTranscription
def main():
root = tk.Tk()
root.title("Audio Transcriptor")
root.geometry("800x600")
for col in range(4):
root.grid_columnconfigure(col, weight=1)
root.grid_rowconfigure(6, weight=1)
WINDOW_WIDTH = 900
WINDOW_HEIGHT = 650
### Buttons selector
check_torch_baton = tk.Button(root, text="Check Torch")
check_torch_baton.grid(row=0, column=0, padx=5, pady=5, sticky="ew")
# TODO: implement saving function
save_configuration_baton = tk.Button(root, text="Save configuration")
save_configuration_baton.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
class TranscriberApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Notecast")
self.geometry(f"{WINDOW_WIDTH}x{WINDOW_HEIGHT}")
ctk.set_appearance_mode("System")
ctk.set_default_color_theme("blue")
# TODO: implement deleting function
delete_configuration_baton = tk.Button(root, text="Delete configuration")
delete_configuration_baton.grid(row=0, column=2, padx=5, pady=5, sticky="ew")
# states
self.progress_queue = queue.Queue()
self.transcribe_thread = None
self.stop_flag = threading.Event()
start_transcription_baton = tk.Button(root, text="Transcript")
start_transcription_baton.grid(row=0, column=3, padx=5, pady=5, sticky="ew")
# user variables
self.model_var = tk.StringVar(value="openai/whisper-large-v2")
self.batch_var = tk.StringVar(value="32")
self.dtype_var = tk.StringVar(value="torch.float16")
self.chunk_var = tk.StringVar(value="30")
### Model options selector
model_options = [
"openai/whisper-large-v2",
"openai/whisper-large",
"openai/whisper-medium",
"openai/whisper-small",
"openai/whisper-tiny"
]
selected_model = tk.StringVar(value=model_options[0])
label_model = tk.Label(root, text="Model name:")
label_model.grid(row=1, column=0, sticky="w", pady=5, padx=5)
dropdown_model_selection = tk.OptionMenu(root, selected_model, *model_options)
dropdown_model_selection.grid(row=1, column=1, sticky="ew", pady=5, padx=5)
# settings device options
device_opts = []
if torch.cuda.is_available():
device_opts.append("cuda")
if torch.backends.mps.is_available():
device_opts.append("mps")
device_opts.append("cpu")
self.device_var = tk.StringVar(value=device_opts[0])
self.device_opts = device_opts
### Batch size selector
batch_sizes = ["32", "16", "8", "4", "2"]
selected_batch_size = tk.StringVar(value=batch_sizes[0])
label_batch_size = tk.Label(root, text="Batch size:")
label_batch_size.grid(row=1, column=2, sticky="w", pady=5, padx=5)
dropdown_batch_size_selection = tk.OptionMenu(root, selected_batch_size, *batch_sizes)
dropdown_batch_size_selection.grid(row=1, column=3, sticky="ew", pady=5, padx=5)
self.input_file_var = tk.StringVar()
self.output_file_var = tk.StringVar()
### Data type selector
data_types = ["torch.float16", "torch.float32", "torch.bfloat16"]
selected_data_type = tk.StringVar(value=data_types[0])
label_data_type = tk.Label(root, text="Data type:")
label_data_type.grid(row=2, column=0, sticky="w", pady=5, padx=5)
dropdown_data_type_selection = tk.OptionMenu(root, selected_data_type, *data_types)
dropdown_data_type_selection.grid(row=2, column=1, sticky="ew", pady=5, padx=5)
# tabs packing
self.tabview = ctk.CTkTabview(self)
self.tabview.pack(expand=True, fill="both", padx=10, pady=10)
### Chunk length selector
chunk_lengths = ["30", "25", "20", "15", "10", "5"]
selected_chunk_length = tk.StringVar(value=chunk_lengths[0])
label_chunk_length = tk.Label(root, text="Chunk length:")
label_chunk_length.grid(row=2, column=2, sticky="w", pady=5, padx=5)
dropdown_chunk_length_selection = tk.OptionMenu(root, selected_chunk_length, *chunk_lengths)
dropdown_chunk_length_selection.grid(row=2, column=3, sticky="ew", pady=5, padx=5)
self.transcript_tab = self.tabview.add("Transcription")
self.settings_tab = self.tabview.add("Settings")
# TODO: add device selector (cuda/mps/cpu)
self._build_transcription_tab()
self._build_settings_tab()
### Filepath (input)
# TODO: add checker if path is valid/invalid (i think in utils or something)
label_file_path = tk.Label(root, text="Input filepath:")
label_file_path.grid(row=3, column=0, sticky="w", pady=5, padx=5)
file_path = tk.Text(root, height=1)
file_path.grid(row=3, column=1, columnspan=3, sticky="ew", pady=5, padx=5)
# logger
self.ui_logger = setup_ui_logger(self.log_box)
### Filepath (output)
# TODO: add question mark here with tip while mouse is on it
label_output_file_path = tk.Label(root, text="Output filepath:")
label_output_file_path.grid(row=4, column=0, sticky="w", pady=5, padx=5)
output_file_path = tk.Text(root, height=1)
output_file_path.grid(row=4, column=1, columnspan=3, sticky="ew", pady=5, padx=5)
# main transcription tab
def _build_transcription_tab(self):
# file selectors
file_frame = ctk.CTkFrame(self.transcript_tab, corner_radius=10)
file_frame.pack(padx=20, pady=10, fill="x")
def show_selections():
ui_logger.info(f"Selected model: {selected_model.get()}")
ui_logger.info(f"Selected batch size: {selected_batch_size.get()} chunks")
ui_logger.info(f"Selected data type: {selected_data_type.get()}")
show_selections_baton = tk.Button(root, text="Show Selections", command=show_selections)
show_selections_baton.grid(row=5, column=0, columnspan=4, pady=5, sticky="ew")
log_box = ScrolledText(root, wrap="word")
log_box.grid(row=6, column=0, columnspan=4, sticky="nsew", padx=10, pady=5)
ui_logger = setup_ui_logger(log_box)
def transcribe():
current_device_config = DeviceConfiguration(
device="cuda",
model_name=selected_model.get(),
batch_size=int(selected_batch_size.get()),
chunk_length_s=30,
data_type=selected_data_type.get()
ctk.CTkLabel(file_frame, text="Input file:").pack(side="left", padx=5, pady=5)
ctk.CTkEntry(file_frame, textvariable=self.input_file_var, width=400).pack(
side="left", padx=5, pady=5, expand=True, fill="x"
)
Audio = AudioTranscription(
filepath=file_path.get("1.0", "end-1c"),
device_configuration=current_device_config,
logger=ui_logger
ctk.CTkButton(file_frame, text="Browse", command=self._browse_input).pack(
side="left", padx=5, pady=5
)
transcription = Audio.transcribe_audio()
with open(f"{file_path.get('1.0', 'end-1c')}.txt", "w") as output_file:
output_file.write(transcription)
check_torch_baton.config(command=lambda: check_torch(ui_logger))
start_transcription_baton.config(command=transcribe)
out_frame = ctk.CTkFrame(self.transcript_tab, corner_radius=10)
out_frame.pack(padx=20, pady=5, fill="x")
ctk.CTkLabel(out_frame, text="Output file:").pack(side="left", padx=5, pady=5)
ctk.CTkEntry(out_frame, textvariable=self.output_file_var, width=400).pack(
side="left", padx=5, pady=5, expand=True, fill="x"
)
ctk.CTkButton(out_frame, text="Browse", command=self._browse_output).pack(
side="left", padx=5, pady=5
)
root.mainloop()
# controls
ctrl_frame = ctk.CTkFrame(self.transcript_tab, corner_radius=10)
ctrl_frame.pack(padx=20, pady=10, fill="x")
ctk.CTkButton(ctrl_frame, text="Check Torch", command=self._check_torch).pack(
side="left", padx=10, pady=5
)
self.start_btn = ctk.CTkButton(
ctrl_frame, text="Start transcription", command=self._start_transcription
)
self.start_btn.pack(side="left", padx=10, pady=5)
self.stop_btn = ctk.CTkButton(
ctrl_frame, text="Stop", command=self._stop_transcription, state="disabled"
)
self.stop_btn.pack(side="left", padx=10, pady=5)
if __name__ == "__main__":
main()
# log box
self.log_box = scrolledtext.ScrolledText(
self.transcript_tab,
wrap="word",
height=20,
font=("Consolas", 16),
)
self.log_box.pack(padx=20, pady=10, expand=True, fill="both")
def _build_settings_tab(self):
pad = 20
### Model
ctk.CTkLabel(self.settings_tab, text="Model:").pack(
anchor="w", padx=pad, pady=(pad, 5)
)
ctk.CTkOptionMenu(
self.settings_tab,
variable=self.model_var,
values=[
"openai/whisper-large-v2",
"openai/whisper-large",
"openai/whisper-medium",
"openai/whisper-small",
"openai/whisper-tiny",
],
).pack(fill="x", padx=pad, pady=5)
### Batch size
ctk.CTkLabel(self.settings_tab, text="Batch size:").pack(
anchor="w", padx=pad, pady=(pad, 5)
)
ctk.CTkOptionMenu(
self.settings_tab,
variable=self.batch_var,
values=["32", "16", "8", "4", "2"],
).pack(fill="x", padx=pad, pady=5)
### Data type
ctk.CTkLabel(self.settings_tab, text="Data type:").pack(
anchor="w", padx=pad, pady=(pad, 5)
)
ctk.CTkOptionMenu(
self.settings_tab,
variable=self.dtype_var,
values=["torch.float16", "torch.float32", "torch.bfloat16"],
).pack(fill="x", padx=pad, pady=5)
### Chunk length
ctk.CTkLabel(self.settings_tab, text="Chunk length (s):").pack(
anchor="w", padx=pad, pady=(pad, 5)
)
ctk.CTkOptionMenu(
self.settings_tab,
variable=self.chunk_var,
values=["30", "25", "20", "15", "10", "5"],
).pack(fill="x", padx=pad, pady=5)
### Device
ctk.CTkLabel(self.settings_tab, text="Device:").pack(
anchor="w", padx=pad, pady=(pad, 5)
)
ctk.CTkOptionMenu(
self.settings_tab,
variable=self.device_var,
values=self.device_opts,
).pack(fill="x", padx=pad, pady=5)
# action buttons
def _browse_input(self):
path = filedialog.askopenfilename(
title="Select input audio file",
filetypes=[("Audio files", "*.wav *.mp3 *.m4a *.flac"), ("All files", "*.*")]
)
if path:
self.input_file_var.set(path)
def _browse_output(self):
path = filedialog.asksaveasfilename(
title="Select output file",
defaultextension=".txt",
filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
)
if path:
self.output_file_var.set(path)
def _check_torch(self):
check_torch(self.ui_logger)
def _start_transcription(self):
infile = self.input_file_var.get().strip()
if not infile or not os.path.isfile(infile):
messagebox.showerror("Error", "Please select a valid input file.")
return
self.start_btn.configure(state="disabled")
self.stop_btn.configure(state="normal")
self.ui_logger.info("Starting transcription...")
self.stop_flag.clear()
self.transcribe_thread = threading.Thread(
target=self._transcribe_worker, args=(infile,), daemon=True
)
self.transcribe_thread.start()
def _stop_transcription(self):
self.stop_flag.set()
self.ui_logger.info("Stopping transcription...")
self.stop_btn.configure(state="disabled")
def _transcribe_worker(self, infile: str):
try:
config = DeviceConfiguration(
device=self.device_var.get(),
model_name=self.model_var.get(),
batch_size=int(self.batch_var.get()),
chunk_length_s=int(self.chunk_var.get()),
data_type=self.dtype_var.get(),
)
Audio = AudioTranscription(
filepath=infile,
device_configuration=config,
logger=self.ui_logger,
)
transcription = Audio.transcribe_audio()
outfile = self.output_file_var.get().strip()
if not outfile:
outfile = infile + ".txt"
with open(outfile, "w", encoding="utf-8") as f:
f.write(transcription)
self.ui_logger.info(f"Transcription saved to {outfile}")
except Exception as e:
self.ui_logger.error(f"Error: {e}")
messagebox.showerror("Error", str(e))
finally:
self.start_btn.configure(state="normal")
self.stop_btn.configure(state="disabled")