Fish Audio APIでイーロンマスク風の音声で`text to speech`する際のメモ

Fish Audio APIでイーロンマスク風の音声で`text to speech`する際の備忘メモです
尚、個人アカウントでFree版を使用しています。実装は基本的にpythonです。

https://fish.audio/ja/

## 準備

実行環境：`Macbook Pro macOS Monterey 12.5.1, Apple M1 Max, 32GB memory`
pythonバージョン：

```python
$ python --version
Python 3.12.5
```

[こちらを参考に](https://qiita.com/seigot/items/256e04d326dc009313f2)`venv`環境を構築

```shell
$ python3 -m venv venvtest  
$ source venvtest/bin/activate
```

[こちらを参考に](https://github.com/fishaudio/fish-audio-python)`fish audio python library`をインストール

```shell
$ pip install fish-audio-sdk
```

APIコールに必要な準備をする
- [APIキーを作成](https://fish.audio/ja/go-api/api-keys/)
- [text-to-speechを参照してAPIの使用例を確認(pythonの例)](https://docs.fish.audio/text-to-speech/text-to-speech)
- チャージ（例えばお試しで$1以下を課金）

## 実行

実際に`text to speech API`コール

ポイント
- "your_api_key"；ここは"FISHAUDIO API KEY"を指定するように変更
- "MODEL_ID_UPLOADED_OR_CHOSEN_FROM_PLAYGROUND": ここは期待する"reference_id"を指定するように変更（[イーロンマスク風音声の場合はこちらからモデルIDをコピーする](https://fish.audio/ja/m/03397b4c4be74759b72533b663fbd001/)）

```python:example.py
from fish_audio_sdk import Session, TTSRequest, ReferenceAudio

# ここは"FISHAUDIO API KEY"を指定するように変更
session = Session("your_api_key")

# Option 1: Using a reference_id
with open("output1.mp3", "wb") as f:
    for chunk in session.tts(TTSRequest(
        # ここは期待する"reference_id"を指定するように変更
        reference_id="MODEL_ID_UPLOADED_OR_CHOSEN_FROM_PLAYGROUND",
        text="Hello, world!"
    )):
        f.write(chunk)
```

出力ファイル確認

```shell
$ python example.py
## "output1.mp3" というファイルが出力されていればOK
$ ls
output1.mp3
```

音声ファイルを再生（今回はmp3だが他にもフォーマット指定が可能）

```
## 例えばQuick Playerを使ってコマンドラインから再生する場合はqmanageを使えばOK
$ qlmanage -p output1.mp3
```

→ 指定したテキストファイルが読み上げられたら動作OK（今回は`Hello, world!`と言えばOK）

`Hello World!`だと分かりづらいので、サンプルと同じ文章を与える
[Elon Musk(Noise reduction)](https://fish.audio/ja/m/03397b4c4be74759b72533b663fbd001/)

```shell
text="So, the key to effective noise reduction is... um... understanding signal processing at a fundamental level. We're basically teaching AI to distinguish between what humans want to hear and what they don't. It's like teaching a computer to be really good at selective hearing."
```

`275文字`だと大体変換に4sec掛かる

```shell
$ time python example.py
python3 example.py  0.22s user 0.04s system 6% cpu 3.981 total
```

フォーマットを`mp3`-->`wav`に変える場合は以下をオプション指定

```shell
...
with open("output1.mp3", "wb") as f:
...
        format="wav"
```

`wav`に変えた場合は変換時間が若干伸びている気がする

```shell
% time python example.py
python3 example.py  0.23s user 0.05s system 5% cpu 4.663 total
```

この場合は、　約17sec, `1.4MB`のWAVデータになる

```
% ls -lh output1.wav
-rw-r--r--@ 1 xxx  xxx  1.4M  2  4 00:00 output1.wav
```

## `C言語`の場合

[こちらを参考](https://ec.haxx.se/install/macos.html)に`libcurl`をインストール
以下はMacOSの場合

```
brew install curl 
```

コード
- "your_api_key"；ここは"FISHAUDIO API KEY"を指定するように変更

```c:example.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

// 書き込みコールバック関数
size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) {
    size_t written = fwrite(ptr, size, nmemb, (FILE *)stream);
    return written;
}

int main(void) {
    CURL *curl;
    CURLcode res;
    FILE *fp;
    const char *output_filename = "output2.wav";

    // APIエンドポイントのURL（例）
    const char *url = "https://api.fish.audio/v1/tts";

    // APIキー
    // "your_api_key"；ここは"FISHAUDIO API KEY"を指定するように変更
    const char *api_key = "your_api_key";

    // JSON形式のリクエストデータを組み立てる（例）
    // 実際のフォーマットはAPI仕様に合わせてください
    const char *json_data = "{"
        "\"reference_id\": \"03397b4c4be74759b72533b663fbd001\","
        "\"text\": \"So, the key to effective noise reduction is... um... understanding signal processing at a fundamental level. We're basically teaching AI to distinguish between what humans want to hear and what they don't. It's like teaching a computer to be really good at selective hearing.\","
        "\"format\": \"wav\""
    "}";

    // libcurlの初期化
    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();
    if(curl) {
        // 出力ファイルをバイナリ書き込みでオープン
        fp = fopen(output_filename, "wb");
        if (!fp) {
            fprintf(stderr, "ファイルを開けません: %s\n", output_filename);
            return 1;
        }

        // URL設定
        curl_easy_setopt(curl, CURLOPT_URL, url);

        // HTTP POSTを行う設定
        curl_easy_setopt(curl, CURLOPT_POST, 1L);

        // POSTフィールドとしてJSONデータを送信
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data);

        // APIキーをヘッダに設定（例：Authorizationヘッダとして）
        struct curl_slist *headers = NULL;
        char auth_header[256];
        snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", api_key);
        headers = curl_slist_append(headers, "Content-Type: application/json");
        headers = curl_slist_append(headers, auth_header);
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

        // レスポンスデータの書き込み先を設定
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);

        // リクエストの実行
        res = curl_easy_perform(curl);
        if(res != CURLE_OK) {
            fprintf(stderr, "curl_easy_perform() 失敗: %s\n", curl_easy_strerror(res));
        }

        // 後始末
        fclose(fp);
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }
    curl_global_cleanup();
    return 0;
}
```

コンパイル

```sh
$ gcc -o tts example.c -lcurl
```

実行

```sh
$ ./tts
```

以下データが取得できていれば＆再生できればOK

```
"output2.wav"
```

## `C++`の場合

```example.cpp
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <curl/curl.h>

// 書き込みコールバック関数
size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) {
    return fwrite(ptr, size, nmemb, static_cast<FILE*>(stream));
}

int main() {
    CURL *curl;
    CURLcode res;
    FILE *fp;
    const char *output_filename = "output2.wav";

    // APIエンドポイントのURL
    const char *url = "https://api.fish.audio/v1/tts";

    // APIキー ("FISHAUDIO API KEY"に置き換えてください)
    const char *api_key = "your_api_key";

    // JSON形式のリクエストデータを生文字列リテラルで宣言
    // R"(...)" の形式を使うと、内部に " や \n をそのまま書けます
    const char *json_data = R"({
        "reference_id": "03397b4c4be74759b72533b663fbd001",
        "text": "So, the key to effective noise reduction is... um... understanding signal processing at a fundamental level. We're basically teaching AI to distinguish between what humans want to hear and what they don't. It's like teaching a computer to be really good at selective hearing.",
        "format": "wav"
    })";

    // libcurlの初期化
    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();
    if (curl) {
        // 出力ファイルをバイナリ書き込みでオープン
        fp = fopen(output_filename, "wb");
        if (!fp) {
            std::cerr << "ファイルを開けません: " << output_filename << std::endl;
            curl_easy_cleanup(curl);
            curl_global_cleanup();
            return 1;
        }

        // URL設定
        curl_easy_setopt(curl, CURLOPT_URL, url);

        // HTTP POSTを行う設定
        curl_easy_setopt(curl, CURLOPT_POST, 1L);

        // POSTフィールドとしてJSONデータを送信
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data);

        // APIキーをヘッダに設定
        struct curl_slist *headers = nullptr;
        {
            // Authorizationヘッダを用意
            char auth_header[256];
            snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", api_key);

            headers = curl_slist_append(headers, "Content-Type: application/json");
            headers = curl_slist_append(headers, auth_header);
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        }

        // レスポンスデータの書き込み先を設定
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);

        // リクエストの実行
        res = curl_easy_perform(curl);
        if (res != CURLE_OK) {
            std::cerr << "curl_easy_perform() 失敗: "
                      << curl_easy_strerror(res) << std::endl;
        }

        // 後始末
        fclose(fp);
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }
    curl_global_cleanup();

    return 0;
}
```

コンパイル

```
g++ -std=c++11 -o tts example.cpp -lcurl
```

実行

```
$ time ./tts
./tts  0.04s user 0.03s system 1% cpu 4.588 total
$ ls -l output2.wav 
-rw-r--r--  1 xxx  xxx  1208364  x xx xx:xx output2.wav
```

テキストを"300m ahead, turn right on Test Street."にすると、

```
        "text": "300m ahead, turn right on Test Street.",
```

おおよそ3秒ほど（200kByte程）のデータになる

```
ls -l output2.wav 
xxx204844 xxx output2.wav
```

```
time ./tts                              
./tts  0.02s user 0.01s system 1% cpu 1.813 total
```

## トラブルシューティング

### `fish_audio_sdk.exceptions.HttpCodeErr: 402 Payment Required`

→ この機能を利用するには課金が必要です（課金してください）というエラー、一旦課金すると解決する

> https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402
> The HTTP 402 Payment Required client error response status code is a nonstandard response status code reserved for future use.

### `fish_audio_sdk.exceptions.HttpCodeErr: 500 Internal Server Error`

→ サーバ側でエラー、例えばAPIパラメータのうち`reference_id`が間違っているとこのような結果が返ることがある

---

以上です