async def render(req, **kwargs): app = req.ctx['app'] song = req.ctx['model'] view = SongExploreView(app=app) app.ui.right_panel.set_body(view) # show song album cover song = await aio.run_fn(app.library.song_upgrade, song) # bind signals view.play_btn.clicked.connect(lambda: app.player.play_song(song)) album = app.library.cast_model_to_v1(song.album) view.header_label.setText(f'<h1>{song.title}</h1>') aio.create_task(view.album_info_label.show_song(song)) cover = await aio.run_fn(lambda: album.cover) aio.create_task(view.cover_label.show_cover(cover, reverse(album))) if app.library.check_flags_by_model(song, PF.hot_comments): comments = await aio.run_fn(app.library.song_list_hot_comments, song) comments_reader = create_reader(comments) view.comments_view.setModel(CommentListModel(comments_reader)) else: view.comments_header.setText('<span>该提供方暂不支持查看歌曲评论,555</span>') song_v1 = app.library.cast_model_to_v1(song) lyric = await aio.run_fn(lambda: song_v1.lyric) if lyric and lyric.content: ms_sentence_map = parse(lyric.content) sentences = [] for _, sentence in sorted(ms_sentence_map.items(), key=lambda item: item[0]): sentences.append(sentence) view.lyric_label.set_lyric('\n'.join(sentences))
def _goto(self, page, ctx): x = self.router.dispatch(page, ctx) if inspect.iscoroutine(x): aio.create_task(x) self._last_page = self.current_page self.current_page = page
async def render(self): playlist = self.playlist # show playlist title self.meta_widget.show() self.meta_widget.title = playlist.name # show playlist song list with suppress(ProviderIOError): if playlist.meta.allow_create_songs_g: reader = wrap(playlist.create_songs_g()) else: songs = await async_run(lambda: playlist.songs) reader = wrap(songs) self.show_songs(reader=reader, show_count=True) # show playlist cover if playlist.cover: aio.create_task( self.show_cover(playlist.cover, reverse(playlist, '/cover'))) def remove_song(song): playlist.remove(song.identifier) self.songs_table.remove_song_func = remove_song self.tabbar.show_desc_needed.connect( lambda: aio.create_task(self._show_desc()))
def _show_pure_videos_coll(coll): from feeluown.gui.page_containers.table import VideosRenderer self.set_body(self.scrollarea) reader = wrap(coll.models) renderer = VideosRenderer(reader) aio.create_task(self.table_container.set_renderer(renderer))
def fetchMore(self, _=QModelIndex()): expect_len = 10 try: items = list(itertools.islice(self.reader, expect_len)) except ProviderIOError: logger.exception('fetch more items failed') return acture_len = len(items) colors = [ random.choice(list(COLORS.values())) for _ in range(0, acture_len) ] if acture_len < expect_len: self._maybe_has_more = False begin = len(self.items) self.beginInsertRows(QModelIndex(), begin, begin + acture_len - 1) self.items.extend(items) self.colors.extend(colors) self.endInsertRows() for item in items: aio.create_task( self.fetch_image(item, self._fetch_image_callback(item), uid=reverse(item) + '/cover'))
def _show_pure_videos_coll(coll): from feeluown.containers.table import VideosRenderer self.collection_container.hide() self.scrollarea.show() reader = RandomSequentialReader.from_list(coll.models) renderer = VideosRenderer(reader) aio.create_task(self.table_container.set_renderer(renderer))
async def render(req, **kwargs): """/player_playlist handler """ app = req.ctx['app'] right_panel = app.ui.right_panel right_panel.set_body(right_panel.table_container) aio.create_task(app.ui.table_container.set_renderer(PlayerPlaylistRenderer()))
def autologin(self): """Try to load user cookies and login with it Generally, you can call this method after dialog is shown. """ cookies = self.load_user_cookies() if cookies is not None: self.show_hint('正在尝试加载已有用户...', color='green') self.cookies_text_edit.setText(json.dumps(cookies, indent=2)) aio.create_task(self.login_with_cookies(cookies))
def __init__(self, app, parent=None): super().__init__(parent) self._app = app self._renderer = None self._table = None # current visible table self._tables = [] self._extra = None self.toolbar = SongsTableToolbar() self.tabbar = TableTabBarV2() self.meta_widget = TableMetaWidget(parent=self) self.songs_table = SongsTableView(parent=self) self.albums_table = AlbumListView(parent=self) self.artists_table = ArtistListView(parent=self) self.videos_table = VideoListView(parent=self) self.playlists_table = PlaylistListView(parent=self) self.comments_table = CommentListView(parent=self) self.desc_widget = DescLabel(parent=self) self._tables.append(self.songs_table) self._tables.append(self.albums_table) self._tables.append(self.artists_table) self._tables.append(self.playlists_table) self._tables.append(self.videos_table) self._tables.append(self.comments_table) self.songs_table.play_song_needed.connect( lambda song: asyncio.ensure_future(self.play_song(song))) self.videos_table.play_video_needed.connect( lambda video: aio.create_task(self.play_video(video))) def goto_model(model): self._app.browser.goto(model=model) for signal in [ self.songs_table.show_artist_needed, self.songs_table.show_album_needed, self.albums_table.show_album_needed, self.artists_table.show_artist_needed, self.playlists_table.show_playlist_needed, ]: signal.connect(goto_model) self.toolbar.play_all_needed.connect(self.play_all) self.songs_table.add_to_playlist_needed.connect( self._add_songs_to_playlist) self.songs_table.about_to_show_menu.connect( self._songs_table_about_to_show_menu) self.songs_table.activated.connect(lambda index: aio.create_task( self._on_songs_table_activated(index))) self._setup_ui()
def _goto(self, page, ctx): # Do some initialization self.ui.toolbar.clear_stacked_widget() # To keep backward compat, we add magicbox by default self.ui.toolbar.add_stacked_widget(self.ui.magicbox) x = self.router.dispatch(page, ctx) if inspect.iscoroutine(x): aio.create_task(x) self._last_page = self.current_page self.current_page = page
def _fetch_more_cb(self, items): self._is_fetching = False if items is not None and not items: self.no_more_item.emit() return items_len = len(items) colors = [random.choice(list(COLORS.values())) for _ in range(0, items_len)] self.colors.extend(colors) self.on_items_fetched(items) for item in items: aio.create_task(self.fetch_image( item, self._fetch_image_callback(item), uid=reverse(item) + '/cover'))
async def render(self): self.meta_widget.title = '当前播放列表' self.meta_widget.show() player = self._app.player playlist = player.playlist async def clear_playlist(): playlist.clear() await self.render() # re-render songs = playlist.list() self.show_songs(wrap(songs.copy())) btn = TextButton('清空', self.toolbar) btn.clicked.connect(lambda *args: aio.create_task(clear_playlist())) self.toolbar.add_tmp_button(btn) self.songs_table.remove_song_func = playlist.remove # scroll to current song current_song = self._app.playlist.current_song if current_song is not None: row = songs.index(current_song) model_index = self.songs_table.model().index(row, 0) self.songs_table.scrollTo(model_index) self.songs_table.selectRow(row)
def fetch_more_impl(self): """fetch more items from reader """ reader = self._reader step = self._fetch_more_step if reader.is_async: async def fetch(): items = [] count = 0 async for item in reader: items.append(item) count += 1 if count == step: break return items future = aio.create_task(fetch()) future.add_done_callback(self._async_fetch_cb) else: try: items = list(itertools.islice(reader, step)) except ProviderIOError: logger.exception('fetch more items failed') self._fetch_more_cb(None) else: self._fetch_more_cb(items)
async def render(self): self.meta_widget.title = '当前播放列表' self.meta_widget.show() self.container.current_table = self.songs_table self.songs_table.remove_song_func = self._app.playlist.remove source_name_map = {p.identifier: p.name for p in self._app.library.list()} model = PlaylistTableModel(source_name_map, self._app.playlist) filter_model = SongFilterProxyModel(self.songs_table) filter_model.setSourceModel(model) self.songs_table.setModel(filter_model) disconnect_slots_if_has(self._app.ui.magicbox.filter_text_changed) self._app.ui.magicbox.filter_text_changed.connect(filter_model.filter_by_text) self.toolbar.show() btn = TextButton('清空', self.toolbar) btn.clicked.connect(lambda *args: aio.create_task(self.clear_playlist())) self.toolbar.add_tmp_button(btn) # scroll to current song current_song = self._app.playlist.current_song if current_song is not None: row = self._app.playlist.list().index(current_song) model_index = self.songs_table.model().index(row, Column.song) self.songs_table.scrollTo(model_index) self.songs_table.selectRow(row)
async def render(self): playlist = self.playlist # show playlist title self.meta_widget.show() self.meta_widget.title = playlist.name await self._show_songs() # show playlist cover if playlist.cover: aio.create_task( self.show_cover(playlist.cover, reverse(playlist) + '/cover')) self.songs_table.remove_song_func = self.remove_song
def contextMenuEvent(self, e): song = self._app.playlist.current_song if song is None: return menu = QMenu() menu.hovered.connect(self.on_action_hovered) artist_menu = menu.addMenu('查看歌手') album_action = menu.addAction('查看专辑') artist_menu.menuAction().setData({'artists': None, 'song': song}) album_action.triggered.connect( lambda: aio.create_task(self._goto_album(song))) if self._app.library.check_flags_by_model(song, ProviderFlags.similar): similar_song_action = menu.addAction('相似歌曲') similar_song_action.triggered.connect( lambda: self._app.browser.goto(model=song, path='/similar')) if self._app.library.check_flags_by_model(song, ProviderFlags.hot_comments): song_comments_action = menu.addAction('歌曲评论') song_comments_action.triggered.connect( lambda: self._app.browser.goto(model=song, path='/hot_comments')) menu.exec(e.globalPos())
def contextMenuEvent(self, e): song = self._app.playlist.current_song if song is None: return menu = QMenu() menu.hovered.connect(self.on_action_hovered) artist_menu = menu.addMenu('查看歌手') album_action = menu.addAction('查看专辑') artist_menu.menuAction().setData({'artists': None, 'song': song}) album_action.setData({'song': song}) artist_menu.menuAction().triggered.connect( lambda: aio.create_task(self._goto_artists(song))) album_action.triggered.connect( lambda: aio.create_task(self._goto_album(song))) menu.exec(e.globalPos())
async def render(self): artist = self.artist # bind signal first # we only show album that implements create_albums_g if artist.meta.allow_create_albums_g: self.toolbar.filter_albums_needed.connect( lambda types: self.albums_table.model().filter_by_types(types)) self.tabbar.show_albums_needed.connect( lambda: self.show_albums(self.artist.create_albums_g())) if hasattr(artist, 'contributed_albums') and artist.contributed_albums: # show contributed_album list self.tabbar.show_contributed_albums_needed.connect( lambda: self.show_albums(self.artist. create_contributed_albums_g())) # fetch and render basic metadata self.meta_widget.title = artist.name self.meta_widget.show() self.tabbar.show() self.tabbar.artist_mode() # fetch and render songs songs = songs_g = None if artist.meta.allow_create_songs_g: songs_g = wrap(artist.create_songs_g()) self.tabbar.show_songs_needed.connect( lambda: self.show_songs(songs_g=wrap(artist.create_songs_g()), songs=songs, show_count=True)) else: songs = await async_run(lambda: artist.songs) self.tabbar.show_songs_needed.connect(lambda: self.show_songs( songs_g=None, songs=songs, show_count=True)) self.show_songs(songs_g=songs_g, songs=songs, show_count=True) # finally, we render cover and description cover = await async_run(lambda: artist.cover) if cover: aio.create_task( self.show_cover(cover, reverse(artist, '/cover'), as_background=True)) self.tabbar.show_desc_needed.connect( lambda: aio.create_task(self._show_desc()))
async def show_album(self, album): meta_widget = self.collection_body.meta_widget meta_widget.clear() meta_widget.title = album.name_display meta_widget.creator = album.artists_name_display songs = await async_run(lambda: album.songs) meta_widget.songs_count = len(songs) reader = wrap(songs) model = SongListModel(reader) self.collection_body.song_list_view.show() self.collection_body.song_list_view.setModel(model) meta_widget.desc = await async_run(lambda: album.desc) meta_widget.title = await async_run(lambda: album.name) meta_widget.creator = await async_run(lambda: album.artists_name) cover = await async_run(lambda: album.cover) if cover: aio.create_task(self.show_cover(cover, reverse(album, '/cover')))
def show_songs(self, songs=None, songs_g=None): """(DEPRECATED) provided only for backward compatibility""" warnings.warn('use readerer.show_songs please') renderer = Renderer() task = aio.create_task(self.set_renderer(renderer)) if songs is not None: reader = wrap(songs) else: reader = songs_g task.add_done_callback(lambda _: renderer.show_songs(reader=reader))
async def render(self): album = self.album songs = await async_run(lambda: album.songs) self.show_songs(wrap(songs)) self.meta_widget.title = album.name_display self.meta_widget.songs_count = len(songs) self.meta_widget.creator = album.artists_name_display self.meta_widget.show() # fetch cover and description cover = await async_run(lambda: album.cover) if cover: aio.create_task(self.show_cover(cover, reverse(album, '/cover'))) self.tabbar.show() self.tabbar.album_mode() self.tabbar.show_desc_needed.connect(lambda: aio.create_task(self._show_desc())) self.tabbar.show_songs_needed.connect(lambda: self.show_songs(songs))
def _on_media_changed(self, media): async def func(media): # FIXME: current media may be unrelated to current song song = self._app.playlist.current_song if song is None or media is None: self.cur_song_dl_btn.setEnabled(False) return media_url = media.url if media_url and not media_url.startswith('http'): self.cur_song_dl_btn.setEnabled(False) self.cur_song_dl_btn.setChecked(True) return ext = guess_media_url_ext(media_url) if self._tag_mgr._name_fmts: if hasattr(song, 'state') and song.state is ModelState.cant_upgrade: # BriefSongModdel tag_obj, cover_url = await aio.run_fn( self._tag_mgr.prepare_tag, song) else: song = await aio.run_fn(self._app.library.song_upgrade, song) album = await aio.run_fn(self._app.library.album_upgrade, song.album) artists = [ await aio.run_fn(self._app.library.artist_upgrade, artist) for artist in song.artists ] tag_obj, cover_url = await aio.run_fn( self._tag_mgr.prepare_tag, song, album, artists) filename = self._tag_mgr.prepare_filename(tag_obj, ext) is_downloaded = self._mgr.is_file_downloaded(filename) if is_downloaded: self.cur_song_dl_btn.setEnabled(False) self.cur_song_dl_btn.setChecked(True) else: self.cur_song_dl_btn.setEnabled(True) self.cur_song_dl_btn.setChecked(False) aio.create_task(func(media))
def bind_coro(self, coro): """run the coroutine and bind the task it will cancel the previous task if exists :return: :class:`asyncio.Task` """ self._before_bind() if is_in_loop_thread(): self._task = aio.create_task(coro) else: self._task = asyncio.run_coroutine_threadsafe(coro, loop=self._mgr.loop) return self._task
def enable(app): logger.info('Register provider: %s', provider) app.library.register(provider) provider.initialize(app) app.initialized.connect(lambda *args: aio.create_task(autoload(*args)), weak=False, aioqueue=False) if app.mode & app.GuiMode: app.browser.route('/local')(show_provider) pm = app.pvd_uimgr.create_item( name=provider.identifier, text='本地音乐', symbol='♪ ', desc='点击显示所有本地音乐', ) pm.clicked.connect(partial(app.browser.goto, uri='/local'), weak=False) app.pvd_uimgr.add_item(pm)
def __init__(self, app, parent=None): super().__init__(parent=parent) self._app = app self._splitter = QSplitter(self) self.collection_toc = CollectionTOCView(self._app, self._splitter) self.collection_body = CollectionBody(self._app, self._splitter) self.collection_toc.show_album_needed.connect( lambda album: aio.create_task(self.show_album(album))) self.collection_toc.play_song_needed.connect( self._app.player.play_song) self.collection_body.song_list_view.play_song_needed.connect( self._app.player.play_song) self._layout = QHBoxLayout(self) self._setup_ui()
def __init__(self, uri: str = None, required_cookies_fields=None): if has_webengine and uri and required_cookies_fields: use_webview = True flags = Qt.Window else: use_webview = False flags = Qt.Popup super().__init__(None, flags) self._use_webview = use_webview self._uri = uri self._required_cookies_fields = required_cookies_fields self.cookies_text_edit = QTextEdit(self) self.hint_label = QLabel(self) self.login_btn = QPushButton('登录', self) self.weblogin_btn = QPushButton('网页登录', self) self.hint_label.setTextFormat(Qt.RichText) self._layout = QVBoxLayout(self) self._layout.addWidget(self.cookies_text_edit) self._layout.addWidget(self.hint_label) self._layout.addWidget(self.login_btn) self._layout.addWidget(self.weblogin_btn) self.cookies_text_edit.setAcceptRichText(False) self.cookies_text_edit.setPlaceholderText( '请从浏览器中复制 Cookie:\n\n' 'Chrome 复制的 cookie 格式类似:key1=value1; key2=value2\n\n' 'Firefox 复制的 cookie 格式类似:{"key1": value1, "key1": value2}' ) if self._use_webview is True: self.weblogin_btn.clicked.connect(self._start_web_login) else: # disable the button if feeluown does not support if uri and required_cookies_fields and not has_webengine: self.weblogin_btn.setEnabled(False) else: # hide the button if provider does not support self.weblogin_btn.hide() self.login_btn.clicked.connect(lambda: aio.create_task(self.login())) self.login_succeed.connect(self.hide)
async def render(req, **kwargs): # pylint: disable=too-many-locals app = req.ctx['app'] song = req.ctx['model'] # TODO: Initialize the view with song object, and it should reduce # the code complexity. view = SongExploreView(app=app) app.ui.right_panel.set_body(view) # show song album cover song = await aio.run_fn(app.library.song_upgrade, song) # bind signals view.play_btn.clicked.connect(lambda: app.playlist.play_model(song)) if app.library.check_flags_by_model(song, PF.web_url): async def copy_song_web_url(): web_url = await aio.run_fn(app.library.song_get_web_url, song) app.show_msg(f'已经复制:{web_url}') QGuiApplication.clipboard().setText(web_url) view.copy_web_url_btn.clicked.connect( lambda: aio.run_afn(copy_song_web_url)) # TODO: Open url in browser when alt key is pressed. Use # QDesktopServices.openUrl to open url in browser, and # you may use QGuiApplication::keyboardModifiers to check # if alt key is pressed. # # NOTE(cosven): Since switching from applications is inconvenience, # the default behaviour of button is url-copy instead of url-open. else: view.copy_web_url_btn.hide() view.header_label.setText(f'<h1>{song.title}</h1>') aio.create_task(view.album_info_label.show_song(song)) if app.library.check_flags_by_model(song, PF.similar): songs = await aio.run_fn(app.library.song_list_similar, song) model = SongListModel(create_reader(songs)) view.similar_songs_view.setModel(model) else: view.similar_songs_header.setText('<span>该提供方暂不支持查看相似歌曲,555</span>') if app.library.check_flags_by_model(song, PF.hot_comments): comments = await aio.run_fn(app.library.song_list_hot_comments, song) comments_reader = create_reader(comments) view.comments_view.setModel(CommentListModel(comments_reader)) else: view.comments_header.setText('<span>该提供方暂不支持查看歌曲评论,555</span>') try: lyric = app.library.song_get_lyric(song) except NotSupported as e: logger.info('cant show lyric due to %s', str(e)) else: if lyric is not None: ms_sentence_map = parse_lyric_text(lyric.content) sentences = [] for _, sentence in sorted(ms_sentence_map.items(), key=lambda item: item[0]): sentences.append(sentence) view.lyric_label.set_lyric('\n'.join(sentences)) # Show album cover in the end since it's an expensive CPU/IO operation. # FIXME: handle NotSupported exception if song.album is not None: album = await aio.run_fn(app.library.album_upgrade, song.album) aio.create_task( view.cover_label.show_cover(album.cover, reverse(album)))
def show_artists_coll(self, artists_g): aio.create_task(self.set_renderer( ArtistsCollectionRenderer(artists_g)))
def show_player_playlist(self): aio.create_task(self.set_renderer(PlayerPlaylistRenderer()))
def show_albums_coll(self, albums_g): aio.create_task(self.set_renderer(AlbumsCollectionRenderer(albums_g)))