StillMade AIDeveloper docs
Browse documentation · SDK 0.1.0

Images and media references

Handle portable media identities and decoded RGBA images.

Portable references are {assetId,versionId,kind}; a reference is not an absolute file path. Current pure image operations require a decoded fixture/preview image: {width,height,data:[R,G,B,A,...]} with 0–255 integer channels, up to 1 megapixel. Large production media must use a host media adapter; this SDK does not pretend to transcode an arbitrary asset reference. Line thickening uses a square dilation kernel and thresholded luminance; it returns a new RGBA image.

Connected Project Type previews include a small, temporary media bridge. If an image-producing Block returns pixels and the next input accepts an asset, StillMade encodes a real PNG data URL in memory and supplies a preview media reference. If a later image input receives that same exact generated reference, the bridge supplies copied pixels to image-processing code. Original pixel outputs remain visible as pixel previews; direct image-to-image connections do not need this conversion.

This behavior is shared by the browser's connected preview, CLI preview, and Project Type admission tests. It does not upload media, fetch external URLs, or create project assets. Only references generated by that preview session can be hydrated; changing their kind, asset/version identity, URL or semantic role fails. Optional dimensions and labels may be omitted or changed; the private pixel copy remains authoritative. Other references remain inert references for the caller's supported host runner. Encoding and private cache costs share the existing 4 MB sample budget and preview deadline, so use small fixtures. Production execution uses its normal authorized media storage and decoding path instead of these temporary references.

Saved media collections

Use an asset[], audio[] or video[] input to collect original media with standard controls. Inside a project, these controls upload, select, order and remove files without custom host code. Project uploads are saved before the input is published, with exact per-file receipts bound to the Block version. Compatible direct remixes retain uploaded media. Builder previews use temporary local files; upload in a project for durable use. Project controls admit up to 64 list entries and 50 MiB per original file, subject to the shared format table. The sandbox receives typed references, never upload credentials or local paths.

Import media 2.0.0 is a complete portable example. Its owned JavaScript preserves metadata and selection order, optionally filters media kinds or removes exact duplicate references, and returns an asset[] output for subsequent Blocks. It does not transcode media or claim rights over user files. Its license covers the original Block implementation. Empty selections return an empty list. The native legacy upload node remains available to old placements; new SDK placements use the standard sandbox.

Validate and test the downloaded archive with the same public CLI:

sh
node packages/block-cli/cli.js validate import-media.stillmade-block
node packages/block-cli/cli.js test import-media.stillmade-block
node packages/block-cli/cli.js remix import-media.stillmade-block my-import.stillmade-block
node packages/block-cli/cli.js test my-import.stillmade-block

Mandatory host-controlled exports

Blocks return typed results or file-bundle outputs for StillMade to deliver. Do not create download links, invoke save-file pickers, open export destinations, or implement a separate export/paywall/referral bypass. The upload admission report requires Host-controlled exports (host-export-1), alongside existing sandbox and interface checks. A passing static check is not a delivery receipt. StillMade owns the current export policy for all creators; it may add a referral step, checkout or other requirement without changing your Block. It can also require the StillMade watermark on exports from accounts without a paid plan; StillMade draws it while rendering videos and delivering images, so do not add your own. Block pricing and export access are separate. Never claim payment or referral completion from Block code. Native integrations must declare the host export service and route delivery through it; an extracted source folder does not satisfy this boundary.

Image-processing example

json
{
  "manifest": {
    "schemaVersion": 1,
    "license": "MIT",
    "provenance": {
      "notice": "MIT License\n\nCopyright (c) 2026 StillMade SDK contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and \nassociated documentation files (the \"Software\"), to deal in the Software without restriction, including \nwithout limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell \ncopies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the \nfollowing conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial \nportions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT \nLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO \nEVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER \nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE \nUSE OR OTHER DEALINGS IN THE SOFTWARE.\n"
    },
    "sdkVersion": "0.1.0",
    "id": "example.thicken-lines",
    "version": "1.0.1",
    "name": "Thicken line art",
    "description": "Expands dark outlines in an image while preserving surrounding colors, creating a new image for the next production step.",
    "kind": "task",
    "runtime": "recipe",
    "inputs": {
      "image": {
        "type": "image",
        "primary": true,
        "semantic": "source_image",
        "required": true,
        "description": "Line-art image whose dark outlines should be expanded."
      },
      "thickness": {
        "type": "integer",
        "default": 2,
        "min": 1,
        "max": 8,
        "semantic": "line_thickness",
        "required": false,
        "description": "Number of pixels by which to expand dark outlines."
      },
      "threshold": {
        "type": "number",
        "default": 100,
        "min": 0,
        "max": 255,
        "semantic": "darkness_threshold",
        "required": false,
        "description": "Brightness threshold used to identify dark outline pixels."
      }
    },
    "outputs": {
      "image": {
        "type": "image",
        "primary": true,
        "semantic": "processed_image",
        "description": "A new image with expanded dark outlines."
      }
    },
    "permissions": {
      "project": [],
      "network": [],
      "filesystem": [],
      "secrets": []
    },
    "ui": [
      {
        "control": "asset",
        "port": "image"
      },
      {
        "control": "slider",
        "port": "thickness"
      },
      {
        "control": "slider",
        "port": "threshold"
      }
    ]
  },
  "recipe": {
    "schemaVersion": 1,
    "steps": [
      {
        "id": "outlines",
        "op": "image.thicken-lines",
        "args": {
          "image": {
            "$input": "image"
          },
          "thickness": {
            "$input": "thickness"
          },
          "threshold": {
            "$input": "threshold"
          }
        }
      }
    ],
    "outputs": {
      "image": {
        "$step": "outlines"
      }
    }
  },
  "tests": [
    {
      "name": "One dark point expands to a 3×3 square",
      "input": {
        "image": {
          "width": 5,
          "height": 5,
          "data": [
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            0,
            0,
            0,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255
          ]
        },
        "thickness": 1
      },
      "expected": {
        "image": {
          "width": 5,
          "height": 5,
          "data": [
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            0,
            0,
            0,
            255,
            0,
            0,
            0,
            255,
            0,
            0,
            0,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            0,
            0,
            0,
            255,
            0,
            0,
            0,
            255,
            0,
            0,
            0,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            0,
            0,
            0,
            255,
            0,
            0,
            0,
            255,
            0,
            0,
            0,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255,
            255
          ]
        }
      }
    }
  ]
}

Host boundary

The host resolves a selected image into RGBA before execution and stores accepted output as a new media version. A guest receives no filesystem path or network access. Large files require an explicit host-side conversion or resized copy.

See batch execution for whole-scene and project-wide processing.