py_2772024bilibilivd.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. #coding=utf-8
  2. #!/usr/bin/python
  3. import sys
  4. import json
  5. import time
  6. from datetime import datetime
  7. from urllib.parse import quote, unquote
  8. sys.path.append('..')
  9. from base.spider import Spider
  10. class Spider(Spider): # 元类 默认的元类 type
  11. def getName(self):
  12. return "B站视频"
  13. def init(self, extend):
  14. try:
  15. self.extendDict = json.loads(extend)
  16. except:
  17. self.extendDict = {}
  18. def destroy(self):
  19. pass
  20. def isVideoFormat(self, url):
  21. pass
  22. def manualVideoCheck(self):
  23. pass
  24. def homeContent(self, filter):
  25. result = {}
  26. result['filters'] = {}
  27. cookie = ''
  28. if 'cookie' in self.extendDict:
  29. cookie = self.extendDict['cookie']
  30. if 'json' in self.extendDict:
  31. r = self.fetch(self.extendDict['json'], timeout=10)
  32. if 'cookie' in r.json():
  33. cookie = r.json()['cookie']
  34. if cookie == '':
  35. cookie = '{}'
  36. elif type(cookie) == str and cookie.startswith('http'):
  37. cookie = self.fetch(cookie, timeout=10).text.strip()
  38. try:
  39. if type(cookie) == dict:
  40. cookie = json.dumps(cookie, ensure_ascii=False)
  41. except:
  42. pass
  43. _, _, _ = self.getCookie(cookie)
  44. bblogin = self.getCache('bblogin')
  45. if bblogin:
  46. result['class'] = [{"type_name": "动态", "type_id": "动态"}, {"type_name": "收藏夹", "type_id": "收藏夹"}, {"type_name": "历史记录", "type_id": "历史记录"}]
  47. else:
  48. result['class'] = []
  49. if 'json' in self.extendDict:
  50. r = self.fetch(self.extendDict['json'], timeout=10)
  51. params = r.json()
  52. if 'classes' in params:
  53. result['class'] = result['class'] + params['classes']
  54. if filter:
  55. if 'filter' in params:
  56. result['filters'] = params['filter']
  57. elif 'categories' in self.extendDict or 'type' in self.extendDict:
  58. if 'categories' in self.extendDict:
  59. cateList = self.extendDict['categories'].split('#')
  60. else:
  61. cateList = self.extendDict['type'].split('#')
  62. for cate in cateList:
  63. result['class'].append({'type_name': cate, 'type_id': cate})
  64. if not 'class' in result:
  65. result['class'] = {"type_name": "沙雕动漫", "type_id": "沙雕动漫"}
  66. return result
  67. def homeVideoContent(self):
  68. result = {}
  69. cookie = ''
  70. if 'cookie' in self.extendDict:
  71. cookie = self.extendDict['cookie']
  72. if 'json' in self.extendDict:
  73. r = self.fetch(self.extendDict['json'], timeout=10)
  74. if 'cookie' in r.json():
  75. cookie = r.json()['cookie']
  76. if cookie == '':
  77. cookie = '{}'
  78. elif type(cookie) == str and cookie.startswith('http'):
  79. cookie = self.fetch(cookie, timeout=10).text.strip()
  80. try:
  81. if type(cookie) == dict:
  82. cookie = json.dumps(cookie, ensure_ascii=False)
  83. except:
  84. pass
  85. cookie, imgKey, subKey = self.getCookie(cookie)
  86. url = 'https://api.bilibili.com/x/web-interface/index/top/feed/rcmd?&y_num=1&fresh_type=3&feed_version=SEO_VIDEO&fresh_idx_1h=1&fetch_row=1&fresh_idx=1&brush=0&homepage_ver=1&ps=20'
  87. r = self.fetch(url, cookies=cookie, headers=self.header, timeout=5)
  88. data = json.loads(self.cleanText(r.text))
  89. try:
  90. result['list'] = []
  91. vodList = data['data']['item']
  92. for vod in vodList:
  93. aid = str(vod['id']).strip()
  94. title = self.removeHtmlTags(vod['title']).strip()
  95. img = vod['pic'].strip()
  96. remark = time.strftime('%H:%M:%S', time.gmtime(vod['duration']))
  97. if remark.startswith('00:'):
  98. remark = remark[3:]
  99. if remark == '00:00':
  100. continue
  101. result['list'].append({
  102. 'vod_id': aid,
  103. 'vod_name': title,
  104. 'vod_pic': img,
  105. 'vod_remarks': remark
  106. })
  107. except:
  108. pass
  109. return result
  110. def categoryContent(self, cid, page, filter, ext):
  111. page = int(page)
  112. result = {}
  113. videos = []
  114. cookie = ''
  115. pagecount = page
  116. if 'cookie' in self.extendDict:
  117. cookie = self.extendDict['cookie']
  118. if 'json' in self.extendDict:
  119. r = self.fetch(self.extendDict['json'], timeout=10)
  120. if 'cookie' in r.json():
  121. cookie = r.json()['cookie']
  122. if cookie == '':
  123. cookie = '{}'
  124. elif type(cookie) == str and cookie.startswith('http'):
  125. cookie = self.fetch(cookie, timeout=10).text.strip()
  126. try:
  127. if type(cookie) == dict:
  128. cookie = json.dumps(cookie, ensure_ascii=False)
  129. except:
  130. pass
  131. cookie, imgKey, subKey = self.getCookie(cookie)
  132. if cid == '动态':
  133. if page > 1:
  134. offset = self.getCache('offset')
  135. if not offset:
  136. offset = ''
  137. url = f'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all?timezone_offset=-480&type=all&offset={offset}&page={page}'
  138. else:
  139. url = f'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all?timezone_offset=-480&type=all&page={page}'
  140. r = self.fetch(url, cookies=cookie, headers=self.header, timeout=5)
  141. data = json.loads(self.cleanText(r.text))
  142. self.setCache('offset', data['data']['offset'])
  143. vodList = data['data']['items']
  144. if data['data']['has_more']:
  145. pagecount = page + 1
  146. for vod in vodList:
  147. if vod['type'] != 'DYNAMIC_TYPE_AV':
  148. continue
  149. vid = str(vod['modules']['module_dynamic']['major']['archive']['aid']).strip()
  150. remark = vod['modules']['module_dynamic']['major']['archive']['duration_text'].strip()
  151. title = self.removeHtmlTags(vod['modules']['module_dynamic']['major']['archive']['title']).strip()
  152. img = vod['modules']['module_dynamic']['major']['archive']['cover']
  153. videos.append({
  154. "vod_id": vid,
  155. "vod_name": title,
  156. "vod_pic": img,
  157. "vod_remarks": remark
  158. })
  159. elif cid == "收藏夹":
  160. userid = self.getUserid(cookie)
  161. if userid is None:
  162. return {}, 1
  163. url = f'http://api.bilibili.com/x/v3/fav/folder/created/list-all?up_mid={userid}&jsonp=jsonp'
  164. r = self.fetch(url, cookies=cookie, headers=self.header, timeout=5)
  165. data = json.loads(self.cleanText(r.text))
  166. vodList = data['data']['list']
  167. pagecount = page
  168. for vod in vodList:
  169. vid = vod['id']
  170. title = vod['title'].strip()
  171. remark = vod['media_count']
  172. img = 'https://api-lmteam.koyeb.app/files/shoucang.png'
  173. videos.append({
  174. "vod_id": f'fav&&&{vid}',
  175. "vod_name": title,
  176. "vod_pic": img,
  177. "vod_tag": 'folder',
  178. "vod_remarks": remark
  179. })
  180. elif cid.startswith('fav&&&'):
  181. cid = cid[6:]
  182. url = f'http://api.bilibili.com/x/v3/fav/resource/list?media_id={cid}&pn={page}&ps=20&platform=web&type=0'
  183. r = self.fetch(url, cookies=cookie, headers=self.header, timeout=5)
  184. data = json.loads(self.cleanText(r.text))
  185. if data['data']['has_more']:
  186. pagecount = page + 1
  187. else:
  188. pagecount = page
  189. vodList = data['data']['medias']
  190. for vod in vodList:
  191. vid = str(vod['id']).strip()
  192. title = self.removeHtmlTags(vod['title']).replace(""", '"')
  193. img = vod['cover'].strip()
  194. remark = time.strftime('%H:%M:%S', time.gmtime(vod['duration']))
  195. if remark.startswith('00:'):
  196. remark = remark[3:]
  197. videos.append({
  198. "vod_id": vid,
  199. "vod_name": title,
  200. "vod_pic": img,
  201. "vod_remarks": remark
  202. })
  203. elif cid.startswith('UP主&&&'):
  204. cid = cid[6:]
  205. params = {'mid': cid, 'ps': 30, 'pn': page}
  206. params = self.encWbi(params, imgKey, subKey)
  207. url = 'https://api.bilibili.com/x/space/wbi/arc/search?'
  208. for key in params:
  209. url += f'&{key}={quote(params[key])}'
  210. r = self.fetch(url, cookies=cookie, headers=self.header, timeout=5)
  211. data = json.loads(self.cleanText(r.text))
  212. if page < data['data']['page']['count']:
  213. pagecount = page + 1
  214. else:
  215. pagecount = page
  216. if page == 1:
  217. bizId = self.regStr(reg='play/(.*?)\?', src=data['data']['episodic_button']['uri'])
  218. videos = [{"vod_id": f'UP主&&&{bizId}', "vod_name": '播放列表'}]
  219. vodList = data['data']['list']['vlist']
  220. for vod in vodList:
  221. vid = str(vod['aid']).strip()
  222. title = self.removeHtmlTags(vod['title']).replace("&quot;", '"')
  223. img = vod['pic'].strip()
  224. remarkinfos = vod['length'].split(':')
  225. minutes = int(remarkinfos[0])
  226. if minutes >= 60:
  227. hours = str(minutes // 60)
  228. minutes = str(minutes % 60)
  229. if len(hours) == 1:
  230. hours = '0' + hours
  231. if len(minutes) == 1:
  232. minutes = '0' + minutes
  233. remark = hours + ':' + minutes + ':' + remarkinfos[1]
  234. else:
  235. remark = vod['length']
  236. videos.append({
  237. "vod_id": vid,
  238. "vod_name": title,
  239. "vod_pic": img,
  240. "vod_remarks": remark
  241. })
  242. elif cid == '历史记录':
  243. url = f'http://api.bilibili.com/x/v2/history?pn={page}'
  244. r = self.fetch(url, cookies=cookie, headers=self.header, timeout=5)
  245. data = json.loads(self.cleanText(r.text))
  246. if len(data['data']) == 300:
  247. pagecount = page + 1
  248. else:
  249. pagecount = page
  250. vodList = data['data']
  251. for vod in vodList:
  252. if vod['duration'] <= 0:
  253. continue
  254. vid = str(vod["aid"]).strip()
  255. img = vod["pic"].strip()
  256. title = self.removeHtmlTags(vod["title"]).replace("&quot;", '"')
  257. if vod['progress'] != -1:
  258. process = time.strftime('%H:%M:%S', time.gmtime(vod['progress']))
  259. totalTime = time.strftime('%H:%M:%S', time.gmtime(vod['duration']))
  260. if process.startswith('00:'):
  261. process = process[3:]
  262. if totalTime.startswith('00:'):
  263. totalTime = totalTime[3:]
  264. remark = process + '|' + totalTime
  265. videos.append({
  266. "vod_id": vid,
  267. "vod_name": title,
  268. "vod_pic": img,
  269. "vod_remarks": remark
  270. })
  271. else:
  272. url = 'https://api.bilibili.com/x/web-interface/search/type?search_type=video&keyword={}&page={}'
  273. for key in ext:
  274. if key == 'tid':
  275. cid = ext[key]
  276. continue
  277. url += f'&{key}={ext[key]}'
  278. url = url.format(cid, page)
  279. r = self.fetch(url, cookies=cookie, headers=self.header, timeout=5)
  280. data = json.loads(self.cleanText(r.text))
  281. pagecount = data['data']['numPages']
  282. vodList = data['data']['result']
  283. for vod in vodList:
  284. if vod['type'] != 'video':
  285. continue
  286. vid = str(vod['aid']).strip()
  287. title = self.removeHtmlTags(self.cleanText(vod['title']))
  288. img = 'https:' + vod['pic'].strip()
  289. remarkinfo = vod['duration'].split(':')
  290. minutes = int(remarkinfo[0])
  291. seconds = remarkinfo[1]
  292. if len(seconds) == 1:
  293. seconds = '0' + seconds
  294. if minutes >= 60:
  295. hour = str(minutes // 60)
  296. minutes = str(minutes % 60)
  297. if len(hour) == 1:
  298. hour = '0' + hour
  299. if len(minutes) == 1:
  300. minutes = '0' + minutes
  301. remark = f'{hour}:{minutes}:{seconds}'
  302. else:
  303. minutes = str(minutes)
  304. if len(minutes) == 1:
  305. minutes = '0' + minutes
  306. remark = f'{minutes}:{seconds}'
  307. videos.append({
  308. "vod_id": vid,
  309. "vod_name": title,
  310. "vod_pic": img,
  311. "vod_remarks": remark
  312. })
  313. lenvideos = len(videos)
  314. result['list'] = videos
  315. result['page'] = page
  316. result['pagecount'] = pagecount
  317. result['limit'] = lenvideos
  318. result['total'] = lenvideos
  319. return result
  320. def detailContent(self, did):
  321. aid = did[0]
  322. if aid.startswith('UP主&&&'):
  323. bizId = aid[6:]
  324. oid = ''
  325. url = f'https://api.bilibili.com/x/v2/medialist/resource/list?mobi_app=web&type=1&oid={oid}&biz_id={bizId}&otype=1&ps=100&direction=false&desc=true&sort_field=1&tid=0&with_current=false'
  326. r = self.fetch(url, headers=self.header, timeout=5)
  327. videoList = r.json()['data']['media_list']
  328. vod = {
  329. "vod_id": aid,
  330. "vod_name": '播放列表',
  331. 'vod_play_from': 'B站视频'
  332. }
  333. playUrl = ''
  334. for video in videoList:
  335. remark = time.strftime('%H:%M:%S', time.gmtime(video['duration']))
  336. name = self.removeHtmlTags(video['title']).strip().replace("#", "-").replace('$', '*')
  337. if remark.startswith('00:'):
  338. remark = remark[3:]
  339. playUrl += f"[{remark}]/{name}$bvid&&&{video['bv_id']}#"
  340. vod['vod_play_url'] = playUrl.strip('#')
  341. result = {'list': [vod]}
  342. return result
  343. url = f"https://api.bilibili.com/x/web-interface/view?aid={aid}"
  344. r = self.fetch(url, headers=self.header, timeout=10)
  345. data = json.loads(self.cleanText(r.text))
  346. vod = {
  347. "vod_id": aid,
  348. "vod_name": self.removeHtmlTags(data['data']['title']),
  349. "vod_pic": data['data']['pic'],
  350. "type_name": data['data']['tname'],
  351. "vod_year": datetime.fromtimestamp(data['data']['pubdate']).strftime('%Y-%m-%d %H:%M:%S'),
  352. "vod_content": data['data']['desc'].replace('\xa0', ' ').replace('\n\n', '\n').strip(),
  353. "vod_director": '[a=cr:{{"id":"UP主&&&{}","name":"{}"}}/]{}[/a]'.format(data['data']['owner']['mid'], data['data']['owner']['name'], data['data']['owner']['name'])
  354. }
  355. videoList = data['data']['pages']
  356. playUrl = ''
  357. for video in videoList:
  358. remark = time.strftime('%H:%M:%S', time.gmtime(video['duration']))
  359. name = self.removeHtmlTags(video['part']).strip().replace("#", "-").replace('$', '*')
  360. if remark.startswith('00:'):
  361. remark = remark[3:]
  362. playUrl = playUrl + f"[{remark}]/{name}${aid}_{video['cid']}#"
  363. url = f'https://api.bilibili.com/x/web-interface/archive/related?aid={aid}'
  364. r = self.fetch(url, headers=self.header, timeout=5)
  365. data = json.loads(self.cleanText(r.text))
  366. videoList = data['data']
  367. playUrl = playUrl.strip('#') + '$$$'
  368. for video in videoList:
  369. remark = time.strftime('%H:%M:%S', time.gmtime(video['duration']))
  370. if remark.startswith('00:'):
  371. remark = remark[3:]
  372. name = self.removeHtmlTags(video['title']).strip().replace("#", "-").replace('$', '*')
  373. playUrl = playUrl + '[{}]/{}${}_{}#'.format(remark, name, video['aid'], video['cid'])
  374. vod['vod_play_from'] = 'B站视频$$$相关视频'
  375. vod['vod_play_url'] = playUrl.strip('#')
  376. result = {
  377. 'list': [
  378. vod
  379. ]
  380. }
  381. return result
  382. def searchContent(self, key, quick):
  383. return self.searchContentPage(key, quick, '1')
  384. def searchContentPage(self, key, quick, page):
  385. videos = []
  386. if quick:
  387. result = {
  388. 'list': videos
  389. }
  390. return result, 14400
  391. cookie = ''
  392. if 'cookie' in self.extendDict:
  393. cookie = self.extendDict['cookie']
  394. if 'json' in self.extendDict:
  395. r = self.fetch(self.extendDict['json'], timeout=10)
  396. if 'cookie' in r.json():
  397. cookie = r.json()['cookie']
  398. if cookie == '':
  399. cookie = '{}'
  400. elif type(cookie) == str and cookie.startswith('http'):
  401. cookie = self.fetch(cookie, timeout=10).text.strip()
  402. try:
  403. if type(cookie) == dict:
  404. cookie = json.dumps(cookie, ensure_ascii=False)
  405. except:
  406. pass
  407. cookie, _, _ = self.getCookie(cookie)
  408. url = f'https://api.bilibili.com/x/web-interface/search/type?search_type=video&keyword={key}&page={page}'
  409. r = self.fetch(url, headers=self.header, cookies=cookie, timeout=5)
  410. jo = json.loads(self.cleanText(r.text))
  411. if 'result' not in jo['data']:
  412. return {'list': videos}, 1
  413. vodList = jo['data']['result']
  414. for vod in vodList:
  415. aid = str(vod['aid']).strip()
  416. title = self.removeHtmlTags(self.cleanText(vod['title']))
  417. img = 'https:' + vod['pic'].strip()
  418. remarkinfo = vod['duration'].split(':')
  419. minutes = int(remarkinfo[0])
  420. seconds = remarkinfo[1]
  421. if len(seconds) == 1:
  422. seconds = '0' + seconds
  423. if minutes >= 60:
  424. hour = str(minutes // 60)
  425. minutes = str(minutes % 60)
  426. if len(hour) == 1:
  427. hour = '0' + hour
  428. if len(minutes) == 1:
  429. minutes = '0' + minutes
  430. remark = f'{hour}:{minutes}:{seconds}'
  431. else:
  432. minutes = str(minutes)
  433. if len(minutes) == 1:
  434. minutes = '0' + minutes
  435. remark = f'{minutes}:{seconds}'
  436. videos.append({
  437. "vod_id": aid,
  438. "vod_name": title,
  439. "vod_pic": img,
  440. "vod_remarks": remark
  441. })
  442. result = {
  443. 'list': videos
  444. }
  445. return result
  446. def playerContent(self, flag, pid, vipFlags):
  447. result = {}
  448. if pid.startswith('bvid&&&'):
  449. url = "https://api.bilibili.com/x/web-interface/view?bvid={}".format(pid[7:])
  450. r = self.fetch(url, headers=self.header, timeout=10)
  451. data = r.json()['data']
  452. aid = data['aid']
  453. cid = data['cid']
  454. else:
  455. idList = pid.split("_")
  456. aid = idList[0]
  457. cid = idList[1]
  458. url = 'https://api.bilibili.com/x/player/playurl?avid={}&cid={}&qn=120&fnval=4048&fnver=0&fourk=1'.format(aid, cid)
  459. cookie = ''
  460. extendDict = self.extendDict
  461. if 'cookie' in extendDict:
  462. cookie = extendDict['cookie']
  463. if 'json' in extendDict:
  464. r = self.fetch(extendDict['json'], timeout=10)
  465. if 'cookie' in r.json():
  466. cookie = r.json()['cookie']
  467. if cookie == '':
  468. cookie = '{}'
  469. elif type(cookie) == str and cookie.startswith('http'):
  470. cookie = self.fetch(cookie, timeout=10).text.strip()
  471. try:
  472. if type(cookie) == dict:
  473. cookie = json.dumps(cookie, ensure_ascii=False)
  474. except:
  475. pass
  476. cookiesDict, _, _ = self.getCookie(cookie)
  477. cookies = quote(json.dumps(cookiesDict))
  478. if 'thread' in extendDict:
  479. thread = str(extendDict['thread'])
  480. else:
  481. thread = '0'
  482. result["parse"] = 0
  483. result["playUrl"] = ''
  484. result["url"] = f'http://127.0.0.1:9978/proxy?do=py&type=mpd&cookies={cookies}&url={quote(url)}&aid={aid}&cid={cid}&thread={thread}'
  485. result["header"] = self.header
  486. result['danmaku'] = 'https://api.bilibili.com/x/v1/dm/list.so?oid={}'.format(cid)
  487. result["format"] = 'application/dash+xml'
  488. return result
  489. def localProxy(self, params):
  490. if params['type'] == "mpd":
  491. return self.proxyMpd(params)
  492. if params['type'] == "media":
  493. return self.proxyMedia(params)
  494. return None
  495. def proxyMpd(self, params):
  496. content, durlinfos, mediaType = self.getDash(params)
  497. if mediaType == 'mpd':
  498. return [200, "application/dash+xml", content]
  499. else:
  500. url = content
  501. durlinfo = durlinfos['durl'][0]['backup_url']
  502. try:
  503. r = self.fetch(url, headers=self.header, stream=True, timeout=1)
  504. statusCode = r.status_code
  505. try:
  506. r.close()
  507. except:
  508. pass
  509. except:
  510. try:
  511. r.close()
  512. except:
  513. pass
  514. statusCode = 404
  515. for url in durlinfo:
  516. try:
  517. r = self.fetch(url, headers=self.header, stream=True, timeout=1)
  518. statusCode = r.status_code
  519. except:
  520. statusCode = 404
  521. if statusCode == 200:
  522. break
  523. try:
  524. r.close()
  525. except:
  526. pass
  527. if statusCode != 200 and self.retry == 0:
  528. self.retry += 1
  529. self.proxyMedia(params, True)
  530. header = self.header.copy()
  531. if 'range' in params:
  532. header['Range'] = params['range']
  533. if '127.0.0.1:7777' in url:
  534. header['Location'] = url
  535. return [302, "video/MP2T", None, header]
  536. return [206, "application/octet-stream", self.fetch(url, headers=header, stream=True).content]
  537. def proxyMedia(self, params, forceRefresh=False):
  538. _, dashinfos, _ = self.getDash(params)
  539. if 'videoid' in params:
  540. videoid = int(params['videoid'])
  541. dashinfo = dashinfos['video'][videoid]
  542. url = dashinfo['baseUrl']
  543. elif 'audioid' in params:
  544. audioid = int(params['audioid'])
  545. dashinfo = dashinfos['audio'][audioid]
  546. url = dashinfo['baseUrl']
  547. else:
  548. return [404, "text/plain", ""]
  549. try:
  550. r = self.fetch(url, headers=self.header, stream=True, timeout=1)
  551. statusCode = r.status_code
  552. try:
  553. r.close()
  554. except:
  555. pass
  556. except:
  557. try:
  558. r.close()
  559. except:
  560. pass
  561. statusCode = 404
  562. for url in dashinfo['backupUrl']:
  563. try:
  564. r = self.fetch(url, headers=self.header, stream=True, timeout=1)
  565. statusCode = r.status_code
  566. except:
  567. statusCode = 404
  568. if statusCode == 200:
  569. break
  570. try:
  571. r.close()
  572. except:
  573. pass
  574. if statusCode != 200 and self.retry == 0:
  575. self.retry += 1
  576. self.proxyMedia(params, True)
  577. header = self.header.copy()
  578. if 'range' in params:
  579. header['Range'] = params['range']
  580. return [206, "application/octet-stream", self.fetch(url, headers=header, stream=True).content]
  581. def getDash(self, params, forceRefresh=False):
  582. aid = params['aid']
  583. cid = params['cid']
  584. url = unquote(params['url'])
  585. if 'thread' in params:
  586. thread = params['thread']
  587. else:
  588. thread = 0
  589. header = self.header.copy()
  590. cookieDict = json.loads(params['cookies'])
  591. key = f'bilivdmpdcache_{aid}_{cid}'
  592. if forceRefresh:
  593. self.delCache(key)
  594. else:
  595. data = self.getCache(key)
  596. if data:
  597. return data['content'], data['dashinfos'], data['type']
  598. cookies = cookieDict.copy()
  599. r = self.fetch(url, cookies=cookies, headers=header, timeout=5)
  600. data = json.loads(self.cleanText(r.text))
  601. if data['code'] != 0:
  602. return '', {}, ''
  603. if not 'dash' in data['data']:
  604. purl = data['data']['durl'][0]['url']
  605. try:
  606. expiresAt = int(self.regStr(reg='deadline=(\d+)', src=purl).group(1)) - 60
  607. except:
  608. expiresAt = int(time.time()) + 600
  609. if int(thread) > 0:
  610. try:
  611. self.fetch('http://127.0.0.1:7777')
  612. except:
  613. self.fetch('http://127.0.0.1:9978/go')
  614. purl = f'http://127.0.0.1:7777?url={quote(purl)}&thread={thread}'
  615. self.setCache(key, {'content': purl, 'type': 'mp4', 'dashinfos': data['data'], 'expiresAt': expiresAt})
  616. return purl, data['data'], 'mp4'
  617. dashinfos = data['data']['dash']
  618. duration = dashinfos['duration']
  619. minBufferTime = dashinfos['minBufferTime']
  620. videoinfo = ''
  621. videoid = 0
  622. deadlineList = []
  623. # videoList = sorted(dashinfos['video'], key=lambda x: x['bandwidth'], reverse=True)
  624. for video in dashinfos['video']:
  625. try:
  626. deadline = int(self.regStr(reg='deadline=(\d+)', src=video['baseUrl']).group(1))
  627. except:
  628. deadline = int(time.time()) + 600
  629. deadlineList.append(deadline)
  630. codecs = video['codecs']
  631. bandwidth = video['bandwidth']
  632. frameRate = video['frameRate']
  633. height = video['height']
  634. width = video['width']
  635. void = video['id']
  636. vidparams = params.copy()
  637. vidparams['videoid'] = videoid
  638. baseUrl = f'http://127.0.0.1:9978/proxy?do=py&type=media&cookies={quote(json.dumps(cookies))}&url={quote(url)}&aid={aid}&cid={cid}&videoid={videoid}'
  639. videoinfo = videoinfo + f""" <Representation bandwidth="{bandwidth}" codecs="{codecs}" frameRate="{frameRate}" height="{height}" id="{void}" width="{width}">
  640. <BaseURL>{baseUrl}</BaseURL>
  641. <SegmentBase indexRange="{video['SegmentBase']['indexRange']}">
  642. <Initialization range="{video['SegmentBase']['Initialization']}"/>
  643. </SegmentBase>
  644. </Representation>\n"""
  645. videoid += 1
  646. audioinfo = ''
  647. audioid = 0
  648. # audioList = sorted(dashinfos['audio'], key=lambda x: x['bandwidth'], reverse=True)
  649. for audio in dashinfos['audio']:
  650. try:
  651. deadline = int(self.regStr(reg='deadline=(\d+)', src=audio['baseUrl']).group(1))
  652. except:
  653. deadline = int(time.time()) + 600
  654. deadlineList.append(deadline)
  655. bandwidth = audio['bandwidth']
  656. codecs = audio['codecs']
  657. aoid = audio['id']
  658. aidparams = params.copy()
  659. aidparams['audioid'] = audioid
  660. baseUrl = f'http://127.0.0.1:9978/proxy?do=py&type=media&cookies={quote(json.dumps(cookies))}&url={quote(url)}&aid={aid}&cid={cid}&audioid={audioid}'
  661. audioinfo = audioinfo + f""" <Representation audioSamplingRate="44100" bandwidth="{bandwidth}" codecs="{codecs}" id="{aoid}">
  662. <BaseURL>{baseUrl}</BaseURL>
  663. <SegmentBase indexRange="{audio['SegmentBase']['indexRange']}">
  664. <Initialization range="{audio['SegmentBase']['Initialization']}"/>
  665. </SegmentBase>
  666. </Representation>\n"""
  667. audioid += 1
  668. mpd = f"""<?xml version="1.0" encoding="UTF-8"?>
  669. <MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" type="static" mediaPresentationDuration="PT{duration}S" minBufferTime="PT{minBufferTime}S">
  670. <Period>
  671. <AdaptationSet mimeType="video/mp4" startWithSAP="1" scanType="progressive" segmentAlignment="true">
  672. {videoinfo.strip()}
  673. </AdaptationSet>
  674. <AdaptationSet mimeType="audio/mp4" startWithSAP="1" segmentAlignment="true" lang="und">
  675. {audioinfo.strip()}
  676. </AdaptationSet>
  677. </Period>
  678. </MPD>"""
  679. expiresAt = min(deadlineList) - 60
  680. self.setCache(key, {'type': 'mpd', 'content': mpd.replace('&', '&amp;'), 'dashinfos': dashinfos, 'expiresAt': expiresAt})
  681. return mpd.replace('&', '&amp;'), dashinfos, 'mpd'
  682. def getCookie(self, cookie):
  683. if '{' in cookie and '}' in cookie:
  684. cookies = json.loads(cookie)
  685. else:
  686. cookies = dict([co.strip().split('=', 1) for co in cookie.strip(';').split(';')])
  687. bblogin = self.getCache('bblogin')
  688. if bblogin:
  689. imgKey = bblogin['imgKey']
  690. subKey = bblogin['subKey']
  691. return cookies, imgKey, subKey
  692. header = {
  693. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36"
  694. }
  695. r = self.fetch("http://api.bilibili.com/x/web-interface/nav", cookies=cookies, headers=header, timeout=10)
  696. data = json.loads(r.text)
  697. code = data["code"]
  698. if code == 0:
  699. imgKey = data['data']['wbi_img']['img_url'].rsplit('/', 1)[1].split('.')[0]
  700. subKey = data['data']['wbi_img']['sub_url'].rsplit('/', 1)[1].split('.')[0]
  701. self.setCache('bblogin', {'imgKey': imgKey, 'subKey': subKey, 'expiresAt': int(time.time()) + 1200})
  702. return cookies, imgKey, subKey
  703. r = self.fetch("https://www.bilibili.com/", headers=header, timeout=5)
  704. cookies = r.cookies.get_dict()
  705. imgKey = ''
  706. subKey = ''
  707. return cookies, imgKey, subKey
  708. def getUserid(self, cookie):
  709. # 获取自己的userid(cookies拥有者)
  710. url = 'http://api.bilibili.com/x/space/myinfo'
  711. r = self.fetch(url, cookies=cookie, headers=self.header, timeout=5)
  712. data = json.loads(self.cleanText(r.text))
  713. if data['code'] == 0:
  714. return data['data']['mid']
  715. def removeHtmlTags(self, src):
  716. from re import sub, compile
  717. clean = compile('<.*?>')
  718. return sub(clean, '', src)
  719. def encWbi(self, params, imgKey, subKey):
  720. from hashlib import md5
  721. from functools import reduce
  722. from urllib.parse import urlencode
  723. mixinKeyEncTab = [46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52]
  724. orig = imgKey + subKey
  725. mixinKey = reduce(lambda s, i: s + orig[i], mixinKeyEncTab, '')[:32]
  726. params['wts'] = round(time.time()) # 添加 wts 字段
  727. params = dict(sorted(params.items())) # 按照 key 重排参数
  728. # 过滤 value 中的 "!'()*" 字符
  729. params = {
  730. k: ''.join(filter(lambda chr: chr not in "!'()*", str(v)))
  731. for k, v
  732. in params.items()
  733. }
  734. query = urlencode(params) # 序列化参数
  735. params['w_rid'] = md5((query + mixinKey).encode()).hexdigest() # 计算 w_rid
  736. return params
  737. retry = 0
  738. header = {
  739. "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36",
  740. "Referer": "https://www.bilibili.com"
  741. }