mirror of
https://github.com/glomatico/gamdl.git
synced 2025-01-22 11:18:39 +00:00
Merge pull request #155 from chu23465/main
Reworked slightly to add PlayReady DRM support.
This commit is contained in:
commit
f219dd1deb
@ -97,7 +97,7 @@ Config file values can be overridden using command line arguments.
|
||||
| `--language`, `-l` / `language` | Metadata language as an ISO-2A language code (don't always work for videos). | `en-US` |
|
||||
| `--output-path`, `-o` / `output_path` | Path to output directory. | `./Apple Music` |
|
||||
| `--temp-path` / `temp_path` | Path to temporary directory. | `./temp` |
|
||||
| `--wvd-path` / `wvd_path` | Path to .wvd file. | `null` |
|
||||
| `--device-path` / `device_path` | Path to .wvd or .prd file. | `null` |
|
||||
| `--nm3u8dlre-path` / `nm3u8dlre_path` | Path to N_m3u8DL-RE binary. | `N_m3u8DL-RE` |
|
||||
| `--mp4decrypt-path` / `mp4decrypt_path` | Path to mp4decrypt binary. | `mp4decrypt` |
|
||||
| `--ffmpeg-path` / `ffmpeg_path` | Path to FFmpeg binary. | `ffmpeg` |
|
||||
@ -121,7 +121,7 @@ Config file values can be overridden using command line arguments.
|
||||
| `--codec-music-video` / `codec_music_video` | Music video codec. | `h264` |
|
||||
| `--quality-post` / `quality_post` | Post video quality. | `best` |
|
||||
| `--no-config-file`, `-n` / - | Do not use a config file. | `false` |
|
||||
|
||||
| `--playready`, `playready` / - | Use Playready DRM | `false` |
|
||||
|
||||
### Tags variables
|
||||
The following variables can be used in the template folders/files and/or in the `exclude_tags` list:
|
||||
@ -178,6 +178,7 @@ The following codecs are available:
|
||||
* `aac-legacy`
|
||||
* `aac-he-legacy`
|
||||
|
||||
|
||||
The following codecs are also available, **but are not guaranteed to work**, as currently most (or all) of the songs fails to be downloaded when using them:
|
||||
* `aac`
|
||||
* `aac-he`
|
||||
@ -190,6 +191,7 @@ The following codecs are also available, **but are not guaranteed to work**, as
|
||||
* `alac`
|
||||
* `ask`
|
||||
* When using this option, Gamdl will ask you which codec from this list to use that is available for the song.
|
||||
With PlayReady and the right CDM, binaural, atmos and aac should download.
|
||||
|
||||
### Music videos codecs
|
||||
The following codecs are available:
|
||||
|
@ -1,11 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import functools
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import typing
|
||||
from http.cookiejar import MozillaCookieJar
|
||||
from pathlib import Path
|
||||
from .enums import DRM
|
||||
|
||||
|
||||
import requests
|
||||
|
||||
@ -39,7 +42,7 @@ class AppleMusicApi:
|
||||
self.storefront = self.session.cookies.get_dict()["itua"]
|
||||
self.session.headers.update(
|
||||
{
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0",
|
||||
"User-Agent": "iTunes/12.11.3 (Windows; Microsoft Windows 10 x64 Professional Edition (Build 19041); x64) AppleWebKit/7611.1022.4001.1 (dt:2)", #"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.2903.86", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0",
|
||||
"Accept": "application/json",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
@ -256,32 +259,41 @@ class AppleMusicApi:
|
||||
self._raise_response_exception(response)
|
||||
return webplayback[0]
|
||||
|
||||
def get_widevine_license(
|
||||
def get_license(
|
||||
self,
|
||||
track_id: str,
|
||||
track_uri: str,
|
||||
challenge: str,
|
||||
drm: DRM
|
||||
) -> str:
|
||||
response = self.session.post(
|
||||
self.LICENSE_API_URL,
|
||||
json={
|
||||
"challenge": challenge,
|
||||
"key-system": "com.widevine.alpha",
|
||||
"key-system": "com.widevine.alpha" if drm == DRM.Widevine else "com.microsoft.playready",
|
||||
"uri": track_uri,
|
||||
"adamId": track_id,
|
||||
"isLibrary": False,
|
||||
"user-initiated": True,
|
||||
"user-initiated": False,
|
||||
},
|
||||
)
|
||||
try:
|
||||
response.raise_for_status()
|
||||
response_dict = response.json()
|
||||
widevine_license = response_dict.get("license")
|
||||
assert widevine_license
|
||||
license = response_dict.get("license")
|
||||
assert license
|
||||
except (
|
||||
requests.HTTPError,
|
||||
requests.exceptions.JSONDecodeError,
|
||||
AssertionError,
|
||||
):
|
||||
if response_dict.get("status") == -1002:
|
||||
raise Exception("-1002: You do not own the title in the requested quality.")
|
||||
elif response_dict.get("status") == -1021:
|
||||
raise Exception("-1021: Device has insufficient security level or is blacklisted/revoked.")
|
||||
self._raise_response_exception(response)
|
||||
return widevine_license
|
||||
|
||||
return license
|
||||
|
||||
|
||||
|
||||
|
27
gamdl/cli.py
27
gamdl/cli.py
@ -16,7 +16,7 @@ from .downloader_music_video import DownloaderMusicVideo
|
||||
from .downloader_post import DownloaderPost
|
||||
from .downloader_song import DownloaderSong
|
||||
from .downloader_song_legacy import DownloaderSongLegacy
|
||||
from .enums import CoverFormat, DownloadMode, MusicVideoCodec, PostQuality, RemuxMode
|
||||
from .enums import CoverFormat, DownloadMode, MusicVideoCodec, PostQuality, RemuxMode, DRM
|
||||
from .itunes_api import ItunesApi
|
||||
|
||||
apple_music_api_sig = inspect.signature(AppleMusicApi.__init__)
|
||||
@ -159,10 +159,15 @@ def load_config_file(
|
||||
help="Path to temporary directory.",
|
||||
)
|
||||
@click.option(
|
||||
"--wvd-path",
|
||||
"--device-path",
|
||||
type=Path,
|
||||
default=downloader_sig.parameters["wvd_path"].default,
|
||||
help="Path to .wvd file.",
|
||||
default=downloader_sig.parameters["device_path"].default,
|
||||
help="Path to .wvd or .prd file.",
|
||||
)
|
||||
@click.option(
|
||||
"--playready",
|
||||
is_flag=True,
|
||||
help="Use PlayReady DRM.",
|
||||
)
|
||||
@click.option(
|
||||
"--nm3u8dlre-path",
|
||||
@ -323,7 +328,8 @@ def main(
|
||||
language: str,
|
||||
output_path: Path,
|
||||
temp_path: Path,
|
||||
wvd_path: Path,
|
||||
device_path: Path,
|
||||
playready: bool,
|
||||
nm3u8dlre_path: str,
|
||||
mp4decrypt_path: str,
|
||||
ffmpeg_path: str,
|
||||
@ -371,7 +377,7 @@ def main(
|
||||
itunes_api,
|
||||
output_path,
|
||||
temp_path,
|
||||
wvd_path,
|
||||
device_path,
|
||||
nm3u8dlre_path,
|
||||
mp4decrypt_path,
|
||||
ffmpeg_path,
|
||||
@ -390,6 +396,7 @@ def main(
|
||||
exclude_tags,
|
||||
cover_size,
|
||||
truncate,
|
||||
DRM.Playready if playready else DRM.Widevine
|
||||
)
|
||||
downloader_song = DownloaderSong(
|
||||
downloader,
|
||||
@ -409,8 +416,8 @@ def main(
|
||||
quality_post,
|
||||
)
|
||||
if not synced_lyrics_only:
|
||||
if wvd_path and not wvd_path.exists():
|
||||
logger.critical(X_NOT_FOUND_STRING.format(".wvd file", wvd_path))
|
||||
if device_path and not device_path.exists():
|
||||
logger.critical(X_NOT_FOUND_STRING.format(".wvd file", device_path))
|
||||
return
|
||||
logger.debug("Setting up CDM")
|
||||
downloader.set_cdm()
|
||||
@ -509,6 +516,7 @@ def main(
|
||||
lyrics = downloader_song.get_lyrics(track_metadata)
|
||||
logger.debug("Getting webplayback")
|
||||
webplayback = apple_music_api.get_webplayback(track_metadata["id"])
|
||||
logger.debug(webplayback)
|
||||
tags = downloader_song.get_tags(webplayback, lyrics.unsynced)
|
||||
if playlist_track:
|
||||
tags = {
|
||||
@ -548,10 +556,13 @@ def main(
|
||||
stream_info = downloader_song.get_stream_info(
|
||||
track_metadata
|
||||
)
|
||||
logger.debug(track_metadata)
|
||||
if not stream_info.stream_url or not stream_info.pssh:
|
||||
logger.warning(
|
||||
f"({queue_progress}) Song is not downloadable or is not"
|
||||
" available in the chosen codec, skipping"
|
||||
f"\n{stream_info.pssh}, {stream_info.stream_url}"
|
||||
|
||||
)
|
||||
continue
|
||||
logger.debug("Getting decryption key")
|
||||
|
@ -15,13 +15,13 @@ from InquirerPy import inquirer
|
||||
from InquirerPy.base.control import Choice
|
||||
from mutagen.mp4 import MP4, MP4Cover
|
||||
from PIL import Image
|
||||
from pywidevine import PSSH, Cdm, Device
|
||||
|
||||
from yt_dlp import YoutubeDL
|
||||
|
||||
from .apple_music_api import AppleMusicApi
|
||||
from .constants import IMAGE_FILE_EXTENSION_MAP, MP4_TAGS_MAP
|
||||
from .enums import CoverFormat, DownloadMode, RemuxMode
|
||||
from .hardcoded_wvd import HARDCODED_WVD
|
||||
from .enums import CoverFormat, DownloadMode, RemuxMode, DRM
|
||||
from .hardcoded import HARDCODED_WVD, HARDCODED_PRD
|
||||
from .itunes_api import ItunesApi
|
||||
from .models import DownloadQueue, UrlInfo
|
||||
|
||||
@ -37,7 +37,7 @@ class Downloader:
|
||||
itunes_api: ItunesApi,
|
||||
output_path: Path = Path("./Apple Music"),
|
||||
temp_path: Path = Path("./temp"),
|
||||
wvd_path: Path = None,
|
||||
device_path: Path = None,
|
||||
nm3u8dlre_path: str = "N_m3u8DL-RE",
|
||||
mp4decrypt_path: str = "mp4decrypt",
|
||||
ffmpeg_path: str = "ffmpeg",
|
||||
@ -56,13 +56,14 @@ class Downloader:
|
||||
exclude_tags: str = None,
|
||||
cover_size: int = 1200,
|
||||
truncate: int = None,
|
||||
silent: bool = False,
|
||||
drm: DRM = DRM.Widevine,
|
||||
silent: bool = False
|
||||
):
|
||||
self.apple_music_api = apple_music_api
|
||||
self.itunes_api = itunes_api
|
||||
self.output_path = output_path
|
||||
self.temp_path = temp_path
|
||||
self.wvd_path = wvd_path
|
||||
self.device_path = device_path
|
||||
self.nm3u8dlre_path = nm3u8dlre_path
|
||||
self.mp4decrypt_path = mp4decrypt_path
|
||||
self.ffmpeg_path = ffmpeg_path
|
||||
@ -82,6 +83,7 @@ class Downloader:
|
||||
self.cover_size = cover_size
|
||||
self.truncate = truncate
|
||||
self.silent = silent
|
||||
self.drm = drm
|
||||
self._set_binaries_path_full()
|
||||
self._set_exclude_tags_list()
|
||||
self._set_truncate()
|
||||
@ -114,10 +116,14 @@ class Downloader:
|
||||
self.subprocess_additional_args = {}
|
||||
|
||||
def set_cdm(self):
|
||||
if self.wvd_path:
|
||||
self.cdm = Cdm.from_device(Device.load(self.wvd_path))
|
||||
if self.drm == DRM.Widevine:
|
||||
from pywidevine import Cdm, Device
|
||||
elif self.drm == DRM.Playready:
|
||||
from pyplayready import Cdm, Device
|
||||
if self.device_path:
|
||||
self.cdm = Cdm.from_device(Device.load(self.device_path))
|
||||
else:
|
||||
self.cdm = Cdm.from_device(Device.loads(HARDCODED_WVD))
|
||||
self.cdm = Cdm.from_device(Device.loads(HARDCODED_WVD if self.drm == DRM.Widevine else HARDCODED_PRD))
|
||||
|
||||
def get_url_info(self, url: str) -> UrlInfo:
|
||||
url_info = UrlInfo()
|
||||
@ -314,16 +320,19 @@ class Downloader:
|
||||
return datetime.datetime.fromisoformat(date[:-1]).strftime(self.template_date)
|
||||
|
||||
def get_decryption_key(self, pssh: str, track_id: str) -> str:
|
||||
if self.drm == DRM.Widevine:
|
||||
from pywidevine import PSSH
|
||||
try:
|
||||
pssh_obj = PSSH(pssh.split(",")[-1])
|
||||
cdm_session = self.cdm.open()
|
||||
challenge = base64.b64encode(
|
||||
self.cdm.get_license_challenge(cdm_session, pssh_obj)
|
||||
).decode()
|
||||
license = self.apple_music_api.get_widevine_license(
|
||||
license = self.apple_music_api.get_license(
|
||||
track_id,
|
||||
pssh,
|
||||
challenge,
|
||||
self.drm,
|
||||
)
|
||||
self.cdm.parse_license(cdm_session, license)
|
||||
decryption_key = next(
|
||||
@ -331,7 +340,29 @@ class Downloader:
|
||||
).key.hex()
|
||||
finally:
|
||||
self.cdm.close(cdm_session)
|
||||
return decryption_key
|
||||
elif self.drm == DRM.Playready:
|
||||
from pyplayready import PSSH
|
||||
try:
|
||||
pssh_obj = PSSH(pssh.split(",")[-1]).get_wrm_headers(downgrade_to_v4=True)[0] # Downgrade to v4 has to be set to True
|
||||
cdm_session = self.cdm.open()
|
||||
challenge = base64.b64encode(
|
||||
self.cdm.get_license_challenge(cdm_session, pssh_obj).encode("utf-8")
|
||||
).decode()
|
||||
license = self.apple_music_api.get_license(
|
||||
track_id,
|
||||
pssh,
|
||||
challenge,
|
||||
self.drm,
|
||||
)
|
||||
self.cdm.parse_license(cdm_session, base64.b64decode(license.encode("utf-8")).decode("utf-8"))
|
||||
decryption_keys = [i.key.hex() for i in self.cdm.get_keys(cdm_session)]
|
||||
if len(decryption_keys) != 1:
|
||||
raise ValueError(f"Expecting only one key to be returned, but {len(decryption_keys)} keys were returned")
|
||||
elif len(decryption_keys) == 1 and "32b8ade1769e26b1ffb8986352793fc6" in decryption_keys:
|
||||
raise ValueError("Only default key returned for track.")
|
||||
finally:
|
||||
self.cdm.close(cdm_session)
|
||||
return decryption_keys[0]
|
||||
|
||||
def download(self, path: Path, stream_url: str):
|
||||
if self.download_mode == DownloadMode.YTDLP:
|
||||
|
@ -15,7 +15,7 @@ from InquirerPy.base.control import Choice
|
||||
|
||||
from .constants import SONG_CODEC_REGEX_MAP, SYNCED_LYRICS_FILE_EXTENSION_MAP
|
||||
from .downloader import Downloader
|
||||
from .enums import RemuxMode, SongCodec, SyncedLyricsFormat
|
||||
from .enums import RemuxMode, SongCodec, SyncedLyricsFormat, DRM
|
||||
from .models import Lyrics, StreamInfo
|
||||
|
||||
|
||||
@ -90,6 +90,7 @@ class DownloaderSong:
|
||||
drm_infos: dict,
|
||||
drm_ids: list,
|
||||
) -> str | None:
|
||||
if self.downloader.drm == DRM.Widevine:
|
||||
drm_info = next(
|
||||
(
|
||||
drm_infos[drm_id]
|
||||
@ -104,6 +105,21 @@ class DownloaderSong:
|
||||
if not drm_info:
|
||||
return None
|
||||
return drm_info["urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"]["URI"]
|
||||
else:
|
||||
drm_info = next(
|
||||
(
|
||||
drm_infos[drm_id]
|
||||
for drm_id in drm_ids
|
||||
if drm_infos[drm_id].get(
|
||||
"com.microsoft.playready"
|
||||
)
|
||||
and drm_id != "1"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not drm_info:
|
||||
return None
|
||||
return drm_info["com.microsoft.playready"]["URI"]
|
||||
|
||||
def get_stream_info(self, track_metadata: dict) -> StreamInfo:
|
||||
m3u8_url = track_metadata["attributes"]["extendedAssetUrls"].get("enhancedHls")
|
||||
|
@ -37,7 +37,7 @@ class DownloaderSongLegacy(DownloaderSong):
|
||||
challenge = base64.b64encode(
|
||||
self.downloader.cdm.get_license_challenge(cdm_session, pssh_obj)
|
||||
).decode()
|
||||
license = self.downloader.apple_music_api.get_widevine_license(
|
||||
license = self.downloader.apple_music_api.get_license(
|
||||
track_id,
|
||||
pssh,
|
||||
challenge,
|
||||
|
@ -47,3 +47,7 @@ class CoverFormat(Enum):
|
||||
JPG = "jpg"
|
||||
PNG = "png"
|
||||
RAW = "raw"
|
||||
|
||||
class DRM(Enum):
|
||||
Widevine: str = "WIDEVINE"
|
||||
Playready: str = "PLAYREADY"
|
@ -1 +1,2 @@
|
||||
HARDCODED_WVD = """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QrSyZcBqHO0QrdPcU35GBT7S7+IJr2AAXvnjqnb8yrtpPWN2ZW/IWUJN2z4vZ7/HV4aj3OZhkxC1DIMNyvsusUKoQQuf8gwKiEe8cFwbwFSicywlFk9la2IPe8oFShcxAzHLCCn/TIYUAvEL3/4LgaZvqWm80qCPYbgIP5HT8hPYkKWJ4WYknEWK+3InbnkzteFfGrQFCq4CCAESEGnj6Ji7LD+4o7MoHYT4jBQYjtW+kQUijgIwggEKAoIBAQDY9um1ifBRIOmkPtDZTqH+CZUBbb0eK0Cn3NHFf8MFUDzPEz+emK/OTub/hNxCJCao//pP5L8tRNUPFDrrvCBMo7Rn+iUb+mA/2yXiJ6ivqcN9Cu9i5qOU1ygon9SWZRsujFFB8nxVreY5Lzeq0283zn1Cg1stcX4tOHT7utPzFG/ReDFQt0O/GLlzVwB0d1sn3SKMO4XLjhZdncrtF9jljpg7xjMIlnWJUqxDo7TQkTytJmUl0kcM7bndBLerAdJFGaXc6oSY4eNy/IGDluLCQR3KZEQsy/mLeV1ggQ44MFr7XOM+rd+4/314q/deQbjHqjWFuVr8iIaKbq+R63ShAgMBAAEo8CISgAMii2Mw6z+Qs1bvvxGStie9tpcgoO2uAt5Zvv0CDXvrFlwnSbo+qR71Ru2IlZWVSbN5XYSIDwcwBzHjY8rNr3fgsXtSJty425djNQtF5+J2jrAhf3Q2m7EI5aohZGpD2E0cr+dVj9o8x0uJR2NWR8FVoVQSXZpad3M/4QzBLNto/tz+UKyZwa7Sc/eTQc2+ZcDS3ZEO3lGRsH864Kf/cEGvJRBBqcpJXKfG+ItqEW1AAPptjuggzmZEzRq5xTGf6or+bXrKjCpBS9G1SOyvCNF1k5z6lG8KsXhgQxL6ADHMoulxvUIihyPY5MpimdXfUdEQ5HA2EqNiNVNIO4qP007jW51yAeThOry4J22xs8RdkIClOGAauLIl0lLA4flMzW+VfQl5xYxP0E5tuhn0h+844DslU8ZF7U1dU2QprIApffXD9wgAACk26Rggy8e96z8i86/+YYyZQkc9hIdCAERrgEYCEbByzONrdRDs1MrS/ch1moV5pJv63BIKvQHGvLkaFwoMY29tcGFueV9uYW1lEgd1bmtub3duGioKCm1vZGVsX25hbWUSHEFuZHJvaWQgU0RLIGJ1aWx0IGZvciB4ODZfNjQaGwoRYXJjaGl0ZWN0dXJlX25hbWUSBng4Nl82NBodCgtkZXZpY2VfbmFtZRIOZ2VuZXJpY194ODZfNjQaIAoMcHJvZHVjdF9uYW1lEhBzZGtfcGhvbmVfeDg2XzY0GmMKCmJ1aWxkX2luZm8SVUFuZHJvaWQvc2RrX3Bob25lX3g4Nl82NC9nZW5lcmljX3g4Nl82NDo5L1BTUjEuMTgwNzIwLjAxMi80OTIzMjE0OnVzZXJkZWJ1Zy90ZXN0LWtleXMaHgoUd2lkZXZpbmVfY2RtX3ZlcnNpb24SBjE0LjAuMBokCh9vZW1fY3J5cHRvX3NlY3VyaXR5X3BhdGNoX2xldmVsEgEwMg4QASAAKA0wAEAASABQAA=="""
|
||||
HARDCODED_PRD = """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"""
|
@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from enum import Enum
|
||||
|
||||
@dataclass
|
||||
class UrlInfo:
|
||||
@ -27,3 +27,4 @@ class StreamInfo:
|
||||
stream_url: str = None
|
||||
pssh: str = None
|
||||
codec: str = None
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user