# API key for The Movie Database (TMDB) tmdb_api_key: "" # Client ID for SIMKL API (optional, improves metadata matching) # Get your free client ID at: https://simkl.com/settings/developer/ simkl_client_id: "" # Group or Username to postfix to the end of all download filenames following a dash tag: user_tag # Enable/disable tagging with group name (default: true) tag_group_name: true # Enable/disable tagging with IMDB/TMDB/TVDB details (default: true) tag_imdb_tmdb: true # Set terminal background color (custom option not in CONFIG.md) set_terminal_bg: false # Custom output templates for filenames # Configure output_template in your unshackle.yaml to control filename format. # If not configured, default scene-style templates are used and a warning is shown. # Available variables: {title}, {year}, {season}, {episode}, {season_episode}, {episode_name}, # {quality}, {resolution}, {source}, {audio}, {audio_channels}, {audio_full}, # {video}, {hdr}, {hfr}, {atmos}, {dual}, {multi}, {tag}, {edition}, {repack}, # {lang_tag} # Conditional variables (included only if present): Add ? suffix like {year?}, {episode_name?}, {hdr?} # Customize the templates below: # # Example outputs: # Scene movies: 'The.Matrix.1999.1080p.SERVICE.WEB-DL.DDP5.1.H.264-EXAMPLE' # Scene movies (HDR): 'Dune.2021.2160p.SERVICE.WEB-DL.DDP5.1.HDR10.H.265-EXAMPLE' # Scene movies (REPACK): 'Dune.2021.REPACK.2160p.SERVICE.WEB-DL.DDP5.1.H.265-EXAMPLE' # Scene series: 'Breaking.Bad.2008.S01E01.Pilot.1080p.SERVICE.WEB-DL.DDP5.1.H.264-EXAMPLE' # Plex movies: 'The Matrix (1999) 1080p' # Plex series: 'Breaking Bad S01E01 Pilot' output_template: # Scene-style naming (dot-separated) movies: '{title}.{year}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}' series: '{title}.{year?}.{season_episode}.{episode_name?}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}' songs: '{track_number}.{title}.{repack?}.{edition?}.{source?}.WEB-DL.{audio_full}.{atmos?}-{tag}' # # Plex-friendly naming (space-separated, clean format) # movies: '{title} ({year}) {quality}' # series: '{title} {season_episode} {episode_name?}' # songs: '{track_number}. {title}' # # Minimal naming (basic info only) # movies: '{title}.{year}.{quality}' # series: '{title}.{season_episode}.{episode_name?}' # # Custom scene-style with specific elements # movies: '{title}.{year}.{quality}.{hdr?}.{source}.WEB-DL.{audio_full}.{video}-{tag}' # series: '{title}.{year?}.{season_episode}.{episode_name?}.{quality}.{hdr?}.{source}.WEB-DL.{audio_full}.{atmos?}.{video}-{tag}' # Language-based tagging for output filenames # Automatically adds language identifiers (e.g., DANiSH, NORDiC, DKsubs) based on # audio and subtitle track languages. Rules are evaluated in order; first match wins. # Use {lang_tag?} in your output_template to place the tag in the filename. # # Conditions (all conditions in a rule must match): # audio: - any audio track matches this language # subs_contain: - any subtitle matches this language # subs_contain_all: [lang, ...] - subtitles include ALL listed languages # # language_tags: # rules: # - audio: da # tag: DANiSH # - audio: sv # tag: SWEDiSH # - audio: nb # tag: NORWEGiAN # - audio: en # subs_contain_all: [da, sv, nb] # tag: NORDiC # - audio: en # subs_contain: da # tag: DKsubs # Check for updates from GitHub repository on startup (default: true) update_checks: true # How often to check for updates, in hours (default: 24) update_check_interval: 24 # Title caching configuration # Cache title metadata to reduce redundant API calls title_cache_enabled: true # Enable/disable title caching globally (default: true) title_cache_time: 1800 # Cache duration in seconds (default: 1800 = 30 minutes) title_cache_max_retention: 86400 # Maximum cache retention for fallback when API fails (default: 86400 = 24 hours) # Filename Configuration unicode_filenames: false # optionally replace non-ASCII characters with ASCII equivalents # Debug logging configuration # Comprehensive JSON-based debug logging for troubleshooting and service development debug: false # Enable structured JSON debug logging (default: false) # When enabled with --debug flag or set to true: # - Creates JSON Lines (.jsonl) log files with complete debugging context # - Logs: session info, CLI params, service config, CDM details, authentication, # titles, tracks metadata, DRM operations, vault queries, errors with stack traces # - File location: logs/unshackle_debug_{service}_{timestamp}.jsonl # - Also creates text log: logs/unshackle_root_{timestamp}.log debug_keys: false # Log decryption keys in debug logs (default: false) # Set to true to include actual decryption keys in logs # Useful for debugging key retrieval and decryption issues # SECURITY NOTE: Passwords, tokens, cookies, and session tokens # are ALWAYS redacted regardless of this setting # Only affects: content_key, key fields (the actual CEKs) # Never affects: kid, keys_count, key_id (metadata is always logged) # Muxing configuration muxing: set_title: false # merge_audio: Merge all audio tracks into each output file # true (default): All selected audio in one MKV per quality # false: Separate MKV per (quality, audio_codec) combination # Example: Title.1080p.AAC.mkv, Title.1080p.EC3.mkv merge_audio: true # Login credentials for each Service credentials: # Direct credentials (no profile support) EXAMPLE: email@example.com:password # Per-profile credentials with default fallback SERVICE_NAME: default: default@email.com:password # Used when no -p/--profile is specified profile1: user1@email.com:password1 profile2: user2@email.com:password2 # Per-profile credentials without default (requires -p/--profile) SERVICE_NAME2: john: john@example.com:johnspassword jane: jane@example.com:janespassword # You can also use list format for passwords with special characters SERVICE_NAME3: default: ["user@email.com", ":PasswordWith:Colons"] # Override default directories used across unshackle directories: cache: Cache cookies: Cookies dcsl: DCSL # Device Certificate Status List downloads: Downloads logs: Logs temp: Temp wvds: WVDs prds: PRDs # Additional directories that can be configured: # commands: Commands services: - /path/to/services - /other/path/to/services # vaults: Vaults # fonts: Fonts # Pre-define which Widevine or PlayReady device to use for each Service cdm: # Global default CDM device (fallback for all services/profiles) default: WVD_1 # Direct service-specific CDM DIFFERENT_EXAMPLE: PRD_1 # Per-profile CDM configuration EXAMPLE: john_sd: chromecdm_903_l3 # Profile 'john_sd' uses Chrome CDM L3 jane_uhd: nexus_5_l1 # Profile 'jane_uhd' uses Nexus 5 L1 default: generic_android_l3 # Default CDM for this service # NEW: Quality-based CDM selection # Use different CDMs based on video resolution # Supports operators: >=, >, <=, <, or exact match EXAMPLE_QUALITY: "<=1080": generic_android_l3 # Use L3 for 1080p and below ">1080": nexus_5_l1 # Use L1 for above 1080p (1440p, 2160p) default: generic_android_l3 # Optional: fallback if no quality match # You can mix profiles and quality thresholds in the same service NETFLIX: # Profile-based selection (existing functionality) john: netflix_l3_profile jane: netflix_l1_profile # Quality-based selection (new functionality) "<=720": netflix_mobile_l3 "1080": netflix_standard_l3 ">=1440": netflix_premium_l1 # Fallback default: netflix_standard_l3 # Use pywidevine Serve-compliant Remote CDMs # Example: Custom CDM API Configuration # This demonstrates the highly configurable custom_api type that can adapt to any CDM API format # - name: "chrome" # type: "custom_api" # host: "http://remotecdm.test/" # timeout: 30 # device: # name: "ChromeCDM" # type: "CHROME" # system_id: 34312 # security_level: 3 # auth: # type: "header" # header_name: "x-api-key" # key: "YOUR_API_KEY_HERE" # custom_headers: # User-Agent: "Unshackle/2.0.0" # endpoints: # get_request: # path: "/get-challenge" # method: "POST" # timeout: 30 # decrypt_response: # path: "/get-keys" # method: "POST" # timeout: 30 # request_mapping: # get_request: # param_names: # scheme: "device" # init_data: "init_data" # static_params: # scheme: "Widevine" # decrypt_response: # param_names: # scheme: "device" # license_request: "license_request" # license_response: "license_response" # static_params: # scheme: "Widevine" # response_mapping: # get_request: # fields: # challenge: "challenge" # session_id: "session_id" # message: "message" # message_type: "message_type" # response_types: # - condition: "message_type == 'license-request'" # type: "license_request" # success_conditions: # - "message == 'success'" # decrypt_response: # fields: # keys: "keys" # message: "message" # key_fields: # kid: "kid" # key: "key" # type: "type" # success_conditions: # - "message == 'success'" # caching: # enabled: true # use_vaults: true # check_cached_first: true remote_cdm: - name: "chrome" device_name: chrome device_type: CHROME system_id: 27175 security_level: 3 host: https://domain.com/api secret: secret_key - name: "chrome-2" device_name: chrome device_type: CHROME system_id: 26830 security_level: 3 host: https://domain-2.com/api secret: secret_key - name: "decrypt_labs_chrome" type: "decrypt_labs" # Required to identify as DecryptLabs CDM device_name: "ChromeCDM" # Scheme identifier - must match exactly device_type: CHROME system_id: 4464 # Doesn't matter security_level: 3 host: "https://keyxtractor.decryptlabs.com" secret: "your_decrypt_labs_api_key_here" # Replace with your API key - name: "decrypt_labs_l1" type: "decrypt_labs" device_name: "L1" # Scheme identifier - must match exactly device_type: ANDROID system_id: 4464 security_level: 1 host: "https://keyxtractor.decryptlabs.com" secret: "your_decrypt_labs_api_key_here" - name: "decrypt_labs_l2" type: "decrypt_labs" device_name: "L2" # Scheme identifier - must match exactly device_type: ANDROID system_id: 4464 security_level: 2 host: "https://keyxtractor.decryptlabs.com" secret: "your_decrypt_labs_api_key_here" - name: "decrypt_labs_playready_sl2" type: "decrypt_labs" device_name: "SL2" # Scheme identifier - must match exactly device_type: PLAYREADY system_id: 0 security_level: 2000 host: "https://keyxtractor.decryptlabs.com" secret: "your_decrypt_labs_api_key_here" - name: "decrypt_labs_playready_sl3" type: "decrypt_labs" device_name: "SL3" # Scheme identifier - must match exactly device_type: PLAYREADY system_id: 0 security_level: 3000 host: "https://keyxtractor.decryptlabs.com" secret: "your_decrypt_labs_api_key_here" # PyPlayReady RemoteCdm - connects to an unshackle serve instance - name: "playready_remote" device_name: "my_prd_device" # Device name on the serve instance device_type: PLAYREADY system_id: 0 security_level: 3000 # 2000 for SL2000, 3000 for SL3000 host: "http://127.0.0.1:8786/playready" # Include /playready path secret: "your-api-secret-key" # Key Vaults store your obtained Content Encryption Keys (CEKs) # Use 'no_push: true' to prevent a vault from receiving pushed keys # while still allowing it to provide keys when requested key_vaults: - type: SQLite name: Local path: key_store.db # Additional vault types: # - type: API # name: "Remote Vault" # uri: "https://key-vault.example.com" # token: "secret_token" # no_push: true # This vault will only provide keys, not receive them # - type: MySQL # name: "MySQL Vault" # host: "127.0.0.1" # port: 3306 # database: vault # username: user # password: pass # no_push: false # Default behavior - vault both provides and receives keys # Choose what software to use to download data downloader: aria2c # Options: requests | aria2c | curl_impersonate | n_m3u8dl_re # Can also be a mapping: # downloader: # NF: requests # AMZN: n_m3u8dl_re # DSNP: n_m3u8dl_re # default: requests # aria2c downloader configuration aria2c: max_concurrent_downloads: 4 max_connection_per_server: 3 split: 5 file_allocation: falloc # none | prealloc | falloc | trunc # N_m3u8DL-RE downloader configuration n_m3u8dl_re: thread_count: 16 ad_keyword: "advertisement" use_proxy: true # curl_impersonate downloader configuration curl_impersonate: browser: chrome120 # Pre-define default options and switches of the dl command dl: sub_format: srt downloads: 4 workers: 16 lang: - en - fr EXAMPLE: bitrate: CBR # Chapter Name to use when exporting a Chapter without a Name chapter_fallback_name: "Chapter {j:02}" # Case-Insensitive dictionary of headers for all Services headers: Accept-Language: "en-US,en;q=0.8" User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36" # Override default filenames used across unshackle filenames: debug_log: "unshackle_debug_{service}_{time}.jsonl" # JSON Lines debug log file config: "config.yaml" root_config: "unshackle.yaml" chapters: "Chapters_{title}_{random}.txt" subtitle: "Subtitle_{id}_{language}.srt" # conversion_method: # - auto (default): Smart routing - subby for WebVTT/SAMI, pycaption for others # - subby: Always use subby with advanced processing # - pycaption: Use only pycaption library (no SubtitleEdit, no subby) # - subtitleedit: Prefer SubtitleEdit when available, fall back to pycaption # - pysubs2: Use pysubs2 library (supports SRT/SSA/ASS/WebVTT/TTML/SAMI/MicroDVD/MPL2/TMP) subtitle: conversion_method: auto # sdh_method: Method to use for SDH (hearing impaired) stripping # - auto (default): Try subby (SRT only), then SubtitleEdit (if available), then subtitle-filter # - subby: Use subby library (SRT only) # - subtitleedit: Use SubtitleEdit tool (Windows only, falls back to subtitle-filter) # - filter-subs: Use subtitle-filter library directly sdh_method: auto # strip_sdh: Automatically create stripped (non-SDH) versions of SDH subtitles # Set to false to disable automatic SDH stripping entirely (default: true) strip_sdh: true # convert_before_strip: Auto-convert VTT/other formats to SRT before using subtitle-filter # This ensures compatibility when subtitle-filter is used as fallback (default: true) convert_before_strip: true # preserve_formatting: Preserve original subtitle formatting (tags, positioning, styling) # When true, skips pycaption processing for WebVTT files to keep tags like , , positioning intact # Combined with no sub_format setting, ensures subtitles remain in their original format (default: true) preserve_formatting: true # output_mode: Output mode for subtitles # - mux: Embed subtitles in MKV container only (default) # - sidecar: Save subtitles as separate files only # - both: Embed in MKV AND save as sidecar files output_mode: mux # sidecar_format: Format for sidecar subtitle files # Options: srt, vtt, ass, original (keep current format) sidecar_format: srt # Configuration for pywidevine and pyplayready's serve functionality serve: api_secret: "your-secret-key-here" users: secret_key_for_user: devices: # Widevine devices (WVDs) this user can access - generic_nexus_4464_l3 playready_devices: # PlayReady devices (PRDs) this user can access - playready_device_sl3000 username: user # devices: # Widevine device paths (auto-populated from directories.wvds) # - '/path/to/device.wvd' # playready_devices: # PlayReady device paths (auto-populated from directories.prds) # - '/path/to/device.prd' # Configuration data for each Service services: # Service-specific configuration goes here # Profile-specific configurations can be nested under service names # You can override ANY global configuration option on a per-service basis # This allows fine-tuned control for services with special requirements # Supported overrides: dl, aria2c, n_m3u8dl_re, curl_impersonate, subtitle, muxing, headers, etc. # Example: Comprehensive service configuration showing all features EXAMPLE: # Standard service config api_key: "service_api_key" # Service certificate for Widevine L1/L2 (base64 encoded) # This certificate is automatically used when L1/L2 schemes are selected # Services obtain this from their DRM provider or license server certificate: | CAUSwwUKvQIIAxIQ5US6QAvBDzfTtjb4tU/7QxiH8c+TBSKOAjCCAQoCggEBAObzvlu2hZRsapAPx4Aa4GUZj4/GjxgXUtBH4THSkM40x63wQeyVxlEEo # ... (full base64 certificate here) # Profile-specific device configurations profiles: john_sd: device: app_name: "AIV" device_model: "SHIELD Android TV" jane_uhd: device: app_name: "AIV" device_model: "Fire TV Stick 4K" # Service-specific proxy mappings # Override global proxy selection with specific servers for this service # When --proxy matches a key in proxy_map, the mapped server will be used # instead of the default/random server selection proxy_map: nordvpn:ca: ca1577 # Use ca1577 when --proxy nordvpn:ca is specified nordvpn:us: us9842 # Use us9842 when --proxy nordvpn:us is specified us: 123 # Use server 123 (from any provider) when --proxy us is specified gb: 456 # Use server 456 (from any provider) when --proxy gb is specified # Without this service, --proxy nordvpn:ca picks a random CA server # With this config, --proxy nordvpn:ca EXAMPLE uses ca1577 specifically # Other services or no service specified will still use random selection # NEW: Configuration overrides (can be combined with profiles and certificates) # Override dl command defaults for this service dl: downloads: 4 # Limit concurrent track downloads (global default: 6) workers: 8 # Reduce workers per track (global default: 16) lang: ["en", "es-419"] # Different language priority for this service sub_format: srt # Force SRT subtitle format # Override n_m3u8dl_re downloader settings n_m3u8dl_re: thread_count: 8 # Lower thread count for rate-limited service (global default: 16) use_proxy: true # Force proxy usage for this service retry_count: 10 # More retries for unstable connections ad_keyword: "advertisement" # Service-specific ad filtering # Override aria2c downloader settings aria2c: max_concurrent_downloads: 2 # Limit concurrent downloads (global default: 4) max_connection_per_server: 1 # Single connection per server split: 3 # Fewer splits (global default: 5) file_allocation: none # Faster allocation for this service # Override subtitle processing for this service subtitle: conversion_method: pycaption # Use specific subtitle converter sdh_method: auto # Service-specific headers headers: User-Agent: "Service-specific user agent string" Accept-Language: "en-US,en;q=0.9" # Override muxing options muxing: set_title: true # Example: Service with different regions per profile SERVICE_NAME: profiles: us_account: region: "US" api_endpoint: "https://api.us.service.com" uk_account: region: "GB" api_endpoint: "https://api.uk.service.com" # Example: Rate-limited service RATE_LIMITED_SERVICE: dl: downloads: 2 # Limit concurrent downloads workers: 4 # Reduce workers to avoid rate limits n_m3u8dl_re: thread_count: 4 # Very low thread count retry_count: 20 # More retries for flaky service aria2c: max_concurrent_downloads: 1 # Download tracks one at a time max_connection_per_server: 1 # Single connection only # Notes on service-specific overrides: # - Overrides are merged with global config, not replaced # - Only specified keys are overridden, others use global defaults # - Reserved keys (profiles, api_key, certificate, etc.) are NOT treated as overrides # - Any dict-type config option can be overridden (dl, aria2c, n_m3u8dl_re, subtitle, etc.) # - CLI arguments always take priority over service-specific config # External proxy provider services proxy_providers: nordvpn: username: username_from_service_credentials password: password_from_service_credentials # server_map: global mapping that applies to ALL services # Difference from service-specific proxy_map: # - server_map: applies to ALL services when --proxy nordvpn:us is used # - proxy_map: only applies to the specific service configured (see services: EXAMPLE: proxy_map above) # - proxy_map takes precedence over server_map for that service server_map: us: 12 # force US server #12 for US proxies ca:calgary: 2534 # force CA server #2534 for Calgary proxies us:seattle: 7890 # force US server #7890 for Seattle proxies surfsharkvpn: username: your_surfshark_service_username # Service credentials from https://my.surfshark.com/vpn/manual-setup/main/openvpn password: your_surfshark_service_password # Service credentials (not your login password) server_map: us: 3844 # force US server #3844 for US proxies gb: 2697 # force GB server #2697 for GB proxies au: 4621 # force AU server #4621 for AU proxies us:seattle: 5678 # force US server #5678 for Seattle proxies ca:toronto: 1234 # force CA server #1234 for Toronto proxies windscribevpn: username: your_windscribe_username # Service credentials from https://windscribe.com/getconfig/openvpn password: your_windscribe_password # Service credentials (not your login password) server_map: us: "us-central-096.totallyacdn.com" # force US server gb: "uk-london-055.totallyacdn.com" # force GB server us:seattle: "us-west-011.totallyacdn.com" # force US Seattle server ca:toronto: "ca-toronto-012.totallyacdn.com" # force CA Toronto server # Gluetun: Dynamic Docker-based VPN proxy (supports 50+ VPN providers) # Creates Docker containers running Gluetun to bridge VPN connections to HTTP proxies # Requires Docker to be installed and running # Usage: --proxy gluetun:windscribe:us or --proxy gluetun:nordvpn:de gluetun: # Global settings base_port: 8888 # Starting port for HTTP proxies (increments for each container) auto_cleanup: true # Automatically remove containers when done container_prefix: "unshackle-gluetun" # Docker container name prefix verify_ip: true # Verify VPN IP matches expected region # Optional HTTP proxy authentication (for the proxy itself, not VPN) # auth_user: proxy_user # auth_password: proxy_password # VPN provider configurations providers: # Windscribe (WireGuard) - Get credentials from https://windscribe.com/getconfig/wireguard windscribe: vpn_type: wireguard credentials: private_key: "YOUR_WIREGUARD_PRIVATE_KEY" addresses: "YOUR_WIREGUARD_ADDRESS" # e.g., "10.x.x.x/32" # Map friendly names to country codes server_countries: us: US uk: GB ca: CA de: DE # NordVPN (OpenVPN) - Get service credentials from https://my.nordaccount.com/dashboard/nordvpn/manual-configuration/ # Note: Service credentials are NOT your email+password - generate them from the link above # nordvpn: # vpn_type: openvpn # credentials: # username: "YOUR_NORDVPN_SERVICE_USERNAME" # password: "YOUR_NORDVPN_SERVICE_PASSWORD" # server_countries: # us: US # uk: GB # ExpressVPN (OpenVPN) - Get credentials from ExpressVPN setup page # expressvpn: # vpn_type: openvpn # credentials: # username: "YOUR_EXPRESSVPN_USERNAME" # password: "YOUR_EXPRESSVPN_PASSWORD" # server_countries: # us: US # uk: GB # Surfshark (WireGuard) - Get credentials from https://my.surfshark.com/vpn/manual-setup/main/wireguard # surfshark: # vpn_type: wireguard # credentials: # private_key: "YOUR_SURFSHARK_PRIVATE_KEY" # addresses: "YOUR_SURFSHARK_ADDRESS" # server_countries: # us: US # uk: GB # Specific server selection: Use format like "us1239" to select specific servers # Example: --proxy gluetun:nordvpn:us1239 connects to us1239.nordvpn.com # Supported providers: nordvpn, surfshark, expressvpn, cyberghost basic: GB: - "socks5://username:password@bhx.socks.ipvanish.com:1080" # 1 (Birmingham) - "socks5://username:password@gla.socks.ipvanish.com:1080" # 2 (Glasgow) AU: - "socks5://username:password@syd.socks.ipvanish.com:1080" # 1 (Sydney) - "https://username:password@au-syd.prod.surfshark.com" # 2 (Sydney) - "https://username:password@au-bne.prod.surfshark.com" # 3 (Brisbane) BG: "https://username:password@bg-sof.prod.surfshark.com"