Widevine/DRM research and video download scripts (Apple, CDA, Sky, TVP), CDM tooling, and helper utilities. Excludes downloads/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WFUhtZEpkGZJjNCGFS9r4Z
122 lines
3.6 KiB
Plaintext
Executable File
122 lines
3.6 KiB
Plaintext
Executable File
TVP VOD Downloader
|
||
==================
|
||
|
||
Downloads Widevine L3 DRM-protected videos from vod.tvp.pl as decrypted MP4 files.
|
||
|
||
|
||
REQUIREMENTS
|
||
------------
|
||
Python packages:
|
||
pip install pywidevine requests
|
||
|
||
System tools (already present in ./tools/):
|
||
tools/N_m3u8DL-RE — DASH segment downloader
|
||
tools/mp4decrypt — CENC decryption (Bento4)
|
||
ffmpeg — must be in PATH (used for muxing)
|
||
|
||
Widevine device file (already present in ./L3/):
|
||
L3/google_sdk_gphone64_x86_64_3198748f_22596_l3.wvd
|
||
|
||
|
||
USAGE
|
||
-----
|
||
|
||
1. Download a single episode by product ID:
|
||
|
||
python3 tvp_download.py 2993670
|
||
|
||
2. Download multiple episodes by product ID:
|
||
|
||
python3 tvp_download.py 2726892 2785411 2871666
|
||
|
||
3. Download all episodes of a series by series ID:
|
||
|
||
python3 tvp_download.py --series 2726890
|
||
|
||
Output files are saved to ./downloads/ as:
|
||
sXXeXX_title.mp4 (e.g. s01e13_zaslubiny.mp4)
|
||
|
||
|
||
FINDING IDs
|
||
-----------
|
||
|
||
Product ID and series ID come from the vod.tvp.pl URL:
|
||
|
||
https://vod.tvp.pl/seriale,18/nazwa-serialu-odcinki,{SERIES_ID}/odcinek-N,S01E01,{PRODUCT_ID}
|
||
|
||
Example — "Wojna zastępcza":
|
||
Series ID: 2726890 (from the URL segment "...-odcinki,2726890/...")
|
||
Episode 13: 2993670 (from the URL segment "...,S01E13,2993670")
|
||
|
||
You can also pass the URL directly to the browser and copy the product ID from the address bar
|
||
when playing an episode.
|
||
|
||
|
||
HOW IT WORKS
|
||
------------
|
||
|
||
For each episode:
|
||
1. Fetches the DASH playlist:
|
||
GET /api/products/{id}/videos/playlist?platform=BROWSER&videoType=MOVIE
|
||
2. Extracts the Widevine PSSH from the MPD manifest.
|
||
3. Sends a license challenge to TVP's Widevine license server using the
|
||
L3 device certificate (.wvd file) and pywidevine.
|
||
4. Gets the AES-128 content key from the license response.
|
||
5. Downloads all encrypted video and audio segments via N_m3u8DL-RE.
|
||
6. Decrypts segments with mp4decrypt using the content key.
|
||
7. Muxes video + audio into a single MP4 with ffmpeg.
|
||
|
||
|
||
OUTPUT
|
||
------
|
||
|
||
Each episode is saved as a single MP4:
|
||
./downloads/{product_id}_{title}.mp4
|
||
|
||
Quality: best available (typically 1920x1080 H.264, AAC audio, Polish).
|
||
Size: roughly 1.2-1.5 GB per 45-minute episode at full quality.
|
||
|
||
|
||
EXAMPLE SESSION
|
||
---------------
|
||
|
||
# List all episodes of "Wojna zastępcza" and download them all:
|
||
python3 tvp_download.py --series 2726890
|
||
|
||
# Output:
|
||
# Found 13 episodes:
|
||
# E01 | 2726892 | odc. 1 – Werbunek
|
||
# E02 | 2785411 | odc. 2 – Legnica
|
||
# ...
|
||
# ============================================================
|
||
# Product 2726892: 'odc. 1 – Werbunek' [EPISODE]
|
||
# Key: d431b94c...:c3e2e41c...
|
||
# Running: N_m3u8DL-RE 2726892_odc__1___Werbunek
|
||
# [+] Saved: s01e01_werbunek.mp4 (1248 MB)
|
||
# ...
|
||
|
||
|
||
TROUBLESHOOTING
|
||
---------------
|
||
|
||
"No .wvd file found":
|
||
The Widevine device file is missing from ./L3/. The file
|
||
google_sdk_gphone64_x86_64_3198748f_22596_l3.wvd must be present.
|
||
|
||
"Playlist fetch failed" / 404:
|
||
The product ID may be wrong, or the content may have been removed from TVP VOD.
|
||
Verify the URL on vod.tvp.pl.
|
||
|
||
"License server 406":
|
||
Do not add Accept: application/json to the license POST request.
|
||
The fetch_content_key() function uses only Content-Type: application/octet-stream.
|
||
|
||
"No CONTENT keys returned":
|
||
The WVD file may have system_id=0 (broken). The script auto-selects the WVD
|
||
with a non-zero system_id. If all WVDs have system_id=0, the device file needs
|
||
to be regenerated.
|
||
|
||
Download hangs at 0%:
|
||
Avoid piping N_m3u8DL-RE output through head or other tools — the broken pipe
|
||
stalls the process. Always let it write to stdout directly or redirect to a file.
|