Merge pull request 'CR service update' (#2) from ToonsHub/devine:master into master

Reviewed-on: TPD94/devine#2
master
TPD94 2024-03-06 20:04:45 +00:00
commit 8f218aa792
1 changed files with 104 additions and 25 deletions

View File

@ -159,7 +159,13 @@ class CR(Service):
for seies_seasons in range(series_metadata.json()['total']):
# Get the season id
season_id = series_metadata.json()['data'][seies_seasons]['id']
season_data = series_metadata.json()['data'][seies_seasons]
# Attempt to get season IDs for the version with original language.
if season_data.get("versions"):
season_id = [ s["guid"] for s in season_data["versions"] if s["original"] ][0]
else:
season_id = season_id['id']
# Append it to the season ids list
season_ids.append(season_id)
@ -200,12 +206,12 @@ class CR(Service):
episode_class = Episode(id_=episode_id, title=episode_season_title, season=episode_season, number=episode_number, name=episode_name, year=episode_year, service=self.__class__)
# Check whether you can stream the title or not
if not episode_metadata.json()['data'][episode].get('streams_link'):
logging.getLogger("CR").info(f"Unable to stream ID: {episode_id}")
if self.no_login and not episode_metadata.json()['data'][episode].get('streams_link'):
logging.getLogger("CR").warning(f"Media ID: {episode_id} is Premium Only")
continue
# Append the stream link
episode_class.data = episode_metadata.json()['data'][episode]['streams_link']
# Append the stream links into a list
episode_class.streams = episode_metadata.json()['data'][0]['versions'] or []
# Append it to the list
episodes.append(episode_class)
@ -224,33 +230,89 @@ class CR(Service):
# Update the headers
self.session.headers.update(headers)
# Get the MPD info
mpd_info = self.session.get(url=f'https://www.crunchyroll.com/{title.data}')
tracks = Tracks()
# Get the MPD URL
mpd_url = mpd_info.json()['data'][0]['drm_adaptive_dash']['']['url']
mpd_lang = mpd_info.json()['meta']['audio_locale']
# Get the MPD info for each stream
for stream in title.streams:
# Grab the tracks from the MPD
tracks = DASH.from_url(url=mpd_url).to_tracks(language=mpd_lang)
# Get the MPD info
media_guid = stream["media_guid"]
episode_id = stream['guid']
mpd_info = self.session.get(f'https://www.crunchyroll.com/content/v2/cms/videos/{media_guid}/streams')
# Get subtitles
for _, sub in mpd_info.json()['meta']['subtitles'].items():
tracks.add(
Subtitle(
url=sub['url'],
codec=Subtitle.Codec.from_mime(sub['format']),
language=sub['locale'],
forced=(sub['locale'] == mpd_lang), # If audio language matches subtitle language, it's Forced.
# Check for error HTML pages
try: mpd_info.json()
except:
logging.getLogger("CR").warning(f"Unable to get streams for Media ID: {episode_id}")
continue
# Get the MPD URL
mpd_url = mpd_info.json()['data'][0]['drm_adaptive_dash']['']['url']
mpd_lang = mpd_info.json()['meta']['audio_locale']
# Check whether MPD has original lang
original_vid = [ v["media_guid"] for v in mpd_info.json()['meta']['versions'] if v['original']][0]
is_original = original_vid == mpd_info.json()['meta']['media_id']
# Grab the tracks from the MPD
mpd_tracks = DASH.from_url(url=mpd_url).to_tracks(language=mpd_lang)
# Only save the video stream from the first MPD
if len(tracks.videos) == 0:
for video_track in mpd_tracks.videos:
video_track.data.update({"episode_id": stream['guid'], "media_id": stream['media_guid']})
tracks.add(video_track)
# Save audio tracks
for audio_track in mpd_tracks.audio:
audio_track.is_original_lang = is_original
audio_track.data.update({"episode_id": stream['guid'], "media_id": stream['media_guid']})
tracks.add(audio_track)
# Get subtitles
for _, sub in mpd_info.json()['meta']['subtitles'].items():
tracks.add(
Subtitle(
url=sub['url'],
codec=Subtitle.Codec.from_mime(sub['format']),
language=sub['locale'],
forced=(sub['locale'] == mpd_lang), # If audio language matches subtitle language, it's Forced.
)
)
)
# Return the tracks
return tracks
# Defining a function to get chapters
def get_chapters(self, title):
return []
chapters_data = requests.get(f"https://static.crunchyroll.com/skip-events/production/{title.id}.json")
# When Chapters are missing, it returns Access Denied XML Page.
if "Access Denied" in chapters_data.text:
logging.getLogger("CR").warning(f"No chapters found for Media ID: {title.id}")
return []
# Sort Chapters
raw_chapters = []
for _, chapter_data in chapters_data.json().items():
if type(chapter_data) is str: continue
if not chapter_data.get("start"): continue
raw_chapters.append(chapter_data)
raw_chapters = sorted( raw_chapters, key=lambda k: k["start"] )
# Convert to Chapter Class (More Explanation needs to be added)
chapters = []
chapter_index = 1
next_chapter_start = 0
for chapter in raw_chapters:
if chapter['start'] != next_chapter_start:
chapters.append(Chapter(timestamp=next_chapter_start*1000, name=f"Chapter {chapter_index}"))
chapter_index += 1
chapters.append(Chapter(timestamp=chapter['start']*1000, name=chapter['type'].capitalize()))
next_chapter_start = chapter['end']
return chapters
# Defining a function to get 'x-cr-video-token'
def get_video_token(self, video_id: str = None):
@ -269,6 +331,10 @@ class CR(Service):
# Send a request to get the video token
get_token = self.session.get(f'https://cr-play-service.prd.crunchyrollsvc.com/v1/{list_video_id[0]}/web/firefox/play')
# Check for errors
if get_token.json().get('error'):
raise Exception(f"Error encountered while getting token: " + get_token.json()['error'])
# Get token from JSON response
token = get_token.json()['token']
@ -281,14 +347,21 @@ class CR(Service):
# Set the license server
crunchyroll_license_server = 'https://cr-license-proxy.prd.crunchyrollsvc.com/v1/license/widevine'
# Get the Episode ID
episode_id = track.data["episode_id"]
# Get Video Token
video_token = self.get_video_token(video_id={episode_id})
jwtToken = self.authenticate(cookies=self.session.cookies)
# Set the headers
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
'Referer': 'https://static.crunchyroll.com/',
'Authorization': f'{self.authenticate(cookies=self.session.cookies)}',
'Authorization': f'{jwtToken}',
'content-type': 'application/octet-stream',
'x-cr-content-id': f'{title.id}',
'x-cr-video-token': f'{self.get_video_token(video_id={title.id})}',
'x-cr-content-id': f'{episode_id}',
'x-cr-video-token': f'{video_token}',
'Origin': 'https://static.crunchyroll.com',
}
@ -298,5 +371,11 @@ class CR(Service):
# Send the post request to the license server
license = self.session.post(url=crunchyroll_license_server, data=challenge)
# Close session to avoid TOO_MANY_ACTIVE_STREAMS error
self.session.delete(
f'https://cr-play-service.prd.crunchyrollsvc.com/v1/token/{episode_id}/{video_token}'
)
# Return the license
return license.json()['license']