musikautomat.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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. from display import Display
  12. from player import Player
  13. class WebHandler(tornado.web.RequestHandler):
  14. def initialize(self, musikautomat):
  15. self.musikautomat = musikautomat
  16. def get(self, a):
  17. if a.startswith("play"):
  18. if a == "play/dir":
  19. path = self.get_argument('path')
  20. self.musikautomat.play({
  21. "type" : "dir",
  22. "name" : self.get_argument('name', path),
  23. "path" : path
  24. }, start=True)
  25. class Musikautomat:
  26. def __init__(self, pydsp, config: Dict):
  27. # Initialise state objects
  28. self._pydsp = pydsp
  29. self._display = Display(config, self._pydsp)
  30. self._display.update()
  31. self._eventSink = self._display
  32. self._player = Player(config, self._pydsp)
  33. self._dx = config.get("dimx", 128)
  34. threading.Thread(target=self.runWebServer, daemon=True).start()
  35. def runWebServer(self):
  36. '''
  37. Run event loop for web interface.
  38. '''
  39. urls = [
  40. (r"/api/(.*)", WebHandler, dict(musikautomat=self)),
  41. (r"/(.*)", tornado.web.StaticFileHandler, {"path" : "./web",
  42. "default_filename" : "index.html" }),
  43. ]
  44. asyncio.set_event_loop(asyncio.new_event_loop())
  45. app = tornado.web.Application(urls, debug=False)
  46. app.listen(8080)
  47. tornado.ioloop.IOLoop.instance().start()
  48. def runDisplay(self):
  49. '''
  50. Run event loop for display and buttons.
  51. '''
  52. clk = pygame.time.Clock()
  53. while True:
  54. clk.tick(10) # We only need 10 FPS
  55. try:
  56. for event in pygame.event.get():
  57. # Handle exit event
  58. if event.type == pygame.QUIT:
  59. pygame.quit()
  60. sys.exit()
  61. # Handle key events
  62. if event.type == pygame.KEYDOWN:
  63. if event.key == pygame.K_DOWN:
  64. self._eventSink.action("down")
  65. elif event.key == pygame.K_UP:
  66. self._eventSink.action("up")
  67. elif event.key == pygame.K_RIGHT:
  68. if self._eventSink == self._player:
  69. self._player.togglePlay()
  70. else:
  71. self.play(self._display.currentItem())
  72. elif event.key == pygame.K_LEFT:
  73. # Get back to display
  74. self._eventSink = self._display
  75. self._display.update()
  76. except Exception as e:
  77. # Display error
  78. traceback.print_exc()
  79. self._pydsp.fill((0,0,0))
  80. font = pygame.font.Font('freesansbold.ttf', 12)
  81. s = str(e)
  82. n = int(self._dx / 6)
  83. for i, start in enumerate(range(0, len(s), n)):
  84. text = font.render(s[start:start+n], True, (255, 0, 0) , (0, 0, 0))
  85. self._pydsp.blit(text, (0, i * 12))
  86. self._eventSink = self._display
  87. pygame.display.update()
  88. def play(self, item, start=False):
  89. '''
  90. Play an item.
  91. '''
  92. print("Player data:", item)
  93. self._eventSink = self._player
  94. t = item.get("type")
  95. if t == "dir":
  96. self.playDir(item)
  97. else:
  98. raise Exception("Unknown type: %s" % t)
  99. if start:
  100. self._player.togglePlay()
  101. def playDir(self, item):
  102. '''
  103. Play all files in a directory.
  104. '''
  105. path = item.get("path")
  106. files = os.listdir(path)
  107. def toDisplayName(s):
  108. s = os.path.splitext(s)[0]
  109. return s.replace("_", " ").title()
  110. self._player.setPlaylist(sorted([{
  111. "name" : toDisplayName(f),
  112. "path" : os.path.join(path, f)
  113. } for f in files], key=lambda i: i["name"]))
  114. self._player.update()