From be0166fcb2cbf141a68d58661bdff2cb5d79076d Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 29 Nov 2024 12:02:25 +0000 Subject: [PATCH] Upload files to "Services" --- Services/jio.py | 343 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100644 Services/jio.py diff --git a/Services/jio.py b/Services/jio.py new file mode 100644 index 0000000..454ca8b --- /dev/null +++ b/Services/jio.py @@ -0,0 +1,343 @@ +from __future__ import annotations + +import base64 +import json +import re as rex +import os +import requests +from langcodes import Language +#from typing import Any, Optional, Union +import datetime +import click + +from vinetrimmer.objects import MenuTrack, TextTrack, Title, Tracks, Track +from vinetrimmer.services.BaseService import BaseService + + +class Jio(BaseService): + """ + Service code for Viacom18's JioCinema streaming service (https://www.jiocinema.com/). + + \b + Authorization: Token + Security: UHD@L3 FHD@L3 + + """ + + PHONE_NUMBER = "" # Add number with country code + + ALIASES = ["JIO", "JioCinema"] + #GEOFENCE = ["in2"] + + @staticmethod + @click.command(name="Jio", short_help="https://www.jiocinema.com") + @click.argument("title", type=str) + @click.option("-m", "--movie", is_flag=True, default=False, help="Title is a Movie.") + @click.pass_context + def cli(ctx, **kwargs): + return Jio(ctx, **kwargs) + + def __init__(self, ctx, title: str, movie: bool): + self.title = title + self.movie = movie + super().__init__(ctx) + + assert ctx.parent is not None + + self.vcodec = ctx.parent.params["vcodec"] + self.acodec = ctx.parent.params["acodec"] + self.range = ctx.parent.params["range_"] + + self.profile = ctx.obj.profile + + self.token: str + self.refresh_token: str + self.license_api = None + + self.configure() + + def get_titles(self): + titles = [] + if self.movie: + res = self.session.get( + url='https://content-jiovoot.voot.com/psapi/voot/v1/voot-tv/content/query/asset-details?ids=include%3A{id}&devicePlatformType=androidtv&responseType=common&page=1'.format(id=self.title) + ) + try: + data = res.json()['result'][0] + self.log.debug(json.dumps(data, indent=4)) + except json.JSONDecodeError: + raise ValueError(f"Failed to load title manifest: {res.text}") + + titles.append(Title( + id_=data['id'], + type_=Title.Types.MOVIE, + name=rex.sub(r'\([^)]*\)', '', data["fullTitle"]).strip(), + year=data.get("releaseYear"), + original_lang=Language.find(data['languages'][0]), + source=self.ALIASES[0], + service_data=data + )) + + else: + def get_recursive_episodes(season_id): + total_attempts = 1 + recursive_episodes = [] + season_params = { + 'sort': 'episode:asc', + 'responseType': 'common', + 'id': season_id, + 'page': 1 + } + while True: + episode = self.session.get(url='https://content-jiovoot.voot.com/psapi/voot/v1/voot-web/content/generic/series-wise-episode', params=season_params).json() + if any(episode["result"]): + total_attempts += 1 + recursive_episodes.extend(episode["result"]) + season_params.update({'page': total_attempts}) + else: + break + return recursive_episodes + # params = { + # 'sort': 'season:asc', + # 'id': self.title, + # 'responseType': 'common' + # } + re = self.session.get(url='https://content-jiovoot.voot.com/psapi/voot/v1/voot-tv/view/show/{id}?devicePlatformType=androidtv&responseType=common&page=1'.format(id=self.title)).json()['trays'][1] + self.log.debug(json.dumps(re, indent=4)) + for season in re['trayTabs']: + season_id = season["id"] + recursive_episodes = get_recursive_episodes(season_id) + self.log.debug(json.dumps(recursive_episodes, indent=4)) + for episodes in recursive_episodes: + titles.append(Title( + id_=episodes["id"], + type_=Title.Types.TV, + name=rex.sub(r'\([^)]*\)', '', episodes["showName"]).strip(), + season=int(float(episodes["season"])), + episode=int(float(episodes["episode"])), + episode_name=episodes["fullTitle"], + original_lang=Language.find(episodes['languages'][0]), + source=self.ALIASES[0], + service_data=episodes + )) + + return titles + + def get_tracks(self, title: Title) -> Tracks: + #self.log.debug(json.dumps(title.service_data, indent=4)) + json_data = { + '4k': True, + 'ageGroup': '18+', + 'appVersion': '4.0.9', + 'bitrateProfile': 'xxxhdpi', + 'capability': { + 'drmCapability': { + 'aesSupport': 'yes', + 'fairPlayDrmSupport': 'yes', + 'playreadyDrmSupport': 'yes', + 'widevineDRMSupport': 'L1', + }, + 'frameRateCapability': [ + { + 'frameRateSupport': '60fps', + 'videoQuality': '2160p', + }, + ], + }, + 'continueWatchingRequired': False, + 'dolby': True, + 'downloadRequest': False, + 'hevc': False, + 'kidsSafe': False, + 'manufacturer': 'NVIDIA', + 'model': 'SHIELDTV', + 'multiAudioRequired': True, + 'osVersion': '12', + 'parentalPinValid': True, + 'x-apisignatures': 'o668nxgzwff', + } + + try: + res = self.session.post( + url = f'https://apis-jiovoot.voot.com/playbackjv/v3/{title.id}', + json=json_data, + ) + except requests.exceptions.RequestException: + self.refresh() + try: + res = self.session.post( + url = f'https://apis-jiovoot.voot.com/playbackjv/v3/{title.id}', + json=json_data + ) + except requests.exceptions.RequestException: + self.log.exit("Unable to retrive manifest") + + res = res.json() + self.log.debug(json.dumps(res, indent=4)) + self.license_api = res['data']['playbackUrls'][0].get('licenseurl') + vid_url = res['data']['playbackUrls'][0].get('url') + + if "mpd" in vid_url: + tracks = Tracks.from_mpd( + url=vid_url, + session=self.session, + #lang=title.original_lang, + source=self.ALIASES[0] + ) + else: + self.log.exit('No mpd found') + + self.log.info(f"Getting audio from Various manifests for potential higher bitrate or better codec") + for device in ['androidtablet']: #'androidmobile', 'androidweb' ==> what more devices ? + self.session.headers.update({'x-platform': device}) + audio_mpd_url = self.session.post(url=f'https://apis-jiovoot.voot.com/playbackjv/v3/{title.id}', json=json_data) + if audio_mpd_url.status_code != 200: + self.log.warning("Unable to retrive manifest") + else: + audio_mpd_url = audio_mpd_url.json()['data']['playbackUrls'][0].get('url') + if "mpd" in audio_mpd_url: + audio_mpd = Tracks([ + x for x in iter(Tracks.from_mpd( + url=audio_mpd_url, + session=self.session, + source=self.ALIASES[0], + #lang=title.original_lang, + )) + ]) + tracks.add(audio_mpd.audios) + else: + self.log.warning('No mpd found') + + for track in tracks: + #track.language = Language.get('ta') + track.needs_proxy = True + + return tracks + + def get_chapters(self, title: Title) -> list[MenuTrack]: + return [] + + def certificate(self, **kwargs) -> None: + return self.license(**kwargs) + + def license(self, challenge: bytes, **_) -> bytes: + assert self.license_api is not None + self.session.headers.update({ + 'x-playbackid': '5ec82c75-6fda-4b47-b2a5-84b8d9079675', + 'x-feature-code': 'ytvjywxwkn', + 'origin': 'https://www.jiocinema.com', + 'referer': 'https://www.jiocinema.com/', + }) + return self.session.post( + url=self.license_api, + data=challenge, # expects bytes + ).content + + def refresh(self) -> None: + self.log.info(" + Refreshing auth tokens...") + res = self.session.post( + url="https://auth-jiocinema.voot.com/tokenservice/apis/v4/refreshtoken", + json={ + 'appName': 'RJIL_JioCinema', + 'deviceId': '332536276', + 'refreshToken': self.refresh_token, + 'appVersion': '5.6.0' + } + ) + if res.status_code != 200: + return self.log.warning('Tokens cannot be Refreshed. Something went wrong..') + + self.token = res.json()["authToken"] + self.refresh_token = res.json()["refreshTokenId"] + self.session.headers.update({'accesstoken': self.token}) + token_cache_path = self.get_cache("token_{profile}.json".format(profile=self.profile)) + old_data = json.load(open(token_cache_path, "r", encoding="utf-8")) + old_data.update({ + 'authToken': self.token, + 'refreshToken': self.refresh_token + }) + json.dump(old_data, open(token_cache_path, "w", encoding="utf-8"), indent=4) + + def login(self): + self.log.info(' + Logging into JioCinema') + if not self.PHONE_NUMBER: + self.log.exit('Please provide Jiocinema registered Phone number....') + guest = self.session.post( + url="https://auth-jiocinema.voot.com/tokenservice/apis/v4/guest", + json={ + 'appName': 'RJIL_JioCinema', + 'deviceType': 'phone', + 'os': 'ios', + 'deviceId': '332536276', + 'freshLaunch': False, + 'adId': '332536276', + 'appVersion': '5.6.0', + } + ) + headers = { + 'accesstoken': guest.json()["authToken"], + 'appname': 'RJIL_JioCinema', + 'devicetype': 'phone', + 'os': 'ios' + } + send = self.session.post( + url="https://auth-jiocinema.voot.com/userservice/apis/v4/loginotp/send", + headers=headers, + json={ + 'number': '{}'.format(base64.b64encode(self.PHONE_NUMBER.encode("utf-8")).decode("utf-8")), + 'appVersion': '5.6.0' + } + ) + if send.status_code != 200: + self.log.exit("OTP Send Failed!") + else: + self.log.info("OTP has been sent. Please write it down below and press Enter") + otp = input() + verify_data = { + 'deviceInfo': { + 'consumptionDeviceName': 'iPhone', + 'info': { + 'platform': { + 'name': 'iPhone OS', + }, + 'androidId': '332536276', + 'type': 'iOS', + }, + }, + 'appVersion': '5.6.0', + 'number': '{}'.format(base64.b64encode(self.PHONE_NUMBER.encode("utf-8")).decode("utf-8")), + 'otp': '{}'.format(otp) + } + verify = self.session.post( + url="https://auth-jiocinema.voot.com/userservice/apis/v4/loginotp/verify", + headers=headers, + json=verify_data + ) + if verify.status_code != 200: + self.log.exit("Cannot be verified") + self.log.info(" + Verified!") + + return verify.json() + + def configure(self) -> None: + token_cache_path = self.get_cache("token_{profile}.json".format(profile=self.profile)) + if os.path.isfile(token_cache_path): + tokens = json.load(open(token_cache_path, "r", encoding="utf-8")) + self.log.info(" + Using cached auth tokens...") + else: + tokens = self.login() + os.makedirs(os.path.dirname(token_cache_path), exist_ok=True) + with open(token_cache_path, "w", encoding="utf-8") as file: + json.dump(tokens, file, indent=4) + self.token = tokens["authToken"] + self.refresh_token = tokens["refreshToken"] + self.session.headers.update({ + 'deviceid': '332536276', + 'accesstoken': self.token, + 'appname': 'RJIL_JioCinema', + 'uniqueid': 'be277ebe-e50b-441e-bc37-bd803286f3d5', + 'user-agent': 'Dalvik/2.1.0 (Linux; U; Android 9; SHIELD Android TV Build/PPR1.180610.011)', + 'x-apisignatures': 'o668nxgzwff', + 'x-platform': 'androidtv', # base device + 'x-platform-token': 'android', + }) \ No newline at end of file