freeflix-cli 2.2.2__tar.gz → 2.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/CHANGELOG.md +21 -0
  2. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/PKG-INFO +5 -3
  3. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/README.md +4 -2
  4. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/pyproject.toml +1 -1
  5. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/match.py +51 -5
  6. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/i18n.py +2 -0
  7. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/install_health.py +1 -1
  8. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/yacine_match.py +4 -0
  9. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_yacine_match.py +42 -0
  10. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/uv.lock +1 -1
  11. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/.github/workflows/binaries.yml +0 -0
  12. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/.github/workflows/ci.yml +0 -0
  13. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/.github/workflows/macos.yml +0 -0
  14. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/.github/workflows/release.yml +0 -0
  15. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/.gitignore +0 -0
  16. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/CONTRIBUTING.md +0 -0
  17. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/LICENSE +0 -0
  18. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/SECURITY.md +0 -0
  19. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/assets/logo.png +0 -0
  20. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/config/freeflix_position.lua +0 -0
  21. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/config/input.conf +0 -0
  22. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/config/mpv.conf +0 -0
  23. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/data/selectors.jsonc +0 -0
  24. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/data/source_portal.jsonc +0 -0
  25. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/docs/ARCHITECTURE.md +0 -0
  26. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/docs/AUDIT-FREEFLIX-2.0-2026-11-04.md +0 -0
  27. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/docs/CORRECTIONS-2.1.0.md +0 -0
  28. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/docs/README.md +0 -0
  29. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/docs/RELEASE.md +0 -0
  30. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/docs/SCORECARD-2.1.0.md +0 -0
  31. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/docs/TROUBLESHOOTING.md +0 -0
  32. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/docs/USAGE.md +0 -0
  33. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/scripts/build-binary.sh +0 -0
  34. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/scripts/install-mac.sh +0 -0
  35. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/scripts/install.ps1 +0 -0
  36. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/scripts/install.py +0 -0
  37. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/scripts/install.sh +0 -0
  38. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/scripts/wrappers/mpv +0 -0
  39. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/__init__.py +0 -0
  40. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/__main__.py +0 -0
  41. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/anilist.py +0 -0
  42. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/cli.py +0 -0
  43. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/cli_utils.py +0 -0
  44. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/cloudflare.py +0 -0
  45. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/config_loader.py +0 -0
  46. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/defaults.py +0 -0
  47. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/doctor.py +0 -0
  48. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/__init__.py +0 -0
  49. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/anilist.py +0 -0
  50. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/anime_sama.py +0 -0
  51. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/coflix.py +0 -0
  52. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/french_manga.py +0 -0
  53. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/french_stream.py +0 -0
  54. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/goldenanime.py +0 -0
  55. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/goldenms.py +0 -0
  56. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/hianime.py +0 -0
  57. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/nyaa.py +0 -0
  58. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/papystreaming.py +0 -0
  59. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/playback.py +0 -0
  60. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/handlers/yts.py +0 -0
  61. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/health.py +0 -0
  62. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/history_ui.py +0 -0
  63. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/httpcache.py +0 -0
  64. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/icons.py +0 -0
  65. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/languages.py +0 -0
  66. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/logsetup.py +0 -0
  67. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/main.py +0 -0
  68. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/motion.py +0 -0
  69. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/mpv_ipc.py +0 -0
  70. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/neon.py +0 -0
  71. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/net_config.py +0 -0
  72. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/notifications.py +0 -0
  73. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/platform_android.py +0 -0
  74. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/player_manager.py +0 -0
  75. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/preview_picker.py +0 -0
  76. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/progress.py +0 -0
  77. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/providers_registry.py +0 -0
  78. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/proxy.py +0 -0
  79. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/recent.py +0 -0
  80. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/__init__.py +0 -0
  81. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/anime_sama.py +0 -0
  82. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/anime_sama_planning.py +0 -0
  83. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/coflix.py +0 -0
  84. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/config.py +0 -0
  85. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/deobfuscate.py +0 -0
  86. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/eztv.py +0 -0
  87. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/french_manga.py +0 -0
  88. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/french_stream.py +0 -0
  89. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/goldenanime.py +0 -0
  90. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/goldenms.py +0 -0
  91. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/nyaa.py +0 -0
  92. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/objects.py +0 -0
  93. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/papystreaming.py +0 -0
  94. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/player.py +0 -0
  95. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/resilient.py +0 -0
  96. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/subtitles.py +0 -0
  97. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/utils.py +0 -0
  98. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/scraping/yts.py +0 -0
  99. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/setup_assistant.py +0 -0
  100. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/splash.py +0 -0
  101. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/subtitles.py +0 -0
  102. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/terminal_image.py +0 -0
  103. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/themes.py +0 -0
  104. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/tracker.py +0 -0
  105. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/ui.py +0 -0
  106. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/update_checker.py +0 -0
  107. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/src/freeflix_cli/yacine_live_proxy.py +0 -0
  108. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/conftest.py +0 -0
  109. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_android_setup.py +0 -0
  110. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_anilist_errors.py +0 -0
  111. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_anilist_token_input.py +0 -0
  112. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_anime_sama_planning.py +0 -0
  113. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_audit_fixes.py +0 -0
  114. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_batch_download.py +0 -0
  115. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_coflix_parse.py +0 -0
  116. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_core_units.py +0 -0
  117. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_cover_cache.py +0 -0
  118. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_decoy_stream.py +0 -0
  119. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_download_fixes.py +0 -0
  120. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_download_integration.py +0 -0
  121. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_download_robustness.py +0 -0
  122. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_episode_parsing.py +0 -0
  123. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_fsvid_decode.py +0 -0
  124. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_health.py +0 -0
  125. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_httpcache.py +0 -0
  126. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_i18n_complete.py +0 -0
  127. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_keyboard.py +0 -0
  128. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_managed_binaries.py +0 -0
  129. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_mouse.py +0 -0
  130. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_mpv_ipc.py +0 -0
  131. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_navigation.py +0 -0
  132. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_nerd_font.py +0 -0
  133. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_nyaa.py +0 -0
  134. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_pack_a_polish.py +0 -0
  135. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_performance.py +0 -0
  136. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_platform_android.py +0 -0
  137. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_player_dash.py +0 -0
  138. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_polish.py +0 -0
  139. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_premium_players.py +0 -0
  140. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_providers_repair.py +0 -0
  141. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_proxy_endpoint.py +0 -0
  142. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_proxy_integration.py +0 -0
  143. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_proxy_resilient.py +0 -0
  144. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_proxy_ssrf.py +0 -0
  145. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_recent_new_episodes.py +0 -0
  146. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_release_config.py +0 -0
  147. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_resilient.py +0 -0
  148. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_scraper_resilience.py +0 -0
  149. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_search_parity.py +0 -0
  150. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_sendvid.py +0 -0
  151. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_stream_analysis.py +0 -0
  152. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_stream_integrity.py +0 -0
  153. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_system_deps_cache.py +0 -0
  154. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_terminal_image.py +0 -0
  155. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_update_prompt.py +0 -0
  156. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_v1105_improvements.py +0 -0
  157. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tests/test_v2_aesthetic.py +0 -0
  158. {freeflix_cli-2.2.2 → freeflix_cli-2.3.1}/tools/audit_providers.py +0 -0
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.1
4
+
5
+ - **Qualités Match prêtes pour le futur** : 4K/2160p (≥ 15,0 Mbps,
6
+ ~6,75 Go/h) et 1440p (≥ 8,0 Mbps, ~3,60 Go/h) ; une qualité inconnue
7
+ garde une estimation prudente au lieu de casser l'affichage.
8
+ - Tests supplémentaires avec des équipes/compétitions arbitraires
9
+ (Al Ahly / Zamalek) et des chaînes hors beIN : la traduction et
10
+ l'affichage restent dynamiques.
11
+ - 607 tests au vert, ruff OK.
12
+
13
+ ## 2.3.0
14
+
15
+ - **Match** : la chaîne est affichée entre parenthèses **devant** le match,
16
+ avec un badge court (`beIN SPORTS 1..4` → `B1..B4`), ex.
17
+ `(B4) ● LIVE 20:45 · Toulouse VS Strasbourg`.
18
+ - **Qualités Match** : chaque ligne affiche désormais la résolution, le débit
19
+ minimum estimé et la consommation horaire estimée, ex.
20
+ `HD (1080p) · ≥ 5.0 Mbps · ~2.25 Go/h`, `SD (720p) · ≥ 2.5 Mbps · ~1.13 Go/h`,
21
+ `Low (480p) · ≥ 1.0 Mbps · ~0.45 Go/h` ; `Auto` reste en bas.
22
+ - 604 tests au vert, ruff OK.
23
+
3
24
  ## 2.2.2
4
25
 
5
26
  - Menus **qualité** et **choix du lecteur** en plein écran : souris alignée
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: freeflix-cli
3
- Version: 2.2.2
3
+ Version: 2.3.1
4
4
  Summary: Watch movies, series, and anime from your terminal — multi-language, multi-source, no browser required. A maintained fork of autoflix-cli.
5
5
  Project-URL: Homepage, https://github.com/freedy237/freeflix-cli
6
6
  Project-URL: Repository, https://github.com/freedy237/freeflix-cli.git
@@ -121,7 +121,7 @@ Description-Content-Type: text/markdown
121
121
  | 🆕 | **Nouveautés** : new episodes of the shows you follow, cleared once watched |
122
122
  | 🖱 | **Full mouse control** : hover, click, wheel, right-click back, clickable pagination |
123
123
  | 🎨 | **Cinema Noir** (2.0) design system, 12 themes, reduced-motion support |
124
- | ⚽ | **Match** : live football from YacineTV, Cameroon time, HD/SD/Low + Auto, mpv/VLC |
124
+ | ⚽ | **Match** : live football from YacineTV, Cameroon time, B1–B4 channel badges, HD/SD/Low/4K + Auto, minimum bitrate + GB/h estimate, mpv/VLC |
125
125
  | 🎬 | **Browse Films, Séries & Anime** : the provider list, grouped as before |
126
126
  | 🎮 | **4 player backends** : mpv, VLC, browser, download-only |
127
127
  | 🇫🇷 | **French UI** : the whole CLI is in French when language is set to FR |
@@ -247,7 +247,9 @@ That's it. The menu walks you through everything.
247
247
 
248
248
  - **Films, Séries & Anime** opens the provider list (unchanged grouping).
249
249
  - **Match** shows live/upcoming football with kick-off in Cameroon time
250
- (UTC+1); pick a match, then HD/SD/Low or **Auto**, then mpv or VLC.
250
+ (UTC+1), the channel badge (B1–B4), and quality rows with resolution,
251
+ minimum bitrate and estimated GB/hour; pick **Auto** or a quality, then
252
+ mpv or VLC.
251
253
  - Player settings are isolated: FreeFlix never edits your global mpv/VLC
252
254
  configuration.
253
255
 
@@ -89,7 +89,7 @@
89
89
  | 🆕 | **Nouveautés** : new episodes of the shows you follow, cleared once watched |
90
90
  | 🖱 | **Full mouse control** : hover, click, wheel, right-click back, clickable pagination |
91
91
  | 🎨 | **Cinema Noir** (2.0) design system, 12 themes, reduced-motion support |
92
- | ⚽ | **Match** : live football from YacineTV, Cameroon time, HD/SD/Low + Auto, mpv/VLC |
92
+ | ⚽ | **Match** : live football from YacineTV, Cameroon time, B1–B4 channel badges, HD/SD/Low/4K + Auto, minimum bitrate + GB/h estimate, mpv/VLC |
93
93
  | 🎬 | **Browse Films, Séries & Anime** : the provider list, grouped as before |
94
94
  | 🎮 | **4 player backends** : mpv, VLC, browser, download-only |
95
95
  | 🇫🇷 | **French UI** : the whole CLI is in French when language is set to FR |
@@ -215,7 +215,9 @@ That's it. The menu walks you through everything.
215
215
 
216
216
  - **Films, Séries & Anime** opens the provider list (unchanged grouping).
217
217
  - **Match** shows live/upcoming football with kick-off in Cameroon time
218
- (UTC+1); pick a match, then HD/SD/Low or **Auto**, then mpv or VLC.
218
+ (UTC+1), the channel badge (B1–B4), and quality rows with resolution,
219
+ minimum bitrate and estimated GB/hour; pick **Auto** or a quality, then
220
+ mpv or VLC.
219
221
  - Player settings are isolated: FreeFlix never edits your global mpv/VLC
220
222
  configuration.
221
223
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "freeflix-cli"
7
- version = "2.2.2"
7
+ version = "2.3.1"
8
8
  description = "Watch movies, series, and anime from your terminal — multi-language, multi-source, no browser required. A maintained fork of autoflix-cli."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9.2"
@@ -6,6 +6,8 @@ falls back to the last snapshot only when the API is unreachable.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import re
10
+
9
11
  from datetime import datetime, timedelta, timezone
10
12
 
11
13
  from ..cli_utils import (
@@ -33,6 +35,12 @@ def _when_label(ev: ym.MatchEvent, now: datetime) -> str:
33
35
  return f"{dt:%d/%m %H:%M}"
34
36
 
35
37
 
38
+ def _channel_label(channel: str) -> str:
39
+ """Short live-TV badge: beIN SPORTS 1..4 → B1..B4."""
40
+ m = re.search(r"be\s*in\s*sports?\s*([1-4])", channel or "", re.I)
41
+ return f"B{m.group(1)}" if m else (channel or "")
42
+
43
+
36
44
  def _match_label(ev: ym.MatchEvent, now_ts: int, now: datetime) -> str:
37
45
  state = ev.status(now_ts)
38
46
  when = _when_label(ev, now)
@@ -43,7 +51,8 @@ def _match_label(ev: ym.MatchEvent, now_ts: int, now: datetime) -> str:
43
51
  else:
44
52
  badge = f"○ {t('Upcoming')}"
45
53
  league = f" · {ev.championship}" if ev.championship else ""
46
- return f"{badge} {when} · {ev.home} VS {ev.away}{league}"
54
+ channel = f"({_channel_label(ev.channel)}) " if ev.channel else ""
55
+ return f"{channel}{badge} {when} · {ev.home} VS {ev.away}{league}"
47
56
 
48
57
 
49
58
  def _sort_events(events: list[ym.MatchEvent], now_ts: int) -> list[ym.MatchEvent]:
@@ -51,9 +60,40 @@ def _sort_events(events: list[ym.MatchEvent], now_ts: int) -> list[ym.MatchEvent
51
60
  return sorted(events, key=lambda e: (order.get(e.status(now_ts), 3), e.start))
52
61
 
53
62
 
63
+ # Conservative live HLS minimums (Mbps) used for the GB/hour estimate.
64
+ _MIN_MBPS = {2160: 15.0, 1440: 8.0, 1080: 5.0, 720: 2.5,
65
+ 480: 1.0, 360: 0.7}
66
+
67
+
68
+ def _quality_metrics(stream: dict) -> tuple[int | None, float, float]:
69
+ """(height, minimum Mbps, estimated GB/hour) for a stream label."""
70
+ name = str(stream.get("name") or "?")
71
+ low = name.lower()
72
+ m = re.search(r"(\d{3,4})p", low)
73
+ if m:
74
+ height = int(m.group(1))
75
+ elif any(k in low for k in ("4k", "uhd", "2160")):
76
+ height = 2160
77
+ elif any(k in low for k in ("1440", "2k")):
78
+ height = 1440
79
+ elif "hd" in low or "fhd" in low:
80
+ height = 1080
81
+ elif "sd" in low:
82
+ height = 720
83
+ elif "low" in low:
84
+ height = 480
85
+ else:
86
+ height = None
87
+ mbps = _MIN_MBPS.get(height, 2.0)
88
+ return height, mbps, mbps * 3600 / 8 / 1000
89
+
90
+
54
91
  def _quality_label(stream: dict) -> str:
55
92
  name = str(stream.get("name") or "?")
56
- return f"{icon('play')} {name}"
93
+ height, mbps, gb = _quality_metrics(stream)
94
+ res = f" ({height}p)" if height else ""
95
+ return (f"{icon('play')} {name}{res} · ≥ {mbps:.1f} Mbps · "
96
+ f"~{gb:.2f} {t('GB/h')}")
57
97
 
58
98
 
59
99
  def _play(event: ym.MatchEvent, stream: dict, player_name: str = "") -> None:
@@ -95,7 +135,7 @@ def _choose_player(current: str, allow_cancel: bool = False):
95
135
  print_warning(t("No video player found (mpv or VLC)."))
96
136
  return None
97
137
  labels = ["mpv" if n == "mpv" else "VLC" for n in avail]
98
- options = [f"{icon('play')} {label}" for label in labels]
138
+ options = list(labels)
99
139
  if allow_cancel:
100
140
  options.append(t("← Back"))
101
141
  default = labels.index(current) if current in labels else 0
@@ -165,7 +205,10 @@ def handle_match() -> None:
165
205
  choice = select_from_list(
166
206
  options,
167
207
  f"{t('Choose the quality')} · {event.home} VS {event.away}",
168
- header=f"{icon('football')} {event.championship or t('Football')}",
208
+ header=(f"{icon('football')} "
209
+ f"{event.championship or t('Football')}"
210
+ + (f" · {_channel_label(event.channel)}"
211
+ if event.channel else "")),
169
212
  )
170
213
  if choice < 0 or choice >= auto_idx:
171
214
  if choice == auto_idx:
@@ -173,7 +216,10 @@ def handle_match() -> None:
173
216
  if not stream:
174
217
  print_warning(t("No stream available for this match."))
175
218
  continue
176
- print_info(f"{t('Auto quality:')} {stream.get('name', '?')}")
219
+ _h, _mbps, _gb = _quality_metrics(stream)
220
+ print_info(f"{t('Auto quality:')} {stream.get('name', '?')} · "
221
+ f"≥ {_mbps:.1f} {t('Mbps')} · ~{_gb:.2f} "
222
+ f"{t('GB/h')}")
177
223
  chosen = _choose_player(player_name, allow_cancel=True)
178
224
  if chosen:
179
225
  _play(event, stream, chosen)
@@ -1023,6 +1023,8 @@ _FR = {
1023
1023
  'Playing in VLC…': 'Lecture dans VLC…',
1024
1024
  'VLC stopped early. Log:': 'VLC s’est arrêté trop tôt. Journal :',
1025
1025
  'Movies, Series & Anime': 'Films, Séries & Anime',
1026
+ 'Mbps': 'Mbps',
1027
+ 'GB/h': 'Go/h',
1026
1028
  'Match player:': 'Lecteur des matchs :',
1027
1029
  'Match player set to:': 'Lecteur des matchs :',
1028
1030
  'Choose a player': 'Choisis un lecteur',
@@ -13,7 +13,7 @@ import os
13
13
  import sys
14
14
 
15
15
  # Version BAKED INTO THE CODE — bump together with pyproject.toml.
16
- CODE_VERSION = "2.2.2"
16
+ CODE_VERSION = "2.3.1"
17
17
 
18
18
 
19
19
  def metadata_version() -> str | None:
@@ -220,6 +220,10 @@ def fetch_events(timeout: int = 10) -> tuple[list[MatchEvent], bool]:
220
220
 
221
221
  def _quality_rank(name: str) -> int:
222
222
  n = (name or "").lower()
223
+ if any(k in n for k in ("4k", "2160", "uhd")):
224
+ return 4
225
+ if any(k in n for k in ("1440", "2k")):
226
+ return 3
223
227
  if any(k in n for k in ("hd", "fhd", "1080", "full")):
224
228
  return 3
225
229
  if any(k in n for k in ("sd", "720", "medium")):
@@ -279,3 +279,45 @@ def test_play_direct_stream_respects_explicit_vlc(monkeypatch):
279
279
  assert pm._VLC_KEEPALIVE
280
280
  finally:
281
281
  pm._stop_vlc_keepalive()
282
+
283
+
284
+ def test_match_channel_and_quality_estimates():
285
+ from datetime import datetime, timedelta, timezone
286
+ from freeflix_cli.handlers import match as mh
287
+
288
+ now = datetime.now(tz=timezone(timedelta(hours=1)))
289
+ ev = ym.MatchEvent("1", "Ligue 1", "beIN SPORTS 4",
290
+ int(now.timestamp()) - 60, int(now.timestamp()) + 3600,
291
+ "Toulouse", "Strasbourg")
292
+ label = mh._match_label(ev, int(now.timestamp()), now)
293
+ assert label.startswith("(B4)")
294
+ hd = mh._quality_label({"name": "HD"})
295
+ assert "5.0" in hd and "2.25" in hd
296
+ low = mh._quality_label({"name": "Low"})
297
+ assert "1.0" in low and "0.45" in low
298
+
299
+
300
+ def test_channel_aliases_and_other_channels():
301
+ from freeflix_cli.handlers import match as mh
302
+ assert mh._channel_label("beIN SPORTS 1") == "B1"
303
+ assert mh._channel_label("beIN SPORTS 4") == "B4"
304
+ assert mh._channel_label("beIN SPORTS 5") == "beIN SPORTS 5"
305
+ assert mh._channel_label("Canal+ Sport") == "Canal+ Sport"
306
+
307
+
308
+ def test_future_4k_quality_estimate():
309
+ from freeflix_cli.handlers import match as mh
310
+ label = mh._quality_label({"name": "4K"})
311
+ assert "2160p" in label and "15.0" in label and "6.75" in label
312
+
313
+
314
+ def test_other_teams_are_dynamic(monkeypatch, tmp_path):
315
+ monkeypatch.setattr(ym, "_cache_dir", lambda: tmp_path)
316
+ monkeypatch.setattr(ym, "_translate_one",
317
+ lambda text, lang, timeout=5:
318
+ {"الأهلي": "Al Ahly", "الزمالك": "Zamalek"}.get(text, text))
319
+ ev = ym.MatchEvent("x", "الدوري المصري", "Canal+ Sport", 1, 2,
320
+ "الأهلي", "الزمالك")
321
+ out = ym.localize_events([ev], "fr")[0]
322
+ assert out.home == "Al Ahly" and out.away == "Zamalek"
323
+ assert out.championship == "الدوري المصري"
@@ -238,7 +238,7 @@ wheels = [
238
238
 
239
239
  [[package]]
240
240
  name = "freeflix-cli"
241
- version = "2.2.2"
241
+ version = "2.3.1"
242
242
  source = { editable = "." }
243
243
  dependencies = [
244
244
  { name = "beautifulsoup4" },
File without changes
File without changes
File without changes
File without changes