Skip to content

Integration DRM (old)

Stefano Gottardo edited this page Oct 1, 2024 · 1 revision

This method can be used to configure a DRM, it is supported from Kodi v18 to v22. It requires to setting multiple properties, but compared to new configuration methods this one has some more limited functionality.

NOTE: Has been deprecated from Kodi v22, and planned to remove it in future versions of Kodi.


How to configure the DRM

Please note that despite a manifest can support more DRM's at same time, currently only a single DRM can be configured at a time, then the configuration must be set appropriately according to the use cases and type of operating system in use.

🔵 Advanced method (deprecated) [click to expand]

This method provides a more in-depth configuration of DRM, such as certificates, wrappers and more. Currently, multiple properties must be used according to the provider's needs.

inputstream.adaptive.license_type

[mandatory for DRM encrypted media]

Specify which DRM System must be used, set the value as shown in table on top.

listitem.setProperty('inputstream.adaptive.license_type', 'com.widevine.alpha')

inputstream.adaptive.license_key

[mandatory for some DRM encrypted media]

Can be used to specify how to handle the HTTP request / response of the DRM license.

NOTE: The PlayReady DRM do not support this property, because license-URL is part of the PlayReady WRM header (manifest data).

The license_key value must be constructed as a string with 4 template fields separated by 3 pipe | chars:

[license server URL] | [Headers] | [Post-Data] | [Response-Data] (without spaces)

NOTE: For HLS AES encrypted, the key URL is supported in the HLS playlist, and for this use case the license_key parameter works this way, taking into account only the first two template fields:

[parameters to append to the key URL] | [Key URL Headers] || (without spaces)

Examples:

# Constructed string example
license_headers = {
    'Content-Type': 'application/octet-stream',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0',
    'Origin': 'https://www.somesite.com'
}
from urllib.parse import urlencode
license_config = {
    'License server url': 'https://license.server.com/licenserequest',
    'Headers': urlencode(license_headers),
    'Post data': 'R{SSM}',
    'Response data': 'R'
}
listitem.setProperty('inputstream.adaptive.license_key', '|'.join(license_config.values()))

# Flat string example - common case
listitem.setProperty('inputstream.adaptive.license_key', 'https:\\www.licserver.com\data\example\||R{SSM}|R')

Template fields:

[license server URL]
The license server URL where make the license HTTP request. In the URL is possible optionally inject the DRM Challenge by using following placeholders:

  • B{SSM} Inject to the URL the DRM Challenge as base64, URL encoded
  • {HASH} Inject to the URL the DRM Challenge hashed as MD5

[Headers]
Allow you to add the HTTP headers for the license HTTP request, the paramters will be URL encoded automatically. Leave blank if not required. The headers must be follow this scheme: param1=value1&param2=value2

[Post-Data]
You can use this field to make an HTTP POST request instead of HTTP GET. Optionally value can be URL encoded to keep customised data intact.
Add at least one placeholder with a single prefix:

  • [R/b/B/D]{SSM} placeholder to transport the DRM Challenge
    With it you can use also other optional placeholders as:
  • [R/b/B]{SID} placeholder to transport the DRM Session ID
  • [R/H]{KID} placeholder to transport the DRM Key ID
  • [b/B]{PSSH} placeholder to transport the initial PSSH (Android only)

Most of time a license server accept (key request) challenge as raw data R{SSM} only.

Prefixes summary:
R - The data will be kept as is raw
b - The data will be base64 encoded
B - The data will be base64 encoded and URL encoded
D - The data will be decimal converted (each char converted as integer concatenated by comma)
H - The data will be hexadecimal converted (each character converted as hexadecimal and concatenated)

[Response-Data]
Specify how handle the data of HTTP license response:

  • Not specified, or, R if the response payload is in binary raw format
  • B payload is wrapped with base64
  • J[license tokens] payload is wrapped with JSON format. You must specify the license tokens names to allow inputstream.adaptive searches for the license key and optionally the HDCP limit. The tokens must be separated by ;. The first token must be for the key license, the second one, optional, for the HDCP limit. The HDCP limit is the result of resolution width multiplied for its height. For example to limit until to 720p: 1280x720 the result will be 921600.
  • JB[license tokens] same meaning of J[license tokens] but the value contained in the JSON path is encoded as base64.
  • BJ[license tokens] same meaning of J[license tokens] but the JSON is encoded as base64 and the value contained in the JSON path is raw.
  • HB if the payload is after two return chars \r\n\r\n in binary raw format. [removed on Kodi 22]

Most of time a license server return raw data R.

Default values when license_key property is not set:

  • Widevine: [license server URL] | Content-Type=application/octet-stream | R{SSM} | R
  • PlayReady: [license server URL] | Content-Type=text/xml&SOAPAction=http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense | R{SSM} | R
  • WisePlay: [license server URL] | Content-Type=application/json | R{SSM} | R

inputstream.adaptive.license_data

Allow to specify the PSSH initialization license data, that will be sent to the license server.

Can be used when a MPEG-DASH manifest do not specify the ContentProtection tag with schemeIdUri attribute to identify a content protection scheme for a specific DRM. The data provided must be encoded as base64.

The placeholder {KID} can be used to specify where to inject the default KID into the initialization data, it must also be base64 encoded with the data.
WARNING: Due to its limited and specific use case, moreover for a difficult future maintenances, the placeholder {KID} HAS BEEN REMOVED FROM Kodi v21. We discourage its use even on older versions of Kodi, as the KID obtained is specifically limited only to some DASH PlayReady ContentProtection tags.

listitem.setProperty('inputstream.adaptive.license_data', 'base64data')

WARNING: Currently there are undocumented mixed features of this property that some old add-on may use, these will be reviewed and changed in the future versions, so may break your addon. But genarally speaking the main purpose to set the initialization PSSH data will be kept.

inputstream.adaptive.server_certificate

Specifies a server certificate to be used to encrypt messages to the license server. Should be encoded as Base64.

listitem.setProperty('inputstream.adaptive.server_certificate', 'certificate_data')

inputstream.adaptive.license_flags

Can be used to force some behaviours.

Possible values are:

  • 'persistent_storage': To force the server certificate (available only on OS's other than Android)
  • 'force_secure_decoder': To force secure decoder

More than one flag can be set, you can separate by comma.

listitem.setProperty('inputstream.adaptive.license_flags', 'persistent_storage')

inputstream.adaptive.pre_init_data

Some VOD services works with licensed manifests. Compared to the standard manifests, this one also encloses the license data in the manifest.

Usually the request for these types of manifests are customised by the service, so a proxy managed by your add-on will have to act as an intermediary between ISAdaptive and your VOD service, in order to allow your add-on to generate an appropriate manifest request with the DRM data and then make it instead of ISAdaptive.

Enabling this feature you must provide a PSSH and KID. Both values must be provided encoded as Base 64.

listitem.setProperty('inputstream.adaptive.pre_init_data', 'PSSH encoded base 64|KID encoded base 64')

This will pre-initialize the DRM by opening a DRM session and will provide these data in the ISAdaptive HTTP manifest request:

  • challengeB64 - The Challenge (Key Request) value encoded as base 64, and URL encoded.
  • sessionId - The Session ID value in clear.

You can intercept the ISAdaptive manifest/license HTTP requests by implementing a proxy server in your add-on, as in the example How to provide custom manifest and license, will allow you to create the appropriate manifest data and manage the HTTP request through your add-on. When you will get the HTTP manifest response, you will also need to transfer the license data into the ISAdaptive license HTTP request.

WARNINGS:

  • Do not enable this feature without a proxy server in an add-on, otherwise the DRM data will not be managed.
  • This feature is currently intended for non-Android systems. Can works also on Android system, but to keep in mind that it is not possible to maintain the same DRM session, this mismatch will result in the license data may not working.
🔵 Advanced method (deprecated) - Full examples [click to expand]

DASH + WIDEVINE: Play encrypted video stream from add-on, a common DRM configuration.

listitem = xbmcgui.ListItem(path='https://www.videoservice.com/manifest.mpd', offscreen=True)

# These two lines are needed to prevent the HTTP HEAD request from Kodi core, used to determine the mimetype
listitem.setMimeType('application/dash+xml')
listitem.setContentLookup(False)

listitem.setProperty('inputstream', 'inputstream.adaptive')
listitem.setProperty('inputstream.adaptive.manifest_type', 'mpd') # Deprecated on Kodi 21, removed on Kodi 22
listitem.setProperty('inputstream.adaptive.license_type', 'com.widevine.alpha')

license_headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0',
    'Content-Type': 'application/octet-stream'
}
from urllib.parse import urlencode
license_config = { # for Python < v3.7 you should use OrderedDict to keep order
    'License server url': 'https://license.server.com/licenserequest/',
    'Headers': urlencode(license_headers),
    'Post data': 'R{SSM}',
    'Response data': 'R'
}
listitem.setProperty('inputstream.adaptive.license_key', '|'.join(license_config.values()))

xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=listitem)

DASH + WIDEVINE: Play encrypted video stream from playlist file STRM / M3U8, a common DRM configuration.

#KODIPROP:inputstream=inputstream.adaptive
#KODIPROP:inputstream.adaptive.manifest_type=mpd
#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha
#KODIPROP:inputstream.adaptive.license_key=https:\\www.licserver.com\data\example\|User-Agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F106.0.0.0+Safari%2F537.36|R{SSM}|R
#KODIPROP:mimetype=application/dash+xml
https://www.videoservice.com/manifest.mpd

SMOOTHSTREAMING + PLAYREADY: Play encrypted video stream from add-on, a common DRM configuration.

listitem = xbmcgui.ListItem(path='https://www.videoservice.com/tearsofsteel_4k.ism/manifest', offscreen=True)

# These two lines are needed to prevent the HTTP HEAD request from Kodi core, used to determine the mimetype
listitem.setMimeType('application/vnd.ms-sstr+xml')
listitem.setContentLookup(False)

listitem.setProperty('inputstream', 'inputstream.adaptive')
listitem.setProperty('inputstream.adaptive.manifest_type', 'ism') # Deprecated on Kodi 21, removed on Kodi 22
listitem.setProperty('inputstream.adaptive.license_type', 'com.microsoft.playready')
# inputstream.adaptive.license_key is not set, usually the playready data is embedded with manifest

xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=listitem)

HLS + AES-128: Play encrypted video stream from add-on.

listitem = xbmcgui.ListItem(path='https://www.videoservice.com/master_manifest.m3u8', offscreen=True)

# These two lines are needed to prevent the HTTP HEAD request from Kodi core, used to determine the mimetype
listitem.setMimeType('application/vnd.apple.mpegurl')
listitem.setContentLookup(False)

listitem.setProperty('inputstream', 'inputstream.adaptive')
listitem.setProperty('inputstream.adaptive.manifest_type', 'hls') # Deprecated on Kodi 21, removed on Kodi 22
# inputstream.adaptive.license_type is not set, since there is no DRM, AES-128 encryption only
# inputstream.adaptive.license_key is not set, usually is not required, just to handle special use cases for the HLS key request

xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=listitem)

Other

🔵 License URL too long - How to solve PVR binary API bug (Only for Kodi v20-v21) [click to expand]

On all Kodi versions until v21, there is a know Kodi PVR API interface bug, that truncate ISAdaptive properties values to max 1024 chars, then if the server license URL is too long will trucate the data set on the InputStream properties (e.g. inputstream.adaptive.license_key), and the video playback will fails.

The PVR API bug has been fixed on Kodi v22.

To workaround this bug, has been added these two properties (that can be used only on Kodi 20/21) to split the URL on two parts of 1024 chars:

  • inputstream.adaptive.license_url [deprecated on Kodi 22]
  • inputstream.adaptive.license_url_append [deprecated on Kodi 22]

How to do:

  1. Leave empty the field used to set the license URL (e.g. on inputstream.adaptive.license_key the [license server URL] template field)
  2. Take care to split the license server url in two parts of 1024 chars, then use license_url to specify the first 1024 chars, and license_url_append to add the last part of remaining url chars.

NOTE: On Kodi v20, has been introduced from ISA v20.3.15

🔵 How to configure SmoothStreaming PlayReady manifest to use Widevine [click to expand]

Some video services can use SmoothStreaming manifests protected with PlayReady DRM, and may also provide a Widevine server license URL. Then it is possible try to force use Widevine DRM to play the contents protected with PlayReady DRM. This is useful because currently the use of PlayReady is limited on android only, and some android devices may not support PlayReady.

To force Widevine you need just to specify "com.widevine.alpha" (instead of "com.microsoft.playready") and set the license server URL, as follow:

listitem = xbmcgui.ListItem(path='https://www.videoservice.com/tearsofsteel_4k.ism/manifest', offscreen=True)

# These two lines are needed to prevent the HTTP HEAD request from Kodi core, used to determine the mimetype
listitem.setMimeType('application/vnd.ms-sstr+xml')
listitem.setContentLookup(False)

listitem.setProperty('inputstream', 'inputstream.adaptive')
listitem.setProperty('inputstream.adaptive.manifest_type', 'ism') # Deprecated on Kodi 21, removed on Kodi 22
listitem.setProperty('inputstream.adaptive.license_type', 'com.widevine.alpha')
listitem.setProperty('inputstream.adaptive.license_key', 'https://widevinelicenseserverurl')

xbmcplugin.setResolvedUrl(pluginhandle, True, listitem=listitem)

If the video service require custom data to the Widevine PSSH:

You can pass your custom data by using inputstream.adaptive.license_data property, the data must be encoded as base64.

Two placeholders can be optionally used to inject data in to the custom data:

  • {KID} to inject the KID as bytes
  • {UUID} to inject the KID as UUID string

The placeholders if used must be encoded as base64 together with the raw data, and not separately.

Technically speaking the custom data will be added to content_id field of WidevinePsshData structure of Widevine PSSH. The WidevinePsshData generated will be add to a standard generated PSSH box.

Clone this wiki locally