Advertisement
Humour

Untitled

Feb 14th, 2017
134
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.00 KB | None | 0 0
  1. import datetime
  2. import traceback
  3. from collections import deque
  4. from itertools import islice
  5. from random import shuffle
  6.  
  7. from .utils import get_header
  8. from .entry import URLPlaylistEntry
  9. from .exceptions import ExtractionError, WrongEntryTypeError
  10. from .lib.event_emitter import EventEmitter
  11.  
  12.  
  13. class Playlist(EventEmitter):
  14. """
  15. A playlist is manages the list of songs that will be played.
  16. """
  17.  
  18. def __init__(self, bot):
  19. super().__init__()
  20. self.bot = bot
  21. self.loop = bot.loop
  22. self.downloader = bot.downloader
  23. self.entries = deque()
  24.  
  25. def __iter__(self):
  26. return iter(self.entries)
  27.  
  28. def shuffle(self):
  29. shuffle(self.entries)
  30.  
  31. def clear(self):
  32. self.entries.clear()
  33.  
  34. async def add_entry(self, song_url, **meta):
  35. """
  36. Validates and adds a song_url to be played. This does not start the download of the song.
  37.  
  38. Returns the entry & the position it is in the queue.
  39.  
  40. :param song_url: The song url to add to the playlist.
  41. :param meta: Any additional metadata to add to the playlist entry.
  42. """
  43.  
  44. try:
  45. info = await self.downloader.extract_info(self.loop, song_url, download=False)
  46. except Exception as e:
  47. raise ExtractionError('Could not extract information from {}\n\n{}'.format(song_url, e))
  48.  
  49. if not info:
  50. raise ExtractionError('Could not extract information from %s' % song_url)
  51.  
  52. # TODO: Sort out what happens next when this happens
  53. if info.get('_type', None) == 'playlist':
  54. raise WrongEntryTypeError("This is a playlist.", True, info.get('webpage_url', None) or info.get('url', None))
  55.  
  56. if info['extractor'] in ['generic', 'Dropbox']:
  57. try:
  58. # unfortunately this is literally broken
  59. # https://github.com/KeepSafe/aiohttp/issues/758
  60. # https://github.com/KeepSafe/aiohttp/issues/852
  61. content_type = await get_header(self.bot.aiosession, info['url'], 'CONTENT-TYPE')
  62. print("Got content type", content_type)
  63.  
  64. except Exception as e:
  65. print("[Warning] Failed to get content type for url %s (%s)" % (song_url, e))
  66. content_type = None
  67.  
  68. if content_type:
  69. if content_type.startswith(('application/', 'image/')):
  70. if '/ogg' not in content_type: # How does a server say `application/ogg` what the actual fuck
  71. raise ExtractionError("Invalid content type \"%s\" for url %s" % (content_type, song_url))
  72.  
  73. elif not content_type.startswith(('audio/', 'video/')):
  74. print("[Warning] Questionable content type \"%s\" for url %s" % (content_type, song_url))
  75.  
  76. entry = URLPlaylistEntry(
  77. self,
  78. song_url,
  79. info.get('title', 'Untitled'),
  80. info.get('duration', 0) or 0,
  81. self.downloader.ytdl.prepare_filename(info),
  82. **meta
  83. )
  84. self._add_entry(entry)
  85. return entry, len(self.entries)
  86.  
  87. async def import_from(self, playlist_url, **meta):
  88. """
  89. Imports the songs from `playlist_url` and queues them to be played.
  90.  
  91. Returns a list of `entries` that have been enqueued.
  92.  
  93. :param playlist_url: The playlist url to be cut into individual urls and added to the playlist
  94. :param meta: Any additional metadata to add to the playlist entry
  95. """
  96. position = len(self.entries) + 1
  97. entry_list = []
  98.  
  99. try:
  100. info = await self.downloader.safe_extract_info(self.loop, playlist_url, download=False)
  101. except Exception as e:
  102. raise ExtractionError('Could not extract information from {}\n\n{}'.format(playlist_url, e))
  103.  
  104. if not info:
  105. raise ExtractionError('Could not extract information from %s' % playlist_url)
  106.  
  107. # Once again, the generic extractor fucks things up.
  108. if info.get('extractor', None) == 'generic':
  109. url_field = 'url'
  110. else:
  111. url_field = 'webpage_url'
  112.  
  113. baditems = 0
  114. for items in info['entries']:
  115. if items:
  116. try:
  117. entry = URLPlaylistEntry(
  118. self,
  119. items[url_field],
  120. items.get('title', 'Untitled'),
  121. items.get('duration', 0) or 0,
  122. self.downloader.ytdl.prepare_filename(items),
  123. **meta
  124. )
  125.  
  126. self._add_entry(entry)
  127. entry_list.append(entry)
  128. except:
  129. baditems += 1
  130. # Once I know more about what's happening here I can add a proper message
  131. traceback.print_exc()
  132. print(items)
  133. print("Could not add item")
  134. else:
  135. baditems += 1
  136.  
  137. if baditems:
  138. print("Skipped %s bad entries" % baditems)
  139.  
  140. return entry_list, position
  141.  
  142. async def async_process_youtube_playlist(self, playlist_url, **meta):
  143. """
  144. Processes youtube playlists links from `playlist_url` in a questionable, async fashion.
  145.  
  146. :param playlist_url: The playlist url to be cut into individual urls and added to the playlist
  147. :param meta: Any additional metadata to add to the playlist entry
  148. """
  149.  
  150. try:
  151. info = await self.downloader.safe_extract_info(self.loop, playlist_url, download=False, process=False)
  152. except Exception as e:
  153. raise ExtractionError('Could not extract information from {}\n\n{}'.format(playlist_url, e))
  154.  
  155. if not info:
  156. raise ExtractionError('Could not extract information from %s' % playlist_url)
  157.  
  158. gooditems = []
  159. baditems = 0
  160. for entry_data in info['entries']:
  161. if entry_data:
  162. baseurl = info['webpage_url'].split('playlist?list=')[0]
  163. song_url = baseurl + 'watch?v=%s' % entry_data['id']
  164.  
  165. try:
  166. entry, elen = await self.add_entry(song_url, **meta)
  167. gooditems.append(entry)
  168. except ExtractionError:
  169. baditems += 1
  170. except Exception as e:
  171. baditems += 1
  172. print("There was an error adding the song {}: {}: {}\n".format(
  173. entry_data['id'], e.__class__.__name__, e))
  174. else:
  175. baditems += 1
  176.  
  177. if baditems:
  178. print("Skipped %s bad entries" % baditems)
  179.  
  180. return gooditems
  181.  
  182. async def async_process_sc_bc_playlist(self, playlist_url, **meta):
  183. """
  184. Processes soundcloud set and bancdamp album links from `playlist_url` in a questionable, async fashion.
  185.  
  186. :param playlist_url: The playlist url to be cut into individual urls and added to the playlist
  187. :param meta: Any additional metadata to add to the playlist entry
  188. """
  189.  
  190. try:
  191. info = await self.downloader.safe_extract_info(self.loop, playlist_url, download=False, process=False)
  192. except Exception as e:
  193. raise ExtractionError('Could not extract information from {}\n\n{}'.format(playlist_url, e))
  194.  
  195. if not info:
  196. raise ExtractionError('Could not extract information from %s' % playlist_url)
  197.  
  198. gooditems = []
  199. baditems = 0
  200. for entry_data in info['entries']:
  201. if entry_data:
  202. song_url = entry_data['url']
  203.  
  204. try:
  205. entry, elen = await self.add_entry(song_url, **meta)
  206. gooditems.append(entry)
  207. except ExtractionError:
  208. baditems += 1
  209. except Exception as e:
  210. baditems += 1
  211. print("There was an error adding the song {}: {}: {}\n".format(
  212. entry_data['id'], e.__class__.__name__, e))
  213. else:
  214. baditems += 1
  215.  
  216. if baditems:
  217. print("Skipped %s bad entries" % baditems)
  218.  
  219. return gooditems
  220.  
  221. def _add_entry(self, entry):
  222. self.entries.append(entry)
  223. self.emit('entry-added', playlist=self, entry=entry)
  224.  
  225. if self.peek() is entry:
  226. entry.get_ready_future()
  227.  
  228. async def get_next_entry(self, predownload_next=True):
  229. """
  230. A coroutine which will return the next song or None if no songs left to play.
  231.  
  232. Additionally, if predownload_next is set to True, it will attempt to download the next
  233. song to be played - so that it's ready by the time we get to it.
  234. """
  235. if not self.entries:
  236. return None
  237.  
  238. entry = self.entries.popleft()
  239.  
  240. if predownload_next:
  241. next_entry = self.peek()
  242. if next_entry:
  243. next_entry.get_ready_future()
  244.  
  245. return await entry.get_ready_future()
  246.  
  247. def peek(self):
  248. """
  249. Returns the next entry that should be scheduled to be played.
  250. """
  251. if self.entries:
  252. return self.entries[0]
  253.  
  254. async def estimate_time_until(self, position, player):
  255. """
  256. (very) Roughly estimates the time till the queue will 'position'
  257. """
  258. estimated_time = sum([e.duration for e in islice(self.entries, position - 1)])
  259.  
  260. # When the player plays a song, it eats the first playlist item, so we just have to add the time back
  261. if not player.is_stopped and player.current_entry:
  262. estimated_time += player.current_entry.duration - player.progress
  263.  
  264. return datetime.timedelta(seconds=estimated_time)
  265.  
  266. def count_for_user(self, user):
  267. return sum(1 for e in self.entries if e.meta.get('author', None) == user)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement