Beispiel #1
0
    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()
Beispiel #2
0
    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()
Beispiel #3
0
    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)
Beispiel #4
0
    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)