Skip to main content

blog-image

CI

Génère les images de couverture d'un article dans plusieurs formats, à partir d'un fichier YAML de trois lignes.

# image.yml
titre: "Combien coûte une application mobile"
accent: "#00ff99"
fond:   "#1c1c22"
$ blog-image image.yml
  -> article-16x9.png   1600x900
  -> article-4x3.png    1600x1200
  -> article-1x1.png    1200x1200

Exemple de carte 16:9

Trois formats parce qu'un article en a besoin de trois : le 16:9 pour Open Graph et l'en-tête du billet, le 4:3 pour l'emplacement Discover, le 1:1 pour les recadrages sociaux. Les générer séparément, c'est trois fichiers qui divergent au premier changement de titre.

Installation

Avec uv, sans rien installer de permanent :

$ uvx --from blog-image blog-image image.yml

Pour l'avoir sous la main partout :

$ uv tool install blog-image      # ou : pipx install blog-image

Et en développement, depuis les sources :

$ git clone https://github.com/foverseas/blog-image
$ cd blog-image
$ uv sync --extra dev             # crée .venv d'après uv.lock
$ uv run blog-image examples/image.yml

pip install blog-image fonctionne aussi : c'est un paquet ordinaire, uv n'est pas une dépendance.

Il faut Python 3.10, Pillow et PyYAML. La police par défaut est JetBrains Mono ; si elle n'est pas installée, le programme retombe sur IBM Plex Mono, DejaVu puis Arial, et dit où il a cherché s'il ne trouve rien.

Le fichier de description

Seul titre est obligatoire. Les clés s'écrivent en français ou en anglais.

Clé Alias Défaut Rôle
titre title — le titre, coupé automatiquement sur la largeur réelle de la police
sous-titre subtitle vide une ligne sous le titre ; une liste devient autant de lignes
chapeau eyebrow vide surtitre en capitales espacées, dans la couleur d'accent
pied footer vide le pied de carte, précédé d'une pastille
fond background #1c1c22 fond de la carte
texte foreground #ffffff couleur du texte
accent — #00ff99 pastille, surtitre, point final, motif
motif — particules particules, grille ou aucun
graine seed 0 change la disposition du motif sans rien toucher d'autre
police font JetBrains Mono nom de famille, gabarit de chemin contenant %s, ou {regular:, bold:}
formats — [16x9, 4x3, 1x1] ratios connus ou tailles explicites en pixels
nom name article base du nom de fichier
sortie out . dossier de sortie, relatif au fichier YAML
point dot true le point d'accent après le titre
lignes-max max_lines 4 au-delà, le titre rapetisse au lieu de déborder

Les couleurs s'écrivent #rgb, #rrggbb ou [28, 28, 34]. Une clé inconnue est une erreur : une faute de frappe dans un nom de clé ne se voit sinon qu'une fois la carte terminée.

Ratios connus : 16x9, 4x3, 3x2, 2x1, 1x1, 4x5, 9x16. Pour tout autre besoin, écrivez la taille en pixels — 1240x700.

Voir examples/complet.yml pour un fichier avec toutes les clés.

En ligne de commande

$ blog-image image.yml                      # formats du fichier
$ blog-image image.yml -o public/blog       # ailleurs
$ blog-image image.yml -f 16x9 -f 1200x628  # autres formats
$ blog-image image.yml --dry-run            # ce qui serait écrit, sans écrire

-n/--name remplace la base du nom de fichier, -q/--quiet coupe la sortie. Une erreur de description sort en code 2 avec un message en clair, pas une trace.

Comme bibliothèque

from pathlib import Path
from blog_image import load_spec, render, resolve_fonts

spec = load_spec(Path("image.yml"))
fonts = resolve_fonts(spec.font)
for fmt in spec.formats:
    render(spec, fonts, fmt, spec.path_for(fmt))

Trois décisions qui expliquent le reste du code

Tout est dessiné à 3× puis réduit en Lanczos. C'est ce qui tient les filets d'un pixel et le petit corps, pas l'anticrénelage du rastériseur, qui est inégal à ces tailles. La réduction est suivie d'une quantification sur 256 couleurs : sur un aplat, elle ne se voit pas et divise le poids du fichier par deux.

Le corps du texte est calculé sur une unité, pas sur la largeur. Une carte 1:1 n'est pas plus large qu'une 4:3 mais elle a bien plus de place ; dimensionner sur la largeur seule la laisse creuse. L'unité corrige la largeur par l'écart entre le ratio demandé et le 16:9 :

unit = width * (1 + max(0.0, height / width - 0.5625) * 0.34)

Le titre est coupé sur la largeur mesurée de la police, pas sur un nombre de caractères, et le corps diminue tant que le compte de lignes dépasse lignes-max. C'est ce qui évite d'écrire une coupe différente par format.

Le motif, lui, est dimensionné sur la même unité et non sur la bande qu'il occupe : en 16:9 cette bande fait quelques centaines de pixels, en 1:1 cinq fois plus, et un décor calé sur la bande disparaît dans le premier cas et écrase le second. Seul le nombre de marques dépend de la place disponible.

Tests

$ uv run pytest

Le rendu est reproductible : les mêmes descriptions donnent les mêmes fichiers, octet pour octet, d'une version de Pillow à l'autre. uv.lock est versionné pour que ce soit vrai d'une machine à l'autre aussi.

Journal des changements

Voir CHANGELOG.md.

Contact

Une question d'usage, un titre qui se coupe mal, une idée de format : le Discord est le chemin le plus court. Les questions posées dans les Discussions y gagnent une réponse qui reste trouvable, et les bugs vont dans les issues.

Licence

MIT.

Metadata

Release files for blog-image 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for blog-image 0.1.0
File Size Uploaded Provenance
blog_image-0.1.0.tar.gz 96.5 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for blog-image 0.1.0
File Interpreter ABI Platform
blog_image-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 112.7 kB

Release files / blog_image-0.1.0.tar.gz

Download URL blog_image-0.1.0.tar.gz
Size 96.5 kB
Tags Source
SHA-256 checksum
How to use checksums
47ea6d4f248c319b12e0db49ae9ea758605ac6953dc364af5b45f5d6418d1639
BLAKE2b-256 checksum
How to use checksums
e098d9bfe670acc06013204b48c51e8d8cd21b12aebc02d209bcbbd8c108babe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Release files / blog_image-0.1.0-py3-none-any.whl

Download URL blog_image-0.1.0-py3-none-any.whl
Size 16.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
63f82c1796e3a2170fec493a854947b9b91d12bc4776a202f0ea0a40fd618085
BLAKE2b-256 checksum
How to use checksums
c21e85a9a47e5dc47178ff355805aec8e58bd17b6618b357496fff11628d41be
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Security for blog-image 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for blog_image-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page