def _print_omitted_bits(self, tell): """ Display the number of bits omitted since the last displayed value up until 'tell'. """ offset = bitstream.to_bit_offset(*tell) last_displayed_offset = bitstream.to_bit_offset(*self._last_displayed_tell) num_bits = offset - last_displayed_offset print(format_omission_line(last_displayed_offset, num_bits))
def _print_error(self, message): """ Print an error message to stderr. """ # Avoid interleaving with stdout (and make causality clearer) sys.stdout.flush() # The 'trailing bits' print out below might move the read position # so record it now. if self._reader is not None: offset = bitstream.to_bit_offset(*self._reader.tell()) # Display the trailing bits from the bitstream if self._verbose >= 1: if self._reader is not None: self._reader.seek(*self._last_tell) bits = self._reader.try_read_bitarray(self._num_trailing_bits_on_error) if len(bits) > 0: sys.stderr.write( "{}\n".format( format_value_line( bitstream.to_bit_offset(*self._last_tell), bitarray(bits), "<next {} bits>".format(len(bits)), ) ) ) # Display the traceback if self._verbose >= 2: if sys.exc_info()[0] is not None: traceback.print_exc() # Display offset/target information prog = os.path.basename(sys.argv[0]) if self._verbose >= 1: if self._reader is not None: sys.stderr.write( "{}: offset: {}\n".format( prog, offset, ) ) if self._serdes is not None: sys.stderr.write( "{}: target: {}\n".format( prog, format_path_summary(self._serdes.path()), ) ) # Display the message message = "{}: error: {}".format(prog, message) sys.stderr.write("{}\n".format(message))
def monitor(des, target, _value): # Dirty: Relies on implementation detail of vc2_fixeddicts... if des.path(target)[4:] in ( ["picture_parse", "wavelet_transform", "padding"], ["fragment_parse", "fragment_header", "fragment_slice_count"], ["fragment_parse", "fragment_header", "fragment_y_offset"], ): slice_start_offset[0] = to_bit_offset(*des.io.tell()) elif target == "qindex": contained_slices[0] = True elif target == "parse_code" and slice_start_offset[0] is not None: if contained_slices[0]: out.append(to_bit_offset(*des.io.tell()) - slice_start_offset[0]) contained_slices[0] = False
def _update_status_line(self, target): """ Update the status line, if a sufficiently long time has ellpased (to avoid spending too long printing to the console). """ now = time.time() if now - self._status_line_updated >= STATUS_LINE_UPDATE_INTERVAL: self._status_line_visible = True self._status_line_updated = now cur_offset = bitstream.to_bit_offset(*self._reader.tell()) cur_path = format_path_summary(self._serdes.path(target)) terminal_width = get_terminal_size()[0] line = "{:0{}d}: <{}>".format( cur_offset, OFFSET_DIGITS, ellipsise_lossy(cur_path, terminal_width - OFFSET_DIGITS - 4), ) # Ensure stdout is fully displayed before printing the status line sys.stdout.flush() sys.stderr.write( ( "\033[2K" # Clear to end of line "\033[s" # Save cursor position "{}" "\033[u" # Restore cursor position ).format(line) ) sys.stderr.flush()
def _print_conformance_error(self, exception, tb): """ Display detailed information from a ConformanceError on stdout. """ terminal_width = get_terminal_size()[0] summary, _, details = wrap_paragraphs( exception.explain()).partition("\n") offending_offset = exception.offending_offset() if offending_offset is None: offending_offset = to_bit_offset(*tell(self._state)) title = "Conformance error at bit offset {}".format(offending_offset) bitstream_viewer_hint = (dedent( exception.bitstream_viewer_hint()).strip().format( cmd="vc2-bitstream-viewer", file=quote(self._filename), offset=offending_offset, )) out = "" out += title + "\n" out += ("=" * len(title)) + "\n" out += "\n" out += wrap_paragraphs(summary, terminal_width) + "\n" out += "\n" out += "\n" out += "Details\n" out += "-------\n" out += "\n" out += wrap_paragraphs(details, terminal_width) + "\n" out += "\n" out += "\n" out += "Suggested bitstream viewer commands\n" out += "-----------------------------------\n" out += "\n" out += bitstream_viewer_hint + "\n" out += "\n" out += "\n" out += "Pseudocode traceback\n" out += "--------------------\n" out += "\n" out += "Most recent call last:\n" out += "\n" out += format_pseudocode_traceback(tb) + "\n" print(out)
def test_generate_exp_golomb_numbers_with_ascending_lengths(sign): for length, number in islice( generate_exp_golomb_with_ascending_lengths(sign), 128, ): # Use a known-good implementation of a signed exp-golmb encoder and # check length is correct. f = BytesIO() w = BitstreamWriter(f) w.write_sint(number) actual_length = to_bit_offset(*w.tell()) assert actual_length == length # Check sign of number if sign < 0: assert number <= 0 else: assert number >= 0
def test_to_and_from_bit_offset(bytes, bits, offset): assert bitstream.to_bit_offset(bytes, bits) == offset assert bitstream.from_bit_offset(offset) == (bytes, bits)
def __call__(self, serdes, target, value): """ Called as the monitoring function of the the :py:class:`MonitoredDeserialiser`. Responsible for printing values from the bitstream and terminating once the region of interest has been parsed. """ last_tell = self._last_tell this_tell = self._reader.tell() self._last_tell = this_tell if self._show_internal_state: # Only show at start of new data unit (NB: strictly speaking this # will display the state was it was after parsing the first field # of each data unit. Since this is a padding field, the state # should still be correct. num_data_units = sum( len(sequence["data_units"]) for sequence in self._serdes.context["sequences"] ) if self._last_num_data_units != num_data_units: self._last_num_data_units = num_data_units self._hide_status_line() self._print_internal_state() this_offset = bitstream.to_bit_offset(*this_tell) enable_display = False enable_display = ( # Within the specified region (NB: we terminate once past it) (self._from_offset == 0 or this_offset > self._from_offset) and # Is the current context selected to be shown ( self._hidden_types is None or self._serdes.cur_context.__class__ not in self._hidden_types ) and ( self._shown_types is None or self._serdes.cur_context.__class__ in self._shown_types ) ) if enable_display: self._hide_status_line() last_offset = bitstream.to_bit_offset(*last_tell) # Print message if we've skipped some bits if self._last_displayed_tell != last_tell: self._print_omitted_bits(last_tell) self._last_displayed_tell = this_tell # Re-read the bits within the value just read and display it self._reader.seek(*last_tell) raw_bits = self._reader.read_bitarray(this_offset - last_offset) self._print_value(last_offset, raw_bits, target, value) else: if self._show_status: self._update_status_line(target) # Stop if we've encountered an invalid parse_info prefix code if self._check_parse_info_prefix: # The test below relies on the only use of the target name # 'parse_info_prefix' being for the parse info prefix. (This should # be the case). if target == "parse_info_prefix" and value != PARSE_INFO_PREFIX: raise BitstreamViewer._TerminateError( "invalid parse_info prefix ({})".format(Hex(8)(value)) ) # Stop when we've reached/passed the end of the region of interest if self._to_offset != 0 and this_offset >= self._to_offset: raise BitstreamViewer._TerminateSuccess() # Save memory by discarding previously deserialised data units current_data_unit = True for sequence in reversed(self._serdes.context["sequences"]): for i in reversed(range(len(sequence["data_units"]))): if not current_data_unit: sequence["data_units"][i] = None current_data_unit = False
def test_happy_cases(self, block_bits, num_values, magnitude): value_sets = { dangle_type: generate_dangling_transform_values( block_bits, num_values, dangle_type, magnitude, ) for dangle_type in DanglingTransformValueType } values_and_bits_beyond_ends = {} for description, values in value_sets.items(): # Should all have required number of values assert len(values) == num_values # Should correctly encode into bounded block f = BytesIO() w = BitstreamWriter(f) w.bounded_block_begin(block_bits) for value in values: w.write_sint(value) # Should completely fill the block length_used = to_bit_offset(*w.tell()) assert length_used == block_bits # Check we actually wrote 'beyond' the end of the block assert w.bits_remaining < 0 # Work out which value and which bits actually first crossed the # end-of-block boundary (we'll later check that these actually # match our expectations later) w.flush() f.seek(0) r = BitstreamReader(f) r.bounded_block_begin(block_bits) value_beyond_end = None bits_beyond_end = None while r.bits_remaining >= 0: value_beyond_end = r.read_sint() bits_beyond_end = -r.bits_remaining values_and_bits_beyond_ends[description] = ( value_beyond_end, bits_beyond_end, ) # Check that the dangling value dangles in the expected way v, b = values_and_bits_beyond_ends[ DanglingTransformValueType.zero_dangling] assert v == 0 assert b == 1 v, b = values_and_bits_beyond_ends[ DanglingTransformValueType.sign_dangling] assert v != 0 assert (-v).bit_length() == magnitude assert b == 1 v, b = values_and_bits_beyond_ends[ DanglingTransformValueType.stop_and_sign_dangling] assert v != 0 assert (-v).bit_length() == magnitude assert b == 2 v, b = values_and_bits_beyond_ends[ DanglingTransformValueType.lsb_stop_and_sign_dangling] assert v != 0 # NB: Larger due to exp-golmb code needing to end in 1 assert (-v).bit_length() == magnitude + 1 assert b == 3