PageRenderTime 24ms CodeModel.GetById 39ms RepoModel.GetById 0ms app.codeStats 0ms

/mopidy/backends/local/__init__.py

https://github.com/grunskis/mopidy
Python | 250 lines | 177 code | 56 blank | 17 comment | 41 complexity | f3f703532097bec0bd11694a68007663 MD5 | raw file
  1. import glob
  2. import logging
  3. import multiprocessing
  4. import os
  5. import shutil
  6. from mopidy import settings
  7. from mopidy.backends.base import (Backend, CurrentPlaylistController,
  8. LibraryController, BaseLibraryProvider, PlaybackController,
  9. BasePlaybackProvider, StoredPlaylistsController,
  10. BaseStoredPlaylistsProvider)
  11. from mopidy.models import Playlist, Track, Album
  12. from mopidy.utils.process import pickle_connection
  13. from .translator import parse_m3u, parse_mpd_tag_cache
  14. logger = logging.getLogger(u'mopidy.backends.local')
  15. class LocalBackend(Backend):
  16. """
  17. A backend for playing music from a local music archive.
  18. **Issues:** http://github.com/mopidy/mopidy/issues/labels/backend-local
  19. **Settings:**
  20. - :attr:`mopidy.settings.LOCAL_MUSIC_PATH`
  21. - :attr:`mopidy.settings.LOCAL_PLAYLIST_PATH`
  22. - :attr:`mopidy.settings.LOCAL_TAG_CACHE_FILE`
  23. """
  24. def __init__(self, *args, **kwargs):
  25. super(LocalBackend, self).__init__(*args, **kwargs)
  26. self.current_playlist = CurrentPlaylistController(backend=self)
  27. library_provider = LocalLibraryProvider(backend=self)
  28. self.library = LibraryController(backend=self,
  29. provider=library_provider)
  30. playback_provider = LocalPlaybackProvider(backend=self)
  31. self.playback = LocalPlaybackController(backend=self,
  32. provider=playback_provider)
  33. stored_playlists_provider = LocalStoredPlaylistsProvider(backend=self)
  34. self.stored_playlists = StoredPlaylistsController(backend=self,
  35. provider=stored_playlists_provider)
  36. self.uri_handlers = [u'file://']
  37. class LocalPlaybackController(PlaybackController):
  38. def __init__(self, *args, **kwargs):
  39. super(LocalPlaybackController, self).__init__(*args, **kwargs)
  40. # XXX Why do we call stop()? Is it to set GStreamer state to 'READY'?
  41. self.stop()
  42. @property
  43. def time_position(self):
  44. return self.backend.output.get_position()
  45. class LocalPlaybackProvider(BasePlaybackProvider):
  46. def pause(self):
  47. return self.backend.output.set_state('PAUSED')
  48. def play(self, track):
  49. return self.backend.output.play_uri(track.uri)
  50. def resume(self):
  51. return self.backend.output.set_state('PLAYING')
  52. def seek(self, time_position):
  53. return self.backend.output.set_position(time_position)
  54. def stop(self):
  55. return self.backend.output.set_state('READY')
  56. class LocalStoredPlaylistsProvider(BaseStoredPlaylistsProvider):
  57. def __init__(self, *args, **kwargs):
  58. super(LocalStoredPlaylistsProvider, self).__init__(*args, **kwargs)
  59. self._folder = settings.LOCAL_PLAYLIST_PATH
  60. self.refresh()
  61. def lookup(self, uri):
  62. pass # TODO
  63. def refresh(self):
  64. playlists = []
  65. logger.info('Loading playlists from %s', self._folder)
  66. for m3u in glob.glob(os.path.join(self._folder, '*.m3u')):
  67. name = os.path.basename(m3u)[:len('.m3u')]
  68. tracks = []
  69. for uri in parse_m3u(m3u):
  70. try:
  71. tracks.append(self.backend.library.lookup(uri))
  72. except LookupError, e:
  73. logger.error('Playlist item could not be added: %s', e)
  74. playlist = Playlist(tracks=tracks, name=name)
  75. # FIXME playlist name needs better handling
  76. # FIXME tracks should come from lib. lookup
  77. playlists.append(playlist)
  78. self.playlists = playlists
  79. def create(self, name):
  80. playlist = Playlist(name=name)
  81. self.save(playlist)
  82. return playlist
  83. def delete(self, playlist):
  84. if playlist not in self._playlists:
  85. return
  86. self._playlists.remove(playlist)
  87. filename = os.path.join(self._folder, playlist.name + '.m3u')
  88. if os.path.exists(filename):
  89. os.remove(filename)
  90. def rename(self, playlist, name):
  91. if playlist not in self._playlists:
  92. return
  93. src = os.path.join(self._folder, playlist.name + '.m3u')
  94. dst = os.path.join(self._folder, name + '.m3u')
  95. renamed = playlist.copy(name=name)
  96. index = self._playlists.index(playlist)
  97. self._playlists[index] = renamed
  98. shutil.move(src, dst)
  99. def save(self, playlist):
  100. file_path = os.path.join(self._folder, playlist.name + '.m3u')
  101. # FIXME this should be a save_m3u function, not inside save
  102. with open(file_path, 'w') as file_handle:
  103. for track in playlist.tracks:
  104. if track.uri.startswith('file://'):
  105. file_handle.write(track.uri[len('file://'):] + '\n')
  106. else:
  107. file_handle.write(track.uri + '\n')
  108. self._playlists.append(playlist)
  109. class LocalLibraryProvider(BaseLibraryProvider):
  110. def __init__(self, *args, **kwargs):
  111. super(LocalLibraryProvider, self).__init__(*args, **kwargs)
  112. self._uri_mapping = {}
  113. self.refresh()
  114. def refresh(self, uri=None):
  115. tag_cache = settings.LOCAL_TAG_CACHE_FILE
  116. music_folder = settings.LOCAL_MUSIC_PATH
  117. tracks = parse_mpd_tag_cache(tag_cache, music_folder)
  118. logger.info('Loading songs in %s from %s', music_folder, tag_cache)
  119. for track in tracks:
  120. self._uri_mapping[track.uri] = track
  121. def lookup(self, uri):
  122. try:
  123. return self._uri_mapping[uri]
  124. except KeyError:
  125. raise LookupError('%s not found.' % uri)
  126. def find_exact(self, **query):
  127. self._validate_query(query)
  128. result_tracks = self._uri_mapping.values()
  129. for (field, values) in query.iteritems():
  130. if not hasattr(values, '__iter__'):
  131. values = [values]
  132. # FIXME this is bound to be slow for large libraries
  133. for value in values:
  134. q = value.strip()
  135. track_filter = lambda t: q == t.name
  136. album_filter = lambda t: q == getattr(t, 'album', Album()).name
  137. artist_filter = lambda t: filter(
  138. lambda a: q == a.name, t.artists)
  139. uri_filter = lambda t: q == t.uri
  140. any_filter = lambda t: (track_filter(t) or album_filter(t) or
  141. artist_filter(t) or uri_filter(t))
  142. if field == 'track':
  143. result_tracks = filter(track_filter, result_tracks)
  144. elif field == 'album':
  145. result_tracks = filter(album_filter, result_tracks)
  146. elif field == 'artist':
  147. result_tracks = filter(artist_filter, result_tracks)
  148. elif field == 'uri':
  149. result_tracks = filter(uri_filter, result_tracks)
  150. elif field == 'any':
  151. result_tracks = filter(any_filter, result_tracks)
  152. else:
  153. raise LookupError('Invalid lookup field: %s' % field)
  154. return Playlist(tracks=result_tracks)
  155. def search(self, **query):
  156. self._validate_query(query)
  157. result_tracks = self._uri_mapping.values()
  158. for (field, values) in query.iteritems():
  159. if not hasattr(values, '__iter__'):
  160. values = [values]
  161. # FIXME this is bound to be slow for large libraries
  162. for value in values:
  163. q = value.strip().lower()
  164. track_filter = lambda t: q in t.name.lower()
  165. album_filter = lambda t: q in getattr(
  166. t, 'album', Album()).name.lower()
  167. artist_filter = lambda t: filter(
  168. lambda a: q in a.name.lower(), t.artists)
  169. uri_filter = lambda t: q in t.uri.lower()
  170. any_filter = lambda t: track_filter(t) or album_filter(t) or \
  171. artist_filter(t) or uri_filter(t)
  172. if field == 'track':
  173. result_tracks = filter(track_filter, result_tracks)
  174. elif field == 'album':
  175. result_tracks = filter(album_filter, result_tracks)
  176. elif field == 'artist':
  177. result_tracks = filter(artist_filter, result_tracks)
  178. elif field == 'uri':
  179. result_tracks = filter(uri_filter, result_tracks)
  180. elif field == 'any':
  181. result_tracks = filter(any_filter, result_tracks)
  182. else:
  183. raise LookupError('Invalid lookup field: %s' % field)
  184. return Playlist(tracks=result_tracks)
  185. def _validate_query(self, query):
  186. for (_, values) in query.iteritems():
  187. if not values:
  188. raise LookupError('Missing query')
  189. for value in values:
  190. if not value:
  191. raise LookupError('Missing query')