鱼C论坛

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

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

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

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

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

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

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

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

voices = engine.getProperty("voices")

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

        self.language_var = tk.StringVar(value="english")
        self.word_count_var = tk.IntVar(value=10)
        self.sleep_time_var = tk.DoubleVar(value=1.0)
        self.order_var = tk.StringVar(value="random")
        self.file_var = tk.StringVar(value="primary")
        self.grade_var = tk.StringVar()


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

        self.create_widgets()

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

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

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

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

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

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

    def toggle_pause(self):
        self.is_paused = not self.is_paused
        if self.is_paused:
            self.pause_button.config(text="Resume")
        else:
            self.pause_button.config(text="Pause")

    def start_learning(self):
        file_map = {
            "primary": "words.json",
            "high_school": "high_school_words.json",
            "college": "college_words.json"
        }

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

        language = self.language_var.get()
        word_count = self.word_count_var.get()
        sleep_time = self.sleep_time_var.get()
        order = self.order_var.get()

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

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

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

    def display_and_pronounce(self, words, sleep_time):
        for word in words:
            if self.stop_thread:  # Check if the thread needs to be stopped
                break
            while self.is_paused:
                time.sleep(0.1)
            length = len(word.split(" "))
            if length == 1:
                gap = 2 * sleep_time
            elif length == 2:
                gap = 3 * sleep_time
            elif length == 3: 
                gap = 5 * sleep_time
            else:
                gap = 9 * sleep_time
            
            engine.say(word)
            engine.runAndWait()
            time.sleep(gap)
            engine.say(word)
            engine.runAndWait()
            time.sleep(gap)

        self.show_words_popup(words)

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

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

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

if __name__ == "__main__":
    root = tk.Tk()
    app = LanguageLearningApp(root)
    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` 和相关逻辑:
def display_and_pronounce(self, words, sleep_time):
    for word in words:
        if self.stop_thread:  # Check if the thread needs to be stopped
            break
        while self.is_paused:
            time.sleep(0.1)

        gap = self.calculate_gap(word, sleep_time)
        
        engine.say(word)      
        engine.runAndWait()   
        
        if self.stop_thread: 
            break  # Stop if the thread needs to end

        time.sleep(gap)       
        engine.say(word)      
        engine.runAndWait()   

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

def calculate_gap(self, word, sleep_time):
    length = len(word.split(" "))
    if length == 1:
        return 2 * sleep_time
    elif length == 2:
        return 3 * sleep_time
    elif length == 3: 
        return 5 * sleep_time
    else:
        return 9 * sleep_time

def close_app(self):
    self.stop_thread = True  # Set the flag to stop the thread
    if self.is_running:      # Only stop if it's running
        engine.stop()       # Stop the TTS engine
    self.master.destroy()    # Close the main window

### 总结

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

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

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> 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` 和相关逻辑:
def display_and_pronounce(self, words, sleep_time):
    for word in words:
        if self.stop_thread:  # Check if the thread needs to be stopped
            break
        while self.is_paused:
            time.sleep(0.1)

        gap = self.calculate_gap(word, sleep_time)
        
        engine.say(word)      
        engine.runAndWait()   
        
        if self.stop_thread: 
            break  # Stop if the thread needs to end

        time.sleep(gap)       
        engine.say(word)      
        engine.runAndWait()   

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

def calculate_gap(self, word, sleep_time):
    length = len(word.split(" "))
    if length == 1:
        return 2 * sleep_time
    elif length == 2:
        return 3 * sleep_time
    elif length == 3: 
        return 5 * sleep_time
    else:
        return 9 * sleep_time

def close_app(self):
    self.stop_thread = True  # Set the flag to stop the thread
    if self.is_running:      # Only stop if it's running
        engine.stop()       # Stop the TTS engine
    self.master.destroy()    # Close the main window

### 总结

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

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

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

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

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-12-23 00:35

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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