isort & black
- trying to fix environment issues
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
|||||||
|
name: notecast
|
||||||
|
channels:
|
||||||
|
- pytorch
|
||||||
|
- conda-forge
|
||||||
|
- defaults
|
||||||
|
dependencies:
|
||||||
|
- python=3.10
|
||||||
|
- pytorch::pytorch=2.0.1
|
||||||
|
- pytorch::torchvision=0.15.2
|
||||||
|
- pytorch::torchaudio=2.0.2
|
||||||
|
- transformers=4.30.0
|
||||||
|
- accelerate=0.20.0
|
||||||
|
- ffmpeg
|
||||||
|
- pyinstaller
|
||||||
|
- customtkinter
|
||||||
|
- isort
|
||||||
|
- mypy
|
||||||
|
- black
|
||||||
|
- tqdm
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
name: notecast
|
||||||
|
channels:
|
||||||
|
- pytorch
|
||||||
|
- nvidia
|
||||||
|
- conda-forge
|
||||||
|
- defaults
|
||||||
|
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
|
||||||
|
- ffmpeg
|
||||||
|
- pyinstaller
|
||||||
|
- customtkinter
|
||||||
|
- isort
|
||||||
|
- mypy
|
||||||
|
- black
|
||||||
|
- tqdm
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
name: notecast
|
||||||
|
channels:
|
||||||
|
- pytorch
|
||||||
|
- conda-forge
|
||||||
|
- defaults
|
||||||
|
dependencies:
|
||||||
|
- python=3.10
|
||||||
|
- pytorch::pytorch=2.0.1
|
||||||
|
- pytorch::torchvision=0.15.2
|
||||||
|
- pytorch::torchaudio=2.0.2
|
||||||
|
- transformers=4.30.0
|
||||||
|
- accelerate=0.20.0
|
||||||
|
- ffmpeg
|
||||||
|
- pyinstaller
|
||||||
|
- customtkinter
|
||||||
|
- isort
|
||||||
|
- mypy
|
||||||
|
- black
|
||||||
|
- tqdm
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
name: notecast
|
|
||||||
channels:
|
|
||||||
- defaults
|
|
||||||
- conda-forge
|
|
||||||
dependencies:
|
|
||||||
- torchvision
|
|
||||||
- pytorch
|
|
||||||
- torchaudio
|
|
||||||
- transformers
|
|
||||||
- accelerate
|
|
||||||
- ffmpeg
|
|
||||||
- pyinstaller
|
|
||||||
- customtkinter
|
|
||||||
- isort
|
|
||||||
- mypy
|
|
||||||
- black
|
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
from transformers import WhisperProcessor, WhisperForConditionalGeneration
|
import logging
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
import torchaudio
|
import torchaudio
|
||||||
from ui.ui_log_handler import UILogHandler
|
|
||||||
from transcription.device_configuration import DeviceConfiguration
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
import math
|
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
from transformers import WhisperForConditionalGeneration, WhisperProcessor
|
||||||
|
|
||||||
|
from transcription.device_configuration import DeviceConfiguration
|
||||||
|
from ui.ui_log_handler import UILogHandler
|
||||||
|
|
||||||
|
|
||||||
# TODO: implement transcription with shift
|
# TODO: implement transcription with shift
|
||||||
class AudioTranscription:
|
class AudioTranscription:
|
||||||
@@ -37,7 +40,7 @@ class AudioTranscription:
|
|||||||
filepath: str,
|
filepath: str,
|
||||||
device_configuration: DeviceConfiguration,
|
device_configuration: DeviceConfiguration,
|
||||||
logger: logging.Logger,
|
logger: logging.Logger,
|
||||||
language = "ru"
|
language="ru",
|
||||||
) -> None:
|
) -> None:
|
||||||
# TODO: add pretty docs here
|
# TODO: add pretty docs here
|
||||||
self.filepath = filepath
|
self.filepath = filepath
|
||||||
@@ -59,13 +62,14 @@ class AudioTranscription:
|
|||||||
self.processor = WhisperProcessor.from_pretrained(self.model_name)
|
self.processor = WhisperProcessor.from_pretrained(self.model_name)
|
||||||
|
|
||||||
self.model = WhisperForConditionalGeneration.from_pretrained(
|
self.model = WhisperForConditionalGeneration.from_pretrained(
|
||||||
self.model_name,
|
self.model_name, torch_dtype=self.torch_dtype
|
||||||
torch_dtype=self.torch_dtype
|
|
||||||
).to(self.device)
|
).to(self.device)
|
||||||
|
|
||||||
logger.info("Model loaded.")
|
logger.info("Model loaded.")
|
||||||
|
|
||||||
self.waveform, self.sampling_rate = torchaudio.load(filepath, format="mp3", backend="ffmpeg")
|
self.waveform, self.sampling_rate = torchaudio.load(
|
||||||
|
filepath, format="mp3", backend="ffmpeg"
|
||||||
|
)
|
||||||
logger.info(f"Successfully loaded file {filepath}.")
|
logger.info(f"Successfully loaded file {filepath}.")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -73,7 +77,9 @@ class AudioTranscription:
|
|||||||
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:
|
||||||
@@ -87,7 +93,9 @@ class AudioTranscription:
|
|||||||
total_samples = self.waveform.shape[0]
|
total_samples = self.waveform.shape[0]
|
||||||
chunks_count = (total_samples + self.chunk_size - 1) // self.chunk_size
|
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.")
|
self.logger.info(
|
||||||
|
f"File length - {total_samples / 16000:.1f} seconds, splitting on {chunks_count} chunks by {chunk_length_s} seconds per chunk."
|
||||||
|
)
|
||||||
|
|
||||||
self.chunks = []
|
self.chunks = []
|
||||||
for idx in tqdm(range(chunks_count)):
|
for idx in tqdm(range(chunks_count)):
|
||||||
@@ -100,7 +108,7 @@ class AudioTranscription:
|
|||||||
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:
|
||||||
@@ -113,28 +121,33 @@ class AudioTranscription:
|
|||||||
self.batches[idx],
|
self.batches[idx],
|
||||||
sampling_rate=16000,
|
sampling_rate=16000,
|
||||||
return_tensors="pt",
|
return_tensors="pt",
|
||||||
padding=True
|
padding=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
input_features = inputs.input_features.to(self.device).to(self.torch_dtype)
|
input_features = inputs.input_features.to(self.device).to(
|
||||||
|
self.torch_dtype
|
||||||
|
)
|
||||||
|
|
||||||
with torch.no_grad():
|
with torch.no_grad():
|
||||||
predicted_ids = self.model.generate(
|
predicted_ids = self.model.generate(
|
||||||
input_features,
|
input_features,
|
||||||
language=self.language,
|
language=self.language,
|
||||||
task="transcribe",
|
task="transcribe",
|
||||||
temperature=0.0
|
temperature=0.0,
|
||||||
)
|
)
|
||||||
|
|
||||||
texts = self.processor.batch_decode(predicted_ids, skip_special_tokens=True)
|
texts = self.processor.batch_decode(
|
||||||
|
predicted_ids, skip_special_tokens=True
|
||||||
|
)
|
||||||
self.all_transcription.extend(texts)
|
self.all_transcription.extend(texts)
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
self.logger.info(f"Transcription completed in {end_time - start_time:.2f} seconds")
|
self.logger.info(
|
||||||
|
f"Transcription completed in {end_time - start_time:.2f} seconds"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Errors occured while processing chunks: {e}")
|
self.logger.error(f"Errors occured while processing chunks: {e}")
|
||||||
|
|
||||||
|
|
||||||
def transcribe_audio(self) -> str:
|
def transcribe_audio(self) -> str:
|
||||||
# TODO: maybe something else, not str?
|
# TODO: maybe something else, not str?
|
||||||
self._resample()
|
self._resample()
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DeviceConfiguration:
|
class DeviceConfiguration:
|
||||||
"""
|
"""
|
||||||
@@ -25,6 +27,7 @@ class DeviceConfiguration:
|
|||||||
- torch.float32 - for CPU / weak GPU
|
- torch.float32 - for CPU / weak GPU
|
||||||
- torch.bfloat16 - for GPUs which has BF16 support
|
- torch.bfloat16 - for GPUs which has BF16 support
|
||||||
"""
|
"""
|
||||||
|
|
||||||
device: str = "cuda"
|
device: str = "cuda"
|
||||||
model_name: str = "openai/whisper-large-v2"
|
model_name: str = "openai/whisper-large-v2"
|
||||||
batch_size: int = 16
|
batch_size: int = 16
|
||||||
@@ -34,7 +37,7 @@ class DeviceConfiguration:
|
|||||||
_dtype_map = {
|
_dtype_map = {
|
||||||
"torch.float16": torch.float16,
|
"torch.float16": torch.float16,
|
||||||
"torch.float32": torch.float32,
|
"torch.float32": torch.float32,
|
||||||
"torch.bfloat16": torch.bfloat16
|
"torch.bfloat16": torch.bfloat16,
|
||||||
}
|
}
|
||||||
|
|
||||||
torch_dtype: torch.dtype = None
|
torch_dtype: torch.dtype = None
|
||||||
|
|||||||
@@ -1,31 +1,37 @@
|
|||||||
import torch
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import logging
|
import logging
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
|
||||||
def check_torch(logger: logging.Logger) -> None:
|
def check_torch(logger: logging.Logger) -> None:
|
||||||
logger.info("=== Checking PyTorch ===")
|
logger.info("=== Checking PyTorch ===")
|
||||||
logger.info(f"Torch version: {torch.__version__}")
|
logger.info(f"Torch version: {torch.__version__}")
|
||||||
|
|
||||||
# === NVIDIA / AMD (CUDA API) ===
|
# NVIDIA / AMD (CUDA API)
|
||||||
if torch.cuda.is_available():
|
if torch.cuda.is_available():
|
||||||
backend = "CUDA"
|
backend = "CUDA"
|
||||||
if torch.version.hip is not None:
|
if torch.version.hip is not None:
|
||||||
backend = "ROCm (AMD HIP)"
|
backend = "ROCm (AMD HIP)"
|
||||||
|
|
||||||
logger.info(f"{backend} backend is available")
|
logger.info(f"{backend} backend is available")
|
||||||
logger.info(f"Compiled with: CUDA {torch.version.cuda}, ROCm {torch.version.hip}")
|
logger.info(
|
||||||
|
f"Compiled with: CUDA {torch.version.cuda}, ROCm {torch.version.hip}"
|
||||||
|
)
|
||||||
logger.info(f"Number of devices: {torch.cuda.device_count()}")
|
logger.info(f"Number of devices: {torch.cuda.device_count()}")
|
||||||
|
|
||||||
for i in range(torch.cuda.device_count()):
|
for i in range(torch.cuda.device_count()):
|
||||||
logger.info(f"GPU {i}: {torch.cuda.get_device_name(i)}")
|
logger.info(f"GPU {i}: {torch.cuda.get_device_name(i)}")
|
||||||
|
|
||||||
# === Apple Silicon (MPS) ===
|
# Apple Silicon (MPS)
|
||||||
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
|
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
|
||||||
logger.info("MPS backend is available (Apple Silicon)")
|
logger.info("MPS backend is available (Apple Silicon)")
|
||||||
logger.info(f"MPS version: {getattr(torch.backends.mps, '__version__', 'unknown')}")
|
logger.info(
|
||||||
|
f"MPS version: {getattr(torch.backends.mps, '__version__', 'unknown')}"
|
||||||
|
)
|
||||||
logger.info("GPU: Apple Silicon (Metal)")
|
logger.info("GPU: Apple Silicon (Metal)")
|
||||||
|
|
||||||
# === CPU only mode ===
|
# CPU only mode
|
||||||
else:
|
else:
|
||||||
logger.info("Only CPU is available")
|
logger.info("Only CPU is available")
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
from transcription.device_configuration import DeviceConfiguration
|
from transcription.device_configuration import DeviceConfiguration
|
||||||
|
|
||||||
|
|
||||||
# TODO: implement saving & removing configuration
|
# TODO: implement saving & removing configuration
|
||||||
def save_configuration(cfg: DeviceConfiguration):
|
def save_configuration(cfg: DeviceConfiguration):
|
||||||
config = {
|
config = {
|
||||||
"Model": cfg.model_name,
|
"Model": cfg.model_name,
|
||||||
"Batch Size": cfg.batch_size,
|
"Batch Size": cfg.batch_size,
|
||||||
"Data Type": cfg.data_type
|
"Data Type": cfg.data_type,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def delete_config():
|
||||||
|
pass
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import os
|
import os
|
||||||
import threading
|
|
||||||
import queue
|
import queue
|
||||||
|
import threading
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import scrolledtext, filedialog, messagebox
|
from tkinter import filedialog, messagebox, scrolledtext
|
||||||
|
|
||||||
import customtkinter as ctk
|
import customtkinter as ctk
|
||||||
import torch
|
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
|
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
|
||||||
|
|
||||||
WINDOW_WIDTH = 900
|
WINDOW_WIDTH = 900
|
||||||
WINDOW_HEIGHT = 650
|
WINDOW_HEIGHT = 650
|
||||||
@@ -175,7 +174,10 @@ class TranscriberApp(ctk.CTk):
|
|||||||
def _browse_input(self):
|
def _browse_input(self):
|
||||||
path = filedialog.askopenfilename(
|
path = filedialog.askopenfilename(
|
||||||
title="Select input audio file",
|
title="Select input audio file",
|
||||||
filetypes=[("Audio files", "*.wav *.mp3 *.m4a *.flac"), ("All files", "*.*")]
|
filetypes=[
|
||||||
|
("Audio files", "*.wav *.mp3 *.m4a *.flac"),
|
||||||
|
("All files", "*.*"),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
if path:
|
if path:
|
||||||
self.input_file_var.set(path)
|
self.input_file_var.set(path)
|
||||||
@@ -184,7 +186,7 @@ class TranscriberApp(ctk.CTk):
|
|||||||
path = filedialog.asksaveasfilename(
|
path = filedialog.asksaveasfilename(
|
||||||
title="Select output file",
|
title="Select output file",
|
||||||
defaultextension=".txt",
|
defaultextension=".txt",
|
||||||
filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
|
filetypes=[("Text files", "*.txt"), ("All files", "*.*")],
|
||||||
)
|
)
|
||||||
if path:
|
if path:
|
||||||
self.output_file_var.set(path)
|
self.output_file_var.set(path)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import tkinter as tk
|
|
||||||
import logging
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
|
||||||
class UILogHandler(logging.Handler):
|
class UILogHandler(logging.Handler):
|
||||||
def __init__(self, text_widget: tk.Text):
|
def __init__(self, text_widget: tk.Text):
|
||||||
@@ -11,6 +12,7 @@ class UILogHandler(logging.Handler):
|
|||||||
self.text_widget.insert(tk.END, log_entry + "\n")
|
self.text_widget.insert(tk.END, log_entry + "\n")
|
||||||
self.text_widget.see(tk.END)
|
self.text_widget.see(tk.END)
|
||||||
|
|
||||||
|
|
||||||
# TODO: maybe some tqdm here, not in console?
|
# TODO: maybe some tqdm here, not in console?
|
||||||
def setup_ui_logger(text_widget: tk.Text, level=logging.INFO):
|
def setup_ui_logger(text_widget: tk.Text, level=logging.INFO):
|
||||||
logger = logging.getLogger("UI_LOGGER")
|
logger = logging.getLogger("UI_LOGGER")
|
||||||
|
|||||||
Reference in New Issue
Block a user