superdac.py 33 KB

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