Esempio n. 1
0
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))
Esempio n. 2
0
    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
Esempio n. 3
0
    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()))
Esempio n. 4
0
        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))
Esempio n. 5
0
    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'))
Esempio n. 6
0
        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))
Esempio n. 7
0
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()))
Esempio n. 8
0
    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))
Esempio n. 9
0
    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()
Esempio n. 10
0
    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
Esempio n. 11
0
 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'))
Esempio n. 12
0
    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)
Esempio n. 13
0
    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)
Esempio n. 14
0
    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)
Esempio n. 15
0
    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
Esempio n. 16
0
    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())
Esempio n. 17
0
    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())
Esempio n. 18
0
    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()))
Esempio n. 19
0
 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')))
Esempio n. 20
0
 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))
Esempio n. 21
0
    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))
Esempio n. 22
0
    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))
Esempio n. 23
0
    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
Esempio n. 24
0
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)
Esempio n. 25
0
    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()
Esempio n. 26
0
    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)
Esempio n. 27
0
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)))
Esempio n. 28
0
 def show_artists_coll(self, artists_g):
     aio.create_task(self.set_renderer(
         ArtistsCollectionRenderer(artists_g)))
Esempio n. 29
0
 def show_player_playlist(self):
     aio.create_task(self.set_renderer(PlayerPlaylistRenderer()))
Esempio n. 30
0
 def show_albums_coll(self, albums_g):
     aio.create_task(self.set_renderer(AlbumsCollectionRenderer(albums_g)))