class Audio(object): """ Note: Title attribute will never be blank """ def __init__(self, uri, **kwargs): self.type = kwargs['type'] if 'type' in kwargs else UNKNOWN self.artist = kwargs['artist'] if 'artist' in kwargs else u'' self.title = kwargs['title'] if 'title' in kwargs else u'' self.album = kwargs['album'] if 'album' in kwargs else u'' self.track = int(kwargs['track']) if 'track' in kwargs else -1 self.length = int(kwargs['length']) if 'length' in kwargs else -1 self.year = kwargs['year'] if 'year' in kwargs else u'' if self.type == UNKNOWN: self.type = uri_type(uri) self.uri = unicode(uri) if self.is_file(): if self.uri.find(os.sep): self.uri = unicode(os.path.abspath(self.uri)) if not kwargs: # analyze the track ourselves if no info was given info = audio_info(self.uri) if info is None: raise Exception("Not an audio file") self.update(**info) elif self.is_stream(): if not self.title: self.title = "Stream: %s" % self.uri self.player = None def update(self, **kwargs): if 'length' in kwargs: self.length = kwargs['length'] if 'track' in kwargs: self.track = kwargs['track'] if 'year' in kwargs: self.year = kwargs['year'] if 'artist' in kwargs: self.artist = kwargs['artist'] if 'title' in kwargs: self.title = kwargs['title'] if 'album' in kwargs: self.album = kwargs['album'] # FIXME: the on_finish function should only # be part of the player until the audio # file is done playing, and not remain # on the player (as it does now) def play(self, player=None, rate=None, on_finish=None): if self.player is not None: self.stop() if player is None: from pyap.player import Player self.player = Player(on_finish=on_finish) else: self.player = player if on_finish is not None: self.player.connect('audio_finished', on_finish) def cleanup_player(audio): audio.stop() self.player.connect('audio_finished', cleanup_player) self.player.play(self, rate=rate) def pause(self): if self.player is not None: self.player.pause() def resume(self): if self.player is not None: self.player.resume() def stop(self): if self.player is not None: self.player.stop() self.player = None def is_playing(self): if self.player is None: return False return self.player.is_playing() def is_streaming(self): if self.player is None: return False return self.player.is_streaming() def is_paused(self): if self.player is None: return False return self.player.is_paused() def is_stopped(self): if self.player is None: return True return self.player.is_stopped() def is_file(self): return self.type == FILE def is_stream(self): return self.type == STREAM # TODO: handle case where band name begins with "The" or other irrelevant words def __cmp__(self, audio): if self.artist and audio.artist: if self.artist == audio.artist: if self.album and audio.album: if self.album == audio.album: if self.track != -1 and audio.track != -1: return cmp(self.track, audio.track) else: return cmp(self.title, audio.title) else: if self.year and audio.year: return cmp(self.year, audio.year) else: return cmp(self.album, audio.album) else: return cmp(self.title, audio.title) else: return cmp(self.artist, audio.artist) return cmp(self.title, audio.title) def __str__(self): if self.artist: return u"%s - %s" % (self.artist, self.title) return self.title def __repr__(self): id = "" if hasattr(self, 'id'): id = str(self.id) return u"<Audio('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')>" % ( id, "Stream" if self.is_stream() else "File", self.uri, self.artist, self.title, self.album, str(self.track), str(self.length), self.year )
class Muuse(Window): def __init__(self): Window.__init__(self) self.player = Player() self.player.connect('audio_ended', self.on_audio_end) self.library = Library() self.playlists = {} audio_list = self.library.all_audio() self.playlists['library'] = Playlist('Library', audio_list) # we want to see the library to start with self.set_playlist_view('library') self.show() def set_playlist_view(self, playlist): self._playlist_view = playlist self.listbox.clear() self.extend_listbox(self.playlists[playlist]) # in case the repeat state was changed in a previous playlist, # make sure the button matches the state for this playlist self.set_repeat(self.playlists[playlist].get_repeat()) # same for shuffle self.set_shuffle(self.playlists[playlist].is_shuffling()) # only the library playlist is persistent if playlist == 'library': #self.playlist_listbox.hide() #self.library_listbox.show() # (re)enable all buttons affecting library self.add_file_btn.set_sensitive(True) self.add_folder_btn.set_sensitive(True) self.rem_sel_btn.set_sensitive(True) self.rem_all_btn.set_sensitive(True) self.sel_inverse_btn.set_sensitive(True) self.sel_all_btn.set_sensitive(True) self.sel_none_btn.set_sensitive(True) self.list_load_btn.set_sensitive(True) self.list_save_btn.set_sensitive(True) else: #self.library_listbox.hide() #self.playlist_listbox.show() # disable all buttons that affect the library self.add_file_btn.set_sensitive(False) self.add_folder_btn.set_sensitive(False) self.rem_sel_btn.set_sensitive(False) self.rem_all_btn.set_sensitive(False) self.sel_inverse_btn.set_sensitive(False) self.sel_all_btn.set_sensitive(False) self.sel_none_btn.set_sensitive(False) self.list_load_btn.set_sensitive(False) self.list_save_btn.set_sensitive(False) def playlist_view(self): return self._playlist_view def current_playlist(self): return self.playlists[self.playlist_view()] def extend_listbox(self, audio_list): text_list = [str(audio) for audio in audio_list] self.listbox.extend(text_list) def extend_library(self, audio_list): self.playlists['library'].extend(audio_list) self.library.add_audio(audio_list) if self.playlist_view() == 'library': self.extend_listbox(audio_list) def set_repeat(self, state): self.current_playlist().set_repeat(state) if state == playlist.REPEAT_ALL: self.repeat_btn.set_image(get_icon('repeat_all')) elif state == playlist.REPEAT_ONE: self.repeat_btn.set_image(get_icon('repeat_one')) elif state == playlist.REPEAT_OFF: self.repeat_btn.set_image(get_icon('repeat_off')) def set_shuffle(self, shuffling): self.current_playlist().set_shuffle(shuffling) if shuffling: self.shuffle_btn.set_image(get_icon('shuffle_on')) else: self.shuffle_btn.set_image(get_icon('shuffle_off')) def set_volume(self, volume): self.player.set_volume(volume) if volume >= 0.7: self.volume_btn.set_image(get_icon('volume_max')) elif volume >= 0.3: self.volume_btn.set_image(get_icon('volume_mid')) else: self.volume_btn.set_image(get_icon('volume_min')) def pause(self): self.player.pause() self.play_btn.set_image(get_icon('play')) self.status_icon.set_tooltip(u'Paused: %s' % self.player.current_audio()) def resume(self): self.player.resume() self.play_btn.set_image(get_icon('pause')) self.status_icon.set_tooltip(u'Playing: %s' % self.player.current_audio()) def play(self, audio, focus=False): self.player.stop() index = self.current_playlist().current_index self.listbox.select_and_scroll(index, focus) self.audio_label.set_text(str(audio)) self.status_icon.set_tooltip(u'Playing: %s' % audio) update_progress = self.progress_updater() gobject.timeout_add(250, update_progress.next) self.player.play(audio) self.play_btn.set_image(get_icon('pause')) def stop(self): self.player.stop() self.audio_label.set_text('') self.status_icon.set_tooltip('Muuse') self.play_btn.set_image(get_icon('play')) self.audio_slider.set_fraction(0) self.audio_slider.set_text('') def progress_updater(self): position = self.player.position() duration = self.player.audio_duration() while position < duration: pos_fmt = format_time(position) dur_fmt = format_time(duration) self.audio_slider.set_fraction(position * 1.0 / duration) self.audio_slider.set_text("%s / %s" % (pos_fmt, dur_fmt)) position = self.player.position() duration = self.player.audio_duration() yield True yield False def on_window_close(self, *arg, **kwargs): gtk.main_quit() def on_icon_click(self, *args, **kwargs): if self.window.props.visible: self.window.hide() else: self.window.show() def on_icon_right_click(self, *args, **kwargs): if 'data' in kwargs: menu.popup(self.menu, None, None, 3, args[2]) def on_show_click(self, *args, **kwargs): if self.window.props.visible: self.window.hide() else: self.window.show() def on_quit_click(self, *args, **kwargs): gtk.main_quit() def on_audio_end(self, audio): next_audio = self.current_playlist().next() if next_audio: self.play(next_audio, focus=True) def on_slider_click(self, widget, event, **kwargs): audio_duration = self.player.audio_duration() if audio_duration: slider_width = self.audio_slider.get_allocation()[2] seek_time = event.x / slider_width * audio_duration self.player.set_position(seek_time) def on_play_click(self, *args, **kwargs): if self.player.is_playing(): self.pause() elif self.player.is_paused(): self.resume() else: # the user manually selected a song to play index = self.listbox.get_selected_row() playlist = self.current_playlist() playlist.current_index = index self.play(playlist[index], focus=False) def on_stop_click(self, *args, **kwargs): self.stop() def on_previous_click(self, *args, **kwargs): audio = self.current_playlist().previous() if audio: self.play(audio) def on_next_click(self, *args, **kwargs): audio = self.current_playlist().next() if audio: self.play(audio) def on_repeat_click(self, *args, **kwargs): # Order by click: NO_REPEAT, REPEAT_ONE, REPEAT_ALL if self.current_playlist().is_repeating_one(): self.set_repeat(playlist.REPEAT_ALL) elif self.current_playlist().is_repeating_all(): self.set_repeat(playlist.REPEAT_OFF) else: self.set_repeat(playlist.REPEAT_ONE) def on_shuffle_click(self, *args, **kwargs): self.set_shuffle(not self.current_playlist().is_shuffling()) def on_volume_change(self, widget, value): self.set_volume(value) def on_audio_click(self, widget, path, *args, **kwargs): index = path[0] playlist = self.current_playlist() playlist.current_index = index self.play(playlist[index], focus=False) # TODO: handle playlist files def on_add_file_click(self, *args, **kwargs): dialog = gtk.FileChooserDialog("Select File(s)", None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_select_multiple(True) filter = gtk.FileFilter() filter.set_name('Audio') for ext_items in extensions['audio'].items(): for ext in ext_items: filter.add_pattern('*.%s' % ext) dialog.add_filter(filter) response = dialog.run() if response == gtk.RESPONSE_OK: uris = dialog.get_filenames() audio_list = [Audio(uri) for uri in uris] self.extend_library(audio_list) dialog.destroy() def on_add_folder_click(self, widget, data=None): dialog = gtk.FileChooserDialog("Select Folder(s)", None, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) dialog.set_select_multiple(True) response = dialog.run() if response == gtk.RESPONSE_OK: uris = [] folders = dialog.get_filenames() for folder in folders: for root, dirs, files in os.walk(folder): for file in files: ext = os.path.splitext(file)[1].replace('.', '') if util.is_audio(ext): uri = os.path.join(root, file) uris.append(uri) audio_list = [Audio(uri) for uri in uris] self.extend_library(audio_list) dialog.destroy() # TODO def on_remove_selected_click(self, *args, **kwargs): pass # TODO def on_remove_all_click(self, *args, **kwargs): pass def on_select_inverse_click(self, *args, **kwargs): self.listbox.select_inverse() def on_select_all_click(self, *args, **kwargs): self.listbox.select_all() def on_select_none_click(self, *args, **kwargs): self.listbox.unselect_all() def on_list_save_click(self, *args, **kwargs): dialog = gtk.FileChooserDialog("Save Playlist", None, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) response = dialog.run() if response == gtk.RESPONSE_OK: uri = dialog.get_filename() # FIXME: it's not that simple self.current_playlist().export('m3u', uri) dialog.destroy() def on_list_load_click(self, *args, **kwargs): dialog = gtk.FileChooserDialog("Load Playlist", None, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) filter = gtk.FileFilter() filter.set_name('Playlists') for ext_items in extensions['playlist'].items(): for ext in ext_items: filter.add_pattern('*.%s' % ext) dialog.add_filter(filter) response = dialog.run() if response == gtk.RESPONSE_OK: uri = dialog.get_filename() playlist = import_playlist(uri) self.playlists[str(playlist)] = playlist dialog.destroy() def main(self): gtk.main()