Vickes 318ce96cbc refactor: implement provider-based service architecture (#40)
Comprehensive refactoring to improve maintainability, reduce code duplication, and facilitate the addition of new music providers.

Key architectural improvements:
- Introduced BaseDownloadService template method pattern, eliminating ~80% code duplication between Deezer and Qobuz services
- Organized services by provider (Services/Deezer/, Services/Qobuz/, Services/Local/, Services/Subsonic/)
- Extracted SubsonicController logic into 4 specialized services, reducing controller size by 43% (1,174 → 666 lines)
- Reorganized Models into domain-driven subdirectories (Domain/, Settings/, Download/, Search/)
- Implemented Result<T> pattern and GlobalExceptionHandler for consistent error handling
- Created unified validation architecture with IStartupValidator interface

Testing & Quality:
- Increased test coverage from ~30 to 127 tests (+323%)
- Added comprehensive test suites for QobuzDownloadService and Subsonic services
- All tests passing, zero build errors

Impact:
- Net reduction of 343 lines while adding more functionality
- No breaking changes - Subsonic API surface unchanged
- Foundation ready for easy addition of new providers (Tidal, Spotify, etc.)
2026-01-08 23:57:09 +01:00
2026-01-03 22:04:53 +01:00

Octo-Fiesta

A Subsonic API proxy server that transparently integrates multiple music streaming providers as sources. When a song is not available in your local Navidrome library, it is automatically fetched from your configured provider, downloaded, and served to your Subsonic-compatible client. The downloaded song is then added to your library, making it available locally for future listens.

Why "Octo-Fiesta"?

The name was randomly generated by GitHub when creating the repository. We found it amusing and somewhat fitting for a music application — after all, "fiesta" evokes a party atmosphere, which goes well with music streaming. So we kept it!

Features

  • Multi-Provider Architecture: Pluggable music service system supporting multiple streaming providers (Deezer, Qobuz, and more to come)
  • Transparent Proxy: Acts as a middleware between Subsonic clients (like Aonsoku, Sublime Music, etc.) and your Navidrome server
  • Seamless Integration: Automatically searches and streams music from your configured provider when not available locally
  • Automatic Downloads: Songs are downloaded on-the-fly and cached for future use
  • Hi-Res Audio Support: Qobuz provider supports up to 24-bit/192kHz FLAC quality
  • Full Metadata Embedding: Downloaded files include complete ID3 tags (title, artist, album, track number, year, genre, BPM, ISRC, etc.) and embedded cover art
  • Organized Library: Downloads are saved in a clean Artist/Album/Track folder structure
  • Artist Deduplication: Merges local and streaming provider artists to avoid duplicates in search results
  • Album Enrichment: Local albums are enriched with missing tracks from streaming providers
  • Cover Art Proxy: Serves cover art for external content transparently

Compatible Clients

PC

Android

Want to improve client compatibility? Pull requests are welcome!

Incompatible Clients

These clients are not compatible with octo-fiesta due to architectural limitations:

  • Symfonium - Uses offline-first architecture and never queries the server for searches, making streaming provider integration impossible. See details

Supported Music Providers

  • Deezer - Quality: FLAC, MP3_320, MP3_128
  • Qobuz - Quality: FLAC, FLAC_24_HIGH (Hi-Res 24-bit/192kHz), FLAC_24_LOW, FLAC_16, MP3_320

Choose your preferred provider via the MUSIC_SERVICE environment variable. Additional providers may be added in future releases.

Requirements

  • A running Subsonic-compatible server (developed and tested with Navidrome)
  • Credentials for at least one music provider:
    • Deezer: ARL token from browser cookies
    • Qobuz: User ID + User Auth Token from browser localStorage (see Wiki guide)
  • Docker and Docker Compose (recommended) or .NET 9.0 SDK for manual installation

Quick Start (Docker)

The easiest way to run Octo-Fiesta is with Docker Compose.

  1. Create your environment file

    cp .env.example .env
    
  2. Edit the .env file with your configuration:

    # Navidrome/Subsonic server URL
    SUBSONIC_URL=http://your-navidrome-server:4533
    
    # Path where downloaded songs will be stored on the host
    DOWNLOAD_PATH=./downloads
    
    # Music service provider (Deezer or Qobuz)
    MUSIC_SERVICE=Qobuz
    
    # === Qobuz Configuration (if using Qobuz) ===
    QOBUZ_USER_AUTH_TOKEN=your-qobuz-token
    QOBUZ_USER_ID=your-qobuz-user-id
    QOBUZ_QUALITY=FLAC  # FLAC, FLAC_24_HIGH, FLAC_24_LOW, FLAC_16, MP3_320
    
    # === Deezer Configuration (if using Deezer) ===
    DEEZER_ARL=your-deezer-arl-token
    DEEZER_QUALITY=FLAC  # FLAC, MP3_320, MP3_128
    
  3. Start the container

    docker-compose up -d
    

    The proxy will be available at http://localhost:5274.

  4. Configure your Subsonic client

    Point your Subsonic client to http://localhost:5274 instead of your Navidrome server directly.

Tip

: Make sure the DOWNLOAD_PATH points to a directory that Navidrome can scan, so downloaded songs appear in your library.

Configuration

General Settings

Setting Description
Subsonic:Url URL of your Navidrome/Subsonic server
Subsonic:MusicService Music provider to use: Deezer or Qobuz (default: Deezer)
Library:DownloadPath Directory where downloaded songs are stored

Deezer Settings

Setting Description
Deezer:Arl Your Deezer ARL token (required if using Deezer)
Deezer:ArlFallback Backup ARL token if primary fails
Deezer:Quality Preferred audio quality: FLAC, MP3_320, MP3_128. If not specified, the highest available quality for your account will be used

Qobuz Settings

Setting Description
Qobuz:UserAuthToken Your Qobuz User Auth Token (required if using Qobuz) - How to get it
Qobuz:UserId Your Qobuz User ID (required if using Qobuz)
Qobuz:Quality Preferred audio quality: FLAC, FLAC_24_HIGH, FLAC_24_LOW, FLAC_16, MP3_320. If not specified, the highest available quality will be used

Getting Credentials

Deezer ARL Token

See the Wiki guide for detailed instructions on obtaining your Deezer ARL token.

Qobuz Credentials

See the Wiki guide for detailed instructions on obtaining your Qobuz User ID and User Auth Token.

Limitations

  • Playlist Search: Subsonic clients like Aonsoku filter playlists client-side from a cached getPlaylists call. Streaming provider playlists appear in global search (search3) but not in the Playlists tab filter.
  • Region Restrictions: Some tracks may be unavailable depending on your region and provider.
  • Token Expiration: Provider authentication tokens expire and need periodic refresh.

Architecture

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  Subsonic       │────▶│   Octo-Fiesta    │────▶│   Navidrome     │
│  Client         │◀────│   (Proxy)        │◀────│   Server        │
│  (Aonsoku)      │     │                  │     │                 │
└─────────────────┘     └────────┬─────────┘     └─────────────────┘
                                 │
                                 ▼
                        ┌─────────────────┐
                        │ Music Providers │
                        │  - Deezer       │
                        │  - Qobuz        │
                        │  - (more...)    │
                        └─────────────────┘

Manual Installation

If you prefer to run Octo-Fiesta without Docker:

  1. Clone the repository

    git clone https://github.com/your-username/octo-fiesta.git
    cd octo-fiesta
    
  2. Restore dependencies

    dotnet restore
    
  3. Configure the application

    Edit octo-fiesta/appsettings.json:

    {
      "Subsonic": {
        "Url": "http://your-navidrome-server:4533",
        "MusicService": "Qobuz"
      },
      "Library": {
        "DownloadPath": "./downloads"
      },
      "Qobuz": {
        "UserAuthToken": "your-qobuz-token",
        "UserId": "your-qobuz-user-id",
        "Quality": "FLAC"
      },
      "Deezer": {
        "Arl": "your-deezer-arl-token",
        "ArlFallback": "",
        "Quality": "FLAC"
      }
    }
    
  4. Run the server

    cd octo-fiesta
    dotnet run
    

    The proxy will start on http://localhost:5274 by default.

  5. Configure your Subsonic client

    Point your Subsonic client to http://localhost:5274 instead of your Navidrome server directly.

API Endpoints

The proxy implements the Subsonic API and adds transparent streaming provider integration to:

Endpoint Description
GET /rest/search3 Merged search results from Navidrome + streaming provider
GET /rest/stream Streams audio, downloading from provider if needed
GET /rest/getSong Returns song details (local or from provider)
GET /rest/getAlbum Returns album with tracks from both sources
GET /rest/getArtist Returns artist with albums from both sources
GET /rest/getCoverArt Proxies cover art for external content

All other Subsonic API endpoints are passed through to Navidrome unchanged.

External ID Format

External (streaming provider) content uses typed IDs:

Type Format Example
Song ext-{provider}-song-{id} ext-deezer-song-123456, ext-qobuz-song-789012
Album ext-{provider}-album-{id} ext-deezer-album-789012, ext-qobuz-album-456789
Artist ext-{provider}-artist-{id} ext-deezer-artist-259, ext-qobuz-artist-123

Legacy format ext-deezer-{id} is also supported (assumes song type).

Download Folder Structure

Downloaded music is organized as:

downloads/
├── Artist Name/
│   ├── Album Title/
│   │   ├── 01 - Track One.mp3
│   │   ├── 02 - Track Two.mp3
│   │   └── ...
│   └── Another Album/
│       └── ...
└── Another Artist/
    └── ...

Metadata Embedding

Downloaded files include:

  • Basic: Title, Artist, Album, Album Artist
  • Track Info: Track Number, Total Tracks, Disc Number
  • Dates: Year, Release Date
  • Audio: BPM, Duration
  • Identifiers: ISRC (in comments)
  • Credits: Contributors/Composers
  • Visual: Embedded cover art (high resolution)
  • Rights: Copyright, Label

Development

Build

dotnet build

Run Tests

dotnet test

Project Structure

octo-fiesta/
├── Controllers/
│   └── SubsonicController.cs              # Main API controller
├── Middleware/
│   └── GlobalExceptionHandler.cs          # Global error handling
├── Models/
│   ├── Domain/                            # Domain entities
│   │   ├── Song.cs
│   │   ├── Album.cs
│   │   └── Artist.cs
│   ├── Settings/                          # Configuration models
│   │   ├── SubsonicSettings.cs
│   │   ├── DeezerSettings.cs
│   │   └── QobuzSettings.cs
│   ├── Download/                          # Download-related models
│   │   ├── DownloadInfo.cs
│   │   └── DownloadStatus.cs
│   ├── Search/
│   │   └── SearchResult.cs
│   └── Subsonic/
│       └── ScanStatus.cs
├── Services/
│   ├── Common/                            # Shared services
│   │   ├── BaseDownloadService.cs         # Template method base class
│   │   ├── PathHelper.cs                  # Path utilities
│   │   ├── Result.cs                      # Result<T> pattern
│   │   └── Error.cs                       # Error types
│   ├── Deezer/                            # Deezer provider
│   │   ├── DeezerDownloadService.cs
│   │   ├── DeezerMetadataService.cs
│   │   └── DeezerStartupValidator.cs
│   ├── Qobuz/                             # Qobuz provider
│   │   ├── QobuzDownloadService.cs
│   │   ├── QobuzMetadataService.cs
│   │   ├── QobuzBundleService.cs
│   │   └── QobuzStartupValidator.cs
│   ├── Local/                             # Local library
│   │   ├── ILocalLibraryService.cs
│   │   └── LocalLibraryService.cs
│   ├── Subsonic/                          # Subsonic API logic
│   │   ├── SubsonicProxyService.cs        # Request proxying
│   │   ├── SubsonicModelMapper.cs         # Model mapping
│   │   ├── SubsonicRequestParser.cs       # Request parsing
│   │   └── SubsonicResponseBuilder.cs     # Response building
│   ├── Validation/                        # Startup validation
│   │   ├── IStartupValidator.cs
│   │   ├── BaseStartupValidator.cs
│   │   ├── SubsonicStartupValidator.cs
│   │   ├── StartupValidationOrchestrator.cs
│   │   └── ValidationResult.cs
│   ├── IDownloadService.cs                # Download interface
│   ├── IMusicMetadataService.cs           # Metadata interface
│   └── StartupValidationService.cs
├── Program.cs                             # Application entry point
└── appsettings.json                       # Configuration

octo-fiesta.Tests/
├── DeezerDownloadServiceTests.cs          # Deezer download tests
├── DeezerMetadataServiceTests.cs          # Deezer metadata tests
├── QobuzDownloadServiceTests.cs           # Qobuz download tests (127 tests)
├── LocalLibraryServiceTests.cs            # Local library tests
├── SubsonicModelMapperTests.cs            # Model mapping tests
├── SubsonicProxyServiceTests.cs           # Proxy service tests
├── SubsonicRequestParserTests.cs          # Request parser tests
└── SubsonicResponseBuilderTests.cs        # Response builder tests

Dependencies

  • BouncyCastle.Cryptography - Blowfish decryption for Deezer streams
  • TagLibSharp - ID3 tag and cover art embedding
  • Swashbuckle.AspNetCore - Swagger/OpenAPI documentation
  • xUnit - Unit testing framework
  • Moq - Mocking library for tests
  • FluentAssertions - Fluent assertion library for tests

License

GPL-3.0

Acknowledgments

  • Navidrome - The excellent self-hosted music server
  • Deezer - Music streaming service
  • Qobuz - Hi-Res music streaming service
  • Subsonic API - The API specification
Description
No description provided
Readme GPL-3.0 8.1 MiB
Languages
C# 90.1%
HTML 9.9%