diff --git a/src/knuckles/media_retrieval.py b/src/knuckles/media_retrieval.py index b882227..f8d7194 100644 --- a/src/knuckles/media_retrieval.py +++ b/src/knuckles/media_retrieval.py @@ -93,8 +93,44 @@ def hls(self, id: str) -> str: def get_captions(self) -> None: ... - def get_cover_art(self) -> None: - ... + def get_cover_art(self, id: str, file_or_directory_path: Path, size: int) -> Path: + """Calls the "getCoverArt" endpoint of the API. + + :param id: The id of the cover art to download. + :type id: str + :param file_or_directory_path: If a directory path is passed the file will be + inside of it with the filename being the name of the user and + a guessed file extension, if not the file will be saved + directly in the given path. + :type file_or_directory_path: Path + :param size: The size of the image to be scale to in a square. + :type size: int + :return Returns the given path + :rtype Path + """ + + response = self.api.raw_request("getCoverArt", {"id": id, "size": size}) + response.raise_for_status() + + if file_or_directory_path.is_dir(): + file_extension = guess_extension( + response.headers["content-type"].partition(";")[0].strip() + ) + + filename = id + file_extension if file_extension else id + + download_path = Path( + file_or_directory_path, + filename, + ) + else: + download_path = file_or_directory_path + + with open(download_path, "wb") as f: + for chunk in response.iter_content(chunk_size=8192): + f.write(chunk) + + return download_path def get_lyrics(self) -> None: ... diff --git a/tests/api/test_media_retrieval.py b/tests/api/test_media_retrieval.py index 263fdc8..f8295c6 100644 --- a/tests/api/test_media_retrieval.py +++ b/tests/api/test_media_retrieval.py @@ -79,6 +79,68 @@ def test_hls(subsonic: Subsonic, song: dict[str, Any]) -> None: assert parse.parse_qs(stream_url.query)["id"][0] == song["id"] +@responses.activate +def test_get_cover_art_with_a_given_filename( + tmp_path: Path, + output_cover_art_filename: str, + placeholder_data: str, + mock_download_file: MockDownload, + subsonic: Subsonic, + song: dict[str, Any], + cover_art_content_type: str, + cover_art_size: int, +) -> None: + responses.add( + mock_download_file( + "getCoverArt", + {"id": song["coverArt"], "size": cover_art_size}, + tmp_path, + cover_art_content_type, + ) + ) + + download_path = subsonic.media_retrieval.get_cover_art( + song["coverArt"], tmp_path / output_cover_art_filename, cover_art_size + ) + + # Check if the file data has been unaltered + with open(tmp_path / output_cover_art_filename, "r") as file: + assert placeholder_data == file.read() + + assert download_path == tmp_path / output_cover_art_filename + + +@responses.activate +def test_get_cover_art_without_a_given_filename( + tmp_path: Path, + default_cover_art_filename: str, + placeholder_data: str, + mock_download_file: MockDownload, + subsonic: Subsonic, + song: dict[str, Any], + cover_art_content_type: str, + cover_art_size: int, +) -> None: + responses.add( + mock_download_file( + "getCoverArt", + {"id": song["coverArt"], "size": cover_art_size}, + tmp_path, + cover_art_content_type, + ) + ) + + download_path = subsonic.media_retrieval.get_cover_art( + song["coverArt"], tmp_path, cover_art_size + ) + + # Check if the file data has been unaltered + with open(tmp_path / default_cover_art_filename, "r") as file: + assert placeholder_data == file.read() + + assert download_path == tmp_path / default_cover_art_filename + + @responses.activate def test_get_avatar_with_a_given_filename( tmp_path: Path, diff --git a/tests/mocks/media_retrieval.py b/tests/mocks/media_retrieval.py index 440d49f..e30895c 100644 --- a/tests/mocks/media_retrieval.py +++ b/tests/mocks/media_retrieval.py @@ -54,6 +54,26 @@ def avatar_content_type() -> str: return "image/png" +@pytest.fixture +def default_cover_art_filename(song: dict[str, Any]) -> str: + return f"{song['coverArt']}.png" + + +@pytest.fixture +def output_cover_art_filename() -> str: + return "output.png" + + +@pytest.fixture +def cover_art_content_type() -> str: + return "image/png" + + +@pytest.fixture +def cover_art_size() -> int: + return 512 + + @pytest.fixture def mock_download_file( placeholder_data: str,