Files
video_extractor/tvp_README.txt
T

122 lines
3.6 KiB
Plaintext
Raw Normal View History

2026-08-08 12:35:28 +02:00
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.