HBO-MAX-BLIM-TV-Paramount-4.../blimtv.py

595 lines
25 KiB
Python

# -*- coding: utf-8 -*-
# Module: Blim Downloader
# Created on: 26-11-2020
# Authors: JUNi
# Version: 3.5
import requests
import subprocess
import xmltodict
import ffmpy
import time, shutil
import glob, json
import sys, os, re
import isodate
import oauthlib
from oauthlib import oauth1
from subprocess import Popen
from titlecase import titlecase
from unidecode import unidecode
from pymediainfo import MediaInfo
from os.path import isfile, join
import pywidevine.clients.blim.manifest_parse as manifestParse
import pywidevine.clients.blim.client as blim_client
import pywidevine.clients.blim.config as blim_cfg
from pywidevine.clients.proxy_config import ProxyConfig
from pywidevine.muxer.muxer import Muxer
from pywidevine.clients.blim.downloader_pr import PrDownloader
from pywidevine.clients.blim.downloader_wv import WvDownloader
from pywidevine.clients.blim.config import PrDownloaderConfig
from pywidevine.clients.blim.config import WvDownloaderConfig
currentFile = 'blimtv'
realPath = os.path.realpath(currentFile)
dirPath = os.path.dirname(realPath)
def main(args):
SubtitleEditexe = shutil.which("subtitleedit") or shutil.which("SubtitleEdit")
proxies = {}
proxy_meta = args.proxy
if proxy_meta == 'none':
proxies['meta'] = {'http': None, 'https': None}
elif proxy_meta:
proxies['meta'] = {'http': proxy_meta, 'https': proxy_meta}
SESSION = requests.Session()
SESSION.proxies = proxies.get('meta')
proxy_cfg = ProxyConfig(proxies)
def alphanumericSort(l):
def convert(text):
if text.isdigit():
return int(text)
else:
return text
def alphanum_key(key):
return [convert(c) for c in re.split('([0-9]+)', key)]
return sorted(l, key=alphanum_key)
def convert_size(size_bytes):
if size_bytes == 0:
return '0bps'
else:
s = round(size_bytes / 1000, 0)
return '%ikbps' % s
def get_size(size):
power = 1024
n = 0
Dic_powerN = {0:'', 1:'K', 2:'M', 3:'G', 4:'T'}
while size > power:
size /= power
n += 1
return str(round(size, 2)) + Dic_powerN[n] + 'B'
def find_str(s, char):
index = 0
if char in s:
c = char[0]
for ch in s:
if ch == c:
if s[index:index + len(char)] == char:
return index
index += 1
return -1
def getKeyId(name):
mp4dump = subprocess.Popen([blim_cfg.MP4DUMP, name], stdout=(subprocess.PIPE))
mp4dump = str(mp4dump.stdout.read())
A = find_str(mp4dump, 'default_KID')
KEY_ID_ORI = ''
KEY_ID_ORI = mp4dump[A:A + 63].replace('default_KID = ', '').replace('[', '').replace(']', '').replace(' ', '')
if KEY_ID_ORI == '' or KEY_ID_ORI == "'":
KEY_ID_ORI = 'nothing'
return KEY_ID_ORI
def replace_words(x):
x = re.sub(r'[]¡!"#$%\'()*+,:;<=>¿?@\\^_`{|}~[-]', '', x)
return unidecode(x)
def downloadFile2(link, file_name):
with open(file_name, 'wb') as (f):
print('\nDownloading %s' % file_name)
response = requests.get(link, stream=True)
total_length = response.headers.get('content-length')
if total_length is None:
f.write(response.content)
else:
dl = 0
total_length = int(total_length)
for data in response.iter_content(chunk_size=4096):
dl += len(data)
f.write(data)
done = int(50 * dl / total_length)
sys.stdout.write('\r[%s%s]' % ('=' * done, ' ' * (50 - done)))
sys.stdout.flush()
global folderdownloader
if args.output:
if not os.path.exists(args.output):
os.makedirs(args.output)
os.chdir(args.output)
if ":" in str(args.output):
folderdownloader = str(args.output).replace('/','\\').replace('.\\','\\')
else:
folderdownloader = dirPath + str(args.output).replace('/','\\').replace('.\\','\\')
else:
folderdownloader = dirPath.replace('/','\\').replace('.\\','\\')
def manifest_parse(manifest_url):
r = SESSION.get(url=manifest_url)
r.raise_for_status()
xml = xmltodict.parse(r.text)
manifest = json.loads(json.dumps(xml))
if '.mpd' in manifest_url:
length, video_list, audio_list, subs_list = manifestParse.get_mpd_list(manifest)
base_url = manifest['MPD']['Period']['BaseURL']
else:
length, video_list, audio_list, subs_list = manifestParse.get_ism_list(manifest)
base_url = manifest_url.split('Manifest')[0]
video_list = sorted(video_list, key=(lambda k: int(k['Bandwidth'])))
height_all = []
for x in video_list:
height_all.append(x['Height'])
try:
while args.customquality != [] and int(video_list[(-1)]['Height']) > int(args.customquality[0]):
video_list.pop(-1)
except Exception:
video_list = []
if video_list == []:
video_list = video_list
args.novideo = True
audio_list = sorted(audio_list, key=(lambda k: (int(k['Bandwidth']), str(k['Language']))), reverse=True)
BitrateList = []
AudioLanguageList = []
for x in audio_list:
BitrateList.append(x['Bandwidth'])
AudioLanguageList.append(x['Language'])
BitrateList = alphanumericSort(list(set(BitrateList)))
AudioLanguageList = alphanumericSort(list(set(AudioLanguageList)))
audioList_new = []
audio_Dict_new = {}
for y in AudioLanguageList:
counter = 0
for x in audio_list:
if x['Language'] == y and counter == 0:
audio_Dict_new = {
'Language':x['Language'],
'Bandwidth':x['Bandwidth'],
'ID':x['ID'],
'Codec': x['Codec']}
audioList_new.append(audio_Dict_new)
counter = counter + 1
audioList = audioList_new
audio_list = sorted(audioList, key=(lambda k: (int(k['Bandwidth']), str(k['Language']))))
subs_list = []
subsList_new = []
if args.sublang:
for x in subs_list:
langAbbrev = x['Language']
if langAbbrev in list(args.sublang):
subsList_new.append(x)
subs_list = subsList_new
return base_url, length, video_list, audio_list, subs_list, manifest
def get_episodes(ep_str, num_eps):
eps = ep_str.split(',')
eps_final = []
for ep in eps:
if '-' in ep:
(start, end) = ep.split('-')
start = int(start)
end = int(end or num_eps)
eps_final += list(range(start, end + 1))
else:
eps_final.append(int(ep))
return eps_final
tokenIsOk = False
os.makedirs(blim_cfg.COOKIES_FOLDER, exist_ok=True)
BLIMLOGINDATA_FILE = join(blim_cfg.COOKIES_FOLDER, 'blim_login_data.json')
SESSION, costumer_key, access_key_secret = blim_client.login(SESSION)
def get_auth_header(api_url):
client = oauthlib.oauth1.Client(costumer_key, client_secret=access_key_secret)
uri, auth_header, body = client.sign(api_url)
return auth_header
def get_season(blim_id):
season_req = requests.get(url=blim_cfg.ENDPOINTS['seasons'] + str(blim_id)).json()['data']
if 'episode' in season_req['category'] and args.season:
blim_id = season_req['parentShow']['id']
season_req = requests.get(url=blim_cfg.ENDPOINTS['seasons'] + str(blim_id)).json()['data']
if not args.season:
args.season = 'all'
seasons = []
if args.season:
if args.season == 'all':
seasons = 'all'
elif ',' in args.season:
seasons = [int(x) for x in args.season.split(',')]
elif '-' in args.season:
(start, end) = args.season.split('-')
seasons = list(range(int(start), int(end) + 1))
else:
seasons = [int(args.season)]
if 'series' in season_req['category']:
if seasons == 'all':
seasons = [x['number'] for x in season_req['seasons']]
for season_num in seasons:
episode_list = season_req['seasons'][int(season_num) - 1]['episodes']
episodes_list_new = []
for num, ep in enumerate(episode_list, start=1):
episodes_list_new.insert(num - 0, {
'id': ep['id'],
'episode_num': num
})
episode_list = sorted(episodes_list_new, key=lambda x: x['episode_num'])
if args.episodeStart:
eps = get_episodes(args.episodeStart, len(episode_list))
episode_list = [x for x in episode_list if x['episode_num'] in eps]
for episode in episode_list:
get_metadata(blim_id=episode['id'])
else:
get_metadata(blim_id)
def get_metadata(blim_id):
content_url = blim_cfg.ENDPOINTS['content'] + str(blim_id)
info_json = requests.get(url=content_url, headers=get_auth_header(content_url), proxies=proxy_cfg.get_proxy('meta')).json()
if 'episode' in info_json['data']['category']:
blimType = "show"
seriesTitles = info_json['data']['parentShow']['titleEditorial']
seasonNumber = info_json['data']['parentSeason']['number']
episodeNumber = info_json['data']['episodeNumber']
episodeTitle = info_json['data']['titleEditorial']
if 'movie' in info_json['data']['category']:
blimType = "movie"
seriesTitles = info_json['data']['titleEditorial']
releaseYearSearch = info_json['data']['airDate']
releaseYear = re.search(r"^[0-9]{4}", releaseYearSearch)
if blimType=="movie":
seriesName = replace_words(seriesTitles) + ' (' + releaseYear.group() + ')'
folderName = None
if blimType=="show":
seriesName = f'{replace_words(seriesTitles)} S{seasonNumber:02}E{episodeNumber:02} - {replace_words(episodeTitle)}'
folderName = f'{replace_words(seriesTitles)} S{seasonNumber:02}'
start_process(get_manifest_url(info_json), seriesName, folderName, blimType)
codec = "mpd" if args.codec == "widevine" else "ss"
def get_manifest_url(api_json):
video_json = api_json['data']['videos'][0]['files']
for x in video_json:
if x['type'] == codec:
videoURL = x['path'].replace("AVOD.", "")
if 'ss_629d09c4372f297f2760c820711c4d4737b14f26c25c55e58f1147819005089e' in videoURL or "468842.mpd" in videoURL:
print("Lo sentimos, por el momento Blim no está disponible en tu país")
sys.exit(0)
break
return videoURL
def get_drm_info(): # não está utilizando
resp = requests.get(url=blim_cfg.ENDPOINTS['config'], proxies=proxy_cfg.get_proxy('meta')).json()
wvlic = resp['widevineLicenseServer']
wvcert = resp['widevineCertificateServer']
return wvlic, wvcert
def start_process(manifest_url, seriesName, folderName, blimType):
base_url, length, video_list, audio_list, subs_list, xml = manifest_parse(manifest_url)
video_bandwidth = dict(video_list[(-1)])['Bandwidth']
video_height = str(dict(video_list[(-1)])['Height'])
video_width = str(dict(video_list[(-1)])['Width'])
video_codec = str(dict(video_list[(-1)])['Codec'])
video_format_id = str(dict(video_list[(-1)])['ID'])
if not args.novideo:
print('\nVIDEO - Bitrate: ' + convert_size(int(video_bandwidth)) + ' - Profile: ' + video_codec.split('=')[0] + ' - Size: ' + get_size(length * float(video_bandwidth) * 0.125) + ' - Dimensions: ' + video_width + 'x' + video_height)
print()
if not args.noaudio:
if audio_list != []:
for x in audio_list:
audio_bandwidth = x['Bandwidth']
audio_representation_id = str(x['Codec'])
audio_lang = x['Language']
print('AUDIO - Bitrate: ' + convert_size(int(audio_bandwidth)) + ' - Profile: ' + audio_representation_id.split('=')[0] + ' - Size: ' + get_size(length * float(audio_bandwidth) * 0.125) + ' - Language: ' + audio_lang)
print()
if not args.nosubs:
if subs_list != []:
for z in subs_list:
sub_lang = str(dict(z)['Language'])
print('SUBTITLE - Profile: Normal - Language: ' + sub_lang)
print()
print('Name: ' + seriesName)
if blimType == 'show':
CurrentName = seriesName
CurrentHeigh = str(video_height)
outputName = folderdownloader + '\\' + str(folderName) + str(CurrentName) + ' [' + str(CurrentHeigh) + 'p].mkv'
else:
CurrentName = seriesName
CurrentHeigh = str(video_height)
outputName = folderdownloader + str(CurrentName) + ' [' + str(CurrentHeigh) + 'p].mkv'
if 'ism' in manifest_url:
if video_height == "1080":
init_url = blim_cfg.init_files["1080p"]
elif video_height == "480":
init_url = blim_cfg.init_files["480p"]
if not os.path.isfile(outputName):
if not args.novideo:
inputVideo = seriesName + ' [' + str(CurrentHeigh) + 'p].mp4'
if os.path.isfile(inputVideo):
print('\n' + inputVideo + '\nFile has already been successfully downloaded previously.\n')
else:
if args.codec == 'playready':
prdl_cfg = PrDownloaderConfig(xml, base_url, inputVideo, video_bandwidth, init_url, 'video')
downloader = PrDownloader(prdl_cfg)
else:
wvdl_cfg = WvDownloaderConfig(xml, base_url, inputVideo, video_format_id, 'video/mp4')
downloader = WvDownloader(wvdl_cfg)
downloader.run()
if not args.noaudio:
for x in audio_list:
audio_lang = x['Language']
inputAudio = seriesName + ' ' + '(' + audio_lang + ')' + '.mp4'
inputAudio_demuxed = seriesName + ' ' + '(' + audio_lang + ')' + '.m4a'
if os.path.isfile(inputAudio) or os.path.isfile(inputAudio_demuxed):
print('\n' + inputAudio + '\nFile has already been successfully downloaded previously.\n')
else:
if args.codec == 'playready':
prdl_cfg = PrDownloaderConfig(xml, base_url, inputAudio, x['Bandwidth'], blim_cfg.init_files["audio"], 'audio')
downloader = PrDownloader(prdl_cfg)
else:
wvdl_cfg = WvDownloaderConfig(xml, base_url, inputAudio, x['ID'], 'audio/mp4')
downloader = WvDownloader(wvdl_cfg)
downloader.run()
if not args.nosubs:
if subs_list != []:
for z in subs_list:
langAbbrev = str(dict(z)['Language'])
inputSub = seriesName + " " + "(" + langAbbrev + ")"
if os.path.isfile(inputSub + ".vtt") or os.path.isfile(inputSub + ".srt"):
print("\n" + inputSub + "\nFile has already been successfully downloaded previously.\n")
else:
downloadFile2(str(dict(z)['File_URL']), inputSub + ".vtt")
print('\nConverting subtitles...')
SubtitleEdit_process = subprocess.Popen([SubtitleEditexe, "/convert", inputSub + ".vtt", "srt", "/fixcommonerrors", "/encoding:utf-8", "/RemoveLineBreaks"], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).wait()
for f in glob.glob(inputSub + ".vtt"):
os.remove(f)
print("Done!")
else:
print ("\nNo subtitles available.")
CorrectDecryptVideo = False
if not args.novideo:
inputVideo = seriesName + ' [' + str(CurrentHeigh) + 'p].mp4'
if os.path.isfile(inputVideo):
CorrectDecryptVideo = DecryptVideo(inputVideo=inputVideo, keys_video=blim_cfg.protection_keys)
else:
CorrectDecryptVideo = True
CorrectDecryptAudio = False
if not args.noaudio:
for x in audio_list:
audio_lang = x['Language']
inputAudio = seriesName + ' ' + '(' + audio_lang + ')' + '.mp4'
if os.path.isfile(inputAudio):
CorrectDecryptAudio = DecryptAudio(inputAudio=inputAudio, keys_audio=blim_cfg.protection_keys)
else:
CorrectDecryptAudio = True
if not args.nomux:
if not args.novideo:
if not args.noaudio:
if CorrectDecryptVideo == True:
if CorrectDecryptAudio == True:
print('\nMuxing...')
if blimType=="show":
MKV_Muxer=Muxer(CurrentName=CurrentName,
SeasonFolder=folderName,
CurrentHeigh=CurrentHeigh,
Type=blimType,
mkvmergeexe=blim_cfg.MKVMERGE)
else:
MKV_Muxer=Muxer(CurrentName=CurrentName,
SeasonFolder=None,
CurrentHeigh=CurrentHeigh,
Type=blimType,
mkvmergeexe=blim_cfg.MKVMERGE)
MKV_Muxer.mkvmerge_muxer(lang="English")
if args.tag:
inputName = CurrentName + ' [' + CurrentHeigh + 'p].mkv'
release_group(base_filename=inputName,
default_filename=CurrentName,
folder_name=folderName,
type=blimType,
video_height=CurrentHeigh)
if not args.keep:
for f in os.listdir():
if re.fullmatch(re.escape(CurrentName) + r'.*\.(mp4|m4a|h264|h265|eac3|srt|txt|avs|lwi|mpd)', f):
os.remove(f)
print("Done!")
else:
print("\nFile '" + str(outputName) + "' already exists.")
def release_group(base_filename, default_filename, folder_name, type, video_height):
if type=='show':
video_mkv = os.path.join(folder_name, base_filename)
else:
video_mkv = base_filename
mediainfo = MediaInfo.parse(video_mkv)
video_info = next(x for x in mediainfo.tracks if x.track_type == "Video")
video_format = video_info.format
video_codec = ''
if video_format == "AVC":
video_codec = 'H.264'
elif video_format == "HEVC":
video_codec = 'H.265'
audio_info = next(x for x in mediainfo.tracks if x.track_type == "Audio")
codec_name = audio_info.format
channels_number = int(audio_info.other_channel_positions[0].split('/')[0])
audio_codec = ''
audio_channels = ''
if codec_name == "AAC":
audio_codec = "AAC"
elif codec_name == "AC-3":
audio_codec = "DD"
elif codec_name == "E-AC-3":
audio_codec = "DDP"
elif codec_name == "E-AC-3 JOC":
audio_codec = "ATMOS"
if channels_number == "2":
audio_channels = "2.0"
elif channels_number == "6":
audio_channels = "5.1"
audio_ = audio_codec + audio_channels
# renomear arquivo
default_filename = default_filename.replace('&', '.and.')
default_filename = re.sub(r'[]!"#$%\'()*+,:;<=>?@\\^_`{|}~[-]', '', default_filename)
default_filename = default_filename.replace(' ', '.')
default_filename = re.sub(r'\.{2,}', '.', default_filename)
default_filename = unidecode(default_filename)
output_name = '{}.{}p.BLIM.WEB-DL.{}.{}-{}'.format(default_filename, video_height, audio_, video_codec, args.tag)
if type=='show':
outputName = os.path.join(folder_name, output_name + '.mkv')
else:
outputName = output_name + '.mkv'
os.rename(video_mkv, outputName)
print("{} -> {}".format(base_filename, output_name))
def DecryptAudio(inputAudio, keys_audio):
key_audio_id_original = getKeyId(inputAudio)
outputAudioTemp = inputAudio.replace(".mp4", "_dec.mp4")
if key_audio_id_original != "nothing":
for key in keys_audio:
key_id=key[0:32]
if key_id == key_audio_id_original:
print("\nDecrypting audio...")
print ("Using KEY: " + key)
wvdecrypt_process = subprocess.Popen([blim_cfg.MP4DECRYPT, "--show-progress", "--key", key, inputAudio, outputAudioTemp])
stdoutdata, stderrdata = wvdecrypt_process.communicate()
wvdecrypt_process.wait()
time.sleep (50.0/1000.0)
os.remove(inputAudio)
print("\nDemuxing audio...")
mediainfo = MediaInfo.parse(outputAudioTemp)
audio_info = next(x for x in mediainfo.tracks if x.track_type == "Audio")
codec_name = audio_info.format
ext = ''
if codec_name == "AAC":
ext = '.m4a'
elif codec_name == "E-AC-3":
ext = ".eac3"
elif codec_name == "AC-3":
ext = ".ac3"
outputAudio = outputAudioTemp.replace("_dec.mp4", ext)
print("{} -> {}".format(outputAudioTemp, outputAudio))
ff = ffmpy.FFmpeg(executable=blim_cfg.FFMPEG, inputs={outputAudioTemp: None}, outputs={outputAudio: '-c copy'}, global_options="-y -hide_banner -loglevel warning")
ff.run()
time.sleep (50.0/1000.0)
os.remove(outputAudioTemp)
print("Done!")
return True
elif key_audio_id_original == "nothing":
return True
def DecryptVideo(inputVideo, keys_video):
key_video_id_original = getKeyId(inputVideo)
inputVideo = inputVideo
outputVideoTemp = inputVideo.replace('.mp4', '_dec.mp4')
outputVideo = inputVideo
if key_video_id_original != 'nothing':
for key in keys_video:
key_id = key[0:32]
if key_id == key_video_id_original:
print('\nDecrypting video...')
print('Using KEY: ' + key)
wvdecrypt_process = subprocess.Popen([blim_cfg.MP4DECRYPT, '--show-progress', '--key', key, inputVideo, outputVideoTemp])
stdoutdata, stderrdata = wvdecrypt_process.communicate()
wvdecrypt_process.wait()
print('\nRemuxing video...')
ff = ffmpy.FFmpeg(executable=blim_cfg.FFMPEG, inputs={outputVideoTemp: None}, outputs={outputVideo: '-c copy'}, global_options='-y -hide_banner -loglevel warning')
ff.run()
time.sleep(0.05)
os.remove(outputVideoTemp)
print('Done!')
return True
elif key_video_id_original == 'nothing':
return True
def id_parse(x):
if 'player' in args.url_season:
id_ = args.url_season.split('/')[-2]
else:
id_ = args.url_season.split('/')[-1]
return id_
blim_id = id_parse(args.url_season)
if 'player' in args.url_season:
get_metadata(blim_id)
else:
get_season(blim_id)