codai docs
Gateway

Randări

Randări codai Studio — transformi o compoziție HyperFrames HTML (cu asset-urile tale Studio) într-un MP4, apoi interoghezi până când e gata.

O randare transformă un video descris în HTML într-un MP4. Trimiți o compoziție HyperFrames, gateway-ul o pune în coada worker-ului de randare, iar tu interoghezi până când video-ul e gata. Id-ul public al modelului este codai-render.

MetodăCaleRezultat
POST/v1/studio/renders202 — randarea e în coadă; întoarce id-ul și prețul
GET/v1/studio/renders/{id}Starea, progress și asset-ul MP4 după succeeded

Compoziții HyperFrames

O compoziție este un document HTML complet (index.html, până la 2.000.000 de caractere), așezat pe o linie de timp cu atribute data-*:

  • elementul rădăcină are data-composition-id, data-width și data-height (pânza, în pixeli);
  • fiecare clip (text, imagine, video, audio, orice element) are data-start și data-duration în secunde și un data-track-index (pistele mai mari se desenează deasupra);
  • animațiile CSS și JavaScript rulează așa cum sunt scrise. GSAP este permis, la fel Lottie, Three.js și anime.js — URL-urile lor de CDN sunt servite din copii locale în randator, așa că un URL de CDN obișnuit funcționează.

Ieșirea durează până la finalul ultimului clip (data-start + data-duration), cel mult 180 de secunde.

<div data-composition-id="launch" data-width="1920" data-height="1080" style="background:#0b0b10">
  <img class="clip" src="asset:3f2b8c1e-0000-4000-8000-000000000001"
       data-start="0" data-duration="8" data-track-index="0" style="width:100%;height:100%;object-fit:cover" />
  <h1 id="title" class="clip" data-start="1" data-duration="6" data-track-index="1">codai Studio</h1>
  <audio class="clip" src="asset:3f2b8c1e-0000-4000-8000-000000000002"
         data-start="0" data-duration="8" data-track-index="2"></audio>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
  <script>gsap.from('#title', { opacity: 0, y: 40, duration: 1, delay: 1 });</script>
</div>

Folosirea asset-urilor Studio

Referă orice asset al tău ca asset:<asset_id> într-un src, href sau url(...) din CSS — imagini generate, clipuri muzicale, video-uri, vorbire sau fișiere încărcate. Înainte de a pune randarea în coadă, gateway-ul verifică faptul că fiecare asset referit există, îți aparține și este ready; altfel cererea eșuează cu 400 asset_not_found și nu se taxează nimic. Cel mult 50 de asset-uri distincte per randare.

Pentru lovituri, whoosh-uri și alte efecte sonore, importă gratuit un sunet CC0 din biblioteca de efecte sonore și referă asset-ul rezultat la fel.

Pentru video-uri narate, creează vocea cu POST /v1/audio/speech și { "model": "codai-speech", "store": true, "timestamps": true }: asset.asset_id din răspuns intră în <audio src="asset:<id>">, iar words ([{ word, start, end }]) îți dă momentele pentru subtitrări (vezi text-to-speech).

variables (opțional) este un obiect cu variabilele compoziției HyperFrames, pentru șabloane care citesc valori la randare.

Cererea

{
  "model": "codai-render",
  "html": "<div data-composition-id=\"launch\" data-width=\"1920\" data-height=\"1080\">…</div>",
  "aspect_ratio": "16:9",
  "quality": "final",
  "fps": 30,
  "title": "Launch teaser"
}
CâmpImplicitSemnificație
html—Compoziția (obligatoriu).
modelcodai-renderSingurul model de randare.
aspect_ratio16:916:9 (1920×1080), 9:16 (1080×1920) sau 1:1 (1080×1080). Trebuie să se potrivească cu data-width/data-height ale rădăcinii, altfel 400.
qualityfinalfinal sau draft (vezi mai jos).
fps3024, 25 sau 30; doar la final.
variables—Variabilele compoziției HyperFrames.
title—Până la 200 de caractere, stocat cu asset-ul.
ai_labelautomatcorner, end_card sau none — eticheta IA vizibilă (vezi mai jos).

Limite

  • cel mult 180 de secunde de ieșire;
  • cel mult 1080p (cele trei rapoarte de aspect de mai sus);
  • 24, 25 sau 30 fps;
  • 3 randări în coadă sau în lucru per utilizator în același timp — a patra primește 429 too_many_renders.

Draft sau final

draftfinal
Cadre pe secundă15 fps24 / 25 / 30 fps
Rezoluție540p pe latura scurtă1080p complet
Normalizarea volumuluinula −14 LUFS integrat, −1 dBTP
Prețjumătateîntreg

Iterezi cu draft, apoi randezi ca final versiunea pe care o păstrezi.

Eticheta IA (Regulamentul UE privind IA, art. 50 alin. 4)

ai_label imprimă în MP4 o etichetă vizibilă:

ValoareEfect
cornerO etichetă mică „Generat cu IA” (pentru conținut în engleză: „AI-generated”) în colțul din dreapta jos, pe toată durata videoclipului.
end_cardUn cadru final de circa 2 s cu eticheta.
noneFără etichetă.

Dacă lipsește, eticheta este corner când randarea folosește narațiune TTS (o voce sintetică) sau imagini generate cu IA și none în rest. Un none explicit la o randare cu narațiune TTS este refuzat cu 400 ai_label_required. Fiecare răspuns de randare (202 și GET) raportează valoarea rezolvată în ai_label; aceeași opțiune există la randarea creațiilor și la loturi (ai_label se aplică randării fiecărui rând).

Regulamentul UE privind IA, art. 50 alin. 4. Dacă publici un videoclip generat sau manipulat cu IA care seamănă cu persoane, locuri sau evenimente reale (un deepfake), tu — ca implementator — ești obligat să dezvălui că acesta a fost generat artificial. Eticheta te ajută să faci asta, dar nu îți înlocuiește obligația. Vezi Politica de utilizare acceptabilă.

Starea și QA

GET /v1/studio/renders/{id} întoarce queued, rendering (cu progress între 0 și 1), succeeded sau failed.

{
  "id": "9c1d2e3f-0000-4000-8000-000000000010",
  "object": "studio.render",
  "status": "succeeded",
  "progress": 1,
  "quality": "final",
  "aspect_ratio": "16:9",
  "fps": 30,
  "error": null,
  "qa": { "duration_seconds": 31.2, "width": 1920, "height": 1080, "lufs": -14.1, "warnings": [] },
  "cost_micro_usd": "320000",
  "data": [{ "asset_id": "9c1d2e3f-0000-4000-8000-000000000010", "kind": "render", "url": "https://storage.googleapis.com/…", "mime_type": "video/mp4", "…": "…" }],
  "poster_url": "https://storage.googleapis.com/…poster.jpg…"
}

După succes, data conține MP4-ul ca un singur obiect asset (kind: "render", URL semnat valabil o oră), iar poster_url un cadru-poster JPEG. id-ul randării este și asset_id-ul ei, așa că apare în GET /v1/assets?kind=render și poți obține oricând un URL proaspăt cu GET /v1/assets/{id}.

Fiecare randare trece printr-o verificare tehnică QA. O compoziție care nu trece de lint, iese mai lungă decât limita, are dimensiuni greșite sau e aproape complet neagră ori înghețată eșuează — error are forma <cod>: <mesaj>, cu coduri precum lint_failed, render_failed, too_long, dimension_mismatch, black_video, frozen_video sau timeout. Problemele mai mici nu opresc randarea; apar în qa.warnings:

AvertismentSemnificație
no_audioVideo-ul nu are pistă audio.
silent_audioExistă o pistă audio, dar e tăcută.
lufs_off_targetVolumul perceput e la mai mult de 1 LU de −14 LUFS după normalizare.

Cronologii de scene

POST /v1/studio/timelines construiește un video dintr-o listă de scene, fără să scrii HTML. Fiecare scenă este una dintre imaginile sau video-urile tale ready, afișată pe tot cadrul; imaginile primesc un zoom lent, iar video-urile își păstrează sunetul propriu la video_volume.

{
  "title": "Teaser de produs",
  "aspect_ratio": "16:9",
  "quality": "draft",
  "transition": "fade",
  "music_asset_id": "3f2b8c1e-0000-4000-8000-000000000003",
  "narration_defaults": { "model": "codai-speech", "language": "ro-RO" },
  "scenes": [
    { "id": "intro", "kind": "image", "asset_id": "3f2b8c1e-0000-4000-8000-000000000001", "seconds": 4,
      "prompt": "A sunrise over a mountain lake", "narration": { "text": "Fiecare dimineață începe undeva." } },
    { "kind": "video", "asset_id": "3f2b8c1e-0000-4000-8000-000000000002", "seconds": 6 }
  ]
}
CâmpObservații
scenes1–30 de scene, cel mult 180 s în total (timeline_too_long). kind trebuie să corespundă tipului MIME al asset-ului.
scenes[].idOpțional, ^[a-z0-9-]{1,32}$, implicit s1…sN. Rămâne același de la o regenerare la alta.
scenes[].seconds2–30 s (implicit 5). O scenă narată durează cel puțin cât narațiunea plus 0,6 s.
scenes[].fitcover (implicit, cu decupare) sau contain (cu benzi negre).
scenes[].narrationSintetizată acum cu narration_defaults.model și taxată per apel (narration_cost_micro_usd). Nu este disponibilă pentru conturile cu rezidența datelor în UE (403 residency_unavailable).
transitionfade (implicit, estompare de 0,5 s) sau none.
music_asset_id, music_volumeUnul dintre fișierele tale audio, sub întreaga cronologie (volum 0–1, implicit 0,126).

Răspunsul este 202, cu id, root_render_id, scene_count, duration_seconds și prețul. Interoghează GET /v1/studio/renders/{id} ca la orice randare; randarea se taxează la fel, o singură dată, după succes. Starea unei randări cu cronologie conține și timeline (scenele, fiecare cu asset și narration_asset), parent_render_id și root_render_id. Adaugă ?expand=assets ca să primești URL-uri semnate pentru asset-urile scenelor; fără el, url este null.

Un asset necunoscut, al altcuiva, neterminat sau de tip greșit întoarce 400 asset_not_found, iar id-urile problematice sunt listate în error.details.missing.

Regenerarea unei scene

POST /v1/studio/renders/{id}/scenes/{sceneId}/regenerate creează o randare nouă în care o singură scenă este înlocuită. Randarea sursă rămâne neschimbată; cea nouă are parent_render_id egal cu sursa și păstrează root_render_id, așa că GET /v1/studio/renders?root_render_id=… listează toate versiunile. Sursa trebuie să fie o randare cu cronologie reușită (altfel 409 not_a_timeline).

CorpEfectCost
{"visual": {"asset_id": "…"}}Folosește în schimb una dintre imaginile sau video-urile tale.niciunul
{"visual": {"prompt": "…"}} pe o scenă cu imagineImagine nouă cu codai-image, în raportul de aspect al cronologiei.taxat acum
{"visual": {"prompt": "…", "seconds": 6}} pe o scenă videoClip nou codai-video (4, 6 sau 8 s, implicit 8). Randarea rămâne queued cu stage: "generating_scene" până când un poll vede clipul.taxat o singură dată, când clipul e gata
{"narration": {"text": "…"}}Narează din nou scena.taxat acum
{"narration": {"asset_id": "…"}}Folosește unul dintre fișierele tale audio ca narațiune.niciunul
{"narration": null}Elimină narațiunea.niciunul

Dacă omiți prompt sau text, se refolosește valoarea stocată în scenă. Răspunsul 202 întoarce scene_cost_micro_usd (generarea taxată până acum) alături de prețul randării; noua randare se taxează după succes. Video-ul și narațiunea nu sunt disponibile pentru conturile cu rezidența datelor în UE.

GET /v1/studio/renders listează randările tale, cele mai noi primele (before, limit 1–100, root_render_id), fără să le interogheze și fără URL-uri semnate.

Prețuri

Se taxează o singură dată, la primul poll care vede randarea terminată, pe secundă de ieșire (rotunjit în sus la secunde întregi; până la 50 ms de padding al encoderului nu se taxează):

CalitatePe secundă de ieșireMinimum
final0,01 $0,05 $
draft0,005 $0,025 $

O randare final de 31,2 s costă 32 × 0,01 $ = 0,32 $. Randările eșuate nu costă nimic. Răspunsul 202 repetă prețul în price_micro_usd_per_second și minimum_micro_usd; costul apare în cost_micro_usd după succes. Conturile cu portofel plătesc aceeași sumă, convertită ca la celelalte servicii media (vezi Limite și prețuri).

Erori

HTTPcodeCând
400bad_requestCorp invalid sau aspect_ratio nu se potrivește cu data-width/data-height ale rădăcinii.
400asset_not_foundUn asset referit e necunoscut, nu e al tău sau nu e ready.
400timeline_too_longO cronologie (sau o versiune regenerată) ar dura mai mult de 180 s.
400ai_label_requiredai_label: "none" la o randare cu narațiune TTS (vocea sintetică trebuie etichetată).
403forbiddenCheia nu are voie să folosească codai-render.
403residency_unavailableS-a cerut narațiune sau o scenă video generată pe un cont cu rezidența datelor în UE.
402 / 429subscription_inactive / rate_limit_exceededAbonament inactiv sau plafon de cheltuieli atins.
429too_many_rendersAi deja 3 randări în coadă sau în lucru.
404not_foundId de randare necunoscut sau randarea altui utilizator.
409not_a_timelineRegenerare de scenă pe o randare care nu s-a terminat cu succes sau nu are cronologie.
503renders_unavailableRandările nu sunt configurate pe acest gateway.

Exemple

curl

ID=$(curl -s https://ai.codai.ro/v1/studio/renders \
  -H "Authorization: Bearer $CODAI_API_KEY" -H "Content-Type: application/json" \
  -d @render.json | jq -r .id)

while :; do
  R=$(curl -s "https://ai.codai.ro/v1/studio/renders/$ID" -H "Authorization: Bearer $CODAI_API_KEY")
  S=$(echo "$R" | jq -r .status)
  echo "$S $(echo "$R" | jq -r .progress)"
  [ "$S" = succeeded ] || [ "$S" = failed ] && break
  sleep 5
done
curl -L -o render.mp4 "$(echo "$R" | jq -r '.data[0].url')"

SDK TypeScript

import { readFile } from 'node:fs/promises';
import { Codai } from 'codai-sdk';
const client = new Codai({ apiKey: process.env.CODAI_API_KEY! });

const html = await readFile('index.html', 'utf8');
const r = await client.renders.render(
  { html, aspect_ratio: '16:9', quality: 'draft' },
  { pollIntervalMs: 5000, timeoutMs: 15 * 60_000 },
);
console.log(r.asset.url, r.qa?.warnings, r.costMicroUsd);

client.renders.create(body) și client.renders.retrieve(id) sunt cele două apeluri brute; render() interoghează până la succeeded și aruncă o eroare la failed sau la expirarea timpului.

Cronologii și regenerarea scenelor:

const job = await client.renders.createTimeline({
  quality: 'draft',
  scenes: [
    { id: 'intro', kind: 'image', asset_id: imageId, seconds: 4, narration: { text: 'Bună ziua.' } },
    { kind: 'video', asset_id: videoId },
  ],
});
// …interoghează client.renders.retrieve(job.id) până la succeeded, apoi:
const v2 = await client.renders.regenerateScene(job.id, 'intro', { visual: { prompt: 'The same lake at dusk' } });
const versions = await client.renders.list({ rootRenderId: job.root_render_id });
const detail = await client.renders.retrieve(v2.id, { expandAssets: true });

SDK Python

import os
from pathlib import Path
from codai import Codai

client = Codai(api_key=os.environ["CODAI_API_KEY"])
r = client.renders.render(
    {"html": Path("index.html").read_text(encoding="utf-8"), "aspect_ratio": "9:16", "quality": "final"},
    poll_interval=5,
    timeout=900,
)
print(r.asset["url"], r.qa["warnings"], r.cost_micro_usd)

render() ridică RuntimeError când randarea a eșuat și TimeoutError când nu se termină la timp; create() și retrieve() întorc JSON-ul brut.

Și cronologiile, și regenerarea scenelor întorc JSON-ul brut:

job = client.renders.create_timeline({
  "quality": "draft",
  "scenes": [
    {"id": "intro", "kind": "image", "asset_id": image_id, "seconds": 4, "narration": {"text": "Bună ziua."}},
    {"kind": "video", "asset_id": video_id},
  ],
})
# …interoghează client.renders.retrieve(job["id"]) până la succeeded, apoi:
v2 = client.renders.regenerate_scene(job["id"], "intro", {"narration": None})
versions = client.renders.list(root_render_id=job["root_render_id"])
detail = client.renders.retrieve(v2["id"], expand_assets=True)

Pe această pagină