PyWidevine

raw JSON →
1.9.0 verified Sat May 09 auth: no python

A Python implementation of the Widevine Content Decryption Module (CDM) for decrypting DRM-protected content. Version 1.9.0 supports Python 3.9–3.14. Regular releases, roughly quarterly.

pip install pywidevine
error ModuleNotFoundError: No module named 'yaml'
cause yaml dependency was not installed unless the 'serve' extras were used. Fixed in v1.8.0.
fix
Upgrade to v1.8.0+ or install yaml separately: pip install pyyaml
error AttributeError: module 'pywidevine' has no attribute 'Cdm'
cause Importing incorrectly; Cdm is only available in top-level since v1.5.1. Older versions or wrong import path.
fix
Use correct import: from pywidevine import Cdm. Or upgrade to v1.5.1+. Alternatively use: from pywidevine.cdm import Cdm
error pywidevine.exceptions.InvalidInitData: Failed to parse PSSH data
cause PSSH data is malformed or not base64 encoded correctly.
fix
Ensure PSSH string is a valid base64-encoded Widevine PSSH box. Use PSSH() with raw bytes or a valid base64 string.
breaking Dropped Python 3.8 support in v1.9.0. Python 3.7 dropped in v1.8.0.
fix Upgrade to Python 3.9 or newer. If you must use older Python, pin to pywidevine<1.9.0.
gotcha The 'serve' extras group installs flask and yaml. Without it, yaml dependency was missing before v1.8.0, causing import errors if YAML config is used.
fix Upgrade to v1.8.0+ or install with extras: pip install pywidevine[serve]
gotcha License responses must be the raw bytes returned from the license server, not base64-encoded strings.
fix Ensure the response is passed as bytes, e.g., response.content from requests.
pip install pywidevine[serve]

Basic usage: load a device, open session, get license challenge, parse response, print keys.

from pywidevine import Cdm, Device, PSSH

# Load a Widevine device (WVD file)
device = Device.load('path/to/device.wvd')

# Create a CDM session
cdm = Cdm()
session = cdm.open(device)

# Set a service certificate (optional, for privacy mode)
cert_data = b'...'  # your service certificate bytes
cdm.set_service_certificate(session, cert_data)

# Obtain a license challenge from a PSSH box
pssh = PSSH(pssh='AAAAWnBzc2gAAAAA...')
challenge = cdm.get_license_challenge(session, pssh)

# Send the challenge to your license server, get license response
license_response = b'...'  # bytes from server
cdm.parse_license(session, license_response)

# Extract content keys
for key in cdm.get_keys(session):
    print(f'{key.type}: {key.kid.hex} -> {key.key.hex()}')

cdm.close(session)