diff --git a/Music/Downloader.py b/Music/Downloader.py index b3b3dd2..b9093b4 100644 --- a/Music/Downloader.py +++ b/Music/Downloader.py @@ -56,8 +56,8 @@ def finish_one_song(self, song: Song) -> Song: song.finish_down(song_info) return song # Convert yt_dlp error to my own error - except DownloadError: - raise DownloadingError() + except DownloadError as e: + raise DownloadingError(e.msg) @run_async def extract_info(self, url: str) -> List[dict]: diff --git a/Music/Playlist.py b/Music/Playlist.py index b044486..19bdf25 100644 --- a/Music/Playlist.py +++ b/Music/Playlist.py @@ -106,6 +106,10 @@ def add_song(self, song: Song) -> Song: self.__queue.append(song) return song + def add_song_start(self, song: Song) -> Song: + self.__queue.insert(0, song) + return song + def shuffle(self) -> None: random.shuffle(self.__queue) diff --git a/Music/Song.py b/Music/Song.py index a1bf00b..2582310 100644 --- a/Music/Song.py +++ b/Music/Song.py @@ -1,15 +1,20 @@ +from time import time + + class Song: def __init__(self, identifier: str, playlist, requester: str) -> None: self.__identifier = identifier self.__info = {'requester': requester} self.__problematic = False self.__playlist = playlist + self.__downloadTime: int = time() def finish_down(self, info: dict) -> None: if info is None or info == {}: self.destroy() return None + self.__downloadTime = time() self.__useful_keys = ['duration', 'title', 'webpage_url', 'channel', 'id', 'uploader', @@ -35,6 +40,10 @@ def __cleanTitle(self) -> None: self.__info['title'] = ''.join(char if char.isalnum() or char == ' ' else ' ' for char in self.__info['title']) + @property + def downloadTime(self) -> int: + return self.__downloadTime + @property def source(self) -> str: if 'url' in self.__info.keys(): @@ -42,6 +51,10 @@ def source(self) -> str: else: return None + @source.setter + def source(self, value) -> None: + self.__info['url'] = value + @property def title(self) -> str: if 'title' in self.__info.keys(): @@ -53,13 +66,17 @@ def title(self) -> str: def duration(self) -> str: if 'duration' in self.__info.keys(): return self.__info['duration'] - else: - return 0.0 + else: # Default minimum duration + return 5.0 @property def identifier(self) -> str: return self.__identifier + @identifier.setter + def identifier(self, value) -> None: + self.__identifier = value + @property def problematic(self) -> bool: return self.__problematic diff --git a/Parallelism/PlayerProcess.py b/Parallelism/PlayerProcess.py index 3caa5d8..9ff61ca 100644 --- a/Parallelism/PlayerProcess.py +++ b/Parallelism/PlayerProcess.py @@ -1,4 +1,6 @@ import asyncio +from time import time +from urllib.parse import parse_qs, urlparse from Music.VulkanInitializer import VulkanInitializer from discord import User, Member, Message, VoiceClient from asyncio import AbstractEventLoop, Semaphore, Queue @@ -11,6 +13,7 @@ from Config.Configs import VConfigs from Config.Messages import Messages from Music.VulkanBot import VulkanBot +from Music.Downloader import Downloader from Config.Embeds import VEmbeds from Parallelism.Commands import VCommands, VCommandsType @@ -78,6 +81,7 @@ def run(self) -> None: self.__configs = VConfigs() self.__messages = Messages() self.__embeds = VEmbeds() + self.__downloader = Downloader() self.__semStopPlaying = Semaphore(0) self.__loop.run_until_complete(self._run()) @@ -147,11 +151,17 @@ async def __playSong(self, song: Song) -> None: if not self.__voiceClient.is_connected(): print('[VOICE CHANNEL NOT NULL BUT DISCONNECTED, CONNECTING AGAIN]') await self.__connectToVoiceChannel() - # If the player is connected and playing + # If the player is connected and playing return the song to the playlist elif self.__voiceClient.is_playing(): print('[SONG ALREADY PLAYING, RETURNING]') + self.__playlist.add_song_start(song) return + songStillAvailable = self.__verifyIfSongAvailable(song) + if not songStillAvailable: + print('[SONG NOT AVAILABLE ANYMORE, DOWNLOADING AGAIN]') + song = self.__downloadSongAgain(song) + self.__playing = True self.__songPlaying = song @@ -192,6 +202,30 @@ def __playNext(self, error) -> None: # Release the semaphore to finish the process self.__semStopPlaying.release() + def __verifyIfSongAvailable(self, song: Song) -> bool: + """Verify the song source to see if it's already expired""" + try: + parsedUrl = urlparse(song.source) + + if 'expire' not in parsedUrl.query: + # If already passed 5 hours since the download + if song.downloadTime + 18000 < int(time()): + return False + return True + + # If the current time plus the song duration plus 10min exceeds the expirationValue + expireValue = parse_qs(parsedUrl.query)['expire'][0] + if int(time()) + song.duration + 600 > int(str(expireValue)): + return False + return True + except Exception as e: + print(f'[ERROR VERIFYING SONG AVAILABILITY] -> {e}') + return False + + def __downloadSongAgain(self, song: Song) -> Song: + """Force a download to be executed again, one use case is when the song.source expired and needs to refresh""" + return self.__downloader.finish_one_song(song) + async def __playPrev(self, voiceChannelID: int) -> None: with self.__playlistLock: song = self.__playlist.prev_song()