diff --git a/README.md b/README.md
index 113eb4eb..c92ad2a8 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
* Insert an _All_ category when missing so you can easily **search your entire catalog**.
* Update ***[Mpv](https://mpv.io/)*** and ***[Sfvip Player](https://github.com/K4L4Uz/SFVIP-Player/tree/master)*** so you can enjoy their latest features.
+* Translated in all ***Sfvip Player*** languages.
* Support an **external EPG**[^1].
[^1]: External EPG doesn't work with **local** m3u accounts.
@@ -42,12 +43,12 @@ The logs go **in pairs** (one for each process: ***main*** & ***mitmproxy***) an
# Build
[![version](https://custom-icon-badges.demolab.com/badge/Build%201.4.12.40-informational?logo=github)](/build_config.py#L27)
-[![Sloc](https://custom-icon-badges.demolab.com/badge/Sloc%208.2k-informational?logo=file-code)](https://api.codetabs.com/v1/loc/?github=sebdelsol/sfvip-all)
+[![Sloc](https://custom-icon-badges.demolab.com/badge/Sloc%208.3k-informational?logo=file-code)](https://api.codetabs.com/v1/loc/?github=sebdelsol/sfvip-all)
[![Ruff](https://custom-icon-badges.demolab.com/badge/Ruff-informational?logo=ruff-color)](https://docs.astral.sh/ruff/)
[![Python](https://custom-icon-badges.demolab.com/badge/Python%203.11.8-linen?logo=python-color)](https://www.python.org/downloads/release/python-3118/)
[![mitmproxy](https://custom-icon-badges.demolab.com/badge/Mitmproxy%2010.2.4-linen?logo=mitmproxy-black)](https://mitmproxy.org/)
[![Nsis](https://custom-icon-badges.demolab.com/badge/Nsis%203.09-linen?logo=nsis-color)](https://nsis.sourceforge.io/Download)
-[![Nuitka](https://custom-icon-badges.demolab.com/badge/Nuitka%202.1.1-linen?logo=nuitka)](https://nuitka.net/)
+[![Nuitka](https://custom-icon-badges.demolab.com/badge/Nuitka%202.1.2-linen?logo=nuitka)](https://nuitka.net/)
[![PyInstaller](https://custom-icon-badges.demolab.com/badge/PyInstaller%206.5.0-linen?logo=pyinstaller-windowed)](https://pyinstaller.org/en/stable/)
* [***NSIS***](https://nsis.sourceforge.io/Download) will be automatically installed if missing.
diff --git a/build/changelog.md b/build/changelog.md
index d7f888ad..9889fe11 100644
--- a/build/changelog.md
+++ b/build/changelog.md
@@ -1,7 +1,8 @@
## 1.4.12.40
-* Fix EPG channels with wrong programmes.
-* Fix EPG processing restarting when closing the UI.
+* Option to prefer the IPTV provider EPG over external EPG.
* Faster EPG processing and loading that use less memory.
+* Fix EPG processing restarting when closing the UI.
+* Better fuzzy match for external EPG.
## 1.4.12.39
* Bump _mitmproxy_ to 10.2.4.
diff --git a/resources/README_template.md b/resources/README_template.md
index 6c9ba377..a17d1409 100644
--- a/resources/README_template.md
+++ b/resources/README_template.md
@@ -3,6 +3,7 @@
* Insert an _All_ category when missing so you can easily **search your entire catalog**.
* Update ***[Mpv](https://mpv.io/)*** and ***[Sfvip Player](https://github.com/K4L4Uz/SFVIP-Player/tree/master)*** so you can enjoy their latest features.
+* Translated in all ***Sfvip Player*** languages.
* Support an **external EPG**[^1].
[^1]: External EPG doesn't work with **local** m3u accounts.
diff --git a/sfvip_all_config.py b/sfvip_all_config.py
index 3a5dbd6f..45bbf38e 100644
--- a/sfvip_all_config.py
+++ b/sfvip_all_config.py
@@ -24,6 +24,7 @@ class EPG:
url: str | None = None
confidence: int = 30
requests_timeout: int = 5
+ prefer_internal: bool = True
class AllCategory:
inject_in_live: bool = False
diff --git a/src/mitm/addon/__init__.py b/src/mitm/addon/__init__.py
index 430a1c86..fc60751c 100644
--- a/src/mitm/addon/__init__.py
+++ b/src/mitm/addon/__init__.py
@@ -35,6 +35,10 @@ def get_short_epg(flow: http.HTTPFlow, epg: EPG, api: APItype) -> None:
if response := flow.response:
def set_response(stream_id: str, limit: str, programmes: str) -> None:
+ # already an epg ?
+ if epg.prefer_updater.prefer_internal and (json_response := response_json(flow.response)):
+ if isinstance(json_response, dict) and json_response.get(programmes):
+ return
server = flow.request.host_header
if _id := get_query_key(flow, stream_id):
_limit = get_query_key(flow, limit)
@@ -153,12 +157,15 @@ def __init__(
self.m3u_stream = M3UStream(self.epg)
self.panels = AllPanels(all_config.all_name)
- def epg_update(self, url: str):
+ def epg_update(self, url: str) -> None:
self.epg.ask_update(url)
- def epg_confidence_update(self, confidence: int):
+ def epg_confidence_update(self, confidence: int) -> None:
self.epg.update_confidence(confidence)
+ def epg_prefer_update(self, prefer_internal: bool) -> None:
+ self.epg.update_prefer(prefer_internal)
+
def running(self) -> None:
self.epg.start()
diff --git a/src/mitm/epg/__init__.py b/src/mitm/epg/__init__.py
index 19231b39..348239e3 100644
--- a/src/mitm/epg/__init__.py
+++ b/src/mitm/epg/__init__.py
@@ -54,6 +54,23 @@ def confidence(self) -> Optional[int]:
return self._confidence
+class PreferUpdater(JobRunner[bool]):
+ def __init__(self) -> None:
+ self._prefer_internal_lock = multiprocessing.Lock()
+ self._prefer_internal: Optional[bool] = None
+ super().__init__(self._updating, "Epg prefer internal updater")
+
+ def _updating(self, prefer_internal: bool) -> None:
+ with self._prefer_internal_lock:
+ self._prefer_internal = prefer_internal
+
+ @property
+ def prefer_internal(self) -> Optional[bool]:
+ with self._prefer_internal_lock:
+ return self._prefer_internal
+
+
+# pylint: disable=too-many-instance-attributes
class EPG:
_programme_type = {APItype.XC: EPGprogrammeXC, APItype.MAC: EPGprogrammeMAC, APItype.M3U: EPGprogrammeM3U}
_m3u_server = "m3u.server"
@@ -63,6 +80,7 @@ def __init__(self, roaming: Path, callbacks: EpgCallbacks, timeout: int) -> None
self.servers: dict[str, EPGserverChannels] = {}
self.updater = EPGupdater(roaming, callbacks.update_status, timeout)
self.confidence_updater = ConfidenceUpdater()
+ self.prefer_updater = PreferUpdater()
self.show_channel = callbacks.show_channel
self.channel_shown = False
self.show_epg = callbacks.show_epg
@@ -74,15 +92,20 @@ def ask_update(self, url: str) -> None:
def update_confidence(self, confidence: int) -> None:
self.confidence_updater.add_job(confidence)
+ def update_prefer(self, prefer_internal: bool) -> None:
+ self.prefer_updater.add_job(prefer_internal)
+
def wait_running(self, timeout: int) -> bool:
return self.updater.wait_running(timeout)
def start(self) -> None:
self.confidence_updater.start()
+ self.prefer_updater.start()
self.updater.start()
def stop(self) -> None:
self.updater.stop()
+ self.prefer_updater.stop()
self.confidence_updater.stop()
def set_server_channels(self, server: Optional[str], channels: Any, api: APItype) -> None:
diff --git a/src/mitm/epg/programme.py b/src/mitm/epg/programme.py
index 56ec43ed..b729e097 100644
--- a/src/mitm/epg/programme.py
+++ b/src/mitm/epg/programme.py
@@ -26,7 +26,6 @@ def from_programme(cls, programme: InternalProgramme, now: float) -> Optional[Se
return cls(start, end)
return None
- # TODO check time (CNN bug)
@staticmethod
def _get_timestamp(date: str) -> Optional[int]:
try:
diff --git a/src/mitm/epg/update.py b/src/mitm/epg/update.py
index 03689ecb..bb2bd62c 100644
--- a/src/mitm/epg/update.py
+++ b/src/mitm/epg/update.py
@@ -5,9 +5,17 @@
import re
import tempfile
from contextlib import contextmanager
-from enum import Enum, auto
+from enum import Enum, auto, member
from pathlib import Path
-from typing import IO, Callable, Iterator, NamedTuple, Optional, Self
+from typing import (
+ IO,
+ Callable,
+ Container,
+ Iterator,
+ NamedTuple,
+ Optional,
+ Self,
+)
from urllib.parse import urlparse
import lxml.etree as ET
@@ -50,8 +58,11 @@ class EPGProcess(NamedTuple):
def _normalize(name: str) -> str:
name = re.sub(r"(\.)([\d]+)", r"\2", name) # turn channel.2 into channel2
- for sub, repl in (".+", "plus"), ("+", "plus"), ("*", "star"):
+ for sub, repl in ("+", "plus"), ("*", "star"):
name = name.replace(sub, repl)
+ for char in ".|()[]-":
+ name = name.replace(char, " ")
+ name = re.sub(r"\s+", " ", name).strip() # remove extra white spaces
return name
@@ -66,6 +77,7 @@ def _valid_url(url: str) -> bool:
def parse_programme(file_obj: IO[bytes] | gzip.GzipFile, epg_process: EPGProcess) -> Iterator[NamedProgrammes]:
current_programmes: dict[InternalProgramme, bool] = {}
current_channel_id: Optional[str] = None
+ normalized: dict[str, str] = {}
progress_step = ProgressStep()
elem: ET.ElementBase
title: str = ""
@@ -88,12 +100,13 @@ def parse_programme(file_obj: IO[bytes] | gzip.GzipFile, epg_process: EPGProcess
desc = elem.text or ""
case "programme":
if channel_id := elem.get("channel", None):
- channel_id = _normalize(channel_id)
- if channel_id != current_channel_id:
+ if not (norm_channel_id := normalized.get(channel_id)):
+ norm_channel_id = normalized[channel_id] = _normalize(channel_id)
+ if norm_channel_id != current_channel_id:
if current_channel_id and current_programmes:
yield NamedProgrammes(tuple(current_programmes), current_channel_id)
current_programmes = {}
- current_channel_id = channel_id
+ current_channel_id = norm_channel_id
if progress := progress_step.increment_progress(1):
epg_process.update_status(EPGProgress(EPGstatus.PROCESSING, progress))
start = elem.get("start", "")
@@ -112,6 +125,74 @@ class FoundProgammes(NamedTuple):
confidence: int
+class FuzzResult(NamedTuple):
+ name: str
+ score: float
+
+ @classmethod
+ def from_result(cls, result: tuple) -> Self:
+ return cls(*result[:2])
+
+
+class Scorer(Enum):
+ RATIO = member(fuzz.ratio)
+ PARTIAL_RATIO = member(fuzz.partial_ratio)
+ TOKEN_SET_RATIO = member(fuzz.token_set_ratio)
+ TOKEN_SORT_RATIO = member(fuzz.token_sort_ratio)
+ PARTIAL_TOKEN_SET_RATIO = member(fuzz.partial_token_set_ratio)
+ PARTIAL_TOKEN_SORT_RATIO = member(fuzz.partial_token_sort_ratio)
+
+
+class FuzzBest:
+ _scorers = (
+ # used for cuttoff and overall score
+ (Scorer.TOKEN_SET_RATIO, 0.5),
+ # used overal score
+ (Scorer.TOKEN_SORT_RATIO, 1),
+ (Scorer.PARTIAL_TOKEN_SORT_RATIO, 0.5),
+ (Scorer.RATIO, 1),
+ )
+ _limit = 5
+
+ def __init__(self, choices: Container[str]) -> None:
+ self._choices = choices
+
+ @staticmethod
+ def _extract(query: str, choices: Container[str], scorer: Scorer, cutoff: int = 0) -> list[FuzzResult]:
+ results = process.extractBests(
+ query, choices, limit=FuzzBest._limit, scorer=scorer.value, score_cutoff=cutoff
+ )
+ return [FuzzResult.from_result(result) for result in results]
+
+ def _best(self, query: str, confidence: int) -> Optional[FuzzResult]:
+ # cutoff using the 1st scorer
+ scorer, weight = FuzzBest._scorers[0]
+ if results := self._extract(query, self._choices, scorer=scorer, cutoff=100 - confidence):
+ # print(f"{query=}")
+ # print("found", results)
+ if len(results) == 1:
+ return results[0]
+ # get the accumulated score with weight
+ scores = {result.name: result.score for result in results}
+ accumulated_scores = {name: score * weight for name, score in scores.items()}
+ for scorer, weight in FuzzBest._scorers[1:]:
+ for result in self._extract(query, scores.keys(), scorer=scorer):
+ accumulated_scores[result.name] += result.score * weight
+ # print(accumulated_scores)
+ best = sorted(
+ (FuzzResult(name, score) for name, score in accumulated_scores.items()),
+ key=lambda result: result.score,
+ reverse=True,
+ )[0]
+ return FuzzResult(best.name, scores[best.name])
+ return None
+
+ def get(self, query: str, confidence: int) -> Optional[FuzzResult]:
+ if query in self._choices:
+ return FuzzResult(query, 100)
+ return self._best(query, confidence)
+
+
class EPGupdate(NamedTuple):
_chunk_size = 1024 * 128
url: str
@@ -199,23 +280,16 @@ def from_url(cls, url: str, cache: ChannelsCache, epg_process: EPGProcess, timeo
def get_programmes(self, epg_id: str, confidence: int) -> Optional[FoundProgammes]:
if self.programmes:
- normalized_epg_id = _normalize(epg_id)
- if result := process.extractOne(
- normalized_epg_id,
- self.programmes.all_names,
- scorer=fuzz.token_sort_ratio,
- score_cutoff=100 - confidence,
- ):
- normalized_epg_id, score = result[:2]
- if programmes := self.programmes.get_programmes(normalized_epg_id):
+ if found := FuzzBest(self.programmes.all_names).get(_normalize(epg_id), confidence):
+ if programmes := self.programmes.get_programmes(found.name):
logger.info(
- "Found Epg %s for %s with confidence %s%% (cut off @%s%%)",
- normalized_epg_id,
+ "Found Epg '%s' for %s with confidence %s%% (cut off @%s%%)",
+ found.name,
epg_id,
- score,
+ found.score,
100 - confidence,
)
- return FoundProgammes(programmes, int(score))
+ return FoundProgammes(programmes, int(found.score))
return None
diff --git a/src/sfvip/epg.py b/src/sfvip/epg.py
index 578c226c..d884a6fc 100644
--- a/src/sfvip/epg.py
+++ b/src/sfvip/epg.py
@@ -1,5 +1,5 @@
import time
-from typing import Callable
+from typing import Callable, NamedTuple
from shared.job_runner import JobRunner
from translations.loc import LOC
@@ -48,15 +48,15 @@ def on_key_pressed(self, _: str) -> None:
self.ui.hover_message.hide()
+class EPGUpdates(NamedTuple):
+ confidence: Callable[[int], None]
+ prefer: Callable[[bool], None]
+ url: Callable[[str], None]
+
+
class EpgUpdater:
# pylint: disable=too-many-instance-attributes
- def __init__(
- self,
- config: AppConfig,
- epg_update: Callable[[str], None],
- epg_confidence_update: Callable[[int], None],
- ui: UI,
- ) -> None:
+ def __init__(self, config: AppConfig, epg_updates: EPGUpdates, ui: UI) -> None:
self.hover_epg = HoverEPG(ui)
self.keyboard_watcher = KeyboardWatcher("e", self.hover_epg.on_key_pressed)
self.show_epg_job = JobRunner[ShowEpg](self.hover_epg.show_epg, "Show epg job", check_new=False)
@@ -64,8 +64,7 @@ def __init__(
self.hover_epg.show_channel, "Show channel job", check_new=False
)
self.status_job = JobRunner[EPGProgress](ui.set_epg_status, "Epg status job")
- self.epg_confidence_update = epg_confidence_update
- self.epg_update = epg_update
+ self.epg_updates = epg_updates
self.config = config
self.ui = ui
@@ -87,9 +86,11 @@ def start(self) -> None:
self.show_epg_job.start()
self.show_channel_job.start()
self.ui.set_epg_url_update(self.config.EPG.url, self.on_epg_url_changed)
- self.epg_update(self.config.EPG.url or "")
+ self.epg_updates.url(self.config.EPG.url or "")
self.ui.set_epg_confidence_update(self.config.EPG.confidence, self.on_epg_confidence_changed)
- self.epg_confidence_update(self.config.EPG.confidence)
+ self.epg_updates.confidence(self.config.EPG.confidence)
+ self.ui.set_epg_prefer_update(self.config.EPG.prefer_internal, self.on_epg_prefer_changed)
+ self.epg_updates.prefer(self.config.EPG.prefer_internal)
def stop(self) -> None:
self.show_channel_job.stop()
@@ -99,8 +100,12 @@ def stop(self) -> None:
def on_epg_url_changed(self, epg_url: str) -> None:
self.config.EPG.url = epg_url if epg_url else None
- self.epg_update(epg_url)
+ self.epg_updates.url(epg_url)
def on_epg_confidence_changed(self, epg_confidence: int) -> None:
self.config.EPG.confidence = epg_confidence
- self.epg_confidence_update(epg_confidence)
+ self.epg_updates.confidence(epg_confidence)
+
+ def on_epg_prefer_changed(self, prefer_internal: bool) -> None:
+ self.config.EPG.prefer_internal = prefer_internal
+ self.epg_updates.prefer(prefer_internal)
diff --git a/src/sfvip/proxies.py b/src/sfvip/proxies.py
index 46000128..caa0151e 100644
--- a/src/sfvip/proxies.py
+++ b/src/sfvip/proxies.py
@@ -12,7 +12,7 @@
from .accounts import AccountsProxies
from .app_info import AppInfo
from .cache import CacheProgressListener
-from .epg import EpgUpdater
+from .epg import EpgUpdater, EPGUpdates
from .exceptions import LocalproxyError
from .ui import UI
@@ -76,7 +76,15 @@ class LocalProxies:
_find_ports_retry = 10
def __init__(self, app_info: AppInfo, inject_in_live: bool, accounts_proxies: AccountsProxies, ui: UI) -> None:
- self._epg_updater = EpgUpdater(app_info.config, self.epg_update, self.epg_confidence_update, ui)
+ self._epg_updater = EpgUpdater(
+ app_info.config,
+ EPGUpdates(
+ self.epg_confidence_update,
+ self.epg_prefer_update,
+ self.epg_update,
+ ),
+ ui,
+ )
self._cache_progress = CacheProgressListener(ui)
self._addon = SfVipAddOn(
accounts_proxies.urls,
@@ -105,6 +113,9 @@ def epg_update(self, url: str) -> None:
def epg_confidence_update(self, confidence: int) -> None:
self._addon.epg_confidence_update(confidence)
+ def epg_prefer_update(self, prefer_internal: bool) -> None:
+ self._addon.epg_prefer_update(prefer_internal)
+
def __enter__(self) -> Self:
with self._bind_free_ports:
modes: set[Mode] = set()
diff --git a/src/sfvip/ui/__init__.py b/src/sfvip/ui/__init__.py
index 2d918c39..7508bd42 100644
--- a/src/sfvip/ui/__init__.py
+++ b/src/sfvip/ui/__init__.py
@@ -64,6 +64,9 @@ def set_epg_status(self, epg_status: EPGProgress) -> None:
def set_epg_confidence_update(self, confidence: int, callback: Callable[[int], None]) -> None:
self._infos.set_epg_confidence_update(confidence, callback)
+ def set_epg_prefer_update(self, prefer_external: bool, callback: Callable[[bool], None]) -> None:
+ self._infos.set_epg_prefer_update(prefer_external, callback)
+
def set_app_auto_update(self, is_checked: bool, callback: Callable[[bool], None]) -> None:
self._infos.set_app_auto_update(is_checked, callback)
diff --git a/src/sfvip/ui/infos.py b/src/sfvip/ui/infos.py
index 832a1f64..98bdf18e 100644
--- a/src/sfvip/ui/infos.py
+++ b/src/sfvip/ui/infos.py
@@ -244,6 +244,19 @@ def _epg_status(epg_status: EPGProgress) -> Style:
return _epg_status_styles(progress).get(epg_status.status, _InfoStyle.app("").grey)
+def _get_epg_prefer_update(on: bool = True) -> Style:
+ return _InfoStyle.app(LOC.Yes).grey if on else _InfoStyle.app(LOC.No).grey
+
+
+def _get_epg_prefer_label() -> Style:
+ return _InfoStyle.app(LOC.EPGPrefer).grey.no_truncate
+
+
+def _epg_prefer_tooltip() -> Style:
+ msg = "".join((f"\n\n • {text}" for text in (LOC.EPGPreferYes, LOC.EPGPreferNo)))
+ return _InfoStyle.tooltip(f"{LOC.EPGPrefer}:{msg}")
+
+
def _player_changelog_tooltip() -> Style:
return _InfoStyle.tooltip()
@@ -434,6 +447,11 @@ def __init__(self, app_info: AppInfo) -> None:
bg=_InfoTheme.bg_rows,
**_InfoTheme.confidence_slider, # type:ignore
)
+ epg_prefer_frame = tk.Frame(frame, bg=_InfoTheme.bg_rows)
+ epg_prefer_label = tk.Label(epg_prefer_frame, bg=_InfoTheme.bg_rows, **_get_epg_prefer_label().to_tk)
+ self._epg_prefer_check = CheckBox(
+ epg_prefer_frame, bg=_InfoTheme.bg_rows, **_InfoTheme.checkbox, **_get_epg_prefer_update().to_tk
+ )
separator5 = tk.Frame(frame, bg=_InfoTheme.separator)
# layout
pad = _InfoTheme.pad
@@ -470,12 +488,16 @@ def __init__(self, app_info: AppInfo) -> None:
self._epg_url.pack(side=tk.LEFT, padx=pad, fill=tk.X, expand=True)
self._epg_status.pack(side=tk.LEFT)
row += 1
- epg_confidence_frame.grid(row=row, columnspan=3, padx=pad, pady=(0, pad), sticky=tk.NSEW)
+ epg_confidence_frame.grid(row=row, columnspan=3, padx=pad, pady=0, sticky=tk.NSEW)
epg_confidence_label.pack(side=tk.LEFT)
self._epg_confidence.pack(side=tk.LEFT)
epg_confidence_percent.pack(side=tk.LEFT)
self._epg_confidence_slider.pack(padx=(pad, 0), side=tk.LEFT, fill=tk.X, expand=True)
row += 1
+ epg_prefer_frame.grid(row=row, columnspan=3, padx=pad, pady=pad, sticky=tk.NSEW)
+ epg_prefer_label.pack(side=tk.LEFT)
+ self._epg_prefer_check.pack(side=tk.LEFT)
+ row += 1
separator5.grid(row=row, columnspan=3, sticky=tk.EW)
row += 1
super()._layout(row=row)
@@ -485,6 +507,7 @@ def __init__(self, app_info: AppInfo) -> None:
set_tooltip(_player_changelog_tooltip(), self._player_version, msg_refresh=self.get_changelog)
set_tooltip(_app_version_tooltip(app_info), app_version)
set_tooltip(_epg_confidence_tooltip(), epg_confidence_label)
+ set_tooltip(_epg_prefer_tooltip(), epg_prefer_label)
def get_changelog(self) -> str:
return self._changelog_callback() if self._changelog_callback else ""
@@ -550,6 +573,9 @@ def _validate(entry: str) -> bool:
self._epg_confidence_slider.bind("", _callback_slider)
self._epg_confidence.bind("", _callback_entry)
+ def set_epg_prefer_update(self, is_checked: bool, callback: Callable[[bool], None]) -> None:
+ self._epg_prefer_check.set_callback(is_checked, callback, _get_epg_prefer_update)
+
def set_app_update(
self,
action_name: Optional[str],
diff --git a/translations/bulgarian.json b/translations/bulgarian.json
index f5e13875..00b048a2 100644
--- a/translations/bulgarian.json
+++ b/translations/bulgarian.json
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Актуализиране на всички серии",
"UpdateAllMovies": "Актуализиране на всички филми",
"Confidence0": "0 %: Не се доверявате на EPG и ще получите само точно съвпадение и често никакво.",
- "Confidence100": "100 %: Доверявате се напълно на EPG и винаги получавате съвпадение, дори и с лошо качество"
+ "Confidence100": "100 %: Доверявате се напълно на EPG и винаги получавате съвпадение, дори и с лошо качество",
+ "EPGPrefer": "Търсене първо при доставчика на IPTV",
+ "Yes": "Да",
+ "No": "Не",
+ "EPGPreferYes": "Да: Първо търсите в EPG на IPTV доставчика. Използвайте външния EPG само когато той не успее.",
+ "EPGPreferNo": "Не: Търсете първо във външния EPG. Използвайте EPG на доставчика на IPTV само когато той не успее."
}
\ No newline at end of file
diff --git a/translations/english.json b/translations/english.json
index 6a7d481e..dd4799b6 100644
--- a/translations/english.json
+++ b/translations/english.json
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Update All series",
"UpdateAllMovies": "Update All movies",
"Confidence0": "0 %: You don't trust the EPG and you'll only get an exact match and often none",
- "Confidence100": "100 %: You completely trust the EPG and you'll always get a match even one of poor quality"
+ "Confidence100": "100 %: You completely trust the EPG and you'll always get a match even one of poor quality",
+ "EPGPrefer": "Search the IPTV provider first",
+ "Yes": "Yes",
+ "No": "No",
+ "EPGPreferYes": "Yes: Search the IPTV provider EPG first. Use the external EPG only when it fails.",
+ "EPGPreferNo": "No: Search the external EPG first. Use the IPTV provider EPG only when it fails."
}
\ No newline at end of file
diff --git a/translations/french.json b/translations/french.json
index bf8fe96e..f26a32a9 100644
--- a/translations/french.json
+++ b/translations/french.json
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Mise à jour de toutes les séries",
"UpdateAllMovies": "Mise à jour de tous les films",
"Confidence0": "0 % : Vous ne faites pas confiance à l'EPG et vous n'obtiendrez qu'une correspondance exacte et souvent aucune.",
- "Confidence100": "100 % : Vous faites entièrement confiance à l'EPG et vous obtiendrez toujours une correspondance, même de mauvaise qualité."
+ "Confidence100": "100 % : Vous faites entièrement confiance à l'EPG et vous obtiendrez toujours une correspondance, même de mauvaise qualité.",
+ "EPGPrefer": "Chercher d'abord chez le fournisseur IPTV",
+ "Yes": "Oui",
+ "No": "Non",
+ "EPGPreferYes": "Oui : Cherchez d'abord dans l'EPG du fournisseur de télévision par internet. N'utilisez l'EPG externe qu'en cas d'échec.",
+ "EPGPreferNo": "Non : rechercher d'abord l'EPG externe. Utiliser l'EPG du fournisseur IPTV uniquement en cas d'échec."
}
\ No newline at end of file
diff --git a/translations/german.json b/translations/german.json
index bab54951..6b5fe018 100644
--- a/translations/german.json
+++ b/translations/german.json
@@ -21,7 +21,7 @@
"PleaseWait": "Bitte warten",
"CheckLastestLibmpv": "Letzte libmpv-Aktualisierung prüfen",
"RestartInstall": "Möchten Sie neu starten, um %s zu installieren?",
- "SearchOrDownload": "Möchten Sie es suchen oder herunterladen ?",
+ "SearchOrDownload": "Möchten Sie es suchen oder herunterladen?",
"NoSocketPort": "Kein Socket-Port verfügbar !",
"CantStartProxies": "Lokale Proxys können nicht gestartet werden !",
"NotFound": "%s nicht gefunden",
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Alle Serien aktualisieren",
"UpdateAllMovies": "Alle Filme aktualisieren",
"Confidence0": "0 %: Sie vertrauen dem EPG nicht und erhalten nur eine exakte Übereinstimmung und oft keine",
- "Confidence100": "100 %: Sie vertrauen dem EPG voll und ganz und erhalten immer einen Treffer, auch wenn er von schlechter Qualität ist"
+ "Confidence100": "100 %: Sie vertrauen dem EPG voll und ganz und erhalten immer einen Treffer, auch wenn er von schlechter Qualität ist",
+ "EPGPrefer": "Suchen Sie zuerst den IPTV-Anbieter",
+ "Yes": "Ja",
+ "No": "Nein",
+ "EPGPreferYes": "Ja: Suchen Sie zuerst im EPG des IPTV-Anbieters. Verwenden Sie den externen EPG nur, wenn er nicht funktioniert.",
+ "EPGPreferNo": "Nein: Zuerst im externen EPG suchen. Verwenden Sie den EPG des IPTV-Anbieters nur, wenn dieser nicht funktioniert."
}
\ No newline at end of file
diff --git a/translations/greek.json b/translations/greek.json
index b3eedef8..0ec81ad9 100644
--- a/translations/greek.json
+++ b/translations/greek.json
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Ενημέρωση Όλες οι σειρές",
"UpdateAllMovies": "Ενημέρωση όλων των ταινιών",
"Confidence0": "0 %: Δεν εμπιστεύεστε το EPG και θα έχετε μόνο μια ακριβή αντιστοιχία και συχνά καμία.",
- "Confidence100": "100 %: Εμπιστεύεστε πλήρως το EPG και θα έχετε πάντα μια αντιστοιχία, ακόμη και κακής ποιότητας."
+ "Confidence100": "100 %: Εμπιστεύεστε πλήρως το EPG και θα έχετε πάντα μια αντιστοιχία, ακόμη και κακής ποιότητας.",
+ "EPGPrefer": "Αναζητήστε πρώτα τον πάροχο IPTV",
+ "Yes": "Ναι",
+ "No": "Όχι",
+ "EPGPreferYes": "Ναι: Αναζητήστε πρώτα το EPG του παρόχου IPTV. Χρησιμοποιείτε το εξωτερικό EPG μόνο όταν αυτό αποτυγχάνει.",
+ "EPGPreferNo": "Όχι: Αναζητήστε πρώτα το εξωτερικό EPG. Χρησιμοποιήστε το EPG του παρόχου IPTV μόνο όταν αυτό αποτύχει."
}
\ No newline at end of file
diff --git a/translations/italian.json b/translations/italian.json
index 84da067e..690c6427 100644
--- a/translations/italian.json
+++ b/translations/italian.json
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Aggiorna tutte le serie",
"UpdateAllMovies": "Aggiorna tutti i film",
"Confidence0": "0 %: Non ci si fida dell'EPG e si ottiene solo una corrispondenza esatta e spesso nessuna.",
- "Confidence100": "100 %: Ci si fida completamente dell'EPG e si ottiene sempre una corrispondenza, anche se di scarsa qualità."
+ "Confidence100": "100 %: ci si fida completamente dell'EPG e si ottiene sempre una corrispondenza, anche se di scarsa qualità.",
+ "EPGPrefer": "Cercare prima il provider IPTV",
+ "Yes": "Si",
+ "No": "No",
+ "EPGPreferYes": "Sì: cercate prima l'EPG del provider IPTV. Utilizzare l'EPG esterno solo in caso di errore.",
+ "EPGPreferNo": "No: Cerca prima l'EPG esterno. Utilizzare l'EPG del provider IPTV solo in caso di errore."
}
\ No newline at end of file
diff --git a/translations/loc/texts.py b/translations/loc/texts.py
index 3c8f533e..a0dbdc28 100644
--- a/translations/loc/texts.py
+++ b/translations/loc/texts.py
@@ -58,10 +58,14 @@ class Texts:
UpdateAllSeries: str = "Update All series"
UpdateAllMovies: str = "Update All movies"
Confidence0: str = "0 %: You don't trust the EPG and you'll only get an exact match and often none"
- # fmt: off
- # pylint: disable=line-too-long
- Confidence100: str = "100 %: You completely trust the EPG and you'll always get a match even one of poor quality"
- # fmt: on
+ Confidence100: str = (
+ "100 %: You completely trust the EPG and you'll always get a match even one of poor quality"
+ )
+ EPGPrefer: str = "Search the IPTV provider first"
+ Yes: str = "Yes"
+ No: str = "No"
+ EPGPreferYes: str = "Yes: Search the IPTV provider EPG first. Use the external EPG only when it fails."
+ EPGPreferNo: str = "No: Search the external EPG first. Use the IPTV provider EPG only when it fails."
def as_dict(self) -> dict[str, str]:
return dataclasses.asdict(self)
diff --git a/translations/polish.json b/translations/polish.json
index ca0292f4..301360ff 100644
--- a/translations/polish.json
+++ b/translations/polish.json
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Aktualizacja wszystkich seriali",
"UpdateAllMovies": "Aktualizacja wszystkich filmów",
"Confidence0": "0%: Nie ufasz EPG i otrzymasz tylko dokładne dopasowanie, a często żadnego.",
- "Confidence100": "100%: Całkowicie ufasz EPG i zawsze otrzymasz dopasowanie, nawet słabej jakości."
+ "Confidence100": "100%: Całkowicie ufasz EPG i zawsze otrzymasz dopasowanie, nawet słabej jakości.",
+ "EPGPrefer": "Najpierw wyszukaj dostawcę IPTV",
+ "Yes": "Tak",
+ "No": "Nie",
+ "EPGPreferYes": "Tak: Najpierw należy przeszukać EPG dostawcy IPTV. Używaj zewnętrznego EPG tylko wtedy, gdy zawiedzie.",
+ "EPGPreferNo": "Nie: Najpierw należy przeszukać zewnętrzne EPG. Użyj EPG dostawcy IPTV tylko w przypadku niepowodzenia."
}
\ No newline at end of file
diff --git a/translations/russian.json b/translations/russian.json
index 7e04c8d2..1ef0fa80 100644
--- a/translations/russian.json
+++ b/translations/russian.json
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Обновить все серии",
"UpdateAllMovies": "Обновить все фильмы",
"Confidence0": "0 %: Вы не доверяете EPG и получаете только точные совпадения, а часто и вовсе их не получаете",
- "Confidence100": "100 %: Вы полностью доверяете EPG и всегда получите совпадение, даже если оно плохого качества"
+ "Confidence100": "100 %: Вы полностью доверяете EPG и всегда получите совпадение, даже если оно будет плохого качества.",
+ "EPGPrefer": "Поиск IPTV-провайдера в первую очередь",
+ "Yes": "Да",
+ "No": "Нет",
+ "EPGPreferYes": "Да: Сначала ищите EPG провайдера IPTV. Используйте внешний EPG только в случае неудачи.",
+ "EPGPreferNo": "Нет: Поиск внешнего EPG первым. Используйте EPG провайдера IPTV только в случае неудачи."
}
\ No newline at end of file
diff --git a/translations/serbian.json b/translations/serbian.json
index 54a1353d..7cfe7f38 100644
--- a/translations/serbian.json
+++ b/translations/serbian.json
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Ажурирајте све серије",
"UpdateAllMovies": "Ажурирајте све филмове",
"Confidence0": "0 %: Не верујете ЕПГ-у и добићете само потпуно подударање, а често ни једно",
- "Confidence100": "100 %: У потпуности верујете ЕПГ-у и увек ћете добити меч, чак и онај лошег квалитета"
+ "Confidence100": "100 %: У потпуности верујете ЕПГ-у и увек ћете добити меч, чак и онај лошег квалитета",
+ "EPGPrefer": "Прво потражите ИПТВ провајдера",
+ "Yes": "да",
+ "No": "Не",
+ "EPGPreferYes": "Да: Прво претражите ЕПГ ИПТВ провајдера. Користите екстерни ЕПГ само када не успе.",
+ "EPGPreferNo": "Не: прво претражите екстерни ЕПГ. Користите ЕПГ ИПТВ провајдера само када не успе."
}
\ No newline at end of file
diff --git a/translations/slovenian.json b/translations/slovenian.json
index 1fbd1a52..a01891d5 100644
--- a/translations/slovenian.json
+++ b/translations/slovenian.json
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Posodobitev vseh serij",
"UpdateAllMovies": "Posodobitev vseh filmov",
"Confidence0": "0 %: Ne zaupate EPG in dobite le natančno ujemanje, pogosto pa nobenega.",
- "Confidence100": "100 %: popolnoma zaupate EPG in vedno dobite ujemanje, tudi če je slabe kakovosti"
+ "Confidence100": "100 %: popolnoma zaupate EPG in vedno dobite ujemanje, tudi če je slabe kakovosti",
+ "EPGPrefer": "Najprej poiščite ponudnika IPTV",
+ "Yes": "Da",
+ "No": "Ne",
+ "EPGPreferYes": "Da: najprej poiščete EPG ponudnika IPTV. Zunanji program EPG uporabite le, kadar ta ne uspe.",
+ "EPGPreferNo": "Ne: Najprej poiščite zunanji EPG. EPG ponudnika IPTV uporabite le, če je neuspešen."
}
\ No newline at end of file
diff --git a/translations/spanish.json b/translations/spanish.json
index ac3e316b..9b461843 100644
--- a/translations/spanish.json
+++ b/translations/spanish.json
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Actualizar todas las series",
"UpdateAllMovies": "Actualizar todas las películas",
"Confidence0": "0 %: No confías en la EPG y sólo obtendrás una coincidencia exacta y a menudo ninguna",
- "Confidence100": "100 %: Confías plenamente en la EPG y siempre obtendrás una coincidencia, aunque sea de mala calidad."
+ "Confidence100": "100 %: Confías plenamente en la EPG y siempre obtendrás una coincidencia, aunque sea de mala calidad.",
+ "EPGPrefer": "Busca primero en el proveedor de IPTV",
+ "Yes": "Sí",
+ "No": "No",
+ "EPGPreferYes": "Sí: Busca primero en la EPG del proveedor de IPTV. Utiliza la EPG externa sólo cuando falle.",
+ "EPGPreferNo": "No: Buscar primero en la EPG externa. Utiliza la EPG del proveedor de IPTV sólo cuando falla."
}
\ No newline at end of file
diff --git a/translations/turkish.json b/translations/turkish.json
index 4d0d61bf..69e73c48 100644
--- a/translations/turkish.json
+++ b/translations/turkish.json
@@ -50,5 +50,10 @@
"UpdateAllSeries": "Tüm serileri güncelle",
"UpdateAllMovies": "Tüm filmleri güncelle",
"Confidence0": "0 %: EPG'ye güvenmiyorsunuz ve yalnızca tam bir eşleşme elde edeceksiniz ve genellikle hiçbir eşleşme elde edemeyeceksiniz",
- "Confidence100": "100: EPG'ye tamamen güvenirsiniz ve düşük kalitede bile olsa her zaman bir eşleşme elde edersiniz"
+ "Confidence100": "100: EPG'ye tamamen güvenirsiniz ve düşük kalitede bile olsa her zaman bir eşleşme elde edersiniz",
+ "EPGPrefer": "Önce IPTV sağlayıcısını arayın",
+ "Yes": "Evet",
+ "No": "Hayır",
+ "EPGPreferYes": "Evet: Önce IPTV sağlayıcısı EPG'sini arayın. Harici EPG'yi yalnızca başarısız olduğunda kullanın.",
+ "EPGPreferNo": "Hayır: Önce harici EPG'yi arayın. IPTV sağlayıcısı EPG'sini yalnızca başarısız olduğunda kullanın."
}
\ No newline at end of file