Skip to content

Commit 3731cea

Browse files
added AWS Signed Url, Fairplay DRM, Widevine DRM, Playready DRM
1 parent 66c4674 commit 3731cea

File tree

11 files changed

+356
-43
lines changed

11 files changed

+356
-43
lines changed

.gitignore

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

88
.cache/
99
.vscode/
10-
.idea/
10+
.idea/
11+
build/
12+
dist/

README.md

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,59 @@ sudo pip install qencode
2121
````
2222
import qencode
2323
24+
API_KEY = 'your-api-qencode-key'
25+
26+
QUERY = """
27+
{"query": {
28+
"source": "https://nyc3.s3.qencode.com/qencode/samples/1080-sample.mov",
29+
"format": [
30+
{
31+
"output": "mp4",
32+
"size": "320x240",
33+
"video_codec": "libx264"
34+
}
35+
]
36+
}
37+
}
38+
"""
39+
2440
client = qencode.client(API_KEY)
2541
client.create()
2642
2743
task = client.create_task()
28-
task.start(TRANSCODING_PROFILEID, VIDEO_URL)
44+
task.custom_start(QUERY)
45+
````
2946

47+
````
48+
#getting status
49+
50+
status = task.status()
51+
or
52+
status = task.extend_status()
53+
````
54+
55+
````
56+
#getting video metadata
3057
31-
#getting video metadata:
3258
metadata = client.get_metadata(VIDEO_URL)
59+
````
60+
61+
**DRM** <sub><sup>*[details](https://docs.qencode.com/api-reference/transcoding/#start_encode2___query__attributes--format__attributes--fps_drm__attributes)*</sup></sub>
62+
63+
````
64+
# getting Fairplay DRM encryption parameters
65+
encryption_parameters, payload = cenc_drm(DRM_USERNAME, DRW_PASSWORD)
66+
67+
# getting Widevine and Playready DRM encryption parameters
68+
encryption_parameters, payload = fps_drm(DRM_USERNAME, DRW_PASSWORD)
69+
70+
````
71+
72+
73+
**AWS Signed URL**
74+
75+
````
76+
source_url = generate_aws_signed_url(region, bucket, object_key, access_key, secret_key, expiration)
3377
3478
````
3579

qencode/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ def x265_video_codec():
2929

3030
from exeptions import QencodeClientException, QencodeTaskException
3131

32-
__version__ = "1.0"
32+
from tools import generate_aws_signed_url, fps_drm, cenc_drm
33+
34+
__version__ = "1.0.1"
3335
__status__ = "Production/Stable"
3436
__author__ = "Qencode"
3537

qencode/const.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@
1717
ERROR_BAD_PROFILE = 11
1818
ERROR_PROFILE_NOT_FOUND = 12
1919
ERROR_BAD_TOKENS = 13
20-
ERROR_FIELD_REQUIRED = 14
20+
ERROR_FIELD_REQUIRED = 14
21+
22+
FPS_DRM_KEYGENERATOR_URI_TEMPLATE = 'https://cpix.ezdrm.com/KeyGenerator/cpix.aspx?k=%s&u=%s&p=%s&c=resourcename&m=2'
23+
CENC_DRM_KEYGENERATOR_URI_TEMPLATE = 'https://cpix.ezdrm.com/KeyGenerator/cpix.aspx?k=%s&u=%s&p=%s&c=resourcename&m=1'
24+
DRM_KEY_URL_TEMPLATE = 'skd://fps.ezdrm.com/;%s'

qencode/task.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ def custom_start(self, data, **kwargs):
6464
def status(self):
6565
return self._status()
6666

67-
def main_status(self):
68-
return self._status2()
67+
def extend_status(self):
68+
return self._extend_status()
6969

7070
def progress_changed(self, callback, *args, **kwargs):
7171
while 1:
@@ -188,4 +188,16 @@ def _status(self):
188188
self.status_url = status['status_url']
189189

190190
return status
191+
192+
def _extend_status(self):
193+
response = self.connect.post(self.main_status_url, dict(task_tokens=self.task_token))
194+
status = None
195+
196+
if response['error'] == ERROR_BAD_TOKENS:
197+
raise ValueError('Bad token: ' + str(self.task_token))
198+
199+
if 'statuses' in response and self.task_token in response['statuses']:
200+
status = response['statuses'][self.task_token]
201+
202+
return status
191203

qencode/tools.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#
2+
from const import *
3+
import datetime
4+
import hashlib
5+
import hmac
6+
import requests
7+
from requests.utils import quote
8+
import xml.etree.cElementTree as et
9+
import uuid
10+
11+
def generate_aws_signed_url(region, bucket, object_key, access_key, secret_key, expiration, endpoint=None):
12+
13+
# request elements
14+
http_method = 'GET'
15+
endpoint = endpoint if endpoint else 's3.amazonaws.com'
16+
host = bucket + '.' + endpoint
17+
endpoint = 'https://' + host
18+
19+
# hashing methods
20+
def hash(key, msg):
21+
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
22+
23+
# region is a wildcard value that takes the place of the AWS region value
24+
# as COS doen't use regions like AWS, this parameter can accept any string
25+
def createSignatureKey(key, datestamp, region, service):
26+
keyDate = hash(('AWS4' + key).encode('utf-8'), datestamp)
27+
keyRegion = hash(keyDate, region)
28+
keyService = hash(keyRegion, service)
29+
keySigning = hash(keyService, 'aws4_request')
30+
return keySigning
31+
32+
# assemble the standardized request
33+
time = datetime.datetime.utcnow()
34+
timestamp = time.strftime('%Y%m%dT%H%M%SZ')
35+
datestamp = time.strftime('%Y%m%d')
36+
37+
standardized_querystring = ('X-Amz-Algorithm=AWS4-HMAC-SHA256' +
38+
'&X-Amz-Credential=' + access_key + '/' + datestamp + '/' + region + '/s3/aws4_request' +
39+
'&X-Amz-Date=' + timestamp +
40+
'&X-Amz-Expires=' + str(expiration) +
41+
'&X-Amz-SignedHeaders=host')
42+
standardized_querystring_url_encoded = quote(standardized_querystring, safe='&=')
43+
44+
standardized_resource = '/' + object_key
45+
standardized_resource_url_encoded = quote(standardized_resource, safe='&')
46+
47+
payload_hash = 'UNSIGNED-PAYLOAD'
48+
standardized_headers = 'host:' + host
49+
signed_headers = 'host'
50+
51+
standardized_request = (http_method + '\n' +
52+
standardized_resource + '\n' +
53+
standardized_querystring_url_encoded + '\n' +
54+
standardized_headers + '\n' +
55+
'\n' +
56+
signed_headers + '\n' +
57+
payload_hash).encode('utf-8')
58+
59+
# assemble string-to-sign
60+
hashing_algorithm = 'AWS4-HMAC-SHA256'
61+
credential_scope = datestamp + '/' + region + '/' + 's3' + '/' + 'aws4_request'
62+
sts = (hashing_algorithm + '\n' +
63+
timestamp + '\n' +
64+
credential_scope + '\n' +
65+
hashlib.sha256(standardized_request).hexdigest())
66+
67+
# generate the signature
68+
signature_key = createSignatureKey(secret_key, datestamp, region, 's3')
69+
signature = hmac.new(signature_key,
70+
(sts).encode('utf-8'),
71+
hashlib.sha256).hexdigest()
72+
73+
# create and send the request
74+
# the 'requests' package autmatically adds the required 'host' header
75+
request_url = (endpoint + '/' +
76+
object_key + '?' +
77+
standardized_querystring_url_encoded +
78+
'&X-Amz-Signature=' +
79+
signature)
80+
81+
def hex_hash(key, msg):
82+
return hmac.new(b'key', msg.encode('utf-8'), hashlib.sha256).hexdigest()
83+
84+
def createHexSignatureKey(key, datestamp, region, service):
85+
keyDate = hex_hash(('AWS4' + key).encode('utf-8'), datestamp)
86+
keyRegion = hex_hash(keyDate, region)
87+
keyService = hex_hash(keyRegion, service)
88+
keySigning = hex_hash(keyService, 'aws4_request')
89+
return keySigning
90+
91+
signature_key_hex = createHexSignatureKey(secret_key, datestamp, region, 's3')
92+
93+
# print(request_url)
94+
return request_url
95+
96+
97+
def fps_drm(username, password, uid=None):
98+
asset_id = uid if uid else uuid.uuid4()
99+
url = FPS_DRM_KEYGENERATOR_URI_TEMPLATE % (asset_id, username, password)
100+
response = requests.post(url, {})
101+
tree = et.ElementTree(et.fromstring(response.content))
102+
root = tree.getroot()
103+
kid = root[0][0].get('kid')
104+
iv = root[0][0].get('explicitIV')
105+
key = root[0][0][0][0][0].text
106+
key_hex = key.decode('base64').encode('hex')
107+
iv_hex = iv.decode('base64').encode('hex')
108+
key_url = DRM_KEY_URL_TEMPLATE % kid
109+
payload = dict(AssetID=asset_id)
110+
data = dict(key=key_hex, iv=iv_hex, key_url=key_url)
111+
return data, payload
112+
113+
114+
def cenc_drm(username, password, uid=None):
115+
asset_id = uid if uid else uuid.uuid4()
116+
url = CENC_DRM_KEYGENERATOR_URI_TEMPLATE % (asset_id, username, password)
117+
response = requests.post(url, {})
118+
tree = et.ElementTree(et.fromstring(response.content))
119+
root = tree.getroot()
120+
key_id = root[0][0].get('kid')
121+
key = root[0][0][0][0][0].text
122+
pssh = root[1][0][0].text
123+
key_id_hex = key_id.replace('-', '')
124+
key_hex = key.decode('base64').encode('hex')
125+
key_url = DRM_KEY_URL_TEMPLATE % key_id
126+
payload = dict(AssetID=asset_id)
127+
data = dict(key=key_hex, key_id=key_id_hex, pssh=pssh, key_url=key_url)
128+
return data, payload

requirements.txt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
json
2-
urllib
3-
urllib2
4-
urlparse
5-
tuspy
1+
tuspy
2+
requests
3+
4+
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
4+
import sys
5+
import os.path
6+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
7+
import qencode
8+
import time
9+
import json
10+
from qencode import QencodeClientException, QencodeTaskException
11+
from qencode import generate_aws_signed_url
12+
13+
# replace with your API KEY (can be found in your Project settings on Qencode portal)
14+
API_KEY = 'your-api-qencode-key'
15+
16+
# request elements
17+
region = 'us-east-2'
18+
bucket = 'your-bucket-name'
19+
object_key = 'path'
20+
expiration = 86400 # time in seconds
21+
access_key = 'your-AWS-access-key'
22+
secret_key = 'your-AWS-secret-key'
23+
24+
# generate AWS signed url
25+
source_url = generate_aws_signed_url(region, bucket, object_key, access_key, secret_key, expiration)
26+
print(source_url)
27+
28+
format_240 = dict(
29+
output="mp4",
30+
size="320x240",
31+
video_codec="libx264"
32+
)
33+
34+
format_720 = dict(
35+
output="mp4",
36+
size="1280x720",
37+
video_codec="libx264"
38+
)
39+
40+
format = [format_240, format_720]
41+
42+
query = dict(
43+
source=source_url,
44+
format=format
45+
)
46+
47+
params = dict(query=query)
48+
49+
def start_encode():
50+
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+
60+
client = qencode.client(API_KEY)
61+
if client.error:
62+
raise QencodeClientException(client.message)
63+
64+
print('The client created. Expire date: {0}'.format(client.expire))
65+
66+
task = client.create_task()
67+
68+
if task.error:
69+
raise QencodeTaskException(task.message)
70+
71+
task.custom_start(params)
72+
73+
if task.error:
74+
raise QencodeTaskException(task.message)
75+
76+
print('Start encode. Task: {0}'.format(task.task_token))
77+
78+
line = "-"*80
79+
while True:
80+
print(line)
81+
status = task.status()
82+
# print status
83+
print(json.dumps(status, indent=2, sort_keys=True))
84+
if status['error'] or status['status'] == 'completed':
85+
break
86+
time.sleep(5)
87+
88+
if __name__ == '__main__':
89+
start_encode()

0 commit comments

Comments
 (0)