Advertisement
Guest User

Untitled

a guest
Jan 22nd, 2018
549
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.10 KB | None | 0 0
  1. # -*- coding: utf-8-*-
  2. import re
  3. import logging
  4. import difflib
  5. import mpd
  6. from client.mic import Mic
  7.  
  8. # Standard module stuff
  9. WORDS = ["MUSIC", "SPOTIFY"]
  10.  
  11.  
  12. def handle(text, mic, profile):
  13. """
  14. Responds to user-input, typically speech text, by telling a joke.
  15.  
  16. Arguments:
  17. text -- user-input, typically transcribed speech
  18. mic -- used to interact with the user (for both input and output)
  19. profile -- contains information related to the user (e.g., phone
  20. number)
  21. """
  22. logger = logging.getLogger(__name__)
  23.  
  24. kwargs = {}
  25. if 'mpdclient' in profile:
  26. if 'server' in profile['mpdclient']:
  27. kwargs['server'] = profile['mpdclient']['server']
  28. if 'port' in profile['mpdclient']:
  29. kwargs['port'] = int(profile['mpdclient']['port'])
  30.  
  31. logger.debug("Preparing to start music module")
  32. try:
  33. mpdwrapper = MPDWrapper(**kwargs)
  34. except:
  35. logger.error("Couldn't connect to MPD server", exc_info=True)
  36. mic.say("I'm sorry. It seems that Spotify is not enabled. Please " +
  37. "read the documentation to learn how to configure Spotify.")
  38. return
  39.  
  40. mic.say("Please give me a moment, I'm loading your Spotify playlists.")
  41.  
  42. # FIXME: Make this configurable
  43. persona = 'JASPER'
  44.  
  45. logger.debug("Starting music mode")
  46. music_mode = MusicMode(persona, mic, mpdwrapper)
  47. music_mode.handleForever()
  48. logger.debug("Exiting music mode")
  49.  
  50. return
  51.  
  52.  
  53. def isValid(text):
  54. """
  55. Returns True if the input is related to jokes/humor.
  56.  
  57. Arguments:
  58. text -- user-input, typically transcribed speech
  59. """
  60. return any(word in text.upper() for word in WORDS)
  61.  
  62.  
  63. # The interesting part
  64. class MusicMode(object):
  65.  
  66. def __init__(self, PERSONA, mic, mpdwrapper):
  67. self._logger = logging.getLogger(__name__)
  68. self.persona = PERSONA
  69. # self.mic - we're actually going to ignore the mic they passed in
  70. self.music = mpdwrapper
  71.  
  72. # index spotify playlists into new dictionary and language models
  73. phrases = ["STOP", "CLOSE", "PLAY", "PAUSE", "NEXT", "PREVIOUS",
  74. "LOUDER", "SOFTER", "LOWER", "HIGHER", "VOLUME",
  75. "PLAYLIST"]
  76. phrases.extend(self.music.get_soup_playlist())
  77.  
  78. music_stt_engine = mic.active_stt_engine.get_instance('music', phrases)
  79.  
  80. self.mic = Mic(mic.speaker,
  81. mic.passive_stt_engine,
  82. music_stt_engine)
  83.  
  84. def delegateInput(self, input):
  85.  
  86. command = input.upper()
  87.  
  88. # check if input is meant to start the music module
  89. if "PLAYLIST" in command:
  90. command = command.replace("PLAYLIST", "")
  91. elif "STOP" in command:
  92. self.mic.say("Stopping music")
  93. self.music.stop()
  94. return
  95. elif "PLAY" in command:
  96. self.mic.say("Playing %s" % self.music.current_song())
  97. self.music.play()
  98. return
  99. elif "PAUSE" in command:
  100. self.mic.say("Pausing music")
  101. # not pause because would need a way to keep track of pause/play
  102. # state
  103. self.music.stop()
  104. return
  105. elif any(ext in command for ext in ["LOUDER", "HIGHER"]):
  106. self.mic.say("Louder")
  107. self.music.volume(interval=10)
  108. self.music.play()
  109. return
  110. elif any(ext in command for ext in ["SOFTER", "LOWER"]):
  111. self.mic.say("Softer")
  112. self.music.volume(interval=-10)
  113. self.music.play()
  114. return
  115. elif "NEXT" in command:
  116. self.mic.say("Next song")
  117. self.music.play() # backwards necessary to get mopidy to work
  118. self.music.next()
  119. self.mic.say("Playing %s" % self.music.current_song())
  120. return
  121. elif "PREVIOUS" in command:
  122. self.mic.say("Previous song")
  123. self.music.play() # backwards necessary to get mopidy to work
  124. self.music.previous()
  125. self.mic.say("Playing %s" % self.music.current_song())
  126. return
  127.  
  128. # SONG SELECTION... requires long-loading dictionary and language model
  129. # songs = self.music.fuzzy_songs(query = command.replace("PLAY", ""))
  130. # if songs:
  131. # self.mic.say("Found songs")
  132. # self.music.play(songs = songs)
  133.  
  134. # print("SONG RESULTS")
  135. # print("============")
  136. # for song in songs:
  137. # print("Song: %s Artist: %s" % (song.title, song.artist))
  138.  
  139. # self.mic.say("Playing %s" % self.music.current_song())
  140.  
  141. # else:
  142. # self.mic.say("No songs found. Resuming current song.")
  143. # self.music.play()
  144.  
  145. # PLAYLIST SELECTION
  146. playlists = self.music.fuzzy_playlists(query=command)
  147. if playlists:
  148. self.mic.say("Loading playlist %s" % playlists[0])
  149. self.music.play(playlist_name=playlists[0])
  150. self.mic.say("Playing %s" % self.music.current_song())
  151. else:
  152. self.mic.say("No playlists found. Resuming current song.")
  153. self.music.play()
  154.  
  155. return
  156.  
  157. def handleForever(self):
  158.  
  159. self.music.play()
  160. self.mic.say("Playing %s" % self.music.current_song())
  161.  
  162. while True:
  163.  
  164. threshold, transcribed = self.mic.passiveListen(self.persona)
  165.  
  166. if not transcribed or not threshold:
  167. self._logger.info("Nothing has been said or transcribed.")
  168. continue
  169.  
  170. self.music.pause()
  171.  
  172. input = self.mic.activeListen(MUSIC=True)
  173.  
  174. if input:
  175. if "close" in input.lower():
  176. self.mic.say("Closing Spotify")
  177. return
  178. self.delegateInput(input)
  179. else:
  180. self.mic.say("Pardon?")
  181. self.music.play()
  182.  
  183.  
  184. def reconnect(func, *default_args, **default_kwargs):
  185. """
  186. Reconnects before running
  187. """
  188.  
  189. def wrap(self, *default_args, **default_kwargs):
  190. try:
  191. self.client.connect(self.server, self.port)
  192. except:
  193. pass
  194.  
  195. # sometimes not enough to just connect
  196. try:
  197. return func(self, *default_args, **default_kwargs)
  198. except:
  199. self.client = mpd.MPDClient()
  200. self.client.timeout = None
  201. self.client.idletimeout = None
  202. self.client.connect(self.server, self.port)
  203.  
  204. return func(self, *default_args, **default_kwargs)
  205.  
  206. return wrap
  207.  
  208.  
  209. class Song(object):
  210. def __init__(self, id, title, artist, album):
  211.  
  212. self.id = id
  213. self.title = title
  214. self.artist = artist
  215. self.album = album
  216.  
  217.  
  218. class MPDWrapper(object):
  219. def __init__(self, server="localhost", port=6600):
  220. """
  221. Prepare the client and music variables
  222. """
  223. self.server = server
  224. self.port = port
  225.  
  226. # prepare client
  227. self.client = mpd.MPDClient()
  228. self.client.timeout = None
  229. self.client.idletimeout = None
  230. self.client.connect(self.server, self.port)
  231.  
  232. # gather playlists
  233. self.playlists = [x["playlist"] for x in self.client.listplaylists()]
  234.  
  235. # gather songs
  236. self.client.clear()
  237. for playlist in self.playlists:
  238. self.client.load(playlist)
  239.  
  240. self.songs = [] # may have duplicates
  241. # capitalized strings
  242. self.song_titles = []
  243. self.song_artists = []
  244.  
  245. soup = self.client.playlist()
  246. for i in range(0, len(soup) / 10):
  247. index = i * 10
  248. id = soup[index].strip()
  249. title = soup[index + 3].strip().upper()
  250. artist = soup[index + 2].strip().upper()
  251. album = soup[index + 4].strip().upper()
  252.  
  253. self.songs.append(Song(id, title, artist, album))
  254.  
  255. self.song_titles.append(title)
  256. self.song_artists.append(artist)
  257.  
  258. @reconnect
  259. def play(self, songs=False, playlist_name=False):
  260. """
  261. Plays the current song or accepts a song to play.
  262.  
  263. Arguments:
  264. songs -- a list of song objects
  265. playlist_name -- user-defined, something like "Love Song Playlist"
  266. """
  267. if songs:
  268. self.client.clear()
  269. for song in songs:
  270. try: # for some reason, certain ids don't work
  271. self.client.add(song.id)
  272. except:
  273. pass
  274.  
  275. if playlist_name:
  276. self.client.clear()
  277. self.client.load(playlist_name)
  278.  
  279. self.client.play()
  280.  
  281. @reconnect
  282. def current_song(self):
  283. item = self.client.playlistinfo(int(self.client.status()["song"]))[0]
  284. result = "%s by %s" % (item["title"], item["artist"])
  285. return result
  286.  
  287. @reconnect
  288. def volume(self, level=None, interval=None):
  289.  
  290. if level:
  291. self.client.setvol(int(level))
  292. return
  293.  
  294. if interval:
  295. level = int(self.client.status()['volume']) + int(interval)
  296. self.client.setvol(int(level))
  297. return
  298.  
  299. @reconnect
  300. def pause(self):
  301. self.client.pause()
  302.  
  303. @reconnect
  304. def stop(self):
  305. self.client.stop()
  306.  
  307. @reconnect
  308. def next(self):
  309. self.client.next()
  310. return
  311.  
  312. @reconnect
  313. def previous(self):
  314. self.client.previous()
  315. return
  316.  
  317. def get_soup(self):
  318. """
  319. Returns the list of unique words that comprise song and artist titles
  320. """
  321.  
  322. soup = []
  323.  
  324. for song in self.songs:
  325. song_words = song.title.split(" ")
  326. artist_words = song.artist.split(" ")
  327. soup.extend(song_words)
  328. soup.extend(artist_words)
  329.  
  330. title_trans = ''.join(chr(c) if chr(c).isupper() or chr(c).islower()
  331. else '_' for c in range(256))
  332. soup = [x.decode('utf-8').encode("ascii", "ignore").upper().translate(
  333. title_trans).replace("_", "") for x in soup]
  334. soup = [x for x in soup if x != ""]
  335.  
  336. return list(set(soup))
  337.  
  338. def get_soup_playlist(self):
  339. """
  340. Returns the list of unique words that comprise playlist names
  341. """
  342.  
  343. soup = []
  344.  
  345. for name in self.playlists:
  346. soup.extend(name.split(" "))
  347.  
  348. title_trans = ''.join(chr(c) if chr(c).isupper() or chr(c).islower()
  349. else '_' for c in range(256))
  350. soup = [x.decode('utf-8').encode("ascii", "ignore").upper().translate(
  351. title_trans).replace("_", "") for x in soup]
  352. soup = [x for x in soup if x != ""]
  353.  
  354. return list(set(soup))
  355.  
  356. def get_soup_separated(self):
  357. """
  358. Returns the list of PHRASES that comprise song and artist titles
  359. """
  360.  
  361. title_soup = [song.title for song in self.songs]
  362. artist_soup = [song.artist for song in self.songs]
  363.  
  364. soup = list(set(title_soup + artist_soup))
  365.  
  366. title_trans = ''.join(chr(c) if chr(c).isupper() or chr(c).islower()
  367. else '_' for c in range(256))
  368. soup = [x.decode('utf-8').encode("ascii", "ignore").upper().translate(
  369. title_trans).replace("_", " ") for x in soup]
  370. soup = [re.sub(' +', ' ', x) for x in soup if x != ""]
  371.  
  372. return soup
  373.  
  374. def fuzzy_songs(self, query):
  375. """
  376. Returns songs matching a query best as possible on either artist
  377. field, etc
  378. """
  379.  
  380. query = query.upper()
  381.  
  382. matched_song_titles = difflib.get_close_matches(query,
  383. self.song_titles)
  384. matched_song_artists = difflib.get_close_matches(query,
  385. self.song_artists)
  386.  
  387. # if query is beautifully matched, then forget about everything else
  388. strict_priority_title = [x for x in matched_song_titles if x == query]
  389. strict_priority_artists = [
  390. x for x in matched_song_artists if x == query]
  391.  
  392. if strict_priority_title:
  393. matched_song_titles = strict_priority_title
  394. if strict_priority_artists:
  395. matched_song_artists = strict_priority_artists
  396.  
  397. matched_songs_bytitle = [
  398. song for song in self.songs if song.title in matched_song_titles]
  399. matched_songs_byartist = [
  400. song for song in self.songs if song.artist in matched_song_artists]
  401.  
  402. matches = list(set(matched_songs_bytitle + matched_songs_byartist))
  403.  
  404. return matches
  405.  
  406. def fuzzy_playlists(self, query):
  407. """
  408. returns playlist names that match query best as possible
  409. """
  410. query = query.upper()
  411. lookup = {n.upper(): n for n in self.playlists}
  412. results = [lookup[r] for r in difflib.get_close_matches(query, lookup)]
  413. return results
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement