Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Rust]C++ Windows向けのexampleを修正します #208

Merged
merged 3 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions example/cpp/windows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,23 @@ Visual Studio Installerを使用しインストールしてください。

### 環境構築・ビルド方法

ビルドして実行するには、「core.dll」「core.lib」「Open JTalk辞書フォルダ」が必要です。
以下はDebug x64でビルドする場合です。他の構成・プラットフォーム向けにビルドする場合は、適宜読み替えてください。

Releasesから「voicevox_core-windows-x64-cpu-{バージョン名}.zip」をダウンロードします。
zipファイルを展開し、展開されたフォルダに含まれているdllファイルを「core.dll」にリネームします。
出力フォルダを作成するために、一度ビルドします。「windows_example.sln」をVisual Studioで開き、メニューの「ビルド」→「ソリューションのビルド」を押します。
この段階では、ビルドは失敗します。「bin」フォルダと「lib」フォルダが生成されていればOKです。
Releasesから「voicevox_core-windows-x64-cpu-{バージョン名}.zip」をダウンロードし、展開します。
展開してできたファイルをそれぞれ下記のフォルダへ配置します。

「core.lib」を「simple_tts\lib\x64」に配置します。
「core.dll」を「simple_tts\bin\x64\Debug」に配置します。
- simple_tts に配置
- core.h

- simple_tts\bin\x64\Debug に配置
- core.dll
- onnxruntime.dll
- onnxruntime_providers_shared.dll

- simple_tts\lib\x64 に配置
- core.lib

もう一度ビルドします。今度は成功するはずです。失敗した場合は、「core.lib」の場所を確認してください。

Expand All @@ -31,7 +38,7 @@ http://open-jtalk.sourceforge.net/ を開き、Dictionary for Open JTalk 欄の
最終的には以下のようなフォルダ構成になっているはずです。
```
simple_tts
packages.config
core.h
│ simple_tts.cpp
│ simple_tts.h
│ simple_tts.vcxproj
Expand Down
4 changes: 0 additions & 4 deletions example/cpp/windows/simple_tts/packages.config

This file was deleted.

48 changes: 27 additions & 21 deletions example/cpp/windows/simple_tts/simple_tts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#include <vector>
#include <fstream>

#include "..\..\..\core\src\core.h"
#include "core.h"

#define OPENJTALK_DICT_NAME L"open_jtalk_dic_utf_8-1.11"

Expand All @@ -28,14 +28,17 @@ int main() {
std::wcin >> speak_words;

std::wcout << L"coreの初期化中" << std::endl;
initialize(false);
if (!initialize(false, 0, true)) {
std::wcout << L"coreの初期化に失敗しました" << std::endl;
return 0;
}

VoicevoxResultCode result = VoicevoxResultCode::VOICEVOX_RESULT_SUCCEED;

std::wcout << L"openjtalk辞書の読み込み" << std::endl;
result = voicevox_load_openjtalk_dict(GetOpenJTalkDict().c_str());
Copy link
Member

@PickledChair PickledChair Jul 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

別でissueを出すべきだと思いますが、日本語を含むパスで動作しませんでした。

とのことでしたが、ここの GetOpenJTalkDict の戻り値の文字コードで問題が起こっていそうですね……。GetOpenJTalkDict の実装を見ると文字コードを Shift-JIS としているみたいですが、以前の C++ 版コアの voicevox_load_openjtalk_dict 関数は引数のパス文字列の文字コードが Shift-JIS だったのでしょうか?

また、現在の Rust 版の voicevox_load_openjtalk_dict の実装は以下なのですが、UTF-8 の文字列としてデコードできない入力ははじくようになっています(Rust の文字列の内部エンコーディングが環境によらず UTF-8 なので):

#[no_mangle]
pub extern "C" fn voicevox_load_openjtalk_dict(dict_path: *const c_char) -> VoicevoxResultCode {
let (_, result_code) = {
if let Ok(dict_path) = unsafe { CStr::from_ptr(dict_path) }.to_str() {
convert_result(lock_internal().voicevox_load_openjtalk_dict(dict_path))
} else {
(None, VoicevoxResultCode::VOICEVOX_RESULT_INVALID_UTF8_INPUT)
}
};
result_code
}

もし UTF-8 の文字列を渡した場合も試していた場合、それでも日本語を含むパスをうまく扱えなかった感じでしょうか?(もしそうであれば、引数の文字コードは UTF-8 のままとしたいですが、内部でOpenJTalk の API に渡す際は Windows 版だけ扱う文字コードを変更するように実装する必要がありそうですね……!)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTF-8でも試しましたが、文字化けします。
きれいに、UTF-8をShift-JISで見たときの化け方です。

C++版コアのときも、パスはShift-JISで渡していました。

rust側で文字コードの変換を行うということですね。
GetOpenJTalkDict の戻り値をUTF-8に変更します。

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

文字化けしてるのRustで実装されてるcore側の実装バグっぽそうなのでなおしたらutf-8で行けそうなきがします

Copy link
Member

@PickledChair PickledChair Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

文字化けしてるのRustで実装されてるcore側の実装バグっぽそうなのでなおしたらutf-8で行けそうなきがします

そうですね! 個人的にはおそらく以下の open_jtalk-rs 内の open_jtalk_sys::Mecab_load を呼ぶ部分で Windows 版だけパス文字列を Shift-JIS に変換するのが良い気がしています(Rust コード内では一貫して UTF-8 で扱った方が都合が良いので)。

https://github.com/VOICEVOX/open_jtalk-rs/blob/4cd7ff11943f2c0b976f9c8e9593f47339a60444/crates/open_jtalk/src/mecab/mod.rs#L40

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OS別に分けるのではなくto_strをas_os_strにするだけでいいはずです

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメント化されていますが、WPATH_WIN32が定義されていたらMeCab::Utf8ToWideに渡して文字コードを変換するようです。なんでコメント化されているのでしょうか?

言われてみれば、確かに元々は文字コードを変換するようにしていたのをコメントアウトしたようですね……。

https://github.com/VOICEVOX/open_jtalk/blob/dc5c4fadcb0d281f5ca19a58c9df94292b1358fc/src/mecab/src/common.h#L147-L154

経緯は不明です……(コミットログを見ると、CreateFileA 関数を使うようにした変更は 2012 年、WPATH マクロをコメントアウトしたのは 2016 年のようでした。コミットは大雑把にまとめられており、有用な情報は得られませんでした。おそらく、元の MeCab のソースを OpenJTalk 開発時の都合で変更したものと思われます。ユーザーが Shift-JIS の文字列を入力した時に変換なしでそれを扱いたかった?)。

確かにそっちのほうが正攻法そうですね

そうですね……! OpenJTalk に渡す文字列を全て UTF-8 に統一できそうなのでスッキリしそうです。WPATH はファイルパスだけに関わるマクロだと思うので、これを変更したところで不具合は出ない、と思いたいですね……。

Copy link
Member

@PickledChair PickledChair Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あ、ただ、open_jtalk のコードをいじると、それに依存している pyopenjtalk にも影響が出ますね!

https://github.com/VOICEVOX/pyopenjtalk/blob/master/.gitmodules

pyopenjtalk は、今回の Mecab_load 関数に関わるところで言えば、たとえば以下のような箇所でシステムロケールの文字コードを要求しているようです(他の箇所にもある)。open_jtalk を直すなら、pyopenjtalk もそれに合わせて修正しなければなりませんね……。

https://github.com/VOICEVOX/pyopenjtalk/blob/50b0296a9e1b666e5a09a41ec9e9284a2a9b608f/pyopenjtalk/__init__.py#L103

これを考えると、今はとりあえず open_jtalk-rs 内で対応するのが無難のような気がしてきました……

Copy link
Member

@Hiroshiba Hiroshiba Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

記憶を呼び覚ましていました。
結論から言うと、ボイボのpyopenjtalkのことは無視してopenjtalk/mecabの中のコードを変えちゃうのが良いのかなと思いました!

理由は単純に、本家のpyopenjtalk(とopenjtalk)は全部UTF8で扱う実装になっているのに対して、VOICEVOXはpyopenjtalkだけがsystem localの文字コードを使うようになっているためです。
本家に合わせるのがいろいろ楽そうなので、pyopenjtalk側を合わせちゃうのが良いのかな~と。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2転しちゃってすみません 🙇‍♂️
が、たぶんですが、system localの文字コードを扱い始めるのはなかなかにしんどさを連れてくる可能性があるので、ゴリッと全部UTF8にしちゃいたいな~という気持ちです・・・!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PRつくりました
VOICEVOX/open_jtalk#7

if (result != VoicevoxResultCode::VOICEVOX_RESULT_SUCCEED) {
std::cout << voicevox_error_result_to_message(result) << std::endl;
OutErrorMessage(result);
return 0;
}

Expand All @@ -45,7 +48,7 @@ int main() {
uint8_t* output_wav = nullptr;
result = voicevox_tts(wide_to_utf8_cppapi(speak_words).c_str(), speaker_id, &output_binary_size, &output_wav);
if (result != VoicevoxResultCode::VOICEVOX_RESULT_SUCCEED) {
std::cout << voicevox_error_result_to_message(result) << std::endl;
OutErrorMessage(result);
return 0;
}

Expand All @@ -71,7 +74,7 @@ int main() {
std::string GetOpenJTalkDict() {
wchar_t buff[MAX_PATH] = {0};
PathCchCombine(buff, MAX_PATH, GetExeDirectory().c_str(), OPENJTALK_DICT_NAME);
std::string retVal = wide_to_multi_capi(buff);
std::string retVal = wide_to_utf8_cppapi(buff);
return retVal;
}

Expand Down Expand Up @@ -108,23 +111,13 @@ std::wstring GetExeDirectory() {
}

/// <summary>
/// ワイド文字列をShift_JISに変換します
/// コンソール画面にエラーメッセージを出力します
/// </summary>
/// <param name="src">ワイド文字列</param>
/// <returns>Shift_JIS文字列</returns>
/// <remarks>
/// https://nekko1119.hatenablog.com/entry/2017/01/02/054629 から引用
/// </remarks>
std::string wide_to_multi_capi(std::wstring const& src) {
std::size_t converted{};
std::vector<char> dest(src.size() * sizeof(wchar_t) + 1, '\0');
if (::_wcstombs_s_l(&converted, dest.data(), dest.size(), src.data(), _TRUNCATE, ::_create_locale(LC_ALL, "jpn")) !=
0) {
throw std::system_error{errno, std::system_category()};
}
dest.resize(std::char_traits<char>::length(dest.data()));
dest.shrink_to_fit();
return std::string(dest.begin(), dest.end());
/// <param name="messageCode">メッセージコード</param>
void OutErrorMessage(VoicevoxResultCode messageCode) {
const char* utf8Str = voicevox_error_result_to_message(messageCode);
std::wstring wideStr = utf8_to_wide_cppapi(utf8Str);
std::wcout << wideStr << std::endl;
}

/// <summary>
Expand All @@ -139,3 +132,16 @@ std::string wide_to_utf8_cppapi(std::wstring const& src) {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.to_bytes(src);
}

/// <summary>
/// UTF8をワイド文字に変換します。
/// </summary>
/// <param name="src">UTF8文字列</param>
/// <returns>ワイド文字列</returns>
/// <remarks>
/// https://nekko1119.hatenablog.com/entry/2017/01/02/054629 から引用
/// </remarks>
std::wstring utf8_to_wide_cppapi(std::string const& src) {
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.from_bytes(src);
}
6 changes: 4 additions & 2 deletions example/cpp/windows/simple_tts/simple_tts.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#pragma once
#include <iostream>
#include "core.h"

std::string GetOpenJTalkDict();
std::wstring GetWaveFileName();
std::wstring GetExePath();
std::wstring GetExeDirectory();
std::string wide_to_multi_capi(std::wstring const& src);
std::string wide_to_utf8_cppapi(std::wstring const& src);
void OutErrorMessage(VoicevoxResultCode messageCode);
std::string wide_to_utf8_cppapi(std::wstring const& src);
std::wstring utf8_to_wide_cppapi(std::string const& src);
15 changes: 1 addition & 14 deletions example/cpp/windows/simple_tts/simple_tts.vcxproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.ML.OnnxRuntime.1.10.0\build\native\Microsoft.ML.OnnxRuntime.props" Condition="Exists('..\packages\Microsoft.ML.OnnxRuntime.1.10.0\build\native\Microsoft.ML.OnnxRuntime.props')" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
Expand Down Expand Up @@ -165,18 +164,6 @@
<ItemGroup>
<ClInclude Include="simple_tts.h" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\packages\Microsoft.ML.OnnxRuntime.1.10.0\build\native\Microsoft.ML.OnnxRuntime.targets" Condition="Exists('..\packages\Microsoft.ML.OnnxRuntime.1.10.0\build\native\Microsoft.ML.OnnxRuntime.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>このプロジェクトは、このコンピューター上にない NuGet パッケージを参照しています。それらのパッケージをダウンロードするには、[NuGet パッケージの復元] を使用します。詳細については、http://go.microsoft.com/fwlink/?LinkID=322105 を参照してください。見つからないファイルは {0} です。</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.ML.OnnxRuntime.1.10.0\build\native\Microsoft.ML.OnnxRuntime.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.OnnxRuntime.1.10.0\build\native\Microsoft.ML.OnnxRuntime.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.OnnxRuntime.1.10.0\build\native\Microsoft.ML.OnnxRuntime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.OnnxRuntime.1.10.0\build\native\Microsoft.ML.OnnxRuntime.targets'))" />
</Target>
<ImportGroup Label="ExtensionTargets" />
</Project>
5 changes: 0 additions & 5 deletions example/cpp/windows/simple_tts/simple_tts.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,4 @@
<Filter>ヘッダー ファイル</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\..\runtimes\win-x64\native\onnxruntime_providers_shared.dll" />
<None Include="$(MSBuildThisFileDirectory)..\..\runtimes\win-x64\native\onnxruntime.dll" />
<None Include="packages.config" />
</ItemGroup>
</Project>