pywidevine's serve module expects users to be a dict mapping secret keys to user objects with devices and username, not a simple list.
This was causing TypeError when accessing CDM endpoints.
Session keys from master playlists often contain PSSHs with multiple KIDs covering all tracks, causing licensing to return keys for wrong KIDs.
Changes:
- Unified DRM licensing logic for all downloaders
- Prefer media playlist EXT-X-KEY tags which contain track-specific KIDs
- Add filter_keys_for_cdm() to select keys matching configured CDM type
- Add get_track_kid_from_init() to extract KID from init segment with fallback to drm.kid from PSSH
- Track initial_drm_key to prevent double-licensing on first segment
- Simplify n_m3u8dl_re block to reuse common licensing flow
- Use strict PlayReady keyformat matching via PR_PSSH.SYSTEM_ID URN instead of loose substring match
- Fix PlayReady keyformat comparisons that incorrectly compared strings to PlayReadyCdm class
- Fix byterange header format in get_track_kid_from_init() to use HLS.calculate_byte_range()
Also fixes PlayReady keyformat matching in:
- unshackle/core/tracks/track.py
- unshackle/core/drm/playready.py
Fixes download failures where track_kid was null or mismatched, causing wrong content keys to be obtained during PlayReady/Widevine licensing.
When DASH manifests have multiple audio AdaptationSets with the same representation IDs (e.g., both English and Japanese having id="0"), N_m3u8DL-RE would download the same track twice.
Now includes the language alongside the ID in selection args to properly disambiguate tracks across adaptation sets.
- Use lowercase format names (subrip, webvtt, advancedsubstationalpha) to match SubtitleEdit 4.x CLI requirements
- Change /Convert to /convert for consistency with CLI docs
- Convert Path objects to strings explicitly for subprocess calls
- Respect conversion_method config in SDH stripping - skip SubtitleEdit when user has set pysubs2/pycaption/subby as their preferred method
- Add stderr suppression to SubtitleEdit calls
Some services use WebVTT files with:
- Cue identifiers (Q0, Q1, etc.) before timing lines that pysubs2/pycaption incorrectly parses as subtitle text
- Multi-line subtitles split into separate cues with 1ms offset times and different line: positions (e.g., line:77% for top, line:84% for bottom)
Added detection and sanitization functions:
- has_webvtt_cue_identifiers(): detects cue identifiers before timing
- sanitize_webvtt_cue_identifiers(): removes problematic cue identifiers
- has_overlapping_webvtt_cues(): detects overlapping cues needing merge
- merge_overlapping_webvtt_cues(): merges cues sorted by line position
The previous regex only matched negative size values when they were the entire quoted attribute (e.g., "-5%"). This failed for multi-value attributes like tts:extent="-5% 7.5%" causing pycaption parse errors.
The new pattern matches negative values anywhere in the text and preserves the unit during replacement.
Closes#47
Add config option to disable ASCII transliteration in filenames, allowing preservation of Korean, Japanese, Chinese, and other native language characters instead of converting them via unidecode.
Closes#49
- Add skip_merge flag for N_m3u8DL-RE to prevent duplicate init data
- Pass content_keys to N_m3u8DL-RE for internal decryption handling
- Use shutil.move() instead of manual merge when skip_merge is True
- Skip manual decryption when N_m3u8DL-RE handles it internally
Fixes audio corruption ("Box 'OG 2' size is too large") when using N_m3u8DL-RE with DASH manifests that have SegmentBase init data. The init segment was being written twice: once by N_m3u8DL-RE during its internal merge, and again by dash.py during post-processing.
- Add CENC namespace support for kid/default_KID attributes
- Detect and replace placeholder/test KIDs in Widevine PSSH:
- All zeros (key rotation default)
- Sequential 0x00-0x0f pattern
- Shaka Packager test pattern
- Change DRM init condition from `not track.drm` to `init_data` to ensure DRM is always re-initialized from init segments
Fixes issue where Widevine PSSH contains placeholder KIDs while the real KID is only in ContentProtection default_KID attributes.
Remove erroneous `.bytes` accessor from PSSH.SYSTEM_ID comparisons in from_track() and from_init_data() methods. The pyplayready PSSH.SYSTEM_ID is already the correct type for comparison with parsed PSSH box system_ID values.
Add PlayReady PSSH/KID extraction from track and init data with CDM-aware ordering. When PlayReady CDM is selected, tries PlayReady first then falls back to Widevine. When Widevine CDM is selected (default), tries Widevine first then falls back to PlayReady.
Hybrid DV+HDR10 files were named "DV.H.265" instead of "DV.HDR.H.265" because the HDR10 detection only checked hdr_format_full which contains "Dolby Vision / SMPTE ST 2094". The "HDR10" indicator is in hdr_format_commercial, not hdr_format_full.
Now checks both fields for HDR10 compatibility indicators.
Remove space from structural chars regex so spaces are kept as-is
rather than being replaced with the spacer character.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Attachments (screenshots, fonts) were being dropped when title.tracks was rebuilt from kept_tracks, causing image files to remain in temp directory after muxing. The cleanup code iterated over an empty attachments list since they were orphaned during track filtering.
Add support for BaseURL elements at the AdaptationSet level per DASH spec. The URL resolution chain now properly follows: MPD → Period → AdaptationSet → Representation.
Pre-process space-hyphen-space patterns (e.g., "Title - Episode") before other character replacements to prevent creating problematic dot-hyphen-dot (.-.) patterns in filenames.
This addresses PR #44 by fixing the root cause rather than post-processing the problematic pattern. The fix ensures that titles like "Show - S01E01" become "Show.S01E01"
Enable quality-based CDM selection during runtime DRM switching by passing track quality to get_cdm() calls. This allows different CDMs to be used for different video quality levels within the same download session.
Example configuration:
cdm:
SERVICE:
"<=1080": wv_l3_local # Widevine L3 for SD/HD
">1080": pr_sl3_remote # PlayReady SL3 for 4K
- Add Primaries.Unspecified (value 2) per user request and H.273 spec
- Rename Primaries value 0 from Unspecified to Reserved for spec accuracy
- Rename Transfer value 0 from Unspecified to Reserved for consistency
- Simplify Transfer value 2 from Unspecified_Image to Unspecified
- Update condition check to use enum values instead of numeric tuple
- Enhance docstring with detailed sources and rationale for changes
All CICP values verified against ITU-T H.273, ISO/IEC 23091-2, H.264/H.265 specifications, and FFmpeg AVColorPrimaries/AVColorTransferCharacteristic enums.
Fixed TypeError in calculate_byte_range where range_offset was a string instead of int. The byte_range.split("-")[0] returns a string, but the calculate_byte_range method expects fallback_offset parameter to be int.