Compare commits

...

23 Commits

Author SHA1 Message Date
rlaphoenix
4298db7546 Improve Installation/Usage in README, update badges for uv 2025-10-27 13:20:50 +00:00
rlaphoenix
0f2f34c83f Migrate from poetry to uv/hatchling 2025-10-27 13:20:50 +00:00
rlaphoenix
b30171302f Bump to v1.9.0 2025-10-27 09:11:08 +00:00
rlaphoenix
8c310ad5e3 Remove DeepSource config & badge 2025-10-27 09:03:33 +00:00
rlaphoenix
d58db89baf Use official config repos for ruff and mypy, fix poetry dependency resolution 2025-10-27 08:58:15 +00:00
rlaphoenix
8cd623a217 Update pre-commit config 2025-10-27 08:35:08 +00:00
rlaphoenix
72f84249bd Update packages overall to get latest wheels for 3.12 and newer 2025-10-27 08:28:11 +00:00
rlaphoenix
b624355764 Update versions in GitHub Workflows
(I bloody hate github workflows, annoying ahh hell to maintain)
2025-10-27 08:20:36 +00:00
rlaphoenix
c32dd6a757 Drop support for 3.8 from readme badge, update copyright year 2025-10-27 08:14:56 +00:00
rlaphoenix
e75bde1de2 Update dev dependencies, support 3.14 + its been a while
Note that isort isn't the latest version as support for 3.9 was dropped. I feel like it's a tad bit early and a bit sudden to drop support for both 3.8 and 3.9 in the span of a few commits so I will stick to 6.1.0 for now.
2025-10-27 08:13:56 +00:00
rlaphoenix
1bb0b246f4 Updated requests, PyYAML, & aiohttp for full Python 3.14 support 2025-10-27 08:07:18 +00:00
rlaphoenix
9ec88ef1a8 Update pycryptodome package to take advantage of Windows ARM wheels 2025-10-27 07:54:33 +00:00
rlaphoenix
7c2caa2e7e
Merge pull request #56 from UltimaHoarder/master
Support for Python 3.14
2025-10-27 07:48:29 +00:00
UltimaHoarder
fe033c9647 Update Python minimum to 3.9 and protobuf to ^6.33.0 for Python 3.14 2025-10-27 03:04:05 +00:00
rlaphoenix
7ea2a72a8c Update Changelog for v1.8.0 2023-12-22 11:12:09 +00:00
rlaphoenix
84d30a69a9 Bump to v1.8.0 2023-12-22 11:08:57 +00:00
sr0lle
c39dd6df5d Create py.typed to silence mypy (PEP561) (#43) 2023-12-22 10:58:12 +00:00
rlaphoenix
94f8eba960 Remove PyYAML from the "serve" extras group
Fixes #44
2023-12-22 10:43:35 +00:00
rlaphoenix
25e03529f6 Simplify verification of parsing in Cdm.set_service_certificate 2023-12-06 16:00:52 +00:00
rlaphoenix
a04e751aa1 Support duplicated SignedMessages in Cdm.set_service_certificate
Fixes #41

Seems some services like TF1 (France) returns a SignedMessage twice in one response body by mistake, resulting in a partial parse decoding error as pywidevine doesn't expect the parsed-then-serialized data to differ from the received data.

This workaround checks if the parsed-then-serialized data is in the received data multiple times without any leftover data. If there's no leftover data it considers it safe to continue.
2023-12-06 15:36:27 +00:00
rlaphoenix
17cefbf1d8 Recompile protobuffers for v4.25 2023-12-06 15:31:53 +00:00
rlaphoenix
bcb2185f75 Add Python 3.12 to CI/CD workflows 2023-12-06 15:29:59 +00:00
rlaphoenix
532e68aba9 Drop Support for Python 3.7, update Dependencies 2023-12-06 15:29:06 +00:00
14 changed files with 1856 additions and 1496 deletions

View File

@ -1,17 +0,0 @@
version = 1
exclude_patterns = [
"pywidevine/license_protocol_pb2.py"
]
[[analyzers]]
name = "python"
enabled = true
[analyzers.meta]
runtime_version = "3.x.x"
max_line_length = 120
[[analyzers]]
name = "secrets"
enabled = false

View File

@ -9,33 +9,25 @@ jobs:
tagged-release: tagged-release:
name: Tagged Release name: Tagged Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment:
name: pypi
permissions:
id-token: write
contents: read
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v6
with: with:
python-version: "3.11.x" python-version: "3.14"
- name: Install Poetry - name: Install uv
uses: abatilo/actions-poetry@v2 uses: astral-sh/setup-uv@v6
with: with:
poetry-version: 1.6.1 version: "0.9.5"
- name: Install project enable-cache: true
run: poetry install --only main - name: Install the project
run: uv sync --locked
- name: Build project - name: Build project
run: poetry build run: uv build
- name: Upload wheel
uses: actions/upload-artifact@v3
with:
name: Python Wheel
path: "dist/*.whl"
- name: Deploy release
uses: marvinpinto/action-automatic-releases@latest
with:
prerelease: false
repo_token: "${{ secrets.GITHUB_TOKEN }}"
files: |
dist/*.whl
- name: Publish to PyPI - name: Publish to PyPI
env: run: uv publish
POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }}
run: poetry publish

View File

@ -10,35 +10,37 @@ jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v6
with: with:
python-version: "3.11" python-version: "3.14"
- name: Install poetry - name: Install uv
uses: abatilo/actions-poetry@v2 uses: astral-sh/setup-uv@v6
with: with:
poetry-version: 1.6.1 version: "0.9.5"
- name: Install project enable-cache: true
run: poetry install --all-extras - name: Install the project
run: uv sync --locked --all-extras --dev
- name: Run pre-commit which does various checks - name: Run pre-commit which does various checks
run: poetry run pre-commit run --all-files --show-diff-on-failure run: uv run pre-commit run --all-files --show-diff-on-failure
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4 uses: actions/setup-python@v6
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install poetry - name: Install uv
uses: abatilo/actions-poetry@v2 uses: astral-sh/setup-uv@v6
with: with:
poetry-version: 1.5.1 # 1.6.x+ requires Python 3.8+ version: "0.9.5"
- name: Install project enable-cache: true
run: poetry install --all-extras --only main - name: Install the project
run: uv sync --locked --all-extras
- name: Build project - name: Build project
run: poetry build run: uv build

View File

@ -3,17 +3,27 @@
exclude: '_pb2.pyi?$' exclude: '_pb2.pyi?$'
repos: repos:
- repo: https://github.com/mtkennerly/pre-commit-hooks - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.0 rev: v0.14.2
hooks: hooks:
- id: poetry-ruff - id: ruff-check
- id: poetry-mypy language: system
types: [ python ]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.18.2
hooks:
- id: mypy
language: system
types: [ python ]
args: [] # override defaults to empty
- repo: https://github.com/pycqa/isort - repo: https://github.com/pycqa/isort
rev: 5.11.5 rev: 6.1.0
hooks: hooks:
- id: isort - id: isort
language: system
types: [ python ]
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0 rev: v6.0.0
hooks: hooks:
- id: end-of-file-fixer - id: end-of-file-fixer
- id: trailing-whitespace - id: trailing-whitespace

View File

@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.9.0] - 2025-12-22
Just a small update to update imports, support the latest python version, and fix configs.
### Added
- Support for Python 3.14 (and retroactively 3.13).
### Removed
- Dropped support for Python 3.8.
- DeepSource config and badge.
### Fixed
- Fixed pre-commit config, now using all official repos while still utilizing poetry dependencies.
- Fixed the GitHub workflows for CI/CD, now using the latest action versions.
## [1.8.0] - 2023-12-22
### Added
- Added `py.typed` file to support PEP561 and silence Mypy.
### Changed
- Dropped support for Python 3.7.
- Recompiled protobuffers for version 4.25.
### Fixed
- Missing `yaml` dependency as it was only installed alongside the `serve` extras group.
- Duplicate Concatenated SignedMessages no longer throw a verification failure in `Cdm.set_service_certificate()`.
To ensure security of the messages, verification will still fail if any of the SignedMessages do not match each other.
### New Contributors
- [sr0lle](https://github.com/sr0lle)
## [1.7.0] - 2023-11-21 ## [1.7.0] - 2023-11-21
- Supported Serve API: `v1.4.3` or newer - Supported Serve API: `v1.4.3` or newer
@ -473,6 +512,8 @@ Initial Release.
- Service Certificate Signatures are unverified as the signing public key is Unknown. - Service Certificate Signatures are unverified as the signing public key is Unknown.
[1.8.0]: https://github.com/devine-dl/pywidevine/releases/tag/v1.8.0
[1.7.0]: https://github.com/devine-dl/pywidevine/releases/tag/v1.7.0
[1.6.0]: https://github.com/devine-dl/pywidevine/releases/tag/v1.6.0 [1.6.0]: https://github.com/devine-dl/pywidevine/releases/tag/v1.6.0
[1.5.3]: https://github.com/devine-dl/pywidevine/releases/tag/v1.5.3 [1.5.3]: https://github.com/devine-dl/pywidevine/releases/tag/v1.5.3
[1.5.2]: https://github.com/devine-dl/pywidevine/releases/tag/v1.5.2 [1.5.2]: https://github.com/devine-dl/pywidevine/releases/tag/v1.5.2

View File

@ -1,7 +1,7 @@
# Development # Development
This project is managed using [Poetry](https://python-poetry.org), a fantastic Python packaging and dependency manager. This project is managed using [Poetry](https://python-poetry.org), a fantastic Python packaging and dependency manager.
Install the latest version of Poetry before continuing. Development currently requires Python 3.7+. Install the latest version of Poetry before continuing. Development currently requires Python 3.8+.
## Set up ## Set up

137
README.md
View File

@ -5,22 +5,20 @@
</p> </p>
<p align="center"> <p align="center">
<a href="https://github.com/devine-dl/pywidevine/actions/workflows/ci.yml"> <a href="https://github.com/devine-dl/pywidevine/blob/master/LICENSE">
<img src="https://github.com/devine-dl/pywidevine/actions/workflows/ci.yml/badge.svg" alt="Build status"> <img src="https://img.shields.io/:license-GPL%203.0-blue.svg" alt="License">
</a> </a>
<a href="https://pypi.org/project/pywidevine"> <a href="https://pypi.org/project/pywidevine">
<img src="https://img.shields.io/badge/python-3.7%2B-informational" alt="Python version"> <img src="https://img.shields.io/badge/python-3.9%2B-informational" alt="Python version">
</a> </a>
<a href="https://deepsource.io/gh/devine-dl/pywidevine"> <a href="https://github.com/astral-sh/uv">
<img src="https://deepsource.io/gh/devine-dl/pywidevine.svg/?label=active+issues" alt="DeepSource"> <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Onyx-Nostalgia/uv/refs/heads/fix/logo-badge/assets/badge/v0.json" alt="Manager: uv">
</a> </a>
</p>
<p align="center">
<a href="https://github.com/astral-sh/ruff"> <a href="https://github.com/astral-sh/ruff">
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Linter: Ruff"> <img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Linter: Ruff">
</a> </a>
<a href="https://python-poetry.org"> <a href="https://github.com/devine-dl/pywidevine/actions/workflows/ci.yml">
<img src="https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json" alt="Dependency management: Poetry"> <img src="https://github.com/devine-dl/pywidevine/actions/workflows/ci.yml/badge.svg" alt="Build status">
</a> </a>
</p> </p>
@ -37,22 +35,31 @@
## Installation ## Installation
```shell ### With pip
$ pip install pywidevine
```
> **Note** > Since *pip* is pre-installed with Python, it is the most straight forward way to install pywidevine.
If pip gives you a warning about a path not being in your PATH environment variable then promptly add that path then
close all open command prompt/terminal windows, or `pywidevine` CLI won't work as it will not be found.
Voilà 🎉 — You now have the `pywidevine` package installed! Simply run `pip install pywidevine` and it will be ready to use from the CLI or within scripts in a minute.
You can now import pywidevine in scripts ([see below](#usage)).
A command-line interface is also available, try `pywidevine --help`. ### With uv
> This is recommended for those who wish to install from the source code, are working on changes in the source code, or
just simply prefer it's many handy features.
Go to to the official website and [get uv installed](https://docs.astral.sh/uv/getting-started/installation/). Download
or clone this repository, go inside it, and run `uv run pywidevine --version`. To run scripts, like a `license.py` that
is importing pywidevine, do `uv run license.py`. Effectively, put `uv run` before calling whatever is using pywidevine.
For other ways to run pywidevine with uv, see [Running commands](https://docs.astral.sh/uv/guides/projects/#running-commands).
## Usage ## Usage
The following is a minimal example of using pywidevine in a script to get a License for Bitmovin's There are two ways to use pywidevine, through scripts, or the CLI (command-line interface).
Art of Motion Demo. Most people would be using it through scripts due to complexities working with license server APIs.
### Scripts
The following is a minimal example of using pywidevine in a script to get a License for Bitmovin's Art of Motion Demo.
This demo can be found on [Bitmovin's DRM Stream Test demo page](https://bitmovin.com/demos/drm/).
```py ```py
from pywidevine.cdm import Cdm from pywidevine.cdm import Cdm
@ -61,50 +68,93 @@ from pywidevine.pssh import PSSH
import requests import requests
# prepare pssh # prepare pssh (usually inside the MPD/M3U8, an API response, the player page, or inside the pssh mp4 box)
pssh = PSSH("AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa" pssh = PSSH("AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa"
"7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA==") "7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA==")
# load device # load device from a WVD file (your provision)
device = Device.load("C:/Path/To/A/Provision.wvd") device = Device.load("C:/Path/To/A/Provision.wvd")
# load cdm # load cdm (creating a CDM instance using that device)
cdm = Cdm.from_device(device) cdm = Cdm.from_device(device)
# open cdm session # open cdm session (note that any one device should have a practical limit to amount of sessions open at any one time)
session_id = cdm.open() session_id = cdm.open()
# get license challenge # get license challenge (generate a license request message, signed using the device with the pssh)
challenge = cdm.get_license_challenge(session_id, pssh) challenge = cdm.get_license_challenge(session_id, pssh)
# send license challenge (assuming a generic license server SDK with no API front) # send license challenge to bitmovin's license server (which has no auth and asks simply for the license challenge as-is)
licence = requests.post("https://...", data=challenge) # another license server may require authentication and ask for it as JSON or form data instead
# you may also be required to use privacy mode, where you use their service certificate when creating the challenge
licence = requests.post("https://cwip-shaka-proxy.appspot.com/no_auth", data=challenge)
licence.raise_for_status() licence.raise_for_status()
# parse license challenge # parse the license response message received from the license server API
cdm.parse_license(session_id, licence.content) cdm.parse_license(session_id, licence.content)
# print keys # print keys
for key in cdm.get_keys(session_id): for key in cdm.get_keys(session_id):
print(f"[{key.type}] {key.kid.hex}:{key.key.hex()}") print(f"[{key.type}] {key.kid.hex}:{key.key.hex()}")
# close session, disposes of session data # finished, close the session, disposing of all keys and other related data
cdm.close(session_id) cdm.close(session_id)
``` ```
> **Note** There are other features not shown in this small example like:
> There are various features not shown in this specific example like:
> - Privacy Mode
> - Privacy Mode - Setting Service Certificates
> - Setting Service Certificates - Remote CDMs and Serving
> - Remote CDMs and Serving - Choosing a License Type
> - Choosing a License Type to request - Creating WVD files
> - Creating WVD files - and much more!
> - and much more!
> > [!TIP]
> Take a look at the methods available in the [Cdm class](/pywidevine/cdm.py) and their doc-strings for > For examples, take a look at the methods available in the [Cdm class](/pywidevine/cdm.py) and read their doc-strings
> further information. For more examples see the [CLI functions](/pywidevine/main.py) which uses a lot > for further information.
> of previously mentioned features.
### Command-line Interface
The CLI can be useful to do simple license calls, migrate WVD files, and test provisions.
Take a look at `pywidevine --help` to see a list of commands available.
```plain
Usage: pywidevine [OPTIONS] COMMAND [ARGS]...
pywidevine—Python Widevine CDM implementation.
Options:
-v, --version Print version information.
-d, --debug Enable DEBUG level logs.
--help Show this message and exit.
Commands:
create-device Create a Widevine Device (.wvd) file from an RSA Private...
export-device Export a Widevine Device (.wvd) file to an RSA Private...
license Make a License Request for PSSH to SERVER using DEVICE.
migrate Upgrade from earlier versions of the Widevine Device...
serve Serve your local CDM and Widevine Devices Remotely.
test Test the CDM code by getting Content Keys for Bitmovin's...
```
Every command has further help information, simply type `pywidevine <command> --help`.
For example, `pywidevine test --help`:
```plain
Usage: pywidevine test [OPTIONS] DEVICE
Test the CDM code by getting Content Keys for Bitmovin's Art of Motion
example. https://bitmovin.com/demos/drm
https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/mpds/11331.mpd
The device argument is a Path to a Widevine Device (.wvd) file which
contains the device private key among other required information.
Options:
-p, --privacy Use Privacy Mode, off by default.
--help Show this message and exit.
```
## Disclaimer ## Disclaimer
@ -151,6 +201,7 @@ making a CDM in C++ has immediate security benefits and a lot of methods to obsc
<a href="https://github.com/rlaphoenix"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17136956?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt=""/></a> <a href="https://github.com/rlaphoenix"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/17136956?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt=""/></a>
<a href="https://github.com/mediaminister"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45148099?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt=""/></a> <a href="https://github.com/mediaminister"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/45148099?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt=""/></a>
<a href="https://github.com/sr0lle"><img src="https://images.weserv.nl/?url=avatars.githubusercontent.com/u/111277375?v=4&h=25&w=25&fit=cover&mask=circle&maxage=7d" alt=""/></a>
## Licensing ## Licensing
@ -162,4 +213,4 @@ You can find a copy of the license in the LICENSE file in the root folder.
* * * * * *
© rlaphoenix 2022-2023 © rlaphoenix 2022-2025

1173
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,87 +1,97 @@
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["hatchling"]
build-backend = "poetry.core.masonry.api" build-backend = "hatchling.build"
[tool.poetry] [project]
name = "pywidevine" name = "pywidevine"
version = "1.7.0" version = "1.9.0"
description = "Widevine CDM (Content Decryption Module) implementation in Python." description = "Widevine CDM (Content Decryption Module) implementation in Python."
license = "GPL-3.0-only" authors = [{ name = "rlaphoenix", email = "rlaphoenix@pm.me" }]
authors = ["rlaphoenix <rlaphoenix@pm.me>"] requires-python = ">=3.9"
readme = "README.md" readme = "README.md"
repository = "https://github.com/devine-dl/pywidevine" license = "GPL-3.0-only"
keywords = ["python", "drm", "widevine", "google"] keywords = [
classifiers = [ "python",
"Development Status :: 5 - Production/Stable", "drm",
"Intended Audience :: Developers", "widevine",
"Intended Audience :: End Users/Desktop", "google",
"Natural Language :: English", ]
"Operating System :: OS Independent", classifiers = [
"Topic :: Multimedia :: Video", "Development Status :: 5 - Production/Stable",
"Topic :: Security :: Cryptography", "Intended Audience :: Developers",
"Topic :: Software Development :: Libraries :: Python Modules" "Intended Audience :: End Users/Desktop",
] "Natural Language :: English",
include = [ "Operating System :: OS Independent",
{ path = "CHANGELOG.md", format = "sdist" }, "Topic :: Multimedia :: Video",
{ path = "README.md", format = "sdist" }, "Topic :: Security :: Cryptography",
{ path = "LICENSE", format = "sdist" }, "Topic :: Software Development :: Libraries :: Python Modules",
] ]
dependencies = [
[tool.poetry.urls] "protobuf~=6.33.0",
"Issues" = "https://github.com/devine-dl/pywidevine/issues" "pymp4~=1.4.0",
"Discussions" = "https://github.com/devine-dl/pywidevine/discussions" "pycryptodome~=3.23.0",
"Changelog" = "https://github.com/devine-dl/pywidevine/blob/master/CHANGELOG.md" "click~=8.1.7",
"requests~=2.32.5",
[tool.poetry.dependencies] "Unidecode~=1.3.7",
python = ">=3.7,<4.0" "PyYAML~=6.0.3",
protobuf = "^4.24.4" ]
pymp4 = "^1.4.0"
pycryptodome = "^3.19.0" [project.optional-dependencies]
click = "^8.1.7" serve = ["aiohttp~=3.13.1"]
requests = "^2.31.0"
Unidecode = "^1.3.7" [project.urls]
PyYAML = "^6.0.1" Repository = "https://github.com/devine-dl/pywidevine"
aiohttp = {version = "^3.8.6", optional = true} Issues = "https://github.com/devine-dl/pywidevine/issues"
Discussions = "https://github.com/devine-dl/pywidevine/discussions"
[tool.poetry.group.dev.dependencies] Changelog = "https://github.com/devine-dl/pywidevine/blob/master/CHANGELOG.md"
pre-commit = "^2.2.1"
mypy = "^1.4.1" [project.scripts]
mypy-protobuf = "^3.4.0" pywidevine = "pywidevine.main:main"
types-protobuf = "^4.24.0.4"
types-requests = "^2.31.0.10" [dependency-groups]
types-PyYAML = "^6.0.12.12" dev = [
isort = "^5.11.5" "pre-commit~=4.3.0",
ruff = "~0.1.4" "mypy~=1.18.2",
"mypy-protobuf~=3.6.0",
[tool.poetry.extras] "types-protobuf~=6.32.1.20250918",
serve = ["aiohttp", "PyYAML"] "types-requests~=2.32.4.20250913",
"types-PyYAML~=6.0.12.20250915",
[tool.poetry.scripts] "isort~=6.1.0",
pywidevine = "pywidevine.main:main" "ruff~=0.14.2",
]
[tool.ruff]
extend-exclude = [ [tool.hatch.build.targets.sdist]
"*_pb2.py", include = [
"*.pyi", "pywidevine",
] "CHANGELOG.md",
force-exclude = true ]
line-length = 120
select = ["E4", "E7", "E9", "F", "W"] [tool.hatch.build.targets.wheel]
packages = ["pywidevine"]
[tool.ruff.extend-per-file-ignores]
"pywidevine/__init__.py" = ["F403"] [tool.ruff]
extend-exclude = [
[tool.isort] "*_pb2.py",
line_length = 118 "*.pyi",
extend_skip_glob = ["*_pb2.py", "*.pyi"] ]
force-exclude = true
[tool.mypy] line-length = 120
check_untyped_defs = true select = ["E4", "E7", "E9", "F", "W"]
disallow_incomplete_defs = true
disallow_untyped_defs = true [tool.ruff.extend-per-file-ignores]
exclude = [ "pywidevine/__init__.py" = ["F403"]
'_pb2.pyi?$' # generated protobuffer files
] [tool.isort]
follow_imports = "silent" line_length = 118
ignore_missing_imports = true extend_skip_glob = ["*_pb2.py", "*.pyi"]
no_implicit_optional = true
[tool.mypy]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_defs = true
exclude = [
'_pb2.pyi?$' # generated protobuffer files
]
follow_imports = "silent"
ignore_missing_imports = true
no_implicit_optional = true

View File

@ -5,4 +5,4 @@ from .pssh import *
from .remotecdm import * from .remotecdm import *
from .session import * from .session import *
__version__ = "1.7.0" __version__ = "1.9.0"

View File

@ -222,7 +222,11 @@ class Cdm:
try: try:
signed_message.ParseFromString(certificate) signed_message.ParseFromString(certificate)
if signed_message.SerializeToString() == certificate: if all(
# See https://github.com/devine-dl/pywidevine/issues/41
bytes(chunk) == signed_message.SerializeToString()
for chunk in zip(*[iter(certificate)] * len(signed_message.SerializeToString()))
):
signed_drm_certificate.ParseFromString(signed_message.msg) signed_drm_certificate.ParseFromString(signed_message.msg)
else: else:
signed_drm_certificate.ParseFromString(certificate) signed_drm_certificate.ParseFromString(certificate)

File diff suppressed because one or more lines are too long

0
pywidevine/py.typed Normal file
View File

1439
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff