14 KiB
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
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
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
MusicSourcetrait (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,pmometadatapmojspf(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 :
pmolocalscanne/home/user/Musicpmomusicboximporte les items découverts- Tags automatiques basés sur métadonnées (genre, année)
🔄 Flux d'import
Import depuis une source PMO (ex: Qobuz)
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<br/>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
flowchart LR
URL[URL simple] --> META["pmometadata<br/>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
flowchart LR
FILE[Fichier playlist] --> JSPF["pmojspf<br/>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)
{
"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
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
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_artdans 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
- Découverte : Écouter Radio Paradise, tomber sur un morceau génial
- Ajout :
musicbox.import_from_source(¶dise, "track-123") - Organisation : Ajouter tags
mood:chill,occasion:focus - Playlist : Smart playlist "Focus Music" avec requête
mood:chill + occasion:focus - É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
Inspirations
- Beets - Music library manager
- Navidrome - Music server
- MusicBrainz Picard - Tagger