← Whispering

feat: Add target language persistence and debug output control

09eb590f9e12542c02dbafd580669422ad9d9670 · Claude · Dec 1, 2025 19:07

- Add target language persistence: save and load source/target language
  settings across application restarts (gui.py lines 726-749, 1242-1249)

- Add debug output control via settings file:
  * Created debug.py module with debug_print() function that checks
    debug_enabled setting from whispering_settings.json
  * Updated gui.py to load debug_enabled flag and use it for debug logs
  * Updated text_widget.py to use debug_print for queue logging
  * Updated processing.py to use debug_print for all [DEBUG] and [INFO] logs

- Debug mode is controlled via "debug_enabled": true/false in settings file
  (no GUI implementation, settings only as requested)

- All debug output is now centrally controlled and disabled by default

+122 −55 · 4 files changed

  • src/core_parts/processing.py +42 −41
  • src/debug.py +30 −0
  • src/gui.py +47 −12
  • src/gui_parts/text_widget.py +3 −2
diff --git a/src/core_parts/processing.py b/src/core_parts/processing.py
index 45b8f23..3a08582 100644
--- a/src/core_parts/processing.py
+++ b/src/core_parts/processing.py
@@ -7,6 +7,7 @@ import requests
 import sounddevice as sd
 from cmque import DataDeque, PairDeque, Queue
 from faster_whisper import WhisperModel
+from debug import debug_print
 
 # Import AI modules
 try:
@@ -179,7 +180,7 @@ def proc(index, model, vad, memory, patience, timeout, prompt, source, target, t
 
             # Check for auto-stop (only if enabled AND no activity for specified duration)
             if auto_stop_enabled and (time.time() - last_activity_time >= AUTO_STOP_DURATION):
-                print(f"[INFO] Auto-stopping after {auto_stop_minutes} minutes of inactivity", flush=True)
+                debug_print(f"[INFO] Auto-stopping after {auto_stop_minutes} minutes of inactivity", flush=True)
                 ready[0] = False  # Signal stop
                 break
 
@@ -199,7 +200,7 @@ def proc(index, model, vad, memory, patience, timeout, prompt, source, target, t
                 # Manual trigger - process immediately when requested
                 manual_trigger_requested = manual_trigger and manual_trigger[0]
                 if manual_trigger_requested and accumulated_done:
-                    print("[DEBUG] Manual AI trigger detected, processing immediately", flush=True)
+                    debug_print("[DEBUG] Manual AI trigger detected, processing immediately", flush=True)
                     manual_trigger[0] = False  # Reset flag
 
                 # Determine automatic triggers based on mode
@@ -253,29 +254,29 @@ def proc(index, model, vad, memory, patience, timeout, prompt, source, target, t
 
                     if to_process:
                         # Process with AI - DETAILED DEBUG
-                        print(f"\n[DEBUG] ========== CONDITION CHECK ==========", flush=True)
-                        print(f"[DEBUG] 1. prres_queue = {prres_queue}", flush=True)
-                        print(f"[DEBUG]    prres_queue is not None = {prres_queue is not None}", flush=True)
-                        print(f"[DEBUG]    prres_queue bool = {bool(prres_queue)} (NOTE: Queue.__bool__ returns False when empty!)", flush=True)
-                        print(f"[DEBUG] 2. ai_processor = {ai_processor}", flush=True)
-                        print(f"[DEBUG]    ai_processor bool = {bool(ai_processor)}", flush=True)
+                        debug_print(f"\n[DEBUG] ========== CONDITION CHECK ==========", flush=True)
+                        debug_print(f"[DEBUG] 1. prres_queue = {prres_queue}", flush=True)
+                        debug_print(f"[DEBUG]    prres_queue is not None = {prres_queue is not None}", flush=True)
+                        debug_print(f"[DEBUG]    prres_queue bool = {bool(prres_queue)} (NOTE: Queue.__bool__ returns False when empty!)", flush=True)
+                        debug_print(f"[DEBUG] 2. ai_processor = {ai_processor}", flush=True)
+                        debug_print(f"[DEBUG]    ai_processor bool = {bool(ai_processor)}", flush=True)
                         if ai_processor:
-                            print(f"[DEBUG] 3. ai_processor.mode = '{ai_processor.mode}'", flush=True)
-                            print(f"[DEBUG]    mode == 'proofread_translate' = {ai_processor.mode == 'proofread_translate'}", flush=True)
-                        print(f"[DEBUG] 4. AI_AVAILABLE = {AI_AVAILABLE}", flush=True)
+                            debug_print(f"[DEBUG] 3. ai_processor.mode = '{ai_processor.mode}'", flush=True)
+                            debug_print(f"[DEBUG]    mode == 'proofread_translate' = {ai_processor.mode == 'proofread_translate'}", flush=True)
+                        debug_print(f"[DEBUG] 4. AI_AVAILABLE = {AI_AVAILABLE}", flush=True)
 
                         # Check combined condition (FIX: use 'is not None' instead of bool check!)
                         combined = bool(prres_queue is not None and ai_processor and ai_processor.mode == "proofread_translate" and AI_AVAILABLE)
-                        print(f"[DEBUG] 5. COMBINED CONDITION = {combined}", flush=True)
-                        print(f"[DEBUG] ======================================\n", flush=True)
+                        debug_print(f"[DEBUG] 5. COMBINED CONDITION = {combined}", flush=True)
+                        debug_print(f"[DEBUG] ======================================\n", flush=True)
 
                         if prres_queue is not None and ai_processor and ai_processor.mode == "proofread_translate" and AI_AVAILABLE:
                             # Make TWO separate calls for proofread+translate mode
-                            print(f"[DEBUG] ========== EXECUTING TWO-CALL AI PROCESSING ==========", flush=True)
-                            print(f"[DEBUG] Input text: {to_process[:100]}...", flush=True)
+                            debug_print(f"[DEBUG] ========== EXECUTING TWO-CALL AI PROCESSING ==========", flush=True)
+                            debug_print(f"[DEBUG] Input text: {to_process[:100]}...", flush=True)
 
                             # FIRST CALL: Proofread only
-                            print(f"[DEBUG] STEP 1: Creating proofread-only processor", flush=True)
+                            debug_print(f"[DEBUG] STEP 1: Creating proofread-only processor", flush=True)
                             config = AIConfig()
                             proofread_processor = AITextProcessor(
                                 config=config,
@@ -284,14 +285,14 @@ def proc(index, model, vad, memory, patience, timeout, prompt, source, target, t
                                 source_lang=ai_processor.source_lang,
                                 target_lang=None
                             )
-                            print(f"[DEBUG] STEP 2: Calling AI for proofread", flush=True)
+                            debug_print(f"[DEBUG] STEP 2: Calling AI for proofread", flush=True)
                             proofread_text, pr_error = ai_translate(to_process, proofread_processor)
-                            print(f"[DEBUG] STEP 3: Proofread result: '{proofread_text[:150]}'...", flush=True)
+                            debug_print(f"[DEBUG] STEP 3: Proofread result: '{proofread_text[:150]}'...", flush=True)
                             if pr_error:
-                                print(f"[DEBUG] Proofread Error: {pr_error}", flush=True)
+                                debug_print(f"[DEBUG] Proofread Error: {pr_error}", flush=True)
 
                             # SECOND CALL: Translate the proofread text
-                            print(f"[DEBUG] STEP 4: Creating translate-only processor", flush=True)
+                            debug_print(f"[DEBUG] STEP 4: Creating translate-only processor", flush=True)
                             translate_processor = AITextProcessor(
                                 config=config,
                                 model_id=ai_processor.provider.model_id,
@@ -299,11 +300,11 @@ def proc(index, model, vad, memory, patience, timeout, prompt, source, target, t
                                 source_lang=ai_processor.source_lang,
                                 target_lang=ai_processor.target_lang
                             )
-                            print(f"[DEBUG] STEP 5: Calling AI for translation (input=proofread text)", flush=True)
+                            debug_print(f"[DEBUG] STEP 5: Calling AI for translation (input=proofread text)", flush=True)
                             translate_text, tr_error = ai_translate(proofread_text, translate_processor)
-                            print(f"[DEBUG] STEP 6: Translation result: '{translate_text[:150]}'...", flush=True)
+                            debug_print(f"[DEBUG] STEP 6: Translation result: '{translate_text[:150]}'...", flush=True)
                             if tr_error:
-                                print(f"[DEBUG] Translation Error: {tr_error}", flush=True)
+                                debug_print(f"[DEBUG] Translation Error: {tr_error}", flush=True)
 
                             # Determine separators - use paragraph breaks for proofread for better readability
                             # Use smart separator for translation (paragraph break or space)
@@ -312,19 +313,19 @@ def proc(index, model, vad, memory, patience, timeout, prompt, source, target, t
                             last_process_time = time.time()  # Reset timer after processing
 
                             # Send proofread to pr queue, translation to tl queue
-                            print(f"[DEBUG] STEP 7: Sending to queues...", flush=True)
-                            print(f"[DEBUG]   - Sending proofread ({len(proofread_text)} chars) to PR_QUEUE", flush=True)
-                            print(f"[DEBUG]   - Sending translation ({len(translate_text)} chars) to TL_QUEUE", flush=True)
+                            debug_print(f"[DEBUG] STEP 7: Sending to queues...", flush=True)
+                            debug_print(f"[DEBUG]   - Sending proofread ({len(proofread_text)} chars) to PR_QUEUE", flush=True)
+                            debug_print(f"[DEBUG]   - Sending translation ({len(translate_text)} chars) to TL_QUEUE", flush=True)
                             if proofread_text:
                                 prres_queue.put((proofread_text + proofread_separator, ""))
-                                print(f"[DEBUG]   ✓ Sent to PR_QUEUE", flush=True)
+                                debug_print(f"[DEBUG]   ✓ Sent to PR_QUEUE", flush=True)
                             if translate_text:
                                 tlres_queue.put((translate_text + translation_separator, ""))
-                                print(f"[DEBUG]   ✓ Sent to TL_QUEUE", flush=True)
-                            print(f"[DEBUG] ========== TWO-CALL PROCESSING COMPLETE ==========", flush=True)
+                                debug_print(f"[DEBUG]   ✓ Sent to TL_QUEUE", flush=True)
+                            debug_print(f"[DEBUG] ========== TWO-CALL PROCESSING COMPLETE ==========", flush=True)
                         else:
                             # Single AI call (proofread-only or translate-only)
-                            print(f"[DEBUG] Using SINGLE AI call (mode: {ai_processor.mode if ai_processor else 'None'})", flush=True)
+                            debug_print(f"[DEBUG] Using SINGLE AI call (mode: {ai_processor.mode if ai_processor else 'None'})", flush=True)
                             processed, ai_error = ai_translate(to_process, ai_processor)
                             if ai_error:
                                 # Log error to console but continue with result
@@ -336,10 +337,10 @@ def proc(index, model, vad, memory, patience, timeout, prompt, source, target, t
 
                             # Send the NEW processed chunk to the correct queue based on mode
                             if ai_processor.mode == "proofread" and prres_queue is not None:
-                                print(f"[DEBUG] Sending proofread result to PR_QUEUE", flush=True)
+                                debug_print(f"[DEBUG] Sending proofread result to PR_QUEUE", flush=True)
                                 prres_queue.put((processed + separator, ""))
                             else:
-                                print(f"[DEBUG] Sending result to TL_QUEUE", flush=True)
+                                debug_print(f"[DEBUG] Sending result to TL_QUEUE", flush=True)
                                 tlres_queue.put((processed + separator, ""))
             else:
                 # Use original Google Translate
@@ -369,15 +370,15 @@ def proc(index, model, vad, memory, patience, timeout, prompt, source, target, t
         # Combine accumulated_done with last_curr_src to ensure nothing is lost
         final_text = accumulated_done
         if last_curr_src and last_curr_src.strip():
-            print(f"[DEBUG] EXIT: Found curr_src text: '{last_curr_src.strip()}'", flush=True)
+            debug_print(f"[DEBUG] EXIT: Found curr_src text: '{last_curr_src.strip()}'", flush=True)
             final_text = accumulated_done + last_curr_src
 
         if ai_processor and final_text and final_text.strip():
-            print(f"[DEBUG] EXIT: Processing remaining text ({len(final_text)} chars)", flush=True)
-            print(f"[DEBUG] EXIT: Text = '{final_text[:200]}'...", flush=True)
+            debug_print(f"[DEBUG] EXIT: Processing remaining text ({len(final_text)} chars)", flush=True)
+            debug_print(f"[DEBUG] EXIT: Text = '{final_text[:200]}'...", flush=True)
             if prres_queue is not None and ai_processor.mode == "proofread_translate" and AI_AVAILABLE:
                 # Make TWO separate calls for proofread+translate mode
-                print(f"[DEBUG] EXIT: Using two-call processing", flush=True)
+                debug_print(f"[DEBUG] EXIT: Using two-call processing", flush=True)
                 config = AIConfig()
                 proofread_processor = AITextProcessor(
                     config=config,
@@ -388,7 +389,7 @@ def proc(index, model, vad, memory, patience, timeout, prompt, source, target, t
                 )
 
                 proofread_text, _ = ai_translate(final_text, proofread_processor)
-                print(f"[DEBUG] EXIT: Proofread result: '{proofread_text[:100]}'", flush=True)
+                debug_print(f"[DEBUG] EXIT: Proofread result: '{proofread_text[:100]}'", flush=True)
 
                 translate_processor = AITextProcessor(
                     config=config,
@@ -399,17 +400,17 @@ def proc(index, model, vad, memory, patience, timeout, prompt, source, target, t
                 )
 
                 translate_text, _ = ai_translate(proofread_text, translate_processor)
-                print(f"[DEBUG] EXIT: Translation result: '{translate_text[:100]}'", flush=True)
+                debug_print(f"[DEBUG] EXIT: Translation result: '{translate_text[:100]}'", flush=True)
 
                 if proofread_text:
                     prres_queue.put((proofread_text, ""))
-                    print(f"[DEBUG] EXIT: Sent proofread to PR_QUEUE", flush=True)
+                    debug_print(f"[DEBUG] EXIT: Sent proofread to PR_QUEUE", flush=True)
                 if translate_text:
                     tlres_queue.put((translate_text, ""))
-                    print(f"[DEBUG] EXIT: Sent translation to TL_QUEUE", flush=True)
+                    debug_print(f"[DEBUG] EXIT: Sent translation to TL_QUEUE", flush=True)
             else:
                 # Single AI call
-                print(f"[DEBUG] EXIT: Using single AI call", flush=True)
+                debug_print(f"[DEBUG] EXIT: Using single AI call", flush=True)
                 final_tgt, _ = ai_translate(final_text, ai_processor)
                 tlres_queue.put((final_tgt, ""))
 
diff --git a/src/debug.py b/src/debug.py
new file mode 100644
index 0000000..b9dbb5f
--- /dev/null
+++ b/src/debug.py
@@ -0,0 +1,30 @@
+"""Debug output control module.
+
+Reads debug settings from configuration and provides a simple
+debug_print() function that only outputs when debug mode is enabled.
+"""
+
+from settings import Settings
+
+# Module-level settings instance
+_settings = None
+DEBUG_ENABLED = False
+
+
+def _load_debug_setting():
+    """Load debug setting from configuration file."""
+    global _settings, DEBUG_ENABLED
+    if _settings is None:
+        _settings = Settings()
+    DEBUG_ENABLED = _settings.get("debug_enabled", False)
+    return DEBUG_ENABLED
+
+
+# Load on module import
+_load_debug_setting()
+
+
+def debug_print(*args, **kwargs):
+    """Print debug message only if debug mode is enabled in settings."""
+    if DEBUG_ENABLED:
+        print(*args, **kwargs)
diff --git a/src/gui.py b/src/gui.py
index a1f4a99..cb5f1fb 100755
--- a/src/gui.py
+++ b/src/gui.py
@@ -24,6 +24,9 @@ class App(tk.Tk):
         # Load settings first
         self.settings = Settings()
 
+        # Load debug mode from settings (default: False)
+        self.debug_enabled = self.settings.get("debug_enabled", False)
+
         # Load text visibility state from settings (default: False - start in minimal mode)
         self.text_visible = self.settings.get("text_visible", False)
 
@@ -720,6 +723,23 @@ class App(tk.Tk):
         except:
             pass
 
+        # Load translation language settings
+        try:
+            source_lang = self.settings.get("source_language", "auto")
+            target_lang = self.settings.get("target_language", "none")
+
+            # Set source language
+            source_values = self.source_combo.cget("values")
+            if source_lang in source_values:
+                self.source_combo.set(source_lang)
+
+            # Set target language
+            target_values = self.target_combo.cget("values")
+            if target_lang in target_values:
+                self.target_combo.set(target_lang)
+        except Exception as e:
+            print(f"Error loading language settings: {e}")
+
         # Set initial trigger field visibility based on trigger mode
         if self.ai_available:
             self.on_trigger_changed()
@@ -982,7 +1002,8 @@ class App(tk.Tk):
 
         # Set flag to request manual processing
         self.manual_trigger_requested[0] = True
-        print("[GUI] Manual AI processing requested", flush=True)
+        if self.debug_enabled:
+            print("[GUI] Manual AI processing requested", flush=True)
 
     def finalize_tts_session(self):
         """Generate TTS audio from accumulated session text."""
@@ -1219,6 +1240,15 @@ class App(tk.Tk):
             except Exception as e:
                 print(f"Error saving AI settings: {e}")
 
+        # Save translation language settings
+        try:
+            source_lang = self.source_combo.get()
+            target_lang = self.target_combo.get()
+            self.settings.set("source_language", source_lang)
+            self.settings.set("target_language", target_lang)
+        except Exception as e:
+            print(f"Error saving language settings: {e}")
+
         # Save auto-stop settings
         try:
             auto_stop_enabled = "selected" in self.autostop_check.state()
@@ -1375,7 +1405,8 @@ class App(tk.Tk):
             ai_trigger_mode = "manual"
             ai_process_interval = 999999  # Large value (won't be used)
             ai_process_words = None
-            print(f"[GUI] AI Trigger Mode: Manual", flush=True)
+            if self.debug_enabled:
+                print(f"[GUI] AI Trigger Mode: Manual", flush=True)
         else:
             # Automatic mode: use configured trigger settings
             if self.ai_available:
@@ -1387,10 +1418,11 @@ class App(tk.Tk):
             ai_process_words = int(self.ai_words_spin.get()) if self.ai_available and ai_trigger_mode == "words" else None
 
             # Debug logging
-            if ai_trigger_mode == "time":
-                print(f"[GUI] AI Trigger Mode: Time, Interval: {ai_process_interval}s", flush=True)
-            else:
-                print(f"[GUI] AI Trigger Mode: Words, Word Count: {ai_process_words}", flush=True)
+            if self.debug_enabled:
+                if ai_trigger_mode == "time":
+                    print(f"[GUI] AI Trigger Mode: Time, Interval: {ai_process_interval}s", flush=True)
+                else:
+                    print(f"[GUI] AI Trigger Mode: Words, Word Count: {ai_process_words}", flush=True)
 
         # Get auto-stop parameters from GUI
         auto_stop_enabled = "selected" in self.autostop_check.state()
@@ -1401,21 +1433,24 @@ class App(tk.Tk):
         if ai_processor and ai_processor.mode == "proofread_translate":
             prres_queue = self.pr_text.res_queue
             # Enable proofread window and show buttons
-            print(f"[GUI] Enabling proofread window (mode: {ai_processor.mode})", flush=True)
-            print(f"[GUI] prres_queue ID: {id(prres_queue)}", flush=True)
-            print(f"[GUI] self.pr_text.res_queue ID: {id(self.pr_text.res_queue)}", flush=True)
+            if self.debug_enabled:
+                print(f"[GUI] Enabling proofread window (mode: {ai_processor.mode})", flush=True)
+                print(f"[GUI] prres_queue ID: {id(prres_queue)}", flush=True)
+                print(f"[GUI] self.pr_text.res_queue ID: {id(self.pr_text.res_queue)}", flush=True)
             self.pr_text.config(state="normal", background="white")
             self.ai_output_buttons_frame.grid()
         elif ai_processor and ai_processor.mode == "proofread":
             # Enable proofread window for proofread-only mode, show buttons
             prres_queue = self.pr_text.res_queue  # FIX: Set queue for proofread-only mode
-            print(f"[GUI] Enabling proofread window (mode: {ai_processor.mode})", flush=True)
-            print(f"[GUI] prres_queue ID: {id(prres_queue)}", flush=True)
+            if self.debug_enabled:
+                print(f"[GUI] Enabling proofread window (mode: {ai_processor.mode})", flush=True)
+                print(f"[GUI] prres_queue ID: {id(prres_queue)}", flush=True)
             self.pr_text.config(state="normal", background="white")
             self.ai_output_buttons_frame.grid()
         else:
             # Disable proofread window and hide buttons (keep visible but grayed)
-            print(f"[GUI] Disabling proofread window (mode: {ai_processor.mode if ai_processor else 'None'})", flush=True)
+            if self.debug_enabled:
+                print(f"[GUI] Disabling proofread window (mode: {ai_processor.mode if ai_processor else 'None'})", flush=True)
             self.pr_text.config(state="disabled", background="#f0f0f0")
             self.ai_output_buttons_frame.grid_remove()
 
diff --git a/src/gui_parts/text_widget.py b/src/gui_parts/text_widget.py
index c5fec4e..22f65dc 100644
--- a/src/gui_parts/text_widget.py
+++ b/src/gui_parts/text_widget.py
@@ -1,5 +1,6 @@
 import tkinter as tk
 from cmque import PairDeque, Queue
+from debug import debug_print
 
 class Text(tk.Text):
     def __init__(self, master, on_new_text=None, on_text_changed=None):
@@ -50,9 +51,9 @@ class Text(tk.Text):
                 self.prev_done = done
 
                 if queue_name:
-                    print(f"[TEXT-{queue_name}] Received: done={done[:50]}... curr={curr[:50]}...", flush=True)
+                    debug_print(f"[TEXT-{queue_name}] Received: done={done[:50]}... curr={curr[:50]}...", flush=True)
                     if new_text:
-                        print(f"[TEXT-{queue_name}] NEW text: {new_text[:100]}...", flush=True)
+                        debug_print(f"[TEXT-{queue_name}] NEW text: {new_text[:100]}...", flush=True)
 
                 # Update display
                 self.delete(self.record, "end")