├── .editorconfig ├── .vscode └── settings.json ├── Pipfile ├── License.txt ├── .gitignore ├── .github └── workflows │ └── build.yaml ├── Pipfile.lock ├── Readme.md └── TarakoTalk.py /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_size = 4 9 | indent_style = space 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.yaml] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // Pylance の Type Checking を有効化 3 | "python.analysis.typeCheckingMode": "basic", 4 | // Pylance の Type Checking のうち、いくつかのエラー報告を抑制する 5 | "python.analysis.diagnosticSeverityOverrides": { 6 | "reportOptionalMemberAccess": "none", 7 | "reportPrivateImportUsage": "none" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [scripts] 7 | build-windows = "python -m nuitka --mingw64 --follow-imports --onefile --assume-yes-for-downloads -o TarakoTalk.exe TarakoTalk.py" 8 | build-macos = "python -m nuitka --mingw64 --follow-imports --onefile --assume-yes-for-downloads --macos-target-arch=x86_64 -o tarakotalk-macos TarakoTalk.py" 9 | build-linux = "python -m nuitka --mingw64 --follow-imports --onefile --assume-yes-for-downloads -o tarakotalk-linux TarakoTalk.py" 10 | build-linux-arm = "python -m nuitka --mingw64 --follow-imports --onefile --assume-yes-for-downloads -o tarakotalk-linux-arm TarakoTalk.py" 11 | 12 | [packages] 13 | rich = "*" 14 | nuitka = "*" 15 | requests = "*" 16 | chardet = "*" 17 | simpleaudio = "*" 18 | 19 | [dev-packages] 20 | 21 | [requires] 22 | python_version = "3.10" 23 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 tsukumi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/python 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=python 4 | 5 | ### Python ### 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # PyInstaller 15 | # Usually these files are written by a python script from a template 16 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 17 | *.manifest 18 | *.spec 19 | 20 | # Installer logs 21 | pip-log.txt 22 | pip-delete-this-directory.txt 23 | 24 | # Unit test / coverage reports 25 | htmlcov/ 26 | .tox/ 27 | .nox/ 28 | .coverage 29 | .coverage.* 30 | .cache 31 | nosetests.xml 32 | coverage.xml 33 | *.cover 34 | *.py,cover 35 | .hypothesis/ 36 | .pytest_cache/ 37 | cover/ 38 | 39 | # Translations 40 | *.mo 41 | *.pot 42 | 43 | # Django stuff: 44 | *.log 45 | local_settings.py 46 | db.sqlite3 47 | db.sqlite3-journal 48 | 49 | # Flask stuff: 50 | instance/ 51 | .webassets-cache 52 | 53 | # Scrapy stuff: 54 | .scrapy 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | .pybuilder/ 61 | target/ 62 | 63 | # Jupyter Notebook 64 | .ipynb_checkpoints 65 | 66 | # IPython 67 | profile_default/ 68 | ipython_config.py 69 | 70 | # pyenv 71 | # For a library or package, you might want to ignore these files since the code is 72 | # intended to run in multiple environments; otherwise, check them in: 73 | # .python-version 74 | 75 | # pipenv 76 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 77 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 78 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 79 | # install all needed dependencies. 80 | #Pipfile.lock 81 | 82 | # poetry 83 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 84 | # This is especially recommended for binary packages to ensure reproducibility, and is more 85 | # commonly ignored for libraries. 86 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 87 | #poetry.lock 88 | 89 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 90 | __pypackages__/ 91 | 92 | # Celery stuff 93 | celerybeat-schedule 94 | celerybeat.pid 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Environments 100 | .env 101 | .venv 102 | env/ 103 | venv/ 104 | ENV/ 105 | env.bak/ 106 | venv.bak/ 107 | 108 | # Spyder project settings 109 | .spyderproject 110 | .spyproject 111 | 112 | # Rope project settings 113 | .ropeproject 114 | 115 | # mkdocs documentation 116 | /site 117 | 118 | # mypy 119 | .mypy_cache/ 120 | .dmypy.json 121 | dmypy.json 122 | 123 | # Pyre type checker 124 | .pyre/ 125 | 126 | # pytype static type analyzer 127 | .pytype/ 128 | 129 | # Cython debug symbols 130 | cython_debug/ 131 | 132 | # PyCharm 133 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 134 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 135 | # and can be added to the global gitignore or merged into this file. For a more nuclear 136 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 137 | #.idea/ 138 | 139 | # End of https://www.toptal.com/developers/gitignore/api/python 140 | 141 | # Nuitka によるビルドキャッシュや成果物 142 | TarakoTalk.build/ 143 | TarakoTalk.dist/ 144 | TarakoTalk.onefile-build/ 145 | TarakoTalk.exe 146 | 147 | # 生成された音声ファイル 148 | *.wav 149 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: Build TarakoTalk 3 | 4 | # 手動実行 5 | on: [workflow_dispatch] 6 | 7 | # ジョブの定義 8 | jobs: 9 | 10 | # Windows 向けビルド 11 | build_windows: 12 | runs-on: windows-2022 13 | 14 | steps: 15 | 16 | # このリポジトリをチェックアウトし、Python 環境をセットアップ 17 | - uses: actions/checkout@v3 18 | - uses: actions/setup-python@v4 19 | with: 20 | python-version: '3.10' 21 | 22 | # 依存関係のインストール 23 | - name: Install Dependencies 24 | run: | 25 | pip install pipenv 26 | $env:PIPENV_VENV_IN_PROJECT="true"; pipenv sync 27 | 28 | # TarakoTalk のビルド 29 | - name: Build TarakoTalk Windows Binary 30 | run: pipenv run build-windows 31 | 32 | # Artifact としてアップロード 33 | - name: Upload TarakoTalk Windows Binary as Artifact 34 | uses: actions/upload-artifact@v3 35 | with: 36 | path: ./TarakoTalk.exe 37 | 38 | # Mac 向けビルド 39 | build_mac: 40 | runs-on: macos-11 41 | 42 | steps: 43 | 44 | # このリポジトリをチェックアウトし、Python 環境をセットアップ 45 | - uses: actions/checkout@v3 46 | - uses: actions/setup-python@v4 47 | with: 48 | python-version: '3.10' 49 | 50 | # 依存関係のインストール 51 | - name: Install Dependencies 52 | run: | 53 | pip install pipenv 54 | PIPENV_VENV_IN_PROJECT="true" pipenv sync 55 | 56 | # TarakoTalk のビルド 57 | - name: Build TarakoTalk macOS Binary 58 | run: | 59 | pipenv run build-macos 60 | 61 | # Artifact としてアップロード 62 | - name: Upload TarakoTalk macOS Binary as Artifact 63 | uses: actions/upload-artifact@v3 64 | with: 65 | path: | 66 | ./tarakotalk-macos 67 | 68 | # Linux 向けビルド 69 | build_linux: 70 | runs-on: ubuntu-22.04 71 | 72 | steps: 73 | 74 | # このリポジトリをチェックアウトし、Python 環境をセットアップ 75 | - uses: actions/checkout@v3 76 | - uses: actions/setup-python@v4 77 | with: 78 | python-version: '3.10' 79 | 80 | # 依存関係のインストール 81 | - name: Install Dependencies 82 | run: | 83 | sudo apt-get update 84 | DEBIAN_FRONTEND=noninteractive sudo -E apt-get install -y libasound2-dev patchelf 85 | pip install pipenv 86 | PIPENV_VENV_IN_PROJECT="true" pipenv sync 87 | 88 | # TarakoTalk のビルド 89 | - name: Build TarakoTalk Linux Binary 90 | run: pipenv run build-linux 91 | 92 | # Artifact としてアップロード 93 | - name: Upload TarakoTalk Linux Binary as Artifact 94 | uses: actions/upload-artifact@v3 95 | with: 96 | path: ./tarakotalk-linux 97 | 98 | # Linux (ARM) 向けビルド 99 | build_linux_arm: 100 | runs-on: ubuntu-22.04 101 | 102 | steps: 103 | 104 | # このリポジトリをチェックアウト 105 | - uses: actions/checkout@v3 106 | 107 | # QEMU 環境をセットアップ 108 | - name: Setup QEMU 109 | uses: docker/setup-qemu-action@v1 110 | 111 | # Python 3.10 のインストール / 依存関係のインストール / TarakoTalk のビルド 112 | ## arm64 向けにクロスコンパイルを行う 113 | ## クロスコンパイルを行う関係で、Linux (ARM) のみ Python 3.10 を apt からインストールする 114 | ## ref: https://scrapbox.io/nwtgck/GitHub_Actions%E4%B8%8A%E3%81%A7ARM%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8BDocker%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9F%E6%96%B9%E6%B3%95 115 | - name: Build TarakoTalk Linux Binary 116 | run: | 117 | 118 | # クロスコンパイル用の Docker コンテナに入る 119 | set -xeu 120 | docker run --rm -i -v $PWD:/app arm64v8/ubuntu:22.04 bash << EOF 121 | set -xeu 122 | cd /app 123 | 124 | # Python 3.10 と依存パッケージのインストール 125 | apt-get update 126 | DEBIAN_FRONTEND=noninteractive apt-get install -y python3-pip python3.10-dev python3.10-full libasound2-dev patchelf 127 | 128 | # 依存関係のインストール 129 | pip install pipenv 130 | PIPENV_VENV_IN_PROJECT="true" pipenv sync 131 | 132 | # TarakoTalk のビルド 133 | pipenv run build-linux-arm 134 | EOF 135 | 136 | # Artifact としてアップロード 137 | - name: Upload TarakoTalk Linux Binary as Artifact 138 | uses: actions/upload-artifact@v3 139 | with: 140 | path: ./tarakotalk-linux-arm 141 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "a0f7c84b3f4d8f609db52104ae74b81a94d6ab112320bc071ccec14f8cb8a180" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.10" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "certifi": { 20 | "hashes": [ 21 | "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", 22 | "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" 23 | ], 24 | "markers": "python_version >= '3.6'", 25 | "version": "==2022.6.15" 26 | }, 27 | "chardet": { 28 | "hashes": [ 29 | "sha256:0368df2bfd78b5fc20572bb4e9bb7fb53e2c094f60ae9993339e8671d0afb8aa", 30 | "sha256:d3e64f022d254183001eccc5db4040520c0f23b1a3f33d6413e099eb7f126557" 31 | ], 32 | "index": "pypi", 33 | "version": "==5.0.0" 34 | }, 35 | "charset-normalizer": { 36 | "hashes": [ 37 | "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", 38 | "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" 39 | ], 40 | "markers": "python_version >= '3.6'", 41 | "version": "==2.1.1" 42 | }, 43 | "commonmark": { 44 | "hashes": [ 45 | "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", 46 | "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9" 47 | ], 48 | "version": "==0.9.1" 49 | }, 50 | "idna": { 51 | "hashes": [ 52 | "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", 53 | "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" 54 | ], 55 | "markers": "python_version >= '3.5'", 56 | "version": "==3.3" 57 | }, 58 | "nuitka": { 59 | "hashes": [ 60 | "sha256:80203fa8816d187dde812ee35c6ad597423786b73ee512d8f6f62c72af227fa4" 61 | ], 62 | "index": "pypi", 63 | "version": "==1.0.6" 64 | }, 65 | "pygments": { 66 | "hashes": [ 67 | "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1", 68 | "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42" 69 | ], 70 | "markers": "python_version >= '3.6'", 71 | "version": "==2.13.0" 72 | }, 73 | "requests": { 74 | "hashes": [ 75 | "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", 76 | "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" 77 | ], 78 | "index": "pypi", 79 | "version": "==2.28.1" 80 | }, 81 | "rich": { 82 | "hashes": [ 83 | "sha256:2eb4e6894cde1e017976d2975ac210ef515d7548bc595ba20e195fb9628acdeb", 84 | "sha256:63a5c5ce3673d3d5fbbf23cd87e11ab84b6b451436f1b7f19ec54b6bc36ed7ca" 85 | ], 86 | "index": "pypi", 87 | "version": "==12.5.1" 88 | }, 89 | "simpleaudio": { 90 | "hashes": [ 91 | "sha256:05b63da515f5fc7c6f40e4d9673d22239c5e03e2bda200fc09fd21c185d73713", 92 | "sha256:67348e3d3ccbae73bd126beed7f1e242976889620dbc6974c36800cd286430fc", 93 | "sha256:691c88649243544db717e7edf6a9831df112104e1aefb5f6038a5d071e8cf41d", 94 | "sha256:86f1b0985629852afe67259ac6c24905ca731cb202a6e96b818865c56ced0c27", 95 | "sha256:f1a4fe3358429b2ea3181fd782e4c4fff5c123ca86ec7fc29e01ee9acd8a227a", 96 | "sha256:f346a4eac9cdbb1b3f3d0995095b7e86c12219964c022f4d920c22f6ca05fb4c", 97 | "sha256:f68820297ad51577e3a77369e7e9b23989d30d5ae923bf34c92cf983c04ade04" 98 | ], 99 | "index": "pypi", 100 | "version": "==1.0.4" 101 | }, 102 | "urllib3": { 103 | "hashes": [ 104 | "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", 105 | "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" 106 | ], 107 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", 108 | "version": "==1.26.12" 109 | } 110 | }, 111 | "develop": {} 112 | } 113 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # TarakoTalk 3 | 4 | ![screenshot](https://user-images.githubusercontent.com/39271166/188684161-f4766df8-797d-4091-bf6e-3176db94e073.png) 5 | 6 | [おしゃべりひろゆきメーカー](https://hiroyuki.coefont.cloud/) を使って CLI からひろゆきに適当な事を喋らせられる、非公式な CLI TTS (Text-to-Speech) ツールです。 7 | 8 | ## Features 9 | 10 | 生成した音声をファイルに保存する `save` と、生成した音声をそのまま PC で再生する `play` の2つのサブコマンドを実装しています。 11 | 12 | 140 文字までの制限がある Web サイトとは異なり、TarakoTalk は 1000 文字までのテキストをひろゆきに喋らせられます (2022/09/06 時点での API の仕様に基づく)。 13 | コピペや短い物語をひろゆきに朗読させることもできます。 14 | 音声の生成には、短いものだと5秒、最大で15秒ほど時間がかかるようです(サーバーの混雑時はもっとかも)。 15 | 16 | - **生成した音声をファイルに保存する (`save`)** 17 | - 喋らせたいテキストは、コマンドライン引数・テキストファイル・標準入力(パイプ渡し)のいずれかから入力可能 18 | - 生成した音声を、指定したファイルパスに wav 形式で保存 19 | - 生成した音声を、wav 形式で標準出力(パイプ渡し)に出力 20 | - 別途 FFmpeg を導入すれば、`tarakotalk save "それって、あなたの感想ですよね?" "-" | ffmpeg -i - test.mp3` で wav から mp3 などの音声フォーマットに変換できます。 21 | - **生成した音声を PC 上で再生する (`play`)** 22 | - 喋らせたいテキストは、コマンドライン引数・テキストファイル・標準入力(パイプ渡し)のいずれかから入力可能 23 | - 生成した音声を、そのまま PC のスピーカーから再生 (クロスプラットフォーム対応) 24 | 25 | ## How to Use 26 | 27 | [Releases](https://github.com/tsukumijima/TarakoTalk/releases) から最新の TarakoTalk をダウンロードして、適宜 PATH の通ったフォルダに配置します。 28 | 29 | > TarakoTalk は Python 製のツールですが、[Nuitka](https://github.com/Nuitka/Nuitka) を使い単一のバイナリにビルドしています。 30 | 31 | - Windows (x64): TarakoTalk.exe 32 | - macOS (x64): tarakotalk-macos 33 | - Intel Mac 版のみですが、Apple Silicon (M1) Mac でも Rosetta 2 が入っていれば動くはず…? 34 | - Linux (x64): tarakotalk-linux 35 | - Linux (arm64): tarakotalk-linux-arm 36 | 37 | 上記の4つのビルドがあります。お使いの OS に合わせてダウンロードしてください。 38 | 39 | ``` 40 | usage: ./tarakotalk [-h] {save,play} ... 41 | 42 | Cross-platform CLI TTS Tools for Hiroyuki's Voice 43 | 44 | positional arguments: 45 | {save,play} 46 | save 生成した音声をファイルに保存する 47 | play 生成した音声を PC 上で再生する 48 | 49 | options: 50 | -h, --help show this help message and exit 51 | ``` 52 | 53 | ### `tarakotalk save` 54 | 55 | ``` 56 | usage: ./tarakotalk save [-h] input output 57 | 58 | positional arguments: 59 | input ひろゆきに喋らせるテキスト (文字列 or ファイルパス、"-" で標準入力から読み込み) 60 | output 生成した音声ファイル (wav) の保存先のファイルパス ("-" で標準出力に出力) 61 | 62 | options: 63 | -h, --help show this help message and exit 64 | ``` 65 | 66 | ```powershell 67 | # コマンドライン引数からテキストを入力し、生成した音声を /path/to/test.wav に保存 68 | ./tarakotalk save "それって、あなたの感想ですよね?" "/path/to/test.wav" 69 | 70 | # ファイルからテキストを入力し、生成した音声を標準出力に出力したあと、FFmpeg に渡して mp3 に変換 71 | ./tarakotalk save "/path/to/yoshinoya.txt" "-" | ffmpeg -i - -c:a libmp3lame /path/to/test.mp3 72 | 73 | # 標準入力からテキストを読み上げ、生成した音声を /path/to/test.wav に保存 74 | echo "それって、あなたの感想ですよね?" | ./tarakotalk save "-" "/path/to/test.wav" 75 | ``` 76 | 77 | ### `tarakotalk play` 78 | 79 | ``` 80 | usage: ./tarakotalk play [-h] input 81 | 82 | positional arguments: 83 | input ひろゆきに喋らせるテキスト (文字列 or ファイルパス、"-" で標準入力から読み込み) 84 | 85 | options: 86 | -h, --help show this help message and exit 87 | ``` 88 | 89 | ```powershell 90 | # コマンドライン引数からテキストを読み上げ 91 | ./tarakotalk play "それって、あなたの感想ですよね?" 92 | 93 | # ファイルからテキストを読み上げ 94 | ./tarakotalk play "/path/to/yoshinoya.txt" 95 | 96 | # 標準入力からテキストを読み上げ 97 | echo "それって、あなたの感想ですよね?" | ./tarakotalk play "-" 98 | ``` 99 | 100 | ## Examples of Use 101 | 102 | とりあえず使えそう (要出典) な例を適当に挙げてみただけで、実際に使えるかどうかは未検証です。 103 | 104 | - [吉野家コピペ](https://dic.nicovideo.jp/a/%E5%90%89%E9%87%8E%E5%AE%B6%E3%82%B3%E3%83%94%E3%83%9A) をひろゆきに朗読させる 105 | - [棒読みちゃん](https://chi.usamimi.info/Program/Application/BouyomiChan/) みたいにライブチャットのコメントを読み上げさせる 106 | - 別途、ライブチャットからコメントを受信した際に、コメント内容とともにコマンドを実行できるツールが必要です。そんなものがあるのかは知らん。 107 | - 音声の生成には短いコメントでも数秒時間がかかるので、どうしてもリアルタイム性は落ちます。 108 | - ラズパイに TarakoTalk をインストールして、ラズパイにつなげたスピーカーから朝8時になったタイミングで今日の天気とニュースをひろゆきに読み上げさせる 109 | - 生成は(当然ながら)CoeFont のサーバーに丸投げしているので、ラズパイのような非力なマシンでもそれなりに早く生成できるはずです。 110 | - 生成した音声は標準出力に流せるので、FFmpeg でパイプ渡しされてきた標準入力を受け取るようにすれば (`-i -`)、FFmpeg のコマンド次第で別の音声形式に変換したり、再生速度を変更することもできます。 111 | - FFmpeg を活用して、読み上げ音声に BGM を入れたりフィルタを掛けたりとかもできそう。 112 | - ひろゆきに読み上げさせたものをナレーションとして動画に使う 113 | - 動画作成に使うなら、公式で [CoeFont Cloud](https://coefont.cloud/) 内の無料無制限で使える CoeFont にひろゆきが入っているので、そっちを使ったほうがイントネーションやスピードの再生もできてより便利だと思います(なぜかあまり宣伝されていない…)。 114 | - CoeFont Cloud の利用にはログインが必要です。 115 | - CoeFont は有料のものもありますが、ひろゆきはアリアル・ミリアルに続いての無料枠扱いみたいです。落差が激しい… 116 | - 音MAD素材用に原曲の歌詞をひろゆきに流し込んで、別途 REAPER か VocalShifter あたりで調教して歌わせる 117 | - CLI ツールなので、シェルスクリプトか何かを組んで歌詞の読み上げ音声を複数の wav ファイルに分割して生成させるようにすることもできそうです。 118 | 119 | ## Disclaimer 120 | 121 | - **TarakoTalk は非公式ツールです。CoeFont 公式とは一切関係がありません。** 122 | - TarakoTalk は、おしゃべりひろゆきメーカーが内部的に使っている非公開 API に直接アクセスすることで、CLI からひろゆきの音声を取得しています。 123 | - TarakoTalk に関して CoeFont 公式に問い合わせるのはやめてください。 124 | - **無保証です。CoeFont 公式やひろゆき本人から怒られが発生しない程度にこっそり使ってください。** 125 | - **万が一、どこからか怒られが発生した場合の責任は一切負いかねます。** 自己の責任のもとで使ってください。 126 | - 非公開 API にアクセスしている時点で元々好ましいことをしているツールではないので、CoeFont のサーバーに過剰に負荷がかかるような使い方はやめてあげてください。 127 | - NG ワードは API 側でバリデーションされているため、TarakoTalk 経由かどうかにかかわらず、NG ワードには同じものが適用されます。 128 | - CoeFont 側の API の仕様変更、あるいはサービス終了などに伴って、急に使えなくなる可能性があります。 129 | 130 | ## License 131 | 132 | [MIT License](License.txt) 133 | -------------------------------------------------------------------------------- /TarakoTalk.py: -------------------------------------------------------------------------------- 1 | 2 | import argparse 3 | import atexit 4 | import chardet 5 | import requests 6 | import simpleaudio 7 | import sys 8 | import tempfile 9 | from pathlib import Path 10 | from rich.console import Console 11 | from rich.progress import Progress 12 | from rich.progress import BarColumn, SpinnerColumn, TextColumn, TimeElapsedColumn 13 | from typing import IO 14 | 15 | 16 | VERSION = '1.0.0' 17 | 18 | def main(): 19 | 20 | # Rich でログを出力するためのコンソールオブジェクト 21 | ## 標準出力と被らないように、標準エラー出力に出力 22 | console = Console(stderr=True) 23 | 24 | # 引数設定 25 | ## ref: https://sig9.org/archives/4478 26 | parser = argparse.ArgumentParser( 27 | formatter_class = argparse.RawTextHelpFormatter, 28 | description = 'Cross-platform CLI TTS Tools for Hiroyuki\'s Voice', 29 | ) 30 | subparsers = parser.add_subparsers() 31 | parser_save = subparsers.add_parser('save', help='生成した音声をファイルに保存する') 32 | parser_save.add_argument('input', help='ひろゆきに喋らせるテキスト (文字列 or ファイルパス、"-" で標準入力から読み込み)') 33 | parser_save.add_argument('output', help='生成した音声ファイル (wav) の保存先のファイルパス ("-" で標準出力に出力)') 34 | parser_play = subparsers.add_parser('play', help='生成した音声を PC 上で再生する') 35 | parser_play.add_argument('input', help='ひろゆきに喋らせるテキスト (文字列 or ファイルパス、"-" で標準入力から読み込み)') 36 | 37 | 38 | # プログレスバーの設定 39 | def CreateProgressBar() -> Progress: 40 | return Progress( 41 | SpinnerColumn(spinner_name='bouncingBar'), 42 | TextColumn("[progress.description]{task.description}"), 43 | BarColumn(bar_width=9999), # とりあえず適当に大きい幅に設定しておく 44 | TimeElapsedColumn(), 45 | console = console, 46 | transient = False, 47 | expand = True, # ターミナルの幅いっぱいに表示 48 | ) 49 | 50 | 51 | def GetTTSText(input_data: str) -> str: 52 | """ 53 | ひろゆきに喋らせるテキストを取得する 54 | 55 | Args: 56 | input_data (str): input 引数 57 | 58 | Returns: 59 | str: ひろゆきに喋らせるテキスト 60 | """ 61 | 62 | input_text: str = '' 63 | 64 | ## 標準入力から読み込み 65 | if input_data == '-': 66 | 67 | # stdin のデータをすべて読み込む 68 | # 文字エンコーディングを自動判定して読み込み 69 | input_text_raw = sys.stdin.buffer.read() 70 | input_text_encoding = chardet.detect(input_text_raw)['encoding'] 71 | input_text = input_text_raw.decode(input_text_encoding) 72 | 73 | # ファイルから読み込み (ファイルが存在する場合のみ) 74 | elif Path(input_data).is_file(): 75 | 76 | # 文字エンコーディングを自動判定して読み込み 77 | with open(input_data, mode='rb') as f: 78 | input_text_raw = f.read() 79 | input_text_encoding = chardet.detect(input_text_raw)['encoding'] 80 | input_text = input_text_raw.decode(input_text_encoding) 81 | 82 | # それ以外の場合、input_data に与えられたテキストをそのままひろゆきに喋らせる 83 | else: 84 | input_text = input_data 85 | 86 | # 改行やホワイトスペースを消した上で返す 87 | return input_text.strip() 88 | 89 | 90 | def TextToSpeech(input_text: str, output_file: IO[bytes]) -> bool: 91 | """ 92 | Text-to-Speech を実行し、生成した音声をファイルに保存する 93 | 94 | Args: 95 | input_text (str): ひろゆきに喋らせるテキスト 96 | output_file (IO[bytes]): 保存先のファイルの file-like オブジェクト 97 | 98 | Returns: 99 | bool: Text-to-Speech の実行に成功したか 100 | """ 101 | 102 | # API に渡すヘッダー 103 | headers ={ 104 | 'Accept': 'application/json, text/plain, */*', 105 | 'Accept-Encoding': 'gzip, deflate, br', 106 | 'Accept-Language': 'ja,ja-JP;q=0.9,und;q=0.8', 107 | 'origin': 'https://hiroyuki.coefont.cloud', 108 | 'referer': 'https://hiroyuki.coefont.cloud/', 109 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36', 110 | } 111 | 112 | console.print(f'📋 テキスト: {input_text}') 113 | 114 | # テキストが 1000 文字以上 115 | ## 1000 文字以上だと "Invalid text" のエラーが発生する 116 | if len(input_text) >= 1000: 117 | console.print('[red]❌ テキストは 1000 文字以内で入力してください。') 118 | return False 119 | 120 | # 処理が終わるまでプログレスバーを表示 121 | with CreateProgressBar() as progress: 122 | 123 | # プログレスバー (終了未定) を表示 124 | progress.add_task('音声を生成しています…', total=None) 125 | 126 | # Text-to-Speech を実行 127 | result = requests.post( 128 | url = 'https://tgeedx93af.execute-api.ap-northeast-1.amazonaws.com/production/hiroyuki/text2speech', 129 | headers = headers, 130 | json = { 131 | 'coefont': '19d55439-312d-4a1d-a27b-28f0f31bedc5', # ひろゆきの CoeFont 固定 132 | 'text': input_text, 133 | }, 134 | ) 135 | 136 | # HTTP ステータスコードが 200 で音声の生成に成功している場合のみ 137 | if result.status_code == 200 and result.json()['statusCode'] == 200 and result.json()['body']['success'] is True: 138 | 139 | # 生成された音声のキーを取得 140 | wav_key: str = result.json()['body']['wav_key'] 141 | 142 | # 処理が終わるまでプログレスバーを表示 143 | with CreateProgressBar() as progress: 144 | 145 | # プログレスバー (終了未定) を表示 146 | progress.add_task('生成した音声をダウンロードしています…', total=None) 147 | 148 | # 生成された音声をダウンロード 149 | wav_result = requests.get( 150 | url = f'https://tgeedx93af.execute-api.ap-northeast-1.amazonaws.com/production/chore/get_presigned_url?wav_key={wav_key}', 151 | headers = headers, 152 | ) 153 | 154 | # ダウンロードに成功 155 | if wav_result.status_code == 200: 156 | 157 | # ダウンロードしたデータをファイルに書き込む 158 | output_file.write(wav_result.content) 159 | return True 160 | 161 | # ダウンロードに失敗した 162 | else: 163 | console.print('[red]❌ 生成した音声のダウンロードに失敗しました。CoeFont のサーバーが混雑している可能性があります。\n' 164 | f' (HTTP Error {wav_result.status_code})') 165 | return False 166 | 167 | # 音声の生成に失敗した 168 | else: 169 | 170 | # ステータスコードが 200 以外 171 | if result.status_code != 200: 172 | console.print('[red]❌ 音声の生成に失敗しました。CoeFont のサーバーが混雑している可能性があります。\n' 173 | f' (HTTP Error {result.status_code})') 174 | 175 | # テキストに NG ワードが含まれている 176 | elif result.json()['statusCode'] != 200 and 'ng_word' in result.json()['body']: 177 | console.print( 178 | f'[red]❌ NGワード「{result.json()["body"]["ng_word"]}」が含まれているため、音声の生成に失敗しました。\n' 179 | ' テキストを変更して再度お試しください。' 180 | ) 181 | 182 | # それ以外の理由で音声の生成に失敗した 183 | else: 184 | console.print( 185 | '[red]❌ 音声の生成に失敗しました。CoeFont のサーバーが混雑している可能性があります。\n' 186 | f' (HTTP Error {result.json()["statusCode"]} / Message: {result.json()["body"]["error"]["message"]})' 187 | ) 188 | 189 | return False 190 | 191 | 192 | # 保存時のハンドラー 193 | def SaveHandler(args): 194 | 195 | # ひろゆきに喋らせるテキストを取得 196 | input_text = GetTTSText(args.input) 197 | 198 | # 保存先のファイルの file-like オブジェクトを取得 199 | output_file_path: str = args.output 200 | output_file: IO 201 | 202 | ## 標準出力に出力 203 | is_stdout = False 204 | if output_file_path == '-': 205 | output_file = sys.stdout.buffer # sys.stdout.buffer から BinaryIO が取れる 206 | is_stdout = True 207 | 208 | ## ファイルに保存 209 | else: 210 | 211 | # ファイルではなくフォルダが指定された場合を弾く 212 | if Path(output_file_path).is_dir(): 213 | console.print('[red]❌ 保存先のファイルパスが不正です。') 214 | sys.exit(1) 215 | 216 | # ファイルパス途中にあるフォルダをすべて作成 (すでにある場合は何もしない) 217 | Path(output_file_path).parent.mkdir(parents=True, exist_ok=True) 218 | 219 | # ファイルをバイナリ書き込みモードで開く 220 | try: 221 | output_file = open(output_file_path, mode='wb') 222 | except Exception: 223 | console.print('[red]❌ 保存先のファイルを開けませんでした。') 224 | console.print_exception(width=100) 225 | sys.exit(1) 226 | 227 | # Text-to-Speech を実行し、ファイルに保存 228 | result = TextToSpeech(input_text, output_file) 229 | 230 | # ファイルを閉じる (重要) 231 | output_file.close() 232 | 233 | # 実行に失敗した場合はここで終了 234 | if result is False: 235 | if is_stdout is False: 236 | Path(output_file_path).unlink() 237 | sys.exit(1) 238 | 239 | console.print(f'✅ 生成した音声を{"標準出力" if is_stdout else f" {Path(output_file_path).absolute()} "}に保存しました。') 240 | 241 | 242 | # 再生時のハンドラー 243 | def PlayHandler(args): 244 | 245 | # ひろゆきに喋らせるテキストを取得 246 | input_text = GetTTSText(args.input) 247 | 248 | # 一時保存先の一時ファイルを開く 249 | output_temp_file = tempfile.NamedTemporaryFile(mode='wb', delete=False) 250 | 251 | # Text-to-Speech を実行し、一時ファイルに保存 252 | result = TextToSpeech(input_text, output_temp_file) 253 | 254 | # 一時ファイルを閉じる (まだ削除はされない) 255 | output_temp_file.close() 256 | 257 | # 実行に失敗した場合はここで終了 258 | if result is False: 259 | Path(output_temp_file.name).unlink() # 一時ファイルを削除 260 | sys.exit(1) 261 | 262 | # 処理が終わるまでプログレスバーを表示 263 | with CreateProgressBar() as progress: 264 | 265 | # プログレスバー (終了未定) を表示 266 | progress.add_task('生成した音声を再生しています…', total=None) 267 | 268 | # 生成された音声を再生 (再生し終わるまでブロッキングされる) 269 | # ref: https://laboratory.kazuuu.net/play-an-mp3-file-in-python-using-playsound/ 270 | play_object = simpleaudio.WaveObject.from_wave_file(output_temp_file.name).play() 271 | play_object.wait_done() 272 | 273 | Path(output_temp_file.name).unlink() # 一時ファイルを削除 274 | 275 | console.print('✅ 生成した音声を再生しました。') 276 | 277 | 278 | # サブコマンドのイベントを登録 279 | parser_save.set_defaults(handler=SaveHandler) 280 | parser_play.set_defaults(handler=PlayHandler) 281 | 282 | # 終了時にラインを表示し、標準入出力を閉じる 283 | def OnExit(): 284 | console.rule(characters='─', align='center') 285 | sys.stdin.close() 286 | sys.stdout.close() 287 | sys.stderr.close() 288 | atexit.register(OnExit) 289 | 290 | # 引数解析を実行 291 | console.rule(title=f'TarakoTalk (Voiced by CoeFont) version {VERSION}', characters='─', align='center') 292 | args = parser.parse_args() 293 | if hasattr(args, 'handler'): 294 | args.handler(args) 295 | else: 296 | parser.print_help() 297 | 298 | 299 | if __name__ == "__main__": 300 | main() 301 | --------------------------------------------------------------------------------