def _request_fog_channels_json(cls): url = '{0}{1}'.format( SmoothStreamsConstants.FOG_EPG_BASE_URL, SmoothStreamsConstants.FOG_CHANNELS_JSON_FILE_NAME) logger.debug('Downloading {0}\n' 'URL => {1}'.format( SmoothStreamsConstants.FOG_CHANNELS_JSON_FILE_NAME, url)) requests_session = requests.Session() response = Utility.make_http_request(requests_session.get, url, headers=requests_session.headers, stream=True) if response.status_code == requests.codes.OK: response.raw.decode_content = True logger.trace(Utility.assemble_response_from_log_message(response)) return response.raw else: logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status()
def _request_fog_epg_xml(cls): url = '{0}{1}'.format( SmoothStreamsConstants.FOG_EPG_BASE_URL, SmoothStreamsConstants.FOG_EPG_XML_FILE_NAME, ) logger.debug( 'Downloading %s\nURL => %s', SmoothStreamsConstants.FOG_EPG_XML_FILE_NAME, url, ) requests_session = requests.Session() response = Utility.make_http_request(requests_session.get, url, headers=requests_session.headers, stream=True) if response.status_code == requests.codes.OK: response.raw.decode_content = True logger.trace(Utility.assemble_response_from_log_message(response)) return response.raw logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status()
def download_ts_file(cls, client_ip_address, client_uuid, requested_path, requested_query_string_parameters): authorization_token = requested_query_string_parameters.get( 'wmsAuthSign') channel_number = requested_query_string_parameters.get( 'channel_number') nimble_session_id = requested_query_string_parameters.get( 'nimblesessionid') IPTVProxy.refresh_serviceable_clients(client_uuid, client_ip_address) IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_request_date_time_in_utc', datetime.now(pytz.utc)) IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_requested_channel_number', channel_number) requests_session = cls._get_session_parameter('requests_session') target_url = 'https://{0}.smoothstreams.tv/{1}/ch{2}q1.stream{3}'.format( Configuration.get_configuration_parameter('SMOOTHSTREAMS_SERVER'), Configuration.get_configuration_parameter('SMOOTHSTREAMS_SERVICE'), channel_number, re.sub(r'(/.*)?(/.*\.ts)', r'\2', requested_path)) logger.debug('Proxying request\n' 'Source IP => {0}\n' 'Requested path => {1}\n' ' Parameters\n' ' channel_number => {2}\n' ' client_uuid => {3}\n' 'Target path => {4}\n' ' Parameters\n' ' nimblesessionid => {5}\n' ' wmsAuthSign => {6}'.format( client_ip_address, requested_path, channel_number, client_uuid, target_url, nimble_session_id, authorization_token)) response = Utility.make_http_request( requests_session.get, target_url, params={ 'nimblesessionid': nimble_session_id, 'wmsAuthSign': authorization_token }, headers=requests_session.headers, cookies=requests_session.cookies.get_dict()) if response.status_code == requests.codes.OK: logger.trace( Utility.assemble_response_from_log_message( response, is_content_binary=True)) return response.content else: logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status()
def _request_epg_json(cls, epg_json_path, epg_json_file_name, request_parameters): username = Configuration.get_configuration_parameter( 'VADERSTREAMS_USERNAME') password = SecurityManager.decrypt_password( Configuration.get_configuration_parameter( 'VADERSTREAMS_PASSWORD')).decode() url = '{0}{1}'.format(VaderStreamsConstants.BASE_URL, epg_json_path) logger.debug( 'Downloading %s\n' 'URL => %s\n' ' Parameters\n' ' username => %s\n' ' password => %s%s', epg_json_file_name, url, username, '\u2022' * len(password), '' if request_parameters is None or 'category_id' not in request_parameters else '\n category => {0}'.format(request_parameters['category_id']), ) requests_session = requests.Session() response = Utility.make_http_request( requests_session.get, url, params={ 'username': username, 'password': password, **request_parameters }, headers=requests_session.headers, cookies=requests_session.cookies.get_dict(), stream=True, ) if response.status_code == requests.codes.OK: response.raw.decode_content = True logger.trace(Utility.assemble_response_from_log_message(response)) return response.raw logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status()
def main(): try: Privilege.initialize() Privilege.become_unprivileged_user() (configuration_file_path, optional_settings_file_path, db_file_path, log_file_path, recordings_directory_path, certificate_file_path, key_file_path) = Utility.parse_command_line_arguments() Logging.initialize_logging(log_file_path) logger.info('Starting IPTV Proxy {0}\n' 'Configuration file path => {1}\n' 'Optional settings file path => {2}\n' 'Database file path => {3}\n' 'Log file path => {4}\n' 'Recordings directory path => {5}\n' 'SSL certificate file path => {6}\n' 'SSL key file path => {7}'.format( VERSION, configuration_file_path, optional_settings_file_path, db_file_path, log_file_path, recordings_directory_path, certificate_file_path, key_file_path)) Controller.start_proxy(configuration_file_path, optional_settings_file_path, db_file_path, log_file_path, recordings_directory_path, certificate_file_path, key_file_path) logger.info('Shutting down IPTV Proxy {0}'.format(VERSION)) except Exception: (type_, value_, traceback_) = sys.exc_info() logger.error('\n'.join( traceback.format_exception(type_, value_, traceback_)))
def render_guide_div_template( self, is_server_secure, authorization_required, client_ip_address, client_uuid, guide_number_of_days, guide_provider, guide_group, active_providers_map_class, ): client_ip_address_type = Utility.determine_ip_address_type( client_ip_address) yield self._environment.get_template('guide_div_header.html').render() for rendered_guide_li_template in self._render_guide_lis_template( is_server_secure, authorization_required, client_ip_address_type, client_uuid, guide_number_of_days, guide_provider, guide_group, active_providers_map_class, ): yield rendered_guide_li_template yield self._environment.get_template('guide_div_footer.html').render()
def generate_self_signed_certificate(cls): ip_address_location = Utility.determine_ip_address_location() if ip_address_location is not None: private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) with open(DEFAULT_SSL_KEY_FILE_PATH, 'wb') as output_file: output_file.write( private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), )) current_date_time_in_utc = datetime.now(pytz.utc) subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, ip_address_location['countryCode']), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, ip_address_location['region']), x509.NameAttribute(NameOID.LOCALITY_NAME, ip_address_location['city']), x509.NameAttribute(NameOID.ORGANIZATION_NAME, 'IPTVProxy'), x509.NameAttribute( NameOID.COMMON_NAME, Configuration.get_configuration_parameter( 'SERVER_HOSTNAME_PUBLIC'), ), ]) certificate = (x509.CertificateBuilder( ).subject_name(subject).issuer_name(issuer).public_key( private_key.public_key()).serial_number( x509.random_serial_number() ).not_valid_before(current_date_time_in_utc).not_valid_after( current_date_time_in_utc + timedelta(days=10 * 365)).add_extension( x509.SubjectAlternativeName([ x509.DNSName( Configuration.get_configuration_parameter( 'SERVER_HOSTNAME_LOOPBACK')), x509.DNSName( Configuration.get_configuration_parameter( 'SERVER_HOSTNAME_PRIVATE')), x509.DNSName( Configuration.get_configuration_parameter( 'SERVER_HOSTNAME_PUBLIC')), ]), critical=False, ).sign(private_key, hashes.SHA256(), default_backend())) with open(DEFAULT_SSL_CERTIFICATE_FILE_PATH, 'wb') as output_file: output_file.write( certificate.public_bytes(serialization.Encoding.PEM)) else: logger.error('Failed to generate self signed certificate')
def _request_epg_xml(cls): url = '{0}{1}'.format(VaderStreamsConstants.EPG_BASE_URL, VaderStreamsConstants.XML_EPG_FILE_NAME) logger.debug('Downloading {0}\n' 'URL => {1}'.format(VaderStreamsConstants.XML_EPG_FILE_NAME, url)) requests_session = requests.Session() response = Utility.make_http_request(requests_session.get, url, headers=requests_session.headers, stream=True) if response.status_code == requests.codes.OK: logger.trace(Utility.assemble_response_from_log_message(response)) return response.raw else: logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status()
def generate_playlist_m3u8(cls, is_server_secure, client_ip_address, client_uuid, requested_query_string_parameters, providers): http_token = requested_query_string_parameters.get('http_token') playlist_protocol = requested_query_string_parameters.get('protocol') playlist_type = requested_query_string_parameters.get('type') try: client_ip_address_type = Utility.determine_ip_address_type( client_ip_address) server_hostname = Configuration.get_configuration_parameter( 'SERVER_HOSTNAME_{0}'.format(client_ip_address_type.value)) server_port = Configuration.get_configuration_parameter( 'SERVER_HTTP{0}_PORT'.format('S' if is_server_secure else '')) playlist_m3u8 = [ '#EXTM3U x-tvg-url="{0}://{1}:{2}/live/epg.xml"\n'.format( 'https' if is_server_secure else 'http', server_hostname, server_port) ] for (provider_name, provider) in sorted(list(providers.items())): provider_protocol = playlist_protocol provider_type = playlist_type try: provider_protocol = requested_query_string_parameters[ '{0}_protocol'.format(provider_name)] except KeyError: pass try: provider_type = requested_query_string_parameters[ '{0}_type'.format(provider_name)] except KeyError: pass generate_playlist_m3u8_tracks_mapping = dict( client_uuid=client_uuid, http_token=http_token, is_server_secure=is_server_secure, playlist_protocol=provider_protocol, playlist_type=provider_type, server_hostname=server_hostname, server_port=server_port) playlist_m3u8.append(''.join( provider.api_class().generate_playlist_m3u8_tracks( generate_playlist_m3u8_tracks_mapping))) logger.debug('Generated live IPTVProxy playlist.m3u8') return ''.join(playlist_m3u8) except (KeyError, ValueError): (status, value_, traceback_) = sys.exc_info() logger.error('\n'.join( traceback.format_exception(status, value_, traceback_)))
def generate_xmltv(cls, is_server_secure, authorization_required, client_ip_address, providers_map_class, number_of_days, style): current_date_time_in_utc = datetime.now(pytz.utc) yield '<?xml version="1.0" encoding="utf-8"?>\n<tv date="{0}" generator-info-name="IPTVProxy {1}">\n'.format( current_date_time_in_utc.strftime('%Y%m%d%H%M%S %z'), VERSION) client_ip_address_type = Utility.determine_ip_address_type(client_ip_address) server_password = Configuration.get_configuration_parameter('SERVER_PASSWORD') server_hostname = Configuration.get_configuration_parameter( 'SERVER_HOSTNAME_{0}'.format(client_ip_address_type.value)) server_port = Configuration.get_configuration_parameter( 'SERVER_HTTP{0}_PORT'.format('S' if is_server_secure else '')) cutoff_date_time_in_local = datetime.now(tzlocal.get_localzone()).replace( hour=0, minute=0, second=0, microsecond=0) + timedelta(days=int(number_of_days) + 1) cutoff_date_time_in_utc = cutoff_date_time_in_local.astimezone(pytz.utc) for provider_map_class in providers_map_class.values(): with provider_map_class.database_class().get_access_lock().shared_lock: db_session = provider_map_class.database_class().create_session() try: if style.capitalize() == EPGStyle.COMPLETE.value: query_channels_xmltv = provider_map_class.database_access_class().query_channels_complete_xmltv query_programs_xmltv = provider_map_class.database_access_class().query_programs_complete_xmltv else: query_channels_xmltv = provider_map_class.database_access_class().query_channels_minimal_xmltv query_programs_xmltv = provider_map_class.database_access_class().query_programs_minimal_xmltv for channel_row in query_channels_xmltv(db_session): yield channel_row.xmltv.format( 's' if is_server_secure else '', server_hostname, server_port, '?http_token={0}'.format(server_password) if authorization_required else '') for program_row in query_programs_xmltv(db_session, cutoff_date_time_in_utc): yield program_row.xmltv finally: db_session.close() yield '</tv>\n'
def _do_process_on_modified_event(self, event): do_process_on_modified_event = False if os.path.normpath(event.src_path) == os.path.normpath( self._file_path): if not self._last_file_version_md5_checksum: do_process_on_modified_event = True self._last_file_version_md5_checksum = hashlib.md5( Utility.read_file(self._file_path, in_binary=True)).hexdigest() else: configuration_md5_checksum = hashlib.md5( Utility.read_file(self._file_path, in_binary=True)).hexdigest() if configuration_md5_checksum != self._last_file_version_md5_checksum: do_process_on_modified_event = True self._last_file_version_md5_checksum = configuration_md5_checksum return do_process_on_modified_event
def validate_update_configuration_request(cls, configuration): errors = {} # <editor-fold desc="Validate Server options"> if not Utility.is_valid_server_password( configuration['SERVER_PASSWORD']): errors['serverPassword'] = '******' if not Utility.is_valid_loopback_hostname( configuration['SERVER_HOSTNAME_LOOPBACK']): errors['serverHostnameLoopback'] = 'Must be a valid loopback IP address or hostname\n' \ 'Recommended value => {0}'.format(DEFAULT_HOSTNAME_LOOPBACK) if not Utility.is_valid_private_hostname( configuration['SERVER_HOSTNAME_PRIVATE']): if not Utility.is_valid_public_hostname( configuration['SERVER_HOSTNAME_PRIVATE']): private_ip_address = Utility.determine_private_ip_address() errors[ 'serverHostnamePrivate'] = 'Must be a valid private IP address, public IP address, or hostname' if private_ip_address: errors[ 'serverHostnamePrivate'] += '\nRecommended value => {0}'.format( private_ip_address) if not Utility.is_valid_public_hostname( configuration['SERVER_HOSTNAME_PUBLIC']): public_ip_address = Utility.determine_public_ip_address() errors[ 'serverHostnamePublic'] = 'Must be a valid public IP address or hostname' if public_ip_address: errors[ 'serverHostnamePublic'] += '\nRecommended value => {0}'.format( public_ip_address) if not Utility.is_valid_port_number(configuration['SERVER_HTTP_PORT']): errors['serverPort'] = 'Must be a number between 0 and 65535' # </editor-fold> for provider_name in sorted( ProvidersController.get_providers_map_class()): ProvidersController.get_provider_map_class( provider_name).configuration_class( ).validate_update_configuration_request(configuration, errors) return errors
def on_created(self, event): with self._lock: if os.path.normpath(event.src_path) == os.path.normpath( self._file_path): self._last_file_version_md5_checksum = hashlib.md5( Utility.read_file(self._file_path, in_binary=True)).hexdigest() logger.debug( 'Detected creation of logging configuration file\n' 'Logging configuration file path => {0}'.format( self._file_path)) Logging.set_logging_configuration()
def generate_vod_index_playlist_m3u8(cls, is_server_secure, client_ip_address, client_uuid, http_token): playlist_m3u8 = [] client_ip_address_type = Utility.determine_ip_address_type( client_ip_address) server_hostname = Configuration.get_configuration_parameter( 'SERVER_HOSTNAME_{0}'.format(client_ip_address_type.value)) server_port = Configuration.get_configuration_parameter( 'SERVER_HTTP{0}_PORT'.format('S' if is_server_secure else '')) db_session = Database.create_session() try: for persistent_recording in DatabaseAccess.query_persisted_recordings( db_session): playlist_m3u8.append( '#EXTINF:-1,{0} - [{1} - {2}]\n' '{3}\n'.format( persistent_recording.program_title, persistent_recording.start_date_time_in_utc.astimezone( tzlocal.get_localzone()).strftime( '%Y-%m-%d %H:%M:%S%z'), persistent_recording.end_date_time_in_utc.astimezone( tzlocal.get_localzone()).strftime( '%Y-%m-%d %H:%M:%S%z'), cls.generate_vod_recording_playlist_url( is_server_secure, server_hostname, server_port, client_uuid, persistent_recording.id, http_token, ), )) finally: db_session.close() if playlist_m3u8: playlist_m3u8 = '#EXTM3U\n{0}'.format(''.join(playlist_m3u8)) logger.debug('Generated VOD playlist.m3u8') else: logger.debug( 'No persistent recordings found. VOD playlist.m3u8 will not be generated' ) return playlist_m3u8
def load_ts_file(cls, path, recording_id): db_session = Database.create_session() try: segment_name = re.sub(r'/vod/(.*)\?.*', r'\1', path) segment_row = DatabaseAccess.query_segment_directory_path( db_session, segment_name, recording_id) if segment_row is not None: return Utility.read_file(os.path.join( segment_row.directory_path, segment_name), in_binary=True) else: raise SegmentNotFoundError finally: db_session.close()
def read_optional_settings_file(cls): with cls._lock.writer_lock: cls._backup_optional_settings() try: optional_settings_file_content = Utility.read_file( cls._optional_settings_file_path) cls._set_optional_settings( json.loads(optional_settings_file_content, object_pairs_hook=OrderedDict)) except OSError: logger.error('Failed to read optional settings file\n' 'Optional settings file path => {0}'.format( cls._optional_settings_file_path)) except JSONDecodeError: logger.error('Invalid optional settings file syntax\n' 'Optional settings file path => {0}'.format( cls._optional_settings_file_path))
def read_configuration_file(cls, initial_read=True): with cls._lock.writer_lock: cls._backup_configuration() try: configuration_object = ConfigObj(cls._configuration_file_path, file_error=True, indent_type='', interpolation=False, raise_errors=True, write_empty_values=True) configuration_object_md5 = hashlib.md5( '{0}'.format(configuration_object).encode()).hexdigest() configuration = {} providers = [] non_defaultable_error = False error_message_to_log = [] message_to_log = [] password = None hostname_loopback = DEFAULT_HOSTNAME_LOOPBACK hostname_private = None hostname_public = None http_port = None https_port = None # <editor-fold desc="Read Server section"> try: server_section = configuration_object['Server'] try: password = server_section['password'] except KeyError: non_defaultable_error = True error_message_to_log.append( 'Could not find a password option in the [Server] section\n' ) try: server_hostnames_section = server_section['Hostnames'] # <editor-fold desc="Read loopback option"> try: hostname_loopback = server_hostnames_section[ 'loopback'] if not Utility.is_valid_loopback_hostname( hostname_loopback): error_message_to_log.append( 'The loopback option in the [Hostnames] section has an invalid loopback hostname\n' 'Defaulting to {0}\n'.format( DEFAULT_HOSTNAME_LOOPBACK)) except KeyError: hostname_loopback = DEFAULT_HOSTNAME_LOOPBACK error_message_to_log.append( 'The loopback option in the [Hostnames] section is missing\n' 'Defaulting to {0}\n'.format( DEFAULT_HOSTNAME_LOOPBACK)) # </editor-fold> # <editor-fold desc="Read private option"> do_determine_private_ip_address = False try: hostname_private = server_hostnames_section[ 'private'] if not Utility.is_valid_private_hostname( hostname_private): if Utility.is_valid_public_hostname( hostname_private): error_message_to_log.append( 'The private option in the [Hostnames] section has a public IP address\n' ) else: do_determine_private_ip_address = True except KeyError: do_determine_private_ip_address = True # </editor-fold> # <editor-fold desc="Read public option"> do_determine_public_ip_address = False try: hostname_public = server_hostnames_section[ 'public'] if not Utility.is_valid_public_hostname( hostname_public): do_determine_public_ip_address = True except KeyError: do_determine_public_ip_address = True # </editor-fold> except KeyError: error_message_to_log.append( 'Could not find a [Hostnames] section in the [Server] section\n' ) hostname_loopback = DEFAULT_HOSTNAME_LOOPBACK do_determine_private_ip_address = True do_determine_public_ip_address = True if do_determine_private_ip_address: hostname_private = Utility.determine_private_ip_address( ) if hostname_private: error_message_to_log.append( 'The private option in the [Hostnames] section has an invalid private IP address\n' 'Reverting to {0}\n'.format(hostname_private)) if do_determine_public_ip_address: hostname_public = Utility.determine_public_ip_address() if hostname_public: error_message_to_log.append( 'The public option in the [Hostnames] section has an invalid public IP address\n' 'Reverting to {0}\n'.format(hostname_public)) try: server_ports_section = server_section['Ports'] # <editor-fold desc="Read http option"> try: http_port = server_ports_section['http'] if not Utility.is_valid_port_number(http_port): non_defaultable_error = True error_message_to_log.append( 'The http option in the [Ports] section must be a number between 0 and 65535\n' ) except KeyError: non_defaultable_error = True error_message_to_log.append( 'Could not find an http option in the [Ports] section\n' 'The http option in the [Ports] section must be a number between 0 and 65535\n' ) # </editor-fold> # <editor-fold desc="Read https option"> try: https_port = server_ports_section['https'] if not Utility.is_valid_port_number(https_port): non_defaultable_error = True error_message_to_log.append( 'The https option in the [Ports] section must be a number between 0 and 65535\n' ) except KeyError: non_defaultable_error = True error_message_to_log.append( 'Could not find an https option in the [Ports] section\n' 'The https option in the [Ports] section must be a number between 0 and 65535\n' ) # </editor-fold> except KeyError: non_defaultable_error = True error_message_to_log.append( 'Could not find a [Ports] section in the [Server] section\n' ) except KeyError: non_defaultable_error = True error_message_to_log.append( 'Could not find a [Server] section\n') # </editor-fold> if not non_defaultable_error: configuration = { 'SERVER_PASSWORD': password, 'SERVER_HOSTNAME_LOOPBACK': hostname_loopback, 'SERVER_HOSTNAME_PRIVATE': hostname_private, 'SERVER_HOSTNAME_PUBLIC': hostname_public, 'SERVER_HTTP_PORT': http_port, 'SERVER_HTTPS_PORT': https_port } message_to_log = [ '{0}ead configuration file\n' 'Configuration file path => {1}\n\n' 'SERVER_PASSWORD => {2}\n' 'SERVER_HOSTNAME_LOOPBACK => {3}\n' 'SERVER_HOSTNAME_PRIVATE => {4}\n' 'SERVER_HOSTNAME_PUBLIC => {5}\n' 'SERVER_HTTP_PORT => {6}\n' 'SERVER_HTTPS_PORT => {7}'.format( 'R' if initial_read else 'Rer', cls._configuration_file_path, password, hostname_loopback, hostname_private, hostname_public, http_port, https_port) ] for provider_name in sorted( ProvidersController.get_providers_map_class()): ProvidersController.get_provider_map_class( provider_name).configuration_class( ).read_configuration_file(configuration_object, configuration, providers, message_to_log, error_message_to_log) if not non_defaultable_error: logger.info('\n'.join(message_to_log)) cls._set_configuration(configuration) if configuration_object_md5 != hashlib.md5('{0}'.format( configuration_object).encode()).hexdigest(): cls._update_configuration_file(configuration_object) if initial_read: ProvidersController.initialize_providers(providers) if error_message_to_log: error_message_to_log.insert( 0, '{0} configuration file values\n' 'Configuration file path => {1}\n'.format( 'Invalid' if non_defaultable_error else 'Warnings regarding', cls._configuration_file_path)) if initial_read and non_defaultable_error: error_message_to_log.append('Exiting...') elif non_defaultable_error: error_message_to_log.append( 'Configuration file skipped') else: error_message_to_log.append( 'Configuration file processed') logger.error('\n'.join(error_message_to_log)) if initial_read and non_defaultable_error: sys.exit() except OSError: logger.error( 'Could not open the specified configuration file for reading\n' 'Configuration file path => {0}' '{1}'.format(cls._configuration_file_path, '\n\nExiting...' if initial_read else '')) if initial_read: sys.exit() except SyntaxError as e: logger.error('Invalid configuration file syntax\n' 'Configuration file path => {0}\n' '{1}' '{2}'.format( cls._configuration_file_path, '{0}'.format(e), '\n\nExiting...' if initial_read else '')) if initial_read: sys.exit()
def download_ts_file(cls, client_ip_address, client_uuid, requested_path, requested_query_string_parameters): authorization_token = requested_query_string_parameters.get('authorization_token') channel_name = requested_query_string_parameters.get('channel_name') channel_number = requested_query_string_parameters.get('channel_number') port = requested_query_string_parameters.get('port') server = requested_query_string_parameters.get('server') IPTVProxy.refresh_serviceable_clients(client_uuid, client_ip_address) IPTVProxy.set_serviceable_client_parameter(client_uuid, 'last_request_date_time_in_utc', datetime.now(pytz.utc)) requests_session = requests.Session() target_url = 'http://{0}{1}/{2}{3}'.format(server if re.match(r"\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\Z", server) else '{0}.vaders.tv'.format(server), port if port != ':80' else '', channel_name, re.sub(r'(/.*)?(/.*\.ts)', r'\2', requested_path).replace('_', '/')) logger.debug('Proxying request\n' 'Source IP => {0}\n' 'Requested path => {1}\n' ' Parameters\n' ' channel_name => {2}\n' ' channel_number => {3}\n' ' client_uuid => {4}\n' ' port => {5}\n' ' server => {6}\n' 'Target path => {7}\n' ' Parameters\n' ' token => {8}'.format(client_ip_address, requested_path, channel_name, channel_number, client_uuid, port, server, target_url, authorization_token)) response = Utility.make_http_request(requests_session.get, target_url, params={ 'token': authorization_token }, headers=requests_session.headers, cookies=requests_session.cookies.get_dict()) if response.status_code == requests.codes.OK: logger.trace(Utility.assemble_response_from_log_message(response, is_content_binary=True)) IPTVProxy.set_serviceable_client_parameter(client_uuid, 'last_requested_ts_file_path', re.sub(r'(/.*)?(/.*\.ts)', r'\2', requested_path).replace('_', '/')[1:]) return response.content else: logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status()
def download_playlist_m3u8(cls, client_ip_address, client_uuid, requested_path, requested_query_string_parameters): channel_number = requested_query_string_parameters.get( 'channel_number') http_token = requested_query_string_parameters.get('http_token') protocol = requested_query_string_parameters.get('protocol') IPTVProxy.refresh_serviceable_clients(client_uuid, client_ip_address) IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_request_date_time_in_utc', datetime.now(pytz.utc)) IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_requested_channel_number', channel_number) cls.refresh_session() if protocol == 'hls': authorization_token = cls._get_session_parameter( 'authorization_token') requests_session = cls._get_session_parameter('requests_session') target_url = 'https://{0}.smoothstreams.tv/{1}/ch{2}q1.stream{3}'.format( Configuration.get_configuration_parameter( 'SMOOTHSTREAMS_SERVER'), Configuration.get_configuration_parameter( 'SMOOTHSTREAMS_SERVICE'), channel_number, re.sub(r'(/.*)?(/.*\.m3u8)', r'\2', requested_path)) logger.debug('Proxying request\n' 'Source IP => {0}\n' 'Requested path => {1}\n' ' Parameters\n' ' channel_number => {2}\n' ' client_uuid => {3}\n' ' protocol => {4}\n' 'Target path => {5}\n' ' Parameters\n' ' wmsAuthSign => {6}'.format( client_ip_address, requested_path, channel_number, client_uuid, protocol, target_url, authorization_token)) response = Utility.make_http_request( requests_session.get, target_url, params={'wmsAuthSign': authorization_token}, headers=requests_session.headers, cookies=requests_session.cookies.get_dict()) if response.status_code == requests.codes.OK: logger.trace( Utility.assemble_response_from_log_message( response, is_content_text=True, do_print_content=True)) return response.text.replace( 'chunks.m3u8?', 'chunks.m3u8?channel_number={0}&client_uuid={1}&http_token={2}&' .format( channel_number, client_uuid, urllib.parse.quote(http_token) if http_token else '')) else: logger.error( Utility.assemble_response_from_log_message(response)) response.raise_for_status() elif protocol == 'mpegts': authorization_token = cls._get_session_parameter( 'authorization_token') return '#EXTM3U\n' \ '#EXTINF:-1 ,{0}\n' \ 'https://{1}.smoothstreams.tv:443/{2}/ch{3}q1.stream/mpeg.2ts?' \ 'wmsAuthSign={4}'.format(SmoothStreamsEPG.get_channel_name(int(channel_number)), Configuration.get_configuration_parameter( 'SMOOTHSTREAMS_SERVER'), Configuration.get_configuration_parameter( 'SMOOTHSTREAMS_SERVICE'), channel_number, authorization_token) elif protocol == 'rtmp': authorization_token = cls._get_session_parameter( 'authorization_token') return '#EXTM3U\n' \ '#EXTINF:-1 ,{0}\n' \ 'rtmp://{1}.smoothstreams.tv:3635/{2}/ch{3}q1.stream?' \ 'wmsAuthSign={4}'.format(SmoothStreamsEPG.get_channel_name(int(channel_number)), Configuration.get_configuration_parameter( 'SMOOTHSTREAMS_SERVER'), Configuration.get_configuration_parameter( 'SMOOTHSTREAMS_SERVICE'), channel_number, authorization_token)
def _refresh_session(cls): requests_session = requests.Session() if Configuration.get_configuration_parameter( 'SMOOTHSTREAMS_SERVICE') == 'viewmmasr': url = 'https://www.mma-tv.net/loginForm.php' else: url = 'https://auth.smoothstreams.tv/hash_api.php' username = Configuration.get_configuration_parameter( 'SMOOTHSTREAMS_USERNAME') password = SecurityManager.decrypt_password( Configuration.get_configuration_parameter( 'SMOOTHSTREAMS_PASSWORD')).decode() site = Configuration.get_configuration_parameter( 'SMOOTHSTREAMS_SERVICE') logger.debug('Retrieving SmoothStreams authorization token\n' 'URL => {0}\n' ' Parameters\n' ' username => {0}\n' ' password => {1}\n' ' site => {2}'.format(url, username, '\u2022' * len(password), site)) response = Utility.make_http_request( requests_session.get, url, params={ 'username': username, 'password': password, 'site': site }, headers=requests_session.headers, cookies=requests_session.cookies.get_dict()) response_status_code = response.status_code if response_status_code != requests.codes.OK and response_status_code != requests.codes.NOT_FOUND: logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status() logger.trace( Utility.assemble_response_from_log_message(response, is_content_json=True, do_print_content=True)) authorization_token_response = response.json() session = {} if 'code' in authorization_token_response: if authorization_token_response['code'] == '0': logger.error( 'Failed to retrieve SmoothStreams authorization token\n' 'Error => {0}'.format( authorization_token_response['error'])) elif authorization_token_response['code'] == '1': session['authorization_token'] = authorization_token_response[ 'hash'] session['expires_on'] = datetime.now(pytz.utc) + timedelta( seconds=(authorization_token_response['valid'] * 60)) session['requests_session'] = requests_session logger.info('Retrieved SmoothStreams authorization token\n' 'Hash => {0}\n' 'Expires On => {1}'.format( session['authorization_token'], session['expires_on'].astimezone( tzlocal.get_localzone()).strftime( '%Y-%m-%d %H:%M:%S%z'))) else: logger.error( 'Failed to retrieve SmoothStreams authorization token\n' 'Error => JSON response contains no [\'code\'] field') if response_status_code != requests.codes.OK: response.raise_for_status() return session
def render_index_template( self, is_server_secure, authorization_required, client_ip_address, client_uuid, guide_number_of_days, guide_provider, guide_group, streaming_protocol, active_providers_map_class, ): client_ip_address_type = Utility.determine_ip_address_type( client_ip_address) server_hostname = self._configuration['SERVER_HOSTNAME_{0}'.format( client_ip_address_type.value)] server_http_port = self._configuration['SERVER_HTTP_PORT'] server_https_port = self._configuration['SERVER_HTTPS_PORT'] yield self._environment.get_template('index_header.html').render() yield self._render_head_template(authorization_required, guide_number_of_days, streaming_protocol) yield self._environment.get_template('body_header.html').render() yield self._render_navigation_bar_div_template( guide_provider, guide_group, active_providers_map_class) yield self._render_settings_div_template(guide_number_of_days, streaming_protocol) yield self._environment.get_template('loading_div.html').render() yield self._environment.get_template( 'content_div_header.html').render() yield self._environment.get_template('guide_div_header.html').render() for rendered_guide_li_template in self._render_guide_lis_template( is_server_secure, authorization_required, client_ip_address_type, client_uuid, guide_number_of_days, guide_provider, guide_group, active_providers_map_class, ): yield rendered_guide_li_template yield self._environment.get_template('guide_div_footer.html').render() yield self._environment.get_template('video_div.html').render() yield self._render_recordings_div_template( is_server_secure, authorization_required, client_uuid, server_hostname, server_https_port if is_server_secure else server_http_port, ) yield self._render_configuration_div_template( active_providers_map_class) yield self._environment.get_template('monitor_div.html').render() yield self._render_about_div_template() yield self._environment.get_template( 'content_div_footer.html').render() yield self._environment.get_template('body_footer.html').render() yield self._environment.get_template('index_footer.html').render()
def download_ts_file( cls, client_ip_address, client_uuid, requested_path, requested_query_string_parameters, ): authorization_token = requested_query_string_parameters.get( 'authorization_token') channel_number = requested_query_string_parameters.get( 'channel_number') hostname = requested_query_string_parameters.get('hostname') leaf_directory = requested_query_string_parameters.get( 'leaf_directory') port = requested_query_string_parameters.get('port') scheme = requested_query_string_parameters.get('scheme') IPTVProxy.refresh_serviceable_clients(client_uuid, client_ip_address) IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_request_date_time_in_utc', datetime.now(pytz.utc)) username = Configuration.get_configuration_parameter( '{0}_USERNAME'.format(cls._provider_name.upper())) password = SecurityManager.decrypt_password( Configuration.get_configuration_parameter('{0}_PASSWORD'.format( cls._provider_name.upper()))).decode() requests_session = requests.Session() target_url = '{0}://{1}{2}/hls{3}{4}/{5}/{6}/{7}/{8}{9}'.format( scheme, hostname, port, 'r' if authorization_token else '', '/{0}'.format(authorization_token) if authorization_token else '', username, password, channel_number, leaf_directory, re.sub(r'(/.*)?(/.*\.ts)', r'\2', requested_path), ) logger.debug( 'Proxying request\n' 'Source IP => %s\n' 'Requested path => %s\n' ' Parameters\n' ' channel_number => %s\n' ' client_uuid => %s\n' ' hostname => %s\n' ' port => %s\n' ' scheme => %s\n' 'Target path => %s', client_ip_address, requested_path, channel_number, client_uuid, hostname, port, scheme, target_url, ) response = Utility.make_http_request( requests_session.get, target_url, headers=requests_session.headers, cookies=requests_session.cookies.get_dict(), ) if response.status_code == requests.codes.OK: logger.trace( Utility.assemble_response_from_log_message( response, is_content_binary=True)) IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_requested_ts_file_path', re.sub(r'(/.*)?(/.*\.ts)', r'\2', requested_path)[1:], ) return response.content logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status()
def download_chunks_m3u8(cls, client_ip_address, client_uuid, requested_path, requested_query_string_parameters): authorization_token = requested_query_string_parameters.get( 'wmsAuthSign') channel_number = requested_query_string_parameters.get( 'channel_number') http_token = requested_query_string_parameters.get('http_token') nimble_session_id = requested_query_string_parameters.get( 'nimblesessionid') nimble_session_id = cls._map_nimble_session_id(client_ip_address, channel_number, client_uuid, nimble_session_id, authorization_token) IPTVProxy.refresh_serviceable_clients(client_uuid, client_ip_address) authorization_token = cls._get_session_parameter('authorization_token') requests_session = cls._get_session_parameter('requests_session') target_url = 'https://{0}.smoothstreams.tv/{1}/ch{2}q1.stream{3}'.format( Configuration.get_configuration_parameter('SMOOTHSTREAMS_SERVER'), Configuration.get_configuration_parameter('SMOOTHSTREAMS_SERVICE'), channel_number, re.sub(r'(/.*)?(/.*\.m3u8)', r'\2', requested_path)) logger.debug('Proxying request\n' 'Source IP => {0}\n' 'Requested path => {1}\n' ' Parameters\n' ' channel_number => {2}\n' ' client_uuid => {3}\n' 'Target path => {4}\n' ' Parameters\n' ' nimblesessionid => {5}\n' ' wmsAuthSign => {6}'.format( client_ip_address, requested_path, channel_number, client_uuid, target_url, nimble_session_id, authorization_token)) response = Utility.make_http_request( requests_session.get, target_url, params={ 'nimblesessionid': nimble_session_id, 'wmsAuthSign': authorization_token }, headers=requests_session.headers, cookies=requests_session.cookies.get_dict()) if response.status_code == requests.codes.OK: logger.trace( Utility.assemble_response_from_log_message( response, is_content_text=True, do_print_content=True)) return response.text.replace( '.ts?', '.ts?channel_number={0}&client_uuid={1}&http_token={2}&'. format(channel_number, client_uuid, urllib.parse.quote(http_token) if http_token else '')) else: logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status()
def download_chunks_m3u8(cls, client_ip_address, client_uuid, requested_path, requested_query_string_parameters): authorization_token = requested_query_string_parameters.get('authorization_token') channel_name = requested_query_string_parameters.get('channel_name') channel_number = requested_query_string_parameters.get('channel_number') http_token = requested_query_string_parameters.get('http_token') port = requested_query_string_parameters.get('port') server = requested_query_string_parameters.get('server') IPTVProxy.refresh_serviceable_clients(client_uuid, client_ip_address) IPTVProxy.set_serviceable_client_parameter(client_uuid, 'last_request_date_time_in_utc', datetime.now(pytz.utc)) requests_session = requests.Session() target_url = 'http://{0}{1}/{2}/tracks-v1a1/mono.m3u8'.format( server if re.match(r"\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\Z", server) else '{0}.vaders.tv'.format(server), port if port != ':80' else '', channel_name) logger.debug('Proxying request\n' 'Source IP => {0}\n' 'Requested path => {1}\n' ' Parameters\n' ' channel_name => {2}\n' ' channel_number => {3}\n' ' client_uuid => {4}\n' ' port => {5}\n' ' server => {6}\n' 'Target path => {7}\n' ' Parameters\n' ' token => {8}'.format(client_ip_address, requested_path, channel_name, channel_number, client_uuid, port, server, target_url, authorization_token)) response = Utility.make_http_request(requests_session.get, target_url, params={ 'token': authorization_token }, headers=requests_session.headers, cookies=requests_session.cookies.get_dict()) if response.status_code == requests.codes.OK: logger.trace(Utility.assemble_response_from_log_message(response, is_content_text=True, do_print_content=True)) with cls._do_reduce_hls_stream_delay_lock.reader_lock: if cls._do_reduce_hls_stream_delay: chunks_m3u8 = cls._reduce_hls_stream_delay(response.text, client_uuid, channel_number, number_of_segments_to_keep=3) else: chunks_m3u8 = response.text IPTVProxy.set_serviceable_client_parameter(client_uuid, 'last_requested_channel_number', channel_number) match = re.search(r'http://(.*)\.vaders\.tv(:\d+)?/(.*)/tracks-v1a1/.*', chunks_m3u8) if match is None: match = re.search(r'http://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d+)?/(.*)/tracks-v1a1/.*', chunks_m3u8) if match is not None: server = match.group(1) port = match.group(2) if match.group(2) is not None and len(match.groups()) == 3 else ':80' channel_name = match.group(3) if len(match.groups()) == 3 else match.group(2) chunks_m3u8 = re.sub('.*/tracks-v1a1/', '', chunks_m3u8) return re.sub(r'.ts\?token=(.*)', r'.ts?' r'authorization_token=\1&' 'channel_name={0}&' 'channel_number={1}&' 'client_uuid={2}&' 'http_token={3}&' 'port={4}&' 'server={5}'.format(urllib.parse.quote(channel_name), channel_number, client_uuid, urllib.parse.quote(http_token) if http_token else '', urllib.parse.quote(port), urllib.parse.quote(server)), chunks_m3u8.replace('/', '_')) else: logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status()
def download_playlist_m3u8( cls, client_ip_address, client_uuid, requested_path, requested_query_string_parameters, ): channel_number = requested_query_string_parameters.get( 'channel_number') http_token = requested_query_string_parameters.get('http_token') protocol = requested_query_string_parameters.get('protocol') IPTVProxy.refresh_serviceable_clients(client_uuid, client_ip_address) IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_request_date_time_in_utc', datetime.now(pytz.utc)) IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_requested_channel_number', channel_number) url = Configuration.get_configuration_parameter('{0}_URL'.format( cls._provider_name.upper())) username = Configuration.get_configuration_parameter( '{0}_USERNAME'.format(cls._provider_name.upper())) password = SecurityManager.decrypt_password( Configuration.get_configuration_parameter('{0}_PASSWORD'.format( cls._provider_name.upper()))).decode() if protocol == 'hls': requests_session = requests.Session() target_url = '{0}live/{1}/{2}/{3}.m3u8'.format( url, username, password, channel_number) logger.debug( 'Proxying request\n' 'Source IP => %s\n' 'Requested path => %s\n' ' Parameters\n' ' channel_number => %s\n' ' client_uuid => %s\n' ' protocol => %s\n' 'Target path => %s', client_ip_address, requested_path, channel_number, client_uuid, protocol, target_url, ) response = Utility.make_http_request( requests_session.get, target_url, headers=requests_session.headers, cookies=requests_session.cookies.get_dict(), ) if response.status_code == requests.codes.OK: logger.trace( Utility.assemble_response_from_log_message( response, is_content_text=True, do_print_content=True)) with cls._do_reduce_hls_stream_delay_lock.reader_lock: if cls._do_reduce_hls_stream_delay: chunks_m3u8 = cls._reduce_hls_stream_delay( response.text, client_uuid, channel_number, number_of_segments_to_keep=2, ) else: chunks_m3u8 = response.text parsed_url = urllib.parse.urlparse(response.request.url) scheme = parsed_url.scheme hostname = parsed_url.hostname port = (':{0}'.format(parsed_url.port) if parsed_url.port is not None else '') return re.sub( r'/hls/(.*)/(.*)/(.*)/(.*)/(.*).ts', r'\5.ts?' 'authorization_token=&' 'channel_number={0}&' 'client_uuid={1}&' 'hostname={2}&' 'http_token={3}&' r'leaf_directory=\4&' 'port={4}&' 'scheme={5}'.format( channel_number, client_uuid, urllib.parse.quote(hostname), urllib.parse.quote(http_token) if http_token else '', urllib.parse.quote(port), scheme, ), chunks_m3u8, ) elif response.status_code == requests.codes.FOUND: logger.trace( Utility.assemble_response_from_log_message( response, is_content_text=False, do_print_content=False)) parsed_url = urllib.parse.urlparse( response.headers['Location']) scheme = parsed_url.scheme hostname = parsed_url.hostname port = (':{0}'.format(parsed_url.port) if parsed_url.port is not None else '') return ('#EXTM3U\n' '#EXT-X-VERSION:3\n' '#EXT-X-STREAM-INF:BANDWIDTH=8388608\n' 'chunks.m3u8?authorization_token={0}&' 'channel_number={1}&' 'client_uuid={2}&' 'hostname={3}&' 'http_token={4}&' 'port={5}&' 'scheme={6}'.format( dict(urllib.parse.parse_qsl( parsed_url.query))['token'], channel_number, client_uuid, urllib.parse.quote(hostname), urllib.parse.quote(http_token) if http_token else '', urllib.parse.quote(port), scheme, )) logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status() elif protocol == 'mpegts': provider_map_class = ProvidersController.get_provider_map_class( cls._provider_name) return ('#EXTM3U\n' '#EXTINF:-1 ,{0}\n' '{1}live/{2}/{3}/{4}.ts' ''.format( provider_map_class.epg_class().get_channel_name( int(channel_number)), url, username, password, channel_number, ))
def download_chunks_m3u8(cls, client_ip_address, client_uuid, requested_path, requested_query_string_parameters): channel_number = requested_query_string_parameters.get( 'channel_number') http_token = requested_query_string_parameters.get('http_token') protocol = requested_query_string_parameters.get('protocol') IPTVProxy.refresh_serviceable_clients(client_uuid, client_ip_address) IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_request_date_time_in_utc', datetime.now(pytz.utc)) username = Configuration.get_configuration_parameter( '{0}_USERNAME'.format(cls._provider_name.upper())) password = SecurityManager.decrypt_password( Configuration.get_configuration_parameter('{0}_PASSWORD'.format( cls._provider_name.upper()))).decode() if protocol == 'hls': requests_session = requests.Session() target_url = '{0}live/{1}/{2}/{3}.m3u8'.format( ProvidersController.get_provider_map_class( cls._provider_name).constants_class().BASE_URL, username, password, channel_number) logger.debug('Proxying request\n' 'Source IP => {0}\n' 'Requested path => {1}\n' ' Parameters\n' ' channel_number => {2}\n' ' client_uuid => {3}\n' ' protocol => {4}\n' 'Target path => {5}'.format( client_ip_address, requested_path, channel_number, client_uuid, protocol, target_url)) response = Utility.make_http_request( requests_session.get, target_url, headers=requests_session.headers, cookies=requests_session.cookies.get_dict()) if response.status_code == requests.codes.OK: logger.trace( Utility.assemble_response_from_log_message( response, is_content_text=True, do_print_content=True)) with cls._do_reduce_hls_stream_delay_lock.reader_lock: if cls._do_reduce_hls_stream_delay: chunks_m3u8 = cls._reduce_hls_stream_delay( response.text, client_uuid, channel_number, number_of_segments_to_keep=2) else: chunks_m3u8 = response.text IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_requested_channel_number', channel_number) match = re.search( r'http://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d+)?', response.request.url) server = match.group(1) port = match.group(2) if len(match.groups()) == 2 else ':80' return re.sub( r'/hlsr/(.*)/(.*)/(.*)/(.*)/(.*)/(.*).ts', r'\6.ts?' r'authorization_token=\1&' 'channel_number={0}&' 'client_uuid={1}&' 'http_token={2}&' r'leaf_directory=\5&' 'port={3}&' 'server={4}'.format( channel_number, client_uuid, urllib.parse.quote(http_token) if http_token else '', urllib.parse.quote(port), urllib.parse.quote(server)), chunks_m3u8) else: logger.error( Utility.assemble_response_from_log_message(response)) response.raise_for_status() elif protocol == 'mpegts': provider_map_class = ProvidersController.get_provider_map_class( cls._provider_name) return '#EXTM3U\n' \ '#EXTINF:-1 ,{0}\n' \ '{1}{2}/{3}/{4}' \ ''.format(provider_map_class.constants_class().BASE_URL, provider_map_class.epg_class().get_channel_name(int(channel_number)), username, password, channel_number)
def download_playlist_m3u8(cls, client_ip_address, client_uuid, requested_path, requested_query_string_parameters): channel_number = requested_query_string_parameters.get('channel_number') http_token = requested_query_string_parameters.get('http_token') protocol = requested_query_string_parameters.get('protocol') IPTVProxy.refresh_serviceable_clients(client_uuid, client_ip_address) IPTVProxy.set_serviceable_client_parameter(client_uuid, 'last_request_date_time_in_utc', datetime.now(pytz.utc)) authorization_token = cls._calculate_token() if protocol == 'hls': requests_session = requests.Session() target_url = 'http://vapi.vaders.tv/play/{0}.m3u8'.format(channel_number) logger.debug('Proxying request\n' 'Source IP => {0}\n' 'Requested path => {1}\n' ' Parameters\n' ' channel_number => {2}\n' ' client_uuid => {3}\n' ' protocol => {4}\n' 'Target path => {5}\n' ' Parameters\n' ' token => {6}'.format(client_ip_address, requested_path, channel_number, client_uuid, protocol, target_url, authorization_token)) response = Utility.make_http_request(requests_session.get, target_url, params={ 'token': authorization_token }, headers=requests_session.headers, cookies=requests_session.cookies.get_dict()) if response.status_code == requests.codes.OK: logger.trace(Utility.assemble_response_from_log_message(response, is_content_text=True, do_print_content=True)) match = re.search(r'http://(.*)\.vaders\.tv(:\d+)?/(.*)/tracks-v1a1/.*', response.text) if match is None: match = re.search(r'http://(.*)\.vaders\.tv(:\d+)?/(.*)/index.m3u8.*', response.request.url) if match is None: match = re.search(r'http://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d+)?/(.*)/index.m3u8.*', response.request.url) server = match.group(1) port = match.group(2) if match.group(2) is not None and len(match.groups()) == 3 else ':80' channel_name = match.group(3) if len(match.groups()) == 3 else match.group(2) return re.sub(r'tracks-v1a1/mono.m3u8\?token=(.*)', 'chunks.m3u8?' r'authorization_token=\1&' 'channel_name={0}&' 'channel_number={1}&' 'client_uuid={2}&' 'http_token={3}&' 'port={4}&' 'server={5}'.format(urllib.parse.quote(channel_name), channel_number, client_uuid, urllib.parse.quote(http_token) if http_token else '', urllib.parse.quote(port), urllib.parse.quote(server)), re.sub('.*/tracks-v1a1/', 'tracks-v1a1/', response.text)) else: logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status() elif protocol == 'mpegts': return '#EXTM3U\n' \ '#EXTINF:-1 ,{0}\n' \ 'http://vapi.vaders.tv/play/{1}.ts?' \ 'token={2}'.format(VaderStreamsEPG.get_channel_name(int(channel_number)), channel_number, authorization_token)
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)
def download_chunks_m3u8( cls, client_ip_address, client_uuid, requested_path, requested_query_string_parameters, ): authorization_token = requested_query_string_parameters.get( 'authorization_token') channel_number = requested_query_string_parameters.get( 'channel_number') client_uuid = requested_query_string_parameters.get('client_uuid') hostname = requested_query_string_parameters.get('hostname') http_token = requested_query_string_parameters.get('http_token') port = requested_query_string_parameters.get('port') scheme = requested_query_string_parameters.get('scheme') IPTVProxy.refresh_serviceable_clients(client_uuid, client_ip_address) IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_request_date_time_in_utc', datetime.now(pytz.utc)) IPTVProxy.set_serviceable_client_parameter( client_uuid, 'last_requested_channel_number', channel_number) username = Configuration.get_configuration_parameter( '{0}_USERNAME'.format(cls._provider_name.upper())) password = SecurityManager.decrypt_password( Configuration.get_configuration_parameter('{0}_PASSWORD'.format( cls._provider_name.upper()))).decode() requests_session = requests.Session() target_url = '{0}://{1}{2}/live/{3}/{4}/{5}.m3u8'.format( scheme, hostname, port, username, password, channel_number) logger.debug( 'Proxying request\n' 'Source IP => %s\n' 'Requested path => %s\n' ' Parameters\n' ' authorization_token => %s\n' ' channel_number => %s\n' ' client_uuid => %s\n' ' hostname => %s\n' ' port => %s\n' ' scheme => %s\n' 'Target path => %s\n' ' Parameters\n' ' token => %s', client_ip_address, requested_path, authorization_token, channel_number, client_uuid, hostname, port, scheme, target_url, authorization_token, ) response = Utility.make_http_request( requests_session.get, target_url, params={'token': authorization_token}, headers=requests_session.headers, cookies=requests_session.cookies.get_dict(), ) if response.status_code == requests.codes.OK: logger.trace( Utility.assemble_response_from_log_message( response, is_content_text=True, do_print_content=True)) with cls._do_reduce_hls_stream_delay_lock.reader_lock: if cls._do_reduce_hls_stream_delay: chunks_m3u8 = cls._reduce_hls_stream_delay( response.text, client_uuid, channel_number, number_of_segments_to_keep=2, ) else: chunks_m3u8 = response.text return re.sub( r'/hlsr/(.*)/(.*)/(.*)/(.*)/(.*)/(.*).ts', r'\6.ts?' r'authorization_token=\1&' 'channel_number={0}&' 'client_uuid={1}&' 'hostname={2}&' 'http_token={3}&' r'leaf_directory=\5&' 'port={4}&' 'scheme={5}'.format( channel_number, client_uuid, urllib.parse.quote(hostname), urllib.parse.quote(http_token) if http_token else '', urllib.parse.quote(port), scheme, ), chunks_m3u8, ) else: logger.error(Utility.assemble_response_from_log_message(response)) response.raise_for_status()