├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bugreport.md │ ├── featurerequest.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md ├── actions │ └── prepare_python │ │ └── action.yml └── workflows │ ├── build-docs.yml │ ├── build-engine-container.yml │ ├── build-engine.yml │ ├── build-latest-dev.yml │ ├── test-engine-container.yml │ ├── test-engine-package.yml │ ├── test-issue-freshness.yml │ ├── test-merge-gatekeeper.yml │ ├── test-security.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── Dockerfile ├── LGPL_LICENSE ├── LICENSE ├── README.md ├── docs ├── VOICEVOX音声合成エンジンとの連携.md ├── api │ └── .gitkeep ├── res │ └── マルチエンジン概念図.svg ├── リソースファイルのURLとfilemap.md └── 用語集.md ├── engine_manifest.json ├── pyproject.toml ├── requirements-build.txt ├── requirements-dev.txt ├── requirements.txt ├── resources ├── character_info │ ├── 35b2c544-660e-401e-b503-0e14c635303a │ │ ├── icons │ │ │ └── 8.png │ │ ├── metas.json │ │ ├── policy.md │ │ ├── portrait.png │ │ ├── portraits │ │ │ └── 8.png │ │ └── voice_samples │ │ │ ├── 8_001.wav │ │ │ ├── 8_002.wav │ │ │ └── 8_003.wav │ ├── 388f246b-8c41-4ac1-8e2d-5d79f3ff56d9 │ │ ├── icons │ │ │ ├── 1.png │ │ │ ├── 3.png │ │ │ ├── 5.png │ │ │ └── 7.png │ │ ├── metas.json │ │ ├── policy.md │ │ ├── portrait.png │ │ ├── portraits │ │ │ └── 3.png │ │ └── voice_samples │ │ │ ├── 1_001.wav │ │ │ ├── 1_002.wav │ │ │ ├── 1_003.wav │ │ │ ├── 3_001.wav │ │ │ ├── 3_002.wav │ │ │ ├── 3_003.wav │ │ │ ├── 5_001.wav │ │ │ ├── 5_002.wav │ │ │ ├── 5_003.wav │ │ │ ├── 7_001.wav │ │ │ ├── 7_002.wav │ │ │ └── 7_003.wav │ ├── 7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff │ │ ├── icons │ │ │ ├── 0.png │ │ │ ├── 2.png │ │ │ ├── 4.png │ │ │ └── 6.png │ │ ├── metas.json │ │ ├── policy.md │ │ ├── portrait.png │ │ ├── portraits │ │ │ ├── 0.png │ │ │ ├── 2.png │ │ │ ├── 4.png │ │ │ └── 6.png │ │ └── voice_samples │ │ │ ├── 0_001.wav │ │ │ ├── 0_002.wav │ │ │ ├── 0_003.wav │ │ │ ├── 2_001.wav │ │ │ ├── 2_002.wav │ │ │ ├── 2_003.wav │ │ │ ├── 4_001.wav │ │ │ ├── 4_002.wav │ │ │ ├── 4_003.wav │ │ │ ├── 6_001.wav │ │ │ ├── 6_002.wav │ │ │ └── 6_003.wav │ └── b1a81618-b27b-40d2-b0ea-27a9ad408c4b │ │ ├── icons │ │ └── 9.png │ │ ├── metas.json │ │ ├── policy.md │ │ ├── portrait.png │ │ └── voice_samples │ │ ├── 9_001.wav │ │ ├── 9_002.wav │ │ └── 9_003.wav ├── default.csv ├── engine_manifest_assets │ ├── dependency_licenses.json │ ├── downloadable_libraries.json │ ├── icon.png │ ├── terms_of_service.md │ └── update_infos.json └── setting_ui_template.html ├── run.py ├── run.spec ├── test ├── __init__.py ├── benchmark │ ├── engine_preparation.py │ └── speed │ │ ├── character.py │ │ ├── request.py │ │ └── utility.py ├── conftest.py ├── e2e │ ├── __snapshots__ │ │ ├── test_characters.ambr │ │ ├── test_characters │ │ │ ├── test_喋れるキャラクターの情報をURLで取得できる[35b2c544-660e-401e-b503-0e14c635303a].json │ │ │ ├── test_喋れるキャラクターの情報をURLで取得できる[388f246b-8c41-4ac1-8e2d-5d79f3ff56d9].json │ │ │ ├── test_喋れるキャラクターの情報をURLで取得できる[7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff].json │ │ │ ├── test_喋れるキャラクターの情報を取得できる[35b2c544-660e-401e-b503-0e14c635303a].json │ │ │ ├── test_喋れるキャラクターの情報を取得できる[388f246b-8c41-4ac1-8e2d-5d79f3ff56d9].json │ │ │ ├── test_喋れるキャラクターの情報を取得できる[7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff].json │ │ │ ├── test_喋れるキャラクター一覧が取得できる.json │ │ │ ├── test_歌えるキャラクターの情報をURLで取得できる[388f246b-8c41-4ac1-8e2d-5d79f3ff56d9].json │ │ │ ├── test_歌えるキャラクターの情報をURLで取得できる[7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff].json │ │ │ ├── test_歌えるキャラクターの情報をURLで取得できる[b1a81618-b27b-40d2-b0ea-27a9ad408c4b].json │ │ │ ├── test_歌えるキャラクターの情報を取得できる[388f246b-8c41-4ac1-8e2d-5d79f3ff56d9].json │ │ │ ├── test_歌えるキャラクターの情報を取得できる[7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff].json │ │ │ ├── test_歌えるキャラクターの情報を取得できる[b1a81618-b27b-40d2-b0ea-27a9ad408c4b].json │ │ │ └── test_歌えるキャラクター一覧が取得できる.json │ │ ├── test_missing_core │ │ │ └── test_missing_core_422.json │ │ ├── test_missing_engine │ │ │ └── test_missing_tts_engine_422.json │ │ ├── test_openapi │ │ │ └── test_OpenAPIの形が変わっていないことを確認.json │ │ ├── test_sing.ambr │ │ └── test_tts.ambr │ ├── conftest.py │ ├── single_api │ │ ├── engine_info │ │ │ ├── __snapshots__ │ │ │ │ ├── test_core_versions │ │ │ │ │ └── test_get_core_versions_200.json │ │ │ │ ├── test_engine_manifest │ │ │ │ │ └── test_get_engine_manifest_200.json │ │ │ │ ├── test_supported_devices │ │ │ │ │ └── test_get_supported_devices_200.json │ │ │ │ └── test_version │ │ │ │ │ └── test_get_version_200.json │ │ │ ├── test_core_versions.py │ │ │ ├── test_engine_manifest.py │ │ │ ├── test_supported_devices.py │ │ │ └── test_version.py │ │ ├── morphing │ │ │ ├── __snapshots__ │ │ │ │ ├── test_morphable_targets │ │ │ │ │ └── test_post_morphable_targets_200.json │ │ │ │ └── test_synthesis_morphing │ │ │ │ │ └── test_post_synthesis_morphing_422.json │ │ │ ├── test_morphable_targets.py │ │ │ └── test_synthesis_morphing.py │ │ ├── portal_page │ │ │ ├── __snapshots__ │ │ │ │ └── test_get_root.ambr │ │ │ └── test_get_root.py │ │ ├── preset │ │ │ ├── __snapshots__ │ │ │ │ ├── test_add_preset │ │ │ │ │ ├── test_post_add_preset_200.json │ │ │ │ │ └── test_post_add_preset_422.json │ │ │ │ ├── test_delete_preset.ambr │ │ │ │ ├── test_delete_preset │ │ │ │ │ └── test_post_delete_preset_422.json │ │ │ │ ├── test_presets │ │ │ │ │ └── test_get_presets_200.json │ │ │ │ └── test_update_preset │ │ │ │ │ ├── test_post_update_preset_200.json │ │ │ │ │ └── test_post_update_preset_422.json │ │ │ ├── test_add_preset.py │ │ │ ├── test_delete_preset.py │ │ │ ├── test_presets.py │ │ │ └── test_update_preset.py │ │ ├── setting │ │ │ ├── __snapshots__ │ │ │ │ ├── test_setting_api.ambr │ │ │ │ └── test_setting_api │ │ │ │ │ └── test_post_setting_422.json │ │ │ └── test_setting_api.py │ │ ├── speaker │ │ │ ├── __snapshots__ │ │ │ │ ├── test_singer_info │ │ │ │ │ ├── test_get_singer_info_200.json │ │ │ │ │ ├── test_get_singer_info_404.json │ │ │ │ │ └── test_get_singer_info_with_url_200.json │ │ │ │ ├── test_singers │ │ │ │ │ └── test_get_singers_200.json │ │ │ │ ├── test_speaker_info │ │ │ │ │ ├── test_get_speaker_info_200.json │ │ │ │ │ ├── test_get_speaker_info_404.json │ │ │ │ │ └── test_get_speaker_info_with_url_200.json │ │ │ │ └── test_speakers │ │ │ │ │ └── test_get_speakers_200.json │ │ │ ├── test_singer_info.py │ │ │ ├── test_singers.py │ │ │ ├── test_speaker_info.py │ │ │ └── test_speakers.py │ │ ├── tts_pipeline │ │ │ ├── __snapshots__ │ │ │ │ ├── test_accent_phrases │ │ │ │ │ ├── test_post_accent_phrases_200.json │ │ │ │ │ └── test_post_accent_phrases_enable_e2k_200.json │ │ │ │ ├── test_audio_query │ │ │ │ │ ├── test_post_audio_query_200.json │ │ │ │ │ └── test_post_audio_query_enable_e2k_200.json │ │ │ │ ├── test_audio_query_from_preset │ │ │ │ │ ├── test_post_audio_query_from_preset_200.json │ │ │ │ │ └── test_post_audio_query_from_preset_422.json │ │ │ │ ├── test_cancellable_synthesis.ambr │ │ │ │ ├── test_connect_waves.ambr │ │ │ │ ├── test_connect_waves │ │ │ │ │ └── test_post_connect_waves_422.json │ │ │ │ ├── test_frame_synthesis.ambr │ │ │ │ ├── test_initialize_speaker.ambr │ │ │ │ ├── test_is_initialized_speaker │ │ │ │ │ └── test_get_is_initialized_speaker_200.json │ │ │ │ ├── test_mora_data │ │ │ │ │ └── test_post_mora_data_200.json │ │ │ │ ├── test_mora_length │ │ │ │ │ └── test_post_mora_length_200.json │ │ │ │ ├── test_mora_pitch │ │ │ │ │ └── test_post_mora_pitch_200.json │ │ │ │ ├── test_multi_synthesis.ambr │ │ │ │ ├── test_sing_frame_audio_query │ │ │ │ │ ├── test_post_sing_frame_audio_query_200.json │ │ │ │ │ └── test_post_sing_old_frame_audio_query_200.json │ │ │ │ ├── test_sing_frame_f0 │ │ │ │ │ └── test_post_sing_frame_f0_200.json │ │ │ │ ├── test_sing_frame_volume │ │ │ │ │ └── test_post_sing_frame_volume_200.json │ │ │ │ ├── test_synthesis.ambr │ │ │ │ └── test_validate_kana │ │ │ │ │ ├── test_post_validate_kana_200.json │ │ │ │ │ ├── test_post_validate_kana_400.json │ │ │ │ │ └── test_post_validate_kana_422.json │ │ │ ├── test_accent_phrases.py │ │ │ ├── test_audio │ │ │ │ ├── sample1.wav │ │ │ │ └── sample2.wav │ │ │ ├── test_audio_query.py │ │ │ ├── test_audio_query_from_preset.py │ │ │ ├── test_cancellable_synthesis.py │ │ │ ├── test_connect_waves.py │ │ │ ├── test_frame_synthesis.py │ │ │ ├── test_initialize_speaker.py │ │ │ ├── test_is_initialized_speaker.py │ │ │ ├── test_mora_data.py │ │ │ ├── test_mora_length.py │ │ │ ├── test_mora_pitch.py │ │ │ ├── test_multi_synthesis.py │ │ │ ├── test_sing_frame_audio_query.py │ │ │ ├── test_sing_frame_f0.py │ │ │ ├── test_sing_frame_volume.py │ │ │ ├── test_synthesis.py │ │ │ └── test_validate_kana.py │ │ ├── user_dict │ │ │ ├── __snapshots__ │ │ │ │ ├── test_import_user_dict.ambr │ │ │ │ ├── test_import_user_dict │ │ │ │ │ ├── test_post_import_user_dict_422.json │ │ │ │ │ └── test_post_import_user_dict_contents.json │ │ │ │ ├── test_user_dict_api │ │ │ │ │ └── test_get_user_dict_200.json │ │ │ │ ├── test_user_dict_word.ambr │ │ │ │ └── test_user_dict_word │ │ │ │ │ ├── test_delete_user_dict_word_422.json │ │ │ │ │ ├── test_delete_user_dict_word_contents.json │ │ │ │ │ ├── test_post_user_dict_word_200.json │ │ │ │ │ ├── test_post_user_dict_word_422.json │ │ │ │ │ ├── test_put_user_dict_word_422.json │ │ │ │ │ └── test_put_user_dict_word_contents.json │ │ │ ├── test_import_user_dict.py │ │ │ ├── test_user_dict_api.py │ │ │ └── test_user_dict_word.py │ │ └── utils.py │ ├── test_characters.py │ ├── test_disable_api.py │ ├── test_missing_core.py │ ├── test_missing_engine.py │ ├── test_openapi.py │ ├── test_sing.py │ └── test_tts.py ├── unit │ ├── library │ │ ├── test_library_manager.py │ │ └── vvlib_manifest.json │ ├── preset │ │ ├── presets-test-1.yaml │ │ ├── presets-test-2.yaml │ │ ├── presets-test-3.yaml │ │ ├── presets-test-4.yaml │ │ └── test_preset.py │ ├── resource_manager │ │ ├── test_resource_manager.py │ │ ├── with_filemap │ │ │ ├── dummy.png │ │ │ ├── dummy.txt │ │ │ ├── dummy.wav │ │ │ ├── dummy_same_binary.wav │ │ │ └── filemap.json │ │ └── without_filemap │ │ │ ├── dummy.png │ │ │ ├── dummy.txt │ │ │ ├── dummy.wav │ │ │ └── dummy_same_binary.wav │ ├── setting │ │ ├── setting-test-load-1.yaml │ │ ├── setting-test-load-2.yaml │ │ ├── setting-test-load-3.yaml │ │ └── test_setting.py │ ├── test_core_initializer.py │ ├── test_core_version_utility.py │ ├── test_metas_store.py │ ├── test_mock_tts_engine.py │ ├── test_utility │ │ ├── test_utility_hash_big_ndarray.py │ │ └── test_utility_round_floats.py │ ├── tts_pipeline │ │ ├── __snapshots__ │ │ │ └── test_tts_engine │ │ │ │ ├── test_mocked_create_accent_phrases_from_kana_output.json │ │ │ │ ├── test_mocked_create_accent_phrases_output.json │ │ │ │ ├── test_mocked_create_phoneme_and_f0_and_volume_output[query].json │ │ │ │ ├── test_mocked_create_volume_from_phoneme_and_f0_output.json │ │ │ │ ├── test_mocked_frame_synthesize_wave_output[wave].json │ │ │ │ ├── test_mocked_synthesize_wave_output.json │ │ │ │ ├── test_mocked_update_length_and_pitch_output.json │ │ │ │ ├── test_mocked_update_length_output.json │ │ │ │ └── test_mocked_update_pitch_output.json │ │ ├── test_connect_base64_waves.py │ │ ├── test_kana_converter.py │ │ ├── test_katakana_english.py │ │ ├── test_mora_mapping.py │ │ ├── test_njd_feature_processor.py │ │ ├── test_phoneme.py │ │ ├── test_text_analyzer.py │ │ ├── test_tts_engine.py │ │ ├── test_tts_engines.py │ │ ├── test_wave_synthesizer.py │ │ └── tts_utils.py │ ├── user_dict │ │ ├── test_user_dict.py │ │ ├── test_user_dict_model.py │ │ └── test_word_types.py │ └── utility │ │ └── test_text_utility.py └── utility.py ├── tools ├── build_and_push_multi_platform_docker_images.bash ├── check_release_build.py ├── codesign.bash ├── create_venv_and_generate_licenses.bash ├── generate_docker_image_names.py ├── generate_filemap.py ├── generate_licenses.py ├── get_cost_candidates.py ├── licenses │ ├── cuda │ │ └── EULA.txt │ ├── cudnn │ │ └── LICENSE │ ├── mpg123 │ │ └── COPYING │ ├── open_jtalk │ │ ├── COPYING │ │ ├── mecab-naist-jdic │ │ │ └── COPYING │ │ └── mecab │ │ │ └── COPYING │ └── world │ │ └── LICENSE.txt ├── make_docs.py ├── merge_engine_manifest.py ├── merge_update_infos.py ├── modify_pyinstaller.bash └── process_voicevox_resource.bash ├── uv.lock └── voicevox_engine ├── __init__.py ├── app ├── application.py ├── dependencies.py ├── global_exceptions.py ├── middlewares.py ├── openapi_schema.py └── routers │ ├── character.py │ ├── engine_info.py │ ├── library.py │ ├── morphing.py │ ├── portal_page.py │ ├── preset.py │ ├── setting.py │ ├── tts_pipeline.py │ └── user_dict.py ├── cancellable_engine.py ├── core ├── core_adapter.py ├── core_initializer.py └── core_wrapper.py ├── dev ├── core │ └── mock.py ├── song_engine │ └── mock.py └── tts_engine │ └── mock.py ├── engine_manifest.py ├── library ├── library_manager.py └── model.py ├── metas ├── Metas.py └── MetasStore.py ├── model.py ├── morphing ├── model.py └── morphing.py ├── preset ├── model.py └── preset_manager.py ├── resource_manager.py ├── setting ├── model.py └── setting_manager.py ├── tts_pipeline ├── audio_postprocessing.py ├── connect_base64_waves.py ├── kana_converter.py ├── katakana_english.py ├── model.py ├── mora_mapping.py ├── njd_feature_processor.py ├── phoneme.py ├── song_engine.py ├── text_analyzer.py └── tts_engine.py ├── user_dict ├── model.py ├── user_dict_manager.py └── user_dict_word.py └── utility ├── core_version_utility.py ├── file_utility.py ├── path_utility.py ├── runtime_utility.py └── text_utility.py /.gitattributes: -------------------------------------------------------------------------------- 1 | test/**/__snapshots__/**/*.json linguist-generated=true 2 | 3 | * text=auto eol=lf 4 | *.png -text 5 | *.wav -text 6 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @VOICEVOX/main-reviewer 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bugreport.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: 不具合の報告 4 | labels: バグ 5 | --- 6 | 7 | ## 不具合の内容 8 | 9 | 10 | 11 | ### 現象・ログ 12 | 13 | 14 | 15 | ### 再現手順 16 | 17 | 18 | 19 | ### 期待動作 20 | 21 | 22 | 23 | ## VOICEVOXのバージョン 24 | 25 | 0.?.0 26 | 27 | 28 | 29 | ## OSの種類/ディストリ/バージョン 30 | 31 | 32 | 33 | - [ ] Windows 34 | - [ ] macOS 35 | - [ ] Linux 36 | 37 | 44 | 45 | ## その他 46 | 47 | 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/featurerequest.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: 機能要望・改善提案 4 | labels: 機能向上 5 | --- 6 | 7 | ## 内容 8 | 9 | 10 | 11 | 12 | ### Pros 良くなる点 13 | 14 | 15 | 16 | ### Cons 悪くなる点 17 | 18 | 19 | 20 | ### 実現方法 21 | 22 | 23 | 24 | ## VOICEVOXのバージョン 25 | 26 | 0.?.0 27 | 28 | 29 | 30 | ## OSの種類/ディストリ/バージョン 31 | 32 | 33 | 34 | - [ ] Windows 35 | - [ ] macOS 36 | - [ ] Linux 37 | 38 | 45 | 46 | ## その他 47 | 48 | 49 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: 質問 (既存のIssueや一般事例を良く調べてからしてください) 4 | labels: 要議論 5 | --- 6 | 7 | ## 質問の内容 8 | 9 | 10 | 11 | ## VOICEVOXのバージョン 12 | 13 | 0.?.0 14 | 15 | 16 | 17 | ## OSの種類/ディストリ/バージョン 18 | 19 | 20 | 21 | - [ ] Windows 22 | - [ ] macOS 23 | - [ ] Linux 24 | 25 | 32 | 33 | ## その他 34 | 35 | 36 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 内容 2 | 3 | 6 | 7 | ## 関連 Issue 8 | 9 | 17 | 18 | ## スクリーンショット・動画など 19 | 20 | 23 | 24 | ## その他 25 | -------------------------------------------------------------------------------- /.github/actions/prepare_python/action.yml: -------------------------------------------------------------------------------- 1 | name: "Prepare Python" 2 | description: "Python ランタイムと依存パッケージをインストールし、Python バージョンを出力する" 3 | 4 | inputs: 5 | dependency-group: 6 | description: "uv の依存パッケージインストール時に指定するグループ" 7 | required: false 8 | 9 | runs: 10 | using: "composite" 11 | steps: 12 | - name: Install uv 13 | uses: astral-sh/setup-uv@v5 14 | with: 15 | enable-cache: false # TODO: uvの上手いキャッシュのやり方を考える 16 | 17 | - name: Install Python dependencies 18 | run: | 19 | if [[ -n "${{ inputs.dependency-group }}" ]]; then 20 | uv sync --group ${{ inputs.dependency-group }} 21 | else 22 | uv sync 23 | fi 24 | shell: bash 25 | -------------------------------------------------------------------------------- /.github/workflows/build-docs.yml: -------------------------------------------------------------------------------- 1 | # API docs HTML ファイルを生成し、`gh-pages` ブランチへの push によって GitHub Pages 上のドキュメントとして公開 2 | 3 | name: upload-docs 4 | 5 | on: 6 | push: 7 | branches: 8 | - "master" 9 | 10 | env: 11 | PUBLISH_DIR: "./docs/api" 12 | PUBLISH_BRANCH: "gh-pages" 13 | DESTINATION_DIR: "api" 14 | 15 | defaults: 16 | run: 17 | shell: bash 18 | 19 | jobs: 20 | upload-doc: 21 | runs-on: ubuntu-22.04 22 | steps: 23 | - name: Check out the repository 24 | uses: actions/checkout@v4 25 | 26 | - name: Prepare Python Runtime / Python Dependencies 27 | uses: ./.github/actions/prepare_python 28 | 29 | - name: Make documents 30 | run: PYTHONPATH=. uv run tools/make_docs.py 31 | 32 | - name: Deploy documents to GitHub Pages 33 | uses: peaceiris/actions-gh-pages@v4 34 | with: 35 | github_token: ${{ secrets.GITHUB_TOKEN }} 36 | publish_dir: ${{ env.PUBLISH_DIR }} 37 | publish_branch: ${{ env.PUBLISH_BRANCH }} 38 | destination_dir: ${{ env.DESTINATION_DIR }} 39 | -------------------------------------------------------------------------------- /.github/workflows/build-latest-dev.yml: -------------------------------------------------------------------------------- 1 | name: Release latest dev build 2 | 3 | # masterブランチが更新されるたびに開発版をビルドしてデプロイする。 4 | # バージョン(タグ)は最新リリースのバージョンを`X.Y.Z`としたときの`X.Y+1.0-dev`。 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | paths-ignore: 11 | - "docs/**" 12 | - "test/**" 13 | 14 | jobs: 15 | latest-dev-build: 16 | runs-on: ubuntu-latest 17 | if: github.repository_owner == 'VOICEVOX' 18 | steps: 19 | - name: Trigger workflow_dispatch 20 | uses: actions/github-script@v7 21 | with: 22 | github-token: ${{ secrets.GITHUB_TOKEN }} 23 | script: | 24 | const latest_release = await github.rest.repos.getLatestRelease({ 25 | owner: context.repo.owner, 26 | repo: context.repo.repo, 27 | }); 28 | const split_version = latest_release.data.tag_name.split('.'); 29 | const dev_version = `${split_version[0]}.${parseInt(split_version[1]) + 1}.0-dev`; 30 | github.rest.actions.createWorkflowDispatch({ 31 | owner: context.repo.owner, 32 | repo: context.repo.repo, 33 | workflow_id: 'build-engine.yml', 34 | ref: 'master', 35 | inputs: { 36 | version: dev_version, 37 | prerelease: true, 38 | push_dockerhub: true, 39 | }, 40 | }); 41 | console.log(`Triggered workflow_dispatch for ${dev_version}`); 42 | -------------------------------------------------------------------------------- /.github/workflows/test-issue-freshness.yml: -------------------------------------------------------------------------------- 1 | # Github Issue が停滞状態になっていないか確認する 2 | 3 | name: "Test issue freshness" 4 | on: 5 | schedule: 6 | - cron: "0 3 * * *" 7 | workflow_dispatch: 8 | inputs: 9 | dryrun: 10 | type: boolean 11 | required: true 12 | description: "ドライランする" 13 | jobs: 14 | stale: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Notify inactive 必要性議論 issues 18 | uses: actions/stale@v9 19 | with: 20 | # on.schedule で起動した場合は `inputs.dryrun == null` であるため `debug-only: false` となる 21 | debug-only: ${{ inputs.dryrun == 'true' }} 22 | any-of-labels: "状態:必要性議論" 23 | days-before-stale: 30 24 | days-before-close: -1 25 | stale-issue-message: "本 Issue は直近 30 日間で活動がありません。今後の方針について VOICEVOX チームによる再検討がおこなわれる予定です。" 26 | stale-issue-label: "非アクティブ" 27 | 28 | - name: Notify inactive 設計/実装者募集/実装 issues 29 | uses: actions/stale@v9 30 | with: 31 | # on.schedule で起動した場合は `inputs.dryrun == null` であるため `debug-only: false` となる 32 | debug-only: ${{ inputs.dryrun == 'true' }} 33 | any-of-labels: "状態:設計,状態:実装者募集,状態:実装" 34 | days-before-stale: 180 35 | days-before-close: -1 36 | stale-issue-message: "本 Issue は直近 180 日間で活動がありません。今後の方針について VOICEVOX チームによる再検討がおこなわれる予定です。" 37 | stale-issue-label: "非アクティブ" 38 | -------------------------------------------------------------------------------- /.github/workflows/test-merge-gatekeeper.yml: -------------------------------------------------------------------------------- 1 | name: "Merge Gatekeeper" 2 | 3 | # auto mergeとmerge queue用のチェッカー。 4 | # Approve数が足りているか、すべてのテストが通っているかを確認します。 5 | # 詳細: https://github.com/VOICEVOX/merge-gatekeeper 6 | 7 | on: 8 | pull_request_target: 9 | types: [auto_merge_enabled] 10 | merge_group: 11 | types: [checks_requested] 12 | 13 | jobs: 14 | merge-gatekeeper: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: voicevox/merge-gatekeeper@main 18 | with: 19 | token: ${{ secrets.GATEKEEPER_TOKEN }} 20 | required_score: 2 21 | score_rules: | 22 | #maintainer: 2 23 | #reviewer: 1 24 | - uses: upsidr/merge-gatekeeper@v1 25 | with: 26 | token: ${{ secrets.GITHUB_TOKEN }} 27 | self: merge-gatekeeper 28 | # https://github.com/upsidr/merge-gatekeeper/issues/71#issuecomment-1660607977 29 | ref: ${{ github.event.pull_request && github.event.pull_request.head.sha || github.ref }} 30 | timeout: 18000 # 5 hours 31 | -------------------------------------------------------------------------------- /.github/workflows/test-security.yml: -------------------------------------------------------------------------------- 1 | # Python 依存パッケージの脆弱性診断を定期的に行う 2 | 3 | name: test-security 4 | 5 | on: 6 | schedule: 7 | - cron: "00 04 15 * *" # 毎月15日 13:00 JST 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | jobs: 14 | test-security: 15 | runs-on: ubuntu-22.04 16 | steps: 17 | - name: Check out the repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Prepare Python Runtime / Python Dependencies 21 | uses: ./.github/actions/prepare_python 22 | with: 23 | dependency-group: dev 24 | 25 | - name: Check Python dependency security 26 | uses: pypa/gh-action-pip-audit@v1.1.0 27 | with: 28 | inputs: requirements.txt requirements-dev.txt requirements-build.txt 29 | 30 | - name: Notify Discord of security testing result 31 | uses: sarisia/actions-status-discord@v1 32 | if: always() 33 | with: 34 | webhook: ${{ secrets.DISCORD_WEBHOOK_URL }} 35 | username: GitHub Actions 36 | title: "依存パッケージ脆弱性診断の結果" 37 | status: ${{ job.status }} 38 | color: ${{ job.status == 'success' && '0x00FF00' || '0xFF0000' }} 39 | url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" 40 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: local 5 | hooks: 6 | - id: check-linting 7 | name: check-linting 8 | entry: uv run ruff check 9 | language: python 10 | types: [file, python] 11 | stages: [pre-push] 12 | pass_filenames: false 13 | - id: check-formatting 14 | name: check-formatting 15 | entry: uv run ruff format --check 16 | language: python 17 | types: [file, python] 18 | stages: [pre-push] 19 | pass_filenames: false 20 | - id: check-typing 21 | name: check-typing 22 | entry: uv run mypy . 23 | language: python 24 | types: [file, python] 25 | stages: [pre-push] 26 | pass_filenames: false 27 | - id: uv-check # `pyproject.toml` と `uv.lock` が整合する 28 | name: uv-check 29 | entry: uv lock --check 30 | language: python 31 | stages: [pre-push] 32 | pass_filenames: false 33 | - id: uv-export 34 | name: uv-export 35 | entry: uv export --no-annotate --no-hashes --no-header -o requirements.txt 36 | language: python 37 | stages: [pre-push] 38 | pass_filenames: false 39 | - id: uv-export-dev 40 | name: uv-export-dev 41 | entry: uv export --group dev --no-annotate --no-hashes --no-header -o requirements-dev.txt 42 | language: python 43 | stages: [pre-push] 44 | pass_filenames: false 45 | - id: uv-export-build 46 | name: uv-export-build 47 | entry: uv export --group build --no-annotate --no-hashes --no-header -o requirements-build.txt 48 | language: python 49 | stages: [pre-push] 50 | pass_filenames: false 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LGPL v3 と、ソースコードの公開が不要な別ライセンスのデュアルライセンスです。 2 | 3 | 1. LGPL v3 4 | 5 | LGPL_LICENSEを参照してください。 6 | 7 | 2. ソースコードの公開が不要な別ライセンス 8 | 9 | 別ライセンスを取得したい場合は、ヒホに求めてください。 10 | X アカウント: @hiho_karuta 11 | -------------------------------------------------------------------------------- /docs/VOICEVOX音声合成エンジンとの連携.md: -------------------------------------------------------------------------------- 1 | メモ書き程度ですが、どういう方針で開発を進めているかを紹介します。 2 | 3 | - バージョンが上がっても、`/audio_query`で返ってくる値をそのまま`/synthesis`に POST すれば音声合成できるようにする予定です 4 | - `AudioQuery`のパラメータは増えますが、なるべくデフォルト値で以前と変わらない音声が生成されるようにします 5 | - 以前のバージョンの`AudioQuery`を新しいバージョンの`/synthesis`にそのまま POST できるようにします(後方互換) 6 | - バージョン 0.7 から音声スタイルが実装されました。スタイルの情報は`/speakers`及び`/singers`から取得できます 7 | - スタイルの情報にある`style_id`を`speaker`に指定することで、今まで通り音声合成ができます 8 | - `style_id`の指定先が`speaker`なのは互換性のためです 9 | -------------------------------------------------------------------------------- /docs/api/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/docs/api/.gitkeep -------------------------------------------------------------------------------- /docs/リソースファイルのURLとfilemap.md: -------------------------------------------------------------------------------- 1 | # リソースファイルの URL の仕様について 2 | 3 | VOICEVOX ENGINE では一部のリソースファイルを URL として返します。 4 | リソースファイルを更新しても URL が同じ場合、キャッシュが働いて新しいリソースを取得できないことがあります。 5 | これを防ぐためにリソースファイルのハッシュ値を URL に含め、リソースの変更の度に URL が変わるようにしています。 6 | 7 | ResourceManager はファイルとハッシュの対応を管理します。 8 | filemap.json はファイルとハッシュを予め対応付けたファイルです。 9 | generate_filemap.py は filemap.json の作成を行います。 10 | 11 | ## ResourceManager 12 | 13 | `filemap.json`にあるリソースファイルを登録できます。 14 | 初期化時に`create_filemap_if_not_exist`を`True`にすると`filemap.json`がないディレクトリの登録ができます。 15 | 16 | 細かい仕様は ResourceManager のドキュメントと実装を確認してください。 17 | 18 | ## filemap.json 19 | 20 | `filemap.json`のキーは、登録するディレクトリからリソースファイルへの相対パスです。 21 | パス区切り文字は互換性のため`/`である必要があります。 22 | 23 | 値は登録するファイルを一意に識別できるハッシュ等の文字列です。 24 | `generate_filemap.py`は sha256 ハッシュを生成します。 25 | 26 | ### 例 27 | 28 | #### デイレクトリ構造 29 | 30 | ``` 31 | 登録ディレクトリ/ 32 | ├── filemap.json 33 | ├── dir_1/ 34 | │ ├── 登録ファイル.png 35 | │ ├── samples/ 36 | │ │ └── 登録ファイル.wav 37 | │ └── 非登録ファイル1.txt 38 | └── dir_2/ 39 | ├── 登録ファイル.png 40 | ├── samples/ 41 | │ └── 登録ファイル.wav 42 | └── 非登録ファイル1.txt 43 | ``` 44 | 45 | #### filemap.json 46 | 47 | ```json 48 | { 49 | "dir_1/登録ファイル.png": "HASH-1", 50 | "dir_1/samples/登録ファイル.wav": "HASH-2", 51 | "dir_2/登録ファイル.png": "HASH-3", 52 | "dir_2/samples/登録ファイル.wav": "HASH-4" 53 | } 54 | ``` 55 | 56 | ## generate_filemap.py 57 | 58 | `filemap.json`を生成するためのスクリプトです。 59 | デフォルトでは png ファイルと wav ファイルのみを登録します。 60 | 61 | ### 例 62 | 63 | ```bash 64 | python tools/generate_filemap.py --target_dir resources/character_info 65 | ``` 66 | 67 | png と wav に加えて jpg ファイルを登録する例 68 | 69 | ```bash 70 | python tools/generate_filemap.py --target_dir resources/character_info \ 71 | --target_suffix png --target_suffix wav --target_suffix jpg 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/用語集.md: -------------------------------------------------------------------------------- 1 | # 用語集 2 | 3 | エンジン内で用いられている用語をまとめています。 4 | 随時追加予定です。お気軽に追加プルリクエストを送ってください! 5 | 6 | 12 | 13 | ## ドメイン用語 14 | 15 | TODO: ユーザー向けに案内されている用語をまとめる 16 | 17 | ## エンジン周り 18 | 19 | TODO 20 | 21 | ## OpenJTalk 周り 22 | 23 | ### フルコンテキストラベル: full-context label 24 | 25 | 文章構造を解析して得たデータを音素ごとにまとめたもの。あるいはその集合。 26 | 何の音素か、何番目のモーラか、何番目のアクセント句なのかといった情報が含まれる。 27 | HTS の概念。 28 | 29 | ### ラベル: label 30 | 31 | 1つの音素のフルコンテキストラベルのこと。 32 | VOICEVOX 独自の定義(OpenJTalk 内ではフルコンテキストラベルを指してラベルと呼んでいる)。 33 | 34 | ### コンテキスト: context 35 | 36 | フルコンテキスト内の1つの要素のこと。 37 | VOICEVOX 独自の定義。 38 | 39 | ### feature 40 | 41 | ラベルを1行の文字列にしたもの。 42 | OpenJTalk の概念。 43 | -------------------------------------------------------------------------------- /requirements-build.txt: -------------------------------------------------------------------------------- 1 | altgraph==0.17.4 2 | annotated-types==0.7.0 3 | anyio==4.9.0 4 | cffi==1.17.1 5 | click==8.1.8 6 | colorama==0.4.6 ; sys_platform == 'win32' 7 | fastapi-slim==0.115.12 8 | h11==0.14.0 9 | idna==3.10 10 | jinja2==3.1.6 11 | kanalizer==0.1.1 12 | macholib==1.16.3 ; sys_platform == 'darwin' 13 | markupsafe==3.0.2 14 | numpy==2.2.4 15 | packaging==24.2 16 | pefile==2024.8.26 ; sys_platform == 'win32' 17 | platformdirs==4.3.7 18 | pycparser==2.22 19 | pydantic==2.11.3 20 | pydantic-core==2.33.1 21 | pyinstaller==5.13.2 22 | pyinstaller-hooks-contrib==2025.2 23 | pyopenjtalk @ git+https://github.com/VOICEVOX/pyopenjtalk@74703b034dd90a1f199f49bb70bf3b66b1728a86 24 | python-multipart==0.0.20 25 | pywin32-ctypes==0.2.3 ; sys_platform == 'win32' 26 | pyworld==0.3.5 27 | pyyaml==6.0.2 28 | semver==3.0.4 29 | setuptools==78.1.0 30 | sniffio==1.3.1 31 | soundfile==0.13.1 32 | soxr==0.5.0.post1 33 | starlette==0.45.3 34 | tqdm==4.67.1 35 | typing-extensions==4.13.2 36 | typing-inspection==0.4.0 37 | uvicorn==0.34.0 38 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.7.0 2 | anyio==4.9.0 3 | boolean-py==5.0 4 | cachecontrol==0.14.2 5 | certifi==2025.1.31 6 | cffi==1.17.1 7 | cfgv==3.4.0 8 | charset-normalizer==3.4.1 9 | click==8.1.8 10 | colorama==0.4.6 ; sys_platform == 'win32' 11 | coverage==7.8.0 12 | coveralls==4.0.1 13 | cyclonedx-python-lib==9.1.0 14 | defusedxml==0.7.1 15 | distlib==0.3.9 16 | docopt==0.6.2 17 | fastapi-slim==0.115.12 18 | filelock==3.18.0 19 | h11==0.14.0 20 | httpcore==1.0.8 21 | httpx==0.28.1 22 | identify==2.6.9 23 | idna==3.10 24 | iniconfig==2.1.0 25 | jinja2==3.1.6 26 | kanalizer==0.1.1 27 | license-expression==30.4.1 28 | markdown-it-py==3.0.0 29 | markupsafe==3.0.2 30 | mdurl==0.1.2 31 | msgpack==1.1.0 32 | mypy==1.15.0 33 | mypy-extensions==1.0.0 34 | nodeenv==1.9.1 35 | numpy==2.2.4 36 | packageurl-python==0.16.0 37 | packaging==24.2 38 | pip==25.0.1 39 | pip-api==0.0.34 40 | pip-audit==2.9.0 41 | pip-licenses==5.0.0 42 | pip-requirements-parser==32.0.1 43 | platformdirs==4.3.7 44 | pluggy==1.5.0 45 | pre-commit==4.2.0 46 | prettytable==3.16.0 47 | py-serializable==2.0.0 48 | pycparser==2.22 49 | pydantic==2.11.3 50 | pydantic-core==2.33.1 51 | pygments==2.19.1 52 | pyopenjtalk @ git+https://github.com/VOICEVOX/pyopenjtalk@74703b034dd90a1f199f49bb70bf3b66b1728a86 53 | pyparsing==3.2.3 54 | pytest==8.3.5 55 | python-multipart==0.0.20 56 | pyworld==0.3.5 57 | pyyaml==6.0.2 58 | requests==2.32.3 59 | rich==14.0.0 60 | ruff==0.11.5 61 | semver==3.0.4 62 | setuptools==78.1.0 63 | sniffio==1.3.1 64 | sortedcontainers==2.4.0 65 | soundfile==0.13.1 66 | soxr==0.5.0.post1 67 | starlette==0.45.3 68 | syrupy==4.9.1 69 | toml==0.10.2 70 | tomli==2.2.1 71 | tqdm==4.67.1 72 | types-pyyaml==6.0.12.20250402 73 | typing-extensions==4.13.2 74 | typing-inspection==0.4.0 75 | typos==1.31.1 76 | urllib3==2.4.0 77 | uvicorn==0.34.0 78 | virtualenv==20.30.0 79 | wcwidth==0.2.13 80 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.7.0 2 | anyio==4.9.0 3 | cffi==1.17.1 4 | click==8.1.8 5 | colorama==0.4.6 ; sys_platform == 'win32' 6 | fastapi-slim==0.115.12 7 | h11==0.14.0 8 | idna==3.10 9 | jinja2==3.1.6 10 | kanalizer==0.1.1 11 | markupsafe==3.0.2 12 | numpy==2.2.4 13 | platformdirs==4.3.7 14 | pycparser==2.22 15 | pydantic==2.11.3 16 | pydantic-core==2.33.1 17 | pyopenjtalk @ git+https://github.com/VOICEVOX/pyopenjtalk@74703b034dd90a1f199f49bb70bf3b66b1728a86 18 | python-multipart==0.0.20 19 | pyworld==0.3.5 20 | pyyaml==6.0.2 21 | semver==3.0.4 22 | setuptools==78.1.0 23 | sniffio==1.3.1 24 | soundfile==0.13.1 25 | soxr==0.5.0.post1 26 | starlette==0.45.3 27 | tqdm==4.67.1 28 | typing-extensions==4.13.2 29 | typing-inspection==0.4.0 30 | uvicorn==0.34.0 31 | -------------------------------------------------------------------------------- /resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/icons/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/icons/8.png -------------------------------------------------------------------------------- /resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/metas.json: -------------------------------------------------------------------------------- 1 | { 2 | "supported_features": { "permitted_synthesis_morphing": "NOTHING" } 3 | } 4 | -------------------------------------------------------------------------------- /resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/policy.md: -------------------------------------------------------------------------------- 1 | dummy3 policy 2 | 3 | https://voicevox.hiroshiba.jp/ 4 | -------------------------------------------------------------------------------- /resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/portrait.png -------------------------------------------------------------------------------- /resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/portraits/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/portraits/8.png -------------------------------------------------------------------------------- /resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/voice_samples/8_001.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/voice_samples/8_001.wav -------------------------------------------------------------------------------- /resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/voice_samples/8_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/voice_samples/8_002.wav -------------------------------------------------------------------------------- /resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/voice_samples/8_003.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/35b2c544-660e-401e-b503-0e14c635303a/voice_samples/8_003.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/icons/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/icons/1.png -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/icons/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/icons/3.png -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/icons/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/icons/5.png -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/icons/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/icons/7.png -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/metas.json: -------------------------------------------------------------------------------- 1 | { 2 | "supported_features": { "permitted_synthesis_morphing": "SELF_ONLY" } 3 | } 4 | -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/policy.md: -------------------------------------------------------------------------------- 1 | dummy2 policy 2 | 3 | https://voicevox.hiroshiba.jp/ 4 | -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/portrait.png -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/portraits/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/portraits/3.png -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/1_001.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/1_001.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/1_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/1_002.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/1_003.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/1_003.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/3_001.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/3_001.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/3_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/3_002.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/3_003.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/3_003.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/5_001.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/5_001.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/5_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/5_002.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/5_003.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/5_003.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/7_001.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/7_001.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/7_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/7_002.wav -------------------------------------------------------------------------------- /resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/7_003.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/388f246b-8c41-4ac1-8e2d-5d79f3ff56d9/voice_samples/7_003.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/icons/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/icons/0.png -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/icons/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/icons/2.png -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/icons/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/icons/4.png -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/icons/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/icons/6.png -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/metas.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/policy.md: -------------------------------------------------------------------------------- 1 | dummy1 policy 2 | 3 | https://voicevox.hiroshiba.jp/ 4 | -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/portrait.png -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/portraits/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/portraits/0.png -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/portraits/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/portraits/2.png -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/portraits/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/portraits/4.png -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/portraits/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/portraits/6.png -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/0_001.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/0_001.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/0_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/0_002.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/0_003.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/0_003.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/2_001.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/2_001.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/2_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/2_002.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/2_003.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/2_003.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/4_001.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/4_001.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/4_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/4_002.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/4_003.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/4_003.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/6_001.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/6_001.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/6_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/6_002.wav -------------------------------------------------------------------------------- /resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/6_003.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff/voice_samples/6_003.wav -------------------------------------------------------------------------------- /resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/icons/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/icons/9.png -------------------------------------------------------------------------------- /resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/metas.json: -------------------------------------------------------------------------------- 1 | { 2 | "supported_features": { "permitted_synthesis_morphing": "ALL" } 3 | } 4 | -------------------------------------------------------------------------------- /resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/policy.md: -------------------------------------------------------------------------------- 1 | dummy4 policy 2 | 3 | https://voicevox.hiroshiba.jp/ 4 | -------------------------------------------------------------------------------- /resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/portrait.png -------------------------------------------------------------------------------- /resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/voice_samples/9_001.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/voice_samples/9_001.wav -------------------------------------------------------------------------------- /resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/voice_samples/9_002.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/voice_samples/9_002.wav -------------------------------------------------------------------------------- /resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/voice_samples/9_003.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/character_info/b1a81618-b27b-40d2-b0ea-27a9ad408c4b/voice_samples/9_003.wav -------------------------------------------------------------------------------- /resources/engine_manifest_assets/dependency_licenses.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "dummy library", 4 | "version": "0.0.1", 5 | "license": "dummy license", 6 | "text": "dummy license text" 7 | } 8 | ] -------------------------------------------------------------------------------- /resources/engine_manifest_assets/downloadable_libraries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Dummy Library", 4 | "uuid": "2bb8bccf-1c3f-4bc9-959a-f388e37af3ad", 5 | "version": "0.0.1", 6 | "download_url": "https://github.com/VOICEVOX/voicevox_engine/archive/d7cf31c058bc83e1abf8e14d4231a06409c4cc2d.zip", 7 | "bytes": 1000, 8 | "speakers": [ 9 | { 10 | "speaker": { 11 | "name": "dummy1", 12 | "speaker_uuid": "7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff", 13 | "styles": [ 14 | { 15 | "name": "style1", 16 | "id": 0 17 | }, 18 | { 19 | "name": "style2", 20 | "id": 2 21 | } 22 | ], 23 | "version": "0.0.1" 24 | }, 25 | "speaker_info": { 26 | "policy": "", 27 | "portrait": "", 28 | "style_infos": [ 29 | { 30 | "id": 0, 31 | "icon": "", 32 | "voice_samples": ["", "", ""] 33 | }, 34 | { 35 | "id": 2, 36 | "icon": "", 37 | "voice_samples": ["", "", ""] 38 | } 39 | ] 40 | } 41 | } 42 | ] 43 | } 44 | ] 45 | -------------------------------------------------------------------------------- /resources/engine_manifest_assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/resources/engine_manifest_assets/icon.png -------------------------------------------------------------------------------- /resources/engine_manifest_assets/terms_of_service.md: -------------------------------------------------------------------------------- 1 | dummy teams of service -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | """VOICEVOX ENGINE のテスト。""" 2 | -------------------------------------------------------------------------------- /test/benchmark/speed/request.py: -------------------------------------------------------------------------------- 1 | """エンジンへのリクエストにかかる時間の測定""" 2 | 3 | import argparse 4 | from pathlib import Path 5 | 6 | from test.benchmark.engine_preparation import ServerType, generate_client 7 | from test.benchmark.speed.utility import benchmark_time 8 | 9 | 10 | def benchmark_request(server: ServerType, root_dir: Path | None = None) -> float: 11 | """ 12 | エンジンへのリクエストにかかる時間を測定する。 13 | 14 | `GET /` はエンジン内部処理が最小であるため、全キャラクター分のリクエスト-レスポンス(ネットワーク処理部分)にかかる時間を擬似的に計測できる。 15 | """ 16 | client = generate_client(server, root_dir) 17 | 18 | def execute() -> None: 19 | """計測対象となる処理を実行する""" 20 | client.get("/", params={}) 21 | 22 | average_time = benchmark_time(execute, n_repeat=10) 23 | return average_time 24 | 25 | 26 | if __name__ == "__main__": 27 | # 実行コマンドは `python -m test.benchmark.speed.request` である。 28 | # `server="localhost"` の場合、本ベンチマーク実行に先立ってエンジン起動が必要である。 29 | # エンジン起動コマンドの一例として以下を示す。 30 | # (別プロセスで)`python run.py --voicevox_dir=VOICEVOX/vv-engine` 31 | 32 | parser = argparse.ArgumentParser() 33 | parser.add_argument("--voicevox_dir", type=Path) 34 | args = parser.parse_args() 35 | root_dir: Path | None = args.voicevox_dir 36 | 37 | result_fakeserve = benchmark_request(server="fake", root_dir=root_dir) 38 | result_localhost = benchmark_request(server="localhost", root_dir=root_dir) 39 | print(f"`GET /` fakeserve: {result_fakeserve:.4f} sec") 40 | print(f"`GET /` localhost: {result_localhost:.4f} sec") 41 | -------------------------------------------------------------------------------- /test/benchmark/speed/utility.py: -------------------------------------------------------------------------------- 1 | """速度ベンチマーク用のユーティリティ""" 2 | 3 | import time 4 | from collections.abc import Callable 5 | 6 | 7 | def benchmark_time( 8 | target_function: Callable[[], None], n_repeat: int, sec_sleep: float = 1.0 9 | ) -> float: 10 | """対象関数の平均実行時間を計測する。""" 11 | scores: list[float] = [] 12 | for _ in range(n_repeat): 13 | start = time.perf_counter() 14 | target_function() 15 | end = time.perf_counter() 16 | scores += [end - start] 17 | time.sleep(sec_sleep) 18 | average = sum(scores) / len(scores) 19 | return average 20 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | """全テスト共通の pytest 用 fixtures。""" 2 | 3 | import pytest 4 | from syrupy.assertion import SnapshotAssertion 5 | from syrupy.extensions.json import JSONSnapshotExtension 6 | 7 | 8 | @pytest.fixture 9 | def snapshot_json(snapshot: SnapshotAssertion) -> SnapshotAssertion: 10 | """ 11 | syrupyでJSONをsnapshotするためのfixture。 12 | 13 | Examples 14 | -------- 15 | >>> def test_foo(snapshot_json: SnapshotAssertion): 16 | >>> assert snapshot_json == {"key": "value"} 17 | """ 18 | return snapshot.use_extension(JSONSnapshotExtension) 19 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_喋れるキャラクターの情報をURLで取得できる[35b2c544-660e-401e-b503-0e14c635303a].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy3 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "http://testserver/_resources/06112cbb75adcee7173751694dafd9d05edd90d5f230445290cb51a34caea8d1", 4 | "style_infos": [ 5 | { 6 | "icon": "http://testserver/_resources/759b5b5565e369741809631fcb3bc574c16da011b07d2d8dd9ceaa149d9e139b", 7 | "id": 8, 8 | "portrait": "http://testserver/_resources/9ecaf2beed142fdf6a48b4a8c3726fa2864ef5c1ada4884df2b94fb07685fb14", 9 | "voice_samples": [ 10 | "http://testserver/_resources/7a6a56cb9d571cfb774ac2c3ca8b873e25f68624ede21d6bf2ff625b44245ab5", 11 | "http://testserver/_resources/83fadd140b6b53938654825fa9b8ccd08f235b07de98ddec8c653fc9652f31f6", 12 | "http://testserver/_resources/9a243a14f9b4e20193708598cb76da5dba3a42ebd1f0ad008e0f3205da95bc33" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_喋れるキャラクターの情報をURLで取得できる[388f246b-8c41-4ac1-8e2d-5d79f3ff56d9].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy2 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "http://testserver/_resources/934d5c0f33e027f676543ab7e2b50be9c1abe7e5120b7d0a3b34bdd44efd82ae", 4 | "style_infos": [ 5 | { 6 | "icon": "http://testserver/_resources/71080a525a7629916416d7d3650122fc13ed6539930087372bc56bcda0b3a7b6", 7 | "id": 1, 8 | "portrait": null, 9 | "voice_samples": [ 10 | "http://testserver/_resources/b9b38572be4ef4e05d0d3e763c7a2002b848a33b0fe3fcf7cbc283c489539f56", 11 | "http://testserver/_resources/0867698c258361d6811e800de70c12621865b006e7f4a8ddda70e78d9b43ab36", 12 | "http://testserver/_resources/d70aeae2463763fd4abf93f06305b465c54690922db49427baef14e90fab3bb1" 13 | ] 14 | }, 15 | { 16 | "icon": "http://testserver/_resources/2e4d6acdd84cf9559754f073d7d34ebb3044189e2ad01cbed5cbd1e578fdf8ad", 17 | "id": 3, 18 | "portrait": "http://testserver/_resources/ebe0eb2e71802519f123ddb25debc504ba28b35d8e216667df634327182931d1", 19 | "voice_samples": [ 20 | "http://testserver/_resources/47d116ee6dc96b5fabc2d545287835258fbf4fec2becae5ccdf85c126193637e", 21 | "http://testserver/_resources/df93bb64533008f7165957636ca414a5d898c972065452fb2e8e013c5b56effb", 22 | "http://testserver/_resources/660088d6b340f779f7e95976ba7f9ad7d482d4de51aeb2fc2a3a9f99715f863f" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_喋れるキャラクターの情報をURLで取得できる[7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy1 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "http://testserver/_resources/bc1db0b6614d4433dc98b4ab3bc7b81954471c16633f8c49c15532fabfad7e27", 4 | "style_infos": [ 5 | { 6 | "icon": "http://testserver/_resources/6b7f0d06aa7306be8f887a8e99fa23dc6e0cb7be80cbbe2ce2823fc304a1c350", 7 | "id": 0, 8 | "portrait": "http://testserver/_resources/62071bcc940cba30d87bf1a617482a0987a96954d307fe9ecd34d913f679fa2a", 9 | "voice_samples": [ 10 | "http://testserver/_resources/2926d3d4670d0be0eae498e8b0422ae1f85a44f7cd3df51bd70c8b8c9f607c0a", 11 | "http://testserver/_resources/4ad32dd8e4793fa610160a3c4ef9e026a874a8399d4c86f897e2584bba171dc4", 12 | "http://testserver/_resources/25158f0e0d2b2da3985a7ef5e2d74b46c517d3c6f6240fc3ef42be780634889b" 13 | ] 14 | }, 15 | { 16 | "icon": "http://testserver/_resources/7706521aeee094e95c0e5fecae4f1253361c60900c378403e89b3142523b7d2c", 17 | "id": 2, 18 | "portrait": "http://testserver/_resources/c038f14a8b3916a6d8723cfd72e949b947ff860e9c7536ad41a7f849de2f2031", 19 | "voice_samples": [ 20 | "http://testserver/_resources/89b799594937ae9df49e3b7caffed2392ac9432b4e30273ba09f47de1ec4e31c", 21 | "http://testserver/_resources/7d47428eb2e765aedc0e040873a926ebbfc19c6bb8c07f0dd5e2181003d57481", 22 | "http://testserver/_resources/106106a9d7bcba3862f79660a0295a46cd32ec4f517405a9d18f595f93cfb540" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_喋れるキャラクターの情報を取得できる[35b2c544-660e-401e-b503-0e14c635303a].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy3 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "MD5:90b250e0976f792e2fda2b1ad2643c7b", 4 | "style_infos": [ 5 | { 6 | "icon": "MD5:541aeccd87319c0af159cfa13baf26cb", 7 | "id": 8, 8 | "portrait": "MD5:1bb8b584e8499d601a3f3bf0c3216391", 9 | "voice_samples": [ 10 | "MD5:148c72905d47a308cbdf9858c99ef9d7", 11 | "MD5:46fda5f38dec0df94445066eee9ed128", 12 | "MD5:61e7d2d3180c2c891cf096f50b98e317" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_喋れるキャラクターの情報を取得できる[388f246b-8c41-4ac1-8e2d-5d79f3ff56d9].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy2 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "MD5:72ceb00f20b2a1e449f0b45973cc8b24", 4 | "style_infos": [ 5 | { 6 | "icon": "MD5:3248458ae11d28ec1eb482db7f1927d9", 7 | "id": 1, 8 | "portrait": null, 9 | "voice_samples": [ 10 | "MD5:2cdd82264a8b0ad508ff3f5a84d5c920", 11 | "MD5:14b4a96141c6b9e86ce4f38adaac1fcb", 12 | "MD5:4494752eec42b718ff3b9a3fb934596a" 13 | ] 14 | }, 15 | { 16 | "icon": "MD5:3e32a4a66bd2505cb75f91c8028d061c", 17 | "id": 3, 18 | "portrait": "MD5:1dd8a513f11c204c1449172b7a812be8", 19 | "voice_samples": [ 20 | "MD5:2bd7d3be714fdfdda2e96aa98888a9bd", 21 | "MD5:10a9d6d4bcd02a6fa37d13c3f7335df1", 22 | "MD5:6a21d1007f8957fca45843fde1e2d1c2" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_喋れるキャラクターの情報を取得できる[7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy1 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "MD5:cab33c9fdf563682108666a012dc9853", 4 | "style_infos": [ 5 | { 6 | "icon": "MD5:529b1750562ca339ba05c1b00f4b2854", 7 | "id": 0, 8 | "portrait": "MD5:6c1c461e54ba4f57d0c17171d17e1d80", 9 | "voice_samples": [ 10 | "MD5:85acb767ac22b1d17915c666cc5cee90", 11 | "MD5:ad9e64177d28f960fb9ce40162cd82c2", 12 | "MD5:16bf0d95c463fff08353a3452bdb8d7c" 13 | ] 14 | }, 15 | { 16 | "icon": "MD5:04cce28c375949935497ec3d5d015be9", 17 | "id": 2, 18 | "portrait": "MD5:0a4e78369adc266672d571f4c0663697", 19 | "voice_samples": [ 20 | "MD5:f508aca632a8f1cfd9eee7ed29cff96c", 21 | "MD5:09984e27d23eee8af13809a0f621f7fd", 22 | "MD5:e3f9a4df3f537bfb9d63d1791eda73e6" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_喋れるキャラクター一覧が取得できる.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "dummy1", 4 | "speaker_uuid": "7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff", 5 | "styles": [ 6 | { 7 | "id": 0, 8 | "name": "style0", 9 | "type": "talk" 10 | }, 11 | { 12 | "id": 2, 13 | "name": "style1", 14 | "type": "talk" 15 | } 16 | ], 17 | "supported_features": { 18 | "permitted_synthesis_morphing": "ALL" 19 | }, 20 | "version": "mock" 21 | }, 22 | { 23 | "name": "dummy2", 24 | "speaker_uuid": "388f246b-8c41-4ac1-8e2d-5d79f3ff56d9", 25 | "styles": [ 26 | { 27 | "id": 1, 28 | "name": "style0", 29 | "type": "talk" 30 | }, 31 | { 32 | "id": 3, 33 | "name": "style1", 34 | "type": "talk" 35 | } 36 | ], 37 | "supported_features": { 38 | "permitted_synthesis_morphing": "SELF_ONLY" 39 | }, 40 | "version": "mock" 41 | }, 42 | { 43 | "name": "dummy3", 44 | "speaker_uuid": "35b2c544-660e-401e-b503-0e14c635303a", 45 | "styles": [ 46 | { 47 | "id": 8, 48 | "name": "style0", 49 | "type": "talk" 50 | } 51 | ], 52 | "supported_features": { 53 | "permitted_synthesis_morphing": "NOTHING" 54 | }, 55 | "version": "mock" 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_歌えるキャラクターの情報をURLで取得できる[388f246b-8c41-4ac1-8e2d-5d79f3ff56d9].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy2 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "http://testserver/_resources/934d5c0f33e027f676543ab7e2b50be9c1abe7e5120b7d0a3b34bdd44efd82ae", 4 | "style_infos": [ 5 | { 6 | "icon": "http://testserver/_resources/04cdf27edb6a25e1ba2a0ced57d404151e8d556540f67297ea127984e9ad144b", 7 | "id": 5, 8 | "portrait": null, 9 | "voice_samples": [ 10 | "http://testserver/_resources/8263a333e29381dc24f7412d6b18417b309ce679c446c0dd09aea80a9a9e2c34", 11 | "http://testserver/_resources/5d53a9a385787b822295047093bb9f0745c4cecf1d46957fa392ddb2f1561aa4", 12 | "http://testserver/_resources/da3922b08340941c6fa8f5cb8c1f55093becba8636d5b39e98694780e34bc066" 13 | ] 14 | }, 15 | { 16 | "icon": "http://testserver/_resources/cc23bf07fcf085fbea4015cc7f0e52a67fa0f2c5e6428fb537837fce9b19a6a3", 17 | "id": 7, 18 | "portrait": null, 19 | "voice_samples": [ 20 | "http://testserver/_resources/1db7a4e6530458e5e8998d8f83ade8f3bf9156853c465c16caf1b25ccfc17bdb", 21 | "http://testserver/_resources/8c79bf75f66c9b81c48d251ab34f8bd4bfc28c381a882bdb18053e22e08c7f3b", 22 | "http://testserver/_resources/26a3efd18b612f26186d4b8b6850fe252f43a66607c25d40f4c1d9edc73b4c21" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_歌えるキャラクターの情報をURLで取得できる[7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy1 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "http://testserver/_resources/bc1db0b6614d4433dc98b4ab3bc7b81954471c16633f8c49c15532fabfad7e27", 4 | "style_infos": [ 5 | { 6 | "icon": "http://testserver/_resources/a7eeaee45a7ad967e43af7fbc2f006e0a889252083440c85d3ce5d5b525a0be8", 7 | "id": 4, 8 | "portrait": "http://testserver/_resources/541554b529bb689f0c1c3bd6b606b9885583567039f42a61b0b495a96e0450f9", 9 | "voice_samples": [ 10 | "http://testserver/_resources/41f0f4dd473fbd42a68f4dffa90eae79d77f26b82c534b503e22d46e0202ed0e", 11 | "http://testserver/_resources/8ab50e880fba92355649d4eb866fb39635c9d56fc22b1b58db0c110ac143328c", 12 | "http://testserver/_resources/a5f6299bf35c6aa422b4be49d06152289509a436ee7553728d5b68c5fc1ac880" 13 | ] 14 | }, 15 | { 16 | "icon": "http://testserver/_resources/b1697caeef457c1b46e301c3313ac8ea27735e5313a47d92c0a01cde76ab11cf", 17 | "id": 6, 18 | "portrait": "http://testserver/_resources/5c521d46f5e73788cf26b21fa43c6c898f731c3a88b8c23f31bc7ebd83ca788e", 19 | "voice_samples": [ 20 | "http://testserver/_resources/891f871e89ffb2d5dc8e0483613d81b576221660f00a32893700f964552f5e11", 21 | "http://testserver/_resources/2143f339114bad85ff9ff6253a7b0cc6453e7f6268c3052ed29dba3a810a891a", 22 | "http://testserver/_resources/81c8222bbb4778e5e9c9e94adfa3cc1c041fa9ff39181f8a644371ca6087bae0" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_歌えるキャラクターの情報をURLで取得できる[b1a81618-b27b-40d2-b0ea-27a9ad408c4b].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy4 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "http://testserver/_resources/18739f6fb92ddcf2e7070737c1d763bc2c5daf1efffc6c012e2bd1720e67abb3", 4 | "style_infos": [ 5 | { 6 | "icon": "http://testserver/_resources/531e97491ca6ecc0e664475b501d5db613d4ac1019f92078ed39c08ad8000ff9", 7 | "id": 9, 8 | "portrait": null, 9 | "voice_samples": [ 10 | "http://testserver/_resources/6edf286a07f2bd462bf8fdb89a8976a3878f3c2f60a90a18425c9371002df7b5", 11 | "http://testserver/_resources/00a74dc38ea0b1130ecda9fdad3e26d4e1a3efbdfa00a6a2c4d393aecc4a4683", 12 | "http://testserver/_resources/83988f1e492cc0bfdd5ebfb801fdec7b5f5be6f8e3a56bba9abf39c418ed5fdb" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_歌えるキャラクターの情報を取得できる[388f246b-8c41-4ac1-8e2d-5d79f3ff56d9].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy2 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "MD5:72ceb00f20b2a1e449f0b45973cc8b24", 4 | "style_infos": [ 5 | { 6 | "icon": "MD5:5f2211c3144b8dee613056bef5893d60", 7 | "id": 5, 8 | "portrait": null, 9 | "voice_samples": [ 10 | "MD5:2b7f17f6751b9f0c76950ad3bcc1a619", 11 | "MD5:4bc9f14cda818955cba931b1532e18fd", 12 | "MD5:9ebfc3cf3fba47513a60c464fc57c705" 13 | ] 14 | }, 15 | { 16 | "icon": "MD5:375ecba26764b7c71ce61731b52f71f8", 17 | "id": 7, 18 | "portrait": null, 19 | "voice_samples": [ 20 | "MD5:fc93b361293ce128afd8f48d4cd89bc5", 21 | "MD5:b74ee50cb135ccf29c0a1be2711f8cca", 22 | "MD5:08c325d1cfe72209949a77c327b60302" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_歌えるキャラクターの情報を取得できる[7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy1 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "MD5:cab33c9fdf563682108666a012dc9853", 4 | "style_infos": [ 5 | { 6 | "icon": "MD5:9dfc8b32d4afc3c933388ba85a8d8d12", 7 | "id": 4, 8 | "portrait": "MD5:2aba7f7037d00903dada4401582bf31a", 9 | "voice_samples": [ 10 | "MD5:49c763de77c5c6be4967900b08b561a9", 11 | "MD5:d9736740e3735bbf45efd792f8af7383", 12 | "MD5:58bda86215149663b605d0ba0db59bde" 13 | ] 14 | }, 15 | { 16 | "icon": "MD5:53b0f8ce874e450fc8cc5758d6ed2b03", 17 | "id": 6, 18 | "portrait": "MD5:6a79f7e6d8ca9087be9a0e39eac67e7b", 19 | "voice_samples": [ 20 | "MD5:e9fbdc80f22d91a1ad96612ea60391b4", 21 | "MD5:8c403062ffc5fca5605aca46778ed512", 22 | "MD5:95a626ec8a36a3a550d7ba4188937cb3" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_歌えるキャラクターの情報を取得できる[b1a81618-b27b-40d2-b0ea-27a9ad408c4b].json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy4 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "MD5:70cb4a361935084f00f6956a4e8e4f32", 4 | "style_infos": [ 5 | { 6 | "icon": "MD5:e1e2fab676912fc0796a5b23320a0b67", 7 | "id": 9, 8 | "portrait": null, 9 | "voice_samples": [ 10 | "MD5:fa1230e97dec17b814ec05da1709be19", 11 | "MD5:714f4c4f2d3c51a1d9597a6960b8367c", 12 | "MD5:bc47fd0d1ea9083c2f4621461ae072b8" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_characters/test_歌えるキャラクター一覧が取得できる.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "dummy1", 4 | "speaker_uuid": "7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff", 5 | "styles": [ 6 | { 7 | "id": 4, 8 | "name": "style2", 9 | "type": "frame_decode" 10 | }, 11 | { 12 | "id": 6, 13 | "name": "style3", 14 | "type": "frame_decode" 15 | } 16 | ], 17 | "supported_features": { 18 | "permitted_synthesis_morphing": "ALL" 19 | }, 20 | "version": "mock" 21 | }, 22 | { 23 | "name": "dummy2", 24 | "speaker_uuid": "388f246b-8c41-4ac1-8e2d-5d79f3ff56d9", 25 | "styles": [ 26 | { 27 | "id": 5, 28 | "name": "style2", 29 | "type": "frame_decode" 30 | }, 31 | { 32 | "id": 7, 33 | "name": "style3", 34 | "type": "sing" 35 | } 36 | ], 37 | "supported_features": { 38 | "permitted_synthesis_morphing": "SELF_ONLY" 39 | }, 40 | "version": "mock" 41 | }, 42 | { 43 | "name": "dummy4", 44 | "speaker_uuid": "b1a81618-b27b-40d2-b0ea-27a9ad408c4b", 45 | "styles": [ 46 | { 47 | "id": 9, 48 | "name": "style0", 49 | "type": "sing" 50 | } 51 | ], 52 | "supported_features": { 53 | "permitted_synthesis_morphing": "ALL" 54 | }, 55 | "version": "mock" 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_missing_core/test_missing_core_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "バージョン 4.0.4 のコアが見つかりません" 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_missing_engine/test_missing_tts_engine_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "バージョン 4.0.4 のコアが見つかりません。" 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_sing.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_楽譜とキャラクターIDから音声を合成できる 3 | 'MD5:1c385210acba238994604a8cee96aee3' 4 | # --- 5 | -------------------------------------------------------------------------------- /test/e2e/__snapshots__/test_tts.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_テキストとキャラクターIDから音声を合成できる 3 | 'MD5:8f7ddc461c68542d4d8ef4cd5c54ca82' 4 | # --- 5 | -------------------------------------------------------------------------------- /test/e2e/single_api/engine_info/__snapshots__/test_core_versions/test_get_core_versions_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | "0.0.0" 3 | ] 4 | -------------------------------------------------------------------------------- /test/e2e/single_api/engine_info/__snapshots__/test_supported_devices/test_get_supported_devices_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "cpu": true, 3 | "cuda": false, 4 | "dml": false 5 | } 6 | -------------------------------------------------------------------------------- /test/e2e/single_api/engine_info/__snapshots__/test_version/test_get_version_200.json: -------------------------------------------------------------------------------- 1 | "latest" 2 | -------------------------------------------------------------------------------- /test/e2e/single_api/engine_info/test_core_versions.py: -------------------------------------------------------------------------------- 1 | """/core_versions API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_get_core_versions_200( 8 | client: TestClient, snapshot_json: SnapshotAssertion 9 | ) -> None: 10 | response = client.get("/core_versions", params={}) 11 | assert response.status_code == 200 12 | assert snapshot_json == response.json() 13 | -------------------------------------------------------------------------------- /test/e2e/single_api/engine_info/test_engine_manifest.py: -------------------------------------------------------------------------------- 1 | """/engine_manifest API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.utility import hash_long_string 7 | 8 | 9 | def test_get_engine_manifest_200( 10 | client: TestClient, snapshot_json: SnapshotAssertion 11 | ) -> None: 12 | response = client.get("/engine_manifest") 13 | assert response.status_code == 200 14 | assert snapshot_json == hash_long_string(response.json()) 15 | -------------------------------------------------------------------------------- /test/e2e/single_api/engine_info/test_supported_devices.py: -------------------------------------------------------------------------------- 1 | """/supported_devices API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_get_supported_devices_200( 8 | client: TestClient, snapshot_json: SnapshotAssertion 9 | ) -> None: 10 | response = client.get("/supported_devices", params={}) 11 | assert response.status_code == 200 12 | assert snapshot_json == response.json() 13 | -------------------------------------------------------------------------------- /test/e2e/single_api/engine_info/test_version.py: -------------------------------------------------------------------------------- 1 | """/version API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_get_version_200(client: TestClient, snapshot_json: SnapshotAssertion) -> None: 8 | response = client.get("/version", params={}) 9 | assert response.status_code == 200 10 | assert snapshot_json == response.json() 11 | -------------------------------------------------------------------------------- /test/e2e/single_api/morphing/__snapshots__/test_morphable_targets/test_post_morphable_targets_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "0": { 4 | "is_morphable": true 5 | }, 6 | "1": { 7 | "is_morphable": false 8 | }, 9 | "2": { 10 | "is_morphable": true 11 | }, 12 | "3": { 13 | "is_morphable": false 14 | }, 15 | "4": { 16 | "is_morphable": false 17 | }, 18 | "5": { 19 | "is_morphable": false 20 | }, 21 | "6": { 22 | "is_morphable": false 23 | }, 24 | "7": { 25 | "is_morphable": false 26 | }, 27 | "8": { 28 | "is_morphable": false 29 | }, 30 | "9": { 31 | "is_morphable": false 32 | } 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /test/e2e/single_api/morphing/__snapshots__/test_synthesis_morphing/test_post_synthesis_morphing_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": [ 3 | { 4 | "ctx": { 5 | "le": 1.0 6 | }, 7 | "input": "100", 8 | "loc": [ 9 | "query", 10 | "morph_rate" 11 | ], 12 | "msg": "Input should be less than or equal to 1", 13 | "type": "less_than_equal" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/single_api/morphing/test_morphable_targets.py: -------------------------------------------------------------------------------- 1 | """/morphable_targets API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_post_morphable_targets_200( 8 | client: TestClient, snapshot_json: SnapshotAssertion 9 | ) -> None: 10 | response = client.post("/morphable_targets", json=[0]) 11 | assert response.status_code == 200 12 | assert snapshot_json == response.json() 13 | -------------------------------------------------------------------------------- /test/e2e/single_api/portal_page/__snapshots__/test_get_root.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_get_root_200 3 | ''' 4 | 5 | 6 | 7 | VOICEVOX ENGINE OSS 8 | 9 | 10 |

VOICEVOX ENGINE OSS

11 | VOICEVOX ENGINE OSS へようこそ! 12 | 16 | 17 | ''' 18 | # --- 19 | -------------------------------------------------------------------------------- /test/e2e/single_api/portal_page/test_get_root.py: -------------------------------------------------------------------------------- 1 | """/ API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_get_root_200(client: TestClient, snapshot: SnapshotAssertion) -> None: 8 | response = client.get("/") 9 | assert response.status_code == 200 10 | assert snapshot == response.content.decode("utf-8") 11 | -------------------------------------------------------------------------------- /test/e2e/single_api/preset/__snapshots__/test_add_preset/test_post_add_preset_200.json: -------------------------------------------------------------------------------- 1 | 9999 2 | -------------------------------------------------------------------------------- /test/e2e/single_api/preset/__snapshots__/test_add_preset/test_post_add_preset_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": [ 3 | { 4 | "input": { 5 | "id": 9999, 6 | "intonationScale": 1, 7 | "name": "test_preset", 8 | "pauseLength": null, 9 | "pauseLengthScale": 1, 10 | "pitchScale": 1, 11 | "postPhonemeLength": 10, 12 | "prePhonemeLength": 10, 13 | "speaker_uuid": "123-456-789-234", 14 | "speedScale": "a", 15 | "volumeScale": null 16 | }, 17 | "loc": [ 18 | "body", 19 | "style_id" 20 | ], 21 | "msg": "Field required", 22 | "type": "missing" 23 | }, 24 | { 25 | "input": "a", 26 | "loc": [ 27 | "body", 28 | "speedScale" 29 | ], 30 | "msg": "Input should be a valid number, unable to parse string as a number", 31 | "type": "float_parsing" 32 | }, 33 | { 34 | "input": null, 35 | "loc": [ 36 | "body", 37 | "volumeScale" 38 | ], 39 | "msg": "Input should be a valid number", 40 | "type": "float_type" 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /test/e2e/single_api/preset/__snapshots__/test_delete_preset.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_post_delete_preset_204 3 | b'' 4 | # --- 5 | -------------------------------------------------------------------------------- /test/e2e/single_api/preset/__snapshots__/test_delete_preset/test_post_delete_preset_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "削除対象のプリセットが存在しません" 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/single_api/preset/__snapshots__/test_presets/test_get_presets_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "intonationScale": 1.0, 5 | "name": "サンプルプリセット", 6 | "pauseLength": null, 7 | "pauseLengthScale": 1.0, 8 | "pitchScale": 0.0, 9 | "postPhonemeLength": 0.1, 10 | "prePhonemeLength": 0.1, 11 | "speaker_uuid": "7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff", 12 | "speedScale": 1.0, 13 | "style_id": 0, 14 | "volumeScale": 1.0 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /test/e2e/single_api/preset/__snapshots__/test_update_preset/test_post_update_preset_200.json: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /test/e2e/single_api/preset/__snapshots__/test_update_preset/test_post_update_preset_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "更新先のプリセットが存在しません" 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/single_api/preset/test_add_preset.py: -------------------------------------------------------------------------------- 1 | """/add_preset API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_post_add_preset_200( 8 | client: TestClient, snapshot_json: SnapshotAssertion 9 | ) -> None: 10 | preset = { 11 | "id": 9999, 12 | "name": "test_preset", 13 | "speaker_uuid": "123-456-789-234", 14 | "style_id": 9999, 15 | "speedScale": 1, 16 | "pitchScale": 1, 17 | "intonationScale": 1, 18 | "volumeScale": 1, 19 | "prePhonemeLength": 10, 20 | "postPhonemeLength": 10, 21 | "pauseLength": None, 22 | "pauseLengthScale": 1, 23 | } 24 | response = client.post("/add_preset", params={}, json=preset) 25 | assert response.status_code == 200 26 | assert snapshot_json == response.json() 27 | 28 | 29 | def test_post_add_preset_422( 30 | client: TestClient, snapshot_json: SnapshotAssertion 31 | ) -> None: 32 | wrong_typed_speed_scale = "a" 33 | wrong_typed_volume_scale = None 34 | preset = { 35 | "id": 9999, 36 | "name": "test_preset", 37 | "speaker_uuid": "123-456-789-234", 38 | # "style_id": 9999, # NOTE: 必須パラメータが不足 39 | "speedScale": wrong_typed_speed_scale, 40 | "pitchScale": 1, 41 | "intonationScale": 1, 42 | "volumeScale": wrong_typed_volume_scale, 43 | "prePhonemeLength": 10, 44 | "postPhonemeLength": 10, 45 | "pauseLength": None, 46 | "pauseLengthScale": 1, 47 | } 48 | response = client.post("/add_preset", params={}, json=preset) 49 | assert response.status_code == 422 50 | assert snapshot_json == response.json() 51 | -------------------------------------------------------------------------------- /test/e2e/single_api/preset/test_delete_preset.py: -------------------------------------------------------------------------------- 1 | """/delete_preset API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_post_delete_preset_204( 8 | client: TestClient, snapshot: SnapshotAssertion 9 | ) -> None: 10 | response = client.post("/delete_preset", params={"id": 1}) 11 | assert response.status_code == 204 12 | assert snapshot == response.content 13 | 14 | 15 | def test_post_delete_preset_422( 16 | client: TestClient, snapshot_json: SnapshotAssertion 17 | ) -> None: 18 | response = client.post("/delete_preset", params={"id": 4040000000}) 19 | assert response.status_code == 422 20 | assert snapshot_json == response.json() 21 | -------------------------------------------------------------------------------- /test/e2e/single_api/preset/test_presets.py: -------------------------------------------------------------------------------- 1 | """/presets API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_get_presets_200(client: TestClient, snapshot_json: SnapshotAssertion) -> None: 8 | response = client.get("/presets") 9 | assert response.status_code == 200 10 | assert snapshot_json == response.json() 11 | -------------------------------------------------------------------------------- /test/e2e/single_api/preset/test_update_preset.py: -------------------------------------------------------------------------------- 1 | """/update_preset API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_post_update_preset_200( 8 | client: TestClient, snapshot_json: SnapshotAssertion 9 | ) -> None: 10 | preset = { 11 | "id": 1, 12 | "name": "test_preset", 13 | "speaker_uuid": "123-456-789-234", 14 | "style_id": 9999, 15 | "speedScale": 1, 16 | "pitchScale": 1, 17 | "intonationScale": 1, 18 | "volumeScale": 1, 19 | "prePhonemeLength": 10, 20 | "postPhonemeLength": 10, 21 | "pauseLength": None, 22 | "pauseLengthScale": 1, 23 | } 24 | response = client.post("/update_preset", params={}, json=preset) 25 | assert response.status_code == 200 26 | assert snapshot_json == response.json() 27 | 28 | 29 | def test_post_update_preset_422( 30 | client: TestClient, snapshot_json: SnapshotAssertion 31 | ) -> None: 32 | preset = { 33 | "id": 4040000000, 34 | "name": "Nessie", 35 | "speaker_uuid": "404-404-404-404", 36 | "style_id": 404, 37 | "speedScale": 404, 38 | "pitchScale": 404, 39 | "intonationScale": 404, 40 | "volumeScale": 404, 41 | "prePhonemeLength": 404, 42 | "postPhonemeLength": 404, 43 | "pauseLength": 404, 44 | "pauseLengthScale": 404, 45 | } 46 | response = client.post("/update_preset", params={}, json=preset) 47 | assert response.status_code == 422 48 | assert snapshot_json == response.json() 49 | -------------------------------------------------------------------------------- /test/e2e/single_api/setting/__snapshots__/test_setting_api/test_post_setting_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": [ 3 | { 4 | "ctx": { 5 | "expected": "'all' or 'localapps'" 6 | }, 7 | "input": "wrong", 8 | "loc": [ 9 | "body", 10 | "cors_policy_mode" 11 | ], 12 | "msg": "Input should be 'all' or 'localapps'", 13 | "type": "enum" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/single_api/setting/test_setting_api.py: -------------------------------------------------------------------------------- 1 | """/setting API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_get_setting_200(client: TestClient, snapshot: SnapshotAssertion) -> None: 8 | response = client.get("/setting") 9 | assert response.status_code == 200 10 | # HTML string をテスト 11 | assert snapshot == response.content.decode("utf-8") 12 | 13 | 14 | def test_post_setting_204(client: TestClient, snapshot: SnapshotAssertion) -> None: 15 | response = client.post( 16 | "/setting", data={"cors_policy_mode": "all", "allow_origin": "vv"} 17 | ) 18 | assert response.status_code == 204 19 | assert snapshot == response.content 20 | 21 | 22 | def test_post_setting_422(client: TestClient, snapshot_json: SnapshotAssertion) -> None: 23 | response = client.post( 24 | "/setting", data={"cors_policy_mode": "wrong", "allow_origin": "wrong"} 25 | ) 26 | assert response.status_code == 422 27 | assert snapshot_json == response.json() 28 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/__snapshots__/test_singer_info/test_get_singer_info_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy4 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "MD5:70cb4a361935084f00f6956a4e8e4f32", 4 | "style_infos": [ 5 | { 6 | "icon": "MD5:e1e2fab676912fc0796a5b23320a0b67", 7 | "id": 9, 8 | "portrait": null, 9 | "voice_samples": [ 10 | "MD5:fa1230e97dec17b814ec05da1709be19", 11 | "MD5:714f4c4f2d3c51a1d9597a6960b8367c", 12 | "MD5:bc47fd0d1ea9083c2f4621461ae072b8" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/__snapshots__/test_singer_info/test_get_singer_info_404.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "該当するキャラクターが見つかりません" 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/__snapshots__/test_singer_info/test_get_singer_info_with_url_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy4 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "http://testserver/_resources/18739f6fb92ddcf2e7070737c1d763bc2c5daf1efffc6c012e2bd1720e67abb3", 4 | "style_infos": [ 5 | { 6 | "icon": "http://testserver/_resources/531e97491ca6ecc0e664475b501d5db613d4ac1019f92078ed39c08ad8000ff9", 7 | "id": 9, 8 | "portrait": null, 9 | "voice_samples": [ 10 | "http://testserver/_resources/6edf286a07f2bd462bf8fdb89a8976a3878f3c2f60a90a18425c9371002df7b5", 11 | "http://testserver/_resources/00a74dc38ea0b1130ecda9fdad3e26d4e1a3efbdfa00a6a2c4d393aecc4a4683", 12 | "http://testserver/_resources/83988f1e492cc0bfdd5ebfb801fdec7b5f5be6f8e3a56bba9abf39c418ed5fdb" 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/__snapshots__/test_singers/test_get_singers_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "dummy1", 4 | "speaker_uuid": "7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff", 5 | "styles": [ 6 | { 7 | "id": 4, 8 | "name": "style2", 9 | "type": "frame_decode" 10 | }, 11 | { 12 | "id": 6, 13 | "name": "style3", 14 | "type": "frame_decode" 15 | } 16 | ], 17 | "supported_features": { 18 | "permitted_synthesis_morphing": "ALL" 19 | }, 20 | "version": "mock" 21 | }, 22 | { 23 | "name": "dummy2", 24 | "speaker_uuid": "388f246b-8c41-4ac1-8e2d-5d79f3ff56d9", 25 | "styles": [ 26 | { 27 | "id": 5, 28 | "name": "style2", 29 | "type": "frame_decode" 30 | }, 31 | { 32 | "id": 7, 33 | "name": "style3", 34 | "type": "sing" 35 | } 36 | ], 37 | "supported_features": { 38 | "permitted_synthesis_morphing": "SELF_ONLY" 39 | }, 40 | "version": "mock" 41 | }, 42 | { 43 | "name": "dummy4", 44 | "speaker_uuid": "b1a81618-b27b-40d2-b0ea-27a9ad408c4b", 45 | "styles": [ 46 | { 47 | "id": 9, 48 | "name": "style0", 49 | "type": "sing" 50 | } 51 | ], 52 | "supported_features": { 53 | "permitted_synthesis_morphing": "ALL" 54 | }, 55 | "version": "mock" 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/__snapshots__/test_speaker_info/test_get_speaker_info_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy2 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "MD5:72ceb00f20b2a1e449f0b45973cc8b24", 4 | "style_infos": [ 5 | { 6 | "icon": "MD5:3248458ae11d28ec1eb482db7f1927d9", 7 | "id": 1, 8 | "portrait": null, 9 | "voice_samples": [ 10 | "MD5:2cdd82264a8b0ad508ff3f5a84d5c920", 11 | "MD5:14b4a96141c6b9e86ce4f38adaac1fcb", 12 | "MD5:4494752eec42b718ff3b9a3fb934596a" 13 | ] 14 | }, 15 | { 16 | "icon": "MD5:3e32a4a66bd2505cb75f91c8028d061c", 17 | "id": 3, 18 | "portrait": "MD5:1dd8a513f11c204c1449172b7a812be8", 19 | "voice_samples": [ 20 | "MD5:2bd7d3be714fdfdda2e96aa98888a9bd", 21 | "MD5:10a9d6d4bcd02a6fa37d13c3f7335df1", 22 | "MD5:6a21d1007f8957fca45843fde1e2d1c2" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/__snapshots__/test_speaker_info/test_get_speaker_info_404.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "該当するキャラクターが見つかりません" 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/__snapshots__/test_speaker_info/test_get_speaker_info_with_url_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "policy": "dummy2 policy\n\nhttps://voicevox.hiroshiba.jp/\n", 3 | "portrait": "http://testserver/_resources/934d5c0f33e027f676543ab7e2b50be9c1abe7e5120b7d0a3b34bdd44efd82ae", 4 | "style_infos": [ 5 | { 6 | "icon": "http://testserver/_resources/71080a525a7629916416d7d3650122fc13ed6539930087372bc56bcda0b3a7b6", 7 | "id": 1, 8 | "portrait": null, 9 | "voice_samples": [ 10 | "http://testserver/_resources/b9b38572be4ef4e05d0d3e763c7a2002b848a33b0fe3fcf7cbc283c489539f56", 11 | "http://testserver/_resources/0867698c258361d6811e800de70c12621865b006e7f4a8ddda70e78d9b43ab36", 12 | "http://testserver/_resources/d70aeae2463763fd4abf93f06305b465c54690922db49427baef14e90fab3bb1" 13 | ] 14 | }, 15 | { 16 | "icon": "http://testserver/_resources/2e4d6acdd84cf9559754f073d7d34ebb3044189e2ad01cbed5cbd1e578fdf8ad", 17 | "id": 3, 18 | "portrait": "http://testserver/_resources/ebe0eb2e71802519f123ddb25debc504ba28b35d8e216667df634327182931d1", 19 | "voice_samples": [ 20 | "http://testserver/_resources/47d116ee6dc96b5fabc2d545287835258fbf4fec2becae5ccdf85c126193637e", 21 | "http://testserver/_resources/df93bb64533008f7165957636ca414a5d898c972065452fb2e8e013c5b56effb", 22 | "http://testserver/_resources/660088d6b340f779f7e95976ba7f9ad7d482d4de51aeb2fc2a3a9f99715f863f" 23 | ] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/__snapshots__/test_speakers/test_get_speakers_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "dummy1", 4 | "speaker_uuid": "7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff", 5 | "styles": [ 6 | { 7 | "id": 0, 8 | "name": "style0", 9 | "type": "talk" 10 | }, 11 | { 12 | "id": 2, 13 | "name": "style1", 14 | "type": "talk" 15 | } 16 | ], 17 | "supported_features": { 18 | "permitted_synthesis_morphing": "ALL" 19 | }, 20 | "version": "mock" 21 | }, 22 | { 23 | "name": "dummy2", 24 | "speaker_uuid": "388f246b-8c41-4ac1-8e2d-5d79f3ff56d9", 25 | "styles": [ 26 | { 27 | "id": 1, 28 | "name": "style0", 29 | "type": "talk" 30 | }, 31 | { 32 | "id": 3, 33 | "name": "style1", 34 | "type": "talk" 35 | } 36 | ], 37 | "supported_features": { 38 | "permitted_synthesis_morphing": "SELF_ONLY" 39 | }, 40 | "version": "mock" 41 | }, 42 | { 43 | "name": "dummy3", 44 | "speaker_uuid": "35b2c544-660e-401e-b503-0e14c635303a", 45 | "styles": [ 46 | { 47 | "id": 8, 48 | "name": "style0", 49 | "type": "talk" 50 | } 51 | ], 52 | "supported_features": { 53 | "permitted_synthesis_morphing": "NOTHING" 54 | }, 55 | "version": "mock" 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/test_singer_info.py: -------------------------------------------------------------------------------- 1 | """/singer_info API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.utility import hash_long_string 7 | 8 | 9 | def test_get_singer_info_200( 10 | client: TestClient, snapshot_json: SnapshotAssertion 11 | ) -> None: 12 | response = client.get( 13 | "/singer_info", params={"speaker_uuid": "b1a81618-b27b-40d2-b0ea-27a9ad408c4b"} 14 | ) 15 | assert response.status_code == 200 16 | assert snapshot_json == hash_long_string(response.json()) 17 | 18 | 19 | def test_get_singer_info_with_url_200( 20 | client: TestClient, snapshot_json: SnapshotAssertion 21 | ) -> None: 22 | response = client.get( 23 | "/singer_info", 24 | params={ 25 | "speaker_uuid": "b1a81618-b27b-40d2-b0ea-27a9ad408c4b", 26 | "resource_format": "url", 27 | }, 28 | ) 29 | assert response.status_code == 200 30 | assert snapshot_json == hash_long_string(response.json()) 31 | 32 | 33 | def test_get_singer_info_404( 34 | client: TestClient, snapshot_json: SnapshotAssertion 35 | ) -> None: 36 | response = client.get( 37 | "/singer_info", params={"speaker_uuid": "111a111a-1a11-1aa1-1a1a-1a11a1aa11a1"} 38 | ) 39 | assert response.status_code == 404 40 | assert snapshot_json == hash_long_string(response.json()) 41 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/test_singers.py: -------------------------------------------------------------------------------- 1 | """/singers API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_get_singers_200(client: TestClient, snapshot_json: SnapshotAssertion) -> None: 8 | response = client.get("/singers", params={}) 9 | assert response.status_code == 200 10 | assert snapshot_json == response.json() 11 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/test_speaker_info.py: -------------------------------------------------------------------------------- 1 | """/speaker_info API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.utility import hash_long_string 7 | 8 | 9 | def test_get_speaker_info_200( 10 | client: TestClient, snapshot_json: SnapshotAssertion 11 | ) -> None: 12 | response = client.get( 13 | "/speaker_info", params={"speaker_uuid": "388f246b-8c41-4ac1-8e2d-5d79f3ff56d9"} 14 | ) 15 | assert response.status_code == 200 16 | assert snapshot_json == hash_long_string(response.json()) 17 | 18 | 19 | def test_get_speaker_info_with_url_200( 20 | client: TestClient, snapshot_json: SnapshotAssertion 21 | ) -> None: 22 | response = client.get( 23 | "/speaker_info", 24 | params={ 25 | "speaker_uuid": "388f246b-8c41-4ac1-8e2d-5d79f3ff56d9", 26 | "resource_format": "url", 27 | }, 28 | ) 29 | assert response.status_code == 200 30 | assert snapshot_json == hash_long_string(response.json()) 31 | 32 | 33 | def test_get_speaker_info_404( 34 | client: TestClient, snapshot_json: SnapshotAssertion 35 | ) -> None: 36 | response = client.get( 37 | "/speaker_info", params={"speaker_uuid": "111a111a-1a11-1aa1-1a1a-1a11a1aa11a1"} 38 | ) 39 | assert response.status_code == 404 40 | assert snapshot_json == hash_long_string(response.json()) 41 | -------------------------------------------------------------------------------- /test/e2e/single_api/speaker/test_speakers.py: -------------------------------------------------------------------------------- 1 | """/speakers API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_get_speakers_200(client: TestClient, snapshot_json: SnapshotAssertion) -> None: 8 | response = client.get("/speakers", params={}) 9 | assert response.status_code == 200 10 | assert snapshot_json == response.json() 11 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_accent_phrases/test_post_accent_phrases_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "accent": 1, 4 | "is_interrogative": false, 5 | "moras": [ 6 | { 7 | "consonant": "t", 8 | "consonant_length": 2.31, 9 | "pitch": 3.38, 10 | "text": "テ", 11 | "vowel": "e", 12 | "vowel_length": 0.88 13 | }, 14 | { 15 | "consonant": "s", 16 | "consonant_length": 2.19, 17 | "pitch": 0.0, 18 | "text": "ス", 19 | "vowel": "U", 20 | "vowel_length": 0.38 21 | }, 22 | { 23 | "consonant": "t", 24 | "consonant_length": 2.31, 25 | "pitch": 4.19, 26 | "text": "ト", 27 | "vowel": "o", 28 | "vowel_length": 1.88 29 | }, 30 | { 31 | "consonant": "d", 32 | "consonant_length": 0.75, 33 | "pitch": 1.62, 34 | "text": "デ", 35 | "vowel": "e", 36 | "vowel_length": 0.88 37 | }, 38 | { 39 | "consonant": "s", 40 | "consonant_length": 2.19, 41 | "pitch": 0.0, 42 | "text": "ス", 43 | "vowel": "U", 44 | "vowel_length": 0.38 45 | } 46 | ], 47 | "pause_mora": null 48 | } 49 | ] 50 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_accent_phrases/test_post_accent_phrases_enable_e2k_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "accent": 1, 4 | "is_interrogative": false, 5 | "moras": [ 6 | { 7 | "consonant": "v", 8 | "consonant_length": 2.56, 9 | "pitch": 4.62, 10 | "text": "ヴォ", 11 | "vowel": "o", 12 | "vowel_length": 1.88 13 | }, 14 | { 15 | "consonant": null, 16 | "consonant_length": null, 17 | "pitch": 1.25, 18 | "text": "イ", 19 | "vowel": "i", 20 | "vowel_length": 1.31 21 | }, 22 | { 23 | "consonant": "v", 24 | "consonant_length": 2.56, 25 | "pitch": 4.5, 26 | "text": "ヴォ", 27 | "vowel": "o", 28 | "vowel_length": 1.88 29 | } 30 | ], 31 | "pause_mora": null 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_audio_query/test_post_audio_query_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "accent_phrases": [ 3 | { 4 | "accent": 1, 5 | "is_interrogative": false, 6 | "moras": [ 7 | { 8 | "consonant": "t", 9 | "consonant_length": 2.31, 10 | "pitch": 3.38, 11 | "text": "テ", 12 | "vowel": "e", 13 | "vowel_length": 0.88 14 | }, 15 | { 16 | "consonant": "s", 17 | "consonant_length": 2.19, 18 | "pitch": 0.0, 19 | "text": "ス", 20 | "vowel": "U", 21 | "vowel_length": 0.38 22 | }, 23 | { 24 | "consonant": "t", 25 | "consonant_length": 2.31, 26 | "pitch": 4.19, 27 | "text": "ト", 28 | "vowel": "o", 29 | "vowel_length": 1.88 30 | }, 31 | { 32 | "consonant": "d", 33 | "consonant_length": 0.75, 34 | "pitch": 1.62, 35 | "text": "デ", 36 | "vowel": "e", 37 | "vowel_length": 0.88 38 | }, 39 | { 40 | "consonant": "s", 41 | "consonant_length": 2.19, 42 | "pitch": 0.0, 43 | "text": "ス", 44 | "vowel": "U", 45 | "vowel_length": 0.38 46 | } 47 | ], 48 | "pause_mora": null 49 | } 50 | ], 51 | "intonationScale": 1.0, 52 | "kana": "テ'_ストデ_ス", 53 | "outputSamplingRate": 24000, 54 | "outputStereo": false, 55 | "pauseLength": null, 56 | "pauseLengthScale": 1.0, 57 | "pitchScale": 0.0, 58 | "postPhonemeLength": 0.1, 59 | "prePhonemeLength": 0.1, 60 | "speedScale": 1.0, 61 | "volumeScale": 1.0 62 | } 63 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_audio_query/test_post_audio_query_enable_e2k_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "accent_phrases": [ 3 | { 4 | "accent": 1, 5 | "is_interrogative": false, 6 | "moras": [ 7 | { 8 | "consonant": "v", 9 | "consonant_length": 2.56, 10 | "pitch": 4.62, 11 | "text": "ヴォ", 12 | "vowel": "o", 13 | "vowel_length": 1.88 14 | }, 15 | { 16 | "consonant": null, 17 | "consonant_length": null, 18 | "pitch": 1.25, 19 | "text": "イ", 20 | "vowel": "i", 21 | "vowel_length": 1.31 22 | }, 23 | { 24 | "consonant": "v", 25 | "consonant_length": 2.56, 26 | "pitch": 4.5, 27 | "text": "ヴォ", 28 | "vowel": "o", 29 | "vowel_length": 1.88 30 | } 31 | ], 32 | "pause_mora": null 33 | } 34 | ], 35 | "intonationScale": 1.0, 36 | "kana": "ヴォ'イヴォ", 37 | "outputSamplingRate": 24000, 38 | "outputStereo": false, 39 | "pauseLength": null, 40 | "pauseLengthScale": 1.0, 41 | "pitchScale": 0.0, 42 | "postPhonemeLength": 0.1, 43 | "prePhonemeLength": 0.1, 44 | "speedScale": 1.0, 45 | "volumeScale": 1.0 46 | } 47 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_audio_query_from_preset/test_post_audio_query_from_preset_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "accent_phrases": [ 3 | { 4 | "accent": 1, 5 | "is_interrogative": false, 6 | "moras": [ 7 | { 8 | "consonant": "t", 9 | "consonant_length": 10001.31, 10 | "pitch": 10002.38, 11 | "text": "テ", 12 | "vowel": "e", 13 | "vowel_length": 9999.88 14 | }, 15 | { 16 | "consonant": "s", 17 | "consonant_length": 10001.19, 18 | "pitch": 0.0, 19 | "text": "ス", 20 | "vowel": "U", 21 | "vowel_length": 9999.38 22 | }, 23 | { 24 | "consonant": "t", 25 | "consonant_length": 10001.31, 26 | "pitch": 10003.19, 27 | "text": "ト", 28 | "vowel": "o", 29 | "vowel_length": 10000.88 30 | }, 31 | { 32 | "consonant": "d", 33 | "consonant_length": 9999.75, 34 | "pitch": 10000.62, 35 | "text": "デ", 36 | "vowel": "e", 37 | "vowel_length": 9999.88 38 | }, 39 | { 40 | "consonant": "s", 41 | "consonant_length": 10001.19, 42 | "pitch": 0.0, 43 | "text": "ス", 44 | "vowel": "U", 45 | "vowel_length": 9999.38 46 | } 47 | ], 48 | "pause_mora": null 49 | } 50 | ], 51 | "intonationScale": 1.2, 52 | "kana": "テ'_ストデ_ス", 53 | "outputSamplingRate": 24000, 54 | "outputStereo": false, 55 | "pauseLength": 15.0, 56 | "pauseLengthScale": 1.4, 57 | "pitchScale": 0.9, 58 | "postPhonemeLength": 5.0, 59 | "prePhonemeLength": 20.0, 60 | "speedScale": 1.1, 61 | "volumeScale": 1.3 62 | } 63 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_audio_query_from_preset/test_post_audio_query_from_preset_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "該当するプリセットIDが見つかりません" 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_cancellable_synthesis.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_post_cancellable_synthesis_200 3 | 'MD5:f7d42ce5787856549abc3d2d7561c06f' 4 | # --- 5 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_connect_waves.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_post_connect_waves_200 3 | 'MD5:09eeac5fac75ce47f5002c48fef3044d' 4 | # --- 5 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_connect_waves/test_post_connect_waves_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "wavファイルが含まれていません" 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_frame_synthesis.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_post_frame_synthesis_200 3 | 'MD5:1c385210acba238994604a8cee96aee3' 4 | # --- 5 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_initialize_speaker.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_post_initialize_speaker_204 3 | b'' 4 | # --- 5 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_is_initialized_speaker/test_get_is_initialized_speaker_200.json: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_mora_data/test_post_mora_data_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "accent": 1, 4 | "is_interrogative": false, 5 | "moras": [ 6 | { 7 | "consonant": "t", 8 | "consonant_length": 2.31, 9 | "pitch": 3.38, 10 | "text": "テ", 11 | "vowel": "e", 12 | "vowel_length": 0.88 13 | }, 14 | { 15 | "consonant": "s", 16 | "consonant_length": 2.19, 17 | "pitch": 0.0, 18 | "text": "ス", 19 | "vowel": "U", 20 | "vowel_length": 0.38 21 | }, 22 | { 23 | "consonant": "t", 24 | "consonant_length": 2.31, 25 | "pitch": 4.25, 26 | "text": "ト", 27 | "vowel": "o", 28 | "vowel_length": 1.88 29 | } 30 | ], 31 | "pause_mora": null 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_mora_length/test_post_mora_length_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "accent": 1, 4 | "is_interrogative": false, 5 | "moras": [ 6 | { 7 | "consonant": "t", 8 | "consonant_length": 2.31, 9 | "pitch": 3.3, 10 | "text": "テ", 11 | "vowel": "e", 12 | "vowel_length": 0.88 13 | }, 14 | { 15 | "consonant": "s", 16 | "consonant_length": 2.19, 17 | "pitch": 0.0, 18 | "text": "ス", 19 | "vowel": "U", 20 | "vowel_length": 0.38 21 | }, 22 | { 23 | "consonant": "t", 24 | "consonant_length": 2.31, 25 | "pitch": 4.1, 26 | "text": "ト", 27 | "vowel": "o", 28 | "vowel_length": 1.88 29 | } 30 | ], 31 | "pause_mora": null 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_mora_pitch/test_post_mora_pitch_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "accent": 1, 4 | "is_interrogative": false, 5 | "moras": [ 6 | { 7 | "consonant": "t", 8 | "consonant_length": 2.3, 9 | "pitch": 3.38, 10 | "text": "テ", 11 | "vowel": "e", 12 | "vowel_length": 0.8 13 | }, 14 | { 15 | "consonant": "s", 16 | "consonant_length": 2.1, 17 | "pitch": 0.0, 18 | "text": "ス", 19 | "vowel": "U", 20 | "vowel_length": 0.3 21 | }, 22 | { 23 | "consonant": "t", 24 | "consonant_length": 2.3, 25 | "pitch": 4.25, 26 | "text": "ト", 27 | "vowel": "o", 28 | "vowel_length": 1.8 29 | } 30 | ], 31 | "pause_mora": null 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_multi_synthesis.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_post_multi_synthesis_200 3 | 'MD5:f7d42ce5787856549abc3d2d7561c06f' 4 | # --- 5 | # name: test_post_multi_synthesis_200.1 6 | 'MD5:f7931b06272316e78e375025dbfd03e3' 7 | # --- 8 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_sing_frame_audio_query/test_post_sing_frame_audio_query_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "f0": [ 3 | 0.0, 4 | 0.0, 5 | 46.64, 6 | 46.64, 7 | 46.64, 8 | 46.64, 9 | 46.64, 10 | 46.64, 11 | 46.64, 12 | 46.64, 13 | 46.4, 14 | 46.4, 15 | 46.62, 16 | 46.67, 17 | 83.1, 18 | 83.1, 19 | 82.97, 20 | 0.0, 21 | 0.0, 22 | 0.0, 23 | 0.0, 24 | 0.0, 25 | 0.0, 26 | 0.0, 27 | 0.0, 28 | 0.0, 29 | 0.0 30 | ], 31 | "outputSamplingRate": 24000, 32 | "outputStereo": false, 33 | "phonemes": [ 34 | { 35 | "frame_length": 2, 36 | "note_id": "a", 37 | "phoneme": "pau" 38 | }, 39 | { 40 | "frame_length": 8, 41 | "note_id": "b", 42 | "phoneme": "t" 43 | }, 44 | { 45 | "frame_length": 2, 46 | "note_id": "b", 47 | "phoneme": "e" 48 | }, 49 | { 50 | "frame_length": 1, 51 | "note_id": "c", 52 | "phoneme": "s" 53 | }, 54 | { 55 | "frame_length": 1, 56 | "note_id": "c", 57 | "phoneme": "u" 58 | }, 59 | { 60 | "frame_length": 2, 61 | "note_id": "d", 62 | "phoneme": "t" 63 | }, 64 | { 65 | "frame_length": 1, 66 | "note_id": "d", 67 | "phoneme": "o" 68 | }, 69 | { 70 | "frame_length": 10, 71 | "note_id": "e", 72 | "phoneme": "pau" 73 | } 74 | ], 75 | "volume": [ 76 | 0.0, 77 | 0.0, 78 | 0.33, 79 | 0.33, 80 | 0.33, 81 | 0.33, 82 | 0.33, 83 | 0.33, 84 | 0.33, 85 | 0.33, 86 | 0.13, 87 | 0.13, 88 | 0.32, 89 | 0.36, 90 | 0.79, 91 | 0.79, 92 | 0.64, 93 | 0.0, 94 | 0.0, 95 | 0.0, 96 | 0.0, 97 | 0.0, 98 | 0.0, 99 | 0.0, 100 | 0.0, 101 | 0.0, 102 | 0.0 103 | ], 104 | "volumeScale": 1.0 105 | } 106 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_sing_frame_audio_query/test_post_sing_old_frame_audio_query_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "f0": [ 3 | 0.0, 4 | 0.0, 5 | 46.64, 6 | 46.64, 7 | 46.64, 8 | 46.64, 9 | 46.64, 10 | 46.64, 11 | 46.64, 12 | 46.64, 13 | 46.4, 14 | 46.4, 15 | 46.62, 16 | 46.67, 17 | 83.1, 18 | 83.1, 19 | 82.97, 20 | 0.0, 21 | 0.0, 22 | 0.0, 23 | 0.0, 24 | 0.0, 25 | 0.0, 26 | 0.0, 27 | 0.0, 28 | 0.0, 29 | 0.0 30 | ], 31 | "outputSamplingRate": 24000, 32 | "outputStereo": false, 33 | "phonemes": [ 34 | { 35 | "frame_length": 2, 36 | "note_id": null, 37 | "phoneme": "pau" 38 | }, 39 | { 40 | "frame_length": 8, 41 | "note_id": null, 42 | "phoneme": "t" 43 | }, 44 | { 45 | "frame_length": 2, 46 | "note_id": null, 47 | "phoneme": "e" 48 | }, 49 | { 50 | "frame_length": 1, 51 | "note_id": null, 52 | "phoneme": "s" 53 | }, 54 | { 55 | "frame_length": 1, 56 | "note_id": null, 57 | "phoneme": "u" 58 | }, 59 | { 60 | "frame_length": 2, 61 | "note_id": null, 62 | "phoneme": "t" 63 | }, 64 | { 65 | "frame_length": 1, 66 | "note_id": null, 67 | "phoneme": "o" 68 | }, 69 | { 70 | "frame_length": 10, 71 | "note_id": null, 72 | "phoneme": "pau" 73 | } 74 | ], 75 | "volume": [ 76 | 0.0, 77 | 0.0, 78 | 0.33, 79 | 0.33, 80 | 0.33, 81 | 0.33, 82 | 0.33, 83 | 0.33, 84 | 0.33, 85 | 0.33, 86 | 0.13, 87 | 0.13, 88 | 0.32, 89 | 0.36, 90 | 0.79, 91 | 0.79, 92 | 0.64, 93 | 0.0, 94 | 0.0, 95 | 0.0, 96 | 0.0, 97 | 0.0, 98 | 0.0, 99 | 0.0, 100 | 0.0, 101 | 0.0, 102 | 0.0 103 | ], 104 | "volumeScale": 1.0 105 | } 106 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_sing_frame_f0/test_post_sing_frame_f0_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | 0.0, 3 | 0.0, 4 | 0.0, 5 | 0.0, 6 | 0.0, 7 | 0.0, 8 | 0.0, 9 | 0.0, 10 | 0.0, 11 | 0.0, 12 | 0.0, 13 | 0.0, 14 | 0.0, 15 | 262.34, 16 | 262.34, 17 | 263.41, 18 | 263.41, 19 | 263.41, 20 | 263.41, 21 | 263.41, 22 | 263.41, 23 | 263.41, 24 | 263.41, 25 | 263.41, 26 | 263.41, 27 | 263.41, 28 | 263.41, 29 | 263.41, 30 | 263.41, 31 | 263.41, 32 | 263.41, 33 | 263.41, 34 | 263.41, 35 | 263.41, 36 | 263.41, 37 | 263.41, 38 | 263.41, 39 | 263.41, 40 | 263.41, 41 | 263.41, 42 | 263.41, 43 | 263.41, 44 | 263.41, 45 | 263.41, 46 | 263.41, 47 | 263.41, 48 | 263.41, 49 | 263.41, 50 | 263.41, 51 | 263.41, 52 | 263.41, 53 | 263.41, 54 | 263.41, 55 | 263.41, 56 | 263.41, 57 | 263.41, 58 | 295.87, 59 | 295.87, 60 | 295.87, 61 | 295.87, 62 | 294.6, 63 | 294.6, 64 | 294.6, 65 | 294.6, 66 | 294.6, 67 | 294.6, 68 | 294.6, 69 | 294.6, 70 | 294.6, 71 | 294.6, 72 | 294.6, 73 | 294.6, 74 | 294.6, 75 | 294.6, 76 | 294.6, 77 | 294.6, 78 | 294.6, 79 | 294.6, 80 | 294.6, 81 | 294.6, 82 | 294.6, 83 | 294.6, 84 | 294.6, 85 | 294.6, 86 | 294.6, 87 | 294.6, 88 | 294.6, 89 | 294.6, 90 | 294.6, 91 | 294.6, 92 | 294.6, 93 | 294.6, 94 | 294.6, 95 | 294.6, 96 | 294.6, 97 | 294.6, 98 | 294.6, 99 | 294.6, 100 | 294.6, 101 | 294.6, 102 | 331.58, 103 | 331.58, 104 | 331.58, 105 | 331.58, 106 | 331.58, 107 | 331.2, 108 | 331.2, 109 | 331.2, 110 | 331.2, 111 | 331.2, 112 | 331.2, 113 | 331.2, 114 | 331.2, 115 | 331.2, 116 | 331.2, 117 | 331.2, 118 | 331.2, 119 | 331.2, 120 | 331.2, 121 | 331.2, 122 | 331.2, 123 | 331.2, 124 | 331.2, 125 | 331.2, 126 | 331.2, 127 | 331.2, 128 | 331.2, 129 | 331.2, 130 | 331.2, 131 | 331.2, 132 | 331.2, 133 | 331.2, 134 | 331.2, 135 | 331.2, 136 | 331.2, 137 | 331.2, 138 | 331.2, 139 | 331.2, 140 | 331.2, 141 | 331.2, 142 | 331.2, 143 | 331.2, 144 | 331.2, 145 | 331.2, 146 | 331.2, 147 | 331.2, 148 | 331.2, 149 | 331.2, 150 | 331.2, 151 | 331.2, 152 | 0.0, 153 | 0.0, 154 | 0.0, 155 | 0.0, 156 | 0.0, 157 | 0.0, 158 | 0.0, 159 | 0.0, 160 | 0.0, 161 | 0.0, 162 | 0.0, 163 | 0.0, 164 | 0.0, 165 | 0.0, 166 | 0.0 167 | ] 168 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_sing_frame_volume/test_post_sing_frame_volume_200.json: -------------------------------------------------------------------------------- 1 | [ 2 | 0.0, 3 | 0.0, 4 | 0.0, 5 | 0.0, 6 | 0.0, 7 | 0.0, 8 | 0.0, 9 | 0.0, 10 | 0.0, 11 | 0.0, 12 | 0.0, 13 | 0.0, 14 | 0.0, 15 | 0.85, 16 | 0.95, 17 | 2.63, 18 | 2.73, 19 | 2.77, 20 | 2.73, 21 | 2.7, 22 | 2.67, 23 | 2.67, 24 | 2.68, 25 | 2.69, 26 | 2.72, 27 | 2.76, 28 | 2.8, 29 | 2.84, 30 | 2.86, 31 | 2.89, 32 | 2.92, 33 | 2.97, 34 | 3.01, 35 | 3.05, 36 | 3.06, 37 | 3.07, 38 | 3.07, 39 | 3.07, 40 | 3.07, 41 | 3.07, 42 | 3.06, 43 | 3.04, 44 | 3.03, 45 | 3.02, 46 | 3.02, 47 | 3.03, 48 | 3.04, 49 | 3.05, 50 | 3.05, 51 | 3.05, 52 | 3.05, 53 | 3.05, 54 | 3.05, 55 | 3.06, 56 | 3.07, 57 | 3.08, 58 | 3.51, 59 | 3.51, 60 | 3.58, 61 | 3.67, 62 | 1.6, 63 | 1.64, 64 | 1.67, 65 | 1.68, 66 | 1.68, 67 | 1.68, 68 | 1.68, 69 | 1.67, 70 | 1.65, 71 | 1.64, 72 | 1.62, 73 | 1.61, 74 | 1.61, 75 | 1.61, 76 | 1.61, 77 | 1.62, 78 | 1.63, 79 | 1.64, 80 | 1.65, 81 | 1.66, 82 | 1.66, 83 | 1.66, 84 | 1.67, 85 | 1.67, 86 | 1.68, 87 | 1.68, 88 | 1.67, 89 | 1.67, 90 | 1.66, 91 | 1.66, 92 | 1.66, 93 | 1.66, 94 | 1.67, 95 | 1.67, 96 | 1.67, 97 | 1.67, 98 | 1.67, 99 | 1.67, 100 | 1.66, 101 | 1.63, 102 | 3.03, 103 | 3.04, 104 | 3.06, 105 | 3.07, 106 | 3.11, 107 | 2.63, 108 | 2.71, 109 | 2.73, 110 | 2.76, 111 | 2.79, 112 | 2.83, 113 | 2.85, 114 | 2.85, 115 | 2.83, 116 | 2.8, 117 | 2.77, 118 | 2.77, 119 | 2.78, 120 | 2.79, 121 | 2.81, 122 | 2.82, 123 | 2.84, 124 | 2.87, 125 | 2.9, 126 | 2.94, 127 | 2.96, 128 | 2.97, 129 | 2.96, 130 | 2.94, 131 | 2.91, 132 | 2.87, 133 | 2.83, 134 | 2.79, 135 | 2.78, 136 | 2.78, 137 | 2.79, 138 | 2.82, 139 | 2.84, 140 | 2.87, 141 | 2.9, 142 | 2.92, 143 | 2.93, 144 | 2.94, 145 | 2.94, 146 | 2.92, 147 | 2.87, 148 | 2.77, 149 | 2.7, 150 | 2.75, 151 | 2.78, 152 | 0.0, 153 | 0.0, 154 | 0.0, 155 | 0.0, 156 | 0.0, 157 | 0.0, 158 | 0.0, 159 | 0.0, 160 | 0.0, 161 | 0.0, 162 | 0.0, 163 | 0.0, 164 | 0.0, 165 | 0.0, 166 | 0.0 167 | ] 168 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_synthesis.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_post_synthesis_200 3 | 'MD5:f7d42ce5787856549abc3d2d7561c06f' 4 | # --- 5 | # name: test_post_synthesis_old_audio_query_200 6 | 'MD5:f7d42ce5787856549abc3d2d7561c06f' 7 | # --- 8 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_validate_kana/test_post_validate_kana_200.json: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_validate_kana/test_post_validate_kana_400.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": { 3 | "error_args": { 4 | "text": "こんにちは" 5 | }, 6 | "error_name": "UNKNOWN_TEXT", 7 | "text": "判別できない読み仮名があります: こんにちは" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/__snapshots__/test_validate_kana/test_post_validate_kana_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": [ 3 | { 4 | "input": null, 5 | "loc": [ 6 | "query", 7 | "text" 8 | ], 9 | "msg": "Field required", 10 | "type": "missing" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_accent_phrases.py: -------------------------------------------------------------------------------- 1 | """/accent_phrases API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.utility import round_floats 7 | 8 | 9 | def test_post_accent_phrases_200( 10 | client: TestClient, snapshot_json: SnapshotAssertion 11 | ) -> None: 12 | response = client.post( 13 | "/accent_phrases", params={"text": "テストです", "speaker": 0} 14 | ) 15 | assert response.status_code == 200 16 | assert snapshot_json == round_floats(response.json(), 2) 17 | 18 | 19 | def test_post_accent_phrases_enable_e2k_200( 20 | client: TestClient, snapshot_json: SnapshotAssertion 21 | ) -> None: 22 | response = client.post( 23 | "/accent_phrases", params={"text": "Voivo", "speaker": 0, "enable_e2k": True} 24 | ) 25 | assert response.status_code == 200 26 | assert snapshot_json == round_floats(response.json(), 2) 27 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_audio/sample1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/test/e2e/single_api/tts_pipeline/test_audio/sample1.wav -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_audio/sample2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/test/e2e/single_api/tts_pipeline/test_audio/sample2.wav -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_audio_query.py: -------------------------------------------------------------------------------- 1 | """/audio_query API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.utility import round_floats 7 | 8 | 9 | def test_post_audio_query_200( 10 | client: TestClient, snapshot_json: SnapshotAssertion 11 | ) -> None: 12 | response = client.post("/audio_query", params={"text": "テストです", "speaker": 0}) 13 | assert response.status_code == 200 14 | assert snapshot_json == round_floats(response.json(), round_value=2) 15 | 16 | 17 | def test_post_audio_query_enable_e2k_200( 18 | client: TestClient, snapshot_json: SnapshotAssertion 19 | ) -> None: 20 | response = client.post( 21 | "/audio_query", params={"text": "Voivo", "speaker": 0, "enable_e2k": True} 22 | ) 23 | assert response.status_code == 200 24 | assert snapshot_json == round_floats(response.json(), round_value=2) 25 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_audio_query_from_preset.py: -------------------------------------------------------------------------------- 1 | """/audio_query_from_preset API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.utility import round_floats 7 | 8 | 9 | def test_post_audio_query_from_preset_200( 10 | client: TestClient, snapshot_json: SnapshotAssertion 11 | ) -> None: 12 | # Setup 13 | # NOTE: 事前準備用のプリセット API が壊れた場合、このテストが偽陽性で failed になる可能性がある 14 | preset = { 15 | "id": 8888, 16 | "name": "test_preset", 17 | "speaker_uuid": "123-456-789-234", 18 | "style_id": 9999, 19 | "speedScale": 1.1, 20 | "pitchScale": 0.9, 21 | "intonationScale": 1.2, 22 | "volumeScale": 1.3, 23 | "prePhonemeLength": 20, 24 | "postPhonemeLength": 5, 25 | "pauseLength": 15, 26 | "pauseLengthScale": 1.4, 27 | } 28 | client.post("/add_preset", params={}, json=preset) 29 | 30 | # Test 31 | response = client.post( 32 | "/audio_query_from_preset", params={"text": "テストです", "preset_id": 8888} 33 | ) 34 | assert response.status_code == 200 35 | assert snapshot_json == round_floats(response.json(), 2) 36 | 37 | 38 | def test_post_audio_query_from_preset_422( 39 | client: TestClient, snapshot_json: SnapshotAssertion 40 | ) -> None: 41 | response = client.post( 42 | "/audio_query_from_preset", params={"text": "テストです", "preset_id": 404} 43 | ) 44 | assert response.status_code == 422 45 | assert snapshot_json == response.json() 46 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_cancellable_synthesis.py: -------------------------------------------------------------------------------- 1 | """/cancellable_synthesis API のテスト。""" 2 | 3 | from typing import Any 4 | 5 | import pytest 6 | from fastapi.testclient import TestClient 7 | from syrupy.assertion import SnapshotAssertion 8 | 9 | from test.e2e.single_api.utils import gen_mora 10 | from test.utility import hash_wave_floats_from_wav_bytes 11 | from voicevox_engine.app.application import generate_app 12 | from voicevox_engine.cancellable_engine import CancellableEngine 13 | 14 | 15 | @pytest.fixture() 16 | def cancellable_client(app_params: dict[str, Any]) -> TestClient: 17 | app_params["cancellable_engine"] = CancellableEngine( 18 | init_processes=1, 19 | use_gpu=False, 20 | enable_mock=True, 21 | ) 22 | cancellable_app = generate_app(**app_params) 23 | return TestClient(cancellable_app) 24 | 25 | 26 | def test_post_cancellable_synthesis_200( 27 | cancellable_client: TestClient, snapshot: SnapshotAssertion 28 | ) -> None: 29 | query = { 30 | "accent_phrases": [ 31 | { 32 | "moras": [ 33 | gen_mora("テ", "t", 2.3, "e", 0.8, 3.3), 34 | gen_mora("ス", "s", 2.1, "U", 0.3, 0.0), 35 | gen_mora("ト", "t", 2.3, "o", 1.8, 4.1), 36 | ], 37 | "accent": 1, 38 | "pause_mora": None, 39 | "is_interrogative": False, 40 | } 41 | ], 42 | "speedScale": 1.0, 43 | "pitchScale": 1.0, 44 | "intonationScale": 1.0, 45 | "volumeScale": 1.0, 46 | "prePhonemeLength": 0.1, 47 | "postPhonemeLength": 0.1, 48 | "pauseLength": None, 49 | "pauseLengthScale": 1.0, 50 | "outputSamplingRate": 24000, 51 | "outputStereo": False, 52 | "kana": "テ'_スト", 53 | } 54 | response = cancellable_client.post( 55 | "/cancellable_synthesis", params={"speaker": 0}, json=query 56 | ) 57 | assert response.status_code == 200 58 | 59 | # 音声波形が一致する 60 | assert response.headers["content-type"] == "audio/wav" 61 | assert snapshot == hash_wave_floats_from_wav_bytes(response.read()) 62 | 63 | 64 | # TODO: キャンセルするテストを追加する 65 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_connect_waves.py: -------------------------------------------------------------------------------- 1 | """/connect_waves API のテスト。""" 2 | 3 | import base64 4 | from pathlib import Path 5 | 6 | from fastapi.testclient import TestClient 7 | from syrupy.assertion import SnapshotAssertion 8 | 9 | from test.utility import hash_wave_floats_from_wav_bytes 10 | 11 | 12 | def test_post_connect_waves_200( 13 | client: TestClient, snapshot: SnapshotAssertion 14 | ) -> None: 15 | test_audio_dir = Path(__file__).parent / "test_audio" 16 | wavs = [ 17 | base64.b64encode((test_audio_dir / "sample1.wav").read_bytes()).decode(), 18 | base64.b64encode((test_audio_dir / "sample2.wav").read_bytes()).decode(), 19 | ] 20 | 21 | response = client.post("/connect_waves", json=wavs) 22 | assert response.status_code == 200 23 | 24 | # 音声波形が一致する 25 | assert response.headers["content-type"] == "audio/wav" 26 | assert snapshot == hash_wave_floats_from_wav_bytes(response.read()) 27 | 28 | 29 | def test_post_connect_waves_422( 30 | client: TestClient, snapshot_json: SnapshotAssertion 31 | ) -> None: 32 | wavs: list[None] = [] 33 | 34 | response = client.post("/connect_waves", json=wavs) 35 | 36 | assert response.status_code == 422 37 | assert snapshot_json == response.json() 38 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_initialize_speaker.py: -------------------------------------------------------------------------------- 1 | """/initialize_speaker API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_post_initialize_speaker_204( 8 | client: TestClient, snapshot: SnapshotAssertion 9 | ) -> None: 10 | response = client.post("/initialize_speaker", params={"speaker": 0}) 11 | assert response.status_code == 204 12 | assert snapshot == response.content 13 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_is_initialized_speaker.py: -------------------------------------------------------------------------------- 1 | """/is_initialized_speaker API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_get_is_initialized_speaker_200( 8 | client: TestClient, snapshot_json: SnapshotAssertion 9 | ) -> None: 10 | response = client.get("/is_initialized_speaker", params={"speaker": 0}) 11 | assert response.status_code == 200 12 | assert snapshot_json == response.json() 13 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_mora_data.py: -------------------------------------------------------------------------------- 1 | """/mora_data API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.e2e.single_api.utils import gen_mora 7 | from test.utility import round_floats 8 | 9 | 10 | def test_post_mora_data_200( 11 | client: TestClient, snapshot_json: SnapshotAssertion 12 | ) -> None: 13 | accent_phrases = [ 14 | { 15 | "moras": [ 16 | gen_mora("テ", "t", 2.3, "e", 0.8, 3.3), 17 | gen_mora("ス", "s", 2.1, "U", 0.3, 0.0), 18 | gen_mora("ト", "t", 2.3, "o", 1.8, 4.1), 19 | ], 20 | "accent": 1, 21 | "pause_mora": None, 22 | "is_interrogative": False, 23 | } 24 | ] 25 | response = client.post("/mora_data", params={"speaker": 0}, json=accent_phrases) 26 | assert response.status_code == 200 27 | assert snapshot_json == round_floats(response.json(), 2) 28 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_mora_length.py: -------------------------------------------------------------------------------- 1 | """/mora_length API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.e2e.single_api.utils import gen_mora 7 | from test.utility import round_floats 8 | 9 | 10 | def test_post_mora_length_200( 11 | client: TestClient, snapshot_json: SnapshotAssertion 12 | ) -> None: 13 | accent_phrases = [ 14 | { 15 | "moras": [ 16 | gen_mora("テ", "t", 2.3, "e", 0.8, 3.3), 17 | gen_mora("ス", "s", 2.1, "U", 0.3, 0.0), 18 | gen_mora("ト", "t", 2.3, "o", 1.8, 4.1), 19 | ], 20 | "accent": 1, 21 | "pause_mora": None, 22 | "is_interrogative": False, 23 | } 24 | ] 25 | response = client.post("/mora_length", params={"speaker": 0}, json=accent_phrases) 26 | assert response.status_code == 200 27 | assert snapshot_json == round_floats(response.json(), 2) 28 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_mora_pitch.py: -------------------------------------------------------------------------------- 1 | """/mora_pitch API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.e2e.single_api.utils import gen_mora 7 | from test.utility import round_floats 8 | 9 | 10 | def test_post_mora_pitch_200( 11 | client: TestClient, snapshot_json: SnapshotAssertion 12 | ) -> None: 13 | accent_phrases = [ 14 | { 15 | "moras": [ 16 | gen_mora("テ", "t", 2.3, "e", 0.8, 3.3), 17 | gen_mora("ス", "s", 2.1, "U", 0.3, 0.0), 18 | gen_mora("ト", "t", 2.3, "o", 1.8, 4.1), 19 | ], 20 | "accent": 1, 21 | "pause_mora": None, 22 | "is_interrogative": False, 23 | } 24 | ] 25 | response = client.post("/mora_pitch", params={"speaker": 0}, json=accent_phrases) 26 | assert response.status_code == 200 27 | assert snapshot_json == round_floats(response.json(), 2) 28 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_sing_frame_audio_query.py: -------------------------------------------------------------------------------- 1 | """/sing_frame_audio_query API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.utility import round_floats 7 | 8 | 9 | def test_post_sing_frame_audio_query_200( 10 | client: TestClient, snapshot_json: SnapshotAssertion 11 | ) -> None: 12 | score = { 13 | "notes": [ 14 | {"id": "a", "key": None, "frame_length": 10, "lyric": ""}, 15 | {"id": "b", "key": 30, "frame_length": 3, "lyric": "て"}, 16 | {"id": "c", "key": 30, "frame_length": 3, "lyric": "す"}, 17 | {"id": "d", "key": 40, "frame_length": 1, "lyric": "と"}, 18 | {"id": "e", "key": None, "frame_length": 10, "lyric": ""}, 19 | ] 20 | } 21 | response = client.post("/sing_frame_audio_query", params={"speaker": 0}, json=score) 22 | assert response.status_code == 200 23 | assert snapshot_json == round_floats(response.json(), 2) 24 | 25 | 26 | def test_post_sing_old_frame_audio_query_200( 27 | client: TestClient, snapshot_json: SnapshotAssertion 28 | ) -> None: 29 | """古いバージョンの楽譜でもエラーなく合成できる""" 30 | score = { 31 | "notes": [ 32 | {"key": None, "frame_length": 10, "lyric": ""}, 33 | {"key": 30, "frame_length": 3, "lyric": "て"}, 34 | {"key": 30, "frame_length": 3, "lyric": "す"}, 35 | {"key": 40, "frame_length": 1, "lyric": "と"}, 36 | {"key": None, "frame_length": 10, "lyric": ""}, 37 | ] 38 | } 39 | response = client.post("/sing_frame_audio_query", params={"speaker": 0}, json=score) 40 | assert response.status_code == 200 41 | assert snapshot_json == round_floats(response.json(), 2) 42 | -------------------------------------------------------------------------------- /test/e2e/single_api/tts_pipeline/test_validate_kana.py: -------------------------------------------------------------------------------- 1 | """/validate_kana API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_post_validate_kana_200( 8 | client: TestClient, snapshot_json: SnapshotAssertion 9 | ) -> None: 10 | response = client.post("/validate_kana", params={"text": "コンニチワ'"}) 11 | assert response.status_code == 200 12 | assert snapshot_json == response.json() 13 | 14 | 15 | def test_post_validate_kana_400( 16 | client: TestClient, snapshot_json: SnapshotAssertion 17 | ) -> None: 18 | # text が AquesTalk 風記法に従わない場合はエラー 19 | response = client.post("/validate_kana", params={"text": "こんにちは"}) 20 | assert response.status_code == 400 21 | assert snapshot_json == response.json() 22 | 23 | 24 | def test_post_validate_kana_422( 25 | client: TestClient, snapshot_json: SnapshotAssertion 26 | ) -> None: 27 | # query パラメータに text が無い場合はエラー 28 | response = client.post("/validate_kana") 29 | assert response.status_code == 422 30 | assert snapshot_json == response.json() 31 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/__snapshots__/test_import_user_dict.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_post_import_user_dict_204 3 | b'' 4 | # --- 5 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/__snapshots__/test_import_user_dict/test_post_import_user_dict_contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "a11196ad-caa8-4f4e-8eb3-3d2261c798fd": { 3 | "accent_associative_rule": "*", 4 | "accent_type": 1, 5 | "context_id": 1348, 6 | "inflectional_form": "*", 7 | "inflectional_type": "*", 8 | "mora_count": 3, 9 | "part_of_speech": "名詞", 10 | "part_of_speech_detail_1": "固有名詞", 11 | "part_of_speech_detail_2": "一般", 12 | "part_of_speech_detail_3": "*", 13 | "priority": 4, 14 | "pronunciation": "テストサン", 15 | "stem": "*", 16 | "surface": "test3", 17 | "yomi": "テストサン" 18 | }, 19 | "a89596ad-caa8-4f4e-8eb3-3d2261c798fd": { 20 | "accent_associative_rule": "*", 21 | "accent_type": 1, 22 | "context_id": 1348, 23 | "inflectional_form": "*", 24 | "inflectional_type": "*", 25 | "mora_count": 3, 26 | "part_of_speech": "名詞", 27 | "part_of_speech_detail_1": "固有名詞", 28 | "part_of_speech_detail_2": "一般", 29 | "part_of_speech_detail_3": "*", 30 | "priority": 5, 31 | "pronunciation": "テストイチ", 32 | "stem": "*", 33 | "surface": "test1", 34 | "yomi": "テストイチ" 35 | }, 36 | "c89596ad-caa8-4f4e-8eb3-3d2261c798fd": { 37 | "accent_associative_rule": "*", 38 | "accent_type": 1, 39 | "context_id": 1348, 40 | "inflectional_form": "*", 41 | "inflectional_type": "*", 42 | "mora_count": 2, 43 | "part_of_speech": "名詞", 44 | "part_of_speech_detail_1": "固有名詞", 45 | "part_of_speech_detail_2": "一般", 46 | "part_of_speech_detail_3": "*", 47 | "priority": 5, 48 | "pronunciation": "テストニ", 49 | "stem": "*", 50 | "surface": "test2", 51 | "yomi": "テストニ" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/__snapshots__/test_user_dict_api/test_get_user_dict_200.json: -------------------------------------------------------------------------------- 1 | { 2 | "a89596ad-caa8-4f4e-8eb3-3d2261c798fd": { 3 | "accent_associative_rule": "*", 4 | "accent_type": 1, 5 | "context_id": 1348, 6 | "inflectional_form": "*", 7 | "inflectional_type": "*", 8 | "mora_count": 3, 9 | "part_of_speech": "名詞", 10 | "part_of_speech_detail_1": "固有名詞", 11 | "part_of_speech_detail_2": "一般", 12 | "part_of_speech_detail_3": "*", 13 | "priority": 5, 14 | "pronunciation": "テストイチ", 15 | "stem": "*", 16 | "surface": "test1", 17 | "yomi": "テストイチ" 18 | }, 19 | "c89596ad-caa8-4f4e-8eb3-3d2261c798fd": { 20 | "accent_associative_rule": "*", 21 | "accent_type": 1, 22 | "context_id": 1348, 23 | "inflectional_form": "*", 24 | "inflectional_type": "*", 25 | "mora_count": 2, 26 | "part_of_speech": "名詞", 27 | "part_of_speech_detail_1": "固有名詞", 28 | "part_of_speech_detail_2": "一般", 29 | "part_of_speech_detail_3": "*", 30 | "priority": 5, 31 | "pronunciation": "テストニ", 32 | "stem": "*", 33 | "surface": "test2", 34 | "yomi": "テストニ" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/__snapshots__/test_user_dict_word.ambr: -------------------------------------------------------------------------------- 1 | # serializer version: 1 2 | # name: test_delete_user_dict_word_204 3 | b'' 4 | # --- 5 | # name: test_put_user_dict_word_204 6 | b'' 7 | # --- 8 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/__snapshots__/test_user_dict_word/test_delete_user_dict_word_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "IDに該当するワードが見つかりませんでした" 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/__snapshots__/test_user_dict_word/test_delete_user_dict_word_contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "c89596ad-caa8-4f4e-8eb3-3d2261c798fd": { 3 | "accent_associative_rule": "*", 4 | "accent_type": 1, 5 | "context_id": 1348, 6 | "inflectional_form": "*", 7 | "inflectional_type": "*", 8 | "mora_count": 2, 9 | "part_of_speech": "名詞", 10 | "part_of_speech_detail_1": "固有名詞", 11 | "part_of_speech_detail_2": "一般", 12 | "part_of_speech_detail_3": "*", 13 | "priority": 5, 14 | "pronunciation": "テストニ", 15 | "stem": "*", 16 | "surface": "test2", 17 | "yomi": "テストニ" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/__snapshots__/test_user_dict_word/test_post_user_dict_word_200.json: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/__snapshots__/test_user_dict_word/test_post_user_dict_word_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": [ 3 | { 4 | "ctx": { 5 | "le": 10 6 | }, 7 | "input": "100", 8 | "loc": [ 9 | "query", 10 | "priority" 11 | ], 12 | "msg": "Input should be less than or equal to 10", 13 | "type": "less_than_equal" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/__snapshots__/test_user_dict_word/test_put_user_dict_word_422.json: -------------------------------------------------------------------------------- 1 | { 2 | "detail": "UUIDに該当するワードが見つかりませんでした" 3 | } 4 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/__snapshots__/test_user_dict_word/test_put_user_dict_word_contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "a89596ad-caa8-4f4e-8eb3-3d2261c798fd": { 3 | "accent_associative_rule": "*", 4 | "accent_type": 1, 5 | "context_id": 1348, 6 | "inflectional_form": "*", 7 | "inflectional_type": "*", 8 | "mora_count": 3, 9 | "part_of_speech": "名詞", 10 | "part_of_speech_detail_1": "固有名詞", 11 | "part_of_speech_detail_2": "一般", 12 | "part_of_speech_detail_3": "*", 13 | "priority": 1, 14 | "pronunciation": "テスト", 15 | "stem": "*", 16 | "surface": "test", 17 | "yomi": "テスト" 18 | }, 19 | "c89596ad-caa8-4f4e-8eb3-3d2261c798fd": { 20 | "accent_associative_rule": "*", 21 | "accent_type": 1, 22 | "context_id": 1348, 23 | "inflectional_form": "*", 24 | "inflectional_type": "*", 25 | "mora_count": 2, 26 | "part_of_speech": "名詞", 27 | "part_of_speech_detail_1": "固有名詞", 28 | "part_of_speech_detail_2": "一般", 29 | "part_of_speech_detail_3": "*", 30 | "priority": 5, 31 | "pronunciation": "テストニ", 32 | "stem": "*", 33 | "surface": "test2", 34 | "yomi": "テストニ" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/test_import_user_dict.py: -------------------------------------------------------------------------------- 1 | """/import_user_dict APIのテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_post_import_user_dict_204( 8 | client: TestClient, snapshot: SnapshotAssertion 9 | ) -> None: 10 | user_dict: dict[str, dict[str, str | int]] = {} 11 | response = client.post( 12 | "/import_user_dict", json=user_dict, params={"override": True} 13 | ) 14 | assert response.status_code == 204 15 | assert snapshot == response.content 16 | 17 | 18 | def test_post_import_user_dict_contents( 19 | client: TestClient, snapshot_json: SnapshotAssertion 20 | ) -> None: 21 | """辞書インポートは内容が正しく反映されている。""" 22 | user_dict: dict[str, dict[str, str | int]] = { 23 | "a11196ad-caa8-4f4e-8eb3-3d2261c798fd": { 24 | "accent_associative_rule": "*", 25 | "accent_type": 1, 26 | "context_id": 1348, 27 | "inflectional_form": "*", 28 | "inflectional_type": "*", 29 | "mora_count": 3, 30 | "part_of_speech": "名詞", 31 | "part_of_speech_detail_1": "固有名詞", 32 | "part_of_speech_detail_2": "一般", 33 | "part_of_speech_detail_3": "*", 34 | "priority": 4, 35 | "pronunciation": "テストサン", 36 | "stem": "*", 37 | "surface": "test3", 38 | "yomi": "テストサン", 39 | }, 40 | } 41 | client.post("/import_user_dict", json=user_dict, params={"override": True}) 42 | # NOTE: 'GET /user_dict' が正しく機能することを前提とする 43 | response = client.get("/user_dict", params={}) 44 | assert snapshot_json == response.json() 45 | 46 | 47 | def test_post_import_user_dict_422( 48 | client: TestClient, snapshot_json: SnapshotAssertion 49 | ) -> None: 50 | user_dict: dict[str, dict[str, str | int]] = { 51 | # NOTE: 必須パラメータが不足 52 | "a11196ad-0000-4f4e-8eb3-3d2261c798fd": { 53 | "accent_type": 1, 54 | } 55 | } 56 | response = client.post( 57 | "/import_user_dict", json=user_dict, params={"override": True} 58 | ) 59 | assert response.status_code == 422 60 | assert snapshot_json == response.json() 61 | -------------------------------------------------------------------------------- /test/e2e/single_api/user_dict/test_user_dict_api.py: -------------------------------------------------------------------------------- 1 | """/user_dict API のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_get_user_dict_200( 8 | client: TestClient, snapshot_json: SnapshotAssertion 9 | ) -> None: 10 | response = client.get("/user_dict", params={}) 11 | assert response.status_code == 200 12 | assert snapshot_json == response.json() 13 | -------------------------------------------------------------------------------- /test/e2e/single_api/utils.py: -------------------------------------------------------------------------------- 1 | """単独 API に対する E2E テスト共通のユーティリティ。""" 2 | 3 | from typing import TypedDict 4 | 5 | 6 | class _MoraForTest(TypedDict): 7 | text: str 8 | consonant: str 9 | consonant_length: float 10 | vowel: str 11 | vowel_length: float 12 | pitch: float 13 | 14 | 15 | def gen_mora( 16 | text: str, 17 | consonant: str, 18 | consonant_length: float, 19 | vowel: str, 20 | vowel_length: float, 21 | pitch: float, 22 | ) -> _MoraForTest: 23 | """モーラを生成する。""" 24 | return { 25 | "text": text, 26 | "consonant": consonant, 27 | "consonant_length": consonant_length, 28 | "vowel": vowel, 29 | "vowel_length": vowel_length, 30 | "pitch": pitch, 31 | } 32 | -------------------------------------------------------------------------------- /test/e2e/test_disable_api.py: -------------------------------------------------------------------------------- 1 | """APIを無効化するテスト。""" 2 | 3 | from typing import Any, Literal 4 | 5 | from fastapi.testclient import TestClient 6 | 7 | from voicevox_engine.app.application import generate_app 8 | 9 | 10 | # clientとschemaとパスを受け取ってリクエストを送信し、レスポンスが403であることを確認する 11 | def _assert_request_and_response_403( 12 | client: TestClient, 13 | method: Literal["post", "get", "put", "delete"], 14 | path: str, 15 | ) -> None: 16 | if method == "post": 17 | response = client.post(path) 18 | elif method == "get": 19 | response = client.get(path) 20 | elif method == "put": 21 | response = client.put(path) 22 | elif method == "delete": 23 | response = client.delete(path) 24 | else: 25 | raise ValueError("methodはpost, get, put, deleteのいずれかである必要があります") 26 | 27 | assert response.status_code == 403, f"{method} {path} が403を返しませんでした" 28 | 29 | 30 | def test_disable_mutable_api(app_params: dict[str, Any]) -> None: 31 | """エンジンの静的なデータを変更するAPIを無効化するテスト""" 32 | client = TestClient(generate_app(**app_params, disable_mutable_api=True)) 33 | 34 | # APIが無効化されているか確認 35 | _assert_request_and_response_403(client, "post", "/add_preset") 36 | _assert_request_and_response_403(client, "post", "/update_preset") 37 | _assert_request_and_response_403(client, "post", "/delete_preset") 38 | _assert_request_and_response_403(client, "post", "/user_dict_word") 39 | _assert_request_and_response_403(client, "put", "/user_dict_word/dummy") 40 | _assert_request_and_response_403(client, "delete", "/user_dict_word/dummy") 41 | _assert_request_and_response_403(client, "post", "/import_user_dict") 42 | _assert_request_and_response_403(client, "post", "/setting") 43 | 44 | # FIXME: EngineManifestをDI可能にし、EngineManifestに従ってこれらのAPIを加える 45 | # _assert_request_and_response_403(client, "post", "/install_library/dummy") 46 | # _assert_request_and_response_403(client, "post", "/uninstall_library/dummy") 47 | 48 | # 他のAPIは有効 49 | response = client.get("/version") 50 | assert response.status_code == 200 51 | -------------------------------------------------------------------------------- /test/e2e/test_missing_core.py: -------------------------------------------------------------------------------- 1 | """コア取得失敗のテスト""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_missing_core_422(client: TestClient, snapshot_json: SnapshotAssertion) -> None: 8 | """存在しないコアを指定するとエラーを返す。""" 9 | response = client.get("/speakers", params={"core_version": "4.0.4"}) 10 | assert response.status_code == 422 11 | assert snapshot_json == response.json() 12 | -------------------------------------------------------------------------------- /test/e2e/test_missing_engine.py: -------------------------------------------------------------------------------- 1 | """エンジン取得失敗のテスト""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | 7 | def test_missing_tts_engine_422( 8 | client: TestClient, snapshot_json: SnapshotAssertion 9 | ) -> None: 10 | """存在しないコアを指定するとエラーを返す。""" 11 | response = client.post( 12 | "/audio_query", params={"text": "あ", "speaker": 1, "core_version": "4.0.4"} 13 | ) 14 | assert response.status_code == 422 15 | assert snapshot_json == response.json() 16 | -------------------------------------------------------------------------------- /test/e2e/test_openapi.py: -------------------------------------------------------------------------------- 1 | """OpenAPI スキーマのテスト。""" 2 | 3 | from typing import Any 4 | 5 | from fastapi import FastAPI 6 | from syrupy.assertion import SnapshotAssertion 7 | 8 | 9 | def test_OpenAPIの形が変わっていないことを確認( 10 | app: FastAPI, snapshot_json: SnapshotAssertion 11 | ) -> None: 12 | # 変更があった場合はREADMEの「スナップショットの更新」の手順で更新可能 13 | openapi: Any = ( 14 | app.openapi() 15 | ) # snapshot_jsonがmypyに対応していないのでワークアラウンド 16 | assert snapshot_json == openapi 17 | -------------------------------------------------------------------------------- /test/e2e/test_sing.py: -------------------------------------------------------------------------------- 1 | """歌唱のテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.utility import hash_wave_floats_from_wav_bytes 7 | 8 | 9 | def test_楽譜とキャラクターIDから音声を合成できる( 10 | client: TestClient, snapshot: SnapshotAssertion 11 | ) -> None: 12 | # 楽譜とキャラクター ID から FrameAudioQuery を生成する 13 | score = { 14 | "notes": [ 15 | {"key": None, "frame_length": 10, "lyric": ""}, 16 | {"key": 30, "frame_length": 3, "lyric": "て"}, 17 | {"key": 30, "frame_length": 3, "lyric": "す"}, 18 | {"key": 40, "frame_length": 1, "lyric": "と"}, 19 | {"key": None, "frame_length": 10, "lyric": ""}, 20 | ] 21 | } 22 | frame_audio_query_res = client.post( 23 | "/sing_frame_audio_query", params={"speaker": 0}, json=score 24 | ) 25 | frame_audio_query = frame_audio_query_res.json() 26 | 27 | # FrameAudioQuery から音声波形を生成する 28 | frame_synthesis_res = client.post( 29 | "/frame_synthesis", params={"speaker": 0}, json=frame_audio_query 30 | ) 31 | 32 | # リクエストが成功している 33 | assert frame_synthesis_res.status_code == 200 34 | 35 | # FileResponse 内の .wav から抽出された音声波形が一致する 36 | assert frame_synthesis_res.headers["content-type"] == "audio/wav" 37 | assert snapshot == hash_wave_floats_from_wav_bytes(frame_synthesis_res.read()) 38 | -------------------------------------------------------------------------------- /test/e2e/test_tts.py: -------------------------------------------------------------------------------- 1 | """TTSのテスト。""" 2 | 3 | from fastapi.testclient import TestClient 4 | from syrupy.assertion import SnapshotAssertion 5 | 6 | from test.utility import hash_wave_floats_from_wav_bytes 7 | 8 | 9 | def test_テキストとキャラクターIDから音声を合成できる( 10 | client: TestClient, snapshot: SnapshotAssertion 11 | ) -> None: 12 | # テキストとキャラクター ID から AudioQuery を生成する 13 | audio_query_res = client.post( 14 | "/audio_query", params={"text": "テストです", "speaker": 0} 15 | ) 16 | audio_query = audio_query_res.json() 17 | 18 | # AudioQuery から音声波形を生成する 19 | synthesis_res = client.post("/synthesis", params={"speaker": 0}, json=audio_query) 20 | 21 | # リクエストが成功している 22 | assert synthesis_res.status_code == 200 23 | 24 | # FileResponse 内の .wav から抽出された音声波形が一致する 25 | assert synthesis_res.headers["content-type"] == "audio/wav" 26 | assert snapshot == hash_wave_floats_from_wav_bytes(synthesis_res.read()) 27 | -------------------------------------------------------------------------------- /test/unit/library/vvlib_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": "0.15.0", 3 | "name": "Test vvlib", 4 | "version": "0.0.1", 5 | "uuid": "2bb8bccf-1c3f-4bc9-959a-f388e37af3ad", 6 | "engine_name": "Test Engine", 7 | "brand_name": "Test", 8 | "engine_uuid": "c7b58856-bd56-4aa1-afb7-b8415f824b06" 9 | } -------------------------------------------------------------------------------- /test/unit/preset/presets-test-1.yaml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | name: test 3 | speaker_uuid: 7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff 4 | style_id: 0 5 | speedScale: 1 6 | pitchScale: 0 7 | intonationScale: 1 8 | volumeScale: 1 9 | prePhonemeLength: 0.1 10 | postPhonemeLength: 0.1 11 | pauseLength: null 12 | pauseLengthScale: 1.0 13 | 14 | - id: 2 15 | name: test2 16 | speaker_uuid: 7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff 17 | style_id: 2 18 | speedScale: 1.5 19 | pitchScale: 0 20 | intonationScale: 1 21 | volumeScale: 0.7 22 | prePhonemeLength: 0.5 23 | postPhonemeLength: 0.5 24 | pauseLength: null 25 | pauseLengthScale: 1.0 26 | -------------------------------------------------------------------------------- /test/unit/preset/presets-test-2.yaml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | name: test 3 | speaker_uuid: 7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff 4 | style_id: not_int 5 | speedScale: 1 6 | pitchScale: 0 7 | intonationScale: 1 8 | volumeScale: 1 9 | prePhonemeLength: 0.1 10 | postPhonemeLength: 0.1 11 | pauseLength: null 12 | pauseLengthScale: 1.0 13 | 14 | - id: 2 15 | name: test2 16 | speaker_uuid: 7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff 17 | style_id: 2 18 | speedScale: 1.5 19 | pitchScale: 0 20 | intonationScale: 1 21 | volumeScale: 0.7 22 | prePhonemeLength: 0.5 23 | postPhonemeLength: 0.5 24 | pauseLength: null 25 | pauseLengthScale: 1.0 26 | -------------------------------------------------------------------------------- /test/unit/preset/presets-test-3.yaml: -------------------------------------------------------------------------------- 1 | - id: 1 2 | name: test 3 | speaker_uuid: 7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff 4 | style_id: 0 5 | speedScale: 1 6 | pitchScale: 0 7 | intonationScale: 1 8 | volumeScale: 1 9 | prePhonemeLength: 0.1 10 | postPhonemeLength: 0.1 11 | pauseLength: null 12 | pauseLengthScale: 1.0 13 | 14 | - id: 1 15 | name: test2 16 | speaker_uuid: 7ffcb7ce-00ec-4bdc-82cd-45a8889e43ff 17 | style_id: 2 18 | speedScale: 1.5 19 | pitchScale: 0 20 | intonationScale: 1 21 | volumeScale: 0.7 22 | prePhonemeLength: 0.5 23 | postPhonemeLength: 0.5 24 | pauseLength: null 25 | pauseLengthScale: 1.0 26 | -------------------------------------------------------------------------------- /test/unit/preset/presets-test-4.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/test/unit/preset/presets-test-4.yaml -------------------------------------------------------------------------------- /test/unit/resource_manager/with_filemap/dummy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/test/unit/resource_manager/with_filemap/dummy.png -------------------------------------------------------------------------------- /test/unit/resource_manager/with_filemap/dummy.txt: -------------------------------------------------------------------------------- 1 | DUMMY-TEXT 2 | -------------------------------------------------------------------------------- /test/unit/resource_manager/with_filemap/dummy.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/test/unit/resource_manager/with_filemap/dummy.wav -------------------------------------------------------------------------------- /test/unit/resource_manager/with_filemap/dummy_same_binary.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/test/unit/resource_manager/with_filemap/dummy_same_binary.wav -------------------------------------------------------------------------------- /test/unit/resource_manager/with_filemap/filemap.json: -------------------------------------------------------------------------------- 1 | {"dummy.png": "475dac9a64f6ad7b2d94ce570219b11320acb90d6e28ffa97bd6617d19b2cdfb", "dummy.wav": "712618020da1c97986d60975b6a279bb8b4f90b4ab392fa5d278df7fb89c5cc0", "dummy_same_binary.wav": "712618020da1c97986d60975b6a279bb8b4f90b4ab392fa5d278df7fb89c5cc0"} -------------------------------------------------------------------------------- /test/unit/resource_manager/without_filemap/dummy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/test/unit/resource_manager/without_filemap/dummy.png -------------------------------------------------------------------------------- /test/unit/resource_manager/without_filemap/dummy.txt: -------------------------------------------------------------------------------- 1 | DUMMY-TEXT 2 | -------------------------------------------------------------------------------- /test/unit/resource_manager/without_filemap/dummy.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/test/unit/resource_manager/without_filemap/dummy.wav -------------------------------------------------------------------------------- /test/unit/resource_manager/without_filemap/dummy_same_binary.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VOICEVOX/voicevox_engine/c95bb9e387043e7f7a2eb4fd3e46692fea28716a/test/unit/resource_manager/without_filemap/dummy_same_binary.wav -------------------------------------------------------------------------------- /test/unit/setting/setting-test-load-1.yaml: -------------------------------------------------------------------------------- 1 | allow_origin: null 2 | cors_policy_mode: localapps 3 | -------------------------------------------------------------------------------- /test/unit/setting/setting-test-load-2.yaml: -------------------------------------------------------------------------------- 1 | allow_origin: null 2 | cors_policy_mode: all 3 | -------------------------------------------------------------------------------- /test/unit/setting/setting-test-load-3.yaml: -------------------------------------------------------------------------------- 1 | allow_origin: "192.168.254.255 192.168.255.255" 2 | cors_policy_mode: localapps 3 | -------------------------------------------------------------------------------- /test/unit/test_core_version_utility.py: -------------------------------------------------------------------------------- 1 | """コアバージョンに関する utilities のテスト。""" 2 | 3 | from voicevox_engine.utility.core_version_utility import get_latest_version 4 | 5 | 6 | def test_get_latest_version_same_preview_normal() -> None: 7 | """`get_latest_version()` は同一バージョンの preview 版より正規版を優先する。""" 8 | # Inputs 9 | versions = [ 10 | "0.0.0", 11 | "0.1.0", 12 | "0.10.0", 13 | "0.10.0-preview.1", 14 | "0.14.0", 15 | "0.14.0-preview.1", 16 | "0.14.0-preview.10", 17 | ] 18 | 19 | # Expects 20 | true_latest = "0.14.0" 21 | 22 | # Outputs 23 | latest = get_latest_version(versions) 24 | 25 | # Tests 26 | assert true_latest == latest 27 | 28 | 29 | def test_get_latest_version_newer_preview() -> None: 30 | """`get_latest_version()` は旧バージョンの正規版より新バージョンの preview 版を優先する。""" 31 | # Inputs 32 | versions = [ 33 | "0.14.0", 34 | "0.15.0-preview.1", 35 | ] 36 | 37 | # Expects 38 | true_latest = "0.15.0-preview.1" 39 | 40 | # Outputs 41 | latest = get_latest_version(versions) 42 | 43 | # Tests 44 | assert true_latest == latest 45 | -------------------------------------------------------------------------------- /test/unit/test_utility/test_utility_hash_big_ndarray.py: -------------------------------------------------------------------------------- 1 | """テストユーティリティ `summarize_big_ndarray()` のテスト""" 2 | 3 | import numpy as np 4 | 5 | from test.utility import summarize_big_ndarray 6 | 7 | 8 | def test_summarize_big_ndarray_raw_small_array() -> None: 9 | """`summarize_big_ndarray()` は小さい NumPy 配列を要約しない。""" 10 | # Inputs 11 | target = np.array([111.111]) 12 | # Tests 13 | assert summarize_big_ndarray(target) == np.array([111.111]) 14 | 15 | 16 | def test_summarize_big_ndarray_raw_big_array() -> None: 17 | """`summarize_big_ndarray()` は大きい NumPy 配列を要約する。""" 18 | # Inputs 19 | target = np.ones([10, 10, 10]) 20 | # Expects 21 | true_hash_header = "MD5:" 22 | true_shape = target.shape 23 | # Outputs 24 | summary = summarize_big_ndarray(target) 25 | hash_header = summary["hash"][:4] 26 | shape = tuple(summary["shape"]) 27 | # Tests 28 | assert true_hash_header == hash_header 29 | assert true_shape == shape 30 | -------------------------------------------------------------------------------- /test/unit/test_utility/test_utility_round_floats.py: -------------------------------------------------------------------------------- 1 | """テストユーティリティ `round_floats()` のテスト""" 2 | 3 | import numpy as np 4 | 5 | from test.utility import round_floats 6 | 7 | 8 | def test_round_floats_raw_float() -> None: 9 | """`round_floats()` は値を丸める。""" 10 | # Inputs 11 | target = 111.111 12 | # Tests 13 | assert round_floats(target, -2) == 100 14 | assert round_floats(target, -1) == 110 15 | assert round_floats(target, 0) == 111 16 | assert round_floats(target, +1) == 111.1 17 | assert round_floats(target, +2) == 111.11 18 | 19 | 20 | def test_round_floats_list() -> None: 21 | """`round_floats()` はリスト内の値を丸める。""" 22 | # Inputs 23 | target = [1.111, 8.888] 24 | # Tests 25 | assert round_floats(target, 2) == [1.11, 8.89] 26 | 27 | 28 | def test_round_floats_dict() -> None: 29 | """`round_floats()` は辞書内の値を丸める。""" 30 | # Inputs 31 | target = {"hello": 1.111} 32 | # Tests 33 | assert round_floats(target, 2) == {"hello": 1.11} 34 | 35 | 36 | def test_round_floats_numpy() -> None: 37 | """`round_floats()` は NumPy 値を丸める。""" 38 | # Inputs 39 | target = np.array([111.111]) 40 | # Tests 41 | assert round_floats(target, 2) == np.array([111.11]) 42 | 43 | 44 | def test_round_floats_nested() -> None: 45 | """`round_floats()` はネストしたオブジェクト内の値を丸める。""" 46 | # Inputs 47 | target = [1.111, {"hello": 1.111, "world": [1.111]}, np.array([1.111])] 48 | # Expects 49 | true_rounded = [1.11, {"hello": 1.11, "world": [1.11]}, np.array([1.11])] 50 | # Outputs 51 | rounded = round_floats(target, 2) 52 | # Tests 53 | assert true_rounded == rounded 54 | -------------------------------------------------------------------------------- /test/unit/tts_pipeline/__snapshots__/test_tts_engine/test_mocked_create_accent_phrases_from_kana_output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "accent": 5, 4 | "is_interrogative": false, 5 | "moras": [ 6 | { 7 | "consonant": "k", 8 | "consonant_length": 2.44, 9 | "pitch": 4.38, 10 | "text": "コ", 11 | "vowel": "o", 12 | "vowel_length": 2.88 13 | }, 14 | { 15 | "consonant": null, 16 | "consonant_length": null, 17 | "pitch": 1.25, 18 | "text": "ン", 19 | "vowel": "N", 20 | "vowel_length": 1.25 21 | }, 22 | { 23 | "consonant": "n", 24 | "consonant_length": 2.75, 25 | "pitch": 4.06, 26 | "text": "ニ", 27 | "vowel": "i", 28 | "vowel_length": 2.31 29 | }, 30 | { 31 | "consonant": "ch", 32 | "consonant_length": 1.62, 33 | "pitch": 2.94, 34 | "text": "チ", 35 | "vowel": "i", 36 | "vowel_length": 2.31 37 | }, 38 | { 39 | "consonant": "w", 40 | "consonant_length": 3.62, 41 | "pitch": 4.19, 42 | "text": "ワ", 43 | "vowel": "a", 44 | "vowel_length": 1.44 45 | } 46 | ], 47 | "pause_mora": { 48 | "consonant": null, 49 | "consonant_length": null, 50 | "pitch": 0.0, 51 | "text": "、", 52 | "vowel": "pau", 53 | "vowel_length": 1.0 54 | } 55 | }, 56 | { 57 | "accent": 1, 58 | "is_interrogative": false, 59 | "moras": [ 60 | { 61 | "consonant": "h", 62 | "consonant_length": 2.19, 63 | "pitch": 3.69, 64 | "text": "ヒ", 65 | "vowel": "i", 66 | "vowel_length": 2.31 67 | }, 68 | { 69 | "consonant": "h", 70 | "consonant_length": 2.19, 71 | "pitch": 4.06, 72 | "text": "ホ", 73 | "vowel": "o", 74 | "vowel_length": 2.88 75 | }, 76 | { 77 | "consonant": "d", 78 | "consonant_length": 1.75, 79 | "pitch": 2.62, 80 | "text": "デ", 81 | "vowel": "e", 82 | "vowel_length": 1.88 83 | }, 84 | { 85 | "consonant": "s", 86 | "consonant_length": 3.19, 87 | "pitch": 0.0, 88 | "text": "ス", 89 | "vowel": "U", 90 | "vowel_length": 1.38 91 | } 92 | ], 93 | "pause_mora": null 94 | } 95 | ] 96 | -------------------------------------------------------------------------------- /test/unit/tts_pipeline/__snapshots__/test_tts_engine/test_mocked_create_accent_phrases_output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "accent": 5, 4 | "is_interrogative": false, 5 | "moras": [ 6 | { 7 | "consonant": "k", 8 | "consonant_length": 2.44, 9 | "pitch": 4.38, 10 | "text": "コ", 11 | "vowel": "o", 12 | "vowel_length": 2.88 13 | }, 14 | { 15 | "consonant": null, 16 | "consonant_length": null, 17 | "pitch": 1.25, 18 | "text": "ン", 19 | "vowel": "N", 20 | "vowel_length": 1.25 21 | }, 22 | { 23 | "consonant": "n", 24 | "consonant_length": 2.75, 25 | "pitch": 4.06, 26 | "text": "ニ", 27 | "vowel": "i", 28 | "vowel_length": 2.31 29 | }, 30 | { 31 | "consonant": "ch", 32 | "consonant_length": 1.62, 33 | "pitch": 2.94, 34 | "text": "チ", 35 | "vowel": "i", 36 | "vowel_length": 2.31 37 | }, 38 | { 39 | "consonant": "w", 40 | "consonant_length": 3.62, 41 | "pitch": 4.19, 42 | "text": "ワ", 43 | "vowel": "a", 44 | "vowel_length": 1.44 45 | } 46 | ], 47 | "pause_mora": { 48 | "consonant": null, 49 | "consonant_length": null, 50 | "pitch": 0.0, 51 | "text": "、", 52 | "vowel": "pau", 53 | "vowel_length": 1.0 54 | } 55 | }, 56 | { 57 | "accent": 1, 58 | "is_interrogative": false, 59 | "moras": [ 60 | { 61 | "consonant": "h", 62 | "consonant_length": 2.19, 63 | "pitch": 3.69, 64 | "text": "ヒ", 65 | "vowel": "i", 66 | "vowel_length": 2.31 67 | }, 68 | { 69 | "consonant": "h", 70 | "consonant_length": 2.19, 71 | "pitch": 4.06, 72 | "text": "ホ", 73 | "vowel": "o", 74 | "vowel_length": 2.88 75 | }, 76 | { 77 | "consonant": "d", 78 | "consonant_length": 1.75, 79 | "pitch": 2.62, 80 | "text": "デ", 81 | "vowel": "e", 82 | "vowel_length": 1.88 83 | }, 84 | { 85 | "consonant": "s", 86 | "consonant_length": 3.19, 87 | "pitch": 0.0, 88 | "text": "ス", 89 | "vowel": "U", 90 | "vowel_length": 1.38 91 | } 92 | ], 93 | "pause_mora": null 94 | } 95 | ] 96 | -------------------------------------------------------------------------------- /test/unit/tts_pipeline/__snapshots__/test_tts_engine/test_mocked_create_volume_from_phoneme_and_f0_output.json: -------------------------------------------------------------------------------- 1 | [ 2 | 0.0, 3 | 0.0, 4 | 0.0, 5 | 0.0, 6 | 0.61, 7 | 0.61, 8 | 0.61, 9 | 0.61, 10 | 0.61, 11 | 0.61, 12 | 1.53, 13 | 1.53, 14 | 1.53, 15 | 1.53, 16 | 1.96, 17 | 1.96, 18 | 1.96, 19 | 1.96, 20 | 1.96, 21 | 1.96, 22 | 1.96, 23 | 1.96, 24 | 0.83, 25 | 0.83, 26 | 0.83, 27 | 0.83, 28 | 0.83, 29 | 0.83, 30 | 0.83, 31 | 0.83, 32 | 0.83, 33 | 0.83, 34 | 0.83, 35 | 0.83, 36 | 0.83, 37 | 1.79, 38 | 1.79, 39 | 1.79, 40 | 1.79, 41 | 1.44, 42 | 1.44, 43 | 1.44, 44 | 1.44, 45 | 1.44, 46 | 1.44, 47 | 1.44, 48 | 1.44, 49 | 1.44, 50 | 1.44, 51 | 1.44, 52 | 1.44, 53 | 1.44, 54 | 1.44, 55 | 1.44, 56 | 1.44, 57 | 1.44, 58 | 1.44, 59 | 1.44, 60 | 1.44, 61 | 1.44, 62 | 0.0, 63 | 0.0, 64 | 0.0, 65 | 1.11, 66 | 1.11, 67 | 0.51, 68 | 0.51, 69 | 0.51, 70 | 0.51, 71 | 0.51, 72 | 0.51, 73 | 3.0, 74 | 3.0, 75 | 3.0, 76 | 3.0, 77 | 3.0, 78 | 3.0, 79 | 2.57, 80 | 2.57, 81 | 2.57, 82 | 2.57, 83 | 2.57, 84 | 2.57, 85 | 2.57, 86 | 2.57, 87 | 2.57, 88 | 2.57, 89 | 2.57, 90 | 2.57, 91 | 2.57, 92 | 2.57, 93 | 2.57, 94 | 2.57, 95 | 2.57, 96 | 0.0, 97 | 0.0, 98 | 0.0, 99 | 0.0, 100 | 0.0, 101 | 0.0, 102 | 0.0, 103 | 0.0, 104 | 0.0, 105 | 0.0 106 | ] 107 | -------------------------------------------------------------------------------- /test/unit/tts_pipeline/__snapshots__/test_tts_engine/test_mocked_synthesize_wave_output.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "MD5:0972d97e926609c90a9117490fe9813e", 3 | "shape": [ 4 | 1, 5 | 14080, 6 | 2 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /test/unit/tts_pipeline/__snapshots__/test_tts_engine/test_mocked_update_length_and_pitch_output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "accent": 5, 4 | "is_interrogative": false, 5 | "moras": [ 6 | { 7 | "consonant": "k", 8 | "consonant_length": 2.44, 9 | "pitch": 4.38, 10 | "text": "コ", 11 | "vowel": "o", 12 | "vowel_length": 2.88 13 | }, 14 | { 15 | "consonant": null, 16 | "consonant_length": null, 17 | "pitch": 1.25, 18 | "text": "ン", 19 | "vowel": "N", 20 | "vowel_length": 1.25 21 | }, 22 | { 23 | "consonant": "n", 24 | "consonant_length": 2.75, 25 | "pitch": 4.06, 26 | "text": "ニ", 27 | "vowel": "i", 28 | "vowel_length": 2.31 29 | }, 30 | { 31 | "consonant": "ch", 32 | "consonant_length": 1.62, 33 | "pitch": 2.94, 34 | "text": "チ", 35 | "vowel": "i", 36 | "vowel_length": 2.31 37 | }, 38 | { 39 | "consonant": "w", 40 | "consonant_length": 3.62, 41 | "pitch": 4.19, 42 | "text": "ワ", 43 | "vowel": "a", 44 | "vowel_length": 1.44 45 | } 46 | ], 47 | "pause_mora": { 48 | "consonant": null, 49 | "consonant_length": null, 50 | "pitch": 0.0, 51 | "text": "、", 52 | "vowel": "pau", 53 | "vowel_length": 1.0 54 | } 55 | }, 56 | { 57 | "accent": 1, 58 | "is_interrogative": false, 59 | "moras": [ 60 | { 61 | "consonant": "h", 62 | "consonant_length": 2.19, 63 | "pitch": 3.69, 64 | "text": "ヒ", 65 | "vowel": "i", 66 | "vowel_length": 2.31 67 | }, 68 | { 69 | "consonant": "h", 70 | "consonant_length": 2.19, 71 | "pitch": 4.06, 72 | "text": "ホ", 73 | "vowel": "o", 74 | "vowel_length": 2.88 75 | }, 76 | { 77 | "consonant": "d", 78 | "consonant_length": 1.75, 79 | "pitch": 2.62, 80 | "text": "デ", 81 | "vowel": "e", 82 | "vowel_length": 1.88 83 | }, 84 | { 85 | "consonant": "s", 86 | "consonant_length": 3.19, 87 | "pitch": 0.0, 88 | "text": "ス", 89 | "vowel": "U", 90 | "vowel_length": 1.38 91 | } 92 | ], 93 | "pause_mora": null 94 | } 95 | ] 96 | -------------------------------------------------------------------------------- /test/unit/tts_pipeline/__snapshots__/test_tts_engine/test_mocked_update_length_output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "accent": 5, 4 | "is_interrogative": false, 5 | "moras": [ 6 | { 7 | "consonant": "k", 8 | "consonant_length": 2.44, 9 | "pitch": 5.0, 10 | "text": "コ", 11 | "vowel": "o", 12 | "vowel_length": 2.88 13 | }, 14 | { 15 | "consonant": null, 16 | "consonant_length": null, 17 | "pitch": 5.0, 18 | "text": "ン", 19 | "vowel": "N", 20 | "vowel_length": 1.25 21 | }, 22 | { 23 | "consonant": "n", 24 | "consonant_length": 2.75, 25 | "pitch": 5.0, 26 | "text": "ニ", 27 | "vowel": "i", 28 | "vowel_length": 2.31 29 | }, 30 | { 31 | "consonant": "ch", 32 | "consonant_length": 1.62, 33 | "pitch": 5.0, 34 | "text": "チ", 35 | "vowel": "i", 36 | "vowel_length": 2.31 37 | }, 38 | { 39 | "consonant": "w", 40 | "consonant_length": 3.62, 41 | "pitch": 5.0, 42 | "text": "ワ", 43 | "vowel": "a", 44 | "vowel_length": 1.44 45 | } 46 | ], 47 | "pause_mora": { 48 | "consonant": null, 49 | "consonant_length": null, 50 | "pitch": 0.0, 51 | "text": "、", 52 | "vowel": "pau", 53 | "vowel_length": 1.0 54 | } 55 | }, 56 | { 57 | "accent": 1, 58 | "is_interrogative": false, 59 | "moras": [ 60 | { 61 | "consonant": "h", 62 | "consonant_length": 2.19, 63 | "pitch": 0.0, 64 | "text": "ヒ", 65 | "vowel": "i", 66 | "vowel_length": 2.31 67 | }, 68 | { 69 | "consonant": "h", 70 | "consonant_length": 2.19, 71 | "pitch": 5.0, 72 | "text": "ホ", 73 | "vowel": "o", 74 | "vowel_length": 2.88 75 | }, 76 | { 77 | "consonant": "d", 78 | "consonant_length": 1.75, 79 | "pitch": 5.0, 80 | "text": "デ", 81 | "vowel": "e", 82 | "vowel_length": 1.88 83 | }, 84 | { 85 | "consonant": "s", 86 | "consonant_length": 3.19, 87 | "pitch": 0.0, 88 | "text": "ス", 89 | "vowel": "U", 90 | "vowel_length": 1.38 91 | } 92 | ], 93 | "pause_mora": null 94 | } 95 | ] 96 | -------------------------------------------------------------------------------- /test/unit/tts_pipeline/__snapshots__/test_tts_engine/test_mocked_update_pitch_output.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "accent": 5, 4 | "is_interrogative": false, 5 | "moras": [ 6 | { 7 | "consonant": "k", 8 | "consonant_length": 0.1, 9 | "pitch": 4.38, 10 | "text": "コ", 11 | "vowel": "o", 12 | "vowel_length": 0.1 13 | }, 14 | { 15 | "consonant": null, 16 | "consonant_length": null, 17 | "pitch": 1.25, 18 | "text": "ン", 19 | "vowel": "N", 20 | "vowel_length": 0.1 21 | }, 22 | { 23 | "consonant": "n", 24 | "consonant_length": 0.1, 25 | "pitch": 4.06, 26 | "text": "ニ", 27 | "vowel": "i", 28 | "vowel_length": 0.1 29 | }, 30 | { 31 | "consonant": "ch", 32 | "consonant_length": 0.1, 33 | "pitch": 2.94, 34 | "text": "チ", 35 | "vowel": "i", 36 | "vowel_length": 0.1 37 | }, 38 | { 39 | "consonant": "w", 40 | "consonant_length": 0.1, 41 | "pitch": 4.19, 42 | "text": "ワ", 43 | "vowel": "a", 44 | "vowel_length": 0.1 45 | } 46 | ], 47 | "pause_mora": { 48 | "consonant": null, 49 | "consonant_length": null, 50 | "pitch": 0.0, 51 | "text": "、", 52 | "vowel": "pau", 53 | "vowel_length": 0.1 54 | } 55 | }, 56 | { 57 | "accent": 1, 58 | "is_interrogative": false, 59 | "moras": [ 60 | { 61 | "consonant": "h", 62 | "consonant_length": 0.1, 63 | "pitch": 3.69, 64 | "text": "ヒ", 65 | "vowel": "i", 66 | "vowel_length": 0.1 67 | }, 68 | { 69 | "consonant": "h", 70 | "consonant_length": 0.1, 71 | "pitch": 4.06, 72 | "text": "ホ", 73 | "vowel": "o", 74 | "vowel_length": 0.1 75 | }, 76 | { 77 | "consonant": "d", 78 | "consonant_length": 0.1, 79 | "pitch": 2.62, 80 | "text": "デ", 81 | "vowel": "e", 82 | "vowel_length": 0.1 83 | }, 84 | { 85 | "consonant": "s", 86 | "consonant_length": 0.1, 87 | "pitch": 0.0, 88 | "text": "ス", 89 | "vowel": "U", 90 | "vowel_length": 0.1 91 | } 92 | ], 93 | "pause_mora": null 94 | } 95 | ] 96 | -------------------------------------------------------------------------------- /test/unit/tts_pipeline/test_katakana_english.py: -------------------------------------------------------------------------------- 1 | """英単語のカタカナ変換機能の単体テスト。""" 2 | 3 | import pytest 4 | 5 | from voicevox_engine.tts_pipeline.katakana_english import ( 6 | HankakuAlphabet, 7 | _convert_as_char_wise_katakana, 8 | _should_convert_english_to_katakana, 9 | _split_into_words, 10 | convert_english_to_katakana, 11 | is_hankaku_alphabet, 12 | ) 13 | 14 | 15 | @pytest.mark.parametrize( 16 | "string,true_words", 17 | [ 18 | ("voicevox", ["voicevox"]), 19 | ("VoiceVox", ["Voice", "Vox"]), 20 | ("VoiceVOX", ["Voice", "V", "O", "X"]), 21 | ("VOICEVOX", ["V", "O", "I", "C", "E", "V", "O", "X"]), 22 | ], 23 | ) 24 | def test_split_into_words(string: str, true_words: list[str]) -> None: 25 | """`_split_into_words()` はアルファベット列を単語列へ分割する。""" 26 | # outputs 27 | words = _split_into_words(HankakuAlphabet(string)) 28 | # expects 29 | true_words_typed = list(map(HankakuAlphabet, true_words)) 30 | # tests 31 | assert true_words_typed == words 32 | 33 | 34 | @pytest.mark.parametrize( 35 | "string,true_should", 36 | [ 37 | ("voivo", True), 38 | ("Voivo", True), 39 | ("VOIVO", False), # 大文字のみの英単語は変換すべきではない 40 | ("Vo", False), # 2文字以下の英単語は変換すべきではない 41 | ], 42 | ) 43 | def test_should_convert_english_to_katakana(string: str, true_should: bool) -> None: 44 | # outputs 45 | should = _should_convert_english_to_katakana(HankakuAlphabet(string)) 46 | # tests 47 | assert true_should == should 48 | 49 | 50 | @pytest.mark.parametrize( 51 | "alphabets,true_yomi", 52 | [ 53 | ("aa", "エーエー"), 54 | ("Aa", "エーエー"), 55 | ("aA", "エーエー"), 56 | ("AA", "エーエー"), 57 | ("VOICE", "ブイオーアイシーイー"), 58 | ], 59 | ) 60 | def test_convert_as_char_wise_katakana(alphabets: str, true_yomi: str) -> None: 61 | """`_convert_as_char_wise_katakana()` はアルファベット列を文字ごとにカタカナ読みする。""" 62 | # outputs 63 | yomi = _convert_as_char_wise_katakana(HankakuAlphabet(alphabets)) 64 | # tests 65 | assert true_yomi == yomi 66 | 67 | 68 | def test_convert_english_to_katakana() -> None: 69 | """`convert_english_to_katakana`は英単語をアルファベットそのままで読まない""" 70 | string = "Voivo" 71 | assert is_hankaku_alphabet(string) 72 | 73 | pron = convert_english_to_katakana(string) 74 | 75 | expected_pron = "ヴォイヴォ" 76 | 77 | assert expected_pron == pron 78 | -------------------------------------------------------------------------------- /test/unit/tts_pipeline/test_mora_mapping.py: -------------------------------------------------------------------------------- 1 | """モーラと音素の対応付けの単体テスト。""" 2 | 3 | from voicevox_engine.tts_pipeline.mora_mapping import mora_phonemes_to_mora_kana 4 | 5 | 6 | def test_mora2text() -> None: 7 | assert "ッ" == mora_phonemes_to_mora_kana["cl"] 8 | assert "ティ" == mora_phonemes_to_mora_kana["ti"] 9 | assert "トゥ" == mora_phonemes_to_mora_kana["tu"] 10 | assert "ディ" == mora_phonemes_to_mora_kana["di"] 11 | # GitHub issue #60 12 | assert "ギェ" == mora_phonemes_to_mora_kana["gye"] 13 | assert "イェ" == mora_phonemes_to_mora_kana["ye"] 14 | 15 | 16 | def test_mora2text_injective() -> None: 17 | """異なるモーラが同じ読みがなに対応しないか確認する""" 18 | mora_kanas = list(mora_phonemes_to_mora_kana.values()) 19 | # NOTE: 同じ読みがなが複数回登場すると set で非重複化して全長が短くなる 20 | assert len(mora_kanas) == len(set(mora_kanas)) 21 | -------------------------------------------------------------------------------- /test/unit/tts_pipeline/test_phoneme.py: -------------------------------------------------------------------------------- 1 | """Phoneme クラスの単体テスト。""" 2 | 3 | import pytest 4 | 5 | from voicevox_engine.tts_pipeline.phoneme import Phoneme 6 | 7 | TRUE_NUM_PHONEME = 45 8 | 9 | 10 | def test_unknown_phoneme() -> None: 11 | """Unknown音素 `xx` のID取得を拒否する""" 12 | # Inputs 13 | unknown_phoneme = Phoneme("xx") 14 | 15 | # Tests 16 | with pytest.raises(ValueError): 17 | _ = unknown_phoneme.id 18 | 19 | 20 | # list_idx 0 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 21 | hello_hiho = "sil k o N n i ch i w a pau h i h o d e s U sil".split() 22 | ojt_hello_hiho = [Phoneme(s) for s in hello_hiho] 23 | 24 | 25 | def test_const() -> None: 26 | assert Phoneme._NUM_PHONEME == TRUE_NUM_PHONEME 27 | assert Phoneme._PHONEME_LIST[1] == "A" 28 | assert Phoneme._PHONEME_LIST[14] == "e" 29 | assert Phoneme._PHONEME_LIST[26] == "m" 30 | assert Phoneme._PHONEME_LIST[38] == "ts" 31 | assert Phoneme._PHONEME_LIST[41] == "v" 32 | 33 | 34 | def test_convert() -> None: 35 | sil_phoneme = Phoneme("sil") 36 | assert sil_phoneme._phoneme == "pau" 37 | 38 | 39 | def test_phoneme_id() -> None: 40 | ojt_str_hello_hiho = " ".join([str(p.id) for p in ojt_hello_hiho]) 41 | assert ojt_str_hello_hiho == "0 23 30 4 28 21 10 21 42 7 0 19 21 19 30 12 14 35 6 0" 42 | 43 | 44 | def test_onehot() -> None: 45 | phoneme_id_list = [ 46 | 0, 47 | 23, 48 | 30, 49 | 4, 50 | 28, 51 | 21, 52 | 10, 53 | 21, 54 | 42, 55 | 7, 56 | 0, 57 | 19, 58 | 21, 59 | 19, 60 | 30, 61 | 12, 62 | 14, 63 | 35, 64 | 6, 65 | 0, 66 | ] 67 | for i, phoneme in enumerate(ojt_hello_hiho): 68 | for j in range(TRUE_NUM_PHONEME): 69 | if phoneme_id_list[i] == j: 70 | assert phoneme.onehot[j] == 1.0 71 | else: 72 | assert phoneme.onehot[j] == 0.0 73 | -------------------------------------------------------------------------------- /test/unit/tts_pipeline/tts_utils.py: -------------------------------------------------------------------------------- 1 | """合成系テスト向けの utility""" 2 | 3 | from voicevox_engine.tts_pipeline.model import Mora 4 | 5 | 6 | def sec(frame: int) -> float: 7 | """フレーム数に相当する秒数を返す。""" 8 | return 0.01067 * frame # 1 フレームが約 10.67 ミリ秒 9 | 10 | 11 | def gen_mora( 12 | text: str, 13 | consonant: str | None, 14 | consonant_length: float | None, 15 | vowel: str, 16 | vowel_length: float, 17 | pitch: float, 18 | ) -> Mora: 19 | """Generate Mora with positional arguments for test simplicity.""" 20 | return Mora( 21 | text=text, 22 | consonant=consonant, 23 | consonant_length=consonant_length, 24 | vowel=vowel, 25 | vowel_length=vowel_length, 26 | pitch=pitch, 27 | ) 28 | -------------------------------------------------------------------------------- /test/unit/user_dict/test_word_types.py: -------------------------------------------------------------------------------- 1 | """WordTypes オブジェクトの単体テスト。""" 2 | 3 | from voicevox_engine.user_dict.model import WordTypes 4 | from voicevox_engine.user_dict.user_dict_word import part_of_speech_data 5 | 6 | 7 | def test_word_types() -> None: 8 | word_types = list(WordTypes) 9 | part_of_speeches = list(part_of_speech_data.keys()) 10 | assert sorted(word_types) == sorted(part_of_speeches) 11 | -------------------------------------------------------------------------------- /test/utility.py: -------------------------------------------------------------------------------- 1 | """全テスト共通のユーティリティ。""" 2 | 3 | import hashlib 4 | import io 5 | from typing import Any 6 | 7 | import numpy as np 8 | import soundfile as sf 9 | from fastapi.encoders import jsonable_encoder 10 | from numpy.typing import NDArray 11 | 12 | 13 | def round_floats(value: Any, round_value: int) -> Any: 14 | """floatの小数点以下を再帰的に丸める""" 15 | if isinstance(value, float): 16 | return round(value, round_value) 17 | elif isinstance(value, np.ndarray) and np.issubdtype(value.dtype, np.floating): 18 | return np.round(value, round_value) 19 | elif isinstance(value, list): 20 | return [round_floats(v, round_value) for v in value] 21 | elif isinstance(value, dict): 22 | return {k: round_floats(v, round_value) for k, v in value.items()} 23 | else: 24 | return value 25 | 26 | 27 | def pydantic_to_native_type(value: Any) -> Any: 28 | """pydanticの型をnativeな型に変換する""" 29 | return jsonable_encoder(value) 30 | 31 | 32 | def hash_long_string(value: Any) -> Any: 33 | """文字数が1000文字を超えるものはハッシュ化する""" 34 | 35 | def to_hash(value: str) -> str: 36 | return "MD5:" + hashlib.md5(value.encode()).hexdigest() 37 | 38 | if isinstance(value, str): 39 | return value if len(value) <= 1000 else to_hash(value) 40 | elif isinstance(value, list): 41 | return [hash_long_string(v) for v in value] 42 | elif isinstance(value, dict): 43 | return {k: hash_long_string(v) for k, v in value.items()} 44 | else: 45 | return value 46 | 47 | 48 | def summarize_big_ndarray(value: Any) -> Any: 49 | """要素数が100を超える NDArray を、ハッシュ値と shape からなる文字列へ要約する""" 50 | 51 | def to_hash(value: NDArray[Any]) -> str: 52 | return "MD5:" + hashlib.md5(value.tobytes()).hexdigest() 53 | 54 | if isinstance(value, np.ndarray): 55 | if value.size <= 100: 56 | return value 57 | else: 58 | return {"hash": to_hash(value), "shape": value.shape} 59 | elif isinstance(value, list): 60 | return [summarize_big_ndarray(v) for v in value] 61 | elif isinstance(value, dict): 62 | return {k: summarize_big_ndarray(v) for k, v in value.items()} 63 | else: 64 | return value 65 | 66 | 67 | def hash_wave_floats_from_wav_bytes(wav_bytes: bytes) -> str: 68 | """.wavファイルバイト列から音声波形を抽出しハッシュ化する""" 69 | wave = sf.read(io.BytesIO(wav_bytes))[0].tolist() 70 | # NOTE: Linux-Windows 数値精度問題に対するワークアラウンド 71 | wave = round_floats(wave, 2) 72 | return "MD5:" + hashlib.md5(np.array(wave).tobytes()).hexdigest() 73 | -------------------------------------------------------------------------------- /tools/create_venv_and_generate_licenses.bash: -------------------------------------------------------------------------------- 1 | # 仮想環境を作ってrequirements.txtをインストールし、ライセンス一覧を生成する 2 | 3 | set -eux 4 | 5 | if [ ! -v OUTPUT_LICENSE_JSON_PATH ]; then 6 | echo "OUTPUT_LICENSE_JSON_PATHが未定義です" 7 | exit 1 8 | fi 9 | 10 | uv venv "licenses_venv" 11 | export VIRTUAL_ENV="licenses_venv" 12 | uv sync --active 13 | # requirements-dev.txt でバージョン指定されている pip-licenses をインストールする 14 | uv pip install "$(grep pip-licenses requirements-dev.txt | cut -f 1 -d ';')" 15 | uv run --active tools/generate_licenses.py > "${OUTPUT_LICENSE_JSON_PATH}" 16 | 17 | rm -rf $VIRTUAL_ENV 18 | unset VIRTUAL_ENV 19 | -------------------------------------------------------------------------------- /tools/generate_filemap.py: -------------------------------------------------------------------------------- 1 | """'ResourceManager'が参照するfilemapを予め生成する。""" 2 | 3 | import json 4 | import os 5 | from argparse import ArgumentParser 6 | from collections.abc import Generator 7 | from hashlib import sha256 8 | from pathlib import Path, PurePosixPath 9 | 10 | FILEMAP_FILENAME = "filemap.json" 11 | DEFAULT_TARGET_SUFFIX = ["png", "wav"] 12 | 13 | 14 | # WindowsとPOSIXで同じファイルが生成されるようにPurePosixPathに変換してから文字列にする。 15 | def _to_posix_str_path(path: Path) -> str: 16 | return str(PurePosixPath(path)) 17 | 18 | 19 | def _make_hash(file: Path) -> str: 20 | digest = sha256(file.read_bytes()).digest() 21 | return digest.hex() 22 | 23 | 24 | def _walk_target_dir(target_dir: Path) -> Generator[Path, None, None]: 25 | for root, _, files in os.walk(target_dir): 26 | for file in files: 27 | yield Path(root, file) 28 | 29 | 30 | def _generate_path_to_hash_dict( 31 | target_dir: Path, target_suffix: list[str] 32 | ) -> dict[str, str]: 33 | suffix = tuple(target_suffix) 34 | return { 35 | _to_posix_str_path(filepath.relative_to(target_dir)): _make_hash(filepath) 36 | for filepath in _walk_target_dir(target_dir) 37 | if filepath.suffix.endswith(suffix) 38 | } 39 | 40 | 41 | if __name__ == "__main__": 42 | parser = ArgumentParser() 43 | parser.add_argument( 44 | "--target_dir", type=Path, required=True, help="filemapを作成するディレクトリ" 45 | ) 46 | parser.add_argument( 47 | "--target_suffix", 48 | nargs="+", 49 | default=DEFAULT_TARGET_SUFFIX, 50 | help=f"filemapに登録するファイルの拡張子\nデフォルトは{', '.join(DEFAULT_TARGET_SUFFIX)}", 51 | ) 52 | args = parser.parse_args() 53 | 54 | target_dir: Path = args.target_dir 55 | if not target_dir.is_dir(): 56 | raise Exception(f"{target_dir}はディレクトリではありません") 57 | 58 | save_path = target_dir / FILEMAP_FILENAME 59 | path_to_hash = _generate_path_to_hash_dict(target_dir, args.target_suffix) 60 | save_path.write_text(json.dumps(path_to_hash, ensure_ascii=False), encoding="utf-8") 61 | -------------------------------------------------------------------------------- /tools/merge_engine_manifest.py: -------------------------------------------------------------------------------- 1 | """エンジンマニフェストをマージする。""" 2 | 3 | import argparse 4 | import json 5 | from pathlib import Path 6 | 7 | JsonValue = str | int | float 8 | 9 | 10 | def _merge_json_string(src: str, dst: str) -> str: 11 | src_json: dict[str, JsonValue | dict[str, dict[str, JsonValue]]] = json.loads(src) 12 | dst_json: dict[str, JsonValue | dict[str, dict[str, JsonValue]]] = json.loads(dst) 13 | 14 | for key, dst_value in dst_json.items(): 15 | assert key in src_json, f"Key {key} is not found in src_json" 16 | 17 | # `manage_library` のみdictなので特別に処理 18 | if key == "supported_features": 19 | assert isinstance(dst_value, dict) 20 | src_value = src_json[key] 21 | assert isinstance(src_value, dict) 22 | src_value.update(dst_value) 23 | 24 | else: 25 | src_value = src_json[key] 26 | assert isinstance(src_value, JsonValue) 27 | assert isinstance(dst_value, JsonValue) 28 | src_json[key] = dst_value 29 | 30 | return json.dumps(src_json, ensure_ascii=False) 31 | 32 | 33 | def _merge_engine_manifest(src_path: Path, dst_path: Path, output_path: Path) -> None: 34 | src = src_path.read_text(encoding="utf-8") 35 | dst = dst_path.read_text(encoding="utf-8") 36 | merged = _merge_json_string(src, dst) 37 | output_path.write_text(merged, encoding="utf-8") 38 | 39 | 40 | if __name__ == "__main__": 41 | parser = argparse.ArgumentParser() 42 | parser.add_argument("src_path", type=Path) 43 | parser.add_argument("dst_path", type=Path) 44 | parser.add_argument("output_path", type=Path) 45 | args = parser.parse_args() 46 | _merge_engine_manifest(args.src_path, args.dst_path, args.output_path) 47 | -------------------------------------------------------------------------------- /tools/merge_update_infos.py: -------------------------------------------------------------------------------- 1 | """更新履歴をマージする。""" 2 | 3 | import argparse 4 | import json 5 | from collections import OrderedDict 6 | from pathlib import Path 7 | 8 | 9 | def merge_json_string(src: str, dst: str) -> str: 10 | """ 11 | バージョンが同じ場合は要素を結合する。 12 | 13 | >>> src = '[{"version": "0.0.1", "a": ["a1"], "b": ["b1", "b2"]}]' 14 | >>> dst = '[{"version": "0.0.1", "a": ["a2"], "b": ["b1", "b3"]}]' 15 | >>> merge_json_string(src, dst) 16 | '[{"version": "0.0.1", "a": ["a1", "a2"], "b": ["b1", "b2", "b3"]}]' 17 | 18 | バージョンが無かった場合は無視される 19 | >>> src = '[{"version": "1"}]' 20 | >>> dst = '[{"version": "1"}, {"version": "2"}]' 21 | >>> merge_json_string(src, dst) 22 | '[{"version": "1"}]' 23 | """ 24 | # FIXME: バリデーションする 25 | # TODO: `str | list[str]`だけど`str`が来るとエラーになるのでならないようにしたい 26 | src_json: list[dict[str, str | list[str]]] = json.loads(src) 27 | dst_json: list[dict[str, str | list[str]]] = json.loads(dst) 28 | 29 | for src_item in src_json: 30 | for dst_item in dst_json: 31 | if src_item["version"] == dst_item["version"]: 32 | for key in src_item: 33 | if key == "version": 34 | continue 35 | 36 | src_value = src_item[key] 37 | dst_value = dst_item[key] 38 | assert isinstance(src_value, list) 39 | assert isinstance(dst_value, list) 40 | 41 | # 異なるものがあった場合だけ後ろに付け足す 42 | src_item[key] = list(OrderedDict.fromkeys(src_value + dst_value)) 43 | 44 | return json.dumps(src_json, ensure_ascii=False) 45 | 46 | 47 | def _merge_update_infos(src_path: Path, dst_path: Path, output_path: Path) -> None: 48 | src = src_path.read_text(encoding="utf-8") 49 | dst = dst_path.read_text(encoding="utf-8") 50 | merged = merge_json_string(src, dst) 51 | output_path.write_text(merged, encoding="utf-8") 52 | 53 | 54 | if __name__ == "__main__": 55 | parser = argparse.ArgumentParser() 56 | parser.add_argument("src_path", type=Path) 57 | parser.add_argument("dst_path", type=Path) 58 | parser.add_argument("output_path", type=Path) 59 | args = parser.parse_args() 60 | _merge_update_infos(args.src_path, args.dst_path, args.output_path) 61 | -------------------------------------------------------------------------------- /tools/modify_pyinstaller.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # PyInstallerをカスタマイズしてから再インストールする 4 | # 良いGPUが自動的に選択されるようにしている 5 | # https://github.com/VOICEVOX/voicevox_engine/issues/502 6 | 7 | # 自前ビルドすることでブートローダーのハッシュ値が変わってウイルス判定を回避する効果もあるかも 8 | 9 | set -eux 10 | 11 | pyinstaller_version=$(uv run pyinstaller -v) 12 | tempdir=$(mktemp -dt modify_pyinstaller.XXXXXXXX) 13 | trap 'rm -rf "$tempdir"' EXIT 14 | git clone https://github.com/pyinstaller/pyinstaller.git "$tempdir" -b "v$pyinstaller_version" --depth 1 15 | cat > "$tempdir/bootloader/src/symbols.c" << EOF 16 | #ifdef _WIN32 17 | #include 18 | 19 | // https://docs.nvidia.com/gameworks/content/technologies/desktop/optimus.htm 20 | __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; 21 | 22 | // https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/ 23 | __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; 24 | #endif 25 | EOF 26 | (cd "$tempdir/bootloader" && python ./waf all --msvc_targets="x64") 27 | uv pip install -U "$tempdir" # TODO: 可能であればuv addでやる 28 | -------------------------------------------------------------------------------- /tools/process_voicevox_resource.bash: -------------------------------------------------------------------------------- 1 | set -eux 2 | 3 | if [ ! -v DOWNLOAD_RESOURCE_PATH ]; then 4 | echo "DOWNLOAD_RESOURCE_PATHが未定義です" 5 | exit 1 6 | fi 7 | 8 | # ダミーのキャラクター情報を置き換える 9 | rm -r resources/character_info 10 | cp -r "${DOWNLOAD_RESOURCE_PATH}/character_info" resources/character_info 11 | 12 | # キャラクター情報を前処理する 13 | uv run "${DOWNLOAD_RESOURCE_PATH}/scripts/clean_character_info.py" \ 14 | --character_info_dir resources/character_info/ 15 | 16 | # エンジンマニフェストを統合する 17 | uv run tools/merge_engine_manifest.py \ 18 | engine_manifest.json \ 19 | "${DOWNLOAD_RESOURCE_PATH}/engine/engine_manifest.json" \ 20 | engine_manifest.json 21 | 22 | # エンジンとリソースの更新情報を統合する 23 | uv run tools/merge_update_infos.py \ 24 | resources/engine_manifest_assets/update_infos.json \ 25 | "${DOWNLOAD_RESOURCE_PATH}/engine/engine_manifest_assets/update_infos.json" \ 26 | resources/engine_manifest_assets/update_infos.json 27 | 28 | # リソースのマニフェストアセットをエンジンのディレクトリへ複製する 29 | for f in "${DOWNLOAD_RESOURCE_PATH}"/engine/engine_manifest_assets/*; do 30 | if [ "$(basename "${f}")" != "update_infos.json" ]; then 31 | cp "${f}" ./resources/engine_manifest_assets/ 32 | fi 33 | done 34 | -------------------------------------------------------------------------------- /voicevox_engine/__init__.py: -------------------------------------------------------------------------------- 1 | """VOICEVOX ENGINE。""" 2 | 3 | __version__ = "latest" 4 | -------------------------------------------------------------------------------- /voicevox_engine/app/dependencies.py: -------------------------------------------------------------------------------- 1 | """FastAPI dependencies""" 2 | 3 | from collections.abc import Callable, Coroutine 4 | from typing import Any, TypeAlias 5 | 6 | from fastapi import HTTPException 7 | 8 | VerifyMutabilityAllowed: TypeAlias = Callable[[], Coroutine[Any, Any, None]] 9 | 10 | 11 | def generate_mutability_allowed_verifier( 12 | disable_mutable_api: bool, 13 | ) -> VerifyMutabilityAllowed: 14 | """verify_mutability_allowed 関数(データ変更の許可を確認する関数)を生成する。""" 15 | 16 | async def verify_mutability_allowed() -> None: 17 | if disable_mutable_api: 18 | msg = "エンジンの静的なデータを変更するAPIは無効化されています" 19 | raise HTTPException(status_code=403, detail=msg) 20 | else: 21 | pass 22 | 23 | return verify_mutability_allowed 24 | -------------------------------------------------------------------------------- /voicevox_engine/app/global_exceptions.py: -------------------------------------------------------------------------------- 1 | """グローバルな例外ハンドラの定義と登録""" 2 | 3 | from fastapi import FastAPI, Request 4 | from fastapi.responses import JSONResponse 5 | 6 | from voicevox_engine.core.core_initializer import CoreNotFound 7 | from voicevox_engine.tts_pipeline.tts_engine import ( 8 | MockTTSEngineNotFound, 9 | TTSEngineNotFound, 10 | ) 11 | 12 | 13 | def configure_global_exception_handlers(app: FastAPI) -> FastAPI: 14 | """グローバルな例外ハンドラを app へ設定する。""" 15 | 16 | # 指定されたコアが見つからないエラー 17 | @app.exception_handler(CoreNotFound) 18 | async def cnf_exception_handler(request: Request, e: CoreNotFound) -> JSONResponse: 19 | return JSONResponse(status_code=422, content={"message": f"{str(e)}"}) 20 | 21 | # 指定されたエンジンが見つからないエラー 22 | @app.exception_handler(TTSEngineNotFound) 23 | async def no_engine_exception_handler( 24 | request: Request, e: TTSEngineNotFound 25 | ) -> JSONResponse: 26 | msg = f"バージョン {e.version} のコアが見つかりません。" 27 | return JSONResponse(status_code=422, content={"message": msg}) 28 | 29 | # 指定されたモック版エンジンが見つからないエラー 30 | @app.exception_handler(MockTTSEngineNotFound) 31 | async def no_mock_exception_handler( 32 | request: Request, e: MockTTSEngineNotFound 33 | ) -> JSONResponse: 34 | msg = "モックが見つかりません。エンジンの起動引数 `--enable_mock` を確認してください。" 35 | return JSONResponse(status_code=422, content={"message": msg}) 36 | 37 | return app 38 | -------------------------------------------------------------------------------- /voicevox_engine/app/openapi_schema.py: -------------------------------------------------------------------------------- 1 | """OpenAPI schema の設定""" 2 | 3 | from typing import Any 4 | 5 | from fastapi import FastAPI 6 | from fastapi.openapi.utils import get_openapi 7 | from fastapi.routing import APIRoute 8 | from pydantic import BaseModel 9 | 10 | from voicevox_engine.library.model import BaseLibraryInfo, VvlibManifest 11 | 12 | 13 | def simplify_operation_ids(app: FastAPI) -> FastAPI: 14 | """operation ID を簡略化してAPIクライアントで生成される関数名をシンプルにする。""" 15 | for route in app.routes: 16 | if isinstance(route, APIRoute): 17 | route.operation_id = route.name 18 | 19 | return app 20 | 21 | 22 | def configure_openapi_schema(app: FastAPI, manage_library: bool | None) -> FastAPI: 23 | """自動生成された OpenAPI schema へカスタム属性を追加する。""" 24 | 25 | # BaseLibraryInfo/VvlibManifestモデルはAPIとして表には出ないが、エディタ側で利用したいので、手動で追加する 26 | # ref: https://fastapi.tiangolo.com/advanced/extending-openapi/#modify-the-openapi-schema 27 | def custom_openapi() -> Any: 28 | if app.openapi_schema: 29 | return app.openapi_schema 30 | openapi_schema = get_openapi( 31 | title=app.title, 32 | version=app.version, 33 | openapi_version=app.openapi_version, 34 | summary=app.summary, 35 | description=app.description, 36 | terms_of_service=app.terms_of_service, 37 | contact=app.contact, 38 | license_info=app.license_info, 39 | routes=app.routes, 40 | webhooks=app.webhooks.routes, 41 | tags=app.openapi_tags, 42 | servers=app.servers, 43 | separate_input_output_schemas=app.separate_input_output_schemas, 44 | ) 45 | if manage_library: 46 | additional_models: list[type[BaseModel]] = [ 47 | BaseLibraryInfo, 48 | VvlibManifest, 49 | ] 50 | for model in additional_models: 51 | # ref_templateを指定しない場合、definitionsを参照してしまうので、手動で指定する 52 | schema = model.model_json_schema( 53 | ref_template="#/components/schemas/{model}" 54 | ) 55 | # definitionsは既存のモデルを重複して定義するため、不要なので削除 56 | if "$defs" in schema: 57 | del schema["$defs"] 58 | openapi_schema["components"]["schemas"][schema["title"]] = schema 59 | app.openapi_schema = openapi_schema 60 | return openapi_schema 61 | 62 | app.openapi = custom_openapi # type: ignore[method-assign] 63 | 64 | return app 65 | -------------------------------------------------------------------------------- /voicevox_engine/app/routers/engine_info.py: -------------------------------------------------------------------------------- 1 | """エンジンの情報機能を提供する API Router""" 2 | 3 | from fastapi import APIRouter 4 | 5 | from voicevox_engine import __version__ 6 | from voicevox_engine.engine_manifest import EngineManifest 7 | 8 | 9 | def generate_engine_info_router( 10 | core_version_list: list[str], engine_manifest_data: EngineManifest 11 | ) -> APIRouter: 12 | """エンジン情報 API Router を生成する""" 13 | router = APIRouter(tags=["その他"]) 14 | 15 | @router.get("/version") 16 | async def version() -> str: 17 | """エンジンのバージョンを取得します。""" 18 | return __version__ 19 | 20 | @router.get("/core_versions") 21 | async def core_versions() -> list[str]: 22 | """利用可能なコアのバージョン一覧を取得します。""" 23 | return core_version_list 24 | 25 | @router.get("/engine_manifest") 26 | async def engine_manifest() -> EngineManifest: 27 | """エンジンマニフェストを取得します。""" 28 | return engine_manifest_data 29 | 30 | return router 31 | -------------------------------------------------------------------------------- /voicevox_engine/app/routers/portal_page.py: -------------------------------------------------------------------------------- 1 | """ポータルページ機能を提供する API Router""" 2 | 3 | from fastapi import APIRouter 4 | from fastapi.responses import HTMLResponse 5 | 6 | from voicevox_engine.engine_manifest import EngineName 7 | 8 | 9 | def generate_portal_page_router(engine_name: EngineName) -> APIRouter: 10 | """ポータルページ API Router を生成する""" 11 | router = APIRouter() 12 | 13 | @router.get("/", response_class=HTMLResponse, tags=["その他"]) 14 | async def get_portal_page() -> str: 15 | """ポータルページを返します。""" 16 | return f""" 17 | 18 | 19 | {engine_name} 20 | 21 | 22 |

{engine_name}

23 | {engine_name} へようこそ! 24 | 28 | """ 29 | 30 | return router 31 | -------------------------------------------------------------------------------- /voicevox_engine/app/routers/setting.py: -------------------------------------------------------------------------------- 1 | """設定機能を提供する API Router""" 2 | 3 | from typing import Annotated 4 | 5 | from fastapi import APIRouter, Depends, Form, Request, Response 6 | from fastapi.templating import Jinja2Templates 7 | from jinja2 import Environment, FileSystemLoader 8 | from pydantic.json_schema import SkipJsonSchema 9 | 10 | from voicevox_engine.engine_manifest import BrandName 11 | from voicevox_engine.setting.model import CorsPolicyMode 12 | from voicevox_engine.setting.setting_manager import Setting, SettingHandler 13 | from voicevox_engine.utility.path_utility import resource_root 14 | 15 | from ..dependencies import VerifyMutabilityAllowed 16 | 17 | _setting_ui_template = Jinja2Templates( 18 | env=Environment( 19 | variable_start_string="", 20 | variable_end_string="", 21 | loader=FileSystemLoader(resource_root()), 22 | ), 23 | ) 24 | 25 | 26 | def generate_setting_router( 27 | setting_loader: SettingHandler, 28 | brand_name: BrandName, 29 | verify_mutability: VerifyMutabilityAllowed, 30 | ) -> APIRouter: 31 | """設定 API Router を生成する""" 32 | router = APIRouter(tags=["設定"]) 33 | 34 | @router.get("/setting", response_class=Response) 35 | def setting_get(request: Request) -> Response: 36 | """設定ページを返します。""" 37 | settings = setting_loader.load() 38 | 39 | cors_policy_mode = settings.cors_policy_mode 40 | allow_origin = settings.allow_origin 41 | 42 | if allow_origin is None: 43 | allow_origin = "" 44 | 45 | return _setting_ui_template.TemplateResponse( 46 | request=request, 47 | name="setting_ui_template.html", 48 | context={ 49 | "brand_name": brand_name, 50 | "cors_policy_mode": cors_policy_mode.value, 51 | "allow_origin": allow_origin, 52 | }, 53 | ) 54 | 55 | @router.post("/setting", status_code=204, dependencies=[Depends(verify_mutability)]) 56 | def setting_post( 57 | cors_policy_mode: Annotated[CorsPolicyMode, Form()], 58 | allow_origin: Annotated[str | SkipJsonSchema[None], Form()] = None, 59 | ) -> None: 60 | """設定を更新します。""" 61 | settings = Setting( 62 | cors_policy_mode=cors_policy_mode, 63 | allow_origin=allow_origin, 64 | ) 65 | 66 | # 更新した設定へ上書き 67 | setting_loader.save(settings) 68 | 69 | return router 70 | -------------------------------------------------------------------------------- /voicevox_engine/dev/song_engine/mock.py: -------------------------------------------------------------------------------- 1 | """SongEngine のモック""" 2 | 3 | from ...tts_pipeline.song_engine import ( 4 | SongEngine, 5 | ) 6 | from ..core.mock import MockCoreWrapper 7 | 8 | 9 | class MockSongEngine(SongEngine): 10 | """製品版コア無しに歌声音声合成可能なモック版SongEngine""" 11 | 12 | def __init__(self) -> None: 13 | super().__init__(MockCoreWrapper()) 14 | -------------------------------------------------------------------------------- /voicevox_engine/dev/tts_engine/mock.py: -------------------------------------------------------------------------------- 1 | """TTSEngine のモック""" 2 | 3 | import copy 4 | from typing import Final 5 | 6 | import numpy as np 7 | from numpy.typing import NDArray 8 | from pyopenjtalk import tts 9 | 10 | from ...metas.Metas import StyleId 11 | from ...model import AudioQuery 12 | from ...tts_pipeline.audio_postprocessing import raw_wave_to_output_wave 13 | from ...tts_pipeline.tts_engine import ( 14 | TTSEngine, 15 | to_flatten_moras, 16 | ) 17 | from ..core.mock import MockCoreWrapper 18 | 19 | 20 | class MockTTSEngine(TTSEngine): 21 | """製品版コア無しに音声合成が可能なモック版TTSEngine""" 22 | 23 | def __init__(self) -> None: 24 | super().__init__(MockCoreWrapper()) 25 | 26 | def synthesize_wave( 27 | self, 28 | query: AudioQuery, 29 | style_id: StyleId, 30 | enable_interrogative_upspeak: bool, 31 | ) -> NDArray[np.float32]: 32 | """音声合成用のクエリに含まれる読み仮名に基づいてOpenJTalkで音声波形を生成する。モーラごとの調整は反映されない。""" 33 | # モーフィング時などに同一参照のqueryで複数回呼ばれる可能性があるので、元の引数のqueryに破壊的変更を行わない 34 | query = copy.deepcopy(query) 35 | 36 | # recall text in katakana 37 | flatten_moras = to_flatten_moras(query.accent_phrases) 38 | kana_text = "".join([mora.text for mora in flatten_moras]) 39 | 40 | raw_wave, sr_raw_wave = self.forward(kana_text) 41 | wave = raw_wave_to_output_wave(query, raw_wave, sr_raw_wave) 42 | return wave 43 | 44 | def forward(self, text: str) -> tuple[NDArray[np.float32], int]: 45 | """文字列から pyopenjtalk を用いて音声を合成する。""" 46 | OJT_SAMPLING_RATE: Final = 48000 47 | OJT_AMPLITUDE_MAX: Final = 2 ** (16 - 1) 48 | raw_wave: NDArray[np.float64] = tts(text)[0] 49 | raw_wave /= OJT_AMPLITUDE_MAX 50 | return raw_wave.astype(np.float32), OJT_SAMPLING_RATE 51 | -------------------------------------------------------------------------------- /voicevox_engine/library/model.py: -------------------------------------------------------------------------------- 1 | """ 2 | ライブラリ機能に関して API と ENGINE 内部実装が共有するモデル(データ構造) 3 | 4 | モデルの注意点は `voicevox_engine/model.py` の module docstring を確認すること。 5 | """ 6 | 7 | from pydantic import BaseModel, Field, StrictStr 8 | 9 | from voicevox_engine.metas.Metas import Speaker, SpeakerInfo 10 | 11 | 12 | class LibrarySpeaker(BaseModel): 13 | """音声ライブラリに含まれるキャラクターの情報。""" 14 | 15 | speaker: Speaker = Field(description="キャラクター情報") 16 | speaker_info: SpeakerInfo = Field(description="キャラクターの追加情報") 17 | 18 | 19 | class BaseLibraryInfo(BaseModel): 20 | """音声ライブラリの情報。""" 21 | 22 | name: str = Field(description="音声ライブラリの名前") 23 | uuid: str = Field(description="音声ライブラリのUUID") 24 | version: str = Field(description="音声ライブラリのバージョン") 25 | download_url: str = Field(description="音声ライブラリのダウンロードURL") 26 | bytes: int = Field(description="音声ライブラリのバイト数") 27 | speakers: list[LibrarySpeaker] = Field( 28 | description="音声ライブラリに含まれるキャラクターのリスト" 29 | ) 30 | 31 | 32 | # 今後InstalledLibraryInfo同様に拡張する可能性を考え、モデルを分けている 33 | class DownloadableLibraryInfo(BaseLibraryInfo): 34 | """ダウンロード可能な音声ライブラリの情報。""" 35 | 36 | pass 37 | 38 | 39 | class InstalledLibraryInfo(BaseLibraryInfo): 40 | """インストール済み音声ライブラリの情報。""" 41 | 42 | uninstallable: bool = Field(description="アンインストール可能かどうか") 43 | 44 | 45 | class VvlibManifest(BaseModel): 46 | """vvlib(VOICEVOX Library)に関する情報。""" 47 | 48 | manifest_version: StrictStr = Field(description="マニフェストバージョン") 49 | name: StrictStr = Field(description="音声ライブラリ名") 50 | version: StrictStr = Field(description="音声ライブラリバージョン") 51 | uuid: StrictStr = Field(description="音声ライブラリのUUID") 52 | brand_name: StrictStr = Field(description="エンジンのブランド名") 53 | engine_name: StrictStr = Field(description="エンジン名") 54 | engine_uuid: StrictStr = Field(description="エンジンのUUID") 55 | -------------------------------------------------------------------------------- /voicevox_engine/metas/Metas.py: -------------------------------------------------------------------------------- 1 | """キャラクター情報とキャラクターメタ情報""" 2 | 3 | from typing import Literal, NewType 4 | 5 | from pydantic import BaseModel, Field 6 | from pydantic.json_schema import SkipJsonSchema 7 | 8 | # NOTE: 循環importを防ぐためにとりあえずここに書いている 9 | # FIXME: 他のmodelに依存せず、全modelから参照できる場所に配置する 10 | StyleId = NewType("StyleId", int) 11 | StyleType = Literal["talk", "singing_teacher", "frame_decode", "sing"] 12 | 13 | 14 | class SpeakerStyle(BaseModel): 15 | """キャラクターのスタイル情報""" 16 | 17 | name: str = Field(description="スタイル名") 18 | id: StyleId = Field(description="スタイルID") 19 | type: StyleType = Field( 20 | default="talk", 21 | description=( 22 | "スタイルの種類。" 23 | "talk:音声合成クエリの作成と音声合成が可能。" 24 | "singing_teacher:歌唱音声合成用のクエリの作成が可能。" 25 | "frame_decode:歌唱音声合成が可能。" 26 | "sing:歌唱音声合成用のクエリの作成と歌唱音声合成が可能。" 27 | ), 28 | ) 29 | 30 | 31 | class SpeakerSupportedFeatures(BaseModel): 32 | """キャラクターの対応機能の情報""" 33 | 34 | permitted_synthesis_morphing: Literal["ALL", "SELF_ONLY", "NOTHING"] = Field( 35 | description=( 36 | "モーフィング機能への対応。" 37 | "'ALL' は「全て許可」、'SELF_ONLY' は「同じキャラクター内でのみ許可」、'NOTHING' は「全て禁止」" 38 | ), 39 | default="ALL", 40 | ) 41 | 42 | 43 | class Speaker(BaseModel): 44 | """キャラクター情報""" 45 | 46 | name: str = Field(description="名前") 47 | speaker_uuid: str = Field(description="キャラクターのUUID") 48 | styles: list[SpeakerStyle] = Field(description="スタイルの一覧") 49 | version: str = Field(description="キャラクターのバージョン") 50 | supported_features: SpeakerSupportedFeatures = Field( 51 | description="キャラクターの対応機能", default_factory=SpeakerSupportedFeatures 52 | ) 53 | 54 | 55 | class StyleInfo(BaseModel): 56 | """スタイルの追加情報""" 57 | 58 | id: StyleId = Field(description="スタイルID") 59 | icon: str = Field( 60 | description="このスタイルのアイコンをbase64エンコードしたもの、あるいはURL" 61 | ) 62 | portrait: str | SkipJsonSchema[None] = Field( 63 | default=None, 64 | description="このスタイルの立ち絵画像をbase64エンコードしたもの、あるいはURL", 65 | ) 66 | voice_samples: list[str] = Field( 67 | description="サンプル音声をbase64エンコードしたもの、あるいはURL" 68 | ) 69 | 70 | 71 | class SpeakerInfo(BaseModel): 72 | """キャラクターの追加情報""" 73 | 74 | policy: str = Field(description="policy.md") 75 | portrait: str = Field( 76 | description="立ち絵画像をbase64エンコードしたもの、あるいはURL" 77 | ) 78 | style_infos: list[StyleInfo] = Field(description="スタイルの追加情報") 79 | -------------------------------------------------------------------------------- /voicevox_engine/model.py: -------------------------------------------------------------------------------- 1 | """ 2 | API と ENGINE 内部実装が共有するモデル 3 | 4 | このモジュールで定義されるモデル(データ構造)は API と ENGINE の 2 箇所から使われる。そのため 5 | - モデルの変更は API 変更となるため慎重に検討する。 6 | - モデルの docstring や Field は API スキーマとして使われるため、ユーザー向けに丁寧に書く。 7 | - モデルクラスは FastAPI の制約から `BaseModel` を継承しなければならない。 8 | """ 9 | 10 | from pydantic import BaseModel, Field 11 | from pydantic.json_schema import SkipJsonSchema 12 | 13 | from voicevox_engine.tts_pipeline.model import AccentPhrase 14 | 15 | 16 | class AudioQuery(BaseModel): 17 | """音声合成用のクエリ。""" 18 | 19 | accent_phrases: list[AccentPhrase] = Field(description="アクセント句のリスト") 20 | speedScale: float = Field(description="全体の話速") 21 | pitchScale: float = Field(description="全体の音高") 22 | intonationScale: float = Field(description="全体の抑揚") 23 | volumeScale: float = Field(description="全体の音量") 24 | prePhonemeLength: float = Field(description="音声の前の無音時間") 25 | postPhonemeLength: float = Field(description="音声の後の無音時間") 26 | pauseLength: float | None = Field( 27 | default=None, 28 | description="句読点などの無音時間。nullのときは無視される。デフォルト値はnull", 29 | ) 30 | pauseLengthScale: float = Field( 31 | default=1, description="句読点などの無音時間(倍率)。デフォルト値は1" 32 | ) 33 | outputSamplingRate: int = Field(description="音声データの出力サンプリングレート") 34 | outputStereo: bool = Field(description="音声データをステレオ出力するか否か") 35 | kana: str | SkipJsonSchema[None] = Field( 36 | default=None, 37 | description="[読み取り専用]AquesTalk 風記法によるテキスト。音声合成用のクエリとしては無視される", 38 | ) 39 | 40 | def __hash__(self) -> int: 41 | """内容に対して一意なハッシュ値を返す。""" 42 | # NOTE: lru_cache がユースケースのひとつ 43 | items = [ 44 | (k, tuple(v)) if isinstance(v, list) else (k, v) 45 | for k, v in self.__dict__.items() 46 | ] 47 | return hash(tuple(sorted(items))) 48 | -------------------------------------------------------------------------------- /voicevox_engine/morphing/model.py: -------------------------------------------------------------------------------- 1 | """ 2 | モーフィング機能に関して API と ENGINE 内部実装が共有するモデル(データ構造) 3 | 4 | モデルの注意点は `voicevox_engine/model.py` の module docstring を確認すること。 5 | """ 6 | 7 | from pydantic import BaseModel, Field 8 | 9 | 10 | class MorphableTargetInfo(BaseModel): 11 | """モーフィング相手としての情報。""" 12 | 13 | is_morphable: bool = Field( 14 | description="指定したキャラクターに対してモーフィングの可否" 15 | ) 16 | # FIXME: add reason property 17 | # reason: str | None = Field(description="is_morphableがfalseである場合、その理由") 18 | -------------------------------------------------------------------------------- /voicevox_engine/preset/model.py: -------------------------------------------------------------------------------- 1 | """ 2 | プリセット機能に関して API と ENGINE 内部実装が共有するモデル(データ構造) 3 | 4 | モデルの注意点は `voicevox_engine/model.py` の module docstring を確認すること。 5 | """ 6 | 7 | from pydantic import BaseModel, Field 8 | from pydantic.json_schema import SkipJsonSchema 9 | 10 | from voicevox_engine.metas.Metas import StyleId 11 | 12 | 13 | class Preset(BaseModel): 14 | """プリセット情報。""" 15 | 16 | id: int = Field(description="プリセットID") 17 | name: str = Field(description="プリセット名") 18 | speaker_uuid: str = Field(description="キャラクターのUUID") 19 | style_id: StyleId = Field(description="スタイルID") 20 | speedScale: float = Field(description="全体の話速") 21 | pitchScale: float = Field(description="全体の音高") 22 | intonationScale: float = Field(description="全体の抑揚") 23 | volumeScale: float = Field(description="全体の音量") 24 | prePhonemeLength: float = Field(description="音声の前の無音時間") 25 | postPhonemeLength: float = Field(description="音声の後の無音時間") 26 | pauseLength: float | SkipJsonSchema[None] = Field( 27 | default=None, description="句読点などの無音時間" 28 | ) 29 | pauseLengthScale: float = Field( 30 | default=1, description="句読点などの無音時間(倍率)" 31 | ) 32 | -------------------------------------------------------------------------------- /voicevox_engine/setting/model.py: -------------------------------------------------------------------------------- 1 | """ 2 | エンジン設定機能に関して API と ENGINE 内部実装が共有するモデル(データ構造) 3 | 4 | モデルの注意点は `voicevox_engine/model.py` の module docstring を確認すること。 5 | """ 6 | 7 | from enum import Enum 8 | 9 | 10 | class CorsPolicyMode(str, Enum): 11 | """CORSの許可モード。""" 12 | 13 | all = "all" # 全てのオリジンからのリクエストを許可 14 | localapps = "localapps" # ローカルアプリケーションからのリクエストを許可 15 | -------------------------------------------------------------------------------- /voicevox_engine/setting/setting_manager.py: -------------------------------------------------------------------------------- 1 | """エンジン設定関連の処理""" 2 | 3 | from dataclasses import dataclass 4 | from enum import Enum 5 | from pathlib import Path 6 | from typing import Any 7 | 8 | import yaml 9 | from pydantic import TypeAdapter 10 | 11 | from ..utility.path_utility import get_save_dir 12 | from .model import CorsPolicyMode 13 | 14 | 15 | @dataclass(frozen=True) 16 | class Setting: 17 | """エンジンの設定情報""" 18 | 19 | cors_policy_mode: CorsPolicyMode # リソース共有ポリシー 20 | allow_origin: str | None = None # 許可するオリジン 21 | 22 | 23 | _setting_adapter = TypeAdapter(Setting) 24 | 25 | 26 | USER_SETTING_PATH: Path = get_save_dir() / "setting.yml" 27 | 28 | 29 | class SettingHandler: 30 | """設定ファイルの読み書きを管理する。""" 31 | 32 | # FIXME: クラス名を機能に合わせたものへリネームする(例: `SettingManager`) 33 | 34 | def __init__(self, setting_file_path: Path) -> None: 35 | """ 36 | 設定ファイルの管理 37 | 38 | Parameters 39 | ---------- 40 | setting_file_path : Path 41 | 設定ファイルのパス。存在しない場合はデフォルト値を設定。 42 | """ 43 | self.setting_file_path = setting_file_path 44 | 45 | def load(self) -> Setting: 46 | """設定値をファイルから読み込む。""" 47 | if not self.setting_file_path.is_file(): 48 | # 設定ファイルが存在しないためデフォルト値を取得 49 | setting = {"allow_origin": None, "cors_policy_mode": "localapps"} 50 | else: 51 | # 指定された設定ファイルから値を取得 52 | # FIXME: 例外処理を追加する 53 | setting = yaml.safe_load(self.setting_file_path.read_text(encoding="utf-8")) 54 | 55 | return _setting_adapter.validate_python(setting) 56 | 57 | def save(self, settings: Setting) -> None: 58 | """設定値をファイルへ書き込む。""" 59 | settings_dict: dict[str, Any] = _setting_adapter.dump_python(settings) 60 | 61 | if isinstance(settings_dict["cors_policy_mode"], Enum): 62 | settings_dict["cors_policy_mode"] = settings_dict["cors_policy_mode"].value 63 | 64 | with open(self.setting_file_path, mode="w", encoding="utf-8") as f: 65 | yaml.safe_dump(settings_dict, f) 66 | -------------------------------------------------------------------------------- /voicevox_engine/tts_pipeline/audio_postprocessing.py: -------------------------------------------------------------------------------- 1 | """音声波形を加工する。""" 2 | 3 | import numpy as np 4 | from numpy.typing import NDArray 5 | from soxr import resample 6 | 7 | from ..model import AudioQuery 8 | from .model import ( 9 | FrameAudioQuery, 10 | ) 11 | 12 | 13 | def raw_wave_to_output_wave( 14 | query: AudioQuery | FrameAudioQuery, wave: NDArray[np.float32], sr_wave: int 15 | ) -> NDArray[np.float32]: 16 | """生音声波形に音声合成用のクエリを適用して出力音声波形を生成する""" 17 | wave = _apply_volume_scale(wave, query) 18 | wave = _apply_output_sampling_rate(wave, sr_wave, query) 19 | wave = _apply_output_stereo(wave, query) 20 | return wave 21 | 22 | 23 | def _apply_volume_scale( 24 | wave: NDArray[np.float32], query: AudioQuery | FrameAudioQuery 25 | ) -> NDArray[np.float32]: 26 | """音声波形へ音声合成用のクエリがもつ音量スケール(`volumeScale`)を適用する""" 27 | return wave * query.volumeScale 28 | 29 | 30 | def _apply_output_sampling_rate( 31 | wave: NDArray[np.float32], sr_wave: float, query: AudioQuery | FrameAudioQuery 32 | ) -> NDArray[np.float32]: 33 | """音声波形へ音声合成用のクエリがもつ出力サンプリングレート(`outputSamplingRate`)を適用する""" 34 | # サンプリングレート一致のときはスルー 35 | if sr_wave == query.outputSamplingRate: 36 | return wave 37 | wave = resample(wave, sr_wave, query.outputSamplingRate) 38 | return wave 39 | 40 | 41 | def _apply_output_stereo( 42 | wave: NDArray[np.float32], query: AudioQuery | FrameAudioQuery 43 | ) -> NDArray[np.float32]: 44 | """音声波形へ音声合成用のクエリがもつステレオ出力設定(`outputStereo`)を適用する""" 45 | if query.outputStereo: 46 | wave = np.array([wave, wave]).T 47 | return wave 48 | -------------------------------------------------------------------------------- /voicevox_engine/tts_pipeline/connect_base64_waves.py: -------------------------------------------------------------------------------- 1 | """音声データの結合""" 2 | 3 | import base64 4 | import io 5 | 6 | import numpy as np 7 | import soundfile 8 | from numpy.typing import NDArray 9 | from soxr import resample 10 | 11 | 12 | class ConnectBase64WavesException(Exception): 13 | """Base64 エンコードされた音声波形の結合に失敗した。""" 14 | 15 | def __init__(self, message: str): 16 | self.message = message 17 | 18 | 19 | def decode_base64_waves(waves: list[str]) -> list[tuple[NDArray[np.float64], int]]: 20 | """ 21 | base64エンコードされた複数のwavデータをデコードする 22 | 23 | Parameters 24 | ---------- 25 | waves: list[str] 26 | base64エンコードされたwavデータのリスト 27 | 28 | Returns 29 | ------- 30 | waves_nparray_sr: list[tuple[NDArray[np.float64], int]] 31 | (NumPy配列の音声波形データ, サンプリングレート) 形式のタプルのリスト 32 | """ 33 | if len(waves) == 0: 34 | raise ConnectBase64WavesException("wavファイルが含まれていません") 35 | 36 | waves_nparray_sr = [] 37 | for wave in waves: 38 | try: 39 | wav_bin = base64.standard_b64decode(wave) 40 | except ValueError as e: 41 | raise ConnectBase64WavesException("base64デコードに失敗しました") from e 42 | try: 43 | _data = soundfile.read(io.BytesIO(wav_bin)) 44 | except Exception as e: 45 | raise ConnectBase64WavesException( 46 | "wavファイルを読み込めませんでした" 47 | ) from e 48 | waves_nparray_sr.append(_data) 49 | 50 | return waves_nparray_sr 51 | 52 | 53 | def connect_base64_waves(waves: list[str]) -> tuple[NDArray[np.float64], int]: 54 | """複数の base64 エンコードされた音声波形を1つに結合する。""" 55 | waves_nparray_sr = decode_base64_waves(waves) 56 | 57 | max_sampling_rate = max([sr for _, sr in waves_nparray_sr]) 58 | max_channels = max([x.ndim for x, _ in waves_nparray_sr]) 59 | assert 0 < max_channels <= 2 60 | 61 | waves_nparray_list = [] 62 | for nparray, sr in waves_nparray_sr: 63 | if sr != max_sampling_rate: 64 | nparray = resample(nparray, sr, max_sampling_rate) 65 | if nparray.ndim < max_channels: 66 | nparray = np.array([nparray, nparray]).T 67 | waves_nparray_list.append(nparray) 68 | 69 | return np.concatenate(waves_nparray_list), max_sampling_rate 70 | -------------------------------------------------------------------------------- /voicevox_engine/utility/core_version_utility.py: -------------------------------------------------------------------------------- 1 | """バージョンに関する utility""" 2 | 3 | from semver.version import Version 4 | 5 | 6 | def get_latest_version(versions: list[str]) -> str: 7 | """一覧の中で最も新しいバージョン番号を出力する。""" 8 | if len(versions) == 0: 9 | raise Exception("versions must be non-empty.") 10 | return str(max(map(Version.parse, versions))) 11 | -------------------------------------------------------------------------------- /voicevox_engine/utility/file_utility.py: -------------------------------------------------------------------------------- 1 | """ファイル操作に関するユーティリティ""" 2 | 3 | import os 4 | import traceback 5 | 6 | 7 | def try_delete_file(file_path: str) -> None: 8 | """指定されたファイルの削除を試み、失敗したらログを残したうえでエラーを握り潰す。""" 9 | try: 10 | os.remove(file_path) 11 | except OSError: 12 | traceback.print_exc() 13 | -------------------------------------------------------------------------------- /voicevox_engine/utility/path_utility.py: -------------------------------------------------------------------------------- 1 | """パスに関する utility""" 2 | 3 | import sys 4 | from pathlib import Path 5 | 6 | from platformdirs import user_data_dir 7 | 8 | from voicevox_engine.utility.runtime_utility import is_development 9 | 10 | 11 | def engine_root() -> Path: 12 | """エンジンのルートディレクトリを指すパスを取得する。""" 13 | if is_development(): 14 | # git レポジトリのルートを指している 15 | root_dir = Path(__file__).parents[2] 16 | else: 17 | root_dir = Path(sys.executable).parent 18 | 19 | return root_dir.resolve(strict=True) 20 | 21 | 22 | def resource_root() -> Path: 23 | """リソースのルートディレクトリを指すパスを取得する。""" 24 | return engine_root() / "resources" 25 | 26 | 27 | def engine_manifest_path() -> Path: 28 | """エンジンマニフェストのパスを取得する。""" 29 | # NOTE: VOICEVOX API の規定によりエンジンマニフェストファイルは必ず `/engine_manifest.json` に存在する 30 | return engine_root() / "engine_manifest.json" 31 | 32 | 33 | def get_save_dir() -> Path: 34 | """ファイルの保存先ディレクトリを指すパスを取得する。""" 35 | # FIXME: ファイル保存場所をエンジン固有のIDが入ったものにする 36 | # FIXME: Windowsは`voicevox-engine/voicevox-engine`ディレクトリに保存されているので 37 | # `VOICEVOX/voicevox-engine`に変更する 38 | if is_development(): 39 | app_name = "voicevox-engine-dev" 40 | else: 41 | app_name = "voicevox-engine" 42 | return Path(user_data_dir(app_name)) 43 | -------------------------------------------------------------------------------- /voicevox_engine/utility/runtime_utility.py: -------------------------------------------------------------------------------- 1 | """実行環境に関する utility""" 2 | 3 | import sys 4 | 5 | 6 | def is_development() -> bool: 7 | """ 8 | 動作環境が開発版であるか否かを返す。 9 | 10 | Pyinstallerでコンパイルされていない場合は開発環境とする。 11 | """ 12 | # pyinstallerでビルドをした際はsys.frozenが設定される 13 | return False if getattr(sys, "frozen", False) else True 14 | -------------------------------------------------------------------------------- /voicevox_engine/utility/text_utility.py: -------------------------------------------------------------------------------- 1 | """テキスト処理に関するユーティリティ""" 2 | 3 | import re 4 | from typing import Final 5 | 6 | _HANKAKU_CHARS: Final = "".join(chr(0x21 + i) for i in range(94)) 7 | _ZENKAKU_CHARS: Final = "".join(chr(0xFF01 + i) for i in range(94)) 8 | 9 | _HANKAKU_TO_ZENKAKU_TABLE: Final = str.maketrans(_HANKAKU_CHARS, _ZENKAKU_CHARS) 10 | _ZENKAKU_TO_HANKAKU_TABLE: Final = str.maketrans(_ZENKAKU_CHARS, _HANKAKU_CHARS) 11 | 12 | 13 | def replace_hankaku_alphabets_with_zenkaku(string: str) -> str: 14 | """文字列に含まれる半角アルファベットを全角アルファベットで置き換える。""" 15 | return string.translate(_HANKAKU_TO_ZENKAKU_TABLE) 16 | 17 | 18 | def replace_zenkaku_alphabets_with_hankaku(string: str) -> str: 19 | """文字列に含まれる全角アルファベットを半角アルファベットで置き換える。""" 20 | return string.translate(_ZENKAKU_TO_HANKAKU_TABLE) 21 | 22 | 23 | # 複数のカタカナが1つのモーラを構成するパターン 24 | _RULE_OTHERS: Final = ( 25 | "[イ][ェ]|[ヴ][ャュョ]|[ウクグトド][ゥ]|[テデ][ィェャュョ]|[クグ][ヮ]" 26 | ) 27 | _RULE_LINE_I: Final = "[キシチニヒミリギジヂビピ][ェャュョ]|[キニヒミリギビピ][ィ]" 28 | _RULE_LINE_U: Final = "[クツフヴグ][ァ]|[ウクスツフヴグズ][ィ]|[ウクツフヴグ][ェォ]" 29 | # 1つのカタカナが1つのモーラを構成するパターン 30 | _RULE_ONE_MORA: Final = "[ァ-ヴー]" 31 | 32 | _MORA_PATTERN: Final = re.compile( 33 | f"(?:{_RULE_OTHERS}|{_RULE_LINE_I}|{_RULE_LINE_U}|{_RULE_ONE_MORA})" 34 | ) 35 | 36 | 37 | def count_mora(string: str) -> int: 38 | """文字列に含まれるモーラを数える。""" 39 | return len(_MORA_PATTERN.findall(string)) 40 | --------------------------------------------------------------------------------