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.
