# Champs et recherche — le catalogue Judbox

*Ce que chaque champ d'une fiche fait vraiment, et comment il pèse (ou pas) sur la recherche
du Jukebox et de la Platine DJ. À lire avant de se demander « pourquoi je ne retrouve pas ce morceau ».*

Source : `PlaylistController::apiCatalogueSearch`, `CatalogueController::apiAlbums` / `apiAlbumTracks`,
`views/pages/jukebox.php`, `views/pages/platine.php`. Reflète l'état du code au 2026-08-30.

> Cette doc est aussi consultable en ligne dans l'application : **/aide/catalogue**.
> Les deux sont générées depuis ce fichier — le tenir à jour si le comportement de recherche change.

---

## 1. Le modèle en trois entités

Une même chanson vit à trois niveaux dans Judbox. Comprendre lequel reçoit quelle information
évite l'essentiel des erreurs de saisie — et explique pourquoi un champ est « ici et pas ailleurs ».

| Entité | Répond à la question | Exemple |
|---|---|---|
| `jb_works` — **l'Œuvre** | Quelle *chanson* ? Qui l'a écrite / créée à l'origine ? | « Stairway to Heaven », composée par Led Zeppelin |
| `jb_recordings` — **l'Interprétation** | Quel *enregistrement* précis ? Qui joue, quand, sur quel lien ? | La version live 1973 par Led Zeppelin *et* la reprise de First to Eleven = deux interprétations de la même œuvre |
| `jb_albums` — **l'Album** | Sur quel *disque* paraît cet enregistrement ? | « Led Zeppelin IV », plage 4 |

Le lien va toujours dans ce sens : une **Œuvre** porte une ou plusieurs **Interprétations**
(`recordings.work_id`) ; une Interprétation peut être rattachée à un **Album**
(`recordings.album_id`, facultatif) ; une **playlist** et une **platine** jouent des
Interprétations, jamais des Œuvres abstraites.

**Pourquoi séparer Œuvre et Interprétation ?** Parce qu'une chanson existe en plusieurs
versions — studio, live, reprise, remix — qui partagent le titre et le compositeur mais diffèrent
par l'interprète, l'année, le lieu, le lien vidéo. Tout mettre sur une seule ligne rendrait
impossible de distinguer « la version originale » de « la reprise acoustique ». La fiche Œuvre
reste stable ; on lui ajoute des Interprétations au fil du temps.

---

## 2. Où mettre quelle information

| Tu veux saisir… | Champ | Pourquoi là |
|---|---|---|
| Le nom de la chanson | `works.title` | Stable, partagé par toutes les versions. Alimente la recherche par titre. |
| Le groupe / compositeur d'origine | `works.composer` | Propriété de l'œuvre, pas de la version. « Qui a écrit », pas « qui joue ». |
| L'artiste qui joue sur *cet* enregistrement | `recordings.performers` | Change d'une version à l'autre. Pour l'original, c'est souvent le même nom que le compositeur — on le remplit quand même. |
| « Version live », « reprise », « remix » | `recordings.recording_type` + **nouvelle interprétation** | On ne modifie pas la version studio : on *ajoute* une interprétation à la même œuvre. |
| Le lien YouTube de la vidéo | `recordings.embed_id` (champ « Lien de la vidéo YouTube ») | **Piège fréquent** : ce n'est pas « Lieu ». Un lien collé dans `venue` ne sert à rien et le morceau reste injouable. |
| Le lieu du concert | `recordings.venue` | Métadonnée d'affichage + filtre `venue`. Jamais le lien vidéo. |
| L'année de la chanson | `works.year_composed` | Année de composition, une fois pour toutes. |
| L'année de *cette* version | `recordings.year` | Année de sortie / du concert. C'est celle qu'utilisent les filtres `year`. |
| Le disque d'où vient le titre | `recordings.album_id` (datalist « Album ») | Rattache l'interprétation à un `jb_albums`. Sans ça, l'onglet *Albums* ne verra jamais ce titre. |
| Le nom / l'artiste de l'album | `albums.title`, `albums.artist` | Sur la fiche *Album*, pas sur le titre. `artist` vide = disque introuvable par nom d'artiste (repli : l'interprète des titres). |
| Les musiciens détaillés | `recordings.musicians` (un par ligne) | Champ libre wiki. **Non indexé** : utile à lire, invisible à la recherche. |
| Le style / genre | `works.genre_id` ou `recordings.genre_id` | Sur l'interprétation si la version change de style (reprise techno d'un titre rock) ; sinon sur l'œuvre. |

### Saisir un album entier

Pour un disque, l'écran **« Saisir un album entier »** (`/creator/album`, bouton depuis
*Morceaux*) évite de retaper les champs communs : on renseigne une fois l'interprète, l'année,
le studio, le producteur, le line-up, le compositeur et le style, puis chaque plage n'a que
**son titre et son lien**. Les valeurs communes sont **héritées** et affichées en gris sous chaque plage ; « Détails »
ouvre la plage pour en surcharger une (compilation, musicien invité, reprise…). Un seul envoi
crée l'album + toutes les œuvres + toutes les interprétations rattachées, avec le n° de plage.
Le studio commun est écrit dans `recordings.venue` de chaque plage, le producteur dans
`recordings.producer`. Case « Compilation » = l'interprète n'est plus hérité, on le saisit par plage.

La saisie est **sauvegardée automatiquement dans le navigateur** (localStorage) : une coupure
de connexion en cours de route ne fait rien perdre, il suffit de rouvrir la page. On peut aussi
**reprendre un album déjà enregistré** (menu déroulant en haut, ou `?album=ID`) : le bloc commun
et toutes les plages se rechargent, on modifie ce qu'on veut, on ajoute des plages, ou on en
**détache** une du disque (elle n'est jamais supprimée — le morceau reste au catalogue).

### Confusions les plus fréquentes

- **Lien vidéo dans « Lieu ».** Les deux libellés se ressemblaient ; « Lieu » est désormais
  « Lieu de l'enregistrement / du concert » et « Lien » est « Lien de la vidéo YouTube ».
- **Titre et interprète inversés** (« Magritte » comme titre, « Aliose & Maxime Le Forestier »
  comme interprète). L'import propose un bouton « ⇄ inverser » ; en saisie manuelle, relire.
- **Nom de l'artiste collé dans le titre du morceau** (« Dolly Parton Linda Ronstadt Emmylou
  Harris » en `title`). Le titre, c'est la chanson.
- **Titre du morceau saisi comme titre d'album.** Un single reste une Œuvre + une Interprétation ;
  l'Album ne se crée que s'il y a un vrai disque.
- **Interprétation créée sans lien.** Elle apparaît dans le catalogue, pas de vignette, injouable —
  normal tant que `embed_id` est vide.
- **Titre jamais rattaché à son album.** Il se joue à l'unité mais n'entre pas dans
  « charger l'album entier ».

---

## 3. La recherche en un coup d'œil

Cinq points d'entrée. La majorité des recherches passe par `/api/catalogue/search` — le même
endpoint pour le Jukebox, la Platine et l'ajout à une playlist.

| Endpoint | Utilisé par | Ce qu'il cherche |
|---|---|---|
| `/api/catalogue/search` | Jukebox › *Chercher* · Platine › *Chercher un titre* · Playlists › ajout | Voir la ventilation des paramètres ci-dessous. |
| `/api/albums` | Jukebox › *Albums* · Platine › *Albums* | `albums.title_norm`, `albums.artist_norm`, + interprète/compositeur des titres du disque. **N'affiche que les albums ayant au moins un titre YouTube jouable.** |
| `/api/albums/{id}/tracks` | Chargement d'un disque entier | Tous les titres du disque, ordre `track_no` puis `id`. Le lecteur écarte ensuite les non-YouTube. |
| `/api/playlists/{id}` | Jukebox & Platine › *Playlists* | Les titres de la playlist, chacun pointant vers une **interprétation** précise (`recording_id`). |
| `/catalogue` (page) | Navigation du catalogue | Plus restreint : `works.title_norm` ou `works.composer_norm` + style. **Ne cherche ni l'interprète ni l'album.** |

### Paramètres de `/api/catalogue/search`

| Paramètre | Colonne(s) visée(s) | Texte |
|---|---|---|
| `q` *(large)* | `works.title_norm`, `works.composer_norm`, `recordings.performers`, `recordings.ensemble`, `recordings.conductor`, `albums.title`, `albums.artist` | mixte — normalisé sur les `*_norm`, brut ailleurs |
| `performer` | `recordings.performers` | brut |
| `conductor` | `recordings.conductor` | brut |
| `ensemble` | `recordings.ensemble` | brut |
| `venue` | `recordings.venue` | brut — **hors `q`** |
| `genre_id` | `COALESCE(recordings.genre_id, works.genre_id)` | — |
| `recording_type` | `recordings.recording_type` — `studio` / `live` / `cover` / `remix` / `other` | — |
| `album_id` | `recordings.album_id` | — |
| `year` · `year_from` · `year_to` | `recordings.year` | — |

Deux règles : les filtres se combinent en **ET** (tous doivent matcher), et sans aucun critère
l'endpoint renvoie quand même le catalogue — trié par titre d'œuvre, **plafonné à 60 lignes**.

---

## 4. Normalisation du texte

Les colonnes `title_norm`, `composer_norm` et `artist_norm` stockent une version « aplatie » du
champ, recalculée à chaque modification du champ verrouillé correspondant. La recherche `q`
compare contre cette version.

```
norm("Antonín Dvořák — Symphonie n°9")  →  "antonin dvorak symphonie n 9"

· translittération ASCII      Dvořák → dvorak
· minuscules                   The Great → the great
· tout non-alphanumérique      →  espace
· espaces multiples            →  un seul
```

La casse, les accents et la ponctuation n'ont **aucun effet** sur la recherche.
« THE GREAT PRETENDER », « The Great Pretender » et « the great, pretender » trouvent la même fiche.

**Ce que la normalisation ne rattrape pas :**

- Une **faute de frappe** : « The Gr**q**et Pretender » reste une chaîne différente de « great ».
  Pas de recherche floue ni de tolérance orthographique.
- Un champ **vide** : si `albums.artist` n'est pas renseigné, aucune recherche par nom d'artiste
  ne trouvera le disque via ce champ.
- Les champs **non normalisés** (`performers`, `conductor`…) : comparés bruts. Le collation MySQL
  reste insensible à la casse et généralement aux accents, mais la ponctuation compte.

À part : la déduplication d'œuvres et d'albums utilise `normName()`, qui trie les mots en plus —
« Vivaldi, Antonio » et « Antonio Vivaldi » sont considérés identiques.

---

## 5. Lecteur : YouTube uniquement

Le Jukebox comme la Platine ne savent lire qu'un lien **YouTube**. Le filtre est strict :
`embed_id` doit faire exactement 11 caractères `[A-Za-z0-9_-]`.

| Cas | Dans la recherche ? | Jouable ? |
|---|---|---|
| YouTube, `embed_id` valide (11 car.) | oui | **oui** |
| `embed_provider = 'vimeo'` ou `'other'` | oui (résultats `/api/catalogue/search`) | **non** — écarté par le lecteur |
| `media_type = 'upload'` (fichier) | oui | **non** — pas encore géré |
| `embed_id` corrompu (texte au lieu d'un ID, hérité de l'import Terra) | selon les autres champs | **non** — écarté silencieusement |

C'est la cause n°1 des « le morceau a disparu » : il est dans le catalogue et peut remonter dans
une recherche, mais le lecteur le retire de la file parce que son lien n'est pas un ID YouTube
exploitable. La vignette se déduit automatiquement de l'ID YouTube quand `thumbnail_url` est vide.

---

## 6. Champs — l'Œuvre (`jb_works`)

L'œuvre, c'est *la chanson* : un titre + un compositeur / groupe d'origine. Une œuvre porte une
ou plusieurs interprétations.

| Champ | Rôle | Effet sur la recherche |
|---|---|---|
| `title` | Titre du morceau | **clé** — alimente `title_norm` → matché par `q` partout, et par `/catalogue` |
| `composer` | Compositeur / groupe d'origine | **clé** — alimente `composer_norm` → matché par `q` |
| `genre_id` | Style de l'œuvre (référentiel `music_genres`) | filtre `genre_id`, en *repli* si l'interprétation n'a pas son propre style |
| `year_composed` | Année de composition | affichage seul — non filtré |
| `description` | Texte libre, éditable par tout membre (wiki) | non indexé |

---

## 7. Champs — l'Interprétation (`jb_recordings`)

Un enregistrement concret d'une œuvre : qui joue, quand, sur quel disque, avec quel lien média.
C'est ce que pointe une playlist, et ce que joue une platine.

| Champ | Rôle | Effet sur la recherche |
|---|---|---|
| `performers` | Qui joue réellement (l'interprète) | **clé** — `q` (large) + filtre `performer` |
| `conductor` | Chef d'orchestre | `q` + filtre `conductor` |
| `ensemble` | Orchestre / groupe | `q` + filtre `ensemble` |
| `musicians` | Musiciens détaillés, un par ligne | **non indexé** — invisible à la recherche |
| `producer` | Producteur | **non indexé** — crédit d'affichage. Défaut hérité de `albums.producer` |
| `recording_type` | `studio` / `live` / `cover` / `remix` / `other` | filtre `recording_type` |
| `year` | Année de *cette* version | filtres `year`, `year_from`, `year_to` |
| `venue` | Lieu du concert / de l'enregistrement | filtre `venue` seulement — **pas dans `q`** |
| `genre_id` | Style de cette version (une reprise peut changer de style) | filtre `genre_id` — **prioritaire** sur celui de l'œuvre |
| `album_id` | Rattachement à un disque | filtre `album_id` · **obligatoire pour apparaître dans l'onglet Albums** |
| `track_no` | Numéro de plage | ordre de lecture de l'album |
| `embed_provider` · `embed_id` | Lien média | **jouable** uniquement si YouTube, 11 caractères |
| `media_type` · `file_url` | Fichier téléversé (compositions perso) | **pas encore lu** par le Jukebox / la Platine |
| `thumbnail_url` | Vignette | déduite de l'ID YouTube si vide |
| `duration` | Durée | affichage |
| `is_published` | Publié / brouillon | **non filtré** aujourd'hui — toute interprétation remonte |
| `sig_hash` | Signature anti-doublon (performers + conductor + ensemble + year + venue + type + genre) | interne — bloque une interprétation strictement identique |

---

## 8. Champs — l'Album (`jb_albums`)

Le disque parent (33 tours, CD, single…). Un album peut porter plusieurs pochettes
(`jb_album_covers`) et regroupe ses interprétations via `recordings.album_id`.

| Champ | Rôle | Effet sur la recherche |
|---|---|---|
| `title` | Titre du disque | **clé** — `title_norm` → `q` de `/api/albums` |
| `artist` | Artiste du disque | `artist_norm` → `q` de `/api/albums`. **Souvent vide** — dans ce cas le repli est l'interprète des titres du disque |
| `year` | Année de parution | affichage |
| `label` | Label | affichage |
| `studio` | Studio d'enregistrement (défaut des plages → `recordings.venue`) | affichage |
| `producer` | Producteur (défaut des plages → `recordings.producer`) | affichage |
| `format` | `lp` / `cd` / `ep` / `single` / `digital` / `other` | affichage |
| `genre_id` | Style du disque | affichage |
| `description` | Champ libre (tout membre) | non indexé |

Rappel `/api/albums` : un album n'apparaît dans l'onglet *Albums* que s'il a **au moins une
interprétation avec un `embed_id` YouTube**. Un disque saisi sans lien sur ses titres est
invisible côté écoute.

---

## 9. Le piège du jour

Le cas « *je ne retrouve pas l'album The Great Pretender de Dolly Parton* » cumulait quatre
problèmes distincts — un seul était un vrai bug.

1. **`albums.artist` vide.** « Dolly Parton » figurait sur les `performers` des titres mais pas
   sur le disque. La recherche Albums ne regardait alors que le titre. *Corrigé* — `/api/albums`
   cherche désormais aussi dans l'interprète / le compositeur des titres du disque.
2. **Casse « great » vs « Great ».** Sans effet : `title_norm` aplatit tout en minuscules.
   Ce n'était pas le problème.
3. **Doublon « The Gr**q**et Pretender ».** Faute de frappe → entrée d'album distincte, 0 titre
   rattaché. Invisible dans l'onglet Albums mais elle encombre le catalogue. À corriger à la main.
4. **3 titres sur ~10.** « Album complet » est une question de complétude des données,
   pas de recherche.

---

## 10. Pour qu'un morceau soit trouvable *et* jouable

L'ordre compte : les premiers points conditionnent la recherche, les suivants la lecture et le
rangement par album.

1. **Œuvre :** `title` et `composer` renseignés → recherche par titre et par compositeur.
2. **Interprétation :** `performers` renseigné → recherche par nom d'interprète.
3. **Lien :** `embed_id` = un lien **YouTube** valide (11 caractères). Sans ça, le morceau
   remonte peut-être dans la recherche mais ne sera jamais joué.
4. **Album :** pour l'onglet *Albums* — `recordings.album_id` rattaché, `albums.title` propre,
   idéalement `albums.artist` rempli, et `track_no` pour l'ordre des plages.
5. **Style :** `genre_id` sur l'œuvre ou l'interprétation → filtre par style.
6. **Orthographe :** zéro faute dans les champs qui alimentent `title_norm` / `composer_norm` /
   `artist_norm`. La recherche est exacte, pas floue.
