diff --git a/service.subtitles.rvm.addic7ed/addic7ed/actions.py b/service.subtitles.rvm.addic7ed/addic7ed/actions.py
index f16a9116d..1367d53c6 100644
--- a/service.subtitles.rvm.addic7ed/addic7ed/actions.py
+++ b/service.subtitles.rvm.addic7ed/addic7ed/actions.py
@@ -26,7 +26,7 @@
import xbmcplugin
from addic7ed import parser
-from addic7ed.addon import ADDON, PROFILE, ICON, get_ui_string
+from addic7ed.addon import ADDON, PROFILE, ICON, GettextEmulator
from addic7ed.exceptions import NoSubtitlesReturned, ParseError, SubsSearchError, \
Add7ConnectionError
from addic7ed.parser import parse_filename, normalize_showname, get_languages
@@ -35,9 +35,11 @@
__all__ = ['router']
+_ = GettextEmulator.gettext
+
logger = logging.getLogger(__name__)
-TEMP_DIR = os.path.join(PROFILE, 'temp')
+TEMP_DIR = PROFILE / 'temp'
HANDLE = int(sys.argv[1])
@@ -135,24 +137,24 @@ def download_subs(link, referrer, filename):
label - the download location for subs.
"""
# Re-create a download location in a temporary folder
- if not os.path.exists(PROFILE):
- os.mkdir(PROFILE)
- if os.path.exists(TEMP_DIR):
- shutil.rmtree(TEMP_DIR)
- os.mkdir(TEMP_DIR)
+ if not PROFILE.exists():
+ PROFILE.mkdir()
+ if TEMP_DIR.exists():
+ shutil.rmtree(str(TEMP_DIR))
+ TEMP_DIR.mkdir()
# Combine a path where to download the subs
filename = os.path.splitext(filename)[0] + '.srt'
- subspath = os.path.join(TEMP_DIR, filename)
+ subspath = str(TEMP_DIR / filename)
# Download the subs from addic7ed.com
try:
Session().download_subs(link, referrer, subspath)
except Add7ConnectionError:
logger.error('Unable to connect to addic7ed.com')
- DIALOG.notification(get_ui_string(32002), get_ui_string(32005), 'error')
+ DIALOG.notification(_('Error!'), _('Unable to connect to addic7ed.com.'), 'error')
except NoSubtitlesReturned:
- DIALOG.notification(get_ui_string(32002), get_ui_string(32003), 'error',
+ DIALOG.notification(_('Error!'), _('Exceeded daily limit for subs downloads.'), 'error',
3000)
- logger.error('Exceeded daily limit for subs downloads.')
+ logger.error('A HTML page returned instead of subtitles for link: %s', link)
else:
# Create a ListItem for downloaded subs and pass it
# to the Kodi subtitles engine to move the downloaded subs file
@@ -165,8 +167,7 @@ def download_subs(link, referrer, filename):
url=subspath,
listitem=list_item,
isFolder=False)
- DIALOG.notification(get_ui_string(32000), get_ui_string(32001), ICON,
- 3000, False)
+ DIALOG.notification(_('Success!'), _('Subtitles downloaded.'), ICON, 3000, False)
logger.info('Subs downloaded.')
@@ -197,7 +198,7 @@ def extract_episode_data():
showname, season, episode = parse_filename(filename)
except ParseError:
logger.error('Unable to determine episode data for %s', filename)
- DIALOG.notification(get_ui_string(32002), get_ui_string(32006),
+ DIALOG.notification(_('Error!'), _('Unable to determine episode data.'),
'error', 3000)
raise
else:
@@ -241,24 +242,22 @@ def search_subs(params):
results = parser.search_episode(query, languages)
except Add7ConnectionError:
logger.error('Unable to connect to addic7ed.com')
- DIALOG.notification(
- get_ui_string(32002), get_ui_string(32005), 'error'
- )
+ DIALOG.notification(_('Error!'), _('Unable to connect to addic7ed.com.'), 'error')
except SubsSearchError:
logger.info('No subs for "%s" found.', query)
else:
if isinstance(results, list):
logger.info('Multiple episodes found:\n%s', results)
i = DIALOG.select(
- get_ui_string(32008), [item.title for item in results]
+ _('Select episode'), [item.title for item in results]
)
if i >= 0:
try:
results = parser.get_episode(results[i].link, languages)
except Add7ConnectionError:
logger.error('Unable to connect to addic7ed.com')
- DIALOG.notification(get_ui_string(32002),
- get_ui_string(32005), 'error')
+ DIALOG.notification(_('Error!'),
+ _('Unable to connect to addic7ed.com.'), 'error')
return
except SubsSearchError:
logger.info('No subs found.')
diff --git a/service.subtitles.rvm.addic7ed/addic7ed/addon.py b/service.subtitles.rvm.addic7ed/addic7ed/addon.py
index 1fd95a245..549915b51 100644
--- a/service.subtitles.rvm.addic7ed/addic7ed/addon.py
+++ b/service.subtitles.rvm.addic7ed/addic7ed/addon.py
@@ -13,26 +13,105 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import os
+import hashlib
+import json
+import re
+from pathlib import Path
import xbmcaddon
from xbmcvfs import translatePath
-__all__ = ['ADDON_ID', 'ADDON', 'ADDON_VERSION', 'PATH', 'PROFILE', 'ICON', 'get_ui_string']
+__all__ = ['ADDON_ID', 'ADDON', 'ADDON_VERSION', 'PATH', 'PROFILE', 'ICON', 'GettextEmulator']
ADDON = xbmcaddon.Addon()
ADDON_ID = ADDON.getAddonInfo('id')
ADDON_VERSION = ADDON.getAddonInfo('version')
-PATH = translatePath(ADDON.getAddonInfo('path'))
-PROFILE = translatePath(ADDON.getAddonInfo('profile'))
-ICON = os.path.join(PATH, 'icon.png')
+PATH = Path(translatePath(ADDON.getAddonInfo('path')))
+PROFILE = Path(translatePath(ADDON.getAddonInfo('profile')))
+ICON = str(PATH / 'icon.png')
-def get_ui_string(string_id):
- """
- Get language string by ID
- :param string_id: UI string ID
- :return: UI string
+class GettextEmulator:
+ """
+ Emulate GNU Gettext by mapping resource.language.en_gb UI strings to their numeric string IDs
"""
- return ADDON.getLocalizedString(string_id)
+ _instance = None
+
+ class LocalizationError(Exception): # pylint: disable=missing-docstring
+ pass
+
+ def __new__(cls):
+ if cls._instance is None:
+ cls._instance = super().__new__(cls)
+ return cls._instance
+
+ def __init__(self):
+ self._en_gb_string_po_path = (PATH / 'resources' / 'language' /
+ 'resource.language.en_gb' / 'strings.po')
+ if not self._en_gb_string_po_path.exists():
+ raise self.LocalizationError(
+ 'Missing resource.language.en_gb strings.po localization file')
+ if not PROFILE.exists():
+ PROFILE.mkdir()
+ self._string_mapping_path = PROFILE / 'strings-map.json'
+ self.strings_mapping = self._load_strings_mapping()
+
+ def _load_strings_po(self): # pylint: disable=missing-docstring
+ with self._en_gb_string_po_path.open('r', encoding='utf-8') as fo:
+ return fo.read()
+
+ def _load_strings_mapping(self):
+ """
+ Load mapping of resource.language.en_gb UI strings to their IDs
+
+ If a mapping file is missing or resource.language.en_gb strins.po file has been updated,
+ a new mapping file is created.
+
+ :return: UI strings mapping
+ """
+ strings_po = self._load_strings_po()
+ strings_po_md5 = hashlib.md5(strings_po.encode('utf-8')).hexdigest()
+ try:
+ with self._string_mapping_path.open('r', encoding='utf-8') as fo:
+ mapping = json.load(fo)
+ if mapping['md5'] != strings_po_md5:
+ raise IOError('resource.language.en_gb strings.po has been updated')
+ except (IOError, ValueError):
+ strings_mapping = self._parse_strings_po(strings_po)
+ mapping = {
+ 'strings': strings_mapping,
+ 'md5': strings_po_md5,
+ }
+ with self._string_mapping_path.open('w', encoding='utf-8') as fo:
+ json.dump(mapping, fo)
+ return mapping['strings']
+
+ @staticmethod
+ def _parse_strings_po(strings_po):
+ """
+ Parse resource.language.en_gb strings.po file contents into a mapping of UI strings
+ to their numeric IDs.
+
+ :param strings_po: the content of strings.po file as a text string
+ :return: UI strings mapping
+ """
+ id_string_pairs = re.findall(r'^msgctxt "#(\d+?)"\r?\nmsgid "(.*)"\r?$', strings_po, re.M)
+ return {string: int(string_id) for string_id, string in id_string_pairs if string}
+
+ @classmethod
+ def gettext(cls, en_string: str) -> str:
+ """
+ Return a localized UI string by a resource.language.en_gb source string
+
+ :param en_string: resource.language.en_gb UI string
+ :return: localized UI string
+ """
+ emulator = cls()
+ try:
+ string_id = emulator.strings_mapping[en_string]
+ except KeyError as exc:
+ raise cls.LocalizationError(
+ f'Unable to find "{en_string}" string in resource.language.en_gb/strings.po'
+ ) from exc
+ return ADDON.getLocalizedString(string_id)
diff --git a/service.subtitles.rvm.addic7ed/addic7ed/simple_requests.py b/service.subtitles.rvm.addic7ed/addic7ed/simple_requests.py
index 7da42d896..8838ec7e7 100644
--- a/service.subtitles.rvm.addic7ed/addic7ed/simple_requests.py
+++ b/service.subtitles.rvm.addic7ed/addic7ed/simple_requests.py
@@ -108,7 +108,7 @@ def headers(self) -> HTTPMessage:
@headers.setter
def headers(self, value: HTTPMessage):
charset = value.get_content_charset()
- if charset is not None:
+ if charset:
self.encoding = charset
self._headers = value
@@ -124,7 +124,7 @@ def text(self) -> str:
if self._text is None:
try:
self._text = self.content.decode(self.encoding)
- except UnicodeDecodeError:
+ except (UnicodeDecodeError, LookupError):
self._text = self.content.decode('utf-8', 'replace')
return self._text
diff --git a/service.subtitles.rvm.addic7ed/addon.xml b/service.subtitles.rvm.addic7ed/addon.xml
index a33b2e6e3..6e5820295 100644
--- a/service.subtitles.rvm.addic7ed/addon.xml
+++ b/service.subtitles.rvm.addic7ed/addon.xml
@@ -1,7 +1,7 @@
@@ -29,8 +29,9 @@
icon.png
fanart.jpg
- 3.2.1:
-- Fixed issues with downloading subtitles.
+ 3.2.2:
+- Fixed the issue with an empty content-charset header.
+- Internal changes.
true