From 6063130e55116c06ce1ecddc37c073e6a6b9f5ea Mon Sep 17 00:00:00 2001 From: laurilako Date: Mon, 17 Jul 2023 22:38:20 +0300 Subject: [PATCH 1/9] Initial speech synthesis commit --- .gitignore | 2 + src/tts_package/package.xml | 20 ++++++++ src/tts_package/resource/tts_package | 0 src/tts_package/setup.cfg | 4 ++ src/tts_package/setup.py | 27 +++++++++++ src/tts_package/srv/StringToWav.srv | 3 ++ src/tts_package/test/test_copyright.py | 23 ++++++++++ src/tts_package/test/test_flake8.py | 25 ++++++++++ src/tts_package/test/test_pep257.py | 23 ++++++++++ src/tts_package/tts_package/__init__.py | 0 .../tts_package/tts_member_function.py | 41 +++++++++++++++++ src/tts_package/tts_package/tts_node.py | 46 +++++++++++++++++++ 12 files changed, 214 insertions(+) create mode 100644 src/tts_package/package.xml create mode 100644 src/tts_package/resource/tts_package create mode 100644 src/tts_package/setup.cfg create mode 100644 src/tts_package/setup.py create mode 100644 src/tts_package/srv/StringToWav.srv create mode 100644 src/tts_package/test/test_copyright.py create mode 100644 src/tts_package/test/test_flake8.py create mode 100644 src/tts_package/test/test_pep257.py create mode 100644 src/tts_package/tts_package/__init__.py create mode 100644 src/tts_package/tts_package/tts_member_function.py create mode 100644 src/tts_package/tts_package/tts_node.py diff --git a/.gitignore b/.gitignore index 5c577a6e..e9333223 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ .vscode/* !.vscode/c_cpp_properties.json +src/tts_package/resource/config.json +src/tts_package/resource/model151k.pth build devel diff --git a/src/tts_package/package.xml b/src/tts_package/package.xml new file mode 100644 index 00000000..123783cb --- /dev/null +++ b/src/tts_package/package.xml @@ -0,0 +1,20 @@ + + + + tts_package + 0.0.0 + Text-to-speech pacakge for synthetizing speech. + vagrant + TODO: License declaration + + TTS + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/tts_package/resource/tts_package b/src/tts_package/resource/tts_package new file mode 100644 index 00000000..e69de29b diff --git a/src/tts_package/setup.cfg b/src/tts_package/setup.cfg new file mode 100644 index 00000000..5fd11624 --- /dev/null +++ b/src/tts_package/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script-dir=$base/lib/tts_package +[install] +install-scripts=$base/lib/tts_package diff --git a/src/tts_package/setup.py b/src/tts_package/setup.py new file mode 100644 index 00000000..953c11ab --- /dev/null +++ b/src/tts_package/setup.py @@ -0,0 +1,27 @@ +from setuptools import setup + +package_name = 'tts_package' + +setup( + name=package_name, + version='0.0.0', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='vagrant', + maintainer_email='vagrant@todo.todo', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'service = tts_package.tts_node:main', + 'client = tts_package.tts_member_function:main', + ], + }, +) diff --git a/src/tts_package/srv/StringToWav.srv b/src/tts_package/srv/StringToWav.srv new file mode 100644 index 00000000..cfec3c56 --- /dev/null +++ b/src/tts_package/srv/StringToWav.srv @@ -0,0 +1,3 @@ +string data +--- +bool success \ No newline at end of file diff --git a/src/tts_package/test/test_copyright.py b/src/tts_package/test/test_copyright.py new file mode 100644 index 00000000..cc8ff03f --- /dev/null +++ b/src/tts_package/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/tts_package/test/test_flake8.py b/src/tts_package/test/test_flake8.py new file mode 100644 index 00000000..27ee1078 --- /dev/null +++ b/src/tts_package/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/tts_package/test/test_pep257.py b/src/tts_package/test/test_pep257.py new file mode 100644 index 00000000..b234a384 --- /dev/null +++ b/src/tts_package/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/tts_package/tts_package/__init__.py b/src/tts_package/tts_package/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/tts_package/tts_package/tts_member_function.py b/src/tts_package/tts_package/tts_member_function.py new file mode 100644 index 00000000..94ef2e95 --- /dev/null +++ b/src/tts_package/tts_package/tts_member_function.py @@ -0,0 +1,41 @@ +import sys + +from tts_package.srv import StringToWav + +import rclpy +from rclpy.node import Node + + +class ttsClientAsync(Node): + + def __init__(self): + super().__init__('tts_client_async') + self.cli = self.create_client(StringToWav, 'StringToWav') + while not self.cli.wait_for_service(timeout_sec=1.0): + self.get_logger().info('service not available, waiting again...') + self.req = StringToWav.Request() + + def send_request(self, data): + self.req.data = data + self.future = self.cli.call_async(self.req) + rclpy.spin_until_future_complete(self, self.future) + return self.future.result() + +def main(): + rclpy.init() + + tts_client = ttsClientAsync() + response = tts_client.send_request(int(sys.argv[1])) + if(response.success): + minimal_client.get_logger().info( + 'Succesfully synthentized!') + else: + minimal_client.get_logger().info( + 'Failed to synthentize!' + ) + tts_client.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/tts_package/tts_package/tts_node.py b/src/tts_package/tts_package/tts_node.py new file mode 100644 index 00000000..f9390a4a --- /dev/null +++ b/src/tts_package/tts_package/tts_node.py @@ -0,0 +1,46 @@ +from TTS.utils.synthetizer import Synthetizer + +from rclpy.node import Node + +from tts_package.srv import StringToWav + +import rclpy +from rclpy.node import Node + + +class TTSService(Node): + + def __init__(self): + super().__init__('TTS_service') + self.srv = self.create_service(StringToWav, 'StringToWav', self.stringToWav_callback) + self.synthetizer = Synthetizer( + "./resource/model.pth", + "./resource/config.json") + self.output = "./resource/output" + + + + def stringToWav_callback(self, request, response): + try: + wav = self.synthetizer.tts(sentence) + self.synthetizer.save_wav(wav, output) + except Exception: + response.success = False + else: + response.success = True + self.get_logger().info("Incoming request to synthentize string: %s" % (request.data)) + return response + + +def main(): + rclpy.init() + + TTSService = TTSService() + + rclpy.spin(TTSService) + + rclpy.shutdown() + + +if __name__ == '__main__': + main() \ No newline at end of file From cf5eda0c33adfee47c5abef4d7cf4364b7c65a4e Mon Sep 17 00:00:00 2001 From: laurilako Date: Tue, 18 Jul 2023 10:38:48 +0300 Subject: [PATCH 2/9] Implement tts_package (service and client) and tts_msgs (interface) for speech synthesis feature --- .gitignore | 1 + src/tts_msgs/CMakeLists.txt | 40 +++++++++++++++++++ src/tts_msgs/package.xml | 23 +++++++++++ .../srv/StringToWav.srv | 0 src/tts_package/package.xml | 6 ++- .../tts_package/tts_member_function.py | 10 +++-- src/tts_package/tts_package/tts_node.py | 26 ++++++------ 7 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 src/tts_msgs/CMakeLists.txt create mode 100644 src/tts_msgs/package.xml rename src/{tts_package => tts_msgs}/srv/StringToWav.srv (100%) diff --git a/.gitignore b/.gitignore index e9333223..d5d86b8b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ src/tts_package/resource/config.json src/tts_package/resource/model151k.pth +src/tts_package/resource/output.wav build devel diff --git a/src/tts_msgs/CMakeLists.txt b/src/tts_msgs/CMakeLists.txt new file mode 100644 index 00000000..51b8b3a5 --- /dev/null +++ b/src/tts_msgs/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.5) +project(tts_msgs) + +# Default to C99 +if(NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) +endif() + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) +find_package(rosidl_default_generators REQUIRED) + +rosidl_generate_interfaces(${PROJECT_NAME} + "srv/StringToWav.srv" +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # uncomment the line when a copyright and license is not present in all source files + #set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # uncomment the line when this package is not in a git repo + #set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/tts_msgs/package.xml b/src/tts_msgs/package.xml new file mode 100644 index 00000000..85867a7f --- /dev/null +++ b/src/tts_msgs/package.xml @@ -0,0 +1,23 @@ + + + + tts_msgs + 1.0.0 + Interface for tts_package + Konsta Laurila + TODO: License declaration + + ament_cmake + + ament_lint_auto + ament_lint_common + + geometry_msgs + rosidl_default_generators + rosidl_default_runtime + rosidl_interface_packages + + + ament_cmake + + diff --git a/src/tts_package/srv/StringToWav.srv b/src/tts_msgs/srv/StringToWav.srv similarity index 100% rename from src/tts_package/srv/StringToWav.srv rename to src/tts_msgs/srv/StringToWav.srv diff --git a/src/tts_package/package.xml b/src/tts_package/package.xml index 123783cb..cfdb312e 100644 --- a/src/tts_package/package.xml +++ b/src/tts_package/package.xml @@ -2,13 +2,15 @@ tts_package - 0.0.0 + 1.0.0 Text-to-speech pacakge for synthetizing speech. - vagrant + Konsta Laurila TODO: License declaration TTS + tts_msgs + ament_copyright ament_flake8 ament_pep257 diff --git a/src/tts_package/tts_package/tts_member_function.py b/src/tts_package/tts_package/tts_member_function.py index 94ef2e95..9bb0fcb6 100644 --- a/src/tts_package/tts_package/tts_member_function.py +++ b/src/tts_package/tts_package/tts_member_function.py @@ -1,6 +1,6 @@ import sys -from tts_package.srv import StringToWav +from tts_msgs.srv import StringToWav import rclpy from rclpy.node import Node @@ -25,12 +25,14 @@ def main(): rclpy.init() tts_client = ttsClientAsync() - response = tts_client.send_request(int(sys.argv[1])) + + response = tts_client.send_request(sys.argv[1]) + if(response.success): - minimal_client.get_logger().info( + tts_client.get_logger().info( 'Succesfully synthentized!') else: - minimal_client.get_logger().info( + tts_client.get_logger().info( 'Failed to synthentize!' ) tts_client.destroy_node() diff --git a/src/tts_package/tts_package/tts_node.py b/src/tts_package/tts_package/tts_node.py index f9390a4a..3e1144e2 100644 --- a/src/tts_package/tts_package/tts_node.py +++ b/src/tts_package/tts_package/tts_node.py @@ -1,8 +1,8 @@ -from TTS.utils.synthetizer import Synthetizer +from TTS.utils.synthesizer import Synthesizer from rclpy.node import Node -from tts_package.srv import StringToWav +from tts_msgs.srv import StringToWav import rclpy from rclpy.node import Node @@ -13,31 +13,33 @@ class TTSService(Node): def __init__(self): super().__init__('TTS_service') self.srv = self.create_service(StringToWav, 'StringToWav', self.stringToWav_callback) - self.synthetizer = Synthetizer( - "./resource/model.pth", - "./resource/config.json") - self.output = "./resource/output" + self.synthetizer = Synthesizer( + "src/tts_package/resource/model151k.pth", + "src/tts_package/resource/config.json") + self.output = "src/tts_package/resource/output.wav" + self.get_logger().info("Service running...") def stringToWav_callback(self, request, response): + self.get_logger().info("Incoming request to synthentize string: %s" % (request.data)) try: - wav = self.synthetizer.tts(sentence) - self.synthetizer.save_wav(wav, output) + wav = self.synthetizer.tts(request.data) + self.synthetizer.save_wav(wav, self.output) except Exception: + print(Exception) response.success = False else: response.success = True - self.get_logger().info("Incoming request to synthentize string: %s" % (request.data)) + self.get_logger().info("Callback over. Service running...") return response - def main(): rclpy.init() - TTSService = TTSService() + TTS_Service = TTSService() - rclpy.spin(TTSService) + rclpy.spin(TTS_Service) rclpy.shutdown() From 0e2217d94034dc32104f3c8173eb6b7432e30c4a Mon Sep 17 00:00:00 2001 From: Aapo2001 Date: Tue, 18 Jul 2023 11:30:57 +0300 Subject: [PATCH 3/9] variable name changed to avoid collisions --- src/tts_package/tts_package/tts_node.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/tts_package/tts_package/tts_node.py b/src/tts_package/tts_package/tts_node.py index 3e1144e2..22c141bc 100644 --- a/src/tts_package/tts_package/tts_node.py +++ b/src/tts_package/tts_package/tts_node.py @@ -5,22 +5,20 @@ from tts_msgs.srv import StringToWav import rclpy -from rclpy.node import Node class TTSService(Node): def __init__(self): super().__init__('TTS_service') - self.srv = self.create_service(StringToWav, 'StringToWav', self.stringToWav_callback) + self.srv = self.create_service( + StringToWav, 'StringToWav', self.stringToWav_callback) self.synthetizer = Synthesizer( "src/tts_package/resource/model151k.pth", "src/tts_package/resource/config.json") self.output = "src/tts_package/resource/output.wav" self.get_logger().info("Service running...") - - def stringToWav_callback(self, request, response): self.get_logger().info("Incoming request to synthentize string: %s" % (request.data)) try: @@ -34,15 +32,16 @@ def stringToWav_callback(self, request, response): self.get_logger().info("Callback over. Service running...") return response + def main(): rclpy.init() - TTS_Service = TTSService() + tts_service = TTSService() - rclpy.spin(TTS_Service) + rclpy.spin(tts_service) rclpy.shutdown() if __name__ == '__main__': - main() \ No newline at end of file + main() From 3048ddc86044a7e01cc174961c8a5905f344c2ca Mon Sep 17 00:00:00 2001 From: laurilako Date: Tue, 18 Jul 2023 15:56:13 +0300 Subject: [PATCH 4/9] Created README for package. Added scripts to install dependencies in bootstrap.sh. --- .gitignore | 1 + README.md | 1 + src/tts_package/README.md | 30 +++++++++++++++++++++++++ src/tts_package/package.xml | 2 +- src/tts_package/tts_package/tts_node.py | 2 +- vagrant-scripts/bootstrap.sh | 10 ++++++++- 6 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 src/tts_package/README.md diff --git a/.gitignore b/.gitignore index d5d86b8b..5c25e783 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .vscode/* !.vscode/c_cpp_properties.json +src/tts_package/resource/* src/tts_package/resource/config.json src/tts_package/resource/model151k.pth src/tts_package/resource/output.wav diff --git a/README.md b/README.md index b0212d35..2004a7f9 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Joint -> Servo mappings are defined in two files. Configuration file connects se * src/inmoov_description - robot files, which define the robot geometry and configuration for simulation (URDF, SRDF & rviz configuration) * src/robot - robot launch files & servo controller configurations * src/robot_hardware - hardware interface for ros2_controller, communicates with U2D2 via dynamixel workbench + * src/tts_package - Text-to-speech package for finnish speech synthesis ## Servo Table diff --git a/src/tts_package/README.md b/src/tts_package/README.md new file mode 100644 index 00000000..7f4eef52 --- /dev/null +++ b/src/tts_package/README.md @@ -0,0 +1,30 @@ +This package contains a service and client for finnish text-to-speech feature. + +# Usage + +## Before +Check that model.pth and config.json are located in src/tts_package/resource/ folder. These should be downloaded and installed automatically when installing environment with vagrant. Scripts located in /vagrant-scripts/bootstrap.sh. + +## Run TTS service +> ros2 run tts_package service + + +## Using the service +Service can be used by calling client with terminal, giving sentence as an argument. Note that sentence should be inside quotes and in finnish. +> ros2 run tts_package client "Hei. Tässä on lause joka syntentisoidaan puheeksi." + +Service will now try to synthentise sentence into .wav file located in 'src/tts_package/resource/output.wav' which can then be played from speaker. + +## Dependencies + +* `TTS` +* `espeak-ng` + +These are included in the newest version of the vagrantfile. If these are not installed during bootstrap, they need to be installed to VM before starting the service. + +> pip install TTS +> apt -y install espeak + +## Potential future improvements + +* Implement feature that generated .wav file will be played automatically once when created diff --git a/src/tts_package/package.xml b/src/tts_package/package.xml index cfdb312e..22ec7530 100644 --- a/src/tts_package/package.xml +++ b/src/tts_package/package.xml @@ -7,7 +7,7 @@ Konsta Laurila TODO: License declaration - TTS + python3-TTS tts_msgs diff --git a/src/tts_package/tts_package/tts_node.py b/src/tts_package/tts_package/tts_node.py index 3e1144e2..da43c182 100644 --- a/src/tts_package/tts_package/tts_node.py +++ b/src/tts_package/tts_package/tts_node.py @@ -14,7 +14,7 @@ def __init__(self): super().__init__('TTS_service') self.srv = self.create_service(StringToWav, 'StringToWav', self.stringToWav_callback) self.synthetizer = Synthesizer( - "src/tts_package/resource/model151k.pth", + "src/tts_package/resource/model.pth", "src/tts_package/resource/config.json") self.output = "src/tts_package/resource/output.wav" self.get_logger().info("Service running...") diff --git a/vagrant-scripts/bootstrap.sh b/vagrant-scripts/bootstrap.sh index 132d2397..02759f82 100644 --- a/vagrant-scripts/bootstrap.sh +++ b/vagrant-scripts/bootstrap.sh @@ -113,7 +113,8 @@ source /opt/ros/foxy/setup.bash apt install -y \ ros-foxy-test-msgs ros-foxy-control-msgs \ ros-foxy-realtime-tools ros-foxy-xacro ros-foxy-angles \ - v4l-utils + v4l-utils \ + espeak # Install ros2_control (https://github.com/ros-controls/ros2_control) @@ -157,6 +158,13 @@ cd /workspace rosdep update rosdep install --from-paths src --ignore-src --rosdistro foxy -r -y +# install TTS dependency +python3 -m pip install TTS + +# curl TTS model and config .zip, unzip it into resources +curl -L 'https://www.dropbox.com/scl/fo/vtx8ieqs8n6x4khjcc9nj/h?rlkey=65mddh9yke5wag1zlauwepjg2&dl=1' --output src/tts_package/resource/model.zip +unzip src/tts_package/resource/model.zip -d src/tts_package/resource + # Enable sourcing of built ros2 environment to bash configuration echo "source install/setup.bash" >> /home/vagrant/.bashrc From 6a4716415f198aa64fdd77dedf44eb131e3a95fa Mon Sep 17 00:00:00 2001 From: Konsta Laurila <71126961+laurilako@users.noreply.github.com> Date: Tue, 18 Jul 2023 16:23:48 +0300 Subject: [PATCH 5/9] Update README.md --- src/tts_package/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tts_package/README.md b/src/tts_package/README.md index 7f4eef52..4d5cfc2c 100644 --- a/src/tts_package/README.md +++ b/src/tts_package/README.md @@ -22,7 +22,9 @@ Service will now try to synthentise sentence into .wav file located in 'src/tts_ These are included in the newest version of the vagrantfile. If these are not installed during bootstrap, they need to be installed to VM before starting the service. +Install TTS > pip install TTS +And install espeak > apt -y install espeak ## Potential future improvements From 987bb657ca9df5e7b506217ae7bc119a1454ff96 Mon Sep 17 00:00:00 2001 From: Konsta Laurila <71126961+laurilako@users.noreply.github.com> Date: Tue, 18 Jul 2023 16:26:34 +0300 Subject: [PATCH 6/9] Update README.md --- src/tts_package/README.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/tts_package/README.md b/src/tts_package/README.md index 4d5cfc2c..4341cda9 100644 --- a/src/tts_package/README.md +++ b/src/tts_package/README.md @@ -5,16 +5,6 @@ This package contains a service and client for finnish text-to-speech feature. ## Before Check that model.pth and config.json are located in src/tts_package/resource/ folder. These should be downloaded and installed automatically when installing environment with vagrant. Scripts located in /vagrant-scripts/bootstrap.sh. -## Run TTS service -> ros2 run tts_package service - - -## Using the service -Service can be used by calling client with terminal, giving sentence as an argument. Note that sentence should be inside quotes and in finnish. -> ros2 run tts_package client "Hei. Tässä on lause joka syntentisoidaan puheeksi." - -Service will now try to synthentise sentence into .wav file located in 'src/tts_package/resource/output.wav' which can then be played from speaker. - ## Dependencies * `TTS` @@ -23,10 +13,21 @@ Service will now try to synthentise sentence into .wav file located in 'src/tts_ These are included in the newest version of the vagrantfile. If these are not installed during bootstrap, they need to be installed to VM before starting the service. Install TTS -> pip install TTS +> pip install TTS
+ And install espeak > apt -y install espeak +## Run TTS service +> ros2 run tts_package service + + +## Using the service +Service can be used by calling client with terminal, giving sentence as an argument. Note that sentence should be inside quotes and in finnish. +> ros2 run tts_package client "Hei. Tässä on lause joka syntentisoidaan puheeksi." + +Service will now try to synthentise sentence into .wav file located in 'src/tts_package/resource/output.wav' which can then be played from speaker. + ## Potential future improvements * Implement feature that generated .wav file will be played automatically once when created From 45ce04118ee2f5159292eeefb8682faac48b62c5 Mon Sep 17 00:00:00 2001 From: Konsta Laurila <71126961+laurilako@users.noreply.github.com> Date: Tue, 18 Jul 2023 16:37:26 +0300 Subject: [PATCH 7/9] Update BRINGUP.md Added initial text-to-speech service bring-up documentation --- docs/BRINGUP.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/BRINGUP.md b/docs/BRINGUP.md index e05a0aca..bf4d4a33 100644 --- a/docs/BRINGUP.md +++ b/docs/BRINGUP.md @@ -55,6 +55,22 @@ Anyway, you are able to test the face tracking and eye movements like this. **Note: currently, only jaw, eyes, right hand & head pan movement can be simulated** +### Launching text-to-speech service + +Text-to-speech works as a service which can be called by terminal. + +Run service in a (new) terminal + +```console +ros2 run tts_package service +``` + +Call the service from terminal and synthesise speech + +```console +ros2 run tts_package client "Tämä lause syntentisoidaan puheeksi." +``` + ## Bring-up real HW robot ### (0. Test servo communication) @@ -142,6 +158,22 @@ Finally, start the eye movement node in a new terminal window ros2 run eye_movement eye_movement_node ``` +### 5. Launching text-to-speech service + +Text-to-speech works as a service which can be called by terminal. + +Run service in a (new) terminal window + +```console +ros2 run tts_package service +``` + +Call the service from separate terminal and synthesise speech + +```console +ros2 run tts_package client "Tämä lause syntentisoidaan puheeksi." +``` + **Todo: simplify bring up process (add the starting of the controllers to the launch file)** ## Sending action goals manually From 43ce209481fcfe4c6591ecf25bc6e6b2d75527f0 Mon Sep 17 00:00:00 2001 From: laurilako Date: Tue, 8 Aug 2023 15:16:37 +0300 Subject: [PATCH 8/9] Add automatic play functionality for TTS Service --- src/tts_package/README.md | 9 +++++---- src/tts_package/package.xml | 1 + src/tts_package/tts_package/tts_member_function.py | 5 ++++- src/tts_package/tts_package/tts_node.py | 14 ++++++++++++-- vagrant-scripts/bootstrap.sh | 3 ++- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/tts_package/README.md b/src/tts_package/README.md index 4341cda9..318c5065 100644 --- a/src/tts_package/README.md +++ b/src/tts_package/README.md @@ -1,4 +1,4 @@ -This package contains a service and client for finnish text-to-speech feature. +This package contains service and client for finnish text-to-speech feature. Service will automatically play synthesized speech when called with wanted sentence as an argument. # Usage @@ -9,6 +9,7 @@ Check that model.pth and config.json are located in src/tts_package/resource/ fo * `TTS` * `espeak-ng` +* `simpleaudio` These are included in the newest version of the vagrantfile. If these are not installed during bootstrap, they need to be installed to VM before starting the service. @@ -23,11 +24,11 @@ And install espeak ## Using the service -Service can be used by calling client with terminal, giving sentence as an argument. Note that sentence should be inside quotes and in finnish. +Service can be used by calling client with terminal, giving sentences as an argument. Note that sentences should be inside quotes and in finnish. > ros2 run tts_package client "Hei. Tässä on lause joka syntentisoidaan puheeksi." -Service will now try to synthentise sentence into .wav file located in 'src/tts_package/resource/output.wav' which can then be played from speaker. +Service will now try to synthentize sentence into .wav file located in 'src/tts_package/resource/output.wav' which will then be played automatically. ## Potential future improvements -* Implement feature that generated .wav file will be played automatically once when created +* Implement this feature to work with potential speech-to-text and chatbot features. diff --git a/src/tts_package/package.xml b/src/tts_package/package.xml index 22ec7530..fef5137e 100644 --- a/src/tts_package/package.xml +++ b/src/tts_package/package.xml @@ -8,6 +8,7 @@ TODO: License declaration python3-TTS + python3-simpleaudio tts_msgs diff --git a/src/tts_package/tts_package/tts_member_function.py b/src/tts_package/tts_package/tts_member_function.py index 9bb0fcb6..9fd701a4 100644 --- a/src/tts_package/tts_package/tts_member_function.py +++ b/src/tts_package/tts_package/tts_member_function.py @@ -7,7 +7,10 @@ class ttsClientAsync(Node): - + + # Client Node that is used to call the TTS service. Takes sentence as an argument that service will try to synthetize. + # ros2 run tts_package client "Tässä teksti joka syntentisoidaan. Voi sisältää useampiakin lauseita kunhan ne ovat lainausmerkkien sisällä". + def __init__(self): super().__init__('tts_client_async') self.cli = self.create_client(StringToWav, 'StringToWav') diff --git a/src/tts_package/tts_package/tts_node.py b/src/tts_package/tts_package/tts_node.py index 720fb031..2cbce953 100644 --- a/src/tts_package/tts_package/tts_node.py +++ b/src/tts_package/tts_package/tts_node.py @@ -1,3 +1,5 @@ +import simpleaudio as sa + from TTS.utils.synthesizer import Synthesizer from rclpy.node import Node @@ -9,6 +11,7 @@ class TTSService(Node): + # Initialize node def __init__(self): super().__init__('TTS_service') self.srv = self.create_service( @@ -19,19 +22,26 @@ def __init__(self): self.output = "src/tts_package/resource/output.wav" self.get_logger().info("Service running...") + # Callback function. Waits for call and then synthetizes given request and plays synthetized speech. def stringToWav_callback(self, request, response): self.get_logger().info("Incoming request to synthentize string: %s" % (request.data)) try: wav = self.synthetizer.tts(request.data) self.synthetizer.save_wav(wav, self.output) - except Exception: - print(Exception) + self.play_audio() + except Exception as e: + self.get_logger().info(f"Error happened: {str(e)}") response.success = False else: response.success = True self.get_logger().info("Callback over. Service running...") return response + # Function that plays created .wav file. + def play_audio(self): + wave_obj = sa.WaveObject.from_wave_file(self.output) + play_obj = wave_obj.play() + play_obj.wait_done() def main(): rclpy.init() diff --git a/vagrant-scripts/bootstrap.sh b/vagrant-scripts/bootstrap.sh index 02759f82..0470dd18 100644 --- a/vagrant-scripts/bootstrap.sh +++ b/vagrant-scripts/bootstrap.sh @@ -160,8 +160,9 @@ rosdep install --from-paths src --ignore-src --rosdistro foxy -r -y # install TTS dependency python3 -m pip install TTS +python3 -m pip install simpleaudio -# curl TTS model and config .zip, unzip it into resources +# curl .zip file containing model and config for TTS, unzip it into tts_package/resource folder curl -L 'https://www.dropbox.com/scl/fo/vtx8ieqs8n6x4khjcc9nj/h?rlkey=65mddh9yke5wag1zlauwepjg2&dl=1' --output src/tts_package/resource/model.zip unzip src/tts_package/resource/model.zip -d src/tts_package/resource From 1936c687ee9209e7d46a211b874f0b04020cb7b8 Mon Sep 17 00:00:00 2001 From: laurilako Date: Tue, 8 Aug 2023 15:25:59 +0300 Subject: [PATCH 9/9] Cleaning --- .gitignore | 2 +- docs/BRINGUP.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 5c25e783..7291c2a3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ src/tts_package/resource/* src/tts_package/resource/config.json -src/tts_package/resource/model151k.pth +src/tts_package/resource/model.pth src/tts_package/resource/output.wav build diff --git a/docs/BRINGUP.md b/docs/BRINGUP.md index bf4d4a33..5932200d 100644 --- a/docs/BRINGUP.md +++ b/docs/BRINGUP.md @@ -57,15 +57,15 @@ Anyway, you are able to test the face tracking and eye movements like this. ### Launching text-to-speech service -Text-to-speech works as a service which can be called by terminal. +Text-to-speech works as a service which can be called from terminal utilizing the ros2 client in package. -Run service in a (new) terminal +Run the service in a (new) terminal ```console ros2 run tts_package service ``` -Call the service from terminal and synthesise speech +Call the service from terminal using client and synthetize speech ```console ros2 run tts_package client "Tämä lause syntentisoidaan puheeksi." @@ -160,15 +160,15 @@ ros2 run eye_movement eye_movement_node ### 5. Launching text-to-speech service -Text-to-speech works as a service which can be called by terminal. +Text-to-speech works as a service which can be called from terminal utilizing the ros2 client in package. -Run service in a (new) terminal window +Run the service in a (new) terminal ```console ros2 run tts_package service ``` -Call the service from separate terminal and synthesise speech +Call the service from terminal using client and synthetize speech ```console ros2 run tts_package client "Tämä lause syntentisoidaan puheeksi."