main_screen.py 17 KB

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