mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2025-10-23 15:11:08 +00:00
Compare commits
2 Commits
3ef43afeed
...
1.4.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fd0895128 | ||
|
|
ed744205ad |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "unshackle"
|
name = "unshackle"
|
||||||
version = "1.4.3"
|
version = "1.4.4"
|
||||||
description = "Modular Movie, TV, and Music Archival Software."
|
description = "Modular Movie, TV, and Music Archival Software."
|
||||||
authors = [{ name = "unshackle team" }]
|
authors = [{ name = "unshackle team" }]
|
||||||
requires-python = ">=3.10,<3.13"
|
requires-python = ">=3.10,<3.13"
|
||||||
|
|||||||
@@ -345,7 +345,10 @@ class dl:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if self.cdm:
|
if self.cdm:
|
||||||
if hasattr(self.cdm, "device_type") and self.cdm.device_type.name in ["ANDROID", "CHROME"]:
|
if isinstance(self.cdm, DecryptLabsRemoteCDM):
|
||||||
|
drm_type = "PlayReady" if self.cdm.is_playready else "Widevine"
|
||||||
|
self.log.info(f"Loaded {drm_type} Remote CDM: DecryptLabs (L{self.cdm.security_level})")
|
||||||
|
elif hasattr(self.cdm, "device_type") and self.cdm.device_type.name in ["ANDROID", "CHROME"]:
|
||||||
self.log.info(f"Loaded Widevine CDM: {self.cdm.system_id} (L{self.cdm.security_level})")
|
self.log.info(f"Loaded Widevine CDM: {self.cdm.system_id} (L{self.cdm.security_level})")
|
||||||
else:
|
else:
|
||||||
self.log.info(
|
self.log.info(
|
||||||
@@ -874,7 +877,12 @@ class dl:
|
|||||||
),
|
),
|
||||||
licence=partial(
|
licence=partial(
|
||||||
service.get_playready_license
|
service.get_playready_license
|
||||||
if isinstance(self.cdm, PlayReadyCdm)
|
if (
|
||||||
|
isinstance(self.cdm, PlayReadyCdm)
|
||||||
|
or (
|
||||||
|
isinstance(self.cdm, DecryptLabsRemoteCDM) and self.cdm.is_playready
|
||||||
|
)
|
||||||
|
)
|
||||||
and hasattr(service, "get_playready_license")
|
and hasattr(service, "get_playready_license")
|
||||||
else service.get_widevine_license,
|
else service.get_widevine_license,
|
||||||
title=title,
|
title=title,
|
||||||
@@ -1201,10 +1209,22 @@ class dl:
|
|||||||
if not drm:
|
if not drm:
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(drm, Widevine) and not isinstance(self.cdm, WidevineCdm):
|
if isinstance(drm, Widevine):
|
||||||
self.cdm = self.get_cdm(self.service, self.profile, drm="widevine")
|
if not isinstance(self.cdm, (WidevineCdm, DecryptLabsRemoteCDM)) or (
|
||||||
elif isinstance(drm, PlayReady) and not isinstance(self.cdm, PlayReadyCdm):
|
isinstance(self.cdm, DecryptLabsRemoteCDM) and self.cdm.is_playready
|
||||||
self.cdm = self.get_cdm(self.service, self.profile, drm="playready")
|
):
|
||||||
|
widevine_cdm = self.get_cdm(self.service, self.profile, drm="widevine")
|
||||||
|
if widevine_cdm:
|
||||||
|
self.log.info("Switching to Widevine CDM for Widevine content")
|
||||||
|
self.cdm = widevine_cdm
|
||||||
|
elif isinstance(drm, PlayReady):
|
||||||
|
if not isinstance(self.cdm, (PlayReadyCdm, DecryptLabsRemoteCDM)) or (
|
||||||
|
isinstance(self.cdm, DecryptLabsRemoteCDM) and not self.cdm.is_playready
|
||||||
|
):
|
||||||
|
playready_cdm = self.get_cdm(self.service, self.profile, drm="playready")
|
||||||
|
if playready_cdm:
|
||||||
|
self.log.info("Switching to PlayReady CDM for PlayReady content")
|
||||||
|
self.cdm = playready_cdm
|
||||||
|
|
||||||
if isinstance(drm, Widevine):
|
if isinstance(drm, Widevine):
|
||||||
with self.DRM_TABLE_LOCK:
|
with self.DRM_TABLE_LOCK:
|
||||||
@@ -1477,26 +1497,17 @@ class dl:
|
|||||||
|
|
||||||
cdm_api = next(iter(x for x in config.remote_cdm if x["name"] == cdm_name), None)
|
cdm_api = next(iter(x for x in config.remote_cdm if x["name"] == cdm_name), None)
|
||||||
if cdm_api:
|
if cdm_api:
|
||||||
is_decrypt_lab = True if cdm_api["type"] == "decrypt_labs" else False
|
is_decrypt_lab = True if cdm_api.get("type") == "decrypt_labs" else False
|
||||||
if is_decrypt_lab:
|
if is_decrypt_lab:
|
||||||
device_type = cdm_api.get("device_type")
|
|
||||||
del cdm_api["name"]
|
del cdm_api["name"]
|
||||||
del cdm_api["type"]
|
del cdm_api["type"]
|
||||||
|
|
||||||
# Use the appropriate DecryptLabs CDM class based on device type
|
# All DecryptLabs CDMs use DecryptLabsRemoteCDM
|
||||||
if device_type == "PLAYREADY" or cdm_api.get("device_name") in ["SL2", "SL3"]:
|
return DecryptLabsRemoteCDM(service_name=service, vaults=self.vaults, **cdm_api)
|
||||||
from unshackle.core.cdm.decrypt_labs_remote_cdm import DecryptLabsRemotePlayReadyCDM
|
|
||||||
|
|
||||||
# Remove unused parameters for PlayReady CDM
|
|
||||||
cdm_params = cdm_api.copy()
|
|
||||||
cdm_params.pop("device_type", None)
|
|
||||||
cdm_params.pop("system_id", None)
|
|
||||||
return DecryptLabsRemotePlayReadyCDM(service_name=service, vaults=self.vaults, **cdm_params)
|
|
||||||
else:
|
|
||||||
return DecryptLabsRemoteCDM(service_name=service, vaults=self.vaults, **cdm_api)
|
|
||||||
else:
|
else:
|
||||||
del cdm_api["name"]
|
del cdm_api["name"]
|
||||||
del cdm_api["type"]
|
if "type" in cdm_api:
|
||||||
|
del cdm_api["type"]
|
||||||
return RemoteCdm(**cdm_api)
|
return RemoteCdm(**cdm_api)
|
||||||
|
|
||||||
prd_path = config.directories.prds / f"{cdm_name}.prd"
|
prd_path = config.directories.prds / f"{cdm_name}.prd"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "1.4.3"
|
__version__ = "1.4.4"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -420,6 +420,15 @@ class Track:
|
|||||||
for drm in self.drm:
|
for drm in self.drm:
|
||||||
if isinstance(drm, PlayReady):
|
if isinstance(drm, PlayReady):
|
||||||
return drm
|
return drm
|
||||||
|
elif hasattr(cdm, 'is_playready'):
|
||||||
|
if cdm.is_playready:
|
||||||
|
for drm in self.drm:
|
||||||
|
if isinstance(drm, PlayReady):
|
||||||
|
return drm
|
||||||
|
else:
|
||||||
|
for drm in self.drm:
|
||||||
|
if isinstance(drm, Widevine):
|
||||||
|
return drm
|
||||||
|
|
||||||
return self.drm[0]
|
return self.drm[0]
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from typing import Optional, Tuple
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
from requests.adapters import HTTPAdapter, Retry
|
from requests.adapters import HTTPAdapter, Retry
|
||||||
|
from xml.sax.saxutils import escape
|
||||||
|
|
||||||
from unshackle.core import binaries
|
from unshackle.core import binaries
|
||||||
from unshackle.core.config import config
|
from unshackle.core.config import config
|
||||||
@@ -289,9 +290,9 @@ def _apply_tags(path: Path, tags: dict[str, str]) -> None:
|
|||||||
log.debug("mkvpropedit not found on PATH; skipping tags")
|
log.debug("mkvpropedit not found on PATH; skipping tags")
|
||||||
return
|
return
|
||||||
log.debug("Applying tags to %s: %s", path, tags)
|
log.debug("Applying tags to %s: %s", path, tags)
|
||||||
xml_lines = ["<?xml version='1.0' encoding='UTF-8'?>", "<Tags>", " <Tag>", " <Targets/>"]
|
xml_lines = ['<?xml version="1.0" encoding="UTF-8"?>', "<Tags>", " <Tag>", " <Targets/>"]
|
||||||
for name, value in tags.items():
|
for name, value in tags.items():
|
||||||
xml_lines.append(f" <Simple><Name>{name}</Name><String>{value}</String></Simple>")
|
xml_lines.append(f" <Simple><Name>{escape(name)}</Name><String>{escape(value)}</String></Simple>")
|
||||||
xml_lines.extend([" </Tag>", "</Tags>"])
|
xml_lines.extend([" </Tag>", "</Tags>"])
|
||||||
with tempfile.NamedTemporaryFile("w", suffix=".xml", delete=False) as f:
|
with tempfile.NamedTemporaryFile("w", suffix=".xml", delete=False) as f:
|
||||||
f.write("\n".join(xml_lines))
|
f.write("\n".join(xml_lines))
|
||||||
@@ -351,11 +352,11 @@ def tag_file(path: Path, title: Title, tmdb_id: Optional[int] | None = None) ->
|
|||||||
|
|
||||||
show_ids = simkl_data.get("show", {}).get("ids", {})
|
show_ids = simkl_data.get("show", {}).get("ids", {})
|
||||||
if show_ids.get("imdb"):
|
if show_ids.get("imdb"):
|
||||||
standard_tags["IMDB"] = f"https://www.imdb.com/title/{show_ids['imdb']}"
|
standard_tags["IMDB"] = show_ids["imdb"]
|
||||||
if show_ids.get("tvdb"):
|
if show_ids.get("tvdb"):
|
||||||
standard_tags["TVDB"] = f"https://thetvdb.com/dereferrer/series/{show_ids['tvdb']}"
|
standard_tags["TVDB"] = str(show_ids["tvdb"])
|
||||||
if show_ids.get("tmdbtv"):
|
if show_ids.get("tmdbtv"):
|
||||||
standard_tags["TMDB"] = f"https://www.themoviedb.org/tv/{show_ids['tmdbtv']}"
|
standard_tags["TMDB"] = f"tv/{show_ids['tmdbtv']}"
|
||||||
|
|
||||||
# Use TMDB API for additional metadata (either from provided ID or Simkl lookup)
|
# Use TMDB API for additional metadata (either from provided ID or Simkl lookup)
|
||||||
api_key = _api_key()
|
api_key = _api_key()
|
||||||
@@ -373,8 +374,8 @@ def tag_file(path: Path, title: Title, tmdb_id: Optional[int] | None = None) ->
|
|||||||
_apply_tags(path, custom_tags)
|
_apply_tags(path, custom_tags)
|
||||||
return
|
return
|
||||||
|
|
||||||
tmdb_url = f"https://www.themoviedb.org/{'movie' if kind == 'movie' else 'tv'}/{tmdb_id}"
|
prefix = "movie" if kind == "movie" else "tv"
|
||||||
standard_tags["TMDB"] = tmdb_url
|
standard_tags["TMDB"] = f"{prefix}/{tmdb_id}"
|
||||||
try:
|
try:
|
||||||
ids = external_ids(tmdb_id, kind)
|
ids = external_ids(tmdb_id, kind)
|
||||||
except requests.RequestException as exc:
|
except requests.RequestException as exc:
|
||||||
@@ -385,11 +386,10 @@ def tag_file(path: Path, title: Title, tmdb_id: Optional[int] | None = None) ->
|
|||||||
|
|
||||||
imdb_id = ids.get("imdb_id")
|
imdb_id = ids.get("imdb_id")
|
||||||
if imdb_id:
|
if imdb_id:
|
||||||
standard_tags["IMDB"] = f"https://www.imdb.com/title/{imdb_id}"
|
standard_tags["IMDB"] = imdb_id
|
||||||
tvdb_id = ids.get("tvdb_id")
|
tvdb_id = ids.get("tvdb_id")
|
||||||
if tvdb_id:
|
if tvdb_id:
|
||||||
tvdb_prefix = "movies" if kind == "movie" else "series"
|
standard_tags["TVDB"] = str(tvdb_id)
|
||||||
standard_tags["TVDB"] = f"https://thetvdb.com/dereferrer/{tvdb_prefix}/{tvdb_id}"
|
|
||||||
|
|
||||||
merged_tags = {
|
merged_tags = {
|
||||||
**custom_tags,
|
**custom_tags,
|
||||||
|
|||||||
Reference in New Issue
Block a user