├── .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.
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 |
--------------------------------------------------------------------------------
/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 |
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 |
60 |
61 |
62 |
63 |
64 | );
65 | }
66 |
67 | Layout.propTypes = {
68 | children: PropTypes.object.isRequired, // eslint-disable-line
69 | };
70 |
71 | export default Layout;
72 |
--------------------------------------------------------------------------------
/voices.js:
--------------------------------------------------------------------------------
1 | /* eslint max-len: off */
2 | const ES_TEXT = 'Consciente de su patrimonio espiritual y moral, la Unión está fundada sobre los valores indivisibles y universales de la dignidad humana, la libertad, la igualdad y la solidaridad, y se basa en los principios de la democracia y el Estado de Derecho. Al instituir la ciudadanía de la Unión y crear un espacio de libertad, seguridad y justicia, sitúa a la persona en el centro de su actuación.';
3 | const FR_TEXT = "Consciente de son patrimoine spirituel et moral, l'Union se fonde sur les valeurs indivisibles et universelles de dignité humaine, de liberté, d'égalité et de solidarité; elle repose sur le principe de la démocratie et le principe de l'État de droit. Elle place la personne au coeur de son action en instituant la citoyenneté de l'Union et en créant un espace de liberté, de sécurité et de justice.";
4 | const US_TEXT = 'Conscious of its spiritual and moral heritage, the Union is founded on the indivisible, universal values of human dignity, freedom, equality and solidarity; it is based on the principles of democracy and the rule of law. It places the individual at the heart of its activities, by establishing the citizenship of the Union and by creating an area of freedom, security and justice.';
5 | const DE_TEXT = 'In dem Bewusstsein ihres geistig-religiösen und sittlichen Erbes gründet sich die Union auf die unteilbaren und universellen Werte der Würde des Menschen, der Freiheit, der Gleichheit und der Solidarität. Sie beruht auf den Grundsätzen der Demokratie und der Rechtsstaatlichkeit. Sie stellt den Menschen in den Mittelpunkt ihres Handelns, indem sie die Unionsbürgerschaft und einen Raum der Freiheit, der Sicherheit und des Rechts begründet.';
6 | const IT_TEXT = 'L\'Unione contribuisce alla salvaguardia e allo sviluppo di questi valori comuni nel rispetto della diversità delle culture e delle tradizioni dei popoli d\'Europa, nonché dell\'identità nazionale degli Stati membri e dell\'ordinamento dei loro pubblici poteri a livello nazionale, regionale e locale; essa si sforza di promuovere uno sviluppo equilibrato e sostenibile e assicura la libera circolazione delle persone, dei servizi, delle merci e dei capitali, nonché la libertà di stabilimento.';
7 | const JP_TEXT = 'こちらでは配送手続きのご予約・変更を承っております。お客様の会員番号をお願いいたします。会員番号は、01234567、ですね。確認いたしました。現在、3月25日、ご自宅へ配送のご予約を頂いております。それでは、3月25日、ご自宅へ配送の予定を、3月26日のご配送に変更いたします。3月26日は、降雪のため、配送が遅れることがあります。';
8 | const PT_TEXT = 'Consciente do seu patrimônio espiritual e moral, a União é fundamentada nos valores indivisíveis e universais da dignidade humana, liberdade, igualdade e solidariedade; é baseada nos princípios da democracia e estado de direito. Ela coloca o indivíduo no centro de suas ações, ao instituir a cidadania da União e ao criar um espaço de liberdade, segurança e justiça.';
9 | const AR_TEXT = 'تقوم خدمة I B M النص إلى خدمة الكلام بتحويل النص المكتوب إلى صوت طبيعي في مجموعة متنوعة من اللغات والأصوات.';
10 | const CN_TEXT = '基于海量数据的云计算、大数据、人工智能、区块链等新兴技术,正在对商业产生深远的影响。科技变革的步伐持续加速,各行各业的领先企业正在将关键业务应用转移到云端,并积极利用 AI,重塑业务。';
11 | const NL_TEXT = 'De volkeren van Europa hebben besloten een op gemeenschappelijke waarden gegrondveste vreedzame toekomst te delen door onderling een steeds hechter verbond tot stand te brengen. De Unie, die zich bewust is van haar geestelijke en morele erfgoed, heeft haar grondslag in de ondeelbare en universele waarden van menselijke waardigheid en van vrijheid, gelijkheid en solidariteit. Zij berust op het beginsel van democratie en het beginsel van de rechtsstaat. De Unie stelt de mens centraal in haar optreden, door het burgerschap van de Unie in te stellen en een ruimte van vrijheid, veiligheid en recht tot stand te brengen.';
12 | const KO_TEXT = '11월 27일 일기예보입니다. 현재 전국이 대체로 맑으나 제주도와 경북동해안은 눈이 날리거나 빗방울이 떨어지는 곳이 있겠습니다. 오늘 낮 기온은 어제와 비슷하겠습니다. 내일 저녁 한때 서울, 경기도에는 비가 조금 오는 곳이 있겠으며, 충북북부와 경북남부에는 빗방울이 떨어지는 곳이 있겠습니다. 모레 밤부터 서해안을 중심으로 초속 60m 이상의 매우 강한 돌풍이 부는 곳이 있겠으니, 시설물 관리와 안전사고에 유의하시기 바랍니다. 지금까지 기상정보였습니다.';
13 |
14 | // Sample text values with SSML
15 | const ES_SSML = '
Consciente de su patrimonio espiritual y moral, la Unión está fundada sobre los valores indivisibles y universales de la dignidad humana, la libertad, la igualdad y la solidaridad, y se basa en los principios de la democracia y el Estado de Derecho.Al instituir la ciudadanía de la Unión y crear un espacio de libertad, seguridad y justicia, sitúa a la persona en el centro de su actuación.
';
16 | const FR_SSML = '
Consciente de son patrimoine spirituel et moral, l\'Union se fonde sur les valeurs indivisibles et universelles de dignité humaine, de liberté, d\'égalité et de solidarité; elle repose sur le principe de la démocratie et le principe de l\'État de droit . Elle place la personne au coeur de son action en instituant la citoyenneté de l\'Union et en créant un espace de liberté, de sécurité et de justice.
';
17 | const US_SSML = '
Conscious of its spiritual and moral heritage , the Union is founded on the indivisible, universal values of human dignity, freedom, equality and solidarity. It is based on the principles of democracy and the rule of law . It places the individual at the heart of its activities, by establishing the citizenship of the Union and by creating an area of freedom, security and justice.
';
18 | const US_SSML_EXPRESSIVE = 'I have been assigned to handle your order status request. I am sorry to inform you that the items you requested are back-ordered. We apologize for the inconvenience. We don\'t know when those items will become available. Maybe next week but we are not sure at this time.Because we want you to be a happy customer, management has decided to give you a 50% discount! ';
19 | const US_GB_SSML = '
Conscious of its spiritual and moral heritage , the Union is founded on the indivisible, universal values of human dignity, freedom, equality and solidarity. It is based on the principles of democracy and the rule of law . It places the individual at the heart of its activities, by establishing the citizenship of the Union and by creating an area of freedom, security and justice.
';
20 | const DE_SSML = '
In dem Bewusstsein ihres geistig-religiösen und sittlichen Erbes gründet sich die Union auf die unteilbaren und universellen Werte der Würde des Menschen, der Freiheit, der Gleichheit und der Solidarität. Sie beruht auf den Grundsätzen der Demokratie und der Rechtsstaatlichkeit. Sie stellt den Menschen in den Mittelpunkt ihres Handelns, indem sie die Unionsbürgerschaft und einen Raum der Freiheit, der Sicherheit und des Rechts begründet.
';
21 | const IT_SSML = '
Consapevole del suo patrimonio spirituale e morale, l\'Unione si fonda sui valori indivisibili e universali della dignità umana, della libertà, dell\'uguaglianza e della solidarietà; essa si basa sul principio della democrazia e sul principio dello Stato di diritto. Pone la persona al centro della sua azione istituendo la cittadinanza dell\'Unione e creando uno spazio di libertà, sicurezza e giustizia.
Consciente do seu patrimônio espiritual e moral, a União é fundamentada nos valores indivisíveis e universais da dignidade humana, liberdade, igualdade e solidariedade; é baseada nos princípios da democracia e estado de direito. Ela coloca o indivíduo no centro de suas ações, ao instituir a cidadania da União e ao criar um espaço de liberdade, segurança e justiça.
';
24 | const AR_SSML = 'تقوم خدمة I B M النص إلى خدمة الكلام بتحويل النص المكتوب إلى صوت طبيعي في مجموعة متنوعة من اللغات والأصوات.';
25 | const CN_SSML = '基于海量数据的云计算、大数据、人工智能、区块链等新兴技术,正在对商业产生深远的影响。科技变革的步伐 持续加速,各行各业的领先企业正在将关键业务应用 转移到云端,并积极利用 AI,重塑业务。';
26 | const NL_SSML = 'De volkeren van Europa hebben besloten een op gemeenschappelijke waarden gegrondveste vreedzame toekomst te delen door onderling een steeds hechter verbond tot stand te brengen. De Unie, die zich bewust is van haar geestelijke en morele erfgoed, heeft haar grondslag in de ondeelbare en universele waarden van menselijke waardigheid en van vrijheid, gelijkheid en solidariteit. Zij berust op het beginsel van democratie en het beginsel van de rechtsstaat. De Unie stelt de mens centraal in haar optreden , door het burgerschap van de Unie in te stellen en een ruimte van vrijheid, veiligheid en recht tot stand te brengen.';
27 | const KO_SSML = '
11월 27일 일기예보입니다.현재 전국이 대체로 맑으나 제주도와 경북동해안은 눈이 날리거나 빗방울이 떨어지는 곳이 있겠습니다.오늘 낮 기온은 어제와 비슷하겠습니다.내일 저녁 한때 서울, 경기도에는 비가 조금 오는 곳이 있겠으며, 충북북부와 경북남부에는 빗방울이 떨어지는 곳이 있겠습니다.모레 밤부터 서해안을 중심으로 초속 60m 이상의 매우 강한 돌풍이 부는 곳이 있겠으니, 시설물 관리와 안전사고에 유의하시기 바랍니다.지금까지 기상정보였습니다.