Compare commits
4 Commits
8a1d240341
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ae2556dc1d | |||
| 8ddbee713a | |||
| 492558b0d5 | |||
| 95cae1a1d5 |
@@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) Everyone, except Author
|
||||||
|
|
||||||
|
Everyone is permitted to copy, distribute, modify, merge, sell, publish,
|
||||||
|
sublicense or whatever they want with this software but at their OWN RISK.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The author has absolutely no clue what the code in this project does. It might
|
||||||
|
just work or not, there is no third option.
|
||||||
|
|
||||||
|
|
||||||
|
GOOD LUCK WITH THAT PUBLIC LICENSE TERMS AND CONDITIONS FOR
|
||||||
|
COPYING, DISTRIBUTION, AND MODIFICATION
|
||||||
|
|
||||||
|
0. You just DO WHATEVER YOU WANT TO as long as you NEVER LEAVE A TRACE TO
|
||||||
|
TRACK THE AUTHOR of the original product to blame for or hold responsible.
|
||||||
|
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
Good luck and Godspeed.
|
||||||
@@ -2,23 +2,63 @@
|
|||||||
|
|
||||||
A lightweight, headless Python audio player designed as a fast drop-in replacement for `mpv`.
|
A lightweight, headless Python audio player designed as a fast drop-in replacement for `mpv`.
|
||||||
|
|
||||||
|
This player is specifically built to act as a backend player for **Navidrome** (in Jukebox mode) feeding into **Snapcast**. It avoids the overhead of full `mpv` while maintaining compatibility with its IPC protocol.
|
||||||
|
|
||||||
|
This removes about 5 seconds of audio playback lag and UI seeking lag.
|
||||||
|
|
||||||
## What it does
|
## What it does
|
||||||
|
|
||||||
- Decodes and resamples common audio formats to `48000Hz`, `stereo`, `s16` PCM.
|
- Decodes and resamples common audio formats to `48000Hz`, `stereo`, `s16` PCM.
|
||||||
- Applies on-the-fly EBU R128 volume normalization using FFmpeg's `loudnorm` filter.
|
|
||||||
- Outputs raw PCM audio directly to a FIFO pipe (`/tmp/snapfifo`).
|
- Outputs raw PCM audio directly to a FIFO pipe (`/tmp/snapfifo`).
|
||||||
- Provides a JSON IPC server over a Unix socket for real-time playback control (pause, volume, seek, quit).
|
- Provides a JSON IPC server over a Unix socket for real-time playback control (pause, volume, seek, quit).
|
||||||
|
- Maps 0-100 volume levels to 25-100 and applies log scale (for human hearing).
|
||||||
|
|
||||||
## What it's for
|
## Setup
|
||||||
This player is specifically built to act as a backend player for **Navidrome** (in Jukebox mode) feeding into multi-room audio systems like **Snapcast**. It avoids the overhead of full `mpv` while maintaining compatibility with its IPC protocol.
|
|
||||||
|
|
||||||
## How to use it
|
docker-compose.yml:
|
||||||
|
|
||||||
1. Install the required dependencies (Python 3.11+ recommended):
|
```
|
||||||
```bash
|
services:
|
||||||
pip install av numpy
|
navidrome:
|
||||||
```
|
build: .
|
||||||
|
user: 1000:1000 # should be owner of volumes
|
||||||
|
ports:
|
||||||
|
- "4533:4533"
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
ND_JUKEBOX_ENABLED: true
|
||||||
|
ND_MPVCMDTEMPLATE: '/opt/qotplayer/env/bin/python /opt/qotplayer/main.py %f --input-ipc-server=%s'
|
||||||
|
ND_LOGLEVEL: info
|
||||||
|
volumes:
|
||||||
|
- "./data:/data"
|
||||||
|
- "/mnt/music-combined:/music:ro"
|
||||||
|
- "/tmp/snapfifo:/tmp/snapfifo"
|
||||||
|
```
|
||||||
|
|
||||||
2. Run the player, specifying the IPC socket path and the audio file:
|
Dockerfile:
|
||||||
```bash
|
|
||||||
python main.py --input-ipc-server=/tmp/mpv-socket /path/to/song.flac
|
```
|
||||||
```
|
FROM deluan/navidrome:latest
|
||||||
|
|
||||||
|
RUN apk add --no-cache git curl && \
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
|
||||||
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
|
|
||||||
|
WORKDIR /opt
|
||||||
|
RUN git clone https://git.tanner.vc/tanner/qotplayer.git
|
||||||
|
|
||||||
|
WORKDIR /opt/qotplayer
|
||||||
|
RUN uv venv --python=3.12 env && \
|
||||||
|
. env/bin/activate && \
|
||||||
|
uv pip install -r requirements.txt
|
||||||
|
|
||||||
|
RUN chmod 755 /root
|
||||||
|
```
|
||||||
|
|
||||||
|
Build and run with:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ sudo docker compose build --no-cache
|
||||||
|
$ sudo docker compose up
|
||||||
|
```
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ def playback_thread(file_path, state):
|
|||||||
|
|
||||||
container = av.open(file_path)
|
container = av.open(file_path)
|
||||||
stream = container.streams.audio[0]
|
stream = container.streams.audio[0]
|
||||||
resampler = av.AudioResampler(format='s16', layout='stereo', rate=48000)
|
resampler = av.AudioResampler(format='s16', layout='stereo', rate=44100)
|
||||||
|
|
||||||
with open(fifo_path, 'wb') as fifo:
|
with open(fifo_path, 'wb') as fifo:
|
||||||
iterator = container.decode(stream)
|
iterator = container.decode(stream)
|
||||||
@@ -70,8 +70,11 @@ def playback_thread(file_path, state):
|
|||||||
|
|
||||||
# Apply volume
|
# Apply volume
|
||||||
if vol != 100.0:
|
if vol != 100.0:
|
||||||
|
# Linearly map 0-100 to 25-100 so low volumes aren't too quiet
|
||||||
|
mapped_vol = 25.0 + (vol / 100.0) * 75.0
|
||||||
|
|
||||||
# Use cubic curve for more natural volume adjustment (human hearing is logarithmic)
|
# Use cubic curve for more natural volume adjustment (human hearing is logarithmic)
|
||||||
multiplier = max(0.0, (vol / 100.0) ** 3)
|
multiplier = max(0.0, (mapped_vol / 100.0) ** 3)
|
||||||
arr = arr.astype(np.float32) * multiplier
|
arr = arr.astype(np.float32) * multiplier
|
||||||
arr = np.clip(arr, -32768, 32767).astype(np.int16)
|
arr = np.clip(arr, -32768, 32767).astype(np.int16)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user