bar_code / app.py
mohamedtsou's picture
Create app.py
09ec0b8 verified
raw
history blame
14 kB
from flask import Flask, request, jsonify
from flask_cors import CORS
import cv2
import numpy as np
import base64
import io
from pyzbar.pyzbar import decode
from PIL import Image
import os
import logging
import requests
# Initialisation
app = Flask(__name__)
CORS(app)
# Configuration pour HF Spaces
HF_SPACE = os.environ.get('SPACE_ID') is not None
PORT = 7860 if HF_SPACE else int(os.environ.get('PORT', 5000))
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def decode_barcode(image_bytes):
"""Fonction principale de décodage"""
try:
# Essayer avec PIL d'abord (plus léger)
try:
pil_image = Image.open(io.BytesIO(image_bytes))
barcodes = decode(pil_image)
if barcodes:
barcode = barcodes[0]
return {
'success': True,
'barcode': barcode.data.decode('utf-8'),
'type': barcode.type,
'count': len(barcodes),
'method': 'pil'
}
except Exception as e:
logger.debug(f"PIL method: {e}")
# Essayer avec OpenCV si disponible
try:
nparr = np.frombuffer(image_bytes, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_GRAYSCALE)
if img is not None:
barcodes = decode(img)
if barcodes:
barcode = barcodes[0]
return {
'success': True,
'barcode': barcode.data.decode('utf-8'),
'type': barcode.type,
'count': len(barcodes),
'method': 'opencv'
}
# Améliorer le contraste et réessayer
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
enhanced = clahe.apply(img)
barcodes = decode(enhanced)
if barcodes:
barcode = barcodes[0]
return {
'success': True,
'barcode': barcode.data.decode('utf-8'),
'type': barcode.type,
'count': len(barcodes),
'method': 'enhanced'
}
except Exception as e:
logger.debug(f"OpenCV method: {e}")
return {'success': False, 'error': 'Aucun code-barres détecté'}
except Exception as e:
logger.error(f"Decode error: {e}")
return {'success': False, 'error': str(e)}
@app.route('/api/scan', methods=['POST', 'GET'])
def scan():
"""Endpoint principal - supporte GET pour l'interface web"""
if request.method == 'GET':
return '''
<!DOCTYPE html>
<html>
<head>
<title>Barcode Scanner</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.container { background: #f5f5f5; padding: 20px; border-radius: 10px; }
input, button { margin: 10px 0; padding: 10px; width: 100%; }
#preview { max-width: 300px; margin: 20px 0; }
#result { margin-top: 20px; padding: 10px; background: white; border-radius: 5px; }
</style>
</head>
<body>
<div class="container">
<h1>📷 Barcode Scanner</h1>
<input type="file" id="imageInput" accept="image/*" capture="environment">
<video id="preview" style="display:none;"></video>
<button onclick="captureImage()">📸 Prendre une photo</button>
<button onclick="uploadImage()">📁 Uploader une image</button>
<div id="result"></div>
</div>
<script>
let currentImage = null;
// Accéder à la caméra
async function startCamera() {
const video = document.getElementById('preview');
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' }
});
video.srcObject = stream;
video.style.display = 'block';
video.play();
return video;
} catch (err) {
alert('Erreur caméra: ' + err.message);
}
}
// Capturer une image
async function captureImage() {
const video = document.getElementById('preview');
if (!video.srcObject) {
await startCamera();
return;
}
const canvas = document.createElement('canvas');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0);
currentImage = canvas.toDataURL('image/jpeg');
scanBarcode(currentImage);
}
// Uploader une image
function uploadImage() {
const input = document.getElementById('imageInput');
input.click();
input.onchange = function(e) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = function(event) {
currentImage = event.target.result;
scanBarcode(currentImage);
};
reader.readAsDataURL(file);
};
}
// Scanner le code-barres
async function scanBarcode(imageData) {
const resultDiv = document.getElementById('result');
resultDiv.innerHTML = '⌛ Analyse en cours...';
try {
const response = await fetch('/api/scan', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ image: imageData })
});
const data = await response.json();
if (data.success) {
resultDiv.innerHTML = `
<h3>✅ Code-barres trouvé !</h3>
<p><strong>Code:</strong> ${data.barcode}</p>
<p><strong>Type:</strong> ${data.type}</p>
<button onclick="getProductInfo('${data.barcode}')">
🔍 Voir les infos produit
</button>
`;
} else {
resultDiv.innerHTML = `
<h3>❌ Aucun code-barres détecté</h3>
<p>Erreur: ${data.error}</p>
<p>Essayez avec une image plus claire.</p>
`;
}
} catch (error) {
resultDiv.innerHTML = `❌ Erreur: ${error.message}`;
}
}
// Obtenir les infos produit
async function getProductInfo(barcode) {
const resultDiv = document.getElementById('result');
resultDiv.innerHTML += '<p>🔍 Recherche des infos produit...</p>';
try {
const response = await fetch(`/api/product/${barcode}`);
const data = await response.json();
if (data.success) {
const product = data.product;
resultDiv.innerHTML += `
<h4>📦 Informations produit:</h4>
<p><strong>Nom:</strong> ${product.name || 'Inconnu'}</p>
<p><strong>Marque:</strong> ${product.brand || 'Inconnue'}</p>
<p><strong>Catégorie:</strong> ${product.category || 'Non catégorisé'}</p>
${product.price ? `<p><strong>Prix:</strong> ${product.price} €</p>` : ''}
${product.description ? `<p><strong>Description:</strong> ${product.description}</p>` : ''}
`;
} else {
resultDiv.innerHTML += `<p>ℹ️ ${data.error}</p>`;
}
} catch (error) {
resultDiv.innerHTML += `<p>❌ Erreur recherche: ${error.message}</p>`;
}
}
// Démarrer la caméra au chargement
window.onload = startCamera;
</script>
</body>
</html>
'''
# POST request - API endpoint
try:
if not request.is_json:
return jsonify({'success': False, 'error': 'Content-Type must be application/json'}), 400
data = request.get_json()
if not data or 'image' not in data:
return jsonify({'success': False, 'error': 'No image provided'}), 400
image_data = data['image']
if ',' in image_data:
image_data = image_data.split(',')[1]
# Limiter la taille pour HF Spaces
if len(image_data) > 3 * 1024 * 1024: # 3MB max
return jsonify({'success': False, 'error': 'Image too large (max 3MB)'}), 400
image_bytes = base64.b64decode(image_data)
result = decode_barcode(image_bytes)
return jsonify(result)
except Exception as e:
logger.error(f"Scan error: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/product/<barcode>', methods=['GET'])
def product_info(barcode):
"""Obtenir les infos produit"""
try:
# Base de données locale
products = {
'5901234123457': {
'name': 'Lait UHT Demi-écrémé',
'brand': 'Candia',
'category': 'Alimentation',
'price': 1.20,
'description': 'Lait stérilisé UHT'
},
'9780201379624': {
'name': 'Flutter Cookbook',
'brand': "O'Reilly",
'category': 'Livres',
'price': 45.99,
'description': 'Guide de développement Flutter'
},
'3017620422003': {
'name': 'Nutella',
'brand': 'Ferrero',
'category': 'Alimentation',
'price': 4.99,
'description': 'Pâte à tartiner aux noisettes'
}
}
if barcode in products:
return jsonify({
'success': True,
'product': products[barcode],
'source': 'local'
})
# Essayer OpenFoodFacts
try:
response = requests.get(
f'https://world.openfoodfacts.org/api/v0/product/{barcode}.json',
timeout=3
)
if response.status_code == 200:
data = response.json()
if data.get('status') == 1:
product = data.get('product', {})
return jsonify({
'success': True,
'product': {
'name': product.get('product_name', 'Produit inconnu'),
'brand': product.get('brands', 'Marque inconnue'),
'category': product.get('categories', 'Non catégorisé'),
'description': product.get('generic_name', ''),
'image_url': product.get('image_url', '')
},
'source': 'openfoodfacts'
})
except Exception as e:
logger.debug(f"OpenFoodFacts error: {e}")
return jsonify({
'success': False,
'error': 'Produit non trouvé',
'barcode': barcode
})
except Exception as e:
logger.error(f"Product info error: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/health', methods=['GET'])
def health():
"""Health check endpoint"""
return jsonify({
'status': 'healthy',
'service': 'Barcode Scanner API',
'version': '2.0',
'deployed_on': 'Hugging Face Spaces' if HF_SPACE else 'Local'
})
@app.route('/')
def home():
"""Page d'accueil"""
return jsonify({
'message': 'Barcode Scanner API',
'documentation': {
'scan': 'POST /api/scan - Scanner un code-barres',
'product': 'GET /api/product/<barcode> - Infos produit',
'health': 'GET /api/health - Health check'
},
'web_interface': 'Visitez /api/scan pour l\'interface web'
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=PORT, debug=False)