├── .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 | 
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 |
--------------------------------------------------------------------------------