9e65fc4496397e9d3fad454e866c30f7a20614ea
[vlc/bindings/python.git] / generated / 3.0 / examples / cocoavlc.py
1
2 # -*- coding: utf-8 -*-
3
4 # Example of using PyCocoa <http://PyPI.org/project/PyCocoa> to create
5 # a window, table and an application menu to run a video using VLC on
6 # macOS.  The Python-VLC binding <http://PyPI.Python.org/pypi/python-vlc>
7 # and the corresponding VLC App, see <http://www.VideoLan.org/index.html>.
8
9 # This VLC player has only been tested with VLC 2.2.6, 2.2.8, 3.0.1,
10 # 3.0.2 and 3.0.3 and the compatible vlc.py Python-VLC binding using
11 # 64-bit Python 2.7.14, 2.7.15, 3.6.4, 3.6.5 and/or 3.7.0 on macOS
12 # 10.13.4, 10.13.5 and 10.13.6 High Sierra.  This player does not work
13 # using PyPy Python <http://PyPy.org> nor with Intel(R) Python
14 # <http://Software.Intel.com/en-us/distribution-for-python>.
15
16 # MIT License <http://OpenSource.org/licenses/MIT>
17 #
18 # Copyright (C) 2017-2018 -- mrJean1 at Gmail dot com
19 #
20 # Permission is hereby granted, free of charge, to any person obtaining a
21 # copy of this software and associated documentation files (the "Software"),
22 # to deal in the Software without restriction, including without limitation
23 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
24 # and/or sell copies of the Software, and to permit persons to whom the
25 # Software is furnished to do so, subject to the following conditions:
26 #
27 # The above copyright notice and this permission notice shall be included
28 # in all copies or substantial portions of the Software.
29 #
30 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
31 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
33 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
34 # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
35 # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
36 # OTHER DEALINGS IN THE SOFTWARE.
37
38
39 def _PyPI(package):
40     return 'see <http://PyPI.org/project/%s>' % (package,)
41
42 try:  # PYCHOK expected
43     import vlc
44 except ImportError:
45     raise ImportError('no %s module, %s' % ('vlc.py', _PyPI('Python-VLC')))
46 try:
47     from pycocoa import __version__ as __PyCocoa__
48 except ImportError:
49     raise ImportError('no %s module, %s' % ('pycocoa', _PyPI('PyCocoa')))
50 if __PyCocoa__ < '18.08.09':
51     raise ImportError('%s %s or later required, %s' % ('pycocoa', '18.8.9',
52                      _PyPI('PyCocoa')))
53 del _PyPI
54
55 # all imports listed explicitly to help PyChecker
56 from pycocoa import App, app_title, aspect_ratio, bytes2str, closeTables, \
57                     get_printer, Item, ItemSeparator, MediaWindow, Menu, \
58                     OpenPanel, printf, str2bytes, Table, z1000str, zSIstr
59
60 import os
61 import platform
62 import sys
63 from time import strftime, strptime
64 try:
65     from urllib import unquote as mrl_unquote  # Python 2
66 except ImportError:
67     from urllib.parse import unquote as mrl_unquote  # Python 3+
68
69 __all__  = ('AppVLC',)
70 __version__ = '18.08.15'
71
72 _Adjust  = vlc.VideoAdjustOption  # Enum
73 # <http://Wiki.VideoLan.org/Documentation:Modules/adjust>
74 _Adjust3 = {_Adjust.Brightness: (0, 1, 2),
75             _Adjust.Contrast:   (0, 1, 2),
76             _Adjust.Gamma:   (0.01, 1, 10),
77             _Adjust.Hue:        (0, 0, 360),
78             _Adjust.Saturation: (0, 1, 3)}
79 _Argv0   = os.path.splitext(os.path.basename(__file__))[0]
80 _macOS   = platform.mac_ver()[0:3:2]  # PYCHOK false
81 _Movies  = '.m4v', '.mov', '.mp4'  # lower-case file types for movies, videos
82 _Python  = sys.version.split()[0], platform.architecture()[0]  # PYCHOK false
83 _Select  = 'Select a video file from the panel'
84 _VLC_3_  = vlc.__version__.split('.')[0] > '2' and \
85            bytes2str(vlc.libvlc_get_version().split(b'.')[0]) > '2'
86
87
88 def _mspf(fps):
89     # convert frames per second to frame length in millisecs per frame
90     return 1000.0 / (fps or 25)
91
92
93 class AppVLC(App):
94     '''The application with callback methods for C{app..._},
95        C{menu..._} and C{window..._} events.
96
97        Set things up inside the C{.__init__} and C{.appLauched_}
98        methods, start by calling the C{.run} method.
99     '''
100     adjustr   = ''
101     marquee   = None
102     logostr   = ''
103     player    = None
104     raiser    = False
105     resized   = False
106     scale     = 1  # media zoom factor
107     Snapshot  = Item('Snapshot', key='s', alt=True)
108     snapshots = 0
109     Toggle    = None
110     video     = None
111     window    = None
112
113     def __init__(self, video=None,       # video file name
114                        adjustr='',       # vlc.VideoAdjustOption
115                        logostr='',       # vlc.VideoLogoOption
116                        marquee=False,    # vlc.VideoMarqueeOption
117                        raiser=False,     # re-raise errors
118                        title='AppVLC'):  # window title
119         super(AppVLC, self).__init__(raiser=raiser, title=title)
120         self.adjustr = adjustr
121         self.logostr = logostr
122         self.marquee = marquee
123         self.media   = None
124         self.player  = vlc.MediaPlayer()
125         self.raiser  = raiser
126         self.Toggle  = Item('Play', self.menuToggle_, key='p', ctrl=True)
127         self.video   = video
128
129     def appLaunched_(self, app):
130         super(AppVLC, self).appLaunched_(app)
131         self.window = MediaWindow(title=self.video or self.title)
132
133         if self.player:
134             # the VLC player on macOS needs an ObjC NSView
135             self.player.set_nsobject(self.window.NSview)
136             self.media = self.player.set_mrl(self.video)
137
138             if self.adjustr:  # preset video options
139                 for o in self.adjustr.lower().split(','):
140                     o, v = o.strip().split('=')
141                     o = getattr(_Adjust, o.capitalize(), None)
142                     if o is not None:
143                         self._VLCadjust(o, value=v)
144
145             if self.marquee:  # set up marquee
146                 self._VLCmarquee()
147
148             if self.logostr:  # show logo
149                 self._VLClogo(self.logostr)
150
151             menu = Menu('VLC')
152             menu.append(
153                 # the action/method name for each item
154                 # is string 'menu' + item.title + '_',
155                 # without any spaces and trailing dots,
156                 # see function pycocoa.title2action.
157                 Item('Open...', key='o'),
158                 ItemSeparator(),
159                 Item('Info', key='i'),
160                 Item('Close Windows', key='w'),
161                 ItemSeparator(),
162                 self.Toggle,  # Play >< Pause
163                 Item('Rewind', key='r', ctrl=True),
164                 ItemSeparator(),
165                 Item('Zoom In',  key='+'),
166                 Item('Zoom Out', key='-'),
167                 ItemSeparator(),
168                 Item('Faster', key='>', shift=True),
169                 Item('Slower', key='<', shift=True))
170             if _VLC_3_:
171                 menu.append(
172                     Item('Brighter', key='b', shift=True),
173                     Item('Darker',   key='d', shift=True))
174             menu.append(
175                 ItemSeparator(),
176                 Item('Audio Filters', self.menuFilters_, key='a', shift=True),
177                 Item('Video Filters', self.menuFilters_, key='v', shift=True))
178             menu.append(
179                 ItemSeparator(),
180                 self.Snapshot)
181             self.append(menu)
182
183         self.menuPlay_(None)
184         self.window.front()
185         self._resize(True)
186
187     def menuBrighter_(self, item):
188         self._brightness(item, +0.1)
189
190     def menuCloseWindows_(self, item):  # PYCHOK expected
191         # close window(s) from menu Cmd+W
192         # printf('%s %r', 'close_', item)
193         if not closeTables():
194             self.terminate()
195
196     def menuDarker_(self, item):
197         self._brightness(item, -0.1)
198
199     def menuFaster_(self, item):
200         self._rate(item, 1.25)
201
202     def menuFilters_(self, item):
203         try:
204             self.menuPause_(item)
205             # display audio/video filters table
206             t = Table(' Name:150:bold', ' Short:150:Center:center', ' Long:300', 'Help')
207             i = self.player.get_instance()
208             b = item.title.split()[0]
209             for f in sorted(i.audio_filter_list_get() if b == 'Audio'
210                        else i.video_filter_list_get()):
211                 while f and not f[-1]:  # "rstrip" None
212                     f = f[:-1]
213                 t.append(*map(bytes2str, f))
214
215             t.display('VLC %s Filters' % (b,), width=800)
216
217         except Exception as x:
218             if self.raiser:
219                 raise
220             printf('%s', x, nl=1, nt=1)
221
222     def menuInfo_(self, item):
223         try:
224             self.menuPause_(item)
225             # display Python, vlc, libVLC, media info table
226             p = self.player
227             m = p.get_media()
228
229             t = Table(' Name:bold', ' Value:200:Center:center', ' Alt:100')
230             t.append(_Argv0, __version__, '20' + __version__)
231             t.append('PyCocoa', __PyCocoa__, '20' + __PyCocoa__)
232             t.append('Python', *_Python)
233             t.append('macOS', *_macOS)
234             t.separator()
235
236             t.append('vlc.py', vlc.__version__, hex(vlc.hex_version()))
237             b = ' '.join(vlc.build_date.split()[:5])
238             t.append('built', strftime('%x', strptime(b, '%c')), vlc.build_date)
239             t.separator()
240             t.append('libVLC', bytes2str(vlc.libvlc_get_version()), hex(vlc.libvlc_hex_version()))
241             t.append('libVLC', *bytes2str(vlc.libvlc_get_compiler()).split(None, 1))
242             t.separator()
243
244             f = mrl_unquote(bytes2str(m.get_mrl()))
245             t.append('media', os.path.basename(f), f)
246             if f.lower().startswith('file://'):
247                 z = os.path.getsize(f[7:])
248                 t.append('size', z1000str(z), zSIstr(z))
249             t.append('state', str(p.get_state()))
250             f = max(p.get_position(), 0)
251             t.append('position', '%.2f%%' % (f * 100,), f)
252             f = ['%.3f s' % (ms * 1e-3) for ms in (p.get_time(), m.get_duration())]
253             t.append('time/duration', *f)  # both shown in seconds
254             t.append('track/count', z1000str(p.video_get_track()), z1000str(p.video_get_track_count()))
255             t.separator()
256
257             f = p.get_fps()
258             t.append('fps/mspf', '%.6f' % (f,), '%.3f ms' % (_mspf(f),))
259             r = p.get_rate()
260             t.append('rate', '%s%%' % (int(r * 100),), r)
261             w, h = p.video_get_size(0)
262             r = aspect_ratio(w, h) or ''
263             if r:
264                 r = '%s:%s' % r
265             t.append('video size', '%sx%s' % (w, h))  # num=0
266             t.append('aspect ratio', str(p.video_get_aspect_ratio()), r)
267             t.append('scale', '%.3f' % (p.video_get_scale(),), '%.3f' % (self.scale,))
268             t.separator()
269
270             def VLCadjustr2(option):  # get option value
271                 lo, _, hi = _Adjust3[option]
272                 f = self.player.video_get_adjust_float(option)
273                 p = max(0, (f - lo)) * 100.0 / (hi - lo)
274                 t = '%.2f %.1f%%' % (f, p)
275                 # return 2-tuple (value, percentage) as strings
276                 return t.replace('.0%', '%').replace('.00', '.0').split()
277
278             t.append('brightness', *VLCadjustr2(_Adjust.Brightness))
279             t.append('contrast',   *VLCadjustr2(_Adjust.Contrast))
280             t.append('gamma',      *VLCadjustr2(_Adjust.Gamma))
281             t.append('hue',        *VLCadjustr2(_Adjust.Hue))
282             t.append('saturation', *VLCadjustr2(_Adjust.Saturation))
283             t.separator()
284
285             s = vlc.MediaStats()  # re-use single MediaStats instance?
286             if m.get_stats(s):
287
288                 def Kops2bpstr2(bitrate):  # convert Ko/s to bits/sec
289                     # bitrates are conventionally in kilo-octets-per-sec
290                     return zSIstr(bitrate * 8000, B='bps', K=1000).split()
291
292                 t.append('media read',     *zSIstr(s.read_bytes).split())
293                 t.append('input bitrate',  *Kops2bpstr2(s.input_bitrate))
294                 if s.input_bitrate > 0:  # XXX approximate caching, based
295                     # on <http://GitHub.com/oaubert/python-vlc/issues/61>
296                     b = s.read_bytes - s.demux_read_bytes
297                     c = b / (s.input_bitrate * 1000)
298                     t.append('input caching', '%.3f s' % (c,), zSIstr(b))
299                 t.append('demux read',     *zSIstr(s.demux_read_bytes).split())
300                 t.append('stream bitrate', *Kops2bpstr2(s.demux_bitrate))
301
302                 t.append('video decoded', z1000str(s.decoded_video),      'blocks')
303                 t.append('video played',  z1000str(s.displayed_pictures), 'frames')
304                 t.append('video lost',    z1000str(s.lost_pictures),      'frames')
305
306                 t.append('audio decoded', z1000str(s.decoded_audio),   'blocks')
307                 t.append('audio played',  z1000str(s.played_abuffers), 'buffers')
308                 t.append('audio lost',    z1000str(s.lost_abuffers),   'buffers')
309
310             t.display('Python, VLC & Media Information', width=500)
311
312         except Exception as x:
313             if self.raiser:
314                 raise
315             printf('%s', x, nl=1, nt=1)
316
317     def menuOpen_(self, item):
318         # stop the current video and show
319         # the panel to select another video
320         self.menuPause_(item)
321         self.badge.label = 'O'
322         v = OpenPanel(_Select).pick(_Movies)
323         if v:
324             self.window.title = self.video = v
325             self.player.set_mrl(v)
326             self.ratio = 3
327             self._resize(True)
328
329     def menuPause_(self, item, pause=False):  # PYCHOK expected
330         # note, .player.pause() pauses and un-pauses the video,
331         # .player.stop() stops the video and blanks the window
332         if pause or self.player.is_playing():
333             self.player.pause()
334             self.badge.label = 'S'  # stopped
335             self.Toggle.title = 'Play'  # item.title = 'Play'
336
337     def menuPlay_(self, item_or_None):  # PYCHOK expected
338         self.player.play()
339         self.badge.label = 'P'  # Playing
340         self.Toggle.title = 'Pause'  # item.title = 'Pause'
341
342     def menuRewind_(self, item):  # PYCHOK expected
343         self.player.set_position(0.0)
344         # note, can't re-play once at the end
345         # self.menuPlay_()
346         self.badge.label = 'R'
347         self._resize(False)
348
349     def menuSlower_(self, item):
350         self._rate(item, 0.80)
351
352     def menuSnapshot_(self, item):  # PYCHOK expected
353         w = self.lastWindow
354         if w:
355             self.snapshots += 1
356             s = '-'.join((_Argv0,
357                           'snapshot%d' % (self.snapshots,),
358                            w.__class__.__name__))
359             if isinstance(w, MediaWindow):
360                 self.player.video_take_snapshot(0, s + '.png', 0, 0)
361             elif get_printer:  # in PyCocoa 18.08.04+
362                 get_printer().printView(w.PMview, toPDF=s + '.pdf')
363
364     def menuToggle_(self, item):
365         # toggle between Pause and Play
366         if self.player.is_playing():
367             self.menuPause_(item, pause=True)
368         else:
369             self.menuPlay_(item)
370
371     def menuZoomIn_(self, item):
372         self._zoom(item, 1.25)
373
374     def menuZoomOut_(self, item):
375         self._zoom(item, 0.80)
376
377     def windowClose_(self, window):
378         # quit or click of window close button
379         if window is self.window:
380             self.terminate()
381         self.Snapshot.isEnabled = False
382         super(AppVLC, self).windowClose_(window)
383
384     def windowLast_(self, window):
385         self.Snapshot.isEnabled = window.isPrintable or isinstance(window, MediaWindow)
386         super(AppVLC, self).windowLast_(window)
387
388     def windowResize_(self, window):
389         if window is self.window:
390             self._resize(False)
391         super(AppVLC, self).windowResize_(window)
392
393     def _brightness(self, unused, fraction):  # change brightness
394         self._VLCadjust(_Adjust.Brightness, fraction)
395
396     def _contrast(self, unused, fraction):  # change contrast
397         self._VLCadjust(_Adjust.Contrast, fraction)
398
399     def _gamma(self, unused, fraction):  # change gamma
400         self._VLCadjust(_Adjust.Gamma, fraction)
401
402     def _hue(self, unused, fraction):  # change hue
403         self._VLCadjust(_Adjust.Hue, fraction)
404
405     def _saturation(self, unused, fraction):  # change saturation
406         self._VLCadjust(_Adjust.Saturation, fraction)
407
408     def _rate(self, unused, factor):  # change the video rate
409         r = max(0.2, min(10.0, self.player.get_rate() * factor))
410         self.player.set_rate(r)
411
412     def _resize(self, force):  # adjust aspect ratio and marquee height
413         if force or not self.resized:
414             w, h = self.player.video_get_size()
415             # the first call returns (0, 0),
416             # subsequent calls return (w, h)
417             if h > 0 and w > 0:
418                 self.window.ratio = w, h
419                 self.resized = True
420             else:
421                 self.resized = False
422
423     def _VLCadjust(self, option, fraction=0, value=None):
424         # adjust a video option like brightness, contrast, etc.
425         p = self.player
426         # <http://Wiki.VideoLan.org/Documentation:Modules/adjust>
427         # note, .Enable must be set to 1, but once is sufficient
428         p.video_set_adjust_int(_Adjust.Enable, 1)
429         try:
430             lo, _, hi = _Adjust3[option]
431             if value is None:
432                 v = p.video_get_adjust_float(option)
433             else:
434                 v = float(value)
435             if fraction:
436                 v += fraction * (hi - lo)
437             v = float(max(lo, min(hi, v)))
438             p.video_set_adjust_float(option, v)
439         except (KeyError, ValueError):
440             pass
441
442     def _VLClogo(self, logostr):
443         # add a video logo, example "python cocoavlc.py -logo
444         # cone-altglass2.png\;cone-icon-small.png ..."
445         p = self.player
446         g = vlc.VideoLogoOption  # Enum
447         # <http://Wiki.VideoLan.org/Documentation:Modules/logo>
448         p.video_set_logo_int(g.enable, 1)
449         p.video_set_logo_int(g.position, vlc.Position.Center)
450         p.video_set_logo_int(g.opacity, 128)  # 0-255
451         # p.video_set_logo_int(g.delay, 1000)  # millisec
452         # p.video_set_logo_int(g.repeat, -1)  # forever
453         p.video_set_logo_string(g.file, logostr)
454
455     def _VLCmarquee(self, size=36):
456         # put video marquee at the bottom-center
457         p = self.player
458         m = vlc.VideoMarqueeOption  # Enum
459         # <http://Wiki.VideoLan.org/Documentation:Modules/marq>
460         p.video_set_marquee_int(m.Enable, 1)
461         p.video_set_marquee_int(m.Size, int(size))  # pixels
462         p.video_set_marquee_int(m.Position, vlc.Position.Bottom)
463         p.video_set_marquee_int(m.Opacity, 255)  # 0-255
464         p.video_set_marquee_int(m.Timeout, 0)  # millisec, 0==forever
465         p.video_set_marquee_int(m.Refresh, 1000)  # millisec (or sec?)
466         p.video_set_marquee_string(m.Text, str2bytes('%Y-%m-%d  %T  %z'))
467
468     def _zoom(self, unused, factor):
469         # zoom the video rate in/out
470         self.scale *= factor
471         self.player.video_set_scale(self.scale)
472
473
474 if __name__ == '__main__':  # MCCABE 20
475
476     def _Adjustr():
477         a = []  # get adjust default values
478         for n in _Adjust._enum_names_.values():
479             try:
480                 _, d, _ = _Adjust3[getattr(_Adjust, n)]
481                 a.append('%s=%s' % (n, d))
482             except KeyError:  # ignore .Enable
483                 pass
484         return ','.join(sorted(a))
485
486     _adjustr = ''
487     _argv0   = os.path.basename(sys.argv[0])  # _Title
488     _Argv0   = os.path.splitext(_argv0)[0]
489     _logostr = ''
490     _marquee = False
491     _raiser  = False
492     _timeout = None
493     _title   = os.path.splitext(_argv0)[0]
494     _video   = None
495
496     args = sys.argv[1:]
497     while args and args[0].startswith('-'):
498         o = args.pop(0)
499         t = o.lower()
500         if t in ('-h', '--help'):
501             u = ('-h|--help',
502                  '-adjust %s' % (_Adjustr(),))
503             if _VLC_3_:  # require VLC 3+ and libvlc 3+
504                 u += ('-logo <image_file_name>[\\;<image_file_name>...]',
505                       '-marquee')
506             u += ('-raiser',
507                   '-timeout <secs>',
508                   '-title <string>',
509                   '<video_file_name>')
510             printf('usage:  [%s]', ']  ['.join(u), argv0=_argv0)
511             sys.exit(0)
512         elif '-adjust'.startswith(t) and len(t) > 1 and args:
513             _adjustr = args.pop(0)
514         elif '-logo'.startswith(t) and len(t) > 1 and args and _VLC_3_:
515             _logostr = args.pop(0)
516         elif '-marquee'.startswith(t) and len(t) > 1 and _VLC_3_:
517             _marquee = True
518         elif '-raiser'.startswith(t) and len(t) > 1:
519             _raiser = True
520         elif '-timeout'.startswith(t) and len(t) > 3 and args:
521             _timeout = args.pop(0)
522         elif '-title'.startswith(t) and len(t) > 3 and args:
523             _title = args.pop(0).strip()
524         else:
525             printf('invalid option: %s', o, argv0=_argv0)
526             sys.exit(1)
527
528     if _raiser:  # get traceback at SIG- faults or ...
529         try:  # ... use: python3 -X faulthandler ...
530             import faulthandler
531             faulthandler.enable()
532         except ImportError:  # not in Python 3.3-
533             pass
534
535     if args:
536         _video = args.pop(0)
537     else:
538         printf('- %s', _Select.lower(), argv0=_argv0, nl=1, nt=1)
539         app_title(_title)  # App.title when there's no App yet
540         _video = OpenPanel('Select a video file').pick(_Movies)
541
542     if _video:
543         app = AppVLC(video=_video, adjustr=_adjustr,
544                                    logostr=_logostr,
545                                    marquee=_marquee,
546                                     raiser=_raiser,
547                                      title=_title)
548         app.run(timeout=_timeout)  # never returns