Esempio n. 1
0
    def update_sdt_names(self, _sdt_msg, _service_provider, _service_name):
        # must start with x474011  (pid for SDT is x0011
        # update the full msg length 2 bytes with first 4 bits as xF at position 7-8  (includes crc)
        # update descriptor length lower 12 bits at position 20-21 (remaining bytes - crc)
        # x48 service descriptor tag
        # 1 byte length of service provider
        # byte string of the service provider
        # 1 byte length of service name
        # byte string of the service name
        # crc
        if _sdt_msg[:3] != b'\x47\x40\x11':
            self.logger.info('WRONG ATSC MSG {}'.format(
                bytes(_sdt_msg[:20]).hex()))
            return _sdt_msg

        descr = b'\x01' \
                + utils.set_str(_service_provider, False) \
                + utils.set_str(_service_name, False)
        descr = b'\x48' + utils.set_u8(len(descr)) + descr
        msg = _sdt_msg[8:20] + utils.set_u8(len(descr)) + descr
        length = utils.set_u16(len(msg) + 4 + 0xF000)
        msg = ATSC_SERVICE_DESCR_TABLE_TAG + length + msg
        crc = self.gen_crc_mpeg(msg)
        msg = _sdt_msg[:5] + msg + crc
        msg = msg.ljust(len(_sdt_msg), b'\xFF')
        return msg
Esempio n. 2
0
    def datagram_received(self, _data, _host_port):
        """Handle a received multicast datagram."""

        (host, port) = _host_port
        if self.config['hdhomerun']['udp_netmask'] is None:
            is_allowed = True
        else:
            try:
                net = IPv4Network(self.config['hdhomerun']['udp_netmask'])
            except (ipaddress.AddressValueError, ValueError) as err:
                self.logger.error(
                    'Illegal value in [hdhomerun][udp_netmask].  '
                    'Format must be #.#.#.#/#. Exiting hdhr service. ERROR: {}'
                    .format(err))
                sys.exit(1)
            is_allowed = IPv4Address(host) in net

        if not is_allowed:
            return

        self.logger.debug('UDP: from {}:{}'.format(host, port))
        try:
            (frame_type, msg_len, device_type, sub_dt_len, sub_dt, device_id, sub_did_len, sub_did) = \
                struct.unpack('>HHBBIBBI', _data[0:-4])
            # (crc,) = struct.unpack('<I', _data[-4:])
        except ValueError as err:
            self.logger.error('UDP: {}'.format(err))
            return

        if frame_type != HDHOMERUN_TYPE_DISCOVER_REQ:
            self.logger.error('UDP: Unknown from type = {}'.format(frame_type))
        else:
            msg_type = bytes.fromhex('0003')
            header = bytes.fromhex('010400000001')
            device_id = bytes.fromhex('0204' +
                                      self.config['hdhomerun']['hdhr_id'])
            base_url = 'http://' + \
                       self.config['web']['plex_accessible_ip'] + \
                       ':' + str(self.config['web']['web_admin_port'])
            base_url_msg = b'\x2a' + utils.set_str(base_url.encode(), False)

            namespace = None
            for area, area_data in self.config.items():
                if 'player-tuner_count' in area_data.keys():
                    namespace = area
            tuner_count = b'\x10\x01' + utils.set_u8(
                self.config[namespace]['player-tuner_count'])

            lineup_url = base_url + '/lineup.json'
            lineup_url = b'\x27' + utils.set_str(lineup_url.encode(), False)
            msg = header + device_id + base_url_msg + tuner_count + lineup_url
            msg_len = utils.set_u16(len(msg))
            response = msg_type + msg_len + msg

            x = zlib.crc32(response)
            crc = struct.pack('<I', x)
            response += crc
            self.logger.debug('UDP Response={} {}'.format(
                _host_port, response))
            self.sock_multicast.sendto(response, _host_port)
Esempio n. 3
0
    def gen_vct(self, _mux_stream):
        # Table 6.4 Terrestrial Virtual Channel Table
        # 1byte table_id = 0xc8
        # 1111 static bits
        # 12bits length including crc
        # 2bytes TSID (transport stream id) = 0x0b21 (how is this generated?)
        # 11 static bits
        # 5bits version_no = 1
        # 1bit current_next_indicator = 1
        # 1byte section_number = 0 (only one section)
        # 1bytes last_section_number = 0 (only one section)
        # 1byte protocol_version = 0
        # 1byte number of channels
        #    gen_vct_channel(channel)
        # CRC_32
        msg = b''
        tsid = _mux_stream['tsid']
        ver_sect_last_sect_proto = b'\xc3\x00\x00\x00'
        channels_len = utils.set_u8(len(_mux_stream['channels']))
        for short_name in _mux_stream['channels'].keys():
            msg += self.gen_vct_channel(tsid, short_name,
                                        _mux_stream['channels'][short_name])
        extra_empty_descr = b'\xfc\x00'
        msg = tsid + ver_sect_last_sect_proto + channels_len + msg + extra_empty_descr
        length = utils.set_u16(len(msg) + 4 + 0xF000)

        msg = ATSC_VIRTUAL_CHANNEL_TABLE_TAG + length + msg
        crc = self.gen_crc_mpeg(msg)
        msg = self.gen_header(0x1ffb) + msg + crc

        # channels is a dict with the key being the primary channel name (short_name)
        return self.format_video_packets([msg])
Esempio n. 4
0
 def gen_err_response(_frame_type, _tag, _text):
     # This is a tag type of HDHOMERUN_ERROR_MESSAGE
     # does not include the crc
     msg = msgs[_tag].format(*_text).encode()
     tag = utils.set_u8(HDHOMERUN_ERROR_MESSAGE)
     err_resp = utils.set_str(msg, True)
     msg_len = utils.set_u16(len(tag) + len(err_resp))
     response = _frame_type + msg_len + tag + err_resp
     return response
Esempio n. 5
0
 def gen_channel_longnames(self, names):
     # Table 6.28 Extended Channel Name Descriptor
     # channel  name  for  the  virtual  channel  containing this descriptor
     # allows for upto 245 character strings
     # 8bit = tag = 0xA0
     # 8bit = description length
     # long_channel_name_text = gen_multiple_string_structure()
     long_name = self.gen_multiple_string_structure(names)
     return ATSC_EXTENDED_CHANNEL_DESCR_TAG + utils.set_u8(
         len(long_name)) + long_name
Esempio n. 6
0
 def gen_multiple_string_structure(self, _names):
     # Table 6.39 Multiple String Structure
     # event titles, long channel names, the ETT messages, and RRT text items
     # allows for upto 255 character strings
     # 8bit = array size
     # for each name
     #    3byte ISO_639_language_string = 'eng'
     #    1byte segments = normally 0x01
     #    for each segment
     #        1byte compression = 0x00 (not compression)
     #        1byte mode = 0x00 (used with unicode 2 byte letters, assume ASCII)
     #        1byte length in bytes = calculated
     #        string in byte format.  1 byte per character
     msg = utils.set_u8(len(_names))
     for name in _names:
         lang = self.gen_lang(b'eng')
         segment_len = utils.set_u8(1)
         compress_mode = utils.set_u16(0)
         name_bytes = utils.set_str(name.encode(), False)
         msg += lang + segment_len + compress_mode + name_bytes
     return msg
Esempio n. 7
0
    def gen_sld(self, _base_pid, _elements):
        # Table 6.29 Service Location Descriptor
        # Appears in each channel in the VCT
        # 8bit = tag = 0xA1
        # 8bit = description length
        # 3bits = 111 static bits
        # 13bits = PCR_PID = 0x1FFF or the program ID value found in the TS_prog_map
        # 8bits = number of elements
        # for each element
        #    8bits = stream_type = 0x02 (video stream) or 0x81 (audio stream)
        #    3bits = 111 static bits
        #    13bits = PCR_PID = (same as abovefor video stream) unique for audio
        #    8*3bytes = lang (spa, eng, mul, null for video stream
        #
        # element is an array of languages

        # the PID is based on the program number.
        # Base_PID = prog_num << 4
        # Video Stream Element = Base_PID + 1 with the 12th bit set
        # Audio Stream Elements = Vide Stream PID + 4 with the 12th bit set, then +1 for each additional lang

        elem_len = utils.set_u8(len(_elements) + 1)
        video_pid = utils.set_u16(_base_pid + 1 + 57344)  # E000
        stream_type = b'\x02'
        lang_0 = b'\x00\x00\x00'
        msg = stream_type + video_pid + lang_0
        stream_type = b'\x81'
        audio_pid_int = _base_pid + 3 + 57344  # E000 (starts at 0x34 and increments by 1)
        for lang in _elements:
            audio_pid_int += 1
            audio_pid = utils.set_u16(audio_pid_int)
            lang_msg = struct.pack('%ds' % (len(lang)), lang.encode())
            msg += stream_type + audio_pid + lang_msg
        msg = video_pid + elem_len + msg
        length = utils.set_u8(len(msg))
        return ATSC_SERVICE_LOCATION_DESCR_TAG + length + msg
Esempio n. 8
0
    def gen_stt(self):
        # Table 6.1 System Time Table
        # 1byte table_id = 0xcd
        # 1111 static bits
        # 12bits length including crc
        # 2bytes table id extension = 0x0000
        # 2bits = 11 static bits
        # 5bits version_no = 1
        # 1bit current_next_indicator = 1
        # 1byte section_number = 0 (only one section)
        # 1byte last_section_number = 0 (only one section)
        # 1byte protocol_version = 0
        # 4bytes system time = time since 1980 (GPS)
        # 1byte GPS_UTC_offset = 12 (last checked 2021)
        # 2bytes daylight_saving = 0x60
        #  1bit ds_status
        #  2bits 11 static bits
        #  5bits DS day of month
        #  8bits DS hour
        # CRC_32

        #         475f fb17 00cd f011 0000 c100 0000  ..G_............
        # 0x01b0:  4d3c e809 1060 00f3 30ca 76
        # 1295837193
        table_id_ext = b'\x00\x00'
        ver_sect_proto = b'\xc1\x00\x00\x00'

        time_gps = datetime.datetime.utcnow() - datetime.datetime(1980, 1, 6) \
            - datetime.timedelta(seconds=LEAP_SECONDS_2021 - LEAP_SECONDS_1980)
        time_gps_sec = int(time_gps.total_seconds())
        system_time = utils.set_u32(time_gps_sec)
        delta_time = utils.set_u8(LEAP_SECONDS_2021 - LEAP_SECONDS_1980)
        daylight_savings = b'\x60'

        msg = table_id_ext + ver_sect_proto + system_time + \
            delta_time + daylight_savings + b'\x00'
        length = utils.set_u16(len(msg) + 4 + 0xF000)
        msg = MPEG2_PROGRAM_SYSTEM_TIME_TABLE_TAG + length + msg
        crc = self.gen_crc_mpeg(msg)
        msg = self.gen_header(0x1ffb) + msg + crc
        return self.format_video_packets([msg])
Esempio n. 9
0
 def gen_pat(self, _mux_stream):
     # Table Program Association Table : MPEG-2 protocol
     # 1byte table_id = 0x00
     # 1011 static bits
     # 12bits length including crc
     # 2bytes = tsid  (0000 11xx xxxx xxxx)
     # 2bits = 11 static bits
     # 5bits version_no = 1
     # 1bit current_next_indicator = 1
     # 1byte section_number = 0 (only one section)
     # 1byte last_section_number = 0 (only one section)
     # gen_pat_channels()
     # crc
     tsid = _mux_stream['tsid']
     ver_sect = b'\xc3\x00\x00'
     channels_len = utils.set_u8(len(_mux_stream['channels']))
     for i in range(len(_mux_stream['channels'])):
         pid = self.gen_pid(i)
     msg = tsid + ver_sect + self.gen_pat_channels(_mux_stream['channels'])
     length = utils.set_u16(len(msg) + 4 + 0xB000)
     msg = MPEG2_PROGRAM_ASSOCIATION_TABLE_TAG + length + msg
     crc = self.gen_crc_mpeg(msg)
     msg = self.gen_header(0) + msg + crc
     return self.format_video_packets([msg])
Esempio n. 10
0
    def create_getset_response(self, _req_dict, _address):
        (host, port) = _address
        frame_type = utils.set_u16(HDHOMERUN_TYPE_GETSET_RSP)
        name = _req_dict[HDHOMERUN_GETSET_NAME]
        name_str = name.decode('utf-8')
        # if HDHOMERUN_GETSET_VALUE in _req_dict.keys():
        #     value = _req_dict[HDHOMERUN_GETSET_VALUE]
        # else:
        #     value = None

        if name == b'/sys/model':
            # required to id the device
            name_resp = utils.set_u8(HDHOMERUN_GETSET_NAME) + utils.set_str(
                name, True)
            value_resp = utils.set_u8(HDHOMERUN_GETSET_VALUE) + utils.set_str(
                b'hdhomerun4_atsc', True)
            msg_len = utils.set_u16(len(name_resp) + len(value_resp))
            response = frame_type + msg_len + name_resp + value_resp
            x = zlib.crc32(response)
            crc = struct.pack('<I', x)
            response += crc
            return response

        elif name_str.startswith('/tuner'):
            tuner_index = int(name_str[6])
            if name_str.endswith('/lockkey'):
                self.logger.error(
                    'TCP: NOT IMPLEMENTED GETSET LOCKKEY MSG REQUEST: {} '.
                    format(_req_dict))
                response = HDHRServer.gen_err_response(frame_type,
                                                       'lockedErrMsg', [host])
                x = zlib.crc32(response)
                crc = struct.pack('<I', x)
                response += crc
                return response
            elif name_str.endswith('/status'):
                response = None
                tuner_status = None
                for area, area_data in self.tuners.items():
                    if area_data[tuner_index]['status'] == 'Scan':
                        response = HDHRServer.gen_err_response(
                            frame_type, 'scanErrMsg', [host])
                        break
                    else:
                        tuner_status = area_data[tuner_index]['status']
                if response is None:
                    value_resp = utils.set_u8(HDHOMERUN_GETSET_VALUE) \
                                 + utils.set_str(tuner_status_msg[tuner_status], True)
                    name_resp = utils.set_u8(
                        HDHOMERUN_GETSET_NAME) + utils.set_str(name, True)
                    msg_len = utils.set_u16(len(name_resp) + len(value_resp))
                    response = frame_type + msg_len + name_resp + value_resp
                x = zlib.crc32(response)
                crc = struct.pack('<I', x)
                response += crc
                return response

            elif name_str.endswith('/vchannel'):
                tuner_status = self.tuners[tuner_index]['status']
                if tuner_status == 'Stream':
                    value_resp = utils.set_u8(HDHOMERUN_GETSET_VALUE) \
                                 + utils.set_str(self.tuners[tuner_index]['channel'].encode(), True)
                else:
                    value_resp = utils.set_u8(HDHOMERUN_GETSET_VALUE) \
                                 + utils.set_str('none', True)
                name_resp = utils.set_u8(
                    HDHOMERUN_GETSET_NAME) + utils.set_str(name, True)
                msg_len = utils.set_u16(len(name_resp) + len(value_resp))
                response = frame_type + msg_len + name_resp + value_resp
                x = zlib.crc32(response)
                crc = struct.pack('<I', x)
                response += crc
                return response

            else:
                self.logger.error(
                    'TCP: NOT IMPLEMENTED GETSET MSG REQUEST: {} '.format(
                        _req_dict))
                return None
        else:
            self.logger.error(
                'TCP: 3 UNKNOWN GETSET MSG REQUEST: {} '.format(_req_dict))
            return None