5 from .common
import InfoExtractor
6 from .videa
import VideaIE
17 class XimalayaBaseIE(InfoExtractor
):
18 _GEO_COUNTRIES
= ['CN']
21 class XimalayaIE(XimalayaBaseIE
):
24 _VALID_URL
= r
'https?://(?:www\.|m\.)?ximalaya\.com/(?:(?P<uid>\d+)/)?sound/(?P<id>[0-9]+)'
27 'url': 'http://www.ximalaya.com/sound/47740352/',
32 'uploader_id': '61425525',
33 'uploader_url': 'http://www.ximalaya.com/zhubo/61425525/',
34 'title': '261.唐诗三百首.卷八.送孟浩然之广陵.李白',
35 'description': 'contains:《送孟浩然之广陵》\n作者:李白\n故人西辞黄鹤楼,烟花三月下扬州。\n孤帆远影碧空尽,惟见长江天际流。',
36 'thumbnail': r
're:^https?://.*\.jpg',
40 'url': r
're:^https?://.*\.jpg',
43 'name': 'cover_url_142',
44 'url': r
're:^https?://.*\.jpg',
56 'url': 'http://m.ximalaya.com/61425525/sound/47740352/',
61 'uploader_id': '61425525',
62 'uploader_url': 'http://www.ximalaya.com/zhubo/61425525/',
63 'title': '261.唐诗三百首.卷八.送孟浩然之广陵.李白',
64 'description': 'contains:《送孟浩然之广陵》\n作者:李白\n故人西辞黄鹤楼,烟花三月下扬州。\n孤帆远影碧空尽,惟见长江天际流。',
65 'thumbnail': r
're:^https?://.*\.jpg',
69 'url': r
're:^https?://.*\.jpg',
72 'name': 'cover_url_142',
73 'url': r
're:^https?://.*\.jpg',
85 # VIP-restricted audio
86 'url': 'https://www.ximalaya.com/sound/562111701',
87 'only_matching': True,
92 def _decrypt_filename(file_id
, seed
):
94 key
= 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\\:._-1234567890'
96 seed
= float(int(211 * seed
+ 30031) % 65536)
97 r
= int(seed
/ 65536 * len(key
))
99 key
= key
.replace(key
[r
], '')
100 parts
= file_id
.split('*')
101 filename
= ''.join(cgstr
[int(part
)] for part
in parts
if part
.isdecimal())
102 if not filename
.startswith('/'):
103 filename
= '/' + filename
107 def _decrypt_url_params(encrypted_params
):
108 params
= VideaIE
.rc4(
109 base64
.b64decode(encrypted_params
), 'xkt3a41psizxrh9l').split('-')
110 # sign, token, timestamp
111 return params
[1], params
[2], params
[3]
113 def _real_extract(self
, url
):
114 scheme
= 'https' if url
.startswith('https') else 'http'
116 audio_id
= self
._match
_id
(url
)
117 audio_info
= self
._download
_json
(
118 f
'{scheme}://m.ximalaya.com/tracks/{audio_id}.json', audio_id
,
119 'Downloading info json', 'Unable to download info file')
122 # NOTE: VIP-restricted audio
123 if audio_info
.get('is_paid'):
124 ts
= int(time
.time())
125 vip_info
= self
._download
_json
(
126 f
'{scheme}://mpay.ximalaya.com/mobile/track/pay/{audio_id}/{ts}',
127 audio_id
, 'Downloading VIP info json', 'Unable to download VIP info file',
128 query
={'device': 'pc', 'isBackend': 'true', '_': ts
})
129 filename
= self
._decrypt
_filename
(vip_info
['fileId'], vip_info
['seed'])
130 sign
, token
, timestamp
= self
._decrypt
_url
_params
(vip_info
['ep'])
131 vip_url
= update_url_query(
132 f
'{vip_info["domain"]}/download/{vip_info["apiVersion"]}{filename}', {
135 'timestamp': timestamp
,
136 'buy_key': vip_info
['buyKey'],
137 'duration': vip_info
['duration'],
144 if '_preview_' in vip_url
:
146 f
'This tracks requires a VIP account. Using a sample instead. {self._login_hint()}')
148 'format_note': 'Sample',
150 **traverse_obj(vip_info
, {
151 'filesize': ('sampleLength', {int_or_none}
),
152 'duration': ('sampleDuration', {int_or_none}
),
156 fmt
.update(traverse_obj(vip_info
, {
157 'filesize': ('totalLength', {int_or_none}
),
158 'duration': ('duration', {int_or_none}
),
161 fmt
['abr'] = try_call(lambda: fmt
['filesize'] * 8 / fmt
['duration'] / 1024)
165 'format_id': f
'{bps}k',
166 'url': audio_info
[k
],
169 } for bps
, k
in ((24, 'play_path_32'), (64, 'play_path_64')) if audio_info
.get(k
)])
173 # cover pics kyes like: cover_url', 'cover_url_142'
174 if k
.startswith('cover_url'):
175 thumbnail
= {'name': k
, 'url': audio_info
[k
]}
176 if k
== 'cover_url_142':
177 thumbnail
['width'] = 180
178 thumbnail
['height'] = 180
179 thumbnails
.append(thumbnail
)
181 audio_uploader_id
= audio_info
.get('uid')
183 audio_description
= try_call(
184 lambda: audio_info
['intro'].replace('\r\n\r\n\r\n ', '\n').replace('\r\n', '\n'))
188 'uploader': audio_info
.get('nickname'),
189 'uploader_id': str_or_none(audio_uploader_id
),
190 'uploader_url': f
'{scheme}://www.ximalaya.com/zhubo/{audio_uploader_id}/' if audio_uploader_id
else None,
191 'title': audio_info
['title'],
192 'thumbnails': thumbnails
,
193 'description': audio_description
,
194 'categories': list(filter(None, [audio_info
.get('category_name')])),
195 'duration': audio_info
.get('duration'),
196 'view_count': audio_info
.get('play_count'),
197 'like_count': audio_info
.get('favorites_count'),
202 class XimalayaAlbumIE(XimalayaBaseIE
):
203 IE_NAME
= 'ximalaya:album'
204 IE_DESC
= '喜马拉雅FM 专辑'
205 _VALID_URL
= r
'https?://(?:www\.|m\.)?ximalaya\.com/(?:\d+/)?album/(?P<id>[0-9]+)'
207 'url': 'http://www.ximalaya.com/61425525/album/5534601/',
209 'title': '唐诗三百首(含赏析)',
212 'playlist_mincount': 323,
214 'url': 'https://www.ximalaya.com/album/6912905',
216 'title': '埃克哈特《修炼当下的力量》',
219 'playlist_mincount': 41,
222 def _real_extract(self
, url
):
223 playlist_id
= self
._match
_id
(url
)
225 first_page
= self
._fetch
_page
(playlist_id
, 1)
226 page_count
= math
.ceil(first_page
['trackTotalCount'] / first_page
['pageSize'])
228 entries
= InAdvancePagedList(
229 lambda idx
: self
._get
_entries
(self
._fetch
_page
(playlist_id
, idx
+ 1) if idx
else first_page
),
230 page_count
, first_page
['pageSize'])
232 title
= traverse_obj(first_page
, ('tracks', 0, 'albumTitle'), expected_type
=str)
234 return self
.playlist_result(entries
, playlist_id
, title
)
236 def _fetch_page(self
, playlist_id
, page_idx
):
237 return self
._download
_json
(
238 'https://www.ximalaya.com/revision/album/v1/getTracksList',
239 playlist_id
, note
=f
'Downloading tracks list page {page_idx}',
240 query
={'albumId': playlist_id
, 'pageNum': page_idx
})['data']
242 def _get_entries(self
, page_data
):
243 for e
in page_data
['tracks']:
244 yield self
.url_result(
245 self
._proto
_relative
_url
(f
'//www.ximalaya.com{e["url"]}'),
246 XimalayaIE
, e
.get('trackId'), e
.get('title'))