Skip to content

Media Retrieval

MediaRetrieval(api, subsonic)

Class that contains all the methods needed to interact with the media retrieval endpoints in the Subsonic API.

Source code in .venv/lib/python3.11/site-packages/knuckles/_media_retrieval.py
def __init__(self, api: Api, subsonic: "Subsonic") -> None:
    self.api = api
    self.subsonic = subsonic

download(song_or_video_id, file_or_directory_path, use_stream=False)

Download a song or video from the server.

Parameters:

Name Type Description Default
song_or_video_id str

The ID of the song or video to download.

required
file_or_directory_path Path

The path where the downloaded file should be saved. If the given path is a directory then the file will be downloaded inside of it, if its a valid file path it will be downloaded using this exact filename.

required

Returns:

Type Description
Path

The path where the song or video was finally saved.

Source code in .venv/lib/python3.11/site-packages/knuckles/_media_retrieval.py
def download(
    self, song_or_video_id: str, file_or_directory_path: Path, use_stream=False
) -> Path:
    """Download a song or video from the server.

    Args:
        song_or_video_id: The ID of the song or video to download.
        file_or_directory_path: The path where the downloaded file should
            be saved. If the given path is a directory then the file will
            be downloaded inside of it, if its a valid file path it will be
            downloaded using this exact filename.

    Returns:
        The path where the song or video was finally saved.
    """

    if not use_stream:
        response = self.api.raw_request("download", {"id": song_or_video_id})
    else:
        response = self.api.raw_request("stream", {"id": song_or_video_id})

    def determinate_filename(file_response: Response) -> str:
        if "Content-Disposition" in file_response.headers:
            filename = (
                file_response.headers["Content-Disposition"]
                .split("filename=")[1]
                .strip()
            )
        else:
            filename = str(datetime.datetime.now())

        # Remove leading quote char
        if filename[0] == '"':
            filename = filename[1:]

        # Remove trailing quote char
        if filename[-1] == '"':
            filename = filename[:-1]

        return filename

    return self._handle_download(
        response, file_or_directory_path, determinate_filename
    )

get_avatar(username, file_or_directory_path)

Download the avatar image of a user from the server.

Parameters:

Name Type Description Default
username str

The username of the user to get its avatar from.

required
file_or_directory_path Path

The path where the downloaded file should be saved. If the given path is a directory then the file will be downloaded inside of it, if its a valid file path it will be downloaded using this exact filename.

required

Returns:

Type Description
Path

The path where the avatar image was finally saved.

Source code in .venv/lib/python3.11/site-packages/knuckles/_media_retrieval.py
def get_avatar(self, username: str, file_or_directory_path: Path) -> Path:
    """Download the avatar image of a user from the server.

    Args:
        username: The username of the user to get its avatar from.
        file_or_directory_path: The path where the downloaded file should
            be saved. If the given path is a directory then the file will
            be downloaded inside of it, if its a valid file path it will be
            downloaded using this exact filename.

    Returns:
        The path where the avatar image was finally saved.
    """

    response = self.api.raw_request("getAvatar", {"username": username})

    def determinate_filename(file_response: Response) -> str:
        file_extension = guess_extension(
            file_response.headers["content-type"].partition(";")[0].strip()
        )

        return username + file_extension if file_extension else username

    return self._handle_download(
        response, file_or_directory_path, determinate_filename
    )

get_captions(caption_id, file_or_directory_path, subtitles_file_format=SubtitlesFileFormat.VTT)

Download a video caption file from the server.

Parameters:

Name Type Description Default
caption_id str

The ID of the caption to download.

required
file_or_directory_path Path

The path where the downloaded file should be saved. If the given path is a directory then the file will be downloaded inside of it, if its a valid file path it will be downloaded using this exact filename.

required
subtitles_file_format SubtitlesFileFormat

The format that the subtitle file should have.

VTT

Returns:

Type Description
Path

The path where the captions was finally saved.

Source code in .venv/lib/python3.11/site-packages/knuckles/_media_retrieval.py
def get_captions(
    self,
    caption_id: str,
    file_or_directory_path: Path,
    subtitles_file_format: SubtitlesFileFormat = SubtitlesFileFormat.VTT,
) -> Path:
    """Download a video caption file from the server.

    Args:
        caption_id: The ID of the caption to download.
        file_or_directory_path: The path where the downloaded file should
            be saved. If the given path is a directory then the file will
            be downloaded inside of it, if its a valid file path it will be
            downloaded using this exact filename.
        subtitles_file_format: The format that the subtitle file should
            have.

    Returns:
        The path where the captions was finally saved.
    """

    # Check if the given file format is a valid one
    SubtitlesFileFormat(subtitles_file_format.value)

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

    def determinate_filename(file_response: Response) -> str:
        mime_type = file_response.headers["content-type"].partition(";")[0].strip()

        # application/x-subrip is not a valid MIME TYPE so a manual check is needed
        file_extension: str | None = None
        if mime_type == "application/x-subrip":
            file_extension = ".srt"
        else:
            file_extension = guess_extension(mime_type)

        return caption_id + file_extension if file_extension else caption_id

    return self._handle_download(
        response, file_or_directory_path, determinate_filename
    )

get_cover_art(cover_art_id, file_or_directory_path, size=None)

Download the cover art from the server.

Parameters:

Name Type Description Default
cover_art_id str

The ID of the cover art to download.

required
file_or_directory_path Path

The path where the downloaded file should be saved. If the given path is a directory then the file will be downloaded inside of it, if its a valid file path it will be downloaded using this exact filename.

required
size int | None

The width in pixels that the image should have, the cover arts are always squares.

None

Returns:

Type Description
Path

The path where the captions was finally saved.

Source code in .venv/lib/python3.11/site-packages/knuckles/_media_retrieval.py
def get_cover_art(
    self, cover_art_id: str, file_or_directory_path: Path, size: int | None = None
) -> Path:
    """Download the cover art from the server.

    Args:
        cover_art_id: The ID of the cover art to download.
        file_or_directory_path: The path where the downloaded file should
            be saved. If the given path is a directory then the file will
            be downloaded inside of it, if its a valid file path it will be
            downloaded using this exact filename.
        size: The width in pixels that the image should have,
            the cover arts are always squares.

    Returns:
        The path where the captions was finally saved.
    """

    response = self.api.raw_request(
        "getCoverArt", {"id": cover_art_id, "size": size}
    )

    def determinate_filename(file_response: Response) -> str:
        file_extension = guess_extension(
            file_response.headers["content-type"].partition(";")[0].strip()
        )

        return cover_art_id + file_extension if file_extension else cover_art_id

    return self._handle_download(
        response, file_or_directory_path, determinate_filename
    )

get_lyrics(artist_name=None, song_title=None)

Get the lyrics of a song.

Parameters:

Name Type Description Default
artist_name str | None

The name of the artist that made the song to get its lyrics from.

None
song_title str | None

The title of the song to get its lyrics from.

None

Returns:

Type Description
Lyrics

An object that contains all the info about the requested lyrics.

Source code in .venv/lib/python3.11/site-packages/knuckles/_media_retrieval.py
def get_lyrics(
    self, artist_name: str | None = None, song_title: str | None = None
) -> Lyrics:
    """Get the lyrics of a song.

    Args:
        artist_name: The name of the artist that made the song to get its
            lyrics from.
        song_title: The title of the song to get its lyrics from.

    Returns:
        An object that contains all the info about the requested
            lyrics.
    """

    response = self.api.json_request(
        "getLyrics", {"artist": artist_name, "title": song_title}
    )["lyrics"]

    return Lyrics(subsonic=self.subsonic, **response)

hls(song_or_video_id, custom_bitrates=None, audio_track_id=None)

Get the URL required to stream a song or video with hls.m3u8.

Parameters:

Name Type Description Default
song_or_video_id str

The ID of the song or video to stream.

required
custom_bitrates list[str] | None

The bitrate that the server should try to limit the stream to. If more that one is specified the server will create a variant playlist, suitable for adaptive bitrate streaming.

None
audio_track_id str | None

The ID of an audio track to be added to the stream if video is being streamed.

None

Returns:

Type Description
str

An URL with all the needed parameters to start a streaming with hls.m3u8 using a GET request.

Source code in .venv/lib/python3.11/site-packages/knuckles/_media_retrieval.py
def hls(
    self,
    song_or_video_id: str,
    custom_bitrates: list[str] | None = None,
    audio_track_id: str | None = None,
) -> str:
    """Get the URL required to stream a song or video with hls.m3u8.

    Args:
        song_or_video_id: The ID of the song or video to stream.
        custom_bitrates: The bitrate that the server should try to
            limit the stream to. If more that one is specified the
            server will create a `variant playlist`, suitable for adaptive
            bitrate streaming.
        audio_track_id: The ID of an audio track to be added to the stream
            if video is being streamed.

    Returns:
        An URL with all the needed parameters to start a streaming
            with hls.m3u8 using a GET request.
    """

    return self.subsonic.api.generate_url(
        "hls.m3u8",
        {
            "id": song_or_video_id,
            "bitRate": custom_bitrates,
            "audioTrack": audio_track_id,
        },
    )

stream(song_or_video_id, max_bitrate_rate=None, stream_format=None, time_offset=None, size=None, estimate_content_length=None, converted=None)

Get the URL required to stream a song or video.

Parameters:

Name Type Description Default
song_or_video_id str

The ID of the song or video to get its steam URL

required
max_bitrate_rate int | None

The max bitrate the stream should have.

None
stream_format str | None

The format the song or video should be. Warning: The available formats are dependant of the server implementation. The only secure format is "raw", which disabled transcoding at all.

None
time_offset int | None

An offset where the stream should start. It may not work with video, depending of the server configuration.

None
size str | None

The maximum resolution of the streaming in the format WxH, only works with video streaming.

None
estimate_content_length bool | None

When set to true the response with have the Content-Length HTTP header set to a estimated duration for the streamed song or video.

None
converted bool | None

If set to true the server will try to stream a transcoded version in MP4. Only works with video streaming.

None

Returns:

Type Description
str

An URL with all the needed parameters to start a streaming using a GET request.

Source code in .venv/lib/python3.11/site-packages/knuckles/_media_retrieval.py
def stream(
    self,
    song_or_video_id: str,
    max_bitrate_rate: int | None = None,
    stream_format: str | None = None,
    time_offset: int | None = None,
    size: str | None = None,
    estimate_content_length: bool | None = None,
    converted: bool | None = None,
) -> str:
    """Get the URL required to stream a song or video.

    Args:
        song_or_video_id: The ID of the song or video to get its
            steam URL
        max_bitrate_rate: The max bitrate the stream should have.
        stream_format: The format the song or video should be.
            **Warning**: The available formats are dependant of the
            server implementation. The only secure format is "raw",
            which disabled transcoding at all.
        time_offset: An offset where the stream should start. It may
            not work with video, depending of the server configuration.
        size: The maximum resolution of the streaming in the format `WxH`,
            only works with video streaming.
        estimate_content_length: When set to true the response with have
            the `Content-Length` HTTP header set to a estimated duration
            for the streamed song or video.
        converted: If set to true the server will try to stream a
            transcoded version in `MP4`. Only works with video
            streaming.

    Returns:
        An URL with all the needed parameters to start a streaming
            using a GET request.
    """

    return self.subsonic.api.generate_url(
        "stream",
        {
            "id": song_or_video_id,
            "maxBitRate": max_bitrate_rate,
            "format": stream_format,
            "timeOffset": time_offset,
            "size": size,
            "estimateContentLength": estimate_content_length,
            "converted": converted,
        },
    )