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 name: notecast
channels: channels:
- conda-forge
- defaults - defaults
- nvidia - conda-forge
- pytorch
dependencies: dependencies:
- accelerate=1.10.1=pyhcf101f3_0 - torchvision
- aiohappyeyeballs=2.6.1=pyhd8ed1ab_0 - pytorch
- aiohttp=3.12.15=pyh7db6752_0 - torchaudio
- aiosignal=1.4.0=pyhd8ed1ab_0 - transformers
- aom=3.6.0=hd77b12b_0 - accelerate
- arrow-cpp=19.0.0=h82e8c66_4 - ffmpeg
- async-timeout=5.0.1=pyhd8ed1ab_1 - pyinstaller
- attrs=25.3.0=pyh71513ae_0 - customtkinter
- aws-c-auth=0.9.0=hd490b63_15 - isort
- aws-c-cal=0.9.2=hd8a8e38_0 - mypy
- aws-c-common=0.12.3=h2466b09_0 - black
- 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
+4 -12
View File
@@ -1,13 +1,5 @@
from transcription.audio_transcription import AudioTranscription from ui.ui import TranscriberApp
from transcription.torch_checker import check_torch
from utils.logger import setup_logger
logger = setup_logger("main") if __name__ == "__main__":
app = TranscriberApp()
check_torch() app.mainloop()
try:
track = AudioTranscription("sample.mp3")
print(track.transcribe_audio())
except Exception as e:
logger.error(f"Execution error: {e}")
+13 -12
View File
@@ -8,7 +8,7 @@ import time
import math import math
from tqdm import tqdm from tqdm import tqdm
# TODO: rename naming # TODO: implement transcription with shift
class AudioTranscription: class AudioTranscription:
model_name = "openai/whisper-large-v2" model_name = "openai/whisper-large-v2"
@@ -53,6 +53,7 @@ class AudioTranscription:
self.chunks: list = [] self.chunks: list = []
self.batches: list = [] self.batches: list = []
self.all_transcription: list = []
try: try:
logger.info("Loading model WhisperProcessor...") logger.info("Loading model WhisperProcessor...")
self.processor = WhisperProcessor.from_pretrained(self.model_name) self.processor = WhisperProcessor.from_pretrained(self.model_name)
@@ -71,15 +72,15 @@ class AudioTranscription:
logger.error(f"Unable to load file {self.filepath}: {e}") logger.error(f"Unable to load file {self.filepath}: {e}")
raise raise
def resample(self) -> None: def _resample(self) -> None:
self.waveform = torchaudio.functional.resample(self.waveform, self.sampling_rate, 16000) 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: if self.waveform.shape[0] > 1:
self.waveform = self.waveform.mean(dim=0, keepdim=True) self.waveform = self.waveform.mean(dim=0, keepdim=True)
self.waveform = self.waveform.squeeze(0) 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.logger.info(f"Splitting audio on chunks...")
self.chunk_size = chunk_length_s * 16000 # 16kHz after resampling 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") chunk = self.waveform[start:end].cpu().numpy().astype("float32")
self.chunks.append(chunk) 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.logger.info(f"Splitting chunks into batches...")
self.batches = [] self.batches = []
for i in range(0, len(self.chunks), self.custom_batch_length): for i in range(0, len(self.chunks), self.custom_batch_length):
batch = self.chunks[i:i + self.custom_batch_length] batch = self.chunks[i:i + self.custom_batch_length]
self.batches.append(batch) self.batches.append(batch)
def _process_all_batches(self) -> None:
def process_all_batches(self) -> None:
start_time = time.time() start_time = time.time()
try: try:
self.all_transcription = [] self.all_transcription = []
@@ -136,9 +136,10 @@ class AudioTranscription:
def transcribe_audio(self) -> str: def transcribe_audio(self) -> str:
self.resample() # TODO: maybe something else, not str?
self.to_mono() self._resample()
self.split_to_chunks() self._to_mono()
self.resplit_to_batches() self._split_to_chunks()
self.process_all_batches() self._resplit_to_batches()
self._process_all_batches()
return " ".join(self.all_transcription) 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
}
+213 -91
View File
@@ -1,123 +1,245 @@
import os
import threading
import queue
import tkinter as tk import tkinter as tk
from tkinter.scrolledtext import ScrolledText from tkinter import scrolledtext, filedialog, messagebox
from ui.ui_log_handler import UILogHandler, setup_ui_logger
import customtkinter as ctk
import torch
from ui.ui_log_handler import setup_ui_logger
from transcription.torch_checker import check_torch from transcription.torch_checker import check_torch
from transcription.device_configuration import DeviceConfiguration from transcription.device_configuration import DeviceConfiguration
from transcription.audio_transcription import AudioTranscription from transcription.audio_transcription import AudioTranscription
def main():
root = tk.Tk()
root.title("Audio Transcriptor")
root.geometry("800x600")
for col in range(4): WINDOW_WIDTH = 900
root.grid_columnconfigure(col, weight=1) WINDOW_HEIGHT = 650
root.grid_rowconfigure(6, weight=1)
### 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 class TranscriberApp(ctk.CTk):
save_configuration_baton = tk.Button(root, text="Save configuration") def __init__(self):
save_configuration_baton.grid(row=0, column=1, padx=5, pady=5, sticky="ew") 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 # states
delete_configuration_baton = tk.Button(root, text="Delete configuration") self.progress_queue = queue.Queue()
delete_configuration_baton.grid(row=0, column=2, padx=5, pady=5, sticky="ew") self.transcribe_thread = None
self.stop_flag = threading.Event()
start_transcription_baton = tk.Button(root, text="Transcript") # user variables
start_transcription_baton.grid(row=0, column=3, padx=5, pady=5, sticky="ew") 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 # settings device options
model_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
self.input_file_var = tk.StringVar()
self.output_file_var = tk.StringVar()
# tabs packing
self.tabview = ctk.CTkTabview(self)
self.tabview.pack(expand=True, fill="both", padx=10, pady=10)
self.transcript_tab = self.tabview.add("Transcription")
self.settings_tab = self.tabview.add("Settings")
self._build_transcription_tab()
self._build_settings_tab()
# logger
self.ui_logger = setup_ui_logger(self.log_box)
# 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")
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"
)
ctk.CTkButton(file_frame, text="Browse", command=self._browse_input).pack(
side="left", padx=5, pady=5
)
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
)
# 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)
# 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-v2",
"openai/whisper-large", "openai/whisper-large",
"openai/whisper-medium", "openai/whisper-medium",
"openai/whisper-small", "openai/whisper-small",
"openai/whisper-tiny" "openai/whisper-tiny",
] ],
selected_model = tk.StringVar(value=model_options[0]) ).pack(fill="x", padx=pad, pady=5)
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)
### Batch size selector ### Batch size
batch_sizes = ["32", "16", "8", "4", "2"] ctk.CTkLabel(self.settings_tab, text="Batch size:").pack(
selected_batch_size = tk.StringVar(value=batch_sizes[0]) anchor="w", padx=pad, pady=(pad, 5)
label_batch_size = tk.Label(root, text="Batch size:") )
label_batch_size.grid(row=1, column=2, sticky="w", pady=5, padx=5) ctk.CTkOptionMenu(
dropdown_batch_size_selection = tk.OptionMenu(root, selected_batch_size, *batch_sizes) self.settings_tab,
dropdown_batch_size_selection.grid(row=1, column=3, sticky="ew", pady=5, padx=5) variable=self.batch_var,
values=["32", "16", "8", "4", "2"],
).pack(fill="x", padx=pad, pady=5)
### Data type selector ### Data type
data_types = ["torch.float16", "torch.float32", "torch.bfloat16"] ctk.CTkLabel(self.settings_tab, text="Data type:").pack(
selected_data_type = tk.StringVar(value=data_types[0]) anchor="w", padx=pad, pady=(pad, 5)
label_data_type = tk.Label(root, text="Data type:") )
label_data_type.grid(row=2, column=0, sticky="w", pady=5, padx=5) ctk.CTkOptionMenu(
dropdown_data_type_selection = tk.OptionMenu(root, selected_data_type, *data_types) self.settings_tab,
dropdown_data_type_selection.grid(row=2, column=1, sticky="ew", pady=5, padx=5) variable=self.dtype_var,
values=["torch.float16", "torch.float32", "torch.bfloat16"],
).pack(fill="x", padx=pad, pady=5)
### Chunk length selector ### Chunk length
chunk_lengths = ["30", "25", "20", "15", "10", "5"] ctk.CTkLabel(self.settings_tab, text="Chunk length (s):").pack(
selected_chunk_length = tk.StringVar(value=chunk_lengths[0]) anchor="w", padx=pad, pady=(pad, 5)
label_chunk_length = tk.Label(root, text="Chunk length:") )
label_chunk_length.grid(row=2, column=2, sticky="w", pady=5, padx=5) ctk.CTkOptionMenu(
dropdown_chunk_length_selection = tk.OptionMenu(root, selected_chunk_length, *chunk_lengths) self.settings_tab,
dropdown_chunk_length_selection.grid(row=2, column=3, sticky="ew", pady=5, padx=5) variable=self.chunk_var,
values=["30", "25", "20", "15", "10", "5"],
).pack(fill="x", padx=pad, pady=5)
# TODO: add device selector (cuda/mps/cpu) ### 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)
### Filepath (input) # action buttons
# TODO: add checker if path is valid/invalid (i think in utils or something) def _browse_input(self):
label_file_path = tk.Label(root, text="Input filepath:") path = filedialog.askopenfilename(
label_file_path.grid(row=3, column=0, sticky="w", pady=5, padx=5) title="Select input audio file",
file_path = tk.Text(root, height=1) filetypes=[("Audio files", "*.wav *.mp3 *.m4a *.flac"), ("All files", "*.*")]
file_path.grid(row=3, column=1, columnspan=3, sticky="ew", pady=5, padx=5) )
if path:
self.input_file_var.set(path)
### Filepath (output) def _browse_output(self):
# TODO: add question mark here with tip while mouse is on it path = filedialog.asksaveasfilename(
label_output_file_path = tk.Label(root, text="Output filepath:") title="Select output file",
label_output_file_path.grid(row=4, column=0, sticky="w", pady=5, padx=5) defaultextension=".txt",
output_file_path = tk.Text(root, height=1) filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
output_file_path.grid(row=4, column=1, columnspan=3, sticky="ew", pady=5, padx=5) )
if path:
self.output_file_var.set(path)
def show_selections(): def _check_torch(self):
ui_logger.info(f"Selected model: {selected_model.get()}") check_torch(self.ui_logger)
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) def _start_transcription(self):
show_selections_baton.grid(row=5, column=0, columnspan=4, pady=5, sticky="ew") 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
log_box = ScrolledText(root, wrap="word") self.start_btn.configure(state="disabled")
log_box.grid(row=6, column=0, columnspan=4, sticky="nsew", padx=10, pady=5) self.stop_btn.configure(state="normal")
ui_logger = setup_ui_logger(log_box) self.ui_logger.info("Starting transcription...")
def transcribe(): self.stop_flag.clear()
current_device_config = DeviceConfiguration( self.transcribe_thread = threading.Thread(
device="cuda", target=self._transcribe_worker, args=(infile,), daemon=True
model_name=selected_model.get(), )
batch_size=int(selected_batch_size.get()), self.transcribe_thread.start()
chunk_length_s=30,
data_type=selected_data_type.get() 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( Audio = AudioTranscription(
filepath=file_path.get("1.0", "end-1c"), filepath=infile,
device_configuration=current_device_config, device_configuration=config,
logger=ui_logger logger=self.ui_logger,
) )
transcription = Audio.transcribe_audio() 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)) outfile = self.output_file_var.get().strip()
start_transcription_baton.config(command=transcribe) if not outfile:
outfile = infile + ".txt"
root.mainloop() 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:
if __name__ == "__main__": self.ui_logger.error(f"Error: {e}")
main() messagebox.showerror("Error", str(e))
finally:
self.start_btn.configure(state="normal")
self.stop_btn.configure(state="disabled")