python: release 3.0.6109
[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.4 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, 3.7.0, 3.7.1 and/or 3.7.2
12 # on macOS 10.13.4, 10.13.5 and 10.13.6 High Sierra.  This player does
13 # not work with 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__ = '19.01.31'
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 def _ms2str(ms):
94     # convert milliseconds to seconds string
95     return '%.3f s' % (max(ms, 0) * 0.001,)
96
97
98 class AppVLC(App):
99     '''The application with callback methods for C{app..._},
100        C{menu..._} and C{window..._} events.
101
102        Set things up inside the C{.__init__} and C{.appLauched_}
103        methods, start by calling the C{.run} method.
104     '''
105     adjustr   = ''
106     marquee   = None
107     logostr   = ''
108     player    = None
109     raiser    = False
110     resized   = False
111     scale     = 1  # media zoom factor
112     Snapshot  = Item('Snapshot', key='s', alt=True)
113     snapshots = 0
114     Toggle    = None
115     video     = None
116     window    = None
117
118     def __init__(self, video=None,       # video file name
119                        adjustr='',       # vlc.VideoAdjustOption
120                        logostr='',       # vlc.VideoLogoOption
121                        marquee=False,    # vlc.VideoMarqueeOption
122                        raiser=False,     # re-raise errors
123                        title='AppVLC'):  # window title
124         super(AppVLC, self).__init__(raiser=raiser, title=title)
125         self.adjustr = adjustr
126         self.logostr = logostr
127         self.marquee = marquee
128         self.media   = None
129         self.player  = vlc.MediaPlayer()
130         self.raiser  = raiser
131         self.Toggle  = Item('Play', self.menuToggle_, key='p', ctrl=True)
132         self.video   = video
133
134     def appLaunched_(self, app):
135         super(AppVLC, self).appLaunched_(app)
136         self.window = MediaWindow(title=self.video or self.title)
137
138         if self.player:
139             # the VLC player on macOS needs an ObjC NSView
140             self.player.set_nsobject(self.window.NSview)
141             self.media = self.player.set_mrl(self.video)
142
143             if self.adjustr:  # preset video options
144                 for o in self.adjustr.lower().split(','):
145                     o, v = o.strip().split('=')
146                     o = getattr(_Adjust, o.capitalize(), None)
147                     if o is not None:
148                         self._VLCadjust(o, value=v)
149
150             if self.marquee:  # set up marquee
151                 self._VLCmarquee()
152
153             if self.logostr:  # show logo
154                 self._VLClogo(self.logostr)
155
156             menu = Menu('VLC')
157             menu.append(
158                 # the action/method name for each item
159                 # is string 'menu' + item.title + '_',
160                 # without any spaces and trailing dots,
161                 # see function pycocoa.title2action.
162                 Item('Open...', key='o'),
163                 ItemSeparator(),
164                 Item('Info', key='i'),
165                 Item('Close Windows', key='w'),
166                 ItemSeparator(),
167                 self.Toggle,  # Play >< Pause
168                 Item('Rewind', key='r', ctrl=True),
169                 ItemSeparator(),
170                 Item('Zoom In',  key='+'),
171                 Item('Zoom Out', key='-'),
172                 ItemSeparator(),
173                 Item('Faster', key='>', shift=True),
174                 Item('Slower', key='<', shift=True))
175             if _VLC_3_:
176                 menu.append(
177                     Item('Brighter', key='b', shift=True),
178                     Item('Darker',   key='d', shift=True))
179             menu.append(
180                 ItemSeparator(),
181                 Item('Audio Filters', self.menuFilters_, key='a', shift=True),
182                 Item('Video Filters', self.menuFilters_, key='v', shift=True))
183             menu.append(
184                 ItemSeparator(),
185                 self.Snapshot)
186             self.append(menu)
187
188         self.menuPlay_(None)
189         self.window.front()
190         self._resize(True)
191
192     def menuBrighter_(self, item):
193         self._brightness(item, +0.1)
194
195     def menuCloseWindows_(self, item):  # PYCHOK expected
196         # close window(s) from menu Cmd+W
197         # printf('%s %r', 'close_', item)
198         if not closeTables():
199             self.terminate()
200
201     def menuDarker_(self, item):
202         self._brightness(item, -0.1)
203
204     def menuFaster_(self, item):
205         self._rate(item, 1.25)
206
207     def menuFilters_(self, item):
208         try:
209             self.menuPause_(item)
210             # display a table of audio/video filters
211             t = Table(' Name:150:bold', ' Short:150:Center:center', ' Long:300', 'Help')
212             i = self.player.get_instance()
213             b = item.title.split()[0]
214             for f in sorted(i.audio_filter_list_get() if b == 'Audio'
215                        else i.video_filter_list_get()):
216                 while f and not f[-1]:  # "rstrip" None
217                     f = f[:-1]
218                 t.append(*map(bytes2str, f))
219
220             t.display('VLC %s Filters' % (b,), width=800)
221
222         except Exception as x:
223             if self.raiser:
224                 raise
225             printf('%s', x, nl=1, nt=1)
226
227     def menuInfo_(self, item):
228         try:
229             self.menuPause_(item)
230             # display Python, vlc, libVLC, media info table
231             p = self.player
232             m = p.get_media()
233
234             t = Table(' Name:bold', ' Value:200:Center:center', ' Alt:100')
235             t.append(_Argv0, __version__, '20' + __version__)
236             t.append('PyCocoa', __PyCocoa__, '20' + __PyCocoa__)
237             t.append('Python', *_Python)
238             t.append('macOS', *_macOS)
239             t.separator()
240
241             t.append('vlc.py', vlc.__version__, hex(vlc.hex_version()))
242             b = ' '.join(vlc.build_date.split()[:5])
243             t.append('built', strftime('%x', strptime(b, '%c')), vlc.build_date)
244             t.separator()
245             t.append('libVLC', bytes2str(vlc.libvlc_get_version()), hex(vlc.libvlc_hex_version()))
246             t.append('libVLC', *bytes2str(vlc.libvlc_get_compiler()).split(None, 1))
247             t.separator()
248
249             f = mrl_unquote(bytes2str(m.get_mrl()))
250             t.append('media', os.path.basename(f), f)
251             if f.lower().startswith('file://'):
252                 z = os.path.getsize(f[7:])
253                 t.append('size', z1000str(z), zSIstr(z))
254             t.append('state', str(p.get_state()))
255             f = max(p.get_position(), 0)
256             t.append('position/length', '%.2f%%' % (f * 100,), _ms2str(p.get_length()))
257             f = map(_ms2str, (p.get_time(), m.get_duration()))
258             t.append('time/duration', *f)
259             t.append('track/count', z1000str(p.video_get_track()), z1000str(p.video_get_track_count()))
260             t.separator()
261
262             f = p.get_fps()
263             t.append('fps/mspf', '%.6f' % (f,), '%.3f ms' % (_mspf(f),))
264             r = p.get_rate()
265             t.append('rate', '%s%%' % (int(r * 100),), r)
266             w, h = p.video_get_size(0)
267             r = aspect_ratio(w, h) or ''
268             if r:
269                 r = '%s:%s' % r
270             t.append('video size', '%sx%s' % (w, h))  # num=0
271             t.append('aspect ratio', str(p.video_get_aspect_ratio()), r)
272             t.append('scale', '%.3f' % (p.video_get_scale(),), '%.3f' % (self.scale,))
273             t.separator()
274
275             def VLCadjustr2(option):  # get option value
276                 lo, _, hi = _Adjust3[option]
277                 f = self.player.video_get_adjust_float(option)
278                 p = max(0, (f - lo)) * 100.0 / (hi - lo)
279                 t = '%.2f %.1f%%' % (f, p)
280                 # return 2-tuple (value, percentage) as strings
281                 return t.replace('.0%', '%').replace('.00', '.0').split()
282
283             t.append('brightness', *VLCadjustr2(_Adjust.Brightness))
284             t.append('contrast',   *VLCadjustr2(_Adjust.Contrast))
285             t.append('gamma',      *VLCadjustr2(_Adjust.Gamma))
286             t.append('hue',        *VLCadjustr2(_Adjust.Hue))
287             t.append('saturation', *VLCadjustr2(_Adjust.Saturation))
288             t.separator()
289
290             s = vlc.MediaStats()  # re-use single MediaStats instance?
291             if m.get_stats(s):
292
293                 def Kops2bpstr2(bitrate):  # convert Ko/s to bits/sec
294                     # bitrates are conventionally in kilo-octets-per-sec
295                     return zSIstr(bitrate * 8000, B='bps', K=1000).split()
296
297                 t.append('media read',     *zSIstr(s.read_bytes).split())
298                 t.append('input bitrate',  *Kops2bpstr2(s.input_bitrate))
299                 if s.input_bitrate > 0:  # XXX approximate caching, based
300                     # on <http://GitHub.com/oaubert/python-vlc/issues/61>
301                     b = s.read_bytes - s.demux_read_bytes
302                     t.append('input caching', _ms2str(b / s.input_bitrate), zSIstr(b))
303                 t.append('demux read',     *zSIstr(s.demux_read_bytes).split())
304                 t.append('stream bitrate', *Kops2bpstr2(s.demux_bitrate))
305
306                 t.append('video decoded', z1000str(s.decoded_video),      'blocks')
307                 t.append('video played',  z1000str(s.displayed_pictures), 'frames')
308                 t.append('video lost',    z1000str(s.lost_pictures),      'frames')
309
310                 t.append('audio decoded', z1000str(s.decoded_audio),   'blocks')
311                 t.append('audio played',  z1000str(s.played_abuffers), 'buffers')
312                 t.append('audio lost',    z1000str(s.lost_abuffers),   'buffers')
313
314             t.display('Python, VLC & Media Information', width=500)
315
316         except Exception as x:
317             if self.raiser:
318                 raise
319             printf('%s', x, nl=1, nt=1)
320
321     def menuOpen_(self, item):
322         # stop the current video and show
323         # the panel to select another video
324         self.menuPause_(item)
325         self.badge.label = 'O'
326         v = OpenPanel(_Select).pick(_Movies)
327         if v:
328             self.window.title = self.video = v
329             self.player.set_mrl(v)
330             self.ratio = 3
331             self._resize(True)
332
333     def menuPause_(self, item, pause=False):  # PYCHOK expected
334         # note, .player.pause() pauses and un-pauses the video,
335         # .player.stop() stops the video and blanks the window
336         if pause or self.player.is_playing():
337             self.player.pause()
338             self.badge.label = 'S'  # stopped
339             self.Toggle.title = 'Play'  # item.title = 'Play'
340
341     def menuPlay_(self, item_or_None):  # PYCHOK expected
342         self.player.play()
343         self.badge.label = 'P'  # Playing
344         self.Toggle.title = 'Pause'  # item.title = 'Pause'
345
346     def menuRewind_(self, item):  # PYCHOK expected
347         self.player.set_position(0.0)
348         # note, can't re-play once at the end
349         # self.menuPlay_()
350         self.badge.label = 'R'
351         self._resize(False)
352
353     def menuSlower_(self, item):
354         self._rate(item, 0.80)
355
356     def menuSnapshot_(self, item):  # PYCHOK expected
357         w = self.lastWindow
358         if w:
359             self.snapshots += 1
360             s = '-'.join((_Argv0,
361                           'snapshot%d' % (self.snapshots,),
362                            w.__class__.__name__))
363             if isinstance(w, MediaWindow):
364                 self.player.video_take_snapshot(0, s + '.png', 0, 0)
365             elif get_printer:  # in PyCocoa 18.08.04+
366                 get_printer().printView(w.PMview, toPDF=s + '.pdf')
367
368     def menuToggle_(self, item):
369         # toggle between Pause and Play
370         if self.player.is_playing():
371             self.menuPause_(item, pause=True)
372         else:
373             self.menuPlay_(item)
374
375     def menuZoomIn_(self, item):
376         self._zoom(item, 1.25)
377
378     def menuZoomOut_(self, item):
379         self._zoom(item, 0.80)
380
381     def windowClose_(self, window):
382         # quit or click of window close button
383         if window is self.window:
384             self.terminate()
385         self.Snapshot.isEnabled = False
386         super(AppVLC, self).windowClose_(window)
387
388     def windowLast_(self, window):
389         self.Snapshot.isEnabled = window.isPrintable or isinstance(window, MediaWindow)
390         super(AppVLC, self).windowLast_(window)
391
392     def windowResize_(self, window):
393         if window is self.window:
394             self._resize(False)
395         super(AppVLC, self).windowResize_(window)
396
397     def _brightness(self, unused, fraction):  # change brightness
398         self._VLCadjust(_Adjust.Brightness, fraction)
399
400     def _contrast(self, unused, fraction):  # change contrast
401         self._VLCadjust(_Adjust.Contrast, fraction)
402
403     def _gamma(self, unused, fraction):  # change gamma
404         self._VLCadjust(_Adjust.Gamma, fraction)
405
406     def _hue(self, unused, fraction):  # change hue
407         self._VLCadjust(_Adjust.Hue, fraction)
408
409     def _saturation(self, unused, fraction):  # change saturation
410         self._VLCadjust(_Adjust.Saturation, fraction)
411
412     def _rate(self, unused, factor):  # change the video rate
413         r = max(0.2, min(10.0, self.player.get_rate() * factor))
414         self.player.set_rate(r)
415
416     def _resize(self, force):  # adjust aspect ratio and marquee height
417         if force or not self.resized:
418             w, h = self.player.video_get_size()
419             # the first call returns (0, 0),
420             # subsequent calls return (w, h)
421             if h > 0 and w > 0:
422                 self.window.ratio = w, h
423                 self.resized = True
424             else:
425                 self.resized = False
426
427     def _VLCadjust(self, option, fraction=0, value=None):
428         # adjust a video option like brightness, contrast, etc.
429         p = self.player
430         # <http://Wiki.VideoLan.org/Documentation:Modules/adjust>
431         # note, .Enable must be set to 1, but once is sufficient
432         p.video_set_adjust_int(_Adjust.Enable, 1)
433         try:
434             lo, _, hi = _Adjust3[option]
435             if value is None:
436                 v = p.video_get_adjust_float(option)
437             else:
438                 v = float(value)
439             if fraction:
440                 v += fraction * (hi - lo)
441             v = float(max(lo, min(hi, v)))
442             p.video_set_adjust_float(option, v)
443         except (KeyError, ValueError):
444             pass
445
446     def _VLClogo(self, logostr):
447         # add a video logo, example "python cocoavlc.py -logo
448         # cone-altglass2.png\;cone-icon-small.png ..."
449         p = self.player
450         g = vlc.VideoLogoOption  # Enum
451         # <http://Wiki.VideoLan.org/Documentation:Modules/logo>
452         p.video_set_logo_int(g.enable, 1)
453         p.video_set_logo_int(g.position, vlc.Position.Center)
454         p.video_set_logo_int(g.opacity, 128)  # 0-255
455         # p.video_set_logo_int(g.delay, 1000)  # millisec
456         # p.video_set_logo_int(g.repeat, -1)  # forever
457         p.video_set_logo_string(g.file, logostr)
458
459     def _VLCmarquee(self, size=36):
460         # put video marquee at the bottom-center
461         p = self.player
462         m = vlc.VideoMarqueeOption  # Enum
463         # <http://Wiki.VideoLan.org/Documentation:Modules/marq>
464         p.video_set_marquee_int(m.Enable, 1)
465         p.video_set_marquee_int(m.Size, int(size))  # pixels
466         p.video_set_marquee_int(m.Position, vlc.Position.Bottom)
467         p.video_set_marquee_int(m.Opacity, 255)  # 0-255
468         p.video_set_marquee_int(m.Timeout, 0)  # millisec, 0==forever
469         p.video_set_marquee_int(m.Refresh, 1000)  # millisec (or sec?)
470         p.video_set_marquee_string(m.Text, str2bytes('%Y-%m-%d  %T  %z'))
471
472     def _zoom(self, unused, factor):
473         # zoom the video rate in/out
474         self.scale *= factor
475         self.player.video_set_scale(self.scale)
476
477
478 if __name__ == '__main__':  # MCCABE 20
479
480     def _Adjustr():
481         a = []  # get adjust default values
482         for n in _Adjust._enum_names_.values():
483             try:
484                 _, d, _ = _Adjust3[getattr(_Adjust, n)]
485                 a.append('%s=%s' % (n, d))
486             except KeyError:  # ignore .Enable
487                 pass
488         return ','.join(sorted(a))
489
490     _adjustr = ''
491     _argv0   = os.path.basename(sys.argv[0])  # _Title
492     _Argv0   = os.path.splitext(_argv0)[0]
493     _logostr = ''
494     _marquee = False
495     _raiser  = False
496     _timeout = None
497     _title   = os.path.splitext(_argv0)[0]
498     _video   = None
499
500     args = sys.argv[1:]
501     while args and args[0].startswith('-'):
502         o = args.pop(0)
503         t = o.lower()
504         if t in ('-h', '--help'):
505             u = ('-h|--help',
506                  '-adjust %s' % (_Adjustr(),))
507             if _VLC_3_:  # require VLC 3+ and libvlc 3+
508                 u += ('-logo <image_file_name>[\\;<image_file_name>...]',
509                       '-marquee')
510             u += ('-raiser',
511                   '-timeout <secs>',
512                   '-title <string>',
513                   '<video_file_name>')
514             printf('usage:  [%s]', ']  ['.join(u), argv0=_argv0)
515             sys.exit(0)
516         elif '-adjust'.startswith(t) and len(t) > 1 and args:
517             _adjustr = args.pop(0)
518         elif '-logo'.startswith(t) and len(t) > 1 and args and _VLC_3_:
519             _logostr = args.pop(0)
520         elif '-marquee'.startswith(t) and len(t) > 1 and _VLC_3_:
521             _marquee = True
522         elif '-raiser'.startswith(t) and len(t) > 1:
523             _raiser = True
524         elif '-timeout'.startswith(t) and len(t) > 3 and args:
525             _timeout = args.pop(0)
526         elif '-title'.startswith(t) and len(t) > 3 and args:
527             _title = args.pop(0).strip()
528         else:
529             printf('invalid option: %s', o, argv0=_argv0)
530             sys.exit(1)
531
532     if _raiser:  # get traceback at SIG- faults or ...
533         try:  # ... use: python3 -X faulthandler ...
534             import faulthandler
535             faulthandler.enable()
536         except ImportError:  # not in Python 3.3-
537             pass
538
539     if args:
540         _video = args.pop(0)
541     else:
542         printf('- %s', _Select.lower(), argv0=_argv0, nl=1, nt=1)
543         app_title(_title)  # App.title when there's no App yet
544         _video = OpenPanel('Select a video file').pick(_Movies)
545
546     if _video:
547         app = AppVLC(video=_video, adjustr=_adjustr,
548                                    logostr=_logostr,
549                                    marquee=_marquee,
550                                     raiser=_raiser,
551                                      title=_title)
552         app.run(timeout=_timeout)  # never returns