vidformer 0.6.1__tar.gz → 0.6.3__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vidformer
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: A Python library for creating and viewing videos with vidformer.
5
5
  Author-email: Dominik Winecki <dominikwinecki@gmail.com>
6
6
  Requires-Python: >=3.8
@@ -19,13 +19,13 @@ Project-URL: Issues, https://github.com/ixlab/vidformer/issues
19
19
  [![PyPI version](https://img.shields.io/pypi/v/vidformer.svg)](https://pypi.org/project/vidformer/)
20
20
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ixlab/vidformer/blob/main/LICENSE)
21
21
 
22
-
23
- vidformer-py is our Python 🐍 interface for [vidformer](https://github.com/ixlab/vidformer).
22
+ vidformer-py is a Python 🐍 interface for [vidformer](https://github.com/ixlab/vidformer).
24
23
  Our [getting started guide](https://ixlab.github.io/vidformer/getting-started.html) explains how to use it.
25
24
 
26
25
  **Quick links:**
27
26
  * [📦 PyPI](https://pypi.org/project/vidformer/)
28
- * [📘 Documentation](https://ixlab.github.io/vidformer/vidformer-py/)
27
+ * [📘 Documentation - vidformer-py](https://ixlab.github.io/vidformer/vidformer-py/)
28
+ * [📘 Documentation - vidformer.cv2](https://ixlab.github.io/vidformer/vidformer-py-cv2/)
29
29
  * [🧑‍💻 Source Code](https://github.com/ixlab/vidformer/tree/main/vidformer-py/)
30
30
 
31
31
  **Publish:**
@@ -3,13 +3,13 @@
3
3
  [![PyPI version](https://img.shields.io/pypi/v/vidformer.svg)](https://pypi.org/project/vidformer/)
4
4
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ixlab/vidformer/blob/main/LICENSE)
5
5
 
6
-
7
- vidformer-py is our Python 🐍 interface for [vidformer](https://github.com/ixlab/vidformer).
6
+ vidformer-py is a Python 🐍 interface for [vidformer](https://github.com/ixlab/vidformer).
8
7
  Our [getting started guide](https://ixlab.github.io/vidformer/getting-started.html) explains how to use it.
9
8
 
10
9
  **Quick links:**
11
10
  * [📦 PyPI](https://pypi.org/project/vidformer/)
12
- * [📘 Documentation](https://ixlab.github.io/vidformer/vidformer-py/)
11
+ * [📘 Documentation - vidformer-py](https://ixlab.github.io/vidformer/vidformer-py/)
12
+ * [📘 Documentation - vidformer.cv2](https://ixlab.github.io/vidformer/vidformer-py-cv2/)
13
13
  * [🧑‍💻 Source Code](https://github.com/ixlab/vidformer/tree/main/vidformer-py/)
14
14
 
15
15
  **Publish:**
@@ -0,0 +1,59 @@
1
+ # OpenCV/cv2 Functions
2
+
3
+ See [vidformer.cv2 API docs](https://ixlab.github.io/vidformer/vidformer-py-cv2/).
4
+
5
+ > ⚠️ The `cv2` module is a work in progress. If you find a bug or need a missing feature implemented feel free to [file an issue](https://github.com/ixlab/vidformer/issues) or contribute yourself!
6
+
7
+ Legend:
8
+ * ✅ - Support
9
+ * 🔸 - Support via OpenCV cv2
10
+ * ❌ - Not yet implemented
11
+
12
+ ## Vidformer-specific Functions
13
+
14
+ * `cv2.vidplay(video2)` - Play a VideoWriter, Spec, or Source
15
+ * `VideoWriter.spec()` - Return the Spec of an output video
16
+ * `Frame.numpy()` - Return the frame as a numpy array
17
+ * `cv2.setTo` - The OpenCV `Mat.setTo` function (not in cv2)
18
+
19
+ ## opencv
20
+
21
+ |**Class**|**Status**|
22
+ |---|---|
23
+ |VideoCapture|✅|
24
+ |VideoWriter|✅|
25
+ |VideoWriter_fourcc|✅|
26
+
27
+ |**Function**|**Status**|
28
+ |---|---|
29
+ |imread|✅|
30
+ |imwrite|✅|
31
+
32
+
33
+ ## opencv.imgproc
34
+
35
+ Drawing Functions:
36
+
37
+ |**Function**|**Status**|
38
+ |---|---|
39
+ |arrowedLine|✅|
40
+ |circle|✅|
41
+ |clipLine|❌|
42
+ |drawContours|❌|
43
+ |drawMarker|❌|
44
+ |ellipse|❌|
45
+ |ellipse2Poly|❌|
46
+ |fillConvexPoly|❌|
47
+ |fillPoly|❌|
48
+ |getFontScaleFromHeight|🔸|
49
+ |getTextSize|🔸|
50
+ |line|✅|
51
+ |polylines|❌|
52
+ |putText|✅|
53
+ |rectangle|✅|
54
+
55
+ ## opencv.core
56
+
57
+ |**Function**|**Status**|
58
+ |---|---|
59
+ |addWeighted|✅|
@@ -1,5 +1,5 @@
1
1
  """A Python library for creating and viewing videos with vidformer."""
2
2
 
3
- __version__ = "0.6.1"
3
+ __version__ = "0.6.3"
4
4
 
5
5
  from .vf import *
@@ -1,3 +1,15 @@
1
+ """
2
+ vidformer.cv2 is the cv2 frontend for [vidformer](https://github.com/ixlab/vidformer).
3
+
4
+ > ⚠️ This module is a work in progress. See the [implemented functions list](https://ixlab.github.io/vidformer/opencv-filters.html).
5
+
6
+ **Quick links:**
7
+ * [📦 PyPI](https://pypi.org/project/vidformer/)
8
+ * [📘 Documentation - vidformer-py](https://ixlab.github.io/vidformer/vidformer-py/)
9
+ * [📘 Documentation - vidformer.cv2](https://ixlab.github.io/vidformer/vidformer-py-cv2/)
10
+ * [🧑‍💻 Source Code](https://github.com/ixlab/vidformer/tree/main/vidformer-py/)
11
+ """
12
+
1
13
  from .. import vf
2
14
 
3
15
  try:
@@ -5,6 +17,8 @@ try:
5
17
  except:
6
18
  _opencv2 = None
7
19
 
20
+ import numpy as np
21
+
8
22
  import uuid
9
23
  from fractions import Fraction
10
24
  from bisect import bisect_right
@@ -14,6 +28,7 @@ CAP_PROP_POS_FRAMES = 1
14
28
  CAP_PROP_FRAME_WIDTH = 3
15
29
  CAP_PROP_FRAME_HEIGHT = 4
16
30
  CAP_PROP_FPS = 5
31
+ CAP_PROP_FRAME_COUNT = 7
17
32
 
18
33
  FONT_HERSHEY_SIMPLEX = 0
19
34
  FONT_HERSHEY_PLAIN = 1
@@ -58,16 +73,18 @@ def _server():
58
73
  return _global_cv2_server
59
74
 
60
75
 
61
- def set_cv2_server(server):
76
+ def set_cv2_server(server: vf.YrdenServer):
62
77
  """Set the server to use for the cv2 frontend."""
63
78
  global _global_cv2_server
64
79
  assert isinstance(server, vf.YrdenServer)
65
80
  _global_cv2_server = server
66
81
 
67
82
 
68
- class _Frame:
69
- def __init__(self, f):
83
+ class Frame:
84
+ def __init__(self, f, fmt):
70
85
  self._f = f
86
+ self._fmt = fmt
87
+ self.shape = (fmt["height"], fmt["width"], 3)
71
88
 
72
89
  # denotes that the frame has not yet been modified
73
90
  # when a frame is modified, it is converted to rgb24 first
@@ -76,6 +93,23 @@ class _Frame:
76
93
  def _mut(self):
77
94
  self._modified = True
78
95
  self._f = _filter_scale(self._f, pix_fmt="rgb24")
96
+ self._fmt["pix_fmt"] = "rgb24"
97
+
98
+ def numpy(self):
99
+ """
100
+ Return the frame as a numpy array.
101
+ """
102
+
103
+ self._mut()
104
+ spec = vf.Spec([Fraction(0, 1)], lambda t, i: self._f, self._fmt)
105
+ loader = spec.load(_server())
106
+
107
+ frame_raster_rgb24 = loader[0]
108
+ assert type(frame_raster_rgb24) == bytes
109
+ assert len(frame_raster_rgb24) == self.shape[0] * self.shape[1] * 3
110
+ raw_data_array = np.frombuffer(frame_raster_rgb24, dtype=np.uint8)
111
+ frame = raw_data_array.reshape(self.shape)
112
+ return frame
79
113
 
80
114
 
81
115
  class VideoCapture:
@@ -95,6 +129,10 @@ class VideoCapture:
95
129
  return self._source.fmt()["width"]
96
130
  elif prop == CAP_PROP_FRAME_HEIGHT:
97
131
  return self._source.fmt()["height"]
132
+ elif prop == CAP_PROP_FRAME_COUNT:
133
+ return len(self._source.ts())
134
+ elif prop == CAP_PROP_POS_FRAMES:
135
+ return self._next_frame_idx
98
136
 
99
137
  raise Exception(f"Unknown property {prop}")
100
138
 
@@ -115,7 +153,7 @@ class VideoCapture:
115
153
  return False, None
116
154
  frame = self._source.iloc[self._next_frame_idx]
117
155
  self._next_frame_idx += 1
118
- frame = _Frame(frame)
156
+ frame = Frame(frame, self._source.fmt())
119
157
  return True, frame
120
158
 
121
159
  def release(self):
@@ -125,6 +163,8 @@ class VideoCapture:
125
163
  class VideoWriter:
126
164
  def __init__(self, path, fourcc, fps, size):
127
165
  assert isinstance(fourcc, VideoWriter_fourcc)
166
+ if path is not None and not isinstance(path, str):
167
+ raise Exception("path must be a string or None")
128
168
  self._path = path
129
169
  self._fourcc = fourcc
130
170
  self._fps = fps
@@ -134,8 +174,8 @@ class VideoWriter:
134
174
  self._pix_fmt = "yuv420p"
135
175
 
136
176
  def write(self, frame):
137
- if not isinstance(frame, _Frame):
138
- raise Exception("frame must be a _Frame object")
177
+ if not isinstance(frame, Frame):
178
+ raise Exception("frame must be a vidformer.cv2.Frame object")
139
179
  if frame._modified:
140
180
  f_obj = _filter_scale(frame._f, pix_fmt=self._pix_fmt)
141
181
  self._frames.append(f_obj)
@@ -143,11 +183,14 @@ class VideoWriter:
143
183
  self._frames.append(frame._f)
144
184
 
145
185
  def release(self):
146
- spec = self.vf_spec()
186
+ if self._path is None:
187
+ return
188
+
189
+ spec = self.spec()
147
190
  server = _server()
148
191
  spec.save(server, self._path)
149
192
 
150
- def vf_spec(self):
193
+ def spec(self) -> vf.Spec:
151
194
  fmt = {
152
195
  "width": self._size[0],
153
196
  "height": self._size[1],
@@ -163,12 +206,79 @@ class VideoWriter_fourcc:
163
206
  self._args = args
164
207
 
165
208
 
209
+ def imread(path, *args):
210
+ if len(args) > 0:
211
+ raise NotImplementedError("imread does not support additional arguments")
212
+
213
+ assert path.lower().endswith((".jpg", ".jpeg", ".png"))
214
+ server = _server()
215
+ source = vf.Source(server, str(uuid.uuid4()), path, 0)
216
+ frame = Frame(source.iloc[0], source.fmt())
217
+ return frame
218
+
219
+
220
+ def imwrite(path, img, *args):
221
+ if len(args) > 0:
222
+ raise NotImplementedError("imwrite does not support additional arguments")
223
+
224
+ if not isinstance(img, Frame):
225
+ raise Exception("img must be a vidformer.cv2.Frame object")
226
+
227
+ fmt = img._fmt.copy()
228
+ width = fmt["width"]
229
+ height = fmt["height"]
230
+ f = img._f
231
+
232
+ domain = [Fraction(0, 1)]
233
+
234
+ if path.lower().endswith(".png"):
235
+ img._mut() # Make sure it's in rgb24
236
+ spec = vf.Spec(
237
+ domain,
238
+ lambda t, i: img._f,
239
+ {"width": width, "height": height, "pix_fmt": "rgb24"},
240
+ )
241
+ spec.save(_server(), path, encoder="png")
242
+ elif path.lower().endswith((".jpg", ".jpeg")):
243
+ if img._modified:
244
+ # it's rgb24, we need to convert to something jpeg can handle
245
+ f = _filter_scale(img._f, pix_fmt="yuv420p")
246
+ fmt["pix_fmt"] = "yuv420p"
247
+ else:
248
+ if fmt["pix_fmt"] not in ["yuvj420p", "yuvj422p", "yuvj444p"]:
249
+ f = _filter_scale(img._f, pix_fmt="yuvj420p")
250
+ fmt["pix_fmt"] = "yuvj420p"
251
+
252
+ spec = vf.Spec(domain, lambda t, i: f, fmt)
253
+ spec.save(_server(), path, encoder="mjpeg")
254
+ else:
255
+ raise Exception("Unsupported image format")
256
+
257
+
258
+ def vidplay(video, *args, **kwargs):
259
+ """
260
+ Play a vidformer video specification.
261
+
262
+ Args:
263
+ video: one of [vidformer.Spec, vidformer.Source, vidformer.cv2.VideoWriter]
264
+ """
265
+
266
+ if isinstance(video, vf.Spec):
267
+ return video.play(_server(), *args, **kwargs)
268
+ elif isinstance(video, vf.Source):
269
+ return video.play(_server(), *args, **kwargs)
270
+ elif isinstance(video, VideoWriter):
271
+ return video.spec().play(_server(), *args, **kwargs)
272
+ else:
273
+ raise Exception("Unsupported video type to vidplay")
274
+
275
+
166
276
  def rectangle(img, pt1, pt2, color, thickness=None, lineType=None, shift=None):
167
277
  """
168
278
  cv.rectangle( img, pt1, pt2, color[, thickness[, lineType[, shift]]] )
169
279
  """
170
280
 
171
- assert isinstance(img, _Frame)
281
+ assert isinstance(img, Frame)
172
282
  img._mut()
173
283
 
174
284
  assert len(pt1) == 2
@@ -212,7 +322,7 @@ def putText(
212
322
  cv.putText( img, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomLeftOrigin]]] )
213
323
  """
214
324
 
215
- assert isinstance(img, _Frame)
325
+ assert isinstance(img, Frame)
216
326
  img._mut()
217
327
 
218
328
  assert isinstance(text, str)
@@ -251,7 +361,7 @@ def arrowedLine(
251
361
  """
252
362
  cv.arrowedLine( img, pt1, pt2, color[, thickness[, line_type[, shift[, tipLength]]]] )
253
363
  """
254
- assert isinstance(img, _Frame)
364
+ assert isinstance(img, Frame)
255
365
  img._mut()
256
366
 
257
367
  assert len(pt1) == 2
@@ -285,7 +395,7 @@ def arrowedLine(
285
395
 
286
396
 
287
397
  def line(img, pt1, pt2, color, thickness=None, lineType=None, shift=None):
288
- assert isinstance(img, _Frame)
398
+ assert isinstance(img, Frame)
289
399
  img._mut()
290
400
 
291
401
  assert len(pt1) == 2
@@ -315,7 +425,7 @@ def line(img, pt1, pt2, color, thickness=None, lineType=None, shift=None):
315
425
 
316
426
 
317
427
  def circle(img, center, radius, color, thickness=None, lineType=None, shift=None):
318
- assert isinstance(img, _Frame)
428
+ assert isinstance(img, Frame)
319
429
  img._mut()
320
430
 
321
431
  assert len(center) == 2
@@ -366,15 +476,15 @@ def addWeighted(src1, alpha, src2, beta, gamma, dst=None, dtype=-1):
366
476
  """
367
477
  cv.addWeighted( src1, alpha, src2, beta, gamma[, dst[, dtype]] ) -> dst
368
478
  """
369
- assert isinstance(src1, _Frame)
370
- assert isinstance(src2, _Frame)
479
+ assert isinstance(src1, Frame)
480
+ assert isinstance(src2, Frame)
371
481
  src1._mut()
372
482
  src2._mut()
373
483
 
374
484
  if dst is None:
375
- dst = _Frame(src1._f)
485
+ dst = Frame(src1._f, src1._fmt.copy())
376
486
  else:
377
- assert isinstance(dst, _Frame)
487
+ assert isinstance(dst, Frame)
378
488
  dst._mut()
379
489
 
380
490
  assert isinstance(alpha, float) or isinstance(alpha, int)
@@ -1,3 +1,13 @@
1
+ """
2
+ vidformer-py is a Python 🐍 interface for [vidformer](https://github.com/ixlab/vidformer).
3
+
4
+ **Quick links:**
5
+ * [📦 PyPI](https://pypi.org/project/vidformer/)
6
+ * [📘 Documentation - vidformer-py](https://ixlab.github.io/vidformer/vidformer-py/)
7
+ * [📘 Documentation - vidformer.cv2](https://ixlab.github.io/vidformer/vidformer-py-cv2/)
8
+ * [🧑‍💻 Source Code](https://github.com/ixlab/vidformer/tree/main/vidformer-py/)
9
+ """
10
+
1
11
  import subprocess
2
12
  from fractions import Fraction
3
13
  import random
@@ -43,19 +53,40 @@ def _check_hls_link_exists(url, max_attempts=150, delay=0.1):
43
53
 
44
54
 
45
55
  class Spec:
56
+ """
57
+ A video transformation specification.
58
+
59
+ See https://ixlab.github.io/vidformer/concepts.html for more information.
60
+ """
61
+
46
62
  def __init__(self, domain: list[Fraction], render, fmt: dict):
47
63
  self._domain = domain
48
64
  self._render = render
49
65
  self._fmt = fmt
50
66
 
51
67
  def __repr__(self):
52
- lines = []
53
- for i, t in enumerate(self._domain):
54
- frame_expr = self._render(t, i)
55
- lines.append(
56
- f"{t.numerator}/{t.denominator} => {frame_expr}",
57
- )
58
- return "\n".join(lines)
68
+ if len(self._domain) <= 20:
69
+ lines = []
70
+ for i, t in enumerate(self._domain):
71
+ frame_expr = self._render(t, i)
72
+ lines.append(
73
+ f"{t.numerator}/{t.denominator} => {frame_expr}",
74
+ )
75
+ return "\n".join(lines)
76
+ else:
77
+ lines = []
78
+ for i, t in enumerate(self._domain[:10]):
79
+ frame_expr = self._render(t, i)
80
+ lines.append(
81
+ f"{t.numerator}/{t.denominator} => {frame_expr}",
82
+ )
83
+ lines.append("...")
84
+ for i, t in enumerate(self._domain[-10:]):
85
+ frame_expr = self._render(t, i)
86
+ lines.append(
87
+ f"{t.numerator}/{t.denominator} => {frame_expr}",
88
+ )
89
+ return "\n".join(lines)
59
90
 
60
91
  def _sources(self):
61
92
  s = set()
@@ -351,12 +382,18 @@ class Loader:
351
382
 
352
383
 
353
384
  class YrdenServer:
354
- """A connection to a Yrden server"""
385
+ """
386
+ A connection to a Yrden server.
355
387
 
356
- def __init__(self, domain=None, port=None, bin=None):
357
- """Connect to a Yrden server
388
+ A yrden server is the main API for local use of vidformer.
389
+ """
358
390
 
359
- Can either connect to an existing server, if domain and port are provided, or start a new server using the provided binary
391
+ def __init__(self, domain=None, port=None, bin=None, hls_prefix=None):
392
+ """
393
+ Connect to a Yrden server
394
+
395
+ Can either connect to an existing server, if domain and port are provided, or start a new server using the provided binary.
396
+ If no domain or binary is provided, the `VIDFORMER_BIN` environment variable is used.
360
397
  """
361
398
 
362
399
  self._domain = domain
@@ -376,6 +413,12 @@ class YrdenServer:
376
413
  # We need to print the URL in the notebook
377
414
  # This is a trick to get VS Code to forward the port
378
415
  cmd += ["--print-url"]
416
+
417
+ if hls_prefix is not None:
418
+ if not type(hls_prefix) == str:
419
+ raise Exception("hls_prefix must be a string")
420
+ cmd += ["--hls-prefix", hls_prefix]
421
+
379
422
  self._proc = subprocess.Popen(cmd)
380
423
 
381
424
  version = _check_hls_link_exists(f"http://{self._domain}:{self._port}/")
@@ -503,11 +546,13 @@ class SourceILoc:
503
546
 
504
547
  def __getitem__(self, idx):
505
548
  if type(idx) != int:
506
- raise Exception("Source iloc index must be an integer")
549
+ raise Exception(f"Source iloc index must be an integer, got a {type(idx)}")
507
550
  return SourceExpr(self._source, idx, True)
508
551
 
509
552
 
510
553
  class Source:
554
+ """A video source."""
555
+
511
556
  def __init__(
512
557
  self, server: YrdenServer, name: str, path: str, stream: int, service=None
513
558
  ):
@@ -610,6 +655,8 @@ def _json_arg(arg, skip_data_anot=False):
610
655
 
611
656
 
612
657
  class Filter:
658
+ """A video filter."""
659
+
613
660
  def __init__(self, name: str, tl_func=None, **kwargs):
614
661
  self._name = name
615
662
 
@@ -854,6 +901,10 @@ class UDF:
854
901
 
855
902
 
856
903
  class UDFFrameType:
904
+ """
905
+ Frame type for use in UDFs.
906
+ """
907
+
857
908
  def __init__(self, width: int, height: int, pix_fmt: str):
858
909
  assert type(width) == int
859
910
  assert type(height) == int
@@ -886,6 +937,8 @@ class UDFFrameType:
886
937
 
887
938
 
888
939
  class UDFFrame:
940
+ """A symbolic reference to a frame for use in UDFs."""
941
+
889
942
  def __init__(self, data: np.ndarray, f_type: UDFFrameType):
890
943
  assert type(data) == np.ndarray
891
944
  assert type(f_type) == UDFFrameType
File without changes