freeflix-cli 2.1.2__tar.gz → 2.3.0__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.1.2 → freeflix_cli-2.3.0}/CHANGELOG.md +60 -0
  2. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/PKG-INFO +27 -9
  3. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/README.md +26 -8
  4. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/config/mpv.conf +8 -15
  5. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/pyproject.toml +1 -1
  6. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/cli_utils.py +51 -8
  7. freeflix_cli-2.3.0/src/freeflix_cli/handlers/match.py +227 -0
  8. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/i18n.py +48 -0
  9. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/icons.py +2 -0
  10. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/install_health.py +1 -1
  11. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/main.py +17 -7
  12. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/player_manager.py +281 -10
  13. freeflix_cli-2.3.0/src/freeflix_cli/scraping/yacine_match.py +337 -0
  14. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/setup_assistant.py +27 -16
  15. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/tracker.py +8 -0
  16. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/update_checker.py +36 -7
  17. freeflix_cli-2.3.0/src/freeflix_cli/yacine_live_proxy.py +197 -0
  18. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_audit_fixes.py +34 -0
  19. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_keyboard.py +6 -5
  20. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_update_prompt.py +18 -0
  21. freeflix_cli-2.3.0/tests/test_yacine_match.py +297 -0
  22. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/uv.lock +1 -1
  23. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/.github/workflows/binaries.yml +0 -0
  24. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/.github/workflows/ci.yml +0 -0
  25. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/.github/workflows/macos.yml +0 -0
  26. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/.github/workflows/release.yml +0 -0
  27. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/.gitignore +0 -0
  28. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/CONTRIBUTING.md +0 -0
  29. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/LICENSE +0 -0
  30. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/SECURITY.md +0 -0
  31. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/assets/logo.png +0 -0
  32. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/config/freeflix_position.lua +0 -0
  33. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/config/input.conf +0 -0
  34. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/data/selectors.jsonc +0 -0
  35. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/data/source_portal.jsonc +0 -0
  36. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/docs/ARCHITECTURE.md +0 -0
  37. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/docs/AUDIT-FREEFLIX-2.0-2026-11-04.md +0 -0
  38. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/docs/CORRECTIONS-2.1.0.md +0 -0
  39. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/docs/README.md +0 -0
  40. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/docs/RELEASE.md +0 -0
  41. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/docs/SCORECARD-2.1.0.md +0 -0
  42. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/docs/TROUBLESHOOTING.md +0 -0
  43. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/docs/USAGE.md +0 -0
  44. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/scripts/build-binary.sh +0 -0
  45. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/scripts/install-mac.sh +0 -0
  46. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/scripts/install.ps1 +0 -0
  47. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/scripts/install.py +0 -0
  48. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/scripts/install.sh +0 -0
  49. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/scripts/wrappers/mpv +0 -0
  50. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/__init__.py +0 -0
  51. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/__main__.py +0 -0
  52. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/anilist.py +0 -0
  53. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/cli.py +0 -0
  54. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/cloudflare.py +0 -0
  55. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/config_loader.py +0 -0
  56. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/defaults.py +0 -0
  57. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/doctor.py +0 -0
  58. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/__init__.py +0 -0
  59. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/anilist.py +0 -0
  60. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/anime_sama.py +0 -0
  61. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/coflix.py +0 -0
  62. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/french_manga.py +0 -0
  63. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/french_stream.py +0 -0
  64. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/goldenanime.py +0 -0
  65. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/goldenms.py +0 -0
  66. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/hianime.py +0 -0
  67. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/nyaa.py +0 -0
  68. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/papystreaming.py +0 -0
  69. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/playback.py +0 -0
  70. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/yts.py +0 -0
  71. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/health.py +0 -0
  72. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/history_ui.py +0 -0
  73. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/httpcache.py +0 -0
  74. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/languages.py +0 -0
  75. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/logsetup.py +0 -0
  76. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/motion.py +0 -0
  77. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/mpv_ipc.py +0 -0
  78. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/neon.py +0 -0
  79. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/net_config.py +0 -0
  80. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/notifications.py +0 -0
  81. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/platform_android.py +0 -0
  82. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/preview_picker.py +0 -0
  83. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/progress.py +0 -0
  84. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/providers_registry.py +0 -0
  85. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/proxy.py +0 -0
  86. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/recent.py +0 -0
  87. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/__init__.py +0 -0
  88. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/anime_sama.py +0 -0
  89. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/anime_sama_planning.py +0 -0
  90. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/coflix.py +0 -0
  91. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/config.py +0 -0
  92. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/deobfuscate.py +0 -0
  93. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/eztv.py +0 -0
  94. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/french_manga.py +0 -0
  95. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/french_stream.py +0 -0
  96. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/goldenanime.py +0 -0
  97. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/goldenms.py +0 -0
  98. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/nyaa.py +0 -0
  99. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/objects.py +0 -0
  100. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/papystreaming.py +0 -0
  101. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/player.py +0 -0
  102. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/resilient.py +0 -0
  103. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/subtitles.py +0 -0
  104. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/utils.py +0 -0
  105. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/yts.py +0 -0
  106. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/splash.py +0 -0
  107. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/subtitles.py +0 -0
  108. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/terminal_image.py +0 -0
  109. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/themes.py +0 -0
  110. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/src/freeflix_cli/ui.py +0 -0
  111. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/conftest.py +0 -0
  112. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_android_setup.py +0 -0
  113. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_anilist_errors.py +0 -0
  114. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_anilist_token_input.py +0 -0
  115. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_anime_sama_planning.py +0 -0
  116. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_batch_download.py +0 -0
  117. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_coflix_parse.py +0 -0
  118. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_core_units.py +0 -0
  119. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_cover_cache.py +0 -0
  120. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_decoy_stream.py +0 -0
  121. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_download_fixes.py +0 -0
  122. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_download_integration.py +0 -0
  123. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_download_robustness.py +0 -0
  124. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_episode_parsing.py +0 -0
  125. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_fsvid_decode.py +0 -0
  126. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_health.py +0 -0
  127. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_httpcache.py +0 -0
  128. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_i18n_complete.py +0 -0
  129. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_managed_binaries.py +0 -0
  130. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_mouse.py +0 -0
  131. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_mpv_ipc.py +0 -0
  132. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_navigation.py +0 -0
  133. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_nerd_font.py +0 -0
  134. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_nyaa.py +0 -0
  135. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_pack_a_polish.py +0 -0
  136. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_performance.py +0 -0
  137. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_platform_android.py +0 -0
  138. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_player_dash.py +0 -0
  139. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_polish.py +0 -0
  140. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_premium_players.py +0 -0
  141. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_providers_repair.py +0 -0
  142. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_proxy_endpoint.py +0 -0
  143. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_proxy_integration.py +0 -0
  144. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_proxy_resilient.py +0 -0
  145. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_proxy_ssrf.py +0 -0
  146. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_recent_new_episodes.py +0 -0
  147. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_release_config.py +0 -0
  148. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_resilient.py +0 -0
  149. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_scraper_resilience.py +0 -0
  150. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_search_parity.py +0 -0
  151. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_sendvid.py +0 -0
  152. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_stream_analysis.py +0 -0
  153. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_stream_integrity.py +0 -0
  154. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_system_deps_cache.py +0 -0
  155. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_terminal_image.py +0 -0
  156. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_v1105_improvements.py +0 -0
  157. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tests/test_v2_aesthetic.py +0 -0
  158. {freeflix_cli-2.1.2 → freeflix_cli-2.3.0}/tools/audit_providers.py +0 -0
@@ -1,5 +1,65 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.0
4
+
5
+ - **Match** : la chaîne est affichée entre parenthèses **devant** le match,
6
+ avec un badge court (`beIN SPORTS 1..4` → `B1..B4`), ex.
7
+ `(B4) ● LIVE 20:45 · Toulouse VS Strasbourg`.
8
+ - **Qualités Match** : chaque ligne affiche désormais la résolution, le débit
9
+ minimum estimé et la consommation horaire estimée, ex.
10
+ `HD (1080p) · ≥ 5.0 Mbps · ~2.25 Go/h`, `SD (720p) · ≥ 2.5 Mbps · ~1.13 Go/h`,
11
+ `Low (480p) · ≥ 1.0 Mbps · ~0.45 Go/h` ; `Auto` reste en bas.
12
+ - 604 tests au vert, ruff OK.
13
+
14
+ ## 2.2.2
15
+
16
+ - Menus **qualité** et **choix du lecteur** en plein écran : souris alignée
17
+ (survol + clic) sur Linux et Windows.
18
+ - **Anime4K désactivé par défaut** : activation explicite par l'utilisateur
19
+ (`CTRL+1` / `CTRL+2`, `CTRL+0` pour couper), conseil affiché en bas de mpv
20
+ pendant 5 s, migration des anciennes configs sans toucher aux shaders perso.
21
+ - Entrée du menu principal renommée **Films, Séries & Anime** (traduite) ;
22
+ la liste des providers et son fonctionnement restent inchangés.
23
+ - **Configs lecteurs isolées** : mpv utilise toujours son dossier dédié
24
+ (`--config-dir`, sinon `--no-config`) ; VLC est lancé avec
25
+ `--ignore-config --no-media-library --no-plugins-cache`. Les réglages
26
+ personnels de la machine ne sont ni lus ni modifiés.
27
+ - Derniers correctifs Windows/mise à jour (ancrage console, VT input, redémarrage
28
+ par nouveau processus, recherche uv/pipx hors PATH).
29
+ - 602 tests au vert, ruff OK.
30
+
31
+ ## 2.2.1
32
+
33
+ - **Compatibilité Windows** :
34
+ - ancrage souris des menus inline via `GetConsoleScreenBufferInfo`
35
+ (fini les clics décalés) ;
36
+ - activation VT input avec `ENABLE_EXTENDED_FLAGS` pour que Windows
37
+ Terminal transmette vraiment les rapports SGR ;
38
+ - écran des qualités en plein écran, ancrage fiable sur tous les OS ;
39
+ - redémarrage après mise à jour via un nouveau processus sur Windows
40
+ (`os.execv` n'est pas fiable avec un interpréteur Python verrouillé).
41
+ - **Mises à jour** : correction de la comparaison de version en mode
42
+ source, détection `uv` resserrée, commande de mise à jour adaptée à
43
+ l'installeur réel (uv/pipx/pip).
44
+ - VLC : lecture par **ffmpeg → TS** comme mpv (plus de fin de playlist
45
+ HLS), timestamps normalisés, audio calé.
46
+ - 600 tests + tests Windows dédiés au vert, ruff OK.
47
+
48
+ ## 2.2.0
49
+
50
+ - **Nouvel onglet Match** dans le menu principal : matchs de football du
51
+ catalogue YacineTV, à l'heure du Cameroun (UTC+1), avec badge `LIVE`,
52
+ à venir et terminés.
53
+ - Qualités HD/SD/Low + **Auto** (meilleure disponible), choix du lecteur
54
+ **mpv / VLC** mémorisé, souris en clic seul sur les qualités.
55
+ - Noms d'équipes et de compétitions **traduits dynamiquement** en français
56
+ ou anglais (cache local, aucun mapping en dur).
57
+ - Flux live servis par un mini-proxy HLS local : en-têtes UTF-8 exacts et
58
+ segments `.php/.pdf` réécrits pour mpv/VLC.
59
+ - mpv en direct : cache mémoire 3 s, démarrage au bord du live, plus de
60
+ cache disque de 30 min.
61
+ - 598 tests au vert, ruff OK.
62
+
3
63
  ## 2.1.2
4
64
 
5
65
  - **Téléchargements Vidzy / fsvid / uqload réparés** : l'URL passée à yt-dlp
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: freeflix-cli
3
- Version: 2.1.2
3
+ Version: 2.3.0
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
@@ -49,7 +49,7 @@ Description-Content-Type: text/markdown
49
49
 
50
50
  <p align="center">
51
51
  <b>🎬 Movies · 📺 Series · 🍿 Anime (VF / VOSTFR / VO)</b><br>
52
- <sub>mpv-powered · Anime4K upscaling · downloads · resume · AniList sync</sub>
52
+ <sub>mpv/VLC-powered · live football · Anime4K opt-in · downloads · resume · AniList sync</sub>
53
53
  </p>
54
54
 
55
55
  <p align="center">
@@ -65,13 +65,15 @@ Description-Content-Type: text/markdown
65
65
  - **Everything, always up to date** — anime (Anime-Sama, HiAnime,
66
66
  GoldenAnime), films & series (GoldenMS, Coflix, French-Stream, YTS/EZTV),
67
67
  in **French (VF/VOSTFR)** or **VO**.
68
- - **Live planning & Nouveautés** — the Anime-Sama weekly schedule, and a feed
69
- of new episodes for the shows you follow.
68
+ - **Live planning, Match & Nouveautés** — the Anime-Sama weekly schedule, live
69
+ football (YacineTV, Cameroon time, HD/SD/Low + Auto), and a feed of new
70
+ episodes for the shows you follow.
70
71
  - **Actually nice to use** — poster previews in the terminal (chafa), breadcrumb
71
72
  navigation, `/` to filter, `Esc` to go back anywhere, `?` for help.
72
73
  - **Download & keep** — pick a quality, batch a whole season into its own folder,
73
74
  resume interrupted downloads, see ✓ watched / ▸ resume / ⬇ downloaded badges.
74
- - **Crisp picture** — Anime4K GPU upscaling pre-tuned, one keypress to toggle.
75
+ - **Crisp picture on demand** — Anime4K GPU upscaling pre-bundled but **off by
76
+ default**; enable it with `CTRL+1` (max) or `CTRL+2` (light).
75
77
  - **Optional AniList sync** — your progress updates automatically as you watch.
76
78
 
77
79
  ---
@@ -87,7 +89,13 @@ Description-Content-Type: text/markdown
87
89
  - **Daily new-episode notifications** via systemd timer + libnotify ;
88
90
  - A **Nyaa torrent provider** (downloads via aria2 BitTorrent) ;
89
91
  - A **Local Downloads browser** in the home menu ;
90
- - **Anime4K** shaders pre-bundled (S by default, VL with CTRL+1, off with CTRL+0) ;
92
+ - **Live football Match** (YacineTV) : schedule in Cameroon time, LIVE badges,
93
+ HD/SD/Low + Auto, mpv or VLC;
94
+ - **Anime4K** shaders pre-bundled but **opt-in** (CTRL+1 max, CTRL+2 light,
95
+ CTRL+0 off), with an OSD hint at playback start;
96
+ - **Player configs isolated** : mpv uses a dedicated `--config-dir` (or
97
+ `--no-config`), VLC runs with `--ignore-config`; your machine's own player
98
+ settings are never read or modified;
91
99
  - **French localisation** of the whole UI (auto-switched based on language pref) ;
92
100
  - Multi-mirror portal fallback + `website_origin` caching (24 h TTL) ;
93
101
  - Multiple **bug fixes** : 403 on `/catalogue/?search=` (animes-sama.fr refactor), double-slash legacy URLs in history, settings-menu exit loop, ffmpeg infinite-reconnect at byte 1506, Videasy/Vidlink Referer not propagated, history resume silent-failure, and more.
@@ -113,10 +121,13 @@ Description-Content-Type: text/markdown
113
121
  | 🆕 | **Nouveautés** : new episodes of the shows you follow, cleared once watched |
114
122
  | 🖱 | **Full mouse control** : hover, click, wheel, right-click back, clickable pagination |
115
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 |
125
+ | 🎬 | **Browse Films, Séries & Anime** : the provider list, grouped as before |
116
126
  | 🎮 | **4 player backends** : mpv, VLC, browser, download-only |
117
127
  | 🇫🇷 | **French UI** : the whole CLI is in French when language is set to FR |
118
128
  | 🛡 | **Anti-crash mpv config** : ytdl-hook off, gpu renderer, no infinite reconnect |
119
- | 🎨 | **Anime4K** pre-bundled with runtime toggle CTRL+1 / CTRL+0 |
129
+ | 🎨 | **Anime4K** pre-bundled, **off by default** — CTRL+1 max, CTRL+2 light, CTRL+0 off |
130
+ | 🔒 | **Player configs isolated** — global mpv/VLC settings untouched |
120
131
  | 🩺 | **`freeflix --doctor`** system diagnostic (check deps, config, network) |
121
132
  | 🔄 | **Startup updater** : asks yes/no, upgrades with the same installer (uv/pipx/pip), reloads |
122
133
 
@@ -234,12 +245,19 @@ freeflix
234
245
 
235
246
  That's it. The menu walks you through everything.
236
247
 
248
+ - **Films, Séries & Anime** opens the provider list (unchanged grouping).
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.
251
+ - Player settings are isolated: FreeFlix never edits your global mpv/VLC
252
+ configuration.
253
+
237
254
  ### Common keyboard shortcuts in mpv
238
255
 
239
256
  | Key | Action |
240
257
  |---|---|
241
- | **CTRL + 1** | Enable Anime4K Mode A_VL |
242
- | **CTRL + 0** | Disable Anime4K |
258
+ | **CTRL + 1** | Enable Anime4K Mode A_VL (max) |
259
+ | **CTRL + 2** | Enable Anime4K Mode A_S (light) |
260
+ | **CTRL + 0** | Disable Anime4K (default) |
243
261
  | **`#`** | Cycle audio track |
244
262
  | **`j`** | Cycle subtitle track |
245
263
  | **`_`** (Shift + `-`) | Cycle video track |
@@ -17,7 +17,7 @@
17
17
 
18
18
  <p align="center">
19
19
  <b>🎬 Movies · 📺 Series · 🍿 Anime (VF / VOSTFR / VO)</b><br>
20
- <sub>mpv-powered · Anime4K upscaling · downloads · resume · AniList sync</sub>
20
+ <sub>mpv/VLC-powered · live football · Anime4K opt-in · downloads · resume · AniList sync</sub>
21
21
  </p>
22
22
 
23
23
  <p align="center">
@@ -33,13 +33,15 @@
33
33
  - **Everything, always up to date** — anime (Anime-Sama, HiAnime,
34
34
  GoldenAnime), films & series (GoldenMS, Coflix, French-Stream, YTS/EZTV),
35
35
  in **French (VF/VOSTFR)** or **VO**.
36
- - **Live planning & Nouveautés** — the Anime-Sama weekly schedule, and a feed
37
- of new episodes for the shows you follow.
36
+ - **Live planning, Match & Nouveautés** — the Anime-Sama weekly schedule, live
37
+ football (YacineTV, Cameroon time, HD/SD/Low + Auto), and a feed of new
38
+ episodes for the shows you follow.
38
39
  - **Actually nice to use** — poster previews in the terminal (chafa), breadcrumb
39
40
  navigation, `/` to filter, `Esc` to go back anywhere, `?` for help.
40
41
  - **Download & keep** — pick a quality, batch a whole season into its own folder,
41
42
  resume interrupted downloads, see ✓ watched / ▸ resume / ⬇ downloaded badges.
42
- - **Crisp picture** — Anime4K GPU upscaling pre-tuned, one keypress to toggle.
43
+ - **Crisp picture on demand** — Anime4K GPU upscaling pre-bundled but **off by
44
+ default**; enable it with `CTRL+1` (max) or `CTRL+2` (light).
43
45
  - **Optional AniList sync** — your progress updates automatically as you watch.
44
46
 
45
47
  ---
@@ -55,7 +57,13 @@
55
57
  - **Daily new-episode notifications** via systemd timer + libnotify ;
56
58
  - A **Nyaa torrent provider** (downloads via aria2 BitTorrent) ;
57
59
  - A **Local Downloads browser** in the home menu ;
58
- - **Anime4K** shaders pre-bundled (S by default, VL with CTRL+1, off with CTRL+0) ;
60
+ - **Live football Match** (YacineTV) : schedule in Cameroon time, LIVE badges,
61
+ HD/SD/Low + Auto, mpv or VLC;
62
+ - **Anime4K** shaders pre-bundled but **opt-in** (CTRL+1 max, CTRL+2 light,
63
+ CTRL+0 off), with an OSD hint at playback start;
64
+ - **Player configs isolated** : mpv uses a dedicated `--config-dir` (or
65
+ `--no-config`), VLC runs with `--ignore-config`; your machine's own player
66
+ settings are never read or modified;
59
67
  - **French localisation** of the whole UI (auto-switched based on language pref) ;
60
68
  - Multi-mirror portal fallback + `website_origin` caching (24 h TTL) ;
61
69
  - Multiple **bug fixes** : 403 on `/catalogue/?search=` (animes-sama.fr refactor), double-slash legacy URLs in history, settings-menu exit loop, ffmpeg infinite-reconnect at byte 1506, Videasy/Vidlink Referer not propagated, history resume silent-failure, and more.
@@ -81,10 +89,13 @@
81
89
  | 🆕 | **Nouveautés** : new episodes of the shows you follow, cleared once watched |
82
90
  | 🖱 | **Full mouse control** : hover, click, wheel, right-click back, clickable pagination |
83
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 |
93
+ | 🎬 | **Browse Films, Séries & Anime** : the provider list, grouped as before |
84
94
  | 🎮 | **4 player backends** : mpv, VLC, browser, download-only |
85
95
  | 🇫🇷 | **French UI** : the whole CLI is in French when language is set to FR |
86
96
  | 🛡 | **Anti-crash mpv config** : ytdl-hook off, gpu renderer, no infinite reconnect |
87
- | 🎨 | **Anime4K** pre-bundled with runtime toggle CTRL+1 / CTRL+0 |
97
+ | 🎨 | **Anime4K** pre-bundled, **off by default** — CTRL+1 max, CTRL+2 light, CTRL+0 off |
98
+ | 🔒 | **Player configs isolated** — global mpv/VLC settings untouched |
88
99
  | 🩺 | **`freeflix --doctor`** system diagnostic (check deps, config, network) |
89
100
  | 🔄 | **Startup updater** : asks yes/no, upgrades with the same installer (uv/pipx/pip), reloads |
90
101
 
@@ -202,12 +213,19 @@ freeflix
202
213
 
203
214
  That's it. The menu walks you through everything.
204
215
 
216
+ - **Films, Séries & Anime** opens the provider list (unchanged grouping).
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.
219
+ - Player settings are isolated: FreeFlix never edits your global mpv/VLC
220
+ configuration.
221
+
205
222
  ### Common keyboard shortcuts in mpv
206
223
 
207
224
  | Key | Action |
208
225
  |---|---|
209
- | **CTRL + 1** | Enable Anime4K Mode A_VL |
210
- | **CTRL + 0** | Disable Anime4K |
226
+ | **CTRL + 1** | Enable Anime4K Mode A_VL (max) |
227
+ | **CTRL + 2** | Enable Anime4K Mode A_S (light) |
228
+ | **CTRL + 0** | Disable Anime4K (default) |
211
229
  | **`#`** | Cycle audio track |
212
230
  | **`j`** | Cycle subtitle track |
213
231
  | **`_`** (Shift + `-`) | Cycle video track |
@@ -82,19 +82,12 @@ screenshot-directory=~/Pictures/mpv
82
82
  screenshot-format=png
83
83
  screenshot-template='%F-%P'
84
84
 
85
- # ─── Anime4K : Mode A_S (Small) par défaut — équilibre GPU ──
86
- # 3 niveaux dispos via les keybinds dans input.conf :
87
- # CTRL+1 → Mode A_VL (Very Lite — qualité max, GPU le plus lourd)
88
- # CTRL+2 → Mode A_S (Small — défaut, ~3× moins de GPU)
85
+ # ─── Anime4K : DÉSACTIVÉ au démarrage (choix explicite de l'utilisateur) ──
86
+ # Raccourcis (input.conf) :
87
+ # CTRL+1 → Mode A_VL (qualité max, GPU le plus lourd)
88
+ # CTRL+2 → Mode A_S (Small, plus léger)
89
89
  # CTRL+0 → OFF
90
- # Le S garde quand même le CNN + Restore, juste avec un réseau plus
91
- # petit. Sur un GPU intégré, on passe d'un drop FPS visible à fluide.
92
- glsl-shaders="~~/shaders/Anime4K_Clamp_Highlights.glsl:~~/shaders/Anime4K_Restore_CNN_S.glsl:~~/shaders/Anime4K_Upscale_CNN_x2_S.glsl"
93
-
94
- # Auto-désactive Anime4K quand la source est déjà ≥ 1080p — pas
95
- # de bénéfice à upscaler 1080→2160 sur ce hardware, et ça mange du GPU
96
- # pour rien.
97
- [anime4k-skip-high-res]
98
- profile-cond=p["video-params/h"] ~= nil and p["video-params/h"] >= 1080
99
- profile-restore=copy-equal
100
- glsl-shaders=
90
+ # Rien n'est chargé automatiquement : les vidéos (sport, films…) démarrent
91
+ # sans surcoût GPU. À activer à la demande.
92
+ osd-playing-msg="Anime4K OFF · CTRL+1 max · CTRL+2 léger · CTRL+0 off"
93
+ osd-duration=5000
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "freeflix-cli"
7
- version = "2.1.2"
7
+ version = "2.3.0"
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"
@@ -603,6 +603,7 @@ def _enable_windows_vt() -> bool:
603
603
  STD_INPUT_HANDLE = -10
604
604
  ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
605
605
  ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
606
+ ENABLE_EXTENDED_FLAGS = 0x0080
606
607
 
607
608
  h = k32.GetStdHandle(STD_OUTPUT_HANDLE)
608
609
  if not h or h == wintypes.HANDLE(-1).value:
@@ -626,9 +627,11 @@ def _enable_windows_vt() -> bool:
626
627
  if not k32.GetConsoleMode(hi, ctypes.byref(imode)):
627
628
  return _VT_READY
628
629
  if not (imode.value & ENABLE_VIRTUAL_TERMINAL_INPUT):
629
- if not k32.SetConsoleMode(
630
- hi, imode.value | ENABLE_VIRTUAL_TERMINAL_INPUT
631
- ):
630
+ # Windows requires extended flags (and quick-edit off) for VT
631
+ # input; without it msvcrt never sees SGR mouse reports.
632
+ new_mode = (imode.value | ENABLE_VIRTUAL_TERMINAL_INPUT
633
+ | ENABLE_EXTENDED_FLAGS)
634
+ if not k32.SetConsoleMode(hi, new_mode):
632
635
  return _VT_READY
633
636
 
634
637
  _VT_READY = True
@@ -647,9 +650,13 @@ def _mouse_write(seq: str) -> None:
647
650
 
648
651
 
649
652
  def _mouse_off() -> None:
653
+ """Always send the disable sequence.
654
+
655
+ The terminal (notably Windows VT input) can still be in mouse mode even
656
+ when our flag says otherwise after a nested Live/reader; skipping the OFF
657
+ sequence leaves the user's terminal printing SGR reports.
658
+ """
650
659
  global _mouse_active
651
- if not _mouse_active:
652
- return
653
660
  _mouse_active = False
654
661
  _mouse_write(_MOUSE_OFF)
655
662
 
@@ -739,7 +746,39 @@ def _cursor_row(timeout: float = 0.15):
739
746
  """
740
747
  import sys
741
748
  try:
742
- if os.name == "nt" or not sys.stdin.isatty():
749
+ if os.name == "nt":
750
+ # Windows has no DSR/select; GetConsoleScreenBufferInfo gives the
751
+ # real cursor row, so inline menus keep accurate click coordinates.
752
+ try:
753
+ import ctypes
754
+ from ctypes import wintypes
755
+
756
+ class COORD(ctypes.Structure):
757
+ _fields_ = [("X", wintypes.SHORT), ("Y", wintypes.SHORT)]
758
+
759
+ class SMALL_RECT(ctypes.Structure):
760
+ _fields_ = [("Left", wintypes.SHORT),
761
+ ("Top", wintypes.SHORT),
762
+ ("Right", wintypes.SHORT),
763
+ ("Bottom", wintypes.SHORT)]
764
+
765
+ class CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
766
+ _fields_ = [("dwSize", COORD),
767
+ ("dwCursorPosition", COORD),
768
+ ("wAttributes", wintypes.WORD),
769
+ ("srWindow", SMALL_RECT),
770
+ ("dwMaximumWindowSize", COORD)]
771
+
772
+ k32 = ctypes.WinDLL("kernel32", use_last_error=True)
773
+ handle = k32.GetStdHandle(-11)
774
+ info = CONSOLE_SCREEN_BUFFER_INFO()
775
+ if not k32.GetConsoleScreenBufferInfo(
776
+ handle, ctypes.byref(info)):
777
+ return None
778
+ return int(info.dwCursorPosition.Y) + 1
779
+ except Exception:
780
+ return None
781
+ if not sys.stdin.isatty():
743
782
  return None
744
783
  import select as _sel
745
784
  import termios
@@ -1539,7 +1578,8 @@ def select_from_list(options: list[str], prompt: str, default_index: int = 0,
1539
1578
  top=None, flush_input: bool = False,
1540
1579
  esc_selects_last: bool = True,
1541
1580
  clear_on_back: bool = True,
1542
- inline: bool = False) -> int:
1581
+ inline: bool = False,
1582
+ mouse_motion: bool = True) -> int:
1543
1583
  """
1544
1584
  Display an interactive menu where users can navigate with arrow keys.
1545
1585
 
@@ -1560,6 +1600,9 @@ def select_from_list(options: list[str], prompt: str, default_index: int = 0,
1560
1600
  (default). Set False for a sub-menu that returns to its parent
1561
1601
  inside the same detail view (seasons → languages → episodes), so
1562
1602
  the poster and context stay on screen until the series is left.
1603
+ mouse_motion: False keeps click selection but ignores hover/motion —
1604
+ useful for short quality lists where a moving mouse would change
1605
+ the highlighted row by accident.
1563
1606
 
1564
1607
  Returns:
1565
1608
  Index of the selected option (0-based)
@@ -1752,7 +1795,7 @@ def select_from_list(options: list[str], prompt: str, default_index: int = 0,
1752
1795
  if flush_input:
1753
1796
  # Kept for compat — already covered by the settled flush above.
1754
1797
  drain_stdin()
1755
- with mouse_tracking():
1798
+ with mouse_tracking(all_motion=mouse_motion):
1756
1799
  with Live(generate_renderable(), **live_kwargs) as live:
1757
1800
  while result is None:
1758
1801
  key = _read_menu_key()
@@ -0,0 +1,227 @@
1
+ """Match menu — live football from the YacineTV catalogue.
2
+
3
+ Cameroon time (UTC+1, no DST). The list is fetched fresh on every entry and
4
+ falls back to the last snapshot only when the API is unreachable.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+
11
+ from datetime import datetime, timedelta, timezone
12
+
13
+ from ..cli_utils import (
14
+ print_error,
15
+ print_info,
16
+ print_warning,
17
+ select_from_list,
18
+ spinner,
19
+ )
20
+ from ..i18n import t
21
+ from ..icons import icon
22
+ from ..scraping import yacine_match as ym
23
+ from ..tracker import tracker
24
+
25
+ _CAM = timezone(timedelta(hours=1))
26
+
27
+
28
+ def _when_label(ev: ym.MatchEvent, now: datetime) -> str:
29
+ dt = ev.start_dt
30
+ today = now.date()
31
+ if dt.date() == today:
32
+ return f"{dt:%H:%M}"
33
+ if dt.date() == today + timedelta(days=1):
34
+ return f"{t('Tomorrow')} {dt:%H:%M}"
35
+ return f"{dt:%d/%m %H:%M}"
36
+
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
+
44
+ def _match_label(ev: ym.MatchEvent, now_ts: int, now: datetime) -> str:
45
+ state = ev.status(now_ts)
46
+ when = _when_label(ev, now)
47
+ if state == "live":
48
+ badge = f"● {t('LIVE')}"
49
+ elif state == "ended":
50
+ badge = f"✓ {t('Finished')}"
51
+ else:
52
+ badge = f"○ {t('Upcoming')}"
53
+ league = f" · {ev.championship}" if ev.championship else ""
54
+ channel = f"({_channel_label(ev.channel)}) " if ev.channel else ""
55
+ return f"{channel}{badge} {when} · {ev.home} VS {ev.away}{league}"
56
+
57
+
58
+ def _sort_events(events: list[ym.MatchEvent], now_ts: int) -> list[ym.MatchEvent]:
59
+ order = {"live": 0, "upcoming": 1, "ended": 2}
60
+ return sorted(events, key=lambda e: (order.get(e.status(now_ts), 3), e.start))
61
+
62
+
63
+ # Conservative live HLS minimums (Mbps) used for the GB/hour estimate.
64
+ _MIN_MBPS = {1080: 5.0, 720: 2.5, 480: 1.0, 360: 0.7}
65
+
66
+
67
+ def _quality_metrics(stream: dict) -> tuple[int | None, float, float]:
68
+ """(height, minimum Mbps, estimated GB/hour) for a stream label."""
69
+ name = str(stream.get("name") or "?")
70
+ low = name.lower()
71
+ m = re.search(r"(\d{3,4})p", low)
72
+ if m:
73
+ height = int(m.group(1))
74
+ elif "hd" in low:
75
+ height = 1080
76
+ elif "sd" in low:
77
+ height = 720
78
+ elif "low" in low:
79
+ height = 480
80
+ else:
81
+ height = None
82
+ mbps = _MIN_MBPS.get(height, 2.0)
83
+ return height, mbps, mbps * 3600 / 8 / 1000
84
+
85
+
86
+ def _quality_label(stream: dict) -> str:
87
+ name = str(stream.get("name") or "?")
88
+ height, mbps, gb = _quality_metrics(stream)
89
+ res = f" ({height}p)" if height else ""
90
+ return (f"{icon('play')} {name}{res} · ≥ {mbps:.1f} Mbps · "
91
+ f"~{gb:.2f} {t('GB/h')}")
92
+
93
+
94
+ def _play(event: ym.MatchEvent, stream: dict, player_name: str = "") -> None:
95
+ from ..player_manager import play_direct_stream
96
+
97
+ title = f"YacineTV · {event.home} VS {event.away}"
98
+ print_info(f"{t('Selected quality:')} {stream.get('name', '?')}")
99
+ ok = play_direct_stream(stream["url"], stream.get("headers") or {}, title,
100
+ player_name=player_name or None)
101
+ if not ok:
102
+ print_warning(t("Could not start the match stream."))
103
+
104
+
105
+ def _available_players() -> list[str]:
106
+ from ..player_manager import get_mpv_path, get_vlc_path
107
+
108
+ out = []
109
+ if get_mpv_path():
110
+ out.append("mpv")
111
+ if get_vlc_path():
112
+ out.append("vlc")
113
+ return out
114
+
115
+
116
+ def _current_player() -> str:
117
+ avail = _available_players()
118
+ saved = (tracker.get_match_player() or "").lower()
119
+ if saved in avail:
120
+ return saved
121
+ return "mpv" if "mpv" in avail else ("vlc" if "vlc" in avail else "")
122
+
123
+
124
+ def _choose_player(current: str, allow_cancel: bool = False):
125
+ """Choose mpv/VLC after quality. Returns None when Back is pressed."""
126
+ avail = _available_players()
127
+ if len(avail) < 2:
128
+ if avail:
129
+ return avail[0]
130
+ print_warning(t("No video player found (mpv or VLC)."))
131
+ return None
132
+ labels = ["mpv" if n == "mpv" else "VLC" for n in avail]
133
+ options = list(labels)
134
+ if allow_cancel:
135
+ options.append(t("← Back"))
136
+ default = labels.index(current) if current in labels else 0
137
+ idx = select_from_list(
138
+ options, t("Choose a player"), header=f"{icon('football')} {t('Match')}",
139
+ default_index=default,
140
+ )
141
+ if 0 <= idx < len(avail):
142
+ chosen = avail[idx]
143
+ tracker.set_match_player(chosen)
144
+ print_info(f"{t('Match player set to:')} {chosen}")
145
+ return chosen
146
+ return None if allow_cancel else current
147
+
148
+
149
+ def handle_match() -> None:
150
+ """Live football list → quality (Auto at the bottom) → playback."""
151
+ while True:
152
+ # Refresh on every pass (including after a playback) so the list and
153
+ # the LIVE badges follow the catalogue, like the weekly planning.
154
+ try:
155
+ with spinner(t("Loading matches…")):
156
+ events, cached = ym.fetch_events()
157
+ events = ym.localize_events(
158
+ events, tracker.get_language() or "en")
159
+ except Exception as e:
160
+ print_error(f"{t('Matches unavailable:')} {e}")
161
+ return
162
+
163
+ if cached:
164
+ print_warning(t("Showing cached matches — network unavailable."))
165
+ if not events:
166
+ print_warning(t("No football matches right now."))
167
+ return
168
+
169
+ now = datetime.now(tz=_CAM)
170
+ now_ts = int(now.timestamp())
171
+ ordered = _sort_events(events, now_ts)
172
+ player_name = _current_player()
173
+ labels = [_match_label(ev, now_ts, now) for ev in ordered]
174
+ labels.append(t("← Back"))
175
+ header = (f"{icon('football')} {t('Football matches')} · "
176
+ f"{t('Cameroon time (UTC+1)')}")
177
+
178
+ idx = select_from_list(
179
+ labels, t("Select a match"), header=header, clear_on_back=True,
180
+ )
181
+ if idx < 0 or idx >= len(ordered):
182
+ return
183
+ event = ordered[idx]
184
+
185
+ try:
186
+ with spinner(t("Loading streams…")):
187
+ streams = ym.fetch_streams(event.id)
188
+ except Exception as e:
189
+ print_error(f"{t('Streams unavailable:')} {e}")
190
+ continue
191
+
192
+ if not streams:
193
+ print_warning(t("No stream available for this match."))
194
+ continue
195
+
196
+ options = [_quality_label(s) for s in streams]
197
+ options.append(f"{icon('sparkle')} {t('Auto (best quality)')}")
198
+ options.append(t("← Back"))
199
+ auto_idx = len(streams)
200
+ choice = select_from_list(
201
+ options,
202
+ f"{t('Choose the quality')} · {event.home} VS {event.away}",
203
+ header=(f"{icon('football')} "
204
+ f"{event.championship or t('Football')}"
205
+ + (f" · {_channel_label(event.channel)}"
206
+ if event.channel else "")),
207
+ )
208
+ if choice < 0 or choice >= auto_idx:
209
+ if choice == auto_idx:
210
+ stream = ym.pick_auto(streams)
211
+ if not stream:
212
+ print_warning(t("No stream available for this match."))
213
+ continue
214
+ _h, _mbps, _gb = _quality_metrics(stream)
215
+ print_info(f"{t('Auto quality:')} {stream.get('name', '?')} · "
216
+ f"≥ {_mbps:.1f} {t('Mbps')} · ~{_gb:.2f} "
217
+ f"{t('GB/h')}")
218
+ chosen = _choose_player(player_name, allow_cancel=True)
219
+ if chosen:
220
+ _play(event, stream, chosen)
221
+ else:
222
+ return
223
+ continue
224
+
225
+ chosen = _choose_player(player_name, allow_cancel=True)
226
+ if chosen:
227
+ _play(event, streams[choice], chosen)
@@ -990,6 +990,54 @@ _FR = {
990
990
  "AniList : ton jeton est invalide ou expiré. "
991
991
  "Réglages → AniList → Changer le jeton.",
992
992
  'AniList is unreachable right now:': 'AniList est injoignable pour le moment :',
993
+ # ── Match / football (YacineTV) ──
994
+ 'Match': 'Match',
995
+ 'Football': 'Football',
996
+ 'Football matches': 'Matchs de football',
997
+ 'Cameroon time (UTC+1)': 'Heure du Cameroun (UTC+1)',
998
+ 'Select a match': 'Choisis un match',
999
+ 'Select quality': 'Choisis la qualité',
1000
+ 'Choose the quality': 'Choisis la qualité',
1001
+ 'Loading matches…': 'Chargement des matchs…',
1002
+ 'Matches unavailable:': 'Matchs indisponibles :',
1003
+ 'Showing cached matches — network unavailable.':
1004
+ 'Affichage des matchs en cache — réseau indisponible.',
1005
+ 'No football matches right now.':
1006
+ 'Aucun match de football pour le moment.',
1007
+ 'Loading streams…': 'Chargement des flux…',
1008
+ 'Streams unavailable:': 'Flux indisponibles :',
1009
+ 'No stream available for this match.':
1010
+ 'Aucun flux disponible pour ce match.',
1011
+ 'Auto (best quality)': 'Auto (meilleure qualité)',
1012
+ 'Auto quality:': 'Qualité auto :',
1013
+ 'Selected quality:': 'Qualité sélectionnée :',
1014
+ 'Could not start the match stream.':
1015
+ 'Impossible de lancer le flux du match.',
1016
+ 'Tomorrow': 'Demain',
1017
+ 'Upcoming': 'À venir',
1018
+ 'LIVE': 'LIVE',
1019
+ 'Match streams use mpv or VLC; launching mpv.':
1020
+ 'Les matchs utilisent mpv ou VLC ; lancement de mpv.',
1021
+ 'The stream ended unexpectedly.':
1022
+ 'Le flux s’est arrêté de façon inattendue.',
1023
+ 'Playing in VLC…': 'Lecture dans VLC…',
1024
+ 'VLC stopped early. Log:': 'VLC s’est arrêté trop tôt. Journal :',
1025
+ 'Movies, Series & Anime': 'Films, Séries & Anime',
1026
+ 'Mbps': 'Mbps',
1027
+ 'GB/h': 'Go/h',
1028
+ 'Match player:': 'Lecteur des matchs :',
1029
+ 'Match player set to:': 'Lecteur des matchs :',
1030
+ 'Choose a player': 'Choisis un lecteur',
1031
+ 'No video player found (mpv or VLC).':
1032
+ 'Aucun lecteur vidéo trouvé (mpv ou VLC).',
1033
+ 'Auto': 'Auto',
1034
+ 'mpv is not installed or not in PATH.':
1035
+ "mpv n'est pas installé ou absent du PATH.",
1036
+ 'VLC is not installed or not in PATH.':
1037
+ "VLC n'est pas installé ou absent du PATH.",
1038
+ 'This stream needs mpv or VLC: the required HTTP headers cannot be forwarded to a browser.':
1039
+ 'Ce flux nécessite mpv ou VLC : les en-têtes HTTP requis ne peuvent pas '
1040
+ "être transmis à un navigateur.",
993
1041
  }
994
1042
 
995
1043