def q_media_mapping(self): """fetch media info and save it in q_media_mapping""" q_media_mapping = {} if True: # 注:self.quality_suffix 这里可能会触发一次网络请求 for idx, (q, t, b, s) in enumerate(self.quality_suffix): url = self._api.get_song_url_v2(self.mid, self.media_id, t) if url: q_media_mapping[q] = Media(url, bitrate=b, format=s) # 一般来说,高品质有权限 低品质也会有权限,减少网络请求。 # 这里把值设置为 UNFETCHED_MEDIA,作为一个标记。 for i in range(idx + 1, len(self.quality_suffix)): q_media_mapping[self.quality_suffix[i][0]] = UNFETCHED_MEDIA break else: q_urls_mapping = self._api.get_song_url(self.mid) q_bitrate_mapping = {'shq': 1000, 'hq': 800, 'sq': 500, 'lq': 64} q_media_mapping = {} for quality, url in q_urls_mapping.items(): bitrate = q_bitrate_mapping[quality] format = url.split('?')[0].split('.')[-1] q_media_mapping[quality] = Media(url, bitrate=bitrate, format=format) self.q_media_mapping = q_media_mapping return q_media_mapping
def get_media(self, quality): logger.info(quality) if quality == 'lq': return Media(self.url, format=KuwoApi.FORMATS_BRS[quality], bitrate=KuwoApi.FORMATS_RATES[quality] // 1000) if self._media.get(str(self.identifier)) and self._media.get(str(self.identifier)).get(quality)[0] is not None \ and self._media.get(str(self.identifier)).get(quality)[1] > time.time(): return self._media.get(str(self.identifier)).get(quality)[0] data = self._api.get_song_url_mobi(self.identifier, quality) bitrate = 0 for d in data.split(): if 'bitrate=' in d: bitrate = int(d.split('=')[-1]) if 'url=' in d: if bitrate == 0: bitrate = int(KuwoApi.FORMATS_RATES[quality] // 1000) media = Media(d.split('=')[-1], format=KuwoApi.FORMATS_BRS[quality], bitrate=bitrate) self._media[str(self.identifier)] = {} self._media[str( self.identifier)][quality] = (media, int(time.time()) + 60 * 10) return media or None return None
def get_media(self, quality): if quality == 'lq': return Media(self.url, format=MiguApi.QUALITIES[quality][1], bitrate=MiguApi.QUALITIES[quality][0]) data_song = self._api.get_song(self.identifier, quality=quality) url = data_song.get('data', {}).get('url') return Media(url, format=MiguApi.QUALITIES[quality][1], bitrate=MiguApi.QUALITIES[quality][0])\ if url is not None else None
def to_q_media_mapping(cls, lfiles): q_media_mapping = {} if lfiles: q_q_mapping = { 's': 'shq', 'h': 'hq', 'l': 'sq', 'f': 'lq', 'e': 'lq', } for lfile in filter(lambda lfile: lfile['url'], lfiles): url = lfile['url'] quality = lfile['quality'] format = lfile['format'] # url example: http://m720.xiami.net/... try: bitrate = int(urlparse(url).netloc.split('.')[0][1:]) except: bitrate = None if quality not in q_q_mapping: field = 'lq' logger.warning('unknown quality {}'.format(quality)) else: field = q_q_mapping[quality] q_media_mapping[field] = Media(url, format=format, bitrate=bitrate) return q_media_mapping
def _refresh_url(self): """刷新获取 url,失败的时候返回空而不是 None""" # FIXME: move q_media_mapping fetch logic to somewhere else songs = self._api.weapi_songs_url([int(self.identifier)], 999000) if songs and songs[0]['url']: self.url = songs[0]['url'] else: self.url = '' self.q_media_mapping = {} if songs and songs[0]['url']: media = Media(songs[0]['url'], format=songs[0]['type'], bitrate=songs[0]['br'] // 1000) if songs[0]['br'] > 320000: self.q_media_mapping = { 'shq': media, 'hq': None, 'sq': None, 'lq': None } if songs[0]['br'] == 320000: self.q_media_mapping = {'hq': media, 'sq': None, 'lq': None} if songs[0]['br'] == 192000: self.q_media_mapping = {'sq': media, 'lq': None} if songs[0]['br'] == 128000: self.q_media_mapping = {'lq': media} self.expired_at = int(time.time()) + 60 * 20 * 1
def play(self, media, video=True): # NOTE - API DESGIN: we should return None, see # QMediaPlayer API reference for more details. logger.debug("Player will play: '%s'", media) if video: # FIXME: for some property, we need to set via setattr, however, # some need to be set via _mpv_set_property_string self._mpv.handle.vid = b'auto' # it seems that ytdl will auto choose the default format # if we set ytdl-format to '' _mpv_set_property_string(self._mpv.handle, b'ytdl-format', b'') else: # set vid to no and ytdl-format to bestaudio/best # see https://mpv.io/manual/stable/#options-vid for more details self._mpv.handle.vid = b'no' _mpv_set_property_string(self._mpv.handle, b'ytdl-format', b'bestaudio/best') if isinstance(media, Media): media = media else: # media is a url media = Media(media) url = media.url # Clear playlist before play next song, # otherwise, mpv will seek to the last position and play. self._mpv.playlist_clear() self._mpv.play(url) self._mpv.pause = False self.state = State.playing self._current_media = media # TODO: we will emit a media object self.media_changed.emit(media)
def callback(future): try: media, quality = future.result() except Exception as e: logger.exception('prepare media data failed') else: media = Media(media) if media else None done_cb(media)
def prepare_media(self, song): """prepare media data """ if song.meta.support_multi_quality: media, quality = song.select_media(self.audio_select_policy) else: media = song.url # maybe a empty string return Media(media) if media else None
def after_scan(self): """ 歌曲扫描完成后,对信息进行一些加工,比如 1. 给专辑歌曲排序 2. 给专辑和歌手加封面 """ def sort_album_func(album): if album.songs: return (album.songs[0].date is not None, album.songs[0].date) return (False, '0') for album in self._albums.values(): try: album.songs.sort(key=lambda x: (int(x.disc.split('/')[0]), int(x.track.split('/')[0]))) if album.name != 'Unknown': cover_data, _ = read_audio_cover(album.songs[0].url) if cover_data: cover = Media(reverse(album.songs[0], '/cover/data'), type_=MediaType.image) else: cover = None album.cover = cover except: # noqa logger.exception('Sort album songs failed.') for artist in self._artists.values(): if artist.albums: artist.albums.sort(key=sort_album_func, reverse=True) artist.cover = artist.albums[0].cover if artist.contributed_albums: artist.contributed_albums.sort(key=sort_album_func, reverse=True) if artist.songs: # sort artist songs artist.songs.sort(key=lambda x: x.title) # use song cover as artist cover # https://github.com/feeluown/feeluown-local/pull/3/files#r362126996 songs_with_unknown_album = [song for song in artist.songs if song.album_name == 'Unknown'] for song in sorted(songs_with_unknown_album, key=lambda x: (x.date is not None, x.date), reverse=True): if read_audio_cover(song.url)[0]: artist.cover = Media(reverse(song, '/cover/data'), type_=MediaType.image) break
def prepare_media(self, song, done_cb=None): if song.meta.support_multi_quality: media, quality = song.select_media('hq<>') else: media = song.url media = Media(media) if media else None if done_cb is not None: done_cb(media) return media
async def show_cover(self, cover, cover_uid): cover = Media(cover, MediaType.image) url = cover.url app = self._app content = await app.img_mgr.get(url, cover_uid) img = QImage() img.loadFromData(content) pixmap = QPixmap(img) if not pixmap.isNull(): self.meta_widget.set_cover_pixmap(pixmap)
def from_youtubedl_output(cls, data): duration = data['duration'] url = data['url'] media = Media(url, http_headers={'Referer': 'https://www.bilibili.com/'}) title = data['title'] return cls(identifier=data['id'], duration=duration, url=media, title=title)
def from_youtubedl_output(cls, data): url = data['url'] media = Media(url, http_headers={'Referer': 'https://www.bilibili.com/'}) return cls(identifier=data['id'], duration=data['duration'], cover=data['thumbnail'], description=data['description'], media=media, title=data['title'], stage=ModelStage.gotten)
def q_media_mapping(self): """fetch media info and save it in q_media_mapping""" q_urls_mapping = self._api.get_song_url(self.mid) q_bitrate_mapping = {'shq': 1000, 'hq': 800, 'sq': 500, 'lq': 64} q_media_mapping = {} for quality, url in q_urls_mapping.items(): bitrate = q_bitrate_mapping[quality] q_media_mapping[quality] = Media(url, bitrate=bitrate) self.q_media_mapping = q_media_mapping return q_media_mapping
def get_media(self, quality): media = self.q_media_mapping.get(quality) if media is UNFETCHED_MEDIA: for (q, t, b, s) in self.quality_suffix: if quality == q: url = self._api.get_song_url_v2(self.mid, self.media_id, t) if url: media = Media(url, bitrate=b, format=s) self.q_media_mapping[quality] = media else: media = None self.q_media_mapping[quality] = None break else: media = None return media
def play(self, media, video=True): logger.debug("Player will play: '%s'", media) if isinstance(media, Media): media = media else: # media is a url media = Media(media) self._set_http_headers(media.http_headers) url = media.url # Clear playlist before play next song, # otherwise, mpv will seek to the last position and play. self.media_about_to_changed.emit(self._current_media, media) self._mpv.playlist_clear() self._mpv.play(url) self._current_media = media self.media_changed.emit(media)
async def show_cover(self, cover, cover_uid, as_background=False): cover = Media(cover, MediaType.image) url = cover.url app = self._app content = await app.img_mgr.get(url, cover_uid) img = QImage() img.loadFromData(content) pixmap = QPixmap(img) if not pixmap.isNull(): if as_background: self.meta_widget.set_cover_pixmap(None) self._app.ui.right_panel.show_background_image(pixmap) else: self._app.ui.right_panel.show_background_image(None) self.meta_widget.set_cover_pixmap(pixmap) self._app.ui.table_container.updateGeometry()
def play(self, media, video=True): logger.debug("Player will play: '%s'", media) if isinstance(media, Media): media = media else: # media is a url media = Media(media) self._set_http_headers(media.http_headers) url = media.url # Clear playlist before play next song, # otherwise, mpv will seek to the last position and play. self._mpv.playlist_clear() self._mpv.play(url) self._mpv.pause = False self.state = State.playing self._current_media = media # TODO: we will emit a media object self.media_changed.emit(media)
async def show_cover(self, cover, cover_uid, as_background=False): cover = Media(cover, MediaType.image) url = cover.url app = self._app content = await app.img_mgr.get(url, cover_uid) img = QImage() img.loadFromData(content) pixmap = QPixmap(img) if not pixmap.isNull(): # TODO: currently, background image is shown with dark # overlay. When we are using light theme, the text is also # in dark color, so we only show background image in dark theme if as_background and self._app.theme_mgr.theme == 'dark': self.meta_widget.set_cover_pixmap(None) self._app.ui.right_panel.show_background_image(pixmap) else: self._app.ui.right_panel.show_background_image(None) self.meta_widget.set_cover_pixmap(pixmap) self._app.ui.table_container.updateGeometry()
def get_media(self, quality): if self.is_expired: self._refresh_url() media = self.q_media_mapping.get(quality) if media is None: q_bitrate_mapping = { 'shq': 999000, 'hq': 320000, 'sq': 192000, 'lq': 128000, } bitrate = q_bitrate_mapping[quality] songs = self._api.weapi_songs_url([int(self.identifier)], bitrate) if songs and songs[0]['url']: media = Media(songs[0]['url'], format=songs[0]['type'], bitrate=songs[0]['br'] // 1000) self.q_media_mapping[quality] = media else: self.q_media_mapping[quality] = '' return self.q_media_mapping.get(quality)
def test_media_copy(media): media2 = Media(media) assert media2.http_headers == media.http_headers
def media(): return Media('zzz://xxx.yyy', http_headers={'referer': 'http://xxx.yyy'})
def test_play_media_with_http_headers(self, mock_set_option_string): media = Media('http://xxx', http_headers={'referer': 'http://xxx'}) self.player.play(media) assert mock_set_option_string.called self.player.stop()