Solution 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))

Problem :

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

Comments

Comment posted by Kazuaki Suzuki

Thank you very much, pessolato. Your advice worked perfectly! I also added Australian and Indian voice and my app pretty well. Thanks to your help I am able to realize my app! If you would help me, could you tell me what do “[ ]” and “next(voice for voice in request.form.getlist(‘voiceId[]’) if language in voice)” mean?

Comment posted by pessolato

Of course @KazuakiSuzuki , on this line I create a generator out of all the items that contain the language ID by using an if condition and a generator expression. Then I use the next() function to get its first value. I will update my answer to provide an alternative without using a generator expression.

Comment posted by pessolato

About the brackets (“[]”) in the input name, it is a sort of notation that some servers use to indicate when more than one input fields with the same name should be grouped together as a single server-side object.

Comment posted by Kazuaki Suzuki

I really appreciate your help @pessolato , I learnt a lot of things from your answer. Also, code without Generator Expressions is easier for me to understand. Thank you!

By