“dependent drop down list” does't work [Flask on Google App Engine]

做~自己de王妃 提交于 2020-02-05 06:13:18

问题


I'm a beginner in coding. I would like to make a simple web application using Google Cloud Text to Speech API.

  1. a web site with a text box
  2. you input a sentence in the text box and click a button "submit"
  3. you can download a mp3 file which is made by Google Cloud Text to Speech API

I'm an English teacher in Japan, so I would like my students to use this website to improve their English pronunciation.

Firstly, I'd like to tell you my problem.

I have almost completed my web app. However, one problem happened. Dependent drop down list doesn't work. When a user use the app, she choose country and voiceId.

If you choose US--> you choose from en-US-Wavenet-A or en-US-Wavenet-B or en-US-Wavenet-C.

If you choose GB--> you choose from en-GB-Wavenet-A or en-GB-Wavenet-B or en-GB-Wavenet-C.

If you choose US, it work perfectly. However, if you choose GB, a problem happens.

Even if choose GB--> en-GB-Wavenet-B, you download a mp3 file which sounds voice of en-GB-Wavenet-A.

Also, even if choose GB--> en-GB-Wavenet-C, you download a mp3 file which sounds voice of en-GB-Wavenet-A.


Secondly, I'd like to show you my code. I use Flask on Google App Engine standard environment Python3.7.

This is directory structure.

.
├── app.yaml
├── credentials.json
├── main.py
├── requirements.txt
└── templates
    └── index.html

This is main.py.

from flask import Flask
from flask import render_template
from flask import request
from flask import send_file
import os
from google.cloud import texttospeech

app = Flask(__name__)

@app.route("/", methods=['POST', 'GET'])
def index():
    if request.method == "POST":
        ssml = '<speak><prosody rate="slow">' + request.form['text'] + '</prosody></speak>'
        language = request.form['language']
        voiceid = request.form['voiceId']
        os.environ["GOOGLE_APPLICATION_CREDENTIALS"]="credentials.json"

        client = texttospeech.TextToSpeechClient()
        input_text = texttospeech.types.SynthesisInput(ssml=ssml)
        voice = texttospeech.types.VoiceSelectionParams(
            language_code=language,
            name=voiceid)

        audio_config = texttospeech.types.AudioConfig(
            audio_encoding=texttospeech.enums.AudioEncoding.MP3)

        response = client.synthesize_speech(input_text, voice, audio_config)

        # The response's audio_content is binary.
        with open('/tmp/output.mp3', 'wb') as out:
            out.write(response.audio_content)

        return send_file("/tmp/output.mp3",as_attachment=True)
    else:
        return render_template("index.html")

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

This is index.html(Some explanations are written in Japanese,sorry.).

<html>
<head>
    <style> 
         #text {width: 100%; height: 300px;}
    </style>
    <script type="text/javascript">
        // ▼HTMLの読み込み直後に実行:
        document.addEventListener('DOMContentLoaded', function() {

           // ▼2階層目の要素を全て非表示にする
           var allSubBoxes = document.getElementsByClassName("subbox");
           for( var i=0 ; i<allSubBoxes.length ; i++) {
              allSubBoxes[i].style.display = 'none';
           }

        });
    </script>
    <script type="text/javascript">
        // ▼HTMLの読み込み直後に実行:
        document.addEventListener('DOMContentLoaded', function() {

           // ▼全てのプルダウンメニューセットごとに処理
           var mainBoxes = document.getElementsByClassName('pulldownset');
           for( var i=0 ; i<mainBoxes.length ; i++) {

              var mainSelect = mainBoxes[i].getElementsByClassName("mainselect");   // 1階層目(メイン)のプルダウンメニュー(※後でvalue属性値を参照するので、select要素である必要があります。)
              mainSelect[0].onchange = function () {
                 // ▼同じ親要素に含まれているすべての2階層目(サブ)要素を消す
                 var subBox = this.parentNode.getElementsByClassName("subbox");   // 同じ親要素に含まれる.subbox(※select要素に限らず、どんな要素でも構いません。)
                 for( var j=0 ; j<subBox.length ; j++) {
                    subBox[j].style.display = 'none';
                 }

                 // ▼指定された2階層目(サブ)要素だけを表示する
                 if( this.value ) {
                    var targetSub = document.getElementById( this.value );   // 「1階層目のプルダウンメニューで選択されている項目のvalue属性値」と同じ文字列をid属性値に持つ要素を得る
                    targetSub.style.display = 'inline';
                 }
              }

           }

        });
    </script>
</head>

<body>
<form action="/" method="POST">

   <div class="pulldownset">

      <!-- ========================================== -->
      <select class="mainselect" name="language">
         <option value="">country</option>
         <option value="en-US">US</option>
         <option value="en-GB">GB</option>
      </select>

      <!-- ================================================================ -->
      <select id="en-US" class="subbox" name="voiceId">
         <option value="">voice</option>
         <option value="en-US-Wavenet-A">en-US-Wavenet-A</option>
         <option value="en-US-Wavenet-B">en-US-Wavenet-B</option>
         <option value="en-US-Wavenet-C">en-US-Wavenet-C</option>
      </select>

      <!-- ================================================================ -->
      <select id="en-GB" class="subbox" name="voiceId">
         <option value="">en-GB</option>
         <option value="en-GB-Wavenet-A">en-GB-Wavenet-A</option>
         <option value="en-GB-Wavenet-B">en-GB-Wavenet-B</option>
         <option value="en-GB-Wavenet-C">en-GB-Wavenet-C</option>
      </select>

   </div>

   <textarea id="text" name="text" placeholder="input text here"></textarea>
   <input type="submit" value="download">

</form>
</body>
</html>

This is requirements.txt.

Flask==1.1.1
future==0.18.2
google-cloud-texttospeech==0.5.0
grpcio==1.26.0
gunicorn

This is app.yaml.

runtime: python37
entrypoint: gunicorn -b :$PORT main:app

I thought this is a problem of JavaScript, so I searched the Internet. However, I couldn't get an answer.

Could you give me any information or suggestion?

Thank you in advance.

Sincerely, Kazu


回答1:


Your problem is that you have 2 select inputs with the same name.

When sending a request to the server, it only picks the first, which can be either the default for the British voice, or a American voice, if that was previously selected.

In order to use multiple inputs with the same name, you would need to specify their name with a pair of brackets like so:

<select id="en-US" class="subbox" name="voiceId[]">

And

<select id="en-GB" class="subbox" name="voiceId[]">

And on the server side you could look for the first occurrence of the language ID to determine which of the items in the list should be assigned to the variable.

So instead of:

voiceid = request.form['voiceId']

You would have something like:

voiceid = next(voice for voice in request.form.getlist('voiceId[]') if language in voice)

EDIT:

Below is an alternative without Generator Expressions.

voices = []
for voice in request.form.getlist('voiceId[]'):
    if language in voice:
        voices.append(voice)

voiceid = next(iter(voices))


来源:https://stackoverflow.com/questions/59870600/dependent-drop-down-list-doest-work-flask-on-google-app-engine

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!