- 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:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user