from __future__ import annotations from http.cookiejar import MozillaCookieJar from typing import Any, Optional, Union from functools import partial from pathlib import Path import sys import re import click import isodate from click import Context from unshackle.core.credential import Credential from unshackle.core.service import Service from unshackle.core.titles import Movie, Movies, Episode, Series from unshackle.core.tracks import Track, Chapter, Tracks, Video, Audio, Subtitle from unshackle.core.manifests.hls import HLS from unshackle.core.manifests.dash import DASH class NRK(Service): """ Service code for NRK TV (https://tv.nrk.no) \b Version: 1.0.0 Author: lambda Authorization: None Robustness: Unencrypted: 1080p, DD5.1 """ GEOFENCE = ("no",) TITLE_RE = r"^https://tv.nrk.no/serie/fengselseksperimentet/sesong/1/episode/(?P.+)$" @staticmethod @click.command(name="NRK", short_help="https://tv.nrk.no", help=__doc__) @click.argument("title", type=str) @click.pass_context def cli(ctx: Context, **kwargs: Any) -> NRK: return NRK(ctx, **kwargs) def __init__(self, ctx: Context, title: str): self.title = title super().__init__(ctx) def authenticate(self, cookies: Optional[MozillaCookieJar] = None, credential: Optional[Credential] = None) -> None: pass def get_titles(self) -> Union[Movies, Series]: match = re.match(self.TITLE_RE, self.title) if not match: return content_id = match.group("content_id") r = self.session.get(self.config["endpoints"]["content"].format(content_id=content_id)) item = r.json() episode, name = item["programInformation"]["titles"]["title"].split(". ", maxsplit=1) return Series([Episode( id_=content_id, service=self.__class__, language="nb", year=item["moreInformation"]["productionYear"], title=item["_links"]["seriesPage"]["title"], name=name, season=item["_links"]["season"]["name"], number=episode, )]) def get_tracks(self, title: Union[Episode, Movie]) -> Tracks: r = self.session.get(self.config["endpoints"]["manifest"].format(content_id=title.id)) manifest = r.json() tracks = Tracks() for asset in manifest["playable"]["assets"]: if asset["format"] == "HLS": tracks += Tracks(HLS.from_url(asset["url"], session=self.session).to_tracks("nb")) for sub in manifest["playable"]["subtitles"]: tracks.add(Subtitle( codec=Subtitle.Codec.WebVTT, language=sub["language"], url=sub["webVtt"], sdh=sub["type"] == "ttv", )) for track in tracks: track.needs_proxy = True # if isinstance(track, Audio) and track.channels == 6.0: # track.channels = 5.1 return tracks def get_chapters(self, title: Union[Episode, Movie]) -> list[Chapter]: r = self.session.get(self.config["endpoints"]["metadata"].format(content_id=title.id)) sdi = r.json()["skipDialogInfo"] chapters = [] if sdi["endIntroInSeconds"]: if sdi["startIntroInSeconds"]: chapters.append(Chapter(timestamp=0)) chapters |= [ Chapter(timestamp=sdi["startIntroInSeconds"], name="Intro"), Chapter(timestamp=sdi["endIntroInSeconds"]) ] if sdi["startCreditsInSeconds"]: if not chapters: chapters.append(Chapter(timestamp=0)) credits = isodate.parse_duration(sdi["startCredits"]) chapters.append(Chapter(credits.total_seconds(), name="Credits")) return chapters