vidformer 0.8.0__py3-none-any.whl → 0.9.0__py3-none-any.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.
vidformer/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """A Python library for creating and viewing videos with vidformer."""
2
2
 
3
- __version__ = "0.8.0"
3
+ __version__ = "0.9.0"
4
4
 
5
5
  from .vf import *
vidformer/cv2/vf_cv2.py CHANGED
@@ -11,6 +11,7 @@ vidformer.cv2 is the cv2 frontend for [vidformer](https://github.com/ixlab/vidfo
11
11
  """
12
12
 
13
13
  from .. import vf
14
+ from .. import igni
14
15
 
15
16
  try:
16
17
  import cv2 as _opencv2
@@ -22,6 +23,8 @@ import numpy as np
22
23
  import uuid
23
24
  from fractions import Fraction
24
25
  from bisect import bisect_right
26
+ import zlib
27
+ import re
25
28
 
26
29
  CAP_PROP_POS_MSEC = 0
27
30
  CAP_PROP_POS_FRAMES = 1
@@ -78,10 +81,10 @@ def _server():
78
81
  return _global_cv2_server
79
82
 
80
83
 
81
- def set_cv2_server(server: vf.YrdenServer):
84
+ def set_cv2_server(server):
82
85
  """Set the server to use for the cv2 frontend."""
83
86
  global _global_cv2_server
84
- assert isinstance(server, vf.YrdenServer)
87
+ assert isinstance(server, vf.YrdenServer) or isinstance(server, igni.IgniServer)
85
88
  _global_cv2_server = server
86
89
 
87
90
 
@@ -210,27 +213,59 @@ class Frame:
210
213
 
211
214
 
212
215
  def _inline_frame(arr):
213
- assert arr.dtype == np.uint8
214
- assert arr.ndim == 3
215
- assert arr.shape[2] == 3
216
+ if arr.dtype != np.uint8:
217
+ raise Exception("Only uint8 arrays are supported")
218
+ if len(arr.shape) != 3:
219
+ raise Exception("Only 3D arrays are supported")
220
+ if arr.shape[2] != 3:
221
+ raise Exception("To inline a frame, the array must have 3 channels")
216
222
 
217
- # convert BGR to RGB
218
223
  arr = arr[:, :, ::-1]
224
+ if not arr.flags["C_CONTIGUOUS"]:
225
+ arr = np.ascontiguousarray(arr)
219
226
 
220
227
  width = arr.shape[1]
221
228
  height = arr.shape[0]
222
229
  pix_fmt = "rgb24"
223
230
 
224
- f = _inline_mat(arr.tobytes(), width=width, height=height, pix_fmt=pix_fmt)
231
+ data_gzip = zlib.compress(memoryview(arr), level=1)
232
+
233
+ f = _inline_mat(
234
+ data_gzip, width=width, height=height, pix_fmt=pix_fmt, compression="zlib"
235
+ )
225
236
  fmt = {"width": width, "height": height, "pix_fmt": pix_fmt}
237
+
238
+ # Return the resulting Frame object
226
239
  return Frame(f, fmt)
227
240
 
228
241
 
229
242
  class VideoCapture:
230
243
  def __init__(self, path):
231
- self._path = path
232
244
  server = _server()
233
- self._source = vf.Source(server, str(uuid.uuid4()), path, 0)
245
+ if type(path) == str:
246
+ if isinstance(server, vf.YrdenServer):
247
+ self._path = path
248
+ self._source = vf.Source(server, str(uuid.uuid4()), path, 0)
249
+ else:
250
+ assert isinstance(server, igni.IgniServer)
251
+ match = re.match(r"(http|https)://([^/]+)(.*)", path)
252
+ if match is not None:
253
+ endpoint = f"{match.group(1)}://{match.group(2)}"
254
+ path = match.group(3)
255
+ if path.startswith("/"):
256
+ path = path[1:]
257
+ self._path = path
258
+ self._source = server.source(
259
+ path, 0, "http", {"endpoint": endpoint}
260
+ )
261
+ else:
262
+ raise Exception(
263
+ "Using a VideoCapture source by name only works with http(s) URLs. You need to pass an IgniSource instead."
264
+ )
265
+ elif isinstance(path, igni.IgniSource):
266
+ assert isinstance(server, igni.IgniServer)
267
+ self._path = path._name
268
+ self._source = path
234
269
  self._next_frame_idx = 0
235
270
 
236
271
  def isOpened(self):
@@ -263,7 +298,7 @@ class VideoCapture:
263
298
  raise Exception(f"Unsupported property {prop}")
264
299
 
265
300
  def read(self):
266
- if self._next_frame_idx >= len(self._source.ts()):
301
+ if self._next_frame_idx >= len(self._source):
267
302
  return False, None
268
303
  frame = self._source.iloc[self._next_frame_idx]
269
304
  self._next_frame_idx += 1
@@ -275,6 +310,107 @@ class VideoCapture:
275
310
 
276
311
 
277
312
  class VideoWriter:
313
+ def __init__(self, *args, **kwargs):
314
+ server = _server()
315
+ if isinstance(server, vf.YrdenServer):
316
+ self._writer = _YrdenVideoWriter(*args, **kwargs)
317
+ elif isinstance(server, igni.IgniServer):
318
+ self._writer = _IgniVideoWriter(*args, **kwargs)
319
+ else:
320
+ raise Exception("Unsupported server type")
321
+
322
+ def spec(self):
323
+ return self._writer.spec()
324
+
325
+ def write(self, *args, **kwargs):
326
+ return self._writer.write(*args, **kwargs)
327
+
328
+ def release(self, *args, **kwargs):
329
+ return self._writer.release(*args, **kwargs)
330
+
331
+ def spec(self, *args, **kwargs):
332
+ return self._writer.spec(*args, **kwargs)
333
+
334
+
335
+ class _IgniVideoWriter:
336
+ def __init__(
337
+ self,
338
+ path,
339
+ _fourcc,
340
+ fps,
341
+ size,
342
+ batch_size=1024,
343
+ vod_segment_length=Fraction(2, 1),
344
+ ):
345
+ server = _server()
346
+ assert isinstance(server, igni.IgniServer)
347
+ if path is not None:
348
+ raise Exception(
349
+ "Igni does not support writing to a file. VideoWriter path must be None"
350
+ )
351
+ if isinstance(fps, int):
352
+ self._f_time = Fraction(1, fps)
353
+ elif isinstance(fps, Fraction):
354
+ self._f_time = 1 / fps
355
+ else:
356
+ raise Exception("fps must be an integer or a Fraction")
357
+
358
+ assert isinstance(size, tuple) or isinstance(size, list)
359
+ assert len(size) == 2
360
+ width, height = size
361
+ self._spec = server.create_spec(
362
+ width, height, "yuv420p", vod_segment_length, 1 / self._f_time
363
+ )
364
+ self._batch_size = batch_size
365
+ self._idx = 0
366
+ self._frame_buffer = []
367
+
368
+ def _flush(self, terminal=False):
369
+ server = _server()
370
+ server.push_spec_part(
371
+ self._spec,
372
+ self._idx - len(self._frame_buffer),
373
+ self._frame_buffer,
374
+ terminal=terminal,
375
+ )
376
+ self._frame_buffer = []
377
+
378
+ def _explicit_terminate(self):
379
+ server = _server()
380
+ server.push_spec_part(self._spec._id, self._idx, [], terminal=True)
381
+
382
+ def spec(self):
383
+ return self._spec
384
+
385
+ def write(self, frame):
386
+ if frame is not None:
387
+ frame = frameify(frame, "frame")
388
+ if frame._fmt["width"] != self._spec._fmt["width"]:
389
+ raise Exception(
390
+ f"Frame type error; expected width {self._spec._fmt['width']}, got {frame._fmt['width']}"
391
+ )
392
+ if frame._fmt["height"] != self._spec._fmt["height"]:
393
+ raise Exception(
394
+ f"Frame type error; expected height {self._spec._fmt['height']}, got {frame._fmt['height']}"
395
+ )
396
+ if frame._fmt["pix_fmt"] != self._spec._fmt["pix_fmt"]:
397
+ f_obj = _filter_scale(frame._f, pix_fmt=self._spec._fmt["pix_fmt"])
398
+ frame = Frame(f_obj, self._spec._fmt)
399
+ t = self._f_time * self._idx
400
+ self._frame_buffer.append((t, frame._f if frame is not None else None))
401
+ self._idx += 1
402
+
403
+ if len(self._frame_buffer) >= self._batch_size:
404
+ self._flush()
405
+
406
+ def release(self):
407
+ if len(self._frame_buffer) > 0:
408
+ self._flush(True)
409
+ else:
410
+ self._explicit_terminate()
411
+
412
+
413
+ class _YrdenVideoWriter:
278
414
  def __init__(self, path, fourcc, fps, size):
279
415
  assert isinstance(fourcc, VideoWriter_fourcc)
280
416
  if path is not None and not isinstance(path, str):
@@ -393,13 +529,18 @@ def vidplay(video, *args, **kwargs):
393
529
  Args:
394
530
  video: one of [vidformer.Spec, vidformer.Source, vidformer.cv2.VideoWriter]
395
531
  """
396
-
397
532
  if isinstance(video, vf.Spec):
398
533
  return video.play(_server(), *args, **kwargs)
399
534
  elif isinstance(video, vf.Source):
400
535
  return video.play(_server(), *args, **kwargs)
401
536
  elif isinstance(video, VideoWriter):
537
+ return vidplay(video._writer, *args, **kwargs)
538
+ elif isinstance(video, _YrdenVideoWriter):
402
539
  return video.spec().play(_server(), *args, **kwargs)
540
+ elif isinstance(video, _IgniVideoWriter):
541
+ return video._spec.play(*args, **kwargs)
542
+ elif isinstance(video, igni.IgniSpec):
543
+ return video.play(*args, **kwargs)
403
544
  else:
404
545
  raise Exception("Unsupported video type to vidplay")
405
546
 
@@ -0,0 +1 @@
1
+ from .vf_igni import *
@@ -0,0 +1,285 @@
1
+ from .. import vf
2
+
3
+ import requests
4
+ from fractions import Fraction
5
+ from urllib.parse import urlparse
6
+
7
+
8
+ class IgniServer:
9
+ def __init__(self, endpoint: str, api_key: str):
10
+ if not endpoint.startswith("http://") and not endpoint.startswith("https://"):
11
+ raise Exception("Endpoint must start with http:// or https://")
12
+ if endpoint.endswith("/"):
13
+ raise Exception("Endpoint must not end with /")
14
+ self._endpoint = endpoint
15
+
16
+ self._api_key = api_key
17
+ response = requests.get(
18
+ f"{self._endpoint}/auth",
19
+ headers={"Authorization": f"Bearer {self._api_key}"},
20
+ )
21
+ if not response.ok:
22
+ raise Exception(response.text)
23
+ response = response.json()
24
+ assert response["status"] == "ok"
25
+
26
+ def get_source(self, id: str):
27
+ assert type(id) == str
28
+ response = requests.get(
29
+ f"{self._endpoint}/source/{id}",
30
+ headers={"Authorization": f"Bearer {self._api_key}"},
31
+ )
32
+ if not response.ok:
33
+ raise Exception(response.text)
34
+ response = response.json()
35
+ return IgniSource(response["id"], response)
36
+
37
+ def list_sources(self):
38
+ response = requests.get(
39
+ f"{self._endpoint}/source",
40
+ headers={"Authorization": f"Bearer {self._api_key}"},
41
+ )
42
+ if not response.ok:
43
+ raise Exception(response.text)
44
+ response = response.json()
45
+ return response
46
+
47
+ def delete_source(self, id: str):
48
+ assert type(id) == str
49
+ response = requests.delete(
50
+ f"{self._endpoint}/source/{id}",
51
+ headers={"Authorization": f"Bearer {self._api_key}"},
52
+ )
53
+ if not response.ok:
54
+ raise Exception(response.text)
55
+ response = response.json()
56
+ assert response["status"] == "ok"
57
+
58
+ def search_source(self, name, stream_idx, storage_service, storage_config):
59
+ assert type(name) == str
60
+ assert type(stream_idx) == int
61
+ assert type(storage_service) == str
62
+ assert type(storage_config) == dict
63
+ for k, v in storage_config.items():
64
+ assert type(k) == str
65
+ assert type(v) == str
66
+ req = {
67
+ "name": name,
68
+ "stream_idx": stream_idx,
69
+ "storage_service": storage_service,
70
+ "storage_config": storage_config,
71
+ }
72
+ response = requests.post(
73
+ f"{self._endpoint}/source/search",
74
+ json=req,
75
+ headers={"Authorization": f"Bearer {self._api_key}"},
76
+ )
77
+ if not response.ok:
78
+ raise Exception(response.text)
79
+ response = response.json()
80
+ return response
81
+
82
+ def create_source(self, name, stream_idx, storage_service, storage_config):
83
+ assert type(name) == str
84
+ assert type(stream_idx) == int
85
+ assert type(storage_service) == str
86
+ assert type(storage_config) == dict
87
+ for k, v in storage_config.items():
88
+ assert type(k) == str
89
+ assert type(v) == str
90
+ req = {
91
+ "name": name,
92
+ "stream_idx": stream_idx,
93
+ "storage_service": storage_service,
94
+ "storage_config": storage_config,
95
+ }
96
+ response = requests.post(
97
+ f"{self._endpoint}/source",
98
+ json=req,
99
+ headers={"Authorization": f"Bearer {self._api_key}"},
100
+ )
101
+ if not response.ok:
102
+ raise Exception(response.text)
103
+ response = response.json()
104
+ assert response["status"] == "ok"
105
+ id = response["id"]
106
+ return self.get_source(id)
107
+
108
+ def source(self, name, stream_idx, storage_service, storage_config):
109
+ """Convenience function for accessing sources.
110
+
111
+ Tries to find a source with the given name, stream_idx, storage_service, and storage_config.
112
+ If no source is found, creates a new source with the given parameters.
113
+ """
114
+
115
+ sources = self.search_source(name, stream_idx, storage_service, storage_config)
116
+ if len(sources) == 0:
117
+ return self.create_source(name, stream_idx, storage_service, storage_config)
118
+ return self.get_source(sources[0])
119
+
120
+ def get_spec(self, id: str):
121
+ assert type(id) == str
122
+ response = requests.get(
123
+ f"{self._endpoint}/spec/{id}",
124
+ headers={"Authorization": f"Bearer {self._api_key}"},
125
+ )
126
+ if not response.ok:
127
+ raise Exception(response.text)
128
+ response = response.json()
129
+ return IgniSpec(response["id"], response)
130
+
131
+ def list_specs(self):
132
+ response = requests.get(
133
+ f"{self._endpoint}/spec",
134
+ headers={"Authorization": f"Bearer {self._api_key}"},
135
+ )
136
+ if not response.ok:
137
+ raise Exception(response.text)
138
+ response = response.json()
139
+ return response
140
+
141
+ def create_spec(
142
+ self,
143
+ width,
144
+ height,
145
+ pix_fmt,
146
+ vod_segment_length,
147
+ frame_rate,
148
+ ready_hook=None,
149
+ steer_hook=None,
150
+ ):
151
+ assert type(width) == int
152
+ assert type(height) == int
153
+ assert type(pix_fmt) == str
154
+ assert type(vod_segment_length) == Fraction
155
+ assert type(frame_rate) == Fraction
156
+ assert type(ready_hook) == str or ready_hook is None
157
+ assert type(steer_hook) == str or steer_hook is None
158
+
159
+ req = {
160
+ "width": width,
161
+ "height": height,
162
+ "pix_fmt": pix_fmt,
163
+ "vod_segment_length": [
164
+ vod_segment_length.numerator,
165
+ vod_segment_length.denominator,
166
+ ],
167
+ "frame_rate": [frame_rate.numerator, frame_rate.denominator],
168
+ "ready_hook": ready_hook,
169
+ "steer_hook": steer_hook,
170
+ }
171
+ response = requests.post(
172
+ f"{self._endpoint}/spec",
173
+ json=req,
174
+ headers={"Authorization": f"Bearer {self._api_key}"},
175
+ )
176
+ if not response.ok:
177
+ raise Exception(response.text)
178
+ response = response.json()
179
+ assert response["status"] == "ok"
180
+ return self.get_spec(response["id"])
181
+
182
+ def delete_spec(self, id: str):
183
+ assert type(id) == str
184
+ response = requests.delete(
185
+ f"{self._endpoint}/spec/{id}",
186
+ headers={"Authorization": f"Bearer {self._api_key}"},
187
+ )
188
+ if not response.ok:
189
+ raise Exception(response.text)
190
+ response = response.json()
191
+ assert response["status"] == "ok"
192
+
193
+ def push_spec_part(self, spec_id, pos, frames, terminal):
194
+ if type(spec_id) == IgniSpec:
195
+ spec_id = spec_id._id
196
+ assert type(spec_id) == str
197
+ assert type(pos) == int
198
+ assert type(frames) == list
199
+ assert type(terminal) == bool
200
+
201
+ req_frames = []
202
+ for frame in frames:
203
+ assert type(frame) == tuple
204
+ assert len(frame) == 2
205
+ t = frame[0]
206
+ f = frame[1]
207
+ assert type(t) == Fraction
208
+ assert f is None or type(f) == vf.SourceExpr or type(f) == vf.FilterExpr
209
+ req_frames.append(
210
+ [
211
+ [t.numerator, t.denominator],
212
+ f._to_json_spec() if f is not None else None,
213
+ ]
214
+ )
215
+
216
+ req = {
217
+ "pos": pos,
218
+ "frames": req_frames,
219
+ "terminal": terminal,
220
+ }
221
+ response = requests.post(
222
+ f"{self._endpoint}/spec/{spec_id}/part",
223
+ json=req,
224
+ headers={"Authorization": f"Bearer {self._api_key}"},
225
+ )
226
+ if not response.ok:
227
+ raise Exception(response.text)
228
+ response = response.json()
229
+ assert response["status"] == "ok"
230
+
231
+
232
+ class IgniSource:
233
+ def __init__(self, id, src):
234
+ self._name = id
235
+ self._fmt = {
236
+ "width": src["width"],
237
+ "height": src["height"],
238
+ "pix_fmt": src["pix_fmt"],
239
+ }
240
+ self._ts = [Fraction(x[0], x[1]) for x in src["ts"]]
241
+ self.iloc = vf.SourceILoc(self)
242
+
243
+ def id(self):
244
+ return self._name
245
+
246
+ def fmt(self):
247
+ return {**self._fmt}
248
+
249
+ def ts(self):
250
+ return self._ts.copy()
251
+
252
+ def __len__(self):
253
+ return len(self._ts)
254
+
255
+ def __getitem__(self, idx):
256
+ if type(idx) != Fraction:
257
+ raise Exception("Source index must be a Fraction")
258
+ return vf.SourceExpr(self, idx, False)
259
+
260
+ def __repr__(self):
261
+ return f"IgniSource({self._name})"
262
+
263
+
264
+ class IgniSpec:
265
+ def __init__(self, id, src):
266
+ self._id = id
267
+ self._fmt = {
268
+ "width": src["width"],
269
+ "height": src["height"],
270
+ "pix_fmt": src["pix_fmt"],
271
+ }
272
+ self._vod_endpoint = src["vod_endpoint"]
273
+ parsed_url = urlparse(self._vod_endpoint)
274
+ self._hls_js_url = f"{parsed_url.scheme}://{parsed_url.netloc}/hls.js"
275
+
276
+ def id(self):
277
+ return self._id
278
+
279
+ def play(self, *args, **kwargs):
280
+ url = f"{self._vod_endpoint}playlist.m3u8"
281
+ status_url = f"{self._vod_endpoint}status"
282
+ hls_js_url = self._hls_js_url
283
+ return vf._play(
284
+ self._id, url, hls_js_url, *args, **kwargs, status_url=status_url
285
+ )
vidformer/vf.py CHANGED
@@ -52,6 +52,134 @@ def _check_hls_link_exists(url, max_attempts=150, delay=0.1):
52
52
  return None
53
53
 
54
54
 
55
+ def _play(namespace, hls_video_url, hls_js_url, method="html", status_url=None):
56
+ # The namespace is so multiple videos in one tab don't conflict
57
+
58
+ if method == "html":
59
+ from IPython.display import HTML
60
+
61
+ if not status_url:
62
+ html_code = f"""
63
+ <!DOCTYPE html>
64
+ <html>
65
+ <head>
66
+ <title>HLS Video Player</title>
67
+ <!-- Include hls.js library -->
68
+ <script src="{hls_js_url}"></script>
69
+ </head>
70
+ <body>
71
+ <video id="video-{namespace}" controls width="640" height="360" autoplay></video>
72
+ <script>
73
+ var video = document.getElementById('video-{namespace}');
74
+ var videoSrc = '{hls_video_url}';
75
+
76
+ if (Hls.isSupported()) {{
77
+ var hls = new Hls();
78
+ hls.loadSource(videoSrc);
79
+ hls.attachMedia(video);
80
+ hls.on(Hls.Events.MANIFEST_PARSED, function() {{
81
+ video.play();
82
+ }});
83
+ }} else if (video.canPlayType('application/vnd.apple.mpegurl')) {{
84
+ video.src = videoSrc;
85
+ video.addEventListener('loadedmetadata', function() {{
86
+ video.play();
87
+ }});
88
+ }} else {{
89
+ console.error('This browser does not appear to support HLS.');
90
+ }}
91
+ </script>
92
+ </body>
93
+ </html>
94
+ """
95
+ return HTML(data=html_code)
96
+ else:
97
+ html_code = f"""
98
+ <!DOCTYPE html>
99
+ <html>
100
+ <head>
101
+ <title>HLS Video Player</title>
102
+ <script src="{hls_js_url}"></script>
103
+ </head>
104
+ <body>
105
+ <div id="container"></div>
106
+ <script>
107
+ var statusUrl = '{status_url}';
108
+ var videoSrc = '{hls_video_url}';
109
+ var videoNamespace = '{namespace}';
110
+
111
+ function showWaiting() {{
112
+ document.getElementById('container').textContent = 'Waiting...';
113
+ pollStatus();
114
+ }}
115
+
116
+ function pollStatus() {{
117
+ setTimeout(function() {{
118
+ fetch(statusUrl)
119
+ .then(r => r.json())
120
+ .then(res => {{
121
+ if (res.ready) {{
122
+ document.getElementById('container').textContent = '';
123
+ attachHls();
124
+ }} else {{
125
+ pollStatus();
126
+ }}
127
+ }})
128
+ .catch(e => {{
129
+ console.error(e);
130
+ pollStatus();
131
+ }});
132
+ }}, 250);
133
+ }}
134
+
135
+ function attachHls() {{
136
+ var container = document.getElementById('container');
137
+ container.textContent = '';
138
+ var video = document.createElement('video');
139
+ video.id = 'video-' + videoNamespace;
140
+ video.controls = true;
141
+ video.width = 640;
142
+ video.height = 360;
143
+ container.appendChild(video);
144
+ if (Hls.isSupported()) {{
145
+ var hls = new Hls();
146
+ hls.loadSource(videoSrc);
147
+ hls.attachMedia(video);
148
+ hls.on(Hls.Events.MANIFEST_PARSED, function() {{
149
+ video.play();
150
+ }});
151
+ }} else if (video.canPlayType('application/vnd.apple.mpegurl')) {{
152
+ video.src = videoSrc;
153
+ video.addEventListener('loadedmetadata', function() {{
154
+ video.play();
155
+ }});
156
+ }}
157
+ }}
158
+
159
+ fetch(statusUrl)
160
+ .then(r => r.json())
161
+ .then(res => {{
162
+ if (res.ready) {{
163
+ attachHls();
164
+ }} else {{
165
+ showWaiting();
166
+ }}
167
+ }})
168
+ .catch(e => {{
169
+ console.error(e);
170
+ showWaiting();
171
+ }});
172
+ </script>
173
+ </body>
174
+ </html>
175
+ """
176
+ return HTML(data=html_code)
177
+ elif method == "link":
178
+ return hls_video_url
179
+ else:
180
+ raise ValueError("Invalid method")
181
+
182
+
55
183
  class Spec:
56
184
  """
57
185
  A video transformation specification.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: vidformer
3
- Version: 0.8.0
3
+ Version: 0.9.0
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
@@ -0,0 +1,9 @@
1
+ vidformer/__init__.py,sha256=5Hd99Yj_iTCdwQPWojDy3XTksRGCQW3BNpWHnrX3l2g,113
2
+ vidformer/vf.py,sha256=Niz3V3tMDL2fWM_xTo3vIn9IFMufbaM8gtIfSR8-FSc,35391
3
+ vidformer/cv2/__init__.py,sha256=wOjDsYyUKlP_Hye8-tyz-msu9xwaPMpN2sGMu3Lh3-w,22
4
+ vidformer/cv2/vf_cv2.py,sha256=RzkRQOmKuEuQo_-8NItSx16b3ZgwZ5-lRR_AHWRiZGE,24460
5
+ vidformer/igni/__init__.py,sha256=a7st8_NVuIu3-weoxE4o8AFtQFyXkkMfUBM_8Lj1bl0,23
6
+ vidformer/igni/vf_igni.py,sha256=QUVURg6EYq-e5ID5X8jt_fKc0OOGnoWOvl3VRNK-bRo,9249
7
+ vidformer-0.9.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
8
+ vidformer-0.9.0.dist-info/METADATA,sha256=xnIPh-eICQstIrRUu0efurTRN6p62TycHRiW8eh99Ys,1487
9
+ vidformer-0.9.0.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- vidformer/__init__.py,sha256=scMlj2LLMBZXdv9ob_Dhb4ObK7RZLzxVgELyaR650xU,113
2
- vidformer/vf.py,sha256=GjgaKnbk3o75lwrXP3Ue0LDyTxSf-l0njL5X-6QYZvs,31496
3
- vidformer/cv2/__init__.py,sha256=wOjDsYyUKlP_Hye8-tyz-msu9xwaPMpN2sGMu3Lh3-w,22
4
- vidformer/cv2/vf_cv2.py,sha256=8shv614pmNHQyHMqjJSZf08j3eNxE_XtfdLd_kyh5no,19396
5
- vidformer-0.8.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
6
- vidformer-0.8.0.dist-info/METADATA,sha256=aXFYRxtNx4hfWlqW7Zx_RW-X2CuN3-adYwRrCUkLtI4,1487
7
- vidformer-0.8.0.dist-info/RECORD,,