Upload
This commit is contained in:
45
pywidevine/clients/appletv/client.py
Normal file
45
pywidevine/clients/appletv/client.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import requests, json, os, re
|
||||
import pywidevine.clients.appletv.config as aptv_cfg
|
||||
import urllib.parse
|
||||
|
||||
currentFile = 'appletv'
|
||||
realPath = os.path.realpath(currentFile)
|
||||
dirPath = os.path.dirname(realPath)
|
||||
cookies_file = dirPath + '/cookies/' + 'cookies_aptv.txt'
|
||||
|
||||
def get_auth_headers(content_url):
|
||||
|
||||
def urldecode(str):
|
||||
return urllib.parse.unquote(str)
|
||||
|
||||
def parseCookieFile(cookiefile):
|
||||
cookies = {}
|
||||
with open (cookies_file, 'r') as fp:
|
||||
for line in fp:
|
||||
if not re.match(r'^\#', line):
|
||||
lineFields = line.strip().split('\t')
|
||||
cookies[lineFields[5]] = lineFields[6]
|
||||
return cookies
|
||||
|
||||
COOKIES = parseCookieFile(dirPath + '/cookies/' + cookies_file)
|
||||
COMMOM_HEADERS = aptv_cfg.COMMOM_HEADERS
|
||||
COMMOM_HEADERS["media-user-token"] = COOKIES["media-user-token"]
|
||||
|
||||
while 1:
|
||||
html_data = requests.get(url=content_url, headers=COMMOM_HEADERS)
|
||||
if html_data.ok:
|
||||
break
|
||||
|
||||
html_data = html_data.text.replace('\r\n', '').replace('\n', '').replace('\r', '').replace('\t', '').replace(' ', '')
|
||||
html_data_list = re.split('(</div>)(?i)', html_data)
|
||||
|
||||
json_web = []
|
||||
for div in html_data_list:
|
||||
rg = re.compile('(<meta name="web-tv-app/config/environment" content=")(.*)("><!-- EMBER_CLI_FASTBOOT_TITLE --)')
|
||||
m = rg.search(div)
|
||||
if m:
|
||||
AUTH_TOKEN = json.loads(urldecode(m[2]))["MEDIA_API"]["token"]
|
||||
|
||||
COMMOM_HEADERS["authorization"] = "Bearer %s" % (AUTH_TOKEN)
|
||||
|
||||
return {"wvHeaders": COMMOM_HEADERS}, COOKIES
|
||||
49
pywidevine/clients/appletv/config.py
Normal file
49
pywidevine/clients/appletv/config.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from shutil import which
|
||||
from os.path import dirname, realpath, join
|
||||
from os import pathsep, environ
|
||||
|
||||
APTV_ENDPOINTS = {
|
||||
'SERIES': 'https://tv.apple.com/api/uts/v2/view/show/umc.cmc.%s/episodes',
|
||||
'PRODUCT': 'https://tv.apple.com/api/uts/v2/view/product/%s',
|
||||
'CONTENT_DATA': 'https://tv.apple.com/api/uts/v2/view/product/%s/personalized',
|
||||
'WV_LICENSE': 'https://play.itunes.apple.com/WebObjects/MZPlay.woa/web/video/subscription/license',
|
||||
'WV_CERT': 'https://play.itunes.apple.com/WebObjects/MZPlay.woa/wa/widevineCert'
|
||||
}
|
||||
|
||||
APTV_PARAMS = {'utscf': 'OjAAAAAAAAA~', 'utsk': '6e3013c6d6fae3c2::::::9724ac949afa4bb2', 'caller': 'web', 'sf': '143468', 'v': '40', 'pfm': 'web', 'locale': 'en-US'}
|
||||
|
||||
SCRIPT_PATH = dirname(realpath('appletv'))
|
||||
|
||||
BINARIES_FOLDER = join(SCRIPT_PATH, 'binaries')
|
||||
|
||||
SHAKA_PACKAGER_BINARY = 'packager-win'
|
||||
MEDIAINFO_BINARY = 'mediainfo'
|
||||
MP4DUMP_BINARY = 'mp4dump'
|
||||
MKVMERGE_BINARY = 'mkvmerge'
|
||||
FFMPEG_BINARY = 'ffmpeg'
|
||||
ARIA2C_BINARY = 'aria2c'
|
||||
SUBTITLE_EDIT_BINARY = 'subtitleedit'
|
||||
|
||||
# Add binaries folder to PATH as the first item
|
||||
environ['PATH'] = pathsep.join([BINARIES_FOLDER, environ['PATH']])
|
||||
|
||||
SHAKA_PACKAGER = which(SHAKA_PACKAGER_BINARY)
|
||||
MEDIAINFO = which(MEDIAINFO_BINARY)
|
||||
MP4DUMP = which(MP4DUMP_BINARY)
|
||||
MKVMERGE = which(MKVMERGE_BINARY)
|
||||
FFMPEG = which(FFMPEG_BINARY)
|
||||
ARIA2C = which(ARIA2C_BINARY)
|
||||
SUBTITLE_EDIT = which(SUBTITLE_EDIT_BINARY)
|
||||
|
||||
class WvDownloaderConfig(object):
|
||||
def __init__(self, m3u8, output_file):
|
||||
self.m3u8 = m3u8
|
||||
self.output_file = output_file
|
||||
|
||||
COMMOM_HEADERS = {
|
||||
'Connection': 'keep-alive',
|
||||
'User-Agent': 'AppleCoreMedia/1.0.0.12B466 (Apple TV; U; CPU OS 8_1_3 like Mac OS X; en_us)',
|
||||
'Content-Type': 'application/json',
|
||||
'Origin': 'https://tv.apple.com',
|
||||
'Referer': 'https://tv.apple.com/',
|
||||
}
|
||||
162
pywidevine/clients/appletv/downloader.py
Normal file
162
pywidevine/clients/appletv/downloader.py
Normal file
@@ -0,0 +1,162 @@
|
||||
|
||||
|
||||
import sys, os, shutil, requests, re
|
||||
import subprocess, math, pathlib
|
||||
|
||||
from m3u8 import parse as m3u8parser
|
||||
|
||||
dlthreads = 24
|
||||
|
||||
class WvDownloader(object):
|
||||
def __init__(self, config):
|
||||
self.m3u8_url = config.m3u8
|
||||
self.output_file = config.output_file
|
||||
self.config = config
|
||||
|
||||
def downloadM3u8(self, Link_List, Folder, file_name):
|
||||
TempFolder = file_name.replace('.mp4', '')
|
||||
if os.path.exists(TempFolder):
|
||||
shutil.rmtree(TempFolder)
|
||||
if not os.path.exists(TempFolder):
|
||||
os.makedirs(TempFolder)
|
||||
LinkList_txt = file_name.replace('.mp4', '_LinkList.txt')
|
||||
if os.path.isfile(LinkList_txt):
|
||||
os.remove(LinkList_txt)
|
||||
|
||||
counter = 0
|
||||
with open(LinkList_txt, 'a', encoding='ansi') as (file):
|
||||
for link in Link_List:
|
||||
mp4_segment = re.split('(/)(?i)', link)[(-1)]
|
||||
seg_num = str(counter).zfill(5)
|
||||
seg_num = f"{seg_num}.mp4"
|
||||
file.write(f"{link}\n\tdir={TempFolder}\n\tout={seg_num}\n")
|
||||
counter = counter + 1
|
||||
|
||||
aria_command = [
|
||||
'aria2c', '-i', LinkList_txt,
|
||||
'--user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"',
|
||||
'--async-dns=false',
|
||||
'--enable-color=false',
|
||||
'--allow-overwrite=true',
|
||||
'--download-result=hide',
|
||||
'--auto-file-renaming=false',
|
||||
'--file-allocation=none',
|
||||
'--summary-interval=0',
|
||||
'--retry-wait=5',
|
||||
'--uri-selector=inorder',
|
||||
'--console-log-level=warn',
|
||||
'-x16', '-j16', '-s16']
|
||||
if sys.version_info >= (3, 5):
|
||||
aria_out = subprocess.run(aria_command)
|
||||
aria_out.check_returncode()
|
||||
if os.path.isfile(LinkList_txt):
|
||||
os.remove(LinkList_txt)
|
||||
source_files = pathlib.Path(TempFolder).rglob('./*.mp4')
|
||||
with open(file_name, mode='wb') as (destination):
|
||||
for file in source_files:
|
||||
with open(file, mode='rb') as (source):
|
||||
shutil.copyfileobj(source, destination)
|
||||
|
||||
if os.path.exists(TempFolder):
|
||||
shutil.rmtree(TempFolder)
|
||||
|
||||
if os.path.exists(TempFolder):
|
||||
shutil.rmtree(TempFolder)
|
||||
|
||||
|
||||
|
||||
def get_intro_m3u8(self):
|
||||
m3u8_json = m3u8parser(requests.get(self.m3u8_url).text)
|
||||
url_dict_list = []
|
||||
init_url_list = []
|
||||
|
||||
for segment in m3u8_json['segments']:
|
||||
if not 'key' in segment:
|
||||
init_url_list.append(segment['init_section']['uri'])
|
||||
|
||||
for init_mp4 in init_url_list:
|
||||
url_list = []
|
||||
|
||||
for segment in m3u8_json['segments']:
|
||||
if not 'key' in segment:
|
||||
init_id = 'INTRO'
|
||||
url_list.append(init_mp4)
|
||||
url_list.append(segment['uri'])
|
||||
|
||||
url_dict_list.append({'id':init_id, 'url_list':url_list})
|
||||
|
||||
return url_dict_list
|
||||
|
||||
def get_segs_m3u8(self):
|
||||
m3u8_json = m3u8parser(requests.get(self.m3u8_url).text)
|
||||
url_dict_list = []
|
||||
init_url_list = []
|
||||
for segment in m3u8_json['segments']:
|
||||
if 'key' in segment:
|
||||
init_url_list.append(segment['init_section']['uri'])
|
||||
|
||||
for init_mp4 in init_url_list:
|
||||
url_list = []
|
||||
|
||||
for segment in m3u8_json['segments']:
|
||||
if 'key' in segment:
|
||||
url_list.append(segment['init_section']['uri'])
|
||||
|
||||
init = list(url_list)[0]
|
||||
seg_list = []
|
||||
for segment in m3u8_json['segments']:
|
||||
if init == segment['init_section']['uri']:
|
||||
init_id = 'MAIN'
|
||||
seg_list.append(segment['uri'])
|
||||
seg_list = [init] + seg_list
|
||||
|
||||
url_dict_list.append({'id':init_id, 'url_list':seg_list})
|
||||
|
||||
|
||||
return url_dict_list
|
||||
|
||||
def get_decon_m3u8(self):
|
||||
m3u8_json = m3u8parser(requests.get(self.m3u8_url).text)
|
||||
url_dict_list = []
|
||||
init_url_list = []
|
||||
for segment in m3u8_json['segments']:
|
||||
if 'key' in segment:
|
||||
init_url_list.append(segment['init_section']['uri'])
|
||||
|
||||
for init_mp4 in init_url_list:
|
||||
url_list = []
|
||||
|
||||
for segment in m3u8_json['segments']:
|
||||
if 'key' in segment:
|
||||
url_list.append(segment['init_section']['uri'])
|
||||
|
||||
init = list(url_list)[-1]
|
||||
seg_list = []
|
||||
for segment in m3u8_json['segments']:
|
||||
if init == segment['init_section']['uri']:
|
||||
init_id = 'EXTRAS'
|
||||
seg_list.append(segment['uri'])
|
||||
seg_list = [init] + seg_list
|
||||
|
||||
url_dict_list.append({'id':init_id, 'url_list':seg_list})
|
||||
|
||||
return url_dict_list
|
||||
|
||||
def run(self):
|
||||
segment_list = []
|
||||
segment_list = self.get_segs_m3u8()
|
||||
print('\n' + self.output_file)
|
||||
global temp_folder
|
||||
aria2c_infile = 'aria2c_infile.txt'
|
||||
if os.path.isfile(aria2c_infile):
|
||||
os.remove(aria2c_infile)
|
||||
|
||||
temp_folder = self.output_file.replace('.mp4', '')
|
||||
counter = 0
|
||||
for url_dic in segment_list:
|
||||
url_id = url_dic['id']
|
||||
count_id = str(counter).zfill(4)
|
||||
fileName = temp_folder + f'_{count_id}_{url_id}.mp4'
|
||||
self.downloadM3u8(Link_List=url_dic['url_list'], Folder=temp_folder, file_name=fileName)
|
||||
counter = counter + 1
|
||||
return segment_list
|
||||
15
pywidevine/clients/proxy_config.py
Normal file
15
pywidevine/clients/proxy_config.py
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
config = {
|
||||
'proxies': {
|
||||
'none': None
|
||||
},
|
||||
}
|
||||
|
||||
class ProxyConfig(object):
|
||||
def __init__(self, proxies):
|
||||
self.config = config
|
||||
self.config['proxies'] = proxies
|
||||
|
||||
def get_proxy(self, proxy):
|
||||
return self.config['proxies'].get(proxy)
|
||||
|
||||
Reference in New Issue
Block a user