From 38251e33302ab309e81d69727c32c6cab0609f50 Mon Sep 17 00:00:00 2001 From: Root BA Date: Mon, 25 Apr 2022 12:23:49 +0000 Subject: [PATCH 1/7] Fix soutce video file --- sample-code/start_custom_hls.py | 2 +- sample-code/start_custom_mp4.py | 2 +- sample-code/start_custom_with_dict.py | 2 +- sample-code/start_custom_with_json.py | 2 +- sample-code/start_with_callback.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sample-code/start_custom_hls.py b/sample-code/start_custom_hls.py index 3d9728d..0c5411d 100644 --- a/sample-code/start_custom_hls.py +++ b/sample-code/start_custom_hls.py @@ -36,7 +36,7 @@ FORMAT.destination = DESTINATION #replace with a link to your input video -params.source = 'https://qencode.com/static/1.mp4' +params.source = 'https://nyc3.digitaloceanspaces.com/qencode/bbb_30s.mp4' params.format = [FORMAT] diff --git a/sample-code/start_custom_mp4.py b/sample-code/start_custom_mp4.py index 9541e4c..3dd94e4 100644 --- a/sample-code/start_custom_mp4.py +++ b/sample-code/start_custom_mp4.py @@ -29,7 +29,7 @@ FORMAT.destination = DESTINATION #replace with a link to your input video -params.source = 'https://qencode.com/static/1.mp4' +params.source = 'https://nyc3.digitaloceanspaces.com/qencode/bbb_30s.mp4' params.format = [FORMAT] diff --git a/sample-code/start_custom_with_dict.py b/sample-code/start_custom_with_dict.py index 32601c3..c5b38ff 100644 --- a/sample-code/start_custom_with_dict.py +++ b/sample-code/start_custom_with_dict.py @@ -14,7 +14,7 @@ API_KEY = 'your-api-qencode-key' #replace with a link to your input video -source_url = "https://qencode.com/static/1.mp4" +source_url = "https://nyc3.digitaloceanspaces.com/qencode/bbb_30s.mp4" format_240 = dict( output="mp4", diff --git a/sample-code/start_custom_with_json.py b/sample-code/start_custom_with_json.py index 11d8f01..5101cae 100644 --- a/sample-code/start_custom_with_json.py +++ b/sample-code/start_custom_with_json.py @@ -14,7 +14,7 @@ params = """ {"query": { - "source": "https://qencode.com/static/1.mp4", + "source": "https://nyc3.digitaloceanspaces.com/qencode/bbb_30s.mp4", "format": [ { "output": "mp4", diff --git a/sample-code/start_with_callback.py b/sample-code/start_with_callback.py index 35e3cfc..072d09d 100644 --- a/sample-code/start_with_callback.py +++ b/sample-code/start_with_callback.py @@ -14,7 +14,7 @@ params = """ {"query": { - "source": "https://qencode.com/static/1.mp4", + "source": "https://nyc3.digitaloceanspaces.com/qencode/bbb_30s.mp4", "format": [ { "output": "mp4", From c32b9164bdf9ac5c6dd4d5667c0aa2c2c62ae045 Mon Sep 17 00:00:00 2001 From: Qencode Date: Tue, 6 Sep 2022 17:09:09 +0300 Subject: [PATCH 2/7] support for /v1/tasks method --- qencode/client.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/qencode/client.py b/qencode/client.py index 69d5869..af487ae 100644 --- a/qencode/client.py +++ b/qencode/client.py @@ -49,3 +49,17 @@ def get_metadata(self, uri): metadata = Metadata(self.access_token, self.connect) video_info = metadata.get(uri) return video_info + + def tasks(self, start_date='', end_date='', project_name='', status='', limit=10, offset=0, sort='date:desc'): + params = { + 'access_token': self.access_token, + 'start_date': start_date, + 'end_date': end_date, + 'project_name': project_name, + 'status': status, + 'limit': limit, + 'offset': offset, + 'sort': sort + } + response = self.connect.request('tasks', params) + return response From 9233d7f21c96346c7a22d00166873ee9f7858df2 Mon Sep 17 00:00:00 2001 From: Root BA Date: Wed, 26 Nov 2025 13:57:21 +0200 Subject: [PATCH 3/7] add support BuyDRM.v4 --- qencode/drm/buydrm_v4.py | 75 ++++++++++++++++++++ qencode/drm/const.py | 29 ++++++++ qencode/drm/keys/qencode-public_cert.pem | 31 +++++++++ sample-code/drm/buydrm/buydrm_v4.py | 87 ++++++++++++++++++++++++ sample-code/drm/buydrm/query.json | 18 +++++ 5 files changed, 240 insertions(+) create mode 100644 qencode/drm/buydrm_v4.py create mode 100644 qencode/drm/const.py create mode 100644 qencode/drm/keys/qencode-public_cert.pem create mode 100644 sample-code/drm/buydrm/buydrm_v4.py create mode 100644 sample-code/drm/buydrm/query.json diff --git a/qencode/drm/buydrm_v4.py b/qencode/drm/buydrm_v4.py new file mode 100644 index 0000000..d14f227 --- /dev/null +++ b/qencode/drm/buydrm_v4.py @@ -0,0 +1,75 @@ +# for python2.7 +import os +from lxml import etree +from signxml import XMLSigner, XMLVerifier +# +import const + +def create_cpix_user_request( + key_ids, media_id, + content_id, commonEncryptionScheme, + private_key, public_cert, delivery_public_cert=None, + use_playready=False, use_widevine=False, use_fairplay=False, + nsmap=const.NSMAP + ): + if delivery_public_cert is None: + delivery_public_cert_path = (os.path.dirname(__file__) + '/keys/qencode-public_cert.pem') + delivery_public_cert = open(delivery_public_cert_path, 'rb').read() + + root = etree.Element('{%s}CPIX' % nsmap['cpix'], + name=media_id, contentId=content_id, nsmap=nsmap) + root.set('{%s}schemaLocation' % nsmap['xsi'], + 'urn:dashif:org:cpix cpix.xsd') + + delivery_data_list = etree.SubElement(root, '{%s}DeliveryDataList' % nsmap['cpix']) + delivery_data = etree.SubElement(delivery_data_list,'{%s}DeliveryData' % nsmap['cpix']) + delivery_key = etree.SubElement(delivery_data, '{%s}DeliveryKey' % nsmap['cpix']) + + x509_data = etree.SubElement(delivery_key, '{%s}X509Data' % nsmap['ds']) + x509_cert = etree.SubElement(x509_data, '{%s}X509Certificate' % nsmap['ds']) + x509_cert.text = delivery_public_cert.replace( + '-----BEGIN CERTIFICATE-----', '' + ).replace( + '-----END CERTIFICATE-----', '' + ).replace('\n', '') + + content_key_list = etree.SubElement(root, + '{%s}ContentKeyList' % nsmap['cpix']) + content_key_usage_list = etree.SubElement(root, + '{%s}ContentKeyUsageRuleList' % nsmap['cpix']) + drm_system_list = etree.SubElement(root, + '{%s}DRMSystemList' % nsmap['cpix']) + + for data in key_ids: + if commonEncryptionScheme == 'default': + etree.SubElement(content_key_list, '{%s}ContentKey' % nsmap['cpix'], kid=data['kid']) + else: + etree.SubElement(content_key_list, '{%s}ContentKey' % nsmap['cpix'], kid=data['kid'], + commonEncryptionScheme=commonEncryptionScheme) + + if use_playready: + etree.SubElement(drm_system_list, '{%s}DRMSystem' % nsmap['cpix'], kid=data['kid'], + systemId=const.SYSTEM_ID_PLAYREADY) + + if use_widevine: + etree.SubElement(drm_system_list, '{%s}DRMSystem' % nsmap['cpix'], kid=data['kid'], + systemId=const.SYSTEM_ID_WIDEVINE) + + if use_fairplay: + etree.SubElement(drm_system_list, '{%s}DRMSystem' % nsmap['cpix'], kid=data['kid'], + systemId=const.SYSTEM_ID_FAIRPLAY) + + etree.SubElement(content_key_usage_list, + '{%s}ContentKeyUsageRule' % nsmap['cpix'], + kid=data['kid'], + intendedTrackType=data['track_type']) + + signed_root = XMLSigner( + c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315', + signature_algorithm='rsa-sha256', + digest_algorithm='sha512' + ).sign(root, key=private_key, cert=public_cert) + + xml_text = etree.tostring(signed_root, encoding='utf-8') + + return xml_text diff --git a/qencode/drm/const.py b/qencode/drm/const.py new file mode 100644 index 0000000..a126053 --- /dev/null +++ b/qencode/drm/const.py @@ -0,0 +1,29 @@ +# +CPIX_API_URL_V4 = 'https://cpix-integration.keyos.com/api/v4/getKeys' +LA_URL_V4 = 'https://widevine.keyos.com/api/v4/getLicense' + +NSMAP = { + 'cpix': 'urn:dashif:org:cpix', + 'enc': 'http://www.w3.org/2001/04/xmlenc#', + 'pskc': 'urn:ietf:params:xml:ns:keyprov:pskc', + 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', + 'ds': 'http://www.w3.org/2000/09/xmldsig#' +} + +SYSTEM_ID_PLAYREADY = '9a04f079-9840-4286-ab92-e65be0885f95' +SYSTEM_ID_WIDEVINE = 'edef8ba9-79d6-4ace-a3c8-27dcd51d21ed' +SYSTEM_ID_FAIRPLAY = '94ce86fb-07ff-4f43-adb8-93d2fa968ca2' + +TRACK_TYPES = [ + ('SD', 0), # (<720p) + ('HD', 720), # (720p) + ('FULLHD', 1080), # (1080p) + ('2KUHD', 1440), # (1440p) + ('4KUHD', 2160), # (2160p) + ('8KUHD', 4320), # (4320p) + ('16KUHD', 8640), # (8640p) +] + +DEFAULT_TRACK_TYPE = 'SD' +AUDIO_TRACK_TYPE = 'AUDIO' # SD - old + diff --git a/qencode/drm/keys/qencode-public_cert.pem b/qencode/drm/keys/qencode-public_cert.pem new file mode 100644 index 0000000..4986de0 --- /dev/null +++ b/qencode/drm/keys/qencode-public_cert.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFTzCCAzegAwIBAgIUWkfteBfJlQamCwn6AyXY1X4mhTkwDQYJKoZIhvcNAQEL +BQAwNzELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB1FlbmNvZGUxFjAUBgNVBAMMDSou +cWVuY29kZS5jb20wHhcNMjUxMTA2MTc1NjQ0WhcNMjkxMTA2MTc1NjQ0WjA3MQsw +CQYDVQQGEwJVUzEQMA4GA1UECgwHUWVuY29kZTEWMBQGA1UEAwwNKi5xZW5jb2Rl +LmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMHuQqzvoFV7mFsE +SNOefpN1hyXQvnQ5qRLt/hyX+0CM4wx+HgCylijPuL4tujcxGdC1QhrD3+ntv9yk +e8Et5PxXIsoxanra6LXXP9UAKljmw2ktfEIBPCyblzCgETeedGH/QqDy+ys8cgQ8 +D/y4wDCwJdWU888qFQaFXgY432tzVLpz4pWqJwrV/EacVk5gVSmT/uFIal+D4rDg +OFi9IAsc9FKVwKnZQoSWsVLoqal/pMVqW6jPMqqQhMClBFVvONBBIC4ztEckLhXu +prkToKHE/sffPvOPoZSzlnz5wTD0WZXjj+RbcFU1mPjZ8lwUBgCn/VTD8SOO5YZx +lpagF+3Qf/nCKjYBupBxvvI9Dfdoai6HvP0clT/99igG9yl0v5viynAW+fNuUhg5 +Krc6rZfw8AgQeJPHgUYElA+rcrhbVamVKLhYhCBAoai12sxMa+EvCwW++I+d/SA0 +9Gft2E1fEWgCyFgzXRKJPPI9CONYe5hL4FitERZhe0oktwbnvT0x9YRU1wb56sG/ +3ykTSaVToGROnjn8reuLRqWgR06cuJjtHqtB+yBbRc5jv1OPL97atzyqePnX5wIN +hiMcse+4IRGR/jtD45AAXQkjwT/MaHjcHpcjaHWUO5w3nV6QIxam7dmXadNgoEbg +a33gqLmj29/RX++nvFOq+9HFD18JAgMBAAGjUzBRMB0GA1UdDgQWBBT8cKty8KH6 +TxiSTunAN7RbyQVkNjAfBgNVHSMEGDAWgBT8cKty8KH6TxiSTunAN7RbyQVkNjAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQA5XSQVhTpuSdEAhG+e +/tQlbS0yZOuPZPMApzyy8+axTZjitpRZPULvoRKnpldmTDFVRqPwBFXhKWBLufxo +Ghr6GKAG8IZgcr66dx8XquFHIQLooyHpFAx9t7J/SfdWKoKpIugaSDH6Jg4WywoZ +lKjmql9f5QMTRNgOVhDDlMv+vDMQ+30idUODhMwZ36NOlcb0XCeOxE5iGUH3CE4b +XG6RRS2u3rpRH36PL4KFd28uMy73zsxp5ocSrSpDAIibec3qpYSIRVeiGxBZf9q1 +B1q7Ta1BIWzWr7GtkIXxxxLwfnki/QiiEhCX4fUNAXeBH9/XHbDbBUa/qCLsCJ+m +NqLe6AJqea7xo/+KIy+qM3gmn+RFRLhf1NbxX5J+MKIyBdPqtdAvFCcRsbaRSgcP +nIEUYrHJhH+2d3bTKb7Tb9jx1PAXbWXpt4QpnmhQUHtmXGVgoQFIUgNUEsGBnN3n +DMO8n8pav3s1Rwd9c34DKpgC3PkFxosKKNeH2W/r5nmSBNVGAeIPM/81B7VkjmpE +Lrc9yc0V4risNsekYIJpMY+k6nrSklofyyCDalS263toxPrYNrxHjd899zsekBvn +2X+1hvxRQ5BxFgmIBgTS6/vhs/vFDE80cX0te852cV7toNDRcX/72x/lO18FpjUt +cLKMgddCWrOK6HjBetTK1CkKHA== +-----END CERTIFICATE----- diff --git a/sample-code/drm/buydrm/buydrm_v4.py b/sample-code/drm/buydrm/buydrm_v4.py new file mode 100644 index 0000000..bb71def --- /dev/null +++ b/sample-code/drm/buydrm/buydrm_v4.py @@ -0,0 +1,87 @@ +import base64 +import json +import uuid +import time +import qencode +from qencode.drm.buydrm_v4 import create_cpix_user_request +from qencode import QencodeClientException, QencodeTaskException + +# replace with your API KEY (can be found in your Project settings on Qencode portal) +# https://portal.qencode.com/ +API_KEY = 'your-api-qencode-key' +# specify path to your BuyDRM certificate files, +# for example create dir keys/ and put keys into +enduser_pvk, enduser_pub = [ + open('keys/' + p, 'r').read() for p in ( + 'user-private_key.pem', + 'user-public_cert.pem', +# 'qencode-public_cert.pem', + ) +] +# Qencode query template for job with {cpix_request} +query_json = 'query.json' +# correspond to stream resolution in query.json +key_ids = [ + { 'kid': str(uuid.uuid4()), 'track_type': 'SD' }, + { 'kid': str(uuid.uuid4()), 'track_type': 'HD' } +] +# need for BuyDRM +media_id = 'my asset' +content_id = 'group21' +common_encryption = 'cenc' +# unified with the new BuyDRM API params +drm_list = { + 'PR': True, # use_playready + 'WV': True, # use_widevine + 'FP': False # use_fairplay +} +# for create log files: state-.json, xml +debug = True + +def start_encode(): + cpix_request = create_cpix_user_request( + key_ids, media_id, + content_id, common_encryption, + enduser_pvk, enduser_pub, #delivery_public_cert=None, # if None - get from SDK, else - set public Qenocde certificate + use_playready=drm_list['PR'], use_widevine=drm_list['WV'], use_fairplay=drm_list['FP'] + ) + + client = qencode.client(API_KEY) + if client.error: + raise QencodeClientException(client.message) + + print('The client created. Expire date: %s' % client.expire) + + task = client.create_task() + + if task.error: + raise QencodeTaskException(task.message) + + template = open(query_json, 'r').read() + + query = template.replace('{cpix_request}', base64.b64encode(cpix_request)) + + task.custom_start(query) + + if task.error: + raise QencodeTaskException(task.message) + task_token = task.task_token + print('Start encode. Task: %s' % task_token) + + while True: + status = task.status() + print('Job %s status: \n %s' % (task_token, json.dumps(status, indent=2, sort_keys=True))) + if status['error'] or status['status'] == 'completed': + break + time.sleep(5) + status = task.extend_status() + print('Job %s finished with status "%s" and error: %s' % \ + (task_token, status['status'], status['error']) + ) + if debug: + open('job-cpix_request-%s.xml' % task_token, 'w').write(cpix_request) + open('job-query-%s.json' % task_token, 'w').write(query) + open('job-result-%s.json' % task_token, 'w').write(json.dumps(status, indent=2, sort_keys=True)) + +if __name__ == '__main__': + start_encode() diff --git a/sample-code/drm/buydrm/query.json b/sample-code/drm/buydrm/query.json new file mode 100644 index 0000000..f4edb15 --- /dev/null +++ b/sample-code/drm/buydrm/query.json @@ -0,0 +1,18 @@ +{ + "query": { + "encoder_version": "2", + "format": [ + { + "output": "advanced_dash", + "stream": [ +{ "video_codec": "libx264", "height": 360, "audio_bitrate": 128, "keyframe": 25, "bitrate": 950 }, +{ "video_codec": "libx264", "height": 720, "audio_bitrate": 128, "keyframe": 25, "bitrate": 2000 } + ], + "buydrm_drm": { + "request": "{cpix_request}" + } + } + ], + "source": "https://nyc3.s3.qencode.com/qencode/bbb_30s.mp4" + } +} From 8f9b400d897a73233e4f13bced51338792e5cdd6 Mon Sep 17 00:00:00 2001 From: Root BA Date: Wed, 26 Nov 2025 15:51:41 +0200 Subject: [PATCH 4/7] change buydrm_v4.py --- sample-code/drm/buydrm/buydrm_v4.py | 1 - sample-code/drm/buydrm/query.json | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/sample-code/drm/buydrm/buydrm_v4.py b/sample-code/drm/buydrm/buydrm_v4.py index bb71def..b5d3014 100644 --- a/sample-code/drm/buydrm/buydrm_v4.py +++ b/sample-code/drm/buydrm/buydrm_v4.py @@ -23,7 +23,6 @@ # correspond to stream resolution in query.json key_ids = [ { 'kid': str(uuid.uuid4()), 'track_type': 'SD' }, - { 'kid': str(uuid.uuid4()), 'track_type': 'HD' } ] # need for BuyDRM media_id = 'my asset' diff --git a/sample-code/drm/buydrm/query.json b/sample-code/drm/buydrm/query.json index f4edb15..944e057 100644 --- a/sample-code/drm/buydrm/query.json +++ b/sample-code/drm/buydrm/query.json @@ -5,8 +5,7 @@ { "output": "advanced_dash", "stream": [ -{ "video_codec": "libx264", "height": 360, "audio_bitrate": 128, "keyframe": 25, "bitrate": 950 }, -{ "video_codec": "libx264", "height": 720, "audio_bitrate": 128, "keyframe": 25, "bitrate": 2000 } +{ "video_codec": "libx264", "height": 360, "audio_bitrate": 128, "keyframe": 25, "bitrate": 950 } ], "buydrm_drm": { "request": "{cpix_request}" From 605262a7480e5eb08dbc9f42add08c952dd1947e Mon Sep 17 00:00:00 2001 From: Root BA Date: Mon, 1 Dec 2025 17:31:54 +0200 Subject: [PATCH 5/7] change create_cpix_user_request from cert to cert_path --- qencode/drm/buydrm_v4.py | 6 ++++-- sample-code/drm/buydrm/buydrm_v4.py | 11 +++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/qencode/drm/buydrm_v4.py b/qencode/drm/buydrm_v4.py index d14f227..1b376d9 100644 --- a/qencode/drm/buydrm_v4.py +++ b/qencode/drm/buydrm_v4.py @@ -8,11 +8,13 @@ def create_cpix_user_request( key_ids, media_id, content_id, commonEncryptionScheme, - private_key, public_cert, delivery_public_cert=None, + private_key_path, public_cert_path, delivery_public_cert_path=None, use_playready=False, use_widevine=False, use_fairplay=False, nsmap=const.NSMAP ): - if delivery_public_cert is None: + private_key = open(private_key_path, 'r').read() + public_cert = open(public_cert_path, 'r').read() + if delivery_public_cert_path is None: delivery_public_cert_path = (os.path.dirname(__file__) + '/keys/qencode-public_cert.pem') delivery_public_cert = open(delivery_public_cert_path, 'rb').read() diff --git a/sample-code/drm/buydrm/buydrm_v4.py b/sample-code/drm/buydrm/buydrm_v4.py index b5d3014..d895e46 100644 --- a/sample-code/drm/buydrm/buydrm_v4.py +++ b/sample-code/drm/buydrm/buydrm_v4.py @@ -11,13 +11,8 @@ API_KEY = 'your-api-qencode-key' # specify path to your BuyDRM certificate files, # for example create dir keys/ and put keys into -enduser_pvk, enduser_pub = [ - open('keys/' + p, 'r').read() for p in ( - 'user-private_key.pem', - 'user-public_cert.pem', -# 'qencode-public_cert.pem', - ) -] +enduser_pvk_path = 'keys/user-private_key.pem' +enduser_pub_path = 'keys/user-public_cert.pem' # Qencode query template for job with {cpix_request} query_json = 'query.json' # correspond to stream resolution in query.json @@ -41,7 +36,7 @@ def start_encode(): cpix_request = create_cpix_user_request( key_ids, media_id, content_id, common_encryption, - enduser_pvk, enduser_pub, #delivery_public_cert=None, # if None - get from SDK, else - set public Qenocde certificate + enduser_pvk_path, enduser_pub_path, #delivery_public_cert_path=None, # if None - get from SDK, else - set public Qenocde certificate use_playready=drm_list['PR'], use_widevine=drm_list['WV'], use_fairplay=drm_list['FP'] ) From b16bc0a9dbf095d8cecfdc886eb5a340cb33ab37 Mon Sep 17 00:00:00 2001 From: Root BA Date: Mon, 1 Dec 2025 17:34:43 +0200 Subject: [PATCH 6/7] rename params in demo --- sample-code/drm/buydrm/buydrm_v4.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sample-code/drm/buydrm/buydrm_v4.py b/sample-code/drm/buydrm/buydrm_v4.py index d895e46..e1f3874 100644 --- a/sample-code/drm/buydrm/buydrm_v4.py +++ b/sample-code/drm/buydrm/buydrm_v4.py @@ -11,8 +11,8 @@ API_KEY = 'your-api-qencode-key' # specify path to your BuyDRM certificate files, # for example create dir keys/ and put keys into -enduser_pvk_path = 'keys/user-private_key.pem' -enduser_pub_path = 'keys/user-public_cert.pem' +USER_PVT_KEY_PATH = 'keys/user-private_key.pem' +USER_PUB_CERT_PATH = 'keys/user-public_cert.pem' # Qencode query template for job with {cpix_request} query_json = 'query.json' # correspond to stream resolution in query.json @@ -36,7 +36,7 @@ def start_encode(): cpix_request = create_cpix_user_request( key_ids, media_id, content_id, common_encryption, - enduser_pvk_path, enduser_pub_path, #delivery_public_cert_path=None, # if None - get from SDK, else - set public Qenocde certificate + USER_PVT_KEY_PATH, USER_PUB_CERT_PATH, #delivery_public_cert_path=None, # if None - get from SDK, else - set public Qenocde certificate use_playready=drm_list['PR'], use_widevine=drm_list['WV'], use_fairplay=drm_list['FP'] ) From 5aea7994983a0e07c1ade2ed73338c924278df7b Mon Sep 17 00:00:00 2001 From: Nikita Date: Tue, 2 Dec 2025 22:25:04 +0200 Subject: [PATCH 7/7] Updates for BuyDRM v4 API --- sample-code/drm/buydrm/buydrm_v4.py | 81 ------------------- .../drm/buydrm/buydrm_widevine_playready.py | 80 +++++++++--------- sample-code/drm/buydrm/query.json | 12 ++- 3 files changed, 45 insertions(+), 128 deletions(-) delete mode 100644 sample-code/drm/buydrm/buydrm_v4.py diff --git a/sample-code/drm/buydrm/buydrm_v4.py b/sample-code/drm/buydrm/buydrm_v4.py deleted file mode 100644 index e1f3874..0000000 --- a/sample-code/drm/buydrm/buydrm_v4.py +++ /dev/null @@ -1,81 +0,0 @@ -import base64 -import json -import uuid -import time -import qencode -from qencode.drm.buydrm_v4 import create_cpix_user_request -from qencode import QencodeClientException, QencodeTaskException - -# replace with your API KEY (can be found in your Project settings on Qencode portal) -# https://portal.qencode.com/ -API_KEY = 'your-api-qencode-key' -# specify path to your BuyDRM certificate files, -# for example create dir keys/ and put keys into -USER_PVT_KEY_PATH = 'keys/user-private_key.pem' -USER_PUB_CERT_PATH = 'keys/user-public_cert.pem' -# Qencode query template for job with {cpix_request} -query_json = 'query.json' -# correspond to stream resolution in query.json -key_ids = [ - { 'kid': str(uuid.uuid4()), 'track_type': 'SD' }, -] -# need for BuyDRM -media_id = 'my asset' -content_id = 'group21' -common_encryption = 'cenc' -# unified with the new BuyDRM API params -drm_list = { - 'PR': True, # use_playready - 'WV': True, # use_widevine - 'FP': False # use_fairplay -} -# for create log files: state-.json, xml -debug = True - -def start_encode(): - cpix_request = create_cpix_user_request( - key_ids, media_id, - content_id, common_encryption, - USER_PVT_KEY_PATH, USER_PUB_CERT_PATH, #delivery_public_cert_path=None, # if None - get from SDK, else - set public Qenocde certificate - use_playready=drm_list['PR'], use_widevine=drm_list['WV'], use_fairplay=drm_list['FP'] - ) - - client = qencode.client(API_KEY) - if client.error: - raise QencodeClientException(client.message) - - print('The client created. Expire date: %s' % client.expire) - - task = client.create_task() - - if task.error: - raise QencodeTaskException(task.message) - - template = open(query_json, 'r').read() - - query = template.replace('{cpix_request}', base64.b64encode(cpix_request)) - - task.custom_start(query) - - if task.error: - raise QencodeTaskException(task.message) - task_token = task.task_token - print('Start encode. Task: %s' % task_token) - - while True: - status = task.status() - print('Job %s status: \n %s' % (task_token, json.dumps(status, indent=2, sort_keys=True))) - if status['error'] or status['status'] == 'completed': - break - time.sleep(5) - status = task.extend_status() - print('Job %s finished with status "%s" and error: %s' % \ - (task_token, status['status'], status['error']) - ) - if debug: - open('job-cpix_request-%s.xml' % task_token, 'w').write(cpix_request) - open('job-query-%s.json' % task_token, 'w').write(query) - open('job-result-%s.json' % task_token, 'w').write(json.dumps(status, indent=2, sort_keys=True)) - -if __name__ == '__main__': - start_encode() diff --git a/sample-code/drm/buydrm/buydrm_widevine_playready.py b/sample-code/drm/buydrm/buydrm_widevine_playready.py index 36ef352..eb24523 100644 --- a/sample-code/drm/buydrm/buydrm_widevine_playready.py +++ b/sample-code/drm/buydrm/buydrm_widevine_playready.py @@ -1,87 +1,79 @@ +import base64 +import json import uuid import time -import json -import base64 import qencode -from qencode.drm.buydrm import create_cpix_user_request +from qencode.drm.buydrm_v4 import create_cpix_user_request from qencode import QencodeClientException, QencodeTaskException # replace with your API KEY (can be found in your Project settings on Qencode portal) +# https://portal.qencode.com/ API_KEY = 'your-api-qencode-key' -# specify path to your BuyDRM certificate files -USER_PVT_KEY_PATH = './keys/user_private_key.pem' -USER_PUB_CERT_PATH = './keys/user_public_cert.pem' +# specify path to your BuyDRM certificate files, +# for example create dir keys/ and put keys into +USER_PVT_KEY_PATH = 'keys/user-private_key.pem' +USER_PUB_CERT_PATH = 'keys/user-public_cert.pem' +# Qencode query template for job with {cpix_request} +query_json = 'query.json' +# correspond to stream resolution in query.json key_ids = [ { 'kid': str(uuid.uuid4()), 'track_type': 'SD' }, { 'kid': str(uuid.uuid4()), 'track_type': 'HD' } ] -media_id = 'my first stream' - - -QUERY = """ -{ - "query": { - "format": [ - { - "output": "advanced_dash", - "stream": [ - { - "video_codec": "libx264", - "height": 360, - "audio_bitrate": 128, - "keyframe": 25, - "bitrate": 950 - } - ], - "buydrm_drm": { - "request": "{cpix_request}" - } - } - ], - "source": "https://nyc3.s3.qencode.com/qencode/bbb_30s.mp4" - } +media_id = 'my first stream' +content_id = 'my movies group' +common_encryption = 'cenc' + +# unified with the new BuyDRM API params +drm_list = { + 'PR': True, # use_playready + 'WV': True, # use_widevine + 'FP': False # use_fairplay } -""" - def start_encode(): - # this creates signed request to BuyDRM cpix_request = create_cpix_user_request( - key_ids, media_id, USER_PVT_KEY_PATH, USER_PUB_CERT_PATH, - use_playready=True, use_widevine=True + key_ids, media_id, + content_id, common_encryption, + USER_PVT_KEY_PATH, USER_PUB_CERT_PATH, + use_playready=drm_list['PR'], use_widevine=drm_list['WV'], use_fairplay=drm_list['FP'] ) client = qencode.client(API_KEY) if client.error: raise QencodeClientException(client.message) - print 'The client created. Expire date: %s' % client.expire + print('The client created. Expire date: %s' % client.expire) task = client.create_task() if task.error: raise QencodeTaskException(task.message) - query = QUERY.replace('{cpix_request}', base64.b64encode(cpix_request)) + template = open(query_json, 'r').read() + + query = template.replace('{cpix_request}', base64.b64encode(cpix_request)) task.custom_start(query) if task.error: raise QencodeTaskException(task.message) - - print 'Start encode. Task: %s' % task.task_token + task_token = task.task_token + print('Start encode. Task: %s' % task_token) while True: status = task.status() - # print status - print json.dumps(status, indent=2, sort_keys=True) + print('Job %s status: \n %s' % (task_token, json.dumps(status, indent=2, sort_keys=True))) if status['error'] or status['status'] == 'completed': break time.sleep(5) - - + status = task.extend_status() + print('Job %s finished with status "%s" and error: %s' % \ + (task_token, status['status'], status['error']) + ) + if __name__ == '__main__': start_encode() diff --git a/sample-code/drm/buydrm/query.json b/sample-code/drm/buydrm/query.json index 944e057..476879a 100644 --- a/sample-code/drm/buydrm/query.json +++ b/sample-code/drm/buydrm/query.json @@ -1,11 +1,17 @@ { "query": { - "encoder_version": "2", + "encoder_version": "2", "format": [ { "output": "advanced_dash", "stream": [ -{ "video_codec": "libx264", "height": 360, "audio_bitrate": 128, "keyframe": 25, "bitrate": 950 } + { + "video_codec": "libx264", + "height": 360, + "audio_bitrate": 128, + "keyframe": 25, + "bitrate": 950 + } ], "buydrm_drm": { "request": "{cpix_request}" @@ -14,4 +20,4 @@ ], "source": "https://nyc3.s3.qencode.com/qencode/bbb_30s.mp4" } -} +} \ No newline at end of file