An implementation of OpenSubsonic API in Rust
- Nghe
- Written in Rust with performance in-mind.
- All tags are multiple by default (artists, album artists, languages, etc).
- Well-tested and highly customizable.
- Well-defined permission model with music folders.
- Multi-platform, runs on Linux, FreeBSD, MacOS and Windows. Docker images with two variants GNU or MUSL are also provided.
- Bridging with
ffmpeg c api
for in-memory transcoding and smooth stream experience. Most common formats (opus, mp3, acc, wav, etc) are supported. Does not required any manual configuration beforehand, justmaxBitRate
andformat
in the request parameters are enough. - Synchoronized lyrics from external
lrc
files. - AWS S3 compatible storage support. Tested with Minio for every commit.
The easiest way for getting started is using docker. Below is a docker-compose.yaml
example. A random hex key can be generated using openssl rand -hex 16
.
services:
nghe:
image: ghcr.io/vnghia/nghe-musl:0.3.0
ports:
- "3000:3000"
restart: unless-stopped
environment:
NGHE_DATABASE__URL: postgres://postgres:postgres@db:5432/postgres
NGHE_DATABASE__KEY: a20eb15ac92cabfd96b81fb154b16357
volumes:
- /your-music-folder/:/data/music/:ro
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
The minimum required postgres version is 16. Alternatively, you can also download corresponding binary with your OS and the frontend package in the release section.
Once the server is running, go to [your-server-url]/setup
to setup a first admin account. After that, login and go to the folders menu on the right side of the screen. You can add a new music folder from there, hit the scan button, choose one scan mode (more detail in scan process) and start using the server while your media files are being scanned.
This server works best with Symfonium and Airsonic since they support multiple values well enough.
All configurations can be set by environment variable with a NGHE_
prefix and a __
between each level of inheritance. For example, the config database.url
is correspondent to NGHE_DATABASE__URL
.
Subkey | Meaning | Default value | Note |
---|---|---|---|
host | IP host to bind the server to | 127.0.0.1 |
:: for Docker |
port | Port to bind the server to | 3000 | |
frontend_dir | The directory contains the pre-built frontend | $PWD/frontend/dist/ |
/app/frontend/ for Docker |
Subkey | Meaning | Default value | Note |
---|---|---|---|
url | URL to connect to the database | ||
key | A 32-characters hex string to use as encryption key for sensetive data (password) |
Subkey | Meaning | Default value | Note |
---|---|---|---|
ignored_articles | Articles to ignore while building artist indexes | "The An A Die Das Ein Eine Les Le La" |
Parsing config allows you to tells the server exactly where to read your songs metadata. Currently, belows tags are supported.
Subkey | Meaning | Id3v2 | VorbisComments | Note |
---|---|---|---|---|
song | Subconfiguration for parsing song | song | ||
album | Subconfiguration for parsing album | album | ||
artist | Artist names | TPE1 | ARTIST | |
album_artist | Album artist names | TPE2 | ALBUMARTIST | |
track_number | Track number | TRCK | TRACKNUMBER | number and total |
track_total | Track Total | TRACKTOTAL | number and total | |
disc_number | Disc number | TPOS | DISCNUMBER | number and total |
disc_total | Disc Total | DISCTOTAL | number and total | |
language | Languages | TLAN | LANGUAGE | Should be a ISO 639-3 or 639-1 code, not 639-2 |
genre | Genres | TCON | GENRE | |
artist_mbz_id | Artist musicbrainz id | "MusicBrainz Artist Id" | MUSICBRAINZ_ARTISTID | Should be specified only if you have only one artist in the tag because the order is not perserved while parsing |
artist_mbz_id | Artist musicbrainz id | "MusicBrainz Album Artist Id" | MUSICBRAINZ_ALBUMARTISTID | Should be specified only if you have only one album artist in the tag because the order is not perserved while parsing |
Some fields in this table are not official and usually used to distinguish between date-related information of albums and songs. If you have don't have that information, just leave it as default.
Subkey | Meaning | Id3v2 | VorbisComments | Note |
---|---|---|---|---|
name | Song name | TIT2 | TITLE | |
date | Song recording date | TRCS | SDATE | Set null to completely disable parsing this field |
release_date | Song release date | TSRL | SRELEASEDATE | Set null to completely disable parsing this field |
original_release_date | Song original release date | TSOR | SORIGYEAR | Set null to completely disable parsing this field |
mbz_id | Song musicbrainz id | "MusicBrainz Release Track Id" | MUSICBRAINZ_RELEASETRACKID |
Subkey | Meaning | Id3v2 | VorbisComments | Note |
---|---|---|---|---|
name | Album name | TALB | ALBUM | |
date | Album recording date | TDRC | DATE | Set null to completely disable parsing this field |
release_date | Album release date | TDRL | RELEASEDATE | Set null to completely disable parsing this field |
original_release_date | Album original release date | TDOR | ORIGYEAR | Set null to completely disable parsing this field |
mbz_id | Album musicbrainz id | "MusicBrainz Album Id" | MUSICBRAINZ_ALBUMID |
Id3v2 key is treated as two different ways depending on its length:
- If you supply a 3 or 4 characters string, it will be treated as a frame id. For example TIT2.
- Otherwise, it will be treated as an user text key in the frame TXXX. For example "MusicBrainz Release Track Id".
In additional to those configurations above, id3v2 also has below configuration.
Subkey | Meaning | Default value | Note |
---|---|---|---|
separator | Separator for multiple values in id3v2.3 | '/' | This is only applicable with id3v2.3, id3v2.4 will always be splited by '\0' (or '\\' if you are using mp3tag) |
You can set track/disc number and total by three ways below:
- Setting fields
track_number
,track_total
, etc normally. Unfortunately this does not work on id3v2. - Using only
track_number
and set it to{track_number}/{track_total}
. This setstrack_number
andtrack_total
to the corresponding numbers after spliting and works with all formats. The same thing holds fordisc_number
. - Using only
track_number
and set it to{character}{track_number}
likeA1
,B10
. This sets thedisc_number
to the order of that character in the alphabet andtrack_number
to the following number.track_total
anddisc_total
will be none. This format is encountered while parsing vinyl records.
Scan process has two main threads, one thread (the walking thread) will be responsible for walking directories in the filesystem and send back the result to the second thread (the parsing thread), who is responsible for parsing each file and updating information in the database. More in scan process.
You should tweak this carefully to find the optimized value for your system. Sometimes, running concurrently is not the fastest option.
Subkey | Meaning | Default value | Note |
---|---|---|---|
parallel | If the walking thread should spawn more threads and run concurrently. | false | |
channel_size | The maximum number of results that can be sent back to the parsing thread. If the results queue is full, the walking threads will be blocked until the queue has an empty slot | 10 | |
pool_size | The maximum number of threads that the parsing thread can spawn to process the result | 10 |
Subkey | Meaning | Default value | Note |
---|---|---|---|
buffer_size | Buffer size to allocate for custom AVIOContext |
32 * 1024 | |
cache_dir | The cache directory to save transcoding results | $TMPDIR/nghe/cache/transcoding |
Set null or a non-absolute path to completely disable parsing caching |
Subkey | Meaning | Default value | Note |
---|---|---|---|
artist_dir | The directory to save artist cover arts | $TMPDIR/nghe/art/artist |
Set null or a non-absolute path to completely disable song cover art extract |
song_dir | The directory to save song cover arts | $TMPDIR/nghe/art/song |
Set null or a non-absolute path to completely disable song cover art extract |
Subkey | Meaning | Default value | Note |
---|---|---|---|
key | Lastfm key to fetch information |
Subkey | Meaning | Default value | Note |
---|---|---|---|
id | Spotify client id to fetch information | ||
secret | Spotify client secret to fetch information |
Credentials, region and endpoint configurations should be set by standard AWS environment variables. Authentication by profile and imds are also supported.
Subkey | Meaning | Default value | Note |
---|---|---|---|
enable | Enable S3 integration | ||
use_path_style_endpoint | Use path style endpoint instead of virtual host | whether $AWS_USE_PATH_STYLE_ENDPOINT is empty or not |
|
presigned_url_duration | Duration (in minutes) of the presigned url for transcoding | 15 | |
stalled_stream_grace_preriod | Grace period for stalled stream protection | 0 | Set 0 to disable stalled stream protection |
connect_timeout | The maximum time (in seconds) it takes to initiate a socket connection. | 5 |
A song is uniquely identified either by:
- Its music folder and relative path (to its music folder).
- Its music folder, hash and size. It means that you can not have duplicated songs in the same music folder. If a duplicated songs is detected, the path will be updated to the latest encountered path. It allows you to move freely your songs in the same music folder, only its path will be updated in the database.
Duplication on two different music folders are is allowed and will be treated as two seperate songs.
The song will be first checked by its last modified time and then process the same as full if its updated at time is sooner or it hasn't been added into the database. Otherwise mark it as scanned and move to the next song. This mode is particularly useful for cloud storage service.
As describe above, if an already identified song is scanned (by checking relative path, hash and size), the scanning process will just mark the file as scanned and skip that file. In the end of the scanning process, all old songs and related informations (artists/albums/genres/etc) inside that music folder that are not scanned will be deleted.
Same as full but will try parsing the file regardless if it is identified or not. This mode is useful if there are new metadata that added into the scanning process.
An artist is uniquely identified either by:
- Their musicbrainz id
- Their name if their musicbrainz id is empty.
If you have duplicated artists shown in your client, you should check for the corresponding musicbrainz id field.
An album is uniquely identified either by:
- Their musicbrainz id
- Their name, date, release date, original release date if their musicbrainz id is empty.
If you have duplicated albums shown in your client, you should check for the corresponding musicbrainz id and date fields.
As describe in scan process, songs are tied to a specific music folder but artists and albums are globally identified. There will be two scenarios.
In this case, user is either allowed or denied depending on their allowed music folders. Song-level resource is the song itself, the lyrics, the cover art.
In this case, user will only see a combination of allowed song-level resources, not the whole thing.
For example, if an artist has 10 songs in folder1 and 20 songs in folder2:
- User with permission on both folders will see that artist has 30 songs.
- User with permission on folder1 will only see first 10 songs.
- User with permission on folder2 will only see last 20 songs.
- User with no permission on both folder will not see the artist in the first place and get a not found error if they are trying to access it by manully specifying the id.
The same thing holds true for albums. For example, if an album has 2 songs in 2 folders with different cover arts:
- User with permission on both folders will see the first cover art sorted by the smallest disc number and track number (track 1 disc 1 of the album).
- User with permission on folder1 will only see the first cover art.
- User with permission on folder2 will only see the second cover art.
Nested music folders are allowed and each folder will be treated as a separate unrelated folder.
Permission configuration can be found in the folders menu of the frontend.
If a song has compilation tag, its album will be added to the list of albums of each artist in its artists tag (not to be confused with album artists). For example, if a song has album named "album", compilation enabled, 2 artists "artist1", "artist2" and 1 album aritst "various artists", all of these 3 artists will have album "album" in their information. However, when accessing by album id, only album artists ("various artists" in this case) will be shown in the aritst fields.
- More compatible with Opensubsonic API.
- Fully-feature frontend.
- SQL playlist builder.
- Possibly integrating local machine learning model (like how Immich is doing with image/videos).