playlist.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. from youtube import util, yt_data_extract, proto, local_playlist
  2. from youtube import yt_app
  3. import settings
  4. import base64
  5. import urllib
  6. import json
  7. import string
  8. import gevent
  9. import math
  10. from flask import request
  11. import flask
  12. def playlist_ctoken(playlist_id, offset, include_shorts=True):
  13. offset = proto.uint(1, offset)
  14. offset = b'PT:' + proto.unpadded_b64encode(offset)
  15. offset = proto.string(15, offset)
  16. if not include_shorts:
  17. offset += proto.string(104, proto.uint(2, 1))
  18. continuation_info = proto.string(3, proto.percent_b64encode(offset))
  19. playlist_id = proto.string(2, 'VL' + playlist_id)
  20. pointless_nest = proto.string(80226972, playlist_id + continuation_info)
  21. return base64.urlsafe_b64encode(pointless_nest).decode('ascii')
  22. def playlist_first_page(playlist_id, report_text="Retrieved playlist",
  23. use_mobile=False):
  24. if use_mobile:
  25. url = 'https://m.youtube.com/playlist?list=' + playlist_id + '&pbj=1'
  26. content = util.fetch_url(
  27. url, util.mobile_xhr_headers,
  28. report_text=report_text, debug_name='playlist_first_page'
  29. )
  30. content = json.loads(content.decode('utf-8'))
  31. else:
  32. url = 'https://www.youtube.com/playlist?list=' + playlist_id + '&pbj=1'
  33. content = util.fetch_url(
  34. url, util.desktop_xhr_headers,
  35. report_text=report_text, debug_name='playlist_first_page'
  36. )
  37. content = json.loads(content.decode('utf-8'))
  38. return content
  39. def get_videos(playlist_id, page, include_shorts=True, use_mobile=False,
  40. report_text='Retrieved playlist'):
  41. # mobile requests return 20 videos per page
  42. if use_mobile:
  43. page_size = 20
  44. headers = util.mobile_xhr_headers
  45. # desktop requests return 100 videos per page
  46. else:
  47. page_size = 100
  48. headers = util.desktop_xhr_headers
  49. url = "https://m.youtube.com/playlist?ctoken="
  50. url += playlist_ctoken(playlist_id, (int(page)-1)*page_size,
  51. include_shorts=include_shorts)
  52. url += "&pbj=1"
  53. content = util.fetch_url(
  54. url, headers, report_text=report_text,
  55. debug_name='playlist_videos'
  56. )
  57. info = json.loads(content.decode('utf-8'))
  58. return info
  59. @yt_app.route('/playlist')
  60. def get_playlist_page():
  61. if 'list' not in request.args:
  62. abort(400)
  63. playlist_id = request.args.get('list')
  64. page = request.args.get('page', '1')
  65. if page == '1':
  66. first_page_json = playlist_first_page(playlist_id)
  67. this_page_json = first_page_json
  68. else:
  69. tasks = (
  70. gevent.spawn(
  71. playlist_first_page, playlist_id,
  72. report_text="Retrieved playlist info", use_mobile=True
  73. ),
  74. gevent.spawn(get_videos, playlist_id, page)
  75. )
  76. gevent.joinall(tasks)
  77. util.check_gevent_exceptions(*tasks)
  78. first_page_json, this_page_json = tasks[0].value, tasks[1].value
  79. info = yt_data_extract.extract_playlist_info(this_page_json)
  80. if info['error']:
  81. return flask.render_template('error.html', error_message=info['error'])
  82. if page != '1':
  83. info['metadata'] = yt_data_extract.extract_playlist_metadata(first_page_json)
  84. util.prefix_urls(info['metadata'])
  85. for item in info.get('items', ()):
  86. util.prefix_urls(item)
  87. util.add_extra_html_info(item)
  88. if 'id' in item:
  89. item['thumbnail'] = f"{settings.img_prefix}https://i.ytimg.com/vi/{item['id']}/hqdefault.jpg"
  90. item['url'] += '&list=' + playlist_id
  91. if item['index']:
  92. item['url'] += '&index=' + str(item['index'])
  93. video_count = yt_data_extract.deep_get(info, 'metadata', 'video_count')
  94. if video_count is None:
  95. video_count = 40
  96. return flask.render_template(
  97. 'playlist.html',
  98. header_playlist_names=local_playlist.get_playlist_names(),
  99. video_list=info.get('items', []),
  100. num_pages=math.ceil(video_count/100),
  101. parameters_dictionary=request.args,
  102. **info['metadata']
  103. ).encode('utf-8')