**Il faut suivre les instructions générales placées dans le fichier : Blackboard/Rules.md** # MusicBoxSource : Bibliothèque musicale universelle Créer une **"boîte à musique"** personnelle : un catalogue unifié de morceaux provenant de n'importe quelle source (Qobuz, URLs, fichiers locaux, Radio Paradise, etc.), avec taxonomie de tags et playlists intelligentes. --- ## 🎯 Vision ### Concept **MusicBoxSource** est une bibliothèque musicale curatoriale qui permet de : - **Collecter** : Ajouter des morceaux depuis n'importe quelle source PMOMusic ou URL - **Organiser** : Classifier avec une taxonomie de tags extensible - **Requêter** : Créer des playlists statiques et smart playlists (requêtes dynamiques) - **Exposer** : Servir via UPnP/DIDL-Lite avec navigation multi-axes ### Différence avec `pmoplaylist` - **`pmoplaylist`** : Playlists FIFO **éphémères** pour sources live (Radio Paradise) - **`pmomusicbox`** : Bibliothèque **persistante** cross-sources avec métadonnées enrichies --- ## 🏛️ Architecture globale ```mermaid flowchart TB subgraph Sources[Sources PMOMusic] QOBUZ[pmoqobuz] PARADISE[pmoparadise] LOCAL[pmolocal - à créer] URL[URLs directes] end subgraph Import[Import Layer] IMPORTER[MusicBox Importer] JSPF[pmojspf - Parser playlists] META[pmometadata - Extraction] end subgraph Core[pmomusicbox Core] DB[(SQLite Database)] TAXONOMY[Taxonomie Tags] QUERY[Smart Query Engine] end subgraph Cache[Cache Layer] AUDIO[pmoaudiocache] COVERS[pmocovers] end subgraph Export[Export UPnP] SOURCE[MusicSource Trait] DIDL[DIDL-Lite Generator] BROWSE[Multi-Axis Browser] end Sources --> IMPORTER URL --> IMPORTER JSPF --> IMPORTER META --> IMPORTER IMPORTER --> DB DB --> TAXONOMY DB --> QUERY DB <--> AUDIO DB <--> COVERS DB --> SOURCE TAXONOMY --> BROWSE QUERY --> BROWSE SOURCE --> DIDL BROWSE --> DIDL ``` --- ## 🗄️ Modèle de données (SQLite) ### Tables principales ```mermaid erDiagram TAG_CATEGORIES ||--o{ TAGS : contient TAG_CATEGORIES ||--o{ TAG_CATEGORIES : parent TAGS ||--o{ ITEM_TAGS : associe MUSIC_ITEMS ||--o{ ITEM_TAGS : a MUSIC_ITEMS ||--o{ PLAYLIST_ITEMS : dans PLAYLISTS ||--o{ PLAYLIST_ITEMS : contient TAG_CATEGORIES { text id PK "Ex: mood, genre" text name "Nom affiché" text parent_id FK "Hiérarchie" text color "Hex color" text icon "Emoji/icon" int display_order } TAGS { text id PK "Ex: mood:energetic" text category_id FK text name "energetic, chill" text description text color "Override" } MUSIC_ITEMS { text id PK "UUID" text source_type "qobuz, url, local" text source_id "ID source" text original_uri "URI source" text cache_audio_pk FK "pmoaudiocache" text cache_cover_pk FK "pmocovers" text title text artist text album int year int rating "1-5 étoiles" int play_count } ITEM_TAGS { text item_id PK,FK text tag_id PK,FK int added_at text source "user, auto" } PLAYLISTS { text id PK text name bool is_smart text smart_query "JSON" } PLAYLIST_ITEMS { text playlist_id PK,FK text item_id FK int position PK } ``` ### Tables d'association - **`item_tags`** : Liens items ↔ tags (N:M) - **`playlist_items`** : Items dans playlists statiques (position, ordre) - **`tag_synonyms`** : Synonymes pour recherche (ex: "jazz" → "swing") ### Index & Recherche - **Indexes B-tree** : artist, album, genre, year, rating, play_count - **FTS5 (Full-Text Search)** : title, artist, album, comment - **Triggers** : Maintien des tables FTS en sync avec `music_items` --- ## 🎨 Taxonomie par défaut Catégories préchargées à l'initialisation : | Catégorie | Description | Exemples de tags | |-------------|----------------------------------|--------------------------------------------| | **Mood** | État d'esprit, émotion | energetic, chill, melancholic, happy | | **Genre** | Style musical | rock, jazz, classical, electronic, metal | | **Era** | Période, décennie | 60s, 70s, 80s, 90s, contemporary | | **Occasion**| Contexte d'écoute | workout, focus, party, driving, sleep | | **Tempo** | Vitesse | slow, medium, fast | | **Instrument** | Instrument dominant | piano, guitar, vocal, synthesizer | | **Quality** | Qualité audio | lossless, high-res, remastered, live | | **Origin** | Origine géographique | usa, uk, france, japan, latin, africa | **Extensibilité** : L'utilisateur peut créer ses propres catégories et tags. --- ## 📦 Crates architecture ### 1. **`pmojspf`** - Parser de playlists (utilitaire) **But** : Parser/écrire différents formats de playlists vers/depuis un format pivot JSPF (JSON). ``` pmojspf/ ├── model.rs # Structures JSPF (Playlist, Track, Meta) ├── reader/ │ ├── jspf.rs # JSON natif │ ├── xspf.rs # XML (via quick-xml ou crate xspf) │ ├── m3u.rs # M3U/M3U8 (parsing ligne par ligne) │ └── pls.rs # PLS (format INI-like) └── writer.rs # Export JSPF ``` **Dépendances** : `serde`, `serde_json`, `quick-xml` (ou `xspf` crate) **Usage** : Réutilisé par `pmomusicbox` pour import/export --- ### 2. **`pmomusicbox`** - Bibliothèque musicale core **Responsabilités** : - Gestion base SQLite (CRUD items, tags, playlists) - Import depuis sources PMO (Qobuz, Paradise, Local, URLs) - Smart playlists (query builder + exécution SQL) - Implémentation `MusicSource` trait (exposition UPnP) - Intégration caches audio/covers ``` pmomusicbox/ ├── db/ │ ├── schema.rs # DDL SQLite + migrations │ ├── items.rs # CRUD music_items │ ├── tags.rs # CRUD tags + taxonomie │ ├── playlists.rs # CRUD playlists statiques │ ├── smart.rs # Smart playlists │ └── search.rs # Full-text search (FTS5) │ ├── import/ │ ├── url.rs # Import URL directe │ ├── source.rs # Import depuis MusicSource │ ├── local.rs # Import fichiers locaux (via pmometadata) │ └── playlist.rs # Import JSPF/M3U8 (via pmojspf) │ ├── export/ │ └── playlist.rs # Export playlists (JSPF, M3U8) │ ├── query/ │ ├── builder.rs # SmartPlaylistQuery (DSL) │ └── executor.rs # Génération + exécution SQL │ ├── didl/ │ └── generator.rs # Conversion items → DIDL-Lite │ ├── source.rs # Impl MusicSource trait ├── taxonomy.rs # Taxonomie par défaut + CRUD └── config_ext.rs # Extension pmoconfig ``` **Dépendances** : - `pmosource`, `pmoaudiocache`, `pmocovers`, `pmodidl`, `pmometadata` - `pmojspf` (import/export playlists) - `rusqlite` (features: `bundled`, `serde_json`) - `uuid`, `serde`, `tokio`, `async-trait` --- ### 3. **`pmolocal`** - Source fichiers locaux (à créer) **But** : Scanner des répertoires locaux et exposer les fichiers audio via `MusicSource`. ``` pmolocal/ ├── scanner.rs # Scan récursif de répertoires ├── watcher.rs # Hot reload (notify) ├── source.rs # Impl MusicSource └── config_ext.rs # Extension pmoconfig ``` **Workflow** : 1. `pmolocal` scanne `/home/user/Music` 2. `pmomusicbox` importe les items découverts 3. Tags automatiques basés sur métadonnées (genre, année) --- ## 🔄 Flux d'import ### Import depuis une source PMO (ex: Qobuz) ```mermaid sequenceDiagram participant QS as Qobuz Source participant MB as MusicBox Importer participant DB as SQLite DB participant AC as pmoaudiocache participant CC as pmocovers QS->>MB: get_item(object_id) MB->>QS: resolve_uri(object_id) Note over MB: 1. Extraire métadonnées DIDL-Lite
2. Générer UUID MB->>DB: INSERT INTO music_items opt Auto-cache activé MB->>AC: Cache audio MB->>CC: Cache cover AC-->>DB: Retourner cache_audio_pk CC-->>DB: Retourner cache_cover_pk end MB-->>QS: item_id (UUID) ``` ### Import URL directe ```mermaid flowchart LR URL[URL simple] --> META["pmometadata
Extraction"] META --> UUID[Générer UUID] UUID --> DB[("music_items")] DB --> CACHE{"Auto-cache?"} CACHE -->|Oui| AC[pmoaudiocache] CACHE -->|Non| END[Fin] AC --> END ``` ### Import playlist JSPF/M3U8 ```mermaid flowchart LR FILE[Fichier playlist] --> JSPF["pmojspf
Parser"] JSPF --> STRUCT[Structure JSPF] STRUCT --> LOOP{"Pour chaque track"} LOOP --> IMPORT[Import comme URL] IMPORT --> DB[("music_items")] DB --> PLAYLIST[Créer playlist statique] PLAYLIST --> LINK[Lier tracks à playlist] ``` --- ## 🔍 Smart Playlists (Query DSL) ### Concept Les smart playlists sont des **requêtes sauvegardées** qui génèrent dynamiquement une liste de tracks. ### Structure de requête (JSON) ```json { "include_all_tags": ["mood:energetic", "genre:rock"], "exclude_tags": ["mood:melancholic"], "year_min": 1980, "year_max": 1989, "min_rating": 4, "lossless_only": true, "order_by": "play_count", "order": "desc", "limit": 50 } ``` ### Traduction SQL ```sql SELECT * FROM music_items WHERE id IN ( SELECT item_id FROM item_tags WHERE tag_id IN ('mood:energetic', 'genre:rock') GROUP BY item_id HAVING COUNT(DISTINCT tag_id) = 2 -- ALL tags ) AND id NOT IN ( SELECT item_id FROM item_tags WHERE tag_id = 'mood:melancholic' ) AND year BETWEEN 1980 AND 1989 AND rating >= 4 AND codec IN ('flac', 'alac') ORDER BY play_count DESC LIMIT 50; ``` --- ## 🎭 Exposition UPnP (MusicSource) ### Structure de navigation ```mermaid graph TB ROOT[musicbox/] --> ARTIST[by-artist/] ROOT --> ALBUM[by-album/] ROOT --> GENRE[by-genre/] ROOT --> TAG[by-tag/] ROOT --> PLAYLISTS[playlists/] ROOT --> SMART[smart-playlists/] ROOT --> FAV[favorites/] ROOT --> RECENT[recent/] ARTIST --> PF[Pink Floyd/] ARTIST --> Q[Queen/] PF --> WALL[The Wall/] PF --> WYWH[Wish You Were Here/] WALL --> ITEM1[Another Brick... 🎵] TAG --> MOOD[mood/] TAG --> OCC[occasion/] TAG --> ERA[era/] MOOD --> ENRG[energetic/] MOOD --> CHILL[chill/] ENRG --> ITEMS1[items taggués 🎵] OCC --> WORK[workout/] OCC --> FOCUS[focus/] ERA --> E80[80s/] ERA --> E90[90s/] PLAYLISTS --> PL1[My Favorites/] PLAYLISTS --> PL2[Summer 2024/] SMART --> SP1[80s Rock Workout/] SMART --> SP2[Jazz Dinner/] style ITEM1 fill:#e1f5ff style ITEMS1 fill:#e1f5ff ``` ### Object IDs ``` musicbox:by-artist:{artist_name} musicbox:by-album:{album_id} musicbox:by-tag:{category}:{tag_name} musicbox:playlist:{playlist_id} musicbox:smart:{smart_playlist_id} musicbox:item:{item_id} ``` --- ## 🔌 Intégration avec l'écosystème PMOMusic ### Avec pmoaudiocache - Import → Déclencher cache automatique (si `auto_cache: true`) - `resolve_uri()` → Retourner URI cachée si disponible ### Avec pmocovers - Import → Télécharger cover art - Browse → Inclure `album_art` dans DIDL-Lite ### Avec pmoserver (feature `server`) - API REST pour manipulation (CRUD items, tags, playlists) - SSE pour notifications de changements - Endpoints OpenAPI (utoipa) --- ## 📝 Plan d'implémentation (Phases) ### Phase 1 : Fondations - Schéma SQLite complet - Crate `pmojspf` (parser playlists) - CRUD basique dans `pmomusicbox` (items, tags) - Taxonomie par défaut - Import URL simple - Extension pmoconfig ### Phase 2 : Import cross-sources - Import depuis MusicSource (Qobuz, Paradise) - Import playlists (JSPF/M3U8) - Intégration caches (audio, covers) - Crate `pmolocal` (fichiers locaux) ### Phase 3 : Smart Playlists - Query builder (DSL) - Exécuteur SQL - CRUD smart playlists - Export JSPF ### Phase 4 : MusicSource UPnP - Implémentation trait `MusicSource` - Génération DIDL-Lite - Browse multi-axes (artist, album, tag) - Recherche full-text (FTS5) ### Phase 5 : Fonctionnalités avancées - Statistiques d'écoute (play_count, last_played) - Auto-tagging (genre depuis métadonnées) - API REST (feature `server`) - Recommandations (items similaires) --- ## 🎯 Cas d'usage ### Workflow typique 1. **Découverte** : Écouter Radio Paradise, tomber sur un morceau génial 2. **Ajout** : `musicbox.import_from_source(¶dise, "track-123")` 3. **Organisation** : Ajouter tags `mood:chill`, `occasion:focus` 4. **Playlist** : Smart playlist "Focus Music" avec requête `mood:chill + occasion:focus` 5. **Écoute** : Naviguer dans UPnP → `musicbox/smart-playlists/Focus Music/` ### Scénario : Bibliothèque mixte - Albums Qobuz haute résolution - Playlists M3U8 importées depuis iTunes - Fichiers FLAC locaux scannés - URLs de SoundCloud - Tracks Radio Paradise capturés **Tout unifié dans MusicBox, accessible via UPnP, organisé par tags.** --- ## 📚 Références ### Standards - [JSPF Spec](https://www.xspf.org/jspf) - [XSPF Spec](https://www.xspf.org/spec) - [SQLite FTS5](https://www.sqlite.org/fts5.html) ### Inspirations - [Beets](https://beets.io/) - Music library manager - [Navidrome](https://www.navidrome.org/) - Music server - [MusicBrainz Picard](https://picard.musicbrainz.org/) - Tagger