Skip to content

Commit

Permalink
feat: samples for hybrid glossaries tutorial (#327)
Browse files Browse the repository at this point in the history
* feat: Node.js samples for hybrid glossaries tutorial

* fix: lint

* fix: header

* fix: adding package.json
  • Loading branch information
crowdus authored and leahecole committed Sep 25, 2019
1 parent 1bae395 commit c923bf8
Show file tree
Hide file tree
Showing 4 changed files with 308 additions and 1 deletion.
232 changes: 232 additions & 0 deletions translate/hybridGlossaries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/**
* Copyright 2019 Google LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

async function main(
projectId, // Your GCP Project Id
inFile = 'resources/example.png',
outFile = 'resources/example.mp3',
glossaryLangs = ['fr', 'en'],
glossaryName = 'bistro-glossary',
glossaryUri = 'gs://cloud-samples-data/translation/bistro_glossary.csv'
) {
// [START translate_hybrid_imports]
// Imports the Google Cloud client library
const textToSpeech = require('@google-cloud/text-to-speech');
const translate = require('@google-cloud/translate').v3beta1;
const vision = require('@google-cloud/vision');

// Import other required libraries
const fs = require('fs');
//const escape = require('escape-html');
const util = require('util');
// [END translate_hybrid_imports]

// [START translate_hybrid_vision]
/**
* Detects text in an image file
*
* ARGS
* inputFile: path to image file
* RETURNS
* string of text detected in the input image
**/
async function picToText(inputFile) {
// Creates a client
const client = new vision.ImageAnnotatorClient();

// Performs text detection on the local file
const [result] = await client.textDetection(inputFile);
return result.fullTextAnnotation.text;
}
// [END translate_hybrid_vision]

// [START translate_hybrid_create_glossary]
/** Creates a GCP glossary resource
* Assumes you've already manually uploaded a glossary to Cloud Storage
*
* ARGS
* languages: list of languages in the glossary
* projectId: GCP project id
* glossaryName: name you want to give this glossary resource
* glossaryUri: the uri of the glossary you uploaded to Cloud Storage
* RETURNS
* nothing
**/
async function createGlossary(
languages,
projectId,
glossaryName,
glossaryUri
) {
// Instantiates a client
const translationClient = await new translate.TranslationServiceClient();

// Construct glossary
const glossary = {
languageCodesSet: {
languageCodes: languages,
},
inputConfig: {
gcsSource: {
inputUri: glossaryUri,
},
},
name: translationClient.glossaryPath(
projectId,
'us-central1',
glossaryName
),
};

// Construct request
const request = {
parent: translationClient.locationPath(projectId, 'us-central1'),
glossary: glossary,
};

// Create glossary using a long-running operation.
try {
const [operation] = await translationClient.createGlossary(request);
// Wait for operation to complete.
await operation.promise();
console.log(`Created glossary ` + glossaryName + '.');
} catch (AlreadyExists) {
console.log(
'The glossary ' +
glossaryName +
' already exists. No new glossary was created.'
);
}
}
// [END translate_hybrid_create_glossary]

// [START translate_hybrid_translate]
/**
* Translates text to a given language using a glossary
*
* ARGS
* text: String of text to translate
* sourceLanguageCode: language of input text
* targetLanguageCode: language of output text
* projectId: GCP project id
* glossaryName: name you gave your project's glossary
* resource when you created it
* RETURNS
* String of translated text
**/
async function translateText(
text,
sourceLanguageCode,
targetLanguageCode,
projectId,
glossaryName
) {
// Instantiates a client
const translationClient = new translate.TranslationServiceClient();
const glossary = translationClient.glossaryPath(
projectId,
'us-central1',
glossaryName
);
const glossaryConfig = {
glossary: glossary,
};
// Construct request
const request = {
parent: translationClient.locationPath(projectId, 'us-central1'),
contents: [text],
mimeType: 'text/plain', // mime types: text/plain, text/html
sourceLanguageCode: sourceLanguageCode,
targetLanguageCode: targetLanguageCode,
glossaryConfig: glossaryConfig,
};

// Run request
const [response] = await translationClient.translateText(request);
// Extract the string of translated text
return response.glossaryTranslations[0].translatedText;
}
// [END translate_hybrid_translate]

// [START translate_hybrid_text_to_speech]
/**
* Generates synthetic audio from plaintext tagged with SSML.
*
* Given the name of a text file and an output file name, this function
* tags the text in the text file with SSML. This function then
* calls the Text-to-Speech API. The API returns a synthetic audio
* version of the text, formatted according to the SSML commands. This
* function saves the synthetic audio to the designated output file.
*
* ARGS
* text: String of plaintext
* outFile: String name of file under which to save audio output
* RETURNS
* nothing
*
*/
async function syntheticAudio(text, outFile) {
// Replace special characters with HTML Ampersand Character Codes
// These codes prevent the API from confusing text with SSML tags
// For example, '<' --> '&lt;' and '&' --> '&amp;'
let escapedLines = text.replace(/&/g, '&amp;');
escapedLines = escapedLines.replace(/"/g, '&quot;');
escapedLines = escapedLines.replace(/</g, '&lt;');
escapedLines = escapedLines.replace(/>/g, '&gt;');

// Convert plaintext to SSML
// Tag SSML so that there is a 2 second pause between each address
const expandedNewline = escapedLines.replace(/\n/g, '\n<break time="2s"/>');
const ssmlText = '<speak>' + expandedNewline + '</speak>';

// Creates a client
const client = new textToSpeech.TextToSpeechClient();

// Constructs the request
const request = {
// Select the text to synthesize
input: {ssml: ssmlText},
// Select the language and SSML Voice Gender (optional)
voice: {languageCode: 'en-US', ssmlGender: 'MALE'},
// Select the type of audio encoding
audioConfig: {audioEncoding: 'MP3'},
};

// Performs the Text-to-Speech request
const [response] = await client.synthesizeSpeech(request);
// Write the binary audio content to a local file
const writeFile = util.promisify(fs.writeFile);
await writeFile(outFile, response.audioContent, 'binary');
console.log('Audio content written to file ' + outFile);
}
// [END translate_hybrid_text_to_speech]

// [START translate_hybrid_integration]
await createGlossary(glossaryLangs, projectId, glossaryName, glossaryUri);
const text = await picToText(inFile);
const translatedText = await translateText(
text,
'fr',
'en',
projectId,
glossaryName
);
syntheticAudio(translatedText, outFile);
// [END translate_hybrid_integration]
}

main(...process.argv.slice(2));
4 changes: 3 additions & 1 deletion translate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
},
"dependencies": {
"@google-cloud/automl": "^1.0.0",
"@google-cloud/text-to-speech": "^1.1.4",
"@google-cloud/translate": "^4.1.3",
"@google-cloud/vision": "^1.2.0",
"yargs": "^14.0.0"
},
"devDependencies": {
"@google-cloud/storage": "^3.2.1",
"chai": "^4.2.0",
"@google-cloud/storage": "^3.0.0",
"mocha": "^6.0.0",
"uuid": "^3.3.2"
}
Expand Down
Binary file added translate/resources/example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions translate/test/hybridGlossaries.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Copyright 2019 Google LLC
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const fs = require('fs');
const {assert} = require('chai');
const {TranslationServiceClient} = require('@google-cloud/translate').v3beta1;
const cp = require('child_process');

const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'});

const REGION_TAG = 'translate_hybrid_glossaries';

describe(REGION_TAG, () => {
const translationClient = new TranslationServiceClient();
const location = 'us-central1';
const glossaryId = 'bistro-glossary';
const outputFile = 'resources/example.mp3';

before(async function() {
try {
fs.unlinkSync(outputFile);
} catch (e) {
// don't throw an exception
}
});

it('should create a glossary', async () => {
const projectId = await translationClient.getProjectId();
const output = execSync(`node hybridGlossaries.js ${projectId}`);
assert(output.includes(glossaryId));
assert(
output.includes('Audio content written to file resources/example.mp3')
);
assert.strictEqual(fs.existsSync(outputFile), true);
});

after(async function() {
fs.unlinkSync(outputFile);
assert.strictEqual(fs.existsSync(outputFile), false);
// get projectId
const projectId = await translationClient.getProjectId();
const name = translationClient.glossaryPath(
projectId,
location,
glossaryId
);
const request = {
parent: translationClient.locationPath(projectId, location),
name: name,
};

// Delete glossary using a long-running operation.
// You can wait for now, or get results later.
const [operation] = await translationClient.deleteGlossary(request);

// Wait for operation to complete.
await operation.promise();
});
});

0 comments on commit c923bf8

Please sign in to comment.