Skip to content

Commit f855dc8

Browse files
authored
Add command line usage + some typing (#11)
* Add command line usage * Remove lint action redundant with pre-commit
1 parent 010595f commit f855dc8

File tree

5 files changed

+2669
-38
lines changed

5 files changed

+2669
-38
lines changed

.github/workflows/lint.yml

Lines changed: 0 additions & 13 deletions
This file was deleted.

README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,26 @@ See https://mpl-playback.readthedocs.io/en/latest/gallery/index.html for an exam
99

1010
Directly inspired by https://github.com/matplotlib/matplotlib/issues/19222
1111

12-
# Q: Should you use this?
13-
A: Probably not. I mainly made this so that I could more easily test widget interactions https://github.com/ianhi/mpl-interactions
12+
## Command Line Usage
13+
14+
**recording interactions**
15+
To record a json file for later playback:
16+
```bash
17+
python -m mpl_playback.record example_file.py -figures fig --output example_playback.json
18+
```
19+
20+
This will launch example_file.py and record any interactions with the object named `fig`. Then it will be saved to `example_playback.json`. However, the output argument is optional, if not given then the name will be `example_file-playback.json`
21+
22+
**playback interactions in a gif**
23+
To play back the file you must pass both the original python file and the recording json file. You can optionally pass names for the output gif(s) with the `--output` argument, or allow the names to be chosen automatically. 1 gif will be created for each figure that was recorded.
1424

15-
For one off gifs of interactions it's almost certainly easier to just record your screen to make a gif.
25+
```bash
26+
python -m mpl_playback.playback example_file.py example_playback.json
27+
```
28+
29+
30+
# Q: Should you use this?
31+
A: Depends on what you want. For one off gifs of interactions it's almost certainly easier to just record your screen to make a gif. But if you want integration with `sphinx-gallery` then this is currently the only option.
1632

1733
### Example of a rendered gif:
1834

examples/_multifig-playback.json

Lines changed: 2589 additions & 1 deletion
Large diffs are not rendered by default.

mpl_playback/playback.py

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
from __future__ import annotations
2+
13
import json
4+
from pathlib import Path
5+
from typing import Literal
26
from unittest import mock
37

48
import matplotlib
@@ -71,38 +75,37 @@ def gen_mock_events(events, globals, accessors):
7175
return np.array(times), np.array(mock_events)
7276

7377

74-
def load_events(events):
78+
def load_events(events: Path | str) -> tuple[dict, dict]:
7579
meta = {}
76-
if isinstance(events, str):
77-
with open(events) as f:
78-
loaded = json.load(f)
79-
meta["figures"] = loaded["figures"]
80-
meta["schema-version"] = loaded["schema-version"]
81-
events = loaded["events"]
80+
with open(events) as f:
81+
loaded = json.load(f)
82+
meta["figures"] = loaded["figures"]
83+
meta["schema-version"] = loaded["schema-version"]
84+
events = loaded["events"]
8285
return meta, events
8386

8487

8588
def playback_file(
86-
events,
87-
path,
88-
outputs,
89+
events: Path | str,
90+
path: Path | str,
91+
outputs: str | list[str] | None,
8992
fps=24,
9093
from_first_event=True,
9194
prog_bar=True,
92-
writer="ffmpeg-pillow",
95+
writer: Literal["pillow", "ffmpeg", "imagemagick", "avconv", None] = "pillow",
9396
**kwargs,
9497
):
9598
"""
9699
Parameters
97100
----------
98-
events : str
101+
events : pathlike
99102
Path to the json file defining the events or a dictionary of an
100103
already loaded file.
101-
path : str
104+
path : pathlike
102105
path to the file to be executed.
103106
outputs : str, list of str, or None
104-
The path(s) to the output file(s). If None then the
105-
events will played back but no outputs will saved.
107+
The path(s) to the output file(s). If None then output names will
108+
be automatically generated.
106109
fps : int, default: 24
107110
Frames per second of the output
108111
from_first_event : bool, default: True
@@ -111,14 +114,20 @@ def playback_file(
111114
prog_bar : bool, default: True
112115
Whether to display a progress bar. If tqdm is not
113116
available then this kwarg has no effect.
114-
writer : str, default: 'ffmpeg-pillow'
117+
writer : str, default: 'pillow'
115118
which writer to use. options 'ffmpeg', 'imagemagick', 'avconv', 'pillow'.
116119
If the chosen writer is not available pillow will be used as a fallback.
117120
"""
118-
if isinstance(outputs, str):
119-
outputs = [outputs]
120121
meta, events = load_events(events)
121122
figures = meta["figures"]
123+
if isinstance(outputs, str):
124+
outputs = [outputs]
125+
elif outputs is None:
126+
outputs = []
127+
path = Path(path)
128+
output_base = str(path.name[: -len(path.suffix)])
129+
for i in range(len(figures)):
130+
outputs.append(output_base + f"_{i}.gif")
122131
gbl = exec_no_show(path)
123132
playback_events(
124133
figures,
@@ -143,7 +152,7 @@ def playback_events(
143152
fps=24,
144153
from_first_event=True,
145154
prog_bar=True,
146-
writer="ffmpeg-pillow",
155+
writer: Literal["pillow", "ffmpeg", "imagemagick", "avconv", None] = "pillow",
147156
**kwargs,
148157
):
149158
"""
@@ -203,7 +212,7 @@ def playback_events(
203212
times, mock_events = gen_mock_events(events, globals, accessors)
204213
if from_first_event:
205214
times -= times[0]
206-
N_frames = np.int(times[-1] * fps)
215+
N_frames = int(times[-1] * fps)
207216
# map from frames to events
208217
event_frames = np.round(times * fps)
209218

@@ -250,3 +259,18 @@ def animate(i):
250259
if outputs is not None:
251260
for w in writers:
252261
w.finish()
262+
263+
264+
if __name__ == "__main__":
265+
import argparse
266+
267+
parser = argparse.ArgumentParser()
268+
parser.add_argument("py", type=str, nargs=1)
269+
parser.add_argument("json", type=str, nargs=1)
270+
parser.add_argument("-fps", type=int, default=24)
271+
parser.add_argument("-o", "--output", type=str)
272+
parser.add_argument("--writer", type=str, default="ffmpeg-pillow")
273+
args = parser.parse_args()
274+
playback_file(
275+
args.json[0], args.py[0], args.output, fps=args.fps, writer=args.writer
276+
)

mpl_playback/record.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"record_events",
1414
"record_file",
1515
"record_figure",
16+
"RECORDED_EVENTS",
1617
]
1718
possible_events = [
1819
"button_press_event",
@@ -32,6 +33,10 @@
3233

3334
event_list = []
3435

36+
RECORDED_EVENTS = (
37+
["motion_notify_event", "button_press_event", "button_release_event"],
38+
)
39+
3540

3641
def _find_obj(names, objs, obj, accessors):
3742
"""
@@ -124,7 +129,7 @@ def record_figures(figures, globals, savename, accessors=None):
124129
accessors[_fig] = fig
125130
record_events(
126131
_fig,
127-
["motion_notify_event", "button_press_event", "button_release_event"],
132+
RECORDED_EVENTS,
128133
globals,
129134
accessors,
130135
)
@@ -160,3 +165,14 @@ def record_events(fig, events, globals, accessors=None):
160165
start_time=start_time,
161166
),
162167
)
168+
169+
170+
if __name__ == "__main__":
171+
import argparse
172+
173+
parser = argparse.ArgumentParser()
174+
parser.add_argument("file", type=str, nargs=1)
175+
parser.add_argument("-figs", "--figures", nargs="+", default=["fig"])
176+
parser.add_argument("-o", "--output", type=str)
177+
args = parser.parse_args()
178+
record_file(args.file[0], args.figures, args.output)

0 commit comments

Comments
 (0)