unshackle-services/NRK/__init__.py

123 lines
3.3 KiB
Python
Raw Normal View History

2025-08-03 01:47:10 +02:00
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
2025-10-10 05:17:43 +02:00
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
2025-08-03 01:47:10 +02:00
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<content_id>.+)$"
@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