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


🏛️ 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

Index & Recherche


🎨 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)

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_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(&paradise, "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

Inspirations