Metadata-Version: 2.5
Name: bannerbear
Version: 1.2.0
Summary: Bannerbear API wrapper for Python — an image generation and media processing service.
Project-URL: Homepage, https://github.com/yongfook/bannerbear-python
Project-URL: Documentation, https://developers.bannerbear.com/v5/
Author-email: Jon Yongfook <yongfook@gmail.com>
License: MIT License
        
        Copyright (c) 2026 Jon Yongfook
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Requires-Dist: requests>=2.25
Description-Content-Type: text/markdown

# Bannerbear Python Library

A Python wrapper for the Bannerbear API — an image generation and media processing service.

## Documentation

Find the full API documentation [here](https://developers.bannerbear.com/v5/).

## Requirements

Python 3.8 or higher.

## Installation

```sh
pip install bannerbear
```

## V5 API

The [V5 API](https://developers.bannerbear.com/v5/) is the current generation of the Bannerbear API. V5 API keys are not compatible with V2 endpoints, and vice versa.

> This library currently ships the V5 client only. V2 support may be added in a future release under `bannerbear.v2`.

### Table of Contents

- [Authentication](#authentication)
- [Account](#account)
- [Image Templates](#image-templates)
- [Images](#images)
- [Animation Templates](#animation-templates)
- [Animations](#animations)
- [Workflows](#workflows)
- [Workflow Runs](#workflow-runs)
- [Tools](#tools)
- [Assets](#assets)
- [Publications](#publications)
- [Batches](#batches)
- [Webhooks](#webhooks)
- [Instant URLs](#instant-urls)

### Authentication

```python
from bannerbear.v5 import Client

bb = Client("your V5 API key")
```

Or set `BANNERBEAR_API_KEY` and instantiate without arguments:

```python
bb = Client()
```

### Account

```python
bb.account()
```

### Image Templates

Templates can be created, updated, and deleted through the API — `config` holds the full canvas.

```python
bb.list_image_templates(page=1)
bb.get_image_template("template uid")

bb.create_image_template({
    "name": "My Template",
    "description": "Created from the API",
    "tags": ["portrait"],
    "width": 1080,
    "height": 1080,
    "config": {
        "objects": [
            {"id": "bg", "type": "rectangle", "left": 0, "top": 0,
             "width": 1080, "height": 1080, "background-color": "#0f172a"},
            {"id": "headline", "type": "text", "left": 80, "top": 400, "width": 920,
             "text": "Hello World!", "font-size": 72, "color": "#ffffff"},
        ]
    },
})

bb.update_image_template("template uid", {
    "name": "New Name",
    "description": "...",
    "tags": ["portrait"],
})

bb.delete_image_template("template uid")
```

##### Options for `create_image_template` / `update_image_template`

- `name` *required for create*
- `description`
- `tags`
- `width` / `height`: canvas size in pixels
- `config`: full canvas configuration, `{"objects": [...]}`. Passing it **replaces** the existing config in place

Deleting is a soft delete: images already rendered from the template stay intact, but the template no longer appears in list/get calls and cannot be used for new renders.

### Images

V5's `modifications` is a dict with two sub-keys:

- `template` — template-level changes (width, height, etc.)
- `objects` — list of per-layer changes

```python
bb.create_image("template uid", {
    "modifications": {
        "template": {"width": 1080, "height": 1080},
        "objects": [
            {"name": "headline", "text": "Hello World!"},
            {
                "name": "photo",
                "image_url": "https://images.unsplash.com/photo-1555400038-63f5ba517a47?w=1000&q=80",
            },
        ],
    },
})
```

Synchronous generation routes to `sync.api.bannerbear.com/v5` (10s timeout). `synchronous` is a keyword-only flag — it's a transport switch and is **not** sent in the request body:

```python
bb.create_image("template uid", {"modifications": {"objects": [...]}}, synchronous=True)
```

##### Options for `create_image`

- `modifications`: V5 modifications dict
- `formats`: output formats, e.g. `["jpg", "pdf"]`
- `scale`: scale multiplier, 1–4
- `dpi`: DPI metadata
- `quality`: quality control
- `proxy`: proxy and resize external images before rendering (bool)
- `metadata`: include any metadata to reference at a later point
- `version`: pin template version
- `synchronous` (keyword-only): route to the sync host (SDK-only, not sent to the API)

```python
bb.get_image("image uid")
bb.list_images(page=1)
```

### Animation Templates

Animation templates render video instead of a still image. They carry a `frame_rate` and a `duration_seconds` in place of the image template's static canvas.

```python
bb.list_animation_templates(page=1)
bb.get_animation_template("template uid")

bb.create_animation_template({
    "name": "My Animation",
    "description": "Created from the API",
    "tags": ["promo"],
    "width": 1080,
    "height": 1080,
    "frame_rate": 30,
})

bb.update_animation_template("template uid", {"name": "New Name", "frame_rate": 60})
bb.delete_animation_template("template uid")
```

##### Options for `create_animation_template` / `update_animation_template`

- `name` *required for create*
- `description`
- `tags`
- `width` / `height`: canvas size in pixels, 100–3000
- `frame_rate`: `24`, `30`, or `60`

### Animations

Rendering an animation is **always asynchronous** — there is no sync host for animations. Poll `get_animation` until the status is `"completed"` or `"failed"`, or subscribe to a webhook with the resource `"animation"`.

```python
animation = bb.create_animation("animation template uid", {
    "modifications": {
        "template": {"width": 1080, "height": 1080, "fps": 30},
        "objects": [{"name": "headline", "text": "Hello World!"}],
    },
    "formats": ["mp4"],
})

animation = bb.get_animation(animation["uid"])
animation["status"]  # "queued" | "rendering" | "completed" | "failed"
if animation["status"] == "completed":
    print(animation["files"]["mp4"])

bb.list_animations(page=1)
```

##### Options for `create_animation`

- `modifications`: V5 modifications dict
- `formats`: `["mp4"]` or `["mov"]`. Ignored when `transparent` is set — that always yields MOV
- `metadata`: include any metadata to reference at a later point

Template-level modification keys for animations: `width`, `height`, `fps` (`24`, `30`, or `60`), and `transparent`. Setting `transparent` renders on a transparent background and forces a MOV output, so the alpha channel survives.

### Workflows

Workflows chain several steps into one named, re-runnable operation. They are read-only through the API — build them in the Bannerbear UI, then run them here.

```python
bb.list_workflows(page=1)

workflow = bb.get_workflow("workflow uid")
workflow["inputs"]  # the inputs this workflow declares
workflow["steps"]
```

### Workflow Runs

A run is **asynchronous**. Poll `get_workflow_run` until the status is `"completed"` or `"failed"`, or subscribe to a webhook with the resource `"workflow_run"`.

```python
run = bb.create_workflow_run("workflow uid", {
    "inputs": {"headline": "Hello World!", "photo": "https://example.com/photo.jpg"},
})

run = bb.get_workflow_run(run["uid"])
run["status"]  # "queued" | "running" | "completed" | "failed"
if run["status"] == "completed":
    print(run["outputs"])

bb.list_workflow_runs(page=1)
```

##### Options for `create_workflow_run`

- `inputs`: values for the workflow's declared inputs

### Tools

Tools are standalone media operations that do not use a template. Every tool is **asynchronous**: the call returns a pending *tool job*. Poll `get_tool_job` until the status is `"completed"` or `"failed"`, or subscribe to a webhook with the resource `"tool_job"`.

```python
job = bb.trim_video({"video_url": "https://example.com/clip.mp4", "start": 2.5, "end": 10.0})

job = bb.get_tool_job(job["uid"])
job["status"]  # "pending" | "running" | "completed" | "failed"
if job["status"] == "completed":
    print(job["outputs"]["video_url"])

bb.list_tool_jobs(page=1)
```

Every tool also accepts an optional `metadata` string.

| Method | Required | Optional | Output key |
| --- | --- | --- | --- |
| `remove_bg` | `image_url` | — | `image_url` |
| `create_pdf` | `urls` | — | `pdf_url` |
| `trim_video` | `video_url`, `start`, `end` | — | `video_url` |
| `concat_videos` | `video_urls` | `width`, `height`, `fps` | `video_url` |
| `resize_video` | `video_url`, `width`, `height` | `fit` | `video_url` |
| `crop_video` | `video_url`, `x`, `y`, `width`, `height` | — | `video_url` |
| `overlay_video` | `base_video_url`, `overlay_video_url` | `position`, `margin`, `x`, `y`, `scale`, `start` | `video_url` |
| `overlay_image` | `video_url`, `image_url` | `position`, `margin`, `x`, `y`, `opacity` | `video_url` |
| `subtitle_video` | `video_url` | `language`, `font`, `font_size`, `color`, `bold`, `italic`, `outline_color`, `outline_width`, `shadow_size`, `shadow_color`, `background_style`, `background_color`, `alignment` | `video_url` |
| `generate_voiceover` | `text`, `voice` | — | `audio_url` |
| `add_audio` | `video_url`, `audio_url`, `mode` | `volume`, `loop`, `ducking` | `video_url` |
| `add_cover_art` | `video_url`, `image_url` | — | `video_url` |
| `create_video_slideshow` | `image_urls` | `slide_duration`, `transition`, `transition_duration`, `width`, `height` | `video_url` |
| `apply_color_filter` | `video_url`, `filter` | — | `video_url` |
| `soften_video` | `video_url`, `strength` | — | `video_url` |

Place the two overlay tools with **either** `position` (plus an optional `margin`) **or** `x`/`y` — not both. `position` accepts `top_left`, `top_center`, `top_right`, `center`, `bottom_left`, `bottom_center`, and `bottom_right`. `resize_video` accepts a `fit` of `cover` (crops), `contain` (letterboxes), or `blur` (fills the bars with a blurred copy).

A few examples:

```python
bb.remove_bg({"image_url": "https://example.com/product.png"})

# Corner placement, 40px in from each edge
bb.overlay_image({
    "video_url": "https://example.com/clip.mp4",
    "image_url": "https://example.com/logo.png",
    "position": "bottom_right",
    "margin": 40,
    "opacity": 0.8,
})

bb.subtitle_video({
    "video_url": "https://example.com/talk.mp4",
    "font": "montserrat",
    "font_size": 32,
    "color": "#ffffff",
    "background_style": "outline",
    "alignment": "2",
})

bb.generate_voiceover({"text": "Welcome to Bannerbear.", "voice": "rachel"})

bb.create_video_slideshow({
    "image_urls": ["https://example.com/1.jpg", "https://example.com/2.jpg"],
    "slide_duration": 3,
    "transition": "fade",
    "width": 1280,
    "height": 720,
})
```

`create_tool_job` calls any tool by name — the escape hatch for tools added after this release:

```python
bb.create_tool_job("remove_bg", {"image_url": "https://example.com/product.png"})
```

### Assets

Upload a file (max 5MB) and get back a durable CDN URL you can feed to image modifications or tools. Uploads are deduplicated per workspace by SHA-256, so re-uploading the same bytes returns the existing record instead of creating a duplicate.

```python
with open("logo.png", "rb") as f:
    asset = bb.upload_asset(f.read(), "image/png")

asset["url"]

bb.get_asset("asset uid")
bb.list_assets(page=1)
```

Accepted mime types: `image/jpeg`, `image/png`, `image/webp`, `image/gif`, `video/mp4`, `video/webm`, `video/quicktime`, `audio/mpeg`, `audio/wav`, `audio/mp4`, `audio/webm`, `audio/ogg`, `application/pdf`.

`check_assets` maps each SHA-256 content hash to its existing asset (or `None`), so a syncing client can skip the upload round-trip for content it already pushed. Max 100 hashes per call.

```python
import hashlib

data = open("logo.png", "rb").read()
digest = hashlib.sha256(data).hexdigest()
if bb.check_assets([digest])[digest] is None:
    bb.upload_asset(data, "image/png")
```

### Publications

Publications are templates published to the public library. Installing one clones it into your workspace as a new image template.

```python
bb.list_publications(page=1)
bb.get_publication("publication uid")

template = bb.install_publication("publication uid")
template["uid"]
```

### Batches

Generate multiple images in one request (up to 100).

```python
bb.create_batch({
    "type": "images",
    "items": [
        {"template": "template uid 1", "modifications": {"objects": [...]}},
        {"template": "template uid 2", "modifications": {"objects": [...]}},
    ],
})
bb.get_batch("batch uid")
bb.list_batches(page=1)
```

### Webhooks

Webhooks are managed as a first-class resource in V5.

```python
hook = bb.create_webhook({
    "name": "my-webhook",
    "url": "https://example.com/hook",
    "resource": "image",
    "event": "completed",
    "status": "active",
})

# IMPORTANT: signing_key is ONLY returned in the create response. Store it now —
# subsequent get_webhook calls will not include it.
print(hook["signing_key"])
```

##### Options for `create_webhook` / `update_webhook`

- `name` *required*
- `url` *required* — the URL that receives the events
- `resource`: `"image"`, `"batch"`, `"tool_job"`, `"animation"`, or `"workflow_run"`
- `event`: `"all_events"`, `"completed"`, or `"failed"`
- `status`: `"active"` or `"disabled"`

CRUD:

```python
bb.get_webhook("webhook uid")
bb.update_webhook("webhook uid", {
    "name": "renamed",
    "url": "https://example.com/hook",
    "resource": "image",
    "event": "completed",
    "status": "active",
})
bb.delete_webhook("webhook uid")
bb.list_webhooks(page=1)
```

### Instant URLs

Instant URLs are URLs bound to a template that can be manipulated with query strings.

#### Create an Instant URL base

```python
iurl = bb.create_instant_url({
    "name": "my-instant-url",
    "template": "template uid",
    "mode": "encoded",         # or "named_params"
    "security": "signed",      # or "open"
    "status": "active",
    "scale": 1,                # 1, 2, 3, or 4
})

# IMPORTANT: signing_key is ONLY returned in the create response. Store it now.
print(iurl["signing_key"])
print(iurl["base_url"])
```

##### Options for `create_instant_url` / `update_instant_url`

- `name` *required*
- `template` *required* — image template UID
- `mode`: `"encoded"` or `"named_params"`
- `security`: `"signed"` or `"open"`
- `status`: `"active"` or `"disabled"`
- `scale`: 1, 2, 3, or 4
- `rate_limit`: enable per-IP rate limiting
- `template_version`: pin template version
- `max_renders`: cap total renders
- `expires_at`: ISO 8601 expiry

CRUD:

```python
bb.get_instant_url("uid")
bb.update_instant_url("uid", {"name": "...", "template": "...", ...})
bb.delete_instant_url("uid")
bb.list_instant_urls(page=1)
```

#### Build an Instant URL with modifications

`build_instant_url` is a pure local helper — no API call. It composes the URL from a base + modifications and, if a signing key is provided, appends the HMAC signature.

```python
# Encoded mode, signed
bb.build_instant_url(
    iurl["base_url"],
    mode="encoded",
    signing_key=iurl["signing_key"],
    modifications={
        "template": {"width": 1030, "height": 890},
        "objects": [{"name": "title", "text": "Hello!", "color": "#ffffff"}],
    },
)

# Named params mode, signed
bb.build_instant_url(
    iurl["base_url"],
    mode="named_params",
    signing_key=iurl["signing_key"],
    modifications={
        "template": {"width": 1030, "height": 890},
        "objects": [{"name": "title", "text": "Hello!"}],
    },
)

# Open (unsigned): omit signing_key
bb.build_instant_url(
    iurl["base_url"],
    mode="encoded",
    modifications={"objects": [{"name": "title", "text": "Hello!"}]},
)
```

##### Options for `build_instant_url`

- `base_url` (positional, required) — from the `create_instant_url` response
- `modifications` (kwarg, required) — same shape as `create_image`'s modifications
- `mode` (kwarg) — `"encoded"` (default) or `"named_params"`
- `signing_key` (kwarg) — only needed when the instant URL was created with `security="signed"`

## License

MIT. See [LICENSE](LICENSE).
