11import os
22import time
33import json
4+ import re
5+ import winsound
46import customtkinter as ctk
57from tkinter import filedialog , messagebox
68import threading
@@ -272,6 +274,9 @@ def create_widgets(self):
272274
273275 btn_frame = ctk .CTkFrame (action_frame , fg_color = "transparent" )
274276 btn_frame .pack (fill = "x" , pady = 10 )
277+
278+ self .preview_btn = ctk .CTkButton (btn_frame , text = "Preview Audio" , command = self .preview_conversion , height = 40 , fg_color = "#2B719E" , hover_color = "#205578" )
279+ self .preview_btn .pack (side = "left" , fill = "x" , expand = True , padx = 5 )
275280
276281 self .start_btn = ctk .CTkButton (btn_frame , text = "Start Generation" , command = self .start_conversion , height = 40 , font = ("Roboto" , 14 , "bold" ))
277282 self .start_btn .pack (side = "left" , fill = "x" , expand = True , padx = 5 )
@@ -367,6 +372,7 @@ def set_ui_state(self, is_running):
367372 cancel_state = "normal" if is_running else "disabled"
368373
369374 self .start_btn .configure (state = state )
375+ self .preview_btn .configure (state = state )
370376 self .cancel_btn .configure (state = cancel_state )
371377 self .thread_minus_btn .configure (state = state )
372378 self .thread_plus_btn .configure (state = state )
@@ -375,6 +381,58 @@ def set_ui_state(self, is_running):
375381 if not is_running :
376382 self .progress_bar .set (0 if self .engine .cancel_event .is_set () else 1 )
377383
384+ def preview_conversion (self ):
385+ # 1. Get Text
386+ current_tab = self .tab_view .get ()
387+ text_data = ""
388+
389+ if current_tab == "Direct Text" :
390+ text_data = self .text_entry .get ("1.0" , "end" ).strip ()
391+ else :
392+ fpath = self .file_path_var .get ()
393+ if os .path .exists (fpath ):
394+ try :
395+ text_data = self .engine .extract_text_from_file (fpath )
396+ except :
397+ pass
398+
399+ if not text_data :
400+ text_data = "This is a sample audio preview using the Koh-koh-ro TTS engine. It demonstrates the voice quality and speed settings."
401+
402+ # Truncate to 2 sentences
403+ sentences = re .split (r'(?<=[.!?])\s+' , text_data )
404+ preview_text = " " .join (sentences [:2 ])
405+ if len (preview_text ) > 500 : # Safety cap
406+ preview_text = preview_text [:500 ]
407+
408+ # Config
409+ voice = self .voice_var .get ()
410+ speed = self .speed_var .get ()
411+
412+ # Temp file
413+ import tempfile
414+ tmp_path = os .path .join (tempfile .gettempdir (), "kokoro_preview.wav" )
415+
416+ self .status_label .configure (text = "Generating preview..." , text_color = "blue" )
417+
418+ def _on_preview_done (future ):
419+ def _ui_update ():
420+ try :
421+ success = future .result ()
422+ if success :
423+ self .status_label .configure (text = "Playing preview..." , text_color = "green" )
424+ winsound .PlaySound (tmp_path , winsound .SND_FILENAME | winsound .SND_ASYNC )
425+ self .after (3000 , lambda : self .status_label .configure (text = "Ready" , text_color = "gray" ))
426+ else :
427+ self .status_label .configure (text = "Preview failed." , text_color = "red" )
428+ except Exception as e :
429+ self .status_label .configure (text = f"Preview error: { e } " , text_color = "red" )
430+
431+ self .after (0 , _ui_update )
432+
433+ future = self .engine .worker .run_coro (self .engine .generate_preview (preview_text , voice , speed , tmp_path ))
434+ future .add_done_callback (_on_preview_done )
435+
378436 def start_conversion (self ):
379437 # 0. Validate Threads
380438 try :
0 commit comments