superdac.py 33 KB

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