diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index 7770d75..60e361a 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -506,6 +506,12 @@ class dl: @click.option( "--reset-cache", "reset_cache", is_flag=True, default=False, help="Clear title cache before fetching." ) + @click.option( + "--worst", + is_flag=True, + default=False, + help="Select the lowest bitrate track within the specified quality. Requires -q/--quality.", + ) @click.option( "--best-available", "best_available", @@ -991,6 +997,7 @@ class dl: no_mux: bool, workers: Optional[int], downloads: int, + worst: bool, best_available: bool, split_audio: Optional[bool] = None, *_: Any, @@ -1016,6 +1023,10 @@ class dl: self.log.error("--require-subs and --s-lang cannot be used together") sys.exit(1) + if worst and not quality: + self.log.error("--worst requires -q/--quality to be specified") + sys.exit(1) + if select_titles and wanted: self.log.error("--select-titles and -w/--wanted cannot be used together") sys.exit(1) @@ -1609,20 +1620,18 @@ class dl: for resolution, color_range, codec in product( quality or [None], non_hybrid_ranges, vcodec or [None] ): - match = next( - ( - t - for t in non_hybrid_tracks - if ( - not resolution - or t.height == resolution - or int(t.width * (9 / 16)) == resolution - ) - and (not color_range or t.range == color_range) - and (not codec or t.codec == codec) - ), - None, - ) + candidates = [ + t + for t in non_hybrid_tracks + if ( + not resolution + or t.height == resolution + or int(t.width * (9 / 16)) == resolution + ) + and (not color_range or t.range == color_range) + and (not codec or t.codec == codec) + ] + match = candidates[-1] if worst and candidates else next(iter(candidates), None) if match and match not in non_hybrid_selected: non_hybrid_selected.append(match) @@ -1632,20 +1641,18 @@ class dl: for resolution, color_range, codec in product( quality or [None], range_ or [None], vcodec or [None] ): - match = next( - ( - t - for t in title.tracks.videos - if ( - not resolution - or t.height == resolution - or int(t.width * (9 / 16)) == resolution - ) - and (not color_range or t.range == color_range) - and (not codec or t.codec == codec) - ), - None, - ) + candidates = [ + t + for t in title.tracks.videos + if ( + not resolution + or t.height == resolution + or int(t.width * (9 / 16)) == resolution + ) + and (not color_range or t.range == color_range) + and (not codec or t.codec == codec) + ] + match = candidates[-1] if worst and candidates else next(iter(candidates), None) if match and match not in selected_videos: selected_videos.append(match) title.tracks.videos = selected_videos diff --git a/unshackle/core/session.py b/unshackle/core/session.py index 051dce4..974dd95 100644 --- a/unshackle/core/session.py +++ b/unshackle/core/session.py @@ -44,6 +44,17 @@ FINGERPRINT_PRESETS = { "akamai": "4:16777216|16711681|0|m,p,a,s", "description": "OkHttp 5.x (BoringSSL TLS stack)", }, + "shield_okhttp": { + "ja3": ( + "771," # TLS 1.2 + "4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53," # Ciphers (OkHttp 4.11) + "0-23-65281-10-11-35-16-5-13-51-45-43-21," # Extensions (incl padding ext 21) + "29-23-24," # Named groups (x25519, secp256r1, secp384r1) + "0" # EC point formats + ), + "akamai": "4:16777216|16711681|0|m,p,a,s", + "description": "NVIDIA SHIELD Android TV OkHttp 4.11 (captured JA3)", + }, }