Skip to content

Commit

Permalink
feat: [dl-downer] support new GoPlay site (no DRM content for now)
Browse files Browse the repository at this point in the history
  • Loading branch information
BelgianNoise committed Jul 28, 2024
1 parent be4f2e7 commit ef57bda
Showing 1 changed file with 96 additions and 55 deletions.
151 changes: 96 additions & 55 deletions dl-downer/src/downloaders/GOPLAY.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import base64
import json
import re
import os
Expand Down Expand Up @@ -41,30 +42,33 @@ def extract_goplay_bearer_token() -> str:
playwright = None

try:
assert os.getenv('AUTH_GOPLAY_EMAIL'), 'AUTH_GOPLAY_EMAIL not set'
assert os.getenv('AUTH_GOPLAY_PASSWORD'), 'AUTH_GOPLAY_PASSWORD not set'
playwright, browser, page = create_playwright_page(DLRequestPlatform.GOPLAY)

page.goto("https://www.goplay.be/profiel", wait_until='networkidle')
handle_goplay_consent_popup(page)

try:
page.wait_for_selector('span.profile-page__header__email', timeout=2000)
# Find the email on the page to check if we're already logged in
assert os.getenv('AUTH_GOPLAY_EMAIL') in page.content(), 'Not logged in'
logger.debug('Already logged in')
page.context.storage_state(path=state_file)
except:
logger.debug('Logging in ...')
openLogin = page.wait_for_selector('div.not-logged-in button')
# This used to be a popup, but now it's a link
openLogin = page.wait_for_selector('a[href="/login"]')
openLogin.click()
emailInput = page.wait_for_selector('input#email')
assert os.getenv('AUTH_GOPLAY_EMAIL'), 'AUTH_GOPLAY_EMAIL not set'
emailInput = page.wait_for_selector('input#login-form-email')
emailInput.type(os.getenv('AUTH_GOPLAY_EMAIL'))
passwordInput = page.wait_for_selector('input#password')
assert os.getenv('AUTH_GOPLAY_PASSWORD'), 'AUTH_GOPLAY_PASSWORD not set'
passwordInput = page.wait_for_selector('input#login-form-password')
passwordInput.type(os.getenv('AUTH_GOPLAY_PASSWORD'))
submitButton = page.wait_for_selector('form button[type="submit"]')
submitButton = page.wait_for_selector('form:has(input#login-form-email) button')
submitButton.click()

# find h2 element with test 'Mijn lijst'
page.wait_for_selector('span.profile-page__header__email')
page.wait_for_selector('aside a[href="/"] svg')
page.goto("https://www.goplay.be/profiel", wait_until='networkidle')
assert os.getenv('AUTH_GOPLAY_EMAIL') in page.content(), 'Login failed'
logger.debug('Logged in successfully')
page.context.storage_state(path=state_file)

Expand Down Expand Up @@ -92,28 +96,23 @@ def GOPLAY_DL(dl_request: DLRequest):
# Parse video uuid from the page
page_resp = requests.get(dl_request.video_page_or_manifest_url)
page_content = page_resp.text
obj_string = re.search(r'<div data-hero="(.+?)"', page_content).group(1)
obj_string = re.sub(r'&quot;', '"', obj_string)
# Find the correct script tag that contains the video data
scr_tag = re.search(r'<script>(?:(?!</?script>).)*playerContainer(?:(?!</?script>).)*</script>', page_content)
if scr_tag is None:
logger.error('No video data found')
return
full_obj_string = re.search(r'push\((.*)\)', scr_tag.group(0))
obj_string_match = re.search(r'playerContainer.*?children.*?(\{.*?\})\]\}\]', full_obj_string.group(1))
obj_string = obj_string_match.group(1).replace('\\"', '"')
obj = json.loads(obj_string)

video_object = None
if len(obj['data']['playlists']) == 0:
# This is a movie
video_object = obj['data']['movie']
else:
# This is a series
for playlist in obj['data']['playlists']:
for episode in playlist['episodes']:
if episode['pageInfo']['url'] == dl_request.video_page_or_manifest_url.split('#')[0]:
video_object = episode
break

video_uuid = video_object['videoUuid']
type_form = video_object['type']
type_form = re.sub('_', '-', type_form)
is_drm = video_object['isDrm']
video_object = obj['video']
# logger.debug(f'Video object: {video_object}')
video_uuid = video_object['uuid']
type_form = video_object['videoType']
is_drm = video_object['flags']['isDrm']
if dl_request.output_filename is None:
title = video_object['pageInfo']['title']
title = video_object['title']
title = parse_filename(title)
else:
title = dl_request.output_filename
Expand All @@ -123,40 +122,82 @@ def GOPLAY_DL(dl_request: DLRequest):
logger.debug(f'Is DRM: {is_drm}')
logger.debug(f'Title: {title}')

# get video data
video_data_url = f'https://api.goplay.be/web/v1/videos/{type_form}/{video_uuid}'
logger.debug(f'Video data URL: {video_data_url}')
bearer_token = extract_goplay_bearer_token()
video_data_resp = requests.get(
video_data_url,
headers={ 'authorization': f'Bearer {bearer_token}' },
)
if video_data_resp.status_code != 200:
logger.debug(video_data_resp.text)
video_data = video_data_resp.json()
content_source_id = video_data['ssai']['contentSourceID']
logger.debug(f'Content source ID: {content_source_id}')
video_id = video_data['ssai']['videoID']
logger.debug(f'Video ID: {video_id}')

# get video streams
streams_resp = requests.post(f'https://dai.google.com/ondemand/dash/content/{content_source_id}/vid/{video_id}/streams')
streams = streams_resp.json()
stream_manifest = streams['stream_manifest']
stream_collection = video_object['streamCollection']
stream_collection_streams = stream_collection['streams']
stream_drm_protected = stream_collection['drmProtected']
# Find dash stream
stream_manifest = ''
for stream in stream_collection_streams:
if stream['protocol'] == 'dash':
stream_manifest = stream['url']
break
if stream_manifest == '':
logger.error('No dash stream found')
return
logger.debug(f'Stream manifest: {stream_manifest}')

logger.debug(f'DRM protected: {stream_drm_protected}')

keys = {}

if is_drm:
drm_xml = video_data['drmXml']
logger.debug(f'DRM XML: {drm_xml}')
if is_drm or stream_drm_protected:
raise NotImplementedError('DRM protected content not supported')
manifest_response = requests.get(stream_manifest)
pssh = re.findall(r'<pssh[^>]*>(.{,120})</pssh>', manifest_response.text)[0]
pssh = re.findall(r'<cenc:pssh[^>]*>(.{,120})</cenc:pssh>', manifest_response.text)[0]
logger.debug(f'PSSH: {pssh}')
cdm = Local_CDM()
challenge = cdm.generate_challenge(pssh)
headers = { 'Customdata': drm_xml }
lic_res = requests.post('https://wv-keyos.licensekeyserver.com/', data=challenge, headers=headers)
drm_key = stream_collection['drmKey']
custom_data_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<KeyOSAuthenticationXML>
<Data>
<WidevinePolicy fl_CanPersist="false" fl_CanPlay="true"/>
<WidevineContentKeySpec TrackType="HD">
<SecurityLevel>1</SecurityLevel>
<RequiredHDCP>HDCP_NONE</RequiredHDCP>
<RequiredCGMS>CGMS_NONE</RequiredCGMS>
</WidevineContentKeySpec>
<FairPlayPolicy persistent="false">
<HDCPEnforcement>HDCP_NONE</HDCPEnforcement>
</FairPlayPolicy>
<License type="simple">
<KeyId>{drm_key}</KeyId>
<Policy>
<Id>962c4b25-4c65-4b21-a6a3-fe2c7590053c</Id>
</Policy>
<Play>
<Id>c7e7a2b0-87ad-4659-b59b-e5edf68bd5ea</Id>
</Play>
</License>
<Policy id="962c4b25-4c65-4b21-a6a3-fe2c7590053c">
<MinimumSecurityLevel>2000</MinimumSecurityLevel>
</Policy>
<Play id="c7e7a2b0-87ad-4659-b59b-e5edf68bd5ea">
<OutputProtections>
<OPL>
<CompressedDigitalVideo>500</CompressedDigitalVideo>
<UncompressedDigitalVideo>250</UncompressedDigitalVideo>
<AnalogVideo>150</AnalogVideo>
</OPL>
<AnalogVideoExplicit>
<Id ConfigData="1">C3FD11C6-F8B7-4D20-B008-1DB17D61F2DA</Id>
</AnalogVideoExplicit>
</OutputProtections>
</Play>
<KeyIDList>
<KeyID>{drm_key}</KeyID>
</KeyIDList>
<Username>local</Username>
<GenerationTime>2024-07-28 17:01:37.817</GenerationTime>
<ExpirationTime>2024-07-28 17:11:37.817</ExpirationTime>
<UniqueId>4dcdb908372d410cad01c0fc4120911e</UniqueId>
<RSAPubKeyId>3c202c0ea50420870eb63e2412fa69f9</RSAPubKeyId>
</Data>
<Signature>kDDHB7xDVEbJMb6DucvdA0PxoeUslNn4AkqMq+kKMQQ/zATmjAYmE+iV8pcLanwPIp9kjKt6/RFcZuoYov8F2gWKfvaeSrxvBIMbjsmXpvoio61Kvl9GmnFkjh8KZxFDj23XxZ+dpCmoveUFSJvbq8VAsRqkCQGtpOJMzPdkdifE/TxmyIJXI0f6x5jPThpu/muqpaYLRCzpscbURx5vbU5kOSYDFcgEdL3jgoP4aOlM7p4mwo7FLyifNtqTNCP1+eKwIZf1ZkfhK7JHPFxOo7a//CaCaG52ER6amo2KloR+Vh1xWY4TPAiAj7VdxAac4Sp+AZpak79hdrpwZEDyig==</Signature>
</KeyOSAuthenticationXML>
'''
base64_custom_data = base64.b64encode(custom_data_xml.encode()).decode()
headers = { 'Customdata': base64_custom_data }
lic_res = requests.post('https://widevine.keyos.com/api/v4/getLicense', data=challenge, headers=headers)
lic_res.raise_for_status()
keys = cdm.decrypt_response(lic_res.content)
logger.debug(f'Keys: {keys}')
Expand Down

0 comments on commit ef57bda

Please sign in to comment.