main_screen.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. import hashlib
  2. import json
  3. import logging
  4. import os
  5. import time
  6. import urllib
  7. import urllib2
  8. from threading import Thread
  9. from base_screen import BaseScreen
  10. import mopidy.core
  11. import pygame
  12. from ..graphic_utils import ImageView, Progressbar, \
  13. ScreenObjectsManager, TextItem, TouchAndTextItem
  14. from ..graphic_utils.view_pager import ViewPager
  15. from ..input import InputManager
  16. logger = logging.getLogger(__name__)
  17. class MainScreen(BaseScreen):
  18. def __init__(self, size, base_size, manager, fonts, cache, core,
  19. background):
  20. BaseScreen.__init__(self, size, base_size, manager, fonts)
  21. self.core = core
  22. self.track = None
  23. self.previous_track = None
  24. self.next_track = None
  25. self.cache = cache
  26. self.image = None
  27. self.artists = None
  28. self.update_next_frame = True
  29. self.background = background
  30. self.update_keys = []
  31. self.current_track_pos = 0
  32. self.track_duration = "00:00"
  33. self.view_pager = ViewPager(self.size, self)
  34. self.touch_text_manager = ScreenObjectsManager()
  35. current_track = self.core.playback.current_track.get()
  36. if current_track is None:
  37. self.track_playback_ended(None, None)
  38. else:
  39. self.track_started(current_track)
  40. # Top bar
  41. self.top_bar = pygame.Surface((self.size[0], self.base_size),
  42. pygame.SRCALPHA)
  43. self.top_bar.fill((0, 0, 0, 128))
  44. # Play/pause
  45. button = TouchAndTextItem(self.fonts['icon'], u"\ue615 ",
  46. (0, 0), None)
  47. self.touch_text_manager.set_touch_object("pause_play", button)
  48. x = button.get_right_pos()
  49. # Mute
  50. button = TouchAndTextItem(self.fonts['icon'], u"\ue61f ",
  51. (x, 0), None)
  52. self.touch_text_manager.set_touch_object("mute", button)
  53. x = button.get_right_pos()
  54. # Volume
  55. progress = Progressbar(self.fonts['base'], "100", (x, 0),
  56. (self.size[0] - x, self.base_size),
  57. 100, True)
  58. self.touch_text_manager.set_touch_object("volume", progress)
  59. progress.set_value(self.core.playback.volume.get())
  60. self.progress_show = False
  61. def should_update(self):
  62. update = self.view_pager.should_update()
  63. if update is not BaseScreen.no_update:
  64. return update
  65. elif len(self.update_keys) > 0:
  66. return BaseScreen.update_partial
  67. else:
  68. if self.progress_show:
  69. track_pos_millis = self.core.playback.time_position.get()
  70. new_track_pos = track_pos_millis / 1000
  71. if new_track_pos != self.current_track_pos:
  72. return BaseScreen.update_partial
  73. else:
  74. return BaseScreen.no_update
  75. else:
  76. return BaseScreen.no_update
  77. def set_update_rects(self, rects):
  78. progress = self.update_progress()
  79. if progress is not None:
  80. self.update_keys = [("time_progress")]
  81. rects.append(progress.rect_in_pos)
  82. self.view_pager.set_update_rects(rects)
  83. def update(self, screen, update_type):
  84. if update_type == BaseScreen.update_all:
  85. screen.blit(self.top_bar, (0, 0))
  86. self.update_progress()
  87. self.touch_text_manager.render(screen)
  88. self.view_pager.render(screen, update_type)
  89. if update_type == BaseScreen.update_partial \
  90. and self.track is not None:
  91. for key in self.update_keys:
  92. object = self.touch_text_manager.get_touch_object(key)
  93. object.update()
  94. object.render(screen)
  95. self.view_pager.render(screen, update_type)
  96. def update_progress(self):
  97. if self.progress_show:
  98. track_pos_millis = self.core.playback.time_position.get()
  99. new_track_pos = track_pos_millis / 1000
  100. if new_track_pos != self.current_track_pos:
  101. progress = self.touch_text_manager.get_touch_object(
  102. "time_progress")
  103. progress.set_value(track_pos_millis)
  104. self.current_track_pos = new_track_pos
  105. progress.set_text(
  106. time.strftime('%M:%S', time.gmtime(
  107. self.current_track_pos)) +
  108. "/" + self.track_duration)
  109. return progress
  110. return None
  111. def create_objects_manager(self):
  112. touch_text_manager = ScreenObjectsManager()
  113. x = self.size[1] - self.base_size * 3
  114. width = self.size[0] - self.base_size / 2 - x
  115. # Track name
  116. label = TextItem(self.fonts['base'],
  117. "",
  118. (x, (self.size[1]-self.base_size*3)/2
  119. - self.base_size*0.5),
  120. (width, -1))
  121. touch_text_manager.set_object("track_name", label)
  122. # Album name
  123. label = TextItem(self.fonts['base'],
  124. "",
  125. (x, (self.size[1]-self.base_size*3)/2
  126. + self.base_size*0.5),
  127. (width, -1))
  128. touch_text_manager.set_object("album_name", label)
  129. # Artist
  130. label = TextItem(self.fonts['base'],
  131. "",
  132. (x, (self.size[1]-self.base_size*3)/2
  133. + self.base_size*1.5),
  134. (width, -1))
  135. touch_text_manager.set_object("artist_name", label)
  136. # Cover image
  137. pos = (self.base_size / 2, self.base_size +
  138. self.base_size / 2)
  139. size = self.size[1] - self.base_size * 4
  140. cover = ImageView(pos, (size, size))
  141. touch_text_manager.set_object("cover_image", cover)
  142. return touch_text_manager
  143. def set_page_values(self, screen_objects_manager, pos):
  144. if pos == 0:
  145. track = self.previous_track
  146. elif pos == 1:
  147. track = self.track
  148. elif pos == 2:
  149. track = self.next_track
  150. if track is None:
  151. return False
  152. else:
  153. screen_objects_manager.get_object("track_name").set_horizontal_shift(200)
  154. screen_objects_manager.get_object("track_name").set_text(MainScreen.get_track_name(track), False)
  155. screen_objects_manager.get_object("album_name").set_text(MainScreen.get_track_album_name(track), False)
  156. screen_objects_manager.get_object("artist_name").set_text(MainScreen.get_artist_string(track), False)
  157. return True
  158. def track_started(self, track):
  159. self.next_track = track
  160. self.image = None
  161. self.view_pager.notify_changed()
  162. if self.previous_track is not None and track.uri == self.previous_track.uri:
  163. self.view_pager.change_to_page(-1)
  164. image_view = self.view_pager.objets_manager[0].get_object("cover_image")
  165. else:
  166. self.view_pager.change_to_page(1)
  167. image_view = self.view_pager.objets_manager[2].get_object("cover_image")
  168. image_view.set_image(None)
  169. self.previous_track = self.track
  170. self.track = track
  171. # Previous track button
  172. button = TouchAndTextItem(self.fonts['icon'], u"\ue61c",
  173. (0, self.size[1] - self.base_size * 2), None)
  174. self.touch_text_manager.set_touch_object("previous", button)
  175. size_1 = button.get_right_pos()
  176. size_2 = self.fonts['icon'].size(u"\ue61d")[0]
  177. button = TouchAndTextItem(self.fonts['icon'], u"\ue61d",
  178. (self.size[0] - size_2,
  179. self.size[1] - self.base_size * 2),
  180. None)
  181. self.touch_text_manager.set_touch_object("next", button)
  182. if track.length:
  183. self.track_duration = time.strftime('%M:%S', time.gmtime(
  184. track.length / 1000))
  185. # Progress
  186. progress = Progressbar(self.fonts['base'],
  187. time.strftime('%M:%S', time.gmtime(
  188. 0)) + "/" + time.strftime(
  189. '%M:%S', time.gmtime(0)),
  190. (size_1, self.size[1] - self.base_size * 2),
  191. (
  192. self.size[0] - size_1 - size_2,
  193. self.base_size),
  194. track.length, False)
  195. self.touch_text_manager.set_touch_object("time_progress",
  196. progress)
  197. self.progress_show = True
  198. else:
  199. self.progress_show = False
  200. self.touch_text_manager.delete_touch_object("time_progress")
  201. # Load all artists
  202. self.artists = []
  203. for artist in track.artists:
  204. self.artists.append(artist)
  205. if not self.is_image_in_cache():
  206. thread = Thread(target=self.download_image, args=[image_view])
  207. thread.start()
  208. else:
  209. thread = Thread(target=self.load_image, args=[image_view])
  210. thread.start()
  211. def stream_title_changed(self, title):
  212. self.touch_text_manager.get_object("track_name").set_text(title, False)
  213. def get_image_file_name(self):
  214. name = MainScreen.get_track_album_name(
  215. self.track) + '-' + self.get_artist_string(self.track)
  216. md5name = hashlib.md5(name.encode('utf-8')).hexdigest()
  217. return md5name
  218. def get_cover_folder(self):
  219. if not os.path.isdir(self.cache + "/covers"):
  220. os.makedirs(self.cache + "/covers")
  221. return self.cache + "/covers/"
  222. def is_image_in_cache(self):
  223. self.get_cover_folder()
  224. return os.path.isfile(
  225. self.get_cover_folder() + self.get_image_file_name())
  226. def download_image(self, image_view):
  227. image_uris = self.core.library.get_images(
  228. {self.track.uri}).get()[self.track.uri]
  229. if len(image_uris) > 0:
  230. urllib.urlretrieve(image_uris[0].uri,
  231. self.get_cover_folder() +
  232. self.get_image_file_name())
  233. self.load_image(image_view)
  234. else:
  235. self.download_image_last_fm(0, image_view)
  236. def download_image_last_fm(self, artist_index, image_view):
  237. if artist_index < len(self.artists):
  238. try:
  239. safe_artist = urllib.quote_plus(
  240. self.artists[artist_index].name)
  241. safe_album = urllib.quote_plus(
  242. MainScreen.get_track_album_name(self.track))
  243. url = "http://ws.audioscrobbler.com/2.0/?"
  244. params = "method=album.getinfo&" + \
  245. "api_key=59a04c6a73fb99d6e8996e01db306829&" \
  246. + "artist=" \
  247. + safe_artist + "&album=" + safe_album + \
  248. "&format=json"
  249. response = urllib2.urlopen(url + params)
  250. data = json.load(response)
  251. image = data['album']['image'][-1]['#text']
  252. urllib.urlretrieve(image,
  253. self.get_cover_folder() +
  254. self.get_image_file_name())
  255. self.load_image(image_view)
  256. except:
  257. self.download_image_last_fm(artist_index + 1, image_view)
  258. else:
  259. logger.info("Cover could not be downloaded")
  260. def track_playback_ended(self, tl_track, time_position):
  261. self.background.set_background_image(None)
  262. self.image = None
  263. self.track_duration = "00:00"
  264. def load_image(self, image_view):
  265. size = self.size[1] - self.base_size * 4
  266. image_original = pygame.image.load(
  267. self.get_cover_folder() +
  268. self.get_image_file_name())
  269. image = pygame.transform.scale(image_original, (size, size))
  270. image = image.convert()
  271. image_view.set_image(image)
  272. self.background.set_background_image(image_original)
  273. def touch_event(self, event):
  274. if event.type == InputManager.click:
  275. objects = \
  276. self.touch_text_manager.get_touch_objects_in_pos(
  277. event.current_pos)
  278. if objects is not None:
  279. self.click_on_objects(objects, event)
  280. elif event.type == InputManager.swipe:
  281. if event.direction == InputManager.left:
  282. self.core.playback.next()
  283. elif event.direction == InputManager.right:
  284. self.core.playback.previous()
  285. elif event.direction == InputManager.up:
  286. volume = self.core.playback.volume.get() + 10
  287. if volume > 100:
  288. volume = 100
  289. self.core.mixer.set_volume(volume)
  290. elif event.direction == InputManager.down:
  291. volume = self.core.playback.volume.get() - 10
  292. if volume < 0:
  293. volume = 0
  294. self.core.mixer.set_volume(volume)
  295. elif event.type == InputManager.key:
  296. if event.direction == InputManager.enter:
  297. self.click_on_objects(["pause_play"], None)
  298. elif event.direction == InputManager.up:
  299. vol = self.core.mixer.get_volume().get()
  300. vol += 3
  301. if vol > 100:
  302. vol = 100
  303. self.core.mixer.set_volume(vol)
  304. elif event.direction == InputManager.down:
  305. vol = self.core.mixer.get_volume().get()
  306. vol -= 3
  307. if vol < 0:
  308. vol = 0
  309. self.core.mixer.set_volume(vol)
  310. elif event.longpress:
  311. if event.direction == InputManager.left:
  312. self.click_on_objects(["previous"], None)
  313. elif event.direction == InputManager.right:
  314. self.click_on_objects(["next"], None)
  315. def click_on_objects(self, objects, event):
  316. if objects is not None:
  317. for key in objects:
  318. if key == "time_progress":
  319. value = self.touch_text_manager.get_touch_object(
  320. key).get_pos_value(
  321. event.current_pos)
  322. self.core.playback.seek(value)
  323. elif key == "previous":
  324. self.core.playback.previous()
  325. elif key == "next":
  326. self.core.playback.next()
  327. elif key == "volume":
  328. self.change_volume(event)
  329. elif key == "pause_play":
  330. if self.core.playback.state.get() == \
  331. mopidy.core.PlaybackState.PLAYING:
  332. self.core.playback.pause()
  333. else:
  334. self.core.playback.play()
  335. elif key == "mute":
  336. mute = not self.core.playback.mute.get()
  337. self.core.playback.set_mute(mute)
  338. self.mute_changed(mute)
  339. def change_volume(self, event):
  340. manager = self.touch_text_manager
  341. volume = manager.get_touch_object("volume")
  342. pos = event.current_pos
  343. value = volume.get_pos_value(pos)
  344. self.core.mixer.set_volume(value)
  345. def playback_state_changed(self, old_state, new_state):
  346. if new_state == mopidy.core.PlaybackState.PLAYING:
  347. self.touch_text_manager.get_touch_object(
  348. "pause_play").set_text(u"\ue616", False)
  349. else:
  350. self.touch_text_manager.get_touch_object(
  351. "pause_play").set_text(u"\ue615", False)
  352. def volume_changed(self, volume):
  353. if not self.core.playback.mute.get():
  354. if volume > 80:
  355. self.touch_text_manager.get_touch_object(
  356. "mute").set_text(
  357. u"\ue61f", False)
  358. elif volume > 50:
  359. self.touch_text_manager.get_touch_object(
  360. "mute").set_text(
  361. u"\ue620", False)
  362. elif volume > 20:
  363. self.touch_text_manager.get_touch_object(
  364. "mute").set_text(
  365. u"\ue621", False)
  366. else:
  367. self.touch_text_manager.get_touch_object(
  368. "mute").set_text(
  369. u"\ue622", False)
  370. self.touch_text_manager.get_touch_object("volume").set_value(
  371. volume)
  372. def mute_changed(self, mute):
  373. self.touch_text_manager.get_touch_object("mute").set_active(
  374. not mute)
  375. if mute:
  376. self.touch_text_manager.get_touch_object("mute").set_text(
  377. u"\ue623", False)
  378. else:
  379. self.volume_changed(self.core.playback.volume.get())
  380. @staticmethod
  381. def get_track_name(track):
  382. if track is None:
  383. return ""
  384. elif track.name is None:
  385. return track.uri
  386. else:
  387. return track.name
  388. @staticmethod
  389. def get_track_album_name(track):
  390. if track is None:
  391. return ""
  392. if track.album is not None and track.album.name is not None \
  393. and len(track.album.name) > 0:
  394. return track.album.name
  395. else:
  396. return "Unknow Album"
  397. @staticmethod
  398. def get_artist_string(track):
  399. artists_string = ''
  400. if track is None:
  401. return artists_string
  402. for artist in track.artists:
  403. artists_string += artist.name + ', '
  404. if len(artists_string) > 2:
  405. artists_string = artists_string[:-2]
  406. elif len(artists_string) == 0:
  407. artists_string = "Unknow Artist"
  408. return artists_string