This repository has been archived on 2024-07-02. You can view files and clone it, but cannot push or open issues or pull requests.
Gplay-4k-Downloader/movie.py
widevinedump f910bf33ca Updated
2021-12-23 15:51:08 +05:30

257 lines
12 KiB
Python

import argparse
import base64
import binascii
import json
import os
import requests
import subprocess
import sys
from colorama import init, Fore
from prettytable import PrettyTable
from pywidevine.decrypt.wvdecrypt import WvDecrypt
init(autoreset=True)
class Main(object):
def __init__(self, folders, args):
self.folders = folders
self.args = args
self.auth_json = None
self.movie_id = args.url.split('id=')[-1]
self.movie_details = None
self.movie_resources = {}
self.mpd_representations = {'video': [], 'audio': [], 'subtitle': []}
self.license = None
def auth(self):
if os.path.exists('auth.json'):
with open('auth.json', 'r') as src:
self.auth_json = json.loads(src.read())
else:
sys.exit()
def requests_headers(self):
return {
'accept': '*/*',
'accept-language': 'en-US,en;q=0.9',
'authorization': 'Bearer {0}'.format(self.auth_json['authorization']),
'Host': 'www.googleapis.com',
'origin': 'chrome-extension://gdijeikdkaembjbdobgfkoidjkpbmlkd',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'x-client-data': '{0}'.format(self.auth_json['x-client-data']),
}
def get_movie_details(self):
url = 'https://www.googleapis.com/android_video/v1/asset/list?id=yt%3Amovie%3A{0}&if=imbrg&lr=en_US&cr=US&alt=json&access_token={1}&make=Google&model=ChromeCDM-Windows-x86-32&product=generic&device=generic'.format(self.movie_id, self.auth_json['authorization'])
self.movie_details = requests.get(url=url, headers=self.requests_headers()).json()
def get_movie_resources(self):
url = 'https://www.googleapis.com/android_video/v1/mpd?id=yt%3Amovie%3A{0}&ac3=true&all51=true&nd=false&all=false&secure=true&msu=false&ma=true&fc=true&hdcp=true&alt={1}&ssrc=googlevideo&access_token={2}&make=Google&model=ChromeCDM-Windows-x86-32&product=generic&device=generic'
self.movie_resources['json'] = requests.get(
url = url.format(self.movie_id, 'json', self.auth_json['authorization']),
headers = self.requests_headers()
).json()
#self.movie_resources['protojson'] = requests.get(
#url = url.format(self.movie_id, 'protojson', self.auth_json['authorization']),
#headers = self.requests_headers()
#).json()
def parse_movie_resources(self):
av_representations = self.movie_resources['json']['representations']
for x in av_representations:
if 'audio_info' not in x:
self.mpd_representations['video'].append({
'playback_url': x['playback_url'],
'codec': x['codec'],
'init': x['init'],
'bitrate': x['bitrate'],
'quality': str(x['height'])+'p',
'fps': x['video_fps']
})
elif 'audio_info' in x:
self.mpd_representations['audio'].append({
'playback_url': x['playback_url'],
'codec': x['codec'],
'init': x['init'],
'bitrate': x['bitrate'],
'language': x['audio_info']['language']
})
#subtitle_representations = self.movie_resources['protojson']['1007']['4']
#for x in subtitle_representations:
#self.mpd_representations['subtitle'].append({
#'language': x['1'],
#'url': x['3'] ,
#'format': x['5']
#})
def aria2c(self, url, output_file_name):
aria2c = os.path.join(self.folders['binaries'], 'aria2c.exe')
aria2c_command = [
aria2c, url,
'-d', self.folders['temp'], '-j16',
'-o', output_file_name, '-s16', '-x16',
'-U', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36',
'--allow-overwrite=false',
'--async-dns=false',
'--auto-file-renaming=false',
'--console-log-level=warn',
'--retry-wait=5',
'--summary-interval=0',
]
subprocess.run(aria2c_command)
return os.path.join(self.folders['temp'], output_file_name)
def extract_pssh(self, mp4_file):
mp4dump = os.path.join(self.folders['binaries'], 'mp4dump.exe')
wv_system_id = '[ed ef 8b a9 79 d6 4a ce a3 c8 27 dc d5 1d 21 ed]'
pssh = None
data = subprocess.check_output([mp4dump, '--format', 'json', '--verbosity', '1', mp4_file])
data = json.loads(data)
for atom in data:
if atom['name'] == 'moov':
for child in atom['children']:
if child['name'] == 'pssh' and child['system_id'] == wv_system_id:
pssh = child['data'][1:-1].replace(' ', '')
pssh = binascii.unhexlify(pssh)
pssh = pssh[0:]
pssh = base64.b64encode(pssh).decode('utf-8')
return pssh
def license_request(self, pssh):
license_url = 'https://play.google.com/video/license/GetCencLicense?source=YOUTUBE&video_id={0}&oauth={1}'.format(self.movie_id, self.auth_json['authorization'])
license_headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36'
}
wvdecrypt = WvDecrypt(pssh)
challenge = wvdecrypt.get_challenge()
resp = requests.post(url=license_url, headers=license_headers, data=challenge)
resp1 = resp.content.split('\r\n\r\n'.encode('utf-8'))
resp2 = resp1[1]
license_b64 = base64.b64encode(resp2).decode('utf-8')
wvdecrypt.update_license(license_b64)
keys = wvdecrypt.start_process()
return keys
def mp4decrypt(self, keys):
mp4decrypt_command = [os.path.join(self.folders['binaries'], 'mp4decrypt.exe')]
for key in keys:
if key.type == 'CONTENT':
mp4decrypt_command.append('--show-progress')
mp4decrypt_command.append('--key')
mp4decrypt_command.append('{}:{}'.format(key.kid.hex(), key.key.hex()))
return mp4decrypt_command
def decrypt(self, keys, input, output):
mp4decrypt_command = self.mp4decrypt(keys)
mp4decrypt_command.append(input)
mp4decrypt_command.append(output)
wvdecrypt_process = subprocess.Popen(mp4decrypt_command)
wvdecrypt_process.communicate()
wvdecrypt_process.wait()
def video(self):
table = PrettyTable()
table.field_names = ['ID', 'CODEC', 'QUALITY', 'BITRATE', 'FPS']
for i, j in enumerate(self.mpd_representations['video']):
table.add_row([i, j['codec'], j['quality'], j['bitrate'], j['fps']])
print('\n' + Fore.RED + 'VIDEO')
print(table)
selected_video = self.mpd_representations['video'][int(input('ID: '))]
init_url = selected_video['playback_url'] + '?range={0}-{1}'.format(selected_video['init']['first'], selected_video['init']['last'])
self.aria2c(init_url, 'init.mp4')
selected_video['pssh'] = self.extract_pssh(os.path.join(self.folders['temp'], 'init.mp4'))
os.remove(os.path.join(self.folders['temp'], 'init.mp4'))
print(Fore.YELLOW+'\nAcquiring Content License')
self.license = self.license_request(selected_video['pssh'])
print(Fore.GREEN+'License Acquired Successfully')
print(Fore.YELLOW+'\nURL:', selected_video['playback_url'])
if not self.args.keys:
output_file_name = self.movie_details['resource'][0]['metadata']['title'] + ' ' + f'[{selected_video["quality"]}] Encrypted.mp4'
print(Fore.YELLOW+'\nDownloading', output_file_name)
video_downloaded = self.aria2c(selected_video['playback_url'], output_file_name.replace(':', ''))
print(Fore.YELLOW+'\nDecrypting Video')
self.decrypt(self.license, video_downloaded, video_downloaded.replace(' Encrypted', ''))
os.remove(video_downloaded)
else:
print(Fore.GREEN + 'n\KEYS')
for key in self.license:
if key.type == 'CONTENT':
print('{}:{}'.format(key.kid.hex(), key.key.hex()))
def audio(self):
table = PrettyTable()
table.field_names = ['ID', 'CODEC', 'BITRATE', 'LANGUAGE']
for i, j in enumerate(self.mpd_representations['audio']):
table.add_row([i, j['codec'], j['bitrate'], j['language']])
print('\n' + Fore.RED +'AUDIO')
print(table)
selected_audio = input('ID: ')
if self.args.audio:
init_url = self.mpd_representations['audio'][int(selected_audio.split(',')[-1])]['playback_url']
init_url += '?range={0}-{1}'.format(self.mpd_representations['audio'][int(selected_audio.split(',')[-1])]['init']['first'], self.mpd_representations['audio'][int(selected_audio.split(',')[-1])]['init']['last'])
self.aria2c(init_url, 'init.mp4')
pssh = self.extract_pssh(os.path.join(self.folders['temp'], 'init.mp4'))
os.remove(os.path.join(self.folders['temp'], 'init.mp4'))
print(Fore.YELLOW+'\nAcquiring Content License')
self.license = self.license_request(pssh)
print(Fore.GREEN+'License Acquired Successfully')
for x in selected_audio.split(','):
x = int(x.strip())
playback_url = self.mpd_representations['audio'][x]['playback_url']
print(Fore.YELLOW+'\nURL:', playback_url)
if not self.args.keys:
output_file_name = self.movie_details['resource'][0]['metadata']['title'] + ' ' + f'[{self.mpd_representations["audio"][x]["language"]}-{self.mpd_representations["audio"][x]["codec"]}-{self.mpd_representations["audio"][x]["bitrate"]}] Encrypted.mp4'
print(Fore.YELLOW+'\nDownloading', output_file_name)
audio_downloaded = self.aria2c(playback_url, output_file_name.replace(':', ''))
self.decrypt(self.license, audio_downloaded, audio_downloaded.replace(' Encrypted', ''))
os.remove(audio_downloaded)
else:
print(Fore.GREEN + 'n\KEYS')
for key in self.license:
if key.type == 'CONTENT':
print('{}:{}'.format(key.kid.hex(), key.key.hex()))
def subtitle(self):
table = PrettyTable()
table.field_names = ['ID', 'LANGUAGE', 'FORMAT']
for i, j in enumerate(self.mpd_representations['subtitle']):
table.add_row([i, j['language'], j['format']])
print('\n' + Fore.RED +'SUBTITLE')
print(table)
selected_subtitle = input('ID: ')
for x in selected_subtitle.split(','):
x = int(x.strip())
url = self.mpd_representations['subtitle'][x]['url']
output_file_name = self.movie_details['resource'][0]['metadata']['title'] + ' ' + f'{self.mpd_representations["subtitle"][x]["language"]}-{self.mpd_representations["subtitle"][x]["format"]}'
print(Fore.YELLOW+'\nDownloading', output_file_name)
self.aria2c(url, output_file_name)
cwd = os.getcwd()
folders = {'binaries': os.path.join(cwd, 'binaries'), 'output': os.path.join(cwd, 'output'), 'temp': os.path.join(cwd, 'temp')}
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-u', '--url', required=True)
arg_parser.add_argument('-a', '--audio', action='store_true')
arg_parser.add_argument('-k', '--keys', action='store_true')
args = arg_parser.parse_args()
if __name__ == "__main__":
movie = Main(folders, args)
movie.auth()
movie.get_movie_details()
movie.get_movie_resources()
movie.parse_movie_resources()
if not args.audio:
#movie.subtitle()
movie.video()
movie.audio()