MPD - Announce current song
Posted by Eric Scheibler at December 29, 2018
My server runs an MPD instance for music playback. The following script for Mac announces the currently playing song after every song change. It uses Mac OS build in TTS and tries to guess the song title language.
Download: mpd_speak_current_song.py
#!/Users/eric/.virtualenvs/speak_current_song/bin/python
# -*- coding: utf-8 -*-
# mpd: speak currently playing song after it has changed
#
# This script uses the mpd client mpc to watch for song changes on the running mpd server and speaks them aloud
# Song title language detection with https://github.com/Mimino666/langdetect
# It's written for Mac OS but could be easily adapted to Linux as well (change tts_command variable)
#
# author: Eric Scheibler <email@eric-scheibler.de>
# Date: 2019-03-29
# Version: 0.2
#
# Installation requirements:
# brew install mpc
# pip install langdetect
#
# Usage: Must run in background for example within a screen session
# ./mpd_speak_current_song
# configuration
#
# adapt the following params to your needs
# leave them blank if not required
#
# mpd host and port
mpd_host = "192.168.2.11"
mpd_port = "6600"
# audio output device
# Mac: get all devices with: say -a '?'
audio_output_device = ""
# voice table
# Mac: get all installed voices with: say -v '?'
voices = {
"de" : {"name":"Yannick", "rate":280},
"en" : {"name":"Samantha", "rate":200},
"fr" : {"name":"Thomas", "rate":200},
# default: different German voice
"default" : {"name":"Anna", "rate":240}
}
import subprocess, sys, threading
from langdetect import detect, DetectorFactory
from langdetect.lang_detect_exception import LangDetectException
# construct commands
#
# control mpd with: mpc ...
mpc_command = ["mpc", "--quiet"]
if mpd_host:
mpc_command += ["--host", mpd_host]
if mpd_port:
mpc_command += ["--port", mpd_port]
# speak with: say ...
tts_command = ["say"]
if audio_output_device:
tts_command += ["-a", audio_output_device]
class SpeakCurrentSong(threading.Thread):
def __init__(self, song, voice):
threading.Thread.__init__(self)
self.song = song
self.voice = voice
# class variables
self.process = None
self.finished = False
self.killed = False
def run(self):
# pause playback
if not self.killed:
self.execute(mpc_command + ["pause"])
# announce new song
if not self.killed:
self.execute(tts_command + ["-v", self.voice.get("name"), "-r", str(self.voice.get("rate")), self.song])
# resume playback
if not self.killed:
self.execute(mpc_command + ["play"])
self.finished = True
def execute(self, command):
self.process = subprocess.Popen(command)
self.process.wait()
def kill(self):
self.process.kill()
self.killed = True
def main():
speakCurrentSongThread = None
# force deterministic language detection results
DetectorFactory.seed = 0
while True:
try:
# wait for change of current song
current_song = subprocess.check_output(mpc_command + ["current", "--wait"], encoding='UTF-8').strip()
except subprocess.CalledProcessError as e:
# could not get current song
error_message = "Error: Could not get the current song"
subprocess.Popen(tts_command + ["-v", "Alex", error_message])
print('{}: {}'.format(error_message, e))
sys.exit(1)
else:
# detect language and get tts voice
voice = voices.get("default")
try:
language = detect(current_song)
except LangDetectException as e:
pass
else:
if language in voices.keys():
voice = voices.get(language)
# start SpeakCurrentSong thread
if speakCurrentSongThread:
# kill old thread if necessary
if not speakCurrentSongThread.finished:
speakCurrentSongThread.kill()
speakCurrentSongThread.join()
speakCurrentSongThread = SpeakCurrentSong(current_song, voice)
speakCurrentSongThread.start()
# log to stdout
print('{}.{}: {}'.format(language, voice.get("name"), current_song))
if __name__ == "__main__":
main()