Added ability to download tracks

このコミットが含まれているのは:
Marshal 2019-05-23 12:10:14 +03:00
コミット 922424feb5
7個のファイルの変更99行の追加15行の削除

ファイルの表示

@ -109,12 +109,12 @@ class Client(YandexMusicObject):
return Feed.de_json(result, self)
def tracks_download_info(self, track_id: str or int, timeout=None, *args, **kwargs):
def tracks_download_info(self, track_id: str or int, get_direct_links=False, timeout=None, *args, **kwargs):
url = f'{self.base_url}/tracks/{track_id}/download-info'
result = self._request.get(url, timeout=timeout, *args, **kwargs)
return DownloadInfo.de_list(result, self)
return DownloadInfo.de_list(result, self, get_direct_links)
def play_audio(self,
track_id: str or int,
@ -287,8 +287,10 @@ class Client(YandexMusicObject):
return de_list.get(object_type)(result, self)
def users_likes_tracks(self, user_id: int or str = None, if_modified_since_revision=0, timeout=None, *args, **kwargs):
return self._get_likes('track', user_id, {'if-modified-since-revision': if_modified_since_revision}, timeout, *args, **kwargs)
def users_likes_tracks(self, user_id: int or str = None, if_modified_since_revision=0, timeout=None,
*args, **kwargs):
return self._get_likes('track', user_id, {'if-modified-since-revision': if_modified_since_revision}, timeout,
*args, **kwargs)
def users_likes_albums(self, user_id: int or str = None, rich=True, timeout=None, *args, **kwargs):
return self._get_likes('album', user_id, {'rich': rich}, timeout, *args, **kwargs)

ファイルの表示

@ -1,3 +1,5 @@
import xml.dom.minidom as minidom
from yandex_music import YandexMusicObject
@ -16,8 +18,37 @@ class DownloadInfo(YandexMusicObject):
self.preview = preview
self.download_info_url = download_info_url
self.direct_link = None
self.client = client
@staticmethod
def _get_text_node_data(elements):
for element in elements:
nodes = element.childNodes
for node in nodes:
if node.nodeType == node.TEXT_NODE:
return node.data
def get_direct_link(self):
# Available within one minute after receiving download_info, otherwise 410!
result = self.client._request.retrieve(self.download_info_url)
doc = minidom.parseString(result.text)
host = self._get_text_node_data(doc.getElementsByTagName('host'))
path = self._get_text_node_data(doc.getElementsByTagName('path'))
ts = self._get_text_node_data(doc.getElementsByTagName('ts'))
self.direct_link = f'https://{host}/get-{self.codec}/randomTrash/{ts}{path}'
return self.direct_link
def download(self, filename):
if self.direct_link is None:
self.get_direct_link()
self.client._request.download(self.direct_link, filename)
@classmethod
def de_json(cls, data, client):
if not data:
@ -28,7 +59,7 @@ class DownloadInfo(YandexMusicObject):
return cls(client=client, **data)
@classmethod
def de_list(cls, data, client):
def de_list(cls, data, client, get_direct_links=False):
if not data:
return []
@ -36,4 +67,8 @@ class DownloadInfo(YandexMusicObject):
for download_info in data:
downloads_info.append(cls.de_json(download_info, client))
if get_direct_links:
for info in downloads_info:
info.get_direct_link()
return downloads_info

ファイルの表示

@ -14,6 +14,12 @@ class TracksLikes(YandexMusicObject):
self.client = client
def __getitem__(self, item):
return self.tracks[item]
def __iter__(self):
return iter(self.tracks)
@property
def tracks_ids(self):
return [track.track_id for track in self.tracks]

ファイルの表示

@ -49,9 +49,24 @@ class Track(YandexMusicObject):
self.content_warning = content_warning
self.explicit = explicit
self.download_info = None
self.client = client
self._id_attrs = (self.id,)
def get_download_info(self, get_direct_links=False):
self.download_info = self.client.tracks_download_info(self.track_id, get_direct_links)
return self.download_info
def download(self, filename, codec='mp3', bitrate_in_kbps=192):
if self.download_info is None:
self.get_download_info()
for info in self.download_info:
if info.codec == codec and info.bitrate_in_kbps == bitrate_in_kbps:
info.download(filename)
@property
def track_id(self):
return f'{self.id}'

ファイルの表示

@ -14,8 +14,19 @@ class TrackShort(YandexMusicObject):
self.album_id = album_id
self.timestamp = datetime.fromisoformat(timestamp)
self._track = None
self.client = client
@property
def track(self):
if self._track:
return self._track
self._track = self.client.tracks(self.track_id)[0]
return self._track
@property
def track_id(self):
return f'{self.id}:{self.album_id}'

ファイルの表示

@ -76,14 +76,9 @@ class Request(object):
raise NetworkError(e)
if 200 <= resp.status_code <= 299:
return self._parse(resp.content).result
return resp
try:
message = self._parse(resp.content).error
if message is None:
raise ValueError()
except ValueError:
message = 'Unknown HTTPError'
message = self._parse(resp.content).error or 'Unknown HTTPError'
if resp.status_code in (401, 403):
raise Unauthorized(message)
@ -98,8 +93,22 @@ class Request(object):
raise NetworkError(f'{message} ({resp.status_code})')
def get(self, url, params=None, timeout=5, *args, **kwargs):
return self._request_wrapper('GET', url, params=params, headers=self.headers, timeout=timeout, *args, **kwargs)
result = self._request_wrapper('GET', url, params=params, headers=self.headers, timeout=timeout,
*args, **kwargs)
return self._parse(result.content).result
def post(self, url, data=None, timeout=5, *args, **kwargs):
return self._request_wrapper('POST', url, headers=self.headers, data=data, timeout=timeout, *args, **kwargs)
result = self._request_wrapper('POST', url, headers=self.headers, data=data, timeout=timeout,
*args, **kwargs)
return self._parse(result.content).result
def retrieve(self, url, params=None, timeout=5, *args, **kwargs):
return self._request_wrapper('GET', url, params=params, headers=self.headers, timeout=timeout,
*args, **kwargs)
def download(self, url, filename, timeout=5, *args, **kwargs):
result = self.retrieve(url, timeout=timeout, *args, *kwargs)
with open(filename, 'wb') as f:
f.write(result.content)

ファイルの表示

@ -7,15 +7,21 @@ class Response(YandexMusicObject):
invocation_info=None,
result=None,
error=None,
error_description=None,
client=None,
**kwargs):
self.data = data
self.invocation_info = invocation_info
self._result = result
self.error = error
self._error = error
self.error_description = error_description
self.client = client
@property
def error(self):
return f'{self._error} {self.error_description if self.error_description else ""}'
@property
def result(self):
return self._result or self.data