async def get_playlist_songs_raw(self, playlist_id: typing.Union[int, str], page: int = 1, page_size: int = 200) -> dict: token = await self._get_token(_API_GET_PLAYLIST_SONGS) if token is None: raise exceptions.ClientError("get playlist songs: can't get token") model = { 'listid': playlist_id, 'pagingVO': { 'page': page, 'pageSize': page_size, }, } try: _resp = await self.request('GET', _API_GET_PLAYLIST_DETAIL, params=_sign_payload(token, model)) except (aiohttp.ClientError, asyncio.TimeoutError) as e: raise exceptions.RequestError('get playlist songs: {}'.format(e)) try: resp = await _resp.json(content_type=None) _check(resp['ret'], 'get playlist songs') except (aiohttp.ClientResponseError, json.JSONDecodeError, KeyError) as e: raise exceptions.ResponseError('get playlist songs: {}'.format(e)) return resp
async def get_artist_songs_raw(self, artist_id: typing.Union[int, str], page: int = 1, page_size: int = 50) -> dict: token = await self._get_token(_API_SEARCH) if token is None: raise exceptions.ClientError("get artist songs: can't get token") model = { 'pagingVO': { 'page': page, 'pageSize': page_size, }, } if artist_id.isdigit(): model['artistId'] = artist_id else: model['artistStringId'] = artist_id try: _resp = await self.request('GET', _API_GET_ARTIST_SONGS, params=_sign_payload(token, model)) except (aiohttp.ClientError, asyncio.TimeoutError) as e: raise exceptions.RequestError('get artist songs: {}'.format(e)) try: resp = await _resp.json(content_type=None) _check(resp['ret'], 'get artist songs') except (aiohttp.ClientResponseError, json.JSONDecodeError, KeyError) as e: raise exceptions.ResponseError('get artist songs: {}'.format(e)) return resp
async def search_songs_raw(self, keyword: str, page: int = 1, page_size: int = 50) -> dict: token = await self._get_token(_API_SEARCH) if token is None: raise exceptions.ClientError("search songs: can't get token") model = { 'key': keyword, 'pagingVO': { 'page': page, 'pageSize': page_size, }, } try: _resp = await self.request('GET', _API_SEARCH, params=_sign_payload(token, model)) except (aiohttp.ClientError, asyncio.TimeoutError) as e: raise exceptions.RequestError('search songs: {}'.format(e)) try: resp = await _resp.json(content_type=None) _check(resp['ret'], 'search songs') except (aiohttp.ClientResponseError, json.JSONDecodeError, KeyError) as e: raise exceptions.ResponseError('search songs: {}'.format(e)) return resp
async def get_album_raw(self, album_id: typing.Union[int, str]) -> dict: token = await self._get_token(_API_SEARCH) if token is None: raise exceptions.ClientError("get album: can't get token") model = {} if album_id.isdigit(): model['albumId'] = album_id else: model['albumStringId'] = album_id try: _resp = await self.request('GET', _API_GET_ALBUM, params=_sign_payload(token, model)) except (aiohttp.ClientError, asyncio.TimeoutError) as e: raise exceptions.RequestError('get album: {}'.format(e)) try: resp = await _resp.json(content_type=None) _check(resp['ret'], 'get album') except (aiohttp.ClientResponseError, json.JSONDecodeError, KeyError) as e: raise exceptions.ResponseError('get album: {}'.format(e)) return resp
async def get_songs_raw(self, *song_ids: typing.Union[int, str]) -> dict: token = await self._get_token(_API_GET_SONGS) if token is None: raise exceptions.ClientError("get songs: can't get token") if len(song_ids) > _SONG_REQUEST_LIMIT: song_ids = song_ids[:_SONG_REQUEST_LIMIT] model = { 'songIds': song_ids, } try: _resp = await self.request('GET', _API_GET_SONGS, params=_sign_payload(token, model)) except (aiohttp.ClientError, asyncio.TimeoutError) as e: raise exceptions.RequestError('get songs: {}'.format(e)) try: resp = await _resp.json(content_type=None) _check(resp['ret'], 'get songs') except (aiohttp.ClientResponseError, json.JSONDecodeError, KeyError) as e: raise exceptions.ResponseError('get songs: {}'.format(e)) return resp
def init(self): self._setup_user_dir() self._setup_settings_path() self._init_settings_file() try: self.update(self._settings_from_file()) except (OSError, json.JSONDecodeError) as e: self.update(_DEFAULT_SETTINGS) raise exceptions.ClientError("Can't load settings from file: {}".format(e)) platform_id = self.get('music_platform') if get_site(platform_id) is None: self['music_platform'] = _DEFAULT_SETTINGS['music_platform'] raise exceptions.ClientError('Unexpected music platform: "{}"'.format(platform_id)) self.make_download_dir()
def make_download_dir(self, path: str = None): if path is None: path = self.get('download_dir') download_dir = pathlib.Path(path) if not download_dir.is_dir(): try: download_dir.mkdir(parents=True) except OSError as e: self['download_dir'] = _DEFAULT_SETTINGS['download_dir'] raise exceptions.ClientError("Can't make download dir: {}".format(e))
def save(self, cfg: dict = None): if cfg is None: cfg = { 'music_platform': self['music_platform'], 'download_dir': self['download_dir'], } try: with self.settings_path.open(mode='w') as settings_file: json.dump(cfg, settings_file, indent=4) except OSError as e: raise exceptions.ClientError("Can't save settings to file: {}".format(e))
async def concurrent_download(client: api.API, save_path: str, *songs: api.Song) -> None: limit = conf.settings.get('limit') if limit is None: limit = multiprocessing.cpu_count() if limit < 1: limit = 1 if limit > 32: limit = 32 save_path = pathlib.Path(conf.settings['download_dir']).joinpath( utils.trim_invalid_file_path_chars(save_path)) if not save_path.is_dir(): try: save_path.mkdir(parents=True) except OSError as e: raise exceptions.ClientError( "Can't make download dir: {}".format(e)) sem = asyncio.Semaphore(limit) async def worker(song: api.Song): async with sem: song_info = '{} - {}'.format(song.artist, song.name) if not song.playable: logging.error( 'Download [{}] failed: song unavailable'.format(song_info)) return logging.info('Start download: [{}]'.format(song_info)) filename = utils.trim_invalid_file_path_chars(song_info) mp3_file_path = save_path.joinpath(filename + '.mp3') if mp3_file_path.is_file() and not conf.settings.get( 'force', False): logging.info('Song already downloaded: [{}]'.format(song_info)) return try: resp = await client.request('GET', song.url) f = await aiofiles.open(mp3_file_path, 'wb') await f.write(await resp.read()) await f.close() except (aiohttp.ClientError, asyncio.TimeoutError) as err: logging.error('Download [{}] failed: {}'.format( song_info, err)) if mp3_file_path.is_file(): try: mp3_file_path.unlink() except OSError: pass return logging.info('Download [{}] complete'.format(song_info)) if conf.settings.get('tag'): logging.info('Update music metadata: [{}]'.format(song_info)) await _write_tag(client, mp3_file_path, song) if conf.settings.get('lyric'): logging.info('Save lyric: [{}]'.format(song_info)) lrc_file_path = save_path.joinpath(filename + '.lrc') await _save_lyric(lrc_file_path, song.lyric) tasks = [asyncio.ensure_future(worker(song)) for song in songs] await asyncio.gather(*tasks)