python: release 3.0.6109
[vlc/bindings/python.git] / generated / 3.0 / examples / video_sync / main.py
1 #
2 # PyQt5-based video-sync example for VLC Python bindings
3 # Copyright (C) 2009-2010 the VideoLAN team
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
18 #
19 """
20 A VLC python bindings player implemented with PyQt5 that is meant to be utilized
21 as the "master" player that controls all "slave players".
22
23 Author: Saveliy Yusufov, Columbia University, sy2685@columbia.edu
24 Date: 25 January 2019
25 """
26
27 import platform
28 import queue
29 import os
30 import subprocess
31 import sys
32
33 from PyQt5 import QtWidgets, QtGui, QtCore
34 import vlc
35 from network import Server
36
37
38 class Player(QtWidgets.QMainWindow):
39     """A "master" Media Player using VLC and Qt
40     """
41
42     def __init__(self, master=None):
43         QtWidgets.QMainWindow.__init__(self, master)
44         self.setWindowTitle("Main Media Player")
45
46         # Create a basic vlc instance
47         self.instance = vlc.Instance()
48
49         self.media = None
50
51         # Create an empty vlc media player
52         self.mediaplayer = self.instance.media_player_new()
53
54         self.create_ui()
55         self.data_queue = queue.Queue()
56         self.is_paused = False
57
58         self.timer = QtCore.QTimer(self)
59         self.timer.setInterval(100)
60         self.timer.timeout.connect(self.update_ui)
61         self.timer.timeout.connect(self.update_time_label)
62
63     def create_ui(self):
64         """Set up the user interface, signals & slots
65         """
66         self.widget = QtWidgets.QWidget(self)
67         self.setCentralWidget(self.widget)
68
69         # In this widget, the video will be drawn
70         if platform.system() == "Darwin":  # for MacOS
71             self.videoframe = QtWidgets.QMacCocoaViewContainer(0)
72         else:
73             self.videoframe = QtWidgets.QFrame()
74
75         self.palette = self.videoframe.palette()
76         self.palette.setColor(QtGui.QPalette.Window, QtGui.QColor(0, 0, 0))
77         self.videoframe.setPalette(self.palette)
78         self.videoframe.setAutoFillBackground(True)
79
80         # Create the time display
81         self.timelabel = QtWidgets.QLabel("00:00:00", self)
82
83         # Create the position slider (QSlider)
84         self.positionslider = QtWidgets.QSlider(QtCore.Qt.Horizontal, self)
85         self.positionslider.setToolTip("Position")
86         self.positionslider.setMaximum(1000)
87         self.positionslider.sliderMoved.connect(self.set_position)
88         # self.positionslider.sliderPressed.connect(self.set_position)
89         self.positionslider.sliderMoved.connect(self.update_time_label)
90
91         # Create the "previous frame" button
92         self.previousframe = QtWidgets.QPushButton()
93         self.previousframe.setFixedWidth(25)
94         self.previousframe.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaSkipBackward))
95         self.previousframe.clicked.connect(self.on_previous_frame)
96
97         # Create the play button and connect it to the play/pause function
98         self.playbutton = QtWidgets.QPushButton()
99         self.playbutton.setFixedWidth(40)
100         self.playbutton.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaPlay))
101         self.playbutton.clicked.connect(self.play_pause)
102
103         # Create the "next frame" button
104         self.nextframe = QtWidgets.QPushButton()
105         self.nextframe.setFixedWidth(25)
106         self.nextframe.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaSkipForward))
107         self.nextframe.clicked.connect(self.on_next_frame)
108
109         # Create the "Playback rate" label
110         self.pb_rate_label = QtWidgets.QLabel("Playback rate: {}x".format(self.mediaplayer.get_rate()), self)
111
112         # Create the "decrease playback rate" button
113         self.decr_pb_rate = QtWidgets.QPushButton()
114         self.decr_pb_rate.setFixedWidth(30)
115         self.decr_pb_rate.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaSeekBackward))
116         self.decr_pb_rate.clicked.connect(self.decr_mov_play_rate)
117
118         # Create the stop button and connect it to the stop function
119         self.stopbutton = QtWidgets.QPushButton()
120         self.stopbutton.setFixedWidth(30)
121         self.stopbutton.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaStop))
122         self.stopbutton.clicked.connect(self.stop)
123
124         # Create the "increase playback rate" button
125         self.incr_pb_rate = QtWidgets.QPushButton()
126         self.incr_pb_rate.setFixedWidth(30)
127         self.incr_pb_rate.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaSeekForward))
128         self.incr_pb_rate.clicked.connect(self.incr_mov_play_rate)
129
130         self.top_control_box = QtWidgets.QHBoxLayout()
131
132         # Add the time and position slider to the 1st controls layout
133         self.top_control_box.addWidget(self.timelabel)
134         self.top_control_box.addWidget(self.positionslider)
135
136         self.bottom_control_box = QtWidgets.QHBoxLayout()
137
138         # Add the buttons to the 2nd controls layout
139         self.bottom_control_box.addWidget(self.previousframe)
140         self.bottom_control_box.addWidget(self.playbutton)
141         self.bottom_control_box.addWidget(self.nextframe)
142         self.bottom_control_box.addWidget(self.pb_rate_label)
143         self.bottom_control_box.addWidget(self.decr_pb_rate)
144         self.bottom_control_box.addWidget(self.stopbutton)
145         self.bottom_control_box.addWidget(self.incr_pb_rate)
146
147         self.vboxlayout = QtWidgets.QVBoxLayout()
148
149         self.vboxlayout.addWidget(self.videoframe)
150         self.vboxlayout.addLayout(self.top_control_box)
151         self.vboxlayout.addLayout(self.bottom_control_box)
152
153         self.widget.setLayout(self.vboxlayout)
154
155         menu_bar = self.menuBar()
156
157         # File menu
158         file_menu = menu_bar.addMenu("File")
159
160         # Create submenu to start new processes from file menu
161         new_menu = QtWidgets.QMenu("Launch", self)
162         file_menu.addMenu(new_menu)
163         new_video_action = QtWidgets.QAction("New Video", self)
164         new_menu.addAction(new_video_action)
165         new_video_action.triggered.connect(on_new_video)
166
167         # Create actions to load a new media file and to close the app
168         open_action = QtWidgets.QAction("Load Video", self)
169         close_action = QtWidgets.QAction("Close App", self)
170         file_menu.addAction(open_action)
171         file_menu.addAction(close_action)
172         open_action.triggered.connect(self.open_file)
173         close_action.triggered.connect(sys.exit)
174
175     def play_pause(self):
176         """Toggle play/pause status
177         """
178         if self.mediaplayer.is_playing():
179             signal = 'p'
180             self.mediaplayer.pause()
181             self.playbutton.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaPlay))
182             self.is_paused = True
183             self.timer.stop()
184         else:
185             if self.mediaplayer.play() == -1:
186                 self.open_file()
187                 return
188
189             signal = 'P'
190             self.mediaplayer.play()
191             self.playbutton.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaPause))
192             self.timer.start()
193             self.is_paused = False
194
195         # Reset the queue & send the appropriate signal, i.e., play/pause
196         self.data_queue.queue.clear()
197         self.data_queue.put('d')
198         self.data_queue.put(signal)
199
200     def stop(self):
201         """Stop player
202         """
203         self.mediaplayer.stop()
204         self.playbutton.setIcon(self.style().standardIcon(QtWidgets.QStyle.SP_MediaPlay))
205
206         # Reset the time label back to 00:00:00
207         time = QtCore.QTime(0, 0, 0, 0)
208         self.timelabel.setText(time.toString())
209
210         # Reset the queue
211         self.data_queue.queue.clear()
212         self.data_queue.put('d')
213         self.data_queue.put('S')
214
215         # Reset the media position slider
216         self.positionslider.setValue(0)
217
218         self.timer.stop()
219
220     def on_next_frame(self):
221         """Go forward one frame.
222
223             The Python VLC binding next_frame function causes a:
224
225             "direct3d11 vout display error: SetThumbNailClip failed"
226
227             error when next_frame is called while the video is playing,
228             so we are using our own fucntion to get the next frame.
229         """
230         # self.mediaplayer.next_frame()
231         next_frame_time = self.mediaplayer.get_time() + self.mspf()
232
233         # Reset the queue & put the next frame's time into the queue
234         self.data_queue.queue.clear()
235         self.data_queue.put('d')
236         self.data_queue.put(next_frame_time)
237         self.update_time_label()
238         self.mediaplayer.set_time(next_frame_time)
239
240     def on_previous_frame(self):
241         """Go backward one frame"""
242         next_frame_time = self.mediaplayer.get_time() - self.mspf()
243
244         # Reset the queue & put the next frame's time into the queue
245         self.data_queue.queue.clear()
246         self.data_queue.put('d')
247         self.data_queue.put(next_frame_time)
248         self.update_time_label()
249         self.mediaplayer.set_time(next_frame_time)
250
251     def mspf(self):
252         """Milliseconds per frame"""
253         return int(1000 // (self.mediaplayer.get_fps() or 25))
254
255     def incr_mov_play_rate(self):
256         """Increase the movie play rate by a factor of 2."""
257         if self.mediaplayer.get_rate() >= 64:
258             return
259
260         rate = self.mediaplayer.get_rate() * 2
261         result = self.mediaplayer.set_rate(rate)
262         if result == 0:
263             self.data_queue.queue.clear()
264             self.data_queue.put('d')
265             self.data_queue.put('>')
266             self.update_pb_rate_label()
267
268     def decr_mov_play_rate(self):
269         """Decrease the movie play rate by a factor of 2."""
270         if self.mediaplayer.get_rate() <= 0.125:
271             return
272
273         rate = self.mediaplayer.get_rate() * 0.5
274         result = self.mediaplayer.set_rate(rate)
275         if result == 0:
276             self.data_queue.queue.clear()
277             self.data_queue.put('d')
278             self.data_queue.put('<')
279             self.update_pb_rate_label()
280
281     def open_file(self):
282         """Open a media file in a MediaPlayer
283         """
284         dialog_txt = "Choose Media File"
285         filename = QtWidgets.QFileDialog.getOpenFileName(self, dialog_txt, os.path.expanduser('~'))
286         if not filename[0]:
287             return
288
289         # getOpenFileName returns a tuple, so use only the actual file name
290         self.media = self.instance.media_new(filename[0])
291
292         # Put the media in the media player
293         self.mediaplayer.set_media(self.media)
294
295         # Parse the metadata of the file
296         self.media.parse()
297
298         # Set the title of the track as window title
299         self.setWindowTitle("Main Media Player: {}".format(self.media.get_meta(0)))
300
301         # The media player has to be 'connected' to the QFrame (otherwise the
302         # video would be displayed in it's own window). This is platform
303         # specific, so we must give the ID of the QFrame (or similar object) to
304         # vlc. Different platforms have different functions for this.
305         if platform.system() == "Linux":  # for Linux using the X Server
306             self.mediaplayer.set_xwindow(int(self.videoframe.winId()))
307         elif platform.system() == "Windows":  # for Windows
308             self.mediaplayer.set_hwnd(int(self.videoframe.winId()))
309         elif platform.system() == "Darwin":  # for MacOS
310             self.mediaplayer.set_nsobject(int(self.videoframe.winId()))
311
312         self.play_pause()
313
314     def set_position(self):
315         """Set the movie position according to the position slider.
316
317         The vlc MediaPlayer needs a float value between 0 and 1, Qt uses
318         integer variables, so you need a factor; the higher the factor,
319         the more precise are the results (1000 should suffice).
320         """
321         # Set the media position to where the slider was dragged
322         self.timer.stop()
323         pos = self.positionslider.value()
324
325         if pos >= 0:
326             self.data_queue.queue.clear()
327             self.data_queue.put('d')
328             current_time = self.mediaplayer.get_time()
329
330             # If the player is stopped, do not attempt to send a -1!!!
331             if current_time == -1:
332                 self.timer.start()
333                 return
334             self.data_queue.put(current_time)
335
336         self.mediaplayer.set_position(pos * .001)
337         self.timer.start()
338
339     def update_ui(self):
340         """Updates the user interface"""
341
342         # Set the slider's position to its corresponding media position
343         # Note that the setValue function only takes values of type int,
344         # so we must first convert the corresponding media position.
345         media_pos = int(self.mediaplayer.get_position() * 1000)
346         self.positionslider.setValue(media_pos)
347
348         if media_pos >= 0 and self.mediaplayer.is_playing():
349             current_time = self.mediaplayer.get_time()
350             self.data_queue.put(current_time)
351         else:
352             self.data_queue.queue.clear()
353
354         # No need to call this function if nothing is played
355         if not self.mediaplayer.is_playing():
356             self.timer.stop()
357
358             # After the video finished, the play button stills shows "Pause",
359             # which is not the desired behavior of a media player.
360             # This fixes that "bug".
361             if not self.is_paused:
362                 self.stop()
363
364     def update_time_label(self):
365         mtime = QtCore.QTime(0, 0, 0, 0)
366         self.time = mtime.addMSecs(self.mediaplayer.get_time())
367         self.timelabel.setText(self.time.toString())
368
369     def update_pb_rate_label(self):
370         self.pb_rate_label.setText("Playback rate: {}x".format(str(self.mediaplayer.get_rate())))
371
372
373 def on_new_video():
374     """Launches a new PyQt5-based "mini" media player
375     """
376     if platform.system() == "Windows":
377         subprocess.Popen(["python", "mini_player.py"], shell=True)
378     else:
379         subprocess.Popen(["python", "mini_player.py"])
380
381
382 def main():
383     """Entry point for our simple vlc player
384     """
385     app = QtWidgets.QApplication(sys.argv)
386     player = Player()
387
388     _ = Server("localhost", 10000, player.data_queue)
389
390     player.show()
391     player.resize(640, 480)
392     sys.exit(app.exec_())
393
394
395 if __name__ == "__main__":
396     main()