Préparation de l'environnement
L'exécution de pyzbar nécessite la bibliothèque native zbar ainsi que les modules Python pyzbar et Pillow. L'utilisation de Python 3.6 ou supérieur est fortement reocmmandée pour éviter les problèmes de compatibilité avec les gestionnaires de paquets.
Distributions basées sur Debian (Ubuntu)
sudo apt-get update
sudo apt-get install libzbar0
pip3 install pyzbar Pillow
Distributions basées sur Red Hat (CentOS/RHEL)
sudo yum install epel-release
sudo yum install zbar-devel zbar python3-devel
pip3 install pyzbar Pillow
Windows
pip install pyzbar Pillow
Note : Sous Windows, il est impératif d'installer les packages redistribuables Visual C++ (vcredist) pour que la bibliothèque native soit correctement chargée par l'interpréteur Python.
Implémentation du backend Java (Spring Boot)
Pour contourner la limitation de la taille des arguments en ligne de commande, l'image encodée en Base64 est écrite dans un fichier temporaire. Le processus Python lit ensuite ce fichier. De plus, le script Python doit résider en dehors du JAR exécutable pour être accessible via le système de fichiers standard de l'OS.
Contrôleur REST et Gestion des Fichiers Temporaires
package com.enterprise.qrcode.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@RestController
@RequestMapping("/api/qrcode")
public class QrCodeProcessingController {
@Value("${decoder.python.script-path}")
private String pythonDecoderPath;
private static final String DATA_URI_PREFIX = "data:image/png;base64,";
@PostMapping("/decode")
public ResponseEntity<String> decodeQrCode(@RequestBody String encodedImagePayload) {
if (!encodedImagePayload.startsWith(DATA_URI_PREFIX)) {
return ResponseEntity.badRequest().body("Format de données URI invalide.");
}
String rawBase64 = encodedImagePayload.substring(DATA_URI_PREFIX.length());
Path tempFile = null;
try {
// Création sécurisée d'un fichier temporaire
tempFile = Files.createTempFile("qr_payload_", ".b64");
Files.writeString(tempFile, rawBase64);
// Invocation du script Python
String decodedResult = PythonScriptExecutor.run(pythonDecoderPath, tempFile.toAbsolutePath().toString());
return decodedResult != null ? ResponseEntity.ok(decodedResult) :
ResponseEntity.status(500).body("Échec du décodage du QR code.");
} catch (IOException | InterruptedException e) {
Thread.currentThread().interrupt();
return ResponseEntity.status(500).body("Erreur interne lors du traitement.");
} finally {
// Nettoyage du fichier temporaire
if (tempFile != null) {
try { Files.deleteIfExists(tempFile); } catch (IOException ignored) {}
}
}
}
}
Exécuteur de Processus Python
L'utilisation de ProcessBuilder est préférée à Runtime.exec() pour une meilleure gestion des flux, des redirections et des environnements d'exécution.
package com.enterprise.qrcode.controller;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
public class PythonScriptExecutor {
public static String run(String scriptPath, String filePath) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder("python3", scriptPath, filePath);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
int exitCode = process.waitFor();
if (exitCode == 0) {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
return reader.lines().collect(Collectors.joining(System.lineSeparator())).trim();
}
}
return null;
}
}
Script de décodage Python
Le script suivant lit le fichier temporaire, décode le Base64, applique des filtres d'amélioration d'image pour optimiser la lecture, et extrait les données via pyzbar.
# -*- coding: utf-8 -*-
import sys
import base64
from io import BytesIO
from PIL import Image, ImageEnhance
from pyzbar import pyzbar
def decode_base64_to_image(file_path):
"""Lit le fichier Base64 et le convertit en objet Image PIL."""
with open(file_path, 'r') as file:
b64_data = file.read().strip()
image_bytes = base64.b64decode(b64_data)
return Image.open(BytesIO(image_bytes))
def extract_qr_payload(image):
"""Améliore l'image et décode le QR code."""
# Amélioration de la luminosité et du contraste pour les images de caméra
enhanced_image = ImageEnhance.Brightness(image).enhance(1.5)
enhanced_image = ImageEnhance.Contrast(enhanced_image).enhance(2.5)
# Conversion en niveaux de gris pour une meilleure précision de zbar
grayscale_image = enhanced_image.convert('L')
decoded_objects = pyzbar.decode(grayscale_image)
if not decoded_objects:
raise ValueError("Aucun QR code détecté dans l'image.")
return decoded_objects[0].data.decode('utf-8')
def main():
if len(sys.argv) < 2:
print("Usage: python script.py <path_to_b64_file>", file=sys.stderr)
sys.exit(1)
target_file = sys.argv[1]
try:
img = decode_base64_to_image(target_file)
result = extract_qr_payload(img)
print(result)
except Exception as e:
print(f"Erreur de traitement : {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Configuration de l'application
Le chemin absolu du script Python doit être défini dans le fichier de configuration Spring Boot, car les ressources internes au JAR ne sont pas accessibles en tant que fichiers physiques par les processus externes.
# application.yml
decoder:
python:
script-path: '/opt/scripts/qrcode_decoder.py'
Contraintes techniques et solutions
- Limitation des arguments CLI : Les chaînes Base64 d'images dépassent fréquemment la limite maximale des arguments de ligne de commande du système d'exploitation. L'écriture dans un fichier temporaire sur le disque est la solution la plus robuste pour transmettre les données au processus Python.
- Système de fichiers du JAR : Un script Python embarqué dans les ressources d'un projet Spring Boot (sous forme de JAR) ne possède pas de chemin de système de fichiers standard. Il est donc impossible de l'exécuter directement via
ProcessBuilder. Le script doit être déployé externement et son chemin injecté via les propriétés de configuration. - Gestion des flux de processus : Il est crucial de consommer les flux de sortie (
InputStream) du processus Python avant ou pendant l'apppel àwaitFor(), sous peine de bloquer le thread Java si le tampon de sortie du processus d'exploitation se remplit.