- Correct CR to handle SDH & Force subtitle correctly

- Download VTT as SRT then convert to SRT using on_track_downloaded
This commit is contained in:
2026-03-30 22:03:26 +07:00
parent 3887d28d17
commit e3d6d0ce8b

View File

@@ -8,6 +8,7 @@ import click
import jwt import jwt
from langcodes import Language from langcodes import Language
from unshackle.core.constants import AnyTrack
from unshackle.core.manifests import DASH from unshackle.core.manifests import DASH
from unshackle.core.search_result import SearchResult from unshackle.core.search_result import SearchResult
from unshackle.core.service import Service 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.chapter import Chapter
from unshackle.core.tracks.subtitle import Subtitle from unshackle.core.tracks.subtitle import Subtitle
import pysubs2
from subby import WebVTTConverter,CommonIssuesFixer
from hashlib import md5
class CR(Service): class CR(Service):
""" """
Service code for Crunchyroll streaming service (https://www.crunchyroll.com). Service code for Crunchyroll streaming service (https://www.crunchyroll.com).
\b \b
Version: 3.0.0 Version: 3.0.1
Author: sp4rk.y Author: sp4rk.y
Date: 2026-03-26 Date: 2026-03-26
Authorization: Credentials Authorization: Credentials
@@ -31,6 +35,11 @@ class CR(Service):
Widevine: Widevine:
L3: 1080p, AAC2.0 L3: 1080p, AAC2.0
Updated by: SeFree
Date: 2026-03-30
Change:
- Correct SDH and Force subtitle
\b \b
Tips: Tips:
- Input should be complete URL or series ID - Input should be complete URL or series ID
@@ -71,6 +80,8 @@ class CR(Service):
self.token_expiration: Optional[int] = None self.token_expiration: Optional[int] = None
self.anonymous_id = str(uuid.uuid4()) self.anonymous_id = str(uuid.uuid4())
self.skip_dl=ctx.parent.params.get("skip_dl")
super().__init__(ctx) super().__init__(ctx)
device_cache_key = "cr_device_id" device_cache_key = "cr_device_id"
@@ -372,7 +383,7 @@ class CR(Service):
final_number = season_episode_counts[final_season] final_number = season_episode_counts[final_season]
original_language = None original_language = None
versions = episode_data.get("versions", []) versions = episode_data.get("versions", []) if episode_data.get("versions", []) else []
for version in versions: for version in versions:
if "main" in version.get("roles", []): if "main" in version.get("roles", []):
original_language = version.get("audio_locale") original_language = version.get("audio_locale")
@@ -527,12 +538,12 @@ class CR(Service):
self.close_stream(request_episode_id, version_token) self.close_stream(request_episode_id, version_token)
continue continue
if is_original and endpoint_key == "playback": if endpoint_key == "playback":
captions = version_response.get("captions", {}) captions = version_response.get("captions", {})
subtitles_data = version_response.get("subtitles", {}) 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": if lang_code == "none":
continue continue
@@ -540,21 +551,49 @@ class CR(Service):
try: try:
lang = Language.get(lang_code) lang = Language.get(lang_code)
except (ValueError, LookupError): except (ValueError, LookupError):
lang = Language.get("fr") lang = Language.get("en")
subtitle_format = sub_data.get("format", "vtt").lower() subtitle_format = sub_data.get("format", "vtt").lower()
if subtitle_format == "ass" or subtitle_format == "ssa": if subtitle_format == "ass" or subtitle_format == "ssa":
codec = Subtitle.Codec.SubStationAlphav4 codec = Subtitle.Codec.SubStationAlphav4
else: else:
codec = Subtitle.Codec.WebVTT codec = Subtitle.Codec.SubRip
tracks.add( tracks.add(
Subtitle( Subtitle(
id_=f"subtitle-{audio_locale}-{lang_code}", id_=f"subtitle-{audio_locale}-{lang_code}-sdh",
url=sub_data["url"], url=sub_data["url"],
codec=codec, codec=codec,
language=lang, language=lang,
forced=False, 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, sdh=False,
), ),
warn_only=True, warn_only=True,
@@ -973,3 +1012,27 @@ class CR(Service):
raise ValueError(f"Could not parse series ID from: {title_input}") raise ValueError(f"Could not parse series ID from: {title_input}")
series_id = match.group("id") series_id = match.group("id")
return series_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)