superdac.py 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. #
  4. # Requires:
  5. # apt install mpd
  6. # apt install mpc
  7. # apt install lirc
  8. # apt install python3-pip
  9. # apt install python3-rpi.gpio
  10. # apt install python3-pil
  11. # apt install python3-numpy
  12. # apt install python3-lirc
  13. # pip3 install pyalsaaudio
  14. # pip3 install pysmb
  15. # Python_ST7735-raspimag.tar.gz
  16. # python-mpd2.tar.gz
  17. # stdlibs
  18. import sys
  19. import os
  20. import signal
  21. import threading
  22. from threading import BoundedSemaphore
  23. from threading import Semaphore
  24. import queue
  25. import platform
  26. from select import select
  27. from time import sleep
  28. # ABCMeta
  29. from abc import ABCMeta, abstractmethod
  30. # Raspberry pi
  31. import RPi.GPIO as GPIO
  32. # PIL
  33. from PIL import Image
  34. from PIL import ImageDraw
  35. from PIL import ImageFont
  36. # Adafruit
  37. import ST7735 as TFT
  38. # import Adafruit_GPIO as GPIO
  39. import Adafruit_GPIO.SPI as SPI
  40. # lirc
  41. import lirc
  42. # mpd
  43. from mpd import MPDClient
  44. from socket import error as SocketError
  45. # alsaaudio
  46. from alsaaudio import Mixer
  47. # SMB
  48. from smb.SMBConnection import SMBConnection
  49. from nmb.NetBIOS import NetBIOS
  50. # LCD spec.
  51. LCD_WIDTH = 128
  52. LCD_HEIGHT = 160
  53. SPEED_HZ = 8000000
  54. # SUPERDAC configration
  55. DC = 24
  56. RST = 23
  57. SPI_PORT = 0
  58. SPI_DEVICE = 0
  59. SW1 = 5
  60. SW2 = 6
  61. # FONT settings
  62. DEFAULT_FONT = '/usr/share/fonts/truetype/fonts-japanese-gothic.ttf'
  63. FONT_SIZE = 12
  64. # PID File
  65. PIDFILE='/var/run/superdac.pid'
  66. LOGFILE='/var/log/superdac.log'
  67. # MPD Socket
  68. MPD_HOST='/var/run/mpd/socket'
  69. MPD_PORT=6600
  70. # lirc configration files
  71. LIRCRC = ['/etc/lirc/superdac.lircrc', '/usr/local/etc/superdac.lircrc', './superdac.lircrc' ]
  72. # Volume file
  73. VOLUME_FILE='/var/tmp/.superdac.volume'
  74. # Openning Logo Image
  75. LOGO_FILE = '/home/mpd/scripts/logo.jpg'
  76. class SDTimer(threading.Thread):
  77. __active = False
  78. def __init__(self, sec, callback):
  79. super(SDTimer, self).__init__()
  80. self.daemon = True
  81. self.__sec = sec
  82. self.__callback = callback
  83. def getRemaining(self):
  84. return self.__sec
  85. def setRemaining(self, sec):
  86. self.__sec = sec
  87. def getActive(self):
  88. return self.__active
  89. def cancel(self):
  90. self.__active = False
  91. def run(self):
  92. self.__active = True
  93. while self.__sec != 0:
  94. sleep(1)
  95. self.__sec -= 1
  96. if self.__active == False:
  97. break
  98. if self.__active:
  99. self.__callback()
  100. self.__active = False
  101. return
  102. class castQueue(queue.Queue):
  103. __depth = 5
  104. def __init__(self, depth):
  105. super(castQueue, self).__init__()
  106. self.__depth = depth
  107. self.__semaphore = BoundedSemaphore()
  108. def put(self, obj):
  109. self.__semaphore.acquire()
  110. while self.qsize() > self.__depth:
  111. try:
  112. t = super().get_nowait()
  113. except queue.Empty:
  114. pass
  115. super().put(obj)
  116. self.__semaphore.release()
  117. return
  118. def get_nowait(self):
  119. self.__semaphore.acquire()
  120. r = None
  121. try:
  122. r = super().get_nowait()
  123. except Exception as e:
  124. self.__semaphore.release()
  125. raise e
  126. self.__semaphore.release()
  127. return r
  128. # Message class
  129. class Message():
  130. TERM = 0 # 'term'
  131. DISPLAY = 1 # 'disp'
  132. RCONTROL = 2 # 'rcontrol'
  133. MENUON = 3 # 'menuon'
  134. MENUOFF = 4 # 'menuoff'
  135. MPD = 5 # 'mpd'
  136. MPC = 6 # 'mpc'
  137. PLAYLIST = 7 # 'playlist'
  138. INDICATOR = 8 # 'indicate'
  139. MPC_REPLY = 9 # 'mpc_reply'
  140. VOLUME = 10 # 'volume'
  141. def __init__(self, sender, message, param):
  142. self.__sender = sender
  143. self.__message = message
  144. self.__param = param
  145. def getSender(self):
  146. return self.__sender
  147. def getMessage(self):
  148. return self.__message
  149. def getParam(self):
  150. return self.__param
  151. # LCD Display Item
  152. class LCDItem():
  153. FULL = 0
  154. TOP = 1
  155. INDICATE = 2
  156. BOTTOM = 3
  157. def __init__(self, image, clear=False, pos=FULL, time=0):
  158. self.__image = image
  159. self.__pos = pos
  160. self.__time = time
  161. self.__clear = clear
  162. return
  163. def getClear(self):
  164. return self.__clear
  165. def getImage(self):
  166. return self.__image
  167. def getPos(self):
  168. return self.__pos
  169. def getTime(self):
  170. return self.__time
  171. def getWidth(self):
  172. return self.__image.size[0]
  173. def getHeight(self):
  174. return self.__image.size[1]
  175. # Worker Base
  176. class Worker(metaclass=ABCMeta):
  177. # type: (Object, Master)
  178. def __init__(self, pm):
  179. self.playermain = pm
  180. def sendMessage(self, message):
  181. self.playermain.postMessage(message)
  182. @abstractmethod
  183. def receiveMessage(self, message):
  184. pass
  185. # Master Base
  186. class Master(metaclass=ABCMeta):
  187. @abstractmethod
  188. def postMessage(self, message):
  189. pass
  190. # Switch worker
  191. class Switches(Worker):
  192. def __init__(self, pm):
  193. # type: (Object, Master)
  194. super().__init__(pm)
  195. GPIO.setmode(GPIO.BCM)
  196. GPIO.setup(SW1, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  197. GPIO.setup(SW2, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  198. GPIO.add_event_detect(SW1, GPIO.FALLING, callback=self.swCallback, bouncetime=20)
  199. GPIO.add_event_detect(SW2, GPIO.FALLING, callback=self.swCallback, bouncetime=20)
  200. def swCallback(self, ch):
  201. if ch == SW1:
  202. os.system('/sbin/reboot')
  203. elif ch == SW2:
  204. os.system('/sbin/poweroff')
  205. return
  206. def receiveMessage(self, message):
  207. if message.getMessage() == Message.TERM:
  208. GPIO.remove_event_detect(SW1)
  209. GPIO.remove_event_detect(SW2)
  210. GPIO.cleanup(SW1)
  211. GPIO.cleanup(SW2)
  212. return True
  213. # Playlist manage worker
  214. class Playlist(Worker):
  215. active = False
  216. playermain = None
  217. playlists = []
  218. artists = []
  219. albums = []
  220. items = []
  221. current = []
  222. type = ''
  223. LINES = 10
  224. LINE_HEIGHT = 16
  225. def __init__(self, pm):
  226. # type: (Object, Master)
  227. super().__init__(pm)
  228. # 初期データ取得
  229. self.sendMessage(Message(self, Message.MPC, {'command':'list', 'arg': 'playlists' } ))
  230. self.sendMessage(Message(self, Message.MPC, {'command':'list', 'arg': 'album' } ))
  231. self.sendMessage(Message(self, Message.MPC, {'command':'list', 'arg': 'artist' } ))
  232. def makeMenu(self, draw, font):
  233. self.current = self.items[0:self.LINES-1]
  234. del self.items[0:self.LINES-1]
  235. for i in range(0, len(self.current)):
  236. draw.text((0, i*self.LINE_HEIGHT), str(i+1)+':'+self.current[i], font=font, fill='#FFFFFF' )
  237. i = i + 1
  238. if len(self.items) == 0:
  239. draw.text((0,i*self.LINE_HEIGHT), '0:中止', font=font, fill='#FFFFFF' )
  240. else:
  241. draw.text((0,i*self.LINE_HEIGHT), '0:次へ', font=font, fill='#FFFFFF' )
  242. return
  243. def receiveMessage(self, message):
  244. # リスト取得
  245. if message.getMessage() == Message.MPC_REPLY:
  246. if message.getParam()['type'] == 'playlists':
  247. self.playlists = message.getParam()['list']
  248. elif message.getParam()['type'] == 'artist':
  249. self.artists = message.getParam()['list']
  250. elif message.getParam()['type'] == 'album':
  251. self.albums = message.getParam()['list']
  252. return True
  253. if message.getMessage() == Message.PLAYLIST:
  254. if not self.active:
  255. self.type = message.getParam()
  256. if self.type == 'playlists':
  257. self.items = self.playlists
  258. elif self.type == 'artist':
  259. self.items = self.artists
  260. elif self.type == 'album':
  261. self.items = self.albums
  262. image = Image.new('RGB', (LCD.WIDTH, LCD.HEIGHT), 0)
  263. font = ImageFont.truetype(DEFAULT_FONT, 14, encoding="unic");
  264. draw = ImageDraw.Draw(image)
  265. # プレイリストが空
  266. if len(self.items) == 0:
  267. draw.text((0,64), 'プレイリストが', font=font, fill='#FF0000')
  268. draw.text((0,80), ' ありません', font=font, fill='#FF0000')
  269. item = LCDItem(image, time=2)
  270. self.sendMessage(Message(self, Message.DISPLAY, item))
  271. self.sendMessage(Message(self, Message.MENUOFF, None ))
  272. return False
  273. else:
  274. # 新規メニュー描画
  275. self.makeMenu(draw, font)
  276. item = LCDItem(image)
  277. self.playermain.lcd.displayItem(item, lock=True)
  278. self.active = True
  279. return False
  280. return False
  281. # リモコン
  282. if message.getMessage() == Message.RCONTROL and self.active:
  283. # 数字ボタン
  284. if message.getParam() in '0123456789':
  285. btn = int(message.getParam()) - 1
  286. if btn < 0:
  287. if len(self.items) == 0:
  288. # 終了
  289. self.active = False
  290. self.sendMessage(Message(self, Message.MENUOFF, None ))
  291. self.playermain.lcd.release()
  292. else:
  293. # 次ページ
  294. image = Image.new('RGB', (LCD.WIDTH, LCD.HEIGHT), 0)
  295. font = ImageFont.truetype(DEFAULT_FONT, 14, encoding="unic");
  296. draw = ImageDraw.Draw(image)
  297. self.makeMenu(draw, font)
  298. item = LCDItem(image)
  299. self.playermain.lcd.release()
  300. self.playermain.lcd.displayItem(item, lock=True)
  301. self.active = True
  302. return False
  303. elif btn < len(self.current):
  304. if self.type == 'playlists':
  305. self.sendMessage(Message(self, Message.MPC, {'command': 'load', 'arg': self.current[btn]}))
  306. else:
  307. param = {'command':'findadd', 'arg': {'type':self.type,'value': self.current[btn]}}
  308. msg = Message(self, Message.MPC, param)
  309. self.sendMessage(msg)
  310. self.playermain.lcd.release()
  311. self.sendMessage(Message(self, Message.MENUOFF, None ))
  312. self.active = False
  313. return True
  314. return True
  315. # メインメニューに戻る
  316. elif message.getParam() == 'menu':
  317. self.playermain.lcd.release()
  318. self.active = False
  319. return True
  320. return True
  321. # MainMenu Worker
  322. class MainMenu(Worker):
  323. active = False
  324. playermain = None # type: Master
  325. status = []
  326. playlists = []
  327. def __init__(self, pm):
  328. # type: (Object, Master)
  329. super().__init__(pm)
  330. self.sendMessage(Message(self, Message.MPC, {'command':'list', 'arg': 'playlists' } ))
  331. def receiveMessage(self,message):
  332. # MPD Message
  333. if message.getMessage() == Message.MPD:
  334. param = message.getParam()
  335. self.status = param['status']
  336. return True
  337. if message.getMessage() == Message.MPC_REPLY:
  338. if message.getParam()['type'] == 'playlists':
  339. self.playlists = message.getParam()['list']
  340. return True
  341. # リモコン以外は無視
  342. if message.getMessage() != Message.RCONTROL:
  343. return True
  344. # Remote Control Message
  345. if message.getParam() == 'menu':
  346. # おそらくありえない
  347. if len(self.status) == 0:
  348. return True
  349. # アクティブなら閉じる
  350. if self.active:
  351. self.active = False
  352. self.playermain.lcd.release()
  353. self.sendMessage(Message(self, Message.MENUOFF, None ))
  354. return True
  355. self.sendMessage(Message(self, Message.MPC, {'command':'list', 'arg': 'playlists'} ))
  356. self.sendMessage(Message(self, Message.MPC, {'command':'list', 'arg': 'album' } ))
  357. self.sendMessage(Message(self, Message.MPC, {'command':'list', 'arg': 'artist' } ))
  358. # メニュー描画
  359. image = Image.new('RGB', (LCD.WIDTH, LCD.HEIGHT), 0)
  360. font = ImageFont.truetype(DEFAULT_FONT, 14, encoding="unic");
  361. draw = ImageDraw.Draw(image)
  362. if self.status['random'] == '1':
  363. draw.text((0,0),'1:ノーマル再生', font=font,fill='#FFFFFF')
  364. else:
  365. draw.text((0,0),'1:ランダム再生', font=font,fill='#FFFFFF')
  366. if self.status['repeat'] == '1':
  367. draw.text((0,16),'2:リピートしない', font=font,fill='#FFFFFF')
  368. else:
  369. draw.text((0,16),'2:リピート再生', font=font,fill='#FFFFFF')
  370. draw.text((0,32 ), '3:現在の曲を外す', font=font,fill='#FFFFFF')
  371. draw.text((0,48 ), '4:プレイリスト保存', font=font,fill='#FFFFFF')
  372. draw.text((0,64 ), '5:全曲再生', font=font,fill='#FFFFFF')
  373. draw.text((0,80 ), '6:プレイリスト読込', font=font,fill='#FFFFFF')
  374. draw.text((0,96 ), '7:アルバム選択', font=font,fill='#FFFFFF')
  375. draw.text((0,112), '8:アーティスト選択', font=font,fill='#FFFFFF')
  376. draw.text((0,128), '9:DBアップデート', font=font,fill='#FFFFFF')
  377. # self.sendMessage(Message(self, Message.MENUON, None ))
  378. item = LCDItem(image, pos=LCDItem.FULL)
  379. self.playermain.lcd.displayItem(item,lock=True)
  380. self.active = True
  381. return True
  382. # アクティブかつリモコンが押された
  383. if self.active:
  384. c = message.getParam()
  385. if c in '0123456789':
  386. if c == '1': # random
  387. arg = 'on'
  388. if self.status['random'] == '1':
  389. arg = 'off'
  390. self.sendMessage(Message(self, Message.MPC, {'command':'random', 'arg': arg} ))
  391. self.active = False
  392. self.sendMessage(Message(self, Message.MENUOFF, None ))
  393. elif c == '2': # repeat
  394. arg = 'on'
  395. if self.status['repeat'] == '1':
  396. arg = 'off'
  397. self.sendMessage(Message(self, Message.MPC, {'command':'repeat', 'arg': arg} ))
  398. self.active = False
  399. self.sendMessage(Message(self, Message.MENUOFF, None ))
  400. elif c == '3': # delete
  401. self.sendMessage(Message(self, Message.MPC, {'command':'delete', 'arg': None} ))
  402. self.active = False
  403. self.sendMessage(Message(self, Message.MENUOFF, None ))
  404. elif c == '4': # save
  405. for i in range(0,100):
  406. if not (('playlist'+str(i)) in self.playlists):
  407. break
  408. self.sendMessage(Message(self, Message.MPC, {'command':'save', 'arg': 'playlist'+str(i)}))
  409. self.active = False
  410. self.sendMessage(Message(self, Message.MENUOFF, None ))
  411. elif c == '5': # play all
  412. self.sendMessage(Message(self, Message.MPC, {'command':'playall', 'arg': None} ))
  413. self.active = False
  414. self.sendMessage(Message(self, Message.MENUOFF, None ))
  415. elif c == '6': # playlist
  416. self.sendMessage(Message(self, Message.MPC, {'command':'list', 'arg': 'playlists' } ))
  417. self.sendMessage(Message(self, Message.PLAYLIST, 'playlists' ))
  418. self.active = False
  419. elif c == '7': # album
  420. self.sendMessage(Message(self, Message.MPC, {'command':'list', 'arg': 'album' } ))
  421. self.sendMessage(Message(self, Message.PLAYLIST, 'album' ))
  422. self.active = False
  423. elif c == '8': # artist
  424. self.sendMessage(Message(self, Message.MPC, {'command':'list', 'arg': 'artist' } ))
  425. self.sendMessage(Message(self, Message.PLAYLIST, 'artist' ))
  426. self.active = False
  427. elif c == '9': # db update
  428. self.sendMessage(Message(self, Message.MPC, {'command':'update', 'arg': None} ))
  429. self.active = False
  430. self.sendMessage(Message(self, Message.MENUOFF, None ))
  431. if self.active == False:
  432. self.playermain.lcd.release()
  433. return False
  434. return True
  435. return True
  436. # Volume Control Worker
  437. class VolumeControl(Worker):
  438. MAX_VALUE = 81 # 81% == 0dB
  439. enabled = True
  440. term = False
  441. value = 100
  442. mute = False
  443. def __init__(self, pm):
  444. super().__init__(pm)
  445. # read initial value
  446. if os.path.exists(VOLUME_FILE):
  447. with open(VOLUME_FILE, "r") as f:
  448. try:
  449. self.value = int(f.read())
  450. except Exception as e:
  451. pass
  452. try:
  453. self.mixer = Mixer('Digital')
  454. except Exception as e:
  455. print(e, file=self.playermain.out)
  456. self.enabled = False
  457. if self.enabled:
  458. self.mixer.setvolume(int((self.MAX_VALUE *self.value)/100))
  459. self.mixer.setmute(self.mute)
  460. def receiveMessage(self,message):
  461. if message.getMessage() == Message.RCONTROL:
  462. # enabled ?
  463. if not self.enabled:
  464. return True
  465. if message.getParam() == 'volup':
  466. self.value = self.value + 2
  467. if self.value > 100:
  468. self.value = 100
  469. elif message.getParam() == 'voldown':
  470. self.value = self.value - 2
  471. if self.value < 0:
  472. self.value = 0
  473. elif message.getParam() == 'mute':
  474. self.mute = not(self.mute)
  475. self.mixer.setmute(self.mute)
  476. self.sendMessage(Message(self, Message.VOLUME, {'type':'mute', 'value': self.mute}))
  477. if message.getParam() == 'volup' or message.getParam() == 'voldown':
  478. self.mixer.setvolume(int((self.MAX_VALUE*self.value)/100))
  479. self.sendMessage(Message(self, Message.VOLUME, {'type':'volume', 'value': self.value}))
  480. # Terminate
  481. elif message.getMessage() == Message.TERM:
  482. try:
  483. with open(VOLUME_FILE, "w") as f:
  484. f.write(str(self.value))
  485. except Exception as e:
  486. pass
  487. return True
  488. # LCD Worker
  489. class LCD(Worker, threading.Thread):
  490. term = False
  491. HEIGHT = LCD_HEIGHT
  492. WIDTH = LCD_WIDTH
  493. BOTTOM_HEIGHT = 12
  494. INDICATE_HEIGHT = 10
  495. term = False
  496. def __init__(self, pm):
  497. threading.Thread.__init__(self)
  498. Worker.__init__(self,pm)
  499. self.spi = SPI.SpiDev(SPI_PORT, SPI_DEVICE,max_speed_hz=SPEED_HZ)
  500. self.disp = TFT.ST7735(DC, rst=RST, spi=self.spi)
  501. self.disp.begin()
  502. self.disp.clear((0,0,0));
  503. if os.path.exists(LOGO_FILE):
  504. image = Image.open(LOGO_FILE)
  505. self.disp.buffer.paste(image)
  506. self.disp.display()
  507. self.messageq = castQueue(5)
  508. self.semaphore = BoundedSemaphore()
  509. def receiveMessage(self, message):
  510. if message.getMessage() == Message.DISPLAY:
  511. self.messageq.put(message.getParam())
  512. return False
  513. elif message.getMessage() == Message.TERM:
  514. self.term = True
  515. return True
  516. def release(self):
  517. try:
  518. self.semaphore.release()
  519. except Exception as e:
  520. print(e, file=self.playermain.out)
  521. def displayItem(self, item, lock=False):
  522. self.semaphore.acquire()
  523. try:
  524. # 画面全体に貼り付け
  525. if item.getPos() == LCDItem.FULL:
  526. if item.getClear():
  527. self.disp.clear((0,0,0))
  528. if item.getImage() is not None:
  529. self.disp.buffer.paste(item.getImage())
  530. self.disp.display()
  531. else:
  532. left = 0
  533. upper = 0
  534. item_width = item.getWidth()
  535. item_height = item.getHeight()
  536. image = item.getImage()
  537. # サイズ制限
  538. if item.getWidth() != self.WIDTH:
  539. scale = float(float(self.WIDTH)/float(item.getWidth()))
  540. image = item.getImage().resize((int(item_width * scale), int(item_height*scale)), Image.BILINEAR)
  541. if image.size[1] > self.HEIGHT:
  542. image = image.crop( box=(0,0,self.WIDTH, self.HEIGHT) )
  543. # Display TOP
  544. if item.getPos() == LCDItem.TOP:
  545. self.disp.display(image, x0=left, y0=upper, x1=self.WIDTH-1, y1=image.size[1]-1 )
  546. # Display Bottom
  547. elif item.getPos() == LCDItem.BOTTOM:
  548. if image.size[1] > self.BOTTOM_HEIGHT:
  549. image = image.crop( box=(0, 0, self.WIDTH, self.BOTTOM_HEIGHT) )
  550. self.disp.display(image, x0=left, y0 = self.HEIGHT-self.BOTTOM_HEIGHT-1, x1=self.WIDTH-1, y1=self.HEIGHT-1 )
  551. # Display Indicator
  552. elif item.getPos() == LCDItem.INDICATE:
  553. if image.size[1] > self.INDICATE_HEIGHT:
  554. image = image.crop( box=(0, 0, self.WIDTH, self.INDICATE_HEIGHT) )
  555. self.disp.display(image, x0=left, y0 = self.HEIGHT-self.BOTTOM_HEIGHT-self.INDICATE_HEIGHT-1, x1=self.WIDTH-1, y1=self.HEIGHT-self.BOTTOM_HEIGHT-1 )
  556. if item.getTime() != 0:
  557. sleep(item.getTime())
  558. except Exception as e: # 何かエラーが起きたら無条件で開放
  559. print(e, file=self.playermain.out)
  560. self.release()
  561. return
  562. if lock == False:
  563. self.release()
  564. return
  565. def run(self):
  566. while self.term == False:
  567. try:
  568. # LCDItem
  569. item = self.messageq.get_nowait()
  570. self.displayItem(item)
  571. except queue.Empty:
  572. sleep(0.05)
  573. return True
  574. # MPD state watch worker
  575. class MPD(Worker, threading.Thread):
  576. term = False
  577. def __init__(self, pm):
  578. threading.Thread.__init__(self)
  579. Worker.__init__(self,pm)
  580. try:
  581. self.mpc = MPDClient(use_unicode=True)
  582. self.mpc.connect( MPD_HOST, MPD_PORT );
  583. except SocketError as e:
  584. print(e, file=self.playermain.out);
  585. return
  586. self.config = self.mpc.config()
  587. current = self.mpc.currentsong()
  588. status = self.mpc.status()
  589. self.param = {'changes':None, 'current': current, 'status': status, 'config': self.config }
  590. def receiveMessage(self,message):
  591. if message.getMessage() == Message.TERM:
  592. self.term = True
  593. elif message.getMessage() == Message.MENUOFF:
  594. self.sendMessage()
  595. return True
  596. def sendMessage(self):
  597. super().sendMessage(Message(self, Message.MPD, self.param))
  598. return
  599. def run(self):
  600. # 現状レポート
  601. current = self.mpc.currentsong()
  602. status = self.mpc.status()
  603. self.param = {'changes':None, 'current': current, 'status': status, 'config': self.config }
  604. self.sendMessage()
  605. self.mpc.send_idle()
  606. while self.term == False:
  607. canRead = select([self.mpc], [], [], 0)[0]
  608. if canRead:
  609. changes = self.mpc.fetch_idle()
  610. current = self.mpc.currentsong()
  611. status = self.mpc.status()
  612. self.param = {'changes': changes, 'current': current, 'status': status, 'config': self.config }
  613. self.sendMessage()
  614. self.mpc.send_idle()
  615. sleep(0.1)
  616. # idle中にCloseすると怒られる
  617. # self.mpc.close()
  618. return
  619. # Album art worker
  620. class AlbumArt(Worker, threading.Thread):
  621. TEMP_DIR = '/var/tmp/.superdac.aa'
  622. term = False
  623. refresh = False
  624. prev_path = 'foobar'
  625. term = False
  626. def __init__(self, pm):
  627. threading.Thread.__init__(self)
  628. Worker.__init__(self,pm)
  629. self.messageq = queue.Queue()
  630. def getAlbumArt(self, config, current):
  631. param = config.split('/')
  632. if param[0] == 'smb:':
  633. # SMB
  634. musicfile = (current['file']).split('/')
  635. remote_host = param[2]
  636. share_name = param[3]
  637. remote_path = '/'
  638. for r in param[4:]:
  639. remote_path = remote_path + r + '/'
  640. for r in musicfile[0:len(musicfile)-1]:
  641. remote_path = remote_path + r + '/'
  642. try:
  643. nmb = NetBIOS()
  644. remote_ip = nmb.queryName(remote_host)[0]
  645. if remote_ip is None: # 名前が引けない
  646. return None
  647. con = SMBConnection('','',platform.uname().node, remote_host)
  648. con.connect(remote_ip)
  649. items = con.listPath(share_name, remote_path)
  650. files = [item.filename for item in items]
  651. aafile = ''
  652. if 'Folder.jpg' in files:
  653. aafile = remote_path + 'Folder.jpg'
  654. elif 'folder.jpg' in files:
  655. aafile = remote_Path + 'folder.jpg'
  656. elif 'AlbumArt.jpg' in files:
  657. aafile = remote_path + 'AlbumArt.jpg'
  658. elif 'AlbumArtSmall.jpg' in files:
  659. aafile = remote_path + 'AlbumArtSmall.jpg'
  660. else:
  661. con.close()
  662. return None
  663. if not os.path.exists(self.TEMP_DIR):
  664. os.mkdir(self.TEMP_DIR)
  665. with open( self.TEMP_DIR+'/aa.jpg', 'wb') as f:
  666. con.retrieveFile(share_name, aafile, f)
  667. con.close()
  668. except Exception as e:
  669. print(e, file=self.playermain.out)
  670. return None
  671. return self.TEMP_DIR+'/aa.jpg'
  672. else:
  673. path = config + '/' + os.path.dirname(current['file'])
  674. imgfile = None
  675. if os.path.exists( path+'/Folder.jpg'):
  676. imgfile = path + '/Folder.jpg'
  677. elif os.path.exists(path + '/folder.jpg'):
  678. imgfile = path + '/folder.jpg'
  679. elif os.path.exists(path + '/AlbumArt.jpg'):
  680. imgfile = path + '/AlbumArt.jpg'
  681. elif os.path.exists(path + '/AlbumArtSmall.jpg'):
  682. imgfile = path + '/AlbumArtSmall.jpg'
  683. return imgfile
  684. return None
  685. def draw(self, param):
  686. status = param['status']
  687. current = param['current']
  688. config = param['config']
  689. imgfile = None
  690. if 'file' in current:
  691. path = config + '/' + os.path.dirname(current['file'])
  692. # Album change
  693. if path != self.prev_path:
  694. self.prev_path = path
  695. imgfile = self.getAlbumArt(config, current)
  696. # アルバムアートがなければロゴ
  697. if imgfile is None:
  698. if os.path.exists(LOGO_FILE):
  699. imgfile = LOGO_FILE
  700. else: # 停止中かなにか
  701. if imgfile is None:
  702. if os.path.exists(LOGO_FILE):
  703. imgfile = LOGO_FILE
  704. # Display AlbumArt
  705. if imgfile is not None:
  706. # LCD clear
  707. param = LCDItem(None, clear=True)
  708. msg = Message(self,Message.DISPLAY, param)
  709. self.sendMessage(msg)
  710. #
  711. image = Image.open(imgfile)
  712. param = LCDItem(image, pos=LCDItem.TOP)
  713. msg = Message(self, Message.DISPLAY, param)
  714. self.sendMessage(msg)
  715. # インジケータ再描画
  716. self.sendMessage(Message(self, Message.INDICATOR, None))
  717. # Display Title
  718. if 'title' in current:
  719. title = current['title']
  720. jpfont = ImageFont.truetype(DEFAULT_FONT, LCD.BOTTOM_HEIGHT, encoding="unic");
  721. image = Image.new('RGB', (LCD.WIDTH, LCD.BOTTOM_HEIGHT), 0)
  722. draw = ImageDraw.Draw(image)
  723. draw.text((0,0) , title, font=jpfont, fill='#FFFFFF')
  724. param = LCDItem(image, pos=LCDItem.BOTTOM)
  725. msg = Message(self, Message.DISPLAY, param)
  726. self.sendMessage(msg)
  727. return
  728. def receiveMessage(self,message):
  729. if message.getMessage() == Message.MENUOFF:
  730. self.prev_path = '' # メニューが閉じたらアルバムアートをクリア
  731. elif message.getMessage() == Message.MPD:
  732. self.messageq.put(message.getParam())
  733. elif message.getMessage() == Message.TERM:
  734. self.term = True
  735. return True
  736. def run(self):
  737. while self.term == False:
  738. try:
  739. param = self.messageq.get_nowait()
  740. self.draw(param)
  741. except queue.Empty:
  742. sleep(0.05)
  743. return
  744. # Indicator worker
  745. class Indicator(Worker, threading.Thread):
  746. status = {'state': 'stop'}
  747. term = False
  748. mute = False
  749. def __init__(self, pm):
  750. threading.Thread.__init__(self)
  751. Worker.__init__(self, pm)
  752. self.messageq = queue.Queue()
  753. self.timer = SDTimer(5, self.refresh)
  754. def refresh(self):
  755. jpfont = ImageFont.truetype(DEFAULT_FONT, LCD.INDICATE_HEIGHT, encoding="unic");
  756. image = Image.new('RGB', (LCD.WIDTH, LCD.INDICATE_HEIGHT), 0)
  757. draw = ImageDraw.Draw(image)
  758. str = ''
  759. if self.mute:
  760. str = 'Mute....'
  761. else:
  762. if self.status['state'] == 'stop':
  763. str = 'Stop'
  764. elif self.status['state'] == 'pause':
  765. str = 'Pause'
  766. else:
  767. if self.status['random'] == '1':
  768. str = str + 'Random'
  769. else:
  770. str = str + 'Normal'
  771. if self.status['repeat'] == '1':
  772. str = str + '/Repeat'
  773. str = str + '/'+ self.status['bitrate'] + 'kbps'
  774. draw.text((0,0) , str, font=jpfont, fill='#00BFFF')
  775. param = LCDItem(image, pos=LCDItem.INDICATE)
  776. msg = Message(self, Message.DISPLAY, param)
  777. self.sendMessage(msg)
  778. return
  779. def volumeBar(self, value):
  780. jpfont = ImageFont.truetype(DEFAULT_FONT, LCD.INDICATE_HEIGHT, encoding="unic");
  781. image = Image.new('RGB', (LCD.WIDTH, LCD.INDICATE_HEIGHT), 0)
  782. draw = ImageDraw.Draw(image)
  783. draw.text((0,0) ,'Vol:', font=jpfont, fill='#7CFC00')
  784. bar_length = int(((LCD.WIDTH - 30) * value) / 100)
  785. draw.rectangle((30, 0, bar_length+30, LCD.INDICATE_HEIGHT), fill=(124, 252, 0))
  786. msgparam = LCDItem(image, pos=LCDItem.INDICATE)
  787. self.sendMessage(Message(self, Message.DISPLAY, msgparam))
  788. return
  789. def receiveMessage(self, message):
  790. if message.getMessage() == Message.MENUOFF:
  791. pass
  792. # self.messageq.put(Message(None, Message.INDICATOR, None))
  793. elif message.getMessage() == Message.INDICATOR:
  794. self.messageq.put(message)
  795. elif message.getMessage() == Message.MPD:
  796. self.status = message.getParam()['status']
  797. self.messageq.put(Message(None, Message.INDICATOR, None))
  798. elif message.getMessage() == Message.VOLUME:
  799. self.messageq.put(message)
  800. elif message.getMessage() == Message.TERM:
  801. self.term = True
  802. return True
  803. def run(self):
  804. while self.term == False:
  805. try:
  806. message = self.messageq.get_nowait()
  807. if message.getMessage() == Message.VOLUME:
  808. param = message.getParam()
  809. if param['type'] == 'volume':
  810. self.volumeBar(int(param['value']))
  811. if self.timer.getActive():
  812. self.timer.setRemaining(5)
  813. else:
  814. self.timer = SDTimer(5, self.refresh)
  815. self.timer.start()
  816. elif param['type'] == 'mute':
  817. self.mute = param['value']
  818. self.refresh()
  819. elif message.getMessage() == Message.INDICATOR:
  820. self.refresh()
  821. except queue.Empty:
  822. sleep(0.05)
  823. if self.timer.getActive():
  824. self.timer.cancel()
  825. return
  826. # MPD Client worker
  827. class MPC(Worker, threading.Thread):
  828. term = False
  829. def __init__(self, pm):
  830. threading.Thread.__init__(self)
  831. Worker.__init__(self, pm)
  832. self.messageq = queue.Queue()
  833. self.semaphore = BoundedSemaphore()
  834. def receiveMessage(self, message):
  835. if message.getMessage() == Message.RCONTROL:
  836. param = {'command': message.getParam(), 'arg': None }
  837. self.messageq.put(param)
  838. elif message.getMessage() == Message.MPC:
  839. self.messageq.put(message.getParam())
  840. elif message.getMessage() == Message.TERM:
  841. self.term = True
  842. return True
  843. def execute(self, param):
  844. self.semaphore.acquire()
  845. try:
  846. mpc = MPDClient(use_unicode=True)
  847. mpc.connect(MPD_HOST, MPD_PORT)
  848. except Exception as e:
  849. print(e, file=self.playermain.out)
  850. self.semaphore.release()
  851. return
  852. try:
  853. if param['command'] == 'playall':
  854. mpc.stop()
  855. mpc.clear()
  856. songs = mpc.list('file')
  857. for song in songs:
  858. mpc.add(song)
  859. mpc.play()
  860. elif param['command'] == 'random':
  861. if param['arg'] == 'on':
  862. mpc.random(1)
  863. else:
  864. mpc.random(0)
  865. elif param['command'] == 'repeat':
  866. if param['arg'] == 'on':
  867. mpc.repeat(1)
  868. else:
  869. mpc.repeat(0)
  870. elif param['command'] == 'update':
  871. image = Image.new('RGB', (LCD.WIDTH, LCD.HEIGHT), 0)
  872. font = ImageFont.truetype(DEFAULT_FONT, 14, encoding="unic");
  873. draw = ImageDraw.Draw(image)
  874. draw.text((0,64), 'DB更新中....', font=font, fill='#FF0000')
  875. draw.text((0,80), 'お待ち下さい', font=font, fill='#FF0000')
  876. item = LCDItem(image)
  877. self.playermain.lcd.displayItem(item, lock=True)
  878. jobno = mpc.update()
  879. while 'updating_db' in mpc.status():
  880. if mpc.status()['updating_db'] != jobno:
  881. break
  882. sleep(0.1)
  883. self.playermain.lcd.release()
  884. elif param['command'] == 'load':
  885. mpc.stop()
  886. mpc.clear()
  887. mpc.load(param['arg'])
  888. mpc.play()
  889. elif param['command'] == 'list':
  890. list = []
  891. if param['arg'] == 'playlists':
  892. playlists = mpc.listplaylists()
  893. for p in playlists:
  894. list.append(p['playlist'])
  895. elif param['arg'] == 'artist':
  896. list = mpc.list('artist')
  897. elif param['arg'] == 'album':
  898. list = mpc.list('album')
  899. self.sendMessage(Message(self, Message.MPC_REPLY, { 'type': param['arg'], 'list': list }))
  900. elif param['command'] == 'findadd':
  901. mpc.stop()
  902. mpc.clear()
  903. mpc.findadd(param['arg']['type'], param['arg']['value'])
  904. mpc.play()
  905. elif param['command'] == 'delete':
  906. status = mpc.status()
  907. if 'songid' in status:
  908. mpc.deleteid(status['songid'])
  909. elif param['command'] == 'save':
  910. mpc.save(param['arg'])
  911. elif param['command'] == 'play':
  912. mpc.play()
  913. elif param['command'] == 'stop':
  914. mpc.stop()
  915. elif param['command'] == 'pause':
  916. mpc.pause()
  917. elif param['command'] == 'clear':
  918. mpc.clear()
  919. elif param['command'] == 'next':
  920. mpc.next()
  921. elif param['command'] == 'prev':
  922. mpc.previous()
  923. #
  924. except Exception as e:
  925. print(e, file=self.playermain.out)
  926. mpc.close()
  927. self.semaphore.release()
  928. return
  929. def run(self):
  930. while self.term == False:
  931. try:
  932. param = self.messageq.get_nowait()
  933. self.execute(param)
  934. except queue.Empty:
  935. sleep(0.05)
  936. return
  937. # lirc remote control worker
  938. class Remote(Worker, threading.Thread):
  939. def __init__(self, pm):
  940. threading.Thread.__init__(self)
  941. Worker.__init__(self, pm)
  942. lircrc = None
  943. for f in LIRCRC:
  944. if os.path.exists(f):
  945. lircrc = f
  946. break
  947. if lircrc is None:
  948. raise Exception('lircrc not found')
  949. self.sockid = lirc.init("superdac", lircrc, blocking=False)
  950. self.term = False
  951. def receiveMessage(self, message):
  952. if message.getMessage() == Message.TERM:
  953. self.term = True
  954. return True
  955. def run(self):
  956. while self.term == False:
  957. code = lirc.nextcode()
  958. if len(code) == 0:
  959. sleep(0.1)
  960. continue
  961. message = Message(self, Message.RCONTROL, code[0])
  962. self.sendMessage(message)
  963. lirc.deinit()
  964. return
  965. # デーモンの終了フラグ
  966. Terminate = False
  967. noDaemon = False
  968. class PlayerMain(Master, threading.Thread):
  969. global Terminate
  970. global noDaemon
  971. workers = [] # type: Worker[]
  972. out = None
  973. def __init__(self, err):
  974. super().__init__()
  975. self.out = err
  976. self.messageq = queue.Queue()
  977. # Message Queuing
  978. def postMessage(self, message):
  979. if noDaemon:
  980. print(message.getMessage() , file=self.out)
  981. print(message.getParam() , file=self.out)
  982. self.messageq.put(message)
  983. return
  984. def run(self):
  985. #
  986. # MPD status watcher
  987. mi = None
  988. try:
  989. mi = MPD(self)
  990. except Exception as e:
  991. # MPDが動いていない
  992. print(e, file=self.out)
  993. return
  994. mi.setDaemon(True)
  995. mi.start()
  996. self.workers.append(mi)
  997. # LCD
  998. self.lcd = LCD(self)
  999. self.lcd.setDaemon(True)
  1000. self.lcd.start()
  1001. self.workers.append(self.lcd)
  1002. # AlbumArt
  1003. aa = AlbumArt(self)
  1004. aa.setDaemon(True)
  1005. aa.start()
  1006. self.workers.append(aa)
  1007. # リモコン
  1008. try:
  1009. remocon = Remote(self)
  1010. remocon.setDaemon(True)
  1011. remocon.start()
  1012. self.workers.append(remocon)
  1013. except Exception as e:
  1014. print(e, file=self.out)
  1015. # VolumeControl
  1016. vo = VolumeControl(self)
  1017. self.workers.append(vo)
  1018. # MPC
  1019. self.mpc = MPC(self)
  1020. self.mpc.setDaemon(True)
  1021. self.mpc.start()
  1022. self.workers.append(self.mpc)
  1023. # Indicator
  1024. id = Indicator(self)
  1025. id.setDaemon(True)
  1026. id.start()
  1027. self.workers.append(id)
  1028. # Switches
  1029. sw = Switches(self)
  1030. self.workers.append(sw)
  1031. # Playlist
  1032. ps = Playlist(self)
  1033. self.workers.append(ps)
  1034. # MainMenu
  1035. mm = MainMenu(self)
  1036. self.workers.append(mm)
  1037. #
  1038. # イベントループ
  1039. #
  1040. while Terminate == False:
  1041. try:
  1042. msg = self.messageq.get_nowait()
  1043. # イベント送出
  1044. for w in self.workers:
  1045. if type(msg.getSender()) != type(w):
  1046. if w.receiveMessage(msg) == False:
  1047. break
  1048. except queue.Empty:
  1049. sleep(0.05)
  1050. # 終了
  1051. for w in self.workers:
  1052. w.receiveMessage(Message(self, Message.TERM, None))
  1053. self.out.close()
  1054. return
  1055. def main_loop():
  1056. global noDaemon
  1057. logfile = sys.stdout
  1058. if not noDaemon:
  1059. try:
  1060. logfile = open(LOGFILE, 'w')
  1061. except Exception as e:
  1062. logfile = sys.stdout
  1063. p = PlayerMain(logfile)
  1064. p.setDaemon(False)
  1065. p.start()
  1066. p.join(None)
  1067. def signal_handler(signal, handler):
  1068. global Terminate
  1069. global noDaemon
  1070. Terminate = True
  1071. if not noDaemon:
  1072. os.remove(PIDFILE)
  1073. def daemonize():
  1074. pid = os.fork()
  1075. if pid > 0:
  1076. pidf = open(PIDFILE, 'w')
  1077. pidf.write(str(pid)+"\n")
  1078. pidf.close()
  1079. sys.exit()
  1080. if pid == 0:
  1081. signal.signal(signal.SIGINT, signal_handler)
  1082. signal.signal(signal.SIGTERM, signal_handler)
  1083. main_loop()
  1084. # Script starts here
  1085. if __name__ == "__main__":
  1086. argv = sys.argv
  1087. if len(argv) > 1:
  1088. if argv[1] == '--nodaemon':
  1089. noDaemon = True
  1090. signal.signal(signal.SIGINT, signal_handler)
  1091. signal.signal(signal.SIGTERM, signal_handler)
  1092. main_loop()
  1093. else:
  1094. daemonize()