From e3d6d0ce8b18240300f9658bc6b376f96d55a955 Mon Sep 17 00:00:00 2001 From: panitan103 Date: Mon, 30 Mar 2026 22:03:26 +0700 Subject: [PATCH] - Correct CR to handle SDH & Force subtitle correctly - Download VTT as SRT then convert to SRT using on_track_downloaded --- CR/__init__.py | 79 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 8 deletions(-) diff --git a/CR/__init__.py b/CR/__init__.py index e204bbb..498d781 100644 --- a/CR/__init__.py +++ b/CR/__init__.py @@ -8,6 +8,7 @@ import click import jwt from langcodes import Language +from unshackle.core.constants import AnyTrack from unshackle.core.manifests import DASH from unshackle.core.search_result import SearchResult from unshackle.core.service import Service @@ -17,13 +18,16 @@ from unshackle.core.tracks import Chapters, Tracks from unshackle.core.tracks.chapter import Chapter from unshackle.core.tracks.subtitle import Subtitle +import pysubs2 +from subby import WebVTTConverter,CommonIssuesFixer +from hashlib import md5 class CR(Service): """ Service code for Crunchyroll streaming service (https://www.crunchyroll.com). \b - Version: 3.0.0 + Version: 3.0.1 Author: sp4rk.y Date: 2026-03-26 Authorization: Credentials @@ -31,6 +35,11 @@ class CR(Service): Widevine: L3: 1080p, AAC2.0 + Updated by: SeFree + Date: 2026-03-30 + Change: + - Correct SDH and Force subtitle + \b Tips: - Input should be complete URL or series ID @@ -71,6 +80,8 @@ class CR(Service): self.token_expiration: Optional[int] = None self.anonymous_id = str(uuid.uuid4()) + self.skip_dl=ctx.parent.params.get("skip_dl") + super().__init__(ctx) device_cache_key = "cr_device_id" @@ -372,7 +383,7 @@ class CR(Service): final_number = season_episode_counts[final_season] original_language = None - versions = episode_data.get("versions", []) + versions = episode_data.get("versions", []) if episode_data.get("versions", []) else [] for version in versions: if "main" in version.get("roles", []): original_language = version.get("audio_locale") @@ -527,12 +538,12 @@ class CR(Service): self.close_stream(request_episode_id, version_token) continue - if is_original and endpoint_key == "playback": + if endpoint_key == "playback": captions = version_response.get("captions", {}) subtitles_data = version_response.get("subtitles", {}) - all_subs = {**captions, **subtitles_data} + # all_subs = {**captions, **subtitles_data} - for lang_code, sub_data in all_subs.items(): + for lang_code, sub_data in captions.items(): if lang_code == "none": continue @@ -540,21 +551,49 @@ class CR(Service): try: lang = Language.get(lang_code) except (ValueError, LookupError): - lang = Language.get("fr") + lang = Language.get("en") subtitle_format = sub_data.get("format", "vtt").lower() if subtitle_format == "ass" or subtitle_format == "ssa": codec = Subtitle.Codec.SubStationAlphav4 else: - codec = Subtitle.Codec.WebVTT + codec = Subtitle.Codec.SubRip tracks.add( Subtitle( - id_=f"subtitle-{audio_locale}-{lang_code}", + id_=f"subtitle-{audio_locale}-{lang_code}-sdh", url=sub_data["url"], codec=codec, language=lang, forced=False, + sdh=True, + ), + warn_only=True, + ) + for lang_code, sub_data in subtitles_data.items(): + if lang_code == "none": + continue + + if isinstance(sub_data, dict) and "url" in sub_data: + try: + lang = Language.get(lang_code) + except (ValueError, LookupError): + lang = Language.get("en") + + subtitle_format = sub_data.get("format", "vtt").lower() + if subtitle_format == "ass" or subtitle_format == "ssa": + codec = Subtitle.Codec.SubStationAlphav4 + else: + print(sub_data["url"]) + codec = Subtitle.Codec.SubRip + + tracks.add( + Subtitle( + id_=f"subtitle-{audio_locale}-{lang_code}"+("_force" if (Language.get(audio_locale)== Language.get(lang)) else ""), + url=sub_data["url"], + codec=codec, + language=lang, + forced=True if Language.get(audio_locale)== Language.get(lang) else False, sdh=False, ), warn_only=True, @@ -973,3 +1012,27 @@ class CR(Service): raise ValueError(f"Could not parse series ID from: {title_input}") series_id = match.group("id") return series_id + + def on_track_downloaded(self, track: AnyTrack) -> None: + """ + Called when a Track has finished downloading. + + Parameters: + track: The Track object that was downloaded. + """ + if isinstance(track,Subtitle) and not self.skip_dl: + + if track.path.suffix == ".vtt" and track.path.name: + + with open(track.path.__str__(), 'rb') as fd: + data = fd.read() + converter=WebVTTConverter() + if isinstance(data, bytes): + srt = converter.from_bytes(data) + else: + srt = converter.from_string(data) + fixer = CommonIssuesFixer() + fixed, status = fixer.from_srt(srt) + if status and fixed: + srt = fixed + srt.save(track.path)