1#!/usr/bin/env python
2
3
4#############################################################################
5##
6## Copyright (C) 2013 Riverbank Computing Limited.
7## Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
8## All rights reserved.
9##
10## This file is part of the examples of PyQt.
11##
12## $QT_BEGIN_LICENSE:BSD$
13## You may use this file under the terms of the BSD license as follows:
14##
15## "Redistribution and use in source and binary forms, with or without
16## modification, are permitted provided that the following conditions are
17## met:
18##   * Redistributions of source code must retain the above copyright
19##     notice, this list of conditions and the following disclaimer.
20##   * Redistributions in binary form must reproduce the above copyright
21##     notice, this list of conditions and the following disclaimer in
22##     the documentation and/or other materials provided with the
23##     distribution.
24##   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
25##     the names of its contributors may be used to endorse or promote
26##     products derived from this software without specific prior written
27##     permission.
28##
29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
40## $QT_END_LICENSE$
41##
42#############################################################################
43
44
45from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Q_ARG, QAbstractItemModel,
46        QFileInfo, qFuzzyCompare, QMetaObject, QModelIndex, QObject, Qt,
47        QThread, QTime, QUrl)
48from PyQt5.QtGui import QColor, qGray, QImage, QPainter, QPalette
49from PyQt5.QtMultimedia import (QAbstractVideoBuffer, QMediaContent,
50        QMediaMetaData, QMediaPlayer, QMediaPlaylist, QVideoFrame, QVideoProbe)
51from PyQt5.QtMultimediaWidgets import QVideoWidget
52from PyQt5.QtWidgets import (QApplication, QComboBox, QDialog, QFileDialog,
53        QFormLayout, QHBoxLayout, QLabel, QListView, QMessageBox, QPushButton,
54        QSizePolicy, QSlider, QStyle, QToolButton, QVBoxLayout, QWidget)
55
56
57class VideoWidget(QVideoWidget):
58
59    def __init__(self, parent=None):
60        super(VideoWidget, self).__init__(parent)
61
62        self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
63
64        p = self.palette()
65        p.setColor(QPalette.Window, Qt.black)
66        self.setPalette(p)
67
68        self.setAttribute(Qt.WA_OpaquePaintEvent)
69
70    def keyPressEvent(self, event):
71        if event.key() == Qt.Key_Escape and self.isFullScreen():
72            self.setFullScreen(False)
73            event.accept()
74        elif event.key() == Qt.Key_Enter and event.modifiers() & Qt.Key_Alt:
75            self.setFullScreen(not self.isFullScreen())
76            event.accept()
77        else:
78            super(VideoWidget, self).keyPressEvent(event)
79
80    def mouseDoubleClickEvent(self, event):
81        self.setFullScreen(not self.isFullScreen())
82        event.accept()
83
84
85class PlaylistModel(QAbstractItemModel):
86
87    Title, ColumnCount = range(2)
88
89    def __init__(self, parent=None):
90        super(PlaylistModel, self).__init__(parent)
91
92        self.m_playlist = None
93
94    def rowCount(self, parent=QModelIndex()):
95        return self.m_playlist.mediaCount() if self.m_playlist is not None and not parent.isValid() else 0
96
97    def columnCount(self, parent=QModelIndex()):
98        return self.ColumnCount if not parent.isValid() else 0
99
100    def index(self, row, column, parent=QModelIndex()):
101        return self.createIndex(row, column) if self.m_playlist is not None and not parent.isValid() and row >= 0 and row < self.m_playlist.mediaCount() and column >= 0 and column < self.ColumnCount else QModelIndex()
102
103    def parent(self, child):
104        return QModelIndex()
105
106    def data(self, index, role=Qt.DisplayRole):
107        if index.isValid() and role == Qt.DisplayRole:
108            if index.column() == self.Title:
109                location = self.m_playlist.media(index.row()).canonicalUrl()
110                return QFileInfo(location.path()).fileName()
111
112            return self.m_data[index]
113
114        return None
115
116    def playlist(self):
117        return self.m_playlist
118
119    def setPlaylist(self, playlist):
120        if self.m_playlist is not None:
121            self.m_playlist.mediaAboutToBeInserted.disconnect(
122                    self.beginInsertItems)
123            self.m_playlist.mediaInserted.disconnect(self.endInsertItems)
124            self.m_playlist.mediaAboutToBeRemoved.disconnect(
125                    self.beginRemoveItems)
126            self.m_playlist.mediaRemoved.disconnect(self.endRemoveItems)
127            self.m_playlist.mediaChanged.disconnect(self.changeItems)
128
129        self.beginResetModel()
130        self.m_playlist = playlist
131
132        if self.m_playlist is not None:
133            self.m_playlist.mediaAboutToBeInserted.connect(
134                    self.beginInsertItems)
135            self.m_playlist.mediaInserted.connect(self.endInsertItems)
136            self.m_playlist.mediaAboutToBeRemoved.connect(
137                    self.beginRemoveItems)
138            self.m_playlist.mediaRemoved.connect(self.endRemoveItems)
139            self.m_playlist.mediaChanged.connect(self.changeItems)
140
141        self.endResetModel()
142
143    def beginInsertItems(self, start, end):
144        self.beginInsertRows(QModelIndex(), start, end)
145
146    def endInsertItems(self):
147        self.endInsertRows()
148
149    def beginRemoveItems(self, start, end):
150        self.beginRemoveRows(QModelIndex(), start, end)
151
152    def endRemoveItems(self):
153        self.endRemoveRows()
154
155    def changeItems(self, start, end):
156        self.dataChanged.emit(self.index(start, 0),
157                self.index(end, self.ColumnCount))
158
159
160class PlayerControls(QWidget):
161
162    play = pyqtSignal()
163    pause = pyqtSignal()
164    stop = pyqtSignal()
165    next = pyqtSignal()
166    previous = pyqtSignal()
167    changeVolume = pyqtSignal(int)
168    changeMuting = pyqtSignal(bool)
169    changeRate = pyqtSignal(float)
170
171    def __init__(self, parent=None):
172        super(PlayerControls, self).__init__(parent)
173
174        self.playerState = QMediaPlayer.StoppedState
175        self.playerMuted = False
176
177        self.playButton = QToolButton(clicked=self.playClicked)
178        self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
179
180        self.stopButton = QToolButton(clicked=self.stop)
181        self.stopButton.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
182        self.stopButton.setEnabled(False)
183
184        self.nextButton = QToolButton(clicked=self.next)
185        self.nextButton.setIcon(
186                self.style().standardIcon(QStyle.SP_MediaSkipForward))
187
188        self.previousButton = QToolButton(clicked=self.previous)
189        self.previousButton.setIcon(
190                self.style().standardIcon(QStyle.SP_MediaSkipBackward))
191
192        self.muteButton = QToolButton(clicked=self.muteClicked)
193        self.muteButton.setIcon(
194                self.style().standardIcon(QStyle.SP_MediaVolume))
195
196        self.volumeSlider = QSlider(Qt.Horizontal,
197                sliderMoved=self.changeVolume)
198        self.volumeSlider.setRange(0, 100)
199
200        self.rateBox = QComboBox(activated=self.updateRate)
201        self.rateBox.addItem("0.5x", 0.5)
202        self.rateBox.addItem("1.0x", 1.0)
203        self.rateBox.addItem("2.0x", 2.0)
204        self.rateBox.setCurrentIndex(1)
205
206        layout = QHBoxLayout()
207        layout.setContentsMargins(0, 0, 0, 0)
208        layout.addWidget(self.stopButton)
209        layout.addWidget(self.previousButton)
210        layout.addWidget(self.playButton)
211        layout.addWidget(self.nextButton)
212        layout.addWidget(self.muteButton)
213        layout.addWidget(self.volumeSlider)
214        layout.addWidget(self.rateBox)
215        self.setLayout(layout)
216
217    def state(self):
218        return self.playerState
219
220    def setState(self,state):
221        if state != self.playerState:
222            self.playerState = state
223
224            if state == QMediaPlayer.StoppedState:
225                self.stopButton.setEnabled(False)
226                self.playButton.setIcon(
227                        self.style().standardIcon(QStyle.SP_MediaPlay))
228            elif state == QMediaPlayer.PlayingState:
229                self.stopButton.setEnabled(True)
230                self.playButton.setIcon(
231                        self.style().standardIcon(QStyle.SP_MediaPause))
232            elif state == QMediaPlayer.PausedState:
233                self.stopButton.setEnabled(True)
234                self.playButton.setIcon(
235                        self.style().standardIcon(QStyle.SP_MediaPlay))
236
237    def volume(self):
238        return self.volumeSlider.value()
239
240    def setVolume(self, volume):
241        self.volumeSlider.setValue(volume)
242
243    def isMuted(self):
244        return self.playerMuted
245
246    def setMuted(self, muted):
247        if muted != self.playerMuted:
248            self.playerMuted = muted
249
250            self.muteButton.setIcon(
251                    self.style().standardIcon(
252                            QStyle.SP_MediaVolumeMuted if muted else QStyle.SP_MediaVolume))
253
254    def playClicked(self):
255        if self.playerState in (QMediaPlayer.StoppedState, QMediaPlayer.PausedState):
256            self.play.emit()
257        elif self.playerState == QMediaPlayer.PlayingState:
258            self.pause.emit()
259
260    def muteClicked(self):
261        self.changeMuting.emit(not self.playerMuted)
262
263    def playbackRate(self):
264        return self.rateBox.itemData(self.rateBox.currentIndex())
265
266    def setPlaybackRate(self, rate):
267        for i in range(self.rateBox.count()):
268            if qFuzzyCompare(rate, self.rateBox.itemData(i)):
269                self.rateBox.setCurrentIndex(i)
270                return
271
272        self.rateBox.addItem("%dx" % rate, rate)
273        self.rateBox.setCurrentIndex(self.rateBox.count() - 1)
274
275    def updateRate(self):
276        self.changeRate.emit(self.playbackRate())
277
278
279class FrameProcessor(QObject):
280
281    histogramReady = pyqtSignal(list)
282
283    @pyqtSlot(QVideoFrame, int)
284    def processFrame(self, frame, levels):
285        histogram = [0.0] * levels
286
287        if levels and frame.map(QAbstractVideoBuffer.ReadOnly):
288            pixelFormat = frame.pixelFormat()
289
290            if pixelFormat == QVideoFrame.Format_YUV420P or pixelFormat == QVideoFrame.Format_NV12:
291                # Process YUV data.
292                bits = frame.bits()
293                for idx in range(frame.height() * frame.width()):
294                    histogram[(bits[idx] * levels) >> 8] += 1.0
295            else:
296                imageFormat = QVideoFrame.imageFormatFromPixelFormat(pixelFormat)
297                if imageFormat != QImage.Format_Invalid:
298                    # Process RGB data.
299                    image = QImage(frame.bits(), frame.width(), frame.height(), imageFormat)
300
301                    for y in range(image.height()):
302                        for x in range(image.width()):
303                            pixel = image.pixel(x, y)
304                            histogram[(qGray(pixel) * levels) >> 8] += 1.0
305
306            # Find the maximum value.
307            maxValue = 0.0
308            for value in histogram:
309                if value > maxValue:
310                    maxValue = value
311
312            # Normalise the values between 0 and 1.
313            if maxValue > 0.0:
314                for i in range(len(histogram)):
315                    histogram[i] /= maxValue
316
317            frame.unmap()
318
319        self.histogramReady.emit(histogram)
320
321
322class HistogramWidget(QWidget):
323
324    def __init__(self, parent=None):
325        super(HistogramWidget, self).__init__(parent)
326
327        self.m_levels = 128
328        self.m_isBusy = False
329        self.m_histogram = []
330        self.m_processor = FrameProcessor()
331        self.m_processorThread = QThread()
332
333        self.m_processor.moveToThread(self.m_processorThread)
334        self.m_processor.histogramReady.connect(self.setHistogram)
335
336    def __del__(self):
337        self.m_processorThread.quit()
338        self.m_processorThread.wait(10000)
339
340    def setLevels(self, levels):
341        self.m_levels = levels
342
343    def processFrame(self, frame):
344        if self.m_isBusy:
345            return
346
347        self.m_isBusy = True
348        QMetaObject.invokeMethod(self.m_processor, 'processFrame',
349                Qt.QueuedConnection, Q_ARG(QVideoFrame, frame),
350                Q_ARG(int, self.m_levels))
351
352    @pyqtSlot(list)
353    def setHistogram(self, histogram):
354        self.m_isBusy = False
355        self.m_histogram = list(histogram)
356        self.update()
357
358    def paintEvent(self, event):
359        painter = QPainter(self)
360
361        if len(self.m_histogram) == 0:
362            painter.fillRect(0, 0, self.width(), self.height(),
363                    QColor.fromRgb(0, 0, 0))
364            return
365
366        barWidth = self.width() / float(len(self.m_histogram))
367
368        for i, value in enumerate(self.m_histogram):
369            h = value * self.height()
370            # Draw the level.
371            painter.fillRect(barWidth * i, self.height() - h,
372                    barWidth * (i + 1), self.height(), Qt.red)
373            # Clear the rest of the control.
374            painter.fillRect(barWidth * i, 0, barWidth * (i + 1),
375                    self.height() - h, Qt.black)
376
377
378class Player(QWidget):
379
380    fullScreenChanged = pyqtSignal(bool)
381
382    def __init__(self, playlist, parent=None):
383        super(Player, self).__init__(parent)
384
385        self.colorDialog = None
386        self.trackInfo = ""
387        self.statusInfo = ""
388        self.duration = 0
389
390        self.player = QMediaPlayer()
391        self.playlist = QMediaPlaylist()
392        self.player.setPlaylist(self.playlist)
393
394        self.player.durationChanged.connect(self.durationChanged)
395        self.player.positionChanged.connect(self.positionChanged)
396        self.player.metaDataChanged.connect(self.metaDataChanged)
397        self.playlist.currentIndexChanged.connect(self.playlistPositionChanged)
398        self.player.mediaStatusChanged.connect(self.statusChanged)
399        self.player.bufferStatusChanged.connect(self.bufferingProgress)
400        self.player.videoAvailableChanged.connect(self.videoAvailableChanged)
401        self.player.error.connect(self.displayErrorMessage)
402
403        self.videoWidget = VideoWidget()
404        self.player.setVideoOutput(self.videoWidget)
405
406        self.playlistModel = PlaylistModel()
407        self.playlistModel.setPlaylist(self.playlist)
408
409        self.playlistView = QListView()
410        self.playlistView.setModel(self.playlistModel)
411        self.playlistView.setCurrentIndex(
412                self.playlistModel.index(self.playlist.currentIndex(), 0))
413
414        self.playlistView.activated.connect(self.jump)
415
416        self.slider = QSlider(Qt.Horizontal)
417        self.slider.setRange(0, self.player.duration() / 1000)
418
419        self.labelDuration = QLabel()
420        self.slider.sliderMoved.connect(self.seek)
421
422        self.labelHistogram = QLabel()
423        self.labelHistogram.setText("Histogram:")
424        self.histogram = HistogramWidget()
425        histogramLayout = QHBoxLayout()
426        histogramLayout.addWidget(self.labelHistogram)
427        histogramLayout.addWidget(self.histogram, 1)
428
429        self.probe = QVideoProbe()
430        self.probe.videoFrameProbed.connect(self.histogram.processFrame)
431        self.probe.setSource(self.player)
432
433        openButton = QPushButton("Open", clicked=self.open)
434
435        controls = PlayerControls()
436        controls.setState(self.player.state())
437        controls.setVolume(self.player.volume())
438        controls.setMuted(controls.isMuted())
439
440        controls.play.connect(self.player.play)
441        controls.pause.connect(self.player.pause)
442        controls.stop.connect(self.player.stop)
443        controls.next.connect(self.playlist.next)
444        controls.previous.connect(self.previousClicked)
445        controls.changeVolume.connect(self.player.setVolume)
446        controls.changeMuting.connect(self.player.setMuted)
447        controls.changeRate.connect(self.player.setPlaybackRate)
448        controls.stop.connect(self.videoWidget.update)
449
450        self.player.stateChanged.connect(controls.setState)
451        self.player.volumeChanged.connect(controls.setVolume)
452        self.player.mutedChanged.connect(controls.setMuted)
453
454        self.fullScreenButton = QPushButton("FullScreen")
455        self.fullScreenButton.setCheckable(True)
456
457        self.colorButton = QPushButton("Color Options...")
458        self.colorButton.setEnabled(False)
459        self.colorButton.clicked.connect(self.showColorDialog)
460
461        displayLayout = QHBoxLayout()
462        displayLayout.addWidget(self.videoWidget, 2)
463        displayLayout.addWidget(self.playlistView)
464
465        controlLayout = QHBoxLayout()
466        controlLayout.setContentsMargins(0, 0, 0, 0)
467        controlLayout.addWidget(openButton)
468        controlLayout.addStretch(1)
469        controlLayout.addWidget(controls)
470        controlLayout.addStretch(1)
471        controlLayout.addWidget(self.fullScreenButton)
472        controlLayout.addWidget(self.colorButton)
473
474        layout = QVBoxLayout()
475        layout.addLayout(displayLayout)
476        hLayout = QHBoxLayout()
477        hLayout.addWidget(self.slider)
478        hLayout.addWidget(self.labelDuration)
479        layout.addLayout(hLayout)
480        layout.addLayout(controlLayout)
481        layout.addLayout(histogramLayout)
482
483        self.setLayout(layout)
484
485        if not self.player.isAvailable():
486            QMessageBox.warning(self, "Service not available",
487                    "The QMediaPlayer object does not have a valid service.\n"
488                    "Please check the media service plugins are installed.")
489
490            controls.setEnabled(False)
491            self.playlistView.setEnabled(False)
492            openButton.setEnabled(False)
493            self.colorButton.setEnabled(False)
494            self.fullScreenButton.setEnabled(False)
495
496        self.metaDataChanged()
497
498        self.addToPlaylist(playlist)
499
500    def open(self):
501        fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files")
502        self.addToPlaylist(fileNames)
503
504    def addToPlaylist(self, fileNames):
505        for name in fileNames:
506            fileInfo = QFileInfo(name)
507            if fileInfo.exists():
508                url = QUrl.fromLocalFile(fileInfo.absoluteFilePath())
509                if fileInfo.suffix().lower() == 'm3u':
510                    self.playlist.load(url)
511                else:
512                    self.playlist.addMedia(QMediaContent(url))
513            else:
514                url = QUrl(name)
515                if url.isValid():
516                    self.playlist.addMedia(QMediaContent(url))
517
518    def durationChanged(self, duration):
519        duration /= 1000
520
521        self.duration = duration
522        self.slider.setMaximum(duration)
523
524    def positionChanged(self, progress):
525        progress /= 1000
526
527        if not self.slider.isSliderDown():
528            self.slider.setValue(progress)
529
530        self.updateDurationInfo(progress)
531
532    def metaDataChanged(self):
533        if self.player.isMetaDataAvailable():
534            self.setTrackInfo("%s - %s" % (
535                    self.player.metaData(QMediaMetaData.AlbumArtist),
536                    self.player.metaData(QMediaMetaData.Title)))
537
538    def previousClicked(self):
539        # Go to the previous track if we are within the first 5 seconds of
540        # playback.  Otherwise, seek to the beginning.
541        if self.player.position() <= 5000:
542            self.playlist.previous()
543        else:
544            self.player.setPosition(0)
545
546    def jump(self, index):
547        if index.isValid():
548            self.playlist.setCurrentIndex(index.row())
549            self.player.play()
550
551    def playlistPositionChanged(self, position):
552        self.playlistView.setCurrentIndex(
553                self.playlistModel.index(position, 0))
554
555    def seek(self, seconds):
556        self.player.setPosition(seconds * 1000)
557
558    def statusChanged(self, status):
559        self.handleCursor(status)
560
561        if status == QMediaPlayer.LoadingMedia:
562            self.setStatusInfo("Loading...")
563        elif status == QMediaPlayer.StalledMedia:
564            self.setStatusInfo("Media Stalled")
565        elif status == QMediaPlayer.EndOfMedia:
566            QApplication.alert(self)
567        elif status == QMediaPlayer.InvalidMedia:
568            self.displayErrorMessage()
569        else:
570            self.setStatusInfo("")
571
572    def handleCursor(self, status):
573        if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia):
574            self.setCursor(Qt.BusyCursor)
575        else:
576            self.unsetCursor()
577
578    def bufferingProgress(self, progress):
579        self.setStatusInfo("Buffering %d%" % progress)
580
581    def videoAvailableChanged(self, available):
582        if available:
583            self.fullScreenButton.clicked.connect(
584                    self.videoWidget.setFullScreen)
585            self.videoWidget.fullScreenChanged.connect(
586                    self.fullScreenButton.setChecked)
587
588            if self.fullScreenButton.isChecked():
589                self.videoWidget.setFullScreen(True)
590        else:
591            self.fullScreenButton.clicked.disconnect(
592                    self.videoWidget.setFullScreen)
593            self.videoWidget.fullScreenChanged.disconnect(
594                    self.fullScreenButton.setChecked)
595
596            self.videoWidget.setFullScreen(False)
597
598        self.colorButton.setEnabled(available)
599
600    def setTrackInfo(self, info):
601        self.trackInfo = info
602
603        if self.statusInfo != "":
604            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
605        else:
606            self.setWindowTitle(self.trackInfo)
607
608    def setStatusInfo(self, info):
609        self.statusInfo = info
610
611        if self.statusInfo != "":
612            self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo))
613        else:
614            self.setWindowTitle(self.trackInfo)
615
616    def displayErrorMessage(self):
617        self.setStatusInfo(self.player.errorString())
618
619    def updateDurationInfo(self, currentInfo):
620        duration = self.duration
621        if currentInfo or duration:
622            currentTime = QTime((currentInfo/3600)%60, (currentInfo/60)%60,
623                    currentInfo%60, (currentInfo*1000)%1000)
624            totalTime = QTime((duration/3600)%60, (duration/60)%60,
625                    duration%60, (duration*1000)%1000);
626
627            format = 'hh:mm:ss' if duration > 3600 else 'mm:ss'
628            tStr = currentTime.toString(format) + " / " + totalTime.toString(format)
629        else:
630            tStr = ""
631
632        self.labelDuration.setText(tStr)
633
634    def showColorDialog(self):
635        if self.colorDialog is None:
636            brightnessSlider = QSlider(Qt.Horizontal)
637            brightnessSlider.setRange(-100, 100)
638            brightnessSlider.setValue(self.videoWidget.brightness())
639            brightnessSlider.sliderMoved.connect(
640                    self.videoWidget.setBrightness)
641            self.videoWidget.brightnessChanged.connect(
642                    brightnessSlider.setValue)
643
644            contrastSlider = QSlider(Qt.Horizontal)
645            contrastSlider.setRange(-100, 100)
646            contrastSlider.setValue(self.videoWidget.contrast())
647            contrastSlider.sliderMoved.connect(self.videoWidget.setContrast)
648            self.videoWidget.contrastChanged.connect(contrastSlider.setValue)
649
650            hueSlider = QSlider(Qt.Horizontal)
651            hueSlider.setRange(-100, 100)
652            hueSlider.setValue(self.videoWidget.hue())
653            hueSlider.sliderMoved.connect(self.videoWidget.setHue)
654            self.videoWidget.hueChanged.connect(hueSlider.setValue)
655
656            saturationSlider = QSlider(Qt.Horizontal)
657            saturationSlider.setRange(-100, 100)
658            saturationSlider.setValue(self.videoWidget.saturation())
659            saturationSlider.sliderMoved.connect(
660                    self.videoWidget.setSaturation)
661            self.videoWidget.saturationChanged.connect(
662                    saturationSlider.setValue)
663
664            layout = QFormLayout()
665            layout.addRow("Brightness", brightnessSlider)
666            layout.addRow("Contrast", contrastSlider)
667            layout.addRow("Hue", hueSlider)
668            layout.addRow("Saturation", saturationSlider)
669
670            button = QPushButton("Close")
671            layout.addRow(button)
672
673            self.colorDialog = QDialog(self)
674            self.colorDialog.setWindowTitle("Color Options")
675            self.colorDialog.setLayout(layout)
676
677            button.clicked.connect(self.colorDialog.close)
678
679        self.colorDialog.show()
680
681
682if __name__ == '__main__':
683
684    import sys
685
686    app = QApplication(sys.argv)
687
688    player = Player(sys.argv[1:])
689    player.show()
690
691    sys.exit(app.exec_())
692