commit 6877fd4110926c43ef64699a10dd1e6980026240 Author: hina ntki Date: Sat Aug 3 20:12:30 2024 +0900 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad4a1f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,176 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python diff --git a/README.md b/README.md new file mode 100644 index 0000000..6573d6d --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ + +# VoicePeak API Service +このプロジェクトは、VoicePeakを使用して音声を生成し、生成された音声データを提供するAPIサーバーです。 +PythonのFlaskフレームワークを使用し、Ubuntu上で動作します。 + +## プロジェクト構成 +``` +/path/to/your/project +├── app.py +├── requirements.txt +├── venv/ +└── voicepeak-api.service +``` + +## 前提条件 +- Ubuntuがインストールされたシステム +- VoicePeakがホストOSにインストールされ、動作すること + +## セットアップ手順 +### 1. リポジトリのクローン +リポジトリをクローンします。 + +```bash +git clone ******* +``` + +### 2. Pythonの仮想環境を作成 +仮想環境を作成し、必要なパッケージをインストールします。 + +```bash +# 仮想環境の作成 +python3 -m venv venv + +# 仮想環境のアクティベート +source venv/bin/activate + +# 必要なパッケージのインストール +pip install -r requirements.txt +``` + +### 3. Systemdサービスの設定 +`voicepeak-api.service`ファイルを適切な内容に編集します。 +`voicepeak-api.service`ファイルをシステムのsystemdサービスディレクトリにシンボリックリンクを作成します。 + +```bash +sudo ln -s /path/to/your/project/voicepeak-api.service /etc/systemd/system/voicepeak-api.service +``` + +### 4. サービスのリロードと起動 + +systemdに新しいサービスを認識させ、サービスを起動します。 + +```bash +# systemdデーモンのリロード +sudo systemctl daemon-reload + +# サービスの開始 +sudo systemctl start voicepeak-api.service + +# サービスの有効化(再起動後も自動で起動するようにする) +sudo systemctl enable voicepeak-api.service +``` + +### 5. 動作確認 + +サービスが正常に起動しているか確認します。 + +```bash +sudo systemctl status voicepeak-api.service +``` + +## 使用方法 + +サービスが正常に起動している場合、以下のようにリクエストを送信して音声を生成できます。 + +```bash +curl -X POST http://127.0.0.1:5000/generate_voice -H "Content-Type: application/json" -d '{"text": "こんにちは"}' --output output.wav +``` diff --git a/_voicepeak-api.service b/_voicepeak-api.service new file mode 100644 index 0000000..54879f1 --- /dev/null +++ b/_voicepeak-api.service @@ -0,0 +1,14 @@ +[Unit] +Description=VoicePeak API Service +After=network.target + +[Service] +User=your_user +Group=your_group +WorkingDirectory=/path/to/your/project +Environment="PATH=/path/to/your/project/venv/bin" +ExecStart=/path/to/your/project/venv/bin/python /path/to/your/project/app.py +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/app.py b/app.py new file mode 100644 index 0000000..1e1e1c0 --- /dev/null +++ b/app.py @@ -0,0 +1,87 @@ +from flask import Flask, request, jsonify, send_file +import subprocess +import os +import logging +import uuid +from dotenv import load_dotenv + +# .envファイルを読み込む +load_dotenv() + +app = Flask(__name__) + +# 環境変数から基本設定を取得 +VOICEPEAK_PATH = os.getenv('VOICEPEAK_PATH') +PROJECT_PATH = os.getenv('PROJECT_PATH') + +def _build_command_args(args): + return [ + args['voicepeak_path'], + "-s", args['script'], + "-n", args['narrator'], + "-o", args['output_path'], + "-e", args['emotions'], + "--pitch", str(args['pitch']), + ] + +def _synthesize_with_voicepeak(script, narrator, emotions, pitch, output_path, retries=3): + args = { + 'voicepeak_path': VOICEPEAK_PATH, + 'script': script, + 'narrator': narrator, + 'output_path': output_path, + 'emotions': emotions, + 'pitch': pitch, + } + command_args = _build_command_args(args) + + for attempt in range(retries): + try: + subprocess.run(command_args, check=True) + if os.path.exists(output_path): + return output_path + except subprocess.CalledProcessError as e: + error_message = f"VoicePeak synthesis failed on attempt {attempt + 1}: {e}" + logging.error(error_message) + if attempt < retries - 1: + logging.info(f"Retrying... ({attempt + 2}/{retries})") + else: + raise RuntimeError(error_message) from e + + error_message = f"Expected audio file not found after {retries} attempts: {output_path}" + logging.error(error_message) + raise FileNotFoundError(error_message) + +@app.route('/generate_voice', methods=['POST']) +def generate_voice(): + try: + data = request.json + text = data.get('text') + narrator = data.get('narrator', '1') + emotion_happy = data.get('emotion_happy', '0') + emotion_sad = data.get('emotion_sad', '0') + emotion_angry = data.get('emotion_angry', '0') + emotion_fun = data.get('emotion_fun', '0') + pitch = data.get('pitch', '1') + + if not text: + return jsonify({'error': 'Text is required'}), 400 + + emotions = f"happy={emotion_happy},sad={emotion_sad},angry={emotion_angry},fun={emotion_fun}" + + unique_id = str(uuid.uuid4()) + output_file = os.path.join(PROJECT_PATH, f'{unique_id}.wav') + + _synthesize_with_voicepeak(text, narrator, emotions, pitch, output_file) + + response = send_file(output_file, mimetype='audio/wav') + + os.remove(output_file) + + return response + + except Exception as e: + return jsonify({'error': str(e)}), 500 + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/manage_service.sh b/manage_service.sh new file mode 100644 index 0000000..3ab8306 --- /dev/null +++ b/manage_service.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +SERVICE_NAME="voicepeak-api.service" + +# サービスの状態を確認 +SERVICE_STATUS=$(systemctl is-active $SERVICE_NAME) + +if [ "$SERVICE_STATUS" = "active" ]; then + echo "Restarting $SERVICE_NAME..." + sudo systemctl daemon-reload + sudo systemctl restart $SERVICE_NAME +else + echo "Reloading and starting $SERVICE_NAME..." + sudo systemctl daemon-reload + sudo systemctl start $SERVICE_NAME +fi + +# サービスの状態を表示 +sudo systemctl status $SERVICE_NAME diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2d139d5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Flask +requests +python-dotenv diff --git a/run_dev.sh b/run_dev.sh new file mode 100644 index 0000000..5a798b7 --- /dev/null +++ b/run_dev.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# 仮想環境をアクティブにする +source venv/bin/activate + +# Flaskアプリケーションを起動 +export FLASK_APP=app.py +export FLASK_ENV=development +flask run --host=0.0.0.0 --port=5000 diff --git a/voicepeak-api.service b/voicepeak-api.service new file mode 100644 index 0000000..2c12be4 --- /dev/null +++ b/voicepeak-api.service @@ -0,0 +1,15 @@ +[Unit] +Description=VoicePeak API Service +After=network.target + +[Service] +User=ntki +Group=ntki +WorkingDirectory=/home/ntki/voicepeak_api +Environment="PATH=/home/ntki/voicepeak_api/venv/bin" +ExecStart=/home/ntki/voicepeak_api/venv/bin/python /home/ntki/voicepeak_api/app.py +EnvironmentFile=/home/ntki/voicepeak_api/.env +Restart=always + +[Install] +WantedBy=multi-user.target