From 2d101e7cfb5228f5238d7ff94e68a5527dacb125 Mon Sep 17 00:00:00 2001 From: Kotaro Yoshimoto Date: Sat, 5 Nov 2022 11:20:42 +0900 Subject: [PATCH 1/6] feat: port meta files to ros2 --- CMakeLists.txt | 75 +++++++++++++++++++++++++++++++++++---------- action/Speak.action | 4 +-- package.xml | 20 ++++++++++-- 3 files changed, 78 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6dafcad..67f2777 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,21 +1,64 @@ -cmake_minimum_required(VERSION 2.8.3) +cmake_minimum_required(VERSION 3.5) project(japanese_text_to_speech) -find_package(catkin REQUIRED - actionlib_msgs - genmsg -) +#find_package(catkin REQUIRED +# actionlib_msgs +# genmsg +#) +# +#add_action_files(DIRECTORY action FILES Speak.action) +#generate_messages(DEPENDENCIES actionlib_msgs) +# +#catkin_package( +# CATKIN_DEPENDS actionlib actionlib_msgs +#) +#catkin_install_python( +# PROGRAMS +# nodes/japanese_text_to_speech +# nodes/test_client +# DESTINATION +# ${CATKIN_PACKAGE_BIN_DESTINATION} +#) -add_action_files(DIRECTORY action FILES Speak.action) -generate_messages(DEPENDENCIES actionlib_msgs) +# Default to C99 +if(NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) +endif() -catkin_package( - CATKIN_DEPENDS actionlib actionlib_msgs -) -catkin_install_python( - PROGRAMS - nodes/japanese_text_to_speech - nodes/test_client - DESTINATION - ${CATKIN_PACKAGE_BIN_DESTINATION} +# 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) +find_package(ament_cmake_python REQUIRED) +find_package(builtin_interfaces REQUIRED) +find_package(rosidl_default_generators REQUIRED) + +#ament_python_install_package(nodes) + +### ここにメッセージやサービスのファイルを記述 ### +rosidl_generate_interfaces(${PROJECT_NAME} + "action/Speak.action" + DEPENDENCIES builtin_interfaces ) + +# Install Python executables +install(PROGRAMS + nodes/japanese_text_to_speech + nodes/test_client + DESTINATION lib/${PROJECT_NAME} + ) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_export_dependencies(rosidl_default_runtime) +ament_package() diff --git a/action/Speak.action b/action/Speak.action index 227fdf6..1c9f71e 100644 --- a/action/Speak.action +++ b/action/Speak.action @@ -6,9 +6,9 @@ string text float64 speed_rate --- # Result -time elapsed_time +builtin_interfaces/Time elapsed_time --- # Feedback uint8 GENERATING_VOICE=0 uint8 PLAYING_VOICE=1 -uint8 state \ No newline at end of file +uint8 state diff --git a/package.xml b/package.xml index 0731d19..20a7c58 100644 --- a/package.xml +++ b/package.xml @@ -1,5 +1,5 @@ - + japanese_text_to_speech 0.1.0 The japanese_text_to_speech package @@ -8,8 +8,22 @@ TODO https://github.com/ActiveIntelligentSystemsLab/japanese_tts_ros - catkin + + ament_cmake_python + ament_cmake + actionlib actionlib_msgs - rospy + builtin_interfaces + rclpy + + + rosidl_default_generators + action_msgs + rosidl_default_runtime + rosidl_interface_packages + + + ament_cmake + From 5cf24f5cab1bc7feaf4da3c9acf05e9812ead0ca Mon Sep 17 00:00:00 2001 From: Kotaro Yoshimoto Date: Sat, 5 Nov 2022 11:20:54 +0900 Subject: [PATCH 2/6] feat: port japanese_text_to_speech to ros2 --- nodes/japanese_text_to_speech | 98 +++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/nodes/japanese_text_to_speech b/nodes/japanese_text_to_speech index a8a4e85..d3473e7 100755 --- a/nodes/japanese_text_to_speech +++ b/nodes/japanese_text_to_speech @@ -1,82 +1,102 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import os.path -import rospy -import actionlib -import japanese_text_to_speech.msg +import rclpy +from rclpy.action import ActionServer +from rclpy.node import Node +from japanese_text_to_speech.action import Speak -class JapaneseTextToSpeech: + +class JapaneseTextToSpeech(Node): _TEMPORALY_DIR = '/tmp/japanese_text_to_speech' _INPUT_TEXT_FILE = _TEMPORALY_DIR + '/input_text' _GENERATED_VOICE_FILE = _TEMPORALY_DIR + '/generated_voice.wav' def __init__(self): - self._action_name = rospy.get_param('~action_name', 'japanese_text_to_speech') - self._dictionary_dir = rospy.get_param('~dictionary_dir', '/var/lib/mecab/dic/open-jtalk/naist-jdic') - self._hts_voice_file = rospy.get_param('~hts_voice_file', '/usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice') - self._player_command = rospy.get_param('~player_command', 'aplay') + super().__init__('japanese_text_to_speech') + + self.declare_parameters( + namespace='', + parameters=[ + ('action_name', 'japanese_text_to_speech'), + ('dictionary_dir', '/var/lib/mecab/dic/open-jtalk/naist-jdic'), + ('hts_voice_file', '/usr/share/hts-voice/nitech-jp-atr503-m001/nitech_jp_atr503_m001.htsvoice'), + ('aplayer_command', 'aplay') + ] + ) + self._action_name = self.get_parameter('action_name') + self._dictionary_dir = self.get_parameter('dictionary_dir') + self._hts_voice_file = self.get_parameter('hts_voice_file') + self._player_command = self.get_parameter('aplayer_command') if not os.path.isdir(self._TEMPORALY_DIR): os.mkdir(self._TEMPORALY_DIR) + self._action_server = ActionServer(self, Speak, 'japanese_text_to_speech', self.execute_callback) - self._action_server = actionlib.SimpleActionServer(self._action_name, japanese_text_to_speech.msg.SpeakAction, execute_cb=self.execute_callback, auto_start=False) - self._action_server.start() - - def execute_callback(self, goal): - start_time = rospy.Time.now() + def execute_callback(self, goal_handle): + start_time = rclpy.Time.now() is_succeeded = True - self.write_input_text(goal.text) + self.write_input_text(goal_handle.request.text) - if goal.speed_rate > 0.0: - self.generate_voice(goal.speed_rate) - self.play_voice() + if goal_handle.request.speed_rate > 0.0: + self.generate_voice(goal_handle.request.speed_rate, goal_handle) + self.play_voice(goal_handle) else: print("WARNING: Do not set 'speed_rate' to 0.0") is_succeeded = False - end_time = rospy.Time.now() + end_time = rclpy.Time.now() elapsed_time = end_time - start_time - result = japanese_text_to_speech.msg.SpeakResult() + result = Speak.SpeakResult() result.elapsed_time = elapsed_time + if is_succeeded: - self._action_server.set_succeeded(result) + goal_handle.succeed() else: - self._action_server.set_aborted(result) + goal_handle.abort() + + return result def write_input_text(self, input_text): - rospy.loginfo('Input text: %s', input_text) + self.get_logger().info('Input text: %s', input_text) input_text_file = open(self._INPUT_TEXT_FILE, 'w') input_text_file.write(input_text) input_text_file.close() - - def generate_voice(self, speed_rate): - feedback = japanese_text_to_speech.msg.SpeakFeedback() - feedback.state = japanese_text_to_speech.msg.SpeakFeedback.GENERATING_VOICE - self._action_server.publish_feedback(feedback) - rospy.loginfo('Converting text to wav file...') + def generate_voice(self, speed_rate, goal_handle): + feedback = Speak.SpeakFeedback() + feedback.state = Speak.SpeakFeedback.GENERATING_VOICE + goal_handle.publish_feedback(feedback) + + self.get_logger().info('Converting text to wav file...') command = 'open_jtalk -x ' + self._dictionary_dir + ' -m ' + self._hts_voice_file + ' -ow ' + self._GENERATED_VOICE_FILE + ' -r ' + str(speed_rate) + ' ' + self._INPUT_TEXT_FILE - rospy.loginfo('Send command: %s', command) + self.get_logger().info('Send command: %s', command) os.system(command) - def play_voice(self): - feedback = japanese_text_to_speech.msg.SpeakFeedback() - feedback.state = japanese_text_to_speech.msg.SpeakFeedback.PLAYING_VOICE - self._action_server.publish_feedback(feedback) + def play_voice(self, goal_handle): + feedback = Speak.SpeakFeedback() + feedback.state = Speak.SpeakFeedback.PLAYING_VOICE + goal_handle.publish_feedback(feedback) - rospy.loginfo('Playing wav file...') + self.get_logger().info('Playing wav file...') command = self._player_command + ' ' + self._GENERATED_VOICE_FILE - rospy.loginfo('Send command: %s', command) + self.get_logger().info('Send command: %s', command) os.system(command) -if __name__ == '__main__': - rospy.init_node('japanese_text_to_speech') + +def main(args=None): + + rclpy.init(args=args) tts = JapaneseTextToSpeech() - rospy.spin() + rclpy.spin(tts) + + +if __name__ == '__main__': + main() From db1b293b99dc9c1d766fbe539024c8ea1552f56f Mon Sep 17 00:00:00 2001 From: Kotaro Yoshimoto Date: Sat, 5 Nov 2022 11:33:56 +0900 Subject: [PATCH 3/6] feat: port test_client to ros2 --- nodes/test_client | 63 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/nodes/test_client b/nodes/test_client index b918390..5ae0bc1 100755 --- a/nodes/test_client +++ b/nodes/test_client @@ -1,23 +1,52 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- -import rospy -import actionlib -import japanese_text_to_speech.msg +import rclpy +from rclpy.action import ActionClient +from rclpy.node import Node +from japanese_text_to_speech.action import Speak -if __name__ == '__main__': - rospy.init_node('japanese_tts_test_client') - action_name = rospy.get_param('~action_name', 'japanese_text_to_speech') - text = rospy.get_param('~text', 'テスト') - speed_rate = rospy.get_param('~speed_rate', 0.5) - simple_client = actionlib.SimpleActionClient(action_name, japanese_text_to_speech.msg.SpeakAction) +class TestClient(Node): + + def __init__(self): + super().__init__('japanese_tts_test_client') + self.declare_parameters( + namespace='', + parameters=[ + ('action_name', 'japanese_text_to_speech'), + ('text', 'テスト'), + ('speed_rate', 0.5) + ] + ) + + self._action_name = self.get_parameter('action_name').get_parameter_value().string_value + self._text = self.get_parameter('text').get_parameter_value().string_value + self._speed_rate = self.get_parameter('speed_rate').get_parameter_value().double_value + self._action_client = ActionClient(self, Speak, self._action_name) + + def send_goal(self): + self.get_logger().info('Waiting japanese_text_to_speech server') + self._action_client.wait_for_server() + + self.get_logger().info('Sending goal to server') + goal_msg = Speak.Goal() + goal_msg.text = self._text + goal_msg.speed_rate = self._speed_rate + + return self._action_client.send_goal_async(goal_msg) + - rospy.loginfo('Waiting japanese_text_to_speech server') - simple_client.wait_for_server() +def main(args=None): + rclpy.init(args=args) + + action_client = TestClient() + + future = action_client.send_goal() + + rclpy.spin_until_future_complete(action_client, future) + + +if __name__ == '__main__': + main() - rospy.loginfo('Sending goal to server') - goal = japanese_text_to_speech.msg.SpeakGoal() - goal.text = text - goal.speed_rate = speed_rate - simple_client.send_goal_and_wait(goal) \ No newline at end of file From 08bc6b47750cf081ef19e306102b667cb6f57b0d Mon Sep 17 00:00:00 2001 From: Kotaro Yoshimoto Date: Sat, 5 Nov 2022 11:53:51 +0900 Subject: [PATCH 4/6] fix: runtime errors --- action/Speak.action | 2 +- nodes/japanese_text_to_speech | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/action/Speak.action b/action/Speak.action index 1c9f71e..bc06001 100644 --- a/action/Speak.action +++ b/action/Speak.action @@ -6,7 +6,7 @@ string text float64 speed_rate --- # Result -builtin_interfaces/Time elapsed_time +builtin_interfaces/Duration elapsed_time --- # Feedback uint8 GENERATING_VOICE=0 diff --git a/nodes/japanese_text_to_speech b/nodes/japanese_text_to_speech index d3473e7..a4fa93f 100755 --- a/nodes/japanese_text_to_speech +++ b/nodes/japanese_text_to_speech @@ -27,17 +27,17 @@ class JapaneseTextToSpeech(Node): ('aplayer_command', 'aplay') ] ) - self._action_name = self.get_parameter('action_name') - self._dictionary_dir = self.get_parameter('dictionary_dir') - self._hts_voice_file = self.get_parameter('hts_voice_file') - self._player_command = self.get_parameter('aplayer_command') + self._action_name = self.get_parameter('action_name').get_parameter_value().string_value + self._dictionary_dir = self.get_parameter('dictionary_dir').get_parameter_value().string_value + self._hts_voice_file = self.get_parameter('hts_voice_file').get_parameter_value().string_value + self._player_command = self.get_parameter('aplayer_command').get_parameter_value().string_value if not os.path.isdir(self._TEMPORALY_DIR): os.mkdir(self._TEMPORALY_DIR) - self._action_server = ActionServer(self, Speak, 'japanese_text_to_speech', self.execute_callback) + self._action_server = ActionServer(self, Speak, str(self._action_name), self.execute_callback) def execute_callback(self, goal_handle): - start_time = rclpy.Time.now() + start_time = self.get_clock().now() is_succeeded = True @@ -50,11 +50,11 @@ class JapaneseTextToSpeech(Node): print("WARNING: Do not set 'speed_rate' to 0.0") is_succeeded = False - end_time = rclpy.Time.now() + end_time = self.get_clock().now() elapsed_time = end_time - start_time - result = Speak.SpeakResult() - result.elapsed_time = elapsed_time + result = Speak.Result() + result.elapsed_time = elapsed_time.to_msg() if is_succeeded: goal_handle.succeed() @@ -65,29 +65,29 @@ class JapaneseTextToSpeech(Node): def write_input_text(self, input_text): - self.get_logger().info('Input text: %s', input_text) + self.get_logger().info(f'Input text: {input_text}') input_text_file = open(self._INPUT_TEXT_FILE, 'w') input_text_file.write(input_text) input_text_file.close() def generate_voice(self, speed_rate, goal_handle): - feedback = Speak.SpeakFeedback() - feedback.state = Speak.SpeakFeedback.GENERATING_VOICE + feedback = Speak.Feedback() + feedback.state = Speak.Feedback.GENERATING_VOICE goal_handle.publish_feedback(feedback) self.get_logger().info('Converting text to wav file...') command = 'open_jtalk -x ' + self._dictionary_dir + ' -m ' + self._hts_voice_file + ' -ow ' + self._GENERATED_VOICE_FILE + ' -r ' + str(speed_rate) + ' ' + self._INPUT_TEXT_FILE - self.get_logger().info('Send command: %s', command) + self.get_logger().info(f'Send command: {command}') os.system(command) def play_voice(self, goal_handle): - feedback = Speak.SpeakFeedback() - feedback.state = Speak.SpeakFeedback.PLAYING_VOICE + feedback = Speak.Feedback() + feedback.state = Speak.Feedback.PLAYING_VOICE goal_handle.publish_feedback(feedback) self.get_logger().info('Playing wav file...') command = self._player_command + ' ' + self._GENERATED_VOICE_FILE - self.get_logger().info('Send command: %s', command) + self.get_logger().info(f'Send command: {command}') os.system(command) From 54fe788878a85a2d190de5d3b750760dbf032efb Mon Sep 17 00:00:00 2001 From: Kotaro Yoshimoto Date: Sat, 5 Nov 2022 12:03:23 +0900 Subject: [PATCH 5/6] doc: update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0fb8fae..6827f0c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Open JTalkとaplayコマンドに依存しています. sudo apt install open-jtalk open-jtalk-mecab-naist-jdic hts-voice-nitech-jp-atr503-m001 alsa-utils ``` -catkin workspace内に設置して `catkin_make` してください. +colcon workspace内に設置して `colcon build` してください. ## 使い方 @@ -20,14 +20,14 @@ catkin workspace内に設置して `catkin_make` してください. この node がサーバとなり,クライアントから受け取ったテキストを音声出力します. ``` -rosrun japanese_text_to_speech japanese_text_to_speech +ros2 run japanese_text_to_speech japanese_text_to_speech ``` -この node は [actionlib](http://wiki.ros.org/actionlib) を使って実装されています. +この node は ros2 action を使って実装されています. 簡単なクライアントのサンプルとして `test_client` nodeを用意してあります. ``` -rosrun japanese_text_to_speech test_client +ros2 run japanese_text_to_speech test_client ``` ### 音声モデルの切り替え @@ -37,7 +37,7 @@ rosrun japanese_text_to_speech test_client `.htsvoice` ファイルを適当な場所に設置し, `~hts_voice_file` rosparam をセットしてください. ``` -rosrun japanese_text_to_speech japanese_text_to_speech _hts_voice_file:='/path/to/htsvoice' +ros2 run japanese_text_to_speech japanese_text_to_speech --ros-args -p hts_voice_file:='/path/to/htsvoice' ``` #### メイ&タクミ @@ -55,7 +55,7 @@ unzip MMDAgent_Example-1.8.zip 例えば,メイの幸せな声の音声モデルを利用する場合は,次のように引数を追加します. ```bash -rosrun japanese_text_to_speech japanese_text_to_speech _hts_voice_file:='PATH_TO_UNZIPPED_FILES/MMDAgent_Example-1.8/Voice/mei/mei_happy.htsvoice' +ros2 run japanese_text_to_speech japanese_text_to_speech --ros-args -p hts_voice_file:='PATH_TO_UNZIPPED_FILES/MMDAgent_Example-1.8/Voice/mei/mei_happy.htsvoice' ``` 参考:https://www.rcnp.osaka-u.ac.jp/~kohda/linux/espeak.html From 54d1d037ab86799f2285225c1e181a085f3dfba8 Mon Sep 17 00:00:00 2001 From: Kotaro Yoshimoto Date: Sat, 5 Nov 2022 12:16:42 +0900 Subject: [PATCH 6/6] chore: delete unused lines --- CMakeLists.txt | 23 ----------------------- package.xml | 1 - 2 files changed, 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 67f2777..325d333 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,25 +1,6 @@ cmake_minimum_required(VERSION 3.5) project(japanese_text_to_speech) -#find_package(catkin REQUIRED -# actionlib_msgs -# genmsg -#) -# -#add_action_files(DIRECTORY action FILES Speak.action) -#generate_messages(DEPENDENCIES actionlib_msgs) -# -#catkin_package( -# CATKIN_DEPENDS actionlib actionlib_msgs -#) -#catkin_install_python( -# PROGRAMS -# nodes/japanese_text_to_speech -# nodes/test_client -# DESTINATION -# ${CATKIN_PACKAGE_BIN_DESTINATION} -#) - # Default to C99 if(NOT CMAKE_C_STANDARD) set(CMAKE_C_STANDARD 99) @@ -40,15 +21,11 @@ find_package(ament_cmake_python REQUIRED) find_package(builtin_interfaces REQUIRED) find_package(rosidl_default_generators REQUIRED) -#ament_python_install_package(nodes) - -### ここにメッセージやサービスのファイルを記述 ### rosidl_generate_interfaces(${PROJECT_NAME} "action/Speak.action" DEPENDENCIES builtin_interfaces ) -# Install Python executables install(PROGRAMS nodes/japanese_text_to_speech nodes/test_client diff --git a/package.xml b/package.xml index 20a7c58..988a5db 100644 --- a/package.xml +++ b/package.xml @@ -8,7 +8,6 @@ TODO https://github.com/ActiveIntelligentSystemsLab/japanese_tts_ros - ament_cmake_python ament_cmake