musikautomat.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. #!./bin/python3
  2. import os
  3. import sys
  4. import threading
  5. import time
  6. import traceback
  7. from typing import Dict
  8. import pygame # type: ignore
  9. import tornado.ioloop, tornado.web
  10. import asyncio
  11. try:
  12. import RPi.GPIO as GPIO # Support for Raspberry PI GPIO input
  13. except:
  14. import mock_rpi_gpio as GPIO
  15. from display import Display
  16. from player import Player
  17. class WebHandler(tornado.web.RequestHandler):
  18. def initialize(self, musikautomat):
  19. self.musikautomat = musikautomat
  20. def get(self, a):
  21. if a.startswith("play"):
  22. if a == "play/dir":
  23. path = self.get_argument('path')
  24. self.musikautomat.play({
  25. "type" : "dir",
  26. "name" : self.get_argument('name', path),
  27. "path" : path
  28. }, start=True)
  29. class Musikautomat:
  30. def __init__(self, pydsp, config: Dict):
  31. # Initialise state objects
  32. self._autostart = config.get("autostart")
  33. self._pydsp = pydsp
  34. self._display = Display(config, self._pydsp)
  35. self._display.update()
  36. self._eventSink = self._display
  37. self._player = Player(config, self._pydsp)
  38. self._dx = config.get("dimx", 128)
  39. threading.Thread(target=self.runWebServer, daemon=True).start()
  40. GPIO.setmode(GPIO.BOARD) # Use physical pin numbering
  41. GPIO.setwarnings(False) # Disable warnings
  42. GPIO.setup(3, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
  43. GPIO.setup(5, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
  44. GPIO.setup(7, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  45. GPIO.setup(11, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  46. def runWebServer(self):
  47. '''
  48. Run event loop for web interface.
  49. '''
  50. urls = [
  51. (r"/api/(.*)", WebHandler, dict(musikautomat=self)),
  52. (r"/(.*)", tornado.web.StaticFileHandler, {"path" : "./web",
  53. "default_filename" : "index.html" }),
  54. ]
  55. asyncio.set_event_loop(asyncio.new_event_loop())
  56. app = tornado.web.Application(urls, debug=False)
  57. app.listen(8080)
  58. tornado.ioloop.IOLoop.instance().start()
  59. def eventUp(self):
  60. self._eventSink.action("up")
  61. def eventDown(self):
  62. self._eventSink.action("down")
  63. def eventLeft(self):
  64. # Get back to display
  65. self._eventSink = self._display
  66. self._display.update()
  67. def eventRight(self):
  68. if self._eventSink == self._player:
  69. self._player.togglePlay()
  70. else:
  71. self.play(self._display.currentItem())
  72. def runDisplay(self):
  73. '''
  74. Run event loop for display and buttons.
  75. '''
  76. clk = pygame.time.Clock()
  77. gpio_event_timer = 0
  78. if self._autostart:
  79. self.eventRight()
  80. self.eventRight()
  81. while True:
  82. clk.tick(10) # We only need 10 FPS
  83. if gpio_event_timer > 1:
  84. if GPIO.input(3) == 0: # Up
  85. gpio_event_timer=0
  86. self.eventUp()
  87. elif GPIO.input(5) == 0: # LEFT
  88. gpio_event_timer=0
  89. self.eventLeft()
  90. elif GPIO.input(7) == 0: # RIGHT
  91. gpio_event_timer=0
  92. self.eventRight()
  93. elif GPIO.input(11) == 0: # DOWN
  94. gpio_event_timer=0
  95. self.eventDown()
  96. else:
  97. gpio_event_timer+=1
  98. try:
  99. for event in pygame.event.get():
  100. # Handle exit event
  101. if event.type == pygame.QUIT:
  102. pygame.quit()
  103. sys.exit()
  104. # Handle key events
  105. if event.type == pygame.KEYDOWN:
  106. if event.key == pygame.K_UP:
  107. self.eventUp()
  108. elif event.key == pygame.K_LEFT:
  109. self.eventLeft()
  110. elif event.key == pygame.K_RIGHT:
  111. self.eventRight()
  112. elif event.key == pygame.K_DOWN:
  113. self.eventDown()
  114. except Exception as e:
  115. # Display error
  116. traceback.print_exc()
  117. self._pydsp.fill((0,0,0))
  118. font = pygame.font.Font('freesansbold.ttf', 12)
  119. s = str(e)
  120. n = int(self._dx / 6)
  121. for i, start in enumerate(range(0, len(s), n)):
  122. text = font.render(s[start:start+n], True, (255, 0, 0) , (0, 0, 0))
  123. self._pydsp.blit(text, (0, i * 12))
  124. self._eventSink = self._display
  125. pygame.display.update()
  126. def toDisplayName(self, s):
  127. s = os.path.splitext(s)[0]
  128. return s.replace("_", " ").title()
  129. def play(self, item, start=False):
  130. '''
  131. Play an item.
  132. '''
  133. print("Player data:", item)
  134. self._eventSink = self._player
  135. t = item.get("type")
  136. if t == "dir":
  137. self.playDir(item)
  138. elif t == "m3u":
  139. self.playM3U(item)
  140. elif t == "stream":
  141. self.playStream(item)
  142. else:
  143. raise Exception("Unknown type: %s" % t)
  144. if start:
  145. self._player.togglePlay()
  146. def playStream(self, item):
  147. '''
  148. Play a stream.
  149. '''
  150. self._player.setPlaylist([{
  151. "name" : item["name"],
  152. "path" : item["url"]
  153. }])
  154. self._player.update()
  155. def playDir(self, item):
  156. '''
  157. Play all files in a directory.
  158. '''
  159. path = item.get("path")
  160. files = os.listdir(path)
  161. self._player.setPlaylist(sorted([{
  162. "name" : self.toDisplayName(f),
  163. "path" : os.path.join(path, f)
  164. } for f in files], key=lambda i: i["name"]))
  165. self._player.update()
  166. def playM3U(self, item):
  167. '''
  168. Play all files of a M3U playlist.
  169. '''
  170. path = item.get("path")
  171. items = []
  172. with open(path) as f:
  173. for line in f:
  174. item_path = str(line).strip()
  175. if not item_path.startswith("#"):
  176. items.append({
  177. "name" : self.toDisplayName(os.path.basename(item_path)),
  178. "path" : os.path.join(os.path.dirname(path), item_path)
  179. })
  180. self._player.setPlaylist(items)
  181. self._player.update()