Skip to content

Commit a061766

Browse files
committed
Add IBM Watson as a STT option
1 parent e6789d7 commit a061766

1 file changed

Lines changed: 145 additions & 0 deletions

File tree

client/stt.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,151 @@ def is_available(cls):
427427
return diagnose.check_network_connection()
428428

429429

430+
class WatsonSTT(AbstractSTTEngine):
431+
"""
432+
Speech-To-Text implementation which relies on the IBM Watson Speech-To-Text
433+
API. This requires an IBM Bluemix account, but the first 1000 minutes of
434+
transcribing per month are free.
435+
436+
To obtain a login:
437+
1. Register for IBM Bluemix here:
438+
https://console.ng.bluemix.net/registration/
439+
2. Once you've logged in, click the "Use Services & APIs" link on the
440+
dashboard
441+
3. Click the "Speech To Text" icon
442+
4. In the form on the right, leave all options as defaults and click Create
443+
5. You'll now have a new service listed on your dashboard. If you click
444+
that service there will be a navigation option for "Service Credentials"
445+
in the left hand nav. Find your username and password there.
446+
447+
Excerpt from sample profile.yml:
448+
449+
...
450+
timezone: US/Pacific
451+
stt_engine: watson
452+
watson:
453+
username: $YOUR_USERNAME_HERE
454+
password: $YOUR_PASSWORD_HERE
455+
456+
"""
457+
458+
SLUG = 'watson'
459+
460+
def __init__(self, username=None, password=None, language='en-us'):
461+
# FIXME: get init args from config
462+
"""
463+
Arguments:
464+
username - the watson api username credential
465+
password - the watson api password credential
466+
"""
467+
self._logger = logging.getLogger(__name__)
468+
self._username = None
469+
self._password = None
470+
self._http = requests.Session()
471+
self.username = username
472+
self.password = password
473+
474+
@property
475+
def request_url(self):
476+
return self._request_url
477+
478+
@property
479+
def username(self):
480+
return self._username
481+
482+
@username.setter
483+
def username(self, value):
484+
self._username = value
485+
486+
@property
487+
def password(self):
488+
return self._password
489+
490+
@password.setter
491+
def password(self, value):
492+
self._password = value
493+
494+
@classmethod
495+
def get_config(cls):
496+
# FIXME: Replace this as soon as we have a config module
497+
config = {}
498+
# HMM dir
499+
# Try to get hmm_dir from config
500+
profile_path = jasperpath.config('profile.yml')
501+
if os.path.exists(profile_path):
502+
with open(profile_path, 'r') as f:
503+
profile = yaml.safe_load(f)
504+
if 'watson' in profile:
505+
if 'username' in profile['watson']:
506+
config['username'] = profile['watson']['username']
507+
if 'password' in profile['watson']:
508+
config['password'] = profile['watson']['password']
509+
return config
510+
511+
def transcribe(self, fp):
512+
"""
513+
Performs STT via the Watson Speech-to-Text API, transcribing an audio
514+
file and returning an English string.
515+
516+
Arguments:
517+
fp -- the path to the .wav file to be transcribed
518+
"""
519+
520+
if not self.username:
521+
self._logger.critical('Username missing, transcription request ' +
522+
'aborted.')
523+
return []
524+
elif not self.password:
525+
self._logger.critical('Password missing, transcription ' +
526+
'request aborted.')
527+
return []
528+
529+
wav = wave.open(fp, 'rb')
530+
frame_rate = wav.getframerate()
531+
wav.close()
532+
data = fp.read()
533+
534+
headers = {'content-type':
535+
'audio/l16; rate=%s; channels=1' % frame_rate}
536+
r = self._http.post(
537+
'https://stream.watsonplatform.net/' +
538+
'speech-to-text/api/v1/recognize?continuous=true',
539+
data=data, headers=headers, auth=(self.username, self.password)
540+
)
541+
try:
542+
r.raise_for_status()
543+
except requests.exceptions.HTTPError as e:
544+
self._logger.critical('Request failed with http status %d',
545+
r.status_code)
546+
if r.status_code == requests.codes['forbidden']:
547+
self._logger.warning('Status 403 is probably caused by ' +
548+
'invalid credentials.')
549+
return []
550+
r.encoding = 'utf-8'
551+
try:
552+
response = r.json()
553+
if len(response['results']) == 0:
554+
# Response result is empty
555+
raise ValueError('Nothing has been transcribed.')
556+
results = [alt['transcript'] for alt
557+
in response['results'][0]['alternatives']]
558+
except ValueError as e:
559+
self._logger.warning('Empty response: %s', e.args[0])
560+
results = []
561+
except (KeyError, IndexError):
562+
self._logger.warning('Cannot parse response.', exc_info=True)
563+
results = []
564+
else:
565+
# Convert all results to uppercase
566+
results = tuple(result.strip().upper() for result in results)
567+
self._logger.info('Transcribed: %r', results)
568+
return results
569+
570+
@classmethod
571+
def is_available(cls):
572+
return diagnose.check_network_connection()
573+
574+
430575
class AttSTT(AbstractSTTEngine):
431576
"""
432577
Speech-To-Text implementation which relies on the AT&T Speech API.

0 commit comments

Comments
 (0)