├── .bluemix └── pipeline.yml ├── .cfignore ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── Procfile ├── README.md ├── app.js ├── casper-runner.js ├── config ├── error-handler.js ├── express.js └── security.js ├── manifest.yml ├── package-lock.json ├── package.json ├── public ├── audio │ ├── dieter_17.wav │ ├── dieter_32.wav │ ├── francesca_it_lt_05.wav │ ├── francesca_it_lt_19.wav │ ├── isabela_10.wav │ ├── isabela_38.wav │ ├── lisa_17.wav │ ├── lisa_18.wav │ ├── michael_05.wav │ ├── michael_25.wav │ ├── sample_Emily_01.wav │ ├── sample_Erika_03.wav │ ├── sample_Henry_02.wav │ ├── sample_Kevin_04.wav │ ├── sample_Olivia_03.wav │ ├── sofia_24.wav │ └── sofia_27.wav ├── css │ └── style.css ├── images │ ├── android-icon-192x192.png │ ├── apple-icon-180x180.png │ ├── errow.svg │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ └── service-icon.svg └── js │ └── bundle.jsx ├── server.js ├── test ├── integration │ └── test.mainpage.js └── unit │ ├── express.test.js │ └── react.test.js ├── views ├── demo.jsx ├── index.jsx └── layout.jsx └── voices.js /.bluemix/pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | stages: 3 | - name: Build Stage 4 | inputs: 5 | - type: git 6 | branch: master 7 | service: ${REPO} 8 | jobs: 9 | - name: Build 10 | type: builder 11 | artifact_dir: '' 12 | - name: Deploy Stage 13 | inputs: 14 | - type: job 15 | stage: Build Stage 16 | job: Build 17 | triggers: 18 | - type: stage 19 | jobs: 20 | - name: Deploy 21 | type: deployer 22 | target: 23 | region_id: ${CF_REGION_ID} 24 | organization: ${CF_ORGANIZATION} 25 | space: ${CF_SPACE} 26 | application: ${CF_APP} 27 | script: |- 28 | #!/bin/bash 29 | cf create-service text_to_speech standard my-text-to-speech 30 | # Push app 31 | export CF_APP_NAME="$CF_APP" 32 | cf push "${CF_APP_NAME}" 33 | -------------------------------------------------------------------------------- /.cfignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | 2 | TEXT_TO_SPEECH_IAM_APIKEY= 3 | TEXT_TO_SPEECH_URL=https://stream.watsonplatform.net/text-to-speech/api 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | public/js/analytics.js -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: airbnb 2 | env: 3 | browser: true 4 | node: true 5 | mocha: true 6 | rules: 7 | global-require: 0 8 | no-console: 0 9 | react/forbid-prop-types: 1 10 | react/no-multi-comp: 0 11 | jsx-a11y/media-has-caption: 0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .env 4 | *.log 5 | .DS_Store 6 | /.idea/ 7 | *~ 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: required 3 | node_js: 12 4 | script: 5 | - npm run test 6 | 7 | cache: 8 | directories: 9 | - node_modules 10 | env: 11 | global: 12 | - BX_APP=text-to-speech-demo 13 | - BX_API=https://api.ng.bluemix.net 14 | - BX_ORGANIZATION=WatsonPlatformServices 15 | - BX_SPACE=demos 16 | before_deploy: npm install -g bx-blue-green 17 | deploy: 18 | - provider: script 19 | skip_cleanup: true 20 | script: bx-blue-green-travis 21 | on: 22 | branch: master 23 | repo: watson-developer-cloud/text-to-speech-nodejs 24 | - provider: script 25 | skip_cleanup: true 26 | script: npx semantic-release 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Questions 2 | 3 | If you are having problems using the APIs or have a question about the IBM 4 | Watson Services, please ask a question on 5 | [dW Answers](https://developer.ibm.com/answers/questions/ask/?topics=watson) 6 | or [Stack Overflow](http://stackoverflow.com/questions/ask?tags=ibm-watson). 7 | 8 | # Code 9 | 10 | * Our style guide is based on [Google's](https://google.github.io/styleguide/jsguide.html), most of it is automaticaly enforced (and can be automatically applied with `npm run autofix`) 11 | * Commits should follow the [Angular commit message guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines). This is because our release tool uses this format for determining release versions and generating changelogs. To make this easier, we recommend using the [Commitizen CLI](https://github.com/commitizen/cz-cli) with the `cz-conventional-changelog` adapter. 12 | 13 | # Issues 14 | 15 | If you encounter an issue with the Node.js library, you are welcome to submit 16 | a [bug report](https://github.com/watson-developer-cloud/text-to-speech-nodejs/issues). 17 | Before that, please search for similar issues. It's possible somebody has 18 | already encountered this issue. 19 | 20 | # Pull Requests 21 | 22 | If you want to contribute to the repository, follow these steps: 23 | 24 | 1. Fork the repo. 25 | 2. Develop and test your code changes: `npm install -d && npm test`. 26 | 3. Travis-CI will run the tests for all services once your changes are merged. 27 | 4. Add a test for your changes. Only refactoring and documentation changes require no new tests. 28 | 5. Make the test pass. 29 | 6. Commit your changes. 30 | 7. Push to your fork and submit a pull request. 31 | 32 | # Developer's Certificate of Origin 1.1 33 | 34 | By making a contribution to this project, I certify that: 35 | 36 | (a) The contribution was created in whole or in part by me and I 37 | have the right to submit it under the open source license 38 | indicated in the file; or 39 | 40 | (b) The contribution is based upon previous work that, to the best 41 | of my knowledge, is covered under an appropriate open source 42 | license and I have the right under that license to submit that 43 | work with modifications, whether created in whole or in part 44 | by me, under the same open source license (unless I am 45 | permitted to submit under a different license), as indicated 46 | in the file; or 47 | 48 | (c) The contribution was provided directly to me by some other 49 | person who certified (a), (b) or (c) and I have not modified 50 | it. 51 | 52 | (d) I understand and agree that this project and the contribution 53 | are public and that a record of the contribution (including all 54 | personal information I submit with it, including my sign-off) is 55 | maintained indefinitely and may be redistributed consistent with 56 | this project or the open source license(s) involved. 57 | 58 | ## Tests 59 | 60 | Ideally, we'd like to see both unit and integration tests on each method. 61 | (Unit tests do not actually connect to the Watson service, integration tests do.) 62 | 63 | Out of the box, `npm test` runs linting and unit tests, but skips the integration tests, 64 | because they require credentials. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

# DEPRECATED

2 |

3 | This demo and repo is no longer supported. You can find the newly supported Text to Speech Demo 4 | 5 | here. 6 | 7 |

8 |

🔊 Text to Speech Demo

9 |

Node.js sample applications that shows some of the the IBM Watson Text to Speech service features.

10 |

11 | 12 | Travis 13 | 14 | 15 | semantic-release 16 | 17 |

18 |

19 | 20 | Text to Speech is designed for streaming, low latency, synthesis of audio from text. It is the inverse of the automatic speech recognition. 21 | 22 | You can view a [demo][demo_url] of this app. 23 | 24 | 25 | ## Prerequisites 26 | 27 | 1. Sign up for an [IBM Cloud account](https://cloud.ibm.com/registration/). 28 | 1. Download the [IBM Cloud CLI](https://cloud.ibm.com/docs/cli?topic=cli-getting-started#overview). 29 | 1. Create an instance of the Text to Speech service and get your credentials: 30 | - Go to the [Text to Speech](https://cloud.ibm.com/catalog/services/text-to-speech) page in the IBM Cloud Catalog. 31 | - Log in to your IBM Cloud account. 32 | - Click **Create**. 33 | - Click **Show** to view the service credentials. 34 | - Copy the `apikey` value. 35 | - Copy the `url` value. 36 | 37 | ## Configuring the application 38 | 39 | 1. In the application folder, copy the *.env.example* file and create a file called *.env* 40 | 41 | ``` 42 | cp .env.example .env 43 | ``` 44 | 45 | 2. Open the *.env* file and add the service credentials that you obtained in the previous step. 46 | 47 | Example *.env* file that configures the `apikey` and `url` for a Text to Speech service instance hosted in the US East region: 48 | 49 | ``` 50 | TEXT_TO_SPEECH_IAM_APIKEY=X4rbi8vwZmKpXfowaS3GAsA7vdy17Qh7km5D6EzKLHL2 51 | TEXT_TO_SPEECH_URL=https://gateway-wdc.watsonplatform.net/text-to-speech/api 52 | ``` 53 | 54 | ## Running locally 55 | 56 | 1. Install the dependencies 57 | 58 | ``` 59 | npm install 60 | ``` 61 | 62 | 1. Run the application 63 | 64 | ``` 65 | npm start 66 | ``` 67 | 68 | 1. View the application in a browser at `localhost:3000` 69 | 70 | ## Deploying to IBM Cloud as a Cloud Foundry Application 71 | 72 | 1. Login to IBM Cloud with the [IBM Cloud CLI](https://cloud.ibm.com/docs/cli?topic=cli-getting-started#overview) 73 | 74 | ``` 75 | ibmcloud login 76 | ``` 77 | 78 | 1. Target a Cloud Foundry organization and space. 79 | 80 | ``` 81 | ibmcloud target --cf 82 | ``` 83 | 84 | 1. Edit the *manifest.yml* file. Change the **name** field to something unique. For example, `- name: my-app-name`. 85 | 1. Deploy the application 86 | 87 | ``` 88 | ibmcloud app push 89 | ``` 90 | 91 | 1. View the application online at the app URL, for example: https://my-app-name.mybluemix.net 92 | 93 | 94 | 95 | ## Directory structure 96 | 97 | ```none 98 | . 99 | ├── app.js // express routes 100 | ├── config // express configuration 101 | │ ├── error-handler.js 102 | │ ├── express.js 103 | │ └── security.js 104 | ├── manifest.yml 105 | ├── package.json 106 | ├── public // static resources 107 | ├── server.js // entry point 108 | ├── test // tests 109 | └── views // react components 110 | ``` 111 | 112 | ## License 113 | 114 | This sample code is licensed under Apache 2.0. 115 | 116 | ## Contributing 117 | 118 | See [CONTRIBUTING](./CONTRIBUTING.md). 119 | 120 | ## Open Source @ IBM 121 | Find more open source projects on the [IBM Github Page](http://ibm.github.io/) 122 | 123 | 124 | [service_url]: https://www.ibm.com/watson/services/text-to-speech/ 125 | [docs]: https://cloud.ibm.com/apidocs/text-to-speech 126 | [sign_up]: https://cloud.ibm.com/registration/?target=/catalog/services/text-to-speech/ 127 | [demo_url]: https://text-to-speech-demo.ng.bluemix.net 128 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const app = express(); 4 | const TextToSpeechV1 = require('ibm-watson/text-to-speech/v1.js'); 5 | const { IamAuthenticator } = require('ibm-watson/auth'); 6 | 7 | const textToSpeech = new TextToSpeechV1({ 8 | version: '2018-04-05', 9 | authenticator: new IamAuthenticator({ 10 | apikey: process.env.TEXT_TO_SPEECH_IAM_APIKEY || 'type-key-here', 11 | }), 12 | url: process.env.TEXT_TO_SPEECH_URL, 13 | }); 14 | 15 | // Bootstrap application settings 16 | require('./config/express')(app); 17 | 18 | const getFileExtension = (acceptQuery) => { 19 | const accept = acceptQuery || ''; 20 | switch (accept) { 21 | case 'audio/ogg;codecs=opus': 22 | case 'audio/ogg;codecs=vorbis': 23 | return 'ogg'; 24 | case 'audio/wav': 25 | return 'wav'; 26 | case 'audio/mpeg': 27 | return 'mpeg'; 28 | case 'audio/webm': 29 | return 'webm'; 30 | case 'audio/flac': 31 | return 'flac'; 32 | default: 33 | return 'mp3'; 34 | } 35 | }; 36 | 37 | app.get('/', (req, res) => { 38 | res.render('index'); 39 | }); 40 | 41 | /** 42 | * Pipe the synthesize method 43 | */ 44 | app.get('/api/v3/synthesize', async (req, res, next) => { 45 | try { 46 | const { result } = await textToSpeech.synthesize(req.query); 47 | const transcript = result; 48 | transcript.on('response', (response) => { 49 | if (req.query.download) { 50 | response.headers['content-disposition'] = `attachment; filename=transcript.${getFileExtension(req.query.accept)}`; 51 | } 52 | }); 53 | transcript.on('error', next); 54 | transcript.pipe(res); 55 | } catch (error) { 56 | res.send(error); 57 | } 58 | }); 59 | 60 | // Return the list of voices 61 | app.get('/api/v2/voices', async (req, res, next) => { 62 | try { 63 | const { result } = textToSpeech.listVoices(); 64 | res.json(result); 65 | } catch (error) { 66 | next(error); 67 | } 68 | }); 69 | 70 | // error-handler settings 71 | require('./config/error-handler')(app); 72 | 73 | module.exports = app; 74 | -------------------------------------------------------------------------------- /casper-runner.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | if (!process.env.TEXT_TO_SPEECH_USERNAME && !process.env.TEXT_TO_SPEECH_IAM_APIKEY) { 4 | console.log('Skipping integration tests because TEXT_TO_SPEECH_USERNAME and TEXT_TO_SPEECH_IAM_APIKEY are null'); 5 | process.exit(0); 6 | } 7 | 8 | const spawn = require('child_process').spawn; // eslint-disable-line 9 | const app = require('./app'); 10 | 11 | const port = 3000; 12 | 13 | const server = app.listen(port, () => { 14 | console.log('Server running on port: %d', port); 15 | 16 | function kill(code) { 17 | server.close(() => { 18 | // eslint-disable-next-line no-process-exit 19 | process.exit(code); 20 | }); 21 | } 22 | 23 | function runTests() { 24 | const casper = spawn('npm', ['run', 'test-integration']); 25 | casper.stdout.pipe(process.stdout); 26 | 27 | casper.on('error', (error) => { 28 | // eslint-disable-next-line no-console 29 | console.log(`ERROR: ${error}`); 30 | server.close(() => { 31 | process.exit(1); 32 | }); 33 | }); 34 | 35 | casper.on('close', kill); 36 | } 37 | 38 | runTests(); 39 | }); 40 | -------------------------------------------------------------------------------- /config/error-handler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | module.exports = (app) => { 19 | // catch 404 and forward to error handler 20 | app.use((req, res, next) => { 21 | const err = new Error('Not Found'); 22 | err.code = 404; 23 | err.message = 'Not Found'; 24 | next(err); 25 | }); 26 | 27 | // error handler 28 | // eslint-disable-next-line 29 | app.use((err, req, res, next) => { 30 | const error = { 31 | code: err.code || 500, 32 | error: err.error || err.message, 33 | }; 34 | res.status(error.code).json(error); 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /config/express.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Module dependencies 18 | const express = require('express'); 19 | const bodyParser = require('body-parser'); 20 | const expressBrowserify = require('express-browserify'); 21 | const path = require('path'); 22 | const morgan = require('morgan'); 23 | 24 | module.exports = (app) => { 25 | app.enable('trust proxy'); 26 | app.set('view engine', 'jsx'); 27 | app.engine('jsx', require('express-react-views').createEngine()); 28 | 29 | 30 | // Only loaded when running in IBM Cloud 31 | if (process.env.VCAP_APPLICATION) { 32 | require('./security')(app); 33 | } 34 | 35 | // automatically bundle the front-end js on the fly 36 | // note: this should come before the express.static since bundle.js is in the public folder 37 | const isDev = (app.get('env') === 'development'); 38 | const browserifyier = expressBrowserify('./public/js/bundle.jsx', { 39 | watch: isDev, 40 | debug: isDev, 41 | extension: ['jsx'], 42 | transform: ['babelify'], 43 | }); 44 | if (!isDev) { 45 | browserifyier.browserify.transform('uglifyify', { global: true }); 46 | } 47 | app.get('/js/bundle.js', browserifyier); 48 | 49 | // Configure Express 50 | app.use(bodyParser.json({ limit: '1mb' })); 51 | app.use(bodyParser.urlencoded({ extended: false })); 52 | app.use(express.static(path.join(__dirname, '..', 'public'))); 53 | app.use(express.static(path.join(__dirname, '..', 'node_modules/watson-react-components/dist/'))); 54 | app.use(morgan('dev')); 55 | }; 56 | -------------------------------------------------------------------------------- /config/security.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | // security.js 19 | const secure = require('express-secure-only'); 20 | const helmet = require('helmet'); 21 | const rateLimit = require('express-rate-limit'); 22 | 23 | module.exports = (app) => { 24 | app.use(secure()); 25 | app.use(helmet()); 26 | 27 | const limiter = rateLimit({ 28 | windowMs: 60 * 1000, // seconds 29 | delayMs: 0, 30 | max: 4, 31 | message: JSON.stringify({ 32 | error: 'Too many requests, please try again in 30 seconds.', 33 | code: 429, 34 | }), 35 | }); 36 | app.use('/api/', limiter); 37 | }; 38 | -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: text-to-speech-demo 4 | command: npm start 5 | path: . 6 | memory: 512M 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ibm-watson/text-to-speech-nodejs", 3 | "version": "2.0.0", 4 | "description": "IBM Watson Text to Speech sample application", 5 | "engines": { 6 | "node": ">=8.0" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/watson-developer-cloud/text-to-speech-nodejs.git" 11 | }, 12 | "author": "IBM Corp.", 13 | "license": "Apache-2.0", 14 | "bugs": { 15 | "url": "https://github.com/watson-developer-cloud/text-to-speech-nodejs/issues" 16 | }, 17 | "dependencies": { 18 | "@babel/core": "^7.7.2", 19 | "@babel/preset-env": "^7.7.1", 20 | "@babel/preset-react": "^7.7.0", 21 | "@babel/register": "^7.7.0", 22 | "babel-eslint": "^10.0.3", 23 | "babelify": "^10.0.0", 24 | "body-parser": "^1.19.0", 25 | "casperjs": "^1.1.4", 26 | "core-js": "^3.4.0", 27 | "dotenv": "^8.2.0", 28 | "express": "^4.17.1", 29 | "express-browserify": "^1.0.3", 30 | "express-rate-limit": "^5.0.0", 31 | "express-react-views": "^0.11.0", 32 | "express-secure-only": "^0.2.1", 33 | "helmet": "^3.21.2", 34 | "ibm-watson": "^5.1.0", 35 | "morgan": "^1.9.1", 36 | "prop-types": "^15.7.2", 37 | "react": "^16.11.0", 38 | "react-dom": "^16.11.0", 39 | "uglifyify": "^5.0.2", 40 | "watson-react-components": "^0.6.19", 41 | "whatwg-fetch": "2.0.4" 42 | }, 43 | "scripts": { 44 | "start": "node server.js", 45 | "test-integration": "casperjs test ./test/integration/test.*.js", 46 | "test-integration-runner": "NODE_ENV=test node casper-runner.js", 47 | "test": "npm run lint && npm run test-unit && npm run test-integration-runner", 48 | "test-unit": "mocha test/unit --exit", 49 | "lint": "eslint .", 50 | "autofix": "eslint --fix .", 51 | "codecov": "npm run test && (codecov || true)" 52 | }, 53 | "devDependencies": { 54 | "casperjs": "^1.1.4", 55 | "codecov": "^3.6.5", 56 | "eslint": "^6.6.0", 57 | "eslint-config-airbnb": "^18.0.1", 58 | "eslint-plugin-import": "^2.18.2", 59 | "eslint-plugin-jsx-a11y": "^6.2.3", 60 | "eslint-plugin-react": "^7.16.0", 61 | "mocha": "^6.2.2", 62 | "phantomjs-prebuilt": "^2.1.16", 63 | "supertest": "^4.0.2" 64 | }, 65 | "babel": { 66 | "presets": [ 67 | [ 68 | "@babel/preset-env", 69 | { 70 | "useBuiltIns": "entry", 71 | "corejs": "3.0.0" 72 | } 73 | ], 74 | "@babel/preset-react" 75 | ] 76 | }, 77 | "publishConfig": { 78 | "registry": "https://registry.npmjs.org/", 79 | "access": "public" 80 | }, 81 | "private": true 82 | } 83 | -------------------------------------------------------------------------------- /public/audio/dieter_17.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/dieter_17.wav -------------------------------------------------------------------------------- /public/audio/dieter_32.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/dieter_32.wav -------------------------------------------------------------------------------- /public/audio/francesca_it_lt_05.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/francesca_it_lt_05.wav -------------------------------------------------------------------------------- /public/audio/francesca_it_lt_19.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/francesca_it_lt_19.wav -------------------------------------------------------------------------------- /public/audio/isabela_10.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/isabela_10.wav -------------------------------------------------------------------------------- /public/audio/isabela_38.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/isabela_38.wav -------------------------------------------------------------------------------- /public/audio/lisa_17.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/lisa_17.wav -------------------------------------------------------------------------------- /public/audio/lisa_18.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/lisa_18.wav -------------------------------------------------------------------------------- /public/audio/michael_05.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/michael_05.wav -------------------------------------------------------------------------------- /public/audio/michael_25.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/michael_25.wav -------------------------------------------------------------------------------- /public/audio/sample_Emily_01.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/sample_Emily_01.wav -------------------------------------------------------------------------------- /public/audio/sample_Erika_03.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/sample_Erika_03.wav -------------------------------------------------------------------------------- /public/audio/sample_Henry_02.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/sample_Henry_02.wav -------------------------------------------------------------------------------- /public/audio/sample_Kevin_04.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/sample_Kevin_04.wav -------------------------------------------------------------------------------- /public/audio/sample_Olivia_03.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/sample_Olivia_03.wav -------------------------------------------------------------------------------- /public/audio/sofia_24.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/sofia_24.wav -------------------------------------------------------------------------------- /public/audio/sofia_27.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/audio/sofia_27.wav -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | .textarea { 2 | width: 100%; 3 | border: 0px; 4 | min-height: 300px; 5 | resize: none; 6 | overflow: scroll; 7 | } 8 | 9 | .tab-panels--tab-content { 10 | padding: 1rem; 11 | } 12 | 13 | .speak-button { 14 | margin-left: 1rem; 15 | } 16 | 17 | .speak-button:hover, .download-button:hover { 18 | color: white; 19 | background-color: #55306f; 20 | border: 2px solid #55306f; 21 | } 22 | 23 | .speak-button:focus, .download-button:focus { 24 | outline: none; 25 | } 26 | 27 | .speak-button[disabled], .speak-button[disabled]:hover, .download-button[disabled], .download-button[disabled]:hover { 28 | color: grey; 29 | border: 2px solid grey; 30 | transition: 0s; 31 | } 32 | 33 | .base--select { 34 | max-width: 100%; 35 | background-image: url(/images/errow.svg); 36 | } 37 | 38 | .reset-button { 39 | margin-left: 0.5rem; 40 | cursor: pointer; 41 | } 42 | 43 | .reset-container { 44 | text-align: right; 45 | width: 25%; 46 | } 47 | 48 | .audio { 49 | width: 100%; 50 | margin-top: 1rem; 51 | margin-bottom: 1rem; 52 | } 53 | 54 | .controls-container { 55 | display: flex; 56 | } 57 | 58 | .output-container { 59 | margin-bottom: 4rem; 60 | } 61 | 62 | .buttons-container { 63 | width: 75%; 64 | } 65 | 66 | .errorMessage { 67 | text-align: center; 68 | color: red; 69 | } 70 | 71 | .invisible { 72 | opacity: 0; 73 | } 74 | 75 | .hidden { 76 | display: none; 77 | } 78 | 79 | .text-center { 80 | text-align: center; 81 | } 82 | 83 | .base--h2.title { 84 | margin-top: 2rem; 85 | font-size: 1.5em; 86 | color: #965ad0; 87 | } 88 | 89 | .dimmed { 90 | opacity: 0.5; 91 | } 92 | 93 | .speak-button.loading { 94 | background-color: darkgrey; 95 | border: 2px solid darkgrey; 96 | } 97 | 98 | .reset-container { 99 | font-size: smaller; 100 | } 101 | 102 | .voice-input { 103 | width: 80%; 104 | margin-top: 4rem; 105 | margin-bottom: 4rem; 106 | } 107 | 108 | .gdpr-info { 109 | font-size: 10pt; 110 | margin-top: 1em; 111 | } 112 | 113 | @media (max-width: 888px) { 114 | .voice-input { 115 | width: 100%; 116 | margin-top: 1rem; 117 | margin-bottom: 1rem; 118 | } 119 | } 120 | 121 | div.row { 122 | width: 75%; 123 | } 124 | 125 | .normalfont { 126 | font-size: 1em; 127 | font-weight: 300; 128 | font-family: "Helvetica Neue", Helvetica, "Open Sans", Arial, "Lucida Grande", Roboto, sans-serif; 129 | } 130 | 131 | ul.tab-panels--tab-list:hover li a.active:not(:hover) { 132 | border-bottom: none; 133 | } 134 | 135 | select.base--select::-ms-expand { 136 | display: none; 137 | } 138 | 139 | select.base--select:focus { 140 | outline: none; 141 | } 142 | 143 | @media (max-width: 1199px) { 144 | span.caret { 145 | left: 92% !important; 146 | } 147 | button#dropdownMenu1 { 148 | width: 97% !important; 149 | } 150 | #dropdownMenuList { 151 | width: 97%; 152 | } 153 | } 154 | @media (max-width: 991px) { 155 | span.caret { 156 | left: 97% !important; 157 | } 158 | button#dropdownMenu1 { 159 | width: 80% !important; 160 | } 161 | span.caret { 162 | left: 75% !important; 163 | } 164 | #dropdownMenuList { 165 | width: 80%; 166 | } 167 | 168 | } 169 | @media (max-width: 572px) { 170 | span.caret { 171 | left: 73% !important; 172 | } 173 | #dropdownMenuList { 174 | width: 80% !important; 175 | overflow: hidden; 176 | } 177 | .dropdown-text { 178 | white-space: initial !important; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /public/images/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/images/android-icon-192x192.png -------------------------------------------------------------------------------- /public/images/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/images/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/images/errow.svg: -------------------------------------------------------------------------------- 1 | errow -------------------------------------------------------------------------------- /public/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/images/favicon-32x32.png -------------------------------------------------------------------------------- /public/images/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/images/favicon-96x96.png -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/watson-developer-cloud/text-to-speech-nodejs/0d50d43bf91eeb8c8d1a06729fe579f223ec5d58/public/images/favicon.ico -------------------------------------------------------------------------------- /public/images/service-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 11 | 19 | 26 | 27 | -------------------------------------------------------------------------------- /public/js/bundle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Demo from '../../views/demo.jsx'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | require('dotenv').config({ silent: true }); 4 | 5 | const server = require('./app'); 6 | 7 | const port = process.env.PORT || 3000; 8 | 9 | server.listen(port, () => { 10 | console.log('Server running on port: %d', port); 11 | }); 12 | -------------------------------------------------------------------------------- /test/integration/test.mainpage.js: -------------------------------------------------------------------------------- 1 | /* eslint no-undef: 0 */ 2 | /* eslint prefer-arrow-callback: 0 */ 3 | /* eslint func-names: 0 */ 4 | // Define the suite of tests and give it the following properties: 5 | // - Title, which shows up before any of the pass/fails. 6 | // - Number of tests, must be changed as you add tests. 7 | // - suite(), which contains all of your tests. 8 | // 9 | // @see http://casperjs.readthedocs.org/en/latest 10 | casper.test.begin('Text To Speech', 16, function suite(test) { 11 | const baseHost = 'http://localhost:3000'; 12 | 13 | function testForButtons() { 14 | casper.waitForSelector('div.buttons-container', function () { 15 | test.assertExists('button.base--button.speak-button', 'displays speak button'); 16 | test.assertExists('button.base--button.download-button', 'displays download button'); 17 | test.assertExists('div.reset-container.dimmed', 'displays reset-container dimmed'); 18 | test.assertExists('div.reset-container.dimmed > a.reset-button', 'displays reset-container dimmed with child link'); 19 | }); 20 | } 21 | 22 | function testForSelection() { 23 | casper.waitForSelector('div.voice-input', function () { 24 | test.assertExists('div.voice-input > select.base--select', 'has voice select'); 25 | }); 26 | } 27 | 28 | function testForAudio() { 29 | test.assertExists('audio.audio.hidden', 'has audio play bar hidden'); 30 | test.comment('Clicking the Speak button...'); 31 | 32 | // casper.then() allows us to wait until previous tests and actions are 33 | // completed before moving on to the next steps. 34 | casper.then(function () { 35 | this.click('button.base--button.speak-button'); 36 | casper.waitForSelector('div.text-center', function () { 37 | test.assertExists('audio.audio', 'has audio play bar'); 38 | }); 39 | }); 40 | } 41 | 42 | function testForTabpanels() { 43 | casper.then(function () { 44 | this.click('ul.tab-panels--tab-list li:nth-child(1)'); 45 | test.assertSelectorHasText('ul.tab-panels--tab-list li:nth-child(1)', 'Text'); 46 | test.assertSelectorHasText('div.tab-panels--tab-content > div > textarea', 'Conscious of its spiritual'); 47 | test.assertHttpStatus(200); 48 | }); 49 | casper.then(function () { 50 | this.click('ul.tab-panels--tab-list li:nth-child(2)'); 51 | test.assertSelectorHasText('ul.tab-panels--tab-list li:nth-child(2)', 'SSML'); 52 | test.assertHttpStatus(200); 53 | }); 54 | casper.then(function () { 55 | this.click('ul.tab-panels--tab-list li:nth-child(3)'); 56 | test.assertSelectorHasText('ul.tab-panels--tab-list li:nth-child(3)', 'Voice Transformation SSML'); 57 | test.assertHttpStatus(200); 58 | }); 59 | } 60 | 61 | // casper.start() always wraps your first action. The first argument should 62 | // be the URL of the page you want to test. 63 | casper.start(baseHost, function (result) { 64 | test.assert(result.status === 200, 'Front page opens'); 65 | test.assertEquals(this.getTitle(), 'Text to Speech Demo', 'Title is found'); 66 | testForButtons(); 67 | testForSelection(); 68 | testForAudio(); 69 | testForTabpanels(); 70 | }); 71 | 72 | // This code runs all the tests that we defined above. 73 | casper.run(function () { 74 | test.done(); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/unit/express.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const path = require('path'); 18 | const request = require('supertest'); 19 | 20 | // load default variables for testing 21 | require('dotenv').config({ path: path.join(__dirname, '../../.env.example') }); 22 | 23 | const app = require('../../app'); 24 | 25 | describe('express', function express() { // eslint-disable-line 26 | it('load home page when GET /', () => request(app).get('/').expect(200)); 27 | 28 | it('404 when page not found', () => request(app).get('/foo/bar').expect(404)); 29 | }); 30 | -------------------------------------------------------------------------------- /test/unit/react.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 IBM Corp. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | const assert = require('assert'); 17 | const React = require('react'); 18 | const ReactDOMServer = require('react-dom/server'); 19 | require('@babel/register'); 20 | 21 | // eslint-disable-next-line 22 | describe('react', function () { 23 | it('should render some html', () => { 24 | const index = require('../../views/index.jsx').default; 25 | const element = React.createElement(index, null); 26 | const result = ReactDOMServer.renderToString(element); 27 | assert(result); 28 | assert.equal(result.substr(0, 5), ' { 16 | if (typeof URLSearchParams === 'function') { 17 | return new URLSearchParams(); 18 | } 19 | 20 | // Simple polyfill for URLSearchparams 21 | const SearchParams = function SearchParams() { 22 | }; 23 | 24 | SearchParams.prototype.set = function set(key, value) { 25 | this[key] = value; 26 | }; 27 | 28 | SearchParams.prototype.toString = function toString() { 29 | return Object.keys(this).map(v => `${encodeURI(v)}=${encodeURI(this[v])}`).join('&'); 30 | }; 31 | 32 | return new SearchParams(); 33 | }; 34 | 35 | /** 36 | * Validates that the mimetype is: audio/wav, audio/mpeg;codecs=mp3 or audio/ogg;codecs=opus 37 | * @param {String} mimeType The audio mimetype 38 | * @return {bool} Returns true if the mimetype can be played. 39 | */ 40 | const canPlayAudioFormat = (mimeType) => { 41 | const audio = document.createElement('audio'); 42 | if (audio) { 43 | return (typeof audio.canPlayType === 'function' && audio.canPlayType(mimeType) !== ''); 44 | } 45 | return false; 46 | }; 47 | 48 | export default class Demo extends Component { 49 | constructor(props) { 50 | super(props); 51 | this.state = { 52 | voice: voices[1], // Allison v3 is the first voice 53 | error: null, // the error from calling /classify 54 | text: voices[1].demo.text, // default text 55 | ssml: voices[1].demo.ssml, // SSML text 56 | ssml_voice: voices[1].demo.ssml_voice, // Voice SSML text, only some voices support this 57 | ssmlLabel: 'SSML', 58 | current_tab: 0, 59 | loading: false, 60 | }; 61 | 62 | this.audioElementRef = React.createRef(); 63 | 64 | this.onTabChange = this.onTabChange.bind(this); 65 | this.onTextChange = this.onTextChange.bind(this); 66 | this.onSsmlChange = this.onSsmlChange.bind(this); 67 | this.onVoiceSsmlChange = this.onVoiceSsmlChange.bind(this); 68 | this.onDownload = this.onDownload.bind(this); 69 | this.onSpeak = this.onSpeak.bind(this); 70 | this.onResetClick = this.onResetClick.bind(this); 71 | this.onVoiceChange = this.onVoiceChange.bind(this); 72 | this.onAudioLoaded = this.onAudioLoaded.bind(this); 73 | this.setupParamsFromState = this.setupParamsFromState.bind(this); 74 | this.downloadDisabled = this.downloadDisabled.bind(this); 75 | this.speakDisabled = this.speakDisabled.bind(this); 76 | this.downloadAllowed = this.downloadAllowed.bind(this); 77 | this.handleAudioError = this.handleAudioError.bind(this); 78 | } 79 | 80 | componentDidMount() { 81 | if (this.audioElementRef.current) { 82 | this.audioElementRef.current.addEventListener('play', this.onAudioLoaded); 83 | this.audioElementRef.current.addEventListener('error', this.handleAudioError); 84 | } 85 | } 86 | 87 | componentWillUnmount() { 88 | if (this.audioElementRef.current) { 89 | this.audioElementRef.current.removeEventListener('play', this.onAudioLoaded); 90 | this.audioElementRef.current.removeEventListener('error', this.handleAudioError); 91 | } 92 | } 93 | 94 | onTabChange(idx) { 95 | this.setState({ current_tab: idx }); 96 | } 97 | 98 | onTextChange(event) { 99 | this.setState({ text: event.target.value }); 100 | } 101 | 102 | onSsmlChange(event) { 103 | this.setState({ ssml: event.target.value }); 104 | } 105 | 106 | onVoiceSsmlChange(event) { 107 | this.setState({ ssml_voice: event.target.value }); 108 | } 109 | 110 | onAudioLoaded() { 111 | this.setState({ loading: false, hasAudio: true }); 112 | } 113 | 114 | onDownload(event) { 115 | event.target.blur(); 116 | const params = this.setupParamsFromState(true); 117 | window.location.href = `/api/v3/synthesize?${params.toString()}`; 118 | } 119 | 120 | onSpeak(event) { 121 | event.target.blur(); 122 | const params = this.setupParamsFromState(true); 123 | 124 | const audio = this.audioElementRef.current; 125 | audio.setAttribute('type', 'audio/ogg;codecs=opus'); 126 | audio.setAttribute('src', `/api/v3/synthesize?${params.toString()}`); 127 | 128 | this.setState({ loading: true, hasAudio: false }); 129 | } 130 | 131 | onResetClick() { 132 | // pause audio, if it's playing. 133 | document.querySelector('audio#audio').pause(); 134 | const { voice } = this.state; 135 | this.setState({ 136 | error: null, 137 | hasAudio: false, 138 | text: voice.demo.text, 139 | ssml: voice.demo.ssml, 140 | ssml_voice: voice.demo.ssml_voice, 141 | }); 142 | } 143 | 144 | onVoiceChange(event) { 145 | const voice = voices[voices.map(v => v.name).indexOf(event.target.value)]; 146 | const label = voice.name.indexOf('en-US_Allison') === 0 ? 'Expressive SSML' : 'SSML'; 147 | this.setState({ 148 | voice, 149 | error: null, 150 | text: voice.demo.text, 151 | ssml: voice.demo.ssml, 152 | ssml_voice: voice.demo.ssml_voice, 153 | ssmlLabel: label, 154 | }); 155 | } 156 | 157 | setupParamsFromState(doDownload) { 158 | const { 159 | text, voice, current_tab, ssmlLabel, ssml_voice, ssml, 160 | } = this.state; 161 | 162 | const params = getSearchParams(); 163 | if (this.state && current_tab === 0) { 164 | params.set('text', text); 165 | params.set('voice', voice.name); 166 | } else if (this.state && current_tab === 1) { 167 | params.set('text', ssml); 168 | params.set('voice', voice.name); 169 | params.set('ssmlLabel', ssmlLabel); 170 | } else if (this.state && current_tab === 2) { 171 | params.set('text', ssml_voice); 172 | params.set('voice', voice.name); 173 | } 174 | params.set('download', doDownload); 175 | 176 | if (canPlayAudioFormat('audio/mp3')) { 177 | params.set('accept', 'audio/mp3'); 178 | } else if (canPlayAudioFormat('audio/ogg;codec=opus')) { 179 | params.set('accept', 'audio/ogg;codec=opus'); 180 | } else if (canPlayAudioFormat('audio/wav')) { 181 | params.set('accept', 'audio/wav'); 182 | } 183 | console.log(JSON.stringify(params)); 184 | return params; 185 | } 186 | 187 | handleAudioError(error) { 188 | console.error(error); 189 | this.setState({ error: { error: 'Could not play audio' }, loading: false }); 190 | setTimeout(() => this.setState({ error: null }), 5000); 191 | } 192 | 193 | downloadDisabled() { 194 | return !this.downloadAllowed(); 195 | } 196 | 197 | speakDisabled() { 198 | return this.downloadDisabled(); 199 | } 200 | 201 | downloadAllowed() { 202 | const { 203 | ssml, ssml_voice, current_tab, text, 204 | } = this.state; 205 | return ( 206 | (ssml_voice && current_tab === 2) 207 | || (ssml && current_tab === 1) 208 | || (current_tab === 0 && text) 209 | ); 210 | } 211 | 212 | render() { 213 | const { 214 | ssml, ssml_voice, voice, loading, hasAudio, ssmlLabel, error, text, 215 | } = this.state; 216 | 217 | const textDirection = (voice && voice.language === 'ar-AR') ? 'rtl' : 'ltr'; 218 | 219 | return ( 220 |
221 |
222 |

223 | Input Text 224 |

225 |

226 | {TEXT_DESCRIPTION} 227 |

228 |

229 | Voice Selection 230 |

231 |

232 | For optimal naturalness, select neural voices (V3, enhanced dnn) in the list below.
Please see  233 | 234 | Watson TTS blog 235 | 236 |  for more information. 237 |

238 |
239 | 251 |
252 | 253 | 254 | 255 |