Lire l'add-on EAN-2 et proposer le numéro de la revue scannée
Découverte à la mesure : l'EAN-2 est DÉSACTIVÉ par défaut dans zbar. Il ne suffisait donc pas de cesser de le filtrer — il n'était jamais produit. Vérifié sur un EAN-13 + EAN-2 rendus en canvas : un symbole par défaut, deux après setConfig(ZBAR_EAN2, ZBAR_CFG_ENABLE, 1). L'EAN-5 reste désactivé : il porte un prix, pas un numéro. Ce qui reste non vérifié — que les magazines réels y mettent bien leur numéro de parution — est dit à l'utilisateur : le numéro est proposé dans un champ modifiable, sous une invitation à le comparer à la couverture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -151,6 +151,37 @@ d'obtenir `zbar.wasm` et sa licence.
|
|||||||
**Leçon confirmée, dans les deux sens** : le coût d'une bibliothèque n'est pas le poids de son
|
**Leçon confirmée, dans les deux sens** : le coût d'une bibliothèque n'est pas le poids de son
|
||||||
assembly. Toujours comparer deux publish.
|
assembly. Toujours comparer deux publish.
|
||||||
|
|
||||||
|
### L'add-on EAN-2 — désactivé par défaut, et c'est le piège (2026-08-19)
|
||||||
|
|
||||||
|
Le code-barres d'un périodique porte, à côté du code principal, un petit code à deux chiffres :
|
||||||
|
l'**EAN-2**, qui est normativement le **numéro de parution**. C'est le candidat naturel pour
|
||||||
|
pré-remplir le numéro d'une revue.
|
||||||
|
|
||||||
|
⚠️ **Il ne suffit pas de cesser de le filtrer : zbar ne le produit pas.** Mesuré le 2026-08-19,
|
||||||
|
sur un EAN-13 `9772466671438` et un EAN-2 `43` rendus côte à côte dans un canvas :
|
||||||
|
|
||||||
|
| Scanner | Symboles lus |
|
||||||
|
|---|---|
|
||||||
|
| par défaut | `ZBAR_EAN13=9772466671438` |
|
||||||
|
| après `setConfig(ZBAR_EAN2, ZBAR_CFG_ENABLE, 1)` | `ZBAR_EAN2=43` **et** `ZBAR_EAN13=9772466671438` |
|
||||||
|
|
||||||
|
La ligne de configuration est donc indispensable, et ce fichier affirmait le contraire avant
|
||||||
|
mesure — « `decoder()` les écarte » laissait croire qu'ils arrivaient.
|
||||||
|
|
||||||
|
**L'EAN-5 reste désactivé** : il porte un **prix**. Le confondre avec un numéro créerait des
|
||||||
|
numéros « 00595 » que personne ne comprendrait. Le filtre du `decoder()` le mentionne quand même,
|
||||||
|
par prudence.
|
||||||
|
|
||||||
|
⚠️ **Ce qui reste non vérifié, et qui est dit à l'utilisateur** : que les magazines réels portent
|
||||||
|
bien leur numéro de parution dans cet add-on. La chaîne de décodage est prouvée, l'usage
|
||||||
|
éditorial ne l'est pas — cela demande un exemplaire devant la caméra. Le numéro lu est donc
|
||||||
|
**proposé** dans un champ modifiable, sous un message qui invite à le comparer à la couverture
|
||||||
|
(« Vérifiez-le sur la couverture »), et jamais enregistré tout seul.
|
||||||
|
|
||||||
|
⚠️ Un add-on lu ne doit pas **déteindre** sur la saisie suivante : `_parutionLue` est remis à zéro
|
||||||
|
à chaque saisie manuelle et à chaque ouverture du scanner. Sans cela, le numéro d'un magazine
|
||||||
|
serait proposé pour le suivant.
|
||||||
|
|
||||||
### Ce qui reste vrai de l'ancienne section
|
### Ce qui reste vrai de l'ancienne section
|
||||||
|
|
||||||
- Le scan caméra exige **HTTPS** (garanti par YunoHost ; `localhost` est considéré comme sûr).
|
- Le scan caméra exige **HTTPS** (garanti par YunoHost ; `localhost` est considéré comme sûr).
|
||||||
@@ -165,9 +196,8 @@ assembly. Toujours comparer deux publish.
|
|||||||
|
|
||||||
- Il remonte le **format** du symbole (`ZBAR_EAN13`, `ZBAR_ISBN13`, `ZBAR_EAN5`…), là où nous
|
- Il remonte le **format** du symbole (`ZBAR_EAN13`, `ZBAR_ISBN13`, `ZBAR_EAN5`…), là où nous
|
||||||
déduisons le type du préfixe.
|
déduisons le type du préfixe.
|
||||||
- Il décode les **add-ons EAN-2 (numéro de parution) et EAN-5 (prix)** imprimés à côté du code
|
- Il sait décoder les **add-ons EAN-2 (numéro de parution) et EAN-5 (prix)** imprimés à côté du
|
||||||
principal. ⚠️ `decoder()` les **écarte** : ce ne sont pas le code du livre. L'EAN-2 deviendra
|
code principal. ⚠️ **Mais ils sont DÉSACTIVÉS par défaut** — voir « L'add-on EAN-2 » ci-dessous.
|
||||||
utile le jour où les périodiques seront catalogués.
|
|
||||||
|
|
||||||
## Saisie d'un code-barres — décisions actées le 2026-08-18 (2ᵉ série)
|
## Saisie d'un code-barres — décisions actées le 2026-08-18 (2ᵉ série)
|
||||||
|
|
||||||
|
|||||||
@@ -60,23 +60,17 @@ rendu en canvas, pas sur une image de caméra.
|
|||||||
|
|
||||||
# Retours d'usage du 2026-08-19 (3ᵉ série)
|
# Retours d'usage du 2026-08-19 (3ᵉ série)
|
||||||
|
|
||||||
## Ce qui reste des magazines : le numéro scanné
|
## Ce qui reste des magazines : confirmer l'EAN-2 sur un vrai kiosque
|
||||||
|
|
||||||
Les revues sont implémentées (voir `CLAUDE.md`) : fiche par revue, numéros à l'intérieur, et le
|
Le numéro scanné est implémenté (voir `CLAUDE.md`) : zbar lit l'add-on EAN-2 — après l'avoir
|
||||||
code-barres `977` mène désormais à la fiche de la revue.
|
**activé**, il est désactivé par défaut — et l'écran de la revue le propose dans un champ à
|
||||||
|
vérifier.
|
||||||
|
|
||||||
⚠️ **Le numéro, lui, se saisit à la main.** Les deux chiffres de parution du code-barres ne sont
|
⚠️ **Ce qui n'est toujours pas vérifié** : que les magazines réels portent bien leur numéro de
|
||||||
pas fiables ; l'**add-on EAN-2** imprimé à côté du code principal est le bon candidat, et zbar
|
parution dans cet add-on, et non autre chose. La chaîne de décodage est prouvée sur un code
|
||||||
sait le décoder — mais `decoder()` l'écarte aujourd'hui, et rien n'a pu être vérifié faute d'un
|
généré ; l'usage éditorial demande un exemplaire devant la caméra. À faire sur trois ou quatre
|
||||||
magazine réel devant la caméra. À reprendre avec un exemplaire en main : lire l'EAN-2, le
|
numéros d'un même titre : si l'EAN-2 suit la parution, le message d'avertissement pourra
|
||||||
comparer au numéro imprimé sur la couverture, et ne le pré-remplir que si les deux concordent
|
s'alléger ; s'il ne la suit pas, c'est le pré-remplissage qu'il faudra retirer.
|
||||||
sur plusieurs numéros.
|
|
||||||
|
|
||||||
Autres pistes non traitées, sans urgence :
|
|
||||||
|
|
||||||
- **Champs propres à la BD** (série, tome, scénariste/dessinateur). Le regroupement en série est
|
|
||||||
couvert par les sagas ; il reste la distinction des **rôles**, le modèle mettant tous les
|
|
||||||
auteurs dans `LivreAuteur` sans les distinguer.
|
|
||||||
|
|
||||||
## Auteurs et bibliographie
|
## Auteurs et bibliographie
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace MaBibli.Client.Composants;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ce qu'un coup de scanner a produit : le code du livre ou de la revue, et son add-on éventuel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Code">EAN-13 normalisé, seul code qui identifie l'ouvrage.</param>
|
||||||
|
/// <param name="Parution">
|
||||||
|
/// Contenu de l'add-on <b>EAN-2</b>, imprimé à côté du code principal sur les périodiques.
|
||||||
|
/// </param>
|
||||||
|
/// <remarks>
|
||||||
|
/// ⚠️ <b>L'EAN-2 est une indication, jamais une donnée acquise.</b> Il porte le numéro de
|
||||||
|
/// parution d'une revue — c'est sa raison d'être normative — mais rien n'a pu être vérifié sur
|
||||||
|
/// des magazines réels : la vérification demande un exemplaire devant la caméra. Il est donc
|
||||||
|
/// <b>proposé</b> à l'utilisateur dans un champ qu'il corrige, jamais enregistré tel quel.
|
||||||
|
/// <para>
|
||||||
|
/// L'add-on <b>EAN-5</b> n'est pas remonté : il porte un <i>prix</i>, et le confondre avec un
|
||||||
|
/// numéro créerait des numéros « 00595 » que personne ne comprendrait.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public readonly record struct CodeScanne(string Code, string? Parution);
|
||||||
|
|
||||||
|
/// <summary>Forme brute renvoyée par <c>scanner-camera.js</c>, avant validation de l'ISBN.</summary>
|
||||||
|
internal sealed record LectureCodeBarres(string? Code, string? Parution);
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
/// <summary>Appelé avec l'ISBN-13 dès qu'un code-barres exploitable est décodé.</summary>
|
/// <summary>Appelé avec l'ISBN-13 dès qu'un code-barres exploitable est décodé.</summary>
|
||||||
[Parameter] public EventCallback<string> OnCodeDetecte { get; set; }
|
[Parameter] public EventCallback<CodeScanne> OnCodeDetecte { get; set; }
|
||||||
|
|
||||||
/// <summary>Appelé quand l'utilisateur renonce au scan.</summary>
|
/// <summary>Appelé quand l'utilisateur renonce au scan.</summary>
|
||||||
[Parameter] public EventCallback OnAnnuler { get; set; }
|
[Parameter] public EventCallback OnAnnuler { get; set; }
|
||||||
@@ -168,7 +168,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var depart = Stopwatch.GetTimestamp();
|
var depart = Stopwatch.GetTimestamp();
|
||||||
var code = await _module.InvokeAsync<string?>("decoder", LargeurMax);
|
var lecture = await _module.InvokeAsync<LectureCodeBarres?>("decoder", LargeurMax);
|
||||||
|
var code = lecture?.Code;
|
||||||
_totalMs += Stopwatch.GetElapsedTime(depart).TotalMilliseconds;
|
_totalMs += Stopwatch.GetElapsedTime(depart).TotalMilliseconds;
|
||||||
_framesDecodees++;
|
_framesDecodees++;
|
||||||
|
|
||||||
@@ -192,7 +193,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
await ArreterCameraAsync();
|
await ArreterCameraAsync();
|
||||||
await OnCodeDetecte.InvokeAsync(isbn);
|
await OnCodeDetecte.InvokeAsync(new CodeScanne(isbn, lecture?.Parution));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,6 +198,9 @@
|
|||||||
|
|
||||||
private string? _messagePeriodique;
|
private string? _messagePeriodique;
|
||||||
private PeriodiqueDetecte? _periodique;
|
private PeriodiqueDetecte? _periodique;
|
||||||
|
|
||||||
|
/// <summary>Add-on EAN-2 du dernier scan, quand il y en avait un. Voir <see cref="CodeScanne"/>.</summary>
|
||||||
|
private string? _parutionLue;
|
||||||
private bool _chargement;
|
private bool _chargement;
|
||||||
private bool _enregistrement;
|
private bool _enregistrement;
|
||||||
private string? _erreur;
|
private string? _erreur;
|
||||||
@@ -248,6 +251,7 @@
|
|||||||
_erreur = null;
|
_erreur = null;
|
||||||
_messagePeriodique = null;
|
_messagePeriodique = null;
|
||||||
_periodique = null;
|
_periodique = null;
|
||||||
|
_parutionLue = null;
|
||||||
_avertissements = [];
|
_avertissements = [];
|
||||||
_etape = Etape.Scan;
|
_etape = Etape.Scan;
|
||||||
}
|
}
|
||||||
@@ -255,9 +259,15 @@
|
|||||||
// Le scan enchaîne directement sur le flux de lookup existant : l'utilisateur
|
// Le scan enchaîne directement sur le flux de lookup existant : l'utilisateur
|
||||||
// ne retape jamais ce qui vient d'être scanné, et n'a aucun bouton à confirmer.
|
// ne retape jamais ce qui vient d'être scanné, et n'a aucun bouton à confirmer.
|
||||||
// Le code lu reste dans le champ : si le décodage était mauvais, il se corrige et se relance.
|
// Le code lu reste dans le champ : si le décodage était mauvais, il se corrige et se relance.
|
||||||
private async Task SurCodeDetecteAsync(string isbn)
|
private async Task SurCodeDetecteAsync(CodeScanne lecture)
|
||||||
{
|
{
|
||||||
_isbn = isbn;
|
_isbn = lecture.Code;
|
||||||
|
|
||||||
|
// Gardé de côté pour la seule chose qui sache quoi en faire : la fiche d'une revue.
|
||||||
|
// Sur un livre, l'add-on EAN-5 (le prix) n'est même pas remonté, et l'EAN-2 n'a pas
|
||||||
|
// de sens.
|
||||||
|
_parutionLue = lecture.Parution;
|
||||||
|
|
||||||
await ChercherAsync();
|
await ChercherAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +275,9 @@
|
|||||||
{
|
{
|
||||||
if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(_isbn))
|
if (e.Key == "Enter" && !string.IsNullOrWhiteSpace(_isbn))
|
||||||
{
|
{
|
||||||
|
// Saisi à la main ou à la douchette : aucun add-on, et surtout pas celui d'un
|
||||||
|
// scan précédent, qui se retrouverait proposé pour un autre magazine.
|
||||||
|
_parutionLue = null;
|
||||||
await ChercherAsync();
|
await ChercherAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -393,7 +406,13 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Navigation.NavigateTo($"revues/{resultat.Valeur!.Id}");
|
// Le numéro lu sur l'add-on EAN-2 suit la navigation : la fiche le proposera dans un
|
||||||
|
// champ modifiable. On ne l'enregistre pas soi-même — voir CodeScanne.
|
||||||
|
var numero = string.IsNullOrWhiteSpace(_parutionLue)
|
||||||
|
? string.Empty
|
||||||
|
: $"?numero={Uri.EscapeDataString(_parutionLue)}";
|
||||||
|
|
||||||
|
Navigation.NavigateTo($"revues/{resultat.Valeur!.Id}{numero}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Ramène l'écran à la saisie, champ ISBN de nouveau actif pour la douchette.</summary>
|
/// <summary>Ramène l'écran à la saisie, champ ISBN de nouveau actif pour la douchette.</summary>
|
||||||
|
|||||||
@@ -87,6 +87,15 @@ else
|
|||||||
parution du code-barres ne sont PAS un numéro fiable (CLAUDE.md), et l'add-on EAN-2 qui
|
parution du code-barres ne sont PAS un numéro fiable (CLAUDE.md), et l'add-on EAN-2 qui
|
||||||
le porterait vraiment n'a pas pu être vérifié sur un magazine réel.
|
le porterait vraiment n'a pas pu être vérifié sur un magazine réel.
|
||||||
*@
|
*@
|
||||||
|
@if (_numeroPropose && !string.IsNullOrWhiteSpace(NumeroScanne))
|
||||||
|
{
|
||||||
|
<p class="message-avertissement" role="status">
|
||||||
|
Le code-barres portait « @NumeroScanne » à côté du code principal, ce qui est
|
||||||
|
normalement le numéro de parution. <strong>Vérifiez-le sur la couverture</strong>
|
||||||
|
avant d'enregistrer.
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="champs-numero">
|
<div class="champs-numero">
|
||||||
<label class="champ">
|
<label class="champ">
|
||||||
<span class="champ-libelle">Numéro</span>
|
<span class="champ-libelle">Numéro</span>
|
||||||
@@ -160,6 +169,21 @@ else
|
|||||||
@code {
|
@code {
|
||||||
[Parameter] public int Id { get; set; }
|
[Parameter] public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Numéro lu sur l'add-on <b>EAN-2</b> du code-barres, transmis par l'écran de scan.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// ⚠️ <b>Proposé, pas acquis.</b> L'EAN-2 est normativement le numéro de parution, mais
|
||||||
|
/// rien ne l'a confirmé sur des magazines réels : il atterrit donc dans le champ, où
|
||||||
|
/// l'utilisateur le compare à la couverture et le corrige. C'est aussi pourquoi l'écran le
|
||||||
|
/// dit explicitement au lieu de le glisser en douce.
|
||||||
|
/// </remarks>
|
||||||
|
[SupplyParameterFromQuery(Name = "numero")]
|
||||||
|
public string? NumeroScanne { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Le numéro scanné n'est proposé qu'une fois : ensuite, le champ appartient à l'utilisateur.</summary>
|
||||||
|
private bool _numeroPropose;
|
||||||
|
|
||||||
private RevueDto? _revue;
|
private RevueDto? _revue;
|
||||||
private bool _chargement = true;
|
private bool _chargement = true;
|
||||||
private bool _enCours;
|
private bool _enCours;
|
||||||
@@ -200,6 +224,12 @@ else
|
|||||||
_issn = _revue.Issn;
|
_issn = _revue.Issn;
|
||||||
_editeur = _revue.Editeur;
|
_editeur = _revue.Editeur;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_numeroPropose && !string.IsNullOrWhiteSpace(NumeroScanne))
|
||||||
|
{
|
||||||
|
_numeroPropose = true;
|
||||||
|
_numero = NumeroScanne.Trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Appliquer(RevueDto revue)
|
private void Appliquer(RevueDto revue)
|
||||||
|
|||||||
@@ -77,6 +77,15 @@ export async function demarrer(element) {
|
|||||||
try {
|
try {
|
||||||
await chargerZbar();
|
await chargerZbar();
|
||||||
scanner = await window.zbar.ZBarScanner.create();
|
scanner = await window.zbar.ZBarScanner.create();
|
||||||
|
|
||||||
|
// ⚠️ L'add-on EAN-2 est DÉSACTIVÉ par défaut dans zbar : sans cette ligne il n'est pas
|
||||||
|
// seulement filtré, il n'est jamais produit. Mesuré le 2026-08-19 sur un EAN-13 + EAN-2
|
||||||
|
// rendus en canvas — scanner par défaut : un seul symbole ; EAN-2 activé : deux.
|
||||||
|
// C'est lui qui porte le numéro de parution d'un périodique.
|
||||||
|
// L'EAN-5 reste désactivé : il porte un PRIX, dont on n'a rien à faire, et un symbole
|
||||||
|
// de moins est un symbole de moins à écarter.
|
||||||
|
scanner.setConfig(
|
||||||
|
window.zbar.ZBarSymbolType.ZBAR_EAN2, window.zbar.ZBarConfigType.ZBAR_CFG_ENABLE, 1);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 'erreur:Decodeur — ' + (e.message || 'zbar indisponible');
|
return 'erreur:Decodeur — ' + (e.message || 'zbar indisponible');
|
||||||
}
|
}
|
||||||
@@ -89,7 +98,7 @@ export async function demarrer(element) {
|
|||||||
return 'ok';
|
return 'ok';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture une image et la décode. Renvoie le premier code lu, ou null.
|
// Capture une image et la décode. Renvoie { code, parution }, ou null si rien n'est lu.
|
||||||
//
|
//
|
||||||
// ⚠️ L'image ENTIÈRE est analysée, contrairement à la version ZXing.Net qui n'envoyait que la
|
// ⚠️ L'image ENTIÈRE est analysée, contrairement à la version ZXing.Net qui n'envoyait que la
|
||||||
// bande centrale (45 % de la hauteur) pour limiter ce qui traversait le pont. Ce recadrage était
|
// bande centrale (45 % de la hauteur) pour limiter ce qui traversait le pont. Ce recadrage était
|
||||||
@@ -107,14 +116,19 @@ export async function decoder(largeurMax) {
|
|||||||
const symboles = await window.zbar.scanImageData(ctx.getImageData(0, 0, l, h), scanner);
|
const symboles = await window.zbar.scanImageData(ctx.getImageData(0, 0, l, h), scanner);
|
||||||
if (!symboles || symboles.length === 0) return null;
|
if (!symboles || symboles.length === 0) return null;
|
||||||
|
|
||||||
// Les add-ons EAN-2 (numéro de parution) et EAN-5 (prix) sont imprimés à côté du code
|
const typeDe = s => window.zbar.ZBarSymbolType[s.type];
|
||||||
// principal et remontent comme des symboles distincts : ils ne sont pas ce qu'on cherche.
|
|
||||||
const principal = symboles.find(s => {
|
|
||||||
const type = window.zbar.ZBarSymbolType[s.type];
|
|
||||||
return type !== 'ZBAR_EAN2' && type !== 'ZBAR_EAN5';
|
|
||||||
});
|
|
||||||
|
|
||||||
return principal ? principal.decode() : null;
|
// Les add-ons EAN-2 et EAN-5 sont imprimés À CÔTÉ du code principal et remontent comme des
|
||||||
|
// symboles distincts. Le code du livre — ou de la revue — n'est jamais l'un d'eux.
|
||||||
|
const principal = symboles.find(s => typeDe(s) !== 'ZBAR_EAN2' && typeDe(s) !== 'ZBAR_EAN5');
|
||||||
|
if (!principal) return null;
|
||||||
|
|
||||||
|
// L'EAN-2 porte le numéro de parution d'un périodique ; l'EAN-5 porte un PRIX et n'a rien à
|
||||||
|
// faire ici. On ne remonte donc que le premier, et seulement pour information : c'est
|
||||||
|
// l'écran qui décidera d'en faire quelque chose, et l'utilisateur qui confirmera.
|
||||||
|
const ean2 = symboles.find(s => typeDe(s) === 'ZBAR_EAN2');
|
||||||
|
|
||||||
|
return { code: principal.decode(), parution: ean2 ? ean2.decode() : null };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Libérer la caméra : sinon le voyant reste allumé et la batterie se vide,
|
// Libérer la caméra : sinon le voyant reste allumé et la batterie se vide,
|
||||||
|
|||||||
Reference in New Issue
Block a user