class AudioController(object): """ Controls the playback of audio and the sequential playing of the songs. Attributes: bot: The instance of the bot that will be playing the music. playlist: A Playlist object that stores the history and queue of songs. current_song: A Song object that stores details of the current song. guild: The guild in which the Audiocontroller operates. """ def __init__(self, bot, guild): self.bot = bot self.playlist = Playlist() self.current_song = None self.guild = guild self.voice_client = None async def register_voice_channel(self, channel): self.voice_client = await channel.connect(reconnect=True, timeout=None) def track_history(self): history_string = config.INFO_HISTORY_TITLE for trackname in self.playlist.trackname_history: history_string += "\n" + trackname return history_string def next_song(self, error): """Invoked after a song is finished. Plays the next song if there is one.""" self.current_song = None next_song = self.playlist.next() if next_song is None: return coro = self.play_song(next_song) self.bot.loop.create_task(coro) async def play_song(self, song): """Plays a song object""" if song.origin == linkutils.Origins.Playlist: if song.host == linkutils.Sites.Spotify: conversion = await self.search_youtube( linkutils.convert_spotify(song.info.webpage_url)) song.info.webpage_url = conversion downloader = youtube_dl.YoutubeDL({ 'format': 'bestaudio', 'title': True }) r = downloader.extract_info(song.info.webpage_url, download=False) song.base_url = r.get('url') song.info.uploader = r.get('uploader') song.info.title = r.get('title') song.info.duration = r.get('duration') song.info.webpage_url = r.get('webpage_url') self.playlist.add_name(song.info.title) self.current_song = song self.voice_client.play(discord.FFmpegPCMAudio( song.base_url, before_options= '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5'), after=lambda e: self.next_song(e)) async def process_song(self, track): """Adds the track to the playlist instance and plays it, if it is the first song""" host = linkutils.identify_url(track) is_playlist = linkutils.identify_playlist(track) if is_playlist != linkutils.Playlist_Types.Unknown: if len(self.playlist.playque) == 0: start = True else: start = False await self.process_playlist(is_playlist, track) if is_playlist == linkutils.Playlist_Types.Spotify_Playlist: song = Song(linkutils.Origins.Playlist, linkutils.Sites.Spotify, "", "", "", "", "") if is_playlist == linkutils.Playlist_Types.YouTube_Playlist: song = Song(linkutils.Origins.Playlist, linkutils.Sites.YouTube, "", "", "", "", "") if start == True: await self.play_song(self.playlist.playque[0]) print("Playing {}".format(track)) return song if host == linkutils.Sites.Unknown: if linkutils.get_url(track) is not None: return None track = await self.search_youtube(track) if host == linkutils.Sites.Spotify: title = linkutils.convert_spotify(track) track = await self.search_youtube(title) try: downloader = youtube_dl.YoutubeDL({ 'format': 'bestaudio', 'title': True }) r = downloader.extract_info(track, download=False) except: downloader = youtube_dl.YoutubeDL({'title': True}) r = downloader.extract_info(track, download=False) song = Song(linkutils.Origins.Default, host, r.get('url'), r.get('uploader'), r.get('title'), r.get('duration'), r.get('webpage_url')) self.playlist.add(song) if len(self.playlist.playque) == 1: print("Playing {}".format(track)) await self.play_song(song) return song async def process_playlist(self, playlist_type, url): if playlist_type == linkutils.Playlist_Types.YouTube_Playlist: if ("playlist?list=" in url): listid = url.split('=')[1] else: video = url.split('&')[0] await self.process_song(video) return options = {'format': 'bestaudio/best', 'extract_flat': True} with youtube_dl.YoutubeDL(options) as ydl: r = ydl.extract_info(url, download=False) for entry in r['entries']: link = "https://www.youtube.com/watch?v={}".format( entry['id']) song = Song(linkutils.Origins.Playlist, linkutils.Sites.YouTube, "", "", "", "", link) self.playlist.add(song) if playlist_type == linkutils.Playlist_Types.Spotify_Playlist: links = linkutils.get_spotify_playlist(url) for link in links: song = Song(linkutils.Origins.Playlist, linkutils.Sites.Spotify, "", "", "", "", link) self.playlist.add(song) async def search_youtube(self, title): """Searches youtube for the video title and returns the first results video link""" # if title is already a link if linkutils.get_url(title) is not None: return title options = { 'format': 'bestaudio/best', 'default_search': 'auto', 'noplaylist': True } with youtube_dl.YoutubeDL(options) as ydl: r = ydl.extract_info(title, download=False) videocode = r['entries'][0]['id'] return "https://www.youtube.com/watch?v={}".format(videocode) async def stop_player(self): """Stops the player and removes all songs from the queue""" if self.guild.voice_client is None or ( not self.guild.voice_client.is_paused() and not self.guild.voice_client.is_playing()): return self.playlist.next() self.playlist.playque.clear() self.guild.voice_client.stop() async def prev_song(self): """Loads the last song from the history into the queue and starts it""" if len(self.playlist.playhistory) == 0: return None if self.guild.voice_client is None or ( not self.guild.voice_client.is_paused() and not self.guild.voice_client.is_playing()): prev_song = self.playlist.prev() # The Dummy is used if there is no song in the history if prev_song == "Dummy": self.playlist.next() return None await self.play_youtube(prev_song) else: self.playlist.prev() self.playlist.prev() self.guild.voice_client.stop() def clear_queue(self): self.playlist.playque.clear()
class AudioController(object): """ Controls the playback of audio and the sequential playing of the songs. Attributes: bot: The instance of the bot that will be playing the music. playlist: A Playlist object that stores the history and queue of songs. current_song: A Song object that stores details of the current song. guild: The guild in which the Audiocontroller operates. """ def __init__(self, bot, guild): self.bot = bot self.playlist = Playlist() self.current_song = None self.guild = guild sett = utils.guild_to_settings[guild] self._volume = sett.get('default_volume') self.timer = utils.Timer(self.timeout_handler) @property def volume(self): return self._volume @volume.setter def volume(self, value): self._volume = value try: self.guild.voice_client.source.volume = float(value) / 100.0 except Exception as e: pass async def register_voice_channel(self, channel): await channel.connect(reconnect=True, timeout=None) def track_history(self): history_string = config.INFO_HISTORY_TITLE for trackname in self.playlist.trackname_history: history_string += "\n" + trackname return history_string def next_song(self, error): """Invoked after a song is finished. Plays the next song if there is one.""" next_song = self.playlist.next(self.current_song) self.current_song = None if next_song is None: return coro = self.play_song(next_song) self.bot.loop.create_task(coro) async def play_song(self, song): """Plays a song object""" if self.playlist.loop != True: #let timer run thouh if looping self.timer.cancel() self.timer = utils.Timer(self.timeout_handler) if song.info.title == None: if song.host == linkutils.Sites.Spotify: conversion = self.search_youtube( await linkutils.convert_spotify(song.info.webpage_url)) song.info.webpage_url = conversion downloader = yt_dlp.YoutubeDL({ 'format': 'bestaudio', 'title': True, "cookiefile": config.COOKIE_PATH }) r = downloader.extract_info(song.info.webpage_url, download=False) song.base_url = r.get('url') song.info.uploader = r.get('uploader') song.info.title = r.get('title') song.info.duration = r.get('duration') song.info.webpage_url = r.get('webpage_url') song.info.thumbnail = r.get('thumbnails')[0]['url'] self.playlist.add_name(song.info.title) self.current_song = song self.playlist.playhistory.append(self.current_song) self.guild.voice_client.play(discord.FFmpegPCMAudio( song.base_url, before_options= '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5'), after=lambda e: self.next_song(e)) self.guild.voice_client.source = discord.PCMVolumeTransformer( self.guild.voice_client.source) self.guild.voice_client.source.volume = float(self.volume) / 100.0 self.playlist.playque.popleft() for song in list(self.playlist.playque)[:config.MAX_SONG_PRELOAD]: asyncio.ensure_future(self.preload(song)) async def process_song(self, track): """Adds the track to the playlist instance and plays it, if it is the first song""" host = linkutils.identify_url(track) is_playlist = linkutils.identify_playlist(track) if is_playlist != linkutils.Playlist_Types.Unknown: await self.process_playlist(is_playlist, track) if self.current_song == None: await self.play_song(self.playlist.playque[0]) print("Playing {}".format(track)) song = Song(linkutils.Origins.Playlist, linkutils.Sites.Unknown) return song if host == linkutils.Sites.Unknown: if linkutils.get_url(track) is not None: return None track = self.search_youtube(track) if host == linkutils.Sites.Spotify: title = await linkutils.convert_spotify(track) track = self.search_youtube(title) if host == linkutils.Sites.YouTube: track = track.split("&list=")[0] try: downloader = yt_dlp.YoutubeDL({ 'format': 'bestaudio', 'title': True, "cookiefile": config.COOKIE_PATH }) try: r = downloader.extract_info(track, download=False) except Exception as e: if "ERROR: Sign in to confirm your age" in str(e): return None except: downloader = yt_dlp.YoutubeDL({ 'title': True, "cookiefile": config.COOKIE_PATH }) r = downloader.extract_info(track, download=False) if r.get('thumbnails') is not None: thumbnail = r.get('thumbnails')[len(r.get('thumbnails')) - 1]['url'] else: thumbnail = None song = Song(linkutils.Origins.Default, host, base_url=r.get('url'), uploader=r.get('uploader'), title=r.get('title'), duration=r.get('duration'), webpage_url=r.get('webpage_url'), thumbnail=thumbnail) self.playlist.add(song) if self.current_song == None: print("Playing {}".format(track)) await self.play_song(song) return song async def process_playlist(self, playlist_type, url): if playlist_type == linkutils.Playlist_Types.YouTube_Playlist: if ("playlist?list=" in url): listid = url.split('=')[1] else: video = url.split('&')[0] await self.process_song(video) return options = { 'format': 'bestaudio/best', 'extract_flat': True, "cookiefile": config.COOKIE_PATH } with yt_dlp.YoutubeDL(options) as ydl: r = ydl.extract_info(url, download=False) for entry in r['entries']: link = "https://www.youtube.com/watch?v={}".format( entry['id']) song = Song(linkutils.Origins.Playlist, linkutils.Sites.YouTube, webpage_url=link) self.playlist.add(song) if playlist_type == linkutils.Playlist_Types.Spotify_Playlist: links = await linkutils.get_spotify_playlist(url) for link in links: song = Song(linkutils.Origins.Playlist, linkutils.Sites.Spotify, webpage_url=link) self.playlist.add(song) if playlist_type == linkutils.Playlist_Types.BandCamp_Playlist: options = {'format': 'bestaudio/best', 'extract_flat': True} with yt_dlp.YoutubeDL(options) as ydl: r = ydl.extract_info(url, download=False) for entry in r['entries']: link = entry.get('url') song = Song(linkutils.Origins.Playlist, linkutils.Sites.Bandcamp, webpage_url=link) self.playlist.add(song) for song in list(self.playlist.playque)[:config.MAX_SONG_PRELOAD]: asyncio.ensure_future(self.preload(song)) async def preload(self, song): if song.info.title != None: return def down(song): if song.host == linkutils.Sites.Spotify: song.info.webpage_url = self.search_youtube(song.info.title) if song.info.webpage_url == None: return None downloader = yt_dlp.YoutubeDL({ 'format': 'bestaudio', 'title': True, "cookiefile": config.COOKIE_PATH }) r = downloader.extract_info(song.info.webpage_url, download=False) song.base_url = r.get('url') song.info.uploader = r.get('uploader') song.info.title = r.get('title') song.info.duration = r.get('duration') song.info.webpage_url = r.get('webpage_url') song.info.thumbnail = r.get('thumbnails')[0]['url'] if song.host == linkutils.Sites.Spotify: song.info.title = await linkutils.convert_spotify( song.info.webpage_url) loop = asyncio.get_event_loop() executor = concurrent.futures.ThreadPoolExecutor( max_workers=config.MAX_SONG_PRELOAD) await asyncio.wait(fs={loop.run_in_executor(executor, down, song)}, return_when=asyncio.ALL_COMPLETED) def search_youtube(self, title): """Searches youtube for the video title and returns the first results video link""" # if title is already a link if linkutils.get_url(title) is not None: return title options = { 'format': 'bestaudio/best', 'default_search': 'auto', 'noplaylist': True, "cookiefile": config.COOKIE_PATH } with yt_dlp.YoutubeDL(options) as ydl: r = ydl.extract_info(title, download=False) if r == None: return None videocode = r['entries'][0]['id'] return "https://www.youtube.com/watch?v={}".format(videocode) async def stop_player(self): """Stops the player and removes all songs from the queue""" if self.guild.voice_client is None or ( not self.guild.voice_client.is_paused() and not self.guild.voice_client.is_playing()): return self.playlist.loop = False self.playlist.next(self.current_song) self.clear_queue() self.guild.voice_client.stop() async def prev_song(self): """Loads the last song from the history into the queue and starts it""" self.timer.cancel() self.timer = utils.Timer(self.timeout_handler) if len(self.playlist.playhistory) == 0: return prev_song = self.playlist.prev(self.current_song) if not self.guild.voice_client.is_playing( ) and not self.guild.voice_client.is_paused(): if prev_song == "Dummy": self.playlist.next(self.current_song) return None await self.play_song(prev_song) else: self.guild.voice_client.stop() async def timeout_handler(self): if len(self.guild.voice_client.channel.voice_states) == 1: await self.udisconnect() return sett = utils.guild_to_settings[self.guild] if sett.get('vc_timeout') == False: self.timer = utils.Timer(self.timeout_handler) # restart timer return if self.guild.voice_client.is_playing(): self.timer = utils.Timer(self.timeout_handler) # restart timer return self.timer = utils.Timer(self.timeout_handler) await self.udisconnect() async def uconnect(self, ctx): if not ctx.author.voice: await ctx.send(config.NO_GUILD_MESSAGE) return False if self.guild.voice_client == None: await self.register_voice_channel(ctx.author.voice.channel) else: await ctx.send(config.ALREADY_CONNECTED_MESSAGE) async def udisconnect(self): await self.stop_player() await self.guild.voice_client.disconnect(force=True) def clear_queue(self): self.playlist.playque.clear()
class AudioController(object): """ Controls the playback of audio and the sequential playing of the songs. Attributes: bot: The instance of the bot that will be playing the music. _volume: the volume of the music being played. playlist: A Playlist object that stores the history and queue of songs. current_songinfo: A Songinfo object that stores details of the current song. guild: The guild in which the Audiocontroller operates. """ def __init__(self, bot, guild, volume): self.bot = bot self._volume = volume self.playlist = Playlist() self.current_songinfo = None self.guild = guild self.voice_client = None self.infoMap = {} self.songCount = 0 @property def volume(self): return self._volume @volume.setter def volume(self, value): self._volume = value try: self.voice_client.source.volume = float(value) / 100.0 except Exception as e: print(e) async def register_voice_channel(self, channel): self.voice_client = await channel.connect() def track_history(self): history_string = config.INFO_HISTORY_TITLE for trackname in self.playlist.trackname_history: history_string += "\n" + trackname return history_string def next_song(self, error): """Invoked after a song is finished. Plays the next song if there is one, resets the nickname otherwise""" self.current_songinfo = None next_song = self.playlist.next() if next_song is None: coro = self.guild.me.edit(nick=config.DEFAULT_NICKNAME) else: coro = self.play_youtube(next_song) self.bot.loop.create_task(coro) async def add_youtube(self, link): """Processes a youtube link and passes elements of a playlist to the add_song function one by one""" print("adding youtube...") # Pass it on if it is not a playlist if not ("playlist?list=" in link): await self.add_song(link) return # Parse the playlist page html and get all the individual video links response = urllib.request.urlopen(link) soup = BeautifulSoup(response.read(), "html.parser") res = soup.find_all('a', {'class': 'pl-video-title-link'}) for l in res: await self.add_song('https://www.youtube.com' + l.get("href")) async def add_song(self, track): """Adds the track to the playlist instance and plays it, if it is the first song""" print("adding song...") # If the track is a video title, get the corresponding video link first if not ("watch?v=" in track) and not ("https://youtu.be/" in track): link = self.convert_to_youtube_link('"' + track + '"') if link is None: link = self.convert_to_youtube_link(track) if link is None: return else: link = track self.playlist.add(link) songInfo = await self.getExtractedInfo(track) self.infoMap[track] = songInfo print("gothere2...") if len(self.playlist.playque) == 1: await self.play_youtube(link) async def getExtractedInfo(self, youtube_link): youtube_link = youtube_link.split("&list=")[0] try: downloader = youtube_dl.YoutubeDL({ 'format': 'bestaudio', 'title': True }) extracted_info = downloader.extract_info(youtube_link, download=False) # "format" is not available for livestreams - redownload the page with no options except: try: downloader = youtube_dl.YoutubeDL({}) extracted_info = downloader.extract_info(youtube_link, download=False) except: self.next_song(None) return Songinfo(extracted_info.get('uploader'), extracted_info.get('creator'), extracted_info.get('title'), extracted_info.get('duration'), extracted_info.get('like_count'), extracted_info.get('dislike_count'), extracted_info.get('url')) def convert_to_youtube_link(self, title): """Searches youtube for the video title and returns the first results video link""" filter(lambda x: x in set(printable), title) # Parse the search result page for the first results link query = urllib.parse.quote(title) url = "https://www.youtube.com/results?search_query=" + query response = urllib.request.urlopen(url) html = response.read() soup = BeautifulSoup(html, "html.parser") results = soup.findAll(attrs={'class': 'yt-uix-tile-link'}) checked_videos = 0 while len(results) > checked_videos: if not "user" in results[checked_videos]['href']: return 'https://www.youtube.com' + results[checked_videos][ 'href'] checked_videos += 1 return None async def play_youtube(self, youtube_link): """Downloads and plays the audio of the youtube link passed""" youtube_link = youtube_link.split("&list=")[0] # Update the songinfo to reflect the current song self.current_songinfo = self.infoMap[youtube_link] self.current_songinfo.webpage_url startTime = 0 if "t=" in youtube_link: temp = youtube_link.split("t=")[1] res = re.findall('\d+', temp) if len(res) > 0: startTime = res[0] print("playing song: " + youtube_link + " starting at: " + str(startTime)) # Change the nickname to indicate, what song is currently playing await self.guild.me.edit( nick=playing_string(self.current_songinfo.title)) self.playlist.add_name(self.current_songinfo.title) self.voice_client.play( discord.FFmpegPCMAudio(self.current_songinfo.webpage_url, before_options='-ss ' + str(startTime) + ' -t ' + str(config.MAX_SONG_DURATION)), after=lambda e: self.next_song(e)) self.voice_client.source = discord.PCMVolumeTransformer( self.guild.voice_client.source) self.voice_client.source.volume = float(self.volume) / 100.0 self.songCount = self.songCount + 1 # asyncio.create_task(startSongChat(self.songCount, self.current_songinfo)) async def stop_player(self): """Stops the player and removes all songs from the queue""" if self.guild.voice_client is None or ( not self.guild.voice_client.is_paused() and not self.guild.voice_client.is_playing()): return self.playlist.next() self.playlist.playque.clear() self.guild.voice_client.stop() await self.guild.me.edit(nick=config.DEFAULT_NICKNAME) async def prev_song(self): """Loads the last ong from the history into the queue and starts it""" if len(self.playlist.playhistory) == 0: return None if self.guild.voice_client is None or ( not self.guild.voice_client.is_paused() and not self.guild.voice_client.is_playing()): prev_song = self.playlist.prev() # The Dummy is used if there is no song in the history if prev_song == "Dummy": self.playlist.next() return None await self.play_youtube(prev_song) else: self.playlist.prev() self.playlist.prev() self.guild.voice_client.stop()