Skip to content

MOSS-TTS Local Transformer

Clones a voice in 31 languages, including Cantonese, Persian and Tagalog.

Access: Keys with the media:generate and media:read scopes, on an account with media access granted by Albastar Corp.

Voice cloning31 languagesmoss-local

Samples

ReferenceNarrator A · synthetic

0:00 / 0:14

Transcript Okay, so here's the plan. We land just after sunset, grab the bags, and head straight for the harbour. If the ferry's late again — and honestly, it usually is — we'll get dinner by the water and wait it out. Sound good?

CloneMOSS-TTS Local Transformer in the reference voice

0:00 / 0:11

Transcript I found the photos from the lake house. Do you remember that summer? We stayed up until three, arguing about the stars, and you swore you'd seen a comet. I still think it was a plane.

Capabilities

Model
MOSS-TTS-Local-Transformer v1.5 from the OpenMOSS team; the API id is moss-local
Voice cloning
From voice: a saved voice vo_…, an upload up_… or an https URL, with or without reference_text. The model uses at most 20 s of the clip; a longer clip is cut at a pause between 5 and 20 s
Languages
31: Arabic, Cantonese, Chinese, Czech, Danish, Dutch, English, Finnish, French, German, Greek, Hebrew, Hindi, Hungarian, Italian, Japanese, Korean, Macedonian, Malay, Persian, Polish, Portuguese, Romanian, Russian, Spanish, Swahili, Swedish, Tagalog, Thai, Turkish, Vietnamese (model card); the request has no language parameter
Seed
seed on a media job, 0 to 4294967295: the same seed and parameters give the same audio
Text
Up to 5,000 characters per request

Request

The request that made the sample above, as a media job with seed 7; a sample made again after a failed job or a failed check uses seed 8, then 9, and so on. vo_tBeDCRb2byGFjB6HGyQq6a is Narrator A, a synthetic voice shared with every key that has the media:generate scope. format takes mp3, wav or opus.

curl

text="I found the photos from the lake house. Do you remember that summer?"
text+=" We stayed up until three, arguing about the stars, and you swore"
text+=" you'd seen a comet. I still think it was a plane."

job=$(curl -sS "https://api.albastov.com/v1/media/jobs?wait=60" \
  -H "Authorization: Bearer $ALBASTAR_API_KEY" \
  -H "Content-Type: application/json" \
  --data @- <<EOF
{
  "model": "moss-local",
  "params": {
    "text": "$text",
    "voice": "vo_tBeDCRb2byGFjB6HGyQq6a",
    "seed": 7,
    "format": "mp3"
  }
}
EOF
)

while [[ $(jq -r .state <<<"$job") =~ ^(queued|waiting_device|running)$ ]]; do
  job=$(curl -sS \
    "https://api.albastov.com/v1/media/jobs/$(jq -r .id <<<"$job")?wait=60" \
    -H "Authorization: Bearer $ALBASTAR_API_KEY")
done

curl -sS -o lake-house.mp3 "$(jq -er '.results[0].url' <<<"$job")"

Python

import json
import os
import urllib.request

API = "https://api.albastov.com/v1"
HEADERS = {
    # urllib's default User-Agent gets 403 with error code 1010.
    "User-Agent": "my-app/1.0",
    "Authorization": f"Bearer {os.environ['ALBASTAR_API_KEY']}",
    "Content-Type": "application/json",
}
TEXT = (
    "I found the photos from the lake house. Do you remember that summer?"
    " We stayed up until three, arguing about the stars, and you swore"
    " you'd seen a comet. I still think it was a plane."
)


def fetch(url, body=None):
    data = json.dumps(body).encode() if body else None
    req = urllib.request.Request(url, data=data, headers=HEADERS)
    with urllib.request.urlopen(req, timeout=120) as res:
        return res.read()


job = json.loads(fetch(f"{API}/media/jobs?wait=60", {
    "model": "moss-local",
    "params": {
        "text": TEXT,
        "voice": "vo_tBeDCRb2byGFjB6HGyQq6a",
        "seed": 7,
        "format": "mp3",
    },
}))
while job["state"] in ("queued", "waiting_device", "running"):
    job = json.loads(fetch(f"{API}/media/jobs/{job['id']}?wait=60"))
if job["state"] != "done":
    raise RuntimeError(job["error"])
with open("lake-house.mp3", "wb") as f:
    f.write(fetch(job["results"][0]["url"]))

JavaScript

import { writeFile } from "node:fs/promises";

const api = "https://api.albastov.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.ALBASTAR_API_KEY}`,
  "Content-Type": "application/json",
};
const text =
  "I found the photos from the lake house. Do you remember that summer?" +
  " We stayed up until three, arguing about the stars, and you swore" +
  " you'd seen a comet. I still think it was a plane.";

async function call(path, body) {
  const res = await fetch(api + path, {
    method: body ? "POST" : "GET",
    headers,
    body: body && JSON.stringify(body),
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  return res.json();
}

let job = await call("/media/jobs?wait=60", {
  model: "moss-local",
  params: {
    text,
    voice: "vo_tBeDCRb2byGFjB6HGyQq6a",
    seed: 7,
    format: "mp3",
  },
});
while (["queued", "waiting_device", "running"].includes(job.state)) {
  job = await call(`/media/jobs/${job.id}?wait=60`);
}
if (job.state !== "done") throw new Error(JSON.stringify(job.error));
const audio = await fetch(job.results[0].url);
if (!audio.ok) throw new Error(`${audio.status} ${await audio.text()}`);
await writeFile("lake-house.mp3", Buffer.from(await audio.arrayBuffer()));

With ?wait=60, the response arrives when the job ends, or after 60 s with the job still queued, waiting_device or running; the loop asks again until the job ends. GET /v1/media/jobs/{id} needs the media:read scope. results[0].url is the MP3 file and needs no key.

POST /v1/audio/speech takes the same text as input and the same voice, without seed: Speech reference. Every job parameter, state and error: Media jobs reference.