Skip to main content

Compose App Store marketing screenshots from raw captures, backgrounds, and localized captions

Project description

appshots

CI

Compose App Store marketing screenshots from raw captures, background images, and localized captions — declaratively, from one config.json.

Raw capture Composed screenshot
before after

One Python file, one dependency (Pillow), one CLI. Change a caption or swap a screenshot, re-run one command, and every device size and every language regenerates — no Figma, no per-locale hand editing.

  • Localized captions"caption": {"en": ..., "ru": ...} renders a full per-locale screenshot set, laid out the way fastlane deliver uploads it
  • Device presets"iphone-6.9" instead of memorizing pixel sizes; warns when a custom size isn't one App Store Connect accepts
  • Auto-fitting text — long translations shrink to fit instead of overflowing
  • Glyph safety — a Latin-only font with a Cyrillic caption fails loudly with the missing characters listed, instead of rendering an empty title
  • Composition — background cover, vignette, device-corner rounding and bezel, drop shadows, caption stroke/shadow, pixel-art-safe nearest scaling

Install

pipx install git+https://github.com/DmitryKhryukin/appshots
# or run straight from a checkout:
pip install Pillow && python appshots.py --config CONFIG.json

Quick start

The repo ships a self-contained example (fake lorem-ipsum app, generated assets, EN + RU captions):

appshots --config example/config.json
open example/output/en/iphone-6.9-feed.png

Per-app folder layout

Each app keeps its own config and assets in its own folder (typically inside the app's repo):

my-app-screenshots/
├── config.json
├── fonts/MyFont.ttf
├── backgrounds/
│   ├── feature1.png
│   └── feature2.png
├── input/
│   ├── feature1.png
│   └── feature2.png
└── output/                    # tool writes here
appshots --config /path/to/my-app-screenshots/config.json

Render a single screenshot, device, or locale:

appshots --config CONFIG.json --filter feature1
appshots --config CONFIG.json --device iphone-6.9
appshots --config CONFIG.json --locale ru

Config schema

{
  "devices": ["iphone-6.9", "ipad-13"],
  "output_dir": "./output",
  "missing_background_color": "#1a1a1a",
  "font": {
    "path": "./fonts/MyFont.ttf",
    "size": 140,
    "color": "#FFFFFFFF",
    "shadow": { "color": "#000000AA", "offset": [4, 6], "blur": 0 }
  },
  "layout": {
    "screenshot_scale": 0.78,
    "screenshot_max_height": 0.78,
    "screenshot_offset_y": 0.04,
    "screenshot_shadow": { "color": "#00000080", "offset": [0, 20], "blur": 30 },
    "caption_position_y": 0.10,
    "caption_max_width": 0.92,
    "background_resample": "nearest",
    "screenshot_resample": "nearest"
  },
  "screenshots": [
    {
      "id": "feature1",
      "screenshot": "./input/feature1.png",
      "background": "./backgrounds/feature1.png",
      "caption": "DEFEND YOUR CASTLE!"
    }
  ]
}

Devices

A device is a preset id or a custom {id, width, height} dict:

Preset Size (portrait) Add -landscape for
iphone-6.9 1320 × 2868 2868 × 1320
iphone-6.5 1284 × 2778 2778 × 1284
ipad-13 2064 × 2752 2752 × 2064

Custom dicts still work ({ "id": "my-device", "width": 2778, "height": 1284 }); a warning is printed when the size isn't one App Store Connect accepts.

Per-device screenshots

When iPhone and iPad screenshots differ, pass a dict keyed by device id:

{
  "screenshot": {
    "iphone-6.9": "./input/iphone/feature1.png",
    "ipad-13":    "./input/ipad/feature1.png"
  }
}

Localized captions

A caption can be a plain string (same text for every locale) or a dict keyed by locale:

{
  "caption": {
    "en": "DEFEND YOUR CASTLE!",
    "ru": "ЗАЩИТИ СВОЙ ЗАМОК!",
    "de": "VERTEIDIGE DEINE BURG!"
  }
}

As soon as any caption is a dict, output goes to per-locale subfolders — output/{locale}/{device.id}-{screenshot.id}.png — the layout fastlane deliver uploads from. Locales are the union of keys across all captions; a dict caption missing one of those locales fails that render loudly. String captions still work per locale (rendered identically into each folder).

Long translations are handled automatically: the font size shrinks until the caption fits layout.caption_max_width (fraction of canvas width, default 0.92).

If the font lacks glyphs for a locale's script (e.g. a Latin-only font with a Cyrillic caption), the render fails with the list of missing characters instead of silently drawing an empty title. Fix it with a per-locale font — font.path as a dict with a default fallback:

{
  "font": {
    "path": {
      "default": "./fonts/PatrickHand-Regular.ttf",
      "ru":      "./fonts/Neucha-Regular.ttf"
    },
    "size": 140
  }
}

Notes

  • All paths in config.json are relative to the config file's directory (or absolute).
  • Resample defaults to nearest — preserves pixel art. Use lanczos for smooth photographic backgrounds.
  • If a background file is missing, the tool falls back to a solid color so layout work can continue.

Development

python tests/smoke.py                    # render the example, verify output
python example/make_fake_screenshot.py   # regenerate the example's fake assets

License

MIT. The bundled example font (Neucha) is licensed under the SIL Open Font License — see example/fonts/OFL.txt.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

appshots-0.1.0.tar.gz (10.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

appshots-0.1.0-py3-none-any.whl (11.2 kB view details)

Uploaded Python 3

File details

Details for the file appshots-0.1.0.tar.gz.

File metadata

  • Download URL: appshots-0.1.0.tar.gz
  • Upload date:
  • Size: 10.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for appshots-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1c39477d074948c8f84ccac4d220b48ca180c37ad3fea3988ca495ab461cb9b2
MD5 b3c452b063416d9298862a85bf353ae6
BLAKE2b-256 6a7030c5c72203e8e72c96a9ee6c95af8a4e4a6c9d0be0157ead19af618b95e1

See more details on using hashes here.

Provenance

The following attestation bundles were made for appshots-0.1.0.tar.gz:

Publisher: publish.yml on DmitryKhryukin/appshots

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file appshots-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: appshots-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 11.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for appshots-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bd67e22dad37e8826e13db4525e61c406e31f4e9a8a004b9229339aea64a8364
MD5 d2e4b04117131ed269418468bdb95a0f
BLAKE2b-256 80f778aa772a64a045ea776ddacee2fb92c7307cdb29d5b2b172aa36d468600b

See more details on using hashes here.

Provenance

The following attestation bundles were made for appshots-0.1.0-py3-none-any.whl:

Publisher: publish.yml on DmitryKhryukin/appshots

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page