freeflix-cli 2.1.2__tar.gz → 2.2.2__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.
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/CHANGELOG.md +49 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/PKG-INFO +27 -9
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/README.md +26 -8
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/config/mpv.conf +8 -15
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/pyproject.toml +1 -1
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/cli_utils.py +51 -8
- freeflix_cli-2.2.2/src/freeflix_cli/handlers/match.py +186 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/i18n.py +46 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/icons.py +2 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/install_health.py +1 -1
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/main.py +17 -7
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/player_manager.py +281 -10
- freeflix_cli-2.2.2/src/freeflix_cli/scraping/yacine_match.py +337 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/setup_assistant.py +27 -16
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/tracker.py +8 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/update_checker.py +36 -7
- freeflix_cli-2.2.2/src/freeflix_cli/yacine_live_proxy.py +197 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_audit_fixes.py +34 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_keyboard.py +6 -5
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_update_prompt.py +18 -0
- freeflix_cli-2.2.2/tests/test_yacine_match.py +281 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/uv.lock +1 -1
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/.github/workflows/binaries.yml +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/.github/workflows/ci.yml +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/.github/workflows/macos.yml +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/.github/workflows/release.yml +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/.gitignore +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/CONTRIBUTING.md +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/LICENSE +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/SECURITY.md +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/assets/logo.png +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/config/freeflix_position.lua +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/config/input.conf +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/data/selectors.jsonc +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/data/source_portal.jsonc +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/docs/ARCHITECTURE.md +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/docs/AUDIT-FREEFLIX-2.0-2026-11-04.md +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/docs/CORRECTIONS-2.1.0.md +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/docs/README.md +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/docs/RELEASE.md +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/docs/SCORECARD-2.1.0.md +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/docs/TROUBLESHOOTING.md +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/docs/USAGE.md +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/scripts/build-binary.sh +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/scripts/install-mac.sh +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/scripts/install.ps1 +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/scripts/install.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/scripts/install.sh +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/scripts/wrappers/mpv +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/__init__.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/__main__.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/anilist.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/cli.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/cloudflare.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/config_loader.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/defaults.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/doctor.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/__init__.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/anilist.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/anime_sama.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/coflix.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/french_manga.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/french_stream.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/goldenanime.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/goldenms.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/hianime.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/nyaa.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/papystreaming.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/playback.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/handlers/yts.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/health.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/history_ui.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/httpcache.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/languages.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/logsetup.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/motion.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/mpv_ipc.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/neon.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/net_config.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/notifications.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/platform_android.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/preview_picker.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/progress.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/providers_registry.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/proxy.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/recent.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/__init__.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/anime_sama.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/anime_sama_planning.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/coflix.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/config.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/deobfuscate.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/eztv.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/french_manga.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/french_stream.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/goldenanime.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/goldenms.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/nyaa.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/objects.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/papystreaming.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/player.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/resilient.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/subtitles.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/utils.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/scraping/yts.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/splash.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/subtitles.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/terminal_image.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/themes.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/src/freeflix_cli/ui.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/conftest.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_android_setup.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_anilist_errors.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_anilist_token_input.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_anime_sama_planning.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_batch_download.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_coflix_parse.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_core_units.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_cover_cache.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_decoy_stream.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_download_fixes.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_download_integration.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_download_robustness.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_episode_parsing.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_fsvid_decode.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_health.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_httpcache.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_i18n_complete.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_managed_binaries.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_mouse.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_mpv_ipc.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_navigation.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_nerd_font.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_nyaa.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_pack_a_polish.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_performance.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_platform_android.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_player_dash.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_polish.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_premium_players.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_providers_repair.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_proxy_endpoint.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_proxy_integration.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_proxy_resilient.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_proxy_ssrf.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_recent_new_episodes.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_release_config.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_resilient.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_scraper_resilience.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_search_parity.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_sendvid.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_stream_analysis.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_stream_integrity.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_system_deps_cache.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_terminal_image.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_v1105_improvements.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tests/test_v2_aesthetic.py +0 -0
- {freeflix_cli-2.1.2 → freeflix_cli-2.2.2}/tools/audit_providers.py +0 -0
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.2
|
|
4
|
+
|
|
5
|
+
- Menus **qualité** et **choix du lecteur** en plein écran : souris alignée
|
|
6
|
+
(survol + clic) sur Linux et Windows.
|
|
7
|
+
- **Anime4K désactivé par défaut** : activation explicite par l'utilisateur
|
|
8
|
+
(`CTRL+1` / `CTRL+2`, `CTRL+0` pour couper), conseil affiché en bas de mpv
|
|
9
|
+
pendant 5 s, migration des anciennes configs sans toucher aux shaders perso.
|
|
10
|
+
- Entrée du menu principal renommée **Films, Séries & Anime** (traduite) ;
|
|
11
|
+
la liste des providers et son fonctionnement restent inchangés.
|
|
12
|
+
- **Configs lecteurs isolées** : mpv utilise toujours son dossier dédié
|
|
13
|
+
(`--config-dir`, sinon `--no-config`) ; VLC est lancé avec
|
|
14
|
+
`--ignore-config --no-media-library --no-plugins-cache`. Les réglages
|
|
15
|
+
personnels de la machine ne sont ni lus ni modifiés.
|
|
16
|
+
- Derniers correctifs Windows/mise à jour (ancrage console, VT input, redémarrage
|
|
17
|
+
par nouveau processus, recherche uv/pipx hors PATH).
|
|
18
|
+
- 602 tests au vert, ruff OK.
|
|
19
|
+
|
|
20
|
+
## 2.2.1
|
|
21
|
+
|
|
22
|
+
- **Compatibilité Windows** :
|
|
23
|
+
- ancrage souris des menus inline via `GetConsoleScreenBufferInfo`
|
|
24
|
+
(fini les clics décalés) ;
|
|
25
|
+
- activation VT input avec `ENABLE_EXTENDED_FLAGS` pour que Windows
|
|
26
|
+
Terminal transmette vraiment les rapports SGR ;
|
|
27
|
+
- écran des qualités en plein écran, ancrage fiable sur tous les OS ;
|
|
28
|
+
- redémarrage après mise à jour via un nouveau processus sur Windows
|
|
29
|
+
(`os.execv` n'est pas fiable avec un interpréteur Python verrouillé).
|
|
30
|
+
- **Mises à jour** : correction de la comparaison de version en mode
|
|
31
|
+
source, détection `uv` resserrée, commande de mise à jour adaptée à
|
|
32
|
+
l'installeur réel (uv/pipx/pip).
|
|
33
|
+
- VLC : lecture par **ffmpeg → TS** comme mpv (plus de fin de playlist
|
|
34
|
+
HLS), timestamps normalisés, audio calé.
|
|
35
|
+
- 600 tests + tests Windows dédiés au vert, ruff OK.
|
|
36
|
+
|
|
37
|
+
## 2.2.0
|
|
38
|
+
|
|
39
|
+
- **Nouvel onglet Match** dans le menu principal : matchs de football du
|
|
40
|
+
catalogue YacineTV, à l'heure du Cameroun (UTC+1), avec badge `LIVE`,
|
|
41
|
+
à venir et terminés.
|
|
42
|
+
- Qualités HD/SD/Low + **Auto** (meilleure disponible), choix du lecteur
|
|
43
|
+
**mpv / VLC** mémorisé, souris en clic seul sur les qualités.
|
|
44
|
+
- Noms d'équipes et de compétitions **traduits dynamiquement** en français
|
|
45
|
+
ou anglais (cache local, aucun mapping en dur).
|
|
46
|
+
- Flux live servis par un mini-proxy HLS local : en-têtes UTF-8 exacts et
|
|
47
|
+
segments `.php/.pdf` réécrits pour mpv/VLC.
|
|
48
|
+
- mpv en direct : cache mémoire 3 s, démarrage au bord du live, plus de
|
|
49
|
+
cache disque de 30 min.
|
|
50
|
+
- 598 tests au vert, ruff OK.
|
|
51
|
+
|
|
3
52
|
## 2.1.2
|
|
4
53
|
|
|
5
54
|
- **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.
|
|
3
|
+
Version: 2.2.2
|
|
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
|
|
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,
|
|
69
|
-
|
|
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-
|
|
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
|
-
- **
|
|
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
|
|
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 +
|
|
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
|
|
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,
|
|
37
|
-
|
|
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-
|
|
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
|
-
- **
|
|
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
|
|
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 +
|
|
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 :
|
|
86
|
-
#
|
|
87
|
-
# CTRL+1 → Mode A_VL (
|
|
88
|
-
# CTRL+2 → Mode A_S (Small
|
|
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
|
-
#
|
|
91
|
-
#
|
|
92
|
-
|
|
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.
|
|
7
|
+
version = "2.2.2"
|
|
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
|
-
|
|
630
|
-
|
|
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"
|
|
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
|
|
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,186 @@
|
|
|
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
|
+
from datetime import datetime, timedelta, timezone
|
|
10
|
+
|
|
11
|
+
from ..cli_utils import (
|
|
12
|
+
print_error,
|
|
13
|
+
print_info,
|
|
14
|
+
print_warning,
|
|
15
|
+
select_from_list,
|
|
16
|
+
spinner,
|
|
17
|
+
)
|
|
18
|
+
from ..i18n import t
|
|
19
|
+
from ..icons import icon
|
|
20
|
+
from ..scraping import yacine_match as ym
|
|
21
|
+
from ..tracker import tracker
|
|
22
|
+
|
|
23
|
+
_CAM = timezone(timedelta(hours=1))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _when_label(ev: ym.MatchEvent, now: datetime) -> str:
|
|
27
|
+
dt = ev.start_dt
|
|
28
|
+
today = now.date()
|
|
29
|
+
if dt.date() == today:
|
|
30
|
+
return f"{dt:%H:%M}"
|
|
31
|
+
if dt.date() == today + timedelta(days=1):
|
|
32
|
+
return f"{t('Tomorrow')} {dt:%H:%M}"
|
|
33
|
+
return f"{dt:%d/%m %H:%M}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _match_label(ev: ym.MatchEvent, now_ts: int, now: datetime) -> str:
|
|
37
|
+
state = ev.status(now_ts)
|
|
38
|
+
when = _when_label(ev, now)
|
|
39
|
+
if state == "live":
|
|
40
|
+
badge = f"● {t('LIVE')}"
|
|
41
|
+
elif state == "ended":
|
|
42
|
+
badge = f"✓ {t('Finished')}"
|
|
43
|
+
else:
|
|
44
|
+
badge = f"○ {t('Upcoming')}"
|
|
45
|
+
league = f" · {ev.championship}" if ev.championship else ""
|
|
46
|
+
return f"{badge} {when} · {ev.home} VS {ev.away}{league}"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _sort_events(events: list[ym.MatchEvent], now_ts: int) -> list[ym.MatchEvent]:
|
|
50
|
+
order = {"live": 0, "upcoming": 1, "ended": 2}
|
|
51
|
+
return sorted(events, key=lambda e: (order.get(e.status(now_ts), 3), e.start))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _quality_label(stream: dict) -> str:
|
|
55
|
+
name = str(stream.get("name") or "?")
|
|
56
|
+
return f"{icon('play')} {name}"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _play(event: ym.MatchEvent, stream: dict, player_name: str = "") -> None:
|
|
60
|
+
from ..player_manager import play_direct_stream
|
|
61
|
+
|
|
62
|
+
title = f"YacineTV · {event.home} VS {event.away}"
|
|
63
|
+
print_info(f"{t('Selected quality:')} {stream.get('name', '?')}")
|
|
64
|
+
ok = play_direct_stream(stream["url"], stream.get("headers") or {}, title,
|
|
65
|
+
player_name=player_name or None)
|
|
66
|
+
if not ok:
|
|
67
|
+
print_warning(t("Could not start the match stream."))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _available_players() -> list[str]:
|
|
71
|
+
from ..player_manager import get_mpv_path, get_vlc_path
|
|
72
|
+
|
|
73
|
+
out = []
|
|
74
|
+
if get_mpv_path():
|
|
75
|
+
out.append("mpv")
|
|
76
|
+
if get_vlc_path():
|
|
77
|
+
out.append("vlc")
|
|
78
|
+
return out
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _current_player() -> str:
|
|
82
|
+
avail = _available_players()
|
|
83
|
+
saved = (tracker.get_match_player() or "").lower()
|
|
84
|
+
if saved in avail:
|
|
85
|
+
return saved
|
|
86
|
+
return "mpv" if "mpv" in avail else ("vlc" if "vlc" in avail else "")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _choose_player(current: str, allow_cancel: bool = False):
|
|
90
|
+
"""Choose mpv/VLC after quality. Returns None when Back is pressed."""
|
|
91
|
+
avail = _available_players()
|
|
92
|
+
if len(avail) < 2:
|
|
93
|
+
if avail:
|
|
94
|
+
return avail[0]
|
|
95
|
+
print_warning(t("No video player found (mpv or VLC)."))
|
|
96
|
+
return None
|
|
97
|
+
labels = ["mpv" if n == "mpv" else "VLC" for n in avail]
|
|
98
|
+
options = [f"{icon('play')} {label}" for label in labels]
|
|
99
|
+
if allow_cancel:
|
|
100
|
+
options.append(t("← Back"))
|
|
101
|
+
default = labels.index(current) if current in labels else 0
|
|
102
|
+
idx = select_from_list(
|
|
103
|
+
options, t("Choose a player"), header=f"{icon('football')} {t('Match')}",
|
|
104
|
+
default_index=default,
|
|
105
|
+
)
|
|
106
|
+
if 0 <= idx < len(avail):
|
|
107
|
+
chosen = avail[idx]
|
|
108
|
+
tracker.set_match_player(chosen)
|
|
109
|
+
print_info(f"{t('Match player set to:')} {chosen}")
|
|
110
|
+
return chosen
|
|
111
|
+
return None if allow_cancel else current
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def handle_match() -> None:
|
|
115
|
+
"""Live football list → quality (Auto at the bottom) → playback."""
|
|
116
|
+
while True:
|
|
117
|
+
# Refresh on every pass (including after a playback) so the list and
|
|
118
|
+
# the LIVE badges follow the catalogue, like the weekly planning.
|
|
119
|
+
try:
|
|
120
|
+
with spinner(t("Loading matches…")):
|
|
121
|
+
events, cached = ym.fetch_events()
|
|
122
|
+
events = ym.localize_events(
|
|
123
|
+
events, tracker.get_language() or "en")
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print_error(f"{t('Matches unavailable:')} {e}")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
if cached:
|
|
129
|
+
print_warning(t("Showing cached matches — network unavailable."))
|
|
130
|
+
if not events:
|
|
131
|
+
print_warning(t("No football matches right now."))
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
now = datetime.now(tz=_CAM)
|
|
135
|
+
now_ts = int(now.timestamp())
|
|
136
|
+
ordered = _sort_events(events, now_ts)
|
|
137
|
+
player_name = _current_player()
|
|
138
|
+
labels = [_match_label(ev, now_ts, now) for ev in ordered]
|
|
139
|
+
labels.append(t("← Back"))
|
|
140
|
+
header = (f"{icon('football')} {t('Football matches')} · "
|
|
141
|
+
f"{t('Cameroon time (UTC+1)')}")
|
|
142
|
+
|
|
143
|
+
idx = select_from_list(
|
|
144
|
+
labels, t("Select a match"), header=header, clear_on_back=True,
|
|
145
|
+
)
|
|
146
|
+
if idx < 0 or idx >= len(ordered):
|
|
147
|
+
return
|
|
148
|
+
event = ordered[idx]
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
with spinner(t("Loading streams…")):
|
|
152
|
+
streams = ym.fetch_streams(event.id)
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print_error(f"{t('Streams unavailable:')} {e}")
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
if not streams:
|
|
158
|
+
print_warning(t("No stream available for this match."))
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
options = [_quality_label(s) for s in streams]
|
|
162
|
+
options.append(f"{icon('sparkle')} {t('Auto (best quality)')}")
|
|
163
|
+
options.append(t("← Back"))
|
|
164
|
+
auto_idx = len(streams)
|
|
165
|
+
choice = select_from_list(
|
|
166
|
+
options,
|
|
167
|
+
f"{t('Choose the quality')} · {event.home} VS {event.away}",
|
|
168
|
+
header=f"{icon('football')} {event.championship or t('Football')}",
|
|
169
|
+
)
|
|
170
|
+
if choice < 0 or choice >= auto_idx:
|
|
171
|
+
if choice == auto_idx:
|
|
172
|
+
stream = ym.pick_auto(streams)
|
|
173
|
+
if not stream:
|
|
174
|
+
print_warning(t("No stream available for this match."))
|
|
175
|
+
continue
|
|
176
|
+
print_info(f"{t('Auto quality:')} {stream.get('name', '?')}")
|
|
177
|
+
chosen = _choose_player(player_name, allow_cancel=True)
|
|
178
|
+
if chosen:
|
|
179
|
+
_play(event, stream, chosen)
|
|
180
|
+
else:
|
|
181
|
+
return
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
chosen = _choose_player(player_name, allow_cancel=True)
|
|
185
|
+
if chosen:
|
|
186
|
+
_play(event, streams[choice], chosen)
|
|
@@ -990,6 +990,52 @@ _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
|
+
'Match player:': 'Lecteur des matchs :',
|
|
1027
|
+
'Match player set to:': 'Lecteur des matchs :',
|
|
1028
|
+
'Choose a player': 'Choisis un lecteur',
|
|
1029
|
+
'No video player found (mpv or VLC).':
|
|
1030
|
+
'Aucun lecteur vidéo trouvé (mpv ou VLC).',
|
|
1031
|
+
'Auto': 'Auto',
|
|
1032
|
+
'mpv is not installed or not in PATH.':
|
|
1033
|
+
"mpv n'est pas installé ou absent du PATH.",
|
|
1034
|
+
'VLC is not installed or not in PATH.':
|
|
1035
|
+
"VLC n'est pas installé ou absent du PATH.",
|
|
1036
|
+
'This stream needs mpv or VLC: the required HTTP headers cannot be forwarded to a browser.':
|
|
1037
|
+
'Ce flux nécessite mpv ou VLC : les en-têtes HTTP requis ne peuvent pas '
|
|
1038
|
+
"être transmis à un navigateur.",
|
|
993
1039
|
}
|
|
994
1040
|
|
|
995
1041
|
|
|
@@ -64,6 +64,7 @@ _EMOJI = {
|
|
|
64
64
|
"arrow_down": "⬇",
|
|
65
65
|
"books": "📚",
|
|
66
66
|
"package": "📦",
|
|
67
|
+
"football": "⚽",
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
# Semantic name -> Nerd Font codepoint (Font Awesome range, present in every
|
|
@@ -120,6 +121,7 @@ _NERD = {
|
|
|
120
121
|
"arrow_down": chr(0xF063), # arrow-down (badges keep ✓/⬇/▸ readout)
|
|
121
122
|
"books": chr(0xF02D), # book
|
|
122
123
|
"package": chr(0xF187), # archive
|
|
124
|
+
"football": chr(0xF1E3), # Font Awesome soccer ball (same size as peers)
|
|
123
125
|
}
|
|
124
126
|
|
|
125
127
|
|