bannerbear 0.1.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +9 -7
- data/README.md +358 -0
- data/lib/bannerbear/v5/client.rb +307 -0
- data/lib/bannerbear/version.rb +1 -1
- data/lib/bannerbear.rb +2 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: daa1514b2c5a5dddbef5836f7d64e3863932f4204e95a9625e7577b49dc70cad
|
|
4
|
+
data.tar.gz: 7a33d8e33c07d6b39561e6fbf6b64defafbb7d593a777bd1524f7623528e861c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 20c731f7f32fab1d5afc6fc6f85134febaacd2a386b3e86c526a0c2134a099272f8d98560ca469129f3300286db7439ff8f374e8bd62d1217c985a23a770d06f
|
|
7
|
+
data.tar.gz: df3b49048aa06fb412a3715228ae2af463701267636d00cdf8a7ede033c0a77e6d1c3bd7229cfb84fe1aef3ed1d79123741e23240cedeaa4895b096ed2bd34e2
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
bannerbear (0.
|
|
4
|
+
bannerbear (0.2.0)
|
|
5
5
|
httparty
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
bigdecimal (4.1.2)
|
|
11
|
+
csv (3.3.5)
|
|
12
|
+
httparty (0.24.2)
|
|
13
|
+
csv
|
|
14
|
+
mini_mime (>= 1.0.0)
|
|
12
15
|
multi_xml (>= 0.5.2)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
multi_xml (0.6.0)
|
|
16
|
+
mini_mime (1.1.5)
|
|
17
|
+
multi_xml (0.9.1)
|
|
18
|
+
bigdecimal (>= 3.1, < 5)
|
|
17
19
|
rake (12.3.3)
|
|
18
20
|
|
|
19
21
|
PLATFORMS
|
data/README.md
CHANGED
|
@@ -18,6 +18,364 @@ Or install it yourself as:
|
|
|
18
18
|
|
|
19
19
|
$ gem install bannerbear
|
|
20
20
|
|
|
21
|
+
## V5 API
|
|
22
|
+
|
|
23
|
+
The [V5 API](https://developers.bannerbear.com/v5/) is a new generation of the Bannerbear API. **V5 API keys do not work with V2 endpoints, and V2 API keys do not work with V5 endpoints** — you must use the right client class for your key.
|
|
24
|
+
|
|
25
|
+
For the **V5 API**, use `Bannerbear::V5::Client` (this section).
|
|
26
|
+
For the **legacy V2 API**, see [Usage](#usage) below — that section is unchanged.
|
|
27
|
+
|
|
28
|
+
### Table of Contents
|
|
29
|
+
|
|
30
|
+
- [Authentication (V5)](#authentication-v5)
|
|
31
|
+
- [Account (V5)](#account-v5)
|
|
32
|
+
- [Image Templates (V5)](#image-templates-v5)
|
|
33
|
+
- [Images (V5)](#images-v5)
|
|
34
|
+
- [Tools (V5)](#tools-v5)
|
|
35
|
+
- [Assets (V5)](#assets-v5)
|
|
36
|
+
- [Publications (V5)](#publications-v5)
|
|
37
|
+
- [Batches (V5)](#batches-v5)
|
|
38
|
+
- [Webhooks (V5)](#webhooks-v5)
|
|
39
|
+
- [Instant URLs (V5)](#instant-urls-v5)
|
|
40
|
+
|
|
41
|
+
### Authentication (V5)
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
bb = Bannerbear::V5::Client.new("your V5 API key")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or set `BANNERBEAR_API_KEY` and call without arguments:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
bb = Bannerbear::V5::Client.new
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Account (V5)
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
bb.account
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Image Templates (V5)
|
|
60
|
+
|
|
61
|
+
V5 renames V2's `templates` resource to `image_templates`. Templates can be created, updated, and deleted through the API — `config` holds the full canvas.
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
bb.list_image_templates(page: 1)
|
|
65
|
+
bb.get_image_template("template uid")
|
|
66
|
+
|
|
67
|
+
bb.create_image_template(
|
|
68
|
+
name: "My Template",
|
|
69
|
+
description: "Created from the API",
|
|
70
|
+
tags: ["portrait"],
|
|
71
|
+
width: 1080,
|
|
72
|
+
height: 1080,
|
|
73
|
+
config: { objects: [
|
|
74
|
+
{ id: "bg", type: "rectangle", left: 0, top: 0, width: 1080, height: 1080, "background-color" => "#0f172a" },
|
|
75
|
+
{ id: "headline", type: "text", left: 80, top: 400, width: 920, text: "Hello World!", "font-size" => 72, color: "#ffffff" }
|
|
76
|
+
] }
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
bb.update_image_template("template uid", name: "New Name", description: "...", tags: ["portrait"])
|
|
80
|
+
bb.delete_image_template("template uid")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
##### Options for `create_image_template` / `update_image_template`
|
|
84
|
+
|
|
85
|
+
- `name` *required for create* (`string`)
|
|
86
|
+
- `description` (`string`)
|
|
87
|
+
- `tags` (`array`)
|
|
88
|
+
- `width` / `height`: canvas size in pixels (`integer`)
|
|
89
|
+
- `config`: full canvas configuration, `{ objects: [...] }`. Passing it **replaces** the existing config in place (`hash`)
|
|
90
|
+
|
|
91
|
+
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.
|
|
92
|
+
|
|
93
|
+
### Images (V5)
|
|
94
|
+
|
|
95
|
+
V5's `modifications` is an **object** with two sub-keys:
|
|
96
|
+
|
|
97
|
+
- `template` — template-level changes (width, height, etc.)
|
|
98
|
+
- `objects` — array of per-layer changes (equivalent to V2's flat modifications array)
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
bb.create_image("template uid",
|
|
102
|
+
modifications: {
|
|
103
|
+
template: { width: 1080, height: 1080 },
|
|
104
|
+
objects: [
|
|
105
|
+
{ name: "headline", text: "Hello World!" },
|
|
106
|
+
{ name: "photo", image_url: "https://images.unsplash.com/photo-1555400038-63f5ba517a47?w=1000&q=80" }
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Synchronous generation routes to `sync.api.bannerbear.com/v5` (10s timeout). The `sync:` flag is a Ruby-level switch — it is **not** sent in the request body:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
bb.create_image("template uid", sync: true, modifications: { objects: [...] })
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
##### Options for `create_image`
|
|
119
|
+
|
|
120
|
+
- `modifications`: V5 modifications object (`hash`)
|
|
121
|
+
- `formats`: output formats, e.g. `["jpg", "pdf"]` (`array`)
|
|
122
|
+
- `scale`: scale multiplier, 1–4 (`integer`)
|
|
123
|
+
- `dpi`: DPI metadata (`integer`)
|
|
124
|
+
- `quality`: quality control (`integer`)
|
|
125
|
+
- `proxy`: proxy and resize external images before rendering (`boolean`)
|
|
126
|
+
- `metadata`: include any metadata to reference at a later point (`string`)
|
|
127
|
+
- `version`: pin template version (`integer`)
|
|
128
|
+
- `sync`: route to the sync host (`boolean`; Ruby-only, not sent to the API)
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
bb.get_image("image uid")
|
|
132
|
+
bb.list_images(page: 1)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Tools (V5)
|
|
136
|
+
|
|
137
|
+
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"`.
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
job = bb.trim_video(video_url: "https://example.com/clip.mp4", start: 2.5, end: 10.0)
|
|
141
|
+
|
|
142
|
+
job = bb.get_tool_job(job["uid"])
|
|
143
|
+
job["status"] # => "pending" | "running" | "completed" | "failed"
|
|
144
|
+
job["outputs"]["video_url"] if job["status"] == "completed"
|
|
145
|
+
|
|
146
|
+
bb.list_tool_jobs(page: 1)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Every tool also accepts an optional `metadata` string.
|
|
150
|
+
|
|
151
|
+
| Method | Required | Optional | Output key |
|
|
152
|
+
| --- | --- | --- | --- |
|
|
153
|
+
| `remove_bg` | `image_url` | — | `image_url` |
|
|
154
|
+
| `create_pdf` | `urls` | — | `pdf_url` |
|
|
155
|
+
| `trim_video` | `video_url`, `start`, `end` | — | `video_url` |
|
|
156
|
+
| `concat_videos` | `video_urls` | `width`, `height` | `video_url` |
|
|
157
|
+
| `resize_video` | `video_url`, `width`, `height` | `fit` | `video_url` |
|
|
158
|
+
| `crop_video` | `video_url`, `x`, `y`, `width`, `height` | — | `video_url` |
|
|
159
|
+
| `overlay_video` | `base_video_url`, `overlay_video_url`, `x`, `y` | `scale`, `start` | `video_url` |
|
|
160
|
+
| `overlay_image` | `video_url`, `image_url`, `x`, `y` | `opacity` | `video_url` |
|
|
161
|
+
| `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` |
|
|
162
|
+
| `generate_voiceover` | `text`, `voice` | — | `audio_url` |
|
|
163
|
+
| `add_audio` | `video_url`, `audio_url`, `mode` | `volume`, `loop`, `ducking` | `video_url` |
|
|
164
|
+
| `add_cover_art` | `video_url`, `image_url` | — | `video_url` |
|
|
165
|
+
| `create_video_slideshow` | `image_urls` | `slide_duration`, `transition`, `transition_duration`, `width`, `height` | `video_url` |
|
|
166
|
+
| `apply_color_filter` | `video_url`, `filter` | — | `video_url` |
|
|
167
|
+
| `soften_video` | `video_url`, `strength` | — | `video_url` |
|
|
168
|
+
|
|
169
|
+
A few examples:
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
bb.remove_bg(image_url: "https://example.com/product.png")
|
|
173
|
+
|
|
174
|
+
bb.subtitle_video(
|
|
175
|
+
video_url: "https://example.com/talk.mp4",
|
|
176
|
+
font: "montserrat",
|
|
177
|
+
font_size: 32,
|
|
178
|
+
color: "#ffffff",
|
|
179
|
+
background_style: "outline",
|
|
180
|
+
alignment: "2"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
bb.generate_voiceover(text: "Welcome to Bannerbear.", voice: "rachel")
|
|
184
|
+
|
|
185
|
+
bb.create_video_slideshow(
|
|
186
|
+
image_urls: ["https://example.com/1.jpg", "https://example.com/2.jpg"],
|
|
187
|
+
slide_duration: 3,
|
|
188
|
+
transition: "fade",
|
|
189
|
+
width: 1280,
|
|
190
|
+
height: 720
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`create_tool_job` calls any tool by name — the escape hatch for tools added after this release:
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
bb.create_tool_job("remove_bg", image_url: "https://example.com/product.png")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Assets (V5)
|
|
201
|
+
|
|
202
|
+
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.
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
asset = bb.upload_asset(File.binread("logo.png"), "image/png")
|
|
206
|
+
asset["url"]
|
|
207
|
+
|
|
208
|
+
bb.get_asset("asset uid")
|
|
209
|
+
bb.list_assets(page: 1)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
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`.
|
|
213
|
+
|
|
214
|
+
`check_assets` maps each SHA-256 content hash to its existing asset (or `nil`), so a syncing client can skip the upload round-trip for content it already pushed. Max 100 hashes per call.
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
digest = OpenSSL::Digest::SHA256.hexdigest(File.binread("logo.png"))
|
|
218
|
+
found = bb.check_assets([digest])
|
|
219
|
+
bb.upload_asset(File.binread("logo.png"), "image/png") if found[digest].nil?
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Publications (V5)
|
|
223
|
+
|
|
224
|
+
Publications are templates published to the public library. Installing one clones it into your workspace as a new image template.
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
bb.list_publications(page: 1)
|
|
228
|
+
bb.get_publication("publication uid")
|
|
229
|
+
|
|
230
|
+
template = bb.install_publication("publication uid")
|
|
231
|
+
template["uid"]
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Batches (V5)
|
|
235
|
+
|
|
236
|
+
Generate multiple images in one request (up to 100).
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
bb.create_batch(
|
|
240
|
+
type: "images",
|
|
241
|
+
items: [
|
|
242
|
+
{ template: "template uid 1", modifications: { objects: [...] } },
|
|
243
|
+
{ template: "template uid 2", modifications: { objects: [...] } }
|
|
244
|
+
]
|
|
245
|
+
)
|
|
246
|
+
bb.get_batch("batch uid")
|
|
247
|
+
bb.list_batches(page: 1)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Webhooks (V5)
|
|
251
|
+
|
|
252
|
+
Webhooks are managed as a first-class resource in V5 (instead of being a per-request `webhook_url` parameter).
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
hook = bb.create_webhook(
|
|
256
|
+
name: "my-webhook",
|
|
257
|
+
url: "https://example.com/hook",
|
|
258
|
+
resource: "image",
|
|
259
|
+
event: "completed",
|
|
260
|
+
status: "active",
|
|
261
|
+
scope: "all_templates",
|
|
262
|
+
templates: []
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# IMPORTANT: signing_key is ONLY returned in the create response. Store it now —
|
|
266
|
+
# subsequent get_webhook calls will not include it.
|
|
267
|
+
puts hook["signing_key"]
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
##### Options for `create_webhook` / `update_webhook`
|
|
271
|
+
|
|
272
|
+
- `name` *required* (`string`)
|
|
273
|
+
- `url` *required* — the URL that receives the events (`string`)
|
|
274
|
+
- `resource`: `"image"`, `"batch"`, or `"tool_job"` (`string`)
|
|
275
|
+
- `event`: `"all_events"`, `"completed"`, or `"failed"` (`string`)
|
|
276
|
+
- `status`: `"active"` or `"disabled"` (`string`)
|
|
277
|
+
- `scope`: `"all_templates"` or `"specific_templates"` (`string`)
|
|
278
|
+
- `templates`: template UIDs, used when `scope` is `"specific_templates"` (`array`)
|
|
279
|
+
|
|
280
|
+
CRUD:
|
|
281
|
+
|
|
282
|
+
```ruby
|
|
283
|
+
bb.get_webhook("webhook uid")
|
|
284
|
+
bb.update_webhook("webhook uid",
|
|
285
|
+
name: "renamed",
|
|
286
|
+
url: "https://example.com/hook",
|
|
287
|
+
resource: "image",
|
|
288
|
+
event: "completed",
|
|
289
|
+
status: "active",
|
|
290
|
+
scope: "all_templates"
|
|
291
|
+
)
|
|
292
|
+
bb.delete_webhook("webhook uid")
|
|
293
|
+
bb.list_webhooks(page: 1)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Instant URLs (V5)
|
|
297
|
+
|
|
298
|
+
Instant URLs are URLs bound to a template that can be manipulated with query strings — the V5 equivalent of V2's "Signed URLs" feature.
|
|
299
|
+
|
|
300
|
+
#### Create an Instant URL base
|
|
301
|
+
|
|
302
|
+
```ruby
|
|
303
|
+
iurl = bb.create_instant_url(
|
|
304
|
+
name: "my-instant-url",
|
|
305
|
+
template: "template uid",
|
|
306
|
+
mode: "encoded", # or "named_params"
|
|
307
|
+
security: "signed", # or "open"
|
|
308
|
+
status: "active",
|
|
309
|
+
scale: 1 # 1, 2, 3, or 4
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# IMPORTANT: signing_key is ONLY returned in the create response. Store it now.
|
|
313
|
+
puts iurl["signing_key"]
|
|
314
|
+
puts iurl["base_url"]
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
##### Options for `create_instant_url` / `update_instant_url`
|
|
318
|
+
|
|
319
|
+
- `name` *required* (`string`)
|
|
320
|
+
- `template` *required* — image template UID (`string`)
|
|
321
|
+
- `mode`: `"encoded"` or `"named_params"` (`string`)
|
|
322
|
+
- `security`: `"signed"` or `"open"` (`string`)
|
|
323
|
+
- `status`: `"active"` or `"disabled"` (`string`)
|
|
324
|
+
- `scale`: 1, 2, 3, or 4 (`integer`)
|
|
325
|
+
- `rate_limit`: enable per-IP rate limiting (`boolean`)
|
|
326
|
+
- `template_version`: pin template version (`integer`, nullable)
|
|
327
|
+
- `max_renders`: cap total renders (`integer`, nullable)
|
|
328
|
+
- `expires_at`: ISO 8601 expiry (`string`, nullable)
|
|
329
|
+
|
|
330
|
+
CRUD:
|
|
331
|
+
|
|
332
|
+
```ruby
|
|
333
|
+
bb.get_instant_url("uid")
|
|
334
|
+
bb.update_instant_url("uid", name: "...", template: "...", ...)
|
|
335
|
+
bb.delete_instant_url("uid")
|
|
336
|
+
bb.list_instant_urls(page: 1)
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### Build an Instant URL with modifications
|
|
340
|
+
|
|
341
|
+
`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.
|
|
342
|
+
|
|
343
|
+
```ruby
|
|
344
|
+
# Encoded mode, signed
|
|
345
|
+
bb.build_instant_url(iurl["base_url"],
|
|
346
|
+
mode: "encoded",
|
|
347
|
+
signing_key: iurl["signing_key"],
|
|
348
|
+
modifications: {
|
|
349
|
+
template: { width: 1030, height: 890 },
|
|
350
|
+
objects: [{ name: "title", text: "Hello!", color: "#ffffff" }]
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# Named params mode, signed
|
|
355
|
+
bb.build_instant_url(iurl["base_url"],
|
|
356
|
+
mode: "named_params",
|
|
357
|
+
signing_key: iurl["signing_key"],
|
|
358
|
+
modifications: {
|
|
359
|
+
template: { width: 1030, height: 890 },
|
|
360
|
+
objects: [{ name: "title", text: "Hello!" }]
|
|
361
|
+
}
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Open (unsigned): omit signing_key
|
|
365
|
+
bb.build_instant_url(iurl["base_url"],
|
|
366
|
+
mode: "encoded",
|
|
367
|
+
modifications: { objects: [{ name: "title", text: "Hello!" }] }
|
|
368
|
+
)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
##### Options for `build_instant_url`
|
|
372
|
+
|
|
373
|
+
- `mode`: `"encoded"` (default) or `"named_params"` (`string`)
|
|
374
|
+
- `signing_key`: only needed when the instant URL was created with `security: "signed"` (`string`)
|
|
375
|
+
- `modifications`: same shape as `create_image`'s modifications (`hash`)
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
21
379
|
## Usage
|
|
22
380
|
|
|
23
381
|
### Table of Contents
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
module Bannerbear
|
|
2
|
+
module V5
|
|
3
|
+
|
|
4
|
+
class Client
|
|
5
|
+
|
|
6
|
+
def initialize(api_key = nil)
|
|
7
|
+
@api_key = api_key || ENV["BANNERBEAR_API_KEY"]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def account
|
|
11
|
+
get_response "/account"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Image Templates
|
|
15
|
+
|
|
16
|
+
def list_image_templates(params = {})
|
|
17
|
+
get_response "/image_templates?#{URI.encode_www_form(params.slice(:page))}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get_image_template(uid)
|
|
21
|
+
get_response "/image_templates/#{uid}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_image_template(payload = {})
|
|
25
|
+
post_response "/image_templates", payload.slice(:name, :description, :tags, :width, :height, :config)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def update_image_template(uid, payload = {})
|
|
29
|
+
patch_response "/image_templates/#{uid}", payload.slice(:name, :description, :tags, :width, :height, :config)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def delete_image_template(uid)
|
|
33
|
+
delete_response "/image_templates/#{uid}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Images
|
|
37
|
+
|
|
38
|
+
def list_images(params = {})
|
|
39
|
+
get_response "/images?#{URI.encode_www_form(params.slice(:page))}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get_image(uid)
|
|
43
|
+
get_response "/images/#{uid}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def create_image(uid, payload = {})
|
|
47
|
+
post_response "/images", payload.slice(:modifications, :formats, :scale, :dpi, :quality, :proxy, :metadata, :version).merge({:template => uid}), payload[:sync]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Tools
|
|
51
|
+
#
|
|
52
|
+
# Every tool is asynchronous: the POST returns a pending tool job. Poll
|
|
53
|
+
# get_tool_job until the status is "completed" or "failed", or subscribe
|
|
54
|
+
# to a webhook with the resource "tool_job".
|
|
55
|
+
|
|
56
|
+
TOOL_PARAMS = {
|
|
57
|
+
"remove_bg" => [:image_url],
|
|
58
|
+
"create_pdf" => [:urls],
|
|
59
|
+
"trim_video" => [:video_url, :start, :end],
|
|
60
|
+
"concat_videos" => [:video_urls, :width, :height],
|
|
61
|
+
"resize_video" => [:video_url, :width, :height, :fit],
|
|
62
|
+
"crop_video" => [:video_url, :x, :y, :width, :height],
|
|
63
|
+
"overlay_video" => [:base_video_url, :overlay_video_url, :x, :y, :scale, :start],
|
|
64
|
+
"overlay_image" => [:video_url, :image_url, :x, :y, :opacity],
|
|
65
|
+
"subtitle_video" => [:video_url, :language, :font, :font_size, :color, :bold, :italic,
|
|
66
|
+
:outline_color, :outline_width, :shadow_size, :shadow_color,
|
|
67
|
+
:background_style, :background_color, :alignment],
|
|
68
|
+
"generate_voiceover" => [:text, :voice],
|
|
69
|
+
"add_audio" => [:video_url, :audio_url, :mode, :volume, :loop, :ducking],
|
|
70
|
+
"add_cover_art" => [:video_url, :image_url],
|
|
71
|
+
"create_video_slideshow" => [:image_urls, :slide_duration, :transition, :transition_duration, :width, :height],
|
|
72
|
+
"apply_color_filter" => [:video_url, :filter],
|
|
73
|
+
"soften_video" => [:video_url, :strength]
|
|
74
|
+
}.freeze
|
|
75
|
+
|
|
76
|
+
def create_tool_job(tool, payload = {})
|
|
77
|
+
allowed = TOOL_PARAMS[tool.to_s]
|
|
78
|
+
raise ArgumentError, "unknown tool: #{tool.inspect}" if allowed.nil?
|
|
79
|
+
post_response "/tools/#{tool}", payload.slice(*allowed, :metadata)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Defines one method per tool, e.g. remove_bg(payload) or trim_video(payload).
|
|
83
|
+
TOOL_PARAMS.each_key do |tool|
|
|
84
|
+
define_method(tool) { |payload = {}| create_tool_job(tool, payload) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Tool Jobs
|
|
88
|
+
|
|
89
|
+
def list_tool_jobs(params = {})
|
|
90
|
+
get_response "/tool_jobs?#{URI.encode_www_form(params.slice(:page))}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def get_tool_job(uid)
|
|
94
|
+
get_response "/tool_jobs/#{uid}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Assets
|
|
98
|
+
|
|
99
|
+
def list_assets(params = {})
|
|
100
|
+
get_response "/assets?#{URI.encode_www_form(params.slice(:page))}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def get_asset(uid)
|
|
104
|
+
get_response "/assets/#{uid}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Uploads raw file bytes (max 5MB). content_type must be the mime type of
|
|
108
|
+
# the data, e.g. "image/png" or "video/mp4". Uploads are deduplicated per
|
|
109
|
+
# workspace by SHA-256, so re-uploading the same bytes returns the
|
|
110
|
+
# existing asset instead of creating a duplicate.
|
|
111
|
+
def upload_asset(data, content_type)
|
|
112
|
+
upload_response "/assets", data, content_type
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns a map of SHA-256 content hash => asset (or nil when the hash is
|
|
116
|
+
# not stored in this workspace). Max 100 hashes per call.
|
|
117
|
+
def check_assets(content_hashes)
|
|
118
|
+
post_response "/assets/check", { :content_hashes => content_hashes }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Publications
|
|
122
|
+
|
|
123
|
+
def list_publications(params = {})
|
|
124
|
+
get_response "/publications?#{URI.encode_www_form(params.slice(:page))}"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def get_publication(uid)
|
|
128
|
+
get_response "/publications/#{uid}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Clones a publication into the workspace as a new image template.
|
|
132
|
+
def install_publication(uid)
|
|
133
|
+
post_response "/publications/#{uid}/install", {}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Batches
|
|
137
|
+
|
|
138
|
+
def list_batches(params = {})
|
|
139
|
+
get_response "/batches?#{URI.encode_www_form(params.slice(:page))}"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def get_batch(uid)
|
|
143
|
+
get_response "/batches/#{uid}"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def create_batch(payload = {})
|
|
147
|
+
post_response "/batches", payload.slice(:type, :items)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Webhooks
|
|
151
|
+
|
|
152
|
+
def list_webhooks(params = {})
|
|
153
|
+
get_response "/webhooks?#{URI.encode_www_form(params.slice(:page))}"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def get_webhook(uid)
|
|
157
|
+
get_response "/webhooks/#{uid}"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def create_webhook(payload = {})
|
|
161
|
+
post_response "/webhooks", payload.slice(:name, :url, :resource, :event, :status, :scope, :templates)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def update_webhook(uid, payload = {})
|
|
165
|
+
patch_response "/webhooks/#{uid}", payload.slice(:name, :url, :resource, :event, :status, :scope, :templates)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def delete_webhook(uid)
|
|
169
|
+
delete_response "/webhooks/#{uid}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Instant URLs
|
|
173
|
+
|
|
174
|
+
def list_instant_urls(params = {})
|
|
175
|
+
get_response "/instant_urls?#{URI.encode_www_form(params.slice(:page))}"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def get_instant_url(uid)
|
|
179
|
+
get_response "/instant_urls/#{uid}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def create_instant_url(payload = {})
|
|
183
|
+
post_response "/instant_urls", payload.slice(:name, :template, :mode, :security, :status, :scale, :rate_limit, :template_version, :max_renders, :expires_at)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def update_instant_url(uid, payload = {})
|
|
187
|
+
patch_response "/instant_urls/#{uid}", payload.slice(:name, :template, :mode, :security, :status, :scale, :rate_limit, :template_version, :max_renders, :expires_at)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def delete_instant_url(uid)
|
|
191
|
+
delete_response "/instant_urls/#{uid}"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def build_instant_url(base_url, payload = {})
|
|
195
|
+
modifications = payload[:modifications]
|
|
196
|
+
mode = (payload[:mode] || "encoded").to_s
|
|
197
|
+
|
|
198
|
+
url = case mode
|
|
199
|
+
when "encoded"
|
|
200
|
+
data =
|
|
201
|
+
if modifications.is_a?(Hash) && modifications.keys.map(&:to_s) == ["objects"]
|
|
202
|
+
modifications[:objects] || modifications["objects"]
|
|
203
|
+
else
|
|
204
|
+
modifications
|
|
205
|
+
end
|
|
206
|
+
"#{base_url}?modifications=#{Base64.urlsafe_encode64(data.to_json, padding: false)}"
|
|
207
|
+
when "named_params"
|
|
208
|
+
template = modifications.is_a?(Hash) ? (modifications[:template] || modifications["template"]) : nil
|
|
209
|
+
objects = modifications.is_a?(Hash) ? (modifications[:objects] || modifications["objects"]) : modifications
|
|
210
|
+
parts = []
|
|
211
|
+
(template || {}).each do |k, v|
|
|
212
|
+
parts << "template:#{k}=#{URI.encode_www_form_component(v)}"
|
|
213
|
+
end
|
|
214
|
+
(objects || []).each do |obj|
|
|
215
|
+
name = obj[:name] || obj["name"]
|
|
216
|
+
obj.each do |k, v|
|
|
217
|
+
key = k.to_s
|
|
218
|
+
next if key == "name"
|
|
219
|
+
parts << "#{name}:#{key}=#{URI.encode_www_form_component(v)}"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
"#{base_url}?#{parts.join('&')}"
|
|
223
|
+
else
|
|
224
|
+
raise ArgumentError, "unknown instant URL mode: #{mode.inspect}"
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
signing_key = payload[:signing_key]
|
|
228
|
+
return url if signing_key.nil? || signing_key.empty?
|
|
229
|
+
sig = OpenSSL::HMAC.hexdigest("SHA256", signing_key, url)
|
|
230
|
+
"#{url}&s=#{sig}"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
private
|
|
235
|
+
|
|
236
|
+
BB_API_ENDPOINT = "https://api.bannerbear.com/v5"
|
|
237
|
+
BB_API_ENDPOINT_SYNCHRONOUS = "https://sync.api.bannerbear.com/v5"
|
|
238
|
+
|
|
239
|
+
def get_response(url)
|
|
240
|
+
response = HTTParty.get("#{BB_API_ENDPOINT}#{url}", timeout: 3, headers: { 'Authorization' => "Bearer #{@api_key}" })
|
|
241
|
+
body = JSON.parse(response.body)
|
|
242
|
+
return {"error" => body['message'], "code" => response.code} if response.code >= 400
|
|
243
|
+
return body
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def patch_response(url, payload)
|
|
247
|
+
response = HTTParty.patch("#{BB_API_ENDPOINT}#{url}",
|
|
248
|
+
body: payload.to_json,
|
|
249
|
+
timeout: 5,
|
|
250
|
+
headers: {
|
|
251
|
+
'Authorization' => "Bearer #{@api_key}",
|
|
252
|
+
'Content-Type' => 'application/json'
|
|
253
|
+
}
|
|
254
|
+
)
|
|
255
|
+
body = JSON.parse(response.body)
|
|
256
|
+
return {"error" => body['message'], "code" => response.code} if response.code >= 400
|
|
257
|
+
return body
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def post_response(url, payload, sync = false)
|
|
261
|
+
endpoint = BB_API_ENDPOINT
|
|
262
|
+
timeout = 5
|
|
263
|
+
if sync == true
|
|
264
|
+
endpoint = BB_API_ENDPOINT_SYNCHRONOUS
|
|
265
|
+
timeout = 10
|
|
266
|
+
end
|
|
267
|
+
response = HTTParty.post("#{endpoint}#{url}",
|
|
268
|
+
body: payload.to_json,
|
|
269
|
+
timeout: timeout,
|
|
270
|
+
headers: {
|
|
271
|
+
'Authorization' => "Bearer #{@api_key}",
|
|
272
|
+
'Content-Type' => 'application/json'
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
body = JSON.parse(response.body)
|
|
276
|
+
return {"error" => body['message'], "code" => response.code} if response.code >= 400
|
|
277
|
+
return body
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def upload_response(url, data, content_type)
|
|
281
|
+
response = HTTParty.post("#{BB_API_ENDPOINT}#{url}",
|
|
282
|
+
body: data,
|
|
283
|
+
timeout: 30,
|
|
284
|
+
headers: {
|
|
285
|
+
'Authorization' => "Bearer #{@api_key}",
|
|
286
|
+
'Content-Type' => content_type
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
body = JSON.parse(response.body)
|
|
290
|
+
return {"error" => body['message'], "code" => response.code} if response.code >= 400
|
|
291
|
+
return body
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def delete_response(url)
|
|
295
|
+
response = HTTParty.delete("#{BB_API_ENDPOINT}#{url}",
|
|
296
|
+
timeout: 5,
|
|
297
|
+
headers: { 'Authorization' => "Bearer #{@api_key}" }
|
|
298
|
+
)
|
|
299
|
+
body = response.body.to_s.empty? ? {} : JSON.parse(response.body)
|
|
300
|
+
return {"error" => body['message'], "code" => response.code} if response.code >= 400
|
|
301
|
+
return {"code" => response.code}
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
end
|
|
307
|
+
end
|
data/lib/bannerbear/version.rb
CHANGED
data/lib/bannerbear.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bannerbear
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jon Yongfook
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-08-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: httparty
|
|
@@ -43,6 +43,7 @@ files:
|
|
|
43
43
|
- bin/setup
|
|
44
44
|
- lib/bannerbear.rb
|
|
45
45
|
- lib/bannerbear/client.rb
|
|
46
|
+
- lib/bannerbear/v5/client.rb
|
|
46
47
|
- lib/bannerbear/version.rb
|
|
47
48
|
homepage: https://github.com/yongfook/bannerbear-ruby
|
|
48
49
|
licenses:
|
|
@@ -64,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
64
65
|
- !ruby/object:Gem::Version
|
|
65
66
|
version: '0'
|
|
66
67
|
requirements: []
|
|
67
|
-
rubygems_version: 3.
|
|
68
|
+
rubygems_version: 3.5.22
|
|
68
69
|
signing_key:
|
|
69
70
|
specification_version: 4
|
|
70
71
|
summary: Ruby wrapper for the Bannerbear API
|