Skip to content

Commit eba9d6e

Browse files
committed
integrated BuyDRM support
1 parent a729f45 commit eba9d6e

File tree

5 files changed

+239
-1
lines changed

5 files changed

+239
-1
lines changed

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,10 @@
77

88
.cache/
99
.vscode/
10-
.idea/
10+
.idea/
11+
.eggs/
12+
build/
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/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-----
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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+
"""
52+
Create client object
53+
:param api_key: string. required
54+
:param api_url: string. not required
55+
:param api_version: int. not required. default 'v1'
56+
:return: task object
57+
"""
58+
59+
# this creates signed request to BuyDRM
60+
cpix_request = create_cpix_user_request(
61+
key_ids, media_id, USER_PVT_KEY_PATH, USER_PUB_CERT_PATH,
62+
use_playready=True, use_widevine=True
63+
)
64+
65+
client = qencode.client(API_KEY)
66+
if client.error:
67+
raise QencodeClientException(client.message)
68+
69+
print 'The client created. Expire date: %s' % client.expire
70+
71+
task = client.create_task()
72+
73+
if task.error:
74+
raise QencodeTaskException(task.message)
75+
76+
query = QUERY.replace('{cpix_request}', base64.b64encode(cpix_request))
77+
78+
task.custom_start(query)
79+
80+
if task.error:
81+
raise QencodeTaskException(task.message)
82+
83+
print 'Start encode. Task: %s' % task.task_token
84+
85+
while True:
86+
status = task.status()
87+
# print status
88+
print json.dumps(status, indent=2, sort_keys=True)
89+
if status['error'] or status['status'] == 'completed':
90+
break
91+
time.sleep(5)
92+
93+
94+
if __name__ == '__main__':
95+
start_encode()

0 commit comments

Comments
 (0)