# -*- coding: utf-8 -*-
# plugin.video.alkoflix
# resources/lib/modules/source_objects.py

import json, re
from urllib.parse import unquote_plus, urlparse, parse_qs
from caches.providers_cache import ExternalProvidersCache
from indexers.metadata import movie_meta, tvshow_meta, season_episodes_meta, get_title
from modules.kodi_utils import local_string as ls, get_setting, logger
from modules.settings import metadata_user_info, date_offset
from modules.source_utils import get_cache_expiry, get_filename_match, get_file_info, normalize
from modules.utils import clean_file_name, safe_string, remove_accents, get_datetime
# from modules.kodi_utils import logger

season_display, show_display, resolutions = ls(32537), ls(32089), '4K 1080p 720p SD total'
pack_check = (season_display, show_display)
MANIFEST_EXTERNAL_PROVIDERS = ('aiostreams', 'aiostreams_custom', 'custom_1', 'custom_2', 'custom_stremio1', 'custom_stremio2')
PRIVATE_EXTERNAL_PROVIDERS = ('c411', 'torr9', 'tr4ker')
HOSTER_EXTERNAL_PROVIDERS = ('alkopaste', 'movix', 'free_telecharger')
CACHED_EXTERNAL_PROVIDERS = PRIVATE_EXTERNAL_PROVIDERS + ('ygg',)
PRIVATE_CACHE_TTL_RESULTS_HOURS = 72
PRIVATE_CACHE_TTL_EMPTY_HOURS = 6
# Les manifestes/AIO/Custom restent des sources personnelles et gardent leur priorité.
# Les trackers privés C411/Torr9/TR4KER, eux, doivent passer dans le tri normal des résultats
# afin de respecter l'ordre choisi dans les réglages (qualité/fournisseur/taille).
PERSONAL_EXTERNAL_PROVIDERS = MANIFEST_EXTERNAL_PROVIDERS
# Les manifestes/AIO/Custom doivent toujours refléter leur configuration externe en temps réel.
# Les trackers C411/Torr9/TR4KER/YGG peuvent utiliser le cache alkoFlix :
# on conserve les hashes déjà obtenus pour éviter de rappeler inutilement leur API.
NO_CACHE_EXTERNAL_PROVIDERS = MANIFEST_EXTERNAL_PROVIDERS + HOSTER_EXTERNAL_PROVIDERS + ('nyaa',)

BLOCKED_FILE_EXTENSIONS = (
	'exe', 'msi', 'bat', 'cmd', 'com', 'scr', 'ps1', 'psm1', 'vbs', 'vbe', 'wsf', 'wsh',
	'jse', 'js', 'jar', 'apk', 'dmg', 'pkg', 'deb', 'rpm', 'appimage', 'run', 'sh',
	'lnk', 'reg', 'hta', 'cpl', 'msc', 'dll', 'sys'
)
BLOCKED_FILE_EXTENSIONS_DOTTED = tuple('.%s' % i for i in BLOCKED_FILE_EXTENSIONS)
BLOCKED_SOURCE_KEYS = (
	'name', 'URLName', 'name_info', 'title', 'filename', 'folder_name', 'description',
	'path', 'file', 'files', 'url', 'unrestricted_link', 'url_dl', 'id', 'link', 'stream'
)

DISPLAY_FILE_EXTENSIONS = tuple(sorted(set(BLOCKED_FILE_EXTENSIONS + (
	'mkv', 'mp4', 'm4v', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mpg', 'mpeg', 'mpe',
	'ts', 'm2ts', 'mts', 'vob', 'ogm', 'ogv', 'divx', 'xvid', '3gp', '3g2', 'asf',
	'rm', 'rmvb', 'iso', 'strm', 'rar', 'zip', '7z', 'tar', 'gz'
))))

def _extract_display_file_extension(value):
	try:
		if not value: return ''
		value = unquote_plus(str(value or '')).strip()
		values = [value]
		try:
			parsed = urlparse(value)
			if parsed.path: values.append(parsed.path)
			query = parse_qs(parsed.query or '')
			for key in ('dn', 'filename', 'file', 'name', 'path'):
				values.extend(query.get(key, []))
		except:
			pass
		pattern = r'(?i)(?:^|[\/\s._\-\(\[])(%s)(?:$|[\?&#;\)\]\s])' % '|'.join(re.escape(i) for i in DISPLAY_FILE_EXTENSIONS)
		for item in values:
			item = unquote_plus(str(item or '')).strip()
			if not item: continue
			for match in reversed(list(re.finditer(pattern, item))):
				if item.rstrip().lower().endswith(match.group(1).lower()):
					return match.group(1).lower()
	except:
		pass
	return ''

def clean_file_name_preserve_extension(value):
	# Variante dédiée à l'affichage : clean_file_name() remplace les points par des espaces,
	# ce qui masque les extensions réelles (.mkv, .mp4, .exe, etc.) dans le sélecteur.
	try:
		ext = _extract_display_file_extension(value)
		display = clean_file_name(str(value or '')).replace('html', ' ').replace('+', ' ').replace('-', ' ')
		display = re.sub(r'\s+', ' ', display).strip()
		if ext:
			display = re.sub(r'(?i)(?:\s+|\.)%s$' % re.escape(ext), '', display).strip()
			display = '%s.%s' % (display, ext.upper()) if display else '.%s' % ext.upper()
		return display
	except:
		try: return clean_file_name(value)
		except: return value


def _normalise_file_text(value):
	try: value = unquote_plus(str(value or ''))
	except: value = str(value or '')
	return re.sub(r'[^a-z0-9]+', '.', value.lower()).strip('.')


def _has_blocked_file_token(value, any_position=False):
	text = _normalise_file_text(value)
	if not text: return False
	tokens = [i for i in text.split('.') if i]
	if not tokens: return False
	if tokens[-1] in BLOCKED_FILE_EXTENSIONS: return True
	return any_position and any(i in BLOCKED_FILE_EXTENSIONS for i in tokens)


def _url_has_blocked_file_extension(value):
	if not value: return False
	try:
		value = str(value)
		parsed = urlparse(value)
		if _has_blocked_file_token(parsed.path or '', any_position=False): return True
		query = parse_qs(parsed.query or '')
		for key in ('dn', 'filename', 'file', 'name', 'path'):
			if any(_has_blocked_file_token(item, any_position=False) for item in query.get(key, [])): return True
	except:
		pass
	return False


def _is_url_like(value):
	try:
		parsed = urlparse(str(value or ''))
		return bool(parsed.scheme and parsed.netloc)
	except:
		return False


def _iter_blocked_source_values(value):
	try:
		if isinstance(value, dict):
			for key, item in value.items():
				key = str(key or '')
				if key in BLOCKED_SOURCE_KEYS or key.lower() in BLOCKED_SOURCE_KEYS:
					yield item
				if isinstance(item, (dict, list, tuple, set)):
					for nested in _iter_blocked_source_values(item): yield nested
		elif isinstance(value, (list, tuple, set)):
			for item in value:
				for nested in _iter_blocked_source_values(item): yield nested
		else:
			yield value
	except:
		return


def _blocked_file_value(item):
	# Retourne la valeur exacte qui déclenche le blocage, utile pour les logs et notifications.
	# Important : une URL de débrideur comme https://alldebrid.com/f/... contient le token "com",
	# qui est aussi une extension exécutable Windows (.com). On analyse donc les URL uniquement
	# comme des URL, sans recherche libre dans tous leurs tokens. Sinon des .mkv/.mp4 valides
	# peuvent être refusés après débridage.
	try:
		if not item: return ''
		if isinstance(item, dict): values = list(_iter_blocked_source_values(item))
		else: values = [item]
		for value in values:
			if value is None: continue
			if _is_url_like(value):
				if _url_has_blocked_file_extension(value): return str(value)
				continue
			if _has_blocked_file_token(value, any_position=False): return str(value)
	except:
		return ''
	return ''

def _is_blocked_file_source(item):
	# Filtre de sécurité permanent : les exécutables/scripts ne doivent jamais être proposés,
	# même si les filtres indésirables sont désactivés ou si un ancien cache les contient encore.
	return bool(_blocked_file_value(item))

def _valid_id(value):
	if value is None: return ''
	value = str(value).strip()
	if not value or value.lower() in ('none', 'null', 'false', '0'): return ''
	return value

def _is_personal_provider(provider):
	return str(provider or '').lower() in PERSONAL_EXTERNAL_PROVIDERS

def _bypass_external_provider_cache(provider):
	return str(provider or '').lower() in NO_CACHE_EXTERNAL_PROVIDERS

def _log_private_provider_cache(provider, status, media_type='', season='', episode='', results=None, cache_id=''):
	try:
		if str(provider or '').lower() not in CACHED_EXTERNAL_PROVIDERS: return
		count = '' if results is None else ' | résultats=%s' % len(results or [])
		ctx = media_type or ''
		if season not in ('', None): ctx += ' S%s' % season
		if episode not in ('', None): ctx += 'E%s' % episode
		cache_id_label = ' | id=%s' % cache_id if cache_id else ''
		logger('Cache trackers', '%s | provider=%s | %s%s%s' % (status, provider, ctx.strip(), cache_id_label, count))
	except: pass


def _private_provider_cache_expiry(provider, results, default_expiry):
	try:
		if str(provider or '').lower() not in CACHED_EXTERNAL_PROVIDERS:
			return default_expiry
		# Règle volontairement limitée aux trackers intégrés mis en cache : C411/Torr9/TR4KER/YGG.
		# Comet, Nyaa et les manifests externes ne reçoivent pas ce TTL spécial.
		return PRIVATE_CACHE_TTL_RESULTS_HOURS if results else PRIVATE_CACHE_TTL_EMPTY_HOURS
	except:
		return default_expiry


def _private_provider_cache_only(meta, provider):
	try:
		return bool((meta or {}).get('private_tracker_cache_only')) and str(provider or '').lower() in CACHED_EXTERNAL_PROVIDERS
	except:
		return False


def _provider_cache_ids(primary_id, fallback_id=''):
	# Les trackers externes interrogent souvent l'IMDb. On utilise donc l'IMDb comme clé
	# prioritaire lorsqu'il existe, avec fallback TMDb pour relire les caches déjà écrits.
	try:
		ids = []
		for value in (primary_id, fallback_id):
			value = _valid_id(value)
			if value and value not in ids: ids.append(value)
		return ids
	except:
		return [_valid_id(primary_id)] if _valid_id(primary_id) else []


def _get_provider_cache(epc, provider, media_type, cache_ids, title, year, season, episode):
	try:
		for cache_id in cache_ids or []:
			sources = epc.get(provider, media_type, cache_id, title, year, season, episode)
			if sources is not None: return sources, cache_id
	except:
		pass
	return None, ''

def _get_provider_cache_nearby_episode(epc, provider, media_type, cache_ids, title, year, season, episode):
	# Pour les trackers mis en cache, une recherche pack saison déjà connue peut suffire
	# à alimenter les épisodes suivants. On l'utilise uniquement si elle contient des
	# résultats, afin de ne pas masquer une future recherche épisode utile par un vieux vide.
	try:
		if str(provider or '').lower() not in CACHED_EXTERNAL_PROVIDERS: return None, ''
		if media_type != 'episode' or season in ('', None) or episode in ('', None): return None, ''
		for s_check, e_check in ((season, ''), ('', '')):
			sources, cache_id = _get_provider_cache(epc, provider, media_type, cache_ids, title, year, s_check, e_check)
			if sources:
				return sources, cache_id
	except:
		pass
	return None, ''


def _set_provider_cache(epc, provider, media_type, cache_ids, title, year, season, episode, sources, expiry_hours):
	try:
		for cache_id in cache_ids or []:
			epc.set(provider, media_type, cache_id, title, year, season, episode, sources, expiry_hours)
	except:
		pass


def _mark_provider_cache_hit_sources(sources, provider):
	# Marque uniquement les résultats relus depuis le cache local des trackers.
	# Champ strictement visuel/debug : ne change pas le tri, le hash, ni la résolution.
	try:
		if str(provider or '').lower() not in CACHED_EXTERNAL_PROVIDERS: return sources
		marked = []
		for item in sources or []:
			if not isinstance(item, dict): continue
			copy_item = item.copy()
			copy_item['provider_cache_hit'] = True
			copy_item['local_cache'] = True
			if not copy_item.get('local_cache_label'):
				copy_item['local_cache_label'] = 'CACHE'
			marked.append(copy_item)
		return marked
	except:
		return sources


def get_source_meta(params):
	params_get = params.get
	media_type, tmdb_id = params_get('media_type'), params_get('tmdb_id')
	imdb_id_param = _valid_id(params_get('imdb_id'))
	catalogue_id_param = _valid_id(params_get('catalogue_id') or params_get('catalog_id'))
	catalogue_base_url_param = _valid_id(params_get('catalogue_base_url') or params_get('catalogue_manifest_url') or params_get('catalogue_base'))
	season = int(params_get('season')) if 'season' in params else ''
	episode = int(params_get('episode')) if 'episode' in params else ''
	custom_title, custom_year = params_get('custom_title'), params_get('custom_year')
	custom_season = int(params_get('custom_season')) if 'custom_season' in params else None
	custom_episode = int(params_get('custom_episode')) if 'custom_episode' in params else None
	catalogue_video_id = params_get('catalogue_video_id', 'false') == 'true'
	background = params_get('background', 'false') == 'true'
	if 'meta' in params: meta = json.loads(params['meta'])
	else:
		meta_user_info, adjust_hours, current_date = metadata_user_info(), date_offset(), get_datetime()
		if media_type == 'episode':
			id_type, media_id = ('imdb_id', imdb_id_param) if imdb_id_param else ('tmdb_id', tmdb_id)
			meta = tvshow_meta(id_type, media_id, meta_user_info, current_date)
			try:
				episodes_data = season_episodes_meta(season, meta, meta_user_info)
				ep_data = next((i for i in episodes_data if i['episode'] == int(episode)))
				meta.update({
					'mediatype': 'episode', 'season': ep_data['season'], 'episode': ep_data['episode'],
					'premiered': ep_data['premiered'], 'ep_name': ep_data['title'], 'plot': ep_data['plot']
				})
			except: pass
		else:
			id_type, media_id = ('imdb_id', imdb_id_param) if imdb_id_param else ('tmdb_id', tmdb_id)
			meta = movie_meta(id_type, media_id, meta_user_info, current_date)
	meta.update({'background': background, 'media_type': media_type, 'season': season, 'episode': episode})
	if custom_title: meta['custom_title'] = custom_title
	if custom_year: meta['custom_year'] = custom_year
	if custom_season: meta['custom_season'] = custom_season
	if custom_episode: meta['custom_episode'] = custom_episode
	expiry_times = get_cache_expiry(media_type, meta, season)
	title = get_title(meta)
	aliases = _make_alias_dict(meta, title)
	year = _get_search_year(meta)
	ep_name = _get_ep_name(meta)
	meta['search_info'] = {
		'media_type': media_type, 'expiry_times': expiry_times, 'year': year, 'aliases': aliases,
		'tmdb_id': meta.get('tmdb_id') or tmdb_id, 'imdb_id': imdb_id_param or _valid_id(meta.get('imdb_id')), 'tvdb_id': meta.get('tvdb_id'),
		'catalogue_id': catalogue_id_param or _valid_id(meta.get('catalogue_id')) or _valid_id(meta.get('id')),
		'catalogue_base_url': catalogue_base_url_param or _valid_id(meta.get('catalogue_base_url')),
		'catalogue_video_id': catalogue_video_id or meta.get('catalogue_video_id', False),
		'title': title, 'ep_name': ep_name, 'total_seasons': meta.get('total_seasons', ''),
		'season': custom_season or season, 'episode': custom_episode or episode
	}
	return meta

def _make_alias_dict(meta, title):
	aliases = []
	meta_title = meta['title']
	original_title = meta['original_title']
	alternative_titles = meta.get('alternative_titles', [])
	country_codes = set([i.replace('GB', 'UK') for i in meta.get('country_codes', [])])
	if meta_title not in alternative_titles: alternative_titles.append(meta_title)
	if original_title not in alternative_titles: alternative_titles.append(original_title)
	if alternative_titles: aliases = [{'title': i, 'country': ''} for i in alternative_titles]
	if country_codes: aliases.extend([{'title': '%s %s' % (title, i), 'country': ''} for i in country_codes])
	normalized = ({'title': normalize(i['title']), 'country': i['country']} for i in aliases)
	aliases.extend(i for i in normalized if not i in aliases)
	return aliases

def _get_search_year(meta):
	if 'custom_year' in meta: return meta['custom_year']
	year = meta.get('year') or '0'
#	if active_external and get_setting('search.enable.yearcheck', 'false') == 'true':
	if get_setting('search.enable.yearcheck', 'false') == 'true':
		from indexers.imdb_api import imdb_movie_year
		try: year = str(imdb_movie_year(meta.get('imdb_id')) or year)
		except: pass
	return year

def _get_ep_name(meta):
	if meta.get('media_type') == 'episode':
		ep_name = meta.get('ep_name')
		try: ep_name = safe_string(remove_accents(ep_name))
		except: ep_name = safe_string(ep_name)
	else: ep_name = None
	return ep_name

class ExternalSource:
	def __init__(self, meta, args):
		self.sources = []
		self.meta, self.args = meta, args

	def results(self, info):
		try:
			self.media_type, self.tmdb_id, self.year = info['media_type'], str(info['tmdb_id']), info['year']
			self.imdb_id = _valid_id(info.get('imdb_id'))
			self.provider_cache_ids = _provider_cache_ids(self.imdb_id, self.tmdb_id)
			self.season, self.episode, self.total_seasons = info['season'], info['episode'], info['total_seasons']
			self.title, self.orig_title, aliases = normalize(info['title']), info['title'], info['aliases']
			self.single_expiry, self.season_expiry, self.show_expiry = info['expiry_times']
			if self.media_type == 'episode':
				season_divider = (
					i['episode_count'] for i in self.meta['season_data']
					if int(i['season_number']) == int(self.meta['season'])
				)
				self.season_divider = int(next(season_divider, 1))
				self.show_divider = int(self.meta['total_aired_eps'])
				self.data = {
					'timeout': self.timeout, 'imdb': _valid_id(info.get('imdb_id')), 'tmdb': _valid_id(info.get('tmdb_id')), 'catalogue_id': _valid_id(info.get('catalogue_id')), 'catalogue_base_url': _valid_id(info.get('catalogue_base_url')), 'catalogue_video_id': bool(info.get('catalogue_video_id')), 'tvdb': info['tvdb_id'], 'aliases': aliases,
					'title': normalize(info['ep_name']), 'tvshowtitle': self.title, 'year': self.year,
					'season': str(self.season), 'episode': str(self.episode), 'total_seasons': self.total_seasons
				}
				self.get_episode_source(*self.args)
			else:
				self.season_divider, self.show_divider, self.data = 1, 1, {
					'timeout': self.timeout, 'imdb': _valid_id(info.get('imdb_id')), 'tmdb': _valid_id(info.get('tmdb_id')), 'catalogue_id': _valid_id(info.get('catalogue_id')), 'catalogue_base_url': _valid_id(info.get('catalogue_base_url')), 'catalogue_video_id': bool(info.get('catalogue_video_id')), 'aliases': aliases,
					'title': self.title, 'year': self.year
				}
				self.get_movie_source(*self.args)
		except: pass
		return self.sources

	def get_movie_source(self, provider, module):
		# Certaines sources externes ne passent pas par le cache afin de respecter
		# immédiatement leur configuration externe ou leur état réel.
		if _bypass_external_provider_cache(provider):
			sources = module().sources(self.data, self.hostDict)
			sources = self.process_sources(provider, sources)
		else:
			epc = ExternalProvidersCache()
			sources, cache_id = _get_provider_cache(epc, provider, self.media_type, self.provider_cache_ids, self.title, self.year, '', '')
			if sources is None:
				if _private_provider_cache_only(self.meta, provider):
					_log_private_provider_cache(provider, 'CACHE-ONLY SKIP', self.media_type, '', '', cache_id=self.provider_cache_ids[0] if self.provider_cache_ids else '')
					sources = []
				else:
					_log_private_provider_cache(provider, 'MISS', self.media_type, '', '', cache_id=self.provider_cache_ids[0] if self.provider_cache_ids else '')
					sources = module().sources(self.data, self.hostDict)
					sources = self.process_sources(provider, sources)
					_set_provider_cache(epc, provider, self.media_type, self.provider_cache_ids, self.title, self.year, '', '', sources, _private_provider_cache_expiry(provider, sources, self.single_expiry))
					_log_private_provider_cache(provider, 'STORE', self.media_type, '', '', sources, cache_id=self.provider_cache_ids[0] if self.provider_cache_ids else '')
			else:
				sources = _mark_provider_cache_hit_sources(sources, provider)
				_log_private_provider_cache(provider, 'HIT', self.media_type, '', '', sources, cache_id=cache_id)
		sources = self._sanitize_sources(sources)
		if sources:
			self.sources.extend(sources)

	def get_episode_source(self, provider, module, pack):
		if pack in pack_check: s_check, e_check = '' if pack == show_display else self.season, ''
		else: s_check, e_check = self.season, self.episode
		# Certaines sources externes ne passent pas par le cache afin de respecter
		# immédiatement leur configuration externe ou leur état réel.
		if _bypass_external_provider_cache(provider):
			if pack == show_display:
				sources = module().sources_packs(self.data, self.hostDict, search_series=True, total_seasons=self.total_seasons)
			elif pack == season_display:
				sources = module().sources_packs(self.data, self.hostDict)
			else:
				sources = module().sources(self.data, self.hostDict)
			sources = self.process_sources(provider, sources)
		else:
			epc = ExternalProvidersCache()
			sources, cache_id = _get_provider_cache(epc, provider, self.media_type, self.provider_cache_ids, self.title, self.year, s_check, e_check)
			nearby_sources, nearby_cache_id = (None, '')
			if (sources is None or sources == []) and pack not in pack_check:
				nearby_sources, nearby_cache_id = _get_provider_cache_nearby_episode(epc, provider, self.media_type, self.provider_cache_ids, self.title, self.year, self.season, self.episode)
				if nearby_sources:
					sources, cache_id = nearby_sources, nearby_cache_id
					_log_private_provider_cache(provider, 'HIT NEARBY', self.media_type, s_check, e_check, sources, cache_id=cache_id)
			if sources is None:
				if _private_provider_cache_only(self.meta, provider):
					_log_private_provider_cache(provider, 'CACHE-ONLY SKIP', self.media_type, s_check, e_check, cache_id=self.provider_cache_ids[0] if self.provider_cache_ids else '')
					sources = []
				else:
					_log_private_provider_cache(provider, 'MISS', self.media_type, s_check, e_check, cache_id=self.provider_cache_ids[0] if self.provider_cache_ids else '')
					if pack == show_display:
						expiry_hours = self.show_expiry
						sources = module().sources_packs(self.data, self.hostDict, search_series=True, total_seasons=self.total_seasons)
					elif pack == season_display:
						expiry_hours = self.season_expiry
						sources = module().sources_packs(self.data, self.hostDict)
					else:
						expiry_hours = self.single_expiry
						sources = module().sources(self.data, self.hostDict)
					sources = self.process_sources(provider, sources)
					_set_provider_cache(epc, provider, self.media_type, self.provider_cache_ids, self.title, self.year, s_check, e_check, sources, _private_provider_cache_expiry(provider, sources, expiry_hours))
					_log_private_provider_cache(provider, 'STORE', self.media_type, s_check, e_check, sources, cache_id=self.provider_cache_ids[0] if self.provider_cache_ids else '')
			else:
				sources = _mark_provider_cache_hit_sources(sources, provider)
				if not nearby_sources:
					_log_private_provider_cache(provider, 'HIT', self.media_type, s_check, e_check, sources, cache_id=cache_id)
		sources = self._sanitize_sources(sources)
		if sources:
			if pack == season_display: sources = [i for i in sources if not 'episode_start' in i or i['episode_start'] <= self.episode <= i['episode_end']]
			elif pack == show_display: sources = [i for i in sources if i['last_season'] >= self.season]
			self.sources.extend(sources)

	def _sanitize_sources(self, sources):
		try: return [i for i in (sources or []) if isinstance(i, dict) and not _is_blocked_file_source(i)]
		except: return []

	def process_sources(self, provider, sources):
		processed_sources = []
		try:
			for i in sources or []:
				try:
					if not isinstance(i, dict) or _is_blocked_file_source(i): continue
					i_get = i.get
					if 'hash' in i: i['hash'] = str(i['hash']).lower()
					size, size_label, divider = 0, None, None
					if 'name' in i: URLName = clean_file_name_preserve_extension(i_get('name'))
					else:
						URLName = get_filename_match(self.orig_title, i_get('url'), i_get('name'))
						if not URLName: URLName = clean_file_name_preserve_extension(i_get('url'))
					if 'name_info' in i: quality, extraInfo = get_file_info(name_info=i_get('name_info'))
					else: quality, extraInfo = get_file_info(url=i_get('url'))
					try:
						size = float(i_get('size') or 0)
						if 'package' in i and not i_get('true_size', False):
							if i_get('package') == 'season': divider = self.season_divider
							else: divider = self.show_divider
							size = float(size) / divider
						if size > 0: size_label = '%.2f GB' % size
						else: size_label = ''
					except:
						size, size_label = 0, ''
					i.update({
						'external': True, 'provider': provider, 'scrape_provider': self.scrape_provider, 'URLName': URLName,
						'extraInfo': extraInfo, 'quality': quality, 'size_label': size_label, 'size': round(size, 2),
						'personal_source': _is_personal_provider(provider)
					})
					if _is_blocked_file_source(i): continue
					if not quality in self.resolutions: self.resolutions['SD'] += 1
					else: self.resolutions[quality] += 1
					self.resolutions['total'] += 1
					processed_sources.append(i)
				except: pass
		except: pass
		return processed_sources

	scrape_provider = 'external'
	timeout = 10
	hostDict = {}
	resolutions = dict.fromkeys(resolutions.split(), 0)

