I will, but I need to add volumio push state to it, so it executes on track changes and play/stop. Below the complete script.
- Monitors Volumioās push state
- Some logic to avoid the script from stopping when āpacket queue is emptyā
- Put filters in a group so it is easier to add genres instead of copying the same every time.
Tested on:
- Buster V3.843
- Bookworm V4.061
volumio@rivo:~/scripts$ ./switch_genre.py
Genre changed: pop ā Preset: S2 Pop EQ Preset
Updated config.json ā PEQpreset = S2 Pop EQ Preset.json
Applied preset: S2 Pop EQ Preset.json
Genre changed: unknown ā Preset: S2 Neutral EQ Preset
Updated config.json ā PEQpreset = S2 Neutral EQ Preset.json
Applied preset: S2 Neutral EQ Preset.json
Genre changed: pop ā Preset: S2 Pop EQ Preset
Updated config.json ā PEQpreset = S2 Pop EQ Preset.json
Applied preset: S2 Pop EQ Preset.json
Genre changed: unknown ā Preset: S2 Neutral EQ Preset
Updated config.json ā PEQpreset = S2 Neutral EQ Preset.json
Applied preset: S2 Neutral EQ Preset.json
Genre changed: rock ā Preset: S2 Rock EQ Preset
Updated config.json ā PEQpreset = S2 Rock EQ Preset.json
Applied preset: S2 Rock EQ Preset.json
Run:
sudo apt update && sudo apt install python3-socketio python3-websocket
#!/usr/bin/env python3
import subprocess
import socketio
import os
import json
import time
import threading
# Volumio WebSocket endpoint
VOLUMIO_HOST = 'http://127.0.0.1:3000'
# Grouped genre aliases
GENRE_GROUPS = {
"S2 Country EQ Preset": ["country"],
"S2 Rock EQ Preset": ["rock", "rockhard"],
"S2 Pop EQ Preset": ["pop"],
"S2 Reggae EQ Preset": ["reggae", "dub", "dancehall"],
"S2 Neutral EQ Preset": ["classical", "neutral", ""]
}
# Flattened genre-to-preset mapping
GENRE_PRESETS = {
genre: preset
for preset, genres in GENRE_GROUPS.items()
for genre in genres
}
CONFIG_PATH = "/data/configuration/audio_interface/fusiondsp/config.json"
PRESET_DIR = "/data/INTERNAL/FusionDsp/presets/PEQ"
sio = socketio.Client()
last_genre = None
def sanitize_genre(raw):
return ''.join(c for c in raw.lower() if c.isalnum())
def get_current_genre():
try:
raw = subprocess.check_output(
['mpc', '--format', '%genre%', 'current'],
stderr=subprocess.DEVNULL
).decode().strip()
return sanitize_genre(raw)
except Exception:
return ""
def apply_preset(preset):
preset_file = f"{preset}.json"
preset_path = os.path.join(PRESET_DIR, preset_file)
if not os.path.isfile(preset_path):
print(f"Preset file not found: {preset_path}")
return
try:
with open(CONFIG_PATH, "r") as f:
config = json.load(f)
config["PEQpreset"] = {
"type": "string",
"value": preset_file
}
with open(CONFIG_PATH, "w") as f:
json.dump(config, f, indent=2)
f.flush()
os.fsync(f.fileno())
print(f"Updated config.json ā PEQpreset = {preset_file}")
except Exception as e:
print(f"Failed to update config.json: {e}")
return
try:
sio.emit('callMethod', {
"endpoint": "audio_interface/fusiondsp",
"method": "usethispreset",
"data": {
"usethispreset": { "value": preset_file }
}
})
print(f"Applied preset: {preset_file}")
except Exception as e:
print(f"Failed to emit WebSocket call: {e}")
@sio.event
def connect():
print("Connected to Volumio ā listening for playback changes...")
@sio.on('pushState')
def on_push_state(data):
try:
global last_genre
genre = get_current_genre()
if genre == last_genre:
return
last_genre = genre
preset = GENRE_PRESETS.get(genre, "S2 Neutral EQ Preset")
print(f"Genre changed: {genre or 'unknown'} ā Preset: {preset}")
apply_preset(preset)
except Exception as e:
print(f"Error in pushState handler: {e}")
@sio.event
def disconnect():
print("Disconnected from Volumio")
@sio.event
def connect_error(data):
print("WebSocket connection failed:", data)
def keep_alive():
while True:
try:
if not sio.connected:
print("Reconnecting to Volumio...")
sio.connect(VOLUMIO_HOST, transports=['websocket'])
sio.wait()
except Exception as e:
print(f"Listener error: {e}")
time.sleep(2)
# Start listener thread
threading.Thread(target=keep_alive, daemon=True).start()
# Keep main thread alive
try:
while True:
time.sleep(60)
except KeyboardInterrupt:
print("Script interrupted by user")
sio.disconnect()
Only thing I miss or donāt understand. If I go to the plugin teh correct filter is loaded and applied. If I select the speaker icon, bottom right, it shows incorrect filter setting.
