Skip to content

Commit 9738cbb

Browse files
committed
Expose loop delay duration
1 parent 81f1487 commit 9738cbb

File tree

2 files changed

+71
-41
lines changed

2 files changed

+71
-41
lines changed

termtosvg/main.py

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,20 @@
1212

1313
logger = logging.getLogger('termtosvg')
1414

15-
USAGE = """termtosvg [output_path] [-c COMMAND] [-g GEOMETRY] [-m MIN_DURATION]
16-
[-M MAX_DURATION] [-s] [-t TEMPLATE] [-h]
15+
DEFAULT_LOOP_DELAY = 1000
16+
17+
USAGE = """termtosvg [output_path] [-c COMMAND] [-D DELAY] [-g GEOMETRY]
18+
[-m MIN_DURATION] [-M MAX_DURATION] [-s] [-t TEMPLATE] [-h]
1719
1820
Record a terminal session and render an SVG animation on the fly
1921
"""
2022
EPILOG = "See also 'termtosvg record --help' and 'termtosvg render --help'"
2123
RECORD_USAGE = "termtosvg record [output_path] [-c COMMAND] [-g GEOMETRY] [-h]"
22-
RENDER_USAGE = """termtosvg render input_file [output_path] [-m MIN_DURATION]
23-
[-M MAX_DURATION] [-s] [-t TEMPLATE] [-h]"""
24+
RENDER_USAGE = """termtosvg render input_file [output_path] [-D DELAY]
25+
[-m MIN_DURATION] [-M MAX_DURATION] [-s] [-t TEMPLATE] [-h]"""
2426

2527

26-
def integral_duration(duration):
28+
def integral_duration_validation(duration):
2729
if duration.lower().endswith('ms'):
2830
duration = duration[:-len('ms')]
2931

@@ -33,7 +35,7 @@ def integral_duration(duration):
3335

3436

3537
def parse(args, templates, default_template, default_geometry, default_min_dur,
36-
default_max_dur, default_cmd):
38+
default_max_dur, default_cmd, default_loop_delay):
3739
"""Parse command line arguments
3840
3941
:param args: Arguments to parse
@@ -47,6 +49,8 @@ def parse(args, templates, default_template, default_geometry, default_min_dur,
4749
:param default_max_dur: Default maximal duration between frames in
4850
milliseconds
4951
:param default_cmd: Default program (with argument list) recorded
52+
:param default_loop_delay: Duration of the pause between two consecutive
53+
loops of the animation in milliseconds
5054
:return: Tuple made of the subcommand called (None, 'render' or 'record')
5155
and all parsed
5256
arguments
@@ -91,7 +95,7 @@ def parse(args, templates, default_template, default_geometry, default_min_dur,
9195
min_duration_parser = argparse.ArgumentParser(add_help=False)
9296
min_duration_parser.add_argument(
9397
'-m', '--min-frame-duration',
94-
type=integral_duration,
98+
type=integral_duration_validation,
9599
metavar='MIN_DURATION',
96100
default=default_min_dur,
97101
help=('minimum duration of a frame in milliseconds (default: {}ms)'
@@ -106,16 +110,29 @@ def parse(args, templates, default_template, default_geometry, default_min_dur,
106110
max_duration_parser = argparse.ArgumentParser(add_help=False)
107111
max_duration_parser.add_argument(
108112
'-M', '--max-frame-duration',
109-
type=integral_duration,
113+
type=integral_duration_validation,
110114
metavar='MAX_DURATION',
111115
default=default_max_dur,
112116
help=('maximum duration of a frame in milliseconds (default: {})'
113117
.format(default_max_dur_label))
114118
)
119+
120+
loop_delay_parser = argparse.ArgumentParser(add_help=False)
121+
loop_delay_parser.add_argument(
122+
'-D', '--loop-delay',
123+
type=integral_duration_validation,
124+
metavar='DELAY',
125+
default=default_loop_delay,
126+
help=(('duration in milliseconds of the pause between two consecutive '
127+
'loops of the animation (default: {}ms)')
128+
.format(default_loop_delay))
129+
)
130+
115131
parser = argparse.ArgumentParser(
116132
prog='termtosvg',
117133
parents=[command_parser, geometry_parser, min_duration_parser,
118-
max_duration_parser, still_frames_parser, template_parser],
134+
max_duration_parser, still_frames_parser, template_parser,
135+
loop_delay_parser],
119136
usage=USAGE,
120137
epilog=EPILOG
121138
)
@@ -148,7 +165,8 @@ def parse(args, templates, default_template, default_geometry, default_min_dur,
148165
parser = argparse.ArgumentParser(
149166
description='render an asciicast recording as an SVG animation',
150167
parents=[template_parser, min_duration_parser,
151-
max_duration_parser, still_frames_parser],
168+
max_duration_parser, still_frames_parser,
169+
loop_delay_parser],
152170
usage=RENDER_USAGE
153171
)
154172
parser.add_argument(
@@ -191,15 +209,15 @@ def record_subcommand(process_args, geometry, input_fileno, output_fileno,
191209

192210

193211
def render_subcommand(still, template, cast_filename, output_path,
194-
min_frame_duration, max_frame_duration):
212+
min_frame_duration, max_frame_duration, loop_delay):
195213
"""Render the animation from an asciicast recording"""
196214
from termtosvg.asciicast import read_records
197215
from termtosvg.term import timed_frames
198216

199217
logger.info('Rendering started')
200218
asciicast_records = read_records(cast_filename)
201219
geometry, frames = timed_frames(asciicast_records, min_frame_duration,
202-
max_frame_duration)
220+
max_frame_duration, loop_delay)
203221
if still:
204222
termtosvg.anim.render_still_frames(frames=frames,
205223
geometry=geometry,
@@ -217,7 +235,8 @@ def render_subcommand(still, template, cast_filename, output_path,
217235

218236
def record_render_subcommand(process_args, still, template, geometry,
219237
input_fileno, output_fileno, output_path,
220-
min_frame_duration, max_frame_duration):
238+
min_frame_duration, max_frame_duration,
239+
loop_delay):
221240
"""Record and render the animation on the fly"""
222241
from termtosvg.term import get_terminal_size, TerminalMode, record, timed_frames
223242

@@ -233,7 +252,7 @@ def record_render_subcommand(process_args, still, template, geometry,
233252
asciicast_records = record(process_args, columns, lines, input_fileno,
234253
output_fileno)
235254
geometry, frames = timed_frames(asciicast_records, min_frame_duration,
236-
max_frame_duration)
255+
max_frame_duration, loop_delay)
237256

238257
if still:
239258
termtosvg.anim.render_still_frames(frames, geometry, output_path,
@@ -266,7 +285,7 @@ def main(args=None, input_fileno=None, output_fileno=None):
266285
default_template = 'gjm8' if 'gjm8' in templates else sorted(templates)[0]
267286
default_cmd = os.environ.get('SHELL', 'sh')
268287
command, args = parse(args[1:], templates, default_template, None, 1,
269-
None, default_cmd)
288+
None, default_cmd, DEFAULT_LOOP_DELAY)
270289

271290
if command == 'record':
272291
if args.output_path is None:
@@ -295,7 +314,7 @@ def main(args=None, input_fileno=None, output_fileno=None):
295314

296315
render_subcommand(args.still_frames, args.template, args.input_file,
297316
output_path, args.min_frame_duration,
298-
args.max_frame_duration)
317+
args.max_frame_duration, args.loop_delay)
299318
else:
300319
if args.output_path is None:
301320
if args.still_frames:
@@ -317,7 +336,8 @@ def main(args=None, input_fileno=None, output_fileno=None):
317336
args.screen_geometry, input_fileno,
318337
output_fileno, output_path,
319338
args.min_frame_duration,
320-
args.max_frame_duration)
339+
args.max_frame_duration,
340+
args.loop_delay)
321341

322342
for handler in logger.handlers:
323343
handler.close()

termtosvg/tests/test_main.py

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class TestMain(unittest.TestCase):
3131
[],
3232
['-c', 'sh'],
3333
['--screen-geometry', '82x19'],
34+
['-D', '1234'],
35+
['--loop-delay', '1234'],
3436
['-g', '82x19'],
3537
['--template', 'plain'],
3638
['-t', 'plain'],
@@ -59,13 +61,16 @@ class TestMain(unittest.TestCase):
5961
def test_parse(self):
6062
for args in self.test_cases:
6163
with self.subTest(case=args):
62-
cmd, parsed_args = termtosvg.main.parse(args=args,
63-
templates={'plain': b''},
64-
default_template='plain',
65-
default_geometry='48x95',
66-
default_min_dur=2,
67-
default_max_dur=None,
68-
default_cmd='sh')
64+
cmd, parsed_args = termtosvg.main.parse(
65+
args=args,
66+
templates={'plain': b''},
67+
default_template='plain',
68+
default_geometry='48x95',
69+
default_min_dur=2,
70+
default_max_dur=None,
71+
default_cmd='sh',
72+
default_loop_delay=1000
73+
)
6974

7075
@staticmethod
7176
def run_main(args, process_input):
@@ -115,8 +120,8 @@ def test_main(self):
115120
args = ['termtosvg', 'render', cast_filename, svg_filename]
116121
TestMain.run_main(args, [])
117122

118-
with self.subTest(case='render (with geometry)'):
119-
args = ['termtosvg', 'render', cast_filename]
123+
with self.subTest(case='render (with delay)'):
124+
args = ['termtosvg', 'render', cast_filename, '-D', '1234']
120125
TestMain.run_main(args, [])
121126

122127
with self.subTest(case='render (with template)'):
@@ -150,20 +155,22 @@ def test_main(self):
150155
args = ['termtosvg', svg_filename, '--template', 'window_frame']
151156
TestMain.run_main(args, SHELL_INPUT)
152157

153-
cast_v1_data = '\r\n'.join(['{',
154-
' "version": 1,',
155-
' "width": 80,',
156-
' "height": 32,',
157-
' "duration": 10,',
158-
' "command": "/bin/zsh",',
159-
' "title": "",',
160-
' "env": {},',
161-
' "stdout": [',
162-
' [0.010303, "\\u001b[1;31mnico \\u001b[0;34m~\\u001b[0m"],',
163-
' [1.136094, "❤ ☀ ☆ ☂ ☻ ♞ ☯ ☭ ☢ € →"],',
164-
' [0.853603, "\\r\\n"]',
165-
' ]',
166-
'}'])
158+
cast_v1_data = '\r\n'.join([
159+
'{',
160+
' "version": 1,',
161+
' "width": 80,',
162+
' "height": 32,',
163+
' "duration": 10,',
164+
' "command": "/bin/zsh",',
165+
' "title": "",',
166+
' "env": {},',
167+
' "stdout": [',
168+
' [0.010303, "\\u001b[1;31mnico \\u001b[0;34m~\\u001b[0m"],',
169+
' [1.136094, "❤ ☀ ☆ ☂ ☻ ♞ ☯ ☭ ☢ € →"],',
170+
' [0.853603, "\\r\\n"]',
171+
' ]',
172+
'}',
173+
])
167174

168175
with self.subTest(case='render v1 cast file'):
169176
_, cast_filename_v1 = tempfile.mkstemp(prefix='termtosvg_', suffix='.cast')
@@ -181,5 +188,8 @@ def test_integral_duration(self):
181188
]
182189
for case in test_cases:
183190
with self.subTest(case=case):
184-
self.assertEqual(termtosvg.main.integral_duration(case), 100)
191+
self.assertEqual(
192+
termtosvg.main.integral_duration_validation(case),
193+
100
194+
)
185195

0 commit comments

Comments
 (0)