A self-hosted web application for downloading video game soundtracks and remixes from multiple sources. Built with Flask and a plugin-based provider architecture β adding a new source is as simple as dropping a single Python file.
Supported providers (out of the box):
| Provider | Source | What you get |
|---|---|---|
| KHInsider | downloads.khinsider.com | Video game soundtracks (MP3 / FLAC) |
| OCRemix | ocremix.org | Fan-made video game music remixes |
- π Search albums / tracks by name
- β¬οΈ Live download progress streamed to the browser (Server-Sent Events)
- π Plugin architecture β add new sources without touching core code
- π In-app log viewer
- π³ Docker-ready with health check and multi-platform images (amd64 + arm64)
- π₯οΈ Proxmox LXC install script (bare Python, no Docker needed)
# Clone the repository
git clone https://github.com/Psych0meter/music_downloader_webapp.git
cd music_downloader_webapp
# Start the app (downloads saved to ./downloads by default)
docker compose up -dThen open http://localhost:5000 in your browser.
Tip β change the download path: Edit the
volumessection indocker-compose.ymlto point to your music library:volumes: - /your/music/library:/downloads
Images are automatically built and published to the GitHub Container Registry on every tagged release, for both linux/amd64 and linux/arm64.
docker run -d \
--name music-downloader \
-p 5000:5000 \
-v /your/music/library:/downloads \
--restart unless-stopped \
ghcr.io/psych0meter/music_downloader_webapp:latestgit clone https://github.com/Psych0meter/music_downloader_webapp.git
cd music_downloader_webapp
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
DOWNLOAD_DIR=/your/music python3 app.pyOpen http://localhost:5000.
Run the following on your Proxmox host shell to create a Debian 13 LXC container and install the app automatically:
bash -c "$(curl -fsSL https://raw.githubusercontent.com/Psych0meter/music_downloader_webapp/main/ct/music-downloader.sh)"The interactive wizard will guide you through container settings (or use the defaults below).
| Setting | Default |
|---|---|
| OS | Debian 13 |
| CPU | 1 core |
| RAM | 512 MB |
| Disk | 4 GB |
| Port | 5000 |
| Downloads | /opt/music-downloader/downloads |
To deploy a specific branch (e.g. for testing):
export BRANCH=debug
bash -c "$(curl -fsSL https://raw.githubusercontent.com/Psych0meter/music_downloader_webapp/main/ct/music-downloader.sh)"See PROXMOX.md for full documentation including bind mounts and update instructions.
All configuration is done through environment variables (or a .env file in the project root):
| Variable | Default | Description |
|---|---|---|
DOWNLOAD_DIR |
/downloads |
Where downloaded files are saved |
PORT |
5000 |
Port the Flask server listens on |
FLASK_DEBUG |
0 |
Set to 1 to enable debug mode (development only) |
Copy .env.example to .env and adjust values as needed β app.py loads it automatically on startup.
- Create
providers/my_source.py - Subclass
BaseProviderand implementsearch()and/ordownload() - Restart the app β it will be auto-discovered
# providers/my_source.py
import os
import requests
from typing import Any, Generator
from providers.base import BaseProvider
class MySourceProvider(BaseProvider):
id = "mysource"
name = "My Source"
description = "Downloads from My Source"
def search(self, query: str) -> list[dict[str, Any]]:
# Return a list of dicts with at minimum "name" and "url"
res = requests.get("https://mysource.example/search", params={"q": query})
return [{"name": r["title"], "url": r["href"]} for r in res.json()]
def download(self, payload: dict[str, Any]) -> Generator[dict[str, Any], None, None]:
url = payload["url"]
dest = os.path.join(os.environ.get("DOWNLOAD_DIR", "/downloads"), "track.mp3")
yield {"line": f"Downloading {url}", "progress": 10}
with requests.get(url, stream=True) as r:
r.raise_for_status()
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
yield {"line": "Done!", "progress": 100}Providers that work by ID/range (like OCRemix) can skip search() entirely β the base class default returns [] and signals supports_search: false to the frontend, which then renders a custom form instead.
See providers/base.py for the full interface documentation.
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Web UI |
GET |
/logs |
Log viewer UI |
GET |
/api/health |
Liveness probe β returns {"status":"ok","providers":[...]} |
GET |
/api/info/<provider_id> |
Provider metadata |
POST |
/api/search/<provider_id> |
Search β body: {"query": "..."} |
POST |
/api/download/<provider_id> |
Download β body: provider-specific payload, response: SSE stream |
Each event is a JSON object:
{ "line": "Downloading track 3/12...", "progress": 25 }A final event with "progress": 100 signals completion.
music_downloader_webapp/
βββ app.py # Flask application & provider loader
βββ requirements.txt # Python dependencies
βββ Dockerfile # Container image
βββ docker-compose.yml # Compose stack
βββ .env.example # Environment variable template
β
βββ .github/
β βββ workflows/
β βββ docker-build.yml # CI: builds & pushes multi-platform image to GHCR
β
βββ providers/
β βββ base.py # BaseProvider abstract class
β βββ khinsider.py # KHInsider provider (curl_cffi β Cloudflare bypass)
β βββ ocremix.py # OCRemix provider (ID/range-based)
β
βββ templates/
β βββ index.html # Main UI
β βββ logs.html # Log viewer
β
βββ downloads/ # Default download target (gitignored)
β
βββ ct/
β βββ music-downloader.sh # Proxmox host script (creates LXC)
βββ install/
βββ music-downloader-install.sh # In-container install script
The GitHub Actions workflow (.github/workflows/docker-build.yml) automatically builds and pushes a multi-platform image to GHCR whenever a tag is pushed:
git tag v1.0.0
git push origin v1.0.0This produces:
ghcr.io/psych0meter/music_downloader_webapp:v1.0.0ghcr.io/psych0meter/music_downloader_webapp:latest
Both linux/amd64 and linux/arm64 platforms are built in a single manifest.
You can also trigger a build manually from the Actions tab in GitHub (workflow_dispatch), with an optional branch input.
docker build -t music-downloader .
docker run -d -p 5000:5000 -v /your/music:/downloads music-downloaderdocker compose pull
docker compose up -dRe-run the install script and select Update, or SSH into the container:
cd /opt/music-downloader && git pull
systemctl restart music-downloaderPull requests are welcome! To add a provider:
- Fork the repo
- Create
providers/your_source.pyfollowing the pattern above - Test it locally
- Open a PR
MIT β see LICENSE.