Книга целиком: пример
Файл книги → цена → озвучка всех глав → M4B. Один скрипт на Python и на JavaScript.
Путь книги через API — тот же, что в кабинете:
- загрузить файл — программа разделит текст на главы, найдёт героев и подберёт им голоса;
- узнать цену — уже озвученное бесплатно;
- поставить главы в очередь и дождаться;
- собрать M4B (или ZIP с MP3 по главам) и скачать.
Вместо опроса задач раз в 15 секунд можно подключить уведомления.
import os, time, requests
API = "https://golosar-kniga.tech/api/v1"
H = {"x-api-key": os.environ["GOLOSAR_KEY"]}
def wait(job_id):
while True:
job = requests.get(f"{API}/jobs/{job_id}", headers=H).json()["job"]
if job["status"] not in ("queued", "running"):
return job
time.sleep(15)
# 1. книга из файла
r = requests.post(f"{API}/books/upload", headers=H, files={"file": open("book.fb2", "rb")})
r.raise_for_status()
book_id = r.json()["book"]["id"]
book = requests.get(f"{API}/books/{book_id}", headers=H).json()["book"]
ids = [c["id"] for c in book["chapters"] if c["status"] != "ready"]
# 2. цена до озвучки
q = requests.post(f"{API}/books/{book_id}/quote", headers=H, json={"chapter_ids": ids}).json()
print("спишется", q["credits"], "знаков, на балансе", q["left"], "готово примерно через", q["ready_s"], "с")
if q["short"] > 0:
raise SystemExit("не хватает знаков: " + str(q["short"]))
# 3. озвучить все главы
jobs = []
for cid in ids:
r = requests.post(f"{API}/chapters/{cid}/render", headers={**H, "Idempotency-Key": f"render-{cid}"})
r.raise_for_status()
jobs.append(r.json()["job"]["id"])
for j in jobs:
done = wait(j)
assert done["status"] == "done", done["error"]
# 4. собрать M4B и скачать
job = requests.post(f"{API}/books/{book_id}/export", headers=H, json={"format": "m4b"}).json()["job"]
wait(job["id"])
url = requests.get(f"{API}/books/{book_id}/exports", headers=H).json()["files"]["m4b"]["download_url"]
open("book.m4b", "wb").write(requests.get(url).content)
import { openAsBlob } from "node:fs";
import { writeFile } from "node:fs/promises";
const API = "https://golosar-kniga.tech/api/v1";
const KEY = { "x-api-key": process.env.GOLOSAR_KEY };
const H = { ...KEY, "content-type": "application/json" };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const call = async (path, opts = {}) => {
const r = await fetch(`${API}${path}`, { headers: H, ...opts });
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
return r.json();
};
const wait = async (id) => {
for (;;) {
const { job } = await call(`/jobs/${id}`);
if (!["queued", "running"].includes(job.status)) return job;
await sleep(15000);
}
};
// 1. книга из файла
const form = new FormData();
form.append("file", await openAsBlob("book.fb2"), "book.fb2");
const up = await call("/books/upload", { method: "POST", headers: KEY, body: form });
const { book } = await call(`/books/${up.book.id}`);
const ids = book.chapters.filter((c) => c.status !== "ready").map((c) => c.id);
// 2. цена до озвучки
const q = await call(`/books/${book.id}/quote`, { method: "POST", body: JSON.stringify({ chapter_ids: ids }) });
console.log("спишется", q.credits, "знаков; на балансе", q.left);
if (q.short > 0) throw new Error(`не хватает ${q.short} знаков`);
// 3. озвучить все главы
for (const id of ids) {
const { job } = await call(`/chapters/${id}/render`, { method: "POST", headers: { ...H, "Idempotency-Key": `render-${id}` } });
const done = await wait(job.id);
if (done.status !== "done") throw new Error(done.error);
}
// 4. собрать M4B и скачать
const ex = await call(`/books/${book.id}/export`, { method: "POST", body: JSON.stringify({ format: "m4b" }) });
await wait(ex.job.id);
const { files } = await call(`/books/${book.id}/exports`);
await writeFile("book.m4b", Buffer.from(await (await fetch(files.m4b.download_url)).arrayBuffer()));