superdac.py 33 KB

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