+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Module: MSL
+# Created on: 26.01.2017
+
import base64
import gzip
import json
import pprint
import random
from StringIO import StringIO
+
+from datetime import datetime
import requests
import zlib
'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)
# 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'
],
'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)
pssh = manifest['psshb64'][0]
seconds = manifest['runtime']/1000
+ init_length = seconds / 2 * 12 + 20*1000
duration = "PT"+str(seconds)+".00S"
root = ET.Element('MPD')
for downloadable in video_track['downloadables']:
+ codec = 'h264'
+ if 'hevc' in downloadable['contentProfile']:
+ codec = 'hevc'
+
hdcp_versions = '0.0'
for hdcp in downloadable['hdcpVersions']:
if hdcp != 'none':
bandwidth=str(downloadable['bitrate']*1024),
hdcp=hdcp_versions,
nflxContentProfile=str(downloadable['contentProfile']),
- codecs='h264',
+ codecs=codec,
mimeType='video/mp4')
#BaseURL
ET.SubElement(rep, 'BaseURL').text = self.__get_base_url(downloadable['urls'])
# Init an Segment block
- segment_base = ET.SubElement(rep, 'SegmentBase', indexRange="0-60000", indexRangeExact="true")
- ET.SubElement(segment_base, 'Initialization', range='0-60000')
+ segment_base = ET.SubElement(rep, 'SegmentBase', indexRange="0-"+str(init_length), indexRangeExact="true")
+ ET.SubElement(segment_base, 'Initialization', range='0-'+str(init_length))
#BaseURL
ET.SubElement(rep, 'BaseURL').text = self.__get_base_url(downloadable['urls'])
# Index range
- segment_base = ET.SubElement(rep, 'SegmentBase', indexRange="0-60000", indexRangeExact="true")
- ET.SubElement(segment_base, 'Initialization', range='0-60000')
+ 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')
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']
}
}
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'])