390 lines
21 KiB
Markdown
390 lines
21 KiB
Markdown
### ⚠️ THIS PROJECT WAS ENTIRELY VIBECODED AND IS SLOP. DO NOT PROCEED IF YOU DO NOT ALIGN WITH THESE VALUES. DO NOT COMPLAIN ABOUT IT IN PULL RELEASES, ISSUES, ETC. UNLESS THERE ARE SEVERE SECURITY RISKS. THEY WILL BE REMOVED. PROCEED AT YOUR OWN RISK! ⚠️
|
||
|
||
# Stemify
|
||
|
||
Stemify splits every song in your Plex music library into stems (vocals, drums, bass, guitar, piano and other) and gives you players that mix them live. Mute the vocals and sing along, solo the bass to learn a part, or save an instrumental of a song and have it play that way every time.
|
||
|
||
It runs on iPhone (with CarPlay), Android (with Android Auto) and Linux desktop. A GPU on your server does the separating in the background.
|
||
|
||
- [How it works](#how-it-works)
|
||
- [Apps](#apps)
|
||
- [Setup](#setup)
|
||
- [iOS](#ios)
|
||
- [Android](#android)
|
||
- [Desktop](#desktop)
|
||
- [Stem Now](#stem-now)
|
||
- [Reference](#reference)
|
||
- [Building and testing](#building-and-testing)
|
||
- [Troubleshooting](#troubleshooting)
|
||
- [Credits and license](#credits)
|
||
|
||
## How it works
|
||
|
||
The stems folder mirrors the music folder. If a song lives at `Artist/Album/01 Song.flac`, its stems live in `Stems/Artist/Album/01 Song/`.
|
||
|
||
```
|
||
your server
|
||
Music/ ──▶ stemd (GPU) ──▶ Stems/ ──▶ stemserve :8765 ──▶ phone, car, desktop
|
||
▲ │
|
||
└──── Stem Now requests ◀───┘
|
||
Plex Media Server :32400 ───────────────────────────────▶ library, artwork, playlists
|
||
```
|
||
|
||
Three parts, and only stemd is required:
|
||
|
||
- **stemd** is the daemon. It does the actual separating and nothing else.
|
||
- **Stemify web UI** is optional, for desktop users. stemserve serves it at `/`. It plays the stems streamed from the server, and picks up a local stem server on `localhost:8765` by itself.
|
||
- **iOS and Android apps** are optional and built from this repo too.
|
||
|
||
- **stemd** works through the library on the GPU, one song at a time, and rescans every 30 minutes for new music.
|
||
- **stemserve** serves the finished stems over HTTP with range requests, waveforms and optional Opus transcodes.
|
||
- **Plex** handles the library, metadata, artwork and playlists.
|
||
- **The apps** play stems when a song has them and fall back to the original file when it doesn't, so everything plays.
|
||
|
||
## Apps
|
||
|
||
| | iOS | Android | Desktop player | Web |
|
||
|---|---|---|---|---|
|
||
| Per-stem faders, mute, solo, mix presets | yes | yes | yes | yes |
|
||
| Waveform seek bar, live stem meters | yes | yes | | waveform |
|
||
| Shuffle, repeat all/one | yes | yes | | yes |
|
||
| Playback speed (keeps pitch) | yes | | | yes |
|
||
| Saved mix per song | yes | | | |
|
||
| Karaoke mode with synced lyrics | yes | | | |
|
||
| Stem playlists (a mix per song) | yes | | | |
|
||
| Plex playlists | yes | yes | yes | yes |
|
||
| Downloads for offline | yes | yes | | |
|
||
| 10-band and parametric EQ, AutoEQ import | yes | yes | | |
|
||
| Car | CarPlay | Android Auto | | |
|
||
| Voice | Siri | Google Assistant | | |
|
||
| Stem Now | yes | yes | | |
|
||
|
||
Mix presets are Full, Karaoke, A Cappella, No Drums, Rhythm, No Guitar and No Bass.
|
||
|
||
## Setup
|
||
|
||
Set up the server first, then the apps. The examples use `/srv/stemify` for app data, `/srv/music/library` for your music, `/srv/music/stems` for the output and `SERVER_IP` for the machine running the containers. Change them to match your setup.
|
||
|
||
### Server
|
||
|
||
You need Docker, and an NVIDIA GPU for stemd (with the NVIDIA container toolkit installed). A CPU build exists for testing, but it's far too slow for a whole library.
|
||
|
||
Make the folders, owned by whatever user the containers run as (`1000:1000` here):
|
||
|
||
```sh
|
||
mkdir -p /srv/stemify/stemserve/cache \
|
||
/srv/stemify/stemd/models \
|
||
/srv/stemify/stemd/state/queue
|
||
chown -R 1000:1000 /srv/stemify/stemserve /srv/stemify/stemd
|
||
```
|
||
|
||
**stemd** builds from the repo root because it uses the `stemify` package. Copy the repo (at least `stemify/`, `server/` and `.dockerignore`) to `/srv/stemify/stemd/src`, then:
|
||
|
||
```sh
|
||
docker build -f /srv/stemify/stemd/src/server/stemd.Dockerfile -t stemd /srv/stemify/stemd/src
|
||
docker run -d --name stemd --restart unless-stopped --stop-timeout 60 \
|
||
--runtime=nvidia -e NVIDIA_VISIBLE_DEVICES=all -e NVIDIA_DRIVER_CAPABILITIES=compute,utility \
|
||
-v /srv/music/library:/music:ro \
|
||
-v /srv/music/stems:/stems \
|
||
-v /srv/stemify/stemd/models:/models \
|
||
-v /srv/stemify/stemd/state:/state \
|
||
stemd
|
||
```
|
||
|
||
The first song downloads the model (about 700 MB). After that the log shows what's left, what it's on, and how fast it's going.
|
||
|
||
**stemserve** is one Python file using only the standard library. It also serves the web player from `web/`. Copy the repo's `server/` and `web/` folders to `/srv/stemify/stemserve` and run `docker compose up -d --build` in `server/`, or by hand:
|
||
|
||
```sh
|
||
docker build -f /srv/stemify/stemserve/server/Dockerfile -t stemserve /srv/stemify/stemserve
|
||
docker run -d --name stemserve --restart unless-stopped -p 8765:8765 \
|
||
-v /srv/music/stems:/stems:ro \
|
||
-v /srv/stemify/stemserve/cache:/cache \
|
||
-v /srv/stemify/stemd/state/queue:/queue \
|
||
stemserve
|
||
```
|
||
|
||
`curl http://SERVER_IP:8765/api/info` should return JSON with a track count.
|
||
|
||
To update, copy the new files, rebuild, `docker rm -f` the container and run it again. Everything that matters lives in the mounted folders. Update stemd before stemserve.
|
||
|
||
<details>
|
||
<summary>Server environment variables</summary>
|
||
|
||
**stemd**
|
||
|
||
| Variable | Default | What it does |
|
||
|---|---|---|
|
||
| `MUSIC_DIR` | `/music` | Music library (read-only is fine) |
|
||
| `STEMS_DIR` | `/stems` | Where stems go |
|
||
| `STATE_DIR` | `/state` | Holds the failed-track list |
|
||
| `QUEUE_DIR` | `STATE_DIR/queue` | Where Stem Now requests arrive |
|
||
| `MODELS_DIR` | `/models` | Model weights, downloaded on first use |
|
||
| `MODEL` | `roformer_bs_sw` | Any id from [the model table](#separation-models) |
|
||
| `DEVICE` | `cuda` | `cuda`, `cuda:N` or `cpu` |
|
||
| `RESCAN_MIN` | `30` | Minutes between library scans once caught up |
|
||
| `AUTO_SCAN` | `0` | `1` to scan the library on a timer. Off by default: only Stem Now requests are separated |
|
||
| `FP16` | `1` | Half precision on the GPU (`0` for fp32) |
|
||
| `OVERLAP`, `SEGMENT`, `SHIFTS` | `0.25`, `0`, `0` | Separation quality, same as the desktop app |
|
||
|
||
`docker build --build-arg TORCH_INDEX=cpu ...` makes a small CPU-only image.
|
||
|
||
**stemserve**
|
||
|
||
| Variable | Default | What it does |
|
||
|---|---|---|
|
||
| `STEMS_DIR` | `/stems` | The stems folder |
|
||
| `CACHE_DIR` | `/cache` | Opus transcodes and waveforms (safe to delete) |
|
||
| `QUEUE_DIR` | `/queue` | stemd's Stem Now folder, shared with it |
|
||
| `PORT` | `8765` | Listen port |
|
||
| `TOKEN` | empty | If set, requests need `X-Stem-Token` or `?token=` |
|
||
| `INDEX_TTL` | `30` | Seconds between rescans of the stems folder |
|
||
| `PLEX_URL` | empty | Plex address. With `PLEX_TOKEN`, the web UI connects to Plex without signing in |
|
||
| `PLEX_TOKEN` | empty | Plex token. Only handed out to clients that pass `TOKEN`, so set `TOKEN` too |
|
||
| `WEB` | `1` | Set to `0` to turn off the web UI and serve the API only |
|
||
|
||
</details>
|
||
|
||
### Connecting an app
|
||
|
||
Every app asks for the same two things:
|
||
|
||
1. **Plex:** sign in with the 4-character code at [plex.tv/link](https://plex.tv/link) and pick your music library.
|
||
2. **Stem server:** in Settings, enter your stemserve address. It should say Online with a song count.
|
||
|
||
The apps check stemserve for newly separated songs every minute, so the library fills in as stemd works.
|
||
|
||
## iOS
|
||
|
||
A SwiftUI app for iOS 17 or newer, with CarPlay and Siri ("play <song> in Stemify").
|
||
|
||
### Install
|
||
|
||
Grab `stemify-<version>.ipa` from the repo's Releases page and sideload it with AltStore, SideStore, Sideloadly or Feather. `ios/source.json` is an AltStore-style source. Add its raw URL to AltStore, SideStore or Feather for updates:
|
||
|
||
```
|
||
https://<your-git-host>/<owner>/stemify/raw/branch/main/ios/source.json
|
||
```
|
||
|
||
CarPlay and Siri need the `carplay-audio` and `siri` entitlements, which a free Apple ID can't sign. On a free sideload the app works on the phone but not in CarPlay or Siri. A paid developer account covers Siri; CarPlay audio also needs Apple to approve the entitlement for your team.
|
||
|
||
### Using it
|
||
|
||
- **Now Playing:** shuffle and repeat sit either side of the play controls; tap repeat again for repeat one. The speed button (next to the mixer) goes from 0.5× to 2× without changing pitch.
|
||
- **Mixer:** on Now Playing, under the waveform. Tap **Save** to keep a mix for that song; it comes back every time the song plays.
|
||
- **AirPlay:** the AirPlay button on Now Playing picks the speaker or TV. Stems are mixed on the phone, so mixer changes apply live while it plays over AirPlay.
|
||
- **Tab bar:** Settings → Tab Bar. Pick and reorder up to five of Library, Songs, Albums, Artists, Genres, Playlists, Search, Downloads and Settings. Settings always stays.
|
||
- **Debug logs:** Settings → Debug. Turn on Verbose Logging, then View, Export or Clear the log. Export opens the share sheet with the app version, device, iOS version and audio output at the top. Tokens are stripped from the log.
|
||
- **Karaoke:** the mic button mutes vocals on every song and shows synced lyrics from [LRCLIB](https://lrclib.net). Tap a line to jump to it. Vocals can be On, Soft or Off, and Soft has a slider for how quiet.
|
||
- **Playlists:** the Playlists tab switches between Plex and Stem.
|
||
- Plex playlists sync with Plex and Plexamp.
|
||
- Stem playlists stay on the phone, and each song in them has its own mix, so one playlist can hold an instrumental of one song and an a cappella of the next.
|
||
- Copy any Plex playlist to a stem playlist from its menu.
|
||
- **Add to Playlist:** hold any song, album, artist or playlist. One sheet covers both kinds: pick Plex or Stem, pick the mix for stem playlists, then tap a playlist or make a new one right there.
|
||
- **Sorting:** artists, albums, songs and playlists sort like Plexamp. Songs has a shuffle-everything button.
|
||
- **Hold anything** for Add to Front or End of Queue, Download, Add to Playlist and Stem Now.
|
||
- **Equalizer:** same graphic EQ, parametric filters and APO/AutoEQ import as [Android](#equalizer).
|
||
|
||
## Android
|
||
|
||
Kotlin and Jetpack Compose, [build it](#building-and-testing) or grab `Stemify.apk` from Releases if one is attached, and `adb install -r Stemify.apk`.
|
||
|
||
### Using it
|
||
|
||
- **Tabs:** Library, Playlists, Search, Downloads and Settings.
|
||
- **Badges:** songs without stems yet say NO STEMS, and albums and artists show NO STEMS or a count like 7/10 STEMS. They still play from the original file.
|
||
- **Now Playing:** waveform seek bar (drag sideways to seek, up and down for volume), mixer, meters and queue. Your mix can carry over between songs or reset.
|
||
- **Stem Quality:** lossless FLAC, or Opus at a bitrate you choose (about an eighth of the size, transcoded and cached on the server).
|
||
- **Streaming** works anywhere you can reach Plex and stemserve. Both are plain HTTP by default, so use HTTPS or a VPN away from home. Downloads and already-streamed songs play anywhere.
|
||
|
||
### Equalizer
|
||
|
||
Settings → Equalizer has two parts.
|
||
|
||
- **Graphic EQ:** ten bands from 31 Hz to 16 kHz, with presets like Bass Booster, Late Night and Vocal Booster.
|
||
- **Parametric filters**, modeled on [Equalizer APO](https://sourceforge.net/projects/equalizerapo/): Peak, Low/High Shelf, Low/High Pass (6 to 48 dB/oct), Band Pass, Notch and All Pass. Each has frequency, gain and Q, works on both channels or one, and can be toggled on its own. Drag the handle on the curve or tap a value to type it.
|
||
|
||
To correct headphones, copy a `ParametricEQ.txt` from [AutoEQ](https://github.com/jaakkopasanen/AutoEq) and choose **••• → Paste APO Config** on the Filters page. **Copy as APO Config** goes the other way.
|
||
|
||
Profiles are tied to outputs (speaker, wired, each Bluetooth device, USB DACs) and switch automatically. Each has its own preamp, and anti-clipping adds just enough cut to keep the loudest point of the curve at 0 dBFS. A limiter on the master bus catches the rest.
|
||
|
||
### Android Auto
|
||
|
||
Designed for small non-touch head units (800×480, knob and wheel buttons only), and it works the same on larger screens.
|
||
|
||
- Tabs for Playlists, Albums, Artists and Downloads (Downloads first when you're away from home).
|
||
- Every song list starts with Shuffle all, and lists over 40 items split into A to Z folders.
|
||
- Now Playing has a Vocals on/off button and a Mix button that cycles presets. The mix shows on the artist line, like "Artist · Karaoke".
|
||
- Voice: "play <song> on Stemify" works for playlists, artists, albums and songs.
|
||
- Away from home it notices the server is gone and only lists songs that will play.
|
||
|
||
Android Auto hides sideloaded apps until you allow them: in the phone's Android Auto settings, tap Version ten times, then ⋮ → Developer settings → Unknown sources.
|
||
|
||
To test without a car, start the head unit server from Auto's developer settings, then:
|
||
|
||
```sh
|
||
adb forward tcp:5277 tcp:5277
|
||
~/Android/Sdk/extras/google/auto/desktop-head-unit -c ~/Android/Sdk/extras/google/auto/config/rotary.ini
|
||
```
|
||
|
||
## Web
|
||
|
||
stemserve also serves a web player at its root, so `http://<server>:8765/` opens it. Sign in with Plex in Settings (or paste a server URL and token), then browse albums, artists, playlists and search like the apps. **Stems only** lists everything in the stems folder and works without Plex.
|
||
|
||
- Mixer, presets, shuffle, repeat, speed, queue and the waveform seek bar work like the phone apps. The mix carries over to the next song.
|
||
- Songs without stems play straight from Plex.
|
||
- Keys: Space play/pause, arrows seek, S shuffle, R repeat, `[` `]` speed, M mixer, Q queue.
|
||
- Settings, queue and mix are kept in the browser. Nothing syncs with the apps.
|
||
- If stemserve has `TOKEN` set, enter it in Settings.
|
||
|
||
It's plain HTML and JavaScript in `web/` with no build step. To run it somewhere else, serve `web/` and proxy `/api/` and `/file/` to stemserve on the same origin; with a different origin the stem faders fall back to element volume, which iOS Safari ignores.
|
||
|
||
## Desktop
|
||
|
||
Linux, Python 3.12 and [uv](https://docs.astral.sh/uv/). PyTorch defaults to ROCm (AMD); point the torch index in `pyproject.toml` elsewhere for NVIDIA or CPU.
|
||
|
||
```sh
|
||
uv sync
|
||
uv run stemify # the drag-and-drop separator
|
||
uv run stemify-player # the player
|
||
./scripts/install-desktop.sh # add both to your app launcher (--uninstall removes them)
|
||
```
|
||
|
||
**Stemify** is the separator. Drop files on the window and each one is separated with your last-used model and device. You get a lane per stem with a waveform, solo, mute, gain and export (everything, one stem, or the current mix, as WAV, FLAC or MP3). Separation runs in its own process, results are cached, and it works on AMD, NVIDIA and CPU.
|
||
|
||
**Stemify Player** is your Plex library and its stems, dressed up as Microsoft Excel 2016. Sheets are your library, the zoom slider is master volume, Cell Styles are mix presets, and Undo/Redo skip songs. It reports plays to Plex, supports media keys and MPRIS, and remembers your queue and mix. More in [`stemify/player/README.md`](stemify/player/README.md).
|
||
|
||
## Stem Now
|
||
|
||
A big library takes stemd days. Hold a song, album, artist or playlist and choose **Stem Now** to move its unseparated songs to the front.
|
||
|
||
1. The app sends the songs to stemserve, which drops a request file in stemd's queue folder.
|
||
2. stemd finishes the song it's on and does yours next. If it was idle, it starts within about 5 seconds.
|
||
3. About a minute after each song finishes, the app picks up its stems.
|
||
|
||
Requests run oldest first and survive restarts. stemd matches Plex's paths to its own files by the artist/album/file tail, so different mount points don't matter. Songs that failed before are skipped; delete `state/failed.json` to retry them.
|
||
|
||
## Reference
|
||
|
||
### Separation models
|
||
|
||
| Model id | Stems | Notes |
|
||
|---|---|---|
|
||
| `htdemucs` | 4 | HT Demucs, fast |
|
||
| `htdemucs_ft` | 4 | Fine-tuned, about 4x slower |
|
||
| `htdemucs_6s` | 6 | Adds guitar and piano |
|
||
| `roformer_bs_vocals` | 2 | BS-RoFormer vocals/instrumental (Viperx 1297) |
|
||
| `roformer_mel_kim_ft` | 2 | Mel-RoFormer, cleanest vocals (Kim FT by unwa) |
|
||
| `roformer_bs_sw` | 6 | BS-RoFormer SW by jarredou, stemd's default |
|
||
|
||
### Folder layout
|
||
|
||
```
|
||
Music/Artist/Album/01 Song.flac
|
||
Stems/Artist/Album/01 Song/
|
||
.stemify.json done marker
|
||
01 Song - vocals.flac
|
||
01 Song - drums.flac
|
||
...
|
||
```
|
||
|
||
- Stems are written as 24-bit FLAC to a hidden `.partial` folder and the marker goes in last, so an interrupted separation is just redone.
|
||
- If a folder has the same song in several formats, the best one wins (FLAC/WAV, then M4A, Opus/Ogg, MP3, WMA).
|
||
- Plex reports paths as its container sees them, so the apps match songs to stems by path suffix, check durations, and learn the prefix from the first confident match.
|
||
|
||
### stemserve API
|
||
|
||
| Request | Returns |
|
||
|---|---|
|
||
| `GET /api/info` | Track count, ffmpeg availability, whether Stem Now is set up |
|
||
| `GET /api/index` | Every finished song (with ETag) |
|
||
| `GET /api/peaks?key=K&n=256` | Per-stem loudness envelopes for waveforms, 0 to 1000 |
|
||
| `POST /api/prepare?key=K&fmt=opus&br=256` | Starts Opus transcodes in the background |
|
||
| `POST /api/stem` with `{"paths": [...]}` | Stem Now for these Plex files |
|
||
| `GET /file/K/<file>.flac[?fmt=opus&br=256]` | The stem, with range support |
|
||
| `GET /` | The web player from `web/` (or `WEB_DIR`), no token needed |
|
||
|
||
`K` is the song's folder under the stems root, like `Artist/Album/01 Song`. Outside Docker:
|
||
|
||
```sh
|
||
STEMS_DIR=/path/to/Stems CACHE_DIR=~/.cache/stemserve python3 server/stemserve.py
|
||
```
|
||
|
||
### Repo layout
|
||
|
||
```
|
||
ios/ iOS and CarPlay app (SwiftUI, xcodegen project)
|
||
Stemify/ App, Plex, Playback, Stems, Engine, EQ, CarPlay, UI
|
||
scripts/ build-ipa.sh, make-source.py
|
||
android/ Android and Android Auto app (Kotlin, Compose, Media3)
|
||
server/ stemd.py, stemserve.py, Dockerfiles, compose file
|
||
web/ web player (served by stemserve)
|
||
stemify/ desktop separator (PySide6 + QML) and shared separation code
|
||
worker/ Demucs and RoFormer backends, run in a separate process
|
||
player/ Stemify Player
|
||
library_core.py folder layout rules shared with stemd
|
||
scripts/ install-desktop.sh
|
||
specs.txt original design doc for the desktop separator
|
||
```
|
||
|
||
## Building and testing
|
||
|
||
**iOS** needs a Mac with Xcode and xcodegen (`brew install xcodegen`):
|
||
|
||
```sh
|
||
cd ios
|
||
scripts/build-ipa.sh # unsigned build/stemify-<version>.ipa
|
||
xcodegen generate # or open Stemify.xcodeproj in Xcode
|
||
```
|
||
|
||
The version lives in `ios/project.yml` (`MARKETING_VERSION` and `CURRENT_PROJECT_VERSION`). To release, tag `ios-v<version>`, attach `stemify-<version>.ipa`, then run `GITEA_TOKEN=... ios/scripts/make-source.py` and commit the new `ios/source.json`.
|
||
|
||
**Android** needs JDK 21 and the Android SDK (`android/local.properties` points at it):
|
||
|
||
```sh
|
||
cd android
|
||
JAVA_HOME=/path/to/jdk-21 ./gradlew assembleRelease # app/build/outputs/apk/release/
|
||
JAVA_HOME=/path/to/jdk-21 ./gradlew testDebugUnitTest # matching, EQ maths, limiter, cache, car tree
|
||
```
|
||
|
||
The cache and index tests use a stemserve on `localhost:8765` and skip themselves without one.
|
||
|
||
**Desktop** can be checked without a window:
|
||
|
||
```sh
|
||
QT_QPA_PLATFORM=offscreen uv run stemify-player --no-mpris --screenshot /tmp/p.png@5 --quit-after 6
|
||
```
|
||
|
||
## Troubleshooting
|
||
|
||
**A song says NO STEMS.** Hold it and choose Stem Now.
|
||
|
||
**Stem Now says "stemserve needs updating" or "queue folder isn't mounted".** stemserve is older than the app or is missing the `/queue` mount. The queue folder has to be writable by the container user before the container first starts, or Docker creates it as root.
|
||
|
||
**stemd is separating on the CPU.** The container can't see the GPU. Run it with `--runtime=nvidia` and the NVIDIA variables above.
|
||
|
||
**stemd says "out of GPU memory".** Something else is using VRAM. stemd retries that song next pass without marking it failed.
|
||
|
||
**The stem server shows Offline.** Check the address in Settings, try `curl http://<server>:8765/api/info` from another machine, and if stemserve has a `TOKEN`, set the same one in the app.
|
||
|
||
**Stemify isn't in CarPlay or Siri.** The IPA was signed without those entitlements. See [iOS install](#install).
|
||
|
||
**Stemify isn't in Android Auto.** Turn on Unknown sources in Auto's developer settings. See [Android Auto](#android-auto).
|
||
|
||
**Songs listed in `state/failed.json`.** Usually corrupt files or "songs" over 40 minutes (DJ mixes, radio rips). They retry when the file changes; delete the file to retry everything.
|
||
|
||
## Credits
|
||
|
||
- [Demucs](https://github.com/adefossez/demucs) by Alexandre Défossez
|
||
- BS-RoFormer and Mel-RoFormer models (SW 6-stem by jarredou, Viperx 1297, Kim FT by unwa) via [python-audio-separator](https://github.com/nomadkaraoke/python-audio-separator)
|
||
- [PyTorch](https://pytorch.org/), [PySide6](https://www.qt.io/qt-for-python), [Jetpack Compose](https://developer.android.com/compose), [Media3](https://developer.android.com/media/media3)
|
||
- [Equalizer APO](https://sourceforge.net/projects/equalizerapo/) and [AutoEQ](https://github.com/jaakkopasanen/AutoEq), whose formats the EQ reads and writes
|
||
- [LRCLIB](https://lrclib.net) for synced lyrics
|
||
- Inter, Selawik and Carlito fonts (licenses included alongside them)
|
||
|
||
Stemify isn't affiliated with Plex, Apple, Google or Microsoft.
|
||
|
||
## License
|
||
|
||
[GPL-3.0](LICENSE)
|