Décodage de QR codes Base64 via l'intégration Java et Python avec pyzbar

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.

Étiquettes: Java Spring Boot Python pyzbar Pillow

Publié le 15 juillet à 00h30