113 lines
3.6 KiB
Python
113 lines
3.6 KiB
Python
import jack # type: ignore
|
|
import time
|
|
import numpy as np # type: ignore
|
|
from pydub import AudioSegment # type: ignore
|
|
import random
|
|
import sys
|
|
import json
|
|
import os
|
|
import paho.mqtt.client as mqtt # type: ignore
|
|
from paho.mqtt.client import Client # type: ignore
|
|
import re
|
|
|
|
# === load and shuffle .m3u playlist ===
|
|
def load_playlist(m3u_path):
|
|
with open(m3u_path, "r") as f:
|
|
lines = [line.strip() for line in f if line.strip() and not line.startswith("#")]
|
|
random.shuffle(lines)
|
|
return lines
|
|
|
|
# === load mp3 song file ===
|
|
def decode_mp3(path):
|
|
song = AudioSegment.from_file(path)
|
|
song = song.set_frame_rate(48000).set_channels(2).set_sample_width(4)
|
|
samples = np.array(song.get_array_of_samples()).astype(np.float32) / (2**31)
|
|
return samples.reshape((-1,2)) #stereo:L R
|
|
|
|
def update_now_playing(playlist_name, song_title):
|
|
path = "/home/media/Music/now_playing.json"
|
|
try:
|
|
with open(path, "r") as f:
|
|
data = json.load(f)
|
|
except FileNotFoundError:
|
|
data = {}
|
|
|
|
data[playlist_name.lower()] = os.path.basename(song_title)
|
|
with open(path, "w") as f:
|
|
json.dump(data, f, indent=2)
|
|
|
|
# MQTT title broadcast
|
|
mqtt_topic = f"music/now_playing/{playlist_name.lower()}"
|
|
mqtt_payload = os.path.basename(song_title)
|
|
mqtt_payload = os.path.splitext(mqtt_payload)[0]
|
|
mqtt_payload = re.sub(r'\s*\[[^\]]+\]$', '', mqtt_payload)
|
|
|
|
try:
|
|
mqtt_client = mqtt.Client(protocol=mqtt.MQTTv311)
|
|
mqtt_client.connect("10.40.0.2", 1883)
|
|
result = mqtt_client.publish(mqtt_topic, mqtt_payload, retain=True)
|
|
result.wait_for_publish()
|
|
if result.rc != mqtt.MQTT_ERR_SUCCESS:
|
|
raise Exception(f"Publish failed with code {result.rc}")
|
|
mqtt_client.disconnect()
|
|
except Exception as e:
|
|
print(f"[MQTT] failed to publish: {e}")
|
|
|
|
# === Get playlist name from CLI ===
|
|
if len(sys.argv) < 2:
|
|
print ("Usage: python3 jack_mp3_player.py Praise|Worship|Instrumental")
|
|
sys.exit(1)
|
|
|
|
playlist_name = sys.argv[1]
|
|
playlist_file = f"/home/media/Music/{playlist_name}.m3u"
|
|
|
|
# === jack setup ===
|
|
client = jack.Client(playlist_name)
|
|
out_l = client.outports.register("left")
|
|
out_r = client.outports.register("right")
|
|
|
|
playlist = load_playlist(playlist_file)
|
|
current_index = 0
|
|
samples = decode_mp3(playlist[current_index])
|
|
buffer_pos = [0]
|
|
|
|
@client.set_process_callback
|
|
def process(frames):
|
|
bufsize = frames
|
|
end = buffer_pos[0] + bufsize
|
|
|
|
#zero fills if out of data
|
|
if buffer_pos[0] >= len(samples):
|
|
out_l.get_buffer()[:] = np.zeros(bufsize, dtype=np.float32)
|
|
out_r.get_buffer()[:] = np.zeros(bufsize, dtype=np.float32)
|
|
return
|
|
|
|
chunk = samples[buffer_pos[0]:end]
|
|
#pad if last chunk
|
|
if len(chunk) < bufsize:
|
|
pad = np.zeros((bufsize - len(chunk), 2), dtype=np.float32)
|
|
chunk = np.vstack((chunk, pad))
|
|
|
|
out_l.get_buffer()[:] = chunk[:, 0].copy()
|
|
out_r.get_buffer()[:] = chunk[:, 1].copy()
|
|
buffer_pos[0] += bufsize
|
|
|
|
# === Activate jack ===
|
|
with client:
|
|
print(f"Playing playlist: {playlist_name}")
|
|
print(f"Playing song: {playlist[current_index]}")
|
|
update_now_playing(playlist_name, playlist[current_index])
|
|
while True:
|
|
if buffer_pos[0] >= len(samples):
|
|
current_index += 1
|
|
if current_index >= len(playlist):
|
|
playlist = load_playlist(playlist_file)
|
|
current_index = 0
|
|
|
|
print(f"Playing: {playlist[current_index]}")
|
|
update_now_playing(playlist_name, playlist[current_index])
|
|
samples = decode_mp3(playlist[current_index])
|
|
buffer_pos[0] = 0
|
|
|
|
time.sleep(0.1)
|