Working version

- audio/video files support added
This commit is contained in:
2025-09-17 00:59:43 +03:00
parent 7291b148e5
commit 414cb4d38e
6 changed files with 85 additions and 42 deletions
+5
View File
@@ -8,3 +8,8 @@ You can create environment running
```
conda env create -f environment.yml
```
or for CUDA/Nvidia:
```
conda create -n notecast -c pytorch -c nvidia pytorch torchvision torchaudio transformers python=3.12
conda install ffmpeg customtkinter -c conda-forge -c bioconda
```
+6 -13
View File
@@ -3,19 +3,12 @@ channels:
- pytorch
- nvidia
- conda-forge
- defaults
- bioconda
dependencies:
- python=3.10
- pytorch::pytorch=2.0.1
- pytorch::torchvision=0.15.2
- pytorch::torchaudio=2.0.2
- pytorch::cudatoolkit=11.8
- transformers=4.30.0
- accelerate=0.20.0
- pytorch
- torchvision
- torchaudio
- transformers
- python=3.12
- ffmpeg
- pyinstaller
- customtkinter
- isort
- mypy
- black
- tqdm
+28 -18
View File
@@ -18,6 +18,7 @@ class AudioTranscription:
filepath: str
waveform: torch.Tensor
sampling_rate: int
file_format: str
chunks: list = []
batches: list = []
@@ -47,6 +48,9 @@ class AudioTranscription:
self.language = language
self.logger = logger
# setting file extension
self.file_format = filepath.split(".")[-1]
# extracting configuration
self.device = device_configuration.device
self.model_name = device_configuration.model_name
@@ -57,24 +61,28 @@ 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)
def _load_model(self) -> None:
self.logger.info("Loading model WhisperProcessor...")
try:
self.processor = WhisperProcessor.from_pretrained(self.model_name)
self.model = WhisperForConditionalGeneration.from_pretrained(
self.model_name, torch_dtype = self.torch_dtype
).to(self.device)
logger.info("Model loaded.")
self.waveform, self.sampling_rate = torchaudio.load(
filepath, format="mp3", backend="ffmpeg"
)
logger.info(f"Successfully loaded file {filepath}.")
self.logger.info("Model loaded.")
except Exception as e:
logger.error(f"Unable to load file {self.filepath}: {e}")
raise
self.logger.error(f"Error while loading model: {e}")
def _load_file(self) -> None:
self.logger.info(f"Loading file {self.filepath}")
try:
self.waveform, self.sampling_rate = torchaudio.load(
self.filepath, format=self.file_format, backend="ffmpeg"
)
self.logger.info(f"Successfully loaded file {self.filepath}.")
except Exception as e:
self.logger.error(f"Unable to load file {self.filepath}: {e}")
def _resample(self) -> None:
self.waveform = torchaudio.functional.resample(
@@ -86,15 +94,15 @@ class AudioTranscription:
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, shift: bool = False) -> None:
self.logger.info(f"Splitting audio on chunks...")
self.chunk_size = chunk_length_s * 16000 # 16kHz after resampling
self.chunk_size = self.custom_chunk_length * 16000 # 16kHz after resampling
total_samples = self.waveform.shape[0]
chunks_count = (total_samples + self.chunk_size - 1) // self.chunk_size
self.logger.info(
f"File length - {total_samples / 16000:.1f} seconds, splitting on {chunks_count} chunks by {chunk_length_s} seconds per chunk."
f"File length - {total_samples / 16000:.1f} seconds, splitting on {chunks_count} chunks by {self.custom_chunk_length} seconds per chunk."
)
self.chunks = []
@@ -104,12 +112,13 @@ class AudioTranscription:
chunk = self.waveform[start:end].cpu().numpy().astype("float32")
self.chunks.append(chunk)
def _resplit_to_batches(self) -> None:
def _resplit_chunks_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)
self.logger.info(f"Total: {len(self.batches)} batches")
def _process_all_batches(self) -> None:
start_time = time.time()
@@ -149,10 +158,11 @@ class AudioTranscription:
self.logger.error(f"Errors occured while processing chunks: {e}")
def transcribe_audio(self) -> str:
# TODO: maybe something else, not str?
self._load_model()
self._load_file()
self._resample()
self._to_mono()
self._split_to_chunks()
self._resplit_to_batches()
self._resplit_chunks_to_batches()
self._process_all_batches()
return " ".join(self.all_transcription)
+24
View File
@@ -0,0 +1,24 @@
import customtkinter as ctk
class ToolTip(ctk.CTkToplevel):
def __init__(self, widget, text):
super().__init__()
self.withdraw()
self.overrideredirect(True)
self.attributes("-topmost", True)
self.label = ctk.CTkLabel(self, text=text, fg_color="gray20", corner_radius=6, padx=10, pady=5)
self.label.pack()
self.widget = widget
widget.bind("<Enter>", self.show_tooltip)
widget.bind("<Leave>", self.hide_tooltip)
def show_tooltip(self, event=None):
x = self.widget.winfo_rootx() + 20
y = self.widget.winfo_rooty() + 20
self.geometry(f"+{x}+{y}")
self.deiconify()
def hide_tooltip(self, event=None):
self.withdraw()
+18 -7
View File
@@ -11,6 +11,7 @@ from transcription.audio_transcription import AudioTranscription
from transcription.device_configuration import DeviceConfiguration
from transcription.torch_checker import check_torch
from ui.ui_log_handler import setup_ui_logger
from ui.tooltip import ToolTip
WINDOW_WIDTH = 900
WINDOW_HEIGHT = 650
@@ -102,6 +103,8 @@ class TranscriberApp(ctk.CTk):
)
self.stop_btn.pack(side="left", padx=10, pady=5)
# TODO: add unload model button here
# log box
self.log_box = scrolledtext.ScrolledText(
self.transcript_tab,
@@ -112,6 +115,7 @@ class TranscriberApp(ctk.CTk):
self.log_box.pack(padx=20, pady=10, expand=True, fill="both")
def _build_settings_tab(self):
# TODO: add tooltips here
pad = 20
### Model
@@ -175,20 +179,27 @@ class TranscriberApp(ctk.CTk):
path = filedialog.askopenfilename(
title="Select input audio file",
filetypes=[
("Audio files", "*.wav *.mp3 *.m4a *.flac"),
("Media files", "*.wav *.mp3 *.m4a *.flac *.ogg *.mp4 *.mkv *.avi"),
("Audio files", "*.wav *.mp3 *.m4a *.flac *.ogg"),
("Video files", "*.mp4 *.mkv *.avi"),
("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", "*.*")],
# TODO: add custom filename here
directory = filedialog.askdirectory(
title="Select output directory",
)
if path:
if directory:
input_name = os.path.basename(self.input_file_var.get())
name, _ = os.path.splitext(input_name)
# TODO: redo output_name logic maybe?
output_name = f"{"".join(name.split(".").pop())}.txt"
path = os.path.join(directory, output_name)
self.output_file_var.set(path)
def _check_torch(self):
+1 -1
View File
@@ -13,7 +13,7 @@ class UILogHandler(logging.Handler):
self.text_widget.see(tk.END)
# TODO: maybe some tqdm here, not in console?
# TODO: status should be here
def setup_ui_logger(text_widget: tk.Text, level=logging.INFO):
logger = logging.getLogger("UI_LOGGER")
logger.setLevel(level)