freeflix-cli 2.4.0__tar.gz → 2.5.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 (175) hide show
  1. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/CHANGELOG.md +181 -0
  2. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/PKG-INFO +12 -3
  3. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/README.md +11 -2
  4. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/docs/TROUBLESHOOTING.md +61 -0
  5. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/pyproject.toml +1 -1
  6. freeflix_cli-2.5.0/scripts/install-mac.sh +209 -0
  7. freeflix_cli-2.5.0/scripts/install.ps1 +265 -0
  8. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/scripts/install.py +90 -61
  9. freeflix_cli-2.5.0/scripts/install.sh +421 -0
  10. freeflix_cli-2.5.0/scripts/wrappers/mpv +39 -0
  11. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/cli_utils.py +271 -130
  12. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/defaults.py +19 -0
  13. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/doctor.py +123 -41
  14. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/anime_sama.py +4 -0
  15. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/goldenanime.py +16 -1
  16. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/match.py +98 -14
  17. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/playback.py +140 -10
  18. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/i18n.py +41 -0
  19. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/install_health.py +41 -10
  20. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/main.py +33 -19
  21. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/player_manager.py +444 -56
  22. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/preview_picker.py +99 -48
  23. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/proxy.py +39 -9
  24. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/recent.py +156 -72
  25. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/anime_sama.py +329 -1
  26. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/goldenanime.py +110 -35
  27. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/objects.py +4 -1
  28. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/player.py +166 -8
  29. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/resilient.py +11 -6
  30. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/utils.py +62 -6
  31. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/yacine_match.py +47 -14
  32. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/setup_assistant.py +668 -154
  33. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/yacine_live_proxy.py +49 -1
  34. freeflix_cli-2.5.0/tests/test_anime_sama_fr.py +142 -0
  35. freeflix_cli-2.5.0/tests/test_anime_sama_lien.py +260 -0
  36. freeflix_cli-2.5.0/tests/test_hls_preflight.py +104 -0
  37. freeflix_cli-2.5.0/tests/test_install_flow.py +748 -0
  38. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_providers_repair.py +77 -0
  39. freeflix_cli-2.5.0/tests/test_recent_releases.py +134 -0
  40. freeflix_cli-2.5.0/tests/test_release_config.py +163 -0
  41. freeflix_cli-2.5.0/tests/test_ui_exit.py +161 -0
  42. freeflix_cli-2.5.0/tests/test_ui_fluidity.py +197 -0
  43. freeflix_cli-2.5.0/tests/test_voe_mirrors.py +76 -0
  44. freeflix_cli-2.5.0/tests/test_windows_playback.py +505 -0
  45. freeflix_cli-2.5.0/tests/test_windows_players.py +74 -0
  46. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_yacine_match.py +195 -1
  47. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/uv.lock +1 -1
  48. freeflix_cli-2.4.0/scripts/install-mac.sh +0 -124
  49. freeflix_cli-2.4.0/scripts/install.ps1 +0 -157
  50. freeflix_cli-2.4.0/scripts/install.sh +0 -261
  51. freeflix_cli-2.4.0/scripts/wrappers/mpv +0 -14
  52. freeflix_cli-2.4.0/tests/test_release_config.py +0 -80
  53. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/.github/workflows/binaries.yml +0 -0
  54. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/.github/workflows/ci.yml +0 -0
  55. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/.github/workflows/macos.yml +0 -0
  56. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/.github/workflows/release.yml +0 -0
  57. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/.gitignore +0 -0
  58. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/CONTRIBUTING.md +0 -0
  59. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/LICENSE +0 -0
  60. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/SECURITY.md +0 -0
  61. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/assets/logo.png +0 -0
  62. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/config/freeflix_position.lua +0 -0
  63. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/config/input.conf +0 -0
  64. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/config/mpv.conf +0 -0
  65. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/data/selectors.jsonc +0 -0
  66. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/data/source_portal.jsonc +0 -0
  67. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/docs/ARCHITECTURE.md +0 -0
  68. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/docs/AUDIT-FREEFLIX-2.0-2026-11-04.md +0 -0
  69. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/docs/CORRECTIONS-2.1.0.md +0 -0
  70. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/docs/README.md +0 -0
  71. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/docs/RELEASE.md +0 -0
  72. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/docs/SCORECARD-2.1.0.md +0 -0
  73. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/docs/USAGE.md +0 -0
  74. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/scripts/build-binary.sh +0 -0
  75. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/__init__.py +0 -0
  76. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/__main__.py +0 -0
  77. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/anilist.py +0 -0
  78. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/cli.py +0 -0
  79. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/cloudflare.py +0 -0
  80. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/config_loader.py +0 -0
  81. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/data/channel_logos.json +0 -0
  82. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/data/clubs.json +0 -0
  83. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/__init__.py +0 -0
  84. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/anilist.py +0 -0
  85. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/coflix.py +0 -0
  86. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/french_manga.py +0 -0
  87. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/french_stream.py +0 -0
  88. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/goldenms.py +0 -0
  89. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/hianime.py +0 -0
  90. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/nyaa.py +0 -0
  91. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/papystreaming.py +0 -0
  92. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/handlers/yts.py +0 -0
  93. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/health.py +0 -0
  94. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/history_ui.py +0 -0
  95. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/httpcache.py +0 -0
  96. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/icons.py +0 -0
  97. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/languages.py +0 -0
  98. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/logsetup.py +0 -0
  99. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/motion.py +0 -0
  100. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/mpv_ipc.py +0 -0
  101. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/neon.py +0 -0
  102. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/net_config.py +0 -0
  103. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/notifications.py +0 -0
  104. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/platform_android.py +0 -0
  105. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/progress.py +0 -0
  106. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/providers_registry.py +0 -0
  107. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/__init__.py +0 -0
  108. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/anime_sama_planning.py +0 -0
  109. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/coflix.py +0 -0
  110. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/config.py +0 -0
  111. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/deobfuscate.py +0 -0
  112. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/eztv.py +0 -0
  113. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/french_manga.py +0 -0
  114. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/french_stream.py +0 -0
  115. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/goldenms.py +0 -0
  116. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/nyaa.py +0 -0
  117. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/papystreaming.py +0 -0
  118. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/subtitles.py +0 -0
  119. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/scraping/yts.py +0 -0
  120. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/splash.py +0 -0
  121. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/subtitles.py +0 -0
  122. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/terminal_image.py +0 -0
  123. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/themes.py +0 -0
  124. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/tracker.py +0 -0
  125. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/ui.py +0 -0
  126. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/src/freeflix_cli/update_checker.py +0 -0
  127. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/conftest.py +0 -0
  128. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_android_setup.py +0 -0
  129. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_anilist_errors.py +0 -0
  130. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_anilist_token_input.py +0 -0
  131. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_anime_sama_planning.py +0 -0
  132. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_audit_fixes.py +0 -0
  133. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_batch_download.py +0 -0
  134. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_coflix_parse.py +0 -0
  135. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_core_units.py +0 -0
  136. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_cover_cache.py +0 -0
  137. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_decoy_stream.py +0 -0
  138. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_download_fixes.py +0 -0
  139. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_download_integration.py +0 -0
  140. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_download_robustness.py +0 -0
  141. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_episode_parsing.py +0 -0
  142. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_fsvid_decode.py +0 -0
  143. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_health.py +0 -0
  144. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_httpcache.py +0 -0
  145. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_i18n_complete.py +0 -0
  146. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_keyboard.py +0 -0
  147. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_managed_binaries.py +0 -0
  148. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_mouse.py +0 -0
  149. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_mpv_ipc.py +0 -0
  150. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_navigation.py +0 -0
  151. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_nerd_font.py +0 -0
  152. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_nyaa.py +0 -0
  153. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_pack_a_polish.py +0 -0
  154. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_performance.py +0 -0
  155. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_platform_android.py +0 -0
  156. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_player_dash.py +0 -0
  157. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_polish.py +0 -0
  158. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_premium_players.py +0 -0
  159. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_proxy_endpoint.py +0 -0
  160. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_proxy_integration.py +0 -0
  161. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_proxy_resilient.py +0 -0
  162. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_proxy_ssrf.py +0 -0
  163. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_recent_new_episodes.py +0 -0
  164. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_resilient.py +0 -0
  165. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_scraper_resilience.py +0 -0
  166. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_search_parity.py +0 -0
  167. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_sendvid.py +0 -0
  168. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_stream_analysis.py +0 -0
  169. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_stream_integrity.py +0 -0
  170. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_system_deps_cache.py +0 -0
  171. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_terminal_image.py +0 -0
  172. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_update_prompt.py +0 -0
  173. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_v1105_improvements.py +0 -0
  174. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tests/test_v2_aesthetic.py +0 -0
  175. {freeflix_cli-2.4.0 → freeflix_cli-2.5.0}/tools/audit_providers.py +0 -0
@@ -1,5 +1,186 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.5.0
4
+
5
+ - **Anime-Sama : les lecteurs jouent enfin (nouveau format 12/11/2026)**. Les
6
+ tableaux d'épisodes ne contiennent plus d'URL mais `"lien:<id>"` ; le vrai
7
+ lien vient de `/api/lien.php?j=<jeton>&l=<id>`, qui répond
8
+ **`403 {"erreur":"origine"}`** dès que la requête paraît cross-site (le
9
+ fingerprint navigateur envoie `Sec-Fetch-Site: none`). C'était la cause du
10
+ `anime-sama::signed` affiché puis de « Impossible de résoudre l'URL du
11
+ flux ». FreeFlix signe maintenant en `same-origin`, avec rafraîchissement du
12
+ jeton expiré.
13
+ - **Tous les lecteurs visibles sont réellement lisibles** : les lignes
14
+ indirectes sont résolues **en parallèle avant le menu** (et non plus une par
15
+ une au clic), donc les lecteurs morts (uqload bloqué par sa page « premium »,
16
+ ids sendvid supprimés) disparaissent au lieu de faire échouer la lecture.
17
+ L'analyse de qualité (résolutions + bitrate) tourne ensuite normalement sur
18
+ ces URL réelles : `1080p ~4.9 · 480p ~0.5 Mbps`, `720p ~1.1 Mbps` mesurés.
19
+ - **Déduplication corrigée** : toutes les lignes indirectes partagent la même
20
+ URL factice, ce qui réduisait un épisode à un seul « Lecteur 1 ». L'identité
21
+ du slot (épisode/lecteur/id) est maintenant la clé.
22
+ - **Saisons 9× plus rapides** : un seul téléchargement de page (les langues
23
+ déclarées par le site) au lieu de sonder neuf `episodes.js` en 404.
24
+ - **« Nouveautés » visible dès l'ouverture** : le flux coûtait ~30 s de réseau
25
+ alors que l'écran n'attendait que 8 s — d'où la liste vide jusqu'à un détour
26
+ par le Planning. Il est désormais persisté sur disque (affichage immédiat au
27
+ lancement), rafraîchi en parallèle (4 séries à la fois) et l'attente est
28
+ portée à 45 s.
29
+ - **Match : message court** quand la journée est vide —
30
+ « Aucun match disponible aujourd'hui. » au lieu d'un mur de diagnostics.
31
+ - **Navigation fluide dans la recherche et le planning** : les touches d'une
32
+ même rafale (auto-repeat, frappes pendant le rendu d'une affiche) étaient
33
+ **silencieusement jetées** — pire, une touche Entrée coincée dans le même
34
+ lot était avalée et l'écran semblait bloqué. Décodage un-événement-par-lot
35
+ avec rejeu du reste, et l'affiche sixel n'est plus repeinte (~128 ms, 0,5 Mo
36
+ par appui) qu'une fois la navigation stabilisée. Mesures : 10 flèches
37
+ rapides = 10/10 appliquées (avant 1/10), survol souris 120 rapports = retour
38
+ en 10 ms (avant > 30 s).
39
+ - **Sortie propre** : Rich écrivait `?1049l` trois fois, et Konsole rejouait
40
+ son écran sauvegardé à chaque fois — d'où la ligne « Que veux-tu faire ?
41
+ Quitter » ré-affichée sous le prompt du shell. Une seule sortie d'écran
42
+ alterné, purge des rapports souris en vol, et Ctrl-C affiche « Au revoir ! »
43
+ au lieu d'un traceback.
44
+ - **Windows : lecture réparée**. Le mpv/ffmpeg téléchargé par le setup était
45
+ installé dans un dossier hors PATH que la résolution ignorait (et
46
+ `_resolve_tool` cherchait `ffmpeg` sans `.exe`) → « mpv is not installed »
47
+ alors qu'il était là. Aussi : `select()` sur un pipe (crash
48
+ `WinError 10038` du préflight Match) remplacé par un lecteur portable,
49
+ `-nostdin` + `CREATE_NO_WINDOW` sur les pipes ffmpeg, port du proxy publié
50
+ avant le bind réel (course → « connection refused »), en-têtes du mode
51
+ direct mpv joints par des virgules, VLC Windows routé par le proxy HLS.
52
+ - **Installation idempotente** : les « fausses demandes » à chaque lancement
53
+ venaient d'un setup dont le succès dépendait du run courant (un shader
54
+ manquant remettait `setup_done=False` pour toujours). Le succès est dérivé
55
+ des fichiers réellement sur disque, une réparation silencieuse ne
56
+ télécharge que le manquant et ne pose plus de question, les shaders sont
57
+ validés par leur contenu (plus de re-téléchargement quand Anime4K change en
58
+ amont), la Nerd Font n'est plus retéléchargée, le wrapper PRIME et
59
+ FlareSolverr ne sont plus réécrits/recréés. Scripts : pré-vérification des
60
+ paquets (2ᵉ run : 0 install, 0 téléchargement, config utilisateur
61
+ conservée), `uv tool upgrade` au lieu de `--force`, mode non-TTY sans
62
+ abandon, PowerShell 5.1 sans `NativeCommandError`, PATH du bon shell.
63
+ - **`--doctor` honnête** : un binaire système n'est plus annoncé « managed
64
+ MISSING », FlareSolverr non lancé = « NOT RUNNING (optional) », versions
65
+ minimales comparées, commande de réinstallation adaptée à uv/pipx/pip.
66
+ - 752 tests au vert, ruff et shellcheck OK.
67
+
68
+ ## 2.4.4
69
+
70
+ - **animes-sama.fr (avec un « s ») réparé sur trois points**. Ce miroir a
71
+ divergé de `anime-sama.to` et cassait FreeFlix silencieusement :
72
+ 1. la recherche utilise une **troisième mise en page** (`div.card-title`,
73
+ sans titre `h2`/`h3`) → **0 résultat** pour toute requête ;
74
+ 2. `<langue>/episodes.js` répond **404** : les épisodes sont désormais
75
+ **inline** dans la page de saison → « Aucun épisode trouvé » ;
76
+ 3. ces tableaux ne contiennent plus d'URL mais des **drapeaux `"1"`/`""`** :
77
+ le vrai lien est fabriqué à la demande par l'API signée
78
+ `/api/player/sign` puis `/api/player/resolve`. FreeFlix sait maintenant
79
+ signer et résoudre ces liens (même session = même cookie CSRF, sinon
80
+ `419`), donc ces lecteurs jouent au lieu d'être masqués.
81
+ - **Vérifié sur Avatar, le dernier maître de l'air** (saisons 1, 3, 4, film,
82
+ VF et VOSTFR) : 2 à 3 lecteurs par épisode, chaîne complète
83
+ sign → resolve → `PLAYABLE`.
84
+ - **`oneupload.to` masqué** : le domaine répond `Connection refused` depuis
85
+ six pays testés (HK, ID, IR, TR…) et depuis ici — il est mort. C'était le
86
+ « Lecteur 1 » de nombreuses séries.
87
+ - **Anime-Sama : les lecteurs Voe rotatifs s'affichent enfin**. Le site sert
88
+ son lecteur Voe sous des domaines jetables qui changent tous les jours
89
+ (`claireguessthousand`, `derricknationtop`, `ashleyhardat`,
90
+ `shanelocaldog`…). Comme la liste était figée dans le code, ces lignes
91
+ étaient **masquées comme « non supportées »** alors que l'épisode se lit.
92
+ La détection reconnaît maintenant la forme de l'URL (`/e/<id>`) puis
93
+ vérifie en direct (avec cache) que la page parle bien Voe, ce qui couvre
94
+ les prochains domaines sans nouvelle version.
95
+ - **Contrôle complet des lecteurs Anime-Sama** (7 séries, 2026-11-11) :
96
+ `ansembed`, `smoothpre` et les miroirs Voe → **lisibles** ;
97
+ `uqload` réclame désormais de désactiver l'adblocker (bloqué côté site),
98
+ `sibnet` répond 403 sur toute la plateforme, `sendvid` est supprimé (404),
99
+ `vk.com` et `oneupload` ne sont pas des lecteurs supportés.
100
+ - 6 tests dédiés (`tests/test_voe_mirrors.py`).
101
+ - **Fin du `avformat_open_input() failed` incompréhensible**. Un lien CDN
102
+ signé peut expirer entre la résolution et la lecture : la playlist répond
103
+ encore `200` avec tous les segments, mais **chaque segment revient en 403**.
104
+ mpv s'arrêtait sur cette erreur ffmpeg brute, VLC affichait une fenêtre
105
+ noire — impossible de distinguer « lien expiré » de « installation cassée ».
106
+ Un préflight HLS vérifie maintenant la playlist **et le premier segment**
107
+ avant d'ouvrir le lecteur :
108
+ « Ce lien de lecture a expiré (le CDN refuse les segments). »
109
+ + « Rouvre l'épisode ou choisis une autre source pour obtenir un lien neuf. »
110
+ - **Relance automatique de mpv** : si le lecteur se ferme en moins de 3 s
111
+ (échec du démultiplexeur), FreeFlix re-sonde le flux et retente une fois au
112
+ lieu d'abandonner.
113
+ - Le préflight est volontairement **non bloquant** : une erreur réseau, une
114
+ playlist multipiste, un direct sans segment ou un `.mp4` ne refusent jamais
115
+ une lecture. Un flux qui marche n'est pas bloqué par la sonde.
116
+ - 7 tests dédiés (`tests/test_hls_preflight.py`) : expiré, 410, hors ligne,
117
+ flux valide, master playlist, panne réseau, fichier direct.
118
+ - 657 tests au vert, ruff OK.
119
+
120
+ ## 2.4.3
121
+
122
+ - **Installation initiale réparée** : le pin SHA-256 de `mpv.conf` n'avait pas
123
+ suivi la suppression du chargement Anime4K, donc un premier lancement
124
+ téléchargeait le bon fichier puis le refusait (« somme de contrôle
125
+ INCORRECTE »). Le pin est corrigé, le message distingue désormais un fichier
126
+ modifié en amont d'un binaire altéré, et un test CI compare les pins aux
127
+ fichiers `config/` pour empêcher la dérive. Un pin reste valide que le
128
+ fichier arrive en LF (HTTPS) ou en CRLF (copie Windows).
129
+ - **Un `mpv.conf` déjà présent n'est plus écrasé** par le setup (Linux et
130
+ macOS) : un réglage personnel (`hwdec=vaapi-copy`, cache, HDR…) survit
131
+ désormais à un relancement du premier lancement. Seuls les fichiers
132
+ réellement absents sont téléchargés.
133
+ - **mpv sous Windows : vrai binaire CLI** : l'installateur pose
134
+ `shinchiro.mpv` (puis `mpv-player.mpv-CI.MSVC`, `mpv.net` en dernier
135
+ recours) au lieu de `mpv.net` seul. `mpv.net` ne fournit que `mpvnet.exe`,
136
+ une interface graphique qui n'accepte pas toujours la lecture sur stdin,
137
+ l'IPC ou `--config-dir` — d'où les échecs de lecture. La découverte de mpv
138
+ cherche aussi les dossiers winget versionnés, Scoop et Chocolatey, et le
139
+ dossier `WinGet\Links` est ajouté au PATH après installation.
140
+ - **Faux positif antivirus documenté** : Defender signale parfois
141
+ l'installateur PowerShell ou le binaire PyInstaller comme
142
+ `Trojan:Win32/Commando.*!ml` / `Wacatac.*!ml` (heuristique `!ml`, pas une
143
+ signature). Le README remplace le `iwr | iex` par un téléchargement +
144
+ `Unblock-File`, et `docs/TROUBLESHOOTING.md` explique comment vérifier le
145
+ SHA-256, restaurer le fichier et signaler le faux positif.
146
+ - **Match : erreur YacineTV clarifiée**. Quand l'API répond `200` avec un
147
+ catalogue vide, FreeFlix l'annonce comme tel (« côté serveur, 0 rencontre »)
148
+ au lieu d'afficher l'erreur `HTTP 436` du host de secours, qui faisait croire
149
+ à un problème de connexion. Le cache précédent reste utilisé si disponible.
150
+ - 635 tests au vert, ruff OK.
151
+
152
+ ## 2.4.2
153
+
154
+ - **Match ne reste plus bloqué sur un écran vide** : si l'API répond `[]`
155
+ (entre deux journées, ou incident côté serveur), FreeFlix garde le dernier
156
+ catalogue connu et affiche un message clair (« Le catalogue est vide entre
157
+ deux journées ; réessaie plus tard. ») au lieu d'une liste vide.
158
+ - En cas d'erreur réseau, message explicite + pause pour pouvoir lire la
159
+ raison, puis retour au menu.
160
+ - 625 tests au vert, ruff OK.
161
+
162
+ ## 2.4.1
163
+
164
+ - **Match : une seule ligne par affiche**. Le catalogue répétait un même
165
+ match sur plusieurs créneaux (un « Twente - Ajax » en direct alors qu'un
166
+ autre du même jour était déjà terminé). Une entrée live/à venir/terminée
167
+ par match, la plus utile gagne.
168
+ - **Bascule automatique de source** : un match en direct expose désormais
169
+ HD, SD, Low et des flux **Multi**. Si la qualité choisie est refusée par
170
+ le CDN, FreeFlix essaie les autres sources au lieu de s'arrêter.
171
+ - **Contrôle avant lecture fiable** : le préflight passe par **ffmpeg en
172
+ direct** (comme la lecture) et lit sa sortie en continu. Le proxy local
173
+ déclenchait des 404 anti-hotlink et un live qui joue « sans fin » était
174
+ pris pour une panne — les deux sont corrigés. Les erreurs sont
175
+ distinguées : `403` (serveur qui refuse) vs `404/invalid data` (flux sans
176
+ données).
177
+ - **Multi en tête** : les flux « Multi » passent avant HD/SD/Low (source la
178
+ plus fiable en direct) ; tri stable, ordre du catalogue conservé à
179
+ qualité égale.
180
+ - Proxy HLS : rafraîchissement automatique de la playlist quand un segment
181
+ devient introuvable.
182
+ - 618 tests au vert, ruff OK.
183
+
3
184
  ## 2.4.0
4
185
 
5
186
  - **Match regroupé par statut** : sections `En direct (n)`, `À venir (n)`,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: freeflix-cli
3
- Version: 2.4.0
3
+ Version: 2.5.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
@@ -143,12 +143,21 @@ No git, no package manager, no sudo needed. Just Python (or PowerShell on Window
143
143
  # Linux / macOS
144
144
  curl -fsSL https://raw.githubusercontent.com/freedy237/freeflix-cli/main/scripts/install.py | python3
145
145
 
146
- # Windows (PowerShell)
147
- powershell -c "iwr -useb https://raw.githubusercontent.com/freedy237/freeflix-cli/main/scripts/install.ps1 | iex"
146
+ # Windows (PowerShell) — download, inspect, then run
147
+ irm https://raw.githubusercontent.com/freedy237/freeflix-cli/main/scripts/install.ps1 -OutFile install.ps1
148
+ Unblock-File .\install.ps1
149
+ powershell -ExecutionPolicy Bypass -File .\install.ps1
148
150
  ```
149
151
 
150
152
  This installs `uv` if missing, then `freeflix-cli`.
151
153
 
154
+ > **Antivirus on Windows:** Defender's ML engine sometimes flags the installer
155
+ > or the standalone `.exe` as `Trojan:Win32/Commando.*!ml` or
156
+ > `.../Wacatac.*!ml`. The `!ml` suffix means a heuristic guess, not a virus
157
+ > signature — it fires on unsigned installers that download and run things.
158
+ > Verify the SHA-256 published with the release, then restore/add an exclusion.
159
+ > Details: [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#windows--antivirus-faux-positif).
160
+
152
161
  ### From PyPI (if you already have the system deps)
153
162
 
154
163
  If `mpv`, `yt-dlp`, `ffmpeg`, `aria2` and `libnotify` are installed :
@@ -111,12 +111,21 @@ No git, no package manager, no sudo needed. Just Python (or PowerShell on Window
111
111
  # Linux / macOS
112
112
  curl -fsSL https://raw.githubusercontent.com/freedy237/freeflix-cli/main/scripts/install.py | python3
113
113
 
114
- # Windows (PowerShell)
115
- powershell -c "iwr -useb https://raw.githubusercontent.com/freedy237/freeflix-cli/main/scripts/install.ps1 | iex"
114
+ # Windows (PowerShell) — download, inspect, then run
115
+ irm https://raw.githubusercontent.com/freedy237/freeflix-cli/main/scripts/install.ps1 -OutFile install.ps1
116
+ Unblock-File .\install.ps1
117
+ powershell -ExecutionPolicy Bypass -File .\install.ps1
116
118
  ```
117
119
 
118
120
  This installs `uv` if missing, then `freeflix-cli`.
119
121
 
122
+ > **Antivirus on Windows:** Defender's ML engine sometimes flags the installer
123
+ > or the standalone `.exe` as `Trojan:Win32/Commando.*!ml` or
124
+ > `.../Wacatac.*!ml`. The `!ml` suffix means a heuristic guess, not a virus
125
+ > signature — it fires on unsigned installers that download and run things.
126
+ > Verify the SHA-256 published with the release, then restore/add an exclusion.
127
+ > Details: [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#windows--antivirus-faux-positif).
128
+
120
129
  ### From PyPI (if you already have the system deps)
121
130
 
122
131
  If `mpv`, `yt-dlp`, `ffmpeg`, `aria2` and `libnotify` are installed :
@@ -88,6 +88,67 @@ fois) ; annule avec `Échap Échap`.
88
88
  - Police Nerd : l'installateur la pose et configure Windows Terminal ; ferme
89
89
  **toutes** les fenêtres pour que la police s'applique.
90
90
 
91
+ ## Windows : antivirus (faux positif)
92
+
93
+ Defender (ou un antivirus tiers) peut signaler `scripts/install.ps1` ou
94
+ `freeflix-windows-x86_64.exe` comme :
95
+
96
+ - `Trojan:Win32/Commando.A!ml` / `Commando.C!ml`
97
+ - `Trojan:Script/Wacatac.*!ml`
98
+ - `Program:Win32/Wacapew.*!ml`
99
+
100
+ Le suffixe `!ml` = détection **heuristique** (machine learning), pas une
101
+ signature. Un installeur PowerShell non signé qui télécharge `uv`, écrit dans
102
+ le PATH et installe des paquets déclenche régulièrement ces faux positifs ;
103
+ c'est aussi le cas classique des binaires PyInstaller.
104
+
105
+ **Vérifier avant de faire confiance :**
106
+
107
+ 1. Le script est public et lisible en entier :
108
+ <https://github.com/freedy237/freeflix-cli/blob/main/scripts/install.ps1>
109
+ 2. Compare le SHA-256 du binaire téléchargé avec `SHA256SUMS` de la release :
110
+ `Get-FileHash .\freeflix-windows-x86_64.exe -Algorithm SHA256`
111
+ 3. Envoie le fichier sur <https://www.virustotal.com> : quelques détections
112
+ heuristiques parmi 70 moteurs = faux positif probable.
113
+
114
+ **Débloquer :**
115
+
116
+ ```powershell
117
+ # Fichier téléchargé depuis Internet : retire la marque "Mark of the Web"
118
+ Unblock-File .\install.ps1
119
+ Unblock-File .\freeflix-windows-x86_64.exe
120
+
121
+ # Restaure depuis la quarantaine (Sécurité Windows → Protection contre les
122
+ # virus → Historique des menaces → Autoriser sur l'appareil), puis ajoute une
123
+ # exclusion sur le dossier de l'app si nécessaire :
124
+ Add-MpPreference -ExclusionPath "$env:LOCALAPPDATA\Microsoft\WinGet\Packages"
125
+ ```
126
+
127
+ Signale le faux positif à Microsoft :
128
+ <https://www.microsoft.com/en-us/wdsi/filesubmission>
129
+
130
+ ## Windows : mpv introuvable ou ne démarre pas
131
+
132
+ Depuis 2.4.3, l'installateur pose un **vrai mpv CLI** (`shinchiro.mpv`, puis
133
+ `mpv-player.mpv-CI.MSVC` en secours) au lieu de `mpv.net`. `mpv.net` ne fournit
134
+ que `mpvnet.exe`, une interface graphique qui n'accepte pas toujours les
135
+ options utilisées par FreeFlix (lecture sur stdin, IPC, `--config-dir`) —
136
+ d'où les échecs de lecture.
137
+
138
+ ```powershell
139
+ # Vérifier ce que FreeFlix trouve
140
+ freeflix --doctor
141
+ winget install --id shinchiro.mpv -e
142
+ # ou, si le paquet n'existe plus :
143
+ winget search mpv
144
+ ```
145
+
146
+ Après installation, ouvre un **nouveau** terminal (le lien winget est dans
147
+ `%LOCALAPPDATA%\Microsoft\WinGet\Links`). Si seul `mpvnet.exe` est présent,
148
+ FreeFlix l'utilise en dernier recours mais peut refuser certains flux : dans ce
149
+ cas installe VLC (`winget install --id VideoLAN.VLC -e`) et choisis VLC comme
150
+ lecteur des matchs.
151
+
91
152
  ## macOS
92
153
 
93
154
  - `install-mac.sh` écrit dans `~/.config/freeflix/mpv` (dossier dédié).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "freeflix-cli"
7
- version = "2.4.0"
7
+ version = "2.5.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"
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env bash
2
+ # FreeFlix CLI — macOS installer (Homebrew based)
3
+ #
4
+ # Re-runnable by design : already-installed brew packages are skipped, an
5
+ # existing tuned mpv.conf is never overwritten, shaders are only downloaded
6
+ # when missing, and prompts are skipped when stdin is not a terminal.
7
+
8
+ set -euo pipefail
9
+
10
+ BLUE="\033[1;34m"; GREEN="\033[1;32m"; YELLOW="\033[1;33m"; RED="\033[1;31m"; NC="\033[0m"
11
+ log() { echo -e "${BLUE}»${NC} $*"; }
12
+ ok() { echo -e "${GREEN}✓${NC} $*"; }
13
+ warn() { echo -e "${YELLOW}!${NC} $*"; }
14
+ err() { echo -e "${RED}✗${NC} $*" >&2; }
15
+
16
+ # Take the default instead of hanging/aborting when piped (no TTY).
17
+ ask() {
18
+ local prompt="$1" default="$2" ans
19
+ if [[ ! -t 0 ]]; then
20
+ [[ "$default" == "y" ]]
21
+ return
22
+ fi
23
+ read -rp "$prompt" ans
24
+ ans="${ans:-$default}"
25
+ [[ "$ans" =~ ^[Yy] ]]
26
+ }
27
+
28
+ PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
29
+
30
+ # ─── 1. Ensure Homebrew ──────────────────────────────────────────────
31
+ if ! command -v brew >/dev/null 2>&1; then
32
+ log "Homebrew not found, installing it…"
33
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
34
+ # Add brew to PATH for this session (Apple Silicon vs Intel default paths)
35
+ if [[ -x /opt/homebrew/bin/brew ]]; then
36
+ eval "$(/opt/homebrew/bin/brew shellenv)"
37
+ elif [[ -x /usr/local/bin/brew ]]; then
38
+ eval "$(/usr/local/bin/brew shellenv)"
39
+ fi
40
+ fi
41
+ ok "Homebrew available"
42
+
43
+ # ─── 2. Install system packages (only the missing ones) ──────────────
44
+ # `brew list --versions` is a cheap local check ; calling `brew install`
45
+ # again for present packages re-ran the whole dependency resolution and
46
+ # printed upgrade noise on every re-run.
47
+ brew_installed() {
48
+ brew list --versions "$1" >/dev/null 2>&1 \
49
+ || brew list --cask --versions "$1" >/dev/null 2>&1
50
+ }
51
+
52
+ log "Installing system deps via brew (missing only)…"
53
+ for pkg in python yt-dlp ffmpeg aria2 mpv chafa; do
54
+ if brew_installed "$pkg"; then
55
+ continue
56
+ fi
57
+ brew install --quiet "$pkg" || warn "Could not install $pkg — continuing."
58
+ done
59
+ if ! brew_installed vlc; then
60
+ brew install --quiet --cask vlc 2>/dev/null \
61
+ || brew install --quiet vlc 2>/dev/null \
62
+ || warn "VLC unavailable — mpv alone is enough to watch."
63
+ fi
64
+ ok "System packages ready"
65
+
66
+ # ─── 2b. Nerd Font (crisp TUI icons) — idempotent ────────────────────
67
+ if brew_installed font-caskaydia-cove-nerd-font; then
68
+ ok "Nerd Font already installed"
69
+ elif brew install --quiet --cask font-caskaydia-cove-nerd-font 2>/dev/null; then
70
+ ok "Nerd Font installed (set your terminal font to 'CaskaydiaCove Nerd Font')"
71
+ else
72
+ warn "Nerd Font cask unavailable — emoji icons still work out of the box."
73
+ fi
74
+
75
+ # ─── 3. Install freeflix-cli (skip when already at this version) ─────
76
+ install_uv_if_missing() {
77
+ if command -v uv >/dev/null 2>&1; then
78
+ return 0
79
+ fi
80
+ log "uv not found — installing …"
81
+ curl -LsSf https://astral.sh/uv/install.sh | sh
82
+ export PATH="$HOME/.local/bin:$PATH"
83
+ if ! command -v uv >/dev/null 2>&1; then
84
+ err "Could not install uv automatically."
85
+ err "Visit https://docs.astral.sh/uv/#getting-started"
86
+ exit 1
87
+ fi
88
+ ok "uv installed"
89
+ }
90
+
91
+ project_version() {
92
+ sed -n 's/^version = "\(.*\)"/\1/p' "$PROJECT_ROOT/pyproject.toml" | head -n 1
93
+ }
94
+
95
+ install_uv_if_missing
96
+ want="$(project_version)"
97
+ have="$(uv tool list 2>/dev/null | awk '/^freeflix-cli /{print $2; exit}' | sed 's/^v//')"
98
+ if [[ -n "$have" && -n "$want" && "$have" == "$want" \
99
+ && "${FREEFLIX_FORCE_REINSTALL:-0}" != "1" ]]; then
100
+ ok "freeflix-cli ${have} already installed (no reinstall)"
101
+ else
102
+ log "Installing freeflix-cli${want:+ ${want}} via uv…"
103
+ uv tool install --force "$PROJECT_ROOT"
104
+ ok "freeflix command installed"
105
+ fi
106
+
107
+ ensure_permanent_path() {
108
+ local bin_dir="$HOME/.local/bin"
109
+ mkdir -p "$bin_dir"
110
+ local rc
111
+ case "${SHELL:-}" in
112
+ *zsh*) rc="$HOME/.zshrc" ;;
113
+ *bash*) rc="$HOME/.bashrc" ;;
114
+ *) rc="$HOME/.profile" ;;
115
+ esac
116
+ if ! grep -qs "$bin_dir" "$rc" 2>/dev/null; then
117
+ {
118
+ echo ""
119
+ echo "# Added by FreeFlix CLI"
120
+ echo "export PATH=\"$bin_dir:\$PATH\""
121
+ } >> "$rc"
122
+ ok "$bin_dir added to $rc (source it or open a new terminal)"
123
+ else
124
+ ok "$bin_dir already in $rc"
125
+ fi
126
+ }
127
+ ensure_permanent_path
128
+
129
+ # ─── 4. Optional : mpv config + Anime4K (never overwrites) ──────────
130
+ MPV_CFG="$HOME/.config/freeflix/mpv"
131
+
132
+ install_file_once() {
133
+ local src="$1" dest="$2"
134
+ if [[ -s "$dest" ]]; then
135
+ return 0
136
+ fi
137
+ cp "$src" "$dest"
138
+ }
139
+
140
+ download_shader_once() {
141
+ local dest="$1" url="$2"
142
+ if [[ -s "$dest" ]]; then
143
+ return 0
144
+ fi
145
+ if curl -fsSL -o "$dest.part" "$url" && [[ -s "$dest.part" ]]; then
146
+ mv "$dest.part" "$dest"
147
+ else
148
+ rm -f "$dest.part"
149
+ warn "Could not download $(basename "$dest") — anime upscaling may be limited."
150
+ fi
151
+ }
152
+
153
+ if [[ -s "$MPV_CFG/mpv.conf" && -s "$MPV_CFG/input.conf" \
154
+ && -s "$MPV_CFG/scripts/freeflix_position.lua" ]]; then
155
+ ok "mpv config already installed (kept)"
156
+ elif ask "Install default mpv config (Anime4K toggle, anti-crash) ? [Y/n] " y; then
157
+ # FreeFlix launches mpv with --config-dir ~/.config/freeflix/mpv: writing
158
+ # the global ~/.config/mpv made Anime4K invisible to the app.
159
+ mkdir -p "$MPV_CFG/scripts" "$MPV_CFG/shaders"
160
+ install_file_once "$PROJECT_ROOT/config/mpv.conf" "$MPV_CFG/mpv.conf"
161
+ install_file_once "$PROJECT_ROOT/config/input.conf" "$MPV_CFG/input.conf"
162
+ install_file_once "$PROJECT_ROOT/config/freeflix_position.lua" \
163
+ "$MPV_CFG/scripts/freeflix_position.lua"
164
+ ok "mpv config installed (CTRL+1 toggles Anime4K)"
165
+ fi
166
+
167
+ if [[ -d "$MPV_CFG/shaders" ]]; then
168
+ BASE="https://raw.githubusercontent.com/bloc97/Anime4K/master/glsl"
169
+ download_shader_once "$MPV_CFG/shaders/Anime4K_Clamp_Highlights.glsl" \
170
+ "$BASE/Restore/Anime4K_Clamp_Highlights.glsl"
171
+ # mpv.conf loads the S variant by default; CTRL+1 switches to VL.
172
+ download_shader_once "$MPV_CFG/shaders/Anime4K_Restore_CNN_S.glsl" \
173
+ "$BASE/Restore/Anime4K_Restore_CNN_S.glsl"
174
+ download_shader_once "$MPV_CFG/shaders/Anime4K_Upscale_CNN_x2_S.glsl" \
175
+ "$BASE/Upscale/Anime4K_Upscale_CNN_x2_S.glsl"
176
+ download_shader_once "$MPV_CFG/shaders/Anime4K_Restore_CNN_VL.glsl" \
177
+ "$BASE/Restore/Anime4K_Restore_CNN_VL.glsl"
178
+ download_shader_once "$MPV_CFG/shaders/Anime4K_Upscale_CNN_x2_VL.glsl" \
179
+ "$BASE/Upscale/Anime4K_Upscale_CNN_x2_VL.glsl"
180
+ fi
181
+
182
+ # ─── 5. Optional : FlareSolverr (auto-solve Cloudflare) via Docker ───
183
+ flaresolverr_running() {
184
+ curl -fsS --max-time 2 http://127.0.0.1:8191/health >/dev/null 2>&1 \
185
+ || curl -fsS --max-time 2 http://127.0.0.1:8191/ >/dev/null 2>&1
186
+ }
187
+
188
+ if flaresolverr_running; then
189
+ ok "FlareSolverr already running on :8191"
190
+ elif ask "Set up FlareSolverr to auto-solve Cloudflare (needs Docker) ? [y/N] " n; then
191
+ if command -v docker >/dev/null 2>&1; then
192
+ if ! docker start flaresolverr >/dev/null 2>&1; then
193
+ if docker container inspect flaresolverr >/dev/null 2>&1; then
194
+ warn "FlareSolverr container exists but could not be started."
195
+ elif ! docker run -d --name flaresolverr --restart unless-stopped \
196
+ -p 8191:8191 ghcr.io/flaresolverr/flaresolverr:latest; then
197
+ warn "Could not start FlareSolverr — start it manually later."
198
+ fi
199
+ fi
200
+ ok "FlareSolverr requested (give it a moment to be ready)."
201
+ else
202
+ warn "Docker not found. Install Docker, then run the flaresolverr container."
203
+ fi
204
+ fi
205
+
206
+ echo
207
+ ok "Installation complete. Run : ${GREEN}freeflix${NC}"
208
+ log "Icons: set your terminal font to 'CaskaydiaCove Nerd Font', then in"
209
+ log "FreeFlix go to Settings > Icon Style > nerd (emoji is the default)."