Tutoriel API Bigjpg : agrandir des images par lot en Python
Un script prêt à lancer qui téléverse les images, crée les tâches d’agrandissement, suit les résultats et les télécharge, avec la gestion de concurrence et d’erreurs qui évite qu’un lot de 500 images s’arrête à mi-chemin.
Pour quelques images, l’interface web suffit. Avec un dossier de cinq cents fichiers, l’API est plus confortable : quatre endpoints, pas de navigateur, un script relançable à volonté. Voici la version qu’on passerait à un collègue, avec la gestion des échecs qui compte vraiment.
Avant de commencer
- L’accès à l’API est réservé aux comptes payants. La clé se trouve sur la page API une fois connecté avec une formule active.
- Python 3 avec
requestsinstallé. Rien d’autre. - L’agrandissement tourne en asynchrone sur nos workers : le script crée les tâches, puis les interroge. Un lot de 500 images prend le temps de la tâche la plus lente de votre file, pas la somme des images.
Le script
import json
import os
import time
import requests
API = "https://bigjpg.com"
KEY = "YOUR_API_KEY"
STYLE = "art" # "art" pour l’illustration, "photo" pour les photos
NOISE = "1" # -1 aucun, 0 faible, 1 moyen, 2 fort, 3 maximal
X2 = "2" # 1 = 2x, 2 = 4x, 3 = 8x, 4 = 16x
POLL_SECONDS = 8
MAX_RETRY = 1
HEADERS = {"X-API-KEY": KEY}
def upload(path):
"""Get a presigned URL, PUT the file, return its public URL."""
name = path.split("/")[-1]
r = requests.post(
API + "/api/img_upload",
headers=HEADERS,
data={"filename": name, "filetype": "image/jpeg"},
timeout=60,
)
info = r.json()
if info.get("status") != "ok":
raise RuntimeError("upload failed: " + str(info))
with open(path, "rb") as image:
r = requests.put(
info["url"],
data=image,
headers={"Content-Type": "image/jpeg"},
timeout=300,
)
if r.status_code != 200:
raise RuntimeError("upload failed with HTTP %s" % r.status_code)
return info["fileurl"]
def create(image_url):
"""Create an enlargement task, return its task id."""
payload = {
"style": STYLE,
"noise": NOISE,
"x2": X2,
"input": image_url,
}
r = requests.post(
API + "/api/task/",
headers=HEADERS,
data=json.dumps(payload),
timeout=60,
)
data = r.json()
if "tid" not in data:
raise RuntimeError("task failed: " + str(data))
return data["tid"]
def poll(tids):
"""Query up to 100 task ids at once. Returns task id -> result dict."""
r = requests.get(API + "/api/task/" + ",".join(tids), timeout=60)
return r.json()
def download(url, out):
"""Download a finished image, checking the transfer really succeeded."""
r = requests.get(url, timeout=300)
if r.status_code != 200:
raise RuntimeError("download failed with HTTP %s" % r.status_code)
with open(out, "wb") as f:
f.write(r.content)
return out
def main(paths):
pending = {}
attempts = {}
for path in paths:
try:
tid = create(upload(path))
except Exception as e:
print("skipped", path, e)
continue
pending[tid] = path
attempts[tid] = 0
print("queued", path, tid)
while pending:
for tid, info in poll(list(pending)[:100]).items():
if info.get("status") == "success":
out = os.path.splitext(pending[tid])[0] + "_bigjpg.jpg"
try:
download(info["url"], out)
except Exception as e:
print("download failed", pending[tid], e)
pending.pop(tid)
continue
print("done", pending.pop(tid), "->", out)
elif info.get("status") == "error":
# Une seule nouvelle tentative, puis on abandonne ce fichier.
attempts[tid] += 1
if attempts[tid] <= MAX_RETRY:
requests.post(API + "/api/task/" + tid, headers=HEADERS, timeout=60)
print("retrying", pending[tid])
else:
print("giving up on", pending.pop(tid))
if pending:
time.sleep(POLL_SECONDS)
if __name__ == "__main__":
import sys
main(sys.argv[1:])
Ce que fait chaque appel
| Appel | Rôle | Remarques |
|---|---|---|
| POST /api/img_upload | Renvoie une URL de téléversement signée et l’URL publique de l’image | Téléversez le fichier en PUT avec le même Content-Type, puis passez fileurl comme input de la tâche |
| POST /api/task/ | Crée une tâche d’agrandissement | Renvoie tid, un minute estimé et remaining_api_calls |
| GET /api/task/{ids} | État d’une ou de plusieurs tâches | ids séparés par des virgules, jusqu’à 100 par requête |
| POST /api/task/{ids} | Relance les tâches en échec | Seules les tâches en état error repartent dans la file |
Concurrence et bonnes manières
- Interrogez moins souvent que vous ne le pensez. L’agrandissement n’est pas instantané : 5–10 secondes entre deux requêtes suffisent, et demander 100 ids d’un coup garde le nombre d’appels bas.
- Limitez les téléversements. Deux à quatre envois simultanés, c’est un plafond raisonnable par machine. Pour plus de débit, lancez plus d’instances plutôt que plus de threads par instance.
- Réessayez avec un délai croissant. Une erreur réseau n’est pas un échec de tâche. Relancez l’appel HTTP jusqu’à trois fois avec une attente de plus en plus longue avant de déclarer le fichier perdu.
- Surveillez
remaining_api_calls. Chaque tâche créée l’indique ; coupez la boucle à un seuil bas au lieu de découvrir la limite devant un mur d’erreurs. - Tenez le journal des correspondances. Gardez un fichier avec
tid, entrée, réglages et sortie. Si le travail tourne toute la nuit, ce journal dit ce qui est terminé.
Les erreurs que vous verrez vraiment
valid_api_key_required— en-tête absent ou clé inconnue. Vérifiez le nom de l’en-tête et que la formule associée à la clé n’a pas expiré.requires_vip— compte gratuit ou expiré. L’API est prévue pour les formules payantes actives.month_limit— quota mensuel épuisé. Chaque tâche indique le reste, vous pouvez donc vous arrêter avant.param_error—styledoit valoir art ou photo,noisede -1 à 3,x2de 1 à 4.input_url_error— l’input doit être une URL d’image http ou https accessible publiquement. Les URL privées signées expirent : téléversez d’abord, puis transmettez l’URL publique renvoyée.
Ce que coûte un lot
Une image compte pour un crédit en 2x, 4x et 8x ; en 16x elle en compte deux. Un lot de 500 images en 4x coûte 500 crédits, le même en 16x coûte 1000. Comme 4x est de toute façon le bon facteur pour la plupart des photos, l’option la moins chère est souvent la plus belle — l’explication est dans le guide des facteurs.
La référence complète des endpoints, paramètres et états d’erreur compris, est sur la page API. Si un point du script ne correspond pas à ce que vous recevez, c’est cette page que nous tenons à jour.