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