#!/usr/bin/env python # Copyright (c) 2018, Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). # You may not use this file except in compliance with the License. # A copy of the License is located at # # http://aws.amazon.com/apache2.0 # # or in the "license" file accompanying this file. This file 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. import json import unittest import rclpy import launch_testing from launch import LaunchDescription from launch.actions import OpaqueFunction from launch_ros.actions import Node from tts_interfaces.srv import Polly from tts_interfaces.srv import Synthesizer def generate_test_description(ready_fn): polly_server = Node(package='tts', node_executable='polly_server', additional_env={'PYTHONUNBUFFERED': '1'}, output='screen') synthesizer_server = Node(package='tts', node_executable='synthesizer_server', additional_env={'PYTHONUNBUFFERED': '1'}, output='screen') launch_description = LaunchDescription([ polly_server, synthesizer_server, OpaqueFunction(function=lambda context: ready_fn()), ]) return launch_description, locals() class TestPlainText(unittest.TestCase): @classmethod def setUpClass(cls): rclpy.init() @classmethod def tearDownClass(cls): rclpy.shutdown() def test_plain_text_to_wav_via_polly_node(self): node = rclpy.create_node('integtest') client = node.create_client(Polly, 'polly') retries = 0 while not client.wait_for_service(timeout_sec=2.0): retries += 1 self.failIf(retries > 3, 'service is not available') test_text = 'Mary has a little lamb, little lamb, little lamb.' request = Polly.Request(polly_action='SynthesizeSpeech', text=test_text) future = client.call_async(request) while rclpy.ok(): rclpy.spin_once(node) if future.done(): self.failIf(future.result() is None, 'nothing is returned') break print('Waiting for service to be done.') res = future.result() self.assertIsNotNone(res) self.assertTrue(type(res) is Polly.Response) r = json.loads(res.result) self.assertIn('Audio Type', r, 'result should contain audio type') self.assertIn('Audio File', r, 'result should contain file path') self.assertIn('Amazon Polly Response Metadata', r, 'result should contain metadata') audio_type = r['Audio Type'] audio_file = r['Audio File'] md = r['Amazon Polly Response Metadata'] self.assertTrue("'HTTPStatusCode': 200," in md) self.assertEqual('audio/pcm', audio_type) self.assertTrue(audio_file.endswith('.wav')) import subprocess o = subprocess.check_output(['file', audio_file], stderr=subprocess.STDOUT) import re m = re.search(r'.*WAVE audio.*', o.decode('utf-8'), flags=re.MULTILINE) self.assertIsNotNone(m) node.destroy_node() def test_plain_text_using_polly_class(self): from tts.services.amazonpolly import AmazonPolly polly = AmazonPolly() test_text = 'Mary has a little lamb, little lamb, little lamb.' res = polly.synthesize(text=test_text) self.assertIsNotNone(res) self.assertTrue(type(res) is Polly.Response) r = json.loads(res.result) self.assertIn('Audio Type', r, 'result should contain audio type') self.assertIn('Audio File', r, 'result should contain file path') self.assertIn('Amazon Polly Response Metadata', r, 'result should contain metadata') audio_type = r['Audio Type'] audio_file = r['Audio File'] md = r['Amazon Polly Response Metadata'] self.assertTrue("'HTTPStatusCode': 200," in md) self.assertEqual('audio/pcm', audio_type) self.assertTrue(audio_file.endswith('.wav')) import subprocess o = subprocess.check_output(['file', audio_file], stderr=subprocess.STDOUT) import re m = re.search(r'.*WAVE audio.*', o.decode('utf-8'), flags=re.MULTILINE) self.assertIsNotNone(m) def test_plain_text_via_synthesizer_node(self): node = rclpy.create_node('integtest') client = node.create_client(Synthesizer, 'synthesizer') retries = 0 while not client.wait_for_service(timeout_sec=2.0): retries += 1 self.failIf(retries > 3, 'service is not available') test_text = 'Mary has a little lamb, little lamb, little lamb.' request = Synthesizer.Request(text=test_text) future = client.call_async(request) while rclpy.ok(): rclpy.spin_once(node) if future.done(): self.failIf(future.result() is None, 'nothing is returned') break print('Waiting for service to be done.') res = future.result() self.assertIsNotNone(res) self.assertTrue(type(res) is Synthesizer.Response) r = json.loads(res.result) self.assertIn('Audio Type', r, 'result should contain audio type') self.assertIn('Audio File', r, 'result should contain file path') self.assertIn('Amazon Polly Response Metadata', r, 'result should contain metadata') audio_type = r['Audio Type'] audio_file = r['Audio File'] md = r['Amazon Polly Response Metadata'] self.assertTrue("'HTTPStatusCode': 200," in md) self.assertEqual('audio/pcm', audio_type) self.assertTrue(audio_file.endswith('.wav')) import subprocess o = subprocess.check_output(['file', audio_file], stderr=subprocess.STDOUT) import re m = re.search(r'.*WAVE audio.*', o.decode('utf-8'), flags=re.MULTILINE) self.assertIsNotNone(m) node.destroy_node() def test_plain_text_to_mp3_via_polly_node(self): node = rclpy.create_node('integtest') client = node.create_client(Polly, 'polly') retries = 0 while not client.wait_for_service(timeout_sec=2.0): retries += 1 self.failIf(retries > 3, 'service is not available') test_text = 'Mary has a little lamb, little lamb, little lamb.' request = Polly.Request(polly_action='SynthesizeSpeech', text=test_text, output_format='mp3') future = client.call_async(request) while rclpy.ok(): rclpy.spin_once(node) if future.done(): self.failIf(future.result() is None, 'nothing is returned') break print('Waiting for service to be done.') res = future.result() self.assertIsNotNone(res) self.assertTrue(type(res) is Polly.Response) r = json.loads(res.result) self.assertIn('Audio Type', r, 'result should contain audio type') self.assertIn('Audio File', r, 'result should contain file path') self.assertIn('Amazon Polly Response Metadata', r, 'result should contain metadata') audio_type = r['Audio Type'] audio_file = r['Audio File'] md = r['Amazon Polly Response Metadata'] self.assertTrue("'HTTPStatusCode': 200," in md) self.assertEqual('audio/mpeg', audio_type) self.assertTrue(audio_file.endswith('.mp3')) import subprocess o = subprocess.check_output(['file', audio_file], stderr=subprocess.STDOUT) import re m = re.search(r'.*MPEG.*layer III.*', o.decode('utf-8'), flags=re.MULTILINE) self.assertIsNotNone(m) node.destroy_node() def test_simple_ssml_via_polly_node(self): node = rclpy.create_node('integtest') client = node.create_client(Polly, 'polly') retries = 0 while not client.wait_for_service(timeout_sec=2.0): retries += 1 self.failIf(retries > 3, 'service is not available') text = 'Mary has a little lamb, little lamb, little lamb.' request = Polly.Request(polly_action='SynthesizeSpeech', text=text, text_type='ssml') future = client.call_async(request) while rclpy.ok(): rclpy.spin_once(node) if future.done(): self.failIf(future.result() is None, 'nothing is returned') break print('Waiting for service to be done.') res = future.result() self.assertIsNotNone(res) self.assertTrue(type(res) is Polly.Response) r = json.loads(res.result) self.assertIn('Audio Type', r, 'result should contain audio type') self.assertIn('Audio File', r, 'result should contain file path') self.assertIn('Amazon Polly Response Metadata', r, 'result should contain metadata') audio_type = r['Audio Type'] audio_file = r['Audio File'] md = r['Amazon Polly Response Metadata'] self.assertTrue("'HTTPStatusCode': 200," in md) self.assertEqual('audio/pcm', audio_type) self.assertTrue(audio_file.endswith('.wav')) import subprocess o = subprocess.check_output(['file', audio_file], stderr=subprocess.STDOUT) import re m = re.search(r'.*WAVE audio.*', o.decode('utf-8'), flags=re.MULTILINE) self.assertIsNotNone(m) node.destroy_node() def test_simple_ssml_via_synthesizer_node(self): node = rclpy.create_node('integtest') client = node.create_client(Synthesizer, 'synthesizer') retries = 0 while not client.wait_for_service(timeout_sec=2.0): retries += 1 self.failIf(retries > 3, 'service is not available') text = 'Mary has a little lamb, little lamb, little lamb.' request = Synthesizer.Request(text=text, metadata='''{"text_type":"ssml"}''') future = client.call_async(request) while rclpy.ok(): rclpy.spin_once(node) if future.done(): self.failIf(future.result() is None, 'nothing is returned') break print('Waiting for service to be done.') res = future.result() self.assertIsNotNone(res) self.assertTrue(type(res) is Synthesizer.Response) r = json.loads(res.result) self.assertIn('Audio Type', r, 'result should contain audio type') self.assertIn('Audio File', r, 'result should contain file path') self.assertIn('Amazon Polly Response Metadata', r, 'result should contain metadata') audio_type = r['Audio Type'] audio_file = r['Audio File'] md = r['Amazon Polly Response Metadata'] self.assertTrue("'HTTPStatusCode': 200," in md) self.assertEqual('audio/pcm', audio_type) self.assertTrue(audio_file.endswith('.wav')) import subprocess o = subprocess.check_output(['file', audio_file], stderr=subprocess.STDOUT) import re m = re.search(r'.*WAVE audio.*', o.decode('utf-8'), flags=re.MULTILINE) self.assertIsNotNone(m) node.destroy_node() @launch_testing.post_shutdown_test() class TestNodesStatusAfterShutdown(unittest.TestCase): def test_processes_finished_gracefully(self, proc_info): """Test that both executables finished gracefully.""" launch_testing.asserts.assertExitCodes(proc_info)