Skip to content

Commit 822b4f1

Browse files
Merge remote-tracking branch 'origin/master'
# Conflicts: # .gitignore # qencode/__init__.py # setup.py
2 parents 291eefe + 2d74a05 commit 822b4f1

File tree

8 files changed

+237
-4
lines changed

8 files changed

+237
-4
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@
88
.cache/
99
.vscode/
1010
.idea/
11+
.eggs/
1112
build/
12-
dist/
13+
dist/
14+
qencode.egg-info/
15+
sample-code/drm/buydrm/keys/user_private_key.pem
16+
sample-code/drm/buydrm/keys/user_public_cert.pem

qencode/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def x265_video_codec():
2929

3030
from exeptions import QencodeClientException, QencodeTaskException
3131

32+
__version__ = "1.0.3"
3233
from tools import generate_aws_signed_url, fps_drm, cenc_drm
3334

3435
__version__ = "1.0.1"

qencode/drm/__init__.py

Whitespace-only changes.

qencode/drm/buydrm.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from lxml import etree
2+
from signxml import XMLSigner
3+
import os
4+
5+
NSMAP = {
6+
'cpix': 'urn:dashif:org:cpix',
7+
'xenc': 'http://www.w3.org/2001/04/xmlenc#',
8+
'pskc': 'urn:ietf:params:xml:ns:keyprov:pskc',
9+
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
10+
'ds': 'http://www.w3.org/2000/09/xmldsig#'
11+
}
12+
13+
SYSTEM_ID_PLAYREADY = '9a04f079-9840-4286-ab92-e65be0885f95'
14+
SYSTEM_ID_WIDEVINE = 'edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'
15+
SYSTEM_ID_FAIRPLAY = '94ce86fb-07ff-4f43-adb8-93d2fa968ca2'
16+
17+
def create_cpix_user_request(
18+
key_ids, media_id, user_private_key_path, user_public_cert_path,
19+
use_playready=False, use_widevine=False, use_fairplay=False,
20+
nsmap=None
21+
):
22+
document_public_cert_path = os.path.dirname(__file__) + '/keys/buydrm_qencode_public_cert.pem'
23+
"""Creates CPIX request XML signed end user
24+
25+
Arguments:
26+
key_ids {list} -- List of Key IDs and corresponding track quality types. The list is of
27+
the following format - { 'kid': [string in GUID/UUID format], 'track_type': [string track type]}.
28+
29+
media_id {string} -- Some random name for your asset which is shown in KeyOS console and reports.
30+
31+
nsmap {list} -- List of namespaces.
32+
33+
Returns:
34+
string -- CPIX request XML signed end user
35+
"""
36+
nsmap = nsmap if nsmap is not None else NSMAP
37+
38+
# Own private key and end user's private key used to sign the document
39+
end_user_private_key = open(user_private_key_path, 'rb').read()
40+
41+
# Own public certificate and end user's public certificate to include into the CPIX request
42+
end_user_public_cert = open(user_public_cert_path, 'rb').read()
43+
document_public_cert = open(document_public_cert_path, 'rb').read()
44+
45+
root = etree.Element('{%s}CPIX' % nsmap['cpix'], name=media_id, nsmap=nsmap)
46+
root.set('{%s}schemaLocation' % nsmap['xsi'], 'urn:dashif:org:cpix cpix.xsd')
47+
48+
# Delivery data list
49+
delivery_data_list = etree.SubElement(root, '{%s}DeliveryDataList' % nsmap['cpix'])
50+
delivery_data = etree.SubElement(delivery_data_list, '{%s}DeliveryData' % nsmap['cpix'])
51+
delivery_key = etree.SubElement(delivery_data, '{%s}DeliveryKey' % nsmap['cpix'])
52+
53+
# The public certificate of a partner. This certificate's public key will be used
54+
# to encrypt Document Key which will later be used to encrypt Contnet Keys.
55+
x509_data = etree.SubElement(delivery_key, '{%s}X509Data' % nsmap['ds'])
56+
x509_cert = etree.SubElement(x509_data, '{%s}X509Certificate' % nsmap['ds'])
57+
x509_cert.text = document_public_cert.replace('-----BEGIN CERTIFICATE-----', '').replace('-----END CERTIFICATE-----', '').replace('\n', '')
58+
59+
# Content key list
60+
content_key_list = etree.SubElement(root, '{%s}ContentKeyList' % nsmap['cpix'])
61+
62+
# Content key usage rules
63+
content_key_usage_list = etree.SubElement(
64+
root, '{%s}ContentKeyUsageRuleList' % nsmap['cpix']
65+
)
66+
67+
# DRM systems list
68+
drm_system_list = etree.SubElement(root, '{%s}DRMSystemList' % nsmap['cpix'])
69+
70+
for data in key_ids:
71+
etree.SubElement(
72+
content_key_list, '{%s}ContentKey' % nsmap['cpix'], kid=data['kid']
73+
)
74+
if use_playready:
75+
etree.SubElement(
76+
drm_system_list, '{%s}DRMSystem' % nsmap['cpix'], kid=data['kid'],
77+
systemId=SYSTEM_ID_PLAYREADY
78+
)
79+
if use_widevine:
80+
etree.SubElement(
81+
drm_system_list, '{%s}DRMSystem' % nsmap['cpix'], kid=data['kid'],
82+
systemId=SYSTEM_ID_WIDEVINE
83+
)
84+
if use_fairplay:
85+
etree.SubElement(
86+
drm_system_list, '{%s}DRMSystem' % nsmap['cpix'], kid=data['kid'],
87+
systemId=SYSTEM_ID_FAIRPLAY
88+
)
89+
90+
etree.SubElement(
91+
content_key_usage_list, '{%s}ContentKeyUsageRule' % nsmap['cpix'],
92+
kid=data['kid'], intendedTrackType=data['track_type']
93+
)
94+
95+
# Signing document with end user's data
96+
end_user_signed_root = XMLSigner(
97+
c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315',
98+
signature_algorithm='rsa-sha256', digest_algorithm='sha512'
99+
).sign(root, key=end_user_private_key, cert=end_user_public_cert)
100+
x509_sign_cert = end_user_signed_root.xpath(
101+
'//ds:X509Certificate', namespaces=nsmap
102+
)[1]
103+
x509_sign_cert.text = x509_sign_cert.text.replace('\n', '')
104+
#
105+
return etree.tostring(end_user_signed_root).decode('utf-8')
106+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFUzCCAzugAwIBAgIJALYz0aDtoodZMA0GCSqGSIb3DQEBBQUAMEAxCzAJBgNV
3+
BAYTAlVTMRAwDgYDVQQKDAdRZW5jb2RlMR8wHQYDVQQDDBZRZW5jb2RlIEtleU9T
4+
IENQSVggQVBJMB4XDTIwMTIyMTE5MDAyNFoXDTI0MTIyMDE5MDAyNFowQDELMAkG
5+
A1UEBhMCVVMxEDAOBgNVBAoMB1FlbmNvZGUxHzAdBgNVBAMMFlFlbmNvZGUgS2V5
6+
T1MgQ1BJWCBBUEkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQD2D5aP
7+
XSFJC3ErYVBNS54x8AnQ4997NY2Md3QtvWy1UT/8wzs87EJ+3uwoFgfj8Tpl3hZD
8+
amvW9BH6SlSX2uPu5uuPovxnjMVQTg6RAdfQf+dOff4dCX9ubUD1iWen17DODUoY
9+
BeZWky6zGTy5n6o9RGPJsE6Er/mIFWUJdKPrXMpnqJ+hfC2fWurMj8DZ2U+TiuBF
10+
PK8cPkYzF8+L6BH7vkYK8Tvcoy42WPBMwO/fmFT5nTFCtmMROt/x/DaIdafF4tnx
11+
X+liJ3q7rABZfHY66zXv4owQRrpZXupGLVS7vQtoTIHgA6wc/MWVFEyXZN66D0RI
12+
5/uxRD7/GLYAbGcdCVuyJ8i/eXQ3B7OWcEiOxz3TfojlbQcaO8vb9hym5/B4H8Mh
13+
XC99ddB1hSgNX/bWiA6kfCYtw8jR4g86vyDcU/K2e2IcliZsd6ehFWEGluSLqHJq
14+
2g4d49gP72aFO7x5k95id271MMxhF8CW2VjARSqgHG6G+8cWHqaLnB2JJJE1606P
15+
irDrEAcsSuYLsse393pfZMhOkKdq6FXRK/E5nnbh2VKeWP491lJaXakhMVqcjVQ5
16+
NRqHc8iXCS7qDUYDxJubwC00b2qVj+HqN+k3gx9kyU1If2S0dXOEyJEBmCScKw6k
17+
glN+a6N5Wv723TKiMjUq+RgcZBbkCvbuoHdN7QIDAQABo1AwTjAdBgNVHQ4EFgQU
18+
n6hFowsmBOhl3vm3lYj6Uu0baDIwHwYDVR0jBBgwFoAUn6hFowsmBOhl3vm3lYj6
19+
Uu0baDIwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAgEAvVIt4QBYfCi9
20+
Ae1SVHGFN6dVSlnSQwC4pBTt8ucrsDs8kE93xyVNMz4OTDZSP0x/AxwV1LFtZOdZ
21+
zauPGygYMf4puVwkns1DqiUkMaGR8PMPQR+SDPeM6qwIhuYLsAyzhKdRx/n+cKmJ
22+
/lzFi20ZJv5y6ozFwUMIskfWnE2RvfV0bbyraR+LQgOA+fy2Vd0IeTOTgXtuNIi/
23+
q/VMWH2TGjh1vZQmuO1tg8dGU57XFjeK3ZBtcetNwXz6iXzZ5lprGvPG3tr4QRf5
24+
XiUoS+mJ5mJBBOMWlgGFELA7xAB3cYj00bTE5iE1YZCeE2WHyIiDw6OrILffeU9H
25+
5+bo7R7z5uwn+h5Z+XYJHxS2wg3v7wpoRMFeQXFHlJhdVXlI9cpf0NgNPecnSrs5
26+
F2hZt1LmbrrFXVzE3BKVvxUILBW+sN9+79fcXAHewL7yPRy6G307Sy187N2RLyJT
27+
mQdOlEaawR2Cgv4QI7NmAuY0Jy0uYqDCP591ZKg+j1lsWi48Eirf3/hMr/TXNDIk
28+
A1MhY+SPW7mGbz6hVQkkJZNYyisAZcelLWyX5X0RwFo1rMQL/BWemdKU3kV4bk8p
29+
cWmsYLPBMN69npjGWLLzovznOWQ4sVAOBt790pgrHDC+giPeqzx2tmmm4jP14IRj
30+
xSPcwUZxCh50J//DsQ2PpVQxrHPnL94=
31+
-----END CERTIFICATE-----

qencode/httptools.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import urllib
33
import urllib2
44
from urlparse import urljoin
5+
import ssl
56

67
class Http(object):
78
def __init__(self, version, url, debug=False):
@@ -15,8 +16,9 @@ def _call_server(self, url, post_data):
1516
return json.dumps(response)
1617
data = urllib.urlencode(post_data)
1718
request = urllib2.Request(url, data)
19+
context = ssl._create_unverified_context()
1820
try:
19-
res = urllib2.urlopen(request)
21+
res = urllib2.urlopen(request, context=context)
2022
except urllib2.HTTPError as e:
2123
headers = e.headers if self._debug else ''
2224
response = dict(error=True, message='HTTPError: {0} {1} {2}'.format(e.code, e.reason, headers))
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import uuid
2+
import time
3+
import json
4+
import base64
5+
import qencode
6+
from qencode.drm.buydrm import create_cpix_user_request
7+
from qencode import QencodeClientException, QencodeTaskException
8+
9+
# replace with your API KEY (can be found in your Project settings on Qencode portal)
10+
API_KEY = 'your-api-qencode-key'
11+
12+
# specify path to your BuyDRM certificate files
13+
USER_PVT_KEY_PATH = './keys/user_private_key.pem'
14+
USER_PUB_CERT_PATH = './keys/user_public_cert.pem'
15+
16+
key_ids = [
17+
{ 'kid': str(uuid.uuid4()), 'track_type': 'SD' },
18+
{ 'kid': str(uuid.uuid4()), 'track_type': 'HD' }
19+
]
20+
media_id = 'my first stream'
21+
22+
23+
24+
QUERY = """
25+
{
26+
"query": {
27+
"format": [
28+
{
29+
"output": "advanced_dash",
30+
"stream": [
31+
{
32+
"video_codec": "libx264",
33+
"height": 360,
34+
"audio_bitrate": 128,
35+
"keyframe": 25,
36+
"bitrate": 950
37+
}
38+
],
39+
"buydrm_drm": {
40+
"request": "{cpix_request}"
41+
}
42+
}
43+
],
44+
"source": "https://nyc3.s3.qencode.com/qencode/bbb_30s.mp4"
45+
}
46+
}
47+
"""
48+
49+
50+
def start_encode():
51+
# this creates signed request to BuyDRM
52+
cpix_request = create_cpix_user_request(
53+
key_ids, media_id, USER_PVT_KEY_PATH, USER_PUB_CERT_PATH,
54+
use_playready=True, use_widevine=True
55+
)
56+
57+
client = qencode.client(API_KEY)
58+
if client.error:
59+
raise QencodeClientException(client.message)
60+
61+
print 'The client created. Expire date: %s' % client.expire
62+
63+
task = client.create_task()
64+
65+
if task.error:
66+
raise QencodeTaskException(task.message)
67+
68+
query = QUERY.replace('{cpix_request}', base64.b64encode(cpix_request))
69+
70+
task.custom_start(query)
71+
72+
if task.error:
73+
raise QencodeTaskException(task.message)
74+
75+
print 'Start encode. Task: %s' % task.task_token
76+
77+
while True:
78+
status = task.status()
79+
# print status
80+
print json.dumps(status, indent=2, sort_keys=True)
81+
if status['error'] or status['status'] == 'completed':
82+
break
83+
time.sleep(5)
84+
85+
86+
if __name__ == '__main__':
87+
start_encode()

setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
setup(
1313
name='qencode',
14-
version='1.0.1',
14+
version='1.0.3',
1515
description="Client library for main features and functionality of Qencode for Python v2.x.",
1616
long_description=long_description,
1717
long_description_content_type='text/markdown',
@@ -29,5 +29,7 @@
2929

3030
],
3131
keywords='qencode, qencode.com, cloud.qencode.com',
32-
packages=['qencode']
32+
packages=['qencode', 'qencode.drm'],
33+
package_data={'qencode.drm': ['keys/buydrm_qencode_public_cert.pem']},
34+
include_package_data=True
3335
)

0 commit comments

Comments
 (0)