SuperAPI AI Directory

photo-slideshow

Design & creation

View repository

Updated 10-30 20:04

About this skill

Photo promo / slideshow

Assumes the font asset from picsart_scene_fonts (a live.fonts entry), and that picsart_scene_patch returns the patched scene you pass to the next tool.

The most common scene job: N photos, one after another, a transition at each seam, a title. Every value below was validated and rendered at 1080x1920; the whole scene is in the worked example.

Related recipes: picsart_scene_recipe scene-video-edits owns the track, transition-window and blur_fill detail; picsart_scene_recipe motion-intent chooses durations and easing.

1. Probe the photos

Call picsart_media_probe on every photo URL before authoring. Declare its width and height on the asset. Those numbers decide which fit each photo gets (section 3), and they let blur_fill skip its fill on a photo whose aspect already matches the composition.

"p2": { "type": "image", "uri": "https://…/507090463024201.jpg", "width": 2494, "height": 2560 }

Compare each photo's aspect (w/h) with the composition's (1080/1920 = 0.5625). cover crops the difference away: 2296x4080 (0.563) loses nothing visible, and a 2:3 portrait such as 2308x3462 (0.667) loses 16% of its width. A square or landscape photo such as 2494x2560 (0.974) would lose 42% of its width, so it gets a blurred fill instead.

2. A track of clips with transitions

One track layer holds the photos as clips, back to back. Each seam that should not be a hard cut gets one transitions[] entry keyed by between, the index of the gap (0 = between clips 0 and 1):

{ "id": "reel", "start": 0, "duration": 12, "content": { "kind": "track",
  "clips": [
    { "id": "c1", "duration": 3, "content": { "kind": "media", "assetId": "p1", "fit": "cover" } },
    { "id": "c2", "duration": 3, "content": { "kind": "media", "assetId": "p2" },
      "looks": [{ "look": "blur_fill", "params": { "blur": 8, "resFactor": 10, "dim": 0.25 } }] },
    { "id": "c3", "duration": 3, "content": { "kind": "media", "assetId": "p3", "fit": "cover" } },
    { "id": "c4", "duration": 3, "content": { "kind": "media", "assetId": "p4", "fit": "cover" } }
  ],
  "transitions": [
    { "between": 0, "id": "crossfade", "duration": 0.6 },
    { "between": 1, "id": "slide", "duration": 0.6, "params": { "direction": "left" } },
    { "between": 2, "id": "crossfade", "duration": 0.6 }
  ] } }
  • Timing. The track's duration is the sum of its clip durations (4 x 3 = 12). A transition does not shorten the reel. Its window is centred on the seam, so a 0.6s crossfade at the 3.0s seam runs from 2.7 to 3.3.
  • Duration rule. Each window takes half its duration out of each clip beside it. The halves a clip gives up to both of its seams must fit inside that clip, or validation refuses the scene with transition_window_exceeds_clip. With 3s photos and 0.6s transitions, an inner photo still has 2.4s on its own.
  • Choosing a transition. Read picsart_scene_capabilities({ sections: ["transitions"] }). A closed-set param lists its allowed enum values, and the validator refuses anything else. Keep one calm transition, crossfade, as the default, and spend a directional one (slide) on the seam that should read as "next". The rendered example's slide direction: "left" visibly moves through its window.
  • All seams the same? scene://montage (picsart_scene_capabilities, section template:scene://montage) binds a clip list plus one transition id and transitionDuration for every seam. Author the track by hand as above when seams differ or a transition takes params.

3. Cover, or contain with a blurred fill

Photo aspect vs composition Clip content
Portrait, cover loses under about a fifth of the photo "fit": "cover". It fills the frame and crops the overflow. Add align ("top", …) if the subject sits off-centre.
Clearly different (square, landscape) No fit, plus looks: [{ "look": "blur_fill", … }]. The whole photo is contained, and a blurred, cover-fitted copy of it fills the margins.

blur_fill with blur: 8, resFactor: 10, dim: 0.25 renders the fill as an out-of-focus copy of the photo under a light scrim that separates it from the contained photo. Fine detail softens, but large shapes stay recognisable: the example's tree trunks still read in the margins. Raising blur to its maximum of 32 halves the fill's edge detail, and the trunks still show. The fill is a soft backdrop, not an abstract wash. Ranges and defaults are in the looks capability section. A look on a clip is written on the clip itself, as above, and the clip keeps its transitions.

4. The title and its timing

A text layer above the track. Keep the entrance editable as a preset reference in content.animations, or bake it with picsart_scene_apply_preset (textAnimation:<name>). Both render identically.

{ "id": "title", "start": 0.5, "duration": 2.1, "transform": { "position": [540, 1400] },
  "animations": { "opacity": { "keyframes": [ { "time": 2.3, "value": 1 }, { "time": 2.6, "value": 0 } ] } },
  "content": { "kind": "text", "text": "OUR WINTER",
    "font": { "family": "gilroy-bold", "size": 132 }, "color": "#ffffff", "alignment": "center",
    "animations": [ { "preset": "fade_in_chars", "params": { "duration": 0.8, "stagger": 0.4 } } ] },
  "effects": [ { "id": "drop_shadow", "params": { "offset": [0, 6], "blur": 24, "color": "#000000b3" } } ] }
  • An entrance starts at its layer's local time 0. No text-animation preset takes a delay. To reveal later, move the layer: start: 0.5 begins the reveal at 0.5s. Change start and duration together. Rendered: no ink at 0.5, partial at 0.9, the whole title by 1.3.
  • Layer keyframes are composition seconds. A layer's own animations keyframes (the opacity exit 2.3 → 2.6) are composition time by default, unlike the text preset's clock. The validator warns (keyframe_time_outside_window) when one falls outside the layer's window.
  • Gone before the first seam, by choice. The example's title is an opening card over the first photo, so it leaves before anything moves under it. The layer ends at 0.5 + 2.1 = 2.6s, and the first seam's window opens at 2.7s (0.6s centred on 3.0). Rendered at 2.65: no title and no scrim. To keep a title up for the whole promo, give it and its scrim the reel's window instead. The scrim then has to hold over every photo, including the brightest (section 5).
  • Wrap at a column you declare. wrapWidth: 900 wraps a long title inside the 900px between 90px side gutters. Without wrapWidth a line wraps at the composition's full width, so it can run edge to edge. "OUR WINTER" at 132px gilroy-bold draws 776px of ink, one line inside 900px.

5. Readability over photos

White text over a photo is only as readable as the brightest photo it can sit on. Measured over the brightest photo in the example (snow; background median luminance 0.59), with white gilroy-bold 132px, against the median background pixel under the title (brighter patches fare worse). The scrim and plate rows keep the shadow on:

Treatment Contrast vs background
none 1.6 : 1
drop_shadow offset [0,6], blur 24, #000000b3 2.0 : 1
gradient scrim, alpha 0 → 0.6 at the text line → 0.6 at the bottom 4.0 : 1
backing plate: 860x190 rect, roundness 28, black fill opacity 0.55 4.0 : 1

Rules that follow:

  • A shadow defines edges; it does not make text readable on a bright photo. Keep the shadow for crispness, and put a scrim or a plate under any title that can land on a light photo. The large-text floor is 3:1, and both treatments clear it.
  • Scrim. A full-width shape layer beneath the title, with exactly the title's window (start 0.5, duration 2.1) and its own opacity fades at both ends:
{ "id": "scrim", "start": 0.5, "duration": 2.1, "transform": { "position": [540, 1420] },
  "animations": { "opacity": { "keyframes": [
    { "time": 0.5, "value": 0 }, { "time": 0.8, "value": 1 }, { "time": 2.3, "value": 1 }, { "time": 2.6, "value": 0 } ] } },
  "content": { "kind": "shape", "items": [
    { "type": "rect", "position": [0, 0], "size": [1080, 1000] },
    { "type": "gradientFill", "gradientType": "linear", "startPoint": [0, -500], "endPoint": [0, 500],
      "colorStops": [ { "offset": 0, "color": "#000000" }, { "offset": 1, "color": "#000000" } ],
      "alphaStops": [ { "offset": 0, "alpha": 0 }, { "offset": 0.5, "alpha": 0.6 }, { "offset": 1, "alpha": 0.6 } ] } ] } }
  • Placement. A common convention for 9:16 social output is to keep text inside x 90–990 and y 250–1540. The example's title ink spans x 153–928 and y 1351–1447.

6. Fonts

font.family is a NAME. Search the catalog with picsart_scene_fonts({ query: "gilroy" }) and put a returned key in font.family. The example uses gilroy-bold, which that query returns and which is also in the list picsart_scene_fonts() gives with no query. Pass licenseType: "commercial" to list only faces cleared for commercial use; personal-licence faces are listed too otherwise. The renderer has no system fonts, so a CSS name such as Inter is refused with unknown_font_family, and the refusal lists the closest keys.

For a face the catalog lacks, the same picsart_scene_fonts({ query }) call returns it in live.fonts. Each entry carries asset, a ready-made font asset: add it to assets under an id, and name that id:

"assets": { "display": { "type": "font", "name": "Casual Fonts", "uri": "https://cdn111.picsart.com/230454663055900.ttf" } },
… "font": { "family": "display", "size": 64 }

A bare .ttf URL in font.family also renders, but it loses the font's name.

7. Keep the context lean

Author the scene once. Then edit it with picsart_scene_patch ops anchored on ids ({ op: "set", layer: "title", path: "content/text", value: "…" }) rather than re-sending the document. A patch returns the patched scene as scene: pass it as the next tool's scene (patch, validate, contact sheet, export). A successful patch has already been validated, so don't validate again until you hand-edit.

8. Verify with one sweep

Ask for the decisive moments in as few calls as possible: picsart_scene_contact_sheet with times: [t1, t2, …] and resolution set to the scene's composition.width and composition.height. Without resolution the sheet returns downscaled thumbnails, which are not for judging position. The sheet image scales every frame to fit one picture; to judge full-resolution detail, open a frame's url. The example has twelve moments, so it takes two calls, both with resolution: { width: 1080, height: 1920 }:

times: [0.5, 0.9, 1.3, 1.5, 2.65, 3.0]
times: [4.5, 6.0, 7.5, 9.0, 10.5, 11.967]
Times What they prove
0.5, 0.9, 1.3 The title's reveal: nothing, partial, whole
1.5, 4.5, 7.5, 10.5 Clip midpoints: the fit / blur_fill framing of each photo
2.65 Title and scrim are gone before the first seam's window (2.7) opens
3.0, 6.0, 9.0 Seam centres: each transition is mid-flight, not a hard cut
11.967 The last frame (duration − 1/fps): the reel ends on a photo, not on black

A seam centre that shows only one photo is a hard cut. Before you re-author anything, check the transition's values.