6 Commits

Author SHA1 Message Date
rlaphoenix
e4a8316227 Use Python 3.11 in GitHub Workflows 2023-02-03 07:04:22 +00:00
rlaphoenix
9568d7fdb9 Update Poetry Version used in GitHub Workflows 2023-02-03 07:03:36 +00:00
rlaphoenix
ece0914920 Update Changelog for v1.6.0 2023-02-03 07:00:56 +00:00
rlaphoenix
2ab659eab6 Bump to v1.6.0 2023-02-03 06:58:00 +00:00
rlaphoenix
99aef63354 Add export-device command to export WVDs back as files
In reality you wouldn't need this for use with pywidevine, but a lot have asked me for this feature so they can use WVDs in other ways or with other software that does not support WVDs.
2023-02-03 06:53:55 +00:00
rlaphoenix
fd3df13e9c Add Support Python 3.11 2023-02-03 06:26:50 +00:00
6 changed files with 895 additions and 793 deletions

View File

@@ -14,11 +14,11 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.10.x'
python-version: '3.11.x'
- name: Install Poetry
uses: abatilo/actions-poetry@v2.1.0
with:
poetry-version: '1.1.11'
poetry-version: '1.3.2'
- name: Configure poetry
run: poetry config virtualenvs.in-project true
- name: Install dependencies

View File

@@ -13,8 +13,8 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.7', '3.8', '3.9', '3.10']
poetry-version: [1.1.11]
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
poetry-version: [1.3.2]
steps:
- uses: actions/checkout@v2

View File

@@ -5,6 +5,19 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.6.0] - 2023-02-03
### Added
- Added full support for Python 3.11.
- Added new `export-device` command-line function to export WVD files back as files. I.e., a private key and client ID
blob file.
### Changed
- The PyYAML dependency is now required even if you do not install Pywidevine with the `serve` extra dependencies.
- This was required for exporting WVD metadata in the new `export-device` command-line function.
## [1.5.3] - 2022-12-27
### Added
@@ -359,6 +372,7 @@ This release is primarily a maintenance release for `serve` functionality but so
Initial Release.
[1.6.0]: https://github.com/rlaphoenix/pywidevine/releases/tag/v1.6.0
[1.5.3]: https://github.com/rlaphoenix/pywidevine/releases/tag/v1.5.3
[1.5.2]: https://github.com/rlaphoenix/pywidevine/releases/tag/v1.5.2
[1.5.1]: https://github.com/rlaphoenix/pywidevine/releases/tag/v1.5.1

1577
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "pywidevine"
version = "1.5.3"
version = "1.6.0"
description = "Widevine CDM (Content Decryption Module) implementation in Python."
authors = ["rlaphoenix <rlaphoenix@pm.me>"]
license = "GPL-3.0-only"
@@ -27,16 +27,16 @@ classifiers = [
"Changelog" = "https://github.com/rlaphoenix/pywidevine/blob/master/CHANGELOG.md"
[tool.poetry.dependencies]
python = ">=3.7,<3.11"
python = ">=3.7,<3.12"
protobuf = "4.21.6"
pymp4 = "^1.2.0"
pycryptodome = "^3.15.0"
click = "^8.1.3"
requests = "^2.28.1"
lxml = ">=4.9.1"
lxml = ">=4.9.2"
Unidecode = "^1.3.4"
PyYAML = "^6.0"
aiohttp = {version = "^3.8.1", optional = true}
PyYAML = {version = "^6.0", optional = true}
[tool.poetry.extras]
serve = ["aiohttp", "PyYAML"]

View File

@@ -8,6 +8,8 @@ import click
import requests
from construct import ConstructError
from unidecode import unidecode, UnidecodeError
import yaml
from google.protobuf.json_format import MessageToDict
from pywidevine import __version__
from pywidevine.cdm import Cdm
@@ -247,6 +249,85 @@ def create_device(
log.info(" + Saved to: %s", out_path.absolute())
@main.command()
@click.argument("wvd_path", type=Path)
@click.option("-o", "--out_dir", type=Path, default=None, help="Output Directory")
@click.pass_context
def export_device(ctx: click.Context, wvd_path: Path, out_dir: Optional[Path] = None) -> None:
"""
Export a Widevine Device (.wvd) file to an RSA Private Key (PEM and DER) and Client ID Blob.
Optionally also a VMP (Verified Media Path) Blob, which will be stored in the Client ID.
If an output directory is not specified, it will be stored in the current working directory.
"""
if not wvd_path.is_file():
raise click.UsageError("wvd_path: Not a path to a file, or it doesn't exist.", ctx)
log = logging.getLogger("export-device")
log.info("Exporting Widevine Device (.wvd) file, %s", wvd_path.stem)
if not out_dir:
out_dir = Path.cwd()
out_path = out_dir / wvd_path.stem
if out_path.exists():
if any(out_path.iterdir()):
log.error("Output directory is not empty, cannot overwrite.")
return
else:
log.warning("Output directory already exists, but is empty.")
else:
out_path.mkdir(parents=True)
device = Device.load(wvd_path)
log.info(f"L{device.security_level} {device.system_id} {device.type.name}")
log.info(f"Saving to: {out_path}")
device_meta = {
"wvd": {
"device_type": device.type.name,
"security_level": device.security_level,
**device.flags
},
"client_info": {},
"capabilities": MessageToDict(device.client_id, preserving_proto_field_name=True)["client_capabilities"]
}
for client_info in device.client_id.client_info:
device_meta["client_info"][client_info.name] = client_info.value
device_meta_path = out_path / "metadata.yml"
device_meta_path.write_text(yaml.dump(device_meta), encoding="utf8")
log.info("Exported Device Metadata as metadata.yml")
if device.private_key:
private_key_path = out_path / "private_key.pem"
private_key_path.write_text(
data=device.private_key.export_key().decode(),
encoding="utf8"
)
private_key_path.with_suffix(".der").write_bytes(
device.private_key.export_key(format="DER")
)
log.info("Exported Private Key as private_key.der and private_key.pem")
else:
log.warning("No Private Key available")
if device.client_id:
client_id_path = out_path / "client_id.bin"
client_id_path.write_bytes(device.client_id.SerializeToString())
log.info("Exported Client ID as client_id.bin")
else:
log.warning("No Client ID available")
if device.client_id.vmp_data:
vmp_path = out_path / "vmp.bin"
vmp_path.write_bytes(device.client_id.vmp_data)
log.info("Exported VMP (File Hashes) as vmp.bin")
else:
log.info("No VMP (File Hashes) available")
@main.command()
@click.argument("path", type=Path)
@click.pass_context