areksmyk commited on
Commit
9c5969f
verified
1 Parent(s): 98f8d90

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +274 -0
  2. requirements.txt +5 -0
app.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import nemo.collections.asr as nemo_asr
4
+ from pydub import AudioSegment
5
+ import os
6
+ import logging
7
+ from typing import Optional
8
+ import threading
9
+
10
+ # Konfiguracja logowania
11
+ logging.basicConfig(level=logging.INFO)
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class TimeoutException(Exception):
15
+ """Wyj膮tek dla timeoutu transkrypcji."""
16
+ pass
17
+
18
+ class TranscriptionService:
19
+ """Klasa do zarz膮dzania modelami ASR na r贸偶nych urz膮dzeniach."""
20
+
21
+ def __init__(self):
22
+ self.models = {
23
+ 'mps': None,
24
+ 'cuda': None,
25
+ 'cpu': None
26
+ }
27
+ self.model_name = "nvidia/parakeet-tdt-0.6b-v3"
28
+ self.timeout_seconds = 300 # 5 minut timeout
29
+ self.chunk_length_minutes = 5 # Dziel pliki d艂u偶sze ni偶 5 minut
30
+
31
+ def _get_optimal_device(self, audio_length_minutes: float) -> str:
32
+ """
33
+ Wybiera optymalne urz膮dzenie na podstawie d艂ugo艣ci audio i dost臋pno艣ci sprz臋tu.
34
+
35
+ Args:
36
+ audio_length_minutes: D艂ugo艣膰 audio w minutach
37
+
38
+ Returns:
39
+ str: Nazwa urz膮dzenia ('mps', 'cuda' lub 'cpu')
40
+ """
41
+ # Sprawd藕 CUDA jako pierwszy wyb贸r dla wszystkich d艂ugo艣ci
42
+ if torch.cuda.is_available():
43
+ logger.info("U偶ywam CUDA (GPU) - najlepsza wydajno艣膰")
44
+ return "cuda"
45
+
46
+
47
+ # MPS tylko dla kr贸tszych plik贸w
48
+ if torch.backends.mps.is_available() and audio_length_minutes <= 8:
49
+ logger.info(f"Plik kr贸tki ({audio_length_minutes:.2f} min) - u偶ywam MPS")
50
+ return "mps"
51
+
52
+ # CPU jako fallback
53
+ if torch.backends.mps.is_available() and audio_length_minutes > 8:
54
+ logger.info(f"Plik d艂ugi ({audio_length_minutes:.2f} min) - u偶ywam CPU zamiast MPS")
55
+ else:
56
+ logger.info("Brak GPU/MPS - u偶ywam CPU")
57
+
58
+ return "cpu"
59
+
60
+ def _load_model(self, device: str) -> nemo_asr.models.ASRModel:
61
+ """
62
+ 艁aduje model na okre艣lonym urz膮dzeniu (z cache'owaniem).
63
+
64
+ Args:
65
+ device: Urz膮dzenie docelowe
66
+
67
+ Returns:
68
+ Za艂adowany model ASR
69
+ """
70
+ if self.models[device] is None:
71
+ logger.info(f"艁adowanie modelu na {device.upper()}...")
72
+ try:
73
+ model = nemo_asr.models.ASRModel.from_pretrained(
74
+ model_name=self.model_name
75
+ )
76
+ self.models[device] = model.to(device)
77
+ logger.info("Model za艂adowany pomy艣lnie")
78
+ except Exception as e:
79
+ logger.error(f"B艂膮d 艂adowania modelu na {device}: {e}")
80
+ raise
81
+
82
+ return self.models[device]
83
+
84
+ def _split_audio(self, audio_file_path: str, chunk_length_ms: int) -> list:
85
+ """
86
+ Dzieli d艂ugi plik audio na mniejsze fragmenty.
87
+
88
+ Args:
89
+ audio_file_path: 艢cie偶ka do pliku audio
90
+ chunk_length_ms: D艂ugo艣膰 fragmentu w milisekundach
91
+
92
+ Returns:
93
+ list: Lista 艣cie偶ek do plik贸w tymczasowych
94
+ """
95
+ audio = AudioSegment.from_file(audio_file_path)
96
+ chunks = []
97
+
98
+ for i, chunk in enumerate(audio[::chunk_length_ms]):
99
+ chunk_path = f"/tmp/temp_chunk_{i}.wav"
100
+ chunk.export(chunk_path, format="wav")
101
+ chunks.append(chunk_path)
102
+
103
+ return chunks
104
+
105
+ def _transcribe_with_timeout(self, audio_file_path: str, device: str) -> str:
106
+ """
107
+ Wykonuje transkrypcj臋 z timeoutem.
108
+
109
+ Args:
110
+ audio_file_path: 艢cie偶ka do pliku audio
111
+ device: Urz膮dzenie do transkrypcji
112
+
113
+ Returns:
114
+ str: Transkrypcja
115
+ """
116
+ result = {"text": None, "error": None}
117
+
118
+ def transcribe_worker():
119
+ try:
120
+ model = self._load_model(device)
121
+ transcriptions = model.transcribe([audio_file_path])
122
+ if transcriptions and len(transcriptions) > 0:
123
+ result["text"] = transcriptions[0].text
124
+ else:
125
+ result["error"] = "Model nie zwr贸ci艂 偶adnej transkrypcji."
126
+ except Exception as e:
127
+ result["error"] = f"B艂膮d transkrypcji: {str(e)}"
128
+
129
+ thread = threading.Thread(target=transcribe_worker)
130
+ thread.start()
131
+ thread.join(timeout=self.timeout_seconds)
132
+
133
+ if thread.is_alive():
134
+ raise TimeoutException(f"Transkrypcja przekroczy艂a limit {self.timeout_seconds} sekund")
135
+
136
+ if result["error"]:
137
+ raise Exception(result["error"])
138
+
139
+ return result["text"]
140
+
141
+ def transcribe(self, audio_file_path: str, progress=None) -> str:
142
+ """
143
+ G艂贸wna funkcja transkrypcji.
144
+
145
+ Args:
146
+ audio_file_path: 艢cie偶ka do pliku audio
147
+ progress: Obiekt progress Gradio (opcjonalnie)
148
+
149
+ Returns:
150
+ str: Transkrypcja lub komunikat b艂臋du
151
+ """
152
+ # Walidacja pliku
153
+ if not audio_file_path or not os.path.exists(audio_file_path):
154
+ return "B艂膮d: Nie wybrano pliku audio lub plik nie istnieje."
155
+
156
+ temp_files = []
157
+
158
+ try:
159
+ # Analiza d艂ugo艣ci pliku
160
+ logger.info(f"Analizuj臋 plik: {os.path.basename(audio_file_path)}")
161
+ audio = AudioSegment.from_file(audio_file_path)
162
+ length_minutes = len(audio) / (1000 * 60)
163
+ logger.info(f"D艂ugo艣膰 pliku: {length_minutes:.2f} minut")
164
+
165
+ # Wyb贸r optymalnego urz膮dzenia
166
+ device = self._get_optimal_device(length_minutes)
167
+
168
+ # Dziel d艂ugie pliki na fragmenty
169
+ if length_minutes > self.chunk_length_minutes:
170
+ if progress:
171
+ progress(0.1, desc="Dziel臋 plik na fragmenty...")
172
+
173
+ logger.info(f"Dziel臋 plik na fragmenty po {self.chunk_length_minutes} minut")
174
+ chunk_length_ms = self.chunk_length_minutes * 60 * 1000
175
+ chunks = self._split_audio(audio_file_path, chunk_length_ms)
176
+ temp_files.extend(chunks)
177
+
178
+ logger.info(f"Transkrypcja {len(chunks)} fragment贸w...")
179
+ all_transcriptions = []
180
+
181
+ for i, chunk_path in enumerate(chunks):
182
+ if progress:
183
+ progress_value = 0.1 + (0.8 * (i + 1) / len(chunks))
184
+ progress(progress_value, desc=f"Transkrypcja fragmentu {i+1}/{len(chunks)}...")
185
+
186
+ logger.info(f"Transkrypcja fragmentu {i+1}/{len(chunks)}...")
187
+ chunk_text = self._transcribe_with_timeout(chunk_path, device)
188
+ all_transcriptions.append(chunk_text)
189
+ logger.info(f"Fragment {i+1} przetworzony")
190
+
191
+ result_text = " ".join(all_transcriptions)
192
+ else:
193
+ # Kr贸tkie pliki - transkrypcja ca艂o艣ci
194
+ if progress:
195
+ progress(0.5, desc="Rozpoczynam transkrypcj臋...")
196
+
197
+ logger.info("Rozpoczynam transkrypcj臋...")
198
+ result_text = self._transcribe_with_timeout(audio_file_path, device)
199
+
200
+ logger.info("Transkrypcja zako艅czona pomy艣lnie")
201
+ return result_text
202
+
203
+ except FileNotFoundError:
204
+ error_msg = f"B艂膮d: Plik {audio_file_path} nie zosta艂 znaleziony."
205
+ logger.error(error_msg)
206
+ return error_msg
207
+ except TimeoutException as e:
208
+ error_msg = f"Timeout: {str(e)}"
209
+ logger.error(error_msg)
210
+ return error_msg
211
+ except Exception as e:
212
+ error_msg = f"Wyst膮pi艂 b艂膮d podczas transkrypcji: {str(e)}"
213
+ logger.error(error_msg)
214
+ return error_msg
215
+ finally:
216
+ # Sprz膮tanie plik贸w tymczasowych
217
+ for temp_file in temp_files:
218
+ try:
219
+ os.remove(temp_file)
220
+ except:
221
+ pass
222
+
223
+ # Globalna instancja serwisu
224
+ transcription_service = TranscriptionService()
225
+
226
+ def transcribe_audio_wrapper(audio_file_path: str, progress=gr.Progress()) -> str:
227
+ """Wrapper dla Gradio - izoluje logik臋 od interfejsu."""
228
+ return transcription_service.transcribe(audio_file_path, progress)
229
+
230
+ def create_interface() -> gr.Interface:
231
+ """Tworzy i konfiguruje interfejs Gradio."""
232
+ return gr.Interface(
233
+ fn=transcribe_audio_wrapper,
234
+ inputs=gr.Audio(
235
+ type="filepath",
236
+ label="Wybierz plik audio",
237
+ format="wav" # Opcjonalnie: wymu艣 konkretny format
238
+ ),
239
+ outputs=gr.Textbox(
240
+ lines=10,
241
+ label="Wynik transkrypcji",
242
+ placeholder="Tutaj pojawi si臋 transkrypcja..."
243
+ ),
244
+ title="馃帳 Transkrypcja mowy na tekst",
245
+ description="""
246
+ Wybierz plik audio, a model NVIDIA Parakeet wykona transkrypcj臋.
247
+
248
+ **Obs艂ugiwane formaty:** WAV, MP3, FLAC, M4A i inne
249
+ **Optymalizacja urz膮dzenia:** Automatyczny wyb贸r GPU/CPU
250
+ """,
251
+ examples=None, # Mo偶esz doda膰 przyk艂adowe pliki
252
+ cache_examples=False,
253
+ flagging_options=None,
254
+ allow_flagging="never"
255
+ )
256
+
257
+ if __name__ == "__main__":
258
+ # Informacje o dost臋pnych urz膮dzeniach
259
+ logger.info("=== Informacje o systemie ===")
260
+ logger.info(f"CUDA dost臋pne: {torch.cuda.is_available()}")
261
+ logger.info(f"MPS dost臋pne: {torch.backends.mps.is_available()}")
262
+
263
+ if torch.cuda.is_available():
264
+ logger.info(f"GPU: {torch.cuda.get_device_name(0)}")
265
+
266
+ # Uruchomienie interfejsu
267
+ interface = create_interface()
268
+ interface.launch(
269
+ server_name="127.0.0.1", # Bezpieczniejsze ni偶 domy艣lne
270
+ server_port=7860,
271
+ share=False, # Nie udost臋pniaj publicznie
272
+ debug=False, # Wy艂膮cz w produkcji
273
+ show_error=True
274
+ )
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ torch
3
+ nemo-toolkit[asr]
4
+ pydub
5
+ numpy