From f530c57fc96aa200286463d543b357a2e12903a8 Mon Sep 17 00:00:00 2001 From: newt-sc <47229722+newt-sc@users.noreply.github.com> Date: Mon, 11 May 2020 01:34:19 +0300 Subject: [PATCH] release: v0.0.28 --- CHANGELOG.md | 4 + a4kSubtitles/core.py | 9 +- a4kSubtitles/lib/cache.py | 58 ++++++++++ a4kSubtitles/lib/utils.py | 44 -------- a4kSubtitles/lib/video.py | 199 +++++++++++++++++++++++++-------- a4kSubtitles/search.py | 8 +- a4kSubtitles/services/subdb.py | 9 +- addon.xml | 6 +- packages/addons.xml | 6 +- packages/addons.xml.crc | 2 +- tests/test_suite.py | 79 ++++++++++--- 11 files changed, 306 insertions(+), 118 deletions(-) create mode 100644 a4kSubtitles/lib/cache.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d9d66..acbbe14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +* [v0.0.28](https://github.com/newt-sc/a4kSubtitles/releases/tag/service.subtitles.a4ksubtitles%2Fservice.subtitles.a4ksubtitles-0.0.28): + * Attempt scrape of imdb id when missing + * Improve filename parsing + * [v0.0.27](https://github.com/newt-sc/a4kSubtitles/releases/tag/service.subtitles.a4ksubtitles%2Fservice.subtitles.a4ksubtitles-0.0.27): * Attempt to auto-fix a garbled cyrillic encoded subtitles * Fix more encoding issues diff --git a/a4kSubtitles/core.py b/a4kSubtitles/core.py index fedeb2c..35ad72b 100644 --- a/a4kSubtitles/core.py +++ b/a4kSubtitles/core.py @@ -16,12 +16,13 @@ from xml.etree import ElementTree from .lib import ( - utils, - logger, + cache, kodi, - video, - request, + logger, num2ordinal, + request, + utils, + video, ) from .services import services from .search import search diff --git a/a4kSubtitles/lib/cache.py b/a4kSubtitles/lib/cache.py new file mode 100644 index 0000000..cbf3054 --- /dev/null +++ b/a4kSubtitles/lib/cache.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +import os +import json +import hashlib +from . import kodi +from . import utils + +__meta_cache_filepath = os.path.join(kodi.addon_profile, 'last_meta.json') +__tvshow_years_cache_filepath = os.path.join(kodi.addon_profile, 'tvshow_years_cache.json') +__imdb_id_cache_filepath = os.path.join(kodi.addon_profile, 'imdb_id_cache.json') +results_filepath = os.path.join(kodi.addon_profile, 'last_results.json') + +def __get_cache(filepath): + try: + with open(filepath, 'r') as f: + data = json.loads(f.read()) + return utils.DictAsObject(data) + except: + return utils.DictAsObject({}) + +def __save_cache(filepath, cache): + try: + json_data = json.dumps(cache, indent=2) + with open(filepath, 'w') as f: + f.write(json_data) + except: pass + +def hash_data(data): + json_data = json.dumps(data).encode(utils.default_encoding) + return hashlib.sha256(json_data).hexdigest() + +def get_meta_hash(meta): + return hash_data({ + 'imdb_id': meta.imdb_id, + 'filename': meta.filename, + }) + +def get_meta_cache(): + meta_cache = __get_cache(__meta_cache_filepath) + meta_cache.setdefault('imdb_id', '') + meta_cache.setdefault('tvshow_year', '') + return meta_cache + +def save_meta_cache(meta_cache): + return __save_cache(__meta_cache_filepath, meta_cache) + +def get_tvshow_years_cache(): + return __get_cache(__tvshow_years_cache_filepath) + +def save_tvshow_years_cache(data): + return __save_cache(__tvshow_years_cache_filepath, data) + +def get_imdb_id_cache(): + return __get_cache(__imdb_id_cache_filepath) + +def save_imdb_id_cache(data): + return __save_cache(__imdb_id_cache_filepath, data) diff --git a/a4kSubtitles/lib/utils.py b/a4kSubtitles/lib/utils.py index ba75fa3..ebe2f8f 100644 --- a/a4kSubtitles/lib/utils.py +++ b/a4kSubtitles/lib/utils.py @@ -4,7 +4,6 @@ import sys import re import json -import hashlib import string from . import kodi from . import logger @@ -33,49 +32,6 @@ py3 = not py2 temp_dir = os.path.join(kodi.addon_profile, 'temp') -results_filepath = os.path.join(kodi.addon_profile, 'last_results.json') - -__meta_cache_filepath = os.path.join(kodi.addon_profile, 'last_meta.json') -__tvshow_years_cache = os.path.join(kodi.addon_profile, 'tvshow_years_cache.json') - -def __get_cache(filepath): - try: - with open(filepath, 'r') as f: - return json.loads(f.read()) - except: - return {} - -def __save_cache(filepath, cache): - try: - json_data = json.dumps(cache, indent=2) - with open(filepath, 'w') as f: - f.write(json_data) - except: pass - -def get_meta_cache(): - meta_cache = __get_cache(__meta_cache_filepath) - meta_cache.setdefault('imdb_id', '') - return meta_cache - -def save_meta_cache(meta_cache): - return __save_cache(__meta_cache_filepath, meta_cache) - -def get_tvshow_years_cache(): - return __get_cache(__tvshow_years_cache) - -def save_tvshow_years_cache(tvshow_years_cache): - return __save_cache(__tvshow_years_cache, tvshow_years_cache) - -def get_tvshow_cache_key(imdb_id): - return '%s_tvshow_year' % imdb_id - -def get_meta_hash(meta): - hash_data = { - 'imdb_id': meta.imdb_id, - 'filename': meta.filename, - } - json_data = json.dumps(hash_data).encode(default_encoding) - return hashlib.sha256(json_data).hexdigest() class DictAsObject(dict): def __getattr__(self, name): diff --git a/a4kSubtitles/lib/video.py b/a4kSubtitles/lib/video.py index ee8bcbb..49d56b2 100644 --- a/a4kSubtitles/lib/video.py +++ b/a4kSubtitles/lib/video.py @@ -8,7 +8,7 @@ import threading from .kodi import xbmc, xbmcvfs, get_bool_setting -from . import logger, utils, request +from . import logger, cache, utils, request __64k = 65536 __longlong_format_char = 'q' @@ -27,11 +27,11 @@ def __set_size_and_hash(core, meta, filepath): f = xbmcvfs.File(filepath) try: - filesize = meta['filesize'] = f.size() + filesize = meta.filesize = f.size() # used for mocking try: - meta['filehash'] = f.hash() + meta.filehash = f.hash() return except: pass @@ -47,7 +47,7 @@ def __set_size_and_hash(core, meta, filepath): f.seek(filesize - __64k, os.SEEK_SET) __sum_64k_bytes(f, result) - meta['filehash'] = "%016x" % result.filehash + meta.filehash = "%016x" % result.filehash finally: f.close() @@ -56,7 +56,7 @@ def __set_subdb_hash(meta, filepath): try: # used for mocking try: - meta['subdb_hash'] = f.subdb_hash() + meta.subdb_hash = f.subdb_hash() return except: pass @@ -64,10 +64,27 @@ def __set_subdb_hash(meta, filepath): f.seek(-__64k, os.SEEK_END) data += f.read(__64k) - meta['subdb_hash'] = hashlib.md5(data).hexdigest() + meta.subdb_hash = hashlib.md5(data).hexdigest() finally: f.close() +def __get_filename(title): + filename = title + video_exts = ['mkv', 'mp4', 'avi', 'mov', 'mpeg', 'flv', 'wmv'] + + try: + filepath = xbmc.Player().getPlayingFile() + filename = filepath.split('/')[-1] + filename = utils.unquote(filename) + + for ext in video_exts: + if ext in filename: + filename = filename[:filename.index(ext) + len(ext)] + break + except: pass + + return filename + def __scrape_tvshow_year(core, meta): imdb_response = request.execute(core, { 'method': 'GET', @@ -82,45 +99,132 @@ def __scrape_tvshow_year(core, meta): if show_year_match: meta.tvshow_year = show_year_match.group(1).strip() - cache_key = utils.get_tvshow_cache_key(meta.imdb_id) - tvshow_years_cache = utils.get_tvshow_years_cache() - tvshow_years_cache[cache_key] = meta.tvshow_year - utils.save_tvshow_years_cache(tvshow_years_cache) + tvshow_years_cache = cache.get_tvshow_years_cache() + tvshow_years_cache[meta.imdb_id] = meta.tvshow_year + cache.save_tvshow_years_cache(tvshow_years_cache) -def __get_filename(title): - filename = title +def __scrape_imdb_id(core, meta): + if meta.title == '' or meta.year == '': + return - try: - filepath = xbmc.Player().getPlayingFile() - filename = filepath.split('/')[-1] - filename = utils.unquote(filename) - except: pass + is_movie = meta.season == '' and meta.episode == '' - return filename + title = (meta.title if is_movie else meta.tvshow).lower() + year = '_%s' % meta.year if is_movie else '' + query = '%s%s' % (title.lower().replace(' ', '_'), year) + query = query[:20] + + request = { + 'method': 'GET', + 'url': 'https://v2.sg.media-imdb.com/suggestion/%s/%s.json' % (query[:1], query), + 'timeout': 10 + } + + response = core.request.execute(core, request) + if response.status_code != 200: + return + + results = core.json.loads(response.text) + if len(results['d']) == 0: + return + + def filter_movie_results(result): + year_start = result.get('y', None) + return ( + result['l'].lower() == title and + (year_start is not None and year_start == year) + ) + + if is_movie: + year = int(meta.year) + results = list(filter(filter_movie_results, results['d'])) + if len(results) > 0: + meta.imdb_id = results[0]['id'] + return + + show_title = title.lower() + episode_title = meta.title.lower() + episode_year = int(meta.year) + + def filter_tvshow_results(result): + year_start = result.get('y', None) + year_end = result.get('yr', '-').split('-')[1] + return ( + result['l'].lower() == show_title and + (year_start is not None and year_start <= episode_year) and + (year_end == '' or int(year_end) >= episode_year) + ) + + results = list(filter(filter_tvshow_results, results['d'])) + if len(results) == 0: + return + + if len(results) == 1: + meta.tvshow_year = str(results[0]['y']) + meta.imdb_id = results[0]['id'] + return + + episode_title_pattern = r'title=\"' + re.escape(episode_title) + r'\"' + for result in results: + episodes_response = core.request.execute(core, { + 'method': 'GET', + 'url': 'https://www.imdb.com/title/%s/episodes/_ajax?season=%s' % (result['id'], meta.season), + 'timeout': 10 + }) + + if episodes_response.status_code != 200: + continue + + if re.search(episode_title_pattern, episodes_response.text, re.IGNORECASE): + meta.tvshow_year = str(result['y']) + meta.imdb_id = result['id'] + return + +def __get_basic_info(): + meta = utils.DictAsObject({}) + + meta.year = xbmc.getInfoLabel('VideoPlayer.Year') + meta.season = xbmc.getInfoLabel('VideoPlayer.Season') + meta.episode = xbmc.getInfoLabel('VideoPlayer.Episode') + meta.tvshow = xbmc.getInfoLabel('VideoPlayer.TVShowTitle') + meta.tvshow_year = '' + + meta.title = xbmc.getInfoLabel('VideoPlayer.OriginalTitle') + if meta.title == '': + meta.title = xbmc.getInfoLabel('VideoPlayer.Title') + + meta.filename = __get_filename(meta.title) + meta.filename_without_ext = meta.filename + meta.imdb_id = xbmc.getInfoLabel('VideoPlayer.IMDBNumber') + + return meta def get_meta(core): - imdb_id = xbmc.getInfoLabel('VideoPlayer.IMDBNumber') - title = xbmc.getInfoLabel('VideoPlayer.OriginalTitle') - if title == '': - title = xbmc.getInfoLabel('VideoPlayer.Title') - filename = __get_filename(title) - - meta_cache = utils.get_meta_cache() - if imdb_id != '' and meta_cache['imdb_id'] == imdb_id and meta_cache['filename'] == filename: - meta = utils.DictAsObject(meta_cache) + meta = __get_basic_info() + + if meta.imdb_id == '': + cache_key = cache.hash_data(meta) + imdb_id_cache = cache.get_imdb_id_cache() + meta.imdb_id = imdb_id_cache.get(cache_key, '') + + if meta.imdb_id == '': + __scrape_imdb_id(core, meta) + + if meta.imdb_id != '': + imdb_id_cache[cache_key] = meta.imdb_id + cache.save_imdb_id_cache(imdb_id_cache) + + if meta.tvshow_year != '': + tvshow_years_cache = cache.get_tvshow_years_cache() + tvshow_years_cache[meta.imdb_id] = meta.tvshow_year + cache.save_tvshow_years_cache(tvshow_years_cache) + + meta_cache = cache.get_meta_cache() + if meta.imdb_id != '' and meta_cache.imdb_id == meta.imdb_id and meta_cache.filename == meta.filename: + meta = meta_cache else: - meta = {} - meta['year'] = xbmc.getInfoLabel('VideoPlayer.Year') - meta['season'] = xbmc.getInfoLabel('VideoPlayer.Season') - meta['episode'] = xbmc.getInfoLabel('VideoPlayer.Episode') - meta['tvshow'] = xbmc.getInfoLabel('VideoPlayer.TVShowTitle') - meta['title'] = title - meta['imdb_id'] = imdb_id - - meta['filename'] = filename - meta['filename_without_ext'] = filename - meta['filesize'] = '' - meta['filehash'] = '' + meta.filesize = '' + meta.filehash = '' try: filepath = xbmc.Player().getPlayingFile() @@ -131,7 +235,7 @@ def get_meta(core): traceback.print_exc() try: - meta['filename_without_ext'] = os.path.splitext(meta['filename'])[0] + meta.filename_without_ext = os.path.splitext(meta.filename)[0] except: pass meta_json = json.dumps(meta, indent=2) @@ -144,15 +248,20 @@ def get_meta(core): value = utils.strip_non_ascii_and_unprintable(meta[key]) meta[key] = str(value).strip() - utils.save_meta_cache(meta) + cache.save_meta_cache(meta) meta.is_tvshow = meta.tvshow != '' meta.is_movie = not meta.is_tvshow - if meta.is_tvshow and meta.imdb_id != '' and (get_bool_setting('podnadpisi', 'enabled') - or get_bool_setting('addic7ed', 'enabled')): - tvshow_years_cache = utils.get_tvshow_years_cache() - tvshow_year = tvshow_years_cache.get(utils.get_tvshow_cache_key(meta.imdb_id), '') + tvshow_year_requiring_service_enabled = ( + get_bool_setting('podnadpisi', 'enabled') or + get_bool_setting('addic7ed', 'enabled') + ) + + if meta.is_tvshow and meta.imdb_id != '' and meta.tvshow_year == '' and tvshow_year_requiring_service_enabled: + tvshow_years_cache = cache.get_tvshow_years_cache() + tvshow_year = tvshow_years_cache.get(meta.imdb_id, '') + if tvshow_year != '': meta.tvshow_year = tvshow_year else: diff --git a/a4kSubtitles/search.py b/a4kSubtitles/search.py index 326e219..6b590c8 100644 --- a/a4kSubtitles/search.py +++ b/a4kSubtitles/search.py @@ -45,9 +45,9 @@ def __save_results(core, meta, results): try: if len(results) == 0: return - meta_hash = core.utils.get_meta_hash(meta) + meta_hash = core.cache.get_meta_hash(meta) json_data = core.json.dumps({'hash': meta_hash, 'results': results}, indent=2) - with open(core.utils.results_filepath, 'w') as f: + with open(core.cache.results_filepath, 'w') as f: f.write(json_data) except: import traceback @@ -55,10 +55,10 @@ def __save_results(core, meta, results): def __get_last_results(core, meta): try: - with open(core.utils.results_filepath, 'r') as f: + with open(core.cache.results_filepath, 'r') as f: last_results = core.json.loads(f.read()) - meta_hash = core.utils.get_meta_hash(meta) + meta_hash = core.cache.get_meta_hash(meta) if last_results['hash'] == meta_hash: return last_results['results'] except: pass diff --git a/a4kSubtitles/services/subdb.py b/a4kSubtitles/services/subdb.py index 18de2e9..062ee97 100644 --- a/a4kSubtitles/services/subdb.py +++ b/a4kSubtitles/services/subdb.py @@ -24,11 +24,10 @@ def parse_search_response(core, service_name, meta, response): name = '%s.srt' % meta.filename_without_ext results = [] - lang_iso_639_1_codes = response.text.split(',') - for lang_iso_639_1_code in lang_iso_639_1_codes: - if lang_iso_639_1_code in lang_ids: - lang = meta.languages[lang_ids.index(lang_iso_639_1_code)] - lang_code = core.kodi.xbmc.convertLanguage(lang, core.kodi.xbmc.ISO_639_2) + lang_codes = response.text.split(',') + for lang_code in lang_codes: + if lang_code in lang_ids: + lang = meta.languages[lang_ids.index(lang_code)] results.append({ 'service_name': service_name, diff --git a/addon.xml b/addon.xml index ead772f..2ca3a6f 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ @@ -24,6 +24,10 @@ Supports: OpenSubtitles, BSPlayer, Podnadpisi.NET, SubDB, Subscene, Addic7ed icon.png +[v0.0.28]: + * Attempt scrape of imdb id when missing + * Improve filename parsing + [v0.0.27]: * Attempt to auto-fix a garbled cyrillic encoded subtitles * Fix more encoding issues diff --git a/packages/addons.xml b/packages/addons.xml index 7219a5c..98ef827 100644 --- a/packages/addons.xml +++ b/packages/addons.xml @@ -4,7 +4,7 @@ @@ -27,6 +27,10 @@ Supports: OpenSubtitles, BSPlayer, Podnadpisi.NET, SubDB, Subscene, Addic7ed icon.png +[v0.0.28]: + * Attempt scrape of imdb id when missing + * Improve filename parsing + [v0.0.27]: * Attempt to auto-fix a garbled cyrillic encoded subtitles * Fix more encoding issues diff --git a/packages/addons.xml.crc b/packages/addons.xml.crc index 2953353..b1a9370 100644 --- a/packages/addons.xml.crc +++ b/packages/addons.xml.crc @@ -1 +1 @@ -e97fe8337779468558018c00a3b4d465398048e5 \ No newline at end of file +1379e2b76fca8587948a9da1f7933e3e472ea674 \ No newline at end of file diff --git a/tests/test_suite.py b/tests/test_suite.py index 6af5d9f..ff2d106 100644 --- a/tests/test_suite.py +++ b/tests/test_suite.py @@ -45,11 +45,32 @@ "subdb_hash": "2aec1b70afe702e67ab39a0af776ba5a", } +def __remove_meta_cache(a4ksubtitles_api): + try: + os.remove(a4ksubtitles_api.core.cache.__meta_cache_filepath) + except: pass + +def __remove_imdb_id_cache(a4ksubtitles_api): + try: + os.remove(a4ksubtitles_api.core.cache.__imdb_id_cache_filepath) + except: pass + +def __remove_tvshow_years_cache(a4ksubtitles_api): + try: + os.remove(a4ksubtitles_api.core.cache.__tvshow_years_cache_filepath) + except: pass + def __remove_last_results(a4ksubtitles_api): try: - os.remove(a4ksubtitles_api.core.utils.results_filepath) + os.remove(a4ksubtitles_api.core.cache.results_filepath) except: pass +def __remove_all_cache(a4ksubtitles_api): + __remove_imdb_id_cache(a4ksubtitles_api) + __remove_meta_cache(a4ksubtitles_api) + __remove_tvshow_years_cache(a4ksubtitles_api) + __remove_last_results(a4ksubtitles_api) + def __search(a4ksubtitles_api, settings={}, video_meta={}): search = lambda: None search.params = { @@ -116,6 +137,8 @@ def get_error_msg(e): def test_search_missing_imdb_id(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) + __remove_all_cache(a4ksubtitles_api) + log_error_spy = utils.spy_fn(a4ksubtitles_api.core.logger, 'error') params = { @@ -128,7 +151,7 @@ def test_search_missing_imdb_id(): def test_opensubtitles(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search settings = { @@ -144,7 +167,7 @@ def test_opensubtitles(): expected_result_name2 = 'Fantastic.Beasts.and.Where.to.Find.Them.2016.1080p.BluRay.x264.DTS-FGT.srt' assert search.results[0]['name'] == expected_result_name or search.results[0]['name'] == expected_result_name2 - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search (imdb only) video_meta = { @@ -187,7 +210,7 @@ def test_opensubtitles(): def test_opensubtitles_tvshow(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search settings = { @@ -208,9 +231,39 @@ def test_opensubtitles_tvshow(): assert filepath != '' +def test_opensubtitles_missing_imdb_id(): + a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) + __remove_all_cache(a4ksubtitles_api) + + # search + settings = { + 'opensubtitles.enabled': 'true', + } + video_meta = { + 'imdb_id': '', + } + search = __search_movie(a4ksubtitles_api, settings, video_meta) + + assert len(search.results) > 0 + +def test_opensubtitles_tvshow_missing_imdb_id(): + a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) + __remove_all_cache(a4ksubtitles_api) + + # search + settings = { + 'opensubtitles.enabled': 'true', + } + video_meta = { + 'imdb_id': '', + } + search = __search_tvshow(a4ksubtitles_api, settings, video_meta) + + assert len(search.results) > 0 + def test_bsplayer(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search settings = { @@ -247,7 +300,7 @@ def test_bsplayer(): def test_bsplayer_tvshow(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search settings = { @@ -270,7 +323,7 @@ def test_bsplayer_tvshow(): def test_podnadpisi(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search settings = { @@ -293,7 +346,7 @@ def test_podnadpisi(): def test_podnadpisi_tvshow(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search settings = { @@ -316,7 +369,7 @@ def test_podnadpisi_tvshow(): def test_subdb(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search settings = { @@ -339,7 +392,7 @@ def test_subdb(): def test_subdb_tvshow(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search settings = { @@ -363,7 +416,7 @@ def test_subdb_tvshow(): def test_subscene(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search settings = { @@ -389,7 +442,7 @@ def test_subscene(): def test_subscene_tvshow(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search settings = { @@ -419,7 +472,7 @@ def test_subscene_tvshow(): def test_addic7ed_tvshow(): a4ksubtitles_api = api.A4kSubtitlesApi({'kodi': True}) - __remove_last_results(a4ksubtitles_api) + __remove_all_cache(a4ksubtitles_api) # search settings = {