Marek4321 commited on
Commit
97cca2b
·
verified ·
1 Parent(s): ae0710d

Update transcription.py

Browse files
Files changed (1) hide show
  1. transcription.py +216 -116
transcription.py CHANGED
@@ -1,9 +1,9 @@
1
- # transcription.py - Moduł transkrypcji audio używając OpenAI Whisper
2
 
3
  import os
4
  import time
5
  import streamlit as st
6
- from typing import List, Dict, Optional
7
  from pathlib import Path
8
 
9
  try:
@@ -32,11 +32,15 @@ class AudioTranscriber:
32
  'total_cost_estimate': 0
33
  }
34
 
35
- def transcribe_files(self, file_paths: List[str], language: str = "pl") -> str:
36
  """
37
- Transkrypcja listy plików audio
38
  Returns: Połączona transkrypcja wszystkich plików
39
  """
 
 
 
 
40
  transcriptions = []
41
 
42
  for i, file_path in enumerate(file_paths):
@@ -49,8 +53,8 @@ class AudioTranscriber:
49
  if len(file_paths) > 1:
50
  st.info(f"🎙️ Transkrybuję część {i+1}/{len(file_paths)}")
51
 
52
- # Transkrypcja pojedynczego pliku
53
- transcription = self._transcribe_single_file(file_path, language)
54
 
55
  if transcription:
56
  transcriptions.append(transcription)
@@ -68,12 +72,9 @@ class AudioTranscriber:
68
  if transcriptions:
69
  # Jeśli było więcej niż jeden plik, dodaj separatory
70
  if len(transcriptions) > 1:
71
- final_transcription = "\n\n=== CZĘŚĆ 1 ===\n\n".join([
72
- transcriptions[0]
73
- ] + [
74
- f"=== CZĘŚĆ {i+1} ===\n\n{text}"
75
- for i, text in enumerate(transcriptions[1:], 1)
76
- ])
77
  else:
78
  final_transcription = transcriptions[0]
79
 
@@ -81,6 +82,41 @@ class AudioTranscriber:
81
  else:
82
  raise Exception("Wszystkie transkrypcje zakończone błędem")
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  def _transcribe_single_file(self, file_path: str, language: str = "pl") -> Optional[str]:
85
  """Transkrypcja pojedynczego pliku"""
86
  try:
@@ -94,16 +130,32 @@ class AudioTranscriber:
94
  if file_size_mb > 25:
95
  raise Exception(f"Plik za duży dla Whisper API: {file_size_mb:.1f}MB > 25MB")
96
 
 
 
 
 
97
  st.info(f"📤 Wysyłam do Whisper ({file_size_mb:.1f}MB)...")
98
 
99
  # Otwórz plik i wyślij do API
100
  with open(file_path, 'rb') as audio_file:
101
- transcript = self.client.audio.transcriptions.create(
102
- model=MODEL_SETTINGS['whisper']['model'],
103
- file=audio_file,
104
- language=language if language != 'auto' else None,
105
- temperature=MODEL_SETTINGS['whisper']['temperature']
106
- )
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
  # Estymacja kosztu (Whisper API: $0.006 per minute)
109
  estimated_duration = file_size_mb * 60 # Rough estimate: 1MB ≈ 1 minute
@@ -111,72 +163,49 @@ class AudioTranscriber:
111
  self.transcription_stats['total_duration'] += estimated_duration
112
  self.transcription_stats['total_cost_estimate'] += estimated_cost
113
 
114
- st.success(f"✅ Transkrypcja otrzymana (~{estimated_duration:.1f}s audio)")
115
 
116
- return transcript.text
 
117
 
118
  except Exception as e:
119
  st.error(f"❌ Błąd Whisper API: {str(e)}")
 
 
 
 
 
 
 
120
 
121
- # Jeśli błąd rate limit, poczekaj i spróbuj ponownie
122
- if "rate limit" in str(e).lower():
123
- st.warning("⏳ Rate limit - czekam 60s i próbuję ponownie...")
124
- time.sleep(60)
125
- return self._transcribe_single_file(file_path, language)
126
 
127
- return None
128
-
129
- def transcribe_with_retries(self, file_path: str, language: str = "pl", max_retries: int = 3) -> Optional[str]:
130
- """Transkrypcja z ponawianiem przy błędach"""
131
- for attempt in range(max_retries):
132
- try:
133
- result = self._transcribe_single_file(file_path, language)
134
- if result:
135
- return result
136
-
137
- except Exception as e:
138
- st.warning(f"⚠️ Próba {attempt + 1}/{max_retries} nieudana: {str(e)}")
139
 
140
- if attempt < max_retries - 1:
141
- wait_time = (attempt + 1) * 30 # Exponential backoff
142
- st.info(f"⏳ Czekam {wait_time}s przed następną próbą...")
143
- time.sleep(wait_time)
144
- else:
145
- st.error(f"❌ Wszystkie {max_retries} prób nieudane")
146
-
147
- return None
148
-
149
- def estimate_transcription_time(self, file_paths: List[str]) -> Dict:
150
- """Estymuj czas i koszt transkrypcji"""
151
- total_size = sum(os.path.getsize(path) for path in file_paths if os.path.exists(path))
152
- total_size_mb = total_size / (1024 * 1024)
153
-
154
- # Estymacje
155
- estimated_duration_minutes = total_size_mb # 1MB ≈ 1 minute
156
- estimated_api_time = estimated_duration_minutes * 0.1 # Whisper jest ~10x szybszy niż realtime
157
- estimated_cost = estimated_duration_minutes * 0.006 # $0.006 per minute
158
-
159
- return {
160
- 'total_size_mb': total_size_mb,
161
- 'estimated_audio_duration': estimated_duration_minutes,
162
- 'estimated_processing_time': estimated_api_time,
163
- 'estimated_cost_usd': estimated_cost,
164
- 'files_count': len(file_paths)
165
- }
166
-
167
- def validate_api_key(self) -> bool:
168
- """Sprawdź czy klucz API działa"""
169
- try:
170
- # Spróbuj pobrać listę modeli
171
- models = self.client.models.list()
172
- return True
173
  except Exception as e:
174
- st.error(f" Nieprawidłowy klucz API: {str(e)}")
175
- return False
176
-
177
- def get_transcription_stats(self) -> Dict:
178
- """Zwróć statystyki transkrypcji"""
179
- return self.transcription_stats.copy()
180
 
181
  def detect_interview_type(self, transcription: str) -> str:
182
  """
@@ -189,13 +218,15 @@ class AudioTranscriber:
189
  fgi_indicators = [
190
  'moderator', 'grupa', 'wszyscy', 'kto jeszcze', 'a państwo',
191
  'czy zgadzacie się', 'co myślicie', 'focus group',
192
- 'uczestnicy', 'grupa fokusowa', 'dyskusja grupowa'
 
193
  ]
194
 
195
  # Wskaźniki IDI (Individual)
196
  idi_indicators = [
197
  'wywiad indywidualny', 'jeden na jeden', 'prywatnie',
198
- 'osobiście', 'indywidualne', 'w cztery oczy'
 
199
  ]
200
 
201
  fgi_score = sum(1 for indicator in fgi_indicators if indicator in text_lower)
@@ -203,67 +234,121 @@ class AudioTranscriber:
203
 
204
  # Sprawdź także liczbę różnych głosów/osób
205
  # (FGI zwykle ma więcej przerywników, overlapping speech)
206
- interruption_patterns = ['...', '[', ']', '(', ')', '--']
207
  interruption_count = sum(text_lower.count(pattern) for pattern in interruption_patterns)
208
 
209
- if fgi_score > idi_score and interruption_count > 10:
 
 
 
 
210
  return 'fgi'
211
- elif idi_score > fgi_score:
212
  return 'idi'
213
- elif interruption_count > 20: # Dużo przerywników = prawdopodobnie grupa
214
  return 'fgi'
 
 
215
  else:
216
  return 'unknown'
217
 
218
- def clean_transcription(self, transcription: str) -> str:
219
- """Oczyszczenie i formatowanie transkrypcji"""
220
  try:
221
- # Usuń nadmiarowe spacje
222
- lines = transcription.split('\n')
223
- cleaned_lines = []
224
-
225
- for line in lines:
226
- line = line.strip()
227
- if line: # Pomijaj puste linie
228
- # Usuń nadmiarowe spacje
229
- line = ' '.join(line.split())
230
- cleaned_lines.append(line)
231
-
232
- # Połącz z pojedynczymi przerwami linii
233
- cleaned = '\n\n'.join(cleaned_lines)
234
 
235
- # Dodaj informacje metadata na początek
236
- metadata = f"""TRANSKRYPCJA AUDIO
237
- Data: {time.strftime('%Y-%m-%d %H:%M')}
238
- Typ: {self.detect_interview_type(cleaned).upper()}
239
- Długość: ~{len(cleaned.split())} słów
240
-
241
- ---
242
-
243
- """
244
 
245
- return metadata + cleaned
246
 
247
  except Exception as e:
248
- st.warning(f"⚠️ Błąd czyszczenia transkrypcji: {e}")
249
- return transcription
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
- # Funkcje pomocnicze dla kompatybilności
252
- def validate_audio_file(file_path: str) -> bool:
253
  """Sprawdź czy plik audio jest prawidłowy"""
254
  if not os.path.exists(file_path):
255
- return False
256
 
257
  # Sprawdź rozmiar
258
  file_size = os.path.getsize(file_path)
 
 
259
  if file_size == 0:
260
- return False
 
 
 
261
 
262
  # Sprawdź rozszerzenie
263
  valid_extensions = ['.mp3', '.wav', '.mp4', '.m4a', '.aac']
264
  file_ext = Path(file_path).suffix.lower()
265
 
266
- return file_ext in valid_extensions
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
  # Test modułu
269
  if __name__ == "__main__":
@@ -275,12 +360,27 @@ if __name__ == "__main__":
275
  print("✅ AudioTranscriber zainicjalizowany")
276
 
277
  # Test rozpoznania typu wywiadu
278
- test_fgi = "Moderator: Co wszyscy myślicie o produkcie? Czy zgadzacie się z tym?"
279
- test_idi = "Interviewer: A teraz opowiedz mi o swoich doświadczeniach..."
 
 
 
 
 
 
 
 
 
 
280
 
281
  print(f"Test FGI: {transcriber.detect_interview_type(test_fgi)}")
282
  print(f"Test IDI: {transcriber.detect_interview_type(test_idi)}")
283
 
 
 
 
 
 
284
  except Exception as e:
285
  print(f"❌ Błąd testu: {e}")
286
 
 
1
+ # transcription.py - Poprawiony moduł transkrypcji
2
 
3
  import os
4
  import time
5
  import streamlit as st
6
+ from typing import List, Dict, Optional, Union
7
  from pathlib import Path
8
 
9
  try:
 
32
  'total_cost_estimate': 0
33
  }
34
 
35
+ def transcribe_files(self, file_paths: Union[str, List[str]], language: str = "pl") -> str:
36
  """
37
+ Transkrypcja listy plików audio lub pojedynczego pliku
38
  Returns: Połączona transkrypcja wszystkich plików
39
  """
40
+ # Obsługa pojedynczego pliku
41
+ if isinstance(file_paths, str):
42
+ file_paths = [file_paths]
43
+
44
  transcriptions = []
45
 
46
  for i, file_path in enumerate(file_paths):
 
53
  if len(file_paths) > 1:
54
  st.info(f"🎙️ Transkrybuję część {i+1}/{len(file_paths)}")
55
 
56
+ # Transkrypcja pojedynczego pliku z retry
57
+ transcription = self.transcribe_with_retries(file_path, language)
58
 
59
  if transcription:
60
  transcriptions.append(transcription)
 
72
  if transcriptions:
73
  # Jeśli było więcej niż jeden plik, dodaj separatory
74
  if len(transcriptions) > 1:
75
+ final_transcription = transcriptions[0]
76
+ for i, text in enumerate(transcriptions[1:], 1):
77
+ final_transcription += f"\n\n=== CZĘŚĆ {i+1} ===\n\n{text}"
 
 
 
78
  else:
79
  final_transcription = transcriptions[0]
80
 
 
82
  else:
83
  raise Exception("Wszystkie transkrypcje zakończone błędem")
84
 
85
+ def transcribe_with_retries(self, file_path: str, language: str = "pl", max_retries: int = 3) -> Optional[str]:
86
+ """Transkrypcja z ponawianiem przy błędach"""
87
+ for attempt in range(max_retries):
88
+ try:
89
+ # Sprawdź rozmiar pliku przed każdą próbą
90
+ file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
91
+ if file_size_mb > 25:
92
+ raise Exception(f"Plik za duży dla Whisper API: {file_size_mb:.1f}MB > 25MB")
93
+
94
+ result = self._transcribe_single_file(file_path, language)
95
+ if result:
96
+ return result
97
+
98
+ except Exception as e:
99
+ error_msg = str(e).lower()
100
+ st.warning(f"⚠️ Próba {attempt + 1}/{max_retries} nieudana: {str(e)}")
101
+
102
+ if attempt < max_retries - 1:
103
+ # Exponential backoff z różnymi strategiami
104
+ if "rate limit" in error_msg:
105
+ wait_time = 60 + (attempt * 30) # Rate limit = długa przerwa
106
+ st.info(f"⏳ Rate limit - czekam {wait_time}s...")
107
+ elif "timeout" in error_msg:
108
+ wait_time = 30 + (attempt * 15) # Timeout = średnia przerwa
109
+ st.info(f"⏳ Timeout - czekam {wait_time}s...")
110
+ else:
111
+ wait_time = 15 + (attempt * 10) # Inne błędy = krótka przerwa
112
+ st.info(f"⏳ Błąd - czekam {wait_time}s...")
113
+
114
+ time.sleep(wait_time)
115
+ else:
116
+ st.error(f"❌ Wszystkie {max_retries} prób nieudane dla {file_path}")
117
+
118
+ return None
119
+
120
  def _transcribe_single_file(self, file_path: str, language: str = "pl") -> Optional[str]:
121
  """Transkrypcja pojedynczego pliku"""
122
  try:
 
130
  if file_size_mb > 25:
131
  raise Exception(f"Plik za duży dla Whisper API: {file_size_mb:.1f}MB > 25MB")
132
 
133
+ # Sprawdź czy plik nie jest pusty
134
+ if file_size == 0:
135
+ raise Exception("Plik jest pusty")
136
+
137
  st.info(f"📤 Wysyłam do Whisper ({file_size_mb:.1f}MB)...")
138
 
139
  # Otwórz plik i wyślij do API
140
  with open(file_path, 'rb') as audio_file:
141
+ # Ustaw parametry transkrypcji
142
+ params = {
143
+ 'model': MODEL_SETTINGS['whisper']['model'],
144
+ 'file': audio_file,
145
+ 'temperature': MODEL_SETTINGS['whisper']['temperature'],
146
+ 'response_format': 'text' # Zwróć tylko tekst
147
+ }
148
+
149
+ # Dodaj język tylko jeśli nie jest auto
150
+ if language != 'auto':
151
+ params['language'] = language
152
+
153
+ # Wywołaj API
154
+ transcript = self.client.audio.transcriptions.create(**params)
155
+
156
+ # Sprawdź czy otrzymaliśmy wynik
157
+ if not transcript or len(transcript.strip()) == 0:
158
+ raise Exception("Pusty wynik transkrypcji")
159
 
160
  # Estymacja kosztu (Whisper API: $0.006 per minute)
161
  estimated_duration = file_size_mb * 60 # Rough estimate: 1MB ≈ 1 minute
 
163
  self.transcription_stats['total_duration'] += estimated_duration
164
  self.transcription_stats['total_cost_estimate'] += estimated_cost
165
 
166
+ st.success(f"✅ Transkrypcja otrzymana ({len(transcript.split())} słów)")
167
 
168
+ # Oczyść i zwróć transkrypcję
169
+ return self.clean_transcription(transcript)
170
 
171
  except Exception as e:
172
  st.error(f"❌ Błąd Whisper API: {str(e)}")
173
+ raise e
174
+
175
+ def clean_transcription(self, transcription: str) -> str:
176
+ """Oczyszczenie i formatowanie transkrypcji"""
177
+ try:
178
+ # Usuń nadmiarowe spacje i znaki
179
+ cleaned = transcription.strip()
180
 
181
+ # Usuń nadmiarowe spacje
182
+ cleaned = ' '.join(cleaned.split())
 
 
 
183
 
184
+ # Podziel na akapity w rozsądnych miejscach
185
+ sentences = cleaned.split('. ')
186
+ paragraphs = []
187
+ current_paragraph = []
188
+
189
+ for sentence in sentences:
190
+ current_paragraph.append(sentence)
 
 
 
 
 
191
 
192
+ # Nowy akapit co 3-4 zdania
193
+ if len(current_paragraph) >= 4:
194
+ paragraphs.append('. '.join(current_paragraph) + '.')
195
+ current_paragraph = []
196
+
197
+ # Dodaj ostatni akapit
198
+ if current_paragraph:
199
+ paragraphs.append('. '.join(current_paragraph))
200
+
201
+ # Połącz akapity
202
+ formatted = '\n\n'.join(paragraphs)
203
+
204
+ return formatted
205
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  except Exception as e:
207
+ st.warning(f"⚠️ Błąd formatowania transkrypcji: {e}")
208
+ return transcription
 
 
 
 
209
 
210
  def detect_interview_type(self, transcription: str) -> str:
211
  """
 
218
  fgi_indicators = [
219
  'moderator', 'grupa', 'wszyscy', 'kto jeszcze', 'a państwo',
220
  'czy zgadzacie się', 'co myślicie', 'focus group',
221
+ 'uczestnicy', 'grupa fokusowa', 'dyskusja grupowa',
222
+ 'co sądzicie', 'może ktoś inny', 'a jak pan/pani'
223
  ]
224
 
225
  # Wskaźniki IDI (Individual)
226
  idi_indicators = [
227
  'wywiad indywidualny', 'jeden na jeden', 'prywatnie',
228
+ 'osobiście', 'indywidualne', 'w cztery oczy',
229
+ 'tylko między nami', 'powiedz mi', 'jak się czujesz'
230
  ]
231
 
232
  fgi_score = sum(1 for indicator in fgi_indicators if indicator in text_lower)
 
234
 
235
  # Sprawdź także liczbę różnych głosów/osób
236
  # (FGI zwykle ma więcej przerywników, overlapping speech)
237
+ interruption_patterns = ['...', '[niewyraźnie]', '[nakładanie się głosów]', '(śmiech)', '--']
238
  interruption_count = sum(text_lower.count(pattern) for pattern in interruption_patterns)
239
 
240
+ # Sprawdź długość - FGI zwykle dłuższe
241
+ word_count = len(transcription.split())
242
+
243
+ # Logika decyzyjna
244
+ if fgi_score > idi_score * 1.5 and word_count > 1000:
245
  return 'fgi'
246
+ elif idi_score > fgi_score * 1.5:
247
  return 'idi'
248
+ elif interruption_count > 10 and word_count > 1500:
249
  return 'fgi'
250
+ elif word_count < 800:
251
+ return 'idi'
252
  else:
253
  return 'unknown'
254
 
255
+ def validate_api_key(self) -> bool:
256
+ """Sprawdź czy klucz API działa"""
257
  try:
258
+ # Spróbuj pobrać listę modeli
259
+ models = self.client.models.list()
 
 
 
 
 
 
 
 
 
 
 
260
 
261
+ # Sprawdź czy whisper-1 jest dostępny
262
+ model_names = [model.id for model in models.data]
263
+ if 'whisper-1' not in model_names:
264
+ st.warning("⚠️ Model whisper-1 nie jest dostępny")
265
+ return False
 
 
 
 
266
 
267
+ return True
268
 
269
  except Exception as e:
270
+ st.error(f" Nieprawidłowy klucz API: {str(e)}")
271
+ return False
272
+
273
+ def get_transcription_stats(self) -> Dict:
274
+ """Zwróć statystyki transkrypcji"""
275
+ stats = self.transcription_stats.copy()
276
+
277
+ # Dodaj dodatkowe metryki
278
+ if stats['total_files'] > 0:
279
+ stats['success_rate'] = (stats['successful'] / stats['total_files']) * 100
280
+ else:
281
+ stats['success_rate'] = 0
282
+
283
+ return stats
284
+
285
+ def estimate_transcription_time(self, file_paths: List[str]) -> Dict:
286
+ """Estymuj czas i koszt transkrypcji"""
287
+ valid_files = [path for path in file_paths if os.path.exists(path)]
288
+
289
+ if not valid_files:
290
+ return {
291
+ 'error': 'Brak prawidłowych plików',
292
+ 'files_count': 0
293
+ }
294
+
295
+ total_size = sum(os.path.getsize(path) for path in valid_files)
296
+ total_size_mb = total_size / (1024 * 1024)
297
+
298
+ # Estymacje
299
+ estimated_duration_minutes = total_size_mb # 1MB ≈ 1 minute
300
+ estimated_api_time = estimated_duration_minutes * 0.1 # Whisper jest ~10x szybszy
301
+ estimated_cost = estimated_duration_minutes * 0.006 # $0.006 per minute
302
+
303
+ # Sprawdź limity
304
+ files_too_large = []
305
+ for path in valid_files:
306
+ file_size_mb = os.path.getsize(path) / (1024 * 1024)
307
+ if file_size_mb > 25:
308
+ files_too_large.append((path, file_size_mb))
309
+
310
+ return {
311
+ 'total_size_mb': total_size_mb,
312
+ 'estimated_audio_duration': estimated_duration_minutes,
313
+ 'estimated_processing_time': estimated_api_time,
314
+ 'estimated_cost_usd': estimated_cost,
315
+ 'files_count': len(valid_files),
316
+ 'files_too_large': files_too_large
317
+ }
318
 
319
+ # Funkcje pomocnicze
320
+ def validate_audio_file(file_path: str) -> Tuple[bool, str]:
321
  """Sprawdź czy plik audio jest prawidłowy"""
322
  if not os.path.exists(file_path):
323
+ return False, "Plik nie istnieje"
324
 
325
  # Sprawdź rozmiar
326
  file_size = os.path.getsize(file_path)
327
+ file_size_mb = file_size / (1024 * 1024)
328
+
329
  if file_size == 0:
330
+ return False, "Plik jest pusty"
331
+
332
+ if file_size_mb > 25:
333
+ return False, f"Plik za duży: {file_size_mb:.1f}MB > 25MB"
334
 
335
  # Sprawdź rozszerzenie
336
  valid_extensions = ['.mp3', '.wav', '.mp4', '.m4a', '.aac']
337
  file_ext = Path(file_path).suffix.lower()
338
 
339
+ if file_ext not in valid_extensions:
340
+ return False, f"Nieobsługiwane rozszerzenie: {file_ext}"
341
+
342
+ return True, "OK"
343
+
344
+ def get_file_duration_estimate(file_path: str) -> float:
345
+ """Estymuj długość pliku audio w minutach"""
346
+ try:
347
+ file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
348
+ # Przybliżenie: 1MB ≈ 1 minuta dla typowego MP3
349
+ return file_size_mb
350
+ except:
351
+ return 0.0
352
 
353
  # Test modułu
354
  if __name__ == "__main__":
 
360
  print("✅ AudioTranscriber zainicjalizowany")
361
 
362
  # Test rozpoznania typu wywiadu
363
+ test_fgi = """
364
+ Moderator: Dzień dobry wszystkim. Co wszyscy myślicie o tym produkcie?
365
+ Uczestnik 1: Ja uważam, że...
366
+ Uczestnik 2: Ale czy zgadzacie się, że...
367
+ Moderator: A co sądzicie o tym?
368
+ """
369
+
370
+ test_idi = """
371
+ Interviewer: Opowiedz mi o swoich doświadczeniach z tym produktem.
372
+ Respondent: Moje doświadczenia są bardzo pozytywne...
373
+ Interviewer: A jak się czujesz gdy używasz tego produktu?
374
+ """
375
 
376
  print(f"Test FGI: {transcriber.detect_interview_type(test_fgi)}")
377
  print(f"Test IDI: {transcriber.detect_interview_type(test_idi)}")
378
 
379
+ # Test walidacji pliku
380
+ test_file = "test.mp3"
381
+ result, message = validate_audio_file(test_file)
382
+ print(f"Test walidacji: {result} - {message}")
383
+
384
  except Exception as e:
385
  print(f"❌ Błąd testu: {e}")
386