pictify 1.0.0 → 1.1.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/CHANGELOG.md +25 -0
- data/README.md +56 -0
- data/lib/pictify/client.rb +166 -9
- data/lib/pictify/errors.rb +11 -3
- data/lib/pictify/types.rb +93 -0
- data/lib/pictify/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f414dc090d1668bcca2f5b240dbfdd0b1b1c1463dfddfe332f40dd1acd0ad2a
|
|
4
|
+
data.tar.gz: 1404b77c7a5acc442a1345b62bcb4cafc7140799f748ae6ee277b3df992f4651
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eacf2d2150cc55bb131fface0a92de28e04bab52df5873a8765508e96d79d6a08739e38cc1bf34b3a226c209e3c75afbc955a712863c15ded0b035185ae9434e
|
|
7
|
+
data.tar.gz: a82cc93aafbf0b99985550d2dc476e3a33d7a38faf848a4d09b82bffc546211b5dfcd87deeba8d00c46e2467679e741195dbcff4342e558ef91dc44d0ec7b883
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.0] - 2026-08-03
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Video template surface (`/video/templates`):
|
|
13
|
+
- `list_video_templates` — `GET /video/templates`, unwraps `{ templates }`
|
|
14
|
+
into an array of `VideoTemplate`.
|
|
15
|
+
- `get_video_template_variables(template_id)` —
|
|
16
|
+
`GET /video/templates/:uid/variables`, returns `VideoTemplateVariables`.
|
|
17
|
+
- `render_video(template_id, variables:, format:, timeout:)` —
|
|
18
|
+
`POST /video/templates/:uid/render`, waits for the finished MP4/GIF and
|
|
19
|
+
returns `VideoRenderResult` (`url`, `duration_in_frames`, `format`).
|
|
20
|
+
- `generate_video_template(prompt:, width:, height:, duration_seconds:, brand_color:, timeout:)`
|
|
21
|
+
— `POST /video/templates/generate`, returns `GenerateVideoTemplateResult`
|
|
22
|
+
(`template`, `preview_url`).
|
|
23
|
+
- `create_video_template(name:, tsx:, width:, height:, fps:, duration_seconds:, timeout:)`
|
|
24
|
+
— `POST /video/templates` with `kind: "tsx"`, `status: "draft"` and
|
|
25
|
+
`durationInFrames = (duration_seconds * fps).round`. Invalid tsx fails the
|
|
26
|
+
server-side compile gate with a 422 `RenderError` carrying the compiler
|
|
27
|
+
errors and saves nothing (retries are safe).
|
|
28
|
+
- Per-request timeout override in the HTTP layer: video renders run minutes,
|
|
29
|
+
so the video methods default to their own timeouts (render 300s,
|
|
30
|
+
generate/create 180s) without changing the global `timeout` default.
|
|
31
|
+
`TimeoutError#timeout` now reports the effective per-call timeout.
|
|
32
|
+
|
|
8
33
|
## [1.0.0] - 2026-06-08
|
|
9
34
|
|
|
10
35
|
### Changed (BREAKING)
|
data/README.md
CHANGED
|
@@ -223,6 +223,62 @@ template = client.create_template(
|
|
|
223
223
|
puts template.uid
|
|
224
224
|
```
|
|
225
225
|
|
|
226
|
+
## Video Templates
|
|
227
|
+
|
|
228
|
+
Video templates render MP4s (or animated GIFs) server-side. Render requests
|
|
229
|
+
wait for the finished file and can legitimately run minutes — the video methods
|
|
230
|
+
use their own per-call timeouts (render 300s, generate/create 180s) instead of
|
|
231
|
+
the global `timeout`, and every method accepts a `timeout:` override.
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
# List video templates — GET /video/templates
|
|
235
|
+
templates = client.list_video_templates
|
|
236
|
+
templates.each { |t| puts "#{t.uid}: #{t.name} (#{t.kind}, #{t.duration_in_frames} frames)" }
|
|
237
|
+
|
|
238
|
+
# What variables can I set? — GET /video/templates/:uid/variables
|
|
239
|
+
vars = client.get_video_template_variables("video-template-uid")
|
|
240
|
+
vars.variables.each { |v| puts " - #{v.name} (#{v.type}, default: #{v.default_value})" }
|
|
241
|
+
|
|
242
|
+
# Render to MP4 (or GIF) — POST /video/templates/:uid/render
|
|
243
|
+
# Omitted variables render their defaults. Each render consumes one video credit.
|
|
244
|
+
video = client.render_video(
|
|
245
|
+
"video-template-uid",
|
|
246
|
+
variables: { title: "Welcome, Maya!" },
|
|
247
|
+
format: :mp4 # :mp4 (default) or :gif — GIF is capped at 15fps / 720px wide
|
|
248
|
+
)
|
|
249
|
+
puts video.url
|
|
250
|
+
puts video.duration_in_frames
|
|
251
|
+
puts video.format
|
|
252
|
+
|
|
253
|
+
# Generate a template from a prompt with AI — POST /video/templates/generate
|
|
254
|
+
# Takes 30-60 seconds; metered as one render.
|
|
255
|
+
result = client.generate_video_template(
|
|
256
|
+
prompt: "A 8s product launch teaser for a coffee brand, warm and bold",
|
|
257
|
+
width: 1080,
|
|
258
|
+
height: 1080,
|
|
259
|
+
duration_seconds: 8,
|
|
260
|
+
brand_color: "#ff0055" # optional
|
|
261
|
+
)
|
|
262
|
+
puts result.template.uid
|
|
263
|
+
puts result.preview_url
|
|
264
|
+
|
|
265
|
+
# Upload a Remotion scene you wrote — POST /video/templates
|
|
266
|
+
# The tsx must export a zod `schema` (flat fields with defaults — they become
|
|
267
|
+
# the template's variables) and a default React component; imports limited to
|
|
268
|
+
# remotion, react and zod. It passes a compile gate BEFORE anything is saved:
|
|
269
|
+
# invalid tsx raises a 422 Pictify::RenderError carrying the compiler errors
|
|
270
|
+
# (err.errors) and creates NOTHING — retrying is always safe.
|
|
271
|
+
template = client.create_video_template(
|
|
272
|
+
name: "My Scene",
|
|
273
|
+
tsx: tsx_source,
|
|
274
|
+
width: 1080,
|
|
275
|
+
height: 1080,
|
|
276
|
+
fps: 30,
|
|
277
|
+
duration_seconds: 8 # durationInFrames is computed as (seconds * fps).round
|
|
278
|
+
)
|
|
279
|
+
puts template.uid
|
|
280
|
+
```
|
|
281
|
+
|
|
226
282
|
## Error Handling
|
|
227
283
|
|
|
228
284
|
```ruby
|
data/lib/pictify/client.rb
CHANGED
|
@@ -25,6 +25,11 @@ module Pictify
|
|
|
25
25
|
DEFAULT_TIMEOUT = 30
|
|
26
26
|
DEFAULT_MAX_RETRIES = 3
|
|
27
27
|
|
|
28
|
+
# Video renders legitimately run minutes; these per-call defaults override
|
|
29
|
+
# the global +timeout+ for the video endpoints only.
|
|
30
|
+
DEFAULT_VIDEO_RENDER_TIMEOUT = 300
|
|
31
|
+
DEFAULT_VIDEO_TEMPLATE_TIMEOUT = 180
|
|
32
|
+
|
|
28
33
|
# @param api_key [String] Your Pictify API key
|
|
29
34
|
# @param base_url [String] Custom API base URL
|
|
30
35
|
# @param timeout [Integer] Request timeout in seconds
|
|
@@ -288,24 +293,171 @@ module Pictify
|
|
|
288
293
|
Template.new(response["template"] || {})
|
|
289
294
|
end
|
|
290
295
|
|
|
296
|
+
# ------------------------------------------------------------------------
|
|
297
|
+
# Video templates
|
|
298
|
+
# ------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
# List your video templates.
|
|
301
|
+
#
|
|
302
|
+
# +GET /video/templates+ — unwraps the +{ templates }+ envelope.
|
|
303
|
+
#
|
|
304
|
+
# @return [Array<VideoTemplate>]
|
|
305
|
+
def list_video_templates
|
|
306
|
+
response = request(:get, "video/templates")
|
|
307
|
+
(response["templates"] || []).map { |t| VideoTemplate.new(t) }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# A video template's variable definitions — what you can set when
|
|
311
|
+
# rendering it. Call before {#render_video} to know what to pass.
|
|
312
|
+
#
|
|
313
|
+
# +GET /video/templates/:uid/variables+.
|
|
314
|
+
#
|
|
315
|
+
# @param template_id [String] The video template UID
|
|
316
|
+
# @return [VideoTemplateVariables]
|
|
317
|
+
def get_video_template_variables(template_id)
|
|
318
|
+
response = request(:get, "video/templates/#{encode(template_id)}/variables")
|
|
319
|
+
VideoTemplateVariables.new(response)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Render a video template to MP4 — or an animated GIF — with variables.
|
|
323
|
+
#
|
|
324
|
+
# +POST /video/templates/:uid/render+ — the request WAITS for the finished
|
|
325
|
+
# file (up to a few minutes) and returns its hosted URL. Each render
|
|
326
|
+
# consumes one video credit. Omitted variables render their defaults;
|
|
327
|
+
# unknown variable names fail with a 422 {RenderError}.
|
|
328
|
+
#
|
|
329
|
+
# @example
|
|
330
|
+
# video = client.render_video("abc123", variables: { title: "Welcome, Maya!" }, format: :gif)
|
|
331
|
+
# puts video.url
|
|
332
|
+
#
|
|
333
|
+
# @param template_id [String] The video template UID (see {#list_video_templates})
|
|
334
|
+
# @param variables [Hash] Variables to inject; omitted variables render their defaults
|
|
335
|
+
# @param format [Symbol, String] Output format (default mp4). +gif+ is a
|
|
336
|
+
# palette-optimised animated GIF capped at 15fps / 720px wide
|
|
337
|
+
# @param timeout [Integer, nil] Per-call timeout in seconds (default 300 —
|
|
338
|
+
# video renders take minutes; the global +timeout+ is not used here)
|
|
339
|
+
# @return [VideoRenderResult]
|
|
340
|
+
def render_video(template_id, variables: {}, format: "mp4", timeout: nil)
|
|
341
|
+
response = request(:post, "video/templates/#{encode(template_id)}/render", {
|
|
342
|
+
variables: variables || {},
|
|
343
|
+
format: (format || :mp4).to_s
|
|
344
|
+
}, nil, timeout: timeout || DEFAULT_VIDEO_RENDER_TIMEOUT)
|
|
345
|
+
VideoRenderResult.new(response)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Generate a new video template from a prompt using AI.
|
|
349
|
+
#
|
|
350
|
+
# +POST /video/templates/generate+ — the service designs a motion brief,
|
|
351
|
+
# writes the scene as code, compiles it, renders preview frames and reviews
|
|
352
|
+
# them visually — then saves a draft template whose texts, colors and
|
|
353
|
+
# optional image are editable variables. Takes 30-60 seconds; metered as
|
|
354
|
+
# one render.
|
|
355
|
+
#
|
|
356
|
+
# @param prompt [String] What the video is for, with any mood/style
|
|
357
|
+
# guidance (max 2000 chars)
|
|
358
|
+
# @param width [Integer] Canvas width in pixels (default 1080)
|
|
359
|
+
# @param height [Integer] Canvas height in pixels (default 1080)
|
|
360
|
+
# @param duration_seconds [Numeric] Video length in seconds, 1-60 (default 8)
|
|
361
|
+
# @param brand_color [String, nil] Optional brand color (hex) to build the
|
|
362
|
+
# palette around
|
|
363
|
+
# @param timeout [Integer, nil] Per-call timeout in seconds (default 180)
|
|
364
|
+
# @return [GenerateVideoTemplateResult]
|
|
365
|
+
def generate_video_template(prompt:, width: 1080, height: 1080, duration_seconds: 8,
|
|
366
|
+
brand_color: nil, timeout: nil)
|
|
367
|
+
response = request(:post, "video/templates/generate", {
|
|
368
|
+
prompt: prompt,
|
|
369
|
+
width: width,
|
|
370
|
+
height: height,
|
|
371
|
+
durationSeconds: duration_seconds,
|
|
372
|
+
brandColor: brand_color
|
|
373
|
+
}, nil, timeout: timeout || DEFAULT_VIDEO_TEMPLATE_TIMEOUT)
|
|
374
|
+
GenerateVideoTemplateResult.new(response)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Upload a Remotion scene you wrote as a new video template.
|
|
378
|
+
#
|
|
379
|
+
# +POST /video/templates+ — unwraps the +{ template }+ envelope. The SDK
|
|
380
|
+
# sends +kind: "tsx"+, +status: "draft"+ and computes
|
|
381
|
+
# +durationInFrames = (duration_seconds * fps).round+.
|
|
382
|
+
#
|
|
383
|
+
# The source passes a compile gate BEFORE anything is saved: invalid tsx
|
|
384
|
+
# rejects with a 422 {RenderError} carrying the compiler errors
|
|
385
|
+
# (+error.errors+) and creates NOTHING, so retrying cannot litter the
|
|
386
|
+
# account with broken templates.
|
|
387
|
+
#
|
|
388
|
+
# @param name [String] Template name shown in the dashboard
|
|
389
|
+
# @param tsx [String] The complete single-file Remotion scene source. Must
|
|
390
|
+
# export a zod +schema+ (flat fields with defaults — they become the
|
|
391
|
+
# template's variables) and a default React component; imports limited to
|
|
392
|
+
# +remotion+, +react+ and +zod+
|
|
393
|
+
# @param width [Integer] Canvas width in pixels (default 1080)
|
|
394
|
+
# @param height [Integer] Canvas height in pixels (default 1080)
|
|
395
|
+
# @param fps [Integer] Frames per second (default 30)
|
|
396
|
+
# @param duration_seconds [Numeric] Video length in seconds (default 8)
|
|
397
|
+
# @param timeout [Integer, nil] Per-call timeout in seconds (default 180 —
|
|
398
|
+
# the compile gate bundles the scene)
|
|
399
|
+
# @return [VideoTemplate]
|
|
400
|
+
def create_video_template(name:, tsx:, width: 1080, height: 1080, fps: 30,
|
|
401
|
+
duration_seconds: 8, timeout: nil)
|
|
402
|
+
response = request(:post, "video/templates", {
|
|
403
|
+
name: name,
|
|
404
|
+
kind: "tsx",
|
|
405
|
+
tsx: tsx,
|
|
406
|
+
width: width,
|
|
407
|
+
height: height,
|
|
408
|
+
fps: fps,
|
|
409
|
+
durationInFrames: (duration_seconds * fps).round,
|
|
410
|
+
status: "draft"
|
|
411
|
+
}, nil, timeout: timeout || DEFAULT_VIDEO_TEMPLATE_TIMEOUT)
|
|
412
|
+
VideoTemplate.new(response["template"] || {})
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Runs between the retry middleware and the adapter. The net_http adapter
|
|
416
|
+
# wraps +Net::OpenTimeout+ as +Faraday::ConnectionFailed+, which would slip
|
|
417
|
+
# a timeout past a retry exception list that (deliberately) excludes
|
|
418
|
+
# timeouts — so timeout-flavored connection failures are re-raised as
|
|
419
|
+
# +Faraday::TimeoutError+ before the retry middleware sees them.
|
|
420
|
+
class TimeoutClassifier < Faraday::Middleware
|
|
421
|
+
TIMEOUT_HINTS = ["timeout", "timed out", "execution expired"].freeze
|
|
422
|
+
|
|
423
|
+
def call(env)
|
|
424
|
+
@app.call(env)
|
|
425
|
+
rescue Faraday::ConnectionFailed => e
|
|
426
|
+
raise Faraday::TimeoutError, e if e.cause.is_a?(Timeout::Error) ||
|
|
427
|
+
TIMEOUT_HINTS.any? { |hint| e.message.to_s.downcase.include?(hint) }
|
|
428
|
+
|
|
429
|
+
raise
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
|
|
291
433
|
private
|
|
292
434
|
|
|
293
435
|
def encode(value)
|
|
294
|
-
CGI.escape(
|
|
436
|
+
# escapeURIComponent, not escape: CGI.escape is form encoding (space
|
|
437
|
+
# becomes "+", which the server decodes as a literal plus in a path).
|
|
438
|
+
CGI.escapeURIComponent(value.to_s)
|
|
295
439
|
end
|
|
296
440
|
|
|
297
441
|
def connection
|
|
298
442
|
@connection ||= Faraday.new(url: @base_url) do |f|
|
|
299
443
|
f.request :retry,
|
|
300
444
|
max: @max_retries,
|
|
301
|
-
interval:
|
|
445
|
+
interval: 1,
|
|
302
446
|
backoff_factor: 2,
|
|
303
447
|
retry_statuses: [500, 502, 503, 504],
|
|
304
|
-
#
|
|
305
|
-
#
|
|
306
|
-
#
|
|
307
|
-
|
|
448
|
+
# An EXPLICIT exception list. faraday-retry's defaults include
|
|
449
|
+
# Faraday::TimeoutError and Timeout::Error, and with POST
|
|
450
|
+
# opted in a timing-out render was retried — but generation
|
|
451
|
+
# endpoints are billed, non-idempotent POSTs and the server
|
|
452
|
+
# keeps rendering (and meters) after the client aborts, so a
|
|
453
|
+
# timed-out call could bill max_retries+1 credits. Timeouts
|
|
454
|
+
# must surface immediately (TimeoutClassifier below keeps
|
|
455
|
+
# them out of ConnectionFailed); genuine connection failures
|
|
456
|
+
# stay retryable, and RetriableResponse keeps retry_statuses
|
|
457
|
+
# engaged. 4xx never retry.
|
|
458
|
+
exceptions: [Faraday::ConnectionFailed, Faraday::RetriableResponse],
|
|
308
459
|
methods: %i[get post put delete patch]
|
|
460
|
+
f.use TimeoutClassifier
|
|
309
461
|
f.options.timeout = @timeout
|
|
310
462
|
f.options.open_timeout = 10
|
|
311
463
|
f.headers["Authorization"] = "Bearer #{@api_key}"
|
|
@@ -316,9 +468,14 @@ module Pictify
|
|
|
316
468
|
|
|
317
469
|
# Make an authenticated JSON request. Nil body fields are stripped so the
|
|
318
470
|
# backend applies its own defaults. HTTP errors are mapped to typed errors.
|
|
319
|
-
|
|
471
|
+
#
|
|
472
|
+
# +timeout+ overrides the global read timeout for this request only (video
|
|
473
|
+
# renders legitimately run minutes); the client-wide default is untouched.
|
|
474
|
+
def request(method, path, body = nil, params = nil, timeout: nil)
|
|
475
|
+
effective_timeout = timeout || @timeout
|
|
320
476
|
response = connection.send(method) do |req|
|
|
321
477
|
req.url(path, params)
|
|
478
|
+
req.options.timeout = timeout if timeout
|
|
322
479
|
req.body = JSON.generate(strip_nils(body)) if body
|
|
323
480
|
end
|
|
324
481
|
|
|
@@ -329,10 +486,10 @@ module Pictify
|
|
|
329
486
|
env = e.response
|
|
330
487
|
raise Pictify.error_from_response(env.status, error_body(env.body))
|
|
331
488
|
rescue Faraday::TimeoutError
|
|
332
|
-
raise TimeoutError.new("Request timed out", timeout:
|
|
489
|
+
raise TimeoutError.new("Request timed out", timeout: effective_timeout)
|
|
333
490
|
rescue Faraday::ConnectionFailed => e
|
|
334
491
|
if e.message.include?("timeout") || e.message.include?("timed out") || e.message.include?("execution expired")
|
|
335
|
-
raise TimeoutError.new("Request timed out", timeout:
|
|
492
|
+
raise TimeoutError.new("Request timed out", timeout: effective_timeout)
|
|
336
493
|
end
|
|
337
494
|
|
|
338
495
|
raise NetworkError.new(nil, original_error: e)
|
data/lib/pictify/errors.rb
CHANGED
|
@@ -129,8 +129,9 @@ module Pictify
|
|
|
129
129
|
#
|
|
130
130
|
# The Pictify API has no unified error envelope: image/GIF endpoints return
|
|
131
131
|
# +{ error, code }+ while template/CRUD endpoints return +{ message }+ or
|
|
132
|
-
# +{ message, errors }+. Message precedence is +error+ then +message+ then
|
|
133
|
-
#
|
|
132
|
+
# +{ message, errors }+. Message precedence is +error+ then +message+ then
|
|
133
|
+
# the joined +errors+ strings (the video compile gate's 422 shape, where the
|
|
134
|
+
# strings are the fix instructions) then a fallback.
|
|
134
135
|
#
|
|
135
136
|
# Status mapping:
|
|
136
137
|
# - 401 -> AuthenticationError
|
|
@@ -142,7 +143,14 @@ module Pictify
|
|
|
142
143
|
# - 5xx -> ServerError
|
|
143
144
|
def self.error_from_response(status_code, body)
|
|
144
145
|
body ||= {}
|
|
145
|
-
|
|
146
|
+
# The video compile gate returns 422 { errors: ["..."] } with no
|
|
147
|
+
# message/error key — those strings ARE the fix instructions, so they
|
|
148
|
+
# join into the message rather than leaving a generic fallback.
|
|
149
|
+
errors = body["errors"]
|
|
150
|
+
joined_errors = if errors.is_a?(Array) && !errors.empty? && errors.all? { |e| e.is_a?(String) }
|
|
151
|
+
errors.join("; ")
|
|
152
|
+
end
|
|
153
|
+
message = body["error"] || body["message"] || joined_errors || "An unexpected error occurred"
|
|
146
154
|
|
|
147
155
|
case status_code
|
|
148
156
|
when 401
|
data/lib/pictify/types.rb
CHANGED
|
@@ -12,6 +12,12 @@ module Pictify
|
|
|
12
12
|
# GIF quality presets accepted by the +/gif+ endpoint.
|
|
13
13
|
GIF_QUALITIES = %i[low medium high].freeze
|
|
14
14
|
|
|
15
|
+
# Video output formats supported by video template renders.
|
|
16
|
+
#
|
|
17
|
+
# +gif+ produces a palette-optimised animated GIF capped at 15fps / 720px
|
|
18
|
+
# wide — the same render, encoded for places an MP4 cannot autoplay.
|
|
19
|
+
VIDEO_FORMATS = %i[mp4 gif].freeze
|
|
20
|
+
|
|
15
21
|
# Result of an +/image+ render (+render_html+ / +render_url+).
|
|
16
22
|
#
|
|
17
23
|
# Maps the API response +{ url, id, createdAt }+.
|
|
@@ -253,4 +259,91 @@ module Pictify
|
|
|
253
259
|
@pagination = data["pagination"] ? Pagination.new(data["pagination"]) : nil
|
|
254
260
|
end
|
|
255
261
|
end
|
|
262
|
+
|
|
263
|
+
# A video template returned by +list_video_templates+ / +create_video_template+
|
|
264
|
+
# (and nested in +generate_video_template+ results).
|
|
265
|
+
#
|
|
266
|
+
# +kind+ is +"timeline"+ (studio-built) or +"tsx"+ (Remotion scene) — both
|
|
267
|
+
# render identically. The API keys video templates by +uid+ and declares
|
|
268
|
+
# variables in +variableDefinitions+. Unknown fields are kept in +raw+.
|
|
269
|
+
class VideoTemplate
|
|
270
|
+
attr_reader :uid, :name, :kind, :width, :height, :fps, :duration_in_frames,
|
|
271
|
+
:poster_url, :status, :variable_definitions, :created_at,
|
|
272
|
+
:updated_at, :raw
|
|
273
|
+
|
|
274
|
+
def initialize(data)
|
|
275
|
+
@raw = data
|
|
276
|
+
@uid = data["uid"]
|
|
277
|
+
@name = data["name"]
|
|
278
|
+
@kind = data["kind"]
|
|
279
|
+
@width = data["width"]
|
|
280
|
+
@height = data["height"]
|
|
281
|
+
@fps = data["fps"]
|
|
282
|
+
@duration_in_frames = data["durationInFrames"]
|
|
283
|
+
@poster_url = data["posterUrl"]
|
|
284
|
+
@status = data["status"]
|
|
285
|
+
@variable_definitions = (data["variableDefinitions"] || []).map do |v|
|
|
286
|
+
TemplateVariableDefinition.new(v)
|
|
287
|
+
end
|
|
288
|
+
@created_at = parse_time(data["createdAt"])
|
|
289
|
+
@updated_at = parse_time(data["updatedAt"])
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
private
|
|
293
|
+
|
|
294
|
+
def parse_time(value)
|
|
295
|
+
return nil unless value
|
|
296
|
+
|
|
297
|
+
Time.parse(value)
|
|
298
|
+
rescue ArgumentError, TypeError
|
|
299
|
+
nil
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# A video template's variable definitions (+get_video_template_variables+) —
|
|
304
|
+
# what you can set when rendering it.
|
|
305
|
+
#
|
|
306
|
+
# Maps +{ templateUid, templateName, kind, variables: [...], referenced: [...] }+.
|
|
307
|
+
# +referenced+ lists every variable name the document actually references.
|
|
308
|
+
class VideoTemplateVariables
|
|
309
|
+
attr_reader :template_uid, :template_name, :kind, :variables, :referenced, :raw
|
|
310
|
+
|
|
311
|
+
def initialize(data)
|
|
312
|
+
@raw = data
|
|
313
|
+
@template_uid = data["templateUid"]
|
|
314
|
+
@template_name = data["templateName"]
|
|
315
|
+
@kind = data["kind"]
|
|
316
|
+
@variables = (data["variables"] || []).map { |v| TemplateVariableDefinition.new(v) }
|
|
317
|
+
@referenced = data["referenced"] || []
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Result of a video template render (+render_video+).
|
|
322
|
+
#
|
|
323
|
+
# Maps +{ url, durationInFrames, format }+. +format+ echoes what was actually
|
|
324
|
+
# produced ("mp4" or "gif").
|
|
325
|
+
class VideoRenderResult
|
|
326
|
+
attr_reader :url, :duration_in_frames, :format, :raw
|
|
327
|
+
|
|
328
|
+
def initialize(data)
|
|
329
|
+
@raw = data
|
|
330
|
+
@url = data["url"]
|
|
331
|
+
@duration_in_frames = data["durationInFrames"]
|
|
332
|
+
@format = data["format"]
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Result of AI video template generation (+generate_video_template+).
|
|
337
|
+
#
|
|
338
|
+
# Maps +{ template: {...}, previewUrl }+ — the saved draft {VideoTemplate}
|
|
339
|
+
# plus a rendered preview frame of the generated scene (may be +nil+).
|
|
340
|
+
class GenerateVideoTemplateResult
|
|
341
|
+
attr_reader :template, :preview_url, :raw
|
|
342
|
+
|
|
343
|
+
def initialize(data)
|
|
344
|
+
@raw = data
|
|
345
|
+
@template = VideoTemplate.new(data["template"] || {})
|
|
346
|
+
@preview_url = data["previewUrl"]
|
|
347
|
+
end
|
|
348
|
+
end
|
|
256
349
|
end
|
data/lib/pictify/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pictify
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Pictify
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|