Barbagus
56c1e8468a
Significant rewrite after model modification: introducing `*Sources` objects that encapsulate metadata and fetch information (urls, protocols). The API (#20) is organized as pipe elements with sources being what flows through the pipe. 1. fetch program sources 2. fetch rendition sources 3. fetch variant sources 4. fetch targets 5. process (download+mux) targets Some user selection filter or modifiers could then be applied at any step of the pipe. Our __main__.py is an implementation of that scheme. Implied modifications include: - Later failure on unsupported protocols, used to be in `api`, now in `hls`. This offers the possibility to filter and/or support them later. - Give up honoring the http ranges for mp4 download, stream-download them by fixed chunk instead. - Cleaning up of the `hls` module moving the main download function to __init__ and specific (mp4/vtt) download functions to a new `download` module. On the side modifications include: - The progress handler showing downloading rates. - The naming utilities providing rendition and variant code insertion. - Download parts to working directories and skip unnecessary re-downloads on failure. This was a big change for a single commit... too big of a change maybe.
70 lines
2.1 KiB
Python
70 lines
2.1 KiB
Python
# License: GNU AGPL v3: http://www.gnu.org/licenses/
|
|
# This file is part of `delarte` (https://git.afpy.org/fcode/delarte.git)
|
|
|
|
"""Provide ArteTV JSON API utilities."""
|
|
|
|
from .error import UnexpectedAPIResponse
|
|
from .model import Rendition
|
|
|
|
MIME_TYPE = "application/vnd.api+json; charset=utf-8"
|
|
|
|
|
|
def _fetch_api_object(http_session, url, object_type):
|
|
# Fetch an API object.
|
|
|
|
r = http_session.get(url)
|
|
r.raise_for_status()
|
|
|
|
mime_type = r.headers["content-type"]
|
|
if mime_type != MIME_TYPE:
|
|
raise UnexpectedAPIResponse("MIME_TYPE", url, MIME_TYPE, mime_type)
|
|
|
|
obj = r.json()
|
|
|
|
try:
|
|
data_type = obj["data"]["type"]
|
|
if data_type != object_type:
|
|
raise UnexpectedAPIResponse("OBJECT_TYPE", url, object_type, data_type)
|
|
|
|
return obj["data"]["attributes"]
|
|
|
|
except (KeyError, IndexError, ValueError) as e:
|
|
raise UnexpectedAPIResponse("SCHEMA", url) from e
|
|
|
|
|
|
def iter_renditions(program_id, player_config_url, http_session):
|
|
"""Iterate over renditions for the given program."""
|
|
obj = _fetch_api_object(http_session, player_config_url, "ConfigPlayer")
|
|
|
|
codes = set()
|
|
try:
|
|
provider_id = obj["metadata"]["providerId"]
|
|
if provider_id != program_id:
|
|
raise UnexpectedAPIResponse(
|
|
"PROVIDER_ID_MISMATCH", player_config_url, provider_id
|
|
)
|
|
|
|
for s in obj["streams"]:
|
|
code = s["versions"][0]["eStat"]["ml5"]
|
|
|
|
if code in codes:
|
|
raise UnexpectedAPIResponse(
|
|
"DUPLICATE_RENDITION_CODE", player_config_url, code
|
|
)
|
|
codes.add(code)
|
|
|
|
yield (
|
|
Rendition(
|
|
s["versions"][0]["eStat"]["ml5"],
|
|
s["versions"][0]["label"],
|
|
),
|
|
s["protocol"],
|
|
s["url"],
|
|
)
|
|
|
|
except (KeyError, IndexError, ValueError) as e:
|
|
raise UnexpectedAPIResponse("SCHEMA", player_config_url) from e
|
|
|
|
if not codes:
|
|
raise UnexpectedAPIResponse("NO_RENDITIONS", player_config_url)
|