def validate_basic_movie_properties(cls, movie: MovieType, stack_trace: bool = True) -> None: """ Verifies that certain fields in a Kodi VideoInfo dictionary have values. Fields with Missing fields are logged and dummy values are added. Meant to avoid Exceptions. :param movie: :param stack_trace: :return: """ if not cls._logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): return if movie is None: cls._logger.debug_verbose('movie is None') return basic_properties = {} for key in (Movie.TRAILER_TYPE, Movie.FANART, Movie.THUMBNAIL, Movie.TRAILER, Movie.SOURCE, Movie.YEAR, Movie.RATING, Movie.TITLE): basic_properties[key] = Movie.DEFAULT_MOVIE[key] failing_properties = [] is_failed = False for property_name in basic_properties.keys(): if movie.get(property_name) is None: failing_properties.append(property_name) is_failed = True movie.setdefault(property_name, basic_properties[property_name]) if len(failing_properties) > 0: msg = f'{movie.get(Movie.TITLE, "title missing")} ' \ f'{",".join(failing_properties)}' if stack_trace: LazyLogger.dump_stack('Missing basic property: ' + msg) else: cls._logger.debug_verbose('Missing properties:', msg) assert not is_failed, 'LEAK: Invalid property values'
def validate_basic_movie_properties(cls, movie, stack_trace=True): # type: (MovieType, bool) -> None """ Verifies that certain fields in a Kodi VideoInfo dictionary have values. Fields with Missing fields are logged and dummy values are added. Meant to avoid Exceptions. :param movie: :param stack_trace: :return: """ if not cls._logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): return basic_properties = { Movie.TYPE: 'default_' + Movie.TYPE, Movie.FANART: 'default_' + Movie.TYPE, Movie.THUMBNAIL: 'default_ ' + Movie.THUMBNAIL, Movie.TRAILER: 'default_' + Movie.TRAILER, Movie.SOURCE: 'default_' + Movie.SOURCE, # Movie.FILE, Movie.YEAR: 1492, Movie.RATING: 0.0, Movie.TITLE: 'default_' + Movie.TITLE } failing_properties = [] is_failed = False for property_name in basic_properties.keys(): if movie.get(property_name) is None: failing_properties.append(property_name) is_failed = True movie.setdefault(property_name, basic_properties[property_name]) if len(failing_properties) > 0: msg = ', '.join(failing_properties) if stack_trace: LazyLogger.dump_stack('Missing basic property: ' + msg) else: cls._logger.debug_verbose('Missing properties:', msg) assert not is_failed, 'LEAK: Invalid property values'
def runCommand(self, text, outFile): url = self.ttsURL.format(self.language, urllib.parse.quote(text)) LazyLogger.debug_verbose('Google url: ' + url) # # local IFS = +; /usr/bin/mplayer -ao alsa -really -quiet -noconsolecontrols "http://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=$*&tl=en"; headers = { 'Referer': 'http://translate.google.com', 'User-Agent': 'stagefright/1.2 (Linux;Android 5.0)' } req = urllib.request.Request(url, headers=headers) try: resp = urllib.request.urlopen(req) except: OldLogger.ERROR('Failed to open Google TTS URL', hide_tb=True) return False with open(outFile, 'wb') as out: shutil.copyfileobj(resp, out) return True
def notify_settings_changed(cls): # type: () -> None """ Front-end informs others (back-end) that settings may have changed. :return: """ if LazyLogger.isEnabledFor(LazyLogger.DEBUG_EXTRA_VERBOSE): cls._logger.enter() signal_payload = {} cls.send_signal('settings_changed', data=signal_payload, source_id=Constants.BACKEND_ID)
def runCommandAndPipe(self, text): url = self.ttsURL.format(self.language, urllib.parse.quote(text)) LazyLogger.debug_verbose('Google url: ' + url) #req = urllib.request.Request(url) #, headers={ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36' }) headers = { 'Referer': 'http://translate.google.com/', 'User-Agent': 'stagefright/1.2 (Linux;Android 5.0)' } req = urllib.request.Request(url, headers=headers) try: resp = urllib.request.urlopen(req) LazyLogger.debug_verbose('url: ' + req.get_full_url()) LazyLogger.debug_verbose('headers: ' + str(req.header_items())) except: OldLogger.ERROR('Failed to open Google TTS URL', hide_tb=True) return None return resp
from common.exceptions import AbortException from common.logger import LazyLogger from common.messages import Messages from common.constants import Constants from common.configuration_utils import ConfigUtils from common.settings import Settings from common.system_queries import SystemQueries from common import utils from tools import enabler # TODO Remove after eliminating util.getCommand from utils import util module_logger = LazyLogger.get_addon_module_logger(file_path=__file__) __version__ = Constants.VERSION module_logger.info(__version__) module_logger.info('Platform: {0}'.format(sys.platform)) REMOTE_DEBUG: bool = False if REMOTE_DEBUG: try: import pydevd # Note pydevd module need to be copied in XBMC\system\python\Lib\pysrc try: xbmc.log('Trying to attach to debugger', xbmc.LOGDEBUG) '''
import xbmcvfs from common.constants import Constants, Movie from common.imports import * from common.exceptions import AbortException from common.logger import (LazyLogger, Trace) from common.monitor import Monitor from common.playlist import Playlist from common.settings import Settings from frontend.trailer_dialog import TrailerDialog, DialogState from frontend.black_background import BlackBackground from player.player_container import PlayerContainer from frontend.legal_info import LegalInfo module_logger = LazyLogger.get_addon_module_logger(file_path=__file__) ''' Rough outline: Start separate threads to discover basic information about all selected video sources: 1- library 2- trailer folders 3- iTunes 4- TMDB 5- (future) IMDB Each of the above store the discovered info into separate queues. The main function here is to discover the identity of all candidates for playing so that a balanced mix of trailers is available for playing and random selection. It is important to do this quickly. Additional information discovery is performed later, in background threads or just before playing the video.
import xbmc import xbmcgui import xbmcaddon from windowNavigation.action_map import Action from windowNavigation.selection_dialog import SelectionDialog import backends from common.constants import Constants from common.settings import Settings from common.messages import Messages from common.setting_constants import Backends, Players, Genders from common.logger import LazyLogger if Constants.INCLUDE_MODULE_PATH_IN_LOGGER: module_logger = LazyLogger.get_addon_module_logger().getChild( 'lib.windowNavigation') else: module_logger = LazyLogger.get_addon_module_logger() class SettingsDialog(xbmcgui.WindowXMLDialog): HEADER_LABEL = 2 ENGINE_TAB = 100 OPTIONS_TAB = 200 KEYMAP_TAB = 300 ADVANCED_TAB = 400 OK_BUTTON = 28 CANCEL_BUTTON = 29 DEFAULTS_BUTTON = 30 ENGINE_GROUP_LIST = 101
# -*- coding: utf-8 -*- import time import xbmc, xbmcgui from windows.base import WindowReaderBase, WindowHandlerBase from common.constants import Constants from common.logger import LazyLogger from common.messages import Messages from common.settings import Settings from common import utils if Constants.INCLUDE_MODULE_PATH_IN_LOGGER: module_logger = LazyLogger.get_addon_module_logger().getChild( 'lib.windows') else: module_logger = LazyLogger.get_addon_module_logger() class ProgressNotice(xbmcgui.Window): def __init__(self, winID): def __init__(self): super().__init__() self._logger = module_logger.getChild( self.__class__.__name__) # type: LazyLogger self.winID = winID xbmcgui.Window.__init__(self, winID) self.started = False self.finished = False self.seen = False
def validate_detailed_movie_properties(cls, movie: MovieType, stack_trace: bool = True, force_check: bool = False) -> bool: """ Similar to validate_basic_movie_properties. Validates additional fields :param movie: :param stack_trace: :param force_check: Check even if debug level less than DEBUG_VERBOSE :return: True if no problems found """ if not (cls._logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE) or force_check): return True details_properties = { Movie.WRITER: 'default_' + Movie.WRITER, Movie.DETAIL_DIRECTORS: 'default_' + Movie.DETAIL_DIRECTORS, Movie.DETAIL_TITLE: 'default_' + Movie.TITLE, Movie.CAST: 'default_' + Movie.CAST, Movie.PLOT: 'default_' + Movie.PLOT, Movie.GENRE: 'default_' + Movie.GENRE, Movie.STUDIO: 'default_' + Movie.STUDIO, Movie.DETAIL_ACTORS: 'default_' + Movie.ACTORS, Movie.DETAIL_GENRES: 'default_' + Movie.GENRE, Movie.DETAIL_CERTIFICATION: 'default_' + Movie.DETAIL_CERTIFICATION, Movie.DETAIL_CERTIFICATION_IMAGE: 'default_' + Movie.DETAIL_CERTIFICATION_IMAGE, Movie.DETAIL_RUNTIME: 'default_' + Movie.RUNTIME, Movie.DETAIL_WRITERS: 'default_' + Movie.WRITER, # Movie.TMDB_TAGS: 'default_' + Movie.TAG, # For TMDB Movie.DETAIL_STUDIOS: 'default_' + Movie.STUDIO, Movie.RUNTIME: 0, # Movie.ADULT, Movie.MPAA: 'default_' + Movie.MPAA } cls.validate_basic_movie_properties(movie, stack_trace=stack_trace) failing_properties = [] is_ok = True for property_name in details_properties.keys(): if movie.get(property_name) is None: failing_properties.append(property_name) movie.setdefault(property_name, details_properties[property_name]) is_ok = False if len(failing_properties) > 0: msg = ', '.join(failing_properties) if stack_trace: LazyLogger.dump_stack('Missing details property: ' + msg) else: cls._logger.debug_verbose('Missing properties:', msg) country_id = Settings.get_country_iso_3166_1().lower() certifications = WorldCertifications.get_certifications(country_id) if not certifications.is_valid(movie[Movie.MPAA]): if movie[Movie.MPAA] != '': cls._logger.debug_verbose( f'Invalid certification: {movie[Movie.MPAA]} for movie: ' '{movie[Movie.TITLE]} set to NR') movie[Movie.MPAA] = certifications.get_unrated_certification() \ .get_preferred_id() # assert is_ok, 'LEAK, Invalid property values' return is_ok
from typing import Any, List, Union, Type from backends.audio import BasePlayerHandler, WavAudioPlayerHandler from backends.base import SimpleTTSBackendBase, ThreadedTTSBackend from backends import base from backends.audio import BuiltInAudioPlayer, BuiltInAudioPlayerHandler from backends.speechd import Speaker, SSIPCommunicationError from common.constants import Constants from common.setting_constants import Backends, Languages, Players, Genders, Misc from common.logger import LazyLogger from common.messages import Messages from common.settings import Settings from common.system_queries import SystemQueries from common import utils module_logger = LazyLogger.get_addon_module_logger(file_path=__file__) ''' Speech Dispatcher is a Linux TTS abstraction layer. It allows for programs to interact with a speech engine using a consistent interface. It also allows the user to conveniently change which speech engine that they want to use system wide without having to go modify settings everywhere. Speech Dispatcher is long in the tooth, but still useful. ''' if Constants.INCLUDE_MODULE_PATH_IN_LOGGER: module_logger = LazyLogger.get_addon_module_logger().getChild( 'lib.backends') else: module_logger = LazyLogger.get_addon_module_logger()
def notify_non_random_trailer_video(self): for listener in self._listeners: try: listener() except Exception as e: LazyLogger.exception('')
def get_json( cls, url, # type; str second_attempt=False, # type: bool dump_results=False, # type: bool dump_msg='', # type: str error_msg=None, # type: Union[str, int, None] headers=None, # type: Union[dict, None] params=None, # type: Union[Dict[str, Any], None] timeout=3.0 # type: float ): # type: (...) -> (int, str) """ Queries external site for movie/trailer information. Returns JSON result. Retries once on failure. Uses hints from response to adjust delay between requests. :param url: :param second_attempt: :param dump_results: :param dump_msg: :param error_msg: :param headers: :param params: :param timeout: :return: """ if headers is None: headers = {} if params is None: params = {} destination_string = '' request_index = None site = None if 'themoviedb' in url: destination_string = 'TMDB' request_index = JsonUtilsBasic.TMDB_REQUEST_INDEX site = 'TMDB' elif backend_constants.APPLE_URL_PREFIX in url: destination_string = 'iTunes' request_index = JsonUtilsBasic.ITUNES_REQUEST_INDEX site = 'iTunes' elif backend_constants.ROTTEN_TOMATOES_URL_PREFIX in url: destination_string = 'RottenTomatoes' request_index = JsonUtilsBasic.ROTTEN_TOMATOES_REQUEST_INDEX site = 'Tomatoes' destination_data = JsonUtilsBasic.DestinationDataContainer.get_data( request_index) Monitor.throw_exception_if_abort_requested() with destination_data.get_lock(): time_delay = JsonUtilsBasic.get_delay_time(request_index) json_text = None # Some TMDB api calls do NOT give RATE-LIMIT info in header responses # In such cases we detect the failure from the status code and retry # with a forced sleep of 10 seconds, which is the maximum required # wait time. if cls._logger.isEnabledFor(LazyLogger.DEBUG_EXTRA_VERBOSE): cls._logger.debug_extra_verbose( 'requestCount:', destination_data.total_requests, 'serverBlockingRequestUntil:', destination_data.server_blocking_request_until, 'numberOfAdditionalRequestsAllowedByServer:', destination_data. number_of_additional_requests_allowed_by_server, 'hardCodedRequestsPerTimePeriod:', destination_data.hard_coded_requests_per_time_period, 'requestLimitFromServer:', destination_data.actual_max_requests_per_time_period, 'actualOldestRequestInWindowExpirationTime:', destination_data. actual_oldest_request_in_window_expiration_time, trace=Trace.TRACE_JSON) if time_delay > 0: if cls._logger.isEnabledFor(LazyLogger.DEBUG_VERBOSE): cls._logger.debug('Waiting for JSON request to', destination_string, 'for', time_delay, 'seconds', trace=[Trace.STATS, Trace.TRACE_JSON]) Monitor.throw_exception_if_abort_requested(timeout=time_delay) destination_data.total_requests += 1 requests_to_url = destination_data.total_requests request_failed = True now = datetime.datetime.now() response_time_stamp = now try: response = requests.get(url.encode('utf-8'), headers=headers, params=params, timeout=timeout) request_failed = False # We could change our minds now = datetime.datetime.now() response_time_stamp = now status_code = response.status_code if cls._logger.isEnabledFor(LazyLogger.DEBUG): cls._logger.debug('generated url:', response.url) json_text = response.json() returned_header = response.headers except AbortException: reraise(*sys.exc_info()) # # Possible Exceptions: # RequestException, Timeout, URLRequired, # TooManyRedirects, HTTPError, ConnectionError, # FileModeWarning, ConnectTimeout, ReadTimeout except (ReadTimeout, ConnectTimeout, ConnectionError) as e: if cls._logger.isEnabledFor(LazyLogger.DEBUG): cls._logger.debug('Timeout occurred. Will retry.', error_msg) request_failed = True status_code = -1 returned_header = { 'Retry-After': str(destination_data.window_time_period.total_seconds()) } except Exception as e: try: # TODO: Move this after full analysis, not nested if cls._logger.isEnabledFor(LazyLogger.DEBUG): cls._logger.debug('Exception getting movie:', error_msg, 'url:', url) cls._logger.exception('') request_failed = True status_code = -1 returned_header = {} if cls._logger.isEnabledFor(LazyLogger.DEBUG): # The exception frequently doesn't have a complete stack # trace LazyLogger.dump_stack() cls._logger.debug( 'request to', destination_string, 'FAILED.', 'url', url, 'headers:', headers, 'params', params, 'timeout', timeout, trace=[Trace.STATS, Trace.TRACE_JSON]) cls._logger.debug( 'request to', destination_string, 'FAILED total requests:', requests_to_url, trace=[Trace.STATS, Trace.TRACE_JSON]) JsonUtilsBasic.dump_delay_info(request_index) if second_attempt: status_code = -1 json_text = None return status_code, json_text if cls._logger.isEnabledFor(LazyLogger.DEBUG): JsonUtilsBasic.dump_delay_info(request_index) except AbortException: reraise(*sys.exc_info()) except Exception as e: cls._logger.exception('') if cls._logger.isEnabledFor(LazyLogger.DISABLED): cls._logger.debug_extra_verbose('Headers from:', site, returned_header) # TODO- delete or control by setting or config_logger destination_data.number_of_additional_requests_allowed_by_server = -1 destination_data.actual_max_requests_per_time_period = 0 destination_data.actual_oldest_request_in_window_expiration_time = None destination_data.server_blocking_request_until = None tmp = returned_header.get('X-RateLimit-Remaining') if tmp is not None: destination_data.number_of_additional_requests_allowed_by_server = int( tmp) tmp = returned_header.get('X-RateLimit-Limit') if tmp is not None: destination_data.actual_max_requests_per_time_period = int(tmp) # Limit will be lifted at this time, in epoch seconds tmp = returned_header.get('X-RateLimit-Reset') if tmp is not None: destination_data.actual_oldest_request_in_window_expiration_time = ( datetime.datetime.fromtimestamp(int(tmp))) else: # Some calls don't return X-RateLimit-Reset, in those cases there # should be Retry-After indicating how many more seconds to wait # before traffic can resume server_blocking_request_until_value = 0 tmp = returned_header.get('Retry-After') msg = '' if tmp is not None: if cls._logger.isEnabledFor( LazyLogger.DEBUG_EXTRA_VERBOSE): cls._logger.debug_extra_verbose('Retry-After:', tmp) seconds = float(tmp) + 1 server_blocking_request_until_value = response_time_stamp + \ datetime.timedelta(0, seconds) destination_data.server_blocking_request_until = server_blocking_request_until_value request_failed = True # TODO: This is messy. The Date string returned is probably dependent # upon the locale of the user, which means the format will be different # Note also that the time zone GMT, or any timezone, is not recognized # on input and it is assumed that you are in the same timezone (yeesh) # Have to manually clobber the TZ field and reset to UTC. tmp = returned_header.get('Date') if tmp is not None: if cls._logger.isEnabledFor( LazyLogger.DEBUG_EXTRA_VERBOSE): parsed_date = parsedate_tz(tmp) unix_time_stamp = calendar.timegm(parsed_date) time_stamp = datetime.datetime.fromtimestamp( unix_time_stamp) delta = time_stamp - response_time_stamp # cls._logger.debug_extra_verbose( # 'Date: ', tmp) if cls._logger.isEnabledFor(LazyLogger.DISABLED): cls._logger.debug_extra_verbose( 'Timestamp from server:', time_stamp, 'difference from client:', delta.total_seconds()) if request_index == JsonUtilsBasic.TMDB_REQUEST_INDEX: if cls._logger.isEnabledFor( LazyLogger.DEBUG_EXTRA_VERBOSE): cls._logger.debug_extra_verbose( 'TMDB response header missing X-RateLimit info.', msg) # Debug.myLog('get_json json_text: ' + json_text.__class__.__name__ + # ' ' + json.dumps(json_text), xbmc.LOGDEBUG) if ((status_code == Constants.TOO_MANY_TMDB_REQUESTS) and (request_index == JsonUtilsBasic.TMDB_REQUEST_INDEX)): # Too many requests, if cls._logger.isEnabledFor(LazyLogger.INFO): cls._logger.info( 'JSON Request rate to TMDB exceeds limits (' + str(destination_data. hard_coded_requests_per_time_period) + ' every', destination_data.window_time_period.total_seconds(), ' seconds). Consider getting API Key. This session\'s requests: ' + str(destination_data.total_requests), trace=Trace.TRACE_JSON) if cls._logger.isEnabledFor(LazyLogger.DEBUG_EXTRA_VERBOSE): JsonUtilsBasic.dump_delay_info(request_index) if cls._logger.isEnabledFor(LazyLogger.DEBUG_EXTRA_VERBOSE): cls._logger.debug_extra_verbose( 'JSON request source:', destination_string, 'total requests:', requests_to_url, 'serverBlockingRequestUntil:', destination_data.server_blocking_request_until, 'numberOfAdditionalRequetsAllowedByServer:', destination_data. number_of_additional_requests_allowed_by_server, 'hardCodedRequestsPerTimePeriod:', destination_data.hard_coded_requests_per_time_period, 'actualMaxRequestsPerTimePeriod:', destination_data.actual_max_requests_per_time_period, 'actualOldestRequestInWindowExpirationTime:', destination_data. actual_oldest_request_in_window_expiration_time, trace=[Trace.STATS, Trace.TRACE_JSON]) JsonUtilsBasic.record_request_timestamp(request_index, response_time_stamp, failed=request_failed) if request_failed: # # Retry only once # if not second_attempt: try: status_code, json_text = \ JsonUtilsBasic.get_json(url, second_attempt=True, headers=headers, params=params, timeout=0.50) except AbortException: reraise(*sys.exc_info()) except Exception as e: status_code = -1 json_text = None finally: JsonUtilsBasic.record_request_timestamp( request_index, response_time_stamp, failed=request_failed) #if dump_results and cls._logger.isEnabledFor(LazyLogger.DEBUG_EXTRA_VERBOSE): # cls._logger.debug_extra_verbose('JSON DUMP:', dump_msg) # cls._logger.debug_extra_verbose(json.dumps( # json_text, indent=3, sort_keys=True)) if status_code == 200: status_code = 0 return status_code, json_text
''' Created on Jul 7, 2020 @author: fbacher ''' from windowNavigation.settings_dialog import SettingsDialog from common.settings import Settings from common.constants import Constants from common.logger import (Logger, LazyLogger) if Constants.INCLUDE_MODULE_PATH_IN_LOGGER: module_logger = LazyLogger.get_addon_module_logger().getChild( 'common.messages') else: module_logger = LazyLogger.get_addon_module_logger() class SettingsGUI(object): ''' classdocs ''' def __init__(self, params): ''' Constructor ''' self._logger = module_logger.getChild(self.__class__.__name__) @staticmethod def launch():
import datetime import hashlib import io import os import time import xbmcvfs from common.configuration_utils import ConfigUtils from common.constants import Constants from common.logger import LazyLogger from common.settings import Settings if Constants.INCLUDE_MODULE_PATH_IN_LOGGER: module_logger = LazyLogger.get_addon_module_logger().getChild( 'lib.cache') else: module_logger = LazyLogger.get_addon_module_logger() class VoiceCache: _logger = None ignore_cache_count = 0 def __init__(self): VoiceCache._logger = module_logger.getChild( self.__class__.__name__) # type: LazyLogger @classmethod def for_debug_setting_changed(cls):