mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
Clean up repository - remove unnecessary files and folders
Removed: - TODO.md (old todo list, can use GitHub Issues instead) - tests/ (minimal test coverage, not actively maintained) - docs/ (mostly empty documentation files) - contributing/ (redundant with .github/contributing.md) - appimage/ (AppImage-specific files, only needed for AppImage builds) Kept essential files: - .gitignore (required for Git) - .gitmodules (required for submodules) - LICENSE (legal requirement) - README.md (project documentation) - .github/ (workflows, templates, community files) - Dockerfile (Docker support) Repository now has clean, minimal structure with only necessary files.
This commit is contained in:
@@ -1,78 +0,0 @@
|
|||||||
# @michily TODO
|
|
||||||
|
|
||||||
## UI
|
|
||||||
* Auto update WebUI - version check + api missing
|
|
||||||
* Web UI - Remove https from index.html for http support?
|
|
||||||
* Web UI could use continues build like https://github.com/AppImage/AppImageKit/releases/download/continuous/
|
|
||||||
* Web UI - playlist not shown in folder view
|
|
||||||
* rework argparse with subparser. Currently not clear what commands allow what args
|
|
||||||
|
|
||||||
## Building:
|
|
||||||
* AppImage build is currently broken view [python-appimage: Issues 95](https://github.com/niess/python-appimage/issues/94) aka I bypassed it.
|
|
||||||
* Optimise docker/speed build up
|
|
||||||
|
|
||||||
## Server:
|
|
||||||
* Rework song name/autor/.. parsing to only support filetags. Only fall back when user-enabled and manual regex is set. see Telegram
|
|
||||||
* Publish this on PyPi
|
|
||||||
|
|
||||||
## Multithreading
|
|
||||||
* Multiprocessing creates new paths - sync between processes. <- env is recommended.
|
|
||||||
* Fix singleton global in multiprocessing - own process, own memory, own sys.modules cache <- env is recommended.
|
|
||||||
|
|
||||||
## Auth:
|
|
||||||
* more multiuser control
|
|
||||||
* audit log
|
|
||||||
* one auth method for all e.g. jwt in Header?
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
|
|
||||||
- Migrations:
|
|
||||||
|
|
||||||
1. Move userdata to new hashing algorithm
|
|
||||||
- favorites ✅
|
|
||||||
- playlists
|
|
||||||
- scrobble
|
|
||||||
- images
|
|
||||||
- remove image colors
|
|
||||||
|
|
||||||
- Package jsoni and publish on PyPi
|
|
||||||
- last updated date on tracks added via watchdog is broken
|
|
||||||
- Disable the watchdog by default, and mark it as experimental
|
|
||||||
- rename userid to server id in config file
|
|
||||||
- Look into seeding jwts using user password + server id
|
|
||||||
|
|
||||||
|
|
||||||
<!-- CHECKPOINT -->
|
|
||||||
<!-- ALBUM PAGE! -->
|
|
||||||
|
|
||||||
# DONE
|
|
||||||
|
|
||||||
- Support auth headers
|
|
||||||
- Add recently played playlist
|
|
||||||
- Move user track logs to user zero
|
|
||||||
- Move future logs to appropriate user id
|
|
||||||
- Store (and read) from the correct user account:
|
|
||||||
1. Playlists
|
|
||||||
2. Favorites
|
|
||||||
|
|
||||||
# THE BIG ONE
|
|
||||||
|
|
||||||
- Watchdog
|
|
||||||
- Periodic scans
|
|
||||||
- What about our migrations?
|
|
||||||
- Test foreign keys on delete
|
|
||||||
- Normalize playlists table:
|
|
||||||
- New table to hold playlist entries
|
|
||||||
- Normalize similar artists:
|
|
||||||
- New table to hold similar artist entries
|
|
||||||
- Create 2 way relationships, such that if an artist A is similar to another B with a certain weight,
|
|
||||||
then artist B is similar to A with the same weight, unless overwritten.
|
|
||||||
- Clean up tempfiles after transcoding
|
|
||||||
- Double sort artist tracks for consistency (alphabetically then by other field. eg. playcount)
|
|
||||||
|
|
||||||
# Bug fixes
|
|
||||||
|
|
||||||
- Duplicates on search
|
|
||||||
- Audio stops on ending
|
|
||||||
- Show users on account settings when logged in as admin and show users on login is disabled.
|
|
||||||
- Save both filepath and trackhash in favorites and playlists
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
exec "${APPDIR}/usr/bin/python" -m swingmusic --client "${APPDIR}/client" "$@"
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
pillow>=11.1.0
|
|
||||||
Flask>=3.1.0
|
|
||||||
Flask-Cors>=3.0.10
|
|
||||||
requests>=2.27.1
|
|
||||||
colorgram.py>=1.2.0
|
|
||||||
tqdm>=4.65.0
|
|
||||||
tinytag>=2.1.1
|
|
||||||
Unidecode>=1.3.6
|
|
||||||
psutil>=5.9.4
|
|
||||||
show-in-file-manager>=1.1.4
|
|
||||||
flask-compress>=1.13
|
|
||||||
tabulate>=0.9.0
|
|
||||||
setproctitle>=1.3.2
|
|
||||||
locust>=2.20.1
|
|
||||||
watchdog>=4.0.0
|
|
||||||
flask-jwt-extended>=4.6.0
|
|
||||||
sqlalchemy>=2.0.31
|
|
||||||
memory-profiler>=0.61.0
|
|
||||||
sortedcontainers>=2.4.0
|
|
||||||
xxhash>=3.4.1
|
|
||||||
ffmpeg-python>=0.2.0
|
|
||||||
schedule>=1.2.2
|
|
||||||
flask-openapi3==3.0.2
|
|
||||||
rapidfuzz==3.11.0
|
|
||||||
pendulum>=3.0.0
|
|
||||||
pystray>=0.19.5
|
|
||||||
bjoern>=3.2.2
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<component type="desktop-application">
|
|
||||||
<id>swingmusic</id>
|
|
||||||
<metadata_license>MIT</metadata_license>
|
|
||||||
<project_license>AGPL-3.0</project_license>
|
|
||||||
<name>Swing Music</name>
|
|
||||||
<summary>Music server for your files running on Python {{ python-fullversion }}</summary>
|
|
||||||
<description>
|
|
||||||
<p>
|
|
||||||
Swing Music is a fast and beautiful, self-hosted music player for your local audio files.
|
|
||||||
Like a cooler Spotify ... but bring your own music.
|
|
||||||
Just run the app and enjoy your music library in a web browser.
|
|
||||||
</p>
|
|
||||||
</description>
|
|
||||||
<launchable type="desktop-id">swingmusic.desktop</launchable>
|
|
||||||
<url type="homepage">https://swingmx.com/</url>
|
|
||||||
<screenshots>
|
|
||||||
<screenshot type="default">
|
|
||||||
<image>https://raw.githubusercontent.com/swing-opensource/swingmusic/master/.github/images/artist.webp</image>
|
|
||||||
</screenshot>
|
|
||||||
</screenshots>
|
|
||||||
<provides>
|
|
||||||
<id>swingmusic.desktop</id>
|
|
||||||
</provides>
|
|
||||||
</component>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[Desktop Entry]
|
|
||||||
Type=Application
|
|
||||||
Name=swingmusic
|
|
||||||
GenericName=Music player
|
|
||||||
Exec=swingmusic
|
|
||||||
Comment=A Music Server running on {{ python-fullversion }}
|
|
||||||
Icon=swingmusic
|
|
||||||
Categories=AudioVideo;Music;
|
|
||||||
Terminal=true
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1,402 +0,0 @@
|
|||||||
# SwingMusic Project Structure and Architecture
|
|
||||||
|
|
||||||
This document provides a comprehensive overview of how SwingMusic is structured, how its components interact, and the application lifecycle from startup to ready state.
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> Disclaimer: This document was drafted with the help of an LLM. Last updated on July 14th, 2025.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
SwingMusic is a modern music streaming server built with Python (Flask) on the backend and Vue.js on the frontend. It provides a self-hosted music library management system with features like audio streaming, playlist management, favorite tracking, and metadata extraction.
|
|
||||||
|
|
||||||
## High-Level Architecture
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "Client Layer"
|
|
||||||
WEB[Web Client<br/>Vue.js SPA]
|
|
||||||
MOBILE[Mobile Apps<br/>Third-party clients]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "API Layer"
|
|
||||||
FLASK[Flask-OpenAPI3<br/>REST API Server]
|
|
||||||
AUTH[JWT Authentication<br/>Middleware]
|
|
||||||
CORS[CORS & Compression<br/>Middleware]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Business Logic"
|
|
||||||
STORES[In-Memory Stores<br/>Fast Data Access]
|
|
||||||
LIBS[Library Functions<br/>Core Business Logic]
|
|
||||||
PLUGINS[Plugin System<br/>Extensible Features]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Data Layer"
|
|
||||||
SQLITE[SQLite Database<br/>Persistent Storage]
|
|
||||||
FILES[File System<br/>Audio Files & Images]
|
|
||||||
CONFIG[JSON Config<br/>User Settings]
|
|
||||||
end
|
|
||||||
|
|
||||||
WEB --> FLASK
|
|
||||||
MOBILE --> FLASK
|
|
||||||
FLASK --> AUTH
|
|
||||||
AUTH --> STORES
|
|
||||||
STORES --> LIBS
|
|
||||||
LIBS --> SQLITE
|
|
||||||
LIBS --> FILES
|
|
||||||
FLASK --> CONFIG
|
|
||||||
```
|
|
||||||
|
|
||||||
## Directory Structure
|
|
||||||
|
|
||||||
```sh
|
|
||||||
swingmusic/
|
|
||||||
├── client/ # Built Vue.js frontend (static files)
|
|
||||||
├── swingmusic/ # Main Python package
|
|
||||||
│ ├── __main__.py # CLI entry point
|
|
||||||
│ ├── api/ # REST API endpoints
|
|
||||||
│ │ ├── __init__.py # Flask app creation & blueprint registration
|
|
||||||
│ │ ├── album.py # Album-related endpoints
|
|
||||||
│ │ ├── artist.py # Artist-related endpoints
|
|
||||||
│ │ ├── auth.py # Authentication & user management
|
|
||||||
│ │ ├── stream.py # Audio streaming & transcoding
|
|
||||||
│ │ ├── search.py # Search functionality
|
|
||||||
│ │ ├── playlist.py # Playlist management
|
|
||||||
│ │ ├── settings.py # Configuration endpoints
|
|
||||||
│ │ └── ... # Other API modules
|
|
||||||
│ ├── models/ # Data models (dataclasses)
|
|
||||||
│ │ ├── album.py # Album model with type detection
|
|
||||||
│ │ ├── artist.py # Artist model with image handling
|
|
||||||
│ │ ├── track.py # Track model with metadata processing
|
|
||||||
│ │ ├── playlist.py # Playlist model
|
|
||||||
│ │ └── user.py # User model
|
|
||||||
│ ├── store/ # In-memory data stores
|
|
||||||
│ │ ├── albums.py # Album store with hash-based lookup
|
|
||||||
│ │ ├── artists.py # Artist store with relationship mapping
|
|
||||||
│ │ ├── tracks.py # Track store with grouping by hash
|
|
||||||
│ │ └── folder.py # Folder structure store
|
|
||||||
│ ├── db/ # Database layer
|
|
||||||
│ │ ├── engine.py # SQLAlchemy engine & session management
|
|
||||||
│ │ ├── libdata.py # Library data tables (tracks, albums, etc.)
|
|
||||||
│ │ ├── userdata.py # User data tables (favorites, playlists)
|
|
||||||
│ │ └── sqlite/ # SQLite-specific utilities & migrations
|
|
||||||
│ ├── lib/ # Core business logic
|
|
||||||
│ │ ├── populate.py # Media scanning & indexing
|
|
||||||
│ │ ├── trackslib.py # Track processing utilities
|
|
||||||
│ │ ├── albumslib.py # Album processing utilities
|
|
||||||
│ │ ├── artistlib.py # Artist processing utilities
|
|
||||||
│ │ ├── searchlib.py # Search algorithms
|
|
||||||
│ │ ├── tagger.py # Metadata extraction & processing
|
|
||||||
│ │ └── transcoder.py # Audio format conversion
|
|
||||||
│ ├── utils/ # Utility functions
|
|
||||||
│ │ ├── hashing.py # Hash generation for entities
|
|
||||||
│ │ ├── auth.py # Authentication utilities
|
|
||||||
│ │ ├── parsers.py # Text parsing (artists, titles, etc.)
|
|
||||||
│ │ └── ... # Other utilities
|
|
||||||
│ ├── plugins/ # Plugin system
|
|
||||||
│ │ ├── lyrics.py # Lyrics fetching
|
|
||||||
│ │ ├── mixes.py # Auto-generated playlists
|
|
||||||
│ │ └── lastfm.py # Last.fm integration
|
|
||||||
│ ├── crons/ # Background tasks
|
|
||||||
│ │ ├── cron.py # Cron job scheduler
|
|
||||||
│ │ └── mixes.py # Automated playlist generation
|
|
||||||
│ ├── config.py # Configuration management
|
|
||||||
│ ├── settings.py # Path and system settings
|
|
||||||
│ └── start_swingmusic.py # Application startup
|
|
||||||
├── run.py # Application launcher
|
|
||||||
├── requirements.txt # Python dependencies
|
|
||||||
└── docs/ # Documentation
|
|
||||||
```
|
|
||||||
|
|
||||||
## Core Components Deep Dive
|
|
||||||
|
|
||||||
### 1. Data Models (`models/`)
|
|
||||||
|
|
||||||
SwingMusic uses dataclasses for its core data models, providing type safety and automatic serialization:
|
|
||||||
|
|
||||||
#### Track Model (`models/track.py`)
|
|
||||||
- **Purpose**: Represents individual audio files with rich metadata
|
|
||||||
- **Key Features**:
|
|
||||||
- Artist splitting and featured artist extraction
|
|
||||||
- Genre processing with configurable separators
|
|
||||||
- Hash-based unique identification
|
|
||||||
- Play statistics tracking
|
|
||||||
- User-specific favorite status
|
|
||||||
|
|
||||||
#### Album Model (`models/album.py`)
|
|
||||||
- **Purpose**: Groups tracks into albums
|
|
||||||
- **Key Features**:
|
|
||||||
- Automatic album type classification (album, single, EP, compilation, live, soundtrack)
|
|
||||||
- Version detection (deluxe, remastered, etc.)
|
|
||||||
- Artist relationship management
|
|
||||||
|
|
||||||
#### Artist Model (`models/artist.py`)
|
|
||||||
- **Purpose**: Represents music artists with aggregated statistics
|
|
||||||
- **Key Features**:
|
|
||||||
- Album and track counting
|
|
||||||
- Genre aggregation
|
|
||||||
- Image management
|
|
||||||
- Play statistics
|
|
||||||
|
|
||||||
### 2. In-Memory Stores (`store/`)
|
|
||||||
|
|
||||||
The store layer provides fast O(1) access to frequently used data:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
subgraph "Store Architecture"
|
|
||||||
TS[TrackStore<br/>trackhash → TrackGroup]
|
|
||||||
AS[AlbumStore<br/>albumhash → AlbumMapEntry]
|
|
||||||
ARS[ArtistStore<br/>artisthash → ArtistMapEntry]
|
|
||||||
FS[FolderStore<br/>File path mapping]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Relationships"
|
|
||||||
TS --> AS
|
|
||||||
AS --> ARS
|
|
||||||
ARS --> TS
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
#### TrackStore
|
|
||||||
- **Structure**: `dict[trackhash, TrackGroup]`
|
|
||||||
- **Purpose**: Groups duplicate tracks (same content, different quality)
|
|
||||||
- **Key Methods**: `get_tracks_by_trackhashes()`, `get_tracks_by_albumhash()`
|
|
||||||
|
|
||||||
#### AlbumStore
|
|
||||||
- **Structure**: `dict[albumhash, AlbumMapEntry]`
|
|
||||||
- **Purpose**: Maps albums to their track collections
|
|
||||||
- **Key Methods**: `get_album_by_hash()`, `get_albums_by_artisthash()`
|
|
||||||
|
|
||||||
#### ArtistStore
|
|
||||||
- **Structure**: `dict[artisthash, ArtistMapEntry]`
|
|
||||||
- **Purpose**: Maps artists to their albums and tracks
|
|
||||||
- **Key Methods**: `get_artist_by_hash()`, `get_artist_tracks()`
|
|
||||||
|
|
||||||
### 3. Database Layer (`db/`)
|
|
||||||
|
|
||||||
Uses SQLAlchemy with SQLite for persistent storage:
|
|
||||||
|
|
||||||
#### Engine Configuration (`db/engine.py`)
|
|
||||||
```python
|
|
||||||
# Optimized SQLite settings
|
|
||||||
PRAGMA journal_mode=WAL # Write-Ahead Logging for concurrency
|
|
||||||
PRAGMA synchronous=NORMAL # Balanced durability/performance
|
|
||||||
PRAGMA cache_size=10000 # Large memory cache
|
|
||||||
PRAGMA foreign_keys=ON # Referential integrity
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Table Structure
|
|
||||||
- **Library Data**: Tracks, albums, artists, genres
|
|
||||||
- **User Data**: Favorites, playlists, play statistics, user accounts
|
|
||||||
- **Metadata**: Similar artists, color palettes, plugin data
|
|
||||||
|
|
||||||
### 4. API Layer (`api/`)
|
|
||||||
|
|
||||||
Built with Flask-OpenAPI3 for automatic documentation and validation:
|
|
||||||
|
|
||||||
#### Authentication System (`api/auth.py`)
|
|
||||||
- **JWT-based**: 30-day tokens with automatic refresh
|
|
||||||
- **Multiple auth methods**: Username/password, pairing codes for mobile
|
|
||||||
- **Role-based access**: Admin and regular user roles
|
|
||||||
- **Guest support**: Limited access without authentication
|
|
||||||
|
|
||||||
#### Core Endpoints
|
|
||||||
- **`/album`**: Album information, tracks, versions, similar albums
|
|
||||||
- **`/artist`**: Artist details, discography, top tracks
|
|
||||||
- **`/search`**: Fuzzy search across all content types
|
|
||||||
- **`/file`**: Audio streaming with transcoding support
|
|
||||||
- **`/playlist`**: CRUD operations for playlists
|
|
||||||
|
|
||||||
#### Streaming (`api/stream.py`)
|
|
||||||
- **Format support**: MP3, AAC, FLAC, WebM, OGG
|
|
||||||
- **Quality options**: 96kbps to lossless
|
|
||||||
- **Range requests**: Efficient seeking and partial downloads
|
|
||||||
- **Real-time transcoding**: On-demand format conversion (broken)
|
|
||||||
|
|
||||||
## Application Startup Sequence
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
sequenceDiagram
|
|
||||||
participant CLI as run.py
|
|
||||||
participant Main as __main__.py
|
|
||||||
participant Setup as setup/
|
|
||||||
participant DB as Database
|
|
||||||
participant Stores as Memory Stores
|
|
||||||
participant Flask as Flask App
|
|
||||||
participant BG as Background Tasks
|
|
||||||
|
|
||||||
CLI->>Main: start_swingmusic(host, port)
|
|
||||||
Main->>Setup: run_setup()
|
|
||||||
Setup->>DB: Create directories, setup SQLite
|
|
||||||
Setup->>DB: Run migrations
|
|
||||||
Main->>Flask: create_api()
|
|
||||||
Flask->>Flask: Register blueprints & middleware
|
|
||||||
Main->>Stores: load_into_mem()
|
|
||||||
Stores->>DB: Load all tracks
|
|
||||||
Stores->>Stores: Build hash maps
|
|
||||||
Stores->>Stores: Map relationships
|
|
||||||
Main->>BG: Start background tasks
|
|
||||||
BG->>BG: Register plugins
|
|
||||||
BG->>BG: Start cron jobs
|
|
||||||
Main->>Flask: Run server (bjoern/waitress)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Detailed Startup Process
|
|
||||||
|
|
||||||
1. **CLI Initialization** (`run.py`, `__main__.py`)
|
|
||||||
- Parse command line arguments
|
|
||||||
- Set configuration directory
|
|
||||||
- Initialize multiprocessing
|
|
||||||
|
|
||||||
2. **Setup Phase** (`setup/__init__.py`)
|
|
||||||
- **Directory Creation**: Config, database, cache directories
|
|
||||||
- **Database Setup**: SQLite file creation and optimization
|
|
||||||
- **Migrations**: Schema updates and data transformations
|
|
||||||
- **Config Loading**: User preferences and default settings
|
|
||||||
|
|
||||||
3. **Memory Loading** (`setup/__init__.py:load_into_mem()`)
|
|
||||||
```python
|
|
||||||
# Load sequence (order matters!)
|
|
||||||
TrackStore.load_all_tracks() # ~15-30s for large libraries
|
|
||||||
AlbumStore.load_albums() # Depends on tracks
|
|
||||||
ArtistStore.load_artists() # Depends on tracks/albums
|
|
||||||
FolderStore.load_filepaths() # File system mapping
|
|
||||||
|
|
||||||
# Map additional data
|
|
||||||
map_scrobble_data() # Play statistics
|
|
||||||
map_favorites() # User favorites
|
|
||||||
map_artist_colors() # UI color themes
|
|
||||||
map_album_colors() # Album artwork colors
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Flask Application Setup** (`start_swingmusic.py`)
|
|
||||||
- **API Creation**: Blueprint registration, OpenAPI docs
|
|
||||||
- **Middleware Setup**: JWT authentication, CORS, compression
|
|
||||||
- **Static Files**: Client serving with gzip support
|
|
||||||
- **Route Protection**: Authentication requirements
|
|
||||||
|
|
||||||
5. **Background Services** (`start_swingmusic.py`)
|
|
||||||
- **Plugin Registration**: Load and initialize plugins
|
|
||||||
- **Cron Jobs**: Periodic tasks (scanning, cleanup)
|
|
||||||
- **Process Management**: Set process title, resource limits
|
|
||||||
|
|
||||||
6. **Server Launch**
|
|
||||||
- **WSGI Server**: Bjoern (preferred) or Waitress fallback
|
|
||||||
- **Configuration**: Threading, IPv4/IPv6 support
|
|
||||||
- **Ready State**: API accepting requests
|
|
||||||
|
|
||||||
## Data Flow Examples
|
|
||||||
|
|
||||||
### Track Lookup Flow
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
API[API Request<br/>GET /track/abc123] --> Store[TrackStore.get_tracks_by_trackhashes]
|
|
||||||
Store --> Group[TrackGroup.get_best]
|
|
||||||
Group --> Track[Track Object<br/>Highest bitrate version]
|
|
||||||
Track --> Serialize[Serialize for API]
|
|
||||||
Serialize --> Response[JSON Response]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search Flow
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
API[API Request<br/>POST /search] --> Search[SearchLib.search_all]
|
|
||||||
Search --> Tracks[Search TrackStore]
|
|
||||||
Search --> Albums[Search AlbumStore]
|
|
||||||
Search --> Artists[Search ArtistStore]
|
|
||||||
Tracks --> Merge[Merge & Rank Results]
|
|
||||||
Albums --> Merge
|
|
||||||
Artists --> Merge
|
|
||||||
Merge --> Paginate[Apply Pagination]
|
|
||||||
Paginate --> Response[JSON Response]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Audio Streaming Flow
|
|
||||||
```mermaid
|
|
||||||
graph LR
|
|
||||||
API[GET /file/trackhash] --> Auth[Verify JWT Token]
|
|
||||||
Auth --> Lookup[TrackStore Lookup]
|
|
||||||
Lookup --> File[Check File Exists]
|
|
||||||
File --> Transcode{Transcoding<br/>Needed?}
|
|
||||||
Transcode -->|Yes| Convert[FFmpeg Conversion]
|
|
||||||
Transcode -->|No| Direct[Direct File Stream]
|
|
||||||
Convert --> Stream[HTTP Response<br/>with Range Support]
|
|
||||||
Direct --> Stream
|
|
||||||
```
|
|
||||||
|
|
||||||
## Component Coordination
|
|
||||||
|
|
||||||
### Hash-Based Entity System
|
|
||||||
SwingMusic uses hashes for entity identification:
|
|
||||||
|
|
||||||
- **Track Hash**: `hash(title + album + artists)` - Content-based deduplication
|
|
||||||
- **Album Hash**: `hash(title + album_artists)` - Album identification
|
|
||||||
- **Artist Hash**: `hash(artist_name)` - Artist identification
|
|
||||||
- **Weak Hash**: Fuzzy matching for similar content
|
|
||||||
|
|
||||||
### Relationship Management
|
|
||||||
```mermaid
|
|
||||||
graph TB
|
|
||||||
subgraph "Entity Relationships"
|
|
||||||
Track --> Album
|
|
||||||
Track --> Artists[Multiple Artists]
|
|
||||||
Album --> AlbumArtists[Album Artists]
|
|
||||||
Album --> Tracks[Track Collection]
|
|
||||||
Artist --> Albums[Artist Albums]
|
|
||||||
Artist --> ArtistTracks[Artist Tracks]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph "Store Coordination"
|
|
||||||
TrackStore -.-> AlbumStore
|
|
||||||
AlbumStore -.-> ArtistStore
|
|
||||||
ArtistStore -.-> TrackStore
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration System
|
|
||||||
- **JSON Config**: User preferences stored in `config.json`
|
|
||||||
- **Runtime Settings**: Database-backed settings for admin features
|
|
||||||
- **Environment Variables**: System-level configuration
|
|
||||||
- **Default Values**: Sensible defaults for new installations
|
|
||||||
|
|
||||||
## Key Workflows
|
|
||||||
|
|
||||||
### Music Library Scanning
|
|
||||||
1. **Directory Walking**: Recursive file discovery
|
|
||||||
2. **Metadata Extraction**: ID3, FLAC, etc. tag reading
|
|
||||||
3. **Artist Splitting**: Configurable separator handling
|
|
||||||
4. **Duplicate Detection**: Hash-based deduplication
|
|
||||||
5. **Database Updates**: Incremental changes only
|
|
||||||
6. **Store Refresh**: Update in-memory caches
|
|
||||||
|
|
||||||
### User Authentication
|
|
||||||
1. **Login Request**: Username/password or pairing code
|
|
||||||
2. **Credential Validation**: Database lookup and verification
|
|
||||||
3. **JWT Generation**: 30-day token with user claims
|
|
||||||
4. **Cookie Setting**: Secure HTTP-only cookies
|
|
||||||
5. **Request Middleware**: Token validation on each request
|
|
||||||
6. **Auto Refresh**: Transparent token renewal
|
|
||||||
|
|
||||||
### Plugin System
|
|
||||||
1. **Plugin Discovery**: Scan plugins directory
|
|
||||||
2. **Registration**: Import and validate plugin modules
|
|
||||||
3. **Hook Integration**: Connect to application events
|
|
||||||
4. **Configuration**: Plugin-specific settings
|
|
||||||
5. **Background Tasks**: Scheduled plugin execution
|
|
||||||
|
|
||||||
## Monitoring and Logging
|
|
||||||
|
|
||||||
- **Application Logs**: Structured logging with severity levels
|
|
||||||
- **Performance Metrics**: Response times, memory usage
|
|
||||||
- **Error Tracking**: Exception logging and stack traces
|
|
||||||
- **User Analytics**: Play counts, popular content (optional)
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
- **Input Validation**: Pydantic schemas for API requests
|
|
||||||
- **File Path Safety**: Prevent directory traversal attacks
|
|
||||||
- **JWT Security**: Secure token generation and validation
|
|
||||||
- **CORS Configuration**: Controlled cross-origin access
|
|
||||||
- **Rate Limiting**: Protection against abuse (configurable)
|
|
||||||
|
|
||||||
This architecture provides a robust, scalable foundation for music streaming while maintaining simplicity and ease of deployment. The separation of concerns between stores, business logic, and API layers ensures maintainability and extensibility as the project grows.
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
TODO:
|
|
||||||
* add architecture of swingmusic
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
config folder
|
|
||||||
├───swingmusic
|
|
||||||
├───assets
|
|
||||||
├───images
|
|
||||||
│ ├───artists
|
|
||||||
│ │ ├───large
|
|
||||||
│ │ ├───medium
|
|
||||||
│ │ └───small
|
|
||||||
│ ├───mixes
|
|
||||||
│ │ ├───medium
|
|
||||||
│ │ ├───original
|
|
||||||
│ │ └───small
|
|
||||||
│ ├───playlists
|
|
||||||
│ └───thumbnails
|
|
||||||
│ ├───large
|
|
||||||
│ ├───medium
|
|
||||||
│ ├───small
|
|
||||||
│ └───xsmall
|
|
||||||
└───plugins
|
|
||||||
└───lyrics
|
|
||||||
```
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
## Streaming
|
|
||||||
|
|
||||||
## Transcoding
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
import unittest
|
|
||||||
|
|
||||||
def split_artists(src: str, separators: set[str], ignoreList: set[str] = set()):
|
|
||||||
"""
|
|
||||||
Splits a string of artists into a list of artists, preserving those in ignoreList.
|
|
||||||
Case-insensitive matching is used for the ignoreList.
|
|
||||||
"""
|
|
||||||
result = []
|
|
||||||
current = ""
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
# Convert ignoreList to lowercase for case-insensitive matching
|
|
||||||
ignore_lower = {artist.lower() for artist in ignoreList}
|
|
||||||
|
|
||||||
while i < len(src):
|
|
||||||
# Check if any ignored artist starts at this position (case-insensitive)
|
|
||||||
ignored_match = next(
|
|
||||||
(
|
|
||||||
src[i:i+len(ignored)]
|
|
||||||
for ignored in ignoreList
|
|
||||||
if src.lower().startswith(ignored.lower(), i)
|
|
||||||
),
|
|
||||||
None
|
|
||||||
)
|
|
||||||
|
|
||||||
if ignored_match:
|
|
||||||
# If we have accumulated any current string, add it to result
|
|
||||||
if current.strip():
|
|
||||||
result.extend([a.strip() for a in current.split(',') if a.strip()])
|
|
||||||
current = ""
|
|
||||||
# Add the ignored artist to the result (preserving original case)
|
|
||||||
result.append(ignored_match)
|
|
||||||
# Move past the ignored artist
|
|
||||||
i += len(ignored_match)
|
|
||||||
elif src[i] in separators:
|
|
||||||
# If we encounter a separator, process the current string
|
|
||||||
if current.strip():
|
|
||||||
result.extend([a.strip() for a in current.split(',') if a.strip()])
|
|
||||||
current = ""
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
# If it's not an ignored artist or a separator, add to current
|
|
||||||
current += src[i]
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# Process any remaining current string
|
|
||||||
if current.strip():
|
|
||||||
result.extend([a.strip() for a in current.split(',') if a.strip()])
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class TestSplitArtists(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_basic_splitting(self):
|
|
||||||
self.assertEqual(
|
|
||||||
split_artists("Beatles, Queen; Rolling Stones", {";"}),
|
|
||||||
["Beatles", "Queen", "Rolling Stones"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_multiple_separators(self):
|
|
||||||
self.assertEqual(
|
|
||||||
split_artists("Beatles; Queen & Rolling Stones | ABBA", {";", "&", "|"}),
|
|
||||||
["Beatles", "Queen", "Rolling Stones", "ABBA"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_ignore_list(self):
|
|
||||||
self.assertEqual(
|
|
||||||
split_artists(
|
|
||||||
"Beatles; Earth, Wind & Fire; Queen", {";", "&"}, {"Earth, Wind & Fire"}
|
|
||||||
),
|
|
||||||
["Beatles", "Earth, Wind & Fire", "Queen"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_empty_string(self):
|
|
||||||
self.assertEqual(split_artists("", {";"}), [])
|
|
||||||
|
|
||||||
def test_only_separators(self):
|
|
||||||
self.assertEqual(split_artists(";;;", {";"}), [])
|
|
||||||
|
|
||||||
def test_extra_spaces(self):
|
|
||||||
self.assertEqual(
|
|
||||||
split_artists(" Beatles ; Queen ", {";"}), ["Beatles", "Queen"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_comma_splitting(self):
|
|
||||||
self.assertEqual(
|
|
||||||
split_artists("Beatles, Queen; Rolling Stones, ABBA", {";"}),
|
|
||||||
["Beatles", "Queen", "Rolling Stones", "ABBA"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_ignore_list_with_comma(self):
|
|
||||||
self.assertEqual(
|
|
||||||
split_artists(
|
|
||||||
"Beatles; Earth, Wind & Fire, Queen", {";"}, {"Earth, Wind & Fire"}
|
|
||||||
),
|
|
||||||
["Beatles", "Earth, Wind & Fire", "Queen"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_ignore_list_with_separator(self):
|
|
||||||
self.assertEqual(
|
|
||||||
split_artists("Beatles; AC/DC", {"/", ";"}, {"AC/DC"}), ["Beatles", "AC/DC"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_ignore_list_at_start(self):
|
|
||||||
self.assertEqual(
|
|
||||||
split_artists("AC/DC; Beatles", {"/", ";"}, {"AC/DC"}), ["AC/DC", "Beatles"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_ignore_list_at_end(self):
|
|
||||||
self.assertEqual(
|
|
||||||
split_artists("Beatles; AC/DC", {"/", ";"}, {"AC/DC"}), ["Beatles", "AC/DC"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_multiple_ignored_artists(self):
|
|
||||||
self.assertEqual(
|
|
||||||
split_artists(
|
|
||||||
"Beatles; AC/DC; Guns N' Roses; Queen",
|
|
||||||
{"/", ";", "'"},
|
|
||||||
{"AC/DC", "Guns N' Roses"},
|
|
||||||
),
|
|
||||||
["Beatles", "AC/DC", "Guns N' Roses", "Queen"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_bob_marley(self):
|
|
||||||
self.assertEqual(
|
|
||||||
split_artists(
|
|
||||||
"Bob marley & The wailers; Beatles",
|
|
||||||
{";", "&"},
|
|
||||||
{"Bob marley & the wailers"},
|
|
||||||
),
|
|
||||||
["Bob marley & The wailers", "Beatles"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
Reference in New Issue
Block a user