def render_subcommand(template, cast_filename, svg_filename): """Render the animation from an asciicast recording""" import termtosvg.asciicast as asciicast import termtosvg.term as term logger.info('Rendering started') asciicast_records = asciicast.read_records(cast_filename) replayed_records = term.replay(records=asciicast_records, from_pyte_char=anim.CharacterCell.from_pyte) anim.render_animation(records=replayed_records, filename=svg_filename, template=template) logger.info('Rendering ended, SVG animation is {}'.format(svg_filename))
def record_render_subcommand(template, geometry, input_fileno, output_fileno, svg_filename): """Record and render the animation on the fly""" import termtosvg.term as term logger.info('Recording started, enter "exit" command or Control-D to end') if geometry is None: columns, lines = term.get_terminal_size(output_fileno) else: columns, lines = geometry with term.TerminalMode(input_fileno): asciicast_records = term.record(columns, lines, input_fileno, output_fileno) replayed_records = term.replay( records=asciicast_records, from_pyte_char=anim.CharacterCell.from_pyte) anim.render_animation(records=replayed_records, filename=svg_filename, template=template) logger.info('Recording ended, SVG animation is {}'.format(svg_filename))
def test_replay(self): theme = AsciiCastV2Theme('#000000', '#FFFFFF', ':'.join(['#123456'] * 16)) with self.subTest(case='One shell command per event'): nbr_records = 5 records = [AsciiCastV2Header(version=2, width=80, height=24, theme=theme)] + \ [AsciiCastV2Event(time=i, event_type='o', event_data='{}\r\n'.format(i).encode('utf-8'), duration=None) for i in range(1, nbr_records)] records = term.replay(records, lambda x: x.data, 50, 1000) # Last blank line is the cursor lines = [str(i) for i in range(nbr_records)] + [' '] for i, record in enumerate(records): # Skip header and cursor line if i == 0: pass else: self.assertEqual(record.line[0], lines[i]) with self.subTest(case='Shell command spread over multiple lines'): records = [AsciiCastV2Header(version=2, width=80, height=24, theme=theme)] + \ [AsciiCastV2Event(time=i * 60, event_type='o', event_data=data.encode('utf-8'), duration=None)
fallback_theme = AsciiCastTheme('#000000', '#000000', ':'.join(['#000000'] * 16)) theme = AsciiCastTheme('#000000', '#FFFFFF', ':'.join(['#123456'] * 16)) with self.subTest(case='One shell command per event'): nbr_records = 5 records = [AsciiCastHeader(version=2, width=80, height=24, theme=theme)] + \ [AsciiCastEvent(time=i, event_type='o', event_data='{}\r\n'.format(i).encode('utf-8'), duration=None) for i in range(1, nbr_records)] records = term.replay(records, pyte_to_str, None, fallback_theme, 50, 1000) # Last blank line is the cursor lines = [str(i) for i in range(nbr_records)] + [' '] for i, record in enumerate(records): # Skip header and cursor line if i == 0: pass else: self.assertEqual(record.line[0], lines[i]) with self.subTest( case='Shell command spread over multiple lines, no theme'): records = [AsciiCastHeader(version=2, width=80, height=24, theme=None)] + \ [AsciiCastEvent(time=i * 60, event_type='o', event_data=data.encode('utf-8'),
def main(args=None, input_fileno=None, output_fileno=None): # type: (List, Union[int, None], Union[int, None]) -> None if args is None: args = sys.argv if input_fileno is None: input_fileno = sys.stdin.fileno() if output_fileno is None: output_fileno = sys.stdout.fileno() console_handler = logging.StreamHandler(sys.stderr) console_handler.setLevel(logging.INFO) console_formatter = logging.Formatter('%(message)s') console_handler.setFormatter(console_formatter) logger.handlers = [console_handler] logger.setLevel(logging.INFO) configuration = config.init_read_conf() available_themes = config.CaseInsensitiveDict(**configuration) del available_themes['global'] command, args = parse(args[1:], available_themes) if args.verbose: _, log_filename = tempfile.mkstemp(prefix='termtosvg_', suffix='.log') file_handler = logging.FileHandler(filename=log_filename, mode='w') file_handler.setLevel(logging.DEBUG) file_formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') file_handler.setFormatter(file_formatter) logger.handlers.append(file_handler) logger.info('Logging to {}'.format(log_filename)) if command == 'record': logger.info( 'Recording started, enter "exit" command or Control-D to end') if args.output_file is None: _, cast_filename = tempfile.mkstemp(prefix='termtosvg_', suffix='.cast') else: cast_filename = args.output_file columns, lines = term.get_terminal_size(output_fileno) with term.TerminalMode(input_fileno): records = term.record(columns, lines, input_fileno, output_fileno) with open(cast_filename, 'w') as cast_file: for record in records: print(record.to_json_line(), file=cast_file) logger.info('Recording ended, cast file is {}'.format(cast_filename)) elif command == 'render': logger.info('Rendering started') if args.output_file is None: _, svg_filename = tempfile.mkstemp(prefix='termtosvg_', suffix='.svg') else: svg_filename = args.output_file if args.font is None: font = configuration['GLOBAL']['font'] else: font = args.font fallback_theme_name = configuration['GLOBAL']['theme'] fallback_theme = configuration[fallback_theme_name] cli_theme = configuration.get(args.theme) records = asciicast.read_records(args.input_file) replayed_records = term.replay( records=records, from_pyte_char=anim.CharacterCell.from_pyte, override_theme=cli_theme, fallback_theme=fallback_theme) anim.render_animation(replayed_records, svg_filename, font) logger.info( 'Rendering ended, SVG animation is {}'.format(svg_filename)) else: # No command passed: record and render on the fly logger.info( 'Recording started, enter "exit" command or Control-D to end') if args.output_file is None: _, svg_filename = tempfile.mkstemp(prefix='termtosvg_', suffix='.svg') else: svg_filename = args.output_file columns, lines = term.get_terminal_size(output_fileno) if args.font is None: font = configuration['GLOBAL']['font'] else: font = args.font fallback_theme_name = configuration['GLOBAL']['theme'] fallback_theme = configuration[fallback_theme_name] cli_theme = configuration.get(args.theme) with term.TerminalMode(input_fileno): records = term.record(columns, lines, input_fileno, output_fileno) replayed_records = term.replay( records=records, from_pyte_char=anim.CharacterCell.from_pyte, override_theme=cli_theme, fallback_theme=fallback_theme) anim.render_animation(replayed_records, svg_filename, font) logger.info( 'Recording ended, SVG animation is {}'.format(svg_filename)) for handler in logger.handlers: handler.close()
def main(args=None, input_fileno=None, output_fileno=None): # type: (List, Union[int, None], Union[int, None]) -> None if args is None: args = sys.argv if input_fileno is None: input_fileno = sys.stdin.fileno() if output_fileno is None: output_fileno = sys.stdout.fileno() command, args = parse(args[1:]) logger = logging.getLogger('termtosvg') logger.setLevel(logging.INFO) console_handler = logging.StreamHandler(sys.stderr) console_handler.setLevel(logging.INFO) console_formatter = logging.Formatter('%(message)s') console_handler.setFormatter(console_formatter) logger.handlers = [console_handler] if args.verbose: file_handler = logging.FileHandler(filename=LOG_FILENAME, mode='w') file_handler.setLevel(logging.DEBUG) file_formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') file_handler.setFormatter(file_formatter) logger.handlers.append(file_handler) logger.info('Logging to {}'.format(LOG_FILENAME)) fallback_theme_name = 'solarized-dark' xresources_str = term.default_themes()[fallback_theme_name] fallback_theme = asciicast.AsciiCastTheme.from_xresources(xresources_str) if command == 'record': logger.info( 'Recording started, enter "exit" command or Control-D to end') if args.output_file is None: _, cast_filename = tempfile.mkstemp(prefix='termtosvg_', suffix='.cast') else: cast_filename = args.output_file columns, lines, theme = term.get_configuration(output_fileno) with term.TerminalMode(input_fileno): records = term.record(columns, lines, theme, input_fileno, output_fileno) with open(cast_filename, 'w') as cast_file: for record in records: print(record.to_json_line(), file=cast_file) logger.info('Recording ended, cast file is {}'.format(cast_filename)) elif command == 'render': def rec_gen(): with open(args.input_file, 'r') as cast_file: for line in cast_file: yield asciicast.AsciiCastRecord.from_json_line(line) logger.info('Rendering started') if args.output_file is None: _, svg_filename = tempfile.mkstemp(prefix='termtosvg_', suffix='.svg') else: svg_filename = args.output_file if args.theme is None: theme = fallback_theme else: xresources_str = term.default_themes()[args.theme] theme = asciicast.AsciiCastTheme.from_xresources(xresources_str) replayed_records = term.replay(rec_gen(), anim.CharacterCell.from_pyte, theme) anim.render_animation(replayed_records, svg_filename) logger.info( 'Rendering ended, SVG animation is {}'.format(svg_filename)) else: # No command passed: record and render on the fly logger.info( 'Recording started, enter "exit" command or Control-D to end') if args.output_file is None: _, svg_filename = tempfile.mkstemp(prefix='termtosvg_', suffix='.svg') else: svg_filename = args.output_file columns, lines, system_theme = term.get_configuration(output_fileno) if args.theme is None: if system_theme is None: theme = fallback_theme else: theme = system_theme else: xresources_str = term.default_themes()[args.theme] theme = asciicast.AsciiCastTheme.from_xresources(xresources_str) with term.TerminalMode(input_fileno): records = term.record(columns, lines, theme, input_fileno, output_fileno) replayed_records = term.replay(records, anim.CharacterCell.from_pyte, theme) anim.render_animation(replayed_records, svg_filename) logger.info( 'Recording ended, SVG animation is {}'.format(svg_filename)) for handler in logger.handlers: handler.close()