bannerbear 0.2.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 850108dc51b0e1e90d1e7e6452ce69b65c972638e33d7f6b946f0f81ab4fe1cd
4
- data.tar.gz: f4c1e8883281527d9c515a07d358a251629ec19fb06a2dab4506fb95cfd9576a
3
+ metadata.gz: daa1514b2c5a5dddbef5836f7d64e3863932f4204e95a9625e7577b49dc70cad
4
+ data.tar.gz: 7a33d8e33c07d6b39561e6fbf6b64defafbb7d593a777bd1524f7623528e861c
5
5
  SHA512:
6
- metadata.gz: de72ce94b734dbcfc73ca872ce5b2ce9f41c23b6f5c1bf40e2cf78bf8e2938b81933ab20113c41b5e8ad0af2dc034ff251b696ef310219063ea066dfdbf759ba
7
- data.tar.gz: 92bfc10f330f5d318084b8e446491678029de74807ef353e2d679279590e22487780e15f4b093fa0bf374c8601fc6e685faf8d9d96f9fa4266f088638080186f
6
+ metadata.gz: 20c731f7f32fab1d5afc6fc6f85134febaacd2a386b3e86c526a0c2134a099272f8d98560ca469129f3300286db7439ff8f374e8bd62d1217c985a23a770d06f
7
+ data.tar.gz: df3b49048aa06fb412a3715228ae2af463701267636d00cdf8a7ede033c0a77e6d1c3bd7229cfb84fe1aef3ed1d79123741e23240cedeaa4895b096ed2bd34e2
data/README.md CHANGED
@@ -31,6 +31,9 @@ For the **legacy V2 API**, see [Usage](#usage) below — that section is unchang
31
31
  - [Account (V5)](#account-v5)
32
32
  - [Image Templates (V5)](#image-templates-v5)
33
33
  - [Images (V5)](#images-v5)
34
+ - [Tools (V5)](#tools-v5)
35
+ - [Assets (V5)](#assets-v5)
36
+ - [Publications (V5)](#publications-v5)
34
37
  - [Batches (V5)](#batches-v5)
35
38
  - [Webhooks (V5)](#webhooks-v5)
36
39
  - [Instant URLs (V5)](#instant-urls-v5)
@@ -55,14 +58,38 @@ bb.account
55
58
 
56
59
  ### Image Templates (V5)
57
60
 
58
- V5 renames V2's `templates` resource to `image_templates`.
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.
59
62
 
60
63
  ```ruby
61
64
  bb.list_image_templates(page: 1)
62
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
+
63
79
  bb.update_image_template("template uid", name: "New Name", description: "...", tags: ["portrait"])
80
+ bb.delete_image_template("template uid")
64
81
  ```
65
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
+
66
93
  ### Images (V5)
67
94
 
68
95
  V5's `modifications` is an **object** with two sub-keys:
@@ -95,7 +122,7 @@ bb.create_image("template uid", sync: true, modifications: { objects: [...] })
95
122
  - `scale`: scale multiplier, 1–4 (`integer`)
96
123
  - `dpi`: DPI metadata (`integer`)
97
124
  - `quality`: quality control (`integer`)
98
- - `proxy`: proxy server for asset fetching (`string`)
125
+ - `proxy`: proxy and resize external images before rendering (`boolean`)
99
126
  - `metadata`: include any metadata to reference at a later point (`string`)
100
127
  - `version`: pin template version (`integer`)
101
128
  - `sync`: route to the sync host (`boolean`; Ruby-only, not sent to the API)
@@ -105,13 +132,112 @@ bb.get_image("image uid")
105
132
  bb.list_images(page: 1)
106
133
  ```
107
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
+
108
234
  ### Batches (V5)
109
235
 
110
236
  Generate multiple images in one request (up to 100).
111
237
 
112
238
  ```ruby
113
239
  bb.create_batch(
114
- type: "image",
240
+ type: "images",
115
241
  items: [
116
242
  { template: "template uid 1", modifications: { objects: [...] } },
117
243
  { template: "template uid 2", modifications: { objects: [...] } }
@@ -132,7 +258,7 @@ hook = bb.create_webhook(
132
258
  resource: "image",
133
259
  event: "completed",
134
260
  status: "active",
135
- scope: "all",
261
+ scope: "all_templates",
136
262
  templates: []
137
263
  )
138
264
 
@@ -141,6 +267,16 @@ hook = bb.create_webhook(
141
267
  puts hook["signing_key"]
142
268
  ```
143
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
+
144
280
  CRUD:
145
281
 
146
282
  ```ruby
@@ -151,7 +287,7 @@ bb.update_webhook("webhook uid",
151
287
  resource: "image",
152
288
  event: "completed",
153
289
  status: "active",
154
- scope: "all"
290
+ scope: "all_templates"
155
291
  )
156
292
  bb.delete_webhook("webhook uid")
157
293
  bb.list_webhooks(page: 1)
@@ -21,8 +21,16 @@ module Bannerbear
21
21
  get_response "/image_templates/#{uid}"
22
22
  end
23
23
 
24
+ def create_image_template(payload = {})
25
+ post_response "/image_templates", payload.slice(:name, :description, :tags, :width, :height, :config)
26
+ end
27
+
24
28
  def update_image_template(uid, payload = {})
25
- patch_response "/image_templates/#{uid}", payload.slice(:name, :description, :tags)
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}"
26
34
  end
27
35
 
28
36
  # Images
@@ -39,6 +47,92 @@ module Bannerbear
39
47
  post_response "/images", payload.slice(:modifications, :formats, :scale, :dpi, :quality, :proxy, :metadata, :version).merge({:template => uid}), payload[:sync]
40
48
  end
41
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
+
42
136
  # Batches
43
137
 
44
138
  def list_batches(params = {})
@@ -183,6 +277,20 @@ module Bannerbear
183
277
  return body
184
278
  end
185
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
+
186
294
  def delete_response(url)
187
295
  response = HTTParty.delete("#{BB_API_ENDPOINT}#{url}",
188
296
  timeout: 5,
@@ -1,3 +1,3 @@
1
1
  module Bannerbear
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
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.2.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: 2026-05-13 00:00:00.000000000 Z
11
+ date: 2026-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty