Skip to content

Commit

Permalink
Merge pull request #34 from kutu-dev/feat/get-captions
Browse files Browse the repository at this point in the history
Feat/get captions
  • Loading branch information
kutu-dev authored Sep 30, 2023
2 parents fd82eaf + 7e77858 commit c269276
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 19 deletions.
1 change: 0 additions & 1 deletion src/knuckles/media_annotation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from datetime import datetime
from typing import TYPE_CHECKING

from .api import Api
from .exceptions import InvalidRatingNumber

Expand Down
33 changes: 31 additions & 2 deletions src/knuckles/media_retrieval.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from enum import Enum
from mimetypes import guess_extension
from pathlib import Path
from typing import Any
Expand All @@ -8,6 +9,11 @@
from .api import Api


class SubtitlesFileFormat(Enum):
VTT = "vtt"
SRT = "srt"


class MediaRetrieval:
"""Class that contains all the methods needed to interact
with the media retrieval calls in the Subsonic API.
Expand Down Expand Up @@ -95,8 +101,31 @@ def hls(self, id: str) -> str:

return self._generate_url("hls.m3u8", {"id": id})

def get_captions(self) -> None:
...
def get_captions(
self,
id: str,
file_or_directory_path: Path,
subtitles_file_format: SubtitlesFileFormat = SubtitlesFileFormat.VTT,
) -> Path:
# Check if the given file format is a valid one
SubtitlesFileFormat(subtitles_file_format.value)

response = self.api.raw_request(
"getCaptions",
{"id": id, "format": subtitles_file_format.value},
)

mime_type = response.headers["content-type"].partition(";")[0].strip()

# As application/x-subrip is not a valid MIME TYPE a manual check is done
if not mime_type == "application/x-subrip":
file_extension = guess_extension(mime_type)
else:
file_extension = ".srt"

filename = id + file_extension if file_extension else id

return self._download_file(response, file_or_directory_path, filename)

def get_cover_art(
self, id: str, file_or_directory_path: Path, size: int | None = None
Expand Down
104 changes: 98 additions & 6 deletions tests/api/test_media_retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
from pathlib import Path
from typing import Any

import pytest
from _pytest.fixtures import FixtureRequest
from responses import Response
import responses
from knuckles import Subsonic

from knuckles.media_retrieval import SubtitlesFileFormat
from tests.mocks.media_retrieval import FileMetadata


Expand All @@ -31,7 +34,7 @@ def test_download_with_a_given_filename(
song["id"], tmp_path / download_metadata.output_filename
)

# Check if the file data has been unaltered
# Check if the file data has been altered
with open(tmp_path / download_metadata.output_filename, "r") as file:
assert placeholder_data == file.read()

Expand All @@ -51,7 +54,7 @@ def test_download_without_a_given_filename(

download_path = subsonic.media_retrieval.download(song["id"], tmp_path)

# Check if the file data has been unaltered
# Check if the file data has been altered
with open(tmp_path / download_metadata.default_filename, "r") as file:
assert placeholder_data == file.read()

Expand All @@ -65,6 +68,95 @@ 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_captions_with_a_given_filename(
subsonic: Subsonic,
mock_get_captions_vtt: Response,
tmp_path: Path,
placeholder_data: str,
song: dict[str, Any],
vtt_metadata: FileMetadata,
):
responses.add(mock_get_captions_vtt)

download_path = subsonic.media_retrieval.get_captions(
song["id"], tmp_path / vtt_metadata.output_filename
)

# Check if the file data has been altered
with open(tmp_path / vtt_metadata.output_filename, "r") as file:
assert placeholder_data == file.read()

assert download_path == tmp_path / vtt_metadata.output_filename


@responses.activate
@pytest.mark.parametrize(
"mock, metadata",
[
("mock_get_captions_vtt", "vtt_metadata"),
("mock_get_captions_srt", "srt_metadata"),
],
)
def test_get_captions_without_a_given_filename(
request: FixtureRequest,
subsonic: Subsonic,
mock: str,
tmp_path: Path,
placeholder_data: str,
song: dict[str, Any],
metadata: str,
):
# Retrieve the mocks dynamically as their tests are equal
get_mock: Response = request.getfixturevalue(mock)
get_metadata: FileMetadata = request.getfixturevalue(metadata)

responses.add(get_mock)

download_path = subsonic.media_retrieval.get_captions(song["id"], tmp_path)

# Check if the file data has been altered
with open(tmp_path / get_metadata.default_filename, "r") as file:
assert placeholder_data == file.read()

assert download_path == tmp_path / get_metadata.default_filename


@responses.activate
@pytest.mark.parametrize(
"mock, metadata, file_format",
[
("mock_get_captions_prefer_vtt", "vtt_metadata", SubtitlesFileFormat.VTT),
("mock_get_captions_prefer_srt", "srt_metadata", SubtitlesFileFormat.SRT),
],
)
def test_get_captions_with_a_preferred_file_format(
request: FixtureRequest,
subsonic: Subsonic,
mock: str,
tmp_path: Path,
placeholder_data: str,
song: dict[str, Any],
metadata: str,
file_format: SubtitlesFileFormat,
):
# Retrieve the mocks dynamically as their tests are equal
get_mock: Response = request.getfixturevalue(mock)
get_metadata: FileMetadata = request.getfixturevalue(metadata)

responses.add(get_mock)

download_path = subsonic.media_retrieval.get_captions(
song["id"], tmp_path, file_format
)

# Check if the file data has been altered
with open(tmp_path / get_metadata.default_filename, "r") as file:
assert placeholder_data == file.read()

assert download_path == tmp_path / get_metadata.default_filename


@responses.activate
def test_get_cover_art_with_a_given_filename(
subsonic: Subsonic,
Expand All @@ -81,7 +173,7 @@ def test_get_cover_art_with_a_given_filename(
song["coverArt"], tmp_path / cover_art_metadata.output_filename, cover_art_size
)

# Check if the file data has been unaltered
# Check if the file data has been altered
with open(tmp_path / cover_art_metadata.output_filename, "r") as file:
assert placeholder_data == file.read()

Expand All @@ -104,7 +196,7 @@ def test_get_cover_art_without_a_given_filename(
song["coverArt"], tmp_path, cover_art_size
)

# Check if the file data has been unaltered
# Check if the file data has been altered
with open(tmp_path / cover_art_metadata.default_filename, "r") as file:
assert placeholder_data == file.read()

Expand All @@ -126,7 +218,7 @@ def test_get_avatar_with_a_given_filename(
username, tmp_path / avatar_metadata.output_filename
)

# Check if the file data has been unaltered
# Check if the file data has been altered
with open(tmp_path / avatar_metadata.output_filename, "r") as file:
assert placeholder_data == file.read()

Expand All @@ -146,7 +238,7 @@ def test_get_avatar_without_a_given_filename(

download_path = subsonic.media_retrieval.get_avatar(username, tmp_path)

# Check if the file data has been unaltered
# Check if the file data has been altered
with open(tmp_path / avatar_metadata.default_filename, "r") as file:
assert placeholder_data == file.read()

Expand Down
77 changes: 67 additions & 10 deletions tests/mocks/media_retrieval.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from dataclasses import dataclass
from collections import namedtuple
from pathlib import Path
from typing import Any, Protocol

Expand Down Expand Up @@ -52,15 +52,9 @@ def inner(
return inner


@dataclass
class FileMetadata:
# The default filename that Knuckles should use if no custom one is provided
default_filename: str

# The custom filename given to Knuckles
output_filename: str

content_type: str
FileMetadata = namedtuple(
"FileMetadata", ["default_filename", "output_filename", "content_type"]
)


@pytest.fixture
Expand All @@ -85,6 +79,69 @@ def mock_download(
)


@pytest.fixture
def vtt_metadata(song: dict[str, Any]) -> FileMetadata:
return FileMetadata(f"{song['id']}.vtt", "output.vtt", "text/vtt")


@pytest.fixture
def mock_get_captions_vtt(
mock_download_file_generator: MockDownload,
song: dict[str, Any],
vtt_metadata: FileMetadata,
) -> Response:
return mock_download_file_generator(
"getCaptions",
{"id": song["id"]},
vtt_metadata.content_type,
)


@pytest.fixture
def mock_get_captions_prefer_vtt(
mock_download_file_generator: MockDownload,
song: dict[str, Any],
vtt_metadata: FileMetadata,
) -> Response:
return mock_download_file_generator(
"getCaptions",
{"id": song["id"], "format": "vtt"},
vtt_metadata.content_type,
)


@pytest.fixture
def srt_metadata(song: dict[str, Any]) -> FileMetadata:
# This MIME TYPE is not approved by the IANA
return FileMetadata(f"{song['id']}.srt", "output.srt", "application/x-subrip")


@pytest.fixture
def mock_get_captions_srt(
mock_download_file_generator: MockDownload,
song: dict[str, Any],
srt_metadata: FileMetadata,
) -> Response:
return mock_download_file_generator(
"getCaptions",
{"id": song["id"]},
srt_metadata.content_type,
)


@pytest.fixture
def mock_get_captions_prefer_srt(
mock_download_file_generator: MockDownload,
song: dict[str, Any],
srt_metadata: FileMetadata,
) -> Response:
return mock_download_file_generator(
"getCaptions",
{"id": song["id"], "format": "srt"},
srt_metadata.content_type,
)


@pytest.fixture
def cover_art_metadata(song: dict[str, Any]) -> FileMetadata:
return FileMetadata(f"{song['coverArt']}.png", "output.png", "image/png")
Expand Down

0 comments on commit c269276

Please sign in to comment.