Skip to content

Commit e3e533e

Browse files
committed
fix: Suppress all WhisperX/pyannote and Gemini console output
- Add suppress_whisperx_output() context manager to silence WhisperX output - Add suppress_gemini_output() context manager to silence Gemini API output - Suppress warnings, logging, and stdout/stderr from both libraries - Output is still shown when --debug flag is enabled - Fixes #71
1 parent dd51bcd commit e3e533e

2 files changed

Lines changed: 194 additions & 73 deletions

File tree

src/sub_tools/intelligence/gemini.py

Lines changed: 105 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import asyncio
2+
import logging
3+
import os
4+
import sys
5+
import warnings
6+
from contextlib import contextmanager
27
from typing import Callable, Optional
38

49
from google import genai
@@ -14,7 +19,7 @@
1419

1520

1621
def proofread() -> None:
17-
"""Proofread the source SRT file with Gemini."""
22+
"""Proofread the source SRT file using Gemini."""
1823

1924
if should_skip(f"{config.source_language}.srt"):
2025
return
@@ -23,7 +28,7 @@ def proofread() -> None:
2328

2429

2530
async def _proofread() -> None:
26-
info("Proofreading with Gemini...")
31+
info("Proofreading using Gemini...")
2732

2833
language_code = config.source_language
2934
language = get_language_name(language_code)
@@ -65,7 +70,7 @@ async def _proofread() -> None:
6570

6671

6772
def translate() -> None:
68-
"""Translate the source SRT file with Gemini."""
73+
"""Translate the source SRT file using Gemini."""
6974

7075
asyncio.run(_translate())
7176

@@ -87,7 +92,7 @@ async def _translate() -> None:
8792
if not target_language_codes:
8893
return
8994

90-
info("Translating with Gemini...")
95+
info("Translating using Gemini...")
9196

9297
tasks = []
9398

@@ -116,6 +121,57 @@ async def _translate() -> None:
116121
await asyncio.gather(*tasks)
117122

118123

124+
@contextmanager
125+
def _suppress_gemini_output():
126+
"""
127+
Context manager to suppress all Gemini API output unless in debug mode.
128+
129+
Suppresses:
130+
- All warnings from google.genai and related libraries
131+
- All logging output from these libraries
132+
- All stdout/stderr output
133+
"""
134+
if config.debug:
135+
yield
136+
return
137+
138+
# Suppress warnings
139+
with warnings.catch_warnings():
140+
warnings.filterwarnings("ignore")
141+
142+
# Suppress logging
143+
logging_modules = [
144+
"google",
145+
"google.genai",
146+
"google.api_core",
147+
"grpc",
148+
]
149+
original_levels = {}
150+
for module_name in logging_modules:
151+
logger = logging.getLogger(module_name)
152+
original_levels[module_name] = logger.level
153+
logger.setLevel(logging.ERROR)
154+
155+
# Suppress stdout/stderr
156+
original_stdout = sys.stdout
157+
original_stderr = sys.stderr
158+
159+
with open(os.devnull, "w") as devnull:
160+
sys.stdout = devnull
161+
sys.stderr = devnull
162+
163+
try:
164+
yield
165+
finally:
166+
# Restore stdout/stderr
167+
sys.stdout = original_stdout
168+
sys.stderr = original_stderr
169+
170+
# Restore logging levels
171+
for module_name, level in original_levels.items():
172+
logging.getLogger(module_name).setLevel(level)
173+
174+
119175
async def _translate_language(
120176
file: types.File,
121177
srt_content: str,
@@ -173,50 +229,53 @@ async def _call_gemini_api(
173229
"""
174230
Helper method to call Gemini API with retries for rate limits.
175231
"""
176-
client = genai.Client(api_key=config.gemini_api_key)
177-
178-
# Build parts for the content
179-
parts = []
180-
if file:
181-
parts.append(file)
182-
if text:
183-
parts.append(types.Part.from_text(text=text))
184-
185-
tools = [
186-
types.Tool(google_search=types.GoogleSearch()),
187-
]
188-
189-
for attempt in range(config.retry):
190-
try:
191-
response = await client.aio.models.generate_content(
192-
model=config.gemini_model,
193-
contents=parts,
194-
config=types.GenerateContentConfig(
195-
system_instruction=system_instruction,
196-
thinking_config=types.ThinkingConfig(
197-
include_thoughts=True, thinking_level=types.ThinkingLevel.HIGH
232+
with _suppress_gemini_output():
233+
client = genai.Client(api_key=config.gemini_api_key)
234+
235+
# Build parts for the content
236+
parts = []
237+
if file:
238+
parts.append(file)
239+
if text:
240+
parts.append(types.Part.from_text(text=text))
241+
242+
tools = [
243+
types.Tool(google_search=types.GoogleSearch()),
244+
]
245+
246+
for attempt in range(config.retry):
247+
try:
248+
response = await client.aio.models.generate_content(
249+
model=config.gemini_model,
250+
contents=parts,
251+
config=types.GenerateContentConfig(
252+
system_instruction=system_instruction,
253+
thinking_config=types.ThinkingConfig(
254+
include_thoughts=True,
255+
thinking_level=types.ThinkingLevel.HIGH,
256+
),
257+
tools=tools,
198258
),
199-
tools=tools,
200-
),
201-
)
202-
text = response.text
203-
if text:
204-
with open(output_file, "w") as f:
205-
f.write(text)
206-
return
207-
208-
except google_exceptions.ResourceExhausted as e:
209-
if attempt < config.retry - 1:
210-
wait_time = 2**attempt # Exponential backoff: 1, 2, 4 seconds
211-
await asyncio.sleep(wait_time)
212-
continue
213-
else:
259+
)
260+
text = response.text
261+
if text:
262+
with open(output_file, "w") as f:
263+
f.write(text)
264+
return
265+
266+
except google_exceptions.ResourceExhausted as e:
267+
if attempt < config.retry - 1:
268+
wait_time = 2**attempt # Exponential backoff: 1, 2, 4 seconds
269+
await asyncio.sleep(wait_time)
270+
continue
271+
else:
272+
raise e
273+
except Exception as e:
214274
raise e
215-
except Exception as e:
216-
raise e
217275

218276

219277
def _upload_file(file_path: str) -> types.File:
220-
client = genai.Client(api_key=config.gemini_api_key)
221-
file = client.files.upload(file=file_path)
222-
return file
278+
with _suppress_gemini_output():
279+
client = genai.Client(api_key=config.gemini_api_key)
280+
file = client.files.upload(file=file_path)
281+
return file
Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
import logging
2+
import os
3+
import sys
4+
import warnings
5+
from contextlib import contextmanager
6+
17
import whisperx
28

9+
from sub_tools.system.console import info
310
from sub_tools.system.file import should_skip
411

512
from ..config import config
@@ -10,35 +17,90 @@ def transcribe() -> None:
1017
if should_skip(config.srt_file):
1118
return
1219

20+
info("Transcribing audio using WhisperX...")
21+
1322
try:
14-
# Load model
15-
model = whisperx.load_model(
16-
config.whisperx_model,
17-
device=config.whisperx_device,
18-
compute_type=config.whisperx_compute_type,
19-
language=config.source_language,
20-
)
21-
22-
# Transcribe audio
23-
audio = whisperx.load_audio(config.audio_file)
24-
result = model.transcribe(audio, batch_size=config.whisperx_batch_size)
25-
26-
# Align whisper output
27-
model_a, metadata = whisperx.load_align_model(
28-
language_code=config.source_language, device=config.whisperx_device
29-
)
30-
result = whisperx.align(
31-
result["segments"],
32-
model_a,
33-
metadata,
34-
audio,
35-
config.whisperx_device,
36-
return_char_alignments=False,
37-
)
38-
39-
# Save as SRT
40-
serialize_subtitles(result["segments"], config.srt_file)
23+
with _suppress_whisperx_output():
24+
# Load model
25+
model = whisperx.load_model(
26+
config.whisperx_model,
27+
device=config.whisperx_device,
28+
compute_type=config.whisperx_compute_type,
29+
language=config.source_language,
30+
)
31+
32+
# Transcribe audio
33+
audio = whisperx.load_audio(config.audio_file)
34+
result = model.transcribe(audio, batch_size=config.whisperx_batch_size)
35+
36+
# Align whisper output
37+
model_a, metadata = whisperx.load_align_model(
38+
language_code=config.source_language, device=config.whisperx_device
39+
)
40+
result = whisperx.align(
41+
result["segments"],
42+
model_a,
43+
metadata,
44+
audio,
45+
config.whisperx_device,
46+
return_char_alignments=False,
47+
)
48+
49+
# Save as SRT
50+
serialize_subtitles(result["segments"], config.srt_file)
4151

4252
except Exception as e:
4353
print(f"WhisperX transcription failed: {str(e)}")
4454
raise
55+
56+
57+
@contextmanager
58+
def _suppress_whisperx_output():
59+
"""
60+
Context manager to suppress all WhisperX/pyannote output unless in debug mode.
61+
62+
Suppresses:
63+
- All warnings from whisperx, pyannote, pytorch_lightning, and torch
64+
- All logging output from these libraries
65+
- All stdout/stderr output
66+
"""
67+
if config.debug:
68+
yield
69+
return
70+
71+
# Suppress warnings
72+
with warnings.catch_warnings():
73+
warnings.filterwarnings("ignore")
74+
75+
# Suppress logging
76+
logging_modules = [
77+
"whisperx",
78+
"pyannote",
79+
"pytorch_lightning",
80+
"lightning",
81+
"torch",
82+
]
83+
original_levels = {}
84+
for module_name in logging_modules:
85+
logger = logging.getLogger(module_name)
86+
original_levels[module_name] = logger.level
87+
logger.setLevel(logging.ERROR)
88+
89+
# Suppress stdout/stderr
90+
original_stdout = sys.stdout
91+
original_stderr = sys.stderr
92+
93+
with open(os.devnull, "w") as devnull:
94+
sys.stdout = devnull
95+
sys.stderr = devnull
96+
97+
try:
98+
yield
99+
finally:
100+
# Restore stdout/stderr
101+
sys.stdout = original_stdout
102+
sys.stderr = original_stderr
103+
104+
# Restore logging levels
105+
for module_name, level in original_levels.items():
106+
logging.getLogger(module_name).setLevel(level)

0 commit comments

Comments
 (0)