newbison 发表于 2024-8-3 14:34:12

flask 写app遇到的问题

我用flask写了一个听力的app,只有第一个单词发声音,后面就没有声音了,怎么回事?

app.py
from flask import Flask, render_template, request, jsonify
import json
import random
import pyttsx3
import threading
import time
import os

app = Flask(__name__)


current_dir = os.path.join(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
filepath = os.path.join(parent_dir, 'words.json')

# Load words from JSON file
with open(filepath, 'r', encoding='utf-8') as f:
    words_dict = json.load(f)

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

# Global control variables
is_paused = False
stop_thread = False

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/start', methods=['POST'])
def start():
    global is_paused, stop_thread
    is_paused = False
    stop_thread = False

    language = request.form['language']
    num_words = int(request.form['num_words'])
    sleep_time = float(request.form['sleep_time'])
    order = request.form['order']

    words = list(words_dict.keys()) if language == 'english' else list(words_dict.values())
    selected_words = random.sample(words, num_words) if order == 'random' else words[:num_words]

    t = threading.Thread(target=display_and_pronounce, args=(selected_words, sleep_time, language))
    t.start()
    return jsonify({'status': 'started'})

@app.route('/pause', methods=['POST'])
def pause():
    global is_paused
    is_paused = not is_paused
    return jsonify({'status': 'paused' if is_paused else 'resumed'})

@app.route('/stop', methods=['POST'])
def stop():
    global stop_thread
    stop_thread = True
    return jsonify({'status': 'stopped'})

def display_and_pronounce(words, sleep_time, language):
    global is_paused, stop_thread
    engine.setProperty('voice', voices.id if language == 'english' else voices.id)

    for word in words:
      if stop_thread:
            break
      while is_paused:
            time.sleep(0.1)

      gap = calculate_gap(word, sleep_time)
      engine.say(word)
      engine.runAndWait()
      time.sleep(gap)
      engine.say(word)
      engine.runAndWait()
      time.sleep(gap)

def calculate_gap(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

if __name__ == "__main__":
    app.run(debug=True)


templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Language Learning App</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
    <div class="container">
      <h1>Language Learning App</h1>
      <form id="control-form">
            <label for="language">Select Language:</label>
            <select id="language" name="language">
                <option value="english">English</option>
                <option value="chinese">Chinese</option>
            </select>
            <br>
            <label for="num_words">Number of Words:</label>
            <input type="number" id="num_words" name="num_words" value="10">
            <br>
            <label for="sleep_time">Sleep Time (seconds):</label>
            <input type="number" step="0.1" id="sleep_time" name="sleep_time" value="1.0">
            <br>
            <label for="order">Order:</label>
            <select id="order" name="order">
                <option value="sequential">Sequential</option>
                <option value="random">Random</option>
            </select>
            <br>
            <button type="button" onclick="startLearning()">Start</button>
            <button type="button" onclick="pauseLearning()">Pause/Resume</button>
            <button type="button" onclick="stopLearning()">Stop</button>
      </form>
    </div>
    <script>
      function startLearning() {
            $.post('/start', $('#control-form').serialize(), function(data) {
                console.log(data.status);
            });
      }
      function pauseLearning() {
            $.post('/pause', function(data) {
                console.log(data.status);
            });
      }
      function stopLearning() {
            $.post('/stop', function(data) {
                console.log(data.status);
            });
      }
    </script>
</body>
</html>


static/style.css
body {
    font-family: Arial, sans-serif;
    background-color: #f4f4f4;
    text-align: center;
    padding: 50px;
}

.container {
    background-color: #fff;
    padding: 20px;
    border-radius: 5px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
    display: inline-block;
    text-align: left;
}

h1 {
    margin-bottom: 20px;
}

label {
    display: block;
    margin: 10px 0 5px;
}

input, select, button {
    width: 100%;
    padding: 10px;
    margin: 5px 0;
    border-radius: 5px;
    border: 1px solid #ccc;
}

button {
    background-color: #28a745;
    color: white;
    border: none;
    cursor: pointer;
}

button:hover {
    background-color: #218838;
}

FishC 发表于 2024-8-3 14:36:32

哦,看来你的 Flask 听力应用遇到了“一个字母都不想说”的尴尬局面。这种情况通常是由于多线程和 `pyttsx3` 的工作方式造成的。让我们逐步解决这个问题。

首先,`pyttsx3` 在调用 `runAndWait()` 时会阻塞当前线程,这意味着在此期间,它不会处理其他任何事情,包括你想说的下一个单词。因此,如果在同一线程中调用多个 `engine.say()` 和 `engine.runAndWait()`,就会导致问题。

下面是一些可能的解决方案:

### 解决方案

1. **避免重复调用 `runAndWait()`**:
   你可以通过将所有要说的单词放进一个列表,并只调用一次 `runAndWait()` 来避免问题。这样,Flask 线程就不会被阻塞。

   修改 `display_and_pronounce` 函数如下:

   def display_and_pronounce(words, sleep_time, language):
       global is_paused, stop_thread
       engine.setProperty('voice', voices.id if language == 'english' else voices.id)

       for word in words:
         if stop_thread:
               break
         while is_paused:
               time.sleep(0.1)

         gap = calculate_gap(word, sleep_time)
         engine.say(word)
         # 等待发音完成前不再调用
         time.sleep(gap)# 为了给发音留出时间

2. **考虑使用队列**:
   在复杂的应用中,可以使用队列(如 `queue.Queue`)来管理待说的单词。这将使得处理更为灵活。

3. **检查声音设备**:
   确保你的音频设备不会因为多次切换而出现问题。有时候,操作系统对声音输出的处理也会影响。

4. **调试信息**:
   添加一些调试信息,比如在每次发音之前打印当前正在发音的单词,以确保代码逻辑没有问题。

### 其他小建议
- **改进用户体验**:前端的按钮可以在点击后禁用,防止用户疯狂地点击,让服务器喘口气。
- **异常处理**:加上异常处理机制,至少能在控制台看到一些错误提示,不让你的 APP 完全“安静”。

### 总结
以上是解决这个发声只停在第一个单词问题的一些建议。如果你像个歌手一样,想让每个单词都响亮且流畅,那你可能得调整下 `engine.runAndWait()` 的调用方式。而如果这些措施还不能解决问题,请把调试时的错误信息扔出来,我们一起开个会讨论!希望你能早日让你的小应用“发声”!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

newbison 发表于 2024-8-3 15:15:07

改了之后根本不发声了。。。

FishC 发表于 2024-8-3 15:16:25

newbison 发表于 2024-08-03 15:15
改了之后根本不发声了。。。

哎呀,看来我们又打开了“调试黑洞”的大门!让我们再认真检查一下,并确保你的 Flask 应用声如洪钟,而不是小蚊子嗡嗡。

1. **移除重复调用 `runAndWait()`**:如果你只在循环内调用一次 `runAndWait()`,但要确保这个调用是在所有 `engine.say()` 之后。我们可以把逻辑稍作调整如下:

def display_and_pronounce(words, sleep_time, language):
    global is_paused, stop_thread
    engine.setProperty('voice', voices.id if language == 'english' else voices.id)
   
    for word in words:
      if stop_thread:
            break
            
      while is_paused:
            time.sleep(0.1)

      # 说出单词
      engine.say(word)
      
      # 等待发音完成后再处理下一个单词
      engine.runAndWait()

      # 等待 gap 时间
      gap = calculate_gap(word, sleep_time)
      time.sleep(gap)

2. **确保 TTS 引擎正常工作**:在 Flask 线程中尝试手动调用一些简单的发音代码来确认引擎是否正常工作。例如,你可以在 Flask 的 `/start` 路由中增加以下调试语句:

@app.route('/start', methods=['POST'])
def start():
    # ...其他代码...
    engine.say("Testing text-to-speech engine")
    engine.runAndWait()
    return jsonify({'status': 'started'})

如果你能听到“Testing text-to-speech engine”,那么证明 `pyttsx3` 一切正常;如果听不到,那可能是系统音频设置的问题。

3. **多线程使用问题**:这里的多线程需要小心。你也可以考虑将声音播放和 Flask 路由放在同一个线程中,以避免竞争条件造成的不稳定,比如:

import queue

# 创建队列
sound_queue = queue.Queue()

def play_sound():
    while True:
      word = sound_queue.get()# 从队列取出要发音的单词
      engine.say(word)
      engine.runAndWait()
      sound_queue.task_done()

# 在主程序中启动音频播放线程
t = threading.Thread(target=play_sound)
t.daemon = True
t.start()

def display_and_pronounce(words, sleep_time, language):
    global is_paused, stop_thread
   
    for word in words:
      if stop_thread:
            break

      while is_paused:
            time.sleep(0.1)

      sound_queue.put(word)# 将单词放入队列
      time.sleep(calculate_gap(word, sleep_time))

4. **修改后端路由方法**:确保你的 Flask 路由能正确处理请求并返回响应,让我们在 `/start` 的方法上确认它处理 POST 请求:

@app.route('/start', methods=['POST'])
def start():
    # ...之前的代码...
    return jsonify({'status': 'started'})

### 最后的建议
如果这样还是不奏效,请务必在控制台查看是否有错误信息,或者在代码中添加更多的打印语句来检查执行流程。记住,调试就像是一场侦探游戏,任何蛛丝马迹都可能成为关键线索。而且,别忘了时不时看看你的音量设置哦,有时候最简单的问题才是幕后黑手!

希望这些修改和建议能帮助到你,如果还有问题,随时问我,我们一起捋顺这条“发声”的河流!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

newbison 发表于 2024-8-3 15:40:06

能不能将app.py 整个文件给我一下?

FishC 发表于 2024-8-3 15:42:26

newbison 发表于 2024-08-03 15:40
能不能将app.py 整个文件给我一下?

当然可以!下面是一个经过调整的 `app.py` 文件,确保你的 Flask 应用可以顺利地发出声音,同时避免之前提到的问题:

from flask import Flask, render_template, request, jsonify
import json
import random
import pyttsx3
import threading
import time
import os
import queue

app = Flask(__name__)

# Initialize the sound queue
sound_queue = queue.Queue()

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

# Load words from JSON file
current_dir = os.path.join(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
filepath = os.path.join(parent_dir, 'words.json')

with open(filepath, 'r', encoding='utf-8') as f:
    words_dict = json.load(f)

# Global control variables
is_paused = False
stop_thread = False

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/start', methods=['POST'])
def start():
    global is_paused, stop_thread
    is_paused = False
    stop_thread = False

    language = request.form['language']
    num_words = int(request.form['num_words'])
    sleep_time = float(request.form['sleep_time'])
    order = request.form['order']

    words = list(words_dict.keys()) if language == 'english' else list(words_dict.values())
    selected_words = random.sample(words, num_words) if order == 'random' else words[:num_words]

    for word in selected_words:
      sound_queue.put(word)# Put each word in the queue

    return jsonify({'status': 'started'})

@app.route('/pause', methods=['POST'])
def pause():
    global is_paused
    is_paused = not is_paused
    return jsonify({'status': 'paused' if is_paused else 'resumed'})

@app.route('/stop', methods=['POST'])
def stop():
    global stop_thread
    stop_thread = True
    return jsonify({'status': 'stopped'})

def play_sound():
    while True:
      word = sound_queue.get()# Get a word from the queue
      if stop_thread:
            break
      engine.say(word)
      engine.runAndWait()# Wait until the speech is finished
      sound_queue.task_done()# Indicate that the word has been processed

# Start the sound playing thread
threading.Thread(target=play_sound, daemon=True).start()

if __name__ == "__main__":
    app.run(debug=True)

### 主要变动说明:
1. **使用 `queue.Queue()`**:通过队列管理要发音的单词,避免卡住线程。
2. **简化音频播放逻辑**:将播放逻辑放到一个独立的线程中,确保每个单词都能按顺序发音。
3. **在路由中获取参数**:确保从请求中正确获取参数。

### 其他注意事项:
- 请确认 `words.json` 文件结构正确,以便正确加载单词。
- 确保 `pyttsx3` 以及其他依赖项已正确安装。
- 根据需求,记得在前端 HTML 中修改对应的字段名称和格式。

希望这个完整的 `app.py` 文件能帮你顺利运行。若还有其他问题,请随时告诉我!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
页: [1]
查看完整版本: flask 写app遇到的问题