mwxlib 1.0.0__py3-none-any.whl → 1.8.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.
@@ -1,16 +1,15 @@
1
1
  #! python3
2
2
  """FFmpeg wrapper.
3
3
  """
4
- from functools import partial
5
4
  from subprocess import Popen, PIPE
6
5
  import numpy as np
7
6
  import os
8
7
  import wx
9
8
  import wx.media
10
9
 
11
- from mwx.framework import _F, hotkey
10
+ from mwx.framework import _F
12
11
  from mwx.graphman import Layer
13
- from mwx.controls import LParam, Icon, Button, TextCtrl
12
+ from mwx.controls import Param, LParam, Icon, Button, TextBox
14
13
 
15
14
 
16
15
  def read_info(path):
@@ -35,8 +34,8 @@ def capture_video(path, ss=0):
35
34
  '-pix_fmt', 'rgb24', # rgb24, gray, etc.
36
35
  'pipe:' # pipe to stdout: '-'
37
36
  ]
38
- bufsize = 4096 # w * h * 3
39
- buf = b"" # bytearray()
37
+ bufsize = 4096 # w * h * 3
38
+ buf = b""
40
39
  with Popen(command, stdout=PIPE) as fp:
41
40
  while 1:
42
41
  s = fp.stdout.read(bufsize)
@@ -63,9 +62,9 @@ class MyFileDropLoader(wx.FileDropTarget):
63
62
  def __init__(self, target):
64
63
  wx.FileDropTarget.__init__(self)
65
64
  self.target = target
66
-
65
+
67
66
  def OnDropFiles(self, x, y, filenames):
68
- path = filenames[-1] # Only the last one will be loaded.
67
+ path = filenames[-1] # Only the last one will be loaded.
69
68
  if len(filenames) > 1:
70
69
  print("- Drop only one file please."
71
70
  "Loading {!r} ...".format(path))
@@ -78,13 +77,13 @@ class Plugin(Layer):
78
77
  """
79
78
  menukey = "Plugins/Extensions/FFMpeg viewer"
80
79
  dockable = False
81
-
80
+
82
81
  def Init(self):
83
82
  self.mc = wx.media.MediaCtrl()
84
83
  self.mc.Create(self, size=(300,300),
85
84
  style=wx.SIMPLE_BORDER,
86
85
  szBackend=wx.media.MEDIABACKEND_WMP10
87
- ## szBackend=wx.media.MEDIABACKEND_DIRECTSHOW
86
+ # szBackend=wx.media.MEDIABACKEND_DIRECTSHOW
88
87
  )
89
88
  self.mc.ShowPlayerControls()
90
89
  self.mc.Bind(wx.media.EVT_MEDIA_LOADED, self.OnMediaLoaded)
@@ -93,29 +92,32 @@ class Plugin(Layer):
93
92
 
94
93
  self._path = None
95
94
 
96
- self.ss = LParam("ss:", # range/value will be set when loaded later.
95
+ self.ss = LParam("ss:", # range/value will be set when loaded later.
97
96
  handler=self.set_offset,
98
97
  updater=self.get_offset,
99
98
  )
100
- self.to = LParam("to:", # range/value will be set when loaded later.
99
+ self.to = LParam("to:", # range/value will be set when loaded later.
101
100
  handler=self.set_offset,
102
101
  updater=self.get_offset,
103
102
  )
104
- self.crop = TextCtrl(self, icon="cut", size=(130,-1),
103
+ self.crop = TextBox(self, icon="cut", size=(140,-1),
105
104
  handler=self.set_crop,
106
105
  updater=self.get_crop,
107
106
  )
107
+ self.rate = Param("rate", (1/8,1/4,1/2,1,2,4,8),
108
+ handler=self.set_rate,
109
+ )
108
110
 
109
111
  self.snp = Button(self, handler=self.snapshot, icon='clip')
110
112
  self.exp = Button(self, handler=self.export, icon='save')
111
113
 
112
- self.rw = Button(self, handler=lambda v: self.seekdelta(-100), icon='|<-')
113
- self.fw = Button(self, handler=lambda v: self.seekdelta(+100), icon='->|')
114
+ self.rw = Button(self, handler=lambda v: self.seekto(-100), icon='|<-')
115
+ self.fw = Button(self, handler=lambda v: self.seekto(+100), icon='->|')
114
116
 
115
117
  self.layout((self.mc,), expand=2)
116
118
  self.layout((self.ss, self.to, self.rw, self.fw,
117
- self.snp, self.crop, self.exp),
118
- expand=0, row=7, style='button', lw=12, cw=0, tw=64)
119
+ self.snp, self.crop, self.rate, self.exp),
120
+ expand=0, row=8, type='vspin', style='button', lw=32, cw=-1, tw=64)
119
121
 
120
122
  self.menu[0:5] = [
121
123
  (1, "&Load file", Icon('open'),
@@ -129,55 +131,51 @@ class Plugin(Layer):
129
131
 
130
132
  self.parent.handler.bind("unknown_format", self.load_media)
131
133
 
132
- self.handler.update({ # DNA<ffmpeg_viewer>
133
- 0 : {
134
- 'play' : (1, ),
135
- 'space pressed' : (1, _F(self.mc.Play)),
134
+ self.handler.update({ # DNA<ffmpeg_viewer>
135
+ None : {
136
+ 'C-left pressed' : (None, _F(self.seekd, -1000)),
137
+ 'C-right pressed' : (None, _F(self.seekd, 1000)),
138
+ },
139
+ 0 : { # MEDIASTATE_STOPPED
140
+ 'play' : (2, ),
141
+ 'space pressed' : (2, _F(self.mc.Play)),
136
142
  },
137
- 1 : {
143
+ 1 : { # MEDIASTATE_PAUSED
138
144
  'stop' : (0, ),
139
- 'pause' : (0, ),
145
+ 'space pressed' : (2, _F(self.mc.Play)),
146
+ },
147
+ 2 : { # MEDIASTATE_PLAYING
148
+ 'stop' : (0, ),
149
+ 'pause' : (1, ),
140
150
  'space pressed' : (1, _F(self.mc.Pause)),
141
151
  },
142
152
  })
143
153
 
144
- self.mc.Bind(wx.media.EVT_MEDIA_PAUSE, partial(self.handler, 'pause'))
145
- self.mc.Bind(wx.media.EVT_MEDIA_PLAY, partial(self.handler, 'play'))
146
- self.mc.Bind(wx.media.EVT_MEDIA_STOP, partial(self.handler, 'stop'))
147
-
148
- ## self.mc.Bind(wx.EVT_KEY_DOWN, self.on_hotkey_down)
149
- ## self.mc.Bind(wx.EVT_KEY_UP, self.on_hotkey_up)
150
- self.mc.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
151
- self.mc.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
154
+ self.mc.Bind(wx.media.EVT_MEDIA_PAUSE, lambda v: self.handler('pause', v))
155
+ self.mc.Bind(wx.media.EVT_MEDIA_PLAY, lambda v: self.handler('play', v))
156
+ self.mc.Bind(wx.media.EVT_MEDIA_STOP, lambda v: self.handler('stop', v))
152
157
 
153
- self.Bind(wx.EVT_SHOW, self.OnShow)
154
-
158
+ self.mc.Bind(wx.EVT_KEY_DOWN, self.on_hotkey_down)
159
+ self.mc.Bind(wx.EVT_KEY_UP, self.on_hotkey_up)
160
+
155
161
  def Destroy(self):
156
- try:
157
- self.parent.handler.unbind("unknown_format", self.load_media)
158
- finally:
159
- return Layer.Destroy(self)
160
-
161
- def OnKeyDown(self, evt):
162
- if self.handler('{} pressed'.format(hotkey(evt)), evt) is None:
163
- evt.Skip()
164
-
165
- def OnKeyUp(self, evt):
166
- if self.handler('{} released'.format(hotkey(evt)), evt) is None:
167
- evt.Skip()
168
-
162
+ self.parent.handler.unbind("unknown_format", self.load_media)
163
+ if self.mc:
164
+ self.mc.Destroy()
165
+ return Layer.Destroy(self)
166
+
169
167
  def OnShow(self, evt):
170
168
  if not evt.IsShown():
171
169
  if self.mc:
172
170
  self.mc.Stop()
173
- evt.Skip()
174
-
171
+ Layer.OnShow(self, evt)
172
+
175
173
  def OnMediaLoaded(self, evt):
176
174
  self.ss.range = (0, self.video_dur, 0.01)
177
175
  self.to.range = (0, self.video_dur, 0.01)
178
176
  self.Show()
179
177
  evt.Skip()
180
-
178
+
181
179
  def load_media(self, path=None):
182
180
  if path is None:
183
181
  with wx.FileDialog(self, "Choose a media file",
@@ -185,11 +183,11 @@ class Plugin(Layer):
185
183
  if dlg.ShowModal() != wx.ID_OK:
186
184
  return None
187
185
  path = dlg.Path
188
- self.mc.Load(path) # -> True (always)
186
+ self.mc.Load(path) # -> True (always)
189
187
  self.info = read_info(path)
190
188
  if self.info:
191
189
  v = next(x for x in self.info['streams'] if x['codec_type'] == 'video')
192
- ## self.video_fps = eval(v['r_frame_rate']) # real base frame rate
190
+ # self.video_fps = eval(v['r_frame_rate']) # real base frame rate
193
191
  self.video_fps = eval(v['avg_frame_rate']) # averaged frame rate
194
192
  self.video_dur = eval(v['duration']) # duration [s]
195
193
  w, h = v['width'], v['height']
@@ -205,64 +203,72 @@ class Plugin(Layer):
205
203
  else:
206
204
  self.message(f"Failed to load file {path!r}.")
207
205
  return False
208
-
209
- DELTA = 1000 # correction ▲理由は不明 (WMP10 backend only?)
210
-
206
+
207
+ ## Correction for seek position. ▲理由は不明 (WMP10 backend only?)
208
+ @property
209
+ def DELTA(self):
210
+ return int(1000 / self.mc.PlaybackRate)
211
+
211
212
  def set_offset(self, tc):
212
213
  """Set offset value by referring to ss/to value."""
213
- try:
214
+ if self._path:
214
215
  self.mc.Seek(self.DELTA + int(tc.value * 1000))
215
- except Exception:
216
- pass
217
-
216
+
218
217
  def get_offset(self, tc):
219
218
  """Get offset value and assigns it to ss/to value."""
220
- try:
219
+ if self._path:
221
220
  tc.value = round(self.mc.Tell()) / 1000
222
- except Exception:
223
- pass
224
-
221
+
225
222
  def set_crop(self):
226
- """Set crop area (W:H:Left:Top) to roi."""
223
+ """Set crop area (W:H:Left:Top) to ROI."""
227
224
  frame = self.graph.frame
228
225
  if frame:
229
226
  try:
230
- w, h, xo, yo = eval(self.crop.Value.replace(':', ','))
231
- xo -= 0.5 # Correction with half-pixel
232
- yo -= 0.5 # to select left-top (not center) position
227
+ w, h, xo, yo = map(float, self.crop.Value.split(':'))
228
+ xo -= 0.5 # Correction with half-pixel offset.
229
+ yo -= 0.5 # Select left-top corner position.
233
230
  nx = xo, xo+w
234
231
  ny = yo, yo+h
235
232
  frame.region = frame.xyfrompixel(nx, ny)
236
- except Exception:
237
- self.message("Failed to evaluate crop text.")
238
-
233
+ except Exception as e:
234
+ self.message("Failed to evaluate crop text;", e)
235
+
239
236
  def get_crop(self):
240
- """Get crop area (W:H:Left:Top) from roi."""
241
- crop = ''
237
+ """Get crop area (W:H:Left:Top) from ROI."""
242
238
  frame = self.graph.frame
243
239
  if frame:
244
- nx, ny = frame.xytopixel(*frame.region)
240
+ nx, ny = frame.xytopixel(frame.region)
245
241
  if nx.size:
246
- xo, yo = nx[0], ny[1]
247
- xp, yp = nx[1], ny[0]
248
- crop = "{}:{}:{}:{}".format(xp-xo, yp-yo, xo, yo)
249
- if self._path and not crop:
250
- crop = "{}:{}:0:0".format(*self.video_size)
251
- self.crop.Value = crop
252
-
253
- def seekdelta(self, offset):
254
- """Seek relative position [ms]."""
255
- if wx.GetKeyState(wx.WXK_SHIFT):
256
- offset /= 10
257
- try:
242
+ xo, xp = nx
243
+ yp, yo = ny
244
+ self.crop.Value = f"{xp-xo}:{yp-yo}:{xo}:{yo}" # (W:H:left:top)
245
+ return
246
+ if self._path:
247
+ self.crop.Value = "{}:{}:0:0".format(*self.video_size)
248
+
249
+ def set_rate(self, rate):
250
+ if self._path:
251
+ self.mc.PlaybackRate = rate
252
+
253
+ def get_rate(self):
254
+ if self._path:
255
+ return self.mc.PlaybackRate
256
+
257
+ def seekto(self, offset):
258
+ """Seek position with offset [ms] from the `to` position."""
259
+ if self._path:
258
260
  t = self.to.value + offset/1000
259
- except Exception as e:
260
- print(e)
261
- else:
262
- if self._path and 0 <= t < self.video_dur:
261
+ if 0 <= t < self.video_dur:
263
262
  self.to.value = round(t, 3)
264
- self.set_offset(self.to) # => seek
265
-
263
+ self.set_offset(self.to)
264
+
265
+ def seekd(self, offset):
266
+ """Seek position with offset [ms] from the current position."""
267
+ if self._path:
268
+ t = self.mc.Tell() + offset
269
+ if 0 <= t < self.video_dur * 1000:
270
+ self.mc.Seek(self.DELTA + t)
271
+
266
272
  def snapshot(self):
267
273
  """Create a snapshot of the current frame.
268
274
  Load the snapshot image into the graph window.
@@ -274,7 +280,7 @@ class Plugin(Layer):
274
280
  buf = capture_video(self._path, t/1000).reshape((h,w,3))
275
281
  name = "{}-ss{}".format(os.path.basename(self._path), int(t))
276
282
  self.graph.load(buf, name)
277
-
283
+
278
284
  def export(self):
279
285
  """Export the cropped / clipped data to a media file."""
280
286
  if not self._path:
mwx/plugins/fft_view.py CHANGED
@@ -3,7 +3,7 @@
3
3
  """
4
4
  import wx
5
5
  import numpy as np
6
- from numpy.fft import fft2,ifft2,fftshift,ifftshift
6
+ from numpy.fft import fft2, ifft2, fftshift, ifftshift
7
7
 
8
8
  from mwx.graphman import Layer
9
9
  from mwx.controls import Param
@@ -13,7 +13,7 @@ def fftcrop(src):
13
13
  """Crop src image in 2**N square ROI centered at (x, y)."""
14
14
  h, w = src.shape
15
15
  m = min(h, w)
16
- n = 1 if m < 2 else 2 ** int(np.log2(m) - 1) # +-m/2
16
+ n = 1 if m < 2 else 2 ** int(np.log2(m) - 1) # +-m/2
17
17
  x, y = w//2, h//2
18
18
  return src[y-n:y+n, x-n:x+n]
19
19
 
@@ -28,30 +28,30 @@ class Plugin(Layer):
28
28
  """
29
29
  menukey = "Plugins/Extensions/&FFT view\tAlt+f"
30
30
  caption = "FFT view"
31
-
31
+
32
32
  def Init(self):
33
33
  self.pchk = wx.CheckBox(self, label="logical unit")
34
34
  self.pchk.Value = True
35
35
 
36
- self.ftor = Param("mask", (2,4,8,16,32,64)) # masking area factor of 1/2
36
+ self.ftor = Param("mask", (2,4,8,16,32,64)) # masking area factor of 1/2
37
37
 
38
38
  self.layout((self.pchk,), title="normal FFT")
39
39
  self.layout((self.ftor,), title="inverse FFT", style='chkbox', tw=32)
40
40
 
41
41
  self.parent.define_key('C-f', self.newfft)
42
42
  self.parent.define_key('C-S-f', self.newifft)
43
-
43
+
44
44
  def Destroy(self):
45
45
  self.parent.undefine_key('C-f')
46
46
  self.parent.undefine_key('C-S-f')
47
47
  return Layer.Destroy(self)
48
-
48
+
49
49
  def newfft(self):
50
50
  """New FFT of graph to output."""
51
51
  frame = self.graph.frame
52
52
  if frame:
53
53
  self.message("FFT execution...")
54
- src = fftcrop(frame.roi)
54
+ src = fftcrop(frame.roi_or_buffer)
55
55
  h, w = src.shape
56
56
 
57
57
  dst = fftshift(fft2(src))
@@ -60,27 +60,25 @@ class Plugin(Layer):
60
60
  u = 1 / w
61
61
  if self.pchk.Value:
62
62
  u /= frame.unit
63
- self.output.load(dst, "*fft of {}*".format(frame.name),
64
- localunit=u)
63
+ self.output.load(dst, f"*fft of {frame.name}*", localunit=u)
65
64
  self.message("\b done")
66
-
65
+
67
66
  def newifft(self):
68
67
  """New inverse FFT of output to graph."""
69
68
  frame = self.output.frame
70
69
  if frame:
71
70
  self.message("iFFT execution...")
72
- src = frame.roi
71
+ src = frame.buffer # Don't crop fft region
73
72
  h, w = src.shape
74
73
 
75
74
  if self.ftor.check:
76
75
  y, x = np.ogrid[-h/2:h/2, -w/2:w/2]
77
- mask = np.hypot(y,x) > w / self.ftor.value
78
- src = src.copy() # apply mask to the copy
76
+ mask = np.hypot(y, x) > w / self.ftor.value
77
+ src = src.copy() # apply mask to the copy
79
78
  src[mask] = 0
80
79
 
81
80
  dst = ifft2(ifftshift(src))
82
81
 
83
82
  self.message("\b Loading image...")
84
- self.graph.load(dst.real, "*ifft of {}*".format(frame.name),
85
- localunit=1/w/frame.unit)
83
+ self.graph.load(dst.real, f"*ifft of {frame.name}*", localunit=1/w/frame.unit)
86
84
  self.message("\b done")