├── README.md └── speechmatics.py /README.md: -------------------------------------------------------------------------------- 1 | # speechmatics_python 2 | Example script (supported) to help you integrate with our API 3 | 4 | This code was developed and tested with Python 2.7.12 and Python 3.5.2 5 | 6 | This is an example script for uploading audio files and saving the transcript in a .json file 7 | 8 | ## Requirements 9 | You will need to have the requests module installed in your Python environment. 10 | ``` 11 | pip install requests 12 | ``` 13 | 14 | ## Example usage: 15 | ``` 16 | python ./speechmatics.py -a example.mp3 -l en-US -i $user_id -k $auth_token -o example.json 17 | ``` 18 | 19 | In this example the script uploads 'example.mp3', transcribes it using our en-US speech to text product and saves the resulting transcription as 'example.json' when the job has completed. 20 | -------------------------------------------------------------------------------- /speechmatics.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example script for integrating with the Speechmatics API 3 | """ 4 | 5 | import codecs 6 | import json 7 | import logging 8 | import time 9 | from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter 10 | import requests 11 | 12 | 13 | class SpeechmaticsError(Exception): 14 | """ 15 | For errors that are specific to Speechmatics systems and pipelines. 16 | """ 17 | 18 | def __init__(self, msg, returncode=1): 19 | super(SpeechmaticsError, self).__init__(msg) 20 | self.msg = msg 21 | self.returncode = returncode 22 | 23 | def __str__(self): 24 | return self.msg 25 | 26 | 27 | class SpeechmaticsClient(object): 28 | """ 29 | A simple client to interact with the Speechmatics REST API 30 | Documentation at https://app.speechmatics.com/api-details 31 | """ 32 | 33 | def __init__(self, api_user_id, api_token, base_url='https://api.speechmatics.com/v1.0'): 34 | self.api_user_id = api_user_id 35 | self.api_token = api_token 36 | self.base_url = base_url 37 | 38 | def job_post(self, opts): 39 | """ 40 | Upload a new audio file to speechmatics for transcription 41 | If text file is specified upload that as well for an alignment job 42 | If upload suceeds then this method will return the id of the new job 43 | 44 | If succesful returns an integer representing the job id 45 | """ 46 | 47 | url = "".join([self.base_url, '/user/', self.api_user_id, '/jobs/']) 48 | params = {'auth_token': self.api_token} 49 | try: 50 | files = {'data_file': open(opts.audio, "rb")} 51 | except IOError as ex: 52 | logging.error("Problem opening audio file {}".format(opts.audio)) 53 | raise 54 | 55 | if opts.text: 56 | try: 57 | files['text_file'] = open(opts.text, "rb") 58 | except IOError as ex: 59 | logging.error("Problem opening text file {}".format(opts.text)) 60 | raise 61 | 62 | data = {"model": opts.lang} 63 | if "=" in data['model']: 64 | (data['model'], data['version']) = data['model'].split('=', 1) 65 | 66 | if opts.notification: 67 | data['notification'] = opts.notification 68 | if opts.notification == 'callback': 69 | data['callback'] = opts.callback_url 70 | if opts.notification_email: 71 | data['notification_email_address'] = opts.notification_email 72 | 73 | request = requests.post(url, data=data, files=files, params=params) 74 | if request.status_code == 200: 75 | json_out = json.loads(request.text) 76 | return json_out['id'] 77 | else: 78 | err_msg = "Attempt to POST job failed with code {}\n".format(request.status_code) 79 | if request.status_code == 400: 80 | err_msg += ("Common causes of this error are:\n" 81 | "Malformed arguments\n" 82 | "Missing data file\n" 83 | "Absent / unsupported language selection.") 84 | elif request.status_code == 401: 85 | err_msg += ("Common causes of this error are:\n" 86 | "Invalid user id or authentication token.") 87 | elif request.status_code == 403: 88 | err_msg += ("Common causes of this error are:\n" 89 | "Insufficient credit\n" 90 | "User id not in our database\n" 91 | "Incorrect authentication token.") 92 | elif request.status_code == 429: 93 | err_msg += ("Common causes of this error are:\n" 94 | "You are submitting too many POSTs in a short period of time.") 95 | elif request.status_code == 503: 96 | err_msg += ("Common causes of this error are:\n" 97 | "The system is temporarily unavailable or overloaded.\n" 98 | "Your POST will typically succeed if you try again soon.") 99 | err_msg += ("\nIf you are still unsure why your POST failed please contact speechmatics:" 100 | "support@speechmatics.com") 101 | raise SpeechmaticsError(err_msg) 102 | 103 | def job_details(self, job_id): 104 | """ 105 | Checks on the status of the given job. 106 | 107 | If successfuly returns a dictionary of job details. 108 | """ 109 | params = {'auth_token': self.api_token} 110 | url = "".join([self.base_url, '/user/', self.api_user_id, '/jobs/', str(job_id), '/']) 111 | request = requests.get(url, params=params) 112 | if request.status_code == 200: 113 | return json.loads(request.text)['job'] 114 | else: 115 | err_msg = ("Attempt to GET job details failed with code {}\n" 116 | "If you are still unsure why your POST failed please contact speechmatics:" 117 | "support@speechmatics.com").format(request.status_code) 118 | raise SpeechmaticsError(err_msg) 119 | 120 | def get_output(self, job_id, frmat, job_type): 121 | """ 122 | Downloads transcript for given transcription job. 123 | 124 | If successful returns the output. 125 | """ 126 | params = {'auth_token': self.api_token} 127 | if frmat and job_type == 'transcript': 128 | params['format'] = 'txt' 129 | if frmat and job_type == 'alignment': 130 | params['tags'] = 'one_per_line' 131 | url = "".join([self.base_url, '/user/', self.api_user_id, '/jobs/', str(job_id), '/', job_type]) 132 | request = requests.get(url, params=params) 133 | if request.status_code == 200: 134 | request.encoding = 'utf-8' 135 | return request.text 136 | else: 137 | err_msg = ("Attempt to GET job details failed with code {}\n" 138 | "If you are still unsure why your POST failed please contact speechmatics:" 139 | "support@speechmatics.com").format(request.status_code) 140 | raise SpeechmaticsError(err_msg) 141 | 142 | 143 | def parse_args(): 144 | """ 145 | Parse command line arguments 146 | """ 147 | 148 | # Parse the arguments 149 | parser = ArgumentParser( 150 | description='Processes a job through the Speechmatics API', 151 | formatter_class=ArgumentDefaultsHelpFormatter, 152 | ) 153 | parser.add_argument('-a', '--audio', type=str, required=True, 154 | help="Audio file to be processed") 155 | parser.add_argument('-t', '--text', type=str, required=False, 156 | help="Text file to be processed (only required for alignment jobs)", default=None) 157 | parser.add_argument('-o', '--output', type=str, required=False, 158 | help="Output filename (will print to terminal if not specified)", default=None) 159 | parser.add_argument('-i', '--id', type=str, required=True, 160 | help="Your Speechmatics user_id") 161 | parser.add_argument('-k', '--token', type=str, required=True, 162 | help="Your Speechmatics API Authentication Token") 163 | parser.add_argument('-l', '--lang', type=str, required=True, 164 | help="Code of language to use (e.g., en-US)") 165 | parser.add_argument('-f', '--format', action='store_true', required=False, 166 | help="Return results in alternate format.\n" 167 | "Default for transcription is json, alternate is text.\n" 168 | "Default for alignment is one timing per word, alternate is one per line") 169 | parser.add_argument('-n', '--notification', type=str, choices=['email', 'none', 'callback'], required=False, 170 | help="Type of notification to use (default is 'email').\n", default=None) 171 | parser.add_argument('-e', '--notification-email', type=str, required=False, 172 | help="Alternative email address to send notification to upon job completion.\n", default=None) 173 | parser.add_argument('-u', '--callback-url', type=str, required=False, 174 | help="Callback URL to use.", default=None) 175 | parsed = parser.parse_args() 176 | 177 | if parsed.notification_email is not None and parsed.notification in ['none', 'callback']: 178 | parser.error("You specified an alternative email address but selected '{}' notification.".format(parsed.notification)) 179 | if parsed.notification == 'callback' and parsed.callback_url is None: 180 | parser.error("You selected notification type of callback but did not provide a callback URL.") 181 | 182 | return parsed 183 | 184 | 185 | def main(): 186 | """ 187 | Example way to use the Speechmatics Client to process a job 188 | """ 189 | logging.basicConfig(level=logging.INFO) 190 | 191 | opts = parse_args() 192 | 193 | client = SpeechmaticsClient(opts.id, opts.token) 194 | 195 | job_id = client.job_post(opts) 196 | logging.info("Your job has started with ID {}".format(job_id)) 197 | 198 | details = client.job_details(job_id) 199 | 200 | while details[u'job_status'] not in ['done', 'expired', 'unsupported_file_format', 'could_not_align']: 201 | logging.info("Waiting for job to be processed. Will check again in {} seconds".format(details['check_wait'])) 202 | wait_s = details['check_wait'] 203 | time.sleep(wait_s) 204 | details = client.job_details(job_id) 205 | 206 | if details['job_status'] == 'unsupported_file_format': 207 | raise SpeechmaticsError("File was in an unsupported file format and could not be transcribed. " 208 | "You have been reimbursed all credits for this job.") 209 | 210 | if details['job_status'] == 'could_not_align': 211 | raise SpeechmaticsError("Could not align text and audio file. " 212 | "You have been reimbursed all credits for this job.") 213 | 214 | logging.info("Processing complete, getting output") 215 | 216 | if details['job_type'] == 'transcription': 217 | job_type = 'transcript' 218 | elif details['job_type'] == 'alignment': 219 | job_type = 'alignment' 220 | output = client.get_output(job_id, opts.format, job_type) 221 | 222 | if opts.output: 223 | with codecs.open(opts.output, 'wt', 'utf-8') as ouf: 224 | if job_type == 'transcript' and opts.format: 225 | ouf.write(json.dumps(output, indent=4)) 226 | else: 227 | ouf.write(output) 228 | logging.info("Your job output has been written to file {}".format(opts.output)) 229 | else: 230 | logging.info("Your job output:") 231 | if job_type == 'transcript' and opts.format: 232 | print(json.dumps(output, indent=4)) 233 | else: 234 | print(output.encode('utf-8')) 235 | 236 | 237 | if __name__ == '__main__': 238 | main() 239 | --------------------------------------------------------------------------------