New
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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