New
This commit is contained in:
0
pywidevine/downloader/__init__.py
Normal file
0
pywidevine/downloader/__init__.py
Normal file
BIN
pywidevine/downloader/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
pywidevine/downloader/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
pywidevine/downloader/__pycache__/__init__.cpython-37.pyc
Normal file
BIN
pywidevine/downloader/__pycache__/__init__.cpython-37.pyc
Normal file
Binary file not shown.
BIN
pywidevine/downloader/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
pywidevine/downloader/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
BIN
pywidevine/downloader/__pycache__/tracks.cpython-36.pyc
Normal file
BIN
pywidevine/downloader/__pycache__/tracks.cpython-36.pyc
Normal file
Binary file not shown.
BIN
pywidevine/downloader/__pycache__/tracks.cpython-37.pyc
Normal file
BIN
pywidevine/downloader/__pycache__/tracks.cpython-37.pyc
Normal file
Binary file not shown.
BIN
pywidevine/downloader/__pycache__/tracks.cpython-39.pyc
Normal file
BIN
pywidevine/downloader/__pycache__/tracks.cpython-39.pyc
Normal file
Binary file not shown.
BIN
pywidevine/downloader/__pycache__/wvdownloader.cpython-36.pyc
Normal file
BIN
pywidevine/downloader/__pycache__/wvdownloader.cpython-36.pyc
Normal file
Binary file not shown.
BIN
pywidevine/downloader/__pycache__/wvdownloader.cpython-37.pyc
Normal file
BIN
pywidevine/downloader/__pycache__/wvdownloader.cpython-37.pyc
Normal file
Binary file not shown.
BIN
pywidevine/downloader/__pycache__/wvdownloader.cpython-39.pyc
Normal file
BIN
pywidevine/downloader/__pycache__/wvdownloader.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
74
pywidevine/downloader/tracks.py
Normal file
74
pywidevine/downloader/tracks.py
Normal file
@@ -0,0 +1,74 @@
|
||||
import pywidevine.downloader.wvdownloaderconfig as wvdl_cfg
|
||||
|
||||
class VideoTrack(object):
|
||||
def __init__(self, encrypted, size, id, url, codec, bitrate, width, height):
|
||||
self.encrypted = encrypted
|
||||
self.size = size
|
||||
self.id = id
|
||||
self.url = url
|
||||
self.codec = codec
|
||||
self.bitrate = bitrate
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def get_type(self):
|
||||
return "video"
|
||||
|
||||
def __repr__(self):
|
||||
return "(encrypted={}, size={}, id={}, url={}, codec={}, bitrate={}, width={}, height={})"\
|
||||
.format(self.encrypted, self.size, self.id, self.url, self.codec, self.bitrate, self.width, self.height)
|
||||
|
||||
def get_filename(self, filename, decrypted=False, fixed=False):
|
||||
if not self.encrypted or decrypted:
|
||||
fn = wvdl_cfg.DECRYPTED_FILENAME
|
||||
else:
|
||||
fn = wvdl_cfg.ENCRYPTED_FILENAME
|
||||
if fixed:
|
||||
fn = fn + '_fixed.mp4'
|
||||
return fn.format(filename=filename, track_type="video", track_no=self.id)
|
||||
|
||||
|
||||
|
||||
class AudioTrack(object):
|
||||
def __init__(self, encrypted, size, id, url, codec, bitrate, language):
|
||||
self.encrypted = encrypted
|
||||
self.size = size
|
||||
self.id = id
|
||||
self.url = url
|
||||
self.codec = codec
|
||||
self.bitrate = bitrate
|
||||
self.language = language
|
||||
|
||||
def get_type(self):
|
||||
return "audio"
|
||||
|
||||
def get_lang(self):
|
||||
return "audio"
|
||||
|
||||
def __repr__(self):
|
||||
return "(encrypted={}, language={}, size={}, id={}, url={}, codec={}, bitrate={})"\
|
||||
.format(self.encrypted, self.language, self.size, self.id, self.url, self.codec, self.bitrate)
|
||||
|
||||
def get_filename(self, filename, decrypted=False, fixed=False):
|
||||
if not self.encrypted or decrypted:
|
||||
fn = wvdl_cfg.DECRYPTED_FILENAME_AUDIO
|
||||
else:
|
||||
fn = wvdl_cfg.ENCRYPTED_FILENAME_AUDIO
|
||||
if fixed:
|
||||
fn = fn + '_fixed.mka'
|
||||
return fn.format(filename=filename, language=self.language, track_type="audio", track_no=self.id)
|
||||
|
||||
class SubtitleTrack(object):
|
||||
def __init__(self, id, name, language_code, default, url, type):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.language_code = language_code
|
||||
self.url = url
|
||||
self.type = type
|
||||
self.default = default
|
||||
|
||||
def __repr__(self):
|
||||
return "(id={}, name={}, language_code={}, url={}, type={})".format(self.id, self.name, self.language_code, self.url, self.type)
|
||||
|
||||
def get_filename(self, filename, subtitle_format):
|
||||
return wvdl_cfg.SUBTITLES_FILENAME.format(filename=filename, language_code=self.language_code, id=self.id, subtitle_type=subtitle_format)
|
||||
854
pywidevine/downloader/wvdownloader.py
Normal file
854
pywidevine/downloader/wvdownloader.py
Normal file
@@ -0,0 +1,854 @@
|
||||
import logging
|
||||
import threading
|
||||
import pywidevine.downloader.wvdownloaderconfig as wvdl_cfg
|
||||
from pywidevine.decrypt.wvdecryptconfig import WvDecryptConfig
|
||||
from pywidevine.decrypt.wvdecrypt import WvDecrypt
|
||||
from tqdm import tqdm
|
||||
import requests
|
||||
import pycaption
|
||||
import pysubs2
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
import base64
|
||||
import time
|
||||
import binascii
|
||||
|
||||
|
||||
|
||||
|
||||
class WvDownloader(object):
|
||||
|
||||
def __init__(self, config):
|
||||
self.client = config.client
|
||||
self.filename = config.filename
|
||||
self.config = config
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.threads = None
|
||||
|
||||
def get_track_download_thread(self, track, bar_position):
|
||||
self.logger.debug("creating thread for {}_{}".format(track.get_type(), track.id))
|
||||
|
||||
if track.encrypted:
|
||||
output_file = wvdl_cfg.ENCRYPTED_FILENAME
|
||||
else:
|
||||
output_file = wvdl_cfg.DECRYPTED_FILENAME
|
||||
output_file = output_file.format(filename=self.filename, track_type=track.get_type(), track_no=track.id)
|
||||
|
||||
output_dir = output_file.split("\\")[0]
|
||||
output_name = output_file.split("\\")[1]
|
||||
|
||||
file_size = track.size
|
||||
self.logger.debug("file size {}".format(file_size))
|
||||
|
||||
def do_download():
|
||||
"""
|
||||
pbar = tqdm(desc='{}_{}'.format(track.get_type(), track.id), total=int(file_size), unit="bytes", unit_scale=True, position=bar_position)
|
||||
downloaded_size = 0
|
||||
with open(output_file, 'wb') as fd:
|
||||
try:
|
||||
r = self.client.get_track_download(track)
|
||||
for chunk in r.iter_content(1024):
|
||||
fd.write(chunk)
|
||||
downloaded_size += len(chunk)
|
||||
pbar.update(len(chunk))
|
||||
except requests.RequestException as e:
|
||||
self.logger.debug("requests download error for track {}_{}, exiting".format(track.get_type(), track.id))
|
||||
exit(1)
|
||||
except:
|
||||
exit(1)
|
||||
pbar.close()
|
||||
"""
|
||||
aria2_p = subprocess.run(["binaries\\aria2c_nf", "-x16", "-s16", track.url, f"--dir={output_dir}", f"--out={output_name}"])
|
||||
return aria2_p.returncode
|
||||
return threading.Thread(target=do_download)
|
||||
|
||||
|
||||
|
||||
def get_track_download_thread_audio(self, track, bar_position):
|
||||
|
||||
language = track.language
|
||||
#.replace("English [Original]", "English")
|
||||
|
||||
#if track.language == "English [Original]":
|
||||
# track.language = "English"
|
||||
self.logger.debug("creating thread for {}_{}".format(track.get_type(), track.id))
|
||||
|
||||
if track.encrypted:
|
||||
output_file = wvdl_cfg.ENCRYPTED_FILENAME_AUDIO
|
||||
else:
|
||||
output_file = wvdl_cfg.DECRYPTED_FILENAME_AUDIO
|
||||
output_file = output_file.format(filename=self.filename, language=language, track_type=track.get_type(), track_no=track.id)
|
||||
|
||||
output_dir = output_file.split("\\")[0]
|
||||
output_name = output_file.split("\\")[1]
|
||||
|
||||
#output_file = fn.format(filename=self.filename, language=language, track_type="audio", track_no=track.id)
|
||||
file_size = track.size
|
||||
self.logger.debug("file size {}".format(file_size))
|
||||
|
||||
def do_download():
|
||||
"""
|
||||
pbar = tqdm(desc='{}_{}'.format(track.get_type(), track.id), total=int(file_size), unit="bytes", unit_scale=True, position=bar_position)
|
||||
downloaded_size = 0
|
||||
with open(output_file, 'wb') as fd:
|
||||
try:
|
||||
r = self.client.get_track_download(track)
|
||||
for chunk in r.iter_content(1024):
|
||||
fd.write(chunk)
|
||||
downloaded_size += len(chunk)
|
||||
pbar.update(len(chunk))
|
||||
except requests.RequestException as e:
|
||||
self.logger.debug("requests download error for track {}_{}, exiting".format(track.get_type(), track.id))
|
||||
exit(1)
|
||||
except:
|
||||
exit(1)
|
||||
pbar.close()
|
||||
"""
|
||||
aria2_p = subprocess.run(["binaries\\aria2c_nf", "-x16", "-s16", track.url, f"--dir={output_dir}", f"--out={output_name}"])
|
||||
return aria2_p.returncode
|
||||
return threading.Thread(target=do_download)
|
||||
|
||||
|
||||
def find_str(self, s, char):
|
||||
index = 0
|
||||
if char in s:
|
||||
c = char[0]
|
||||
for ch in s:
|
||||
if ch == c and s[index:index + len(char)] == char:
|
||||
return index
|
||||
index += 1
|
||||
|
||||
return -1
|
||||
|
||||
def Get_PSSH(self, mp4_file):
|
||||
currentFile = __file__
|
||||
realPath = os.path.realpath(currentFile)
|
||||
dirPath = os.path.dirname(realPath)
|
||||
dirName = os.path.basename(dirPath)
|
||||
mp4dump = dirPath + "/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)
|
||||
#if pssh.startswith('\x08\x01'):
|
||||
# pssh = pssh[0:]
|
||||
pssh = pssh[0:]
|
||||
pssh = base64.b64encode(pssh).decode('utf-8')
|
||||
return pssh
|
||||
|
||||
def do_decrypt(self, config):
|
||||
wvdecrypt = WvDecrypt(config)
|
||||
chal = wvdecrypt.get_challenge()
|
||||
#sess_id = wvdecrypt.get_sid()
|
||||
license_b64 = self.client.get_license(chal, '123456')
|
||||
if not license_b64:
|
||||
return False
|
||||
wvdecrypt.update_license(license_b64)
|
||||
wvdecrypt.start_process()
|
||||
return True
|
||||
|
||||
def download_and_convert_subtitle(self, subtitle):
|
||||
language_code = subtitle.language_code
|
||||
id = subtitle.id
|
||||
self.logger.info("downloading {}_{} subtitles".format(language_code, id))
|
||||
output_filename = wvdl_cfg.SUBTITLES_FILENAME.format(filename=self.filename, language_code=language_code, id=id, subtitle_type=self.config.subtitle_format)
|
||||
print(output_filename)
|
||||
print(subtitle)
|
||||
subtitle_text = self.client.get_subtitle_download(subtitle).text
|
||||
subtitle_text = self.client.finagle_subs(subtitle_text)
|
||||
if subtitle.type == "dfxp":
|
||||
srt_subs = pycaption.SRTWriter().write(pycaption.DFXPReader().read(subtitle_text.replace('tt:', '')))
|
||||
elif subtitle.type == "srt":
|
||||
srt_subs = subtitle_text
|
||||
else:
|
||||
self.logger.error("subtitle error")
|
||||
exit(1)
|
||||
if self.config.subtitle_format == 'srt':
|
||||
srt_subs = srt_subs.replace('&', '&')
|
||||
with open(output_filename, 'w', encoding='utf-8') as f:
|
||||
f.write(srt_subs)
|
||||
elif self.config.subtitle_format == 'ass':
|
||||
ass_subs = pysubs2.SSAFile.from_string(srt_subs)
|
||||
# you can modify ass_subs to change the output format/style here
|
||||
# eg
|
||||
# ass_subs.styles['Default'].fontname = 'That terrible font HS uses'
|
||||
# see https://github.com/tkarabela/pysubs2/blob/master/pysubs2/ssastyle.py etc
|
||||
ass_subs.styles['Default'].fontname = 'Open Sans Semibold'
|
||||
ass_subs.styles['Default'].fontsize = 36
|
||||
ass_subs.styles['Default'].outlinecolor = pysubs2.Color(19,7,2,0)
|
||||
ass_subs.styles['Default'].outline = 1.7
|
||||
ass_subs.styles['Default'].shadow = 0
|
||||
ass_subs.styles['Default'].marginl = 0
|
||||
ass_subs.styles['Default'].marginr = 0
|
||||
ass_subs.styles['Default'].marginv = 28
|
||||
ass_subs.info.update({'PlayResX': 848})
|
||||
ass_subs.info.update({'PlayResY': 480})
|
||||
ass_subs.save(output_filename)
|
||||
else:
|
||||
self.logger.error("unknown subtitle output format")
|
||||
exit(1)
|
||||
|
||||
def do_ffmpeg_fix(self, track):
|
||||
ffmpeg_command = [wvdl_cfg.FFMPEG_BINARY_PATH,
|
||||
"-i",
|
||||
track.get_filename(self.filename, decrypted=True),
|
||||
"-map_metadata",
|
||||
"-1",
|
||||
"-codec",
|
||||
"copy",
|
||||
track.get_filename(self.filename, decrypted=True, fixed=True)]
|
||||
subprocess.run(ffmpeg_command)
|
||||
|
||||
def do_merge(self, vt, ats, sts):
|
||||
#exit(1)
|
||||
if self.client.needs_ffmpeg():
|
||||
vfn = vt.get_filename(self.filename, decrypted=True, fixed=True)
|
||||
else:
|
||||
vfn = vt.get_filename(self.filename, decrypted=True)
|
||||
|
||||
for audio_track in ats:
|
||||
if 'ddplus-atmos-dash' in audio_track.codec:
|
||||
audiocodec_file = 'DD+5.1.Atmos'
|
||||
if 'heaac-2-dash' in audio_track.codec:
|
||||
audiocodec_file = 'AAC2.0'
|
||||
if 'ddplus-2.0-dash' in audio_track.codec:
|
||||
audiocodec_file = 'DD+2.0'
|
||||
if 'ddplus-2.0-dash' in audio_track.codec:
|
||||
audiocodec_file = 'DD+2.0'
|
||||
if 'ddplus-5.1-dash' in audio_track.codec:
|
||||
audiocodec_file = 'DD+5.1'
|
||||
if 'ddplus-5.1hq-dash' in audio_track.codec:
|
||||
audiocodec_file = 'DD+5.1'
|
||||
|
||||
filename_audiocodec = self.filename.replace('AUDIOCODEC', audiocodec_file)
|
||||
filename_audiocodec_fixed = filename_audiocodec + '_fixed'
|
||||
filename_audiocodec_fixed1 = filename_audiocodec + '_fixed1'
|
||||
|
||||
#self.config1 = config
|
||||
#filename = self.config1.filename + '_' + self.config1.tracktype + '_' + str(self.config1.trackno) + '_'
|
||||
|
||||
currentFile = __file__
|
||||
realPath = os.path.realpath(currentFile)
|
||||
realPath = realPath.replace('pywidevine\\downloader\\wvdownloader.py', '')
|
||||
dirPath = os.path.dirname(realPath)
|
||||
dirName = os.path.basename(dirPath)
|
||||
wvDecrypterexe = dirPath + '/binaries/wvDecrypter/wvDecrypter.exe'
|
||||
challengeBIN = dirPath + '/binaries/wvDecrypter/challenge.bin'
|
||||
licenceBIN = dirPath + '/binaries/wvDecrypter/licence.bin'
|
||||
mp4dump = dirPath + "/binaries/mp4dump.exe"
|
||||
TEMP_FOLDER = dirPath + '/temp'
|
||||
|
||||
|
||||
#if 'h264' in self.filename:
|
||||
# print('x264x624x264x264')
|
||||
# DECRYPTED_FILENAME = os.path.join(TEMP_FOLDER, self.filename + 'decrypted.264')
|
||||
if 'h265' in self.filename:
|
||||
print('x265x265x265x265')
|
||||
vfn = vfn.replace('264', 'h265')
|
||||
#print(self.filename)
|
||||
#print(wvdl_cfg.MUXED_FILENAME.format(filename=DECRYPTED_FILENAME))
|
||||
mkvmerge_command = [wvdl_cfg.MKVMERGE_BINARY_PATH,
|
||||
"--compression",
|
||||
"-1:none",
|
||||
"--no-global-tags",
|
||||
"--output",
|
||||
wvdl_cfg.MUXED_FILENAME.format(filename=filename_audiocodec),
|
||||
"--language",
|
||||
"0:und",
|
||||
"(",
|
||||
vfn,
|
||||
")"]
|
||||
for audio_track in ats:
|
||||
if self.client.needs_ffmpeg():
|
||||
fn = audio_track.get_filename(self.filename, decrypted=True, fixed=True)
|
||||
else:
|
||||
fn = audio_track.get_filename(self.filename, decrypted=True)
|
||||
audio_lang_dict = {
|
||||
'English': 'eng',
|
||||
'Spanish': 'spa',
|
||||
'European Spanish': 'spa',
|
||||
'Brazilian Portuguese': 'por',
|
||||
'Polish': 'pol',
|
||||
'Turkish': 'tur',
|
||||
'French': 'fre',
|
||||
'German': 'ger',
|
||||
'Italian': 'ita',
|
||||
'Czech': 'cze',
|
||||
'Japanese': 'jpn',
|
||||
'Hebrew': 'heb',
|
||||
'Norwegian': 'nor',
|
||||
'Swedish': 'swe',
|
||||
'Arabic': 'ara',
|
||||
'Dutch': 'dut',
|
||||
'Flemish': 'dut'
|
||||
}
|
||||
if audio_track.language and audio_track.language in audio_lang_dict:
|
||||
lang = '0:{}'.format(audio_lang_dict[audio_track.language])
|
||||
else:
|
||||
lang = '0:und'
|
||||
mkvmerge_command = mkvmerge_command + ["--compression",
|
||||
"-1:none",
|
||||
"--no-global-tags",
|
||||
"--language",
|
||||
lang,
|
||||
"(",
|
||||
fn,
|
||||
")"]
|
||||
|
||||
#https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes
|
||||
for subtitle_track in sts:
|
||||
subtitle_lang_dict = {
|
||||
'en': 'eng',
|
||||
'es': 'spa',
|
||||
'es-ES': 'spa',
|
||||
'fr': 'fre',
|
||||
'de': 'ger',
|
||||
'it': 'ita',
|
||||
'pt-BR': 'por',
|
||||
'nl-BE': 'dut',
|
||||
'pl': 'pol',
|
||||
'fi': 'fin',
|
||||
'tl': 'tgl',
|
||||
'cs': 'cze',
|
||||
'zxx': 'zxx',
|
||||
'es-ES': 'spa',
|
||||
'nl': 'dut',
|
||||
'nb': 'nor',
|
||||
'da': 'dan',
|
||||
'pt': 'por',
|
||||
'pl': 'pol',
|
||||
'sv': 'swe',
|
||||
'fi': 'fin',
|
||||
'tr': 'tur',
|
||||
'he': 'heb',
|
||||
'ru': 'rus',
|
||||
'hu': 'hun',
|
||||
'id': 'ind',
|
||||
'el': 'ell',
|
||||
'ar': 'ara',
|
||||
}
|
||||
if subtitle_track.language_code and subtitle_track.language_code in subtitle_lang_dict:
|
||||
lang = '0:{}'.format(subtitle_lang_dict[subtitle_track.language_code])
|
||||
else:
|
||||
lang = '0:und'
|
||||
|
||||
if subtitle_track.name == 'de':
|
||||
track_name = 'German_Full'
|
||||
if subtitle_track.name == 'en':
|
||||
track_name = 'English_Full'
|
||||
if subtitle_track.name == 'it':
|
||||
track_name = 'Italian_Full'
|
||||
if subtitle_track.name == 'es':
|
||||
track_name = 'Spanish_Full'
|
||||
if subtitle_track.name == 'es-ES':
|
||||
track_name = 'Spanish_Full'
|
||||
if subtitle_track.name == 'fr':
|
||||
track_name = 'French_Full'
|
||||
if subtitle_track.name == 'pl':
|
||||
track_name = 'Polish_Full'
|
||||
if subtitle_track.name == 'jp':
|
||||
track_name = 'Japanese_Full'
|
||||
if subtitle_track.name == 'tr':
|
||||
track_name = 'Turkish_Full'
|
||||
if subtitle_track.name == 'pt-BR':
|
||||
track_name = 'Brazilian.Portuguese_Full'
|
||||
if subtitle_track.name == 'da':
|
||||
track_name = 'Danish_Full'
|
||||
if subtitle_track.name == 'nb':
|
||||
track_name = 'Norwegian_Full'
|
||||
if subtitle_track.name == 'sv':
|
||||
track_name = 'Swedish_Full'
|
||||
if subtitle_track.name == 'ar':
|
||||
track_name = 'Arabic_Full'
|
||||
if subtitle_track.name == 'nl-BE':
|
||||
track_name = 'Flemish_Full'
|
||||
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'de':
|
||||
track_name = 'German_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'en':
|
||||
track_name = 'English_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'it':
|
||||
track_name = 'Italian_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'es':
|
||||
track_name = 'Spanish_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'es-ES':
|
||||
track_name = 'Spanish_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'fr':
|
||||
track_name = 'French_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'pl':
|
||||
track_name = 'Polish_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'jp':
|
||||
track_name = 'Japanese_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'tr':
|
||||
track_name = 'Turkish_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'pt-BR':
|
||||
track_name = 'Brazilian.Portuguese_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'da':
|
||||
track_name = 'Danish_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'nb':
|
||||
track_name = 'Norwegian_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'sv':
|
||||
track_name = 'Swedish_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'ar':
|
||||
track_name = 'Arabic_Forced'
|
||||
if subtitle_track.name == 'Forced' and subtitle_track.language_code == 'nl-BE':
|
||||
track_name = 'Flemish_Forced'
|
||||
|
||||
|
||||
"""
|
||||
if subtitle_track.default:
|
||||
mkvmerge_command = mkvmerge_command + ["--compression",
|
||||
"-1:none",
|
||||
"--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:yes",
|
||||
"--forced-track",
|
||||
"0:yes",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
else:
|
||||
mkvmerge_command = mkvmerge_command + ["--compression",
|
||||
"-1:none",
|
||||
"--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
"""
|
||||
|
||||
|
||||
if subtitle_track.default:
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:yes",
|
||||
"--forced-track",
|
||||
"0:yes",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'en':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'it':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'es':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'es-ES':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'it':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'pl':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'jp':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'tr':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'pt-BR':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'da':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'nb':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'sv':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'ar':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
elif subtitle_track.name == 'Forced' and subtitle_track.language_code == 'nl-BE':
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
else:
|
||||
mkvmerge_command = mkvmerge_command + ["--language",
|
||||
lang,
|
||||
"--sub-charset",
|
||||
"0:UTF-8",
|
||||
"--track-name",
|
||||
#"0:{}".format(subtitle_track.name),
|
||||
"0:{}".format( track_name),
|
||||
"--default-track",
|
||||
"0:no",
|
||||
"--forced-track",
|
||||
"0:no",
|
||||
"(",
|
||||
subtitle_track.get_filename(self.filename, self.config.subtitle_format),
|
||||
")"]
|
||||
|
||||
|
||||
|
||||
if self.config.subtitle_format == 'ass':
|
||||
mkvmerge_command = mkvmerge_command + ["--attachment-mime-type",
|
||||
"application/x-truetype-font",
|
||||
"--attachment-name",
|
||||
"OpenSans-Semibold.ttf",
|
||||
"--attach-file",
|
||||
"./fonts/OpenSans-Semibold.ttf"]
|
||||
|
||||
subprocess.run(mkvmerge_command)
|
||||
|
||||
|
||||
"""
|
||||
mkvmerge_command_fixed = [wvdl_cfg.MKVMERGE_BINARY_PATH,
|
||||
"--compression",
|
||||
"-1:none",
|
||||
"--no-attachments",
|
||||
"--no-track-tags",
|
||||
"--no-global-tags",
|
||||
"--disable-track-statistics-tags",
|
||||
"--output",
|
||||
wvdl_cfg.MUXED_FILENAME.format(filename=filename_audiocodec_fixed),
|
||||
"(",
|
||||
wvdl_cfg.MUXED_FILENAME.format(filename=filename_audiocodec),
|
||||
")"]
|
||||
|
||||
subprocess.run(mkvmerge_command_fixed)
|
||||
|
||||
os.remove(wvdl_cfg.MUXED_FILENAME.format(filename=filename_audiocodec))
|
||||
|
||||
mkvmerge_command_fixed1 = [wvdl_cfg.MKVMERGE_BINARY_PATH,
|
||||
"--compression",
|
||||
"-1:none",
|
||||
"--output",
|
||||
wvdl_cfg.MUXED_FILENAME.format(filename=filename_audiocodec_fixed1),
|
||||
"(",
|
||||
wvdl_cfg.MUXED_FILENAME.format(filename=filename_audiocodec_fixed),
|
||||
")"]
|
||||
|
||||
subprocess.run(mkvmerge_command_fixed1)
|
||||
|
||||
os.remove(wvdl_cfg.MUXED_FILENAME.format(filename=filename_audiocodec_fixed))
|
||||
"""
|
||||
|
||||
def run(self):
|
||||
self.logger.info("wvdownloader starting")
|
||||
self.logger.info("logging client in")
|
||||
if self.client.login():
|
||||
self.logger.info("login successful")
|
||||
else:
|
||||
self.logger.error("login failed, please check credentials")
|
||||
exit(1)
|
||||
self.logger.info("getting track and widevine init data")
|
||||
print(self.config.quality)
|
||||
success, data = self.client.get_track_and_init_info(self.config.quality, self.config.profile)
|
||||
if not success:
|
||||
self.logger.error("get_track_and_init_info failed")
|
||||
exit(1)
|
||||
self.logger.info("track info and init data retrieved")
|
||||
if self.config.print_info:
|
||||
self.logger.info("info mode done, quitting")
|
||||
exit(0)
|
||||
|
||||
vt, ats, sts, init_data_b64, cert_data_b64, device = data
|
||||
if not self.config.license:
|
||||
self.logger.info("downloading subtitles")
|
||||
#subs
|
||||
self.logger.debug("requested output format {}".format(self.config.subtitle_format))
|
||||
for subtitle in sts:
|
||||
self.download_and_convert_subtitle(subtitle)
|
||||
self.logger.info("all subtitles downloaded, merging")
|
||||
if self.config.subs_only:
|
||||
self.logger.info("subs downloaded, quitting")
|
||||
exit(0)
|
||||
self.logger.info("creating video & audio track download threads")
|
||||
threads = []
|
||||
threads.append(self.get_track_download_thread(vt, 0))
|
||||
for i, track in enumerate(ats):
|
||||
threads.append(self.get_track_download_thread_audio(track, i+1))
|
||||
self.logger.info("starting video&audio downloads")
|
||||
self.threads = threads
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
self.logger.info("video and audio tracks downloaded successfully")
|
||||
self.logger.info("decrypting encrypted tracks")
|
||||
self.logger.info("note: does not do this in parallel since we're IO bound anyway")
|
||||
enc_tracks = [x for x in [vt] + ats if x.encrypted]
|
||||
for track in enc_tracks:
|
||||
wvdecrypt_config = WvDecryptConfig(self.filename, track.get_type(), track.id, self.config.license,
|
||||
init_data_b64, device, cert_data_b64=cert_data_b64)
|
||||
if self.config.gf is not None and self.client.client_config.config['region'] != 'us':
|
||||
curr_region = self.config.gf.region_get('prime')
|
||||
if self.client.client_config.config['region'] == 'uk' and curr_region != 'GB':
|
||||
self.config.gf.region_set('prime','GB')
|
||||
elif self.client.client_config.config['region'].upper() != curr_region and self.client.client_config.config['region'] != 'uk':
|
||||
self.config.gf.region_set('prime',self.client.client_config.config['region'].upper())
|
||||
try:
|
||||
if not self.do_decrypt(wvdecrypt_config):
|
||||
return False
|
||||
except:
|
||||
if self.config.gf is not None and self.client.client_config.config['region'] != 'us':
|
||||
curr_region = self.config.gf.region_get('prime')
|
||||
if self.client.client_config.config['region'] == 'uk' and curr_region != 'GB':
|
||||
self.config.gf.region_set('prime','GB')
|
||||
elif self.client.client_config.config['region'].upper() != curr_region and self.client.client_config.config['region'] != 'uk':
|
||||
self.config.gf.region_set('prime',self.client.client_config.config['region'].upper())
|
||||
if not self.do_decrypt(wvdecrypt_config):
|
||||
return False
|
||||
|
||||
if self.config.license:
|
||||
return True
|
||||
else:
|
||||
self.logger.info("all decrypting complete")
|
||||
|
||||
if self.client.needs_ffmpeg():
|
||||
self.logger.info("processing mp4s with ffmpeg to fix the headers")
|
||||
#hdr hevc dv no ffmpeg
|
||||
|
||||
self.do_ffmpeg_fix(vt)
|
||||
for track in ats:
|
||||
self.do_ffmpeg_fix(track)
|
||||
|
||||
#self.config.dont_mux = False
|
||||
if self.config.dont_mux:
|
||||
self.logger.info('moving tracks instead of muxing')
|
||||
#hdr hevc dv no ffmpeg fix
|
||||
if self.client.needs_ffmpeg():
|
||||
vfn = vt.get_filename(self.filename, decrypted=True, fixed=True)
|
||||
else:
|
||||
vfn = vt.get_filename(self.filename, decrypted=True)
|
||||
shutil.move(vfn, vfn.replace(wvdl_cfg.TEMP_FOLDER, wvdl_cfg.OUTPUT_FOLDER))
|
||||
for audio_track in ats:
|
||||
if self.client.needs_ffmpeg():
|
||||
fn = audio_track.get_filename(self.filename, decrypted=True, fixed=True)
|
||||
else:
|
||||
fn = audio_track.get_filename(self.filename, decrypted=True)
|
||||
shutil.move(fn, fn.replace(wvdl_cfg.TEMP_FOLDER, wvdl_cfg.OUTPUT_FOLDER))
|
||||
for subtitle_track in sts:
|
||||
fn = subtitle_track.get_filename(self.filename, self.config.subtitle_format)
|
||||
shutil.move(fn, fn.replace(wvdl_cfg.TEMP_FOLDER, wvdl_cfg.OUTPUT_FOLDER))
|
||||
else:
|
||||
self.do_merge(vt, ats, sts)
|
||||
self.logger.info("file written, cleaning up temp")
|
||||
if self.config.skip_cleanup:
|
||||
self.logger.info('skipping clean')
|
||||
return True
|
||||
file_list = [f for f in os.listdir(wvdl_cfg.TEMP_FOLDER)]
|
||||
for f in file_list:
|
||||
if f.startswith("{}_".format(self.filename)):
|
||||
os.remove(os.path.join(wvdl_cfg.TEMP_FOLDER,f))
|
||||
self.logger.info("cleaned up temp")
|
||||
return True
|
||||
|
||||
|
||||
|
||||
59
pywidevine/downloader/wvdownloaderconfig.py
Normal file
59
pywidevine/downloader/wvdownloaderconfig.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import re
|
||||
import os
|
||||
import platform
|
||||
|
||||
# constants
|
||||
|
||||
BINARIES_FOLDER = 'binaries'
|
||||
COOKIES_FOLDER = 'cookies'
|
||||
TEMP_FOLDER = 'temp'
|
||||
OUTPUT_FOLDER = 'output'
|
||||
|
||||
MP4DECRYPT_BINARY = 'mp4decrypt'
|
||||
MKVMERGE_BINARY = 'mkvmerge'
|
||||
MP4DUMP_BINARY = 'mp4dump'
|
||||
FFMPEG_BINARY = 'avconv'
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
MP4DECRYPT_BINARY = 'mp4decrypt.exe'
|
||||
MKVMERGE_BINARY = 'mkvmerge.exe'
|
||||
MP4DUMP_BINARY = 'mp4dump.exe'
|
||||
#FFMPEG_BINARY = 'avconv.exe'
|
||||
FFMPEG_BINARY = 'ffmpeg.exe'
|
||||
|
||||
MP4DECRYPT_BINARY_PATH = os.path.join(BINARIES_FOLDER, MP4DECRYPT_BINARY)
|
||||
MKVMERGE_BINARY_PATH = os.path.join(BINARIES_FOLDER, MKVMERGE_BINARY)
|
||||
MP4DUMP_BINARY_PATH = os.path.join(BINARIES_FOLDER, MP4DUMP_BINARY)
|
||||
FFMPEG_BINARY_PATH = os.path.join(BINARIES_FOLDER, FFMPEG_BINARY)
|
||||
|
||||
MP4DUMP_REGEX = re.compile(b"sample info count = (\d+)")
|
||||
|
||||
BASE_TRACK_FILENAME = '{filename}_{track_type}_{track_no}_'
|
||||
BASE_TRACK_FILENAME_AUDIO = '{filename}_{track_type}_{track_no}_{language}_'
|
||||
|
||||
|
||||
ENCRYPTED_FILENAME = os.path.join(TEMP_FOLDER, BASE_TRACK_FILENAME + 'encrypted.mp4')
|
||||
#DECRYPTED_FILENAME = os.path.join(TEMP_FOLDER, BASE_TRACK_FILENAME + 'decrypted.mp4')
|
||||
|
||||
DECRYPTED_FILENAME = os.path.join(TEMP_FOLDER, BASE_TRACK_FILENAME + 'decrypted.mp4')
|
||||
|
||||
ENCRYPTED_FILENAME_AUDIO = os.path.join(TEMP_FOLDER, BASE_TRACK_FILENAME_AUDIO + 'encrypted.mp4')
|
||||
DECRYPTED_FILENAME_AUDIO = os.path.join(TEMP_FOLDER, BASE_TRACK_FILENAME_AUDIO + 'decrypted.mp4')
|
||||
|
||||
SUBTITLES_FILENAME = os.path.join(TEMP_FOLDER, '{filename}_subtitles_{language_code}_{id}.{subtitle_type}')
|
||||
MUXED_FILENAME = os.path.join(OUTPUT_FOLDER, '{filename}.mkv')
|
||||
|
||||
|
||||
class WvDownloaderConfig(object):
|
||||
def __init__(self, client, filename, subtitle_format, print_info, skip_cleanup, dont_mux, subs_only, license, quality, profile, gf=None):
|
||||
self.client = client
|
||||
self.filename = filename
|
||||
self.subtitle_format = subtitle_format
|
||||
self.print_info = print_info
|
||||
self.skip_cleanup = skip_cleanup
|
||||
self.dont_mux = dont_mux
|
||||
self.subs_only = subs_only
|
||||
self.license = license
|
||||
self.quality = quality
|
||||
self.profile = profile
|
||||
self.gf = gf
|
||||
Reference in New Issue
Block a user