1

I use cURL to make a request. What it returns is suppose to be audio, but it gets stored in a ByteIO object first. My question is how do I save the ByteIO as an audio file? I have successfully saved an audio file, however, I am not able to read it with an audio player.

Here is what I have so far:

import pycurl, json
from io import BytesIO

with open('cred.json') as data_file:
    data = json.load(data_file)

user = data["credentials"]['username']
password = data["credentials"]['password']

buffer = BytesIO()
c = pycurl.Curl()
c.setopt(c.URL, "https://stream.watsonplatform.net/text-to-speech/api/v1/synthesize")
c.setopt(pycurl.HTTPHEADER, ['Content-Type: application/json', 'Accept: audio/mp3'])
c.setopt(pycurl.USERNAME, user)
c.setopt(pycurl.PASSWORD, password)
c.setopt(pycurl.POST, 1)
c.setopt(pycurl.POSTFIELDS, '{\"text\":\"Hello World\"}')
c.setopt(c.WRITEDATA, buffer)
c.perform()
c.close()

open('helloworld.mp3', 'wb').write(buffer.getvalue())

Edit: I am using cURL because I am trying to talk to a service hosted on IBM's Bluemix system. It says to use cURL to make requests to the service. What I am trying to do is use IBM's text-to-speech service. I make a request with the text to be converted to speech (the Hello World portion) and it returns the audio. However, when I save the received buffer and try to open the file, it says it cannot read it. Hope that answers your questions.

Gamegoofs2
  • 813
  • 1
  • 8
  • 15
  • You already copy the contents of the `BytesIO` object to disk. I'm not sure what the question is here? You'll need to give us more details as to how you were not able to use that file. – Martijn Pieters Sep 29 '15 at 15:13
  • Also, why use `pycurl` when there are much more Pythonic options available, such as `requests`? See [How to download image using requests](http://stackoverflow.com/q/13137817) for techniques that apply equally to audio files. – Martijn Pieters Sep 29 '15 at 15:13
  • Also, the [API documention](https://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/text-to-speech/api/v1/#synthesize) doesn't actually list MP3 as an option, and it looks like the `accept` parameter should be specified in the posted JSON request, not the headers. – Martijn Pieters Sep 29 '15 at 15:16
  • `import requests; response = requests.post('https://stream.watsonplatform.net/text-to-speech/api/v1/synthesize', auth=(user, password), json={'text': 'Hello World', 'accept': 'audio/wav'}, stream=True); if response.status_code == 200: response.raw.decode_content = True; with open('helloworld.wav', 'wb') as wavfile; shutil.copyfileobj(response.raw, wavfile)` – Martijn Pieters Sep 29 '15 at 15:22
  • @MartijnPieters Made an edit to the post hope it helps. – Gamegoofs2 Sep 29 '15 at 15:26
  • No, the documentation shows *examples* of how to call the service using curl. It doesn't dictate that you use curl in Python code. – Martijn Pieters Sep 29 '15 at 15:27
  • No it did not, but that is what I thought I would need to be able to get the information. I was not aware of requests. – Gamegoofs2 Sep 29 '15 at 15:30

1 Answers1

2

There is no option to generate MP3; the only options available are to generate Ogg, WAV or FLAC files. Specify the accept parameter as a header (as you've done with the pycurl interface) or as a URL query parameter. Posting it with the JSON parameters resulted in a 400 error for me.

Don't use the pycurl interface here. It is a hugely cumbersome API. You'd be much better of using the requests library here:

import requests
import shutil

url = 'https://stream.watsonplatform.net/text-to-speech/api/v1/synthesize'
user = 'username'
password = 'password'

json_params = {'text': 'Hello World'}
query_params = {'accept': 'audio/wav'}

response = requests.post(
    url, auth=(user, password), 
    json=json_params, params=query_params, stream=True)
if response.status_code == 200:
    response.raw.decode_content = True
    with open('helloworld.wav', 'wb') as wavfile:
        shutil.copyfileobj(response.raw, wavfile)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Thanks, requests looks much easier to use than cURL. One question, is there a limit to the size the text parameter can be? – Gamegoofs2 Sep 29 '15 at 15:38
  • @Gamegoofs2: I saw 5kb mentioned. – Martijn Pieters Sep 29 '15 at 15:42
  • @Gamegoofs2: from the [public documentation](https://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/text-to-speech/api/v1/#synthesize): *Text to synthesize. Text size limited to 5KB.* – Martijn Pieters Sep 29 '15 at 15:52
  • have you considered `response.raise_for_status()` instead of `if response.status_code == 200:`, to avoid silent errors? – jfs Sep 30 '15 at 01:30
  • @J.F.Sebastian: that depends entirely on the context of the application and the url called if raising an exception is appropriate. – Martijn Pieters Sep 30 '15 at 07:34
  • @MartijnPieters: I agree. The disagreement is what should be the default. `python -m this` says *"Errors should never pass silently."* i.e., unless an application has specific reasons to silence the errors; they shouldn't be ignored. – jfs Sep 30 '15 at 07:40
  • @J.F.Sebastian: sure, but it is up to the application to decide what status codes should be treated as an error in their own code or can be handled with an `if` statement. :-) – Martijn Pieters Sep 30 '15 at 07:49
  • @MartijnPieters: yes and I've already agreed. You should expect your code to be copied and therefore if you **don't know** specific needs of the application; the **default behavior** should be to raise an exception. – jfs Sep 30 '15 at 07:55