freeflix-cli 2.2.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.
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/CHANGELOG.md +11 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/PKG-INFO +1 -1
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/pyproject.toml +1 -1
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/match.py +46 -5
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/i18n.py +2 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/install_health.py +1 -1
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_yacine_match.py +16 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/uv.lock +1 -1
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/.github/workflows/binaries.yml +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/.github/workflows/ci.yml +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/.github/workflows/macos.yml +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/.github/workflows/release.yml +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/.gitignore +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/CONTRIBUTING.md +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/LICENSE +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/README.md +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/SECURITY.md +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/assets/logo.png +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/config/freeflix_position.lua +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/config/input.conf +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/config/mpv.conf +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/data/selectors.jsonc +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/data/source_portal.jsonc +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/docs/ARCHITECTURE.md +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/docs/AUDIT-FREEFLIX-2.0-2026-11-04.md +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/docs/CORRECTIONS-2.1.0.md +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/docs/README.md +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/docs/RELEASE.md +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/docs/SCORECARD-2.1.0.md +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/docs/TROUBLESHOOTING.md +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/docs/USAGE.md +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/scripts/build-binary.sh +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/scripts/install-mac.sh +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/scripts/install.ps1 +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/scripts/install.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/scripts/install.sh +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/scripts/wrappers/mpv +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/__init__.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/__main__.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/anilist.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/cli.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/cli_utils.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/cloudflare.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/config_loader.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/defaults.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/doctor.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/__init__.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/anilist.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/anime_sama.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/coflix.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/french_manga.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/french_stream.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/goldenanime.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/goldenms.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/hianime.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/nyaa.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/papystreaming.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/playback.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/handlers/yts.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/health.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/history_ui.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/httpcache.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/icons.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/languages.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/logsetup.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/main.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/motion.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/mpv_ipc.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/neon.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/net_config.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/notifications.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/platform_android.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/player_manager.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/preview_picker.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/progress.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/providers_registry.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/proxy.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/recent.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/__init__.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/anime_sama.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/anime_sama_planning.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/coflix.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/config.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/deobfuscate.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/eztv.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/french_manga.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/french_stream.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/goldenanime.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/goldenms.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/nyaa.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/objects.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/papystreaming.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/player.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/resilient.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/subtitles.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/utils.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/yacine_match.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/scraping/yts.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/setup_assistant.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/splash.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/subtitles.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/terminal_image.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/themes.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/tracker.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/ui.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/update_checker.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/src/freeflix_cli/yacine_live_proxy.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/conftest.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_android_setup.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_anilist_errors.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_anilist_token_input.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_anime_sama_planning.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_audit_fixes.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_batch_download.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_coflix_parse.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_core_units.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_cover_cache.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_decoy_stream.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_download_fixes.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_download_integration.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_download_robustness.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_episode_parsing.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_fsvid_decode.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_health.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_httpcache.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_i18n_complete.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_keyboard.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_managed_binaries.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_mouse.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_mpv_ipc.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_navigation.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_nerd_font.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_nyaa.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_pack_a_polish.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_performance.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_platform_android.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_player_dash.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_polish.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_premium_players.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_providers_repair.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_proxy_endpoint.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_proxy_integration.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_proxy_resilient.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_proxy_ssrf.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_recent_new_episodes.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_release_config.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_resilient.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_scraper_resilience.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_search_parity.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_sendvid.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_stream_analysis.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_stream_integrity.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_system_deps_cache.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_terminal_image.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_update_prompt.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_v1105_improvements.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tests/test_v2_aesthetic.py +0 -0
- {freeflix_cli-2.2.2 → freeflix_cli-2.3.0}/tools/audit_providers.py +0 -0
|
@@ -1,5 +1,16 @@
|
|
|
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
|
+
|
|
3
14
|
## 2.2.2
|
|
4
15
|
|
|
5
16
|
- Menus **qualité** et **choix du lecteur** en plein écran : souris alignée
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: freeflix-cli
|
|
3
|
-
Version: 2.
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "freeflix-cli"
|
|
7
|
-
version = "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"
|
|
@@ -6,6 +6,8 @@ falls back to the last snapshot only when the API is unreachable.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import re
|
|
10
|
+
|
|
9
11
|
from datetime import datetime, timedelta, timezone
|
|
10
12
|
|
|
11
13
|
from ..cli_utils import (
|
|
@@ -33,6 +35,12 @@ def _when_label(ev: ym.MatchEvent, now: datetime) -> str:
|
|
|
33
35
|
return f"{dt:%d/%m %H:%M}"
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
def _channel_label(channel: str) -> str:
|
|
39
|
+
"""Short live-TV badge: beIN SPORTS 1..4 → B1..B4."""
|
|
40
|
+
m = re.search(r"be\s*in\s*sports?\s*([1-4])", channel or "", re.I)
|
|
41
|
+
return f"B{m.group(1)}" if m else (channel or "")
|
|
42
|
+
|
|
43
|
+
|
|
36
44
|
def _match_label(ev: ym.MatchEvent, now_ts: int, now: datetime) -> str:
|
|
37
45
|
state = ev.status(now_ts)
|
|
38
46
|
when = _when_label(ev, now)
|
|
@@ -43,7 +51,8 @@ def _match_label(ev: ym.MatchEvent, now_ts: int, now: datetime) -> str:
|
|
|
43
51
|
else:
|
|
44
52
|
badge = f"○ {t('Upcoming')}"
|
|
45
53
|
league = f" · {ev.championship}" if ev.championship else ""
|
|
46
|
-
|
|
54
|
+
channel = f"({_channel_label(ev.channel)}) " if ev.channel else ""
|
|
55
|
+
return f"{channel}{badge} {when} · {ev.home} VS {ev.away}{league}"
|
|
47
56
|
|
|
48
57
|
|
|
49
58
|
def _sort_events(events: list[ym.MatchEvent], now_ts: int) -> list[ym.MatchEvent]:
|
|
@@ -51,9 +60,35 @@ def _sort_events(events: list[ym.MatchEvent], now_ts: int) -> list[ym.MatchEvent
|
|
|
51
60
|
return sorted(events, key=lambda e: (order.get(e.status(now_ts), 3), e.start))
|
|
52
61
|
|
|
53
62
|
|
|
63
|
+
# Conservative live HLS minimums (Mbps) used for the GB/hour estimate.
|
|
64
|
+
_MIN_MBPS = {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
|
+
|
|
54
86
|
def _quality_label(stream: dict) -> str:
|
|
55
87
|
name = str(stream.get("name") or "?")
|
|
56
|
-
|
|
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')}")
|
|
57
92
|
|
|
58
93
|
|
|
59
94
|
def _play(event: ym.MatchEvent, stream: dict, player_name: str = "") -> None:
|
|
@@ -95,7 +130,7 @@ def _choose_player(current: str, allow_cancel: bool = False):
|
|
|
95
130
|
print_warning(t("No video player found (mpv or VLC)."))
|
|
96
131
|
return None
|
|
97
132
|
labels = ["mpv" if n == "mpv" else "VLC" for n in avail]
|
|
98
|
-
options =
|
|
133
|
+
options = list(labels)
|
|
99
134
|
if allow_cancel:
|
|
100
135
|
options.append(t("← Back"))
|
|
101
136
|
default = labels.index(current) if current in labels else 0
|
|
@@ -165,7 +200,10 @@ def handle_match() -> None:
|
|
|
165
200
|
choice = select_from_list(
|
|
166
201
|
options,
|
|
167
202
|
f"{t('Choose the quality')} · {event.home} VS {event.away}",
|
|
168
|
-
header=f"{icon('football')}
|
|
203
|
+
header=(f"{icon('football')} "
|
|
204
|
+
f"{event.championship or t('Football')}"
|
|
205
|
+
+ (f" · {_channel_label(event.channel)}"
|
|
206
|
+
if event.channel else "")),
|
|
169
207
|
)
|
|
170
208
|
if choice < 0 or choice >= auto_idx:
|
|
171
209
|
if choice == auto_idx:
|
|
@@ -173,7 +211,10 @@ def handle_match() -> None:
|
|
|
173
211
|
if not stream:
|
|
174
212
|
print_warning(t("No stream available for this match."))
|
|
175
213
|
continue
|
|
176
|
-
|
|
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')}")
|
|
177
218
|
chosen = _choose_player(player_name, allow_cancel=True)
|
|
178
219
|
if chosen:
|
|
179
220
|
_play(event, stream, chosen)
|
|
@@ -1023,6 +1023,8 @@ _FR = {
|
|
|
1023
1023
|
'Playing in VLC…': 'Lecture dans VLC…',
|
|
1024
1024
|
'VLC stopped early. Log:': 'VLC s’est arrêté trop tôt. Journal :',
|
|
1025
1025
|
'Movies, Series & Anime': 'Films, Séries & Anime',
|
|
1026
|
+
'Mbps': 'Mbps',
|
|
1027
|
+
'GB/h': 'Go/h',
|
|
1026
1028
|
'Match player:': 'Lecteur des matchs :',
|
|
1027
1029
|
'Match player set to:': 'Lecteur des matchs :',
|
|
1028
1030
|
'Choose a player': 'Choisis un lecteur',
|
|
@@ -279,3 +279,19 @@ def test_play_direct_stream_respects_explicit_vlc(monkeypatch):
|
|
|
279
279
|
assert pm._VLC_KEEPALIVE
|
|
280
280
|
finally:
|
|
281
281
|
pm._stop_vlc_keepalive()
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def test_match_channel_and_quality_estimates():
|
|
285
|
+
from datetime import datetime, timedelta, timezone
|
|
286
|
+
from freeflix_cli.handlers import match as mh
|
|
287
|
+
|
|
288
|
+
now = datetime.now(tz=timezone(timedelta(hours=1)))
|
|
289
|
+
ev = ym.MatchEvent("1", "Ligue 1", "beIN SPORTS 4",
|
|
290
|
+
int(now.timestamp()) - 60, int(now.timestamp()) + 3600,
|
|
291
|
+
"Toulouse", "Strasbourg")
|
|
292
|
+
label = mh._match_label(ev, int(now.timestamp()), now)
|
|
293
|
+
assert label.startswith("(B4)")
|
|
294
|
+
hd = mh._quality_label({"name": "HD"})
|
|
295
|
+
assert "5.0" in hd and "2.25" in hd
|
|
296
|
+
low = mh._quality_label({"name": "Low"})
|
|
297
|
+
assert "1.0" in low and "0.45" in low
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|