class Builders(object): def __init__(self): self.conf = Configs() if 'RESOURCEPATH' in os.environ: self.asset_dir = '{}/assets'.format(os.environ['RESOURCEPATH']) else: self.dir_name = os.path.dirname( os.path.dirname(os.path.abspath(__file__))) self.asset_dir = os.path.join(self.dir_name, 'assets') pass def button_builder(self, parent, label, name): button = wx.Button(parent, label=label, name=name) button.SetForegroundColour(self.conf.get_attr("TEXT_COLOR")) button.SetBackgroundColour(self.conf.get_attr("BACKGROUND_COLOR")) return button def input_builder(self, parent, name, size=(250, 20), value=""): input_field = wx.TextCtrl(parent, size=size, name=name, value=value) input_field.SetBackgroundColour(self.conf.get_attr("BACKGROUND_COLOR")) input_field.SetForegroundColour(self.conf.get_attr("TEXT_COLOR")) return input_field def static_text_builder(self, parent, label): text = wx.StaticText(parent, label=label) text.SetForegroundColour(self.conf.get_attr("TEXT_COLOR")) return text def build_bitmap_button(self, btn_dict): handler = btn_dict['handler'] name = btn_dict['name'] parent = btn_dict['parent'] size_h = btn_dict['size_h'] size_w = btn_dict['size_w'] img = wx.Image(os.path.join(self.asset_dir, btn_dict['bitmap']), wx.BITMAP_TYPE_PNG) img = img.Scale(size_w, size_h, wx.IMAGE_QUALITY_HIGH) img = wx.Bitmap(img) btn = buttons.GenBitmapButton(parent=parent, bitmap=img, name=name) btn.Bind(wx.EVT_BUTTON, handler) return btn def build_playlist_cover(self, playlist_dict): cover_name = playlist_dict['cover'] cover_parent = playlist_dict['parent'] cover = Animation('{}/{}.gif'.format(self.asset_dir, cover_name)) cover_ctrl = AnimationCtrl(cover_parent, -1, cover, name=cover_name) return cover_ctrl
class Login(object): def __init__(self, parent): self.builders = Builders() self.conf = Configs() self.parent = parent self.api = YandexAPI() self.validation_error = wx.StaticText() self.sizer = wx.BoxSizer() self.dialog = wx.Dialog() self.main_pnl = self.parent.panel pass def create_login_popup(self): self.dialog = dialog = wx.Dialog(self.parent, wx.ID_ANY, "", style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, name="login_popup") self.sizer = sizer = wx.BoxSizer(wx.VERTICAL) dialog.BackgroundColour = self.conf.get_attr("BACKGROUND_COLOR") text = self.builders.static_text_builder(dialog, label="Please login") font = text.GetFont() font.PointSize += 10 font = font.Bold() text.SetFont(font) login_label = self.builders.static_text_builder(dialog, label="Login:"******"login_input") password_label = self.builders.static_text_builder(dialog, label="Password:"******"password_input") login_button = self.builders.button_builder(dialog, "Login", "login_button") login_button.Bind(wx.EVT_BUTTON, self.on_login) self.validation_error = self.builders.static_text_builder(dialog, label="") self.validation_error.SetForegroundColour(wx.RED) sizer.Add(text, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.LEFT | wx.RIGHT | wx.TOP, 20) sizer.Add(login_label, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.LEFT | wx.RIGHT | wx.TOP, 5) sizer.Add(login_input, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.LEFT | wx.RIGHT | wx.TOP, 10) sizer.Add(password_label, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.LEFT | wx.RIGHT | wx.TOP, 5) sizer.Add(password_input, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.LEFT | wx.RIGHT | wx.TOP, 10) sizer.Add(login_button, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.LEFT | wx.RIGHT | wx.TOP, 10) self.sizer.Add(self.validation_error, 1, wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, 5) dialog.SetSizer(sizer) dialog.Bind(wx.EVT_CLOSE, self.on_popup_close) dialog.Center() dialog.Show() def on_popup_close(self, event): if self.api.is_logged_in() is True: event.Skip(True) else: self.parent.on_exit(event) def on_login(self, event): login_button = self.parent.FindWindowByName("login_button") login_button.Disable() login = self.parent.FindWindowByName("login_input").GetValue() password = self.parent.FindWindowByName("password_input").GetValue() try: self.api.login(login=login, password=password) popup = self.parent.FindWindowByName("login_popup") popup.Destroy() notify(subtitle="Hello " + self.api.get_display_name()) self.parent.playlist_selection.Enable(True) self.parent.make_menu() except BadRequest as e: self.validation_error.SetLabel(str(e)) size = self.dialog.GetSize() self.dialog.SetInitialSize() self.dialog.SetSize(size) login_button.Enable() event.Skip() def on_logout_menu(self, event): self.api.logout() self.create_login_popup() self.parent.make_menu()
class Window(wx.Frame): def __init__(self, *args, **kw): # ensure the parent"s __init__ is called super(Window, self).__init__(*args, **kw) self.conf = Configs() self.api = YandexAPI() self.builders = builders.Builders() self.panel = main_panel.MainPanel(self) self.login = login.Login(self) self.SetTitle(self.conf.get_attr("APP_TITLE")) self.main_pnl = self.panel.make_main_panel() self.input = wx.TextCtrl() self.gauge = self.panel.playback_slider self.playlists_list = None self.account_menu = None self.playlist_selection = None self.playlists = None self.player = Player(parent=self.panel, slider=self.gauge) self.Bind(events.FIRST_TRACK_APPEAR, self.on_first_track) self.Bind(events.PLAYLIST_READY, self.on_playlist_ready) self.gauge.Bind(wx.EVT_SLIDER, self.player.on_seek) self.make_menu() self.Center() self.Show() if not self.api.is_logged_in() and wx.FindWindowByName( "login_popup") is None: self.login.create_login_popup() def make_menu(self): self.account_menu = wx.Menu() if self.api.is_logged_in(): logout = self.account_menu.Append(1, "&Logout\tCtrl-L", "Logout from account") self.Bind(wx.EVT_MENU, self.login.on_logout_menu, logout) player_menu = wx.Menu() self.playlists_list = wx.Menu(wx.ID_ANY) self.playlist_selection = player_menu.Append(wx.ID_ANY, "Playlists", self.playlists_list) if self.api.is_logged_in(): self.playlists = self.api.get_play_lists_list() for playlist in self.playlists: self.Bind( wx.EVT_MENU, self.on_list_select, self.playlists_list.Append(wx.ID_ANY, playlist['name'])) pass else: self.playlist_selection.Enable(False) pass help_menu = wx.Menu() about_item = help_menu.Append(wx.ID_ABOUT) menu_bar = wx.MenuBar() menu_bar.Append(self.account_menu, "Account") menu_bar.Append(player_menu, "Player") menu_bar.Append(help_menu, "Help") self.SetMenuBar(menu_bar) self.Bind(wx.EVT_MENU, self.on_about, about_item) def on_first_track(self, event): playlist_type = event.playlist_type self.gauge.Enable() self.panel.enable_play_button() self.player.load_playlist(playlist_type) pass def on_playlist_ready(self, event): playlist_name = event.playlist_name playlist_type = event.playlist_type self.player.load_playlist(playlist_type) notify(playlist_name, "Playlist is ready", "") pass def on_prev(self, event): self.player.on_prev(event) pass def on_next(self, event): self.player.on_next(event) pass def on_play(self, event): if not event.GetIsDown(): self.on_pause(event) return self.player.on_play(event) pass def on_pause(self, event): self.player.on_pause(event) pass def on_list_select(self, event): playlist_label = event.GetEventObject().GetLabelText(event.GetId()) playlist_type = None for playlist in self.playlists: if playlist['name'] == playlist_label: playlist_type = playlist['type'] self.api.preparation(playlist_type, self) def on_exit(self, event): self.Close() def on_about(self, event): wx.MessageBox("This is a wxPython Hello World sample", "About Hello World 2", wx.OK | wx.ICON_ASTERISK)
class YandexAPI(object): def __init__(self): self.conf = Configs() self.client = self.login() self.win = None self.list_type = None self.playlists_list = None self.updating_thread = None if 'RESOURCEPATH' in os.environ: self.cache = '{}/cache'.format(os.environ['RESOURCEPATH']) else: self.dirName = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) self.cache = os.path.join(self.dirName, 'cache') pass def __new__(cls, *args, **kw): if not hasattr(cls, '_instance'): orig = super(YandexAPI, cls) cls._instance = orig.__new__(cls) return cls._instance def login(self, login=None, password=None): if self.conf.get_attr("token") is not False: client = Client().from_token(self.conf.get_attr("token")) elif login is not None and password is not None: client = Client().from_credentials(login, password) token = client.token self.conf.set_attr("token", token) else: client = Client() self.client = client return client def is_logged_in(self): if self.client.account.display_name is None: return False else: return True def logout(self): self.conf.remove_attr("token") self.client = Client() pass def get_display_name(self): return str(self.login().account.display_name) def get_play_lists_list(self): entities = self.client.landing(blocks="personalplaylists").blocks[0].entities lists = [] for playlist in entities: lists.append({ "name": playlist.data.data.title, "type": playlist.data.type }) self.playlists_list = lists return lists def preparation(self, list_type, win): self.updating_thread = threading.Thread(target=self.update) self.list_type = list_type self.win = win index = { "date": date.today().__str__(), "last_track_num": 1, "tracks": [] } if not os.path.exists('{}/{}/'.format(self.cache, list_type)): os.mkdir('cache/{}'.format(list_type)) if not os.path.exists('{}/{}/index.json'.format(self.cache, list_type)): with open('{}/{}/index.json'.format(self.cache, list_type), 'w+') as file: json.dump(index, file, indent=4) self.updating_thread.start() else: if self.is_need_update(): with open('{}/{}/index.json'.format(self.cache, list_type), 'w+') as file: json.dump(index, file, indent=4) self.updating_thread.start() else: wx.PostEvent(self.win, events.FirstTrackAppear(playlist_type=list_type)) playlist_title = "" for playlist in self.playlists_list: if playlist['type'] == list_type: playlist_title = playlist['name'] wx.PostEvent(self.win, events.PlaylistReady(playlist_name=playlist_title, playlist_type=list_type)) return True def is_need_update(self): list_type = self.list_type with open('{}/{}/index.json'.format(self.cache, list_type), 'r') as file: index_date = datetime.strptime(json.load(file)['date'], '%Y-%m-%d').date() if index_date == date.today(): return False else: return True def update(self): print("Starting update") list_type = self.list_type blocks = self.client.landing(blocks="personalplaylists").blocks[0].entities playlist = "" print("processing blocks") for block in blocks: if block.data.type == list_type: playlist = block.data.data tracks = self.client.users_playlists(playlist.kind, playlist.owner.uid)[0].tracks index_file = json.load(open('{}/{}/index.json'.format(self.cache, list_type), 'r')) index = 1 print("processing tracks") for track in tracks: if index == 2: wx.PostEvent(self.win, events.FirstTrackAppear(playlist_name=playlist.title, playlist_type=list_type)) full_track_info = track.track index_file['tracks'].append({ "id": full_track_info.id, "title": full_track_info.title, "artist": full_track_info.artists[0]['name'], "duration": full_track_info.duration_ms, "num": index }) with open('{}/{}/index.json'.format(self.cache, list_type), 'w+') as file: json.dump(index_file, file) track.track.download_cover('{}/{}/{}.png'.format(self.cache, list_type, index)) track.track.download('{}/{}/{}.mp3'.format(self.cache, list_type, index), codec="mp3", bitrate_in_kbps=320) if index == 3: break index = index + 1 print("finishing updating") wx.PostEvent(self.win, events.PlaylistReady(playlist_name=playlist.title, playlist_type=list_type)) return True
class MainPanel(object): def __init__(self, parent): self.conf = Configs() self.parent = parent self.api = YandexAPI() self.builders = Builders() if 'RESOURCEPATH' in os.environ: self.asset_dir = '{}/assets'.format(os.environ['RESOURCEPATH']) else: self.dir_name = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) self.asset_dir = os.path.join(self.dir_name, 'assets') pass self.main_pnl = wx.Panel(parent) self.song_band = None self.song_name = None self.playback_slider = None self.play_pause_btn = None self.next_track = None self.prev_track = None def toggle_gauge_slider(self, slider_type='slider'): if slider_type == 'gauge': control = wx.Gauge( self.main_pnl, range=20, size=(self.parent.GetSize()[0], 5), style=wx.GA_HORIZONTAL | wx.GA_SMOOTH ).Pulse() else: control = wx.Slider( self.main_pnl, size=(self.parent.GetSize()[0], 5), minValue=0, maxValue=20 ) control.Disable() return control def make_main_panel(self): self.main_pnl.SetBackgroundColour(self.conf.get_attr("BACKGROUND_COLOR")) self.playback_slider = self.toggle_gauge_slider() main_sizer = wx.BoxSizer(wx.VERTICAL) toolbar = self.build_audio_bar() main_sizer.Add( self.playback_slider, 0, wx.ALL | wx.EXPAND, 0 ) main_sizer.AddStretchSpacer(1) main_sizer.Add( self.playlist_list(), 0 ) main_sizer.AddStretchSpacer(1) main_sizer.Add( toolbar, 0 ) self.main_pnl.SetSizer(main_sizer) return self.main_pnl def build_audio_bar(self): """ Builds the audio bar controls """ audio_bar_sizer = wx.BoxSizer(wx.HORIZONTAL) self.prev_track = self.builders.build_bitmap_button({ 'bitmap': 'player_prev.png', 'handler': self.parent.on_prev, 'name': 'prev', 'parent': self.main_pnl, 'size_h': 30, 'size_w': 30 }) self.song_band = self.builders.static_text_builder(parent=self.main_pnl, label="") self.song_name = self.builders.static_text_builder(parent=self.main_pnl, label="") song_sizer = wx.BoxSizer(wx.VERTICAL) song_sizer.Add( self.song_band, 0 ) song_sizer.Add( self.song_name, 0 ) audio_bar_sizer.Add( self.prev_track, 0 ) img = wx.Image(os.path.join(self.asset_dir, "player_play.png"), wx.BITMAP_TYPE_ANY) img = img.Scale(30, 30, wx.IMAGE_QUALITY_HIGH) img = wx.Bitmap(img) self.play_pause_btn = buttons.GenBitmapToggleButton(self.main_pnl, bitmap=img, name="play") self.play_pause_btn.Enable(False) img = wx.Image(os.path.join(self.asset_dir, "player_pause.png"), wx.BITMAP_TYPE_ANY) img = img.Scale(30, 30, wx.IMAGE_QUALITY_HIGH) img = wx.Bitmap(img) self.play_pause_btn.SetBitmapSelected(img) self.play_pause_btn.SetInitialSize() self.play_pause_btn.Bind(wx.EVT_BUTTON, self.parent.on_play) audio_bar_sizer.Add( self.play_pause_btn, 0 ) self.next_track = self.builders.build_bitmap_button({ 'bitmap': 'player_next.png', 'handler': self.parent.on_next, 'name': 'next', 'parent': self.main_pnl, 'size_h': 30, 'size_w': 30 }) audio_bar_sizer.Add( self.next_track, 0 ) audio_bar_sizer.AddSpacer(5) audio_bar_sizer.Add( song_sizer, 0 ) self.next_track.Disable() self.prev_track.Disable() return audio_bar_sizer def enable_play_button(self): self.play_pause_btn.Enable(True) def playlist_list(self): playlists_sizer = wx.BoxSizer(wx.HORIZONTAL) playlists_sizer.AddSpacer(5) playlists = [ { "cover": "playlistOfTheDay", "parent": self.main_pnl }, { "cover": "neverHeard", "parent": self.main_pnl }, { "cover": "missedLikes", "parent": self.main_pnl }, { "cover": "recentTracks", "parent": self.main_pnl } ] for playlist in playlists: item = self.builders.build_playlist_cover(playlist) playlists_sizer.Add( item, 0 ) playlists_sizer.AddSpacer(5) item.Bind(wx.EVT_ENTER_WINDOW, self.on_hover) item.Bind(wx.EVT_LEAVE_WINDOW, self.on_unhover) item.Bind(wx.EVT_LEFT_DOWN, self.on_click) return playlists_sizer def on_click(self, event): playlist_type = event.EventObject.GetName() self.api.preparation(playlist_type, self.parent) def on_unhover(self, event): event.EventObject.Stop() def on_hover(self, event): event.EventObject.Play()