lavavu 1.8.84__cp39-cp39-win_amd64.whl → 1.9.0__cp39-cp39-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lavavu/_LavaVuPython.cp39-win_amd64.pyd +0 -0
- lavavu/html/webview.html +1 -1
- lavavu/lavavu.py +322 -67
- lavavu/osmesa/LavaVuPython.py +561 -0
- lavavu/osmesa/__init__.py +0 -0
- lavavu/shaders/default.frag +0 -6
- lavavu/shaders/default.vert +4 -4
- lavavu/shaders/fontShader.frag +0 -5
- lavavu/shaders/lineShader.frag +0 -4
- lavavu/shaders/lineShader.vert +0 -2
- lavavu/shaders/pointShader.frag +0 -5
- lavavu/shaders/pointShader.vert +0 -4
- lavavu/shaders/triShader.frag +0 -17
- lavavu/shaders/triShader.vert +0 -4
- lavavu/shaders/volumeShader.frag +0 -63
- {lavavu-1.8.84.dist-info → lavavu-1.9.0.dist-info}/METADATA +5 -4
- {lavavu-1.8.84.dist-info → lavavu-1.9.0.dist-info}/RECORD +21 -19
- {lavavu-1.8.84.dist-info → lavavu-1.9.0.dist-info}/WHEEL +1 -1
- {lavavu-1.8.84.dist-info → lavavu-1.9.0.dist-info}/LICENSE.md +0 -0
- {lavavu-1.8.84.dist-info → lavavu-1.9.0.dist-info}/entry_points.txt +0 -0
- {lavavu-1.8.84.dist-info → lavavu-1.9.0.dist-info}/top_level.txt +0 -0
Binary file
|
lavavu/html/webview.html
CHANGED
@@ -27,7 +27,7 @@
|
|
27
27
|
|
28
28
|
<input id="fileinput" type="file" style="visibility:hidden" onchange="useFileInput(this)" />
|
29
29
|
|
30
|
-
<script async src="https://cdn.jsdelivr.net/gh/lavavu/lavavu.github.io@1.
|
30
|
+
<script async src="https://cdn.jsdelivr.net/gh/lavavu/lavavu.github.io@1.9.0/LavaVu-amalgamated.min.js"></script>
|
31
31
|
<!--script src="dat.gui.min.js"></script>
|
32
32
|
<script src="OK-min.js"></script>
|
33
33
|
|
lavavu/lavavu.py
CHANGED
@@ -14,7 +14,8 @@ See the :any:`lavavu.Viewer` class documentation for more information.
|
|
14
14
|
"""
|
15
15
|
|
16
16
|
__all__ = ['Viewer', 'Object', 'Properties', 'ColourMap', 'DrawData', 'Figure', 'Geometry', 'Image', 'Video',
|
17
|
-
'download', 'grid2d', 'grid3d', 'cubehelix', 'loadCPT', 'matplotlib_colourmap', 'printH5',
|
17
|
+
'download', 'grid2d', 'grid3d', 'cubehelix', 'loadCPT', 'matplotlib_colourmap', 'printH5',
|
18
|
+
'lerp', 'vector_magnitude', 'vector_normalise', 'vector_align',
|
18
19
|
'player', 'inject', 'hidecode', 'style', 'cellstyle', 'cellwidth',
|
19
20
|
'version', 'settings', 'is_ipython', 'is_notebook', 'getname']
|
20
21
|
|
@@ -50,6 +51,8 @@ from collections import deque
|
|
50
51
|
import time
|
51
52
|
import weakref
|
52
53
|
import asyncio
|
54
|
+
import quaternion as quat
|
55
|
+
import platform
|
53
56
|
|
54
57
|
if sys.version_info[0] < 3:
|
55
58
|
print("Python 3 required. LavaVu no longer supports Python 2.7.")
|
@@ -73,8 +76,38 @@ try:
|
|
73
76
|
except (ImportError) as e:
|
74
77
|
moderngl_window = None
|
75
78
|
|
79
|
+
try:
|
80
|
+
import av
|
81
|
+
except (ImportError) as e:
|
82
|
+
av = None
|
83
|
+
|
76
84
|
#import swig module
|
77
|
-
|
85
|
+
context = os.environ.get("LV_CONTEXT", "").strip()
|
86
|
+
if platform.system() == 'Linux':
|
87
|
+
#Default context requires DISPLAY set for X11
|
88
|
+
display = os.environ.get("DISPLAY", "").strip()
|
89
|
+
if len(context) == 0: context = 'default'
|
90
|
+
if context != 'default' or len(display) == 0:
|
91
|
+
if context != 'osmesa':
|
92
|
+
try:
|
93
|
+
#Try EGL via moderngl, will use headless GPU if available
|
94
|
+
testctx = moderngl.create_context(standalone=True, require=330, backend='egl')
|
95
|
+
context = 'moderngl'
|
96
|
+
except Exception as e:
|
97
|
+
context = 'osmesa'
|
98
|
+
|
99
|
+
if context == 'osmesa':
|
100
|
+
#OSMesa fallback, CPU only, multicore
|
101
|
+
from osmesa import LavaVuPython
|
102
|
+
|
103
|
+
#Default module if none already loaded
|
104
|
+
try:
|
105
|
+
LavaVuPython
|
106
|
+
except:
|
107
|
+
import LavaVuPython
|
108
|
+
|
109
|
+
os.environ['LV_CONTEXT'] = context
|
110
|
+
|
78
111
|
version = LavaVuPython.version
|
79
112
|
server_ports = []
|
80
113
|
|
@@ -2052,13 +2085,15 @@ class _LavaVuWrapper(LavaVuPython.LavaVu):
|
|
2052
2085
|
#Shared context
|
2053
2086
|
_ctx = None
|
2054
2087
|
|
2055
|
-
def __init__(self, threaded, runargs, resolution=None, binpath=None, context=
|
2088
|
+
def __init__(self, threaded, runargs, resolution=None, binpath=None, context=None):
|
2056
2089
|
self.args = runargs
|
2057
2090
|
self._closing = False
|
2058
2091
|
self.resolution = resolution
|
2059
2092
|
self._loop = None
|
2060
2093
|
|
2061
2094
|
#OpenGL context creation options
|
2095
|
+
if context is None:
|
2096
|
+
context = os.environ.get("LV_CONTEXT", "default")
|
2062
2097
|
havecontext = False
|
2063
2098
|
self.use_moderngl = False
|
2064
2099
|
self.use_moderngl_window = False
|
@@ -2319,11 +2354,10 @@ class _LavaVuWrapper(LavaVuPython.LavaVu):
|
|
2319
2354
|
if _LavaVuWrapper._ctx:
|
2320
2355
|
self.ctx = _LavaVuWrapper._ctx
|
2321
2356
|
else:
|
2322
|
-
import platform
|
2323
2357
|
if platform.system() == 'Linux':
|
2324
2358
|
self.ctx = moderngl.create_context(standalone=True, require=330, backend='egl')
|
2325
2359
|
else:
|
2326
|
-
self.ctx = moderngl.
|
2360
|
+
self.ctx = moderngl.create_context(standalone=True, require=330)
|
2327
2361
|
#print(self.ctx.info)
|
2328
2362
|
_LavaVuWrapper._ctx = self.ctx
|
2329
2363
|
|
@@ -2716,6 +2750,7 @@ class Viewer(dict):
|
|
2716
2750
|
context : str
|
2717
2751
|
OpenGL context type, *"default"* will create a context and window based on available configurations.
|
2718
2752
|
*"provided"* specifies a user provided context, set this if you have already created and activated the context.
|
2753
|
+
*"osmesa"* if built with the osmesa module, will use the self-contained software renderer to render without a window or GPU, needs to be set in LV_CONTEXT to work
|
2719
2754
|
*"moderngl"* creates a context in python using the moderngl module (experimental).
|
2720
2755
|
*"moderngl."* creates a context and a window in python using the moderngl module (experimental) specify class after separator, eg: moderngl.headless, moderngl.pyglet, moderngl.pyqt5.
|
2721
2756
|
port : int
|
@@ -2735,6 +2770,8 @@ class Viewer(dict):
|
|
2735
2770
|
self._thread = None
|
2736
2771
|
self._collections = {}
|
2737
2772
|
self.validate = True #Property validation flag
|
2773
|
+
self.recording = None
|
2774
|
+
self.context = os.environ.get('LV_CONTEXT', context)
|
2738
2775
|
|
2739
2776
|
#Exit handler to clean up threads
|
2740
2777
|
#(__del__ does not always seem to get called on termination)
|
@@ -2775,7 +2812,7 @@ class Viewer(dict):
|
|
2775
2812
|
if not binpath:
|
2776
2813
|
binpath = os.path.abspath(os.path.dirname(__file__))
|
2777
2814
|
try:
|
2778
|
-
self.app = _LavaVuWrapper(threads, self.args(*args, **kwargs), resolution, binpath, context)
|
2815
|
+
self.app = _LavaVuWrapper(threads, self.args(*args, **kwargs), resolution, binpath, self.context)
|
2779
2816
|
except (RuntimeError) as e:
|
2780
2817
|
print("LavaVu Init error: " + str(e))
|
2781
2818
|
pass
|
@@ -3821,6 +3858,9 @@ class Viewer(dict):
|
|
3821
3858
|
Render a new frame, explicit display update
|
3822
3859
|
"""
|
3823
3860
|
self.app.render()
|
3861
|
+
#Video recording?
|
3862
|
+
if self.recording:
|
3863
|
+
self.recording.frame()
|
3824
3864
|
|
3825
3865
|
def init(self):
|
3826
3866
|
"""
|
@@ -4128,7 +4168,7 @@ class Viewer(dict):
|
|
4128
4168
|
from IPython.display import display,HTML,Javascript
|
4129
4169
|
display(Javascript(js + code))
|
4130
4170
|
|
4131
|
-
def video(self, filename="", fps=30, quality=
|
4171
|
+
def video(self, filename="", resolution=(0,0), fps=30, quality=0, encoder="h264", player=None, options={}, **kwargs):
|
4132
4172
|
"""
|
4133
4173
|
Record and show the generated video inline within an ipython notebook.
|
4134
4174
|
|
@@ -4145,25 +4185,36 @@ class Viewer(dict):
|
|
4145
4185
|
----------
|
4146
4186
|
filename : str
|
4147
4187
|
Name of the file to save, if not provided a default will be used
|
4188
|
+
resolution : list or tuple
|
4189
|
+
Video resolution in pixels [x,y]
|
4148
4190
|
fps : int
|
4149
4191
|
Frames to output per second of video
|
4150
4192
|
quality : int
|
4151
|
-
Encoding quality, 1=low
|
4193
|
+
Encoding quality, 1=low, 2=medium, 3=high, higher quality reduces
|
4152
4194
|
encoding artifacts at cost of larger file size
|
4153
|
-
|
4154
|
-
|
4195
|
+
If omitted will use default settings, can fine tune settings in kwargs
|
4196
|
+
encoder : str
|
4197
|
+
Name of encoder to use, eg: "h264" (default), "mpeg"
|
4198
|
+
player : dict
|
4199
|
+
Args to pass to the player when the video is finished, eg:
|
4200
|
+
{"width" : 800, "height", 400, "params": "controls autoplay"}
|
4201
|
+
options : dict
|
4202
|
+
Args to pass to the encoder, eg: see
|
4203
|
+
https://trac.ffmpeg.org/wiki/Encode/H.264
|
4155
4204
|
**kwargs :
|
4156
|
-
Any additional keyword args will be passed to
|
4205
|
+
Any additional keyword args will also be passed as options to the encoder
|
4157
4206
|
|
4158
4207
|
Returns
|
4159
4208
|
-------
|
4160
4209
|
recorder : Video(object)
|
4161
4210
|
Context manager object that controls the video recording
|
4162
4211
|
"""
|
4163
|
-
return Video(self, filename, resolution, fps, quality, **kwargs)
|
4212
|
+
return Video(self, filename, resolution, fps, quality, encoder, player, options, **kwargs)
|
4164
4213
|
|
4165
4214
|
def video_steps(self, filename="", start=0, end=0, fps=10, quality=1, resolution=(0,0), **kwargs):
|
4166
4215
|
"""
|
4216
|
+
TODO: Fix to use pyAV
|
4217
|
+
|
4167
4218
|
Record a video of the model by looping through all time steps
|
4168
4219
|
|
4169
4220
|
Shows the generated video inline within an ipython notebook.
|
@@ -4378,7 +4429,7 @@ class Viewer(dict):
|
|
4378
4429
|
#Issue redisplay to active viewer
|
4379
4430
|
self.control.redisplay()
|
4380
4431
|
|
4381
|
-
def camera(self, data=None):
|
4432
|
+
def camera(self, data=None, quiet=False):
|
4382
4433
|
"""
|
4383
4434
|
Get/set the current camera viewpoint
|
4384
4435
|
|
@@ -4389,6 +4440,8 @@ class Viewer(dict):
|
|
4389
4440
|
----------
|
4390
4441
|
data : dict
|
4391
4442
|
Camera view to apply if any
|
4443
|
+
quiet : bool
|
4444
|
+
Skip print and calling "camera" script command which produces noisy terminal output
|
4392
4445
|
|
4393
4446
|
Returns
|
4394
4447
|
-------
|
@@ -4399,7 +4452,8 @@ class Viewer(dict):
|
|
4399
4452
|
me = getname(self)
|
4400
4453
|
if not me: me = "lv"
|
4401
4454
|
#Also print in terminal for debugging
|
4402
|
-
|
4455
|
+
if not quiet and not data:
|
4456
|
+
self.commands("camera")
|
4403
4457
|
#Get: export from first view
|
4404
4458
|
vdat = {}
|
4405
4459
|
if len(self.state["views"]) and self.state["views"][0]:
|
@@ -4417,8 +4471,9 @@ class Viewer(dict):
|
|
4417
4471
|
|
4418
4472
|
copyview(vdat, self.state["views"][0])
|
4419
4473
|
|
4420
|
-
|
4421
|
-
|
4474
|
+
if not quiet and not data:
|
4475
|
+
print(me + ".translation(" + str(vdat["translate"])[1:-1] + ")")
|
4476
|
+
print(me + ".rotation(" + str(vdat["xyzrotate"])[1:-1] + ")")
|
4422
4477
|
|
4423
4478
|
#Set
|
4424
4479
|
if data is not None:
|
@@ -4428,6 +4483,88 @@ class Viewer(dict):
|
|
4428
4483
|
#Return
|
4429
4484
|
return vdat
|
4430
4485
|
|
4486
|
+
def lookat(self, pos, at=None, up=None):
|
4487
|
+
"""
|
4488
|
+
Set the camera with a position coord and lookat coord
|
4489
|
+
|
4490
|
+
Parameters
|
4491
|
+
----------
|
4492
|
+
pos : list/numpy.ndarray
|
4493
|
+
Camera position in world coords
|
4494
|
+
lookat : list/numpy.ndarray
|
4495
|
+
Look at position in world coords, defaults to model origin
|
4496
|
+
up : list/numpy.ndarray
|
4497
|
+
Up vector, defaults to Y axis [0,1,0]
|
4498
|
+
"""
|
4499
|
+
|
4500
|
+
# Use the origin from viewer if no target provided
|
4501
|
+
if at is None:
|
4502
|
+
at = self["focus"]
|
4503
|
+
else:
|
4504
|
+
self["focus"] = at
|
4505
|
+
|
4506
|
+
# Default to Y-axis up vector
|
4507
|
+
if up is None:
|
4508
|
+
up = numpy.array([0, 1, 0])
|
4509
|
+
|
4510
|
+
# Calculate the rotation matrix
|
4511
|
+
heading = numpy.array(pos) - numpy.array(at)
|
4512
|
+
zd = vector_normalise(heading)
|
4513
|
+
xd = vector_normalise(numpy.cross(up, zd))
|
4514
|
+
yd = vector_normalise(numpy.cross(zd, xd))
|
4515
|
+
q = quat.from_rotation_matrix(numpy.array([xd, yd, zd]))
|
4516
|
+
q = q.normalized()
|
4517
|
+
|
4518
|
+
# Apply the rotation
|
4519
|
+
self.rotation(q.x, q.y, q.z, q.w)
|
4520
|
+
|
4521
|
+
# Translate back by heading vector length in Z
|
4522
|
+
# (model origin in lavavu takes care of lookat offset)
|
4523
|
+
tr = [0, 0, -vector_magnitude(numpy.array(pos) - numpy.array(at))]
|
4524
|
+
|
4525
|
+
# Apply translation
|
4526
|
+
self.translation(tr)
|
4527
|
+
|
4528
|
+
def camlerpto(self, pos, L):
|
4529
|
+
# Lerp using current camera orientation as start point
|
4530
|
+
pos0 = self.camera(quiet=True)
|
4531
|
+
return self.camlerp(pos0, pos)
|
4532
|
+
|
4533
|
+
def camlerp(self, pos0, pos1, L):
|
4534
|
+
"""
|
4535
|
+
Linearly Interpolate between two camera positions/orientations and
|
4536
|
+
set the camera to the resulting position/orientation
|
4537
|
+
"""
|
4538
|
+
final = {}
|
4539
|
+
for key in ["translate", "rotate", "focus"]:
|
4540
|
+
val0 = numpy.array(pos0[key])
|
4541
|
+
val1 = numpy.array(pos1[key])
|
4542
|
+
res = val0 + (val1 - val0) * L
|
4543
|
+
if len(res) > 3:
|
4544
|
+
# Normalise quaternion
|
4545
|
+
res = res / numpy.linalg.norm(res)
|
4546
|
+
final[key] = res.tolist()
|
4547
|
+
|
4548
|
+
self.camera(final)
|
4549
|
+
|
4550
|
+
def flyto(self, pos, steps, stop=False, callback=None):
|
4551
|
+
# Fly using current camera orientation as start point
|
4552
|
+
pos0 = self.camera(quiet=True)
|
4553
|
+
return self.fly(pos0, pos, steps, stop, callback)
|
4554
|
+
|
4555
|
+
def fly(self, pos0, pos1, steps, stop=False, callback=None):
|
4556
|
+
self.camera(pos0)
|
4557
|
+
self.render()
|
4558
|
+
|
4559
|
+
for i in range(steps):
|
4560
|
+
if stop and i > stop:
|
4561
|
+
break
|
4562
|
+
L = i / (steps - 1)
|
4563
|
+
self.camlerp(pos0, pos1, L)
|
4564
|
+
if callback is not None:
|
4565
|
+
callback(self, i, steps)
|
4566
|
+
self.render()
|
4567
|
+
|
4431
4568
|
def getview(self):
|
4432
4569
|
"""
|
4433
4570
|
Get current view settings
|
@@ -5268,11 +5405,9 @@ def player(filename, params="controls autoplay loop", **kwargs):
|
|
5268
5405
|
return filename
|
5269
5406
|
''';
|
5270
5407
|
|
5271
|
-
import uuid
|
5272
|
-
uid = uuid.uuid1()
|
5273
|
-
|
5274
5408
|
#Embed player
|
5275
|
-
|
5409
|
+
filename = os.path.relpath(filename)
|
5410
|
+
display(Video(url=filename, html_attributes=f"id='{vid}' " + params, **kwargs))
|
5276
5411
|
|
5277
5412
|
#Add download link
|
5278
5413
|
display(HTML(f'<a id="link_{vid}" href="{filename}" download>Download Video</a>'))
|
@@ -5304,6 +5439,8 @@ class Video(object):
|
|
5304
5439
|
"""
|
5305
5440
|
The Video class provides an interface to record animations
|
5306
5441
|
|
5442
|
+
This now uses pyAV avoiding the need to build LavaVu with ffmpeg support
|
5443
|
+
|
5307
5444
|
Example
|
5308
5445
|
-------
|
5309
5446
|
|
@@ -5315,7 +5452,7 @@ class Video(object):
|
|
5315
5452
|
... lv.rotate('y', 10) # doctest: +SKIP
|
5316
5453
|
... lv.render() # doctest: +SKIP
|
5317
5454
|
"""
|
5318
|
-
def __init__(self, viewer=None, filename="", resolution=(0,0), framerate=30, quality=
|
5455
|
+
def __init__(self, viewer=None, filename="", resolution=(0,0), framerate=30, quality=0, encoder="h264", player=None, options={}, **kwargs):
|
5319
5456
|
"""
|
5320
5457
|
Record and show the generated video inline within an ipython notebook.
|
5321
5458
|
|
@@ -5335,79 +5472,141 @@ class Video(object):
|
|
5335
5472
|
fps : int
|
5336
5473
|
Frames to output per second of video
|
5337
5474
|
quality : int
|
5338
|
-
Encoding quality, 1=low
|
5475
|
+
Encoding quality, 1=low, 2=medium, 3=high, higher quality reduces
|
5339
5476
|
encoding artifacts at cost of larger file size
|
5477
|
+
If omitted will use default settings, can fine tune settings in kwargs
|
5340
5478
|
resolution : list or tuple
|
5341
5479
|
Video resolution in pixels [x,y]
|
5480
|
+
encoder : str
|
5481
|
+
Name of encoder to use, eg: "h264" (default), "mpeg"
|
5482
|
+
player : dict
|
5483
|
+
Args to pass to the player when the video is finished, eg:
|
5484
|
+
{"width" : 800, "height", 400, "params": "controls autoplay"}
|
5485
|
+
options : dict
|
5486
|
+
Args to pass to the encoder, eg: see
|
5487
|
+
https://trac.ffmpeg.org/wiki/Encode/H.264
|
5342
5488
|
**kwargs :
|
5343
|
-
Any additional keyword args will be passed to
|
5489
|
+
Any additional keyword args will also be passed as options to the encoder
|
5344
5490
|
"""
|
5491
|
+
if av is None:
|
5492
|
+
raise(ImportError("Video output not supported without pyAV - pip install av"))
|
5493
|
+
return
|
5345
5494
|
self.resolution = resolution
|
5495
|
+
if self.resolution[0] == 0:
|
5496
|
+
self.resolution = viewer.width
|
5497
|
+
if self.resolution[1] == 0:
|
5498
|
+
self.resolution = viewer.height
|
5346
5499
|
self.framerate = framerate
|
5347
5500
|
self.quality = quality
|
5348
5501
|
self.viewer = viewer
|
5349
5502
|
self.filename = filename
|
5350
|
-
self.
|
5351
|
-
|
5352
|
-
|
5353
|
-
|
5354
|
-
|
5503
|
+
if len(self.filename) == 0:
|
5504
|
+
self.filename = "lavavu.mp4"
|
5505
|
+
self.player = player
|
5506
|
+
if self.player is None:
|
5507
|
+
#Default player is half output resolution
|
5508
|
+
self.player = {"width": self.resolution[0] // 2, "height": self.resolution[1] // 2}
|
5509
|
+
self.encoder = encoder
|
5510
|
+
self.options = options
|
5511
|
+
#Also include extra args
|
5512
|
+
options.update(kwargs)
|
5513
|
+
self.container = None
|
5355
5514
|
|
5356
5515
|
def start(self):
|
5357
5516
|
"""
|
5358
5517
|
Start recording, all rendered frames will be added to the video
|
5359
5518
|
"""
|
5360
|
-
|
5361
|
-
|
5362
|
-
|
5363
|
-
|
5364
|
-
|
5365
|
-
|
5366
|
-
|
5367
|
-
|
5368
|
-
|
5369
|
-
|
5519
|
+
#https://trac.ffmpeg.org/wiki/Encode/H.264
|
5520
|
+
# The range of the CRF scale is 0–51, where 0 is lossless (for 8 bit only, for 10 bit use -qp 0),
|
5521
|
+
# 23 is the default, and 51 is worst quality possible
|
5522
|
+
#Compression level, lower = high quality
|
5523
|
+
#See also: https://github.com/PyAV-Org/PyAV/blob/main/tests/test_encode.py
|
5524
|
+
options = {}
|
5525
|
+
if self.encoder == 'h264' or self.encoder == 'libx265':
|
5526
|
+
#Only have default options for h264/265 for now
|
5527
|
+
if self.quality == 1:
|
5528
|
+
options['qmin'] = '30' #'20' #'8'
|
5529
|
+
options['qmax'] = '35' #'41'
|
5530
|
+
options['crf'] = '30' #'40'
|
5531
|
+
elif self.quality == 2:
|
5532
|
+
options['qmin'] = '25' #'2'
|
5533
|
+
options['qmax'] = '30' #'31'
|
5534
|
+
options['crf'] = '20' #'23'
|
5535
|
+
elif self.quality == 3:
|
5536
|
+
options['qmin'] = '20' #'1'
|
5537
|
+
options['qmax'] = '25' #'4'
|
5538
|
+
options['crf'] = '10' #'10'
|
5539
|
+
|
5540
|
+
#Default preset, tune for h264
|
5541
|
+
options["preset"] = 'veryfast' #'veryfast' 'medium' 'slow'
|
5542
|
+
options["tune"] = 'animation' #'film'
|
5543
|
+
|
5544
|
+
#Settings from our original c++ encoder
|
5545
|
+
#self.options['i_quant_factor'] = '0.71'
|
5546
|
+
#self.options['qcompress'] = '0.6'
|
5547
|
+
#self.options['max_qdiff'] = '4'
|
5548
|
+
#self.options['refs'] = '3'
|
5549
|
+
|
5550
|
+
#Merge user options, allowing override of above settings
|
5551
|
+
options.update(self.options)
|
5552
|
+
|
5553
|
+
#print(options)
|
5554
|
+
self.container = av.open(self.filename, mode="w")
|
5555
|
+
self.stream = self.container.add_stream(self.encoder, rate=self.framerate, options=options)
|
5556
|
+
self.stream.width = self.resolution[0]
|
5557
|
+
self.stream.height = self.resolution[1]
|
5558
|
+
self.stream.pix_fmt = "yuv420p"
|
5559
|
+
self.viewer.recording = self
|
5560
|
+
|
5561
|
+
stream = self.stream
|
5562
|
+
#print(stream)
|
5563
|
+
#print(stream.codec)
|
5564
|
+
#print(stream.codec_context)
|
5565
|
+
#print(stream.profiles)
|
5566
|
+
#print(stream.profile)
|
5567
|
+
#print(stream.options)
|
5568
|
+
#Need to set profile here or it isn't applied
|
5569
|
+
#(Default to main)
|
5570
|
+
stream.profile = 'Main' #Baseline / High
|
5571
|
+
cc = stream.codec_context
|
5572
|
+
#print(cc.options)
|
5573
|
+
#print(cc.profile)
|
5574
|
+
|
5575
|
+
def frame(self):
|
5576
|
+
"""
|
5577
|
+
Write a frame, called when viewer.render() is called
|
5578
|
+
while a recording is in progress
|
5579
|
+
"""
|
5580
|
+
img = self.viewer.rawimage(resolution=self.resolution, channels=3)
|
5581
|
+
frame = av.VideoFrame.from_ndarray(img.data, format="rgb24")
|
5582
|
+
for packet in self.stream.encode(frame):
|
5583
|
+
self.container.mux(packet)
|
5370
5584
|
|
5371
5585
|
def pause(self):
|
5372
5586
|
"""
|
5373
5587
|
Pause/resume recording, no rendered frames will be added to the video while paused
|
5374
5588
|
"""
|
5375
|
-
if self.
|
5376
|
-
self.
|
5589
|
+
if self.viewer.recording:
|
5590
|
+
self.viewer.recording = None
|
5377
5591
|
else:
|
5378
|
-
self.viewer.
|
5592
|
+
self.viewer.recording = self
|
5379
5593
|
|
5380
5594
|
def stop(self):
|
5381
5595
|
"""
|
5382
5596
|
Stop recording, final frames will be written and file closed, ready to play.
|
5383
5597
|
No further frames will be added to the video
|
5384
5598
|
"""
|
5385
|
-
|
5386
|
-
|
5387
|
-
self.
|
5388
|
-
|
5389
|
-
|
5390
|
-
|
5391
|
-
|
5392
|
-
if os.path.isdir(self.filename):
|
5393
|
-
log = ""
|
5394
|
-
ffmpeg_exe = 'ffmpeg'
|
5395
|
-
try:
|
5396
|
-
outfn = '{0}.mp4'.format(self.filename)
|
5397
|
-
cmd = ffmpeg_exe + ' -r {1} -i "{0}/frame_%05d.jpg" -c:v libx264 -vf fps={1} -movflags +faststart -pix_fmt yuv420p -y "{2}"'.format(self.filename, self.framerate, outfn)
|
5398
|
-
log += "No built in video encoding, attempting to build movie from frames with ffmpeg:\n"
|
5399
|
-
log += cmd
|
5400
|
-
|
5401
|
-
import subprocess
|
5402
|
-
#os.system(cmd)
|
5403
|
-
subprocess.check_call(cmd, shell=True)
|
5404
|
-
self.filename = outfn
|
5405
|
-
except (Exception) as e:
|
5406
|
-
print("Video encoding failed: ", str(e), "\nlog:\n", log)
|
5599
|
+
# Flush stream
|
5600
|
+
for packet in self.stream.encode():
|
5601
|
+
self.container.mux(packet)
|
5602
|
+
# Close the file
|
5603
|
+
self.container.close()
|
5604
|
+
self.container = None
|
5605
|
+
self.stream = None
|
5407
5606
|
|
5408
5607
|
def write(self, image):
|
5409
5608
|
"""
|
5410
|
-
Add a frame to the video
|
5609
|
+
Add a frame to the video
|
5411
5610
|
|
5412
5611
|
Parameters
|
5413
5612
|
----------
|
@@ -5415,7 +5614,7 @@ class Video(object):
|
|
5415
5614
|
Pass a list or numpy uint8 array of rgba values or an Image object
|
5416
5615
|
values are loaded as 8 bit unsigned integer values
|
5417
5616
|
"""
|
5418
|
-
if self.
|
5617
|
+
if self.container:
|
5419
5618
|
if isinstance(image, Image):
|
5420
5619
|
image = image.data
|
5421
5620
|
else:
|
@@ -5423,13 +5622,18 @@ class Video(object):
|
|
5423
5622
|
if image.size == self.resolution[0] * self.resolution[1] * 4:
|
5424
5623
|
image = image.reshape(self.resolution[0], self.resolution[1], 4)
|
5425
5624
|
image = image[::,::,:3] #Remove alpha channel
|
5426
|
-
self.encoder.copyframe(image.ravel())
|
5625
|
+
#self.encoder.copyframe(image.ravel())
|
5626
|
+
|
5627
|
+
frame = av.VideoFrame.from_ndarray(image, format="rgb24")
|
5628
|
+
for packet in self.stream.encode(frame):
|
5629
|
+
self.container.mux(packet)
|
5427
5630
|
|
5428
5631
|
def play(self):
|
5429
5632
|
"""
|
5430
5633
|
Show the video in an inline player if in an interative notebook
|
5431
5634
|
"""
|
5432
|
-
|
5635
|
+
print(self.player)
|
5636
|
+
player(self.filename, **self.player)
|
5433
5637
|
|
5434
5638
|
def __enter__(self):
|
5435
5639
|
self.start()
|
@@ -5848,6 +6052,56 @@ def lerp(first, second, mu):
|
|
5848
6052
|
final[i] += diff * mu
|
5849
6053
|
return final
|
5850
6054
|
|
6055
|
+
def vector_magnitude(vec):
|
6056
|
+
return numpy.linalg.norm(vec)
|
6057
|
+
|
6058
|
+
def vector_normalise(vec):
|
6059
|
+
norm = numpy.linalg.norm(vec)
|
6060
|
+
if norm == 0:
|
6061
|
+
vn = vec
|
6062
|
+
else:
|
6063
|
+
vn = vec / norm
|
6064
|
+
return vn
|
6065
|
+
|
6066
|
+
def vector_align(v1, v2, lvformat=True):
|
6067
|
+
"""
|
6068
|
+
Get a rotation quaterion to align vectors v1 with v2
|
6069
|
+
|
6070
|
+
Parameters
|
6071
|
+
----------
|
6072
|
+
v1 : list/numpy.ndarray
|
6073
|
+
First 3 component vector
|
6074
|
+
v2 : list/numpy.ndarray
|
6075
|
+
Second 3 component vector to align the first to
|
6076
|
+
|
6077
|
+
Returns
|
6078
|
+
-------
|
6079
|
+
list: quaternion to rotate v1 to v2 (in lavavu format)
|
6080
|
+
"""
|
6081
|
+
|
6082
|
+
# Check for parallel or opposite
|
6083
|
+
v1 = vector_normalise(numpy.array(v1))
|
6084
|
+
v2 = vector_normalise(numpy.array(v2))
|
6085
|
+
epsilon = numpy.finfo(numpy.float32).eps
|
6086
|
+
one_minus_eps = 1.0 - epsilon
|
6087
|
+
if numpy.dot(v1, v2) > one_minus_eps: # 1.0
|
6088
|
+
# No rotation
|
6089
|
+
return [0, 0, 0, 1]
|
6090
|
+
elif numpy.dot(v1, v2) < -one_minus_eps: # -1.0
|
6091
|
+
# 180 rotation about Y
|
6092
|
+
return [0, 1, 0, 1]
|
6093
|
+
xyz = numpy.cross(v1, v2)
|
6094
|
+
l1 = numpy.linalg.norm(v1)
|
6095
|
+
l2 = numpy.linalg.norm(v2)
|
6096
|
+
w = math.sqrt((l1 * l1) * (l2 * l2)) + numpy.dot(v1, v2)
|
6097
|
+
qr = quat.quaternion(w, xyz[0], xyz[1], xyz[2])
|
6098
|
+
qr = qr.normalized()
|
6099
|
+
# Return in LavaVu quaternion format
|
6100
|
+
if lvformat:
|
6101
|
+
return [qr.x, qr.y, qr.z, qr.w]
|
6102
|
+
else:
|
6103
|
+
return qr
|
6104
|
+
|
5851
6105
|
if __name__ == '__main__':
|
5852
6106
|
#Run doctests - only works on numpy 1.14+ due to changes in array printing
|
5853
6107
|
npyv = numpy.__version__.split('.')
|
@@ -5857,3 +6111,4 @@ if __name__ == '__main__':
|
|
5857
6111
|
import doctest
|
5858
6112
|
doctest.testmod()
|
5859
6113
|
|
6114
|
+
|