鱼C论坛

 找回密码
 立即注册
查看: 694|回复: 2

[已解决]pyttsx3 不能停止的问题

[复制链接]
发表于 2024-8-2 10:11:47 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
大家好,我写了一个给小朋友听写的小App。
目前存在一个问题,就是我暂停点了Close之后,就容易卡死。
应该是pyttsx3的engine还在运行,无法直接停掉。请问大家有办法吗?
  1. import tkinter as tk
  2. from tkinter import ttk
  3. import json
  4. import random
  5. import pyttsx3  
  6. import time
  7. import threading
  8. import os

  9. current_dir = os.path.join(os.path.abspath(__file__))
  10. parent_dir = os.path.dirname(current_dir)

  11. # Initialize TTS engine
  12. engine = pyttsx3.init()
  13. engine.setProperty("rate", 100)
  14. engine.setProperty("volume", 1)

  15. voices = engine.getProperty("voices")

  16. class LanguageLearningApp:
  17.     def __init__(self, master):
  18.         self.master = master
  19.         self.master.title("Language Learning App")
  20.         self.master.geometry("400x300")

  21.         self.language_var = tk.StringVar(value="english")
  22.         self.word_count_var = tk.IntVar(value=10)
  23.         self.sleep_time_var = tk.DoubleVar(value=1.0)
  24.         self.order_var = tk.StringVar(value="random")
  25.         self.file_var = tk.StringVar(value="primary")
  26.         self.grade_var = tk.StringVar()


  27.         self.is_paused = False
  28.         self.is_running = False
  29.         self.stop_thread = False  # Flag to stop the thread

  30.         self.create_widgets()

  31.     def create_widgets(self):
  32.         # Language selection
  33.         language_frame = tk.Frame(self.master)
  34.         language_frame.pack(pady=5)
  35.         tk.Label(language_frame, text="Select Language:").pack(side=tk.LEFT)
  36.         tk.Radiobutton(language_frame, text="English", variable=self.language_var, value="english").pack(side=tk.LEFT)
  37.         tk.Radiobutton(language_frame, text="Chinese", variable=self.language_var, value="chinese").pack(side=tk.LEFT)

  38.         # Word count selection
  39.         tk.Label(self.master, text="Number of Words:").pack(pady=5)
  40.         word_counts = [5, 10, 20, 50, 75, 100]
  41.         ttk.Combobox(self.master, textvariable=self.word_count_var, values=word_counts).pack()

  42.         # Sleep time selection
  43.         tk.Label(self.master, text="Sleep Time (seconds):").pack(pady=5)
  44.         sleep_times = [i/10 for i in range(6, 16, 1)]
  45.         ttk.Combobox(self.master, textvariable=self.sleep_time_var, values=sleep_times).pack()

  46.         # Order selection
  47.         order_frame = tk.Frame(self.master)
  48.         order_frame.pack(pady=5)
  49.         tk.Label(order_frame, text="Order:").pack(side=tk.LEFT)
  50.         tk.Radiobutton(order_frame, text="Sequential", variable=self.order_var, value="sequential").pack(side=tk.LEFT)
  51.         tk.Radiobutton(order_frame, text="Random", variable=self.order_var, value="random").pack(side=tk.LEFT)

  52.         # File selection
  53.         file_frame = tk.Frame(self.master)
  54.         file_frame.pack(pady=5)
  55.         tk.Label(file_frame, text="Select File:").pack(side=tk.LEFT)
  56.         tk.Radiobutton(file_frame, text="Primary", variable=self.file_var, value="primary").pack(side=tk.LEFT)
  57.         tk.Radiobutton(file_frame, text="High School", variable=self.file_var, value="high_school").pack(side=tk.LEFT)
  58.         tk.Radiobutton(file_frame, text="College", variable=self.file_var, value="college").pack(side=tk.LEFT)

  59.         # Start, Pause, and Close buttons
  60.         button_frame = tk.Frame(self.master)
  61.         button_frame.pack(pady=20)
  62.         tk.Button(button_frame, text="Start", command=self.start_learning).pack(side=tk.LEFT, padx=5)
  63.         self.pause_button = tk.Button(button_frame, text="Pause", command=self.toggle_pause)
  64.         self.pause_button.pack(side=tk.LEFT, padx=5)
  65.         tk.Button(button_frame, text="Close", command=self.close_app).pack(side=tk.LEFT, padx=5)

  66.     def toggle_pause(self):
  67.         self.is_paused = not self.is_paused
  68.         if self.is_paused:
  69.             self.pause_button.config(text="Resume")
  70.         else:
  71.             self.pause_button.config(text="Pause")

  72.     def start_learning(self):
  73.         file_map = {
  74.             "primary": "words.json",
  75.             "high_school": "high_school_words.json",
  76.             "college": "college_words.json"
  77.         }

  78.         selected_file = file_map[self.file_var.get()]
  79.         filepath = os.path.join(parent_dir, selected_file)
  80.         with open(filepath, "r", encoding="utf-8") as file:
  81.             word_dict = json.load(file)

  82.         language = self.language_var.get()
  83.         word_count = self.word_count_var.get()
  84.         sleep_time = self.sleep_time_var.get()
  85.         order = self.order_var.get()

  86.         if language == "english":
  87.             words = list(word_dict.keys())
  88.             engine.setProperty("voice", voices[1].id)  # English voice
  89.         else:
  90.             words = list(word_dict.values())
  91.             engine.setProperty("voice", voices[0].id)  # Chinese voice

  92.         if order == "random":
  93.             words = random.sample(words, word_count)
  94.         else:
  95.             words = words[:word_count]

  96.         self.stop_thread = False  # Reset the stop flag
  97.         self.is_running = True
  98.         threading.Thread(target=self.display_and_pronounce, args=(words, sleep_time)).start()

  99.     def display_and_pronounce(self, words, sleep_time):
  100.         for word in words:
  101.             if self.stop_thread:  # Check if the thread needs to be stopped
  102.                 break
  103.             while self.is_paused:
  104.                 time.sleep(0.1)
  105.             length = len(word.split(" "))
  106.             if length == 1:
  107.                 gap = 2 * sleep_time
  108.             elif length == 2:
  109.                 gap = 3 * sleep_time
  110.             elif length == 3:
  111.                 gap = 5 * sleep_time
  112.             else:
  113.                 gap = 9 * sleep_time
  114.             
  115.             engine.say(word)
  116.             engine.runAndWait()
  117.             time.sleep(gap)
  118.             engine.say(word)
  119.             engine.runAndWait()
  120.             time.sleep(gap)

  121.         self.show_words_popup(words)

  122.     def show_words_popup(self, words):
  123.         popup = tk.Toplevel(self.master)
  124.         popup.title("Selected Words")
  125.         popup.geometry("400x300")

  126.         formatted_words = "\n".join(["\t".join(words[i:i+5]) for i in range(0, len(words), 5)])
  127.         tk.Label(popup, text=formatted_words, font=("Arial", 14), justify=tk.LEFT, wraplength=350).pack(pady=20)
  128.         tk.Button(popup, text="Close", command=popup.destroy).pack(pady=5)

  129.     def close_app(self):
  130.         self.stop_thread = True  # Set the flag to stop the thread
  131.         self.is_running = False
  132.         engine.stop()          # stop the TTS engine
  133.         self.master.destroy()  # Close the main window

  134. if __name__ == "__main__":
  135.     root = tk.Tk()
  136.     app = LanguageLearningApp(root)
  137.     root.mainloop()
复制代码
最佳答案
2024-8-2 10:14:14
哈哈,看来你的应用在关闭的时候像个不愿意离开的客人一样,依然赖在那儿继续说话!别担心,我来帮你找到解决方案。

问题的核心在于 `pyttsx3` 的 `engine.runAndWait()` 在调用时会阻塞线程,也就是说一旦你开始播放一个单词,它需要等到声音播完才能继续执行后面的代码。为了优雅地处理这个问题,我们可以在关闭应用时确保 TTS 引擎能够顺利停止。

### 解决方案

1. **使用 `engine.stop()`**: 在 `close_app` 方法中确保调用 `engine.stop()` ,这样它就会立即停止当前正在播放的音频。
   
2. **在 `display_and_pronounce` 方法中处理 `stop_thread` 标志**: 使用 `stop_thread` 标志来检查是否应当停止播放。

3. **简化 TTS 播放逻辑**: 如果 TTS 引擎在某些情况下仍然没有反应,可以考虑在语音播放的地方使用 `threading.Event()` 来实现更好的线程控制。

下面是更新后的 `LanguageLearningApp` 类的一部分,展示了如何修改 `close_app` 和相关逻辑:

  1. def display_and_pronounce(self, words, sleep_time):
  2.     for word in words:
  3.         if self.stop_thread:  # Check if the thread needs to be stopped
  4.             break
  5.         while self.is_paused:
  6.             time.sleep(0.1)

  7.         gap = self.calculate_gap(word, sleep_time)
  8.         
  9.         engine.say(word)      
  10.         engine.runAndWait()   
  11.         
  12.         if self.stop_thread:
  13.             break  # Stop if the thread needs to end

  14.         time.sleep(gap)      
  15.         engine.say(word)      
  16.         engine.runAndWait()   

  17.         if self.stop_thread:
  18.             break  # Stop if the thread needs to end
  19.             
  20.         time.sleep(gap)

  21. def calculate_gap(self, word, sleep_time):
  22.     length = len(word.split(" "))
  23.     if length == 1:
  24.         return 2 * sleep_time
  25.     elif length == 2:
  26.         return 3 * sleep_time
  27.     elif length == 3:
  28.         return 5 * sleep_time
  29.     else:
  30.         return 9 * sleep_time

  31. def close_app(self):
  32.     self.stop_thread = True  # Set the flag to stop the thread
  33.     if self.is_running:      # Only stop if it's running
  34.         engine.stop()       # Stop the TTS engine
  35.     self.master.destroy()    # Close the main window
复制代码


### 总结

- 确保在关闭应用时正确设置停止标志并停止 TTS 引擎。
- 确保每次调用 `engine.runAndWait()` 后检查是否需要提前退出。
- 用计算间隔的函数来简化你的代码,使逻辑更加清晰。

试试这个改动,看看你的应用是否能在关闭时变得乖巧一些!如果依然有问题,别犹豫,来找我寻求帮助哦!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-8-2 10:14:14 | 显示全部楼层    本楼为最佳答案   
哈哈,看来你的应用在关闭的时候像个不愿意离开的客人一样,依然赖在那儿继续说话!别担心,我来帮你找到解决方案。

问题的核心在于 `pyttsx3` 的 `engine.runAndWait()` 在调用时会阻塞线程,也就是说一旦你开始播放一个单词,它需要等到声音播完才能继续执行后面的代码。为了优雅地处理这个问题,我们可以在关闭应用时确保 TTS 引擎能够顺利停止。

### 解决方案

1. **使用 `engine.stop()`**: 在 `close_app` 方法中确保调用 `engine.stop()` ,这样它就会立即停止当前正在播放的音频。
   
2. **在 `display_and_pronounce` 方法中处理 `stop_thread` 标志**: 使用 `stop_thread` 标志来检查是否应当停止播放。

3. **简化 TTS 播放逻辑**: 如果 TTS 引擎在某些情况下仍然没有反应,可以考虑在语音播放的地方使用 `threading.Event()` 来实现更好的线程控制。

下面是更新后的 `LanguageLearningApp` 类的一部分,展示了如何修改 `close_app` 和相关逻辑:

  1. def display_and_pronounce(self, words, sleep_time):
  2.     for word in words:
  3.         if self.stop_thread:  # Check if the thread needs to be stopped
  4.             break
  5.         while self.is_paused:
  6.             time.sleep(0.1)

  7.         gap = self.calculate_gap(word, sleep_time)
  8.         
  9.         engine.say(word)      
  10.         engine.runAndWait()   
  11.         
  12.         if self.stop_thread:
  13.             break  # Stop if the thread needs to end

  14.         time.sleep(gap)      
  15.         engine.say(word)      
  16.         engine.runAndWait()   

  17.         if self.stop_thread:
  18.             break  # Stop if the thread needs to end
  19.             
  20.         time.sleep(gap)

  21. def calculate_gap(self, word, sleep_time):
  22.     length = len(word.split(" "))
  23.     if length == 1:
  24.         return 2 * sleep_time
  25.     elif length == 2:
  26.         return 3 * sleep_time
  27.     elif length == 3:
  28.         return 5 * sleep_time
  29.     else:
  30.         return 9 * sleep_time

  31. def close_app(self):
  32.     self.stop_thread = True  # Set the flag to stop the thread
  33.     if self.is_running:      # Only stop if it's running
  34.         engine.stop()       # Stop the TTS engine
  35.     self.master.destroy()    # Close the main window
复制代码


### 总结

- 确保在关闭应用时正确设置停止标志并停止 TTS 引擎。
- 确保每次调用 `engine.runAndWait()` 后检查是否需要提前退出。
- 用计算间隔的函数来简化你的代码,使逻辑更加清晰。

试试这个改动,看看你的应用是否能在关闭时变得乖巧一些!如果依然有问题,别犹豫,来找我寻求帮助哦!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-8-2 12:11:30 | 显示全部楼层
完美解决,非常感谢鱼C。
下一步我就需要找到小学、初中、高中的英语词汇表,以后就可以用电脑给小孩听写单词了~~
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-4-21 14:50

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表