Skip to content
Back to skills

Api Media

ASecurity

Call nodetool.media or nodetool.generations from a code action: generate or edit images, video, speech and music with a picked model, transcribe and embed, judge images with a vision model, read a video, run ffmpeg or ffprobe, download a video, and read the status and cost of a generation. Load before the first call into these namespaces.

  • 552 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 29, 2026
ai-agentsshellnodeapi

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned September 29, 2026

npx -y skills add nodetool-ai/nodetool --skill api-media --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Api Media?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Api Media
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nodetool-ai-api-media/badge)](https://www.skillsdirectory.com/skills/nodetool-ai-api-media)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: api-media
description: "Call nodetool.media or nodetool.generations from a code action: generate or edit images, video, speech and music with a picked model, transcribe and embed, judge images with a vision model, read a video, run ffmpeg or ffprobe, download a video, and read the status and cost of a generation. Load before the first call into these namespaces."
---

# nodetool.media and nodetool.generations

`nodetool.media` makes one result with one picked model and saves it as an
asset. No workflow is needed. `nodetool.generations` is the record of every
media generation: status, cost, and the assets it made. The model comes from
`nodetool.models.pick` (see `api-models`). How to word a prompt for one model
line is the `*-prompting` skill that `find_model` names in `prompting_skill`.

## Generation calls

Each call takes `model` as a `pick`/`find` result, `{provider, model_id}`, or
a `"provider/model_id"` string.

| Call | Options | Capability to pick |
| :--- | :--- | :--- |
| `generateImage(prompt, model, opts)` | `width`, `height`, `quality`, `negative_prompt`, `output_file`, `background` | `text_to_image` |
| `editImage(inputFile, prompt, model, opts)` | `reference_files`, `strength`, `target_width`, `target_height`, `negative_prompt`, `output_file`, `background` | `image_to_image` |
| `generateVideo(prompt, model, opts)` | `duration_seconds`, `aspect_ratio`, `resolution`, `num_frames`, `negative_prompt`, `output_file`, `background` | `text_to_video` |
| `animateImage(inputFile, model, opts)` | `prompt`, `duration_seconds`, `aspect_ratio`, `resolution`, `num_frames`, `output_file`, `background` | `image_to_video` |
| `videoFromReferences(referenceFiles, model, opts)` | `prompt`, `use_reference_video_audio`, `duration_seconds`, `aspect_ratio`, `resolution`, `output_file`, `background` | `reference_to_video` |
| `speak(text, model, opts)` | `voice`, `speed`, `output_file`, `background` | `text_to_speech` |
| `generateMusic(prompt, model, opts)` | `lyrics`, `duration_seconds`, `output_file`, `background` | `text_to_music` |
| `transcribe(inputFile, model, opts)` | `language`, `prompt` | `automatic_speech_recognition` |
| `embed(text, model, opts)` | `dimensions`. `text` is a string or an array. | `generate_embedding` |

A generation answers with `asset_id`, `asset_uri` (`asset://…`),
`generation_id`, `mime_type` and `bytes`, plus `path` when you passed
`output_file`. An input file (`inputFile`, `reference_files`) is an
`asset://` URI or a workspace path.

- `editImage` is the call for "make it like this". Pass the attached image or
  an approved earlier result as `inputFile`, and further images to match in
  `reference_files`.
- `videoFromReferences` is for a shot that more than one reference defines: a
  character plus a garment, a product plus a location. The prompt then says
  the action and the camera, not the subjects.
- Models honour `duration_seconds` loosely. Measure the result with
  `ffprobe` before you cut to it.
- To put words on a picture, draw them yourself with `createCanvas` and
  `fillText`, or with `renderText` from
  `@nodetool-ai/sandbox-flow/lib.image.draw`.

### Background generations

`background: true` returns at once with
`{generation_id, status: "running", background: true}`. Collect the result
with `nodetool.generations.wait(receipt)`. A run can have 16 open at most.
Start several, then wait for all of them in the same action:

```js
const model = await nodetool.models.pick("text_to_video");
const receipts = await Promise.all(prompts.map((p) =>
  nodetool.media.generateVideo(p, model, { background: true })));
const done = await Promise.all(receipts.map((r) =>
  nodetool.generations.wait(r, { timeout_seconds: 900 })));
```

## Handles and saving

`image.*`, `audio.*` and `video.*` (guest globals, no `nodetool.` prefix) take
a generation result, its `asset_uri` or an asset id, and answer run-local
handles. `video.addAudio(videoHandle, audioHandle)` combines media, for
example. Save a finished handle with `nodetool.media.toImage(handle)`,
`toAudio(handle)` or `toVideo(handle)` before the action ends. A handle is dead
in the next action. Do not pull bytes into the guest.

Hold each result in a local variable and feed it straight into the next call.
Record the uris a later action or turn will need with `nodetool.memory.save`,
and never re-run generation for something already saved.

## Judging

The judge is a chat model that reads images (`pick("generate_message")` on a
vision model), not the model that made the picture.

| Call | Answers |
| :--- | :--- |
| `critique(image, brief, visionModel, {taste_profile})` | A pass/revise verdict and concrete defects with locations and fixes. Feed the fixes into the next prompt. When it names no defect, make fresh variations. |
| `compare(images, brief, visionModel, {taste_profile})` | The winner of 2–8 candidates by a pairwise knockout, each match judged twice with the order swapped, plus every verdict |
| `scoreAdherence(image, brief, visionModel, {questions})` | Yes/no answers to up to 12 checks made from the brief, and the fraction that passed |
| `understandVideo(video, prompt, videoModel, {max_tokens})` | `{text, truncated}`. Gemini reads the whole clip with audio. Other models get still frames with no audio. A `truncated` answer is half an answer: raise `max_tokens` or use a model that does not reason. |

## Host binaries

| Call | Does |
| :--- | :--- |
| `ffmpeg(args, {inputs, output_file, timeout_seconds})` | Runs ffmpeg in the workspace with no shell. `args` is the argv after the binary. Paths are workspace-relative, and URLs are refused. `inputs` stages assets first as `{"a.mp4": "asset://…"}` (8 files at most). `output_file` is saved as an asset. The default timeout is 180 s, the maximum 600 s. |
| `ffprobe(path, {inputs, timeout_seconds})` | Reads the format and streams. `path` may be an `asset://` URI. The answer has a `summary` with `duration_seconds`, `width`, `height` and `has_audio` as real numbers and booleans. |
| `downloadVideo(url, outputFile, {format, timeout_seconds})` | Downloads a public video with yt-dlp, up to 2 GiB. |

```js
// Concatenate two assets in one call.
await nodetool.media.ffmpeg(
  ["-i", "a.mp4", "-i", "b.mp4", "-filter_complex",
   "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]", "-map", "[v]", "-map", "[a]",
   "out.mp4"],
  { inputs: { "a.mp4": uriA, "b.mp4": uriB }, output_file: "out.mp4" }
);
```

## nodetool.generations

Every generation result carries `generation_id`. Read the cost with `get`
instead of guessing it.

| Call | Answers |
| :--- | :--- |
| `list({status, provider, capability, thread_id, job_id, since, limit, start_key})` | Generations, newest first, with status, cost, errors and assets |
| `get(idOrResult)` | One generation in full: request parameters, cost and how it was priced, the provider request id, the assets |
| `wait(idOrResult, {timeout_seconds})` | Waits for a background generation. The default is 300 s. A timeout answers the current record; call again to keep waiting. |
| `cancel(idOrResult)` | Stops a running generation. It answers `cancelled: false` when the generation already settled. |
| `reconcile(idOrResult)` | Asks the provider what it billed and replaces the estimate |
| `fromProvider(provider, {model, status, since, until, limit, cursor})` | The provider's own history, from any machine. `fal_ai` answers. |
| `getFromProvider(provider, requestId, {model})` | One provider-side generation, with the output urls it still hosts. This recovers a lost asset. |

Statuses are `pending`, `running`, `recovering`, `completed`, `failed`,
`cancelled`, `needs_attention` and `interrupted`. `completed` means the
output is durable. A failed, cancelled or interrupted generation can still be
billed.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.