DoveRunner DRM
Introduction
Qencode offers built-in integration with DoveRunner DRM. Through this integration, Qencode can encrypt video streams and deliver them with DRM protections that DoveRunner will license at playback time.
Prerequisites
- Qencode
- Project API key - used to obtain a session token (/v1/access_token) and run jobs.
- DoveRunner
- Site ID, Site Key, Access Key
- KMS Token - used to authenticate CPIX API requests. You can find it in the DoveRunner Console under Multi-DRM > DRM Setting.
- Content ID (CID / content_id) (the identifier you'll use when generating license tokens)
1Get encryption material from DoveRunner (CPIX)
DoveRunner provides a CPIX API for exchanging encryption keys with third-party encoders and transcoders. You send a CPIX XML request with your content ID and the DRM system(s) you need, and DoveRunner returns the encryption keys.
The CPIX API endpoint is:
POST https://drm-kms.doverunner.com/v2/cpix/doverunner/getKey/{kms-token}CPIX request
The request body is an XML document specifying the content ID and the DRM system. For Widevine, use systemId edef8ba9-79d6-4ace-a3c8-27dcd51d21ed:
<?xml version="1.0" encoding="UTF-8"?>
<cpix:CPIX id="test-content-01"
xmlns:cpix="urn:dashif:org:cpix"
xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc"
xmlns:speke="urn:aws:amazon:com:speke">
<cpix:ContentKeyList>
<cpix:ContentKey kid="786d2d0a-9857-4ab4-8377-21a6cba1ca24"/>
</cpix:ContentKeyList>
<cpix:DRMSystemList>
<cpix:DRMSystem kid="786d2d0a-9857-4ab4-8377-21a6cba1ca24"
systemId="edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>
</cpix:DRMSystemList>
</cpix:CPIX>The kid attribute can be any UUID you generate. The id attribute on the root element is your content ID.
Sending a CPIX request to DoveRunner KMS:
curl -X POST \
"https://drm-kms.doverunner.com/v2/cpix/doverunner/getKey/YOUR_KMS_TOKEN" \
-H "Content-Type: application/xml" \
-d '<?xml version="1.0" encoding="UTF-8"?>
<cpix:CPIX id="test-content-01"
xmlns:cpix="urn:dashif:org:cpix"
xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc"
xmlns:speke="urn:aws:amazon:com:speke">
<cpix:ContentKeyList>
<cpix:ContentKey kid="786d2d0a-9857-4ab4-8377-21a6cba1ca24"/>
</cpix:ContentKeyList>
<cpix:DRMSystemList>
<cpix:DRMSystem kid="786d2d0a-9857-4ab4-8377-21a6cba1ca24"
systemId="edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>
</cpix:DRMSystemList>
</cpix:CPIX>'import uuid
import urllib.request
KMS_TOKEN = "YOUR_KMS_TOKEN"
CONTENT_ID = "test-content-01"
KID = str(uuid.uuid4())
body = f"""<?xml version="1.0" encoding="UTF-8"?>
<cpix:CPIX id="{CONTENT_ID}"
xmlns:cpix="urn:dashif:org:cpix"
xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc"
xmlns:speke="urn:aws:amazon:com:speke">
<cpix:ContentKeyList>
<cpix:ContentKey kid="{KID}"/>
</cpix:ContentKeyList>
<cpix:DRMSystemList>
<cpix:DRMSystem kid="{KID}"
systemId="edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"/>
</cpix:DRMSystemList>
</cpix:CPIX>"""
req = urllib.request.Request(
f"https://drm-kms.doverunner.com/v2/cpix/doverunner/getKey/{KMS_TOKEN}",
data=body.encode("utf-8"),
headers={"Content-Type": "application/xml"},
method="POST",
)
with urllib.request.urlopen(req) as resp:
print(resp.read().decode("utf-8"))CPIX response
DoveRunner returns a CPIX XML response containing the encryption material:
<?xml version="1.0" ?>
<cpix:CPIX id="test-content-01"
xmlns:cpix="urn:dashif:org:cpix"
xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc"
xmlns:speke="urn:aws:amazon:com:speke">
<cpix:ContentKeyList>
<cpix:ContentKey commonEncryptionScheme="cenc"
explicitIV="NHc5dmE5aHF0Z2NsZXVlMQ=="
kid="e2b281ba-fba5-8760-cfa6-5ae6f3ddd630">
<cpix:Data>
<pskc:Secret>
<pskc:PlainValue>xgjCQm5OdSC4tQ8Lgl9uUg==</pskc:PlainValue>
</pskc:Secret>
</cpix:Data>
</cpix:ContentKey>
</cpix:ContentKeyList>
<cpix:DRMSystemList>
<cpix:DRMSystem kid="e2b281ba-fba5-8760-cfa6-5ae6f3ddd630"
systemId="edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<cpix:PSSH>AAAAUXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADEIARIQ4rKBuvulh2DPplrm893WMBoKZG92ZXJ1bm5lciIPdGVzdC1jb250ZW50LTAx</cpix:PSSH>
</cpix:DRMSystem>
</cpix:DRMSystemList>
</cpix:CPIX>Extract values for Qencode
From the CPIX response, extract and convert the values needed for the Qencode cenc_drm object:
- key_id: the
kidattribute fromContentKey, with dashes removed (e.g.e2b281bafba58760cfa65ae6f3ddd630) - key: the
pskc:PlainValuecontent, decoded from base64 to hex (e.g.c608c2426e4e7520b8b50f0b825f6e52) - iv: the
explicitIVattribute, decoded from base64 to hex (e.g.34773976613968717467636c65756531) - pssh: the
cpix:PSSHcontent, used as-is in base64
2Encrypt during transcoding in Qencode (cenc_drm)
In your Qencode job JSON, you'll:
- choose a packaging output such as 'advanced_dash'
- add a cenc_drm block inside the same format item
- provide key_id, key, iv, pssh, and la_url
Example Qencode job (DASH + Widevine CENC)
{
"query": {
"source": "https://example.com/source.mp4",
"format": [
{
"output": "advanced_dash",
"destination": {
"url": "s3://YOUR_BUCKET/path/to/output/",
"key": "YOUR_STORAGE_KEY",
"secret": "YOUR_STORAGE_SECRET"
},
"stream": [
{
"video_codec": "libx264",
"audio_codec": "aac",
"width": 1280,
"height": 720,
"bitrate": 3000,
"audio_bitrate": 128
}
],
"cenc_drm": {
"key_id": "e2b281bafba58760cfa65ae6f3ddd630",
"key": "c608c2426e4e7520b8b50f0b825f6e52",
"iv": "34773976613968717467636c65756531",
"pssh": "AAAAUXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADEIARIQ4rKBuvulh2DPplrm893WMBoKZG92ZXJ1bm5lciIPdGVzdC1jb250ZW50LTAx",
"la_url": "https://drm-license.doverunner.com/ri/licenseManager.do"
}
}
]
}
}
cenc_drm object params description
- key_id: the kid from the CPIX response, hex without dashes.
- key: the content key from the CPIX response, decoded from base64 to hex.
- iv (optional): the explicitIV from the CPIX response, decoded from base64 to hex. Should be specified if present in the CPIX response.
- pssh: the Widevine PSSH from the CPIX response, base64-encoded.
- la_url (optional): license server URL. DoveRunner uses the same URL for both Widevine and PlayReady:
https://drm-license.doverunner.com/ri/licenseManager.do.
The full example Python script that performs CPIX key exchange and launches a Qencode encoding job with Widevine encryption is available here.
3Generate DoveRunner license token (server-side)
This part is not Qencode API — it's your application backend that prepares playback authorization for DoveRunner. The license token must be generated server-side and has a 10-minute validity window from the timestamp.
Never generate tokens in the browser/app — keep site_key and access_key private.
Python example
DoveRunner (formerly PallyCon) provides a Python SDK on PyPI. Install it with pip install python-pallycon.
Generating a DoveRunner license token:
from pallycon import PallyConClient
client = PallyConClient(
site_id="YOUR_SITE_ID",
site_key="YOUR_SITE_KEY",
access_key="YOUR_ACCESS_KEY",
drm_type=PallyConClient.DrmType.WIDEVINE.value,
user_id="your-user-id",
content_id="your-content-id",
license_rule={
"policy_version": 2,
"playback_policy": {
"persistent": False,
"license_duration": 3600
}
},
)
license_token = client.license_token
print(license_token)Recommended backend pattern
Create an endpoint like:
- GET /api/drm/token?content_id=...
It should:
- Authenticate the viewer (session/JWT).
- Check entitlement (is this user allowed this content?).
- Generate and return the license token string.
Because the token expires after 10 minutes, generate a fresh token for each playback session.
4Configure playback to request the license from DoveRunner
At playback time, your player needs:
- Manifest URL — your Qencode output .mpd
- DoveRunner license server URL —
https://drm-license.doverunner.com/ri/licenseManager.do - License token — from your backend (step 3)
The license token must be sent as a custom HTTP header with the license request:
- Header name:
pallycon-customdata-v2 - Header value: the license token (base64-encoded string)
Qencode Player example
Configuring Qencode Player for DoveRunner Widevine playback:
var params = {
licenseKey: "YOUR_QENCODE_PLAYER_LICENSE_KEY",
videoSources: {
src: "https://yourserver.com/content/playlist.mpd",
keySystems: {
"com.widevine.alpha": "https://drm-license.doverunner.com/ri/licenseManager.do"
},
emeHeaders: {
"pallycon-customdata-v2": "YOUR_LICENSE_TOKEN"
}
}
}
qPlayer('my_player', params, function() {
console.log("Player is loaded");
});Player flow
- Player loads the MPD manifest (encrypted segments).
- Player detects DRM init data (PSSH) in the manifest.
- Player sends a license request to the DoveRunner license server with the
pallycon-customdata-v2header. - DoveRunner validates the token and returns the license.
- Playback starts.