X-Git-Url: http://git.code-monkey.de/?a=blobdiff_plain;f=resources%2Flib%2FMSL.py;h=d0387972ceeab83ea253a55bf22fb89bc2c748eb;hb=e9b625adff4b36a0ed4ee47eb32d27d06ec85c06;hp=7f813d470b33acda3ea7c102751a9f4af4c44cde;hpb=da6c6467a3bbea8f796851b047df9c8f02399426;p=plugin.video.netflix.git diff --git a/resources/lib/MSL.py b/resources/lib/MSL.py index 7f813d4..d038797 100644 --- a/resources/lib/MSL.py +++ b/resources/lib/MSL.py @@ -1,3 +1,8 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Module: MSL +# Created on: 26.01.2017 + import base64 import gzip import json @@ -5,6 +10,8 @@ import os import pprint import random from StringIO import StringIO + +from datetime import datetime import requests import zlib @@ -35,7 +42,7 @@ class MSL: last_drm_context = '' last_playback_context = '' #esn = "NFCDCH-LX-CQE0NU6PA5714R25VPLXVU2A193T36" - esn = "WWW-BROWSE-D7GW1G4NPXGR1F0X1H3EQGY3V1F5WE" + #esn = "WWW-BROWSE-D7GW1G4NPXGR1F0X1H3EQGY3V1F5WE" #esn = "NFCDIE-02-DCH84Q2EK3N6VFVQJ0NLRQ27498N0F" current_message_id = 0 session = requests.session() @@ -46,13 +53,11 @@ class MSL: 'license': 'http://www.netflix.com/api/msl/NFCDCH-LX/cadmium/license' } - def __init__(self, email, password, kodi_helper): + def __init__(self, kodi_helper): """ The Constructor checks for already existing crypto Keys. If they exist it will load the existing keys """ - self.email = email - self.password = password self.kodi_helper = kodi_helper try: os.mkdir(self.kodi_helper.msl_data_path) @@ -66,13 +71,18 @@ class MSL: elif self.file_exists(self.kodi_helper.msl_data_path, 'rsa_key.bin'): self.kodi_helper.log(msg='RSA Keys do already exist load old ones') self.__load_rsa_keys() - self.__perform_key_handshake() + if self.kodi_helper.get_esn(): + self.__perform_key_handshake() else: self.kodi_helper.log(msg='Create new RSA Keys') # Create new Key Pair and save self.rsa_key = RSA.generate(2048) self.__save_rsa_keys() - self.__perform_key_handshake() + if self.kodi_helper.get_esn(): + self.__perform_key_handshake() + + def perform_key_handshake(self): + self.__perform_key_handshake() def load_manifest(self, viewable_id): """ @@ -149,11 +159,13 @@ class MSL: # Audio 'heaac-2-dash', - 'ddplus-2.0-dash', - 'ddplus-5.1-dash', - 'dfxp-ls-sdh', + + #subtiltes + #'dfxp-ls-sdh', 'simplesdh', - 'nflx-cmisc', + #'nflx-cmisc', + + #unkown 'BIF240', 'BIF320' ], @@ -173,6 +185,12 @@ class MSL: 'clientVersion': '4.0004.899.011', 'uiVersion': 'akira' } + + # Check if dolby sound is enabled and add to profles + if self.kodi_helper.get_dolby_setting(): + manifest_request_data['profiles'].append('ddplus-2.0-dash') + manifest_request_data['profiles'].append('ddplus-5.1-dash') + request_data = self.__generate_msl_request_data(manifest_request_data) resp = self.session.post(self.endpoints['manifest'], request_data) @@ -342,6 +360,21 @@ class MSL: segment_base = ET.SubElement(rep, 'SegmentBase', indexRange="0-"+str(init_length), indexRangeExact="true") ET.SubElement(segment_base, 'Initialization', range='0-'+str(init_length)) + # Multiple Adaption Sets for subtiles + for text_track in manifest['textTracks']: + if 'downloadables' not in text_track or text_track['downloadables'] is None: + continue + subtiles_adaption_set = ET.SubElement(period, 'AdaptationSet', + lang=text_track['bcp47'], + codecs='stpp', + contentType='text', + mimeType='application/ttml+xml') + for downloadable in text_track['downloadables']: + rep = ET.SubElement(subtiles_adaption_set, 'Representation', + nflxProfile=downloadable['contentProfile'] + ) + ET.SubElement(rep, 'BaseURL').text = self.__get_base_url(downloadable['urls']) + xml = ET.tostring(root, encoding='utf-8', method='xml') xml = xml.replace('\n', '').replace('\r', '') @@ -378,6 +411,7 @@ class MSL: } def __generate_msl_request_data(self, data): + self.__load_msl_data() header_encryption_envelope = self.__encrypt(self.__generate_msl_header()) header = { 'headerdata': base64.standard_b64encode(header_encryption_envelope), @@ -425,9 +459,10 @@ class MSL: :return: The base64 encoded JSON String of the header """ self.current_message_id = self.rndm.randint(0, pow(2, 52)) + esn = self.kodi_helper.get_esn() header_data = { - 'sender': self.esn, + 'sender': esn, 'handshake': is_handshake, 'nonreplayable': False, 'capabilities': { @@ -459,12 +494,13 @@ class MSL: if 'usertoken' in self.tokens: pass else: + account = self.kodi_helper.get_credentials() # Auth via email and password header_data['userauthdata'] = { 'scheme': 'EMAIL_PASSWORD', 'authdata': { - 'email': self.email, - 'password': self.password + 'email': account['email'], + 'password': account['password'] } } @@ -478,10 +514,12 @@ class MSL: :param plaintext: :return: Serialized JSON String of the encryption Envelope """ + esn = self.kodi_helper.get_esn() + iv = get_random_bytes(16) encryption_envelope = { 'ciphertext': '', - 'keyid': self.esn + '_' + str(self.sequence_number), + 'keyid': esn + '_' + str(self.sequence_number), 'sha256': 'AA==', 'iv': base64.standard_b64encode(iv) } @@ -506,11 +544,13 @@ class MSL: def __perform_key_handshake(self): header = self.__generate_msl_header(is_key_request=True, is_handshake=True, compressionalgo="", encrypt=False) + esn = self.kodi_helper.get_esn() + request = { 'entityauthdata': { 'scheme': 'NONE', 'authdata': { - 'identity': self.esn + 'identity': esn } }, 'headerdata': base64.standard_b64encode(header), @@ -551,10 +591,26 @@ class MSL: def __load_msl_data(self): msl_data = json.JSONDecoder().decode(self.load_file(self.kodi_helper.msl_data_path, 'msl_data.json')) + #Check expire date of the token + master_token = json.JSONDecoder().decode(base64.standard_b64decode(msl_data['tokens']['mastertoken']['tokendata'])) + valid_until = datetime.utcfromtimestamp(int(master_token['expiration'])) + present = datetime.now() + difference = valid_until - present + difference = difference.total_seconds() / 60 / 60 + # If token expires in less then 10 hours or is expires renew it + if difference < 10: + self.__load_rsa_keys() + self.__perform_key_handshake() + return + self.__set_master_token(msl_data['tokens']['mastertoken']) self.encryption_key = base64.standard_b64decode(msl_data['encryption_key']) self.sign_key = base64.standard_b64decode(msl_data['sign_key']) + + def save_msl_data(self): + self.__save_msl_data() + def __save_msl_data(self): """ Saves the keys and tokens in json file