From e3f31d94bb05a4c236777bd0fb099c5538c88dc1 Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Fri, 16 Oct 2026 12:26:59 +0300 Subject: [PATCH 01/17] docs(compose): price compose by output minute Compose now bills per second of delivered video instead of compute time. Add a compose pricing page (rates, 20 s minimum, animated graphics at 3x, failed-render rules, quote from validate, enterprise contact) and update the credits, FAQ, job lifecycle and MCP pages that said every job bills by compute. Escape the dollar signs on lines Mintlify rendered as math. --- concepts/credits.mdx | 29 ++++++++++++------ concepts/job.mdx | 2 +- docs.json | 1 + jobs/compose/index.mdx | 3 ++ jobs/compose/output.mdx | 2 +- jobs/compose/pricing.mdx | 64 ++++++++++++++++++++++++++++++++++++++++ mcp-server.mdx | 2 +- support/faq.mdx | 8 ++--- 8 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 jobs/compose/pricing.mdx diff --git a/concepts/credits.mdx b/concepts/credits.mdx index 2a7f3de..d3ed1f9 100644 --- a/concepts/credits.mdx +++ b/concepts/credits.mdx @@ -23,7 +23,7 @@ canonical: "https://rendobar.com/docs/concepts/credits" }} /> -Per-job billing from a prepaid credit balance. New accounts get $5 free. Every terminal job is charged for the compute it consumed, including jobs that fail. +Per-job billing from a prepaid credit balance. New accounts get $5 free. [Compose](/jobs/compose/pricing) bills per minute of the video you get back. Every other job type bills for the compute it consumed, including jobs that fail. ## Plans @@ -47,21 +47,21 @@ For a full pricing breakdown and plan comparison, see [rendobar.com/pricing/](ht ## Credit packs -$10 / $25 / $50 / $100 (or custom, $10 minimum). Pro adds **+20%** to every purchase. Buy $25, get $30. Credits never expire. +\$10 / \$25 / \$50 / \$100 (or custom, \$10 minimum). Pro adds **+20%** to every purchase. Buy \$25, get \$30. Credits never expire. ## How does nanodollar billing work? Every job is billed in nanodollars (one-billionth of a US dollar) drawn from your prepaid balance. This differs from tier-based quotas where you buy a plan with a bucket of minutes: -- **Cost matches resources used.** A 30-second 1080p job shouldn't cost the same as a 10-minute 4K render of a large master. +- **Cost matches the work.** Compute-billed jobs pay for the seconds they ran, and compose pays for the seconds of video it delivers. A 30-second 1080p job shouldn't cost the same as a 10-minute 4K render of a large master. - **Prepaid means predictable budgets.** You top up when you want; jobs run only if affordable. No end-of-month surprise bill. ## When do credits expire? -1. **Submit gate.** Submit is atomic and does not reject on concurrency. If your balance is greater than zero the job is accepted. Jobs run at your plan's concurrency and any extra wait in a queue, starting in order as slots free. We do not estimate cost up front. A zero balance returns `402 INSUFFICIENT_CREDITS`, and a full queue returns `429 QUEUE_FULL`. +1. **Submit gate.** Submit is atomic and does not reject on concurrency. If your balance is greater than zero the job is accepted. Jobs run at your plan's concurrency and any extra wait in a queue, starting in order as slots free. We do not estimate cost at submit. For compose, `POST /compose/validate` returns the price before you submit. A zero balance returns `402 INSUFFICIENT_CREDITS`, and a full queue returns `429 QUEUE_FULL`. 2. **Execution.** Job runs. Balance untouched. -3. **Debit on terminal status.** Every terminal job (`complete`, `failed`, or `cancelled`) is charged for the compute it consumed. The debit is atomic with the status update. -4. **No auto-refund.** Failed jobs are still charged for the compute they consumed. If you believe a job failed due to a platform bug, contact support. Refunds are issued at our discretion via the dispute process. +3. **Debit on terminal status.** Every terminal job (`complete`, `failed`, or `cancelled`) settles its charge here. The debit is atomic with the status update. [How is a job priced?](#how-is-a-job-priced) covers what each job type pays for. +4. **No auto-refund.** Failed compute-billed jobs are still charged for the compute they consumed. A failed compose render follows its own rule, below. If you believe a job failed due to a platform bug, contact support. Refunds are issued at our discretion via the dispute process. ## What happens when a balance goes negative? @@ -69,7 +69,17 @@ Because the submit gate checks `balance > 0` rather than an estimate, a single l ## How is a job priced? -`ffmpeg` bills per compute second, which is the wall-clock time FFmpeg ran your command, excluding upload and download. The same per-second rate applies whether the job completes successfully or fails partway through. +`ffmpeg` bills per compute second, which is the wall-clock time FFmpeg ran your command, excluding upload and download. The same per-second rate applies whether the job completes successfully or fails partway through. Every job type except compose bills this way. + +`compose` bills per second of the video you get back, with a 20-second minimum per render. + +| Plan | Compose price per output minute | +| --- | --- | +| Free (pay-as-you-go) | \$0.04 | +| Pro | \$0.03 | +| Enterprise | Contract rate. Email `hello@rendobar.com` | + +Animated graphics bill at 3x, and resolutions above 1080p are counted in 1080p frames. A compose render that fails on Rendobar's side is free. When the failure may come from your input, such as the render tool exiting on a bad file or a timeout, it pays for the compute it used at 2x. `POST /compose/validate` returns the price before you render. Full detail: [compose pricing](/jobs/compose/pricing). If your balance is at or below zero when you submit: @@ -110,14 +120,15 @@ Returns daily usage by job type: count, amount charged, and compute seconds. ## Practical advice -- **Start on Free.** $5 signup grant covers many hundreds of small jobs. +- **Start on Free.** The $5 signup grant covers 125 minutes of compose output at the pay-as-you-go rate, or many hundreds of small FFmpeg jobs. - **Store `cost.nanodollars` from every response.** Reconcile against your monthly invoice. ## See also - [Job lifecycle](/concepts/job): where the debit happens - [Limits](/support/limits): full quota table -- [FFmpeg reference](/jobs/ffmpeg): the live cost model +- [FFmpeg reference](/jobs/ffmpeg): the per-compute-second cost model +- [Compose pricing](/jobs/compose/pricing): the per-output-minute cost model ## Related diff --git a/concepts/job.mdx b/concepts/job.mdx index 61c18c3..33778d7 100644 --- a/concepts/job.mdx +++ b/concepts/job.mdx @@ -82,7 +82,7 @@ flowchart LR | `failed` | Errored. Read `error.code` and `error.message`. | | `cancelled` | Stopped before it finished. | -On any terminal status the job is billed for the compute it used, and a [webhook](/guides/webhooks) fires if you configured one. A job that never started settles free. Failures carry an `error.code` from the [error catalogue](/support/errors). A job that runs past its plan's time limit fails with `JOB_TIMEOUT` and is billed for the compute it consumed. A job a runner accepted but never started fails with `RUNNER_TIMEOUT` and is not charged. Cancelling a `running` job stops the upstream execution too. +On any terminal status the job is billed for the compute it used ([compose](/jobs/compose/pricing) bills per second of output instead), and a [webhook](/guides/webhooks) fires if you configured one. A job that never started settles free. Failures carry an `error.code` from the [error catalogue](/support/errors). A job that runs past its plan's time limit fails with `JOB_TIMEOUT` and is billed for the compute it consumed. A job a runner accepted but never started fails with `RUNNER_TIMEOUT` and is not charged. Cancelling a `running` job stops the upstream execution too. ## What does a job return? {#the-output} diff --git a/docs.json b/docs.json index 04a736a..d8e02f8 100644 --- a/docs.json +++ b/docs.json @@ -73,6 +73,7 @@ "jobs/compose/effects", "jobs/compose/audio", "jobs/compose/output", + "jobs/compose/pricing", "jobs/compose/craft", { "group": "Reference", diff --git a/jobs/compose/index.mdx b/jobs/compose/index.mdx index 1dced86..d5a01f8 100644 --- a/jobs/compose/index.mdx +++ b/jobs/compose/index.mdx @@ -221,6 +221,9 @@ This page is the first render and the shape of a request. Each idea past that ha Frame size, format, quality, and a free check before you render. + + Billed per minute of output video, with the price known before you render. + The pacing, transition and text conventions working editors follow. diff --git a/jobs/compose/output.mdx b/jobs/compose/output.mdx index d30e0a9..7b40bbb 100644 --- a/jobs/compose/output.mdx +++ b/jobs/compose/output.mdx @@ -54,7 +54,7 @@ A compose job renders exactly one file. Two sizes of the same edit are two jobs, ## Check it before you render -`validate` parses a timeline exactly as a render would and costs nothing. It returns the params as the engine reads them, anything it had to upgrade from an older shape, and warnings for settings the render will ignore. +`validate` parses a timeline exactly as a render would and costs nothing. It returns the params as the engine reads them, anything it had to upgrade from an older shape, warnings for settings the render will ignore, and what the render will cost. See [compose pricing](/jobs/compose/pricing). diff --git a/jobs/compose/pricing.mdx b/jobs/compose/pricing.mdx new file mode 100644 index 0000000..30647ba --- /dev/null +++ b/jobs/compose/pricing.mdx @@ -0,0 +1,64 @@ +--- +title: "Compose pricing" +sidebarTitle: "Pricing" +description: "See what a compose render costs: a price per minute of output video, a 20-second minimum, animated graphics at 3x, and a quote before you render." +icon: "receipt" +keywords: ["compose pricing", "json to video pricing", "video render cost per minute", "video api price per minute"] +canonical: "https://rendobar.com/docs/jobs/compose/pricing" +--- + +Compose bills per second of the video you get back, not per second of compute. You can read the price before anything renders. + +| Plan | Price per output minute | +| --- | --- | +| Free (pay-as-you-go) | \$0.04 | +| Pro | \$0.03 | +| Enterprise | Contract rate. Email `hello@rendobar.com` | + +Each render bills at least 20 seconds. The price comes out of your [credit balance](/concepts/credits). The \$5 signup credit covers 125 minutes of compose output at the pay-as-you-go rate. + +## What changes the price + +Animated graphics bill at 3x. That covers kinetic text, 3D cards, HTML and Lottie motion, and anything else the compose engine draws as animated graphics. + +Resolutions above 1080p are counted in 1080p frames, so a larger frame bills as more than one. + +| Render | Free | Pro | +| --- | --- | --- | +| 10 s at 1080p (bills the 20 s minimum) | \$0.013 | \$0.01 | +| 2 min at 1080p | \$0.08 | \$0.06 | +| 2 min at 1080p with animated graphics | \$0.24 | \$0.18 | + +## Failed renders + +A render that fails on Rendobar's side is free. A job that never started is free. + +When the failure may come from your input, such as the render tool exiting on a bad file or the job hitting its time limit, the job pays for the compute it used at 2x. Check `error.code` on the job and the [error codes](/support/errors) to tell which case you hit. + +## Get the price before you render + +`POST /compose/validate` returns the quote with the rest of its checks, and validating is free. From an AI agent, the MCP tool `validate_compose` does the same. + + + +```ts SDK +const check = await client.compose.validate(params); +if (!check.valid) console.error(check.problems); +``` + +```bash cURL +curl -X POST https://api.rendobar.com/compose/validate \ + -H "Authorization: Bearer rb_YOUR_KEY" \ + -H "Content-Type: application/json" \ + -d '{ "params": { "schemaVersion": 1, "output": { "format": "mp4", "size": "1080p" }, "timeline": { "tracks": [{ "clips": [{ "asset": { "type": "video", "src": "https://example.com/a.mp4" } }] }] } } }' +``` + + + +Every other job type, [FFmpeg](/jobs/ffmpeg) included, bills per second of compute. See [how a job is priced](/concepts/credits#how-is-a-job-priced). + +## See also + +- [Output and validation](/jobs/compose/output): frame size, format, and the full validate response +- [Credits and billing](/concepts/credits): balance, credit packs, and when the debit lands +- [Plan limits](/support/limits): render time and file-size caps per plan diff --git a/mcp-server.mdx b/mcp-server.mdx index 3f106ba..fb4d764 100644 --- a/mcp-server.mdx +++ b/mcp-server.mdx @@ -71,7 +71,7 @@ Call `list_job_types` first. It returns every active job type fresh, so it never | Tool | What it does | Returns | |---|---|---| | `list_job_types` | Every active job type: use cases, what it chains with, a params digest, and an example payload. With `type`, the full annotated parameter schema for that one type. | `{ jobTypes[], guidance }` or `{ type, paramsJsonSchema, example, guidance }` | -| `validate_compose` | Check compose params without rendering. Free, nothing is stored, and it runs the same checks a submit does. Call it before `submit_job` on any timeline you wrote. | `{ valid, problems[], params?, warnings[] }` | +| `validate_compose` | Check compose params without rendering and get the price of the render. Free, nothing is stored, and it runs the same checks a submit does. Call it before `submit_job` on any timeline you wrote. | `{ valid, problems[], params?, warnings[] }` | | `submit_job` | Submit a job. `inputs` accepts a URL string, `{ url }`, `{ content }` (inline text), or `{ job: "job_..." }` for chaining. A URL can be a `storage://` URI, and `destinations` writes the output to a connected bucket. | `{ jobId, status }` | | `get_job` | Check status and results. `wait: true` blocks up to 45 seconds instead of polling. A completed video job includes an inline poster image. | `{ id, status, output?, cost?, error? }` | | `list_jobs` | Recent jobs, filterable by `status` and `type`. | `{ jobs[], total }` | diff --git a/support/faq.mdx b/support/faq.mdx index 494eba6..6f5011e 100644 --- a/support/faq.mdx +++ b/support/faq.mdx @@ -21,7 +21,7 @@ canonical: "https://rendobar.com/docs/support/faq" "name": "How much does Rendobar cost?", "acceptedAnswer": { "@type": "Answer", - "text": "Free plan ships with a one-time $5 credit grant, no card required. Pro is $9/mo, includes $5/mo in credits, plus 20% bonus on every credit purchase. Credit packs $10–$100 on both plans. Credits never expire." + "text": "Free plan ships with a one-time $5 credit grant, no card required. Pro is $9/mo, includes $5/mo in credits, plus 20% bonus on every credit purchase. Credit packs $10–$100 on both plans. Credits never expire. Compose bills per minute of output video: $0.04 on pay-as-you-go, $0.03 on Pro, with a 20-second minimum per render. Every other job type bills per second of compute." } }, { @@ -69,7 +69,7 @@ canonical: "https://rendobar.com/docs/support/faq" "name": "What happens if a job fails?", "acceptedAnswer": { "@type": "Answer", - "text": "Failed jobs are charged for the compute they used. Rendobar has no auto-refund. If you believe a failure was caused by a Rendobar bug or platform issue, contact support and we will review for a discretionary refund. Successful and failed jobs are billed the same way: for the compute the job consumed. Inspect error.code and error.message on the job, and retry via POST /jobs/{id}/retry if appropriate." + "text": "A failed compose render is free when the failure is on Rendobar's side. When it may come from your input, such as the render tool exiting on a bad file or a timeout, it pays for the compute it used at 2x. Other failed jobs are charged for the compute they used, the same way successful jobs are. A job that never started is free. Rendobar has no auto-refund. If you believe a failure was caused by a Rendobar bug or platform issue, contact support and we will review for a discretionary refund. Inspect error.code and error.message on the job, and retry via POST /jobs/{id}/retry if appropriate." } }, { @@ -96,7 +96,7 @@ canonical: "https://rendobar.com/docs/support/faq" - Free plan ships with a one-time $5 credit grant, no card required. Pro is $9/mo, includes $5/mo in credits, plus 20% bonus on every credit purchase. Credit packs $10–$100 on both plans. Credits never expire. Full breakdown: [Credits and billing](/concepts/credits) or the [pricing page](https://rendobar.com/pricing/). + Free plan ships with a one-time \$5 credit grant, no card required. Pro is \$9/mo, includes \$5/mo in credits, plus 20% bonus on every credit purchase. Credit packs \$10–\$100 on both plans. Credits never expire. Compose bills per minute of output video: \$0.04 on pay-as-you-go, \$0.03 on Pro, with a 20-second minimum per render. Every other job type bills per second of compute. Full breakdown: [Credits and billing](/concepts/credits), [compose pricing](/jobs/compose/pricing), or the [pricing page](https://rendobar.com/pricing/). @@ -120,7 +120,7 @@ canonical: "https://rendobar.com/docs/support/faq" - Failed jobs are charged for the compute they used. Rendobar has no auto-refund. If you believe a failure was caused by a Rendobar bug or platform issue, contact support and we will review for a discretionary refund. Successful and failed jobs are billed the same way: for the compute the job consumed. Inspect `error.code` and `error.message` on the job, and retry via `POST /jobs/{id}/retry` if appropriate. [Error codes](/support/errors). + A failed compose render is free when the failure is on Rendobar's side. When it may come from your input, such as the render tool exiting on a bad file or a timeout, it pays for the compute it used at 2x. Other failed jobs are charged for the compute they used, the same way successful jobs are. A job that never started is free. Rendobar has no auto-refund. If you believe a failure was caused by a Rendobar bug or platform issue, contact support and we will review for a discretionary refund. Inspect `error.code` and `error.message` on the job, and retry via `POST /jobs/{id}/retry` if appropriate. [Error codes](/support/errors). From 934ea7eb62cf840903cbf1cd20bd17a036df3112 Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Fri, 16 Oct 2026 12:32:17 +0300 Subject: [PATCH 02/17] docs(compose): a render past its time limit is free A timeout means the compose engine's own time estimate was off, so it counts as a failure on Rendobar's side and bills nothing. --- concepts/credits.mdx | 2 +- jobs/compose/pricing.mdx | 2 +- support/faq.mdx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/concepts/credits.mdx b/concepts/credits.mdx index d3ed1f9..b162488 100644 --- a/concepts/credits.mdx +++ b/concepts/credits.mdx @@ -79,7 +79,7 @@ Because the submit gate checks `balance > 0` rather than an estimate, a single l | Pro | \$0.03 | | Enterprise | Contract rate. Email `hello@rendobar.com` | -Animated graphics bill at 3x, and resolutions above 1080p are counted in 1080p frames. A compose render that fails on Rendobar's side is free. When the failure may come from your input, such as the render tool exiting on a bad file or a timeout, it pays for the compute it used at 2x. `POST /compose/validate` returns the price before you render. Full detail: [compose pricing](/jobs/compose/pricing). +Animated graphics bill at 3x, and resolutions above 1080p are counted in 1080p frames. A compose render that fails on Rendobar's side is free. When the failure may come from your input, such as the render tool exiting on a bad file, it pays for the compute it used at 2x. `POST /compose/validate` returns the price before you render. Full detail: [compose pricing](/jobs/compose/pricing). If your balance is at or below zero when you submit: diff --git a/jobs/compose/pricing.mdx b/jobs/compose/pricing.mdx index 30647ba..0b3b695 100644 --- a/jobs/compose/pricing.mdx +++ b/jobs/compose/pricing.mdx @@ -33,7 +33,7 @@ Resolutions above 1080p are counted in 1080p frames, so a larger frame bills as A render that fails on Rendobar's side is free. A job that never started is free. -When the failure may come from your input, such as the render tool exiting on a bad file or the job hitting its time limit, the job pays for the compute it used at 2x. Check `error.code` on the job and the [error codes](/support/errors) to tell which case you hit. +When the failure may come from your input, such as the render tool exiting on a bad file, the job pays for the compute it used at 2x. Check `error.code` on the job and the [error codes](/support/errors) to tell which case you hit. ## Get the price before you render diff --git a/support/faq.mdx b/support/faq.mdx index 6f5011e..62b6156 100644 --- a/support/faq.mdx +++ b/support/faq.mdx @@ -69,7 +69,7 @@ canonical: "https://rendobar.com/docs/support/faq" "name": "What happens if a job fails?", "acceptedAnswer": { "@type": "Answer", - "text": "A failed compose render is free when the failure is on Rendobar's side. When it may come from your input, such as the render tool exiting on a bad file or a timeout, it pays for the compute it used at 2x. Other failed jobs are charged for the compute they used, the same way successful jobs are. A job that never started is free. Rendobar has no auto-refund. If you believe a failure was caused by a Rendobar bug or platform issue, contact support and we will review for a discretionary refund. Inspect error.code and error.message on the job, and retry via POST /jobs/{id}/retry if appropriate." + "text": "A failed compose render is free when the failure is on Rendobar's side. When it may come from your input, such as the render tool exiting on a bad file, it pays for the compute it used at 2x. Other failed jobs are charged for the compute they used, the same way successful jobs are. A job that never started is free. Rendobar has no auto-refund. If you believe a failure was caused by a Rendobar bug or platform issue, contact support and we will review for a discretionary refund. Inspect error.code and error.message on the job, and retry via POST /jobs/{id}/retry if appropriate." } }, { @@ -120,7 +120,7 @@ canonical: "https://rendobar.com/docs/support/faq" - A failed compose render is free when the failure is on Rendobar's side. When it may come from your input, such as the render tool exiting on a bad file or a timeout, it pays for the compute it used at 2x. Other failed jobs are charged for the compute they used, the same way successful jobs are. A job that never started is free. Rendobar has no auto-refund. If you believe a failure was caused by a Rendobar bug or platform issue, contact support and we will review for a discretionary refund. Inspect `error.code` and `error.message` on the job, and retry via `POST /jobs/{id}/retry` if appropriate. [Error codes](/support/errors). + A failed compose render is free when the failure is on Rendobar's side. When it may come from your input, such as the render tool exiting on a bad file, it pays for the compute it used at 2x. Other failed jobs are charged for the compute they used, the same way successful jobs are. A job that never started is free. Rendobar has no auto-refund. If you believe a failure was caused by a Rendobar bug or platform issue, contact support and we will review for a discretionary refund. Inspect `error.code` and `error.message` on the job, and retry via `POST /jobs/{id}/retry` if appropriate. [Error codes](/support/errors). From 81ea0f6cc57d450bc1973775713d84f98034cb24 Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Fri, 16 Oct 2026 13:09:49 +0300 Subject: [PATCH 03/17] docs(compose): bill frames above 1080p at 2x --- concepts/credits.mdx | 2 +- jobs/compose/pricing.mdx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/concepts/credits.mdx b/concepts/credits.mdx index b162488..0033103 100644 --- a/concepts/credits.mdx +++ b/concepts/credits.mdx @@ -79,7 +79,7 @@ Because the submit gate checks `balance > 0` rather than an estimate, a single l | Pro | \$0.03 | | Enterprise | Contract rate. Email `hello@rendobar.com` | -Animated graphics bill at 3x, and resolutions above 1080p are counted in 1080p frames. A compose render that fails on Rendobar's side is free. When the failure may come from your input, such as the render tool exiting on a bad file, it pays for the compute it used at 2x. `POST /compose/validate` returns the price before you render. Full detail: [compose pricing](/jobs/compose/pricing). +Animated graphics bill at 3x, and frames larger than 1080p (1440p, 4K) bill at 2x. A compose render that fails on Rendobar's side is free. When the failure may come from your input, such as the render tool exiting on a bad file, it pays for the compute it used at 2x. `POST /compose/validate` returns the price before you render. Full detail: [compose pricing](/jobs/compose/pricing). If your balance is at or below zero when you submit: diff --git a/jobs/compose/pricing.mdx b/jobs/compose/pricing.mdx index 0b3b695..e7aebe1 100644 --- a/jobs/compose/pricing.mdx +++ b/jobs/compose/pricing.mdx @@ -21,13 +21,15 @@ Each render bills at least 20 seconds. The price comes out of your [credit balan Animated graphics bill at 3x. That covers kinetic text, 3D cards, HTML and Lottie motion, and anything else the compose engine draws as animated graphics. -Resolutions above 1080p are counted in 1080p frames, so a larger frame bills as more than one. +Frames larger than 1080p (1440p, 2K, 4K) bill at 2x. Everything up to 1080p bills the same rate. The multipliers stack, so a 4K render with animated graphics bills at 6x. | Render | Free | Pro | | --- | --- | --- | | 10 s at 1080p (bills the 20 s minimum) | \$0.013 | \$0.01 | | 2 min at 1080p | \$0.08 | \$0.06 | | 2 min at 1080p with animated graphics | \$0.24 | \$0.18 | +| 2 min at 4K | \$0.16 | \$0.12 | +| 2 min at 4K with animated graphics | \$0.48 | \$0.36 | ## Failed renders From f6b287b522091d8fe431e60ec92c8601fa4e0e32 Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Fri, 16 Oct 2026 13:24:41 +0300 Subject: [PATCH 04/17] docs(compose): use public filter names in the engine effect The engine effect now takes "filter" with a public name and public parameter names (FFmpeg names for FFmpeg filters, camelCase for the rest). Point readers at GET /compose/effects/engine for the full list. --- jobs/compose/effects.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/jobs/compose/effects.mdx b/jobs/compose/effects.mdx index 27c8272..b002ebf 100644 --- a/jobs/compose/effects.mdx +++ b/jobs/compose/effects.mdx @@ -163,12 +163,14 @@ One `color` effect carries brightness, contrast, saturation, temperature and the ## Anything the engine can do -`{ "type": "engine" }` reaches the rest of the render engine by its own names, for the case a promoted effect does not cover. Every service and property is checked before the job starts, so a typo is a 400 and not a failed render. +`{ "type": "engine" }` reaches the rest of the render engine's filters, for the case a promoted effect does not cover. Every filter and parameter is checked before the job starts, so a typo is a 400 and not a failed render. ```json -{ "type": "engine", "service": "avfilter.colorize", "params": { "av.hue": 200, "av.saturation": 0.6 } } +{ "type": "engine", "filter": "colorize", "params": { "hue": 200, "saturation": 0.6 } } ``` -An engine entry that duplicates a promoted effect is refused, so there is one way to ask for each thing. The names come from the engine and can change when it is upgraded, which the promoted names never do. +An FFmpeg filter goes by its FFmpeg name and takes its FFmpeg option names, so `colorize` takes `hue`, `saturation` and `lightness`. Other filters go by a camelCase name, such as `cartoon` with `triplevel` and `diffspace`. `GET /compose/effects/engine` lists every filter an engine entry can name, with its parameters. + +An engine entry that duplicates a promoted effect is refused, so there is one way to ask for each thing. The list of filters comes from the engine and can change when it is upgraded, which the promoted effects never do. Every effect and every parameter is in the [effect reference](/docs/jobs/compose/reference/effects). Sound effects are on [Audio levels and effects](/docs/jobs/compose/audio). From 39e595a0800b4670b36b4a45eeae35ece6b777a0 Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Sat, 17 Oct 2026 02:22:35 +0300 Subject: [PATCH 05/17] docs: one compose rate and the plan frame limits Compose bills one rate per output minute with a 15-second minimum, the same on every plan. Pro adds 4K and 60 fps compose and GPU FFmpeg, and animated graphics above 1080p or 30 fps are enterprise only. A render over the plan returns PLAN_LIMIT. --- concepts/credits.mdx | 17 ++++++------- jobs/compose/output.mdx | 4 ++- jobs/compose/pricing.mdx | 54 ++++++++++++++++++++++++++-------------- mcp-server.mdx | 2 +- support/errors.mdx | 2 +- support/faq.mdx | 4 +-- support/limits.mdx | 7 ++++-- 7 files changed, 54 insertions(+), 36 deletions(-) diff --git a/concepts/credits.mdx b/concepts/credits.mdx index 0033103..f9b5c7b 100644 --- a/concepts/credits.mdx +++ b/concepts/credits.mdx @@ -39,9 +39,12 @@ Per-job billing from a prepaid credit balance. New accounts get $5 free. [Compos | Max input file | 500 MB | 10 GB | | Job timeout | 1 hour | 9 hours | | Output retention | 7 days | 30 days | +| Compose frame | Up to 1080p at 30 fps | Up to 4K at 60 fps | +| Compose with animated graphics | Up to 1080p at 30 fps | Up to 1080p at 30 fps | +| GPU FFmpeg (NVENC/CUDA) | No | Yes | | Priority queue | No | Yes | -`ffmpeg`, MCP, and webhooks are on both plans. Full limits table: [Limits](/support/limits). +`ffmpeg`, MCP, and webhooks are on both plans. Compose costs the same per output minute on both. Animated graphics above 1080p or 30 fps need an enterprise plan. Full limits table: [Limits](/support/limits). For a full pricing breakdown and plan comparison, see [rendobar.com/pricing/](https://rendobar.com/pricing/). @@ -71,15 +74,9 @@ Because the submit gate checks `balance > 0` rather than an estimate, a single l `ffmpeg` bills per compute second, which is the wall-clock time FFmpeg ran your command, excluding upload and download. The same per-second rate applies whether the job completes successfully or fails partway through. Every job type except compose bills this way. -`compose` bills per second of the video you get back, with a 20-second minimum per render. +`compose` bills per second of the video you get back, at \$0.04 per minute of output on every plan, with a 15-second minimum per render. Frame size and animated graphics do not change the rate. Plans differ in what they can render. 4K and 60 fps need Pro, and a video above your plan returns `403 PLAN_LIMIT` before it renders. An enterprise plan carries its own contract rate. Email `hello@rendobar.com`. -| Plan | Compose price per output minute | -| --- | --- | -| Free (pay-as-you-go) | \$0.04 | -| Pro | \$0.03 | -| Enterprise | Contract rate. Email `hello@rendobar.com` | - -Animated graphics bill at 3x, and frames larger than 1080p (1440p, 4K) bill at 2x. A compose render that fails on Rendobar's side is free. When the failure may come from your input, such as the render tool exiting on a bad file, it pays for the compute it used at 2x. `POST /compose/validate` returns the price before you render. Full detail: [compose pricing](/jobs/compose/pricing). +A compose render that fails on Rendobar's side is free. When the failure may come from your input, such as the render tool exiting on a bad file, it pays for the compute it used at 2x. `POST /compose/validate` returns the price before you render. Full detail: [compose pricing](/jobs/compose/pricing). If your balance is at or below zero when you submit: @@ -120,7 +117,7 @@ Returns daily usage by job type: count, amount charged, and compute seconds. ## Practical advice -- **Start on Free.** The $5 signup grant covers 125 minutes of compose output at the pay-as-you-go rate, or many hundreds of small FFmpeg jobs. +- **Start on Free.** The $5 signup grant covers 125 minutes of compose output, or many hundreds of small FFmpeg jobs. - **Store `cost.nanodollars` from every response.** Reconcile against your monthly invoice. ## See also diff --git a/jobs/compose/output.mdx b/jobs/compose/output.mdx index 7b40bbb..d97e034 100644 --- a/jobs/compose/output.mdx +++ b/jobs/compose/output.mdx @@ -49,12 +49,14 @@ A wide photograph in a phone-shaped box. `contain` shows all of it and leaves ba +Pay-as-you-go renders video up to 1080p at 30 fps. 4K and 60 fps need Pro, and animated graphics above 1080p or 30 fps need an enterprise plan. A video above your plan returns `403 PLAN_LIMIT` at submit, and validate reports it first. See [compose pricing](/jobs/compose/pricing#what-each-plan-renders). + A compose job renders exactly one file. Two sizes of the same edit are two jobs, which is also how you get them rendered at the same time. `gif` carries no audio, `mp3` is audio only and needs no frame size, and `jpg` and `png` are a single frame. ## Check it before you render -`validate` parses a timeline exactly as a render would and costs nothing. It returns the params as the engine reads them, anything it had to upgrade from an older shape, warnings for settings the render will ignore, and what the render will cost. See [compose pricing](/jobs/compose/pricing). +`validate` parses a timeline exactly as a render would and costs nothing. It returns the params as the engine reads them, anything it had to upgrade from an older shape, warnings for settings the render will ignore, and what the render will cost. A frame or frame rate above your plan comes back as a problem at `["output"]`. See [compose pricing](/jobs/compose/pricing). diff --git a/jobs/compose/pricing.mdx b/jobs/compose/pricing.mdx index e7aebe1..40876d3 100644 --- a/jobs/compose/pricing.mdx +++ b/jobs/compose/pricing.mdx @@ -1,35 +1,42 @@ --- title: "Compose pricing" sidebarTitle: "Pricing" -description: "See what a compose render costs: a price per minute of output video, a 20-second minimum, animated graphics at 3x, and a quote before you render." +description: "See what a compose render costs, one price per minute of output video on every plan with a 15-second minimum, and get a quote before you render." icon: "receipt" keywords: ["compose pricing", "json to video pricing", "video render cost per minute", "video api price per minute"] canonical: "https://rendobar.com/docs/jobs/compose/pricing" --- -Compose bills per second of the video you get back, not per second of compute. You can read the price before anything renders. +Compose bills per second of the video you get back, not per second of compute. The rate is \$0.04 per minute of output video. It is the same on every plan and for every video your plan can render, and you can read the price before anything renders. -| Plan | Price per output minute | +Each render bills at least 15 seconds, which is 1 cent. The price comes out of your [credit balance](/concepts/credits). The \$5 signup credit covers 125 minutes of compose output. + +| Render | Price | | --- | --- | -| Free (pay-as-you-go) | \$0.04 | -| Pro | \$0.03 | -| Enterprise | Contract rate. Email `hello@rendobar.com` | +| 10 s (bills the 15 s minimum) | \$0.01 | +| 30 s | \$0.02 | +| 2 min | \$0.08 | +| 10 min | \$0.40 | + +Frame size and animated graphics do not change the price. A 2-minute render costs the same at 720p, at 4K, or with kinetic text and 3D cards on it. + +## What each plan renders -Each render bills at least 20 seconds. The price comes out of your [credit balance](/concepts/credits). The \$5 signup credit covers 125 minutes of compose output at the pay-as-you-go rate. +Plans differ in what they can render, not in what a minute costs. + +| Plan | Renders up to | +| --- | --- | +| Free (pay-as-you-go) | 1080p at 30 fps, animated graphics included | +| Pro | 4K at 60 fps. Animated graphics up to 1080p at 30 fps | +| Enterprise | Animated graphics above 1080p or 30 fps, at a contract rate. Email `hello@rendobar.com` | -## What changes the price +Animated graphics are kinetic text, 3D cards, HTML and Lottie motion, and anything else the compose engine draws as animated graphics. The frame counts by its pixels, so a portrait 1080x1920 video counts as 1080p. -Animated graphics bill at 3x. That covers kinetic text, 3D cards, HTML and Lottie motion, and anything else the compose engine draws as animated graphics. +Pro costs \$9 a month and includes \$5 of credit each month. Its discount on compose is the 20% bonus on every credit purchase. See [credits and billing](/concepts/credits#plans). -Frames larger than 1080p (1440p, 2K, 4K) bill at 2x. Everything up to 1080p bills the same rate. The multipliers stack, so a 4K render with animated graphics bills at 6x. +A video above your plan is refused at submit with `403 PLAN_LIMIT`, before anything renders or is charged. The message names the plan that renders it. Only video outputs (`mp4`, `webm`, `gif`) are checked. A `jpg`, `png` or `mp3` output renders on every plan. -| Render | Free | Pro | -| --- | --- | --- | -| 10 s at 1080p (bills the 20 s minimum) | \$0.013 | \$0.01 | -| 2 min at 1080p | \$0.08 | \$0.06 | -| 2 min at 1080p with animated graphics | \$0.24 | \$0.18 | -| 2 min at 4K | \$0.16 | \$0.12 | -| 2 min at 4K with animated graphics | \$0.48 | \$0.36 | +`GET /account/capabilities` returns your plan's limits as `composeMaxPixels` and `composeMaxFps` for a plain edit, and `composeAnimatedMaxPixels` and `composeAnimatedMaxFps` for one with animated graphics. ## Failed renders @@ -39,13 +46,14 @@ When the failure may come from your input, such as the render tool exiting on a ## Get the price before you render -`POST /compose/validate` returns the quote with the rest of its checks, and validating is free. From an AI agent, the MCP tool `validate_compose` does the same. +`POST /compose/validate` returns the quote with the rest of its checks, and validating is free. It runs the plan check too: a video above your plan comes back as `valid: false` with a problem at `["output"]`. From an AI agent, the MCP tool `validate_compose` does the same. ```ts SDK const check = await client.compose.validate(params); if (!check.valid) console.error(check.problems); +else console.log(check.price); // { perMinuteUsd: 0.04, minSeconds: 15 } ``` ```bash cURL @@ -57,10 +65,18 @@ curl -X POST https://api.rendobar.com/compose/validate \ +A valid result carries `price`: + +```json +"price": { "perMinuteUsd": 0.04, "minSeconds": 15 } +``` + +The charge is the video's length in minutes times `perMinuteUsd`, counted by the second, and never less than `minSeconds`. + Every other job type, [FFmpeg](/jobs/ffmpeg) included, bills per second of compute. See [how a job is priced](/concepts/credits#how-is-a-job-priced). ## See also - [Output and validation](/jobs/compose/output): frame size, format, and the full validate response - [Credits and billing](/concepts/credits): balance, credit packs, and when the debit lands -- [Plan limits](/support/limits): render time and file-size caps per plan +- [Plan limits](/support/limits): render time, file-size and compose frame caps per plan diff --git a/mcp-server.mdx b/mcp-server.mdx index fb4d764..cf77de1 100644 --- a/mcp-server.mdx +++ b/mcp-server.mdx @@ -71,7 +71,7 @@ Call `list_job_types` first. It returns every active job type fresh, so it never | Tool | What it does | Returns | |---|---|---| | `list_job_types` | Every active job type: use cases, what it chains with, a params digest, and an example payload. With `type`, the full annotated parameter schema for that one type. | `{ jobTypes[], guidance }` or `{ type, paramsJsonSchema, example, guidance }` | -| `validate_compose` | Check compose params without rendering and get the price of the render. Free, nothing is stored, and it runs the same checks a submit does. Call it before `submit_job` on any timeline you wrote. | `{ valid, problems[], params?, warnings[] }` | +| `validate_compose` | Check compose params without rendering and get the price of the render. Free, nothing is stored, and it runs the same checks a submit does. Call it before `submit_job` on any timeline you wrote. | `{ valid, problems[], params?, warnings[], price? }` | | `submit_job` | Submit a job. `inputs` accepts a URL string, `{ url }`, `{ content }` (inline text), or `{ job: "job_..." }` for chaining. A URL can be a `storage://` URI, and `destinations` writes the output to a connected bucket. | `{ jobId, status }` | | `get_job` | Check status and results. `wait: true` blocks up to 45 seconds instead of polling. A completed video job includes an inline poster image. | `{ id, status, output?, cost?, error? }` | | `list_jobs` | Recent jobs, filterable by `status` and `type`. | `{ jobs[], total }` | diff --git a/support/errors.mdx b/support/errors.mdx index c529e00..b26a294 100644 --- a/support/errors.mdx +++ b/support/errors.mdx @@ -40,7 +40,7 @@ Check the HTTP status and `error.code` before reading `data`. | `VALIDATION_ERROR` | 400 | Body failed schema validation. `details.path` points at the field | | `INVALID_JOB_TYPE` | 400 | Job type not registered or not on your plan | | `INSUFFICIENT_CREDITS` | 402 | Top up at [app.rendobar.com/billing](https://app.rendobar.com/billing) | -| `PLAN_LIMIT` | 403 | Plan limit hit (concurrency, file size). See [Limits](/support/limits) | +| `PLAN_LIMIT` | 403 | Plan limit hit (job timeout, GPU, a compose frame or frame rate above the plan). See [Limits](/support/limits) | | `RATE_LIMITED` | 429 | Back off per `Retry-After` header | | `NOT_FOUND` | 404 | Resource doesn't exist or isn't visible to your org | | `CONFLICT` | 409 | Request conflicts with current state (e.g. cancelling a complete job) | diff --git a/support/faq.mdx b/support/faq.mdx index 62b6156..687821f 100644 --- a/support/faq.mdx +++ b/support/faq.mdx @@ -21,7 +21,7 @@ canonical: "https://rendobar.com/docs/support/faq" "name": "How much does Rendobar cost?", "acceptedAnswer": { "@type": "Answer", - "text": "Free plan ships with a one-time $5 credit grant, no card required. Pro is $9/mo, includes $5/mo in credits, plus 20% bonus on every credit purchase. Credit packs $10–$100 on both plans. Credits never expire. Compose bills per minute of output video: $0.04 on pay-as-you-go, $0.03 on Pro, with a 20-second minimum per render. Every other job type bills per second of compute." + "text": "Free plan ships with a one-time $5 credit grant, no card required. Pro is $9/mo, includes $5/mo in credits, plus 20% bonus on every credit purchase. Credit packs $10 to $100 on both plans. Credits never expire. Compose bills per minute of output video: $0.04 on every plan, billed per second with a 15-second minimum per render. Pro adds compose at 4K and 60 fps and GPU FFmpeg. Every other job type bills per second of compute." } }, { @@ -96,7 +96,7 @@ canonical: "https://rendobar.com/docs/support/faq" - Free plan ships with a one-time \$5 credit grant, no card required. Pro is \$9/mo, includes \$5/mo in credits, plus 20% bonus on every credit purchase. Credit packs \$10–\$100 on both plans. Credits never expire. Compose bills per minute of output video: \$0.04 on pay-as-you-go, \$0.03 on Pro, with a 20-second minimum per render. Every other job type bills per second of compute. Full breakdown: [Credits and billing](/concepts/credits), [compose pricing](/jobs/compose/pricing), or the [pricing page](https://rendobar.com/pricing/). + Free plan ships with a one-time \$5 credit grant, no card required. Pro is \$9/mo, includes \$5/mo in credits, plus 20% bonus on every credit purchase. Credit packs \$10 to \$100 on both plans. Credits never expire. Compose bills per minute of output video: \$0.04 on every plan, billed per second with a 15-second minimum per render. Pro adds compose at 4K and 60 fps and GPU FFmpeg. Every other job type bills per second of compute. Full breakdown: [Credits and billing](/concepts/credits), [compose pricing](/jobs/compose/pricing), or the [pricing page](https://rendobar.com/pricing/). diff --git a/support/limits.mdx b/support/limits.mdx index 54f2e34..f7a2250 100644 --- a/support/limits.mdx +++ b/support/limits.mdx @@ -39,9 +39,12 @@ Every limit Rendobar enforces, by plan. Pulled from `PLAN_LIMITS` in the API. Fo | Max job timeout | 1 hour | 9 hours | | Output retention | 7 days | 30 days | | Total object storage | 5 GB | 250 GB | +| Compose frame | Up to 1080p at 30 fps | Up to 4K at 60 fps | +| Compose with animated graphics | Up to 1080p at 30 fps | Up to 1080p at 30 fps | +| GPU FFmpeg (NVENC/CUDA) | No | Yes | | Priority queue | No | Yes | -`ffmpeg`, MCP, and webhooks are on both plans. +`ffmpeg`, MCP, and webhooks are on both plans. A compose video above your plan's frame or frame rate returns `403 PLAN_LIMIT` before it renders, and the message names the plan that renders it. Animated graphics above 1080p or 30 fps need an enterprise plan. `GET /account/capabilities` returns these as `composeMaxPixels`, `composeMaxFps`, `composeAnimatedMaxPixels` and `composeAnimatedMaxFps`. See [compose pricing](/jobs/compose/pricing). ## How long do output URLs last? @@ -52,7 +55,7 @@ Every limit Rendobar enforces, by plan. Pulled from `PLAN_LIMITS` in the API. Fo ## Credit packs -$10 / $25 / $50 / $100 (or custom, $10 minimum). Pro adds +20% to every purchase. Credits never expire. Failed jobs are always refunded. +\$10 / \$25 / \$50 / \$100 (or custom, \$10 minimum). Pro adds +20% to every purchase. Credits never expire. Failed jobs are not refunded automatically. See [credits and billing](/concepts/credits) for what a failed job pays. ## API response limits From c1362ac626ee6f9a3e220445fada5e1ea8c3c45f Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Sun, 18 Oct 2026 09:52:14 +0300 Subject: [PATCH 06/17] docs(compose): add the Studio plan and Pro's 1440p limit Studio ($29/mo, $20 monthly credits, 50 concurrent jobs) renders compose up to 4K at 60 fps. Pro now stops a plain edit at 1440p 30 fps. Both paid plans get a 25% credit-purchase bonus, up from 20%. Adds a Studio column to the plan tables, drops the priority queue row (no plan has it yet) and names Studio wherever a limit was listed for Pro only. --- concepts/credits.mdx | 39 +++++++++++++++++++-------------------- jobs/compose/output.mdx | 2 +- jobs/compose/pricing.mdx | 5 +++-- jobs/ffmpeg.mdx | 8 ++++---- quickstart.mdx | 2 +- support/faq.mdx | 14 +++++++------- support/limits.mdx | 39 +++++++++++++++++++-------------------- 7 files changed, 54 insertions(+), 55 deletions(-) diff --git a/concepts/credits.mdx b/concepts/credits.mdx index f9b5c7b..47f66c1 100644 --- a/concepts/credits.mdx +++ b/concepts/credits.mdx @@ -27,30 +27,29 @@ Per-job billing from a prepaid credit balance. New accounts get $5 free. [Compos ## Plans -| | Free | Pro | -|---|---|---| -| Price | $0/mo | $9/mo | -| Signup credits | $5 (one-time) | - | -| Monthly credits included | - | $5 | -| Credit-purchase bonus | - | +20% | -| Concurrent jobs | 1 | 20 | -| Queued backlog | 50 | 1000 | -| API requests/min | 120 | 600 | -| Max input file | 500 MB | 10 GB | -| Job timeout | 1 hour | 9 hours | -| Output retention | 7 days | 30 days | -| Compose frame | Up to 1080p at 30 fps | Up to 4K at 60 fps | -| Compose with animated graphics | Up to 1080p at 30 fps | Up to 1080p at 30 fps | -| GPU FFmpeg (NVENC/CUDA) | No | Yes | -| Priority queue | No | Yes | - -`ffmpeg`, MCP, and webhooks are on both plans. Compose costs the same per output minute on both. Animated graphics above 1080p or 30 fps need an enterprise plan. Full limits table: [Limits](/support/limits). +| | Free | Pro | Studio | +|---|---|---|---| +| Price | $0/mo | $9/mo | $29/mo | +| Signup credits | $5 (one-time) | - | - | +| Monthly credits included | - | $5 | $20 | +| Credit-purchase bonus | - | +25% | +25% | +| Concurrent jobs | 1 | 20 | 50 | +| Queued backlog | 50 | 1000 | 2500 | +| API requests/min | 120 | 600 | 1200 | +| Max input file | 500 MB | 10 GB | 10 GB | +| Job timeout | 1 hour | 9 hours | 9 hours | +| Output retention | 7 days | 30 days | 30 days | +| Compose frame | Up to 1080p at 30 fps | Up to 1440p at 30 fps | Up to 4K at 60 fps | +| Compose with animated graphics | Up to 1080p at 30 fps | Up to 1080p at 30 fps | Up to 1080p at 30 fps | +| GPU FFmpeg (NVENC/CUDA) | No | Yes | Yes | + +`ffmpeg`, MCP, and webhooks are on every plan. Compose costs the same per output minute on every plan. Animated graphics above 1080p or 30 fps need an enterprise plan. Full limits table: [Limits](/support/limits). For a full pricing breakdown and plan comparison, see [rendobar.com/pricing/](https://rendobar.com/pricing/). ## Credit packs -\$10 / \$25 / \$50 / \$100 (or custom, \$10 minimum). Pro adds **+20%** to every purchase. Buy \$25, get \$30. Credits never expire. +\$10 / \$25 / \$50 / \$100 (or custom, \$10 minimum). Pro and Studio add **+25%** to every purchase. Buy \$25, get \$31.25. Credits never expire. ## How does nanodollar billing work? @@ -74,7 +73,7 @@ Because the submit gate checks `balance > 0` rather than an estimate, a single l `ffmpeg` bills per compute second, which is the wall-clock time FFmpeg ran your command, excluding upload and download. The same per-second rate applies whether the job completes successfully or fails partway through. Every job type except compose bills this way. -`compose` bills per second of the video you get back, at \$0.04 per minute of output on every plan, with a 15-second minimum per render. Frame size and animated graphics do not change the rate. Plans differ in what they can render. 4K and 60 fps need Pro, and a video above your plan returns `403 PLAN_LIMIT` before it renders. An enterprise plan carries its own contract rate. Email `hello@rendobar.com`. +`compose` bills per second of the video you get back, at \$0.04 per minute of output on every plan, with a 15-second minimum per render. Frame size and animated graphics do not change the rate. Plans differ in what they can render. 1440p needs Pro, 4K and 60 fps need Studio, and a video above your plan returns `403 PLAN_LIMIT` before it renders. An enterprise plan carries its own contract rate. Email `hello@rendobar.com`. A compose render that fails on Rendobar's side is free. When the failure may come from your input, such as the render tool exiting on a bad file, it pays for the compute it used at 2x. `POST /compose/validate` returns the price before you render. Full detail: [compose pricing](/jobs/compose/pricing). diff --git a/jobs/compose/output.mdx b/jobs/compose/output.mdx index d97e034..f5fcdfa 100644 --- a/jobs/compose/output.mdx +++ b/jobs/compose/output.mdx @@ -49,7 +49,7 @@ A wide photograph in a phone-shaped box. `contain` shows all of it and leaves ba -Pay-as-you-go renders video up to 1080p at 30 fps. 4K and 60 fps need Pro, and animated graphics above 1080p or 30 fps need an enterprise plan. A video above your plan returns `403 PLAN_LIMIT` at submit, and validate reports it first. See [compose pricing](/jobs/compose/pricing#what-each-plan-renders). +Pay-as-you-go renders video up to 1080p at 30 fps. 1440p needs Pro, 4K and 60 fps need Studio, and animated graphics above 1080p or 30 fps need an enterprise plan. A video above your plan returns `403 PLAN_LIMIT` at submit, and validate reports it first. See [compose pricing](/jobs/compose/pricing#what-each-plan-renders). A compose job renders exactly one file. Two sizes of the same edit are two jobs, which is also how you get them rendered at the same time. `gif` carries no audio, `mp3` is audio only and needs no frame size, and `jpg` and `png` are a single frame. diff --git a/jobs/compose/pricing.mdx b/jobs/compose/pricing.mdx index 40876d3..1628140 100644 --- a/jobs/compose/pricing.mdx +++ b/jobs/compose/pricing.mdx @@ -27,12 +27,13 @@ Plans differ in what they can render, not in what a minute costs. | Plan | Renders up to | | --- | --- | | Free (pay-as-you-go) | 1080p at 30 fps, animated graphics included | -| Pro | 4K at 60 fps. Animated graphics up to 1080p at 30 fps | +| Pro | 1440p at 30 fps. Animated graphics up to 1080p at 30 fps | +| Studio | 4K at 60 fps. Animated graphics up to 1080p at 30 fps | | Enterprise | Animated graphics above 1080p or 30 fps, at a contract rate. Email `hello@rendobar.com` | Animated graphics are kinetic text, 3D cards, HTML and Lottie motion, and anything else the compose engine draws as animated graphics. The frame counts by its pixels, so a portrait 1080x1920 video counts as 1080p. -Pro costs \$9 a month and includes \$5 of credit each month. Its discount on compose is the 20% bonus on every credit purchase. See [credits and billing](/concepts/credits#plans). +Pro costs \$9 a month and includes \$5 of credit each month. Studio costs \$29 a month and includes \$20 of credit each month. Their discount on compose is the 25% bonus on every credit purchase, so \$25 of credit buys \$31.25. See [credits and billing](/concepts/credits#plans). A video above your plan is refused at submit with `403 PLAN_LIMIT`, before anything renders or is charged. The message names the plan that renders it. Only video outputs (`mp4`, `webm`, `gif`) are checked. A `jpg`, `png` or `mp3` output renders on every plan. diff --git a/jobs/ffmpeg.mdx b/jobs/ffmpeg.mdx index 0cba160..9fa8d12 100644 --- a/jobs/ffmpeg.mdx +++ b/jobs/ffmpeg.mdx @@ -185,7 +185,7 @@ This burns a subtitle track that lives only in the request, with no file to host - Max execution time in seconds. Defaults to your plan's full job budget (Free 1 hour, Pro 9 hours), so you only set this when you want a job to fail sooner. Asking for more than your plan allows is rejected at submit with `PLAN_LIMIT`. + Max execution time in seconds. Defaults to your plan's full job budget (Free 1 hour, Pro and Studio 9 hours), so you only set this when you want a job to fail sooner. Asking for more than your plan allows is rejected at submit with `PLAN_LIMIT`. @@ -195,7 +195,7 @@ This burns a subtitle track that lives only in the request, with no file to host - `gpu`: force a GPU machine. - `cpu`: force a CPU machine. A command that calls an NVENC encoder is rejected with `VALIDATION_ERROR`. - GPU jobs run the hardware encoders `h264_nvenc`, `hevc_nvenc`, and `av1_nvenc` on NVIDIA L4 GPUs, billed per second. `gpu` and `auto`-routed GPU jobs **require the Pro plan**. A GPU job on the Free plan returns `403 PLAN_LIMIT`. `cpu` and `auto` work on every plan. See [GPU acceleration](#gpu-acceleration). + GPU jobs run the hardware encoders `h264_nvenc`, `hevc_nvenc`, and `av1_nvenc` on NVIDIA L4 GPUs, billed per second. `gpu` and `auto`-routed GPU jobs **require a Pro or Studio plan**. A GPU job on the Free plan returns `403 PLAN_LIMIT`. `cpu` and `auto` work on every plan. See [GPU acceleration](#gpu-acceleration). ## Can I validate a command without running it? @@ -263,7 +263,7 @@ Set `compute` only to force a choice. `gpu` pins the job to a GPU. `cpu` pins it ``` -GPU jobs require the Pro plan and bill per second at GPU rates. A GPU job on the Free plan returns `403 PLAN_LIMIT`. CPU jobs run on every plan. +GPU jobs require a Pro or Studio plan and bill per second at GPU rates. A GPU job on the Free plan returns `403 PLAN_LIMIT`. CPU jobs run on every plan. ## How is a command sandboxed? @@ -279,7 +279,7 @@ Every command is validated before it runs, then executed in an isolated, network Priced per compute second from your [credit balance](/concepts/credits), the wall-clock time FFmpeg ran, excluding file transfer. -Plan job budgets: 1 hour (Free), 9 hours (Pro). That is a bound on processing time, not on how long your output can be. See [plan limits](/support/limits) for the full table. Exceeding the timeout returns `RUNNER_TIMEOUT`, and no credits are charged. +Plan job budgets: 1 hour (Free), 9 hours (Pro and Studio). That is a bound on processing time, not on how long your output can be. See [plan limits](/support/limits) for the full table. Exceeding the timeout returns `RUNNER_TIMEOUT`, and no credits are charged. ## Examples diff --git a/quickstart.mdx b/quickstart.mdx index d2f4127..12ea190 100644 --- a/quickstart.mdx +++ b/quickstart.mdx @@ -165,5 +165,5 @@ Need more credits? See [pricing](https://rendobar.com/pricing/) for plan and cre - [FFmpeg guide](/jobs/ffmpeg): security model, allowed flags, worked examples - [Job lifecycle](/concepts/job): the six statuses and what triggers each -- [Pricing and plans](https://rendobar.com/pricing/): Free vs Pro and credit-pack pricing +- [Pricing and plans](https://rendobar.com/pricing/): Free, Pro and Studio plans and credit-pack pricing - [All supported FFmpeg operations](https://rendobar.com/features/): full feature index diff --git a/support/faq.mdx b/support/faq.mdx index 687821f..c543dfa 100644 --- a/support/faq.mdx +++ b/support/faq.mdx @@ -21,7 +21,7 @@ canonical: "https://rendobar.com/docs/support/faq" "name": "How much does Rendobar cost?", "acceptedAnswer": { "@type": "Answer", - "text": "Free plan ships with a one-time $5 credit grant, no card required. Pro is $9/mo, includes $5/mo in credits, plus 20% bonus on every credit purchase. Credit packs $10 to $100 on both plans. Credits never expire. Compose bills per minute of output video: $0.04 on every plan, billed per second with a 15-second minimum per render. Pro adds compose at 4K and 60 fps and GPU FFmpeg. Every other job type bills per second of compute." + "text": "Free plan ships with a one-time $5 credit grant, no card required. Pro is $9/mo and includes $5/mo in credits. Studio is $29/mo and includes $20/mo in credits. Both add a 25% bonus on every credit purchase. Credit packs $10 to $100 on every plan. Credits never expire. Compose bills per minute of output video: $0.04 on every plan, billed per second with a 15-second minimum per render. Pro adds compose at 1440p and GPU FFmpeg. Studio adds compose at 4K and 60 fps and 50 concurrent jobs. Every other job type bills per second of compute." } }, { @@ -53,7 +53,7 @@ canonical: "https://rendobar.com/docs/support/faq" "name": "How long do jobs take?", "acceptedAnswer": { "@type": "Answer", - "text": "Seconds to minutes, depending on input size and command. Hard caps on processing time: 1 hour (Free), 9 hours (Pro)." + "text": "Seconds to minutes, depending on input size and command. Hard caps on processing time: 1 hour (Free), 9 hours (Pro and Studio)." } }, { @@ -61,7 +61,7 @@ canonical: "https://rendobar.com/docs/support/faq" "name": "Is there a rate limit?", "acceptedAnswer": { "@type": "Answer", - "text": "Yes. 120 req/min (Free), 600 req/min (Pro). Applies to REST and MCP. Limit hit returns 429 RATE_LIMITED with a Retry-After header." + "text": "Yes. 120 req/min (Free), 600 req/min (Pro), 1200 req/min (Studio). Applies to REST and MCP. Limit hit returns 429 RATE_LIMITED with a Retry-After header." } }, { @@ -96,7 +96,7 @@ canonical: "https://rendobar.com/docs/support/faq" - Free plan ships with a one-time \$5 credit grant, no card required. Pro is \$9/mo, includes \$5/mo in credits, plus 20% bonus on every credit purchase. Credit packs \$10 to \$100 on both plans. Credits never expire. Compose bills per minute of output video: \$0.04 on every plan, billed per second with a 15-second minimum per render. Pro adds compose at 4K and 60 fps and GPU FFmpeg. Every other job type bills per second of compute. Full breakdown: [Credits and billing](/concepts/credits), [compose pricing](/jobs/compose/pricing), or the [pricing page](https://rendobar.com/pricing/). + Free plan ships with a one-time \$5 credit grant, no card required. Pro is \$9/mo and includes \$5/mo in credits. Studio is \$29/mo and includes \$20/mo in credits. Both add a 25% bonus on every credit purchase. Credit packs \$10 to \$100 on every plan. Credits never expire. Compose bills per minute of output video: \$0.04 on every plan, billed per second with a 15-second minimum per render. Pro adds compose at 1440p and GPU FFmpeg. Studio adds compose at 4K and 60 fps and 50 concurrent jobs. Every other job type bills per second of compute. Full breakdown: [Credits and billing](/concepts/credits), [compose pricing](/jobs/compose/pricing), or the [pricing page](https://rendobar.com/pricing/). @@ -112,11 +112,11 @@ canonical: "https://rendobar.com/docs/support/faq" - Seconds to minutes, depending on input size and command. Hard caps on processing time: 1 hour (Free), 9 hours (Pro). [Limits](/support/limits). + Seconds to minutes, depending on input size and command. Hard caps on processing time: 1 hour (Free), 9 hours (Pro and Studio). [Limits](/support/limits). - Yes. 120 req/min (Free), 600 req/min (Pro). Applies to REST and MCP. Limit hit returns `429 RATE_LIMITED` with a `Retry-After` header. + Yes. 120 req/min (Free), 600 req/min (Pro), 1200 req/min (Studio). Applies to REST and MCP. Limit hit returns `429 RATE_LIMITED` with a `Retry-After` header. @@ -140,5 +140,5 @@ For more about what Rendobar can do, see [rendobar.com](https://rendobar.com) or - [Credits and billing](/concepts/credits): full pricing and balance model - [Plan limits](/support/limits): every rate, file, and storage cap by plan - [Error codes](/support/errors): every `error.code` the API can return -- [Pricing](https://rendobar.com/pricing/): Free vs Pro and credit packs +- [Pricing](https://rendobar.com/pricing/): Free, Pro and Studio plans and credit packs - [Rendobar vs alternatives](https://rendobar.com/vs/): comparisons with other video APIs diff --git a/support/limits.mdx b/support/limits.mdx index f7a2250..4c284c0 100644 --- a/support/limits.mdx +++ b/support/limits.mdx @@ -27,35 +27,34 @@ Every limit Rendobar enforces, by plan. Pulled from `PLAN_LIMITS` in the API. Fo ## Plan limits -| | Free | Pro ($9/mo) | -|---|---|---| -| Signup credits | $5 (one-time) | - | -| Monthly credits | - | $5/mo included | -| Credit-purchase bonus | - | +20% | -| Concurrent jobs | 1 | 20 | -| Queued backlog | 50 | 1000 | -| API requests / min | 120 | 600 | -| Max input file | 500 MB | 10 GB | -| Max job timeout | 1 hour | 9 hours | -| Output retention | 7 days | 30 days | -| Total object storage | 5 GB | 250 GB | -| Compose frame | Up to 1080p at 30 fps | Up to 4K at 60 fps | -| Compose with animated graphics | Up to 1080p at 30 fps | Up to 1080p at 30 fps | -| GPU FFmpeg (NVENC/CUDA) | No | Yes | -| Priority queue | No | Yes | - -`ffmpeg`, MCP, and webhooks are on both plans. A compose video above your plan's frame or frame rate returns `403 PLAN_LIMIT` before it renders, and the message names the plan that renders it. Animated graphics above 1080p or 30 fps need an enterprise plan. `GET /account/capabilities` returns these as `composeMaxPixels`, `composeMaxFps`, `composeAnimatedMaxPixels` and `composeAnimatedMaxFps`. See [compose pricing](/jobs/compose/pricing). +| | Free | Pro ($9/mo) | Studio ($29/mo) | +|---|---|---|---| +| Signup credits | $5 (one-time) | - | - | +| Monthly credits | - | $5/mo included | $20/mo included | +| Credit-purchase bonus | - | +25% | +25% | +| Concurrent jobs | 1 | 20 | 50 | +| Queued backlog | 50 | 1000 | 2500 | +| API requests / min | 120 | 600 | 1200 | +| Max input file | 500 MB | 10 GB | 10 GB | +| Max job timeout | 1 hour | 9 hours | 9 hours | +| Output retention | 7 days | 30 days | 30 days | +| Total object storage | 5 GB | 250 GB | 250 GB | +| Compose frame | Up to 1080p at 30 fps | Up to 1440p at 30 fps | Up to 4K at 60 fps | +| Compose with animated graphics | Up to 1080p at 30 fps | Up to 1080p at 30 fps | Up to 1080p at 30 fps | +| GPU FFmpeg (NVENC/CUDA) | No | Yes | Yes | + +`ffmpeg`, MCP, and webhooks are on every plan. A compose video above your plan's frame or frame rate returns `403 PLAN_LIMIT` before it renders, and the message names the plan that renders it. Animated graphics above 1080p or 30 fps need an enterprise plan. `GET /account/capabilities` returns these as `composeMaxPixels`, `composeMaxFps`, `composeAnimatedMaxPixels` and `composeAnimatedMaxFps`. See [compose pricing](/jobs/compose/pricing). ## How long do output URLs last? - **Signed download URL** (`output.file.url`) is valid for **1 hour**. Re-fetch via `GET /jobs/{id}` for a fresh URL. -- **Output retention**: 7 days (Free) or 30 days (Pro). After that, the file is deleted; the job record stays but the file URLs return 404. +- **Output retention**: 7 days (Free) or 30 days (Pro and Studio). After that, the file is deleted; the job record stays but the file URLs return 404. - **Storage quota** caps total bytes across all your outputs. Delete old outputs or upgrade if you hit it. - **No separate per-output size limit**. Bounded by input cap and storage quota. ## Credit packs -\$10 / \$25 / \$50 / \$100 (or custom, \$10 minimum). Pro adds +20% to every purchase. Credits never expire. Failed jobs are not refunded automatically. See [credits and billing](/concepts/credits) for what a failed job pays. +\$10 / \$25 / \$50 / \$100 (or custom, \$10 minimum). Pro and Studio add +25% to every purchase. Credits never expire. Failed jobs are not refunded automatically. See [credits and billing](/concepts/credits) for what a failed job pays. ## API response limits From 072b2e0fe97bf23e54e3e1fb17320e7bf8c6995e Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Tue, 20 Oct 2026 13:17:58 +0300 Subject: [PATCH 07/17] docs(compose): regenerate the field reference Generated from the schema the API validates with. Nested fields such as style.animate.by, stagger, order and out now appear, with the 3D transform fields, and schemaVersion, output and style.size show their defaults instead of being marked required. --- jobs/compose/reference/subtitles.mdx | 18 ++++++-- jobs/compose/reference/text.mdx | 18 ++++++-- jobs/compose/reference/timeline.mdx | 64 ++++++++++++++++++++++++---- jobs/compose/reference/timer.mdx | 18 ++++++-- 4 files changed, 101 insertions(+), 17 deletions(-) diff --git a/jobs/compose/reference/subtitles.mdx b/jobs/compose/reference/subtitles.mdx index 6a05021..659b08b 100644 --- a/jobs/compose/reference/subtitles.mdx +++ b/jobs/compose/reference/subtitles.mdx @@ -19,16 +19,28 @@ Set `asset.type` to `"subtitles"`. These are its own fields, on top of the [clip | `cues.text` * | string | The line. Keep it to two short rows. | | `src` | string | An .srt or .vtt file with the lines, instead of `cues`: what a transcription service, or the caption.extract job, gives you. Its times count from the start of the clip. | | `reveal` | `"line"` \| `"typewriter"` | line shows each cue at once, typewriter types it out word by word, the social-video style. default `"line"`. | -| `style` * | object | | +| `style` | object | default `{}`. | | `style.font` | string | Font family, or a CSS fallback stack. Any Google Fonts family works by name. Bundled and instant: Inter, Bebas Neue, Anton, Montserrat, Poppins, Roboto, Oswald. Families declared in the `fonts` job input win over both. A stack falls through in CSS order, so "Display, Inter, sans-serif" tries each in turn. default `"Inter"`. | -| `style.size` * | number | Font size in pixels of the output frame. ~64-96 for titles at 1080p, ~36-48 for lower thirds. over 0. | +| `style.size` | number | Font size in pixels of the output frame. ~64-96 for titles at 1080p, ~36-48 for lower thirds. Defaults to 64. default `64`, over 0. | | `style.weight` | integer | Font weight 100..1000 (400 regular, 700 bold, 800+ display). Honoured exactly on variable fonts; snapped to the nearest available weight otherwise. default `400`, min 100, max 1000. | | `style.italic` | boolean | Render the italic cut of the family, when it has one. default `false`. | | `style.color` | string | Text fill color, hex like "#FFFFFF". default `"#FFFFFF"`, pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | | `style.stroke` | object | Outline around the glyphs. A 2-4px black stroke keeps white text readable on bright footage. | +| `style.stroke.color` * | string | Outline color, hex like "#000000". pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | +| `style.stroke.width` * | number | Outline width in pixels. 2 to 4 keeps text readable over busy footage. min 0. | | `style.background` | string | Solid box color behind the text, hex. Omit for no box. pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | | `style.align` | `"left"` \| `"center"` \| `"right"` | How the lines line up, and which side of the text sits on the clip's transform.position x: left starts there, center is centred on it, right ends there. Place the text with the clip's transform, not here. default `"center"`. | -| `style.animate` | object | Entrance animation. Omit for text that is present from its first frame. | +| `style.animate` | object | How the text enters, and optionally leaves: \{"type":"slideUp","by":"word"\} brings a title in word by word. Omit for text that is present from its first frame to its last. | +| `style.animate.type` * | `"fade"` \| `"slideUp"` \| `"slideDown"` \| `"slideLeft"` \| `"slideRight"` \| `"blur"` \| `"scale"` | How the text arrives: fade in place, slide in from the named direction while fading, blur comes into focus, scale grows from small. | +| `style.animate.duration` | number | Seconds the whole entrance takes. With `by`, the last unit lands at this time. 0.4 to 0.8 reads polished for one piece of text, 0.8 to 1.5 for a split title. default `0.5`, over 0. | +| `style.animate.by` | `"letter"` \| `"word"` \| `"line"` | Animate the text one letter, word or line at a time: kinetic titles and lower thirds. Omit it and the text moves as one piece. | +| `style.animate.stagger` | number | Seconds between one unit starting and the next. Omit it and the units spread evenly over the first 40% of `duration`. min 0. | +| `style.animate.order` | `"start"` \| `"end"` \| `"center"` \| `"edges"` \| `"random"` | Which unit starts first. center and edges start units at the same distance together. random is seeded by the clip, so a re-render is identical. default `"start"`. | +| `style.animate.out` | `"fade"` \| `"slideUp"` \| `"slideDown"` \| `"slideLeft"` \| `"slideRight"` \| `"blur"` \| `"scale"` | How the text leaves over the clip's last `duration` seconds, with the same split and order. Omit it to hold until the clip ends. | | `style.shadow` | object | Drop shadow behind the text, adds depth and readability. | +| `style.shadow.color` | string | Shadow color, hex. default `"#000000"`, pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | +| `style.shadow.opacity` | number | Shadow opacity 0..1. default `0.6`, min 0, max 1. | +| `style.shadow.offsetX` | number | Horizontal shadow offset in pixels. default `4`. | +| `style.shadow.offsetY` | number | Vertical shadow offset in pixels. default `4`. | Fields marked `*` are required. Every table on this page is generated from the schema the API validates with, so it cannot drift from what a render accepts. diff --git a/jobs/compose/reference/text.mdx b/jobs/compose/reference/text.mdx index 67a6f82..cf3a59f 100644 --- a/jobs/compose/reference/text.mdx +++ b/jobs/compose/reference/text.mdx @@ -14,16 +14,28 @@ Set `asset.type` to `"text"`. These are its own fields, on top of the [clip fiel | Field | Type | What it does | | --- | --- | --- | | `text` * | string | The text to render. Keep titles short, 2-6 words reads best. | -| `style` * | object | | +| `style` | object | default `{}`. | | `style.font` | string | Font family, or a CSS fallback stack. Any Google Fonts family works by name. Bundled and instant: Inter, Bebas Neue, Anton, Montserrat, Poppins, Roboto, Oswald. Families declared in the `fonts` job input win over both. A stack falls through in CSS order, so "Display, Inter, sans-serif" tries each in turn. default `"Inter"`. | -| `style.size` * | number | Font size in pixels of the output frame. ~64-96 for titles at 1080p, ~36-48 for lower thirds. over 0. | +| `style.size` | number | Font size in pixels of the output frame. ~64-96 for titles at 1080p, ~36-48 for lower thirds. Defaults to 64. default `64`, over 0. | | `style.weight` | integer | Font weight 100..1000 (400 regular, 700 bold, 800+ display). Honoured exactly on variable fonts; snapped to the nearest available weight otherwise. default `400`, min 100, max 1000. | | `style.italic` | boolean | Render the italic cut of the family, when it has one. default `false`. | | `style.color` | string | Text fill color, hex like "#FFFFFF". default `"#FFFFFF"`, pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | | `style.stroke` | object | Outline around the glyphs. A 2-4px black stroke keeps white text readable on bright footage. | +| `style.stroke.color` * | string | Outline color, hex like "#000000". pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | +| `style.stroke.width` * | number | Outline width in pixels. 2 to 4 keeps text readable over busy footage. min 0. | | `style.background` | string | Solid box color behind the text, hex. Omit for no box. pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | | `style.align` | `"left"` \| `"center"` \| `"right"` | How the lines line up, and which side of the text sits on the clip's transform.position x: left starts there, center is centred on it, right ends there. Place the text with the clip's transform, not here. default `"center"`. | -| `style.animate` | object | Entrance animation. Omit for text that is present from its first frame. | +| `style.animate` | object | How the text enters, and optionally leaves: \{"type":"slideUp","by":"word"\} brings a title in word by word. Omit for text that is present from its first frame to its last. | +| `style.animate.type` * | `"fade"` \| `"slideUp"` \| `"slideDown"` \| `"slideLeft"` \| `"slideRight"` \| `"blur"` \| `"scale"` | How the text arrives: fade in place, slide in from the named direction while fading, blur comes into focus, scale grows from small. | +| `style.animate.duration` | number | Seconds the whole entrance takes. With `by`, the last unit lands at this time. 0.4 to 0.8 reads polished for one piece of text, 0.8 to 1.5 for a split title. default `0.5`, over 0. | +| `style.animate.by` | `"letter"` \| `"word"` \| `"line"` | Animate the text one letter, word or line at a time: kinetic titles and lower thirds. Omit it and the text moves as one piece. | +| `style.animate.stagger` | number | Seconds between one unit starting and the next. Omit it and the units spread evenly over the first 40% of `duration`. min 0. | +| `style.animate.order` | `"start"` \| `"end"` \| `"center"` \| `"edges"` \| `"random"` | Which unit starts first. center and edges start units at the same distance together. random is seeded by the clip, so a re-render is identical. default `"start"`. | +| `style.animate.out` | `"fade"` \| `"slideUp"` \| `"slideDown"` \| `"slideLeft"` \| `"slideRight"` \| `"blur"` \| `"scale"` | How the text leaves over the clip's last `duration` seconds, with the same split and order. Omit it to hold until the clip ends. | | `style.shadow` | object | Drop shadow behind the text, adds depth and readability. | +| `style.shadow.color` | string | Shadow color, hex. default `"#000000"`, pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | +| `style.shadow.opacity` | number | Shadow opacity 0..1. default `0.6`, min 0, max 1. | +| `style.shadow.offsetX` | number | Horizontal shadow offset in pixels. default `4`. | +| `style.shadow.offsetY` | number | Vertical shadow offset in pixels. default `4`. | Fields marked `*` are required. Every table on this page is generated from the schema the API validates with, so it cannot drift from what a render accepts. diff --git a/jobs/compose/reference/timeline.mdx b/jobs/compose/reference/timeline.mdx index f50b640..6c60598 100644 --- a/jobs/compose/reference/timeline.mdx +++ b/jobs/compose/reference/timeline.mdx @@ -15,11 +15,13 @@ The shape of a compose request, from the outside in. Each asset kind adds its ow | Field | Type | What it does | | --- | --- | --- | -| `schemaVersion` * | `1` | Timeline schema version. Always 1. | -| `output` * | object | | +| `schemaVersion` | `1` | Timeline schema version. Always 1, and assumed when omitted. default `1`. | +| `output` | object | Output file. Omit for a 1080p mp4 at 30 fps. default `{"size":"1080p"}`. | | `output.format` | `"mp4"` \| `"webm"` \| `"gif"` \| `"mp3"` \| `"jpg"` \| `"png"` | Output container/format. mp4=H.264+AAC, webm=VP9+Opus, gif=animated (no audio), mp3=audio only and needs no size, jpg/png=single frame. default `"mp4"`. | | `output.size` | `"360p"` \| `"720p"` \| `"1080p"` \| `"1440p"` \| `"4k"` \| `"tiktok"` \| `"reels"` \| `"shorts"` \| `"instagram-story"` \| `"instagram-feed"` \| `"square"` \| `"youtube-hd"` \| `"youtube-4k"` \| `"x-landscape"` \| `"linkedin"` | Named resolution preset (dimensions only): a generic ladder ("1080p", "4k") or a platform alias ("tiktok", "reels", "youtube-4k"). An explicit `resolution` overrides this. Not used by mp3. | | `output.resolution` | object | Explicit output dimensions. Wins over `size`. Every format but mp3 needs this or `size`. | +| `output.resolution.width` * | integer | Output width in pixels. over 0, max 9007199254740991. | +| `output.resolution.height` * | integer | Output height in pixels. over 0, max 9007199254740991. | | `output.fps` | number | Output frame rate. Defaults to 30. default `30`, over 0. | | `output.quality` | `"draft"` \| `"standard"` \| `"high"` \| `"max"` | Encoder quality ladder (crf + speed + audio bitrate). draft=fast preview, standard=web delivery (default), high/max=higher fidelity, slower. Explicit `crf`/`encoderPreset` override the ladder. default `"standard"`. | | `output.videoCodec` | `"h264"` \| `"vp9"` | The video codec. mp4 takes h264 (default) or vp9. webm is always vp9. gif is always GIF, so it takes none. | @@ -27,6 +29,8 @@ The shape of a compose request, from the outside in. Each asset kind adds its ow | `output.crf` | integer | Advanced: constant rate factor (lower = higher quality/larger file). Overrides `quality`. h264 accepts 0-51, vp9 0-63. Does not apply to gif/mp3/jpg/png. min 0, max 63. | | `output.encoderPreset` | `"ultrafast"` \| `"superfast"` \| `"veryfast"` \| `"faster"` \| `"fast"` \| `"medium"` \| `"slow"` \| `"slower"` \| `"veryslow"` | Advanced: x264 speed/compression preset (slower = smaller file, same quality). Overrides `quality`. h264 only. | | `output.range` | object | Which part of the timeline to render: a preview, or one section checked without paying for the whole render. A jpg or png is the single frame at `start`, the first frame when there is no range. | +| `output.range.start` | number | Second of the timeline to start from. For a jpg or png, the moment captured. default `0`, min 0. | +| `output.range.length` | number | Seconds to render. Leave it out to run to the end. A range past the end stops at the end. A jpg or png is one frame and ignores it. over 0. | | `timeline` * | object | The edit itself, in ONE of two shapes: \{ tracks \} for precise multi-track control (layers, exact timing), or \{ scenes, overlays \} for sequential scene-by-scene authoring. Not both. | | `prompt` | string | The natural-language request this timeline was generated from, when AI assist was used. | | `assets` | string[] | The asset URLs supplied to AI assist, when it was used. | @@ -40,23 +44,56 @@ The shape of a compose request, from the outside in. Each asset kind adds its ow | `tracks` | object[] | Layer stack: the FIRST track is the background, each later track composites on top (overlays, picture-in-picture, titles). Within a track, clips play in time; between two clips a transition item blends them. | | `tracks.name` | string | Optional label for your own reference, not rendered. | | `tracks.effects` | object[] | Effects on the whole track, after its clips are laid out: grade every clip on it at once, or put a limiter on a music track. | -| `tracks.clips` * | object[] | Clips in time order, with optional transition items between adjacent clips. Omit start and length and they lay themselves out end to end, each as long as its source, overlapping by any transition between them. Text and image clips have no source, so they run to the end of the video and must come last on their track. | +| `tracks.clips` * | object[] | Clips in time order, with optional transition items between adjacent clips. Omit start and length and they lay themselves out end to end, each as long as its source, overlapping by any transition between them. Two clips that overlap with no transition between them crossfade over the overlap, and the job says which in the render step's `clipOverlaps`. Text and image clips have no source, so they run to the end of the video and must come last on their track. | | `scenes` | object[] | Sequential segments played one after another, each with its own clips and an optional transition into it. | | `scenes.duration` | number | Scene length in seconds. Omit to take it from the longest thing inside: a clip's own length, or the source in it. over 0. | | `scenes.transition` | object | The transition INTO this scene from the previous one. Omit on the first scene and for hard cuts. | +| `scenes.transition.transition` * | `"crossfade"` \| `"fade"` \| `"slide"` \| `"push"` \| `"wipe"` \| `"zoom"` \| `"none"` | crossfade dissolves A into B, fade dips through black, slide moves B in over A, push moves B in and A out together, wipe reveals B in place along an edge or a shape, zoom grows B from the center, none is a hard cut. | +| `scenes.transition.direction` | `"left"` \| `"right"` \| `"up"` \| `"down"` | slide, push and an edge wipe: which way the picture travels. Default left. | +| `scenes.transition.shape` | `"edge"` \| `"circle"` \| `"box"` \| `"barnDoorHorizontal"` \| `"barnDoorVertical"` | wipe only. edge (default) sweeps a straight line across in `direction`, circle opens an iris from the centre, box opens a rectangle from the centre, barnDoorVertical opens a vertical gap from the middle out to the sides, barnDoorHorizontal opens a horizontal gap up and down. | +| `scenes.transition.map` | string | wipe only: a wipe of your own, drawn by a greyscale image (png or jpg) at the frame's shape. Black turns over to the next clip first and white last, so a left-to-right gradient is an edge wipe and a radial one an iris. Takes no `shape` or `direction`. | +| `scenes.transition.duration` * | number | Overlap in seconds. 0.4-0.8 is the professional range. Leave the next clip's `start` out and it is placed this far before the clip before it ends. Two clips placed by hand blend over the overlap they have. over 0. | | `scenes.clips` * | object[] | Clips layered within this scene: first is the background, later clips composite on top. | +| `scenes.clips.asset` * | object | | +| `scenes.clips.start` | number | Seconds into the scene. Omit to start at the scene's beginning. A clip is measured from its own start, so one placed 2s into a 5s scene has 3s of room. min 0. | +| `scenes.clips.length` | number \| `"end"` | Seconds. Omit it and the clip runs as long as its content does, never past the scene. Use "end" to span the scene whatever the content is, which is also what omitting means for text and images. | +| `scenes.clips.opacity` | number \| object[] | Clip opacity: a constant 0..1, or keyframes [\{time, value, easing\}] for animated fades. Fade a clip in with [\{time:0,value:0\},\{time:1,value:1\}]. default `1`. | +| `scenes.clips.transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns), or turn it in 3D with rotateX and rotateY. Omit for full-frame. | +| `scenes.clips.crop` | object | Trim edges off the frame by percent; the kept region scales back up to fill. Use to reframe landscape footage for a vertical canvas, and `radius` to round its corners. | +| `scenes.clips.speed` | number | Playback rate: 0.5 = slow motion, 2 = timelapse. Pitch shifts with speed. Not allowed on composition clips. Avoid slow motion on the OUTGOING side of a transition (it can run out of frames). over 0. | +| `scenes.clips.effects` | object[] | What is done to the clip's picture or sound: one ordered list, each entry an object with a "type". Picture effects run in the order listed, and so do sound effects. Any entry takes "start" and "length" (seconds, or a percent of the clip) to run for part of the clip, and a number parameter marked animatable takes keyframes. Effects: chromaKey(color, similarity): Remove a solid background colour (green screen); color(brightness, contrast, saturation, gamma, hue, temperature): Grade the picture by hand; look(name): A one-word look: greyscale, boost (vivid social pop), muted (desaturated, cinematic), lighten and darken (exposure), contrast, negative, sepia (brown archive tone), oldFilm (sepia with grain, dust, scratches, projector flicker and a vignette); blur(radius): Soften the picture; sharpen(amount): Crisp up soft footage; denoise(amount): Clean the grain a small sensor adds in low light; glow(amount): Make the bright parts of the picture bloom; grain(amount): A layer of film grain, so digital footage feels less clinical; lut(src): Grade the picture with a 3D LUT, the .cube file a colourist or a grading app exports; vignette(amount, radius): Darken the corners to pull the eye to the middle of the frame; shake(amount, speed): Handheld camera energy on a steady shot; glitch(amount): Digital break-up: blocks of the picture jump sideways and the colours tear; pixelate(size): Large square pixels over the whole picture; posterize(levels): Flatten the picture into a few steps of each colour, like a screen print; rgbShift(distance): Pull the red and blue channels apart sideways, for a chromatic, analogue-video edge; scanLines(): The dark horizontal lines of a CRT monitor; sketch(amount): Turn the picture into a pencil drawing; trails(duration): Blend the last moments of the clip together, so moving lights and people leave streaks; blurRegion(x, y, width, height, shape, radius): Blur one part of the picture and leave the rest sharp: a face, a number plate, a logo, a screen; mask(src, channel): Show the clip only where an image says: white or opaque keeps it, black or clear cuts it away, grey fades it; outline(color, width): A stroke round the edge of a layer that has transparency: a keyed subject, a picture in picture, a logo; dropShadow(color, opacity, blur, offsetX, offsetY): A soft shadow under a layer that has transparency, so a logo, a cut-out or a picture in picture lifts off what is behind it; visualizer(kind, color, x, y, width, height): Draw the sound over the picture: the audiogram look; eq(bass, mid, treble): Three-band tone control; compressor(threshold, ratio, makeup): Even out a voice, so quiet words and loud words sit at one level; limiter(gain, ceiling): Stop peaks from clipping; gate(threshold): Close the microphone between phrases, so room noise drops away in the pauses; highPass(cutoff): Cut everything below a frequency; lowPass(cutoff): Cut everything above a frequency; loudness(target): Bring sound to a standard loudness, riding the level as it goes; pitch(semitones): Raise or lower the pitch without changing the speed; echo(delay, amount): A repeat behind the sound; reverb(amount): Place a dry voice in a hall; voiceDenoise(amount): Remove steady background noise from under speech: a river, a fan, a street; engine(filter, params): any other filter by name, strictly typed ones only. | +| `scenes.clips.blendMode` | `"normal"` \| `"multiply"` \| `"screen"` \| `"overlay"` \| `"darken"` \| `"lighten"` \| `"add"` \| `"difference"` | How this clip mixes with the layer below: screen for light leaks/glows, multiply for darkening textures, overlay for contrast looks, add/lighten/darken/difference for effects. Meaningful on overlay tracks. | +| `scenes.clips.pan` | number | Stereo balance: -1 full left, 0 centered, 1 full right. min -1, max 1. | +| `scenes.clips.flip` | object | Mirror the clip horizontally and/or vertically. | | `overlays` | object[] | Clips composited over the WHOLE video regardless of scenes: a logo, a watermark, a persistent title. The whole video is their container, so omitting `length` spans it. `start` defaults to 0. | | `overlays.asset` * | object | | | `overlays.start` | number | When the clip begins, in seconds from the start of the video. Omit it and the clip starts after everything already placed on its track, pulled back by any transition sitting immediately before it. The first clip on a track starts at 0. min 0. | | `overlays.length` | number \| `"end"` | How long the clip plays. Omit it and the clip runs as long as its CONTENT does: the trim span, the source duration divided by speed, or a composition's own end. Use "end" and it runs to the end of the video instead, whatever the source says, which is what a music bed wants. Text and images have no duration of their own, so omitting is the same as "end" for them and a title with no length stays up. A clip that runs to the end must be the last on its track. A video, audio or animation clip that runs past the end of its file holds the last frame, silent, until the clip ends, and the job says which clip in the render step's `clipOverruns`. | | `overlays.opacity` | number \| object[] | Clip opacity: a constant 0..1, or keyframes [\{time, value, easing\}] for animated fades. Fade a clip in with [\{time:0,value:0\},\{time:1,value:1\}]. default `1`. | -| `overlays.transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns). Omit for full-frame. | +| `overlays.transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns), or turn it in 3D with rotateX and rotateY. Omit for full-frame. | +| `overlays.transform.position` | object \| object[] | Where to place the clip, in pixels like \{x:1600,y:900\} or percent like \{x:"75%",y:"75%"\}, or keyframes of them to move it. Omit it and the clip is centred, so a scale of 0.5 alone is a centred picture-in-picture. | +| `overlays.transform.scale` | number \| object[] | Size relative to the frame: 1 = full frame, 0.3 = a corner PiP, above 1 zooms in (crops edges). Keyframes animate it: [\{time:"0%",value:1,easing:"easeInOutSine"\},\{time:"100%",value:1.15\}] is a Ken Burns push-in. A keyframe's easing shapes the move into the next one. default `1`. | +| `overlays.transform.rotate` | number \| object[] | Rotation in degrees, clockwise. Keyframes spin it. default `0`. | +| `overlays.transform.rotateX` | number \| object[] | Tilt about the horizontal axis, in degrees: a number, or keyframes to turn it over time. [\{time:0,value:60\},\{time:0.8,value:0\}] tips the clip up from lying flat. | +| `overlays.transform.rotateY` | number \| object[] | Turn about the vertical axis, in degrees: a number, or keyframes to turn it over time. [\{time:0,value:-90\},\{time:0.8,value:0\}] swings the clip in like a door opening toward you. | +| `overlays.transform.perspective` | number | Distance of the viewer from the frame in pixels. Smaller is a stronger 3D effect: 600 is dramatic, 2000 is subtle. Omit for 1200. over 0. | +| `overlays.transform.anchor` | `"center"` \| `"topLeft"` | What `position` points at: the clip's center (default) or its top-left corner. It is also what the clip turns about. With no position the clip is centred either way. On text, x follows style.align, so anchor only decides whether y is the text's middle or its top edge. default `"center"`. | +| `overlays.transform.fit` | `"contain"` \| `"cover"` \| `"fill"` \| `"blur"` | How a picture fills its box when their shapes differ: contain shows all of it, cover fills the box and cuts what overflows (a vertical phone clip in a 16:9 frame with no bars), fill stretches it, blur shows all of it over an enlarged, blurred copy that fills the bars, for a clip that fills the frame. The box is `size`, or the frame. default `"contain"`. | +| `overlays.transform.size` | object \| object[] | The clip's box, in pixels like \{width:480,height:480\} or a percent of the frame. Omit it and the box is the whole frame. A picture fills it the way `fit` says: \{width:400,height:400\} with fit "cover" is a square cut from 16:9 footage. A shape is drawn to it: \{width:"100%",height:"12%"\} is a lower-third bar. Give one side and the clip keeps its own shape, which for a shape is SQUARE IN PIXELS, the only way to a true circle on a 16:9 frame. `scale` then multiplies the box, for zooms. Keyframes resize it: a width from "0%" to "80%" is a progress bar. | +| `overlays.transform.corners` | object \| object[] | Pins the clip's four corners to four points, in pixels or percent: a picture on a screen, a sign seen in perspective. It is the other form of a transform, so it is not combined with position, scale, rotate or fit. A corner may sit up to a frame beyond each edge. Keyframes move the corners. | | `overlays.crop` | object | Trim edges off the frame by percent; the kept region scales back up to fill. Use to reframe landscape footage for a vertical canvas, and `radius` to round its corners. | +| `overlays.crop.top` | string | Percent of the frame height to remove from the top. default `"0%"`, pattern `^-?\d+(\.\d+)?%$`. | +| `overlays.crop.right` | string | Percent of the frame width to remove from the right. default `"0%"`, pattern `^-?\d+(\.\d+)?%$`. | +| `overlays.crop.bottom` | string | Percent of the frame height to remove from the bottom. default `"0%"`, pattern `^-?\d+(\.\d+)?%$`. | +| `overlays.crop.left` | string | Percent of the frame width to remove from the left. default `"0%"`, pattern `^-?\d+(\.\d+)?%$`. | +| `overlays.crop.radius` | number | Rounds the corners of what is kept, 0 to 1. 1 rounds the shorter side fully, a pill. The corners ride the transform, so a scaled-down clip becomes a rounded picture-in-picture. The kept part fills the frame, so radius 1 is only a circle on a square frame. min 0, max 1. | | `overlays.speed` | number | Playback rate: 0.5 = slow motion, 2 = timelapse. Pitch shifts with speed. Not allowed on composition clips. Avoid slow motion on the OUTGOING side of a transition (it can run out of frames). over 0. | -| `overlays.effects` | object[] | What is done to the clip's picture or sound: one ordered list, each entry an object with a "type". Picture effects run in the order listed, and so do sound effects. Any entry takes "start" and "length" (seconds, or a percent of the clip) to run for part of the clip, and a number parameter marked animatable takes keyframes. Effects: chromaKey(color, similarity): Remove a solid background colour (green screen); color(brightness, contrast, saturation, gamma, hue, temperature): Grade the picture by hand; look(name): A one-word look: greyscale, boost (vivid social pop), muted (desaturated, cinematic), lighten and darken (exposure), contrast, negative, sepia (brown archive tone), oldFilm (sepia with grain, dust, scratches, projector flicker and a vignette); blur(radius): Soften the picture; sharpen(amount): Crisp up soft footage; denoise(amount): Clean the grain a small sensor adds in low light; glow(amount): Make the bright parts of the picture bloom; grain(amount): A layer of film grain, so digital footage feels less clinical; lut(src): Grade the picture with a 3D LUT, the .cube file a colourist or a grading app exports; vignette(amount, radius): Darken the corners to pull the eye to the middle of the frame; shake(amount, speed): Handheld camera energy on a steady shot; glitch(amount): Digital break-up: blocks of the picture jump sideways and the colours tear; pixelate(size): Large square pixels over the whole picture; posterize(levels): Flatten the picture into a few steps of each colour, like a screen print; rgbShift(distance): Pull the red and blue channels apart sideways, for a chromatic, analogue-video edge; scanLines(): The dark horizontal lines of a CRT monitor; sketch(amount): Turn the picture into a pencil drawing; trails(duration): Blend the last moments of the clip together, so moving lights and people leave streaks; blurRegion(x, y, width, height, shape, radius): Blur one part of the picture and leave the rest sharp: a face, a number plate, a logo, a screen; mask(src, channel): Show the clip only where an image says: white or opaque keeps it, black or clear cuts it away, grey fades it; outline(color, width): A stroke round the edge of a layer that has transparency: a keyed subject, a picture in picture, a logo; dropShadow(color, opacity, blur, offsetX, offsetY): A soft shadow under a layer that has transparency, so a logo, a cut-out or a picture in picture lifts off what is behind it; visualizer(kind, color, x, y, width, height): Draw the sound over the picture: the audiogram look; eq(bass, mid, treble): Three-band tone control; compressor(threshold, ratio, makeup): Even out a voice, so quiet words and loud words sit at one level; limiter(gain, ceiling): Stop peaks from clipping; gate(threshold): Close the microphone between phrases, so room noise drops away in the pauses; highPass(cutoff): Cut everything below a frequency; lowPass(cutoff): Cut everything above a frequency; loudness(target): Bring sound to a standard loudness, riding the level as it goes; pitch(semitones): Raise or lower the pitch without changing the speed; echo(delay, amount): A repeat behind the sound; reverb(amount): Place a dry voice in a hall; voiceDenoise(amount): Remove steady background noise from under speech: a river, a fan, a street; engine(service, params): any other engine filter by the engine's own names, strictly typed ones only. | +| `overlays.effects` | object[] | What is done to the clip's picture or sound: one ordered list, each entry an object with a "type". Picture effects run in the order listed, and so do sound effects. Any entry takes "start" and "length" (seconds, or a percent of the clip) to run for part of the clip, and a number parameter marked animatable takes keyframes. Effects: chromaKey(color, similarity): Remove a solid background colour (green screen); color(brightness, contrast, saturation, gamma, hue, temperature): Grade the picture by hand; look(name): A one-word look: greyscale, boost (vivid social pop), muted (desaturated, cinematic), lighten and darken (exposure), contrast, negative, sepia (brown archive tone), oldFilm (sepia with grain, dust, scratches, projector flicker and a vignette); blur(radius): Soften the picture; sharpen(amount): Crisp up soft footage; denoise(amount): Clean the grain a small sensor adds in low light; glow(amount): Make the bright parts of the picture bloom; grain(amount): A layer of film grain, so digital footage feels less clinical; lut(src): Grade the picture with a 3D LUT, the .cube file a colourist or a grading app exports; vignette(amount, radius): Darken the corners to pull the eye to the middle of the frame; shake(amount, speed): Handheld camera energy on a steady shot; glitch(amount): Digital break-up: blocks of the picture jump sideways and the colours tear; pixelate(size): Large square pixels over the whole picture; posterize(levels): Flatten the picture into a few steps of each colour, like a screen print; rgbShift(distance): Pull the red and blue channels apart sideways, for a chromatic, analogue-video edge; scanLines(): The dark horizontal lines of a CRT monitor; sketch(amount): Turn the picture into a pencil drawing; trails(duration): Blend the last moments of the clip together, so moving lights and people leave streaks; blurRegion(x, y, width, height, shape, radius): Blur one part of the picture and leave the rest sharp: a face, a number plate, a logo, a screen; mask(src, channel): Show the clip only where an image says: white or opaque keeps it, black or clear cuts it away, grey fades it; outline(color, width): A stroke round the edge of a layer that has transparency: a keyed subject, a picture in picture, a logo; dropShadow(color, opacity, blur, offsetX, offsetY): A soft shadow under a layer that has transparency, so a logo, a cut-out or a picture in picture lifts off what is behind it; visualizer(kind, color, x, y, width, height): Draw the sound over the picture: the audiogram look; eq(bass, mid, treble): Three-band tone control; compressor(threshold, ratio, makeup): Even out a voice, so quiet words and loud words sit at one level; limiter(gain, ceiling): Stop peaks from clipping; gate(threshold): Close the microphone between phrases, so room noise drops away in the pauses; highPass(cutoff): Cut everything below a frequency; lowPass(cutoff): Cut everything above a frequency; loudness(target): Bring sound to a standard loudness, riding the level as it goes; pitch(semitones): Raise or lower the pitch without changing the speed; echo(delay, amount): A repeat behind the sound; reverb(amount): Place a dry voice in a hall; voiceDenoise(amount): Remove steady background noise from under speech: a river, a fan, a street; engine(filter, params): any other filter by name, strictly typed ones only. | | `overlays.blendMode` | `"normal"` \| `"multiply"` \| `"screen"` \| `"overlay"` \| `"darken"` \| `"lighten"` \| `"add"` \| `"difference"` | How this clip mixes with the layer below: screen for light leaks/glows, multiply for darkening textures, overlay for contrast looks, add/lighten/darken/difference for effects. Meaningful on overlay tracks. | | `overlays.pan` | number | Stereo balance: -1 full left, 0 centered, 1 full right. min -1, max 1. | | `overlays.flip` | object | Mirror the clip horizontally and/or vertically. | +| `overlays.flip.horizontal` | boolean | Mirror left-right. default `false`. | +| `overlays.flip.vertical` | boolean | Mirror top-bottom. default `false`. | ## Track @@ -64,7 +101,7 @@ The shape of a compose request, from the outside in. Each asset kind adds its ow | --- | --- | --- | | `name` | string | Optional label for your own reference, not rendered. | | `effects` | object[] | Effects on the whole track, after its clips are laid out: grade every clip on it at once, or put a limiter on a music track. | -| `clips` * | object[] | Clips in time order, with optional transition items between adjacent clips. Omit start and length and they lay themselves out end to end, each as long as its source, overlapping by any transition between them. Text and image clips have no source, so they run to the end of the video and must come last on their track. | +| `clips` * | object[] | Clips in time order, with optional transition items between adjacent clips. Omit start and length and they lay themselves out end to end, each as long as its source, overlapping by any transition between them. Two clips that overlap with no transition between them crossfade over the overlap, and the job says which in the render step's `clipOverlaps`. Text and image clips have no source, so they run to the end of the video and must come last on their track. | ## Clip @@ -75,14 +112,25 @@ Every clip takes these, whatever its asset. | `start` | number | When the clip begins, in seconds from the start of the video. Omit it and the clip starts after everything already placed on its track, pulled back by any transition sitting immediately before it. The first clip on a track starts at 0. min 0. | | `length` | number \| `"end"` | How long the clip plays. Omit it and the clip runs as long as its CONTENT does: the trim span, the source duration divided by speed, or a composition's own end. Use "end" and it runs to the end of the video instead, whatever the source says, which is what a music bed wants. Text and images have no duration of their own, so omitting is the same as "end" for them and a title with no length stays up. A clip that runs to the end must be the last on its track. A video, audio or animation clip that runs past the end of its file holds the last frame, silent, until the clip ends, and the job says which clip in the render step's `clipOverruns`. | | `opacity` | number \| object[] | Clip opacity: a constant 0..1, or keyframes [\{time, value, easing\}] for animated fades. Fade a clip in with [\{time:0,value:0\},\{time:1,value:1\}]. default `1`. | -| `transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns). Omit for full-frame. | +| `transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns), or turn it in 3D with rotateX and rotateY. Omit for full-frame. | | `transform.position` | object \| object[] | Where to place the clip, in pixels like \{x:1600,y:900\} or percent like \{x:"75%",y:"75%"\}, or keyframes of them to move it. Omit it and the clip is centred, so a scale of 0.5 alone is a centred picture-in-picture. | +| `transform.position.x` * | number \| string | Horizontal: pixels from the left edge of the output frame, like 960, or a percent of its width, like "50%". | +| `transform.position.y` * | number \| string | Vertical: pixels from the top edge, like 540, or a percent of the frame height, like "80%". | | `transform.scale` | number \| object[] | Size relative to the frame: 1 = full frame, 0.3 = a corner PiP, above 1 zooms in (crops edges). Keyframes animate it: [\{time:"0%",value:1,easing:"easeInOutSine"\},\{time:"100%",value:1.15\}] is a Ken Burns push-in. A keyframe's easing shapes the move into the next one. default `1`. | | `transform.rotate` | number \| object[] | Rotation in degrees, clockwise. Keyframes spin it. default `0`. | +| `transform.rotateX` | number \| object[] | Tilt about the horizontal axis, in degrees: a number, or keyframes to turn it over time. [\{time:0,value:60\},\{time:0.8,value:0\}] tips the clip up from lying flat. | +| `transform.rotateY` | number \| object[] | Turn about the vertical axis, in degrees: a number, or keyframes to turn it over time. [\{time:0,value:-90\},\{time:0.8,value:0\}] swings the clip in like a door opening toward you. | +| `transform.perspective` | number | Distance of the viewer from the frame in pixels. Smaller is a stronger 3D effect: 600 is dramatic, 2000 is subtle. Omit for 1200. over 0. | | `transform.anchor` | `"center"` \| `"topLeft"` | What `position` points at: the clip's center (default) or its top-left corner. It is also what the clip turns about. With no position the clip is centred either way. On text, x follows style.align, so anchor only decides whether y is the text's middle or its top edge. default `"center"`. | | `transform.fit` | `"contain"` \| `"cover"` \| `"fill"` \| `"blur"` | How a picture fills its box when their shapes differ: contain shows all of it, cover fills the box and cuts what overflows (a vertical phone clip in a 16:9 frame with no bars), fill stretches it, blur shows all of it over an enlarged, blurred copy that fills the bars, for a clip that fills the frame. The box is `size`, or the frame. default `"contain"`. | | `transform.size` | object \| object[] | The clip's box, in pixels like \{width:480,height:480\} or a percent of the frame. Omit it and the box is the whole frame. A picture fills it the way `fit` says: \{width:400,height:400\} with fit "cover" is a square cut from 16:9 footage. A shape is drawn to it: \{width:"100%",height:"12%"\} is a lower-third bar. Give one side and the clip keeps its own shape, which for a shape is SQUARE IN PIXELS, the only way to a true circle on a 16:9 frame. `scale` then multiplies the box, for zooms. Keyframes resize it: a width from "0%" to "80%" is a progress bar. | +| `transform.size.width` | number \| string | Width: pixels of the output frame, like 480, or a percent of its width, like "25%". | +| `transform.size.height` | number \| string | Height: pixels, or a percent of the frame height. | | `transform.corners` | object \| object[] | Pins the clip's four corners to four points, in pixels or percent: a picture on a screen, a sign seen in perspective. It is the other form of a transform, so it is not combined with position, scale, rotate or fit. A corner may sit up to a frame beyond each edge. Keyframes move the corners. | +| `transform.corners.topLeft` * | object | | +| `transform.corners.topRight` * | object | | +| `transform.corners.bottomRight` * | object | | +| `transform.corners.bottomLeft` * | object | | | `crop` | object | Trim edges off the frame by percent; the kept region scales back up to fill. Use to reframe landscape footage for a vertical canvas, and `radius` to round its corners. | | `crop.top` | string | Percent of the frame height to remove from the top. default `"0%"`, pattern `^-?\d+(\.\d+)?%$`. | | `crop.right` | string | Percent of the frame width to remove from the right. default `"0%"`, pattern `^-?\d+(\.\d+)?%$`. | @@ -90,7 +138,7 @@ Every clip takes these, whatever its asset. | `crop.left` | string | Percent of the frame width to remove from the left. default `"0%"`, pattern `^-?\d+(\.\d+)?%$`. | | `crop.radius` | number | Rounds the corners of what is kept, 0 to 1. 1 rounds the shorter side fully, a pill. The corners ride the transform, so a scaled-down clip becomes a rounded picture-in-picture. The kept part fills the frame, so radius 1 is only a circle on a square frame. min 0, max 1. | | `speed` | number | Playback rate: 0.5 = slow motion, 2 = timelapse. Pitch shifts with speed. Not allowed on composition clips. Avoid slow motion on the OUTGOING side of a transition (it can run out of frames). over 0. | -| `effects` | object[] | What is done to the clip's picture or sound: one ordered list, each entry an object with a "type". Picture effects run in the order listed, and so do sound effects. Any entry takes "start" and "length" (seconds, or a percent of the clip) to run for part of the clip, and a number parameter marked animatable takes keyframes. Effects: chromaKey(color, similarity): Remove a solid background colour (green screen); color(brightness, contrast, saturation, gamma, hue, temperature): Grade the picture by hand; look(name): A one-word look: greyscale, boost (vivid social pop), muted (desaturated, cinematic), lighten and darken (exposure), contrast, negative, sepia (brown archive tone), oldFilm (sepia with grain, dust, scratches, projector flicker and a vignette); blur(radius): Soften the picture; sharpen(amount): Crisp up soft footage; denoise(amount): Clean the grain a small sensor adds in low light; glow(amount): Make the bright parts of the picture bloom; grain(amount): A layer of film grain, so digital footage feels less clinical; lut(src): Grade the picture with a 3D LUT, the .cube file a colourist or a grading app exports; vignette(amount, radius): Darken the corners to pull the eye to the middle of the frame; shake(amount, speed): Handheld camera energy on a steady shot; glitch(amount): Digital break-up: blocks of the picture jump sideways and the colours tear; pixelate(size): Large square pixels over the whole picture; posterize(levels): Flatten the picture into a few steps of each colour, like a screen print; rgbShift(distance): Pull the red and blue channels apart sideways, for a chromatic, analogue-video edge; scanLines(): The dark horizontal lines of a CRT monitor; sketch(amount): Turn the picture into a pencil drawing; trails(duration): Blend the last moments of the clip together, so moving lights and people leave streaks; blurRegion(x, y, width, height, shape, radius): Blur one part of the picture and leave the rest sharp: a face, a number plate, a logo, a screen; mask(src, channel): Show the clip only where an image says: white or opaque keeps it, black or clear cuts it away, grey fades it; outline(color, width): A stroke round the edge of a layer that has transparency: a keyed subject, a picture in picture, a logo; dropShadow(color, opacity, blur, offsetX, offsetY): A soft shadow under a layer that has transparency, so a logo, a cut-out or a picture in picture lifts off what is behind it; visualizer(kind, color, x, y, width, height): Draw the sound over the picture: the audiogram look; eq(bass, mid, treble): Three-band tone control; compressor(threshold, ratio, makeup): Even out a voice, so quiet words and loud words sit at one level; limiter(gain, ceiling): Stop peaks from clipping; gate(threshold): Close the microphone between phrases, so room noise drops away in the pauses; highPass(cutoff): Cut everything below a frequency; lowPass(cutoff): Cut everything above a frequency; loudness(target): Bring sound to a standard loudness, riding the level as it goes; pitch(semitones): Raise or lower the pitch without changing the speed; echo(delay, amount): A repeat behind the sound; reverb(amount): Place a dry voice in a hall; voiceDenoise(amount): Remove steady background noise from under speech: a river, a fan, a street; engine(service, params): any other engine filter by the engine's own names, strictly typed ones only. | +| `effects` | object[] | What is done to the clip's picture or sound: one ordered list, each entry an object with a "type". Picture effects run in the order listed, and so do sound effects. Any entry takes "start" and "length" (seconds, or a percent of the clip) to run for part of the clip, and a number parameter marked animatable takes keyframes. Effects: chromaKey(color, similarity): Remove a solid background colour (green screen); color(brightness, contrast, saturation, gamma, hue, temperature): Grade the picture by hand; look(name): A one-word look: greyscale, boost (vivid social pop), muted (desaturated, cinematic), lighten and darken (exposure), contrast, negative, sepia (brown archive tone), oldFilm (sepia with grain, dust, scratches, projector flicker and a vignette); blur(radius): Soften the picture; sharpen(amount): Crisp up soft footage; denoise(amount): Clean the grain a small sensor adds in low light; glow(amount): Make the bright parts of the picture bloom; grain(amount): A layer of film grain, so digital footage feels less clinical; lut(src): Grade the picture with a 3D LUT, the .cube file a colourist or a grading app exports; vignette(amount, radius): Darken the corners to pull the eye to the middle of the frame; shake(amount, speed): Handheld camera energy on a steady shot; glitch(amount): Digital break-up: blocks of the picture jump sideways and the colours tear; pixelate(size): Large square pixels over the whole picture; posterize(levels): Flatten the picture into a few steps of each colour, like a screen print; rgbShift(distance): Pull the red and blue channels apart sideways, for a chromatic, analogue-video edge; scanLines(): The dark horizontal lines of a CRT monitor; sketch(amount): Turn the picture into a pencil drawing; trails(duration): Blend the last moments of the clip together, so moving lights and people leave streaks; blurRegion(x, y, width, height, shape, radius): Blur one part of the picture and leave the rest sharp: a face, a number plate, a logo, a screen; mask(src, channel): Show the clip only where an image says: white or opaque keeps it, black or clear cuts it away, grey fades it; outline(color, width): A stroke round the edge of a layer that has transparency: a keyed subject, a picture in picture, a logo; dropShadow(color, opacity, blur, offsetX, offsetY): A soft shadow under a layer that has transparency, so a logo, a cut-out or a picture in picture lifts off what is behind it; visualizer(kind, color, x, y, width, height): Draw the sound over the picture: the audiogram look; eq(bass, mid, treble): Three-band tone control; compressor(threshold, ratio, makeup): Even out a voice, so quiet words and loud words sit at one level; limiter(gain, ceiling): Stop peaks from clipping; gate(threshold): Close the microphone between phrases, so room noise drops away in the pauses; highPass(cutoff): Cut everything below a frequency; lowPass(cutoff): Cut everything above a frequency; loudness(target): Bring sound to a standard loudness, riding the level as it goes; pitch(semitones): Raise or lower the pitch without changing the speed; echo(delay, amount): A repeat behind the sound; reverb(amount): Place a dry voice in a hall; voiceDenoise(amount): Remove steady background noise from under speech: a river, a fan, a street; engine(filter, params): any other filter by name, strictly typed ones only. | | `blendMode` | `"normal"` \| `"multiply"` \| `"screen"` \| `"overlay"` \| `"darken"` \| `"lighten"` \| `"add"` \| `"difference"` | How this clip mixes with the layer below: screen for light leaks/glows, multiply for darkening textures, overlay for contrast looks, add/lighten/darken/difference for effects. Meaningful on overlay tracks. | | `pan` | number | Stereo balance: -1 full left, 0 centered, 1 full right. min -1, max 1. | | `flip` | object | Mirror the clip horizontally and/or vertically. | diff --git a/jobs/compose/reference/timer.mdx b/jobs/compose/reference/timer.mdx index 8e21bb1..baa236b 100644 --- a/jobs/compose/reference/timer.mdx +++ b/jobs/compose/reference/timer.mdx @@ -15,16 +15,28 @@ Set `asset.type` to `"timer"`. These are its own fields, on top of the [clip fie | --- | --- | --- | | `direction` | `"up"` \| `"down"` | up counts from zero, down counts to zero from the clip's length. default `"up"`. | | `format` | `"SS"` \| `"SS.S"` \| `"SS.SS"` \| `"MM:SS"` \| `"MM:SS.SS"` \| `"HH:MM:SS"` | What the clock shows. SS.SS is a race clock, MM:SS a countdown to a launch, HH:MM:SS a stream timer. default `"MM:SS"`. | -| `style` * | object | | +| `style` | object | default `{}`. | | `style.font` | string | Font family, or a CSS fallback stack. Any Google Fonts family works by name. Bundled and instant: Inter, Bebas Neue, Anton, Montserrat, Poppins, Roboto, Oswald. Families declared in the `fonts` job input win over both. A stack falls through in CSS order, so "Display, Inter, sans-serif" tries each in turn. default `"Inter"`. | -| `style.size` * | number | Font size in pixels of the output frame. ~64-96 for titles at 1080p, ~36-48 for lower thirds. over 0. | +| `style.size` | number | Font size in pixels of the output frame. ~64-96 for titles at 1080p, ~36-48 for lower thirds. Defaults to 64. default `64`, over 0. | | `style.weight` | integer | Font weight 100..1000 (400 regular, 700 bold, 800+ display). Honoured exactly on variable fonts; snapped to the nearest available weight otherwise. default `400`, min 100, max 1000. | | `style.italic` | boolean | Render the italic cut of the family, when it has one. default `false`. | | `style.color` | string | Text fill color, hex like "#FFFFFF". default `"#FFFFFF"`, pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | | `style.stroke` | object | Outline around the glyphs. A 2-4px black stroke keeps white text readable on bright footage. | +| `style.stroke.color` * | string | Outline color, hex like "#000000". pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | +| `style.stroke.width` * | number | Outline width in pixels. 2 to 4 keeps text readable over busy footage. min 0. | | `style.background` | string | Solid box color behind the text, hex. Omit for no box. pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | | `style.align` | `"left"` \| `"center"` \| `"right"` | How the lines line up, and which side of the text sits on the clip's transform.position x: left starts there, center is centred on it, right ends there. Place the text with the clip's transform, not here. default `"center"`. | -| `style.animate` | object | Entrance animation. Omit for text that is present from its first frame. | +| `style.animate` | object | How the text enters, and optionally leaves: \{"type":"slideUp","by":"word"\} brings a title in word by word. Omit for text that is present from its first frame to its last. | +| `style.animate.type` * | `"fade"` \| `"slideUp"` \| `"slideDown"` \| `"slideLeft"` \| `"slideRight"` \| `"blur"` \| `"scale"` | How the text arrives: fade in place, slide in from the named direction while fading, blur comes into focus, scale grows from small. | +| `style.animate.duration` | number | Seconds the whole entrance takes. With `by`, the last unit lands at this time. 0.4 to 0.8 reads polished for one piece of text, 0.8 to 1.5 for a split title. default `0.5`, over 0. | +| `style.animate.by` | `"letter"` \| `"word"` \| `"line"` | Animate the text one letter, word or line at a time: kinetic titles and lower thirds. Omit it and the text moves as one piece. | +| `style.animate.stagger` | number | Seconds between one unit starting and the next. Omit it and the units spread evenly over the first 40% of `duration`. min 0. | +| `style.animate.order` | `"start"` \| `"end"` \| `"center"` \| `"edges"` \| `"random"` | Which unit starts first. center and edges start units at the same distance together. random is seeded by the clip, so a re-render is identical. default `"start"`. | +| `style.animate.out` | `"fade"` \| `"slideUp"` \| `"slideDown"` \| `"slideLeft"` \| `"slideRight"` \| `"blur"` \| `"scale"` | How the text leaves over the clip's last `duration` seconds, with the same split and order. Omit it to hold until the clip ends. | | `style.shadow` | object | Drop shadow behind the text, adds depth and readability. | +| `style.shadow.color` | string | Shadow color, hex. default `"#000000"`, pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | +| `style.shadow.opacity` | number | Shadow opacity 0..1. default `0.6`, min 0, max 1. | +| `style.shadow.offsetX` | number | Horizontal shadow offset in pixels. default `4`. | +| `style.shadow.offsetY` | number | Vertical shadow offset in pixels. default `4`. | Fields marked `*` are required. Every table on this page is generated from the schema the API validates with, so it cannot drift from what a render accepts. From cbd13a7f99454e934a9e16e2ce53c8de3c8c9a74 Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Tue, 20 Oct 2026 13:17:59 +0300 Subject: [PATCH 08/17] docs(compose): revamp the compose docs for the new engine Every engine capability is now explained in prose. New Media clips page (video, image, audio, Lottie, trim, speed, opacity, blend modes) and Subtitles and timers page. Output becomes a full reference (formats, all size presets, quality, codecs, transparency, range), Framing gets JSON per section and fit, Audio covers ducking, pan and the mix, Effects explains every picture effect, Transitions adds direction and custom wipes, and Pricing explains the validate response. Fixes 18 errors found against the schema (mosaic, visualizer kinds, animated graphics, volume path, fade colour, broken anchors). Plain-English pass on every page, links written as /docs/... so they resolve without JavaScript, and the overview opens with the illustrated story and its request. --- docs.json | 6 +- jobs/compose/audio.mdx | 100 ++++++-- jobs/compose/captions.mdx | 84 +++++++ jobs/compose/craft.mdx | 31 ++- jobs/compose/effects.mdx | 63 +++-- jobs/compose/fonts.mdx | 58 ++--- jobs/compose/framing.mdx | 86 +++++-- jobs/compose/index.mdx | 447 ++++++++++++++++++++++++++++++----- jobs/compose/media.mdx | 126 ++++++++++ jobs/compose/motion.mdx | 52 +++- jobs/compose/output.mdx | 116 +++++---- jobs/compose/pricing.mdx | 59 +++-- jobs/compose/shapes.mdx | 180 +++++++------- jobs/compose/text.mdx | 95 ++++---- jobs/compose/timeline.mdx | 86 ++++--- jobs/compose/transitions.mdx | 41 +++- 16 files changed, 1203 insertions(+), 427 deletions(-) create mode 100644 jobs/compose/captions.mdx create mode 100644 jobs/compose/media.mdx diff --git a/docs.json b/docs.json index d8e02f8..d524378 100644 --- a/docs.json +++ b/docs.json @@ -64,12 +64,14 @@ "pages": [ "jobs/compose/index", "jobs/compose/timeline", + "jobs/compose/media", "jobs/compose/framing", - "jobs/compose/transitions", - "jobs/compose/motion", "jobs/compose/text", + "jobs/compose/captions", "jobs/compose/fonts", "jobs/compose/shapes", + "jobs/compose/motion", + "jobs/compose/transitions", "jobs/compose/effects", "jobs/compose/audio", "jobs/compose/output", diff --git a/jobs/compose/audio.mdx b/jobs/compose/audio.mdx index c6926e8..6b97a31 100644 --- a/jobs/compose/audio.mdx +++ b/jobs/compose/audio.mdx @@ -1,45 +1,115 @@ --- -title: "Audio levels and effects" -sidebarTitle: "Audio" -description: "Set levels and fades, clean up a voice recording, and draw sound onto the picture as a waveform or bars." +title: "Audio levels, mixing and effects" +sidebarTitle: "Audio and mixing" +description: "Set each clip's level, fade and duck music under a voice, pan and mix tracks, clean up a recording, and draw the sound onto the picture." icon: "waveform-lines" -keywords: ["video audio api", "audio normalize api", "audiogram api"] +keywords: ["video audio api", "duck music under voice api", "audio normalize api", "audiogram api", "mix audio json"] canonical: "https://rendobar.com/docs/jobs/compose/audio" --- -Sound is a clip like any other, and a `volume` on any clip that has audio sets its level. Fades sit next to it. Everything past that is an effect on the `sound` medium, and the same effect list works on a clip or on a whole track. +Sound comes from `audio` clips and from the sound in `video` clips. All tracks with sound play at once in one mix. Set each clip's level, then shape the sound with effects on a clip, a track or the whole mix. ```json { - "asset": { "type": "audio", "src": "https://example.com/music.mp3", "volume": 0.35, "fadeIn": 1.5, "fadeOut": 2 }, + "asset": { "type": "audio", "src": "https://example.com/music.mp3", "volume": 0.35, "fadeIn": 1.5, "fadeOut": 3 }, "length": "end" } ``` -## Levels that work +`length: "end"` runs the music to the end of the video, however long it is. +## Levels -`volume` is linear gain. With speech at 1.0: music under speech 0.10 to 0.12, music alone 0.35 to 0.5, sound effects 0.25 to 0.32. Fade music in over 1 to 2 seconds and out over 3 to 5, ending on the last frame. To duck music under speech, keep one music clip and put keyframes on its `volume`: 0.4 before the first word, 0.11 under the speech, back to 0.4 after it. `fadeIn` and `fadeOut` work alongside those keyframes. +`volume` is linear gain on a `video` or `audio` asset. `1` keeps the source level and `0` mutes it. Under speech at `1`, set music to `0.2` to `0.4`. On its own, set it to `0.35` to `0.5`. -## Cleaning up a recording +`fadeIn` and `fadeOut` fade an audio clip in from silence and out to silence, in seconds. Fade music in over 1 to 2 seconds and out over 3 to 5, ending on the last frame. -`loudness` brings a quiet recording to a streaming target. `voiceDenoise` removes steady background noise from under speech. `gate` closes the microphone between phrases, `highPass` cuts rumble below the voice, `eq` shapes it, and `compressor` evens out loud and quiet words. +## Ducking music under a voice -## Shaping and colour +Use one music clip and keyframe its `volume`: up before the first word, down under the speech, up again after. `fadeIn` and `fadeOut` still apply on top. -`reverb`, `echo`, `pitch`, `lowPass` and `limiter`. A limiter makes music louder without letting peaks clip. +```json +{ + "asset": { + "type": "audio", + "src": "https://example.com/music.mp3", + "volume": [ + { "time": 0, "value": 0.4 }, + { "time": 2.5, "value": 0.2, "easing": "easeInOutSine" }, + { "time": 9, "value": 0.2 }, + { "time": 9.6, "value": 0.4 } + ], + "fadeOut": 3 + }, + "length": "end" +} +``` + +## Panning + +`pan` places a clip's sound from `-1` (full left) through `0` (centred) to `1` (full right). It works on any clip with sound. + +```json +{ "asset": { "type": "audio", "src": "https://example.com/door-knock.mp3" }, "start": 4, "pan": -0.6 } +``` + +## The mix + +Music, a voice-over and the footage's own sound need no separate mixing step. An `effects` list works on a clip, a track or the timeline: + +- On a clip, it changes that clip's sound. +- On a track (`track.effects`), it changes everything on the track, such as every line of a voice-over at once. +- On the timeline (`timeline.effects`), it changes the final mix. Put a `limiter` or a `loudness` target here. + +```json +"timeline": { + "tracks": [ + { "clips": [{ "asset": { "type": "video", "src": "https://example.com/interview.mp4" } }] }, + { "clips": [{ "asset": { "type": "audio", "src": "https://example.com/music.mp3", "volume": 0.25 }, "length": "end" }] } + ], + "effects": [{ "type": "loudness" }, { "type": "limiter" }] +} +``` + +## Sound effects + +Each effect is an entry with a `type` and only the settings you change. + +| Effect | What it does | +| --- | --- | +| `loudness` | Sets sound to a standard loudness. Use -16 LUFS for online video and podcasts, -23 for broadcast. | +| `voiceDenoise` | Removes steady background noise from under speech: a river, a fan, a street. Not for music. | +| `gate` | Mutes the sound between phrases, so room noise drops out in the pauses. | +| `highPass` | Cuts everything below a frequency. Wind, traffic and handling noise sit below the voice. | +| `lowPass` | Cuts everything above a frequency: music from the next room, or a voice on the phone. | +| `eq` | Three-band tone control: more bass and treble on music, or less boom on a voice. | +| `compressor` | Evens out a voice so quiet and loud words play at the same level. | +| `limiter` | Stops peaks from clipping, with optional gain to make a track louder. | +| `pitch` | Raises or lowers the pitch without changing the speed. | +| `echo` | Adds a repeat behind the sound. | +| `reverb` | Makes a dry voice sound like it is in a hall. | + +A typical voice chain is `highPass`, then `voiceDenoise`, then `compressor`, then `loudness`. + +```json +"effects": [{ "type": "highPass", "cutoff": 80 }, { "type": "voiceDenoise" }, { "type": "compressor" }, { "type": "loudness" }] +``` ## Sound drawn on the picture -`visualizer` draws the sound onto the frame for an audiogram or a podcast clip, as a waveform, bars or a level meter. +`visualizer` draws the sound over the picture as a `waveform` or a `spectrum`, for an audiogram or a podcast clip. On the timeline, its usual place, it draws the final mix. On a track or a clip, it draws only that sound. + +```json +"effects": [{ "type": "visualizer", "kind": "waveform", "color": "#34D399", "y": "80%", "width": "76%", "height": "20%" }] +``` -Every sound effect and its parameters are in the [effect reference](/docs/jobs/compose/reference/effects). +The [effect reference](/docs/jobs/compose/reference/effects) lists every sound effect and its settings. The [Compose features page](https://rendobar.com/compose/features/#sound) plays each one before and after, with sound. diff --git a/jobs/compose/captions.mdx b/jobs/compose/captions.mdx new file mode 100644 index 0000000..a5e42c1 --- /dev/null +++ b/jobs/compose/captions.mdx @@ -0,0 +1,84 @@ +--- +title: "Subtitles and timers" +sidebarTitle: "Subtitles and timers" +description: "Add every subtitle of a video as one clip, written in the timeline or read from an .srt or .vtt file, and put a countdown or a stopwatch over a clip." +icon: "closed-captioning" +keywords: ["add subtitles to video api", "srt to video api", "countdown timer video api", "burn captions json"] +canonical: "https://rendobar.com/docs/jobs/compose/captions" +--- + +A `subtitles` clip shows every caption of a video, each at its time. A `timer` counts over its own length. Both take the same `style` as a [text clip](/docs/jobs/compose/text): font, size, colour, stroke, shadow and background box. + +## Subtitles + +Each cue has a `start` and an `end`, in seconds from the clip's start, and its `text`. + +```json +{ + "asset": { + "type": "subtitles", + "cues": [ + { "start": 0, "end": 1.8, "text": "Subtitles live in the timeline." }, + { "start": 2, "end": 4, "text": "One clip, every line." } + ], + "style": { "font": "Inter", "size": 44, "color": "#FFFFFF", "background": "#000000B0" } + }, + "transform": { "position": { "x": "50%", "y": "86%" } } +} +``` + + + - The live API is the `ffmpeg` job type, any whitelisted FFmpeg command against URLs you provide. That covers transcoding, trimming, filtering, overlays, audio extraction, frame extraction, and anything else FFmpeg does from the command line. More dedicated job types are on the roadmap. + Nine job types are live: `ffmpeg` and `ffprobe` for any FFmpeg command and file inspection, `compose` to render a video from a JSON timeline, `compress.target` to hit a file size, `caption.burn` and `captions.animate` for subtitles, and `image.generate`, `image.edit` and `image.upscale` for images. diff --git a/support/limits.mdx b/support/limits.mdx index 4c284c0..37be932 100644 --- a/support/limits.mdx +++ b/support/limits.mdx @@ -1,7 +1,7 @@ --- title: "Plan limits and quotas" sidebarTitle: "Limits" -description: "Compare Free and Pro plan limits across rate limits (120 vs 600/min), file size caps (500 MB vs 10 GB), retention, concurrency, storage." +description: "Compare Free, Pro and Studio limits: rate limits, file size caps (500 MB vs 10 GB), output retention, concurrency, storage and compose frames." icon: "gauge" keywords: ["rendobar limits", "api rate limits", "plan limits", "file size limit", "concurrent jobs"] canonical: "https://rendobar.com/docs/support/limits" @@ -15,7 +15,7 @@ canonical: "https://rendobar.com/docs/support/limits" "@type": "TechArticle", "@id": "https://rendobar.com/docs/support/limits#article", "headline": "Plan limits and quotas", - "description": "Free and Pro plan limits: rate limits, file size caps, output retention, concurrent jobs, and storage quotas.", + "description": "Free, Pro and Studio plan limits: rate limits, file size caps, output retention, concurrent jobs, storage quotas and compose frames.", "datePublished": "2026-03-20", "author": { "@type": "Organization", "@id": "https://rendobar.com/#organization" }, "publisher": { "@type": "Organization", "@id": "https://rendobar.com/#organization" } @@ -37,7 +37,7 @@ Every limit Rendobar enforces, by plan. Pulled from `PLAN_LIMITS` in the API. Fo | API requests / min | 120 | 600 | 1200 | | Max input file | 500 MB | 10 GB | 10 GB | | Max job timeout | 1 hour | 9 hours | 9 hours | -| Output retention | 7 days | 30 days | 30 days | +| Output retention | 7 days | 30 days | 90 days | | Total object storage | 5 GB | 250 GB | 250 GB | | Compose frame | Up to 1080p at 30 fps | Up to 1440p at 30 fps | Up to 4K at 60 fps | | Compose with animated graphics | Up to 1080p at 30 fps | Up to 1080p at 30 fps | Up to 1080p at 30 fps | @@ -48,7 +48,7 @@ Every limit Rendobar enforces, by plan. Pulled from `PLAN_LIMITS` in the API. Fo ## How long do output URLs last? - **Signed download URL** (`output.file.url`) is valid for **1 hour**. Re-fetch via `GET /jobs/{id}` for a fresh URL. -- **Output retention**: 7 days (Free) or 30 days (Pro and Studio). After that, the file is deleted; the job record stays but the file URLs return 404. +- **Output retention**: 7 days (Free), 30 days (Pro) or 90 days (Studio). After that, the file is deleted; the job record stays but the file URLs return 404. - **Storage quota** caps total bytes across all your outputs. Delete old outputs or upgrade if you hit it. - **No separate per-output size limit**. Bounded by input cap and storage quota. From 086dc8bc2c63139b058640fd9670eff47d9ae0da Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Tue, 20 Oct 2026 14:29:36 +0300 Subject: [PATCH 11/17] docs(compose): regenerate the field reference with the animated-graphics cap The transform and text animation fields now say they render up to 1080p at 30 fps on self-serve plans, from the schema. --- jobs/compose/reference/subtitles.mdx | 2 +- jobs/compose/reference/text.mdx | 2 +- jobs/compose/reference/timeline.mdx | 6 +++--- jobs/compose/reference/timer.mdx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/jobs/compose/reference/subtitles.mdx b/jobs/compose/reference/subtitles.mdx index 659b08b..8a356da 100644 --- a/jobs/compose/reference/subtitles.mdx +++ b/jobs/compose/reference/subtitles.mdx @@ -30,7 +30,7 @@ Set `asset.type` to `"subtitles"`. These are its own fields, on top of the [clip | `style.stroke.width` * | number | Outline width in pixels. 2 to 4 keeps text readable over busy footage. min 0. | | `style.background` | string | Solid box color behind the text, hex. Omit for no box. pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | | `style.align` | `"left"` \| `"center"` \| `"right"` | How the lines line up, and which side of the text sits on the clip's transform.position x: left starts there, center is centred on it, right ends there. Place the text with the clip's transform, not here. default `"center"`. | -| `style.animate` | object | How the text enters, and optionally leaves: \{"type":"slideUp","by":"word"\} brings a title in word by word. Omit for text that is present from its first frame to its last. | +| `style.animate` | object | How the text enters, and optionally leaves: \{"type":"slideUp","by":"word"\} brings a title in word by word. A split (`by`), a blur or scale entrance and an exit (`out`) render up to 1080p at 30 fps on self-serve plans. Omit for text that is present from its first frame to its last. | | `style.animate.type` * | `"fade"` \| `"slideUp"` \| `"slideDown"` \| `"slideLeft"` \| `"slideRight"` \| `"blur"` \| `"scale"` | How the text arrives: fade in place, slide in from the named direction while fading, blur comes into focus, scale grows from small. | | `style.animate.duration` | number | Seconds the whole entrance takes. With `by`, the last unit lands at this time. 0.4 to 0.8 reads polished for one piece of text, 0.8 to 1.5 for a split title. default `0.5`, over 0. | | `style.animate.by` | `"letter"` \| `"word"` \| `"line"` | Animate the text one letter, word or line at a time: kinetic titles and lower thirds. Omit it and the text moves as one piece. | diff --git a/jobs/compose/reference/text.mdx b/jobs/compose/reference/text.mdx index cf3a59f..fc726d5 100644 --- a/jobs/compose/reference/text.mdx +++ b/jobs/compose/reference/text.mdx @@ -25,7 +25,7 @@ Set `asset.type` to `"text"`. These are its own fields, on top of the [clip fiel | `style.stroke.width` * | number | Outline width in pixels. 2 to 4 keeps text readable over busy footage. min 0. | | `style.background` | string | Solid box color behind the text, hex. Omit for no box. pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | | `style.align` | `"left"` \| `"center"` \| `"right"` | How the lines line up, and which side of the text sits on the clip's transform.position x: left starts there, center is centred on it, right ends there. Place the text with the clip's transform, not here. default `"center"`. | -| `style.animate` | object | How the text enters, and optionally leaves: \{"type":"slideUp","by":"word"\} brings a title in word by word. Omit for text that is present from its first frame to its last. | +| `style.animate` | object | How the text enters, and optionally leaves: \{"type":"slideUp","by":"word"\} brings a title in word by word. A split (`by`), a blur or scale entrance and an exit (`out`) render up to 1080p at 30 fps on self-serve plans. Omit for text that is present from its first frame to its last. | | `style.animate.type` * | `"fade"` \| `"slideUp"` \| `"slideDown"` \| `"slideLeft"` \| `"slideRight"` \| `"blur"` \| `"scale"` | How the text arrives: fade in place, slide in from the named direction while fading, blur comes into focus, scale grows from small. | | `style.animate.duration` | number | Seconds the whole entrance takes. With `by`, the last unit lands at this time. 0.4 to 0.8 reads polished for one piece of text, 0.8 to 1.5 for a split title. default `0.5`, over 0. | | `style.animate.by` | `"letter"` \| `"word"` \| `"line"` | Animate the text one letter, word or line at a time: kinetic titles and lower thirds. Omit it and the text moves as one piece. | diff --git a/jobs/compose/reference/timeline.mdx b/jobs/compose/reference/timeline.mdx index 6c60598..daea1e1 100644 --- a/jobs/compose/reference/timeline.mdx +++ b/jobs/compose/reference/timeline.mdx @@ -58,7 +58,7 @@ The shape of a compose request, from the outside in. Each asset kind adds its ow | `scenes.clips.start` | number | Seconds into the scene. Omit to start at the scene's beginning. A clip is measured from its own start, so one placed 2s into a 5s scene has 3s of room. min 0. | | `scenes.clips.length` | number \| `"end"` | Seconds. Omit it and the clip runs as long as its content does, never past the scene. Use "end" to span the scene whatever the content is, which is also what omitting means for text and images. | | `scenes.clips.opacity` | number \| object[] | Clip opacity: a constant 0..1, or keyframes [\{time, value, easing\}] for animated fades. Fade a clip in with [\{time:0,value:0\},\{time:1,value:1\}]. default `1`. | -| `scenes.clips.transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns), or turn it in 3D with rotateX and rotateY. Omit for full-frame. | +| `scenes.clips.transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns), or turn it in 3D with rotateX and rotateY (rendered up to 1080p at 30 fps on self-serve plans). Omit for full-frame. | | `scenes.clips.crop` | object | Trim edges off the frame by percent; the kept region scales back up to fill. Use to reframe landscape footage for a vertical canvas, and `radius` to round its corners. | | `scenes.clips.speed` | number | Playback rate: 0.5 = slow motion, 2 = timelapse. Pitch shifts with speed. Not allowed on composition clips. Avoid slow motion on the OUTGOING side of a transition (it can run out of frames). over 0. | | `scenes.clips.effects` | object[] | What is done to the clip's picture or sound: one ordered list, each entry an object with a "type". Picture effects run in the order listed, and so do sound effects. Any entry takes "start" and "length" (seconds, or a percent of the clip) to run for part of the clip, and a number parameter marked animatable takes keyframes. Effects: chromaKey(color, similarity): Remove a solid background colour (green screen); color(brightness, contrast, saturation, gamma, hue, temperature): Grade the picture by hand; look(name): A one-word look: greyscale, boost (vivid social pop), muted (desaturated, cinematic), lighten and darken (exposure), contrast, negative, sepia (brown archive tone), oldFilm (sepia with grain, dust, scratches, projector flicker and a vignette); blur(radius): Soften the picture; sharpen(amount): Crisp up soft footage; denoise(amount): Clean the grain a small sensor adds in low light; glow(amount): Make the bright parts of the picture bloom; grain(amount): A layer of film grain, so digital footage feels less clinical; lut(src): Grade the picture with a 3D LUT, the .cube file a colourist or a grading app exports; vignette(amount, radius): Darken the corners to pull the eye to the middle of the frame; shake(amount, speed): Handheld camera energy on a steady shot; glitch(amount): Digital break-up: blocks of the picture jump sideways and the colours tear; pixelate(size): Large square pixels over the whole picture; posterize(levels): Flatten the picture into a few steps of each colour, like a screen print; rgbShift(distance): Pull the red and blue channels apart sideways, for a chromatic, analogue-video edge; scanLines(): The dark horizontal lines of a CRT monitor; sketch(amount): Turn the picture into a pencil drawing; trails(duration): Blend the last moments of the clip together, so moving lights and people leave streaks; blurRegion(x, y, width, height, shape, radius): Blur one part of the picture and leave the rest sharp: a face, a number plate, a logo, a screen; mask(src, channel): Show the clip only where an image says: white or opaque keeps it, black or clear cuts it away, grey fades it; outline(color, width): A stroke round the edge of a layer that has transparency: a keyed subject, a picture in picture, a logo; dropShadow(color, opacity, blur, offsetX, offsetY): A soft shadow under a layer that has transparency, so a logo, a cut-out or a picture in picture lifts off what is behind it; visualizer(kind, color, x, y, width, height): Draw the sound over the picture: the audiogram look; eq(bass, mid, treble): Three-band tone control; compressor(threshold, ratio, makeup): Even out a voice, so quiet words and loud words sit at one level; limiter(gain, ceiling): Stop peaks from clipping; gate(threshold): Close the microphone between phrases, so room noise drops away in the pauses; highPass(cutoff): Cut everything below a frequency; lowPass(cutoff): Cut everything above a frequency; loudness(target): Bring sound to a standard loudness, riding the level as it goes; pitch(semitones): Raise or lower the pitch without changing the speed; echo(delay, amount): A repeat behind the sound; reverb(amount): Place a dry voice in a hall; voiceDenoise(amount): Remove steady background noise from under speech: a river, a fan, a street; engine(filter, params): any other filter by name, strictly typed ones only. | @@ -70,7 +70,7 @@ The shape of a compose request, from the outside in. Each asset kind adds its ow | `overlays.start` | number | When the clip begins, in seconds from the start of the video. Omit it and the clip starts after everything already placed on its track, pulled back by any transition sitting immediately before it. The first clip on a track starts at 0. min 0. | | `overlays.length` | number \| `"end"` | How long the clip plays. Omit it and the clip runs as long as its CONTENT does: the trim span, the source duration divided by speed, or a composition's own end. Use "end" and it runs to the end of the video instead, whatever the source says, which is what a music bed wants. Text and images have no duration of their own, so omitting is the same as "end" for them and a title with no length stays up. A clip that runs to the end must be the last on its track. A video, audio or animation clip that runs past the end of its file holds the last frame, silent, until the clip ends, and the job says which clip in the render step's `clipOverruns`. | | `overlays.opacity` | number \| object[] | Clip opacity: a constant 0..1, or keyframes [\{time, value, easing\}] for animated fades. Fade a clip in with [\{time:0,value:0\},\{time:1,value:1\}]. default `1`. | -| `overlays.transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns), or turn it in 3D with rotateX and rotateY. Omit for full-frame. | +| `overlays.transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns), or turn it in 3D with rotateX and rotateY (rendered up to 1080p at 30 fps on self-serve plans). Omit for full-frame. | | `overlays.transform.position` | object \| object[] | Where to place the clip, in pixels like \{x:1600,y:900\} or percent like \{x:"75%",y:"75%"\}, or keyframes of them to move it. Omit it and the clip is centred, so a scale of 0.5 alone is a centred picture-in-picture. | | `overlays.transform.scale` | number \| object[] | Size relative to the frame: 1 = full frame, 0.3 = a corner PiP, above 1 zooms in (crops edges). Keyframes animate it: [\{time:"0%",value:1,easing:"easeInOutSine"\},\{time:"100%",value:1.15\}] is a Ken Burns push-in. A keyframe's easing shapes the move into the next one. default `1`. | | `overlays.transform.rotate` | number \| object[] | Rotation in degrees, clockwise. Keyframes spin it. default `0`. | @@ -112,7 +112,7 @@ Every clip takes these, whatever its asset. | `start` | number | When the clip begins, in seconds from the start of the video. Omit it and the clip starts after everything already placed on its track, pulled back by any transition sitting immediately before it. The first clip on a track starts at 0. min 0. | | `length` | number \| `"end"` | How long the clip plays. Omit it and the clip runs as long as its CONTENT does: the trim span, the source duration divided by speed, or a composition's own end. Use "end" and it runs to the end of the video instead, whatever the source says, which is what a music bed wants. Text and images have no duration of their own, so omitting is the same as "end" for them and a title with no length stays up. A clip that runs to the end must be the last on its track. A video, audio or animation clip that runs past the end of its file holds the last frame, silent, until the clip ends, and the job says which clip in the render step's `clipOverruns`. | | `opacity` | number \| object[] | Clip opacity: a constant 0..1, or keyframes [\{time, value, easing\}] for animated fades. Fade a clip in with [\{time:0,value:0\},\{time:1,value:1\}]. default `1`. | -| `transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns), or turn it in 3D with rotateX and rotateY. Omit for full-frame. | +| `transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns), or turn it in 3D with rotateX and rotateY (rendered up to 1080p at 30 fps on self-serve plans). Omit for full-frame. | | `transform.position` | object \| object[] | Where to place the clip, in pixels like \{x:1600,y:900\} or percent like \{x:"75%",y:"75%"\}, or keyframes of them to move it. Omit it and the clip is centred, so a scale of 0.5 alone is a centred picture-in-picture. | | `transform.position.x` * | number \| string | Horizontal: pixels from the left edge of the output frame, like 960, or a percent of its width, like "50%". | | `transform.position.y` * | number \| string | Vertical: pixels from the top edge, like 540, or a percent of the frame height, like "80%". | diff --git a/jobs/compose/reference/timer.mdx b/jobs/compose/reference/timer.mdx index baa236b..53922f0 100644 --- a/jobs/compose/reference/timer.mdx +++ b/jobs/compose/reference/timer.mdx @@ -26,7 +26,7 @@ Set `asset.type` to `"timer"`. These are its own fields, on top of the [clip fie | `style.stroke.width` * | number | Outline width in pixels. 2 to 4 keeps text readable over busy footage. min 0. | | `style.background` | string | Solid box color behind the text, hex. Omit for no box. pattern `^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | | `style.align` | `"left"` \| `"center"` \| `"right"` | How the lines line up, and which side of the text sits on the clip's transform.position x: left starts there, center is centred on it, right ends there. Place the text with the clip's transform, not here. default `"center"`. | -| `style.animate` | object | How the text enters, and optionally leaves: \{"type":"slideUp","by":"word"\} brings a title in word by word. Omit for text that is present from its first frame to its last. | +| `style.animate` | object | How the text enters, and optionally leaves: \{"type":"slideUp","by":"word"\} brings a title in word by word. A split (`by`), a blur or scale entrance and an exit (`out`) render up to 1080p at 30 fps on self-serve plans. Omit for text that is present from its first frame to its last. | | `style.animate.type` * | `"fade"` \| `"slideUp"` \| `"slideDown"` \| `"slideLeft"` \| `"slideRight"` \| `"blur"` \| `"scale"` | How the text arrives: fade in place, slide in from the named direction while fading, blur comes into focus, scale grows from small. | | `style.animate.duration` | number | Seconds the whole entrance takes. With `by`, the last unit lands at this time. 0.4 to 0.8 reads polished for one piece of text, 0.8 to 1.5 for a split title. default `0.5`, over 0. | | `style.animate.by` | `"letter"` \| `"word"` \| `"line"` | Animate the text one letter, word or line at a time: kinetic titles and lower thirds. Omit it and the text moves as one piece. | From 67632487d702763577056f2e9050d6bac09b9f64 Mon Sep 17 00:00:00 2001 From: Abdelrahman Essawy Date: Tue, 20 Oct 2026 15:05:13 +0300 Subject: [PATCH 12/17] docs(compose): scope animated graphics to top-level tracks, fix pricing Animated text and 3D turns are refused inside a scene or a composition, so the timeline, text and motion pages now say they work only on top-level tracks and in overlays. The pricing page no longer claims a 4K render with animated graphics costs the same, since those stop at 1080p 30 fps. It names upgradeTo on a plan refusal and points at the job's cost instead of the error codes page. Free replaces pay-as-you-go on the output page, and two float artifacts in the index example are rounded. --- jobs/compose/index.mdx | 4 ++-- jobs/compose/motion.mdx | 2 +- jobs/compose/output.mdx | 2 +- jobs/compose/pricing.mdx | 8 ++++---- jobs/compose/text.mdx | 4 ++-- jobs/compose/timeline.mdx | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/jobs/compose/index.mdx b/jobs/compose/index.mdx index 3242bcc..3b7db9b 100644 --- a/jobs/compose/index.mdx +++ b/jobs/compose/index.mdx @@ -306,7 +306,7 @@ canonical: "https://rendobar.com/docs/jobs/compose" } }, "transform": { "position": { "x": 960, "y": 925 } }, - "start": 4.550000000000001, + "start": 4.55, "length": 2.15 } ] @@ -362,7 +362,7 @@ canonical: "https://rendobar.com/docs/jobs/compose" } }, "transform": { "position": { "x": 960, "y": 925 } }, - "start": 7.8500000000000005, + "start": 7.85, "length": 1.85 } ] diff --git a/jobs/compose/motion.mdx b/jobs/compose/motion.mdx index f1200e6..7041442 100644 --- a/jobs/compose/motion.mdx +++ b/jobs/compose/motion.mdx @@ -65,7 +65,7 @@ The scale keyframes are the only difference between the two videos. Use a turn on a card, a picture-in-picture or a product reveal. On full-frame footage, it shows the frame's edges. -A 3D turn makes the clip an animated graphic. It costs the same per second, and renders up to 1080p at 30 fps on every self-serve plan. See [what each plan renders](/docs/jobs/compose/pricing#what-each-plan-renders). +A 3D turn makes the clip an animated graphic. It costs the same per second, and renders up to 1080p at 30 fps on every self-serve plan. It works only on a top-level track or in `overlays`, not inside a scene or a composition. See [what each plan renders](/docs/jobs/compose/pricing#what-each-plan-renders). ## The 33 easings diff --git a/jobs/compose/output.mdx b/jobs/compose/output.mdx index 8e32294..4e3915f 100644 --- a/jobs/compose/output.mdx +++ b/jobs/compose/output.mdx @@ -51,7 +51,7 @@ For a vertical Reel from landscape footage, set `"fit": "cover"` to fill the fra ## Frame rate -`fps` sets the frame rate, 30 by default. Each plan caps frame size and frame rate together: 1080p at 30 fps on pay-as-you-go, 1440p at 30 fps on Pro, and 4K at 60 fps on Studio. Animated graphics render up to 1080p at 30 fps on every self-serve plan. A video above your plan returns `403 PLAN_LIMIT` at submit, and [validation](/docs/jobs/compose/pricing#check-a-timeline-before-you-render) catches it first. See [what each plan renders](/docs/jobs/compose/pricing#what-each-plan-renders). +`fps` sets the frame rate, 30 by default. Each plan caps frame size and frame rate together: 1080p at 30 fps on Free, 1440p at 30 fps on Pro, and 4K at 60 fps on Studio. Animated graphics render up to 1080p at 30 fps on every self-serve plan. A video above your plan returns `403 PLAN_LIMIT` at submit, and [validation](/docs/jobs/compose/pricing#check-a-timeline-before-you-render) catches it first. See [what each plan renders](/docs/jobs/compose/pricing#what-each-plan-renders). ## Quality diff --git a/jobs/compose/pricing.mdx b/jobs/compose/pricing.mdx index 24d46a4..2552730 100644 --- a/jobs/compose/pricing.mdx +++ b/jobs/compose/pricing.mdx @@ -18,7 +18,7 @@ Each render bills at least 15 seconds, which is 1 cent. You pay from your [credi | 2 min | \$0.08 | | 10 min | \$0.40 | -Frame size and animated graphics do not change the price. A 2-minute render costs the same at 720p or 4K, with or without animated text and 3D turns. +Frame size and animated graphics do not change the price. A 2-minute render costs the same at 720p or 4K. Animated text and 3D turns render up to 1080p at 30 fps, and adding them does not change the price. ## What each plan renders @@ -41,7 +41,7 @@ A `fade` or a slide of a whole title, a Lottie animation and a 2D transform do n Pro costs \$9 a month and includes \$5 of monthly credit. Studio costs \$29 a month and includes \$20 of monthly credit. Their compose discount is a 25% bonus on every credit purchase, so \$25 buys \$31.25 of credit. See [credits and billing](/docs/concepts/credits#plans). -A video above your plan returns `403 PLAN_LIMIT` at submit, before anything renders or is charged. The message names the plan that can render it. Only video outputs (`mp4`, `webm`, `gif`) are checked. A `jpg`, `png` or `mp3` output renders on every plan. +A video above your plan returns `403 PLAN_LIMIT` at submit, before anything renders or is charged. `upgradeTo` in `error.details` names the plan that can render it, or is `null` when only an enterprise plan can. Only video outputs (`mp4`, `webm`, `gif`) are checked. A `jpg`, `png` or `mp3` output renders on every plan. `GET /account/capabilities` returns your plan's limits: `composeMaxPixels` and `composeMaxFps` for a plain edit, and `composeAnimatedMaxPixels` and `composeAnimatedMaxFps` for one with animated graphics. @@ -49,7 +49,7 @@ A video above your plan returns `403 PLAN_LIMIT` at submit, before anything rend You pay nothing for a render that fails on Rendobar's side, or for a job that never started. -If the failure may come from your input, such as the render tool exiting on a bad file, you pay for the compute the job used at 2x. To see which case you hit, check `error.code` on the job against the [error codes](/docs/support/errors). +If the failure may come from your input, such as the render tool exiting on a bad file, you pay for the compute the job used at 2x. `GET /jobs/{id}` returns the job's `cost` once it is billed, so you can see what a failed render was charged. ## Check a timeline before you render @@ -77,7 +77,7 @@ The response contains: | Field | Holds | | --- | --- | | `valid` | Whether the timeline would be accepted | -| `problems` | What is wrong, each with the path of its field. A plan limit appears at `["output"]`, with the plan that can render it. | +| `problems` | What is wrong, each with the path of its field. A plan limit appears at `["output"]`, with `upgradeTo` set to `"pro"` or `"studio"`, or `null` when only an enterprise plan renders it. | | `params` | The timeline as the engine reads it, with every default filled in | | `upgraded` | Anything written for an older API version and upgraded to the current shape. Old timelines keep rendering. | | `warnings` | Settings the render will ignore or adjust, such as a transition longer than a clip, or a font that falls back | diff --git a/jobs/compose/text.mdx b/jobs/compose/text.mdx index 152184e..603dd20 100644 --- a/jobs/compose/text.mdx +++ b/jobs/compose/text.mdx @@ -120,7 +120,7 @@ White text gets lost over bright footage. Three fields fix it, in order of how m -Split one title per scene at most. On body text or captions, a split slows reading. +Split at most one title per shot, on a top-level track or in `overlays`. On body text or captions, a split slows reading. ## Leaving @@ -140,7 +140,7 @@ Split one title per scene at most. On body text or captions, a split slows readi -A split, an exit, or a `blur` or `scale` entrance makes the clip an animated graphic. It costs the same per minute, and renders up to 1080p at 30 fps on every self-serve plan. A `fade` or a slide of the whole title does not count as one. See [what each plan renders](/docs/jobs/compose/pricing#what-each-plan-renders). +A split, an exit, or a `blur` or `scale` entrance makes the clip an animated graphic. It costs the same per minute, and renders up to 1080p at 30 fps on every self-serve plan. It works only on a top-level track or in `overlays`, not inside a scene or a composition. A `fade` or a slide of the whole title does not count as one. See [what each plan renders](/docs/jobs/compose/pricing#what-each-plan-renders). Every text field is in the [text reference](/docs/jobs/compose/reference/text). diff --git a/jobs/compose/timeline.mdx b/jobs/compose/timeline.mdx index 06d648c..4162d3c 100644 --- a/jobs/compose/timeline.mdx +++ b/jobs/compose/timeline.mdx @@ -136,7 +136,7 @@ Tracks give you precise multi-track control. For an edit that plays one segment } ``` -A scene's clips can omit `start` and `length` to fill the whole scene. A scene can omit `duration` to take the length of the video inside it. Clip fields work the same as with tracks. A timeline uses `scenes` or `tracks`, never both. +A scene's clips can omit `start` and `length` to fill the whole scene. A scene can omit `duration` to take the length of the video inside it. Clip fields work the same as with tracks, except that animated text and 3D turns work only on top-level tracks and in `overlays`, not inside a scene or a composition. A timeline uses `scenes` or `tracks`, never both.