Nodes/FishAudioComfyUI/Fish Generate Audio (Ace Data Cloud)
ComfyUI Node

Fish Generate Audio (Ace Data Cloud)

Fish Generate Audio bills you every Queue press — and hands you a task ID to babysit

By AceDataCloud·Created 3 days ago·Updated 2 days ago· 0
Fish Generate Audio (Ace Data Cloud)
    • status
    • task_id
    • media_urls_json
    • first_media_url
    • result_json
    • trace_id
    ◄parameters_json{}►
    ◄model(from JSON/default)►
    ◄text►
    ◄reference_id►
    ◄format►
    ◄sample_rate►
    ◄mp3_bitrate►
    ◄latency►
    ◄chunk_length►
    ◄min_chunk_length►
    ◄temperature►
    ◄top_p►
    ◄repetition_penalty►
    ◄max_new_tokens►
    ◄normalize►
    ◄prosody►
    ◄references►
    ◄callback_url►

    What this node actually is

    It is not a TTS model. Nothing downloads, nothing loads into VRAM, and pressing Queue does not spin up your GPU - it sends your text over HTTPS to Fish Audio, hosted by Ace Data Cloud, and hands you back a URL to an audio file. The whole pack's dependency list is requests and jsonschema. That is the entire install cost, which is genuinely nice if you have ever spent an evening untangling transformers versions to get a local TTS suite running.

    So why would you route out? Because audio is the one layer of this ecosystem that was retrofitted rather than designed in. Local speech is real now - Chatterbox is good enough that people stopped treating a paid API as mandatory for cloning - but the honest state of it in ComfyUI is a maintained workaround, where one new engine breaks three old ones on a dependency conflict. When you need a specific performance, an emotional read, or a language the local stack handles badly, calling a service is the boring reliable move. This node is the on-ramp.

    How the call works

    The node assembles a JSON body, validates it against the pack's vendored copy of Fish Audio's published contract with jsonschema, and only then sends POST /fish/tts with Authorization: Bearer <your key>. The auth token is read from the ComfyUI process environment at run time (ACEDATACLOUD_API_KEY, or ACEDATACLOUD_API_TOKEN as a fallback), never from the workflow. Because validation happens first, a typo in a field name fails locally and costs you nothing.

    Two behaviours matter more than anything else here. Every submission is asynchronous - the client sets async: true, so the node returns almost immediately with status = pending and a task_id. That's a healthy response, not an error; the audio arrives when you ask again. And paid POSTs are never retried, while read-only lookups get three attempts with backoff. So if the connection drops after the service accepted the job, you have a queued task and no confirmation in the graph. Copy the task_id out of the node output, check it with Retrieve Task, and don't blindly press Queue again.

    The inputs worth setting

    Start with text (the only required field) and reference_id (which voice to speak it in - a voice ID from Search Voices or Get Voice). Everything else is optional and empty means "omit it."

    • model - a selector with s1, s2-pro, s2.1-pro, plus (from JSON/default). Leave the sentinel alone and you get the API's own default, which the vendored contract lists as s2-pro.
    • format, sample_rate, mp3_bitrate - output container and quality. The contract allows mp3, wav, pcm and bitrates of 64/128/192. Default format is mp3; pick wav or pcm if something downstream is picky about decoding.
    • latency - normal or balanced.
    • temperature, top_p, repetition_penalty, max_new_tokens - the usual sampling knobs, applied to the speech model. Small nudges on temperature change delivery more than you'd expect.
    • chunk_length / min_chunk_length - long text gets split; relevant mostly when a paragraph goes weird halfway through.
    • normalize - text normalisation, a true/false string. prosody takes a JSON object if you need delivery controls.
    • references - one-shot cloning in a single field: a JSON array with exactly one object holding an audio URL and its text transcript, per the contract.
    • callback_url - where the service notifies you instead of you polling. parameters_json is the escape hatch for anything without a widget, and a non-empty widget always overrides its JSON twin.

    Widget values are strings and get coerced to the types the contract declares, so 128 and true are fine typed as text. Arrays and objects go in as JSON strings.

    Outputs and where they wire

    Six strings: status, task_id, media_urls_json, first_media_url, result_json, trace_id.

    first_media_url is the one you wire - into Fish Audio Load Audio, which downloads it and emits a proper ComfyUI AUDIO. From there it's a normal audio pipeline: SaveAudio, or straight into a talking-head node like InfiniteTalk or Wan S2V so the face moves to the line. task_id is your receipt; keep it. result_json holds the whole normalised response, and trace_id is what support will ask for if a run goes sideways. Note that media URLs are only populated once the task is actually done - while it's pending, first_media_url is an empty string.

    Install

    In ComfyUI Manager, search Fish Audio, confirm the publisher is acedatacloud and the package is Fish Audio by Ace Data Cloud (registry ID fish-audio), install, restart. Manually:

    cd ComfyUI/custom_nodes
    git clone https://github.com/AceDataCloud/FishAudioComfyUI
    cd FishAudioComfyUI && python -m pip install -r requirements.txt
    

    Then set the key in the environment ComfyUI itself starts from - not in a shell you opened afterwards - and restart the server:

    export ACEDATACLOUD_API_KEY=your_key_here
    

    Keep it out of workflow JSON and screenshots. The example workflow in examples/01-configure-and-run.json imports through File → Open and is a template; it doesn't submit anything by itself.

    Where people get burned

    • "Set ACEDATACLOUD_API_KEY in the ComfyUI process environment and restart." That is the exact error when the key is missing. Exporting it in a terminal you then Ctrl-C'd out of, or in a systemd unit you didn't reload, is the usual cause.
    • Loading a pending task. If you wire an empty first_media_url into Load Audio, it raises "Task is pending; retrieve the same task ID again." That is not a bug, it's the node telling you the job hasn't finished. Add a Retrieve Task node with wait_seconds set and feed Load Audio from that instead.
    • Paying twice for one line. Paid runs consume credits at the current service price, so check the pricing page before a batch. Read-only task queries and media downloads shouldn't open a new paid job - which is exactly why the retry policy is asymmetric. On a connection failure, look up the task ID; don't resubmit.
    • Failed tasks raise. If the service reports an error, the node throws "Task failed. Use its task or trace ID to inspect the service request." The failure detail lives in the service's request history, not in the graph.
    CategoryAce Data Cloud/Fish Audio

    Inputs (18)

    NameTypeDefaultDescription
    parameters_jsonSTRING{}—
    modelCOMBO(from JSON/default)4 options: (from JSON/default), s1, s2-pro, s2.1-pro
    textSTRING—
    reference_idSTRING—
    formatSTRING—
    sample_rateSTRING—
    mp3_bitrateSTRING—
    latencySTRING—
    chunk_lengthSTRING—
    min_chunk_lengthSTRING—
    temperatureSTRING—
    top_pSTRING—
    repetition_penaltySTRING—
    max_new_tokensSTRING—
    normalizeSTRING—
    prosodySTRING—
    referencesSTRING—
    callback_urlSTRING—

    Outputs (6)

    NameTypeDescription
    statusSTRING—
    task_idSTRING—
    media_urls_jsonSTRING—
    first_media_urlSTRING—
    result_jsonSTRING—
    trace_idSTRING—