draftaday docs draftaday.com Join the beta
On this page

Docs · page 05 of 09

Media and the format offer

Studio makes the visual half of a post in the same draft as the words: images, quote cards, carousel PDFs, video and voiceover. Three things cost nothing and never call a model — a quote card, the carousel PDF build itself, and any edit you save back to the pool from the canvas. The rest call fal.ai and are stopped by hard spend caps.

What each thing costs

What you makeHow it is madeCost
Quote cardffmpeg drawtext, locally$0 (measured ~400 ms)
Carousel PDF (the build)dependency-free PDF builder in lib/media-gen.js$0
Canvas edit saved to the poolyour browser + POST /api/studio/asset-upload$0
Imagefal$0.03 (flux, leonardo) or $0.048 (nano-banana)
Videofal, per second$0.024/s (ltx-video) → $0.224/s (kling-v3-pro)
Voiceoverfal TTS + one mux$0.06–0.12 per 1,000 characters + $0.01

A carousel is only free end to end in the two photo modes. The written-slide modes call a model for the slide text; the PDF build stays $0 either way.

The image row is what a prompt card can spend, because every render sends the card's Tool. fal/flux-schnell at $0.004 is the endpoint's own fallback when a caller sends no model at all — you cannot reach it from the editor.

Every paid render is priced before it runs and refused if it would breach a cap:

  • SGD 1.50 for a single video — hard-coded, and it applies on every path.
  • SGD 2.00 of total fal spend in any rolling 24 hours for anything you render in the editor. Override with MEDIA_OVERNIGHT_CAP_SGD.
  • SGD 10.00 for the same rolling window when Autopilot renders its own media, via AUTOPILOT_MEDIA_CAP_SGD. Same 24-hour window and same ledger, a different ceiling — so an Autopilot run and your own renders draw on the same recorded spend.

GET /api/media/spend returns { spentSgd, capSgd } at any time.

Over the cap, the server answers 402 and the editor says Image render blocked: media spend cap reached for today. The underlying message is verbatim:

cost guard: overnight cap reached: spent SGD 1.87 + est SGD 0.39 > cap SGD 2
cost guard: video est SGD 3.02 exceeds per-post cap SGD 1.5

Good to know — an unknown model is always priced high, never low: $0.10 an image, $0.15 a video-second. The cap is a gate, so it errs toward refusing.

Image prompts

Generation writes one image prompt per concept family you have selected. There are 16, and the picker is the collapsible 🎨 Image styles row above the cards; the summary reads "🎨 Image styles — 3 selected (one image prompt per style)". The default three are Cinematic scene, Conceptual metaphor and Hand-drawn/sketch/whiteboard. Your selection is saved on the draft, so reopening it restores the same styles.

To add cards without regenerating the whole package: tick styles, then press ➕ Add prompts for selected styles. Each new card arrives with a starter prompt for that style, themed around the draft's theme. ➕ Blank prompt adds an empty one.

Each card carries Tool (nano-banana, leonardo, flux) and Aspect (1:1 or 4:5), a prompt, a Negative prompt, and Render / Edit. "Leonardo" is a UI label — it renders on fal/flux-dev, the same model as flux.

To rewrite a prompt in place: press ✨ Refine, type an instruction ("more contrarian", "no text in image"), press Apply. The post text and theme go along as grounding. Without an Anthropic key the panel says Set your Anthropic API key in Settings to refine.

Seed reference (click a thumbnail to generate inspired by it) appears only when the seed post selected in Seed the Idea carries images. "No seed" is picked to start with; choose a thumbnail and the render becomes img2img — inspired by the seed, not a copy. Originality (left = close to seed) is the denoising strength, 0.3 to 0.9, default 0.6; it stays greyed out until a thumbnail is chosen. Choosing a seed also disables Aspect, because the output follows the source dimensions.

A seed swaps the model for a reference-capable twin — nano-banana becomes fal/nano-banana-edit, anything else becomes fal/flux-dev-img2img — and each twin is priced the same as the model it replaces, so a seeded render costs what an unseeded one would.

Good to know — a seed harvested from LinkedIn is downloaded and re-sent to fal as base64, because fal's servers cannot fetch licdn.com URLs. If that re-host fails, the render falls back to a plain no-seed generation rather than erroring.

The image pool and the editor

Image pool holds every rendered, edited, pasted and pulled image on the draft. (Watermarked wm- copies are kept out of it — they are publish copies, not pickable images.) Each pool item has its own Edit, , a ✓ WM / ✗ WM toggle and 🗑; the thumbnail itself is not a button. Press Edit there, or Edit on a prompt card, to open the editor.

The toolbar is + Zoom, - Zoom, Reset view, + Text, Delete text, a size slider and colour picker, Crop / Apply crop, then two AI buttons. AI: remove bg sends a fixed instruction; AI: edit… prompts you for one ("make the sky dramatic", "remove the text"). Both run fal/nano-banana-edit and are priced as an image under the same cap. Save to pool writes the canvas as a new PNG through POST /api/studio/asset-upload — local, $0, and it never overwrites the original.

Every save produces a new pool asset, and every pool asset is carousel-eligible and selectable for publishing.

Quote cards

A quote card is the post's opening line on a brand-coloured card, rendered by ffmpeg in three sizes: square 1080×1080, portrait 1080×1350, vertical 1080×1920. The hook is the first sentence of the first line, trimmed to 140 characters (160 vertical); when a brand is given, its name sits near the bottom. Text wrapping and font size are computed before ffmpeg runs, because drawtext does not wrap.

POST /api/media/adapt {postId, kind:'quote', brandId?, size?} writes the PNG into the post's media pool and selects it — for that brand in post_brand_bodies.publish_media, or for the source post when no brand is given. On an empty post it answers 422 this post has no text to make media from.

The same endpoint with kind:'carousel' splits the text into slides instead, and answers 422 the text is too short for a carousel when there is not enough to split.

Carousels

The Mode dropdown offers four, labelled as they read in the app:

  • My photos, as a swipeable PDF — pick the photos below, press ⬇ Build carousel PDF, and they become the pages. Nothing is written on them.
  • Written slides on a colour background (PDF) — press ✨ Generate slide text, edit the cards, then ⬇ Build carousel PDF.
  • Written slides over my photos (PDF) — as above, with a background per slide.
  • My photos, as a multi-image post (no PDF) — press Use these photos; they publish as LinkedIn's native swipeable multi-image post.

Slides is clamped to 3–16 server-side (default 3), and the PDF builder caps the document at 16 pages; a photo PDF is capped at 30. Theme offers Light (white), Dark, Navy, Slate, LinkedIn blue, Sand and Mint, and Custom bg takes a #RRGGBB that overrides the background and picks light or dark text by luminance. The PDF is assembled locally with no third-party library, so the build itself is free — only the slide text costs anything.

Common refusals from ⬇ Build carousel PDF: 404 no carousel slides on this draft (generate a carousel first), 404 select images for the carousel first (none of the chosen images are on this draft).

Video

Press Render video under the video prompt. Compose sends POST /api/media/generate {kind:'video'} with fal/ltx-video, no first frame, and the package's own total_duration_seconds, clamped to 1–10 seconds — so a clip you render here is text-to-video on ltx-video. Other models are chosen by passing model to that endpoint. Autopilot is the path that renders a first frame (fal/flux-dev) and then runs image-to-video; its own default there is fal/kling-v3.

Ten seconds costs roughly SGD 0.32 on ltx-video, SGD 0.39 on Kling v3, SGD 1.35 on Veo 3.1, and SGD 3.02 on Kling v3 Pro — which the per-post cap refuses.

The writer prompt is deliberately narrow: one vertical 9:16 clip of 8 seconds and no longer, from one generated first frame, with one continuous camera move on one subject — no cuts, no sequence, no timed cues. Image-to-video models cannot choreograph; they can move a camera through a scene. The editable fields are Paste-ready prompt, On-screen text, First-frame image prompt, Visual, Voiceover and Negative prompt. Paste-ready prompt is what Render video sends; First-frame image prompt is written for the image-to-video path, so in Compose it is text to copy out rather than something the button renders.

Request shapes differ per model and live with the model: ltx-video takes num_frames (seconds × 24); Kling, Veo and Seedance take duration as a string of seconds. Kling and Veo are image-to-video only, so a first frame is what makes the clip match the post — and asking one of them for a text-only clip falls back to ltx-video rather than submitting a body the API would reject.

Voiceover

Press Generate script to write one, edit it in place, then Render voiceover onto video. That runs fal TTS (default fal/elevenlabs-turbo-v2.5, voice "Rachel", script capped at 8,000 characters) and muxes the audio onto the post's most recent rendered video, saving a voiced-<timestamp>.mp4 as a new video asset.

The script is checked first: with an empty one it answers 400 no voiceover script on this draft (write a voiceover first). With a script but no clip, 400 render a video first (no rendered video to add a voiceover to). The cost guard then runs twice — once through the generic budget check, once against the actual TTS + mux estimate — before fal is touched.

The watermark contract

The video badge is burned server-side by ffmpeg, and only when the video is about to be used: ▶ Preview watermark, Publish and Schedule all route through POST /api/media/watermark-now. Nothing is stamped on arrival — most pulled videos are never published, and stamping them bought ~20 s of CPU and a second file for nothing. The original is always kept beside the wm- copy.

The switches live in Render media this run:Watermark video, plus position, solidity and how much of the clip carries the badge. With it off you get the video watermark is switched off in Settings (409); with no handle set, this workspace has no handle to stamp — set a name in Settings → Voice.

watermark_spec is the contract that stops a silent second burn. It records what an encode actually used, in the exact form the browser builds: <source file>|<position>@<opacity>@<span>. When the spec matches your current settings and the wm- file exists, nothing re-encodes. When it differs, you are asked before a ~20 s re-burn rather than having it happen invisibly during a schedule.

Good to know — image badges work differently. They are drawn in the browser canvas (default on) and stored by POST /api/media/stamp as wm-<name>.png, so the file publish sends is the one you saw in the pool. Animated GIFs are never stamped — a canvas would freeze them on frame one — and get an MP4 twin instead.

The per-post Watermark video choice is recorded on a queued free-generation run, so an overnight draft keeps the decision you made when you pressed the button.

The format offer

Instagram has no text-only post. The publisher refuses one outright with Instagram has no text-only post — this needs a picture or a video, and Compose's pre-flight catches it first, naming the destination it is talking about: "Taara AI: Instagram needs a picture or a video, and this post is set to text only".

Rather than just blocking, it offers. Make a quote card for … — the brand's name, or just "Instagram" when that account has no brand — renders the card at $0 and selects it for that destination, so the source post can stay text-only for LinkedIn while Instagram gets a picture. TikTok has no $0 equivalent yet, so the pre-flight says so plainly: add a video under Media, or untick TikTok.

Unattended, a brand's own default decides. In Settings → Brands, a brand with an Instagram account gets Instagram, when a post has no media: with two choices — Make a quote card from the text or Leave it — Compose asks me. The first is what Pollinate applies to a newly derived draft, with no one watching.

One media kind per post

LinkedIn cannot mix images, video and a document in one share, and neither can Studio. The Media for this post tray is where you choose: Text only, Images (n), Carousel PDF, Video (n). Kinds you have not made are greyed out. One kind available selects itself; two or more force an explicit pick.

That choice is saved as gen_package.publish_media and enforced three times — in the browser, at schedule time, and again at publish time, before the post is claimed:

this post has multiple media types (image, video); choose which one to publish in Compose
selected media (video) is not available on this post — generate/render it or change the selection

At publish that is a 422 and nothing is sent; at schedule time the same message comes back as a 400 and the post stays a draft. A draft that carries only one kind and has no selection recorded still publishes. When a kind is chosen but no particular file is — which happens for video and for the carousel PDF — the most recent asset of that kind is used, so a voiced clip or a freshly rebuilt PDF wins.