]>
Raphaël G. Git Repositories - youtubedl/blob - youtube_dl/extractor/neteasemusic.py
2 from __future__
import unicode_literals
4 from hashlib
import md5
5 from base64
import b64encode
6 from datetime
import datetime
9 from .common
import InfoExtractor
10 from ..compat
import (
11 compat_urllib_request
,
14 compat_itertools_count
,
18 class NetEaseMusicBaseIE(InfoExtractor
):
19 _FORMATS
= ['bMusic', 'mMusic', 'hMusic']
20 _NETEASE_SALT
= '3go8&$8*3*3h0k(2)2'
21 _API_BASE
= 'http://music.163.com/api/'
24 def _encrypt(cls
, dfsid
):
25 salt_bytes
= bytearray(cls
._NETEASE
_SALT
.encode('utf-8'))
26 string_bytes
= bytearray(compat_str(dfsid
).encode('ascii'))
27 salt_len
= len(salt_bytes
)
28 for i
in range(len(string_bytes
)):
29 string_bytes
[i
] = string_bytes
[i
] ^ salt_bytes
[i
% salt_len
]
31 m
.update(bytes(string_bytes
))
32 result
= b64encode(m
.digest()).decode('ascii')
33 return result
.replace('/', '_').replace('+', '-')
36 def extract_formats(cls
, info
):
38 for song_format
in cls
._FORMATS
:
39 details
= info
.get(song_format
)
43 'url': 'http://m1.music.126.net/%s/%s.%s' %
44 (cls
._encrypt
(details
['dfsId']), details
['dfsId'],
45 details
['extension']),
46 'ext': details
.get('extension'),
47 'abr': details
.get('bitrate', 0) / 1000,
48 'format_id': song_format
,
49 'filesize': details
.get('size'),
50 'asr': details
.get('sr')
55 def convert_milliseconds(cls
, ms
):
56 return int(round(ms
/ 1000.0))
58 def query_api(self
, endpoint
, video_id
, note
):
59 req
= compat_urllib_request
.Request('%s%s' % (self
._API
_BASE
, endpoint
))
60 req
.add_header('Referer', self
._API
_BASE
)
61 return self
._download
_json
(req
, video_id
, note
)
64 class NetEaseMusicIE(NetEaseMusicBaseIE
):
65 IE_NAME
= 'netease:song'
67 _VALID_URL
= r
'https?://music\.163\.com/(#/)?song\?id=(?P<id>[0-9]+)'
69 'url': 'http://music.163.com/#/song?id=32102397',
70 'md5': 'f2e97280e6345c74ba9d5677dd5dcb45',
74 'title': 'Bad Blood (feat. Kendrick Lamar)',
75 'creator': 'Taylor Swift / Kendrick Lamar',
76 'upload_date': '20150517',
77 'timestamp': 1431878400,
78 'description': 'md5:a10a54589c2860300d02e1de821eb2ef',
81 'note': 'No lyrics translation.',
82 'url': 'http://music.163.com/#/song?id=29822014',
88 'upload_date': '20141225',
89 'timestamp': 1419523200,
90 'description': 'md5:a4d8d89f44656af206b7b2555c0bce6c',
94 'url': 'http://music.163.com/song?id=17241424',
99 'creator': 'Dustin O\'Halloran',
100 'upload_date': '20080211',
101 'timestamp': 1202745600,
104 'note': 'Has translated name.',
105 'url': 'http://music.163.com/#/song?id=22735043',
109 'title': '소원을 말해봐 (Genie)',
111 'description': 'md5:79d99cc560e4ca97e0c4d86800ee4184',
112 'upload_date': '20100127',
113 'timestamp': 1264608000,
114 'alt_title': '说出愿望吧(Genie)',
118 def _process_lyrics(self
, lyrics_info
):
119 original
= lyrics_info
.get('lrc', {}).get('lyric')
120 translated
= lyrics_info
.get('tlyric', {}).get('lyric')
125 lyrics_expr
= r
'(\[[0-9]{2}:[0-9]{2}\.[0-9]{2,}\])([^\n]+)'
126 original_ts_texts
= re
.findall(lyrics_expr
, original
)
127 translation_ts_dict
= dict(
128 (time_stamp
, text
) for time_stamp
, text
in re
.findall(lyrics_expr
, translated
)
131 '%s%s / %s' % (time_stamp
, text
, translation_ts_dict
.get(time_stamp
, ''))
132 for time_stamp
, text
in original_ts_texts
136 def _real_extract(self
, url
):
137 song_id
= self
._match
_id
(url
)
141 'ids': '[%s]' % song_id
143 info
= self
.query_api(
144 'song/detail?' + compat_urllib_parse
.urlencode(params
),
145 song_id
, 'Downloading song info')['songs'][0]
147 formats
= self
.extract_formats(info
)
148 self
._sort
_formats
(formats
)
150 lyrics_info
= self
.query_api(
151 'song/lyric?id=%s&lv=-1&tv=-1' % song_id
,
152 song_id
, 'Downloading lyrics data')
153 lyrics
= self
._process
_lyrics
(lyrics_info
)
156 if info
.get('transNames'):
157 alt_title
= '/'.join(info
.get('transNames'))
161 'title': info
['name'],
162 'alt_title': alt_title
,
163 'creator': ' / '.join([artist
['name'] for artist
in info
.get('artists', [])]),
164 'timestamp': self
.convert_milliseconds(info
.get('album', {}).get('publishTime')),
165 'thumbnail': info
.get('album', {}).get('picUrl'),
166 'duration': self
.convert_milliseconds(info
.get('duration', 0)),
167 'description': lyrics
,
172 class NetEaseMusicAlbumIE(NetEaseMusicBaseIE
):
173 IE_NAME
= 'netease:album'
174 IE_DESC
= '网易云音乐 - 专辑'
175 _VALID_URL
= r
'https?://music\.163\.com/(#/)?album\?id=(?P<id>[0-9]+)'
177 'url': 'http://music.163.com/#/album?id=220780',
182 'playlist_count': 23,
185 def _real_extract(self
, url
):
186 album_id
= self
._match
_id
(url
)
188 info
= self
.query_api(
189 'album/%s?id=%s' % (album_id
, album_id
),
190 album_id
, 'Downloading album data')['album']
193 desc
= info
.get('description')
195 self
.url_result('http://music.163.com/#/song?id=%s' % song
['id'],
196 'NetEaseMusic', song
['id'])
197 for song
in info
['songs']
199 return self
.playlist_result(entries
, album_id
, name
, desc
)
202 class NetEaseMusicSingerIE(NetEaseMusicBaseIE
):
203 IE_NAME
= 'netease:singer'
204 IE_DESC
= '网易云音乐 - 歌手'
205 _VALID_URL
= r
'https?://music\.163\.com/(#/)?artist\?id=(?P<id>[0-9]+)'
207 'note': 'Singer has aliases.',
208 'url': 'http://music.163.com/#/artist?id=10559',
211 'title': '张惠妹 - aMEI;阿密特',
213 'playlist_count': 50,
215 'note': 'Singer has translated name.',
216 'url': 'http://music.163.com/#/artist?id=124098',
219 'title': '李昇基 - 이승기',
221 'playlist_count': 50,
224 def _real_extract(self
, url
):
225 singer_id
= self
._match
_id
(url
)
227 info
= self
.query_api(
228 'artist/%s?id=%s' % (singer_id
, singer_id
),
229 singer_id
, 'Downloading singer data')
231 name
= info
['artist']['name']
232 if info
['artist']['trans']:
233 name
= '%s - %s' % (name
, info
['artist']['trans'])
234 if info
['artist']['alias']:
235 name
= '%s - %s' % (name
, ';'.join(info
['artist']['alias']))
238 self
.url_result('http://music.163.com/#/song?id=%s' % song
['id'],
239 'NetEaseMusic', song
['id'])
240 for song
in info
['hotSongs']
242 return self
.playlist_result(entries
, singer_id
, name
)
245 class NetEaseMusicListIE(NetEaseMusicBaseIE
):
246 IE_NAME
= 'netease:playlist'
247 IE_DESC
= '网易云音乐 - 歌单'
248 _VALID_URL
= r
'https?://music\.163\.com/(#/)?(playlist|discover/toplist)\?id=(?P<id>[0-9]+)'
250 'url': 'http://music.163.com/#/playlist?id=79177352',
253 'title': 'Billboard 2007 Top 100',
254 'description': 'md5:12fd0819cab2965b9583ace0f8b7b022'
256 'playlist_count': 99,
258 'note': 'Toplist/Charts sample',
259 'url': 'http://music.163.com/#/discover/toplist?id=3733003',
262 'title': 're:韩国Melon排行榜周榜 [0-9]{4}-[0-9]{2}-[0-9]{2}',
263 'description': 'md5:73ec782a612711cadc7872d9c1e134fc',
265 'playlist_count': 50,
268 def _real_extract(self
, url
):
269 list_id
= self
._match
_id
(url
)
271 info
= self
.query_api(
272 'playlist/detail?id=%s&lv=-1&tv=-1' % list_id
,
273 list_id
, 'Downloading playlist data')['result']
276 desc
= info
.get('description')
278 if info
.get('specialType') == 10: # is a chart/toplist
279 datestamp
= datetime
.fromtimestamp(
280 self
.convert_milliseconds(info
['updateTime'])).strftime('%Y-%m-%d')
281 name
= '%s %s' % (name
, datestamp
)
284 self
.url_result('http://music.163.com/#/song?id=%s' % song
['id'],
285 'NetEaseMusic', song
['id'])
286 for song
in info
['tracks']
288 return self
.playlist_result(entries
, list_id
, name
, desc
)
291 class NetEaseMusicMvIE(NetEaseMusicBaseIE
):
292 IE_NAME
= 'netease:mv'
293 IE_DESC
= '网易云音乐 - MV'
294 _VALID_URL
= r
'https?://music\.163\.com/(#/)?mv\?id=(?P<id>[0-9]+)'
296 'url': 'http://music.163.com/#/mv?id=415350',
300 'title': '이럴거면 그러지말지',
301 'description': '白雅言自作曲唱甜蜜爱情',
303 'upload_date': '20150520',
307 def _real_extract(self
, url
):
308 mv_id
= self
._match
_id
(url
)
310 info
= self
.query_api(
311 'mv/detail?id=%s&type=mp4' % mv_id
,
312 mv_id
, 'Downloading mv info')['data']
315 {'url': mv_url
, 'ext': 'mp4', 'format_id': '%sp' % brs
, 'height': int(brs
)}
316 for brs
, mv_url
in info
['brs'].items()
318 self
._sort
_formats
(formats
)
322 'title': info
['name'],
323 'description': info
.get('desc') or info
.get('briefDesc'),
324 'creator': info
['artistName'],
325 'upload_date': info
['publishTime'].replace('-', ''),
327 'thumbnail': info
.get('cover'),
328 'duration': self
.convert_milliseconds(info
.get('duration', 0)),
332 class NetEaseMusicProgramIE(NetEaseMusicBaseIE
):
333 IE_NAME
= 'netease:program'
334 IE_DESC
= '网易云音乐 - 电台节目'
335 _VALID_URL
= r
'https?://music\.163\.com/(#/?)program\?id=(?P<id>[0-9]+)'
337 'url': 'http://music.163.com/#/program?id=10109055',
341 'title': '不丹足球背后的故事',
342 'description': '喜马拉雅人的足球梦 ...',
344 'timestamp': 1434179342,
345 'upload_date': '20150613',
349 'note': 'This program has accompanying songs.',
350 'url': 'http://music.163.com/#/program?id=10141022',
353 'title': '25岁,你是自在如风的少年<27°C>',
354 'description': 'md5:8d594db46cc3e6509107ede70a4aaa3b',
358 'note': 'This program has accompanying songs.',
359 'url': 'http://music.163.com/#/program?id=10141022',
363 'title': '25岁,你是自在如风的少年<27°C>',
364 'description': 'md5:8d594db46cc3e6509107ede70a4aaa3b',
365 'timestamp': 1434450841,
366 'upload_date': '20150616',
373 def _real_extract(self
, url
):
374 program_id
= self
._match
_id
(url
)
376 info
= self
.query_api(
377 'dj/program/detail?id=%s' % program_id
,
378 program_id
, 'Downloading program info')['program']
381 description
= info
['description']
383 if not info
['songs'] or self
._downloader
.params
.get('noplaylist'):
386 'Downloading just the main audio %s because of --no-playlist'
387 % info
['mainSong']['id'])
389 formats
= self
.extract_formats(info
['mainSong'])
390 self
._sort
_formats
(formats
)
395 'description': description
,
396 'creator': info
['dj']['brand'],
397 'timestamp': self
.convert_milliseconds(info
['createTime']),
398 'thumbnail': info
['coverUrl'],
399 'duration': self
.convert_milliseconds(info
.get('duration', 0)),
404 'Downloading playlist %s - add --no-playlist to just download the main audio %s'
405 % (program_id
, info
['mainSong']['id']))
407 song_ids
= [info
['mainSong']['id']]
408 song_ids
.extend([song
['id'] for song
in info
['songs']])
410 self
.url_result('http://music.163.com/#/song?id=%s' % song_id
,
411 'NetEaseMusic', song_id
)
412 for song_id
in song_ids
414 return self
.playlist_result(entries
, program_id
, name
, description
)
417 class NetEaseMusicDjRadioIE(NetEaseMusicBaseIE
):
418 IE_NAME
= 'netease:djradio'
419 IE_DESC
= '网易云音乐 - 电台'
420 _VALID_URL
= r
'https?://music\.163\.com/(#/)?djradio\?id=(?P<id>[0-9]+)'
422 'url': 'http://music.163.com/#/djradio?id=42',
426 'description': 'md5:766220985cbd16fdd552f64c578a6b15'
428 'playlist_mincount': 40,
432 def _real_extract(self
, url
):
433 dj_id
= self
._match
_id
(url
)
438 for offset
in compat_itertools_count(start
=0, step
=self
._PAGE
_SIZE
):
439 info
= self
.query_api(
440 'dj/program/byradio?asc=false&limit=%d&radioId=%s&offset=%d'
441 % (self
._PAGE
_SIZE
, dj_id
, offset
),
442 dj_id
, 'Downloading dj programs - %d' % offset
)
446 'http://music.163.com/#/program?id=%s' % program
['id'],
447 'NetEaseMusicProgram', program
['id'])
448 for program
in info
['programs']
452 radio
= info
['programs'][0]['radio']
459 return self
.playlist_result(entries
, dj_id
, name
, desc
)