def test_get_track_path_raises_value_error_if_root_directory_unknown( self, example_group): """ If the `file` attribute of a `Track` is not the link to a YouTube video, return the file path. The file path consists of the root directory combined with the filename. If there is no root directory, raise a `ValueError`. """ example_group.directory = None track_list = example_group.track_lists[0] track_list.directory = None track = track_list.tracks[0] track.file = "file.mp3" with pytest.raises(ValueError): utils.get_track_path(example_group, track_list, track)
def test_get_track_path_raises_value_error_if_path_is_not_existing_file( self, example_group, monkeypatch): """ If the `file` attribute of a `Track` is not the link to a YouTube video, return the file path. But if the file path does not refer to an existing file, raise a `ValueError`. """ track_list = example_group.track_lists[0] track = track_list.tracks[0] track.file = "file.mp3" monkeypatch.setattr( "src.music.utils.get_track_list_root_directory", MagicMock(return_value="root/dir/")) # non-existing dir with pytest.raises(ValueError): utils.get_track_path(example_group, track_list, track)
def check_tracks_do_exist(self, groups: Iterable[MusicGroup], default_dir): """ Iterates through every track and attempts to get its path. Logs any error and re-raises any exception. """ logger.info("Checking that tracks point to valid paths...") for group, track_list, track in utils.music_tuple_generator(groups): try: utils.get_track_path(group, track_list, track, default_dir=default_dir) except Exception as ex: logger.error( f"Track '{track.file}' does not point to a valid path.") raise ex logger.info("Success! All tracks point to valid paths.")
async def _play_track(self, group: MusicGroup, track_list: TrackList, track: Track): """ Plays the given track from the given track list and group. """ try: path = utils.get_track_path(group, track_list, track, default_dir=self.directory) except ValueError: logger.error(f"Failed to play '{track.file}'.") raise asyncio.CancelledError() self._current_player = vlc.MediaPlayer(vlc.Instance("--novideo"), path) self._current_player.audio_set_volume(0) success = self._current_player.play() if success == -1: logger.error(f"Failed to play {path}") raise asyncio.CancelledError if track.start_at is not None: self._current_player.set_time(track.start_at) logger.info(f"Now Playing: {track.file}") await self._wait_for_current_player_to_be_playing() await self._set_master_volume(self.volume, set_global=False) while self._current_player.is_playing(): try: if track.end_at is not None and self._current_player.get_time() >= track.end_at: self._current_player.stop() await asyncio.sleep(self.SLEEP_TIME) except asyncio.CancelledError: logger.debug(f"Received cancellation request for {track.file}") await self._set_master_volume(0, set_global=False) raise logger.info(f"Finished playing: {track.file}")
async def _play_track(self, discord_context, request, group, track_list, tracks_to_play): """ Plays the given track from the given track list and group. """ if self.is_cancelled: self._currently_playing = None self.is_cancelled = False logger.info(f"Cancelled '{track_list.name}'") await self.callback_handler(action=MusicActions.STOP, request=request, state=self.currently_playing) return if len(tracks_to_play) == 0: if not track_list.loop: self._currently_playing = None logger.info(f"Finished '{track_list.name}'") await self.callback_handler(action=MusicActions.FINISH, request=request, state=self.currently_playing) if track_list.next is None: return next_group_index, next_track_list_index = self._get_track_list_index_from_name( track_list.next) if next_group_index is None or next_track_list_index is None: logger.error( f"Could not find a track list named '{track_list.next}'" ) return await self.play_track_list(discord_context, request, next_group_index, next_track_list_index) return tracks_to_play = track_list.tracks track = tracks_to_play.pop(0) path = utils.get_track_path(group, track_list, track, default_dir=self.directory) ffmpeg_before_options = "" if track.start_at is not None: ffmpeg_before_options += f" -ss {track.start_at}ms" if track.end_at is not None: ffmpeg_before_options += f" -to {track.end_at}ms" logger.info(f"Now Playing: {track.file}") source = discord.PCMVolumeTransformer( discord.FFmpegPCMAudio(path, before_options=ffmpeg_before_options)) volume = (self.volume * track_list.volume) // 100 source.volume = volume / 100 discord_context.voice_client.play( source, after=lambda e: logger.error(f"Player error: {e}") if e else asyncio.run_coroutine_threadsafe( self._play_track(discord_context, request, group, track_list, tracks_to_play), self.event_loop), )
def test_get_track_path_returns_file_path_if_track_is_file( self, example_group, monkeypatch): """ If the `file` attribute of a `Track` is not the link to a YouTube video, return the file path. """ monkeypatch.setattr("src.music.utils.get_track_list_root_directory", MagicMock(return_value="root/dir/")) monkeypatch.setattr("src.music.utils.os.path.isfile", lambda x: True) track_list = example_group.track_lists[0] track = track_list.tracks[0] track.file = "file.mp3" path = utils.get_track_path(example_group, track_list, track) assert path == "root/dir/file.mp3"
def test_get_track_path_returns_audio_url_if_track_is_youtube_link( self, example_group, monkeypatch): """ If the `file` attribute of a `Track` is the link to a YouTube video, return the url of its audio stream. """ track_list = example_group.track_lists[0] track = track_list.tracks[0] track.file = "https://www.youtube.com/watch?v=jIxas0a-KgM" get_audio_stream_mock = MagicMock(return_value="some-url") monkeypatch.setattr("src.music.utils.get_audio_stream", get_audio_stream_mock) path = utils.get_track_path(example_group, track_list, track) assert path == "some-url"
def check_tracks_do_exist(self, groups: Iterable[MusicGroup], default_dir): """ Iterates through every track and attempts to get its path. Logs any error and re-raises any exception. """ logger.info("Checking that tracks point to valid paths...") valid_youtube_tracks = collections.deque(cache.load_list(self.VALID_YOUTUBE_TRACKS_CACHE), maxlen=100) for group, track_list, track in utils.music_tuple_generator(groups): try: if not track.is_youtube_link: utils.get_track_path(group, track_list, track, default_dir=default_dir) else: # This is much faster to check if the link is a YouTube video if track.file in valid_youtube_tracks: continue url = f"https://www.youtube.com/oembed?url={track.file}" result = requests.get(url) if result.status_code != 200: raise RuntimeError(f"The url '{track.file}' is not a valid YouTube video.") valid_youtube_tracks.append(track.file) except Exception as ex: logger.error(f"Track '{track.file}' does not point to a valid path.") raise ex cache.save_list(list(valid_youtube_tracks), self.VALID_YOUTUBE_TRACKS_CACHE) logger.info("Success! All tracks point to valid paths.")