def start_proxy( cls, configuration_file_path, optional_settings_file_path, db_file_path, log_file_path, recordings_directory_path, certificate_file_path, key_file_path, ): Configuration.set_configuration_file_path(configuration_file_path) OptionalSettings.set_optional_settings_file_path( optional_settings_file_path) Database.set_database_file_path(db_file_path) Logging.set_log_file_path(log_file_path) PVR.set_recordings_directory_path(recordings_directory_path) SecurityManager.set_certificate_file_path(certificate_file_path) SecurityManager.set_key_file_path(key_file_path) ProvidersController.initialize() OptionalSettings.read_optional_settings_file() Database.initialize() SecurityManager.initialize() Configuration.read_configuration_file() CacheManager.initialize() HTMLTemplateEngine.initialize() HTTPRequestHandler.initialize() PVR.initialize() Configuration.start_configuration_file_watchdog_observer() OptionalSettings.start_optional_settings_file_watchdog_observer() Logging.start_logging_configuration_file_watchdog_observer() cls.start_http_server() cls.start_https_server() PVR.start() while not cls._shutdown_proxy_event.is_set(): if cls._http_server_thread: cls._http_server_thread.join() if cls._https_server_thread: cls._https_server_thread.join() cls._shutdown_proxy_event.wait(0.5) Configuration.join_configuration_file_watchdog_observer() OptionalSettings.join_optional_settings_file_watchdog_observer() Logging.join_logging_configuration_file_watchdog_observer()
def shutdown_proxy(cls): cls._shutdown_proxy_event.set() ProvidersController.terminate() CacheManager.cancel_cleanup_cache_timer() PVR.cancel_start_recording_timer() PVR.stop() if cls._http_server_thread: cls._http_server_thread.stop() if cls._https_server_thread: cls._https_server_thread.stop() Configuration.stop_configuration_file_watchdog_observer() OptionalSettings.stop_optional_settings_file_watchdog_observer() Logging.stop_logging_configuration_file_watchdog_observer()
def process_optional_settings_file_updates(cls): with cls._lock.writer_lock: message_to_log = [] # <editor-fold desc="Detect and handle cache_downloaded_segments change"> if 'cache_downloaded_segments' not in cls._optional_settings: cls._optional_settings['cache_downloaded_segments'] = True if 'cache_downloaded_segments' not in cls._previous_optional_settings: cls._previous_optional_settings[ 'cache_downloaded_segments'] = True if cls._optional_settings['cache_downloaded_segments'] != \ cls._previous_optional_settings['cache_downloaded_segments']: from iptv_proxy.cache import CacheManager message_to_log.append( 'Detected a change in the cache_downloaded_segments setting\n' 'Old value => {0}\n' 'New value => {1}\n'.format( json.dumps(cls._previous_optional_settings[ 'cache_downloaded_segments']), json.dumps( cls._optional_settings['cache_downloaded_segments'] ))) CacheManager.set_do_cache_downloaded_segments( cls._optional_settings['cache_downloaded_segments']) # </editor-fold> # <editor-fold desc="Detect and handle allow_insecure_lan_connections change"> if 'allow_insecure_lan_connections' not in cls._optional_settings: cls._optional_settings['allow_insecure_lan_connections'] = True if 'allow_insecure_lan_connections' not in cls._previous_optional_settings: cls._previous_optional_settings[ 'allow_insecure_lan_connections'] = True if cls._optional_settings['allow_insecure_lan_connections'] != \ cls._previous_optional_settings['allow_insecure_lan_connections']: from iptv_proxy.http_server import HTTPRequestHandler message_to_log.append( 'Detected a change in the allow_insecure_lan_connections setting\n' 'Old value => {0}\n' 'New value => {1}\n'.format( json.dumps(cls._previous_optional_settings[ 'allow_insecure_lan_connections']), json.dumps(cls._optional_settings[ 'allow_insecure_lan_connections']))) HTTPRequestHandler.set_allow_insecure_lan_connections( cls._optional_settings['allow_insecure_lan_connections']) # </editor-fold> # <editor-fold desc="Detect and handle allow_insecure_wan_connections change"> if 'allow_insecure_wan_connections' not in cls._optional_settings: cls._optional_settings[ 'allow_insecure_wan_connections'] = False if 'allow_insecure_wan_connections' not in cls._previous_optional_settings: cls._previous_optional_settings[ 'allow_insecure_wan_connections'] = False if cls._optional_settings['allow_insecure_wan_connections'] != \ cls._previous_optional_settings['allow_insecure_wan_connections']: from iptv_proxy.http_server import HTTPRequestHandler message_to_log.append( 'Detected a change in the allow_insecure_wan_connections setting\n' 'Old value => {0}\n' 'New value => {1}\n'.format( json.dumps(cls._previous_optional_settings[ 'allow_insecure_wan_connections']), json.dumps(cls._optional_settings[ 'allow_insecure_wan_connections']))) HTTPRequestHandler.set_allow_insecure_wan_connections( cls._optional_settings['allow_insecure_wan_connections']) # </editor-fold> # <editor-fold desc="Detect and handle lan_connections_require_credentials change"> if 'lan_connections_require_credentials' not in cls._optional_settings: cls._optional_settings[ 'lan_connections_require_credentials'] = False if 'lan_connections_require_credentials' not in cls._previous_optional_settings: cls._previous_optional_settings[ 'lan_connections_require_credentials'] = False if cls._optional_settings['lan_connections_require_credentials'] != \ cls._previous_optional_settings['lan_connections_require_credentials']: from iptv_proxy.http_server import HTTPRequestHandler message_to_log.append( 'Detected a change in the lan_connections_require_credentials setting\n' 'Old value => {0}\n' 'New value => {1}\n'.format( json.dumps(cls._previous_optional_settings[ 'lan_connections_require_credentials']), json.dumps(cls._optional_settings[ 'lan_connections_require_credentials']))) HTTPRequestHandler.set_lan_connections_require_credentials( cls. _optional_settings['lan_connections_require_credentials']) # </editor-fold> # <editor-fold desc="Detect and handle wan_connections_require_credentials change"> if 'wan_connections_require_credentials' not in cls._optional_settings: cls._optional_settings[ 'wan_connections_require_credentials'] = True if 'wan_connections_require_credentials' not in cls._previous_optional_settings: cls._previous_optional_settings[ 'wan_connections_require_credentials'] = True if cls._optional_settings['wan_connections_require_credentials'] != \ cls._previous_optional_settings['wan_connections_require_credentials']: from iptv_proxy.http_server import HTTPRequestHandler message_to_log.append( 'Detected a change in the wan_connections_require_credentials setting\n' 'Old value => {0}\n' 'New value => {1}\n'.format( json.dumps(cls._previous_optional_settings[ 'wan_connections_require_credentials']), json.dumps(cls._optional_settings[ 'wan_connections_require_credentials']))) HTTPRequestHandler.set_wan_connections_require_credentials( cls. _optional_settings['wan_connections_require_credentials']) # </editor-fold> if message_to_log: message_to_log.append('Action => N/A') logger.debug('\n'.join(message_to_log)) for provider_name in sorted( ProvidersController.get_providers_map_class()): ProvidersController.get_provider_map_class( provider_name).optional_settings_class( ).process_optional_settings_file_updates( cls._optional_settings, cls._previous_optional_settings)
def run(self): logger.info( 'Starting recording\n' 'Provider => {0}\n' 'Channel number => {1}\n' 'Channel name => {2}\n' 'Program title => {3}\n' 'Start date & time => {4}\n' 'End date & time => {5}'.format( self._recording.provider, self._recording.channel_number, self._recording.channel_name, self._recording.program_title, self._recording.start_date_time_in_utc.astimezone( tzlocal.get_localzone()).strftime('%Y-%m-%d %H:%M:%S'), self._recording.end_date_time_in_utc.astimezone( tzlocal.get_localzone()).strftime('%Y-%m-%d %H:%M:%S'))) self._create_recording_directory_tree() try: hls_client = HLSClient(self._id, self._recording.provider.lower(), self._recording.channel_number) playlist_m3u8_object = m3u8.loads( hls_client.download_playlist_m3u8()) chunks_m3u8_object = None try: chunks_url = '/live/{0}/{1}'.format( self._recording.provider.lower(), playlist_m3u8_object.data['playlists'][0]['uri']) except IndexError: chunks_m3u8_object = playlist_m3u8_object downloaded_segment_file_names = [] while not self._stop_recording_event.is_set(): try: chunks_m3u8_object = m3u8.loads( hls_client.download_chunks_m3u8(chunks_url)) except NameError: if chunks_m3u8_object is None: chunks_m3u8_object = m3u8.loads( hls_client.download_playlist_m3u8()) chunks_m3u8_download_date_time_in_utc = datetime.now(pytz.utc) chunks_m3u8_total_duration = 0 for (segment_index, segment) in enumerate(chunks_m3u8_object.segments): segment_url = '/live/{0}'.format(segment.uri) segment_url_components = urllib.parse.urlparse(segment_url) segment_file_name = re.sub(r'(/.*)?(/)(.*\.ts)', r'\3', segment_url_components.path) if segment_file_name not in downloaded_segment_file_names: try: ts_file_content = CacheManager.query_cache( self._recording.channel_number, segment_file_name.lower()) if ts_file_content is None: ts_file_content = hls_client.download_ts_file( segment_url) CacheManager.update_cache( self._recording.channel_number, segment_file_name.lower(), ts_file_content) logger.debug( 'Downloaded segment\n' 'Segment => {0}'.format(segment_file_name)) segment.uri = '{0}?recording_id={1}'.format( segment_file_name, urllib.parse.quote(self._recording.id)) downloaded_segment_file_names.append( segment_file_name) Utility.write_file( ts_file_content, os.path.join(self._recording_directory_path, segment_file_name), in_binary=True) with Database.get_write_lock(): db_session = Database.create_session() try: db_session.add( Segment( segment_file_name, self._recording.id, pickle.dumps(segment, protocol=pickle. HIGHEST_PROTOCOL), self._recording_directory_path)) db_session.commit() except Exception: (type_, value_, traceback_) = sys.exc_info() logger.error('\n'.join( traceback.format_exception( type_, value_, traceback_))) db_session.rollback() finally: db_session.close() except requests.exceptions.HTTPError: logger.error( 'Failed to download segment\n' 'Segment => {0}'.format(segment_file_name)) else: logger.debug( 'Skipped segment since it was already downloaded\n' 'Segment => {0} '.format(segment_file_name)) chunks_m3u8_total_duration += segment.duration current_date_time_in_utc = datetime.now(pytz.utc) wait_duration = chunks_m3u8_total_duration - ( current_date_time_in_utc - chunks_m3u8_download_date_time_in_utc).total_seconds() if wait_duration > 0: self._stop_recording_event.wait(wait_duration) chunks_m3u8_object = None self._recording.status = RecordingStatus.PERSISTED.value db_session.merge(self._recording) db_session.commit() logger.info( 'Finished recording\n' 'Provider => {0}\n' 'Channel number => {1}\n' 'Channel name => {2}\n' 'Program title => {3}\n' 'Start date & time => {4}\n' 'End date & time => {5}'.format( self._recording.provider, self._recording.channel_number, self._recording.channel_name, self._recording.program_title, self._recording.start_date_time_in_utc.astimezone( tzlocal.get_localzone()).strftime('%Y-%m-%d %H:%M:%S'), self._recording.end_date_time_in_utc.astimezone( tzlocal.get_localzone()).strftime( '%Y-%m-%d %H:%M:%S'))) except (HLSPlaylistDownloadError, ProviderNotFoundError): if self._stop_recording_event.is_set(): self._recording.status = RecordingStatus.PERSISTED.value db_session.merge(self._recording) db_session.commit() logger.info( 'Finished recording\n' 'Provider => {0}\n' 'Channel number => {1}\n' 'Channel name => {2}\n' 'Program title => {3}\n' 'Start date & time => {4}\n' 'End date & time => {5}'.format( self._recording.provider, self._recording.channel_number, self._recording.channel_name, self._recording.program_title, self._recording.start_date_time_in_utc.astimezone( tzlocal.get_localzone()).strftime( '%Y-%m-%d %H:%M:%S'), self._recording.end_date_time_in_utc.astimezone( tzlocal.get_localzone()).strftime( '%Y-%m-%d %H:%M:%S'))) else: logger.info( 'Canceling recording\n' 'Provider => {0}\n' 'Channel number => {1}\n' 'Channel name => {2}\n' 'Program title => {3}\n' 'Start date & time => {4}\n' 'End date & time => {5}'.format( self._recording.provider, self._recording.channel_number, self._recording.channel_name, self._recording.program_title, self._recording.start_date_time_in_utc.astimezone( tzlocal.get_localzone()).strftime( '%Y-%m-%d %H:%M:%S'), self._recording.end_date_time_in_utc.astimezone( tzlocal.get_localzone()).strftime( '%Y-%m-%d %H:%M:%S'))) finally: PVR.cleanup_live_recording(self._recording)