🛠

ChatGPTでVOICEROID2の作業を効率化するWebアプリを作ってみた~その3 改良編~

に公開

🛠 ChatGPTでVOICEROID2の作業を効率化するWebアプリを作ってみた~その3 改良編~

前回の記事では、ChatGPTを活用して「VOICEROID2をWebアプリから制御する」構想とChatGPTを活用して環境構築をしてく過程を紹介しました。
今回はいよいよ、実際にアプリを構築して動かすまでの過程をまとめます。

開発中はChatGPTと会話しながら進めたリアルな記録を元にしているので、同じように構築してみたい方にも参考になるはずです。

🚀 今回の改良内容

  1. ファイルの保存先を自由に設定できるように修正
     → 現在はE:\A_動画編集素材\ボイス\琴葉葵に保存されるようになっていますが、任意のパス

  2. AviUtlとの連携
     → 私は普段PSDToolKitを使ってAviUtlで動画編集を行っているため、音声ファイルを保存したうえで同時にexoファイル(AviUtlのインポート用の設定ファイル)も生成できるようにしていきます。

📊 今回作成するのWebアプリの簡単な構成図

前回の記事はこちら
https://zenn.dev/pwrengineer/articles/3315387c142ab5

🔧 ファイルの保存先を自由に設定できるように修正

音声ファイルの保存先で任意のパスを指定できるようにします。

# 現在、savePathが固定になってしまっているので自由に選択できるようにしてください
-- 
from flask import Flask, request, jsonify
from flask_cors import CORS
import subprocess
import os

app = Flask(__name__)
CORS(app)

SAVE_DIR = r"E:\A_動画編集素材\ボイス\琴葉葵"
TEXT_FILE = "genkou.txt"
ASSISTANT_COMMAND = "SeikaSay2"

@app.route('/generate', methods=['POST'])
def generate():
    data = request.json
    text = data.get('text', '')
    cid = data.get('cid', '2002')
    speed = data.get('speed', '1.3')
    pitch = data.get('pitch', '1.1')
    emotion = data.get('emotion', '喜び')
    emotion_value = data.get('emotion_value', '0.2')

    if not text:
        return jsonify({"error": "text is required"}), 400

    # ① テキストファイル作成
    text_path = os.path.join(SAVE_DIR, TEXT_FILE)
    with open(text_path, 'w', encoding='utf-8') as f:
        f.write(text)

    # ② コマンド作成
    command = [
        ASSISTANT_COMMAND,
        "-cid", str(cid),
        "-speed", str(speed),
        "-pitch", str(pitch),
        "-emotion", emotion, str(emotion_value),
        "-savepath", SAVE_DIR,
        "-f", text_path
    ]

    try:
        # ③ コマンド実行
        result = subprocess.run(command, capture_output=True, text=True, shell=True)
        if result.returncode != 0:
            print(result.stderr)  # ログにエラー出しておく
            return jsonify({"error": result.stderr}), 500
        return jsonify({"message": "音声生成成功", "stdout": result.stdout})
    except Exception as e:
        print(e)
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    app.run(port=5000)
ChatGPTの回答

こちらが実際に動かしてみた様子です。
一番下の欄に保存先フォルダのパスを指定できるようになっています。

画像の保存先を指定して動かしてみます。

しっかりと指定したフォルダに保存されています

🎨 AviUtlとの連携

✂️ 私が普段使っている動画編集ソフト:AviUtl

音声合成とあわせて動画編集を行う際、私は「AviUtl」というフリーの動画編集ソフトを使っています。
とても軽量で扱いやすく、有志の方が開発したプラグインを組み合わせることで非常に強力な機能拡張が可能なのが最大の魅力です。

その中でも、今回特に取り上げたいのが次のプラグインです。


🧚 PSDToolKitとは?

PSDToolKit は、VOICEROIDなどで生成した音声に合わせて**PSD形式の立ち絵をアニメーション(口パク・まばたき・表情切り替えなど)**させることができる、非常に便利なAviUtlプラグインです。

下記のような表情アニメーションやキャラクターの動作を、音声にシンクロさせて簡単に再現できます。

また、タイムラインでの見た目はこのようになります:

このプラグインのおかげで、音声さえあればキャラがしゃべっているようなアニメ動画をサクサク作れるのです。

詳細な活用例や使い方は、以下の方の解説も非常に参考になりますので、ご紹介させていただきます:
📘 https://taktk2525.hatenablog.com/entry/ar1717675

最高の立ち絵はふにちか様のものを利用させていただいております。
https://seiga.nicovideo.jp/seiga/im9432587


🎯 今回Webアプリで作る機能の目的

AviUtlでは、タイムラインの各オブジェクト(テキストや画像など)を .exo という独自形式で書き出すことができます。
逆に言えば、.exoファイルを自動で生成できれば、AviUtlにそのまま読み込ませて即編集に使うことが可能になります。

今回の目標は以下のとおりです:

  • VOICEROID2で生成した音声ファイルに合わせて
  • PSDToolKitに適したexoファイルも同時に自動出力
  • AviUtlでそのまま読み込める状態まで仕上げる

つまり、ChatGPT + Webアプリ + VOICEROID + PSDToolKit + AviUtl のシームレスな連携を実現するためのステップに入っていきます。

📁 生成するexoファイルのフォーマット調査

🔍 まずは形式を知るところから

PSDToolKitとAviUtlを連携させるには、音声と同期した.exoファイルを自動生成する必要があります。

まず手始めに、exoファイルの形式や中身を調査するところからスタートしました。


🧰 VoiceroidUtilを利用して調査開始

今回の調査にあたっては、普段から愛用している「VoiceroidUtil」を活用させていただきました 🙇‍♂️

このツールは、VOICEROIDで出力した音声と合わせて .exo ファイルも自動で生成してくれるという超便利な機能があります。

簡潔な手順としては:

  • 音声保存
  • PSDToolKit連携用の .exo ファイル出力

を一連の流れで行ってくれるため、この出力形式を参考にして自作のWebアプリ側でも同様の.exo構造を再現することを目指します。


📄 exoファイルはテキスト形式!

「exoファイルって特殊なバイナリなのかな…?」と思いきや、実際にはプレーンテキスト形式で書かれているため、中身を簡単にエディタで確認できます。

たとえば以下のような情報が含まれています:

  • 動画サイズやフレームレートなどの基本設定
  • 各レイヤーやオブジェクトの配置・タイミング
  • テキスト・音声・画像の情報(位置、サイズ、開始/終了など)

こうした情報をもとに、ChatGPTと連携しながら .exo を自動生成できる仕組みを考えていきます。

ちなみに以下が今回目標にする形式です(実際にVoiceroidUtil経由で保存してみました)

[exedit]
width=1920
height=1080
rate=30
scale=1
length=58
audio_rate=44100
audio_ch=2
[0]
start=1
end=58
layer=1
group=1
overlay=1
camera=0
[0.0]
_name=テキスト
サイズ=34
表示速度=0.0
文字毎に個別オブジェクト=0
移動座標上に表示する=0
自動スクロール=0
B=0
I=0
type=0
autoadjust=0
soft=1
monospace=0
align=0
spacing_x=0
spacing_y=0
precision=1
color=ffffff
color2=000000
font=MS UI Gothic
text=53308c306f30c630b930c8306730593000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
[0.1]
_name=標準描画
X=0.0
Y=0.0
Z=0.0
拡大率=100.00
透明度=0.0
回転=0.00
blend=0
[1]
start=1
end=58
layer=2
group=1
overlay=1
audio=1
[1.0]
_name=音声ファイル
再生位置=0.00
再生速度=100.0
ループ再生=0
動画ファイルと連携=0
file=E:\保存先\250622_104411_琴葉_葵_これはテストです.wav
[1.1]
_name=標準再生
音量=100.0
左右=0.0

次章では、ChatGPTとのプロンプト例と、それに基づく.exoファイルの自動生成ロジックを具体的に紹介していきます。

🧠 exoファイル実装ロジックの生成

実際に .exo ファイルを生成するロジックを組み込んでいきます。
ここまでの実装(音声ファイルの保存・パスの指定)は比較的シンプルで、エラーも少なくスムーズに構築できていました。

しかし、.exo ファイルの生成については、いくつかの点でエラーが発生しました。

今回のテーマのひとつは「ChatGPTを活用して開発する」こと。
エラーのたびにChatGPTに相談し、原因の特定から修正方法の提案まで助けてもらいながら実装を進めています。

以下では、どのように .exo ファイルの自動生成処理を構築していったか、実際のエラー対応も交えてご紹介していきます。


①まずは可能な限り、仕様を伝える

音声ファイルの保存が実装できたら、以下のようなプロンプトを投げかけてみました。

最初のプロンプト
# 音声が保存されたら、同時に以下のテンプレートに従ったexoファイルも作成したいです。
## 作成仕様は以下になります。
- UTF-8のテキストファイル
- {length_wavefile}には保存されたwavファイルの長さ(フレーム数)が入る
- {text_utf-16LE}は読み上げたテキストをUTF-16LEでエンコードしたもの
- {saveWaveFile}は保存されたwavファイルの名前(絶対パスで指定する)
- 作成されたwavファイルの数だけ作成する
- こちらがexoファイルのテンプレートです
[exedit]
width=1920
height=1080
rate=30
scale=1
length={length_wavefile}
audio_rate=44100
audio_ch=2
[0]
start=1
end={length_wavefile}
layer=1
group=1
overlay=1
camera=0
[0.0]
_name=テキスト
サイズ=34
表示速度=0.0
文字毎に個別オブジェクト=0
移動座標上に表示する=0
自動スクロール=0
B=0
I=0
type=0
autoadjust=0
soft=1
monospace=0
align=0
spacing_x=0
spacing_y=0
precision=1
color=ffffff
color2=000000
font=MS UI Gothic
text={text_utf-16LE}
[0.1]
_name=標準描画
X=0.0
Y=0.0
Z=0.0
拡大率=100.00
透明度=0.0
回転=0.00
blend=0
[1]
start=1
end={length_wavefile}
layer=2
group=1
overlay=1
audio=1
[1.0]
_name=音声ファイル
再生位置=0.00
再生速度=100.0
ループ再生=0
動画ファイルと連携=0
file={saveWaveFile}
[1.1]
_name=標準再生
音量=100.0
左右=0.0
## wavファイルは以下の形式で保存されます。
-SAY{nnnnnn}_{cid}.txt
### {nnnnnn}は連番(例、000001、000002、....、999999)
### genkou.txtの改行ごとに1wavファイルが保存される
### {cid}は連携されるcidです。(例、2001、2002)

✅ このプロンプトの工夫したポイント

なかなか複雑な実装をするので、以下の点に注意してプロンプトを作成しています。
基本的には生成されるファイルの仕様、連動してほしいwavファイルがどのような形で保存されるのか等を鳴鶴に伝えることを意識しました。

  • 音声と同期したexoファイルを自動生成する仕様を明示している
    → wavファイルごとにテキストと音声オブジェクトを自動挿入できる構成

  • 各プレースホルダの役割が明確

    • {length_wavefile}:wavファイルのフレーム長を自動挿入
    • {text_utf-16LE}:読み上げたテキストをUTF-16LEで埋め込む
    • {saveWaveFile}:音声ファイルの絶対パスを挿入
  • wavファイル生成との連携が明確

    • 1行ごとに1ファイル作成(セリフ単位)
    • ファイル名の命名ルール(連番+cid)を伝えている
  • exoファイルのテンプレートを提示

    • 調査したexoファイルの構造をそのまま提示し、なにを作成したいかを具体化
  • ファイルフォーマット・文字コードも明記

    • exoファイルはUTF-8、テキストはUTF-16LE指定といった細かな実装要件まで指示済み
ChatGPTの回答

ここまでの提案、仕様に合っているかご確認いただけますか?
OKであれば、この機能をserver.pyに統合した完全版コードをすぐお渡しできます!
👉【はい/少し修正したい】と教えてください!

とのことなので、このまま実装をしてもらおうと思います。
以下、プロンプトです。(text_utf-16LEだけ、少し認識違いも発生しそうだったので補足しました)

# はい ## 以下について、エンコードした結果(16進数文字列)をそのままテキストに埋め込めばよいです。 
- text_utf-16LE の扱い(AviUtlでは通常の文字列埋め込みでOKか確認) 
### 例、テスト→c630b930c830
ChatGPTの回答

この方針で問題なければ、完成版のコード統合案を提示します!
👉 よろしいでしょうか?【はい/調整したい点がある】

とのことなので、このまま続けてもらいましょう!
音声ファイルの保存自体はSeikaSay2を完全に頼る形式のため、その辺を改めて細く説明しています。
以下のプロンプトで補足しています。
こんな感じでやり取りしながら、ChatGPTの理解を補強しながら、進めていくことも重要なのかもしれませんね。

# 以下はSeikaSay2コマンドが実施します。
# そのため、exoファイルを生成するだけでOKです。
各行に対して .wavを生成
自動連番(SAY000001_2001.wav)をつける
ChatGPTの回答

このスクリプトを server.py に組み込む or 別スクリプトで使うかは用途次第です。
必要なら「Flask API化して、Vueから生成指示を出す」バージョンも作成可能です!
👉 統合したい/これで十分、どちらでしょうか?
今回は音声ファイル保存の流れで実装してほしいので、server.pyに組み込んでもらうことにします。
自分が考慮できなかった部分まで提案してくれるのは、生成AIの良いところですね。
というわけで、以下のプロンプトでさらに続けます。

プロンプト
# server.pyに組み込んでください。
# exoファイルは"save_path"に保管してください。
## 以下が現在のserver.pyです。
- server.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import subprocess
import os

app = Flask(__name__)
CORS(app)

TEXT_FILE = "genkou.txt"
ASSISTANT_COMMAND = "SeikaSay2"  # 正しい実行名に注意(seikasay2 なら小文字)

@app.route('/generate', methods=['POST'])
def generate():
    data = request.json
    text = data.get('text', '')
    cid = data.get('cid', '2002')
    speed = data.get('speed', '1.3')
    pitch = data.get('pitch', '1.1')
    emotion = data.get('emotion', '喜び')
    emotion_value = data.get('emotion_value', '0.2')
    save_path = data.get('savepath')  # ← クライアント側から受け取る

    if not text:
        return jsonify({"error": "text is required"}), 400

    if not save_path:
        return jsonify({"error": "savepath is required"}), 400

    if not os.path.exists(save_path):
        try:
            os.makedirs(save_path)
        except Exception as e:
            return jsonify({"error": f"保存先フォルダを作成できませんでした: {str(e)}"}), 500

    # テキストファイル作成
    text_path = os.path.join(save_path, TEXT_FILE)
    with open(text_path, 'w', encoding='utf-8') as f:
        f.write(text)

    # コマンド作成(文字列化)
    command = f'{ASSISTANT_COMMAND} -cid {cid} -speed {speed} -pitch {pitch} -emotion {emotion} {emotion_value} -savepath "{save_path}" -f "{text_path}"'

    try:
        result = subprocess.run(command, capture_output=True, text=True, shell=True)
        if result.returncode != 0:
            print(result.stderr)
            return jsonify({"error": result.stderr}), 500
        return jsonify({"message": "音声生成成功", "stdout": result.stdout})
    except Exception as e:
        print(e)
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    app.run(port=5000)

このプロンプトのポイントとして、実際に自分がコーディングしているserver.pyを伝えています。
ほぼほぼ、ChatGPTの言う通りに作っているのですが、コメントアウトだったり、 SeikaSay2コマンドの大文字、小文字だったりを若干変えたりしているので、念の為、実際に動かして、成功しているソースをそのまま再認識させています。
そして、以下がその回答です。

ChatGPTの回答

②動作確認しながら、エラーを解消していく

では、早速連携されたserver.pyをコピペして起動してみましょう!

# 自分のプロジェクトフォルダで実施
.\venv\Scripts\activate # 仮想環境を有効にする
python server.py # server.pyを起動

すると早速以下のようなエラーが発生します。

こちらもChatGPTに確認しましょう!

解析を依頼するプロンプトにはエラーメッセージもつけてあげましょう。

# サーバーの起動時に以下のエラーが発生しました。
# 解消方法を教えて下さい。
  File "E:\hoge\server.py", line 113 
    text_utf-16LE=encoded_text,
              ^
SyntaxError: invalid decimal literal
ChatGPTの回答

どうやら、プレースホルダーとして利用していた{text_utf-16LE}の記述が悪さをしていたみたいです。
以下が、修正ポイントのようなので、指示通りに修正してみましょう!

修正ポイント 変更前 変更後
テンプレートのプレースホルダー {text_utf-16LE} {text_utf16le}
.format() のキー名 text_utf-16LE=... text_utf16le=...
修正したserver.py
from flask import Flask, request, jsonify
from flask_cors import CORS
import subprocess
import os
import wave

app = Flask(__name__)
CORS(app)

TEXT_FILE = "genkou.txt"
ASSISTANT_COMMAND = "SeikaSay2"  # 正しい実行名に注意

EXO_TEMPLATE = """[exedit]
width=1920
height=1080
rate=30
scale=1
length={length_wavefile}
audio_rate=44100
audio_ch=2
[0]
start=1
end={length_wavefile}
layer=1
group=1
overlay=1
camera=0
[0.0]
_name=テキスト
サイズ=34
表示速度=0.0
文字毎に個別オブジェクト=0
移動座標上に表示する=0
自動スクロール=0
B=0
I=0
type=0
autoadjust=0
soft=1
monospace=0
align=0
spacing_x=0
spacing_y=0
precision=1
color=ffffff
color2=000000
font=MS UI Gothic
text={text_utf16le}
[0.1]
_name=標準描画
X=0.0
Y=0.0
Z=0.0
拡大率=100.00
透明度=0.0
回転=0.00
blend=0
[1]
start=1
end={length_wavefile}
layer=2
group=1
overlay=1
audio=1
[1.0]
_name=音声ファイル
再生位置=0.00
再生速度=100.0
ループ再生=0
動画ファイルと連携=0
file={saveWaveFile}
[1.1]
_name=標準再生
音量=100.0
左右=0.0
"""

def encode_utf16le_hex(text: str) -> str:
    return text.encode('utf-16le').hex()

def count_frames(wav_path: str) -> int:
    with wave.open(wav_path, 'rb') as wf:
        frames = wf.getnframes()
        framerate = wf.getframerate()
        duration = frames / framerate
        return int(duration * 30)

def generate_exo_files_from_wavs(save_dir: str, cid: str):
    genkou_path = os.path.join(save_dir, TEXT_FILE)
    if not os.path.exists(genkou_path):
        print("genkou.txt が見つかりません")
        return

    with open(genkou_path, 'r', encoding='utf-8') as f:
        lines = [line.strip() for line in f if line.strip()]

    for idx, text in enumerate(lines):
        serial = f"{idx+1:06}"
        wav_name = f"SAY{serial}_{cid}.wav"
        wav_path = os.path.join(save_dir, wav_name)

        if not os.path.exists(wav_path):
            print(f"{wav_name} が見つかりません。スキップします。")
            continue

        exo_name = f"SAY{serial}_{cid}.exo"
        exo_path = os.path.join(save_dir, exo_name)

        frame_length = count_frames(wav_path)
        encoded_text = encode_utf16le_hex(text)
        exo_content = EXO_TEMPLATE.format(
            length_wavefile=frame_length,
            text_utf16le=encoded_text,  # ← 修正済み
            saveWaveFile=wav_path.replace("\\", "\\\\")
        )

        with open(exo_path, 'w', encoding='utf-8') as exo_file:
            exo_file.write(exo_content)
        print(f"{exo_name} を生成しました。")

@app.route('/generate', methods=['POST'])
def generate():
    data = request.json
    text = data.get('text', '')
    cid = data.get('cid', '2002')
    speed = data.get('speed', '1.3')
    pitch = data.get('pitch', '1.1')
    emotion = data.get('emotion', '喜び')
    emotion_value = data.get('emotion_value', '0.2')
    save_path = data.get('savepath')

    if not text:
        return jsonify({"error": "text is required"}), 400

    if not save_path:
        return jsonify({"error": "savepath is required"}), 400

    if not os.path.exists(save_path):
        try:
            os.makedirs(save_path)
        except Exception as e:
            return jsonify({"error": f"保存先フォルダを作成できませんでした: {str(e)}"}), 500

    # genkou.txt を保存
    text_path = os.path.join(save_path, TEXT_FILE)
    with open(text_path, 'w', encoding='utf-8') as f:
        f.write(text)

    # SeikaSay2 実行
    command = f'{ASSISTANT_COMMAND} -cid {cid} -speed {speed} -pitch {pitch} -emotion {emotion} {emotion_value} -savepath "{save_path}" -f "{text_path}"'

    try:
        result = subprocess.run(command, capture_output=True, text=True, shell=True)
        if result.returncode != 0:
            print(result.stderr)
            return jsonify({"error": result.stderr}), 500

        # 音声生成後、EXOファイルを生成
        generate_exo_files_from_wavs(save_path, cid)

        return jsonify({"message": "音声とEXO生成成功", "stdout": result.stdout})
    except Exception as e:
        print(e)
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    app.run(port=5000)

無事起動成功しました。

では、今度はクライアントからリクエストを投げてみます

Flaskサーバーのログでは、成功しています。

保存先にも保存されています!

③細かな調整をしていく

実はここまでやって、exoファイルがUTF-8ではなく、SHIFT_JISでエンコードするべきということに気が付きました。(先ほどの生成された一式だけでは、AviUtlではうまく読み込めません。)
というわけで最後に以下のようなプロンプトでshift_jisでのエンコードを依頼します。

# 以下の部分について、shift-jisでエンコードするようにしてください。
        with open(exo_path, 'w', encoding='utf-8') as exo_file:
            exo_file.write(exo_content)
        print(f"{exo_name} を生成しました。")
ChatGPTの回答

また、こちらは単純に出力されたexoファイルを見ていて、気づいたことになりますが、音声ファイルのパスを作成している以下の記載は\\\\にする必要がないので修正しておきます。(これは特にChatGPTに聞くまでもなく修正してしまいました。。)

# 修正前
            saveWaveFile=wav_path.replace("\\", "\\\\")

# 修正後
            saveWaveFile=wav_path.replace("\\", "\\\\")

④実際に動かしてみる

ここまで実装してきましたが、最終的にできたserver.py(サーバーサイドソース)とApp.vue(クライアントサイドソース)はこちらです。

server.py
App.vue

ここまでできたら、実際に動かしてみましょう!

  1. server.pyを起動

  2. Vueプロジェクトを起動

  3. VOICEROID2とAssistantSeikaを起動

投稿者は以下のような設定で利用しています!

  1. http://localhost:5173/にアクセスして音声生成

しっかりと生成されています!

  1. AviUtlで読み込めることの確認

しっかりと読み込めました!

簡単なアプリですが、やりたいことが達成できるとなんだか感動しますね!


✅ まとめ

ここまで3回にわたって記事を投稿してきましたが、我ながら 実用的なWebアプリ を作れたのではないかと思っています。
実際にはもっと紆余曲折もありました(投稿者はPython初学者なので…)が、驚くほどスムーズに開発を進めることができました

特に、ChatGPTを活用した設計・開発で感じたメリットは以下の通りです。


🌱 自分の領域を広げられる

私はPythonが全くの初心者で、今回が初めて本格的な実装へのチャレンジでした。
それでも、ここまで形にできたのはChatGPTのおかげです。

普段からシステムエンジニアとして仕事をしていたり、趣味で何かを開発している人であれば、プログラミング言語の違いは「方言」みたいなもの。
そういった人にとって、たたき台をすぐに提示してくれるChatGPTは、新しい技術に踏み出す強力なサポーターになると思います。


💡 自分が知らない領域のアイディアを活用できる

FlaskでWebサーバーを立てる、というアイディアなんて、Python超初心者の自分からは絶対に出てこない発想でした。
でも「なんとなくこうしたい」と伝えるだけで、設計を具体化してくれるChatGPTの提案力は本当に頼もしいです。


今回のチャレンジを通じて、ChatGPTの可能性の大きさを改めて実感しました。
これからも、いろいろなアイディアを形にしていきたいと思います。

ぜひ、この記事を読んでくださった皆さんのアイディアもコメントでシェアしてもらえたら嬉しいです。
実際に作ったアプリなどは、GitHubでの公開も検討中ですので、興味があればお気軽にコメントしてください!

🐭 おまけ

実際のやり取りはもうちょっといろいろエラー出たり、自分が知らないことだらけで色々質問したりしています。
実際のChatGPTとのチャットを公開します。
参考にしてください。

参考:実際のやり取り

Discussion