class VidCutter(QWidget): def __init__(self, parent): super(VidCutter, self).__init__(parent) self.parent = parent self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.videoWidget = VideoWidget() self.videoService = VideoService(self) QFontDatabase.addApplicationFont( os.path.join(self.getAppPath(), 'fonts', 'DroidSansMono.ttf')) QFontDatabase.addApplicationFont( os.path.join(self.getAppPath(), 'fonts', 'HelveticaNeue.ttf')) qApp.setFont(QFont('Helvetica Neue', 10)) self.clipTimes = [] self.inCut = False self.movieFilename = '' self.movieLoaded = False self.timeformat = 'hh:mm:ss' self.finalFilename = '' self.totalRuntime = 0 self.initIcons() self.initActions() self.toolbar = QToolBar( floatable=False, movable=False, iconSize=QSize(28, 28), toolButtonStyle=Qt.ToolButtonTextUnderIcon, styleSheet= 'QToolBar QToolButton { min-width:82px; margin-left:10px; margin-right:10px; font-size:14px; }' ) self.initToolbar() self.aboutMenu, self.cliplistMenu = QMenu(), QMenu() self.initMenus() self.seekSlider = VideoSlider(parent=self, sliderMoved=self.setPosition) self.seekSlider.installEventFilter(self) self.initNoVideo() self.cliplist = QListWidget( sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding), contextMenuPolicy=Qt.CustomContextMenu, uniformItemSizes=True, iconSize=QSize(100, 700), dragDropMode=QAbstractItemView.InternalMove, alternatingRowColors=True, customContextMenuRequested=self.itemMenu, styleSheet='QListView::item { margin:10px 5px; }') self.cliplist.setFixedWidth(185) self.cliplist.model().rowsMoved.connect(self.syncClipList) listHeader = QLabel(pixmap=QPixmap( os.path.join(self.getAppPath(), 'images', 'clipindex.png'), 'PNG'), alignment=Qt.AlignCenter) listHeader.setStyleSheet( '''padding:5px; padding-top:8px; border:1px solid #b9b9b9; border-bottom:none; background-color:qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #FFF, stop: 0.5 #EAEAEA, stop: 0.6 #EAEAEA stop:1 #FFF);''' ) self.runtimeLabel = QLabel('<div align="right">00:00:00</div>', textFormat=Qt.RichText) self.runtimeLabel.setStyleSheet( '''font-family:Droid Sans Mono; font-size:10pt; color:#FFF; background:rgb(106, 69, 114) url(:images/runtime.png) no-repeat left center; padding:2px; padding-right:8px; border:1px solid #b9b9b9; border-top:none;''' ) self.clipindexLayout = QVBoxLayout(spacing=0) self.clipindexLayout.setContentsMargins(0, 0, 0, 0) self.clipindexLayout.addWidget(listHeader) self.clipindexLayout.addWidget(self.cliplist) self.clipindexLayout.addWidget(self.runtimeLabel) self.videoLayout = QHBoxLayout() self.videoLayout.setContentsMargins(0, 0, 0, 0) self.videoLayout.addWidget(self.novideoWidget) self.videoLayout.addLayout(self.clipindexLayout) self.timeCounter = QLabel('00:00:00 / 00:00:00', autoFillBackground=True, alignment=Qt.AlignCenter, sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed)) self.timeCounter.setStyleSheet( 'color:#FFF; background:#000; font-family:Droid Sans Mono; font-size:10.5pt; padding:4px;' ) videoplayerLayout = QVBoxLayout(spacing=0) videoplayerLayout.setContentsMargins(0, 0, 0, 0) videoplayerLayout.addWidget(self.videoWidget) videoplayerLayout.addWidget(self.timeCounter) self.videoplayerWidget = QWidget(self, visible=False) self.videoplayerWidget.setLayout(videoplayerLayout) self.menuButton = QPushButton(icon=self.aboutIcon, flat=True, toolTip='About', statusTip='About', iconSize=QSize(24, 24), cursor=Qt.PointingHandCursor) self.menuButton.setMenu(self.aboutMenu) self.muteButton = QPushButton(icon=self.unmuteIcon, flat=True, toolTip='Mute', statusTip='Toggle audio mute', cursor=Qt.PointingHandCursor, clicked=self.muteAudio) self.volumeSlider = QSlider(Qt.Horizontal, toolTip='Volume', statusTip='Adjust volume level', cursor=Qt.PointingHandCursor, value=50, sizePolicy=QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Minimum), minimum=0, maximum=100, sliderMoved=self.setVolume) self.volumeSlider.setStyleSheet( '''QSlider::groove:horizontal { height:40px; } QSlider::sub-page:horizontal { border:1px outset #6A4572; background:#6A4572; margin:2px; } QSlider::handle:horizontal { image: url(:images/knob.png) no-repeat top left; width:20px; }''' ) self.saveAction = QPushButton( self.parent, icon=self.saveIcon, text='Save Video', flat=True, toolTip='Save Video', clicked=self.cutVideo, cursor=Qt.PointingHandCursor, iconSize=QSize(30, 30), statusTip='Save video clips merged as a new video file', enabled=False) self.saveAction.setStyleSheet( '''QPushButton { color:#FFF; padding:8px; font-size:12pt; border:1px inset #481953; border-radius:4px; background-color:rgb(106, 69, 114); } QPushButton:!enabled { background-color:rgba(0, 0, 0, 0.1); color:rgba(0, 0, 0, 0.3); border:1px inset #CDCDCD; } QPushButton:hover { background-color:rgba(255, 255, 255, 0.8); color:#444; } QPushButton:pressed { background-color:rgba(218, 218, 219, 0.8); color:#444; }''' ) controlsLayout = QHBoxLayout() controlsLayout.addStretch(1) controlsLayout.addWidget(self.toolbar) controlsLayout.addSpacerItem(QSpacerItem(20, 1)) controlsLayout.addWidget(self.saveAction) controlsLayout.addStretch(1) controlsLayout.addWidget(self.muteButton) controlsLayout.addWidget(self.volumeSlider) controlsLayout.addSpacing(1) controlsLayout.addWidget(self.menuButton) layout = QVBoxLayout() layout.setContentsMargins(10, 10, 10, 4) layout.addLayout(self.videoLayout) layout.addWidget(self.seekSlider) layout.addLayout(controlsLayout) self.setLayout(layout) self.mediaPlayer.setVideoOutput(self.videoWidget) self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.durationChanged.connect(self.durationChanged) self.mediaPlayer.error.connect(self.handleError) def initNoVideo(self) -> None: novideoImage = QLabel(alignment=Qt.AlignCenter, autoFillBackground=False, pixmap=QPixmap( os.path.join(self.getAppPath(), 'images', 'novideo.png'), 'PNG'), sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.MinimumExpanding)) novideoImage.setBackgroundRole(QPalette.Dark) novideoImage.setContentsMargins(0, 20, 0, 20) self.novideoLabel = QLabel(alignment=Qt.AlignCenter, autoFillBackground=True, sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Minimum)) self.novideoLabel.setBackgroundRole(QPalette.Dark) self.novideoLabel.setContentsMargins(0, 20, 15, 60) novideoLayout = QVBoxLayout(spacing=0) novideoLayout.addWidget(novideoImage) novideoLayout.addWidget(self.novideoLabel, alignment=Qt.AlignTop) self.novideoMovie = QMovie( os.path.join(self.getAppPath(), 'images', 'novideotext.gif')) self.novideoMovie.frameChanged.connect(self.setNoVideoText) self.novideoMovie.start() self.novideoWidget = QWidget(self, autoFillBackground=True) self.novideoWidget.setBackgroundRole(QPalette.Dark) self.novideoWidget.setLayout(novideoLayout) def initIcons(self) -> None: self.appIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'vidcutter.png')) self.openIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'addmedia.png')) self.playIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'play.png')) self.pauseIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'pause.png')) self.cutStartIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'cut-start.png')) self.cutEndIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'cut-end.png')) self.saveIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'save.png')) self.muteIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'muted.png')) self.unmuteIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'unmuted.png')) self.upIcon = QIcon(os.path.join(self.getAppPath(), 'images', 'up.png')) self.downIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'down.png')) self.removeIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'remove.png')) self.removeAllIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'remove-all.png')) self.successIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'success.png')) self.aboutIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'about.png')) self.completePlayIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'complete-play.png')) self.completeOpenIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'complete-open.png')) self.completeRestartIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'complete-restart.png')) self.completeExitIcon = QIcon( os.path.join(self.getAppPath(), 'images', 'complete-exit.png')) def initActions(self) -> None: self.openAction = QAction(self.openIcon, 'Add Media', self, statusTip='Select media source', triggered=self.openFile) self.playAction = QAction(self.playIcon, 'Play Video', self, statusTip='Play selected media', triggered=self.playVideo, enabled=False) self.cutStartAction = QAction(self.cutStartIcon, 'Set Start', self, toolTip='Set Start', statusTip='Set start marker', triggered=self.cutStart, enabled=False) self.cutEndAction = QAction(self.cutEndIcon, 'Set End', self, statusTip='Set end marker', triggered=self.cutEnd, enabled=False) self.moveItemUpAction = QAction( self.upIcon, 'Move Up', self, statusTip='Move clip position up in list', triggered=self.moveItemUp, enabled=False) self.moveItemDownAction = QAction( self.downIcon, 'Move Down', self, statusTip='Move clip position down in list', triggered=self.moveItemDown, enabled=False) self.removeItemAction = QAction( self.removeIcon, 'Remove clip', self, statusTip='Remove selected clip from list', triggered=self.removeItem, enabled=False) self.removeAllAction = QAction(self.removeAllIcon, 'Clear list', self, statusTip='Clear all clips from list', triggered=self.clearList, enabled=False) self.aboutAction = QAction('About %s' % qApp.applicationName(), self, statusTip='Credits and acknowledgements', triggered=self.aboutInfo) self.aboutQtAction = QAction('About Qt', self, statusTip='About Qt', triggered=qApp.aboutQt) self.mediaInfoAction = QAction( 'Media Information', self, statusTip='Media information from loaded video file', triggered=self.mediaInfo, enabled=False) def initToolbar(self) -> None: self.toolbar.addAction(self.openAction) self.toolbar.addAction(self.playAction) self.toolbar.addSeparator() self.toolbar.addAction(self.cutStartAction) self.toolbar.addAction(self.cutEndAction) self.toolbar.addSeparator() def initMenus(self) -> None: self.aboutMenu.addAction(self.mediaInfoAction) self.aboutMenu.addSeparator() self.aboutMenu.addAction(self.aboutQtAction) self.aboutMenu.addAction(self.aboutAction) self.cliplistMenu.addAction(self.moveItemUpAction) self.cliplistMenu.addAction(self.moveItemDownAction) self.cliplistMenu.addSeparator() self.cliplistMenu.addAction(self.removeItemAction) self.cliplistMenu.addAction(self.removeAllAction) def setRunningTime(self, runtime: str) -> None: self.runtimeLabel.setText('<div align="right">%s</div>' % runtime) @pyqtSlot(int) def setNoVideoText(self, frame: int) -> None: self.novideoLabel.setPixmap(self.novideoMovie.currentPixmap()) def itemMenu(self, pos: QPoint) -> None: globalPos = self.cliplist.mapToGlobal(pos) self.moveItemUpAction.setEnabled(False) self.moveItemDownAction.setEnabled(False) self.removeItemAction.setEnabled(False) self.removeAllAction.setEnabled(False) index = self.cliplist.currentRow() if index != -1: if not self.inCut: if index > 0: self.moveItemUpAction.setEnabled(True) if index < self.cliplist.count() - 1: self.moveItemDownAction.setEnabled(True) if self.cliplist.count() > 0: self.removeItemAction.setEnabled(True) if self.cliplist.count() > 0: self.removeAllAction.setEnabled(True) self.cliplistMenu.exec_(globalPos) def moveItemUp(self) -> None: index = self.cliplist.currentRow() tmpItem = self.clipTimes[index] del self.clipTimes[index] self.clipTimes.insert(index - 1, tmpItem) self.renderTimes() def moveItemDown(self) -> None: index = self.cliplist.currentRow() tmpItem = self.clipTimes[index] del self.clipTimes[index] self.clipTimes.insert(index + 1, tmpItem) self.renderTimes() def removeItem(self) -> None: index = self.cliplist.currentRow() del self.clipTimes[index] if self.inCut and index == self.cliplist.count() - 1: self.inCut = False self.initMediaControls() self.renderTimes() def clearList(self) -> None: self.clipTimes.clear() self.cliplist.clear() self.inCut = False self.renderTimes() self.initMediaControls() def mediaInfo(self) -> None: if self.mediaPlayer.isMetaDataAvailable(): content = '<table cellpadding="4">' for key in self.mediaPlayer.availableMetaData(): val = self.mediaPlayer.metaData(key) if type(val) is QSize: val = '%s x %s' % (val.width(), val.height()) content += '<tr><td align="right"><b>%s:</b></td><td>%s</td></tr>\n' % ( key, val) content += '</table>' mbox = QMessageBox(windowTitle='Media Information', windowIcon=self.parent.windowIcon(), textFormat=Qt.RichText) mbox.setText('<b>%s</b>' % os.path.basename( self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile())) mbox.setInformativeText(content) mbox.exec_() else: QMessageBox.critical( self.parent, 'Could not retrieve media information', '''There was a problem in tring to retrieve media information. This DOES NOT mean there is a problem with the file and you should be able to continue using it.''') def aboutInfo(self) -> None: about_html = '''<style> a { color:#441d4e; text-decoration:none; font-weight:bold; } a:hover { text-decoration:underline; } </style> <p style="font-size:26pt; font-weight:bold;">%s</p> <p> <span style="font-size:13pt;"><b>Version: %s</b></span> <span style="font-size:10pt;position:relative;left:5px;">( %s )</span> </p> <p style="font-size:13px;"> Copyright © 2016 <a href="mailto:[email protected]">Pete Alexandrou</a> <br/> Website: <a href="%s">%s</a> </p> <p style="font-size:13px;"> Thanks to the folks behind the <b>Qt</b>, <b>PyQt</b> and <b>FFmpeg</b> projects for all their hard and much appreciated work. </p> <p style="font-size:11px;"> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. </p> <p style="font-size:11px;"> This software uses libraries from the <a href="https://www.ffmpeg.org">FFmpeg</a> project under the <a href="https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html">LGPLv2.1</a> </p>''' % (qApp.applicationName(), qApp.applicationVersion(), platform.architecture()[0], qApp.organizationDomain(), qApp.organizationDomain()) QMessageBox.about(self.parent, 'About %s' % qApp.applicationName(), about_html) def openFile(self) -> None: filename, _ = QFileDialog.getOpenFileName(self.parent, caption='Select video', directory=QDir.homePath()) if filename != '': self.loadFile(filename) def loadFile(self, filename: str) -> None: self.movieFilename = filename if not os.path.exists(filename): return self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(filename))) self.initMediaControls(True) self.cliplist.clear() self.clipTimes = [] self.parent.setWindowTitle( '%s - %s' % (qApp.applicationName(), os.path.basename(filename))) if not self.movieLoaded: self.videoLayout.replaceWidget(self.novideoWidget, self.videoplayerWidget) self.novideoMovie.stop() self.novideoMovie.deleteLater() self.novideoWidget.deleteLater() self.videoplayerWidget.show() self.videoWidget.show() self.movieLoaded = True if self.mediaPlayer.isVideoAvailable(): self.mediaPlayer.setPosition(1) self.mediaPlayer.play() self.mediaPlayer.pause() def playVideo(self) -> None: if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() self.playAction.setText('Play Video') else: self.mediaPlayer.play() self.playAction.setText('Pause Video') def initMediaControls(self, flag: bool = True) -> None: self.playAction.setEnabled(flag) self.saveAction.setEnabled(False) self.cutStartAction.setEnabled(flag) self.cutEndAction.setEnabled(False) self.mediaInfoAction.setEnabled(flag) if flag: self.seekSlider.setRestrictValue(0) def setPosition(self, position: int) -> None: self.mediaPlayer.setPosition(position) def positionChanged(self, progress: int) -> None: self.seekSlider.setValue(progress) currentTime = self.deltaToQTime(progress) totalTime = self.deltaToQTime(self.mediaPlayer.duration()) self.timeCounter.setText('%s / %s' % (currentTime.toString( self.timeformat), totalTime.toString(self.timeformat))) @pyqtSlot() def mediaStateChanged(self) -> None: if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.playAction.setIcon(self.pauseIcon) else: self.playAction.setIcon(self.playIcon) def durationChanged(self, duration: int) -> None: self.seekSlider.setRange(0, duration) def muteAudio(self, muted: bool) -> None: if self.mediaPlayer.isMuted(): self.mediaPlayer.setMuted(not self.mediaPlayer.isMuted()) self.muteButton.setIcon(self.unmuteIcon) self.muteButton.setToolTip('Mute') else: self.mediaPlayer.setMuted(not self.mediaPlayer.isMuted()) self.muteButton.setIcon(self.muteIcon) self.muteButton.setToolTip('Unmute') def setVolume(self, volume: int) -> None: self.mediaPlayer.setVolume(volume) def toggleFullscreen(self) -> None: self.videoWidget.setFullScreen(not self.videoWidget.isFullScreen()) def cutStart(self) -> None: self.clipTimes.append([ self.deltaToQTime(self.mediaPlayer.position()), '', self.captureImage() ]) self.cutStartAction.setDisabled(True) self.cutEndAction.setEnabled(True) self.seekSlider.setRestrictValue(self.seekSlider.value() + 1000) self.mediaPlayer.setPosition(self.seekSlider.restrictValue) self.inCut = True self.renderTimes() def cutEnd(self) -> None: item = self.clipTimes[len(self.clipTimes) - 1] selected = self.deltaToQTime(self.mediaPlayer.position()) if selected.__lt__(item[0]): QMessageBox.critical( self.parent, 'Invalid END Time', 'The clip end time must come AFTER it\'s start time. Please try again.' ) return item[1] = selected self.cutStartAction.setEnabled(True) self.cutEndAction.setDisabled(True) self.seekSlider.setRestrictValue(0) self.inCut = False self.renderTimes() @pyqtSlot(QModelIndex, int, int, QModelIndex, int) def syncClipList(self, parent: QModelIndex, start: int, end: int, destination: QModelIndex, row: int) -> None: if start < row: index = row - 1 else: index = row clip = self.clipTimes.pop(start) self.clipTimes.insert(index, clip) def renderTimes(self) -> None: self.cliplist.clear() self.seekSlider.setCutMode(self.inCut) if len(self.clipTimes) > 4: self.cliplist.setFixedWidth(200) else: self.cliplist.setFixedWidth(185) self.totalRuntime = 0 for item in self.clipTimes: endItem = '' if type(item[1]) is QTime: endItem = item[1].toString(self.timeformat) self.totalRuntime += item[0].msecsTo(item[1]) listitem = QListWidgetItem() listitem.setTextAlignment(Qt.AlignVCenter) if type(item[2]) is QPixmap: listitem.setIcon(QIcon(item[2])) self.cliplist.addItem(listitem) marker = QLabel( '''<style>b { font-size:8pt; } p { margin:5px; }</style> <p><b>START</b><br/>%s</p><p><b>END</b><br/>%s</p>''' % (item[0].toString(self.timeformat), endItem)) self.cliplist.setItemWidget(listitem, marker) listitem.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsEnabled) if len(self.clipTimes) and not self.inCut: self.saveAction.setEnabled(True) if self.inCut or len(self.clipTimes) == 0 or not type( self.clipTimes[0][1]) is QTime: self.saveAction.setEnabled(False) self.setRunningTime( self.deltaToQTime(self.totalRuntime).toString(self.timeformat)) @staticmethod def deltaToQTime(millisecs: int) -> QTime: secs = millisecs / 1000 return QTime((secs / 3600) % 60, (secs / 60) % 60, secs % 60, (secs * 1000) % 1000) def captureImage(self) -> None: frametime = self.deltaToQTime( self.mediaPlayer.position()).addSecs(1).toString(self.timeformat) inputfile = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile( ) imagecap = self.videoService.capture(inputfile, frametime) if type(imagecap) is QPixmap: return imagecap def cutVideo(self) -> bool: self.setCursor(Qt.BusyCursor) clips = len(self.clipTimes) filename, filelist = '', [] source = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile() _, sourceext = os.path.splitext(source) if clips > 0: self.finalFilename, _ = QFileDialog.getSaveFileName( self.parent, 'Save video', source, 'Video files (*%s)' % sourceext) if self.finalFilename != '': self.saveAction.setDisabled(True) self.showProgress(clips) file, ext = os.path.splitext(self.finalFilename) index = 1 self.progress.setLabelText('Cutting video clips...') qApp.processEvents() for clip in self.clipTimes: duration = self.deltaToQTime(clip[0].msecsTo( clip[1])).toString(self.timeformat) filename = '%s_%s%s' % (file, '{0:0>2}'.format(index), ext) filelist.append(filename) self.videoService.cut(source, filename, clip[0].toString(self.timeformat), duration) index += 1 if len(filelist) > 1: self.joinVideos(filelist, self.finalFilename) else: QFile.remove(self.finalFilename) QFile.rename(filename, self.finalFilename) self.unsetCursor() self.progress.setLabelText('Complete...') qApp.processEvents() self.saveAction.setEnabled(True) self.progress.close() self.progress.deleteLater() self.complete() self.saveAction.setEnabled(True) self.unsetCursor() self.saveAction.setDisabled(True) return True self.unsetCursor() self.saveAction.setDisabled(True) return False def joinVideos(self, joinlist: list, filename: str) -> None: listfile = os.path.normpath( os.path.join(os.path.dirname(joinlist[0]), '.vidcutter.list')) fobj = open(listfile, 'w') for file in joinlist: fobj.write('file \'%s\'\n' % file.replace("'", "\\'")) fobj.close() self.videoService.join(listfile, filename) try: QFile.remove(listfile) for file in joinlist: if os.path.isfile(file): QFile.remove(file) except: pass def showProgress(self, steps: int, label: str = 'Processing video...') -> None: self.progress = QProgressDialog(label, None, 0, steps, self.parent, windowModality=Qt.ApplicationModal, windowIcon=self.parent.windowIcon(), minimumDuration=0, minimumWidth=500) self.progress.show() for i in range(steps): self.progress.setValue(i) qApp.processEvents() time.sleep(1) def complete(self) -> None: info = QFileInfo(self.finalFilename) mbox = QMessageBox(windowTitle='Success', windowIcon=self.parent.windowIcon(), minimumWidth=500, iconPixmap=self.successIcon.pixmap(48, 49), textFormat=Qt.RichText) mbox.setText( ''' <style> table.info { margin:8px; padding:4px 15px; } td.label { font-weight:bold; font-size:9pt; text-align:right; background-color:#444; color:#FFF; } td.value { background-color:#FFF !important; font-size:10pt; } </style> <p>Your video was successfully created.</p> <p align="center"> <table class="info" cellpadding="6" cellspacing="0"> <tr> <td class="label"><b>Filename</b></td> <td class="value" nowrap>%s</td> </tr> <tr> <td class="label"><b>Size</b></td> <td class="value">%s</td> </tr> <tr> <td class="label"><b>Runtime</b></td> <td class="value">%s</td> </tr> </table> </p> <p>How would you like to proceed?</p>''' % (QDir.toNativeSeparators( self.finalFilename), self.sizeof_fmt(int(info.size())), self.deltaToQTime(self.totalRuntime).toString(self.timeformat))) play = mbox.addButton('Play', QMessageBox.AcceptRole) play.setIcon(self.completePlayIcon) play.clicked.connect(self.openResult) fileman = mbox.addButton('Open', QMessageBox.AcceptRole) fileman.setIcon(self.completeOpenIcon) fileman.clicked.connect(self.openFolder) end = mbox.addButton('Exit', QMessageBox.AcceptRole) end.setIcon(self.completeExitIcon) end.clicked.connect(self.close) new = mbox.addButton('Restart', QMessageBox.AcceptRole) new.setIcon(self.completeRestartIcon) new.clicked.connect(self.startNew) mbox.setDefaultButton(new) mbox.setEscapeButton(new) mbox.exec_() def sizeof_fmt(self, num: float, suffix: chr = 'B') -> str: for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 return "%.1f%s%s" % (num, 'Y', suffix) @pyqtSlot() def openFolder(self) -> None: self.openResult(pathonly=True) @pyqtSlot(bool) def openResult(self, pathonly: bool = False) -> None: self.startNew() if len(self.finalFilename) and os.path.exists(self.finalFilename): target = self.finalFilename if not pathonly else os.path.dirname( self.finalFilename) QDesktopServices.openUrl(QUrl.fromLocalFile(target)) @pyqtSlot() def startNew(self) -> None: self.unsetCursor() self.clearList() self.seekSlider.setValue(0) self.seekSlider.setRange(0, 0) self.mediaPlayer.setMedia(QMediaContent()) self.initNoVideo() self.videoLayout.replaceWidget(self.videoplayerWidget, self.novideoWidget) self.initMediaControls(False) self.parent.setWindowTitle('%s' % qApp.applicationName()) def wheelEvent(self, event: QWheelEvent) -> None: if self.mediaPlayer.isVideoAvailable( ) or self.mediaPlayer.isAudioAvailable(): if event.angleDelta().y() > 0: newval = self.seekSlider.value() - 1000 else: newval = self.seekSlider.value() + 1000 self.seekSlider.setValue(newval) self.seekSlider.setSliderPosition(newval) self.mediaPlayer.setPosition(newval) event.accept() def keyPressEvent(self, event: QKeyEvent) -> None: if self.mediaPlayer.isVideoAvailable( ) or self.mediaPlayer.isAudioAvailable(): addtime = 0 if event.key() == Qt.Key_Left: addtime = -1000 elif event.key() == Qt.Key_PageUp or event.key() == Qt.Key_Up: addtime = -10000 elif event.key() == Qt.Key_Right: addtime = 1000 elif event.key() == Qt.Key_PageDown or event.key() == Qt.Key_Down: addtime = 10000 elif event.key() == Qt.Key_Enter: self.toggleFullscreen() elif event.key( ) == Qt.Key_Escape and self.videoWidget.isFullScreen(): self.videoWidget.setFullScreen(False) if addtime != 0: newval = self.seekSlider.value() + addtime self.seekSlider.setValue(newval) self.seekSlider.setSliderPosition(newval) self.mediaPlayer.setPosition(newval) event.accept() def mousePressEvent(self, event: QMouseEvent) -> None: if event.button() == Qt.BackButton and self.cutStartAction.isEnabled(): self.cutStart() event.accept() elif event.button( ) == Qt.ForwardButton and self.cutEndAction.isEnabled(): self.cutEnd() event.accept() else: super(VidCutter, self).mousePressEvent(event) def eventFilter(self, obj: QObject, event: QEvent) -> bool: if event.type() == QEvent.MouseButtonRelease and isinstance( obj, VideoSlider): if obj.objectName() == 'VideoSlider' and ( self.mediaPlayer.isVideoAvailable() or self.mediaPlayer.isAudioAvailable()): obj.setValue( QStyle.sliderValueFromPosition(obj.minimum(), obj.maximum(), event.x(), obj.width())) self.mediaPlayer.setPosition(obj.sliderPosition()) return QWidget.eventFilter(self, obj, event) @pyqtSlot(QMediaPlayer.Error) def handleError(self, error: QMediaPlayer.Error) -> None: self.unsetCursor() self.startNew() if error == QMediaPlayer.ResourceError: QMessageBox.critical( self.parent, 'Error', 'Invalid media file detected at:<br/><br/><b>%s</b><br/><br/>%s' % (self.movieFilename, self.mediaPlayer.errorString())) else: QMessageBox.critical(self.parent, 'Error', self.mediaPlayer.errorString()) def getAppPath(self) -> str: return ':' def closeEvent(self, event: QCloseEvent) -> None: self.parent.closeEvent(event)
class Player(QWidget): fullScreenChanged = pyqtSignal(bool) def __init__(self, playlist, parent=None, add_button = None): super(Player, self).__init__(parent) self.add_button = add_button self.colorDialog = None self.trackInfo = "" self.statusInfo = "" self.duration = 0 self.player = QMediaPlayer() self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.metaDataChanged.connect(self.metaDataChanged) self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) self.player.mediaStatusChanged.connect(self.statusChanged) self.player.bufferStatusChanged.connect(self.bufferingProgress) self.player.error.connect(self.displayErrorMessage) self.videoWidget = VideoWidget() self.player.setVideoOutput(self.videoWidget) self.playlistModel = PlaylistModel() self.playlistModel.setPlaylist(self.playlist) self.playlistView = QListView() self.playlistView.setModel(self.playlistModel) self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) self.playlistView.activated.connect(self.jump) self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, self.player.duration() / 1000) self.labelDuration = QLabel() self.slider.sliderMoved.connect(self.seek) openButton = QPushButton("Open Audio/Video File", clicked=self.open) controls = PlayerControls() controls.setState(self.player.state()) controls.setVolume(self.player.volume()) controls.setMuted(controls.isMuted()) controls.play.connect(self.player.play) controls.pause.connect(self.player.pause) controls.stop.connect(self.player.stop) controls.next.connect(self.playlist.next) controls.previous.connect(self.previousClicked) controls.changeVolume.connect(self.player.setVolume) controls.changeMuting.connect(self.player.setMuted) controls.changeRate.connect(self.player.setPlaybackRate) controls.stop.connect(self.videoWidget.update) self.player.stateChanged.connect(controls.setState) self.player.volumeChanged.connect(controls.setVolume) self.player.mutedChanged.connect(controls.setMuted) displayLayout = QHBoxLayout() displayLayout.addWidget(self.videoWidget, 2) displayLayout.addWidget(self.playlistView) controlLayout = QHBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) controlLayout.addWidget(openButton) # button to add decoder if add_button: add_decoder_btn = QPushButton("Decode Keystrokes of Selected Media") add_decoder_btn.clicked.connect(add_button) controlLayout.addWidget(add_decoder_btn) # controlLayout.addStretch(1) controlLayout.addWidget(controls) layout = QVBoxLayout() layout.addLayout(displayLayout) hLayout = QHBoxLayout() hLayout.addWidget(self.slider) hLayout.addWidget(self.labelDuration) layout.addLayout(hLayout) layout.addLayout(controlLayout) self.setLayout(layout) if not self.player.isAvailable(): QMessageBox.warning(self, "Service not available", "The QMediaPlayer object does not have a valid service.\n" "Please check the media service plugins are installed.") controls.setEnabled(False) self.playlistView.setEnabled(False) openButton.setEnabled(False) self.metaDataChanged() self.addToPlaylist(playlist) def get_current_file(self): inds = self.playlistView.selectedIndexes() if len(inds) == 1: index = inds[0] location = self.playlistModel.m_playlist.media(index.row()).canonicalUrl() return location.path() def open(self): fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files") self.addToPlaylist(fileNames) def addToPlaylist(self, fileNames): for name in fileNames: fileInfo = QFileInfo(name) if fileInfo.exists(): url = QUrl.fromLocalFile(fileInfo.absoluteFilePath()) if fileInfo.suffix().lower() == 'm3u': self.playlist.load(url) else: self.playlist.addMedia(QMediaContent(url)) else: url = QUrl(name) if url.isValid(): self.playlist.addMedia(QMediaContent(url)) def durationChanged(self, duration): duration /= 1000 self.duration = duration self.slider.setMaximum(duration) def positionChanged(self, progress): progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) self.updateDurationInfo(progress) def metaDataChanged(self): if self.player.isMetaDataAvailable(): self.setTrackInfo("%s - %s" % ( self.player.metaData(QMediaMetaData.AlbumArtist), self.player.metaData(QMediaMetaData.Title))) def previousClicked(self): # Go to the previous track if we are within the first 5 seconds of # playback. Otherwise, seek to the beginning. if self.player.position() <= 5000: self.playlist.previous() else: self.player.setPosition(0) def jump(self, index): if index.isValid(): self.playlist.setCurrentIndex(index.row()) self.player.play() def playlistPositionChanged(self, position): self.playlistView.setCurrentIndex( self.playlistModel.index(position, 0)) def seek(self, seconds): self.player.setPosition(seconds * 1000) def statusChanged(self, status): self.handleCursor(status) if status == QMediaPlayer.LoadingMedia: self.setStatusInfo("Loading...") elif status == QMediaPlayer.StalledMedia: self.setStatusInfo("Media Stalled") elif status == QMediaPlayer.EndOfMedia: QApplication.alert(self) elif status == QMediaPlayer.InvalidMedia: self.displayErrorMessage() else: self.setStatusInfo("") def handleCursor(self, status): if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia): self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def bufferingProgress(self, progress): self.setStatusInfo("Buffering %d%" % progress) def setTrackInfo(self, info): self.trackInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def setStatusInfo(self, info): self.statusInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def displayErrorMessage(self): self.setStatusInfo(self.player.errorString()) def updateDurationInfo(self, currentInfo): duration = self.duration if currentInfo or duration: currentTime = QTime((currentInfo/3600)%60, (currentInfo/60)%60, currentInfo%60, (currentInfo*1000)%1000) totalTime = QTime((duration/3600)%60, (duration/60)%60, duration%60, (duration*1000)%1000); format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' tStr = currentTime.toString(format) + " / " + totalTime.toString(format) else: tStr = "" self.labelDuration.setText(tStr)
class Visard(QWidget, UI): def __init__(self): super().__init__() self.player_window() self.UI = False self.player = QMediaPlayer() self.player.setNotifyInterval(1) self.player.mediaStatusChanged.connect(self.media_status_changed) self.state = 0 self.player.stateChanged.connect(self.state_changed) self.player.durationChanged.connect(self.duration_changed) self.player.positionChanged.connect(self.position_changed) self.player.metaDataChanged.connect(self.change_metadata) self.open_button.clicked.connect(self.open_dialog) self.volume_slider.valueChanged.connect(self.player.setVolume) self.position_slider.sliderPressed.connect(self.change_position_freeze) self.position_slider.sliderReleased.connect( self.change_position_unfreeze) self.play_pause_button.clicked.connect(self.play_pause) self.stop_button.clicked.connect(self.stop) def media_status_changed(self, media_status): self.media_status = media_status if media_status == 7: self.player.setPosition(0) self.play_pause_button.setText('Play') print(f'MS: {media_status}!') def state_changed(self, state): self.state = state print(f'S: {state}!') def duration_changed(self, duration): self.duration_label.setText(ms_to_time(duration)) self.position_slider.setMaximum(duration) print('DC!') def position_changed(self, position): self.position_label.setText(ms_to_time(position)) self.position_slider.setSliderPosition(position) def change_metadata(self): artist = self.player.metaData(QMediaMetaData.ContributingArtist) title = self.player.metaData(QMediaMetaData.Title) image = self.player.metaData(QMediaMetaData.CoverArtImage) if artist: if type(artist) == list: artist = ', '.join(artist) self.artist_label.setText(artist) else: self.artist_label.setText('None') if title: self.title_label.setText(title) else: self.title_label.setText('None') if image: self.image_label.setPixmap(QPixmap.fromImage(image)) else: self.image_label.setText('None') def open_dialog(self): track_directory = QFileDialog.getOpenFileName(self, 'Choose track', '', 'Music (*.flac *.ogg *.mp3 *.wav *.webm)')[0] if track_directory: if self.state != 0: if self.state == 1: self.play_pause_button.setText('Play') self.stop_button.setEnabled(False) self.player.setMedia( QMediaContent(QUrl.fromLocalFile(track_directory))) if not self.UI: self.artist_label.setEnabled(True) self.title_label.setEnabled(True) self.volume_slider.setEnabled(True) self.position_label.setEnabled(True) self.duration_label.setEnabled(True) self.position_slider.setEnabled(True) self.play_pause_button.setEnabled(True) self.UI = True def change_position_freeze(self): self.player.positionChanged.disconnect(self.position_changed) self.position_slider.sliderMoved.connect(self.change_position_label) def change_position_label(self, position): self.position_label.setText(ms_to_time(position)) def change_position_unfreeze(self): self.player.setPosition(self.position_slider.sliderPosition()) self.position_slider.sliderMoved.disconnect(self.change_position_label) self.player.positionChanged.connect(self.position_changed) def play_pause(self): if self.state == 1: self.player.pause() self.play_pause_button.setText('Play') else: if self.state == 0: self.stop_button.setEnabled(True) self.player.play() self.play_pause_button.setText('Pause') def stop(self): if self.state == 1: self.play_pause_button.setText('Play') self.player.stop() self.stop_button.setEnabled(False)
class TranquilityMP(QMainWindow, MainWindow): def __init__(self, playlist, parent=None): super(TranquilityMP, self).__init__(parent) self.statusInfo = "" self.trackInfo = "" self.theme = 0 self.colorTheme = 0 self.mediaPlayer = QMediaPlayer() self.playlist = QMediaPlaylist() self.mediaPlayer.setPlaylist(self.playlist) self.establishLayout() self.connectSignals() self.allPlaylists = self.loadPlaylists() self.toggleTheme() self.addToPlaylist(playlist) def connectSignals(self): self.mediaPlayer.durationChanged.connect(self.durationChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.metaDataChanged.connect(self.metaDataChanged) self.mediaPlayer.mediaStatusChanged.connect(self.statusChanged) self.mediaPlayer.stateChanged.connect(self.stateChanged) def createPlaylist(self): root = QFileInfo(__file__).absolutePath() spot = (root + '/playlists/') playlistName = self.getText() completeName = os.path.join(spot, f'{playlistName}.m3u') file = open(completeName, 'w') file.close() self.playlistView.addItem(playlistName) self.allPlaylists = self.loadPlaylists() def savePlaylist(self): root = "C:\\Users\\dchtk\\Music\\Playlists" playlistName = self.getText() completeName = os.path.join(root, f'{playlistName}.m3u') file = open(completeName, 'a+') for i in range(self.currentPlaylist.count()): file.write(''.join( [str(self.currentPlaylist.item(i).text()), '\n'])) file.close() self.playlistView.addItem(playlistName) def getText(self): text, okPressed = QInputDialog.getText(self, "New Playlist", "Playlist Name:", QLineEdit.Normal, "") if okPressed and text != '': return text def loadPlaylists(self): playlists = [] root = "C:\\Users\\dchtk\\Music\\Playlists" songsPlaylist = os.listdir(root) for item in songsPlaylist: if str(item[-4:]) == '.m3u': self.playlistView.addItem(item[:-4]) playlists.append(root + item) return playlists def addToPlaylist(self, fileNames): for name in fileNames: fileInfo = QFileInfo(name) songFileTitle = os.path.basename(name) if fileInfo.exists(): url = QUrl.fromLocalFile(fileInfo.absoluteFilePath()) if fileInfo.suffix().lower() == 'm3u': self.playlist.load(url) else: self.playlist.addMedia(QMediaContent(url)) self.currentPlaylist.addItem(songFileTitle) else: url = QUrl(name) if url.isValid(): self.playlist.addMedia(QMediaContent(url)) self.currentPlaylist.addItem(songFileTitle) def metaDataChanged(self): if self.mediaPlayer.isMetaDataAvailable(): self.setTrackInfo( "%s - %s" % (self.mediaPlayer.metaData(QMediaMetaData.AlbumArtist), self.mediaPlayer.metaData(QMediaMetaData.Title))) def previousMedia(self): if self.mediaPlayer.position() <= 5000: self.playlist.previous() else: self.playlist.setPosition(0) def setRepeatOne(self): if self.mediaPlayer.state == QMediaPlayer.PlayingState: self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop) def setRepeatAll(self): if self.mediaPlayer.state == QMediaPlayer.PlayingState: self.playlist.setPlaybackMode(QMediaPlaylist.Loop) def durationChanged(self, duration): self.duration = duration self.seekSlider.setMaximum(duration) if duration > 0: self.totalTimeLabel.setText(configureTime(self.duration)) def positionChanged(self, position): if not self.seekSlider.isSliderDown(): self.seekSlider.setValue(position) if position > 0: self.currentTimeLabel.setText(configureTime(position)) def seek(self, seconds): if self.mediaPlayer.isSeekable(): self.mediaPlayer.setPosition(seconds) def stateChanged(self): if self.mediaPlayer.state == QMediaPlayer.StoppedState: self.mediaPlayer.stop() def statusChanged(self, status): self.handleCursor(status) if status == QMediaPlayer.LoadingMedia: self.setStatusInfo("Loading") elif status == QMediaPlayer.LoadedMedia: self.setStatusInfo("Loaded") self.mediaPlayer.play() elif status == QMediaPlayer.BufferingMedia: self.setStatusInfo("Buffering") elif status == QMediaPlayer.EndOfMedia: QApplication.alert(self) elif status == (QMediaPlayer.InvalidMedia or QMediaPlayer.NoMedia): self.displayError() else: self.setStatusInfo("") def handleCursor(self, status): if status == QMediaPlayer.LoadingMedia: self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def setTrackInfo(self, info): self.trackInfo = info if self.statusInfo != "": self.statusBar().showMessage("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.statusBar().showMessage(self.trackInfo) def setStatusInfo(self, info): self.statusInfo = info if self.statusInfo != "": self.statusBar().showMessage("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.statusBar().showMessage(self.trackInfo) def displayError(self): self.setStatusInfo(self.mediaPlayer.errorString()) def toggleTheme(self): """ Fusion dark palette from https://gist.github.com/QuantumCD/6245215. Modified by D.C """ app.setStyle("Fusion") palette = QPalette() if self.theme == 0: palette.setColor(QPalette.Window, QColor(53, 53, 53)) palette.setColor(QPalette.WindowText, Qt.white) palette.setColor(QPalette.Base, QColor(25, 25, 25)) palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53)) palette.setColor(QPalette.ToolTipBase, Qt.white) palette.setColor(QPalette.ToolTipText, Qt.white) palette.setColor(QPalette.Text, Qt.white) palette.setColor(QPalette.Button, QColor(53, 53, 53)) palette.setColor(QPalette.ButtonText, Qt.white) palette.setColor(QPalette.BrightText, Qt.red) palette.setColor(QPalette.Link, QColor(235, 101, 54)) palette.setColor(QPalette.Highlight, QColor(66, 155, 248)) palette.setColor(QPalette.HighlightedText, Qt.black) app.setPalette(palette) self.theme = 1 elif self.theme == 1: palette.setColor(QPalette.Window, Qt.white) palette.setColor(QPalette.WindowText, Qt.black) palette.setColor(QPalette.Base, QColor(240, 240, 240)) palette.setColor(QPalette.AlternateBase, Qt.white) palette.setColor(QPalette.ToolTipBase, Qt.white) palette.setColor(QPalette.ToolTipText, Qt.white) palette.setColor(QPalette.Text, Qt.black) palette.setColor(QPalette.Button, Qt.white) palette.setColor(QPalette.ButtonText, Qt.black) palette.setColor(QPalette.BrightText, Qt.red) palette.setColor(QPalette.Link, QColor(66, 155, 248)) palette.setColor(QPalette.Highlight, QColor(66, 155, 248)) palette.setColor(QPalette.HighlightedText, Qt.black) app.setPalette(palette) self.theme = 0 def toggleColor(self): app.setStyle("Fusion") palette = QPalette() if self.colorTheme == 0: palette.setColor(QPalette.Window, QColor(178, 34, 34)) palette.setColor(QPalette.WindowText, Qt.white) palette.setColor(QPalette.Base, QColor(128, 0, 0)) palette.setColor(QPalette.AlternateBase, QColor(178, 34, 34)) palette.setColor(QPalette.ToolTipBase, Qt.white) palette.setColor(QPalette.ToolTipText, Qt.white) palette.setColor(QPalette.Text, Qt.white) palette.setColor(QPalette.Button, QColor(178, 34, 34)) palette.setColor(QPalette.ButtonText, Qt.white) palette.setColor(QPalette.BrightText, Qt.red) palette.setColor(QPalette.Link, QColor(235, 101, 54)) palette.setColor(QPalette.Highlight, QColor(66, 155, 248)) palette.setColor(QPalette.HighlightedText, Qt.black) app.setPalette(palette) self.colorTheme = 1 elif self.colorTheme == 1: palette.setColor(QPalette.Window, QColor(72, 61, 139)) palette.setColor(QPalette.WindowText, Qt.white) palette.setColor(QPalette.Base, QColor(75, 0, 130)) palette.setColor(QPalette.AlternateBase, QColor(72, 61, 139)) palette.setColor(QPalette.ToolTipBase, Qt.black) palette.setColor(QPalette.ToolTipText, Qt.black) palette.setColor(QPalette.Text, Qt.white) palette.setColor(QPalette.Button, QColor(72, 61, 139)) palette.setColor(QPalette.ButtonText, Qt.white) palette.setColor(QPalette.BrightText, Qt.red) palette.setColor(QPalette.Link, QColor(235, 101, 54)) palette.setColor(QPalette.Highlight, QColor(53, 53, 53)) palette.setColor(QPalette.HighlightedText, Qt.white) app.setPalette(palette) self.colorTheme = 0
class Player(QWidget): audio_path = "audio" lyrics_path = "lyrics" timings_path = os.path.join("lyrics", "timing") settings_path = "settings.json" fullScreenChanged = pyqtSignal(bool) def __init__(self, parent=None): super(Player, self).__init__(parent) self.setWindowTitle("SongScreen") self.setFocusPolicy(Qt.StrongFocus) self.colorDialog = None self.trackInfo = "" self.statusInfo = "" self.duration = 0 self.player = QMediaPlayer() self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.metaDataChanged.connect(self.metaDataChanged) # self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) self.player.mediaStatusChanged.connect(self.statusChanged) self.player.bufferStatusChanged.connect(self.bufferingProgress) self.player.videoAvailableChanged.connect(self.videoAvailableChanged) self.player.error.connect(self.displayErrorMessage) # self.videoWidget = VideoWidget() # self.player.setVideoOutput(self.videoWidget) self.slider = MediaProgressWidget() # QSlider(Qt.Horizontal) self.markers = [] self.songtext_widget = SongTextWidget() self.songtext_widget.show() # self.playlistModel = PlaylistModel() # self.playlistModel.setPlaylist(self.playlist) # # self.playlistView = QListView() # self.playlistView.setModel(self.playlistModel) # self.playlistView.setCurrentIndex( # self.playlistModel.index(self.playlist.currentIndex(), 0)) # # self.playlistView.activated.connect(self.jump) self.slider.setRange(0, self.player.duration() / 1000) self.labelDuration = QLabel() self.slider.sliderMoved.connect(self.seek) # openButton = QPushButton("Open", clicked=self.open) controls = PlayerControlsWidget() controls.setState(self.player.state()) controls.setVolume(self.player.volume()) # controls.setMuted(controls.isMuted()) controls.play.connect(self.player.play) controls.pause.connect(self.player.pause) controls.stop.connect(self.stop_clicked) # controls.stop.connect(self.videoWidget.update) # controls.next.connect(self.playlist.next) # controls.previous.connect(self.previousClicked) controls.changeVolume.connect(self.player.setVolume) # controls.changeMuting.connect(self.player.setMuted) # controls.changeRate.connect(self.player.setPlaybackRate) self.player.stateChanged.connect(controls.setState) self.player.stateChanged.connect(self.setState) self.player.volumeChanged.connect(controls.setVolume) # self.player.mutedChanged.connect(controls.setMuted) # self.fullScreenButton = QPushButton("FullScreen") # self.fullScreenButton.setCheckable(True) # # self.colorButton = QPushButton("Color Options...") # self.colorButton.setEnabled(False) # self.colorButton.clicked.connect(self.showColorDialog) displayLayout = QHBoxLayout() # displayLayout.addWidget(self.videoWidget, 2) # displayLayout.addWidget(self.songtext_widget) # displayLayout.addWidget(self.playlistView) self.song_select_widget = SongSelectWidget() self.song_select_widget.song_selected.connect(self.load_song) self.screen_select_widget = ScreenSelectWidget() self.screen_select_widget.screen_selected.connect(self.display_lyrics_on_screen) self.screen_select_widget.active_screen = QApplication.desktop().screenNumber(self.songtext_widget) self.settings_button = QPushButton() self.settings_button.setText(self.tr("Settings...")) self.settings_button.clicked.connect(self.show_settings) sidebarLayout = QVBoxLayout() sidebarLayout.setContentsMargins(10, 1, 0, 1); sidebarLayout.addWidget(self.settings_button) sidebarLayout.addStretch(1); sidebarLayout.addWidget(self.screen_select_widget) displayLayout.addWidget(self.song_select_widget) displayLayout.addLayout(sidebarLayout) controlLayout = QHBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) # controlLayout.addWidget(openButton) # controlLayout.addStretch(1) controlLayout.addWidget(controls) controlLayout.addStretch(1) controlLayout.addWidget(self.labelDuration) # controlLayout.addWidget(self.fullScreenButton) # controlLayout.addWidget(self.colorButton) layout = QVBoxLayout() layout.addLayout(displayLayout) hLayout = QHBoxLayout() hLayout.addWidget(self.slider) # hLayout.addWidget(self.labelDuration) layout.addLayout(hLayout) layout.addLayout(controlLayout) self.setLayout(layout) if not self.player.isAvailable(): QMessageBox.warning(self, "Service not available", "The QMediaPlayer object does not have a valid service.\n" "Please check the media service plugins are installed.") controls.setEnabled(False) self.playlistView.setEnabled(False) # openButton.setEnabled(False) self.colorButton.setEnabled(False) self.fullScreenButton.setEnabled(False) self.metaDataChanged() self._loading_audio = False self._finished_song = False self._lyrics_fading = False self._song_number = -1 self.settings = { 'font_size': 40, 'line_increment': 2, 'lyrics_language': '', } self._load_settings() self.settings_widget = SettingsWidget(self.settings, self.lyrics_path) self.settings_widget.font_size_changed.connect(self.songtext_widget.set_font_size) self.settings_widget.line_increment_changed.connect(self.songtext_widget.set_line_increment) self.settings_widget.language_changed.connect(self._language_changed) self.song_select_widget.reset(self.available_song_numbers) @property def lyrics_language_path(self): path = QStandardPaths.locate(QStandardPaths.AppDataLocation, self.lyrics_path, QStandardPaths.LocateDirectory) return os.path.join(path, self.settings['lyrics_language']) @property def available_song_numbers(self): audios = set( [int(os.path.splitext(filename)[0]) for filename in os.listdir(self.audio_path) if filename[0] != '.']) try: lyrics = set( [int(os.path.splitext(filename)[0]) for filename in os.listdir(self.lyrics_language_path) if filename[0] != '.'] ) except (ValueError, FileNotFoundError): lyrics = set() return sorted(list(audios.intersection(lyrics))) def show_settings(self): self.settings_widget.hide() self.settings_widget.show() def display_lyrics_on_screen(self, screen_number): desktop = QApplication.desktop() if screen_number >= desktop.screenCount(): screen_number = desktop.screenNumber(self) rect = desktop.availableGeometry(screen_number) for _ in range(3): if screen_number != desktop.screenNumber(self): self.songtext_widget.setWindowFlags(Qt.FramelessWindowHint) self.songtext_widget.hide() self.songtext_widget.move(rect.x(), rect.y()) self.songtext_widget.resize(rect.width(), rect.height()) self.songtext_widget.showFullScreen() else: self.songtext_widget.setWindowFlags(Qt.WindowTitleHint) self.songtext_widget.hide() self.songtext_widget.move(rect.x(), rect.y()) self.songtext_widget.resize(self.songtext_widget.minimumSize()) self.songtext_widget.show() self.screen_select_widget.active_screen = screen_number self.activateWindow() def load_song(self, song_number): if self._song_number == song_number: self.seek(0) else: if self._song_number > 0: self._save_timings() self._song_number = song_number self.slider.dirty = False self._load_audio() self._load_lyrics() # self.player.play() def _load_audio(self): filename = os.path.join(self.audio_path, "{:03}.mp3".format(self._song_number)) self.playlist.clear() fileInfo = QFileInfo(filename) if fileInfo.exists(): url = QUrl.fromLocalFile(fileInfo.absoluteFilePath()) if fileInfo.suffix().lower() == 'm3u': self.playlist.load(url) else: self.playlist.addMedia(QMediaContent(url)) self._loading_audio = True self.player.play() def _load_lyrics(self): with open(os.path.join(self.lyrics_language_path, "{}.json".format(self._song_number)), 'r') as f: song_markers = json.load(f) self.markers = [] for m in song_markers['markers']: marker = MediaMarker(self.slider, m['name']) marker.text = m['text'] marker.progress = 0.0 self.markers.append(marker) self.songtext_widget.title = "{} {}".format(self._song_number, song_markers['title']) self.songtext_widget.markers = self.markers self.songtext_widget.fade_in() try: with open(os.path.join(self.timings_path, "{}.json".format(self._song_number)), 'r') as f: timings = json.load(f) for m, t in zip(self.markers, timings): m.progress = t except FileNotFoundError: pass self.slider.markers = self.markers def _language_changed(self, _): available_song_numbers = self.available_song_numbers self.song_select_widget.reset(available_song_numbers) if self._song_number in available_song_numbers: self._load_lyrics() # def open(self): # fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files") # self.addToPlaylist(fileNames) # # def addToPlaylist(self, fileNames): # for name in fileNames: # fileInfo = QFileInfo(name) # if fileInfo.exists(): # url = QUrl.fromLocalFile(fileInfo.absoluteFilePath()) # if fileInfo.suffix().lower() == 'm3u': # self.playlist.load(url) # else: # self.playlist.addMedia(QMediaContent(url)) # else: # url = QUrl(name) # if url.isValid(): # self.playlist.addMedia(QMediaContent(url)) def durationChanged(self, duration): duration /= 1000 self.duration = duration self.slider.setMaximum(duration) if self._loading_audio: self._loading_audio = False line_total = 0 for marker in self.markers: line_total += marker.linecount - 1 silence_ratio = 5.0 / self.duration offset = 1.8 / line_total linecount = 0 for marker in self.markers: if marker.progress == 0.0: marker.progress = offset + (1 - offset) * (1 - silence_ratio) * linecount / line_total linecount += marker.linecount - 1 self.player.pause() @property def _should_fade_out(self): return self.player.position() / 1000 >= self.duration - 5 def positionChanged(self, progress): progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) self.updateDurationInfo(progress) if self.duration > 0: # if self.player.state() == QMediaPlayer.PlayingState: self.songtext_widget.progress = progress / self.duration if self._should_fade_out: self._fade_out_lyrics() def _fade_out_lyrics(self): if not self._lyrics_fading: self._lyrics_fading = True self.songtext_widget.fade_out() def metaDataChanged(self): if self.player.isMetaDataAvailable(): self.setTrackInfo("%s - %s" % ( self.player.metaData(QMediaMetaData.AlbumArtist), self.player.metaData(QMediaMetaData.Title))) def previousClicked(self): # Go to the previous track if we are within the first 5 seconds of # playback. Otherwise, seek to the beginning. if self.player.position() <= 5000: self.playlist.previous() else: self.player.setPosition(0) def jump(self, index): if index.isValid(): self.playlist.setCurrentIndex(index.row()) self.player.play() def seek(self, seconds): self.player.setPosition(seconds * 1000) def setState(self, status): if status == QMediaPlayer.StoppedState: self._finished_song = True elif status == QMediaPlayer.PlayingState: if self._finished_song or (self._lyrics_fading and not self._should_fade_out): self._finished_song = False self._lyrics_fading = False self.songtext_widget.fade_in() def stop_clicked(self): self.player.stop() self._fade_out_lyrics() def statusChanged(self, status): self.handleCursor(status) if status == QMediaPlayer.LoadingMedia: self.setStatusInfo("Loading...") elif status == QMediaPlayer.StalledMedia: self.setStatusInfo("Media Stalled") elif status == QMediaPlayer.EndOfMedia: QApplication.alert(self) elif status == QMediaPlayer.InvalidMedia: self.displayErrorMessage() else: self.setStatusInfo("") def handleCursor(self, status): if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia): self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def bufferingProgress(self, progress): self.setStatusInfo("Buffering %d%" % progress) def videoAvailableChanged(self, available): if available: self.fullScreenButton.clicked.connect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.connect( self.fullScreenButton.setChecked) if self.fullScreenButton.isChecked(): self.videoWidget.setFullScreen(True) else: self.fullScreenButton.clicked.disconnect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.disconnect( self.fullScreenButton.setChecked) self.videoWidget.setFullScreen(False) self.colorButton.setEnabled(available) def setTrackInfo(self, info): self.trackInfo = info # if self.statusInfo != "": # self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) # else: # self.setWindowTitle(self.trackInfo) def setStatusInfo(self, info): self.statusInfo = info # if self.statusInfo != "": # self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) # else: # self.setWindowTitle(self.trackInfo) def displayErrorMessage(self): self.setStatusInfo(self.player.errorString()) def updateDurationInfo(self, currentInfo): duration = self.duration if currentInfo or duration: currentTime = QTime((currentInfo / 3600) % 60, (currentInfo / 60) % 60, currentInfo % 60, (currentInfo * 1000) % 1000) totalTime = QTime((duration / 3600) % 60, (duration / 60) % 60, duration % 60, (duration * 1000) % 1000); format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' tStr = currentTime.toString(format) + " / " + totalTime.toString(format) else: tStr = "" self.labelDuration.setText(tStr) def showColorDialog(self): if self.colorDialog is None: brightnessSlider = QSlider(Qt.Horizontal) brightnessSlider.setRange(-100, 100) brightnessSlider.setValue(self.videoWidget.brightness()) brightnessSlider.sliderMoved.connect( self.videoWidget.setBrightness) self.videoWidget.brightnessChanged.connect( brightnessSlider.setValue) contrastSlider = QSlider(Qt.Horizontal) contrastSlider.setRange(-100, 100) contrastSlider.setValue(self.videoWidget.contrast()) contrastSlider.sliderMoved.connect(self.videoWidget.setContrast) self.videoWidget.contrastChanged.connect(contrastSlider.setValue) hueSlider = QSlider(Qt.Horizontal) hueSlider.setRange(-100, 100) hueSlider.setValue(self.videoWidget.hue()) hueSlider.sliderMoved.connect(self.videoWidget.setHue) self.videoWidget.hueChanged.connect(hueSlider.setValue) saturationSlider = QSlider(Qt.Horizontal) saturationSlider.setRange(-100, 100) saturationSlider.setValue(self.videoWidget.saturation()) saturationSlider.sliderMoved.connect( self.videoWidget.setSaturation) self.videoWidget.saturationChanged.connect( saturationSlider.setValue) layout = QFormLayout() layout.addRow("Brightness", brightnessSlider) layout.addRow("Contrast", contrastSlider) layout.addRow("Hue", hueSlider) layout.addRow("Saturation", saturationSlider) button = QPushButton("Close") layout.addRow(button) self.colorDialog = QDialog(self) self.colorDialog.setWindowTitle("Color Options") self.colorDialog.setLayout(layout) button.clicked.connect(self.colorDialog.close) self.colorDialog.show() def closeEvent(self, close_event): self._save_timings() self._save_settings() self.songtext_widget.close() self.settings_widget.close() def keyPressEvent(self, key_event): if key_event.key() == Qt.Key_Space: key_event.accept() if self.player.state() == QMediaPlayer.PlayingState: self.player.pause() elif self.player.state() in [QMediaPlayer.PausedState, QMediaPlayer.StoppedState]: self.player.play() elif key_event.key() == Qt.Key_M: key_event.accept() self.slider.set_closest_marker_to_current_progress() def _save_timings(self): if self.slider.dirty: with open(os.path.join(self.timings_path, "{}.json".format(self._song_number)), 'w') as f: json.dump([marker.progress for marker in self.markers], f, indent=2) def _save_settings(self): # TODO : refactor and use QSettings directly # with open(self.settings_path, 'w') as f: self.settings.update({ 'lyrics_screen': QApplication.desktop().screenNumber(self.songtext_widget), 'control_window_position': self.pos(), }) # json.dump(self.settings, f, indent=2) settings = QSettings("Maccesch", "SongScreen") for key, value in self.settings.items(): settings.setValue(key, value) def _load_settings(self): # try: # with open(self.settings_path, 'r') as f: # settings = json.load(f) settings = QSettings("Maccesch", "SongScreen") if settings.contains('lyrics_screen'): self.display_lyrics_on_screen(settings.value('lyrics_screen')) if settings.contains('control_window_position'): self.move(settings.value('control_window_position')) for key in settings.allKeys(): self.settings[key] = settings.value(key) # self.settings.update(settings) self.songtext_widget.set_font_size(self.settings['font_size']) self.songtext_widget.set_line_increment(self.settings['line_increment']) # except (FileNotFoundError, ValueError): # pass if not os.path.exists(self.lyrics_language_path) or not self.settings['lyrics_language']: languages = list( filter(lambda p: os.path.isdir(os.path.join(self.lyrics_path, p)) and p != "timings", os.listdir(self.lyrics_path)) ) self.settings['lyrics_language'] = languages[0] if languages else ""
class Player(QWidget): fullScreenChanged = pyqtSignal(bool) def __init__(self, playlist, parent=None): super(Player, self).__init__(parent) self.colorDialog = None self.trackInfo = "" self.statusInfo = "" self.duration = 0 self.player = QMediaPlayer() self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.metaDataChanged.connect(self.metaDataChanged) self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) self.player.mediaStatusChanged.connect(self.statusChanged) self.player.bufferStatusChanged.connect(self.bufferingProgress) self.player.videoAvailableChanged.connect(self.videoAvailableChanged) self.player.error.connect(self.displayErrorMessage) self.videoWidget = VideoWidget() self.player.setVideoOutput(self.videoWidget) self.playlistModel = PlaylistModel() self.playlistModel.setPlaylist(self.playlist) self.playlistView = QListView() self.playlistView.setModel(self.playlistModel) self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) self.playlistView.activated.connect(self.jump) self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, self.player.duration() / 1000) self.labelDuration = QLabel() self.slider.sliderMoved.connect(self.seek) # self.labelHistogram = QLabel() # self.labelHistogram.setText("Histogram:") # self.histogram = HistogramWidget() # histogramLayout = QHBoxLayout() # histogramLayout.addWidget(self.labelHistogram) # histogramLayout.addWidget(self.histogram, 1) self.probe = QVideoProbe() # self.probe.videoFrameProbed.connect(self.histogram.processFrame) self.probe.setSource(self.player) openButton = QPushButton("打开", clicked=self.open) controls = PlayerControls() controls.setState(self.player.state()) controls.setVolume(self.player.volume()) controls.setMuted(controls.isMuted()) controls.play.connect(self.player.play) controls.pause.connect(self.player.pause) controls.stop.connect(self.player.stop) controls.next.connect(self.playlist.next) controls.previous.connect(self.previousClicked) controls.changeVolume.connect(self.player.setVolume) controls.changeMuting.connect(self.player.setMuted) controls.changeRate.connect(self.player.setPlaybackRate) controls.stop.connect(self.videoWidget.update) self.player.stateChanged.connect(controls.setState) self.player.volumeChanged.connect(controls.setVolume) self.player.mutedChanged.connect(controls.setMuted) self.fullScreenButton = QPushButton("全屏") self.fullScreenButton.setCheckable(True) self.colorButton = QPushButton("颜色选项") self.colorButton.setEnabled(False) self.colorButton.clicked.connect(self.showColorDialog) displayLayout = QHBoxLayout() displayLayout.addWidget(self.videoWidget, 2) displayLayout.addWidget(self.playlistView) controlLayout = QHBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) controlLayout.addWidget(openButton) controlLayout.addStretch(1) controlLayout.addWidget(controls) controlLayout.addStretch(1) controlLayout.addWidget(self.fullScreenButton) controlLayout.addWidget(self.colorButton) layout = QVBoxLayout() layout.addLayout(displayLayout) hLayout = QHBoxLayout() hLayout.addWidget(self.slider) hLayout.addWidget(self.labelDuration) layout.addLayout(hLayout) layout.addLayout(controlLayout) # layout.addLayout(histogramLayout) self.setLayout(layout) if not self.player.isAvailable(): QMessageBox.warning( self, "Service not available", "The QMediaPlayer object does not have a valid service.\n" "Please check the media service plugins are installed.") controls.setEnabled(False) self.playlistView.setEnabled(False) openButton.setEnabled(False) self.colorButton.setEnabled(False) self.fullScreenButton.setEnabled(False) self.metaDataChanged() self.addToPlaylist(playlist) def open(self): fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files") self.addToPlaylist(fileNames) def addToPlaylist(self, fileNames): for name in fileNames: fileInfo = QFileInfo(name) if fileInfo.exists(): url = QUrl.fromLocalFile(fileInfo.absoluteFilePath()) if fileInfo.suffix().lower() == 'm3u': self.playlist.load(url) else: self.playlist.addMedia(QMediaContent(url)) else: url = QUrl(name) if url.isValid(): self.playlist.addMedia(QMediaContent(url)) def durationChanged(self, duration): duration /= 1000 self.duration = duration self.slider.setMaximum(duration) def positionChanged(self, progress): progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) self.updateDurationInfo(progress) def metaDataChanged(self): if self.player.isMetaDataAvailable(): self.setTrackInfo( "%s - %s" % (self.player.metaData(QMediaMetaData.AlbumArtist), self.player.metaData(QMediaMetaData.Title))) def previousClicked(self): # Go to the previous track if we are within the first 5 seconds of # playback. Otherwise, seek to the beginning. if self.player.position() <= 5000: self.playlist.previous() else: self.player.setPosition(0) def jump(self, index): if index.isValid(): self.playlist.setCurrentIndex(index.row()) self.player.play() def playlistPositionChanged(self, position): self.playlistView.setCurrentIndex(self.playlistModel.index( position, 0)) def seek(self, seconds): self.player.setPosition(seconds * 1000) def statusChanged(self, status): self.handleCursor(status) if status == QMediaPlayer.LoadingMedia: self.setStatusInfo("Loading...") elif status == QMediaPlayer.StalledMedia: self.setStatusInfo("Media Stalled") elif status == QMediaPlayer.EndOfMedia: QApplication.alert(self) elif status == QMediaPlayer.InvalidMedia: self.displayErrorMessage() else: self.setStatusInfo("") def handleCursor(self, status): if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia): self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def bufferingProgress(self, progress): self.setStatusInfo("Buffering %d%" % progress) def videoAvailableChanged(self, available): if available: self.fullScreenButton.clicked.connect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.connect( self.fullScreenButton.setChecked) if self.fullScreenButton.isChecked(): self.videoWidget.setFullScreen(True) else: self.fullScreenButton.clicked.disconnect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.disconnect( self.fullScreenButton.setChecked) self.videoWidget.setFullScreen(False) self.colorButton.setEnabled(available) def setTrackInfo(self, info): self.trackInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def setStatusInfo(self, info): self.statusInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def displayErrorMessage(self): self.setStatusInfo(self.player.errorString()) def updateDurationInfo(self, currentInfo): duration = self.duration if currentInfo or duration: currentTime = QTime((currentInfo / 3600) % 60, (currentInfo / 60) % 60, currentInfo % 60, (currentInfo * 1000) % 1000) totalTime = QTime((duration / 3600) % 60, (duration / 60) % 60, duration % 60, (duration * 1000) % 1000) format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' tStr = currentTime.toString(format) + " / " + totalTime.toString( format) else: tStr = "" self.labelDuration.setText(tStr) def showColorDialog(self): if self.colorDialog is None: brightnessSlider = QSlider(Qt.Horizontal) brightnessSlider.setRange(-100, 100) brightnessSlider.setValue(self.videoWidget.brightness()) brightnessSlider.sliderMoved.connect( self.videoWidget.setBrightness) self.videoWidget.brightnessChanged.connect( brightnessSlider.setValue) contrastSlider = QSlider(Qt.Horizontal) contrastSlider.setRange(-100, 100) contrastSlider.setValue(self.videoWidget.contrast()) contrastSlider.sliderMoved.connect(self.videoWidget.setContrast) self.videoWidget.contrastChanged.connect(contrastSlider.setValue) hueSlider = QSlider(Qt.Horizontal) hueSlider.setRange(-100, 100) hueSlider.setValue(self.videoWidget.hue()) hueSlider.sliderMoved.connect(self.videoWidget.setHue) self.videoWidget.hueChanged.connect(hueSlider.setValue) saturationSlider = QSlider(Qt.Horizontal) saturationSlider.setRange(-100, 100) saturationSlider.setValue(self.videoWidget.saturation()) saturationSlider.sliderMoved.connect( self.videoWidget.setSaturation) self.videoWidget.saturationChanged.connect( saturationSlider.setValue) layout = QFormLayout() layout.addRow("亮度", brightnessSlider) layout.addRow("对比度", contrastSlider) layout.addRow("色调", hueSlider) layout.addRow("饱和度", saturationSlider) button = QPushButton("关闭") layout.addRow(button) self.colorDialog = QDialog(self) self.colorDialog.setWindowTitle("颜色选项") self.colorDialog.setLayout(layout) button.clicked.connect(self.colorDialog.close) self.colorDialog.show()
class MediaPlayerTab(GalacteekTab): statePlaying = QMediaPlayer.PlayingState statePaused = QMediaPlayer.PausedState stateStopped = QMediaPlayer.StoppedState def __init__(self, *args, **kw): super(MediaPlayerTab, self).__init__(*args, **kw) self.playlistIpfsPath = None self.playlist = QMediaPlaylist() self.model = ListModel(self.playlist) self.playlistsMenu = QMenu(self) self.playlistsMenu.triggered.connect(self.onPlaylistsMenu) self.pListWidget = QWidget(self) self.uipList = ui_mediaplaylist.Ui_MediaPlaylist() self.uipList.setupUi(self.pListWidget) self.uipList.savePlaylistButton.clicked.connect(self.onSavePlaylist) self.uipList.savePlaylistButton.setEnabled(False) self.uipList.loadPlaylistButton.setPopupMode(QToolButton.InstantPopup) self.uipList.loadPlaylistButton.setMenu(self.playlistsMenu) self.clipMenu = QMenu(self) self.copyPathAction = QAction(getIconIpfsIce(), iCopyPlaylistPath(), self, triggered=self.onCopyPlaylistPath) self.loadPathAction = QAction(getIconIpfsIce(), iLoadPlaylistFromPath(), self, triggered=self.onLoadPlaylistPath) self.copyPathAction.setEnabled(False) self.clipMenu.addAction(self.copyPathAction) self.clipMenu.addAction(self.loadPathAction) self.uipList.clipPlaylistButton.setPopupMode(QToolButton.InstantPopup) self.uipList.clipPlaylistButton.setMenu(self.clipMenu) self.uipList.clearButton.clicked.connect(self.onClearPlaylist) self.uipList.nextButton.clicked.connect(self.onPlaylistNext) self.uipList.previousButton.clicked.connect(self.onPlaylistPrevious) self.uipList.nextButton.setIcon(self.style().standardIcon( QStyle.SP_MediaSkipForward)) self.uipList.previousButton.setIcon(self.style().standardIcon( QStyle.SP_MediaSkipBackward)) self.pListView = self.uipList.listView self.pListView.mousePressEvent = self.playlistMousePressEvent self.pListView.setModel(self.model) self.pListView.setResizeMode(QListView.Adjust) self.pListView.setMinimumWidth(self.width() / 2) self.duration = None self.playerState = None self.player = QMediaPlayer(self) self.player.setPlaylist(self.playlist) self.videoWidget = MPlayerVideoWidget(self.player, self) self.useUpdates(True) self.videoWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.player.setVideoOutput(self.videoWidget) self.player.error.connect(self.onError) self.player.stateChanged.connect(self.onStateChanged) self.player.metaDataChanged.connect(self.onMetaData) self.player.durationChanged.connect(self.mediaDurationChanged) self.player.positionChanged.connect(self.mediaPositionChanged) self.player.videoAvailableChanged.connect(self.onVideoAvailable) self.pListView.activated.connect(self.onListActivated) self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) self.playlist.currentMediaChanged.connect(self.playlistMediaChanged) self.playlist.mediaInserted.connect(self.playlistMediaInserted) self.playlist.mediaRemoved.connect(self.playlistMediaRemoved) self.togglePList = QToolButton(self) self.togglePList.setIcon(self.style().standardIcon( QStyle.SP_ArrowRight)) self.togglePList.setFixedSize(32, 128) self.togglePList.clicked.connect(self.onTogglePlaylist) self.clipboardMediaItem = None self.clipboardButton = QToolButton(clicked=self.onClipboardClicked) self.clipboardButton.setIcon(getIconClipboard()) self.clipboardButton.setEnabled(False) self.clipboardButton.setToolTip('Load media from clipboard') self.pinButton = QToolButton(clicked=self.onPinMediaClicked) self.pinButton.setIcon(getIcon('pin.png')) self.processClipboardItem(self.app.clipTracker.current, force=True) self.app.clipTracker.currentItemChanged.connect(self.onClipItemChange) self.playButton = QToolButton(clicked=self.onPlayClicked) self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.pauseButton = QToolButton(clicked=self.onPauseClicked) self.pauseButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) self.pauseButton.setEnabled(False) self.stopButton = QToolButton(clicked=self.onStopClicked) self.stopButton.setIcon(self.style().standardIcon(QStyle.SP_MediaStop)) self.stopButton.setEnabled(False) self.fullscreenButton = QToolButton(clicked=self.onFullScreen) self.fullscreenButton.setIcon(getIcon('fullscreen.png')) self.fullscreenButton.setToolTip(iFullScreen()) self.seekSlider = QSlider(Qt.Horizontal, sliderMoved=self.onSeek) self.seekSlider.sliderReleased.connect(self.onSliderReleased) self.seekSlider.setObjectName('mediaPlayerSlider') self.durationLabel = QLabel() vLayout = QVBoxLayout() hLayoutControls = QHBoxLayout() hLayoutControls.setContentsMargins(0, 0, 0, 0) hLayoutControls.addWidget(self.clipboardButton) hLayoutControls.addWidget(self.pinButton) hLayoutControls.addWidget(self.playButton) hLayoutControls.addWidget(self.pauseButton) hLayoutControls.addWidget(self.stopButton) hLayoutControls.addWidget(self.seekSlider) hLayoutControls.addWidget(self.durationLabel) hLayoutControls.addWidget(self.fullscreenButton) vLayout.addWidget(self.videoWidget) vLayout.addLayout(hLayoutControls) hLayout = QHBoxLayout() hLayout.addLayout(vLayout) hLayout.addWidget(self.pListWidget) hLayout.addWidget(self.togglePList) self.pListWidget.hide() self.vLayout.addLayout(hLayout) self.update() self.videoWidget.changeFocus() @property def isPlaying(self): return self.playerState == self.statePlaying @property def isPaused(self): return self.playerState == self.statePaused @property def isStopped(self): return self.playerState == self.stateStopped def useUpdates(self, updates=True): # Enable widget updates or not on the video widget self.videoWidget.setUpdatesEnabled(updates) def update(self): self.app.task(self.updatePlaylistsMenu) def onFullScreen(self): self.videoWidget.viewFullScreen(True) def onClearPlaylist(self): self.copyPathAction.setEnabled(False) self.player.stop() self.clearPlaylist() def onLoadPlaylistPath(self): current = self.app.clipTracker.getCurrent() if current: self.app.task(self.loadPlaylistFromPath, current.path) def onCopyPlaylistPath(self): if self.playlistIpfsPath: self.app.setClipboardText(self.playlistIpfsPath) def onPinMediaClicked(self): currentMedia = self.playlist.currentMedia() if currentMedia.isNull(): return messageBox(iNoMediaInPlaylist()) ensure(self.pinMedia(currentMedia)) @ipfsOp async def pinMedia(self, ipfsop, media): mediaUrl = qurlPercentDecode(media.canonicalUrl()) path = IPFSPath(mediaUrl, autoCidConv=True) if path.valid: await ipfsop.ctx.pin(str(path), qname='mediaplayer') @ipfsOp async def updatePlaylistsMenu(self, ipfsop): currentList = [ action.text() for action in self.playlistsMenu.actions() ] listing = await ipfsop.filesList(self.profile.pathPlaylists) for entry in listing: if entry['Name'] in currentList: continue action = QAction(entry['Name'], self) action.setData(entry) self.playlistsMenu.addAction(action) def playlistShowContextMenu(self, event): selModel = self.pListView.selectionModel() idx = self.pListView.indexAt(event.pos()) if not idx.isValid(): return path = self.model.data(idx) if path: selModel.reset() selModel.select(idx, QItemSelectionModel.Select) menu = QMenu(self) menu.addAction(getIcon('clear-all.png'), iPlaylistRemoveMedia(), functools.partial(self.onRemoveMediaFromIndex, idx)) menu.exec_(event.globalPos()) def onRemoveMediaFromIndex(self, idx): self.playlist.removeMedia(idx.row()) def playlistMousePressEvent(self, event): if event.button() == Qt.RightButton: self.pListView.selectionModel().reset() self.playlistShowContextMenu(event) else: if not self.pListView.indexAt(event.pos()).isValid(): self.deselectPlaylistItems() QListView.mousePressEvent(self.pListView, event) def onPlaylistsMenu(self, action): entry = action.data() self.app.task(self.loadPlaylistFromPath, joinIpfs(entry['Hash'])) def onSavePlaylist(self): paths = self.playlistGetPaths() listName = inputText(title=iPlaylistName(), label=iPlaylistName()) if not listName: return obj = JSONPlaylistV1(listName=listName, itemPaths=paths) self.app.task(self.savePlaylist, obj, listName) @ipfsOp async def savePlaylist(self, ipfsop, obj, name): objPath = os.path.join(self.profile.pathPlaylists, name) exists = await ipfsop.filesStat(objPath) if exists: await ipfsop.filesRm(objPath) ent = await ipfsop.client.core.add_json(obj.root) if ent: await ipfsop.filesLinkFp(ent, objPath) self.playlistIpfsPath = joinIpfs(ent['Hash']) self.copyPathAction.setEnabled(True) self.update() @ipfsOp async def loadPlaylistFromPath(self, ipfsop, path): try: obj = await ipfsop.jsonLoad(path) except Exception: return messageBox(iCannotLoadPlaylist()) if obj is None: return messageBox(iCannotLoadPlaylist()) try: # Assume v1 format for now, when the format evolves we'll just # use json validation pList = JSONPlaylistV1(data=obj) self.clearPlaylist() for item in pList.items(): self.queueFromPath(item['path']) self.playlistIpfsPath = path self.copyPathAction.setEnabled(True) except Exception: return messageBox(iCannotLoadPlaylist()) def playlistMediaInserted(self, start, end): self.uipList.savePlaylistButton.setEnabled( self.playlist.mediaCount() > 0) def playlistMediaRemoved(self, start, end): self.uipList.savePlaylistButton.setEnabled( self.playlist.mediaCount() > 0) self.model.modelReset.emit() def playlistGetPaths(self): return [u.path() for u in self.playlistGetUrls()] def playlistGetUrls(self): urls = [] for idx in range(0, self.playlist.mediaCount()): media = self.playlist.media(idx) urls.append(media.canonicalUrl()) return urls def onClipItemChange(self, item): self.processClipboardItem(item) def processClipboardItem(self, item, force=False): if not item: return def analyzeMimeType(cItem): if cItem.mimeCategory in ['audio', 'video', 'image']: self.clipboardMediaItem = cItem self.clipboardButton.setEnabled(True) self.clipboardButton.setToolTip(cItem.path) else: self.clipboardButton.setEnabled(False) self.clipboardButton.setToolTip(iClipboardEmpty()) if force: analyzeMimeType(item) else: item.mimeTypeAvailable.connect(lambda mType: analyzeMimeType(item)) def onClipboardClicked(self): if self.clipboardMediaItem: self.playFromPath(self.clipboardMediaItem.path) else: messageBox('Not a multimedia resource') def onSliderReleased(self): pass def onPlaylistNext(self): self.playlist.next() def onPlaylistPrevious(self): self.playlist.previous() def onPlayClicked(self): self.player.play() def onPauseClicked(self): if self.isPlaying: self.player.pause() elif self.isPaused: self.player.play() def onStopClicked(self): self.player.stop() self.player.setPosition(0) self.seekSlider.setValue(0) self.seekSlider.setRange(0, 0) def onSeek(self, seconds): if self.player.isSeekable(): self.player.setPosition(seconds * 1000) def onTogglePlaylist(self): self.pListWidget.setVisible(self.pListWidget.isHidden()) def onError(self, error): messageBox(iPlayerError(error)) def onStateChanged(self, state): self.playerState = state self.updateControls(state) def updateControls(self, state): if self.isStopped: self.stopButton.setEnabled(False) self.pauseButton.setEnabled(False) self.playButton.setEnabled(True) self.seekSlider.setEnabled(False) self.duration = None elif self.isPlaying: self.seekSlider.setRange(0, self.player.duration() / 1000) self.seekSlider.setEnabled(True) self.pauseButton.setEnabled(True) self.playButton.setEnabled(False) self.stopButton.setEnabled(True) def onListActivated(self, index): if index.isValid(): self.playlist.setCurrentIndex(index.row()) self.player.play() def onMetaData(self): # Unfinished if self.player.isMetaDataAvailable(): availableKeys = self.player.availableMetaData() for key in availableKeys: self.player.metaData(key) def playFromUrl(self, url, mediaName=None): if self.isPlaying: self.player.stop() cUrls = self.playlistGetUrls() for u in cUrls: if u.toString() == url.toString(): return messageBox(iAlreadyInPlaylist()) media = QMediaContent(url) if self.playlist.addMedia(media): count = self.model.rowCount() if count > 0: self.playlist.setCurrentIndex(count - 1) self.player.play() def playFromPath(self, path, mediaName=None): mediaUrl = self.app.subUrl(path) self.playFromUrl(mediaUrl) def queueFromPath(self, path, playLast=False, mediaName=None): mediaUrl = self.app.subUrl(path) self.playlist.addMedia(QMediaContent(mediaUrl)) if playLast: count = self.playlist.mediaCount() if count > 0: self.player.stop() self.playlist.setCurrentIndex(count - 1) self.player.play() def clearPlaylist(self): self.playlist.clear() self.pListView.reset() def playlistPositionChanged(self, position): self.pListView.setCurrentIndex(self.model.index(position, 0)) def deselectPlaylistItems(self): self.pListView.selectionModel().reset() def playlistMediaChanged(self, media): selModel = self.pListView.selectionModel() self.deselectPlaylistItems() self.model.modelReset.emit() idx = self.model.index(self.playlist.currentIndex(), 0) if idx.isValid(): selModel.select(idx, QItemSelectionModel.Select) def onVideoAvailable(self, available): if available: if self.isPlaying: self.useUpdates(False) elif self.isStopped or self.isPaused: self.useUpdates(True) else: self.useUpdates(True) def mediaDurationChanged(self, duration): self.duration = duration / 1000 self.seekSlider.setMaximum(self.duration) def mediaPositionChanged(self, progress): progress /= 1000 if self.duration: cTime = durationConvert(progress) tTime = durationConvert(self.duration) self.durationLabel.setText('{0} ({1})'.format( cTime.toString(), tTime.toString())) if not self.seekSlider.isSliderDown(): self.seekSlider.setValue(progress) async def onClose(self): self.player.stop() self.player.setMedia(QMediaContent(None)) return True def playerAvailable(self): return mediaPlayerAvailable(player=self.player)
class Player(QWidget): fullScreenChanged = pyqtSignal(bool) def __init__(self, playlist, parent=None): super(Player, self).__init__(parent) self.colorDialog = None self.trackInfo = "" self.statusInfo = "" self.duration = 0 self.player = QMediaPlayer() self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.metaDataChanged.connect(self.metaDataChanged) self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) self.player.mediaStatusChanged.connect(self.statusChanged) self.player.bufferStatusChanged.connect(self.bufferingProgress) self.player.videoAvailableChanged.connect(self.videoAvailableChanged) self.player.error.connect(self.displayErrorMessage) self.videoWidget = VideoWidget() self.player.setVideoOutput(self.videoWidget) self.playlistModel = PlaylistModel() self.playlistModel.setPlaylist(self.playlist) self.playlistView = QListView() self.playlistView.setModel(self.playlistModel) self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) self.playlistView.activated.connect(self.jump) self.script_box = QPlainTextEdit() self.segmentList = QTreeWidget() self.segmentList.setSortingEnabled(True) #self.segmentList.setColumnCount(5) self.segmentList.setColumnCount(4) #self.segmentList.setHeaderLabels(['Product','Start','Label','Tool','Behavior']) self.segmentList.setHeaderLabels(['Start segment', 'End segment', 'Label', 'Event']) ''' self.productTextInput = QLineEdit() self.startTextInput = QLineEdit() self.labelTextInput = QLineEdit() self.toolTextInput = QLineEdit() self.behaviorTextInput = QLineEdit() ''' self.startTextInput = QLineEdit() self.endTextInput = QLineEdit() self.labelTextInput = QLineEdit() self.contentTextInput = QLineEdit() self.addBtn = QPushButton("Add") self.addBtn.clicked.connect(self.addSegment) self.saveBtn = QPushButton("Save") self.saveBtn.clicked.connect(self.saveSegments) self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, self.player.duration() / 1000) self.labelDuration = QLabel() self.slider.sliderMoved.connect(self.seek) self.labelHistogram = QLabel() self.labelHistogram.setText("Histogram:") self.histogram = HistogramWidget() histogramLayout = QHBoxLayout() histogramLayout.addWidget(self.labelHistogram) histogramLayout.addWidget(self.histogram, 1) self.probe = QVideoProbe() self.probe.videoFrameProbed.connect(self.histogram.processFrame) self.probe.setSource(self.player) openButton = QPushButton("Open", clicked=self.open) if os.path.isdir(VIDEO_DIR): self.open_folder(VIDEO_DIR) controls = PlayerControls() controls.setState(self.player.state()) controls.setVolume(self.player.volume()) controls.setMuted(controls.isMuted()) controls.play.connect(self.player.play) controls.pause.connect(self.player.pause) controls.stop.connect(self.player.stop) controls.next.connect(self.playlist.next) controls.previous.connect(self.previousClicked) controls.changeVolume.connect(self.player.setVolume) controls.changeMuting.connect(self.player.setMuted) controls.changeRate.connect(self.player.setPlaybackRate) controls.stop.connect(self.videoWidget.update) self.player.stateChanged.connect(controls.setState) self.player.volumeChanged.connect(controls.setVolume) self.player.mutedChanged.connect(controls.setMuted) #self.segmentButton = QPushButton("Segment") #self.segmentButton.clicked.connect(self.createNewSegment) self.startSegmentButton = QPushButton("Start Segment") self.startSegmentButton.clicked.connect(self.createNewStartSegment) # self.segmentButton.setCheckable(True) self.endSegmentButton = QPushButton("End Segment") self.endSegmentButton.clicked.connect(self.createNewEndSegment) #self.fullScreenButton = QPushButton("FullScreen") #self.fullScreenButton.setCheckable(True) self.colorButton = QPushButton("Color Options...") self.colorButton.setEnabled(False) self.colorButton.clicked.connect(self.showColorDialog) displayLayout = QHBoxLayout() # videoLayout = QVBoxLayout() # videoLayout.addWidget(self.videoWidget) # videoLayout.addWidget(self.script_box) displayLayout.addWidget(self.videoWidget, 3) editLayout = QVBoxLayout() editLayout.addWidget(self.playlistView, 2) #editLayout.addWidget(self.script_box, 4) editLayout.addWidget(self.segmentList, 3) segmentInputLayout = QHBoxLayout() ''' segmentInputLayout.addWidget(self.productTextInput) segmentInputLayout.addWidget(self.startTextInput) segmentInputLayout.addWidget(self.labelTextInput) segmentInputLayout.addWidget(self.toolTextInput) segmentInputLayout.addWidget(self.behaviorTextInput) ''' segmentInputLayout.addWidget(self.startTextInput) segmentInputLayout.addWidget(self.endTextInput) segmentInputLayout.addWidget(self.labelTextInput) segmentInputLayout.addWidget(self.contentTextInput) editLayout.addLayout(segmentInputLayout,1) displayLayout.addLayout(editLayout, 2) controlLayout = QHBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) controlLayout.addWidget(openButton) controlLayout.addStretch(1) controlLayout.addWidget(controls) controlLayout.addStretch(1) #controlLayout.addWidget(self.segmentButton) controlLayout.addWidget(self.startSegmentButton) controlLayout.addWidget(self.endSegmentButton) controlLayout.addWidget(self.addBtn) controlLayout.addWidget(self.saveBtn) #controlLayout.addWidget(self.fullScreenButton) # controlLayout.addWidget(self.colorButton) layout = QVBoxLayout() layout.addLayout(displayLayout, 2) hLayout = QHBoxLayout() hLayout.addWidget(self.slider) hLayout.addWidget(self.labelDuration) layout.addLayout(hLayout) layout.addLayout(controlLayout) # layout.addLayout(histogramLayout) self.setLayout(layout) if not self.player.isAvailable(): QMessageBox.warning(self, "Service not available", "The QMediaPlayer object does not have a valid service.\n" "Please check the media service plugins are installed.") controls.setEnabled(False) self.playlistView.setEnabled(False) openButton.setEnabled(False) self.colorButton.setEnabled(False) #self.fullScreenButton.setEnabled(False) self.metaDataChanged() self.addToPlaylist(playlist) def open(self): fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files") self.addToPlaylist(fileNames) def open_folder(self, folder_path): fileNames = [folder_path+x for x in os.listdir(folder_path) if x.endswith('.mp4')] self.addToPlaylist(fileNames) def addToPlaylist(self, fileNames): for name in fileNames: fileInfo = QFileInfo(name) if fileInfo.exists(): url = QUrl.fromLocalFile(fileInfo.absoluteFilePath()) if fileInfo.suffix().lower() == 'm3u': self.playlist.load(url) else: self.playlist.addMedia(QMediaContent(url)) else: url = QUrl(name) if url.isValid(): self.playlist.addMedia(QMediaContent(url)) def addSegment(self): item = TreeWidgetItem(self.segmentList) ''' item.setText(0, self.productTextInput.text()) item.setText(1, self.startTextInput.text()) item.setText(2, self.labelTextInput.text()) item.setText(3, self.toolTextInput.text()) item.setText(4, self.behaviorTextInput.text()) ''' item.setText(0, self.startTextInput.text()) item.setText(1, self.endTextInput.text()) item.setText(2, self.labelTextInput.text()) item.setText(3, self.contentTextInput.text()) item.setFlags(item.flags() | Qt.ItemIsEditable) self.segmentList.addTopLevelItem(item) self.segmentList.sortByColumn(0, Qt.AscendingOrder) self.clear_input_boxes() self.player.play() def saveSegments(self): itemCnt = self.segmentList.topLevelItemCount() colCnt = self.segmentList.columnCount() save_dict = {'segments':[]} for i in range(itemCnt): item = self.segmentList.topLevelItem(i) temp_data = [] for j in range(colCnt): temp_data.append(item.text(j)) #temp_dict = {'product': temp_data[0], 'start': temp_data[1], 'label': temp_data[2], 'tool': temp_data[3], 'behavior': temp_data[4]} if len(temp_data[0]) > 0 and len(temp_data[1]) > 0 and (':' in temp_data[0]) and (':' in temp_data[1]): start_interval_seconds = 0 j = 0 while j < len(temp_data[0].split(':')): start_interval_seconds += (int(temp_data[0].split(':')[- 1 - j]) * (60 ** j)) j += 1 end_interval_seconds = 0 j = 0 while j < len(temp_data[1].split(':')): end_interval_seconds += (int(temp_data[1].split(':')[- 1 - j]) * (60 ** j)) j += 1 else: start_interval_seconds = '' end_interval_seconds = '' temp_dict = {'start_segment': start_interval_seconds, 'end_segment': end_interval_seconds, 'label': temp_data[2], 'event': temp_data[3]} save_dict['segments'].append(temp_dict) import json file_name = self.playlist.currentMedia().canonicalUrl().fileName() with open(SEGMENT_DIR+file_name.replace('.mp4','.json'),'w') as file: json.dump(save_dict, file) def durationChanged(self, duration): duration /= 1000 self.duration = duration self.slider.setMaximum(duration) def positionChanged(self, progress): progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) self.updateDurationInfo(progress) def metaDataChanged(self): if self.player.isMetaDataAvailable(): self.setTrackInfo("%s - %s" % ( self.player.metaData(QMediaMetaData.AlbumArtist), self.player.metaData(QMediaMetaData.Title))) def previousClicked(self): # Go to the previous track if we are within the first 5 seconds of # playback. Otherwise, seek to the beginning. if self.player.position() <= 5000: self.playlist.previous() else: self.player.setPosition(0) def clear_input_boxes(self): ''' self.productTextInput.clear() self.startTextInput.clear() self.labelTextInput.clear() self.toolTextInput.clear() self.behaviorTextInput.clear() ''' self.startTextInput.clear() self.endTextInput.clear() self.labelTextInput.clear() self.contentTextInput.clear() def jump(self, index): if index.isValid(): self.playlist.setCurrentIndex(index.row()) self.player.play() file_name = self.playlist.currentMedia().canonicalUrl().fileName() ''' script_file_name = file_name.replace('.mp4','.txt') if os.path.isfile(SCRIPT_DIR+script_file_name): text=open(SCRIPT_DIR+script_file_name).read() self.script_box.setPlainText(text) ''' segment_file_path = SEGMENT_DIR + file_name.replace('.mp4','.json') json_dict = self.open_json(segment_file_path) self.clear_input_boxes() self.segmentList.clear() for segment in json_dict["segments"]: item = TreeWidgetItem(self.segmentList) ''' item.setText(0, segment['product']) item.setText(1, str(segment['start'])) item.setText(2, segment['label']) item.setText(3, segment['tool']) item.setText(4, segment['behavior']) ''' item.setText(0, segment['start_segment']) item.setText(1, segment['end_segment']) item.setText(2, segment['label']) item.setText(3, segment['content']) item.setFlags(item.flags() | Qt.ItemIsEditable) self.segmentList.addTopLevelItem(item) # print([str(x.text()) for x in self.segmentList.currentItem()]) def open_json(self, file_path): import json try: with open(file_path, 'r') as file: json_dict = json.loads(file.read()) except: json_dict = {"segments":[]} # json_dict = {"segments":[{"product":"Sorry","start":"File not found.","label":"","tool":"","behavior":""}]} return json_dict def playlistPositionChanged(self, position): self.playlistView.setCurrentIndex( self.playlistModel.index(position, 0)) def seek(self, seconds): self.player.setPosition(seconds * 1000) def statusChanged(self, status): self.handleCursor(status) if status == QMediaPlayer.LoadingMedia: self.setStatusInfo("Loading...") elif status == QMediaPlayer.StalledMedia: self.setStatusInfo("Media Stalled") elif status == QMediaPlayer.EndOfMedia: QApplication.alert(self) elif status == QMediaPlayer.InvalidMedia: self.displayErrorMessage() else: self.setStatusInfo("") def handleCursor(self, status): if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia): self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def bufferingProgress(self, progress): self.setStatusInfo("Buffering %d%" % progress) def videoAvailableChanged(self, available): ''' if available: self.fullScreenButton.clicked.connect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.connect( self.fullScreenButton.setChecked) if self.fullScreenButton.isChecked(): self.videoWidget.setFullScreen(True) else: self.fullScreenButton.clicked.disconnect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.disconnect( self.fullScreenButton.setChecked) self.videoWidget.setFullScreen(False) ''' self.colorButton.setEnabled(available) def setTrackInfo(self, info): self.trackInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def setStatusInfo(self, info): self.statusInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def displayErrorMessage(self): self.setStatusInfo(self.player.errorString()) def updateDurationInfo(self, currentInfo): duration = self.duration if currentInfo or duration: currentTime = QTime((currentInfo/3600)%60, (currentInfo/60)%60, currentInfo%60, (currentInfo*1000)%1000) totalTime = QTime((duration/3600)%60, (duration/60)%60, duration%60, (duration*1000)%1000); format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' tStr = currentTime.toString(format) + " / " + totalTime.toString(format) else: tStr = "" self.labelDuration.setText(tStr) ''' def createNewSegment(self): self.startTextInput.setText(str(int(self.player.position()/1000))) ''' def createNewStartSegment(self): seconds = int(self.player.position()/1000) self.startTextInput.setText("{:02d}".format(math.floor(seconds / 3600)) + ':' + "{:02d}".format( math.floor((seconds / 60)) - math.floor(seconds / 3600) * 60) + ':' + "{:02d}".format(seconds % 60)) def createNewEndSegment(self): seconds = int(self.player.position() / 1000) self.endTextInput.setText("{:02d}".format(math.floor(seconds / 3600)) + ':' + "{:02d}".format( math.floor((seconds / 60)) - math.floor(seconds / 3600) * 60) + ':' + "{:02d}".format(seconds % 60)) self.player.pause() def showColorDialog(self): if self.colorDialog is None: brightnessSlider = QSlider(Qt.Horizontal) brightnessSlider.setRange(-100, 100) brightnessSlider.setValue(self.videoWidget.brightness()) brightnessSlider.sliderMoved.connect( self.videoWidget.setBrightness) self.videoWidget.brightnessChanged.connect( brightnessSlider.setValue) contrastSlider = QSlider(Qt.Horizontal) contrastSlider.setRange(-100, 100) contrastSlider.setValue(self.videoWidget.contrast()) contrastSlider.sliderMoved.connect(self.videoWidget.setContrast) self.videoWidget.contrastChanged.connect(contrastSlider.setValue) hueSlider = QSlider(Qt.Horizontal) hueSlider.setRange(-100, 100) hueSlider.setValue(self.videoWidget.hue()) hueSlider.sliderMoved.connect(self.videoWidget.setHue) self.videoWidget.hueChanged.connect(hueSlider.setValue) saturationSlider = QSlider(Qt.Horizontal) saturationSlider.setRange(-100, 100) saturationSlider.setValue(self.videoWidget.saturation()) saturationSlider.sliderMoved.connect( self.videoWidget.setSaturation) self.videoWidget.saturationChanged.connect( saturationSlider.setValue) layout = QFormLayout() layout.addRow("Brightness", brightnessSlider) layout.addRow("Contrast", contrastSlider) layout.addRow("Hue", hueSlider) layout.addRow("Saturation", saturationSlider) button = QPushButton("Close") layout.addRow(button) self.colorDialog = QDialog(self) self.colorDialog.setWindowTitle("Color Options") self.colorDialog.setLayout(layout) button.clicked.connect(self.colorDialog.close) self.colorDialog.show()
class VidCutter(QWidget): def __init__(self, parent): super(VidCutter, self).__init__(parent) self.novideoWidget = QWidget(self, autoFillBackground=True) self.parent = parent self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.videoWidget = VideoWidget(self) self.videoService = VideoService(self) QFontDatabase.addApplicationFont( MainWindow.get_path('fonts/DroidSansMono.ttf')) QFontDatabase.addApplicationFont( MainWindow.get_path('fonts/OpenSans.ttf')) fontSize = 12 if sys.platform == 'darwin' else 10 appFont = QFont('Open Sans', fontSize, 300) qApp.setFont(appFont) self.clipTimes = [] self.inCut = False self.movieFilename = '' self.movieLoaded = False self.timeformat = 'hh:mm:ss' self.finalFilename = '' self.totalRuntime = 0 self.initIcons() self.initActions() self.toolbar = QToolBar(floatable=False, movable=False, iconSize=QSize(40, 36)) self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toolbar.setStyleSheet('''QToolBar { spacing:10px; } QToolBar QToolButton { border:1px solid transparent; min-width:95px; font-size:11pt; font-weight:400; border-radius:5px; padding:1px 2px; color:#444; } QToolBar QToolButton:hover { border:1px inset #6A4572; color:#6A4572; background-color:rgba(255, 255, 255, 0.85); } QToolBar QToolButton:pressed { border:1px inset #6A4572; color:#6A4572; background-color:rgba(255, 255, 255, 0.25); } QToolBar QToolButton:disabled { color:#999; }''') self.initToolbar() self.appMenu, self.cliplistMenu = QMenu(), QMenu() self.initMenus() self.seekSlider = VideoSlider(parent=self, sliderMoved=self.setPosition) self.initNoVideo() self.cliplist = QListWidget( sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding), contextMenuPolicy=Qt.CustomContextMenu, uniformItemSizes=True, iconSize=QSize(100, 700), dragDropMode=QAbstractItemView.InternalMove, alternatingRowColors=True, customContextMenuRequested=self.itemMenu, dragEnabled=True) self.cliplist.setStyleSheet( 'QListView { border-radius:0; border:none; border-left:1px solid #B9B9B9; ' + 'border-right:1px solid #B9B9B9; } QListView::item { padding:10px 0; }' ) self.cliplist.setFixedWidth(185) self.cliplist.model().rowsMoved.connect(self.syncClipList) listHeader = QLabel(pixmap=QPixmap( MainWindow.get_path('images/clipindex.png'), 'PNG'), alignment=Qt.AlignCenter) listHeader.setStyleSheet( '''padding:5px; padding-top:8px; border:1px solid #b9b9b9; background-color:qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #FFF, stop: 0.5 #EAEAEA, stop: 0.6 #EAEAEA stop:1 #FFF);''' ) self.runtimeLabel = QLabel('<div align="right">00:00:00</div>', textFormat=Qt.RichText) self.runtimeLabel.setStyleSheet( '''font-family:Droid Sans Mono; font-size:10pt; color:#FFF; background:rgb(106, 69, 114) url(:images/runtime.png) no-repeat left center; padding:2px; padding-right:8px; border:1px solid #B9B9B9;''') self.clipindexLayout = QVBoxLayout(spacing=0) self.clipindexLayout.setContentsMargins(0, 0, 0, 0) self.clipindexLayout.addWidget(listHeader) self.clipindexLayout.addWidget(self.cliplist) self.clipindexLayout.addWidget(self.runtimeLabel) self.videoLayout = QHBoxLayout() self.videoLayout.setContentsMargins(0, 0, 0, 0) self.videoLayout.addWidget(self.novideoWidget) self.videoLayout.addLayout(self.clipindexLayout) self.timeCounter = QLabel('00:00:00 / 00:00:00', autoFillBackground=True, alignment=Qt.AlignCenter, sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed)) self.timeCounter.setStyleSheet( 'color:#FFF; background:#000; font-family:Droid Sans Mono; font-size:10.5pt; padding:4px;' ) videoplayerLayout = QVBoxLayout(spacing=0) videoplayerLayout.setContentsMargins(0, 0, 0, 0) videoplayerLayout.addWidget(self.videoWidget) videoplayerLayout.addWidget(self.timeCounter) self.videoplayerWidget = QWidget(self, visible=False) self.videoplayerWidget.setLayout(videoplayerLayout) self.muteButton = QPushButton(icon=self.unmuteIcon, flat=True, toolTip='Mute', statusTip='Toggle audio mute', iconSize=QSize(16, 16), cursor=Qt.PointingHandCursor, clicked=self.muteAudio) self.volumeSlider = QSlider(Qt.Horizontal, toolTip='Volume', statusTip='Adjust volume level', cursor=Qt.PointingHandCursor, value=50, minimum=0, maximum=100, sliderMoved=self.setVolume) self.menuButton = QPushButton( icon=self.menuIcon, flat=True, toolTip='Menu', statusTip='Media + application information', iconSize=QSize(24, 24), cursor=Qt.PointingHandCursor) self.menuButton.setMenu(self.appMenu) toolbarLayout = QHBoxLayout() toolbarLayout.addWidget(self.toolbar) toolbarLayout.setContentsMargins(2, 2, 2, 2) toolbarGroup = QGroupBox() toolbarGroup.setFlat(False) toolbarGroup.setCursor(Qt.PointingHandCursor) toolbarGroup.setLayout(toolbarLayout) toolbarGroup.setStyleSheet( '''QGroupBox { background-color:rgba(0, 0, 0, 0.1); border:1px inset #888; border-radius:5px; }''') controlsLayout = QHBoxLayout(spacing=0) controlsLayout.addStretch(1) controlsLayout.addWidget(toolbarGroup) controlsLayout.addStretch(1) controlsLayout.addWidget(self.muteButton) controlsLayout.addWidget(self.volumeSlider) controlsLayout.addSpacing(1) controlsLayout.addWidget(self.menuButton) layout = QVBoxLayout() layout.setContentsMargins(10, 10, 10, 4) layout.addLayout(self.videoLayout) layout.addWidget(self.seekSlider) layout.addSpacing(5) layout.addLayout(controlsLayout) layout.addSpacing(2) self.setLayout(layout) self.mediaPlayer.setVideoOutput(self.videoWidget) self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.durationChanged.connect(self.durationChanged) self.mediaPlayer.error.connect(self.handleError) def initNoVideo(self) -> None: novideoImage = QLabel( alignment=Qt.AlignCenter, autoFillBackground=False, pixmap=QPixmap(MainWindow.get_path('images/novideo.png'), 'PNG'), sizePolicy=QSizePolicy(QSizePolicy.Expanding, QSizePolicy.MinimumExpanding)) novideoImage.setBackgroundRole(QPalette.Dark) novideoImage.setContentsMargins(0, 20, 0, 20) self.novideoLabel = QLabel(alignment=Qt.AlignCenter, autoFillBackground=True, sizePolicy=QSizePolicy( QSizePolicy.Expanding, QSizePolicy.Minimum)) self.novideoLabel.setBackgroundRole(QPalette.Dark) self.novideoLabel.setContentsMargins(0, 20, 15, 60) novideoLayout = QVBoxLayout(spacing=0) novideoLayout.addWidget(novideoImage) novideoLayout.addWidget(self.novideoLabel, alignment=Qt.AlignTop) self.novideoMovie = QMovie( MainWindow.get_path('images/novideotext.gif')) self.novideoMovie.frameChanged.connect(self.setNoVideoText) self.novideoMovie.start() self.novideoWidget.setBackgroundRole(QPalette.Dark) self.novideoWidget.setLayout(novideoLayout) def initIcons(self) -> None: self.appIcon = QIcon(MainWindow.get_path('images/vidcutter.png')) self.openIcon = icon('fa.film', color='#444', color_active='#6A4572', scale_factor=0.9) self.playIcon = icon('fa.play-circle-o', color='#444', color_active='#6A4572', scale_factor=1.1) self.pauseIcon = icon('fa.pause-circle-o', color='#444', color_active='#6A4572', scale_factor=1.1) self.cutStartIcon = icon('fa.scissors', scale_factor=1.15, color='#444', color_active='#6A4572') endicon_normal = icon('fa.scissors', scale_factor=1.15, color='#444').pixmap(QSize(36, 36)).toImage() endicon_active = icon('fa.scissors', scale_factor=1.15, color='#6A4572').pixmap(QSize(36, 36)).toImage() self.cutEndIcon = QIcon() self.cutEndIcon.addPixmap( QPixmap.fromImage( endicon_normal.mirrored(horizontal=True, vertical=False)), QIcon.Normal, QIcon.Off) self.cutEndIcon.addPixmap( QPixmap.fromImage( endicon_active.mirrored(horizontal=True, vertical=False)), QIcon.Active, QIcon.Off) self.saveIcon = icon('fa.video-camera', color='#6A4572', color_active='#6A4572') self.muteIcon = QIcon(MainWindow.get_path('images/muted.png')) self.unmuteIcon = QIcon(MainWindow.get_path('images/unmuted.png')) self.upIcon = icon('ei.caret-up', color='#444') self.downIcon = icon('ei.caret-down', color='#444') self.removeIcon = icon('ei.remove', color='#B41D1D') self.removeAllIcon = icon('ei.trash', color='#B41D1D') self.successIcon = QIcon(MainWindow.get_path('images/success.png')) self.menuIcon = icon('fa.cog', color='#444', scale_factor=1.15) self.completePlayIcon = icon('fa.play', color='#444') self.completeOpenIcon = icon('fa.folder-open', color='#444') self.completeRestartIcon = icon('fa.retweet', color='#444') self.completeExitIcon = icon('fa.sign-out', color='#444') self.mediaInfoIcon = icon('fa.info-circle', color='#444') self.updateCheckIcon = icon('fa.cloud-download', color='#444') def initActions(self) -> None: self.openAction = QAction(self.openIcon, 'Open', self, statusTip='Open media file', triggered=self.openMedia) self.playAction = QAction(self.playIcon, 'Play', self, statusTip='Play media file', triggered=self.playMedia, enabled=False) self.cutStartAction = QAction(self.cutStartIcon, ' Start', self, toolTip='Start', statusTip='Set clip start marker', triggered=self.setCutStart, enabled=False) self.cutEndAction = QAction(self.cutEndIcon, ' End', self, toolTip='End', statusTip='Set clip end marker', triggered=self.setCutEnd, enabled=False) self.saveAction = QAction(self.saveIcon, 'Save', self, statusTip='Save clips to a new video file', triggered=self.cutVideo, enabled=False) self.moveItemUpAction = QAction( self.upIcon, 'Move up', self, statusTip='Move clip position up in list', triggered=self.moveItemUp, enabled=False) self.moveItemDownAction = QAction( self.downIcon, 'Move down', self, statusTip='Move clip position down in list', triggered=self.moveItemDown, enabled=False) self.removeItemAction = QAction( self.removeIcon, 'Remove clip', self, statusTip='Remove selected clip from list', triggered=self.removeItem, enabled=False) self.removeAllAction = QAction(self.removeAllIcon, 'Clear list', self, statusTip='Clear all clips from list', triggered=self.clearList, enabled=False) self.mediaInfoAction = QAction( self.mediaInfoIcon, 'Media information', self, statusTip='View current media file information', triggered=self.mediaInfo, enabled=False) self.updateCheckAction = QAction( self.updateCheckIcon, 'Check for updates...', self, statusTip='Check for application updates', triggered=self.updateCheck) self.aboutQtAction = QAction('About Qt', self, statusTip='About Qt', triggered=qApp.aboutQt) self.aboutAction = QAction('About %s' % qApp.applicationName(), self, statusTip='Credits and licensing', triggered=self.aboutInfo) def initToolbar(self) -> None: self.toolbar.addAction(self.openAction) self.toolbar.addAction(self.playAction) self.toolbar.addAction(self.cutStartAction) self.toolbar.addAction(self.cutEndAction) self.toolbar.addAction(self.saveAction) def initMenus(self) -> None: self.appMenu.addAction(self.mediaInfoAction) self.appMenu.addAction(self.updateCheckAction) self.appMenu.addSeparator() self.appMenu.addAction(self.aboutQtAction) self.appMenu.addAction(self.aboutAction) self.cliplistMenu.addAction(self.moveItemUpAction) self.cliplistMenu.addAction(self.moveItemDownAction) self.cliplistMenu.addSeparator() self.cliplistMenu.addAction(self.removeItemAction) self.cliplistMenu.addAction(self.removeAllAction) @staticmethod def getSpacer() -> QWidget: spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) return spacer def setRunningTime(self, runtime: str) -> None: self.runtimeLabel.setText('<div align="right">%s</div>' % runtime) @pyqtSlot(int) def setNoVideoText(self) -> None: self.novideoLabel.setPixmap(self.novideoMovie.currentPixmap()) def itemMenu(self, pos: QPoint) -> None: globalPos = self.cliplist.mapToGlobal(pos) self.moveItemUpAction.setEnabled(False) self.moveItemDownAction.setEnabled(False) self.removeItemAction.setEnabled(False) self.removeAllAction.setEnabled(False) index = self.cliplist.currentRow() if index != -1: if not self.inCut: if index > 0: self.moveItemUpAction.setEnabled(True) if index < self.cliplist.count() - 1: self.moveItemDownAction.setEnabled(True) if self.cliplist.count() > 0: self.removeItemAction.setEnabled(True) if self.cliplist.count() > 0: self.removeAllAction.setEnabled(True) self.cliplistMenu.exec_(globalPos) def moveItemUp(self) -> None: index = self.cliplist.currentRow() tmpItem = self.clipTimes[index] del self.clipTimes[index] self.clipTimes.insert(index - 1, tmpItem) self.renderTimes() def moveItemDown(self) -> None: index = self.cliplist.currentRow() tmpItem = self.clipTimes[index] del self.clipTimes[index] self.clipTimes.insert(index + 1, tmpItem) self.renderTimes() def removeItem(self) -> None: index = self.cliplist.currentRow() del self.clipTimes[index] if self.inCut and index == self.cliplist.count() - 1: self.inCut = False self.initMediaControls() self.renderTimes() def clearList(self) -> None: self.clipTimes.clear() self.cliplist.clear() self.inCut = False self.renderTimes() self.initMediaControls() def mediaInfo(self) -> None: if self.mediaPlayer.isMetaDataAvailable(): content = '<table cellpadding="4">' for key in self.mediaPlayer.availableMetaData(): val = self.mediaPlayer.metaData(key) if type(val) is QSize: val = '%s x %s' % (val.width(), val.height()) content += '<tr><td align="right"><b>%s:</b></td><td>%s</td></tr>\n' % ( key, val) content += '</table>' mbox = QMessageBox(windowTitle='Media Information', windowIcon=self.parent.windowIcon(), textFormat=Qt.RichText) mbox.setText('<b>%s</b>' % os.path.basename( self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile())) mbox.setInformativeText(content) mbox.exec_() else: QMessageBox.critical( self.parent, 'MEDIA ERROR', '<h3>Could not probe media file.</h3>' + '<p>An error occurred while analyzing the media file for its metadata details.' + '<br/><br/><b>This DOES NOT mean there is a problem with the file and you should ' + 'be able to continue using it.</b></p>') def aboutInfo(self) -> None: about_html = '''<style> a { color:#441d4e; text-decoration:none; font-weight:bold; } a:hover { text-decoration:underline; } </style> <div style="min-width:650px;"> <p style="font-size:26pt; font-weight:bold; color:#6A4572;">%s</p> <p> <span style="font-size:13pt;"><b>Version: %s</b></span> <span style="font-size:10pt;position:relative;left:5px;">( %s )</span> </p> <p style="font-size:13px;"> Copyright © 2016 <a href="mailto:[email protected]">Pete Alexandrou</a> <br/> Website: <a href="%s">%s</a> </p> <p style="font-size:13px;"> Thanks to the folks behind the <b>Qt</b>, <b>PyQt</b> and <b>FFmpeg</b> projects for all their hard and much appreciated work. </p> <p style="font-size:11px;"> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. </p> <p style="font-size:11px;"> This software uses libraries from the <a href="https://www.ffmpeg.org">FFmpeg</a> project under the <a href="https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html">LGPLv2.1</a> </p></div>''' % (qApp.applicationName(), qApp.applicationVersion(), platform.architecture()[0], qApp.organizationDomain(), qApp.organizationDomain()) QMessageBox.about(self.parent, 'About %s' % qApp.applicationName(), about_html) def openMedia(self) -> None: filename, _ = QFileDialog.getOpenFileName(self.parent, caption='Select video', directory=QDir.homePath()) if filename != '': self.loadFile(filename) def loadFile(self, filename: str) -> None: self.movieFilename = filename if not os.path.exists(filename): return self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(filename))) self.initMediaControls(True) self.cliplist.clear() self.clipTimes = [] self.parent.setWindowTitle( '%s - %s' % (qApp.applicationName(), os.path.basename(filename))) if not self.movieLoaded: self.videoLayout.replaceWidget(self.novideoWidget, self.videoplayerWidget) self.novideoMovie.stop() self.novideoMovie.deleteLater() self.novideoWidget.deleteLater() self.videoplayerWidget.show() self.videoWidget.show() self.movieLoaded = True if self.mediaPlayer.isVideoAvailable(): self.mediaPlayer.setPosition(1) self.mediaPlayer.play() self.mediaPlayer.pause() def playMedia(self) -> None: if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() self.playAction.setText('Play') else: self.mediaPlayer.play() self.playAction.setText('Pause') def initMediaControls(self, flag: bool = True) -> None: self.playAction.setEnabled(flag) self.saveAction.setEnabled(False) self.cutStartAction.setEnabled(flag) self.cutEndAction.setEnabled(False) self.mediaInfoAction.setEnabled(flag) if flag: self.seekSlider.setRestrictValue(0) def setPosition(self, position: int) -> None: self.mediaPlayer.setPosition(position) def positionChanged(self, progress: int) -> None: self.seekSlider.setValue(progress) currentTime = self.deltaToQTime(progress) totalTime = self.deltaToQTime(self.mediaPlayer.duration()) self.timeCounter.setText('%s / %s' % (currentTime.toString( self.timeformat), totalTime.toString(self.timeformat))) @pyqtSlot() def mediaStateChanged(self) -> None: if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.playAction.setIcon(self.pauseIcon) else: self.playAction.setIcon(self.playIcon) def durationChanged(self, duration: int) -> None: self.seekSlider.setRange(0, duration) def muteAudio(self) -> None: if self.mediaPlayer.isMuted(): self.muteButton.setIcon(self.unmuteIcon) self.muteButton.setToolTip('Mute') else: self.muteButton.setIcon(self.muteIcon) self.muteButton.setToolTip('Unmute') self.mediaPlayer.setMuted(not self.mediaPlayer.isMuted()) def setVolume(self, volume: int) -> None: self.mediaPlayer.setVolume(volume) def toggleFullscreen(self) -> None: self.videoWidget.setFullScreen(not self.videoWidget.isFullScreen()) def setCutStart(self) -> None: self.clipTimes.append([ self.deltaToQTime(self.mediaPlayer.position()), '', self.captureImage() ]) self.cutStartAction.setDisabled(True) self.cutEndAction.setEnabled(True) self.seekSlider.setRestrictValue(self.seekSlider.value(), True) self.inCut = True self.renderTimes() def setCutEnd(self) -> None: item = self.clipTimes[len(self.clipTimes) - 1] selected = self.deltaToQTime(self.mediaPlayer.position()) if selected.__lt__(item[0]): QMessageBox.critical( self.parent, 'Invalid END Time', 'The clip end time must come AFTER it\'s start time. Please try again.' ) return item[1] = selected self.cutStartAction.setEnabled(True) self.cutEndAction.setDisabled(True) self.seekSlider.setRestrictValue(0, False) self.inCut = False self.renderTimes() @pyqtSlot(QModelIndex, int, int, QModelIndex, int) def syncClipList(self, parent: QModelIndex, start: int, end: int, destination: QModelIndex, row: int) -> None: if start < row: index = row - 1 else: index = row clip = self.clipTimes.pop(start) self.clipTimes.insert(index, clip) def renderTimes(self) -> None: self.cliplist.clear() if len(self.clipTimes) > 4: self.cliplist.setFixedWidth(200) else: self.cliplist.setFixedWidth(185) self.totalRuntime = 0 for item in self.clipTimes: endItem = '' if type(item[1]) is QTime: endItem = item[1].toString(self.timeformat) self.totalRuntime += item[0].msecsTo(item[1]) listitem = QListWidgetItem() listitem.setTextAlignment(Qt.AlignVCenter) if type(item[2]) is QPixmap: listitem.setIcon(QIcon(item[2])) self.cliplist.addItem(listitem) marker = QLabel( '''<style>b { font-size:7pt; } p { margin:2px 5px; }</style> <p><b>START</b><br/>%s<br/><b>END</b><br/>%s</p>''' % (item[0].toString(self.timeformat), endItem)) marker.setStyleSheet('border:none;') self.cliplist.setItemWidget(listitem, marker) listitem.setFlags(Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsEnabled) if len(self.clipTimes) and not self.inCut: self.saveAction.setEnabled(True) if self.inCut or len(self.clipTimes) == 0 or not type( self.clipTimes[0][1]) is QTime: self.saveAction.setEnabled(False) self.setRunningTime( self.deltaToQTime(self.totalRuntime).toString(self.timeformat)) @staticmethod def deltaToQTime(millisecs: int) -> QTime: secs = millisecs / 1000 return QTime((secs / 3600) % 60, (secs / 60) % 60, secs % 60, (secs * 1000) % 1000) def captureImage(self) -> QPixmap: frametime = self.deltaToQTime(self.mediaPlayer.position()).toString( self.timeformat) inputfile = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile( ) imagecap = self.videoService.capture(inputfile, frametime) if type(imagecap) is QPixmap: return imagecap def cutVideo(self) -> bool: clips = len(self.clipTimes) filename, filelist = '', [] source = self.mediaPlayer.currentMedia().canonicalUrl().toLocalFile() _, sourceext = os.path.splitext(source) if clips > 0: self.finalFilename, _ = QFileDialog.getSaveFileName( self.parent, 'Save video', source, 'Video files (*%s)' % sourceext) if self.finalFilename == '': return False qApp.setOverrideCursor(Qt.BusyCursor) self.saveAction.setDisabled(True) self.showProgress(clips) file, ext = os.path.splitext(self.finalFilename) index = 1 self.progress.setLabelText('Cutting media files...') qApp.processEvents() for clip in self.clipTimes: duration = self.deltaToQTime(clip[0].msecsTo( clip[1])).toString(self.timeformat) filename = '%s_%s%s' % (file, '{0:0>2}'.format(index), ext) filelist.append(filename) self.videoService.cut(source, filename, clip[0].toString(self.timeformat), duration) index += 1 if len(filelist) > 1: self.joinVideos(filelist, self.finalFilename) else: QFile.remove(self.finalFilename) QFile.rename(filename, self.finalFilename) self.progress.setLabelText('Complete...') self.progress.setValue(100) qApp.processEvents() self.progress.close() self.progress.deleteLater() qApp.restoreOverrideCursor() self.complete() return True return False def joinVideos(self, joinlist: list, filename: str) -> None: listfile = os.path.normpath( os.path.join(os.path.dirname(joinlist[0]), '.vidcutter.list')) fobj = open(listfile, 'w') for file in joinlist: fobj.write('file \'%s\'\n' % file.replace("'", "\\'")) fobj.close() self.videoService.join(listfile, filename) QFile.remove(listfile) for file in joinlist: if os.path.isfile(file): QFile.remove(file) def updateCheck(self) -> None: self.updater = Updater() self.updater.updateAvailable.connect(self.updateHandler) self.updater.start() def updateHandler(self, updateExists: bool, version: str = None): if updateExists: if Updater.notify_update(self, version) == QMessageBox.AcceptRole: self.updater.install_update(self) else: Updater.notify_no_update(self) def showProgress(self, steps: int, label: str = 'Analyzing media...') -> None: self.progress = QProgressDialog(label, None, 0, steps, self.parent, windowModality=Qt.ApplicationModal, windowIcon=self.parent.windowIcon(), minimumDuration=0, minimumWidth=500) self.progress.show() for i in range(steps): self.progress.setValue(i) qApp.processEvents() time.sleep(1) def complete(self) -> None: info = QFileInfo(self.finalFilename) mbox = QMessageBox(windowTitle='VIDEO PROCESSING COMPLETE', minimumWidth=500, textFormat=Qt.RichText) mbox.setText( ''' <style> table.info { margin:6px; padding:4px 15px; } td.label { font-weight:bold; font-size:10.5pt; text-align:right; } td.value { font-size:10.5pt; } </style> <table class="info" cellpadding="4" cellspacing="0"> <tr> <td class="label"><b>File:</b></td> <td class="value" nowrap>%s</td> </tr> <tr> <td class="label"><b>Size:</b></td> <td class="value">%s</td> </tr> <tr> <td class="label"><b>Length:</b></td> <td class="value">%s</td> </tr> </table><br/>''' % (QDir.toNativeSeparators( self.finalFilename), self.sizeof_fmt(int(info.size())), self.deltaToQTime(self.totalRuntime).toString(self.timeformat))) play = mbox.addButton('Play', QMessageBox.AcceptRole) play.setIcon(self.completePlayIcon) play.clicked.connect(self.openResult) fileman = mbox.addButton('Open', QMessageBox.AcceptRole) fileman.setIcon(self.completeOpenIcon) fileman.clicked.connect(self.openFolder) end = mbox.addButton('Exit', QMessageBox.AcceptRole) end.setIcon(self.completeExitIcon) end.clicked.connect(self.close) new = mbox.addButton('Restart', QMessageBox.AcceptRole) new.setIcon(self.completeRestartIcon) new.clicked.connect(self.parent.restart) mbox.setDefaultButton(new) mbox.setEscapeButton(new) mbox.adjustSize() mbox.exec_() def sizeof_fmt(self, num: float, suffix: chr = 'B') -> str: for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 return "%.1f%s%s" % (num, 'Y', suffix) @pyqtSlot() def openFolder(self) -> None: self.openResult(pathonly=True) @pyqtSlot(bool) def openResult(self, pathonly: bool = False) -> None: self.parent.restart() if len(self.finalFilename) and os.path.exists(self.finalFilename): target = self.finalFilename if not pathonly else os.path.dirname( self.finalFilename) QDesktopServices.openUrl(QUrl.fromLocalFile(target)) @pyqtSlot() def startNew(self) -> None: qApp.restoreOverrideCursor() self.clearList() self.seekSlider.setValue(0) self.seekSlider.setRange(0, 0) self.mediaPlayer.setMedia(QMediaContent()) self.initNoVideo() self.videoLayout.replaceWidget(self.videoplayerWidget, self.novideoWidget) self.initMediaControls(False) self.parent.setWindowTitle('%s' % qApp.applicationName()) def wheelEvent(self, event: QWheelEvent) -> None: if self.mediaPlayer.isVideoAvailable( ) or self.mediaPlayer.isAudioAvailable(): if event.angleDelta().y() > 0: newval = self.seekSlider.value() - 1000 else: newval = self.seekSlider.value() + 1000 self.seekSlider.setValue(newval) self.seekSlider.setSliderPosition(newval) self.mediaPlayer.setPosition(newval) event.accept() def keyPressEvent(self, event: QKeyEvent) -> None: if self.mediaPlayer.isVideoAvailable( ) or self.mediaPlayer.isAudioAvailable(): addtime = 0 if event.key() == Qt.Key_Left: addtime = -1000 elif event.key() == Qt.Key_PageUp or event.key() == Qt.Key_Up: addtime = -10000 elif event.key() == Qt.Key_Right: addtime = 1000 elif event.key() == Qt.Key_PageDown or event.key() == Qt.Key_Down: addtime = 10000 elif event.key() == Qt.Key_Enter: self.toggleFullscreen() elif event.key( ) == Qt.Key_Escape and self.videoWidget.isFullScreen(): self.videoWidget.setFullScreen(False) if addtime != 0: newval = self.seekSlider.value() + addtime self.seekSlider.setValue(newval) self.seekSlider.setSliderPosition(newval) self.mediaPlayer.setPosition(newval) event.accept() def mousePressEvent(self, event: QMouseEvent) -> None: if event.button() == Qt.BackButton and self.cutStartAction.isEnabled(): self.setCutStart() event.accept() elif event.button( ) == Qt.ForwardButton and self.cutEndAction.isEnabled(): self.setCutEnd() event.accept() else: super(VidCutter, self).mousePressEvent(event) @pyqtSlot(QMediaPlayer.Error) def handleError(self, error: QMediaPlayer.Error) -> None: qApp.restoreOverrideCursor() self.startNew() if error == QMediaPlayer.ResourceError: QMessageBox.critical( self.parent, 'INVALID MEDIA', 'Invalid media file detected at:<br/><br/><b>%s</b><br/><br/>%s' % (self.movieFilename, self.mediaPlayer.errorString())) else: QMessageBox.critical(self.parent, 'ERROR NOTIFICATION', self.mediaPlayer.errorString()) def closeEvent(self, event: QCloseEvent) -> None: self.parent.closeEvent(event)
class Player(QWidget): fullScreenChanged = pyqtSignal(bool) def __init__(self, playlist, parent=None): super(Player, self).__init__(parent) self.colorDialog = None self.trackInfo = "" self.statusInfo = "" self.duration = 0 self.player = QMediaPlayer() self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.metaDataChanged.connect(self.metaDataChanged) self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) self.player.mediaStatusChanged.connect(self.statusChanged) self.player.bufferStatusChanged.connect(self.bufferingProgress) self.player.videoAvailableChanged.connect(self.videoAvailableChanged) self.player.error.connect(self.displayErrorMessage) self.videoWidget = VideoWidget() self.player.setVideoOutput(self.videoWidget) self.playlistModel = PlaylistModel() self.playlistModel.setPlaylist(self.playlist) self.playlistView = QListView() self.playlistView.setModel(self.playlistModel) self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) self.playlistView.activated.connect(self.jump) self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, self.player.duration() / 1000) self.labelDuration = QLabel() self.slider.sliderMoved.connect(self.seek) openButton = QPushButton("Открыть файл", clicked=self.open) controls = PlayerControls() controls.setState(self.player.state()) controls.setVolume(self.player.volume()) controls.play.connect(self.player.play) controls.pause.connect(self.player.pause) controls.stop.connect(self.player.stop) controls.next.connect(self.playlist.next) controls.previous.connect(self.previousClicked) controls.changeVolume.connect(self.player.setVolume) controls.changeRate.connect(self.player.setPlaybackRate) controls.stop.connect(self.videoWidget.update) self.player.stateChanged.connect(controls.setState) self.player.volumeChanged.connect(controls.setVolume) self.fullScreenButton = QPushButton("Полный экран") self.fullScreenButton.setCheckable(True) displayLayout = QHBoxLayout() displayLayout.addWidget(self.videoWidget, 2) displayLayout.addWidget(self.playlistView) controlLayout = QHBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) controlLayout.addWidget(openButton) controlLayout.addStretch(1) controlLayout.addWidget(controls) controlLayout.addStretch(1) controlLayout.addWidget(self.fullScreenButton) layout = QVBoxLayout() layout.addLayout(displayLayout) hLayout = QHBoxLayout() hLayout.addWidget(self.slider) hLayout.addWidget(self.labelDuration) layout.addLayout(hLayout) layout.addLayout(controlLayout) self.setLayout(layout) self.metaDataChanged() self.addToPlaylist(playlist) def open(self): fileNames, _ = QFileDialog.getOpenFileNames(self, "Выбрать файл") self.addToPlaylist(fileNames) def addToPlaylist(self, fileNames): for name in fileNames: fileInfo = QFileInfo(name) if fileInfo.exists(): url = QUrl.fromLocalFile(fileInfo.absoluteFilePath()) if fileInfo.suffix().lower() == 'm3u': self.playlist.load(url) else: self.playlist.addMedia(QMediaContent(url)) else: url = QUrl(name) if url.isValid(): self.playlist.addMedia(QMediaContent(url)) def durationChanged(self, duration): duration /= 1000 self.duration = duration self.slider.setMaximum(duration) def positionChanged(self, progress): progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) self.updateDurationInfo(progress) def metaDataChanged(self): if self.player.isMetaDataAvailable(): self.setTrackInfo("%s - %s" % ( self.player.metaData(QMediaMetaData.AlbumArtist), self.player.metaData(QMediaMetaData.Title))) def previousClicked(self): # Go to the previous track if we are within the first 5 seconds of # playback. Otherwise, seek to the beginning. if self.player.position() <= 5000: self.playlist.previous() else: self.player.setPosition(0) def jump(self, index): if index.isValid(): self.playlist.setCurrentIndex(index.row()) self.player.play() def playlistPositionChanged(self, position): self.playlistView.setCurrentIndex( self.playlistModel.index(position, 0)) def seek(self, seconds): self.player.setPosition(seconds * 1000) def statusChanged(self, status): self.handleCursor(status) if status == QMediaPlayer.LoadingMedia: self.setStatusInfo("Загрузка...") elif status == QMediaPlayer.StalledMedia: self.setStatusInfo("Видео стоп") elif status == QMediaPlayer.EndOfMedia: QApplication.alert(self) elif status == QMediaPlayer.InvalidMedia: self.displayErrorMessage() else: self.setStatusInfo("") def handleCursor(self, status): if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia): self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def bufferingProgress(self, progress): self.setStatusInfo("Буферизация %d%" % progress) def videoAvailableChanged(self, available): if available: self.fullScreenButton.clicked.connect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.connect( self.fullScreenButton.setChecked) if self.fullScreenButton.isChecked(): self.videoWidget.setFullScreen(True) else: self.fullScreenButton.clicked.disconnect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.disconnect( self.fullScreenButton.setChecked) self.videoWidget.setFullScreen(False) def setTrackInfo(self, info): self.trackInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def setStatusInfo(self, info): self.statusInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def displayErrorMessage(self): self.setStatusInfo(self.player.errorString()) def updateDurationInfo(self, currentInfo): duration = self.duration if currentInfo or duration: currentTime = QTime((currentInfo/3600)%60, (currentInfo/60)%60, currentInfo%60, (currentInfo*1000)%1000) totalTime = QTime((duration/3600)%60, (duration/60)%60, duration%60, (duration*1000)%1000); format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' tStr = currentTime.toString(format) + " / " + totalTime.toString(format) else: tStr = "" self.labelDuration.setText(tStr)
class DBPlayer(QWidget): # signal signaltxt = pyqtSignal(str) signalnum = pyqtSignal(int) def __init__(self): super(DBPlayer, self).__init__() self.setMaximumSize(16777215, 35) # Init Player self.messtitle = TITL_PROG self.namemedia = '' self.albumname = '' self.currentPlaylist = QMediaPlaylist() self.player = QMediaPlayer() self.player.stateChanged.connect(self.qmp_stateChanged) self.player.positionChanged.connect(self.qmp_positionChanged) self.player.volumeChanged.connect(self.qmp_volumeChanged) self.player.durationChanged.connect(self.qmp_durationChanged) self.player.setVolume(60) # Init GUI self.setLayout(self.addControls()) self.infoBox = None def addControls(self): # buttons self.playBtn = QPushButton() self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.playBtn.setStyleSheet('border: 0px;') stopBtn = QPushButton() stopBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaStop)) stopBtn.setStyleSheet('border: 0px;') prevBtn = QPushButton() prevBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaSkipBackward)) prevBtn.setStyleSheet('border: 0px;') nextBtn = QPushButton() nextBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaSkipForward)) nextBtn.setStyleSheet('border: 0px;') volumeDescBtn = QPushButton('▼') volumeDescBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaVolume)) volumeDescBtn.setMaximumWidth(30) volumeDescBtn.setStyleSheet('border: 0px;') volumeIncBtn = QPushButton('▲') volumeIncBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaVolume)) volumeIncBtn.setMaximumWidth(40) volumeIncBtn.setStyleSheet('border: 0px;') infoBtn = QPushButton() infoBtn.setIcon(self.style().standardIcon( QStyle.SP_FileDialogContentsView)) infoBtn.setStyleSheet('border: 0px;') # seek slider self.seekSlider = QSlider(Qt.Horizontal, self) self.seekSlider.setMinimum(0) self.seekSlider.setMaximum(100) self.seekSlider.setTracking(False) # labels position start/end self.seekSliderLabel1 = QLabel('0:00') self.seekSliderLabel2 = QLabel('0:00') # layout controlArea = QHBoxLayout() controlArea.addWidget(prevBtn) controlArea.addWidget(self.playBtn) controlArea.addWidget(stopBtn) controlArea.addWidget(nextBtn) controlArea.addWidget(self.seekSliderLabel1) controlArea.addWidget(self.seekSlider) controlArea.addWidget(self.seekSliderLabel2) controlArea.addWidget(infoBtn) controlArea.addWidget(volumeDescBtn) controlArea.addWidget(volumeIncBtn) # link buttons to media self.seekSlider.sliderMoved.connect(self.seekPosition) self.playBtn.clicked.connect(self.playHandler) stopBtn.clicked.connect(self.stopHandler) volumeDescBtn.clicked.connect(self.decreaseVolume) volumeIncBtn.clicked.connect(self.increaseVolume) prevBtn.clicked.connect(self.prevItemPlaylist) nextBtn.clicked.connect(self.nextItemPlaylist) infoBtn.clicked.connect(self.displaySongInfo) return controlArea def playHandler(self): if self.player.state() == QMediaPlayer.PlayingState: self.player.pause() message = (' [Paused at position %s]' % self.seekSliderLabel1.text()) self.messtitle = self.namemedia + message self.signaltxt.emit(self.messtitle) else: if self.player.state() == QMediaPlayer.StoppedState: if self.player.mediaStatus() == QMediaPlayer.NoMedia: if self.currentPlaylist.mediaCount() != 0: self.player.setPlaylist(self.currentPlaylist) elif self.player.mediaStatus() == QMediaPlayer.LoadedMedia: self.player.play() elif self.player.mediaStatus() == QMediaPlayer.BufferedMedia: self.player.play() elif self.player.state() == QMediaPlayer.PlayingState: pass elif self.player.state() == QMediaPlayer.PausedState: self.player.play() if self.player.volume() is not None and self.player.state( ) == QMediaPlayer.PlayingState: message = ' [Volume %d]' % self.player.volume() self.messtitle = self.namemedia + message self.signaltxt.emit(self.messtitle) def stopHandler(self): if self.player.state() == QMediaPlayer.PlayingState: self.stopState = True self.player.stop() elif self.player.state() == QMediaPlayer.PausedState: self.player.stop() elif self.player.state() == QMediaPlayer.StoppedState: pass if self.player.volume() is not None and self.player.state( ) == QMediaPlayer.PlayingState: self.messtitle = self.namemedia + (' [Stopped]') self.signaltxt.emit(self.messtitle) def qmp_stateChanged(self): if self.player.state() == QMediaPlayer.StoppedState: self.player.stop() # buttons icon play/pause change if self.player.state() == QMediaPlayer.PlayingState: self.playBtn.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) else: self.playBtn.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) def qmp_positionChanged(self, position): # update position slider self.seekSlider.setValue(position) # update the text label self.seekSliderLabel1.setText( '%d:%02d' % (int(position / 60000), int((position / 1000) % 60))) def seekPosition(self, position): sender = self.sender() if isinstance(sender, QSlider): if self.player.isSeekable(): self.player.setPosition(position) def qmp_volumeChanged(self): if self.player.volume() is not None: message = (' [Playing at Volume %d]' % (self.player.volume())) if self.namemedia != '': self.messtitle = self.namemedia + message else: self.messtitle = "Initialisation player " + message self.signaltxt.emit(self.messtitle) def qmp_durationChanged(self, duration): self.seekSlider.setRange(0, duration) self.seekSliderLabel2.setText( '%d:%02d' % (int(duration / 60000), int((duration / 1000) % 60))) nummedia = self.currentPlaylist.mediaCount() curmedia = self.currentPlaylist.currentIndex() #artist = self.player.metaData(QMediaMetaData.Author) #tittle = self.player.metaData(QMediaMetaData.Title) self.namemedia = path.basename(self.homMed[curmedia]) self.namemedia = '[%02d/%02d' % ( curmedia + 1, nummedia) + '] "' + self.namemedia + '"' self.buildPlaylist() message = (' [Playing at Volume %d]' % (self.player.volume())) if self.player.volume() is not None and self.player.state( ) == QMediaPlayer.PlayingState: self.messtitle = self.namemedia + message self.signaltxt.emit(self.messtitle) def buildPlaylist(self): """Build play list.""" nummedia = self.currentPlaylist.mediaCount() curmedia = self.currentPlaylist.currentIndex() + 1 compteur = 1 self.textplaylist = '<b>' + self.albumname + '</b>' self.textplaylist += '<table class="tftable" border="0">' for namemedia in self.homMed: media = path.basename(namemedia) media = '[%02d/%02d' % (compteur, nummedia) + '] "' + media + '"' if curmedia == compteur: self.textplaylist += '<tr><td><b>' + media + '</b></td></tr>' else: self.textplaylist += '<tr><td>' + media + '</td></tr>' compteur += 1 self.textplaylist = self.textplaylist + '</table>' self.playBtn.setToolTip(self.textplaylist) self.signalnum.emit(curmedia - 1) def increaseVolume(self): """Volume +.""" vol = self.player.volume() vol = min(vol + 5, 100) self.player.setVolume(vol) def decreaseVolume(self): """Volume -.""" vol = self.player.volume() vol = max(vol - 5, 0) self.player.setVolume(vol) def prevItemPlaylist(self): self.player.playlist().previous() if self.currentPlaylist.currentIndex() == -1: self.player.playlist().previous() def nextItemPlaylist(self): self.player.playlist().next() if self.currentPlaylist.currentIndex() == -1: self.player.playlist().next() def addMediaslist(self, listmedias, position, albumname): if self.currentPlaylist.mediaCount() > 0: self.currentPlaylist.removeMedia(0, self.currentPlaylist.mediaCount()) self.player.stop() self.stopHandler() self.currentPlaylist.removeMedia(0, self.currentPlaylist.mediaCount()) self.albumname = albumname if listmedias: self.homMed = listmedias for media in self.homMed: self.currentPlaylist.addMedia( QMediaContent(QUrl.fromLocalFile(media))) self.currentPlaylist.setCurrentIndex(position) self.playHandler() def displaySongInfo(self): # extract datas metaDataKeyList = self.player.availableMetaData() fullText = '<table class="tftable" border="0">' for key in metaDataKeyList: value = str(self.player.metaData(key)).replace("'", "").replace( "[", "").replace("]", "") if key == 'Duration': value = '%d:%02d' % (int( int(value) / 60000), int((int(value) / 1000) % 60)) fullText = fullText + '<tr><td>' + key + '</td><td>' + value + '</td></tr>' fullText = fullText + '</table>' # re-init if self.infoBox is not None: self.infoBox.destroy() # infos box self.infoBox = QMessageBox(self) self.infoBox.setWindowTitle('Detailed Song Information') self.infoBox.setTextFormat(Qt.RichText) self.infoBox.addButton('OK', QMessageBox.AcceptRole) self.infoBox.setText(fullText) self.infoBox.show()
class MainWindow(QMainWindow): def __init__(self): try: with open('favs.yaml', 'r') as yaml_stream: self.fav_list = yaml.load(yaml_stream, Loader=yaml.SafeLoader) except: print("Cannot read yaml config file, check formatting.") self.fav_list = None super(MainWindow, self).__init__() self.setGeometry(0, 0, 700, 400) self.setContentsMargins(6, 6, 6, 6) self.setWindowTitle("pyRadioQt") self.setWindowIcon(QIcon('./icon/icon.png')) self.uiGenreCombo() self.uiSearchField() self.path = './icon/icon.png' self.pix = QPixmap(self.path) self.label_image = QLabel() self.label_image.setPixmap(QPixmap(self.pix)) self.field = QPlainTextEdit() self.field.setContextMenuPolicy(Qt.CustomContextMenu) self.field.customContextMenuRequested.connect(self.contextMenuRequested) self.field.cursorPositionChanged.connect(self.selectLine) self.field.setWordWrapMode(QTextOption.NoWrap) self.saveButton = QPushButton("Save as txt") self.saveButton.setIcon(QIcon.fromTheme("document-save")) self.saveButton.clicked.connect(self.saveStations) self.savePlaylistButton = QPushButton("Save as m3u") self.savePlaylistButton.setIcon(QIcon.fromTheme("document-save")) self.savePlaylistButton.clicked.connect(self.savePlaylist) # Toolbar self.tb = self.addToolBar("tools") self.tb.setContextMenuPolicy(Qt.PreventContextMenu) self.tb.setMovable(False) self.tb.addWidget(self.searchField) self.tb.addWidget(self.saveButton) self.tb.addWidget(self.savePlaylistButton) self.tb.addSeparator() self.tb.addWidget(self.genreCombo) # Main Layout self.createFavoriteLayout() self.mainWidget = QWidget(self) self.mainLayout = QVBoxLayout(self.mainWidget) self.centerLayout = QHBoxLayout() self.centerLayout.addWidget(self.label_image) self.centerLayout.addWidget(self.field) self.mainLayout.addLayout(self.centerLayout) self.mainLayout.addWidget(self.horizontalGroupBox) self.mainWidget.setLayout(self.mainLayout) self.setCentralWidget(self.mainWidget) # player ### self.player = QMediaPlayer() self.player.metaDataChanged.connect(self.metaDataChanged) self.startButton = QPushButton("Play") self.startButton.setIcon(QIcon.fromTheme("media-playback-start")) self.startButton.clicked.connect(self.getURLtoPlay) self.stopButton = QPushButton("Stop") self.stopButton.setIcon(QIcon.fromTheme("media-playback-stop")) self.stopButton.clicked.connect(self.stopPlayer) self.statusBar().addPermanentWidget(self.startButton) self.statusBar().addPermanentWidget(self.stopButton) # actions self.getNameAction = QAction(QIcon.fromTheme("edit-copy"), "copy Station Name", self, triggered=self.getName) self.getUrlAction = QAction(QIcon.fromTheme("edit-copy"), "copy Station URL", self, triggered=self.getURL) self.getNameAndUrlAction = QAction(QIcon.fromTheme("edit-copy"), "copy Station Name,URL", self, triggered=self.getNameAndUrl) self.getURLtoPlayAction = QAction(QIcon.fromTheme("media-playback-start"), "play Station", self, shortcut="F6", triggered=self.getURLtoPlay) self.addAction(self.getURLtoPlayAction) self.stopPlayerAction = QAction(QIcon.fromTheme("media-playback-stop"), "stop playing", self, shortcut="F7", triggered=self.stopPlayer) self.addAction(self.stopPlayerAction) self.helpAction = QAction(QIcon.fromTheme("help-info"), "Help", self, shortcut="F1", triggered=self.showHelp) self.addAction(self.helpAction) self.statusBar().showMessage("Welcome", 0) def uiGenreCombo(self): self.genreList = genres.splitlines() self.genreCombo = QComboBox() self.genreCombo.setFixedWidth(150) self.genreCombo.currentIndexChanged.connect(self.genreSearch) self.genreCombo.addItem("choose Genre") for m in self.genreList: self.genreCombo.addItem(m) def uiSearchField(self): self.searchField = QLineEdit() self.searchField.setFixedWidth(250) self.searchField.addAction(QIcon.fromTheme("edit-find"), 0) self.searchField.setPlaceholderText("type search term and press RETURN ") self.searchField.returnPressed.connect(self.findStations) def createFavoriteLayout(self): self.horizontalGroupBox = QGroupBox("Favorites") layout = QHBoxLayout() if self.fav_list is not None: self.buttongroup = QButtonGroup() self.buttongroup.buttonClicked[int].connect(self.handleButtonClicked) i = 1 for station in self.fav_list: #print(station) self.button = QPushButton(station, self) self.buttongroup.addButton(self.button, i) i = i + 1 layout.addWidget(self.button) else: l1 = QLabel() l1.setText("No Favorites") layout.addWidget(l1) self.horizontalGroupBox.setLayout(layout) def handleButtonClicked(self, id): for button in self.buttongroup.buttons(): if button is self.buttongroup.button(id): #print(button.text() + " Was Clicked ") for station, url in self.fav_list.items(): if station == button.text(): #print(url[0]) self.getURLtoPlay(True, station, url[0]) def genreSearch(self): if self.genreCombo.currentIndex() > 0: self.searchField.setText(self.genreCombo.currentText()) self.findStations() def getName(self): t = self.field.textCursor().selectedText().partition(",")[0] clip = QApplication.clipboard() clip.setText(t) def getURL(self): t = self.field.textCursor().selectedText().partition(",")[2] clip = QApplication.clipboard() clip.setText(t) def getNameAndUrl(self): t = self.field.textCursor().selectedText() clip = QApplication.clipboard() clip.setText(t) def selectLine(self): tc = self.field.textCursor() tc.select(QTextCursor.LineUnderCursor) tc.movePosition(QTextCursor.StartOfLine, QTextCursor.MoveAnchor) tc.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) self.field.setTextCursor(tc) def showHelp(self): QMessageBox.information(self, "Information", "F6 -> play Station (from line where cursor is)\n\ F7 -> stop playing") def stopPlayer(self): self.player.stop() self.statusBar().showMessage("Player stopped", 0) # QPlainTextEdit contextMenu def contextMenuRequested(self, point): cmenu = QMenu() if not self.field.toPlainText() == "": cmenu.addAction(self.getNameAction) cmenu.addAction(self.getUrlAction) cmenu.addAction(self.getNameAndUrlAction) cmenu.addSeparator() cmenu.addAction(self.getURLtoPlayAction) cmenu.addAction(self.stopPlayerAction) cmenu.addSeparator() cmenu.addAction(self.helpAction) cmenu.exec_(self.field.mapToGlobal(point)) def getURLtoPlay(self, fav=False, name="", url_fav=""): url = "" stext = "" if fav: # print("url_fav=",url_fav) # print("name in func:", name) stext = name url = url_fav else: tc = self.field.textCursor() rtext = tc.selectedText().partition(",")[2] stext = tc.selectedText().partition(",")[0] if rtext.endswith(".pls"): url = self.getURLfromPLS(rtext) elif rtext.endswith(".m3u"): url = self.getURLfromM3U(rtext) else: url = rtext # print("stream url=", url) self.player.setMedia(QMediaContent(QUrl(url))) self.player.play() self.statusBar().showMessage("%s %s" % ("playing", stext), 0) def metaDataChanged(self): if self.player.isMetaDataAvailable(): trackInfo = (self.player.metaData("Title")) trackInfo2 = (self.player.metaData("Comment")) if trackInfo is not None: self.statusBar().showMessage(trackInfo, 0) if trackInfo2 is not None: self.statusBar().showMessage("%s %s" % (trackInfo, trackInfo2)) def getURLfromPLS(self, inURL): if "&" in inURL: inURL = inURL.partition("&")[0] response = request.urlopen(inURL) html = response.read().decode("utf-8").splitlines() if len(html) > 3: if "http" in str(html[1]): t = str(html[1]) elif "http" in str(html[2]): t = str(html[2]) elif "http" in str(html[3]): t = str(html[3]) elif len(html) > 2: if "http" in str(html[1]): t = str(html[1]) elif "http" in str(html[2]): t = str(html[2]) else: t = str(html[0]) url = t.partition("=")[2].partition("'")[0] # print(url) return (url) def getURLfromM3U(self, inURL): if "?u=" in inURL: inURL = inURL.partition("?u=")[2] if "&" in inURL: inURL = inURL.partition("&")[0] response = request.urlopen(inURL) html = response.read().splitlines() if len(html) > 1: if "http" in str(html[1]): t = str(html[1]) else: t = str(html[0]) else: t = str(html[0]) url = t.partition("'")[2].partition("'")[0] # print(url) return (url) def findStations(self): self.field.setPlainText("") mysearch = self.searchField.text() self.statusBar().showMessage("searching ...") rb = RadioBrowser() myparams = {'name': 'search', 'nameExact': 'false'} for key in myparams.keys(): if key == "name": myparams[key] = mysearch self.r = rb.station_search(params=myparams) n = "" m = "" for i in range(len(self.r)): for key, value in self.r[i].items(): if str(key) == "favicon": self.path = value print(self.path) if str(key) == "name": n = value.replace(",", " ") # print (n) if str(key) == "url": m = value self.field.appendPlainText("%s,%s" % (n, m)) # self.combo.setCurrentIndex(0) if not self.field.toPlainText() == "": self.statusBar().showMessage("found " + str(self.field.toPlainText().count('\n')+1) + " '" + self.searchField.text() + "' Stations") else: self.statusBar().showMessage("nothing found", 0) # self.field.textCursor().movePosition(QTextCursor.Start, Qt.MoveAnchor) def saveStations(self): if not self.field.toPlainText() == "": path, _ = QFileDialog.getSaveFileName(None, "RadioStations", self.searchField.text() + ".txt", "Text Files (*.txt)") if path: s = self.field.toPlainText() with open(path, 'w') as f: f.write(s) f.close() self.statusBar().showMessage("saved!", 0) def savePlaylist(self): if not self.field.toPlainText() == "": path, _ = QFileDialog.getSaveFileName(None, "RadioStations", self.searchField.text() + ".m3u", "Playlist Files (*.m3u)") if path: result = "" s = self.field.toPlainText() st = [] for line in s.splitlines(): st.append(line) result += "#EXTM3U" result += '\n' for x in range(len(st)): result += "#EXTINF:" + str(x) + "," + st[x].partition(",")[0] result += '\n' result += st[x].partition(",")[2] result += '\n' with open(path, 'w') as f: f.write(result) f.close() self.statusBar().showMessage("saved!", 0)
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.setAttribute(Qt.WA_QuitOnClose, False) self.tIcon = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "radio_bg.png")) self.setWindowIcon(self.tIcon) self.setGeometry(0, 0, 700, 400) self.setContentsMargins(6, 6, 6, 6) self.setStyleSheet(myStyleSheet(self)) self.setWindowTitle("Radio Suche") self.genreList = genres.splitlines() self.findfield = QLineEdit() self.findfield.setFixedWidth(250) self.findfield.addAction(QIcon.fromTheme("edit-find"), 0) self.findfield.setPlaceholderText("Suchbegriff eingeben und RETURN ") self.findfield.returnPressed.connect(self.findStations) self.findfield.setClearButtonEnabled(True) self.field = QPlainTextEdit() self.field.setContextMenuPolicy(Qt.CustomContextMenu) self.field.customContextMenuRequested.connect( self.contextMenuRequested) self.field.cursorPositionChanged.connect(self.selectLine) self.field.setWordWrapMode(QTextOption.NoWrap) ### genre box self.combo = QComboBox() self.combo.currentIndexChanged.connect(self.comboSearch) self.combo.addItem("wähle Genre") for m in self.genreList: self.combo.addItem(m) self.combo.setFixedWidth(150) ### toolbar ### self.tb = self.addToolBar("tools") self.tb.setContextMenuPolicy(Qt.PreventContextMenu) self.tb.setMovable(False) self.setCentralWidget(self.field) self.tb.addWidget(self.findfield) self.tb.addSeparator() self.tb.addWidget(self.combo) ### player ### self.player = QMediaPlayer() self.player.metaDataChanged.connect(self.metaDataChanged) self.startButton = QPushButton("Wiedergabe") self.startButton.setIcon(QIcon.fromTheme("media-playback-start")) self.startButton.clicked.connect(self.getURLtoPlay) self.stopButton = QPushButton("Stop") self.stopButton.setIcon(QIcon.fromTheme("media-playback-stop")) self.stopButton.clicked.connect(self.stopPlayer) self.statusBar().addPermanentWidget(self.startButton) self.statusBar().addPermanentWidget(self.stopButton) ## actions self.addToRadiolistAction = QAction( QIcon.fromTheme("add"), "zu myRadio Senderliste hinzufügen", self, triggered=self.addToRadiolist) self.getNameAction = QAction(QIcon.fromTheme("edit-copy"), "Sendername kopieren", self, triggered=self.getName) self.getUrlAction = QAction(QIcon.fromTheme("edit-copy"), "Sender-URL kopieren", self, triggered=self.getURL) self.getNameAndUrlAction = QAction(QIcon.fromTheme("edit-copy"), "Name,URL kopieren", self, triggered=self.getNameAndUrl) self.getURLtoPlayAction = QAction( QIcon.fromTheme("media-playback-start"), "Sender spielen", self, shortcut="F6", triggered=self.getURLtoPlay) self.addAction(self.getURLtoPlayAction) self.stopPlayerAction = QAction(QIcon.fromTheme("media-playback-stop"), "Wiedergabe stoppen", self, shortcut="F7", triggered=self.stopPlayer) self.addAction(self.stopPlayerAction) self.helpAction = QAction(QIcon.fromTheme("help-info"), "Hilfe", self, shortcut="F1", triggered=self.showHelp) self.addAction(self.helpAction) self.statusBar().showMessage("Welcome", 0) self.modified = False def closeEvent(self, event): self.stopPlayer() if self.modified == True: self.statusBar().showMessage("saved!", 0) self.msgbox( "neue Sender sind nach einem Neustart von myRadio verfügbar") def addToRadiolist(self): text = "" filename = os.path.join(os.path.dirname(sys.argv[0]), "myradio.txt") print(filename) with open(filename, 'r') as f: text = f.read() text = text[:text.rfind('\n')] f.close() textlist = text.splitlines() mycat = [] for line in textlist: if line.startswith("--"): mycat.append(line.replace("-- ", "").replace(" --", "")) ind = 1 for x in range(len(mycat)): if mycat[x] == self.combo.currentText(): ind = x break dlg = QInputDialog() mc, _ = dlg.getItem(self, "", "wähle Genre für den Sender", mycat, ind) entry = self.getNameAndUrl() print(mc, entry) filename = os.path.dirname(sys.argv[0]) + os.sep + "myradio.txt" print(filename) with open(filename, 'r') as f: text = f.read() text = text[:text.rfind('\n')] f.close() textlist = text.splitlines() if mc in mycat: for x in range(len(textlist)): if textlist[x] == f"-- {mc} --": textlist.insert(x + 1, entry) else: textlist.append(f"-- {mc} --") textlist.append(entry) with open(filename, 'w') as f: for x in reversed(range(len(textlist))): if textlist[x] == "\n": print(x) del textlist[x] text = '\n'.join(textlist) f.write(text) f.write('\n\n') f.close() self.modified = True def msgbox(self, message): msg = QMessageBox(1, "Information", message, QMessageBox.Ok) msg.exec() def comboSearch(self): if self.combo.currentIndex() > 0: self.findfield.setText(self.combo.currentText()) self.findStations() def getName(self): t = self.field.textCursor().selectedText().partition(",")[0] clip = QApplication.clipboard() clip.setText(t) def getURL(self): t = self.field.textCursor().selectedText().partition(",")[2] clip = QApplication.clipboard() clip.setText(t) def getNameAndUrl(self): t = self.field.textCursor().selectedText() clip = QApplication.clipboard() clip.setText(t) return (t) def selectLine(self): tc = self.field.textCursor() tc.select(QTextCursor.LineUnderCursor) tc.movePosition(QTextCursor.StartOfLine, QTextCursor.MoveAnchor) ##, tc.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) self.field.setTextCursor(tc) def showHelp(self): QMessageBox.information( self, "Information", "F6 -> Sender spielen\nF7 -> Wiedergabe stoppen") def stopPlayer(self): self.player.stop() self.statusBar().showMessage("Wiedergabe gestoppt", 0) ### QPlainTextEdit contextMenu def contextMenuRequested(self, point): cmenu = QMenu() if not self.field.toPlainText() == "": cmenu.addAction(self.getNameAction) cmenu.addAction(self.getUrlAction) cmenu.addAction(self.getNameAndUrlAction) cmenu.addSeparator() cmenu.addAction(self.addToRadiolistAction) cmenu.addSeparator() cmenu.addAction(self.getURLtoPlayAction) cmenu.addAction(self.stopPlayerAction) cmenu.addSeparator() cmenu.addAction(self.helpAction) cmenu.exec_(self.field.mapToGlobal(point)) def getURLtoPlay(self): url = "" tc = self.field.textCursor() rtext = tc.selectedText().partition(",")[2] stext = tc.selectedText().partition(",")[0] if rtext.endswith(".pls"): url = self.getURLfromPLS(rtext) elif rtext.endswith(".m3u"): url = self.getURLfromM3U(rtext) else: url = rtext print("stream url=", url) self.player.setMedia(QMediaContent(QUrl(url))) self.player.play() self.statusBar().showMessage("%s %s" % ("spiele", stext), 0) def metaDataChanged(self): if self.player.isMetaDataAvailable(): trackInfo = (self.player.metaData("Title")) trackInfo2 = (self.player.metaData("Comment")) if not trackInfo == None: self.statusBar().showMessage(trackInfo, 0) if not trackInfo2 == None: self.statusBar().showMessage("%s %s" % (trackInfo, trackInfo2)) def getURLfromPLS(self, inURL): print("detecting", inURL) t = "" if "&" in inURL: inURL = inURL.partition("&")[0] response = requests.get(inURL) print(response.text) if "http" in response.text: html = response.text.splitlines() if len(html) > 3: if "http" in str(html[1]): t = str(html[1]) elif "http" in str(html[2]): t = str(html[2]) elif "http" in str(html[3]): t = str(html[3]) elif len(html) > 2: if "http" in str(html[1]): t = str(html[1]) elif "http" in str(html[2]): t = str(html[2]) else: t = str(html[0]) url = t.partition("=")[2].partition("'")[0] return (url) else: print("Liste schlecht formatiert") def getURLfromM3U(self, inURL): print("detecting", inURL) response = requests.get(inURL) html = response.text.splitlines() print(html) if "#EXTINF" in str(html): url = str(html[1]).partition("http://")[2].partition('"')[0] url = f"http://{url}" else: if len(html) > 1: url = str(html[1]) else: url = str(html[0]) print(url) return (url) def findStations(self): self.field.setPlainText("") mysearch = self.findfield.text() self.statusBar().showMessage("searching ...") rb = RadioBrowser() myparams = {'name': 'search', 'nameExact': 'false'} for key in myparams.keys(): if key == "name": myparams[key] = mysearch r = rb.station_search(params=myparams) n = "" m = "" for i in range(len(r)): for key, value in r[i].items(): if str(key) == "name": n = value.replace(",", " ") if str(key) == "url": m = value self.field.appendPlainText("%s,%s" % (n, m)) if not self.field.toPlainText() == "": self.statusBar().showMessage(str(self.field.toPlainText().count('\n')+1) \ + " '" + self.findfield.text() + "' Stationen gefunden") else: self.statusBar().showMessage("nothing found", 0)
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.setGeometry(0, 0, 700, 400) self.setContentsMargins(6, 6, 6, 6) self.setStyleSheet(myStyleSheet(self)) self.setWindowTitle("Radio Stations - searching with pyradios") self.genreList = genres.splitlines() self.findfield = QLineEdit() self.findfield.setFixedWidth(250) self.findfield.addAction(QIcon.fromTheme("edit-find"), 0) self.findfield.setPlaceholderText("type search term and press RETURN ") self.findfield.returnPressed.connect(self.findStations) self.findfield.setClearButtonEnabled(True) self.field = QPlainTextEdit() self.field.setContextMenuPolicy(Qt.CustomContextMenu) self.field.customContextMenuRequested.connect( self.contextMenuRequested) self.field.cursorPositionChanged.connect(self.selectLine) self.field.setWordWrapMode(QTextOption.NoWrap) ### volume slider self.volSlider = QSlider() self.volSlider.setFixedWidth(100) self.volSlider.setOrientation(Qt.Horizontal) self.volSlider.valueChanged.connect(self.setVolume) self.volSlider.setMinimum(0) self.volSlider.setMaximum(100) ### genre box self.combo = QComboBox() self.combo.currentIndexChanged.connect(self.comboSearch) self.combo.addItem("choose Genre") for m in self.genreList: self.combo.addItem(m) self.combo.addItem("Country") self.combo.setFixedWidth(150) ### toolbar ### self.tb = self.addToolBar("tools") self.tb.setContextMenuPolicy(Qt.PreventContextMenu) self.tb.setMovable(False) self.saveButton = QPushButton("Save as txt") self.saveButton.setIcon(QIcon.fromTheme("document-save")) self.saveButton.clicked.connect(self.saveStations) self.savePlaylistButton = QPushButton("Save as m3u") self.savePlaylistButton.setIcon(QIcon.fromTheme("document-save")) self.savePlaylistButton.clicked.connect(self.savePlaylist) self.setCentralWidget(self.field) self.tb.addWidget(self.findfield) self.tb.addWidget(self.saveButton) self.tb.addWidget(self.savePlaylistButton) self.tb.addSeparator() self.tb.addWidget(self.combo) ### player ### self.player = QMediaPlayer() self.player.metaDataChanged.connect(self.metaDataChanged) self.startButton = QPushButton("Play") self.startButton.setIcon(QIcon.fromTheme("media-playback-start")) self.startButton.clicked.connect(self.getURLtoPlay) self.stopButton = QPushButton("Stop") self.stopButton.setIcon(QIcon.fromTheme("media-playback-stop")) self.stopButton.clicked.connect(self.stopPlayer) self.statusBar().addPermanentWidget(self.volSlider) self.statusBar().addPermanentWidget(self.startButton) self.statusBar().addPermanentWidget(self.stopButton) ## actions self.getNameAction = QAction(QIcon.fromTheme("edit-copy"), "copy Station Name", self, triggered=self.getName) self.getUrlAction = QAction(QIcon.fromTheme("edit-copy"), "copy Station URL", self, triggered=self.getURL) self.getNameAndUrlAction = QAction(QIcon.fromTheme("edit-copy"), "copy Station Name,URL", self, triggered=self.getNameAndUrl) self.getURLtoPlayAction = QAction( QIcon.fromTheme("media-playback-start"), "play Station", self, shortcut="F6", triggered=self.getURLtoPlay) self.addAction(self.getURLtoPlayAction) self.stopPlayerAction = QAction(QIcon.fromTheme("media-playback-stop"), "stop playing", self, shortcut="F7", triggered=self.stopPlayer) self.addAction(self.stopPlayerAction) self.helpAction = QAction(QIcon.fromTheme("help-info"), "Help", self, shortcut="F1", triggered=self.showHelp) self.addAction(self.helpAction) self.getForWebAction = QAction(QIcon.fromTheme("browser"), "copy for WebPlayer", self, triggered=self.getForWeb) self.volSlider.setValue(60) self.statusBar().showMessage("Welcome", 0) def setVolume(self): self.player.setVolume(self.volSlider.value()) def comboSearch(self): if self.combo.currentIndex() > 0: self.findfield.setText(self.combo.currentText()) self.findStations() def getName(self): t = self.field.textCursor().selectedText().partition(",")[0] clip = QApplication.clipboard() clip.setText(t) def getURL(self): t = self.field.textCursor().selectedText().partition(",")[2] clip = QApplication.clipboard() clip.setText(t) def getNameAndUrl(self): t = self.field.textCursor().selectedText() clip = QApplication.clipboard() clip.setText(t) def getForWeb(self): t = self.field.textCursor().selectedText() name = t.partition(",")[0] url = t.partition(",")[2] result = f"<li><a class='chlist' href='{url}'>{name}</a></li>" clip = QApplication.clipboard() clip.setText(result) def selectLine(self): tc = self.field.textCursor() tc.select(QTextCursor.LineUnderCursor) tc.movePosition(QTextCursor.StartOfLine, QTextCursor.MoveAnchor) ##, tc.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) self.field.setTextCursor(tc) def showHelp(self): QMessageBox.information( self, "Information", "F6 -> play Station (from line where cursor is)\nF7 -> stop playing" ) def stopPlayer(self): self.player.stop() self.statusBar().showMessage("Player stopped", 0) ### QPlainTextEdit contextMenu def contextMenuRequested(self, point): cmenu = QMenu() if not self.field.toPlainText() == "": cmenu.addAction(self.getNameAction) cmenu.addAction(self.getUrlAction) cmenu.addAction(self.getNameAndUrlAction) cmenu.addSeparator() cmenu.addAction(self.getURLtoPlayAction) cmenu.addAction(self.stopPlayerAction) cmenu.addSeparator() cmenu.addAction(self.helpAction) cmenu.addAction(self.getForWebAction) cmenu.exec_(self.field.mapToGlobal(point)) def getURLtoPlay(self): url = "" tc = self.field.textCursor() rtext = tc.selectedText().partition(",")[2] stext = tc.selectedText().partition(",")[0] url = rtext print("stream url=", url) self.player.setMedia(QMediaContent(QUrl(url))) self.player.play() self.statusBar().showMessage("%s %s" % ("playing", stext), 0) def metaDataChanged(self): if self.player.isMetaDataAvailable(): trackInfo = (self.player.metaData("Title")) trackInfo2 = (self.player.metaData("Comment")) if not trackInfo == None: self.statusBar().showMessage(trackInfo, 0) if not trackInfo2 == None: self.statusBar().showMessage("%s %s" % (trackInfo, trackInfo2)) def findStations(self): self.field.setPlainText("") my_value = self.findfield.text() self.statusBar().showMessage("searching ...") base_url = "https://de1.api.radio-browser.info/xml/stations/byname/" url = f"{base_url}{my_value}" xml = requests.get(url).content.decode() if xml: root = ET.fromstring(xml) for child in root: ch_name = child.attrib["name"] ch_url = child.attrib["url"] self.field.appendPlainText(f"{ch_name},{ch_url}") self.copyToClipboard() tc = self.field.textCursor() tc.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) tc.select(QTextCursor.LineUnderCursor) tc.movePosition(QTextCursor.EndOfBlock, QTextCursor.KeepAnchor) self.field.setTextCursor(tc) else: self.statusBar().showMessage("nothing found", 0) self.field.verticalScrollBar().triggerAction( QScrollBar.SliderToMinimum) self.field.horizontalScrollBar().triggerAction( QScrollBar.SliderToMinimum) def saveStations(self): if not self.field.toPlainText() == "": path, _ = QFileDialog.getSaveFileName( None, "RadioStations", self.findfield.text() + ".txt", "Text Files (*.txt)") if path: s = self.field.toPlainText() with open(path, 'w') as f: f.write(s) f.close() self.statusBar().showMessage("saved!", 0) def savePlaylist(self): if not self.field.toPlainText() == "": path, _ = QFileDialog.getSaveFileName( None, "RadioStations", self.findfield.text() + ".m3u", "Playlist Files (*.m3u)") if path: result = "" s = self.field.toPlainText() st = [] for line in s.splitlines(): st.append(line) result += "#EXTM3U" result += '\n' for x in range(len(st)): result += "#EXTINF:" + str(x) + "," + st[x].partition( ",")[0] result += '\n' result += st[x].partition(",")[2] result += '\n' with open(path, 'w') as f: f.write(result) f.close() self.statusBar().showMessage("saved!", 0) def copyToClipboard(self): clip = QApplication.clipboard() if not self.field.toPlainText() == "": clip.setText(self.field.toPlainText())
class Player(QWidget): fullScreenChanged = pyqtSignal(bool) videoWidget = None colorDialog = None videoWidget = None trackInfo = "" statusInfo = "" duration = 0 def __init__(self, playlist, parent=None): """ :param playlist: :param parent: """ super(Player, self).__init__(parent) self._init_audio_player() self._init_video_player() self._init_addtional_controls() control_layout, controls, open_button = self._init_player_controls_layout( ) self._init_playlist_view() self._init_layout(control_layout=control_layout) self.check_for_player_service(controls=controls, open_button=open_button) self.meta_data_changed() self.add_to_playlist(playlist) def check_for_player_service(self, controls, open_button): """ :param controls: :param open_button: :return: """ if not self.player.isAvailable(): QMessageBox.warning( self, "Service not available", "The QMediaPlayer object does not have a valid service.\n" "Please check the media service plugins are installed.") controls.setEnabled(False) self.playlistView.setEnabled(False) open_button.setEnabled(False) self.colorButton.setEnabled(False) self.fullScreenButton.setEnabled(False) def _init_layout(self, control_layout): """ :param control_layout: :return: """ display_layout = QHBoxLayout() if self.videoWidget: display_layout.addWidget(self.videoWidget, 2) if self.playlistView: display_layout.addWidget(self.playlistView) layout = QVBoxLayout() layout.addLayout(display_layout) if self.slider or self.labelDuration: h_layout = QHBoxLayout() if self.slider: h_layout.addWidget(self.slider) if self.labelDuration: h_layout.addWidget(self.labelDuration) layout.addLayout(h_layout) if control_layout: layout.addLayout(control_layout) layout.addLayout(self._get_histogram_layout()) self.setLayout(layout) def _init_playlist_view(self): """ :return: """ self.playlistView = QListView() self.playlistView.setModel(self.playlistModel) self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) self.playlistView.activated.connect(self.jump) def _init_addtional_controls(self): """ :return: """ self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, self.player.duration() / 1000) self.labelDuration = QLabel() self.slider.sliderMoved.connect(self.seek) self.fullScreenButton = QPushButton("FullScreen") self.fullScreenButton.setCheckable(True) self.colorButton = QPushButton("Color Options...") self.colorButton.setEnabled(False) self.colorButton.clicked.connect(self.show_color_dialog) def _init_player_controls_layout(self): """ :return: """ openButton = QPushButton("Open", clicked=self.open) controls = PlayerControls() controls.set_state(self.player.state()) controls.set_volume(self.player.volume()) controls.set_muted(controls.is_muted()) controls.play.connect(self.player.play) controls.pause.connect(self.player.pause) controls.stop.connect(self.player.stop) controls.next.connect(self.playlist.next) controls.previous.connect(self.previous_clicked) controls.changeVolume.connect(self.player.setVolume) controls.changeMuting.connect(self.player.setMuted) controls.changeRate.connect(self.player.setPlaybackRate) if self.videoWidget: controls.stop.connect(self.videoWidget.update) self.player.stateChanged.connect(controls.set_state) self.player.volumeChanged.connect(controls.set_volume) self.player.mutedChanged.connect(controls.set_muted) controlLayout = QHBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) controlLayout.addWidget(openButton) controlLayout.addStretch(1) controlLayout.addWidget(controls) controlLayout.addStretch(1) if self.fullScreenButton: controlLayout.addWidget(self.fullScreenButton) if self.colorButton: controlLayout.addWidget(self.colorButton) return controlLayout, controls, openButton def _init_audio_player(self): """ :return: """ self.playlist = QMediaPlaylist() self.player = QMediaPlayer() self.player.setPlaylist(self.playlist) self.player.durationChanged.connect(self.duration_changed) self.player.positionChanged.connect(self.position_changed) self.player.metaDataChanged.connect(self.meta_data_changed) self.playlist.currentIndexChanged.connect( self.playlist_position_changed) self.player.mediaStatusChanged.connect(self.status_changed) self.player.bufferStatusChanged.connect(self.buffering_progress) self.player.videoAvailableChanged.connect(self.video_available_changed) self.player.error.connect(self.display_error_message) self.playlistModel = PlaylistModel() self.playlistModel.set_playlist(self.playlist) def _init_video_player(self): """ :return: """ self.histogram = HistogramWidget() self.videoWidget = VideoWidget() self.player.setVideoOutput(self.videoWidget) self.probe = QVideoProbe() self.probe.videoFrameProbed.connect(self.histogram.process_frame) self.probe.setSource(self.player) def _get_histogram_layout(self): """ :return: """ self.labelHistogram = QLabel() self.labelHistogram.setText("Histogram:") histogramLayout = QHBoxLayout() histogramLayout.addWidget(self.labelHistogram) if self.histogram: histogramLayout.addWidget(self.histogram, 1) return histogramLayout def open(self): """ :return: """ file_names, _ = QFileDialog.getOpenFileNames(self, "Open Files") self.add_to_playlist(file_names) def add_to_playlist(self, file_names): """ :param file_names: :return: """ for name in file_names: file_info = QFileInfo(name) if file_info.exists(): url = QUrl.fromLocalFile(file_info.absoluteFilePath()) if file_info.suffix().lower() == 'm3u': self.playlist.load(url) else: self.playlist.addMedia(QMediaContent(url)) else: url = QUrl(name) if url.isValid(): self.playlist.addMedia(QMediaContent(url)) def duration_changed(self, duration): """ :param duration: :return: """ duration /= 1000 self.duration = duration self.slider.setMaximum(duration) def position_changed(self, progress): """ :param progress: :return: """ progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) self.update_duration_info(progress) def meta_data_changed(self): """ :return: """ if self.player.isMetaDataAvailable(): self.set_track_info( "%s - %s" % (self.player.metaData(QMediaMetaData.AlbumArtist), self.player.metaData(QMediaMetaData.Title))) def previous_clicked(self): """ :return: """ if self.player.position() <= 5000: self.playlist.previous() else: self.player.setPosition(0) def jump(self, index): """ :param index: :return: """ if index.isValid(): self.playlist.setCurrentIndex(index.row()) self.player.play() def playlist_position_changed(self, position): """ :param position: :return: """ self.playlistView.setCurrentIndex(self.playlistModel.index( position, 0)) def seek(self, seconds): """ :param seconds: :return: """ self.player.setPosition(seconds * 1000) def status_changed(self, status): """ :param status: :return: """ self.handle_cursor(status) if status == QMediaPlayer.LoadingMedia: self.set_status_info("Loading...") elif status == QMediaPlayer.StalledMedia: self.set_status_info("Media Stalled") elif status == QMediaPlayer.EndOfMedia: QApplication.alert(self) elif status == QMediaPlayer.InvalidMedia: self.display_error_message() else: self.set_status_info("") def handle_cursor(self, status): """ :param status: :return: """ if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia): self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def buffering_progress(self, progress): """ :param progress: :return: """ self.set_status_info("Buffering %d%" % progress) def video_available_changed(self, available): """ :param available: :return: """ if available: self.fullScreenButton.clicked.connect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.connect( self.fullScreenButton.setChecked) if self.fullScreenButton.isChecked(): self.videoWidget.setFullScreen(True) else: self.fullScreenButton.clicked.disconnect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.disconnect( self.fullScreenButton.setChecked) self.videoWidget.setFullScreen(False) self.colorButton.setEnabled(available) def set_track_info(self, info): """ :param info: :return: """ self.trackInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def set_status_info(self, info): """ :param info: :return: """ self.statusInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def display_error_message(self): """ :return: """ self.set_status_info(self.player.errorString()) def update_duration_info(self, current_info): """ :param current_info: :return: """ result = "" duration = self.duration if current_info or duration: current_time = QTime((current_info / 3600) % 60, (current_info / 60) % 60, current_info % 60, (current_info * 1000) % 1000) total_time = QTime((duration / 3600) % 60, (duration / 60) % 60, duration % 60, (duration * 1000) % 1000) if duration > 3600: format = 'hh:mm:ss' else: format = 'mm:ss' result = current_time.toString( format) + " / " + total_time.toString(format) self.labelDuration.setText(result) def show_color_dialog(self): """ :return: """ if self.colorDialog is None: brightness_slider = QSlider(Qt.Horizontal) brightness_slider.setRange(-100, 100) brightness_slider.setValue(self.videoWidget.brightness()) brightness_slider.sliderMoved.connect( self.videoWidget.setBrightness) self.videoWidget.brightnessChanged.connect( brightness_slider.setValue) contrast_slider = QSlider(Qt.Horizontal) contrast_slider.setRange(-100, 100) contrast_slider.setValue(self.videoWidget.contrast()) contrast_slider.sliderMoved.connect(self.videoWidget.setContrast) self.videoWidget.contrastChanged.connect(contrast_slider.setValue) hue_slider = QSlider(Qt.Horizontal) hue_slider.setRange(-100, 100) hue_slider.setValue(self.videoWidget.hue()) hue_slider.sliderMoved.connect(self.videoWidget.setHue) self.videoWidget.hueChanged.connect(hue_slider.setValue) saturation_slider = QSlider(Qt.Horizontal) saturation_slider.setRange(-100, 100) saturation_slider.setValue(self.videoWidget.saturation()) saturation_slider.sliderMoved.connect( self.videoWidget.setSaturation) self.videoWidget.saturationChanged.connect( saturation_slider.setValue) layout = QFormLayout() layout.addRow("Brightness", brightness_slider) layout.addRow("Contrast", contrast_slider) layout.addRow("Hue", hue_slider) layout.addRow("Saturation", saturation_slider) button = QPushButton("Close") layout.addRow(button) self.colorDialog = QDialog(self) self.colorDialog.setWindowTitle("Color Options") self.colorDialog.setLayout(layout) button.clicked.connect(self.colorDialog.close) self.colorDialog.show()
class MainWindowMusicPlayer(QMainWindow): stopState: bool def __init__(self): super().__init__() self.currentPlaylist = QMediaPlaylist() self.player = QMediaPlayer() self.userAction = -1 # 0 - stopped, 1 - playing 2 - paused self.player.mediaStatusChanged.connect(self.qmp_media_status_changed) self.player.stateChanged.connect(self.qmp_state_changed) self.player.positionChanged.connect(self.qmp_position_changed) self.player.volumeChanged.connect(self.qmp_volume_changed) self.player.setVolume(60) # Status bar self.statusBar().showMessage('No Media' ' :: %d' % self.player.volume()) self.home_screen() def home_screen(self): self.setWindowTitle('Music Player') self.create_menubar() self.create_toolbar() controlBar = self.add_controls() # need to add both information screen # and control bar to the central widget. centralWidget = QWidget() centralWidget.setLayout(controlBar) self.setCentralWidget(centralWidget) # Set size of the MainWindow self.resize(200, 100) self.show() def create_menubar(self): menubar = self.menuBar() file_menu = menubar.addMenu('File') file_menu.addAction(self.file_open()) file_menu.addAction(self.song_info()) file_menu.addAction(self.folder_open()) file_menu.addAction(self.exit_action()) def create_toolbar(self): pass def add_controls(self): controlArea = QVBoxLayout() seekSliderLayout = QHBoxLayout() controls = QHBoxLayout() playlistCtrlLayout = QHBoxLayout() # creating buttons playBtn = QPushButton('Play') # play button pauseBtn = QPushButton('Pause') # pause button stopBtn = QPushButton('Stop') # stop button volumeDescBtn = QPushButton('V (-)') # Decrease Volume volumeIncBtn = QPushButton('V (+)') # Increase Volume # creating playlist controls prevBtn = QPushButton('Prev Song') nextBtn = QPushButton('Next Song') # creating seek slider seekSlider = QSlider() seekSlider.setMinimum(0) seekSlider.setMaximum(100) seekSlider.setOrientation(Qt.Horizontal) seekSlider.setTracking(False) seekSlider.sliderMoved.connect(self.seek_position) # seekSlider.valueChanged.connect(self.seekPosition) seekSliderLabel1 = QLabel('0.00') seekSliderLabel2 = QLabel('0.00') seekSliderLayout.addWidget(seekSliderLabel1) seekSliderLayout.addWidget(seekSlider) seekSliderLayout.addWidget(seekSliderLabel2) # Add handler for each button. Not using the default slots. playBtn.clicked.connect(self.play_handler) pauseBtn.clicked.connect(self.pause_handler) stopBtn.clicked.connect(self.stop_handler) volumeDescBtn.clicked.connect(self.decrease_volume) volumeIncBtn.clicked.connect(self.increase_volume) # Adding to the horizontal layout controls.addWidget(volumeDescBtn) controls.addWidget(playBtn) controls.addWidget(pauseBtn) controls.addWidget(stopBtn) controls.addWidget(volumeIncBtn) # playlist control button handlers prevBtn.clicked.connect(self.prev_item_playlist) nextBtn.clicked.connect(self.next_item_playlist) playlistCtrlLayout.addWidget(prevBtn) playlistCtrlLayout.addWidget(nextBtn) # Adding to the vertical layout controlArea.addLayout(seekSliderLayout) controlArea.addLayout(controls) controlArea.addLayout(playlistCtrlLayout) return controlArea # Music playback function def play_handler(self): self.userAction = 1 self.statusBar().showMessage('Playing at Volume %d' % self.player.volume()) if self.player.state() == QMediaPlayer.StoppedState: if self.player.mediaStatus() == QMediaPlayer.NoMedia: print(self.currentPlaylist.mediaCount()) if self.currentPlaylist.mediaCount() == 0: self.open_file() if self.currentPlaylist.mediaCount() != 0: self.player.setPlaylist(self.currentPlaylist) elif self.player.mediaStatus() ==\ QMediaPlayer.LoadedMedia: self.player.play() elif self.player.mediaStatus() ==\ QMediaPlayer.BufferedMedia: self.player.play() elif self.player.state() == QMediaPlayer.PlayingState: pass elif self.player.state() == QMediaPlayer.PausedState: self.player.play() # Music pause function def pause_handler(self): self.userAction = 2 self.statusBar().showMessage( 'Paused %s at position' ' %s at Volume %d' % (self.player.metaData(QMediaMetaData.Title), self.centralWidget().layout().itemAt(0).layout().itemAt( 0).widget().text(), self.player.volume())) self.player.pause() # Music stop function def stop_handler(self): self.userAction = 0 self.statusBar().showMessage('Stopped at Volume %d' % (self.player.volume())) if self.player.state() == QMediaPlayer.PlayingState: self.stopState = True self.player.stop() elif self.player.state() == QMediaPlayer.PausedState: self.player.stop() elif self.player.state() == QMediaPlayer.StoppedState: pass # Music status change function def qmp_media_status_changed(self): if self.player.mediaStatus() == QMediaPlayer.LoadedMedia \ and self.userAction == 1: durationT = self.player.duration() self.centralWidget().layout().itemAt(0).layout() \ .itemAt(1).widget().setRange(0, durationT) self.centralWidget().layout().itemAt(0).layout() \ .itemAt(2).widget().setText( '%d:%02d' % (int(durationT / 60000), int((durationT / 1000) % 60))) self.player.play() # Music playing change function def qmp_state_changed(self): if self.player.state() == QMediaPlayer.StoppedState: self.player.stop() # Music time change function def qmp_position_changed(self, position, senderType=False): sliderLayout = self.centralWidget().layout().itemAt(0)\ .layout() if not senderType: sliderLayout.itemAt(1).widget().setValue(position) # update the text label sliderLayout.itemAt(0).widget()\ .setText('%d:%02d' % (int(position / 60000), int((position / 1000) % 60))) def seek_position(self, position): sender = self.sender() if isinstance(sender, QSlider): if self.player.isSeekable(): self.player.setPosition(position) # Music volume change function def qmp_volume_changed(self): msg = self.statusBar().currentMessage() msg = msg[:-2] + str(self.player.volume()) self.statusBar().showMessage(msg) # Music volume + change function def increase_volume(self): vol = self.player.volume() vol = min(vol + 5, 100) self.player.setVolume(vol) # Music volume - change function def decrease_volume(self): vol = self.player.volume() vol = max(vol - 5, 0) self.player.setVolume(vol) # File open function def file_open(self): fileAc = QAction(QIcon('icons\\open.png'), 'Open File', self) fileAc.setShortcut('Ctrl+O') fileAc.setStatusTip('Open File') fileAc.triggered.connect(self.open_file) return fileAc # File opening function def open_file(self): file_Chosen = QFileDialog.getOpenFileUrl(self, 'Open Music File', expanduser('~'), 'Audio (*.mp3 *.ogg *.wav)', '*.mp3 *.ogg *.wav') if file_Chosen is not None: self.currentPlaylist.addMedia(QMediaContent(file_Chosen[0])) # Folder open function def folder_open(self): folderAc = QAction(QIcon('icons\\open_fld.png'), 'Open Folder', self) folderAc.setShortcut('Ctrl+D') folderAc.setStatusTip('Open Folder ' '(Will add all the files in' ' the folder)') folderAc.triggered.connect(self.add_files) return folderAc # Folder opening function def add_files(self): folder_Chosen = QFileDialog\ .getExistingDirectory(self, 'Open Music Folder', expanduser('~')) if folder_Chosen is not None: it = QDirIterator(folder_Chosen) it.next() while it.hasNext(): if it.fileInfo().isDir() == False\ and it.filePath() != '.': fInfo = it.fileInfo() print(it.filePath(), fInfo.suffix()) if fInfo.suffix() in ('mp3', 'ogg', 'wav'): print('added file ', fInfo.fileName()) self.currentPlaylist. \ addMedia(QMediaContent( QUrl.fromLocalFile(it.filePath()))) it.next() # Song information function def song_info(self): infoAc = QAction(QIcon('icons\\info.png'), 'Info', self) infoAc.setShortcut('Ctrl+I') infoAc.setStatusTip('Displays Current Song Information') infoAc.triggered.connect(self.display_song_info) return infoAc # Show song information def display_song_info(self): metaDataKeyList = self.player.availableMetaData() fullText = '<table class="tftable" border="0">' for key in metaDataKeyList: value = self.player.metaData(key) fullText = \ fullText + '<tr><td>' + key + '</td><td>' + \ str(value) + '</td></tr>' fullText = fullText + '</table>' infoBox = QMessageBox(self) infoBox.setWindowTitle('Detailed Song Information') infoBox.setTextFormat(Qt.RichText) infoBox.setText(fullText) infoBox.addButton('OK', QMessageBox.AcceptRole) infoBox.show() # Switch to previous song def prev_item_playlist(self): self.player.playlist().previous() # Switch to next song def next_item_playlist(self): self.player.playlist().next() # exit function def exit_action(self): exitAc = QAction(QIcon('icons\\exit.png'), '&Exit', self) exitAc.setShortcut('Ctrl+Q') exitAc.setStatusTip('Exit App') exitAc.triggered.connect(self.close) return exitAc # exiting function @staticmethod def exit(): sys.exit(app.exec_())
class VideoEditor(QFrame): """ This widget allows to load a video file and zoom onto a specific ROI (region of interest) """ meta_data_loaded = pyqtSignal(dict) @property def is_playing(self): """ True if the video is currently being played. """ return self._media_player.state() == QMediaPlayer.PlayingState @property def duration(self): """ Duration in seconds of the video clip currently loaded. """ return self._duration @property def start_trim(self): """ Gets the duration of the trimmed footage at the beginning of the video clip in seconds. """ return self._start_trim @start_trim.setter def start_trim(self, value): """ Sets how much of the beginning of the clip should be trimmed. :param value: Footage to cut in seconds """ if not self.video_file_open: return if value > self.end_trim: value = self.end_trim if value < 0: value = 0 self._start_trim = value self._start_frame = int(value * self.fps) self._timeline.setMinimum(value * 1000) self._current_time.setText('{:10.3f}'.format( round(self._timeline.value() / self.fps / 1000 - self._start_trim, 3))) self._total_time.setText('{:10.3f}'.format(self._end_trim - self._start_trim)) @property def end_trim(self): """ Gets the time in seconds until which the video clip is shown. The rest is trimmed. """ return self._end_trim @end_trim.setter def end_trim(self, value): """ Sets the time in seconds until which the video clip is displayed. Everything beyond is trimmed. :param value: Time code in seconds until which the video clip shoud be shown """ if not self.video_file_open: return if value > self._duration_time_code / 1000: value = self._duration_time_code / 1000 if value < self._start_trim: value = self._start_trim self._end_trim = value self._end_frame = int(value * self.fps) self._timeline.setMaximum(value * 1000) self._current_time.setText('{:10.3f}'.format( round(self._timeline.value() / self.fps / 1000 - self._start_trim, 3))) self._total_time.setText('{:10.3f}'.format(self._end_trim - self._start_trim)) @property def fps(self): """ Frames per second of the currently loaded video clip. """ return self._fps @property def frame_count(self): """ Total amount of frames of the currently loaded video clip. """ return self._frame_count @property def current_frame(self): """ Gets the current frame """ return self._current_frame @current_frame.setter def current_frame(self, value): """ Sets the current frame :param value: frame number """ self._current_frame = value self._allow_frame_counter_update = False self._media_player.setPosition(self._frame_number_to_time_code(value)) self._allow_frame_counter_update = True @property def start_frame(self): """ Gets the start frame. (Depends on the trimming of the video clip) """ return self._start_frame @property def end_frame(self): """ Gets the end frame. (Depends on the trimming of the video clip) """ return self._end_frame @property def video_width(self): """ Horizontal resolution of the currently loaded video clip. """ return self._video_width @property def video_height(self): """ Vertical resolution of the currently loaded video clip. """ return self._video_height @property def video_path(self): """ Path of the currently loaded video file. """ return self._video_path @property def video_file_open(self): """ True if a video file is currently opened in the editor. """ return self._video_file_open @property def roi(self): """ Returns a QRect representing the current region of interest. (If None, the entire image is the ROI) """ return self._roi @property def overlay_layers(self): return self._image_control.overlay_layers def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.installEventFilter( self) # start listening for mouse events to capture ROI changes self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored ) # make the editor use as much space as possible self._media_player = QMediaPlayer(self, QMediaPlayer.VideoSurface) self._media_player.positionChanged.connect(self._player_time_changed_) self._media_player.metaDataAvailableChanged.connect( self._meta_data_changed_) self._grabber = VideoFrameGrabber(self) self._allow_frame_counter_update = True self._result_frame = None self._media_player.setVideoOutput(self._grabber) self._grabber.frameAvailable.connect(self._frame_ready_) self._video_file_open = False self._video_path = None self._display_image = dict( ) # cache for images that are displayed from the video self._frame_count = 0 self._duration_time_code = 0 self._duration = 0 self._fps = 1 self._current_frame = 0 self._video_width = 0 self._video_height = 0 self._file_path = None self._is_playing = False self._roi = None self._start_trim = 0 self._end_trim = 0 self._start_frame = 0 self._end_frame = 0 self._layers = list( ) # layers for visualizations on top of the video footage self._selection_layer = ImageLayer( enabled=False) # layer for selection indicators self._selection_rectangle = ImageRectangle( 0, 0, filled=False, border_color=Qt.yellow) # selection rectangle self._selection_layer.shapes.append(self._selection_rectangle) self._selection_start = None # start point for selection rectangle self.time_code_changed = list() self._layout = QGridLayout() self._timeline = None self._current_time = None self._total_time = None self._frame_box = None self._play_button = None self._stop_button = None self._next_frame_button = None self._previous_frame_button = None self._box_roi_x = None self._box_roi_y = None self._box_roi_width = None self._box_roi_height = None self._accept_roi_updates_from_boxes = True self._wait = False self.setLayout(self._layout) self._setup_() def _time_code_to_frame_number_(self, time_code): """ Convert time code to frame number :param time_code: time code (in ms) :return: frame number """ return int(time_code / 1000 * self.fps) def _frame_number_to_time_code(self, f_number): """ Convert frame number to time code :param f_number: frame number :return: time code (in ms) """ return int(f_number / self.fps * 1000) def _to_image_space_(self, point: QPoint): """ Convert a point on the editor widget in to a point in the video footage. :param point: point in coordinates of the widget :return: point in the coordinates of the video footage """ control_position = self._image_control.pos( ) # get the position of the video image on the editor control_size = self._image_control.size( ) # get the size of the video image dx = (control_size.width() - self._image_control.image_width ) / 2 # get the x offset of the footage in the image dy = (control_size.height() - self._image_control.image_height ) / 2 # get the y offset of the footage in the image x = (point.x() - dx - control_position.x()) / self._image_control.image_scale_x y = (point.y() - dy - control_position.y()) / self._image_control.image_scale_y return QPoint(x, y) def eventFilter(self, obj, event): """ Check for mouse events to edit the ROI :param obj: object that caused the event :param event: event parameters (i.e. mouse position on the widget) """ if event.type( ) == QEvent.MouseMove: # if the mouse was moved, update the selection size target = self._to_image_space_(event.pos()) self._selection_rectangle.position = self._selection_start self._selection_rectangle.width = target.x( ) - self._selection_start.x() self._selection_rectangle.height = target.y( ) - self._selection_start.y() self._image_control.update() elif event.type( ) == QEvent.MouseButtonPress: # if the left mouse button was pressed designate the point as start of the selection self._selection_layer.enabled = True target = self._to_image_space_(event.pos()) self._selection_start = target elif event.type( ) == QEvent.MouseButtonRelease and self._selection_start is not None: # if the button was release the the ROI self._selection_layer.enabled = False end_point = self._to_image_space_(event.pos()) # get all possible corner points x1 = self._selection_start.x() x2 = end_point.x() y1 = self._selection_start.y() y2 = end_point.y() # find upper left corner of the ROI roi_x = x1 if x1 < x2 else x2 roi_y = y1 if y1 < y2 else y2 # find extent of the ROI roi_width = abs(x1 - x2) roi_height = abs(y1 - y2) # set the ROI if it was not just a click with no extent if roi_width > 0 and roi_height > 0: # take into account if the footage was already focused onto a previous ROI if self._roi is not None: roi_x += self._roi.x() roi_y += self._roi.y() # update spin box values self._accept_roi_updates_from_boxes = False # disable ROI changes from the spin boxes self._box_roi_x.setValue(roi_x) self._box_roi_y.setValue(roi_y) self._box_roi_width.setValue(roi_width) self._box_roi_height.setValue(roi_height) self._accept_roi_updates_from_boxes = True # enable ROI changes from the spin boxes self.set_roi(QRect(roi_x, roi_y, roi_width, roi_height)) # set ROI self._selection_start = None # remove selection start return False def sizeHint(self): """ Needed for widget to expand properly on the UI (Should be improved) """ return QSize(1200, 1200) def load(self, path): """ Load a video file from the given path :param path: path of the file """ if self.video_file_open: # close current video file if one was open self.close() self._media_player.setMedia(QMediaContent(QUrl.fromLocalFile(path))) self._video_file_open = True self._media_player.pause() def _player_time_changed_(self, position): self._timeline.setValueSilent(position) if self._allow_frame_counter_update: self._current_frame = self._time_code_to_frame_number_(position) # used for updating keyframes self._image_control.current_frame = self._current_frame # update the UI self._current_time.setText('{:10.3f}'.format( round(position / 1000 - self._start_trim, 3))) self._frame_box.setText('(frame: {:04})'.format(self.current_frame)) # send event about frame change for callback in self.time_code_changed: callback(position / 1000 - self._start_trim) def _meta_data_changed_(self, available): if self._media_player.isMetaDataAvailable(): resolution = self._media_player.metaData('Resolution') self._duration_time_code = self._media_player.metaData( 'Duration') # get duration in ms self._fps = self._media_player.metaData( 'VideoFrameRate') # get frames per second of the video self._media_player.setNotifyInterval(1000 / self._fps) self._frame_count = self._time_code_to_frame_number_( self._duration_time_code) # get total video frames self._video_width = resolution.width() # get width of the image self._video_height = resolution.height() # get height of the image self.start_trim = 0 # no trimming when video is loaded self._start_frame = 0 # first frame is also the first frame of the video self._duration = self._duration_time_code / 1000 self._end_frame = self._frame_count - 1 # don't trim the end of the video self.end_trim = self.duration # use the duration of the video as trim mark (no trimming) # set maximum values of the ROI spin boxes self._box_roi_x.setMaximum(self._video_width) self._box_roi_width.setMaximum(self._video_width) self._box_roi_y.setMaximum(self._video_height) self._box_roi_height.setMaximum(self._video_height) # update the data on the UI elements self._total_time.setText('{:10.3f}'.format(self._end_trim - self._start_trim)) self._frame_box.setText('(Frame: 0000)') self._timeline.setValue(0) self._timeline.setMaximum(self._duration_time_code) self._timeline.setEnabled(True) self._play_button.setEnabled(True) self._stop_button.setEnabled(True) self._next_frame_button.setEnabled(True) self._previous_frame_button.setEnabled(True) self.reset_roi() meta_data = dict() for key in self._media_player.availableMetaData(): meta_data[key] = self._media_player.metaData(key) self.meta_data_loaded.emit(meta_data) def _frame_ready_(self, frame): if self._roi is not None: # crop the frame to the ROI if one has been specified self._result_frame = frame.copy(self._roi) else: self._result_frame = frame self._image_control.set_image(self._result_frame) self._wait = False if self._media_player.position() > self.end_trim * 1000: self.pause() self._media_player.setPosition(self.end_trim * 1000) elif self._media_player.position() < self.start_trim * 1000: self.pause() self._media_player.setPosition(self.start_trim * 1000) def set_time(self, seconds): """ Display the frame that is the closest to the given time :param seconds: time at which to display the frame in seconds """ if not self.video_file_open: return self._media_player.setPosition(seconds * 1000) def get_time(self, frame_number): """ Return the time code at the specified frame :param frame_number: frame number :return: time code in seconds """ if not self.video_file_open: # return zero if no file is opened return 0 frame_number = self._clamp_frame_number_(frame_number) return frame_number / self.fps - self._start_trim @staticmethod def _to_numpy_array_(frame): channels = int(frame.byteCount() / frame.width() / frame.height()) bits = frame.bits() bits.setsize(frame.byteCount()) return np.frombuffer(bits, np.uint8).reshape(frame.height(), frame.width(), channels) def get_frame(self, frame_number, wait_for_new_frame=False): """ Returns a numpy array containing the video frame at the given frame number :param frame_number: frame number :param wait_for_new_frame: Makes sure that a new frame is retrieved before it is returned (otherwise most recent is returned) :return: numpy array """ if not self.video_file_open: # return None if no video file is opened return None frame_number = self._clamp_frame_number_(frame_number) if wait_for_new_frame: self._wait = True old_interval = self._media_player.notifyInterval() self._media_player.setNotifyInterval(1) self._media_player.setPosition( self._frame_number_to_time_code(frame_number)) while self._wait: sleep(0.001) self._media_player.setNotifyInterval(old_interval) result_frame = self._result_frame data = self._to_numpy_array_(result_frame) return data def _clamp_frame_number_(self, frame_number): """ Clamps the given frame number to an allowed range :param frame_number: frame number :return: frame number between 0 and frame_count - 1 """ if frame_number < self._start_frame: frame_number = self._start_frame elif frame_number > self._end_frame: frame_number = self._end_frame return int(frame_number) def _clamp_time_code_(self, time_code): """ Clamps the given time code to an allowed range :param time_code: time code (in ms) :return: time code (in ms) """ if time_code < self._start_trim * 1000: time_code = self._start_trim * 1000 elif time_code > self._end_trim * 1000: time_code = self._end_trim * 1000 return time_code def set_roi(self, rect: QRect): """ Sets the region of interest on the video footage. :param rect: rectangle representing the region of interest """ self._roi = rect self._box_roi_x.setValue(rect.x()) self._box_roi_y.setValue(rect.y()) self._box_roi_width.setValue(rect.width()) self._box_roi_height.setValue(rect.height()) if self._frame_count > 0: self._display_time_code_( self._timeline.value()) # update the display def reset_roi(self): """ Reset the region of interest """ self._roi = None # set the full image as ROI on the spin boxes self._accept_roi_updates_from_boxes = False # stop the spin boxes from updating the ROI self._box_roi_x.setValue(0) self._box_roi_y.setValue(0) self._box_roi_width.setValue(self._video_width) self._box_roi_height.setValue(self._video_height) self._accept_roi_updates_from_boxes = True # re-enable the spin boxes to update the ROI if self._frame_count > 0: self._display_time_code_(self._timeline.value()) def play(self): """ Start playing the video that is currently loaded. """ if self.is_playing: # do nothing if the video is already playing return self._media_player.play() self._play_button.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) # set pause button icon to pause def pause(self): """ Pauses the video. """ self._media_player.pause() self._play_button.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) # set pause button icon to play def stop(self): """ Stop the video and rewind. """ self.pause() # stop the video from playing self._media_player.setPosition(self.start_trim * 1000) # return to the first frame def next_frame(self): """ Skip one frame ahead. """ if self.current_frame < self.end_frame: self.current_frame += 1 def previous_frame(self): """ Skip to the previous frame. """ if self.current_frame > self.start_frame: self.current_frame -= 1 def _play_pause_(self): """ Play if the video is paused or pause if the video is currently playing. """ if self.is_playing: self.pause() else: self.play() def _display_time_code_(self, time_code): """ Displays the requested time code on the widget. :param time_code: time code (in ms) """ if not self.video_file_open: # do nothing if no video file is open return time_code = self._clamp_time_code_(time_code) # get proper time code self._media_player.setPosition(time_code) def _roi_box_value_changed_(self, *args): """ Callback for changes made in the ROI spin boxes. Adjust the ROI accordingly. """ if self._accept_roi_updates_from_boxes: roi_x = self._box_roi_x.value() roi_y = self._box_roi_y.value() roi_width = self._box_roi_width.value() roi_height = self._box_roi_height.value() self.set_roi(QRect(roi_x, roi_y, roi_width, roi_height)) def close(self): """ Closes the video file which is currently opened. """ if self._video_file_open: self._media_player.stop() self._frame_count = 0 # set the frame count to zero self._fps = 1 # set the frames per second to zero self._media_player.setPosition(0) # set the timeline to zero self._timeline.setEnabled(False) # disable the timeline self._play_button.setEnabled(False) # disable the play button self._stop_button.setEnabled(False) # disable the stop button self._next_frame_button.setEnabled( True) # disable the skip frame button self._previous_frame_button.setEnabled( True) # disable the previous frame button self._current_time.setText( '0.000') # set the current time code to zero self._total_time.setText('0.000') # set the total time to zero self._frame_box.setText( '(frame: 0000)') # set the current frame to zero self._box_roi_x.setMaximum(0) # set the ROI maximum to zero self._box_roi_width.setMaximum(0) # set the ROI maximum to zero self._box_roi_y.setMaximum(0) # set the ROI maximum to zero self._box_roi_height.setMaximum(0) # set the ROI maximum to zero self.reset_roi() # reset the ROI def _setup_(self): self._image_control = ImageRenderWidget() self._image_control.overlay_layers.append(self._selection_layer) self._layout.addWidget(self._image_control, 0, 0, 1, 10, Qt.AlignCenter) self._timeline = QJumpSlider(Qt.Horizontal) self._timeline.setEnabled(False) self._timeline.valueChangedSmart.connect(self._display_time_code_) self._layout.addWidget(self._timeline, 1, 4) self._current_time = QLabel('0.000') self._total_time = QLabel('0.000') self._frame_box = QLabel('(Frame: 0000)') self._layout.addWidget(self._current_time, 1, 5) self._layout.addWidget(QLabel('/'), 1, 6) self._layout.addWidget(self._total_time, 1, 7) self._layout.addWidget(QLabel(' s'), 1, 8) self._layout.addWidget(self._frame_box, 1, 9) self._play_button = QPushButton() self._play_button.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) self._play_button.setEnabled(False) self._play_button.clicked.connect(self._play_pause_) self._layout.addWidget(self._play_button, 1, 0) self._stop_button = QPushButton() self._stop_button.setIcon(self.style().standardIcon( QStyle.SP_MediaStop)) self._stop_button.setEnabled(False) self._stop_button.clicked.connect(self.stop) self._layout.addWidget(self._stop_button, 1, 1) self._next_frame_button = QPushButton() self._next_frame_button.setIcon(self.style().standardIcon( QStyle.SP_MediaSkipForward)) self._next_frame_button.setEnabled(False) self._next_frame_button.clicked.connect(self.next_frame) self._layout.addWidget(self._next_frame_button, 1, 3) self._previous_frame_button = QPushButton() self._previous_frame_button.setIcon(self.style().standardIcon( QStyle.SP_MediaSkipBackward)) self._previous_frame_button.setEnabled(False) self._previous_frame_button.clicked.connect(self.previous_frame) self._layout.addWidget(self._previous_frame_button, 1, 2) roi_frame = QFrame() roi_layout = QHBoxLayout() roi_frame.setLayout(roi_layout) roi_frame.setFixedHeight(38) roi_layout.addWidget(QLabel('ROI: [')) roi_layout.addWidget(QLabel('x:')) self._box_roi_x = QSpinBox() roi_layout.addWidget(self._box_roi_x) roi_layout.addWidget(QLabel('y:')) self._box_roi_y = QSpinBox() roi_layout.addWidget(self._box_roi_y) roi_layout.addWidget(QLabel('width:')) self._box_roi_width = QSpinBox() roi_layout.addWidget(self._box_roi_width) roi_layout.addWidget(QLabel('height:')) self._box_roi_height = QSpinBox() roi_layout.addWidget(self._box_roi_height) roi_layout.addWidget(QLabel(']')) roi_reset_button = QPushButton('Reset') roi_reset_button.clicked.connect(self.reset_roi) roi_layout.addWidget(roi_reset_button) self._box_roi_x.valueChanged.connect(self._roi_box_value_changed_) self._box_roi_y.valueChanged.connect(self._roi_box_value_changed_) self._box_roi_width.valueChanged.connect(self._roi_box_value_changed_) self._box_roi_height.valueChanged.connect(self._roi_box_value_changed_) self._layout.addWidget(roi_frame, 2, 0, 1, 9, Qt.AlignLeft)
class Player(Qt.QWidget): """docstring for Player""" fullScreenChanged = Qt.pyqtSignal(bool) def __init__(self, playlist, parent=None): # create player super(Player, self).__init__(parent) self.trackInfo = '' self.statusInfo = '' self.duration = 0 # create player object self.player = QMediaPlayer() self.playlist = QMediaPlaylist() self.name = 'Current playlist' self.player.setPlaylist(self.playlist) self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.metaDataChanged.connect(self.metaDataChanged) self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) self.player.mediaStatusChanged.connect(self.statusChanged) self.player.bufferStatusChanged.connect(self.bufferingProgress) # self.player.videoAvailableChanged.connect(self.videoAvailableChanged) self.player.error.connect(self.displayErrorMessage) # connect with VideoWidget # self.videoWidget = VideoWidget() # self.player.setVideoOutput(self.videoWidget) # connect with PlaylistModel self.playlistModel = PlaylistModel() self.playlistModel.setPlaylist(self.playlist) self.playlistView = Qt.QListView() self.playlistView.setModel(self.playlistModel) self.playlistView.setCurrentIndex(self.playlistModel.index( self.playlist.currentIndex(), 0)) # change to next song self.playlistView.activated.connect(self.jump) self.slider = Qt.QSlider(QtCore.Qt.Horizontal) self.slider.setRange(0, self.player.duration() / 1000) self.labelDuration = Qt.QLabel() self.slider.sliderMoved.connect(self.seek) # create histogram self.labelHistogram = Qt.QLabel() self.labelHistogram.setText('Histogram: ') self.histogram = HistogramWidget() histogramLayout = Qt.QHBoxLayout() histogramLayout.addWidget(self.labelHistogram) histogramLayout.addWidget(self.histogram, 1) # create videoProbe self.videoProbe = Qt.QVideoProbe() self.videoProbe.videoFrameProbed.connect(self.histogram.processFrame) self.videoProbe.setSource(self.player) # add control controls = Controllers() controls.setState(self.player.state()) controls.setVolume(self.player.volume()) controls.setMuted(controls.isMuted()) # connect player's controls with Controllers controls.play.connect(self.player.play) controls.pause.connect(self.player.pause) controls.stop.connect(self.player.stop) controls.next.connect(self.playlist.next) controls.previous.connect(self.previousAction) controls.changeVolume.connect(self.player.setVolume) controls.changeMuting.connect(self.player.setMuted) # setPlaybackRate is from QMediaPlayer controls.changeSpeed.connect(self.player.setPlaybackRate) # controls.stop.connect(self.videoWidget.update) self.player.stateChanged.connect(controls.setState) self.player.volumeChanged.connect(controls.setVolume) self.player.mutedChanged.connect(controls.setMuted) # create fullScreenButton # self.fullScreenButton = Qt.QPushButton('FullScreen') # self.fullScreenButton.setCheckable(True) # displayLayout displayLayout = Qt.QHBoxLayout() # displayLayout.addWidget(self.videoWidget, 2) displayLayout.addWidget(self.playlistView) # controlLayout controlLayout = Qt.QHBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) # connect controlLayout with controls controlLayout.addWidget(controls) controlLayout.addStretch(1) # connect controlLayout with fullScreenButton # controlLayout.addWidget(self.fullScreenButton) # visualize player layout = Qt.QVBoxLayout() layout.addLayout(displayLayout) # layout for sliding song playing hLayout = Qt.QHBoxLayout() hLayout.addWidget(self.slider) hLayout.addWidget(self.labelDuration) layout.addLayout(hLayout) layout.addLayout(controlLayout) layout.addLayout(histogramLayout) # set icon self.setWindowIcon(Qt.QIcon('favicon.ico')) # create menus toolBar = Qt.QToolBar() # create basic actions self.createActions() # create simple button to repeat song self.repeatButton = Qt.QToolButton() self.repeatButton.setDefaultAction(self.repeatAct) # create playOnceButton self.playOnceButton = Qt.QToolButton() self.playOnceButton.setDefaultAction(self.playOnceAct) self.playOnceButton.setEnabled(False) # create shuffleButton self.shuffleButton = Qt.QToolButton() self.shuffleButton.setDefaultAction(self.shuffleAct) # create sequentialButton self.sequentialButton = Qt.QToolButton() self.sequentialButton.setDefaultAction(self.sequentialAct) # create fileButton for fileMenu fileButton = Qt.QToolButton() fileButton.setText('File') fileButton.setPopupMode(Qt.QToolButton.MenuButtonPopup) fileButton.setMenu(self.popFileMenu()) # create editButton for editMenu closeButton = Qt.QToolButton() closeButton.setText('Edit') closeButton.setDefaultAction(self.fileCloseAct) # display in toolBar these buttons toolBar.addWidget(self.repeatButton) toolBar.addWidget(self.playOnceButton) toolBar.addWidget(self.shuffleButton) toolBar.addWidget(self.sequentialButton) toolBar.addWidget(fileButton) toolBar.addWidget(closeButton) # add toolBar to layout of the player layout.addWidget(toolBar) layout.addWidget(Qt.QGroupBox()) self.setWindowTitle("Python Music Player") self.setLayout(layout) if not self.player.isAvailable(): Qt.QMessageBox(self, 'Unavailable service') # self.displayErrorMessage() controls.setEnabled(False) self.playlistView.setEnabled(False) self.fullScreenButton.setEnabled(False) self.metaDataChanged() self.addToPlaylist(playlist) # create fileMenu def popFileMenu(self): aMenu = Qt.QMenu(self) aMenu.addAction(self.fileOpenAct) aMenu.addAction(self.fileCloseAct) return aMenu def createActions(self): self.repeatAct = Qt.QAction('Repeat', self, triggered=self.repeatSong) self.playOnceAct = Qt.QAction( 'Play once', self, triggered=self.playOnceSong) self.shuffleAct = Qt.QAction( 'Shuffle', self, triggered=self.playlist.shuffle) self.sequentialAct = Qt.QAction( 'Sequential', self, triggered=self.playSequential) self.fileOpenAct = Qt.QAction('Open', self, triggered=self.open) self.fileOpenAct.setShortcut('Ctrl+O') self.fileCloseAct = Qt.QAction('Close', self, triggered=self.close) self.fileCloseAct.setShortcut('Ctrl+Q') def repeatSong(self): self.playlist.setPlaybackMode(QMediaPlaylist.CurrentItemInLoop) self.repeatButton.setEnabled(False) self.playOnceButton.setEnabled(True) def playOnceSong(self): self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) self.playOnceButton.setEnabled(False) self.repeatButton.setEnabled(True) # unproperly used def playSequential(self): self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) # get and display song duration def durationChanged(self, duration): duration /= 1000 self.duration = duration self.slider.setMaximum(duration) # change slider position def positionChanged(self, progress): progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) self.updateDurationInfo(progress) def updateDurationInfo(self, currentInfo): duration = self.duration if currentInfo or duration: currentTime = QtCore.QTime( (currentInfo / 3600) % 60, # hours (currentInfo / 60) % 60, # minutes currentInfo % 60, # seconds (currentInfo * 1000) % 1000) # miliseconds totalTime = QtCore.QTime( (duration / 3600) % 60, # hours (duration / 60) % 60, # minutes duration % 60, # seconds (duration * 1000) % 1000) # miliseconds formating = 'hh:mm:ss' if duration > 3600 else 'mm:ss' toString = (currentTime.toString(formating) + ' / ' + totalTime.toString(formating)) else: toString = '' self.labelDuration.setText(toString) def metaDataChanged(self): if self.player.isMetaDataAvailable(): self.setTrackInfo('{0} - {1}'.format( self.player.metaData(Qt.QMediaMetaData.AlbumArtist), self.player.metaData(Qt.QMediaMetaData.Title))) def setTrackInfo(self, info): self.trackInfo = info if self.statusInfo: self.setWindowTitle('{0} | {1}'.format( self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def playlistPositionChanged(self, position): self.playlistView.setCurrentIndex( self.playlistModel.index(position, 0)) def statusChanged(self, status): self.handleCursor(status) if status == QMediaPlayer.LoadingMedia: self.setStatusInfo('Loading...') elif status == QMediaPlayer.StalledMedia: self.setStatusInfo('Media Stalled') elif status == QMediaPlayer.EndOfMedia: Qt.QApplication.alert(self) elif status == QMediaPlayer.InvalidMedia: self.displayErrorMessage() else: self.setStatusInfo('') def handleCursor(self, status): if status in [QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia]: self.setCursor(QtCore.Qt.BusyCursor) else: self.unsetCursor() def setStatusInfo(self, info): self.statusInfo = info if self.statusInfo: self.setWindowTitle('{0} | {1}'.format( self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def bufferingProgress(self, progress): self.setStatusInfo('Buffering {0}'.format(progress)) def displayErrorMessage(self): self.statusInfo(self.player.errorString()) def jump(self, index): if index.isValid(): self.playlist.setCurrentIndex(index.row()) self.player.play() def seek(self, seconds): self.player.setPosition(seconds * 1000) def previousAction(self): self.playlist.previous() def close(self): choice = Qt.QMessageBox.question( self, 'Close', 'Close the app?', Qt.QMessageBox.Yes | Qt.QMessageBox.No) if choice == Qt.QMessageBox.Yes: sys.exit() def open(self): names, _ = Qt.QFileDialog.getOpenFileNames(self, 'Open Files') # ['/home/milka/Documents/MusicPlayer/song.mp3'] self.addToPlaylist(names) def addToPlaylist(self, names): for name in names: fileInfo = Qt.QFileInfo(name) if fileInfo.exists(): url = QtCore.QUrl.fromLocalFile(fileInfo.absoluteFilePath()) # save_to_db song url create_song( url.path(), self.duration, playlist_name=self.name) if fileInfo.suffix().lower() == 'm3u': self.playlist.load(url) else: self.playlist.addMedia(Qt.QMediaContent(url)) else: url = QtCore.QUrl(name) if url.isValid(): self.playlist.addMedia(Qt.QMediaContent(url))
class VideoWindow(QMainWindow): def __init__(self, parent=None): super(VideoWindow, self).__init__(parent) self.first = True # -추가 : 양팡관련 self.YangPang_play = False self.YangPang_first = 0 global player global channel_ls # 제목 표시줄 self.setWindowTitle( "PyQt Video Player Widget Example - pythonprogramminglanguage.com") # 비디오 파일 재생 위젯 생성 self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface) # 비디오 출력 프레임 부분 위젯 생성 self.videoWidget = QVideoWidget() # 플레이버튼 생성 self.playButton = QPushButton() self.playButton.setEnabled(False) self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.playButton.clicked.connect(self.play) # 플레이용 수평 슬라이드 바 생성 self.positionSlider = QSlider(Qt.Horizontal) # 0부터 0까지 칸 수 만큼 슬라이드 할 수 있는 바 생성 self.positionSlider.setRange(0, 0) self.positionSlider.sliderMoved.connect(self.setPosition) # - 추가 : 사운드 이미지 라벨 생성 self.soundImage = QPushButton() self.soundImage.setIcon(self.style().standardIcon( QStyle.SP_MediaVolume)) self.soundImage.clicked.connect(self.sound) # - 추가 : 사운드용 수평 슬라이드 바 생성 self.soundpositionSlider = QSlider(Qt.Horizontal) self.soundpositionSlider.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) # - 추가 : 0부터 100까지 칸 수 만큼 슬라이드 할 수 있는 바 생성 self.soundpositionSlider.setRange(0, 100) self.soundpositionSlider.setValue(100) self.soundpositionSlider.sliderMoved.connect(self.setsoundPosition) # - 추가 : 링크기능 self.textLink = QLineEdit() self.buttonLink = QPushButton() self.textLink.setObjectName("link") self.buttonLink.setText("연결") self.buttonLink.clicked.connect(self.connect_video) self.buttonLink.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) # 에러 Label 생성 self.errorLabel = QLabel() # sizepolicy 정리 # http://mitk.org/images/5/5e/BugSquashingSeminars%2414-04-30-bugsquashing-Qt-Size-Policy.pdf # https://stackoverflow.com/questions/4553304/understanding-form-layout-mechanisms-in-qt self.errorLabel.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) # 메뉴의 open 생성 openAction = QAction(QIcon('open.png'), '&Open', self) openAction.setShortcut('Ctrl+O') openAction.setStatusTip('Open movie') openAction.triggered.connect(self.openFile) # 메뉴의 exit 생성 exitAction = QAction(QIcon('exit.png'), '&Exit', self) exitAction.setShortcut('Ctrl+Q') exitAction.setStatusTip('Exit application') exitAction.triggered.connect(self.exitCall) # - 추가 : 메뉴의 channel add 생성 channelAction = QAction(QIcon('exit.png'), '&Channel', self) channelAction.setStatusTip('Youtube channel set') channelAction.triggered.connect(opensetwin) # 메뉴바 및 메뉴 생성 menuBar = self.menuBar() fileMenu = menuBar.addMenu('&File') # 메뉴 목록 추가 fileMenu.addAction(openAction) fileMenu.addAction(exitAction) # - 추가 fileMenu.addAction(channelAction) # centralwidget 부분 추가 wid = QWidget(self) self.setCentralWidget(wid) # -추가 : 양팡관련 self.YB = QPushButton() self.YB.setText("양팡 Start") self.YB.clicked.connect(self.YangPang) # -추가 : 채널관련 self.CL = QPushButton() self.CL.setText("채널 Start") self.CL.clicked.connect(self.ChannelVideo) # 레이아웃을 생성하고 위에서 만들었던 Widget 수평으로 순서대로 추가 controlLayout = QHBoxLayout() controlLayout.addWidget(self.playButton) controlLayout.addWidget(self.positionSlider) controlLayout.addWidget(self.soundImage) controlLayout.addWidget(self.soundpositionSlider) # 링크 부분 레이아웃 생성 linkLayout = QHBoxLayout() linkLayout.addWidget(self.textLink) linkLayout.addWidget(self.buttonLink) # controlLayout 바깥으로 여백 추가 (left, right, up, down) controlLayout.setContentsMargins(0, 0, 0, 0) # 레이아웃을 생성하고 이 래이아웃들을 수직 순서대로 추가 layout = QVBoxLayout() layout.addWidget(self.videoWidget) layout.addLayout(controlLayout) layout.addLayout(linkLayout) # -추가 : 양팡관련 layout.addWidget(self.YB) # -추가 : 채널관련 layout.addWidget(self.CL) layout.addWidget(self.errorLabel) # CentralWidget에 layout 넣기 wid.setLayout(layout) # 미디어 위젯에 비디오를 출력 시킴 self.mediaPlayer.setVideoOutput(self.videoWidget) # 미디어 위젯의 플레이 상태가 바뀔 때 self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) # 미디어 위젯의 슬라이드바의 위치가 바뀔 때 self.mediaPlayer.positionChanged.connect(self.positionChanged) # 미디어 위젯의 영상이 바뀌어 슬라이드바의 총 나눔이 달라질 때 self.mediaPlayer.durationChanged.connect(self.durationChanged) # 미디어 위젯에 오류가 났을 때 self.mediaPlayer.error.connect(self.handleError) # - 추가 : 영상 1/3 나누어서 5초 앞 / 시작 or 정지 / 5초 뒤 투명한 버튼 생성 hide_layout = QHBoxLayout() self.before = QPushButton() self.state_change = QPushButton() self.after = QPushButton() self.before.setEnabled(False) self.state_change.setEnabled(False) self.after.setEnabled(False) self.before.clicked.connect(self.before_f) self.state_change.clicked.connect(self.play) self.after.clicked.connect(self.after_f) self.before.setShortcut('left') self.state_change.setShortcut('space') self.after.setShortcut('right') hide_layout.addWidget(self.before) hide_layout.addWidget(self.state_change) hide_layout.addWidget(self.after) self.before.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.state_change.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.after.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.videoWidget.setLayout(hide_layout) # 파일 불러오기 def openFile(self): # QFileDialog.getOpenFileName(self, "제목 표시줄", QDir.homePath()) # 이 함수를 통해 filename엔 파일의 전체 경로와 어떤 종류로 가져 왔는지 변수로 줌(ex. All Files (*)) fileName, _ = QFileDialog.getOpenFileName(self, "Open Movie", QDir.homePath()) # -추가 : 양팡관련 self.YangPang_play = False self.YangPang_first = 0 # 위에서 받아온 경로의 파일을 미디어위젯에 대입 if fileName != '': self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile(fileName))) self.playButton.setEnabled(True) self.check_url = False self.best = None # 윈도우 종료 함수 def exitCall(self): sys.exit(app.exec_()) # - 추가 : 링크로 연결 (유튜브 주소만 호환) def connect_video(self): # -추가 : 양팡관련 if not self.YangPang_play: self.textValueB = self.textLink.text() self.YangPang_first = 0 url = self.textValueB if self.textLink.text() != "": self.YangPang_play = False play_link = pafy.new(url) self.best = play_link.getbest() self.mediaPlayer.setMedia(QMediaContent(QUrl(self.best.url))) self.playButton.setEnabled(True) self.check_url = True self.play() # 플레이 버튼 함수 def play(self): # 현재 미디어 위젯의 플레이 상태가 재생중일 때이거나 다른 경우 # PlayingState는 Q미디어플레이어.play() 인듯 = 1 if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() else: self.mediaPlayer.play() # - 추가 : 사운드 버튼 함수 def sound(self): if self.mediaPlayer.isMuted(): self.mediaPlayer.setMuted(False) self.soundImage.setIcon(self.style().standardIcon( QStyle.SP_MediaVolume)) else: self.mediaPlayer.setMuted(True) self.soundImage.setIcon(self.style().standardIcon( QStyle.SP_MediaVolumeMuted)) # 플레이 버튼 모양 변경 def mediaStateChanged(self, state): # 현재 미디어 위젯의 플레이 상태가 재생중일 때이거나 다른 경우 if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) else: self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) # 양팡 및 채널 관련 if self.mediaPlayer.state() == QMediaPlayer.StoppedState: if self.YangPang_play: if self.YangPang_first == 1: self.YangPang() if self.YangPang_first == 2: self.ChannelVideo() # 영상의 진행 정도에 따른 슬라이드 바 위치 변경 def positionChanged(self, position): self.positionSlider.setValue(position) self.position = position # 영상의 길이만큼 슬라이드 바의 최대값 설정 def durationChanged(self, duration): self.positionSlider.setRange(0, duration) # - 추가 : 슬라이드 이동 간격 설정 self.mediaPlayer.setNotifyInterval(1) # - 추가 : 자동으로 영상크기만큼 윈도우 크기 조절 if self.mediaPlayer.metaData("Resolution") != None: if not self.check_url: player.resize(self.mediaPlayer.metaData("Resolution")) else: if self.best != None: x, y = parse("{}x{}", self.best.resolution) player.resize(int(x), int(y)) # - 추가 : 영상 추가시 버튼 활성화 self.before.setEnabled(True) self.state_change.setEnabled(True) self.after.setEnabled(True) # - 추가 : 5초 앞 def before_f(self): self.mediaPlayer.setPosition(self.position - 5000) # - 추가 : 5초 뒤 def after_f(self): self.mediaPlayer.setPosition(self.position + 5000) # 영상 슬라이드 바 위치에 따른 화면 변경 def setPosition(self, position): self.mediaPlayer.setPosition(position) # 에러 출력 함수 def handleError(self): self.playButton.setEnabled(False) self.before.setEnabled(False) self.state_change.setEnabled(False) self.after.setEnabled(False) self.errorLabel.setText("Error: " + self.mediaPlayer.errorString()) # - 추가 : 사운드 슬라이드 바 위치에 따른 소리 크기 변경 def setsoundPosition(self, position): self.mediaPlayer.setVolume(position) # - 추가 : 양팡 유튜브 영상 재생 def YangPang(self): # if not self.YangPang_play: if self.YangPang_first == 1: web_url = self.textValueB with urllib.request.urlopen(web_url) as response: html = response.read() soup = BeautifulSoup(html, 'html.parser') yp_find = soup.find_all("a") self.yp_find_list = list() for info in yp_find: if parse("/watch?v={}", info["href"]) != None: if str(info.find("span", {"class": "stat attribution" })).count('양팡 YangPang') > 0: self.yp_find_list.append(info["href"]) break if self.YangPang_first != 1: web_url = "https://www.youtube.com/channel/UCMVC92EOs9yDJG5JS-CMesQ/videos" self.YangPang_first = 1 with urllib.request.urlopen(web_url) as response: html = response.read() soup = BeautifulSoup(html, 'html.parser') yp_find = soup.find_all("a") self.yp_find_list = list() for info in yp_find: if parse("/watch?v={}", info["href"]) != None: self.yp_find_list.append(info["href"]) self.YangPang_play = True cnt = len(self.yp_find_list) - 1 self.textValueB = "https://www.youtube.com/" + str( self.yp_find_list[random.randint(0, cnt)]) self.setWindowTitle("양팡플레이어") self.connect_video() def ChannelVideo(self): if self.YangPang_first == 2: web_url = self.textValueB with urllib.request.urlopen(web_url) as response: html = response.read() soup = BeautifulSoup(html, 'html.parser') yp_find = soup.find_all("a") self.yp_find_list = list() for info in yp_find: if parse("/watch?v={}", info["href"]) != None: if str(info.find("span", {"class": "stat attribution" })).count(self.name) > 0: self.yp_find_list.append(info["href"]) break k = random.randint(0, 1) if k == 0: self.YangPang_first = 0 if self.YangPang_first != 2: web_url = channel_ls[random.randint(0, len(channel_ls) - 1)] self.YangPang_first = 2 with urllib.request.urlopen(web_url) as response: html = response.read() soup = BeautifulSoup(html, 'html.parser') yp_find = soup.find_all("a") self.name_ls = soup.find_all("img") for info in self.name_ls: if info["alt"] != None: self.name = info["alt"] break self.yp_find_list = list() for info in yp_find: if parse("/watch?v={}", info["href"]) != None: self.yp_find_list.append(info["href"]) self.YangPang_play = True cnt = len(self.yp_find_list) - 1 self.textValueB = "https://www.youtube.com/" + str( self.yp_find_list[random.randint(0, cnt)]) self.setWindowTitle("랜덤플레이어") self.connect_video()
class VideoPlayer(QWidget): def __init__(self, aPath, parent=None): super(VideoPlayer, self).__init__(parent) self.setAttribute(Qt.WA_NoSystemBackground, True) self.setAcceptDrops(True) self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.StreamPlayback) self.mediaPlayer.mediaStatusChanged.connect(self.printMediaData) self.mediaPlayer.setVolume(80) self.videoWidget = QVideoWidget(self) self.lbl = QLineEdit('00:00:00') self.lbl.setReadOnly(True) self.lbl.setFixedWidth(70) self.lbl.setUpdatesEnabled(True) self.lbl.setStyleSheet(stylesheet(self)) self.elbl = QLineEdit('00:00:00') self.elbl.setReadOnly(True) self.elbl.setFixedWidth(70) self.elbl.setUpdatesEnabled(True) self.elbl.setStyleSheet(stylesheet(self)) self.playButton = QPushButton() self.playButton.setEnabled(False) self.playButton.setFixedWidth(32) self.playButton.setStyleSheet("background-color: black") self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.playButton.clicked.connect(self.play) self.positionSlider = QSlider(Qt.Horizontal, self) self.positionSlider.setStyleSheet(stylesheet(self)) self.positionSlider.setRange(0, 100) self.positionSlider.sliderMoved.connect(self.setPosition) self.positionSlider.sliderMoved.connect(self.handleLabel) self.positionSlider.setSingleStep(2) self.positionSlider.setPageStep(20) self.positionSlider.setAttribute(Qt.WA_TranslucentBackground, True) self.clip = QApplication.clipboard() self.process = QProcess(self) self.process.readyRead.connect(self.dataReady) # self.process.started.connect(lambda: print("grabbing YouTube URL")) self.process.finished.connect(self.playFromURL) self.myurl = "" controlLayout = QHBoxLayout() controlLayout.setContentsMargins(5, 0, 5, 0) controlLayout.addWidget(self.playButton) controlLayout.addWidget(self.lbl) controlLayout.addWidget(self.positionSlider) controlLayout.addWidget(self.elbl) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.videoWidget) layout.addLayout(controlLayout) self.setLayout(layout) self.myinfo = "©2016\nAxel Schneider\n\nMouse Wheel = Zoom\nUP = Volume Up\nDOWN = Volume Down\n" + \ "LEFT = < 1 Minute\nRIGHT = > 1 Minute\n" + \ "SHIFT+LEFT = < 10 Minutes\nSHIFT+RIGHT = > 10 Minutes" self.widescreen = True #### shortcuts #### self.shortcut = QShortcut(QKeySequence("q"), self) self.shortcut.activated.connect(self.handleQuit) self.shortcut = QShortcut(QKeySequence("u"), self) self.shortcut.activated.connect(self.playFromURL) self.shortcut = QShortcut(QKeySequence("y"), self) self.shortcut.activated.connect(self.getYTUrl) self.shortcut = QShortcut(QKeySequence("o"), self) self.shortcut.activated.connect(self.openFile) self.shortcut = QShortcut(QKeySequence(" "), self) self.shortcut.activated.connect(self.play) self.shortcut = QShortcut(QKeySequence("f"), self) self.shortcut.activated.connect(self.handleFullscreen) self.shortcut = QShortcut(QKeySequence("i"), self) self.shortcut.activated.connect(self.handleInfo) self.shortcut = QShortcut(QKeySequence("s"), self) self.shortcut.activated.connect(self.toggleSlider) self.shortcut = QShortcut(QKeySequence(Qt.Key_Right), self) self.shortcut.activated.connect(self.forwardSlider) self.shortcut = QShortcut(QKeySequence(Qt.Key_Left), self) self.shortcut.activated.connect(self.backSlider) self.shortcut = QShortcut(QKeySequence(Qt.Key_Up), self) self.shortcut.activated.connect(self.volumeUp) self.shortcut = QShortcut(QKeySequence(Qt.Key_Down), self) self.shortcut.activated.connect(self.volumeDown) self.shortcut = QShortcut( QKeySequence(Qt.ShiftModifier + Qt.Key_Right), self) self.shortcut.activated.connect(self.forwardSlider10) self.shortcut = QShortcut(QKeySequence(Qt.ShiftModifier + Qt.Key_Left), self) self.shortcut.activated.connect(self.backSlider10) self.mediaPlayer.setVideoOutput(self.videoWidget) self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.positionChanged.connect(self.handleLabel) self.mediaPlayer.durationChanged.connect(self.durationChanged) self.mediaPlayer.error.connect(self.handleError) print("QT5 Player started") self.suspend_screensaver() # msg = QMessageBox.information(self, "Qt5Player", "press o to open file") self.loadFilm("/home/brian/Dokumente/Qt5PlayerIntro.m4v") def playFromURL(self): self.mediaPlayer.pause() self.myurl = self.clip.text() self.mediaPlayer.setMedia(QMediaContent(QUrl(self.myurl))) self.playButton.setEnabled(True) self.mediaPlayer.play() self.hideSlider() print(self.myurl) def getYTUrl(self): cmd = "youtube-dl -g -f best " + self.clip.text() print("grabbing YouTube URL") self.process.start(cmd) def dataReady(self): self.myurl = str(self.process.readAll(), encoding='utf8').rstrip() ### self.myurl = self.myurl.partition("\n")[0] print(self.myurl) self.clip.setText(self.myurl) self.playFromURL() def suspend_screensaver(self): 'suspend linux screensaver' proc = subprocess.Popen( 'gsettings set org.gnome.desktop.screensaver idle-activation-enabled false', shell=True) proc.wait() def resume_screensaver(self): 'resume linux screensaver' proc = subprocess.Popen( 'gsettings set org.gnome.desktop.screensaver idle-activation-enabled true', shell=True) proc.wait() def openFile(self): fileName, _ = QFileDialog.getOpenFileName( self, "Open Movie", QDir.homePath() + "/Videos", "Media (*.webm *.mp4 *.ts *.avi *.mpeg *.mpg *.mkv *.VOB *.m4v *.3gp *.mp3 *.m4a *.wav *.ogg *.flac *.m3u *.m3u8)" ) if fileName != '': self.loadFilm(fileName) print("File loaded") def play(self): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() else: self.mediaPlayer.play() def mediaStateChanged(self, state): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) else: self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) def positionChanged(self, position): self.positionSlider.setValue(position) def durationChanged(self, duration): self.positionSlider.setRange(0, duration) mtime = QTime(0, 0, 0, 0) mtime = mtime.addMSecs(self.mediaPlayer.duration()) self.elbl.setText(mtime.toString()) def setPosition(self, position): self.mediaPlayer.setPosition(position) def handleError(self): self.playButton.setEnabled(False) print("Error: ", self.mediaPlayer.errorString()) def handleQuit(self): self.mediaPlayer.stop() self.resume_screensaver() print("Goodbye ...") app.quit() def contextMenuRequested(self, point): menu = QMenu() actionFile = menu.addAction(QIcon.fromTheme("video-x-generic"), "open File (o)") actionclipboard = menu.addSeparator() actionURL = menu.addAction(QIcon.fromTheme("browser"), "URL from Clipboard (u)") actionclipboard = menu.addSeparator() actionYTurl = menu.addAction(QIcon.fromTheme("youtube"), "URL from YouTube (y)") actionclipboard = menu.addSeparator() actionToggle = menu.addAction(QIcon.fromTheme("next"), "show / hide Slider (s)") actionFull = menu.addAction(QIcon.fromTheme("view-fullscreen"), "Fullscreen (f)") action169 = menu.addAction(QIcon.fromTheme("tv-symbolic"), "16 : 9") action43 = menu.addAction(QIcon.fromTheme("tv-symbolic"), "4 : 3") actionSep = menu.addSeparator() actionInfo = menu.addAction(QIcon.fromTheme("help-about"), "Info (i)") action5 = menu.addSeparator() actionQuit = menu.addAction(QIcon.fromTheme("application-exit"), "Exit (q)") actionFile.triggered.connect(self.openFile) actionQuit.triggered.connect(self.handleQuit) actionFull.triggered.connect(self.handleFullscreen) actionInfo.triggered.connect(self.handleInfo) actionToggle.triggered.connect(self.toggleSlider) actionURL.triggered.connect(self.playFromURL) actionYTurl.triggered.connect(self.getYTUrl) action169.triggered.connect(self.screen169) action43.triggered.connect(self.screen43) menu.exec_(self.mapToGlobal(point)) def wheelEvent(self, event): mwidth = self.frameGeometry().width() mheight = self.frameGeometry().height() mleft = self.frameGeometry().left() mtop = self.frameGeometry().top() mscale = event.angleDelta().y() / 5 if self.widescreen == True: self.setGeometry(mleft, mtop, mwidth + mscale, (mwidth + mscale) / 1.778) else: self.setGeometry(mleft, mtop, mwidth + mscale, (mwidth + mscale) / 1.33) def screen169(self): self.widescreen = True mwidth = self.frameGeometry().width() mheight = self.frameGeometry().height() mleft = self.frameGeometry().left() mtop = self.frameGeometry().top() mratio = 1.778 self.setGeometry(mleft, mtop, mwidth, mwidth / mratio) def screen43(self): self.widescreen = False mwidth = self.frameGeometry().width() mheight = self.frameGeometry().height() mleft = self.frameGeometry().left() mtop = self.frameGeometry().top() mratio = 1.33 self.setGeometry(mleft, mtop, mwidth, mwidth / mratio) def handleFullscreen(self): if self.windowState() & Qt.WindowFullScreen: QApplication.setOverrideCursor(Qt.ArrowCursor) self.showNormal() print("no Fullscreen") else: self.showFullScreen() QApplication.setOverrideCursor(Qt.BlankCursor) print("Fullscreen entered") def handleInfo(self): msg = QMessageBox.about(self, "QT5 Player", self.myinfo) def toggleSlider(self): if self.positionSlider.isVisible(): self.hideSlider() else: self.showSlider() def hideSlider(self): self.playButton.hide() self.lbl.hide() self.positionSlider.hide() self.elbl.hide() mwidth = self.frameGeometry().width() mheight = self.frameGeometry().height() mleft = self.frameGeometry().left() mtop = self.frameGeometry().top() if self.widescreen == True: self.setGeometry(mleft, mtop, mwidth, mwidth / 1.778) else: self.setGeometry(mleft, mtop, mwidth, mwidth / 1.33) def showSlider(self): self.playButton.show() self.lbl.show() self.positionSlider.show() self.elbl.show() mwidth = self.frameGeometry().width() mheight = self.frameGeometry().height() mleft = self.frameGeometry().left() mtop = self.frameGeometry().top() if self.widescreen == True: self.setGeometry(mleft, mtop, mwidth, mwidth / 1.55) else: self.setGeometry(mleft, mtop, mwidth, mwidth / 1.33) def forwardSlider(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() + 1000 * 60) def forwardSlider10(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() + 10000 * 60) def backSlider(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() - 1000 * 60) def backSlider10(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() - 10000 * 60) def volumeUp(self): self.mediaPlayer.setVolume(self.mediaPlayer.volume() + 10) print("Volume: " + str(self.mediaPlayer.volume())) def volumeDown(self): self.mediaPlayer.setVolume(self.mediaPlayer.volume() - 10) print("Volume: " + str(self.mediaPlayer.volume())) def mouseMoveEvent(self, event): if event.buttons() == Qt.LeftButton: self.move(event.globalPos() \ - QPoint(self.frameGeometry().width() / 2, \ self.frameGeometry().height() / 2)) event.accept() def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.accept() elif event.mimeData().hasText(): event.accept() else: event.ignore() def dropEvent(self, event): if event.mimeData().hasUrls(): url = event.mimeData().urls()[0].toString() print("url = ", url) self.mediaPlayer.stop() self.mediaPlayer.setMedia(QMediaContent(QUrl(url))) self.playButton.setEnabled(True) self.mediaPlayer.play() elif event.mimeData().hasText(): mydrop = event.mimeData().text() ### YouTube url if "youtube" in mydrop: print("is YouTube", mydrop) self.clip.setText(mydrop) self.getYTUrl() else: ### normal url print("generic url = ", mydrop) self.mediaPlayer.setMedia(QMediaContent(QUrl(mydrop))) self.playButton.setEnabled(True) self.mediaPlayer.play() self.hideSlider() def loadFilm(self, f): self.mediaPlayer.setMedia(QMediaContent(QUrl.fromLocalFile(f))) self.playButton.setEnabled(True) self.mediaPlayer.play() def printMediaData(self): if self.mediaPlayer.mediaStatus() == 6: if self.mediaPlayer.isMetaDataAvailable(): res = str(self.mediaPlayer.metaData("Resolution")).partition( "PyQt5.QtCore.QSize(")[2].replace(", ", " x ").replace(")", "") print("%s%s" % ("Video Resolution = ", res)) else: print("no metaData available") def openFileAtStart(self, filelist): matching = [s for s in filelist if ".myformat" in s] if len(matching) > 0: self.loadFilm(matching) ##################### update Label ################################## def handleLabel(self): self.lbl.clear() mtime = QTime(0, 0, 0, 0) self.time = mtime.addMSecs(self.mediaPlayer.position()) self.lbl.setText(self.time.toString())
class CanvasVideo(QWidget): """Canvas for comparing videos. Now only support comparing two videos """ def __init__(self, parent): super(CanvasVideo, self).__init__() self.parent = parent self.info_text = [] self.flag_show_info = True # initialize widgets and layout self.init_widgets_layout() self.qview_bg_color = 'white' self.show_fingerprint = False # for auto zoom ratio self.target_zoom_width = 0 def init_player(self): if not hasattr(self, 'player1'): # the first video player self.videoitem1 = QGraphicsVideoItem() self.player1 = QMediaPlayer(self) self.player1.setVideoOutput(self.videoitem1) # the second video player self.videoitem2 = QGraphicsVideoItem() self.player2 = QMediaPlayer(self) self.player2.setVideoOutput(self.videoitem2) # signal-slot self.player1.stateChanged.connect(self.mediaStateChanged) self.player1.positionChanged.connect(self.positionChanged) self.player1.durationChanged.connect(self.durationChanged) # add to scene self.scene_text = self.qscenes[0].addText('') self.qscenes[0].addItem(self.videoitem1) self.qscenes[0].addItem(self.videoitem2) self.flag_front_player = '1' self.pause_pos = 0 def open_files(self): # init players self.init_player() # open the first video file self.video_file = self._open_one_file() self.player1.setMedia( QMediaContent(QUrl.fromLocalFile(self.video_file))) height, width = self.videoitem1.size().height(), self.videoitem1.size( ).width() self.qscenes[0].set_width_height(width, height) # put video always in the center of a QGraphicsView self.qscenes[0].setSceneRect(0, 0, width, height) # open the second video file self.video_file2 = self._open_one_file() self.player2.setMedia( QMediaContent(QUrl.fromLocalFile(self.video_file2))) self.playButton.setEnabled(True) self.syncButton.setEnabled(True) self.clearButton.setEnabled(True) self.infoButton.setEnabled(True) self.show_video(init=True) self.show_video_info() def _open_one_file(self): # get open file name try: with open(os.path.join(ROOT_DIR, 'history.txt'), 'r') as f: history = f.readlines()[0] history = history.strip() except Exception: history = '.' key, ok = QFileDialog.getOpenFileName(self, 'Open Video', history) if ok: # save history try: with open(os.path.join(ROOT_DIR, 'history.txt'), 'r') as f: lines = f.readlines() lines = [line.strip() for line in lines] if len(lines) == 5: del lines[-1] except Exception: lines = [] # add the new record to the first line if key not in lines: lines.insert(0, key) with open(os.path.join(ROOT_DIR, 'history.txt'), 'w') as f: for line in lines: f.write(f'{line}\n') return key def init_widgets_layout(self): # QGraphicsView - QGraphicsScene - QPixmap self.qscenes = [] self.qviews = [] show_info = False self.qscenes.append(HVScene(self, show_info=show_info)) self.qviews.append(HVView(self.qscenes[0], self, show_info=show_info)) # --------------------------------------- # layouts # --------------------------------------- main_layout = QGridLayout(self) # QGridLayout: # int row, int column, int rowSpan, int columnSpan main_layout.addWidget(self.qviews[0], 0, 0, -1, 50) self.infoButton = QPushButton() self.infoButton.setEnabled(False) self.infoButton.setFixedSize(QSize(80, 80)) self.infoButton.setIcon(self.style().standardIcon( QStyle.SP_MessageBoxInformation)) self.infoButton.clicked.connect(self.show_video_info) self.clearButton = QPushButton() self.clearButton.setEnabled(False) self.clearButton.setFixedSize(QSize(80, 80)) self.clearButton.setIcon(self.style().standardIcon( QStyle.SP_TrashIcon)) self.clearButton.clicked.connect(self.clear_players) self.syncButton = QPushButton() self.syncButton.setEnabled(False) self.syncButton.setFixedSize(QSize(80, 80)) self.syncButton.setIcon(self.style().standardIcon( QStyle.SP_BrowserReload)) self.syncButton.clicked.connect(self.sync_two_players) self.playButton = QPushButton() self.playButton.setFixedSize(QSize(80, 80)) self.playButton.setEnabled(False) self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.playButton.clicked.connect(self.play) self.positionSlider = QSlider(Qt.Horizontal) self.positionSlider.setRange(0, 0) self.positionSlider.sliderMoved.connect(self.setPosition) # Create layouts to place inside widget controlLayout = QHBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) controlLayout.addWidget(self.playButton) controlLayout.addWidget(self.syncButton) controlLayout.addWidget(self.positionSlider) # controlLayout2 = QVBoxLayout() # controlLayout2.setContentsMargins(0, 0, 0, 0) # controlLayout2.addWidget(self.playButton) # controlLayout2.addLayout(controlLayout) main_layout.addWidget(self.clearButton, 59, 0, 1, 1) main_layout.addWidget(self.infoButton, 58, 0, 1, 1) main_layout.addLayout(controlLayout, 60, 0, -1, 49) self.qviews[0].set_shown_text( ['Click Open to open ·two· videos for comparison!']) def keyPressEvent(self, event): modifiers = QApplication.keyboardModifiers() if event.key() == QtCore.Qt.Key_F9: self.toggle_bg_color() elif event.key() == QtCore.Qt.Key_R: for qview in self.qviews: qview.set_zoom(1) elif event.key() == QtCore.Qt.Key_C: print('Enter C') self.compare_folders(1) elif event.key() == QtCore.Qt.Key_V: self.compare_folders(-1) elif event.key() == QtCore.Qt.Key_Space: if modifiers == QtCore.Qt.ShiftModifier: self.dir_browse(10) else: self.dir_browse(1) elif event.key() == QtCore.Qt.Key_Backspace: if modifiers == QtCore.Qt.ShiftModifier: self.dir_browse(-10) else: self.dir_browse(-1) elif event.key() == QtCore.Qt.Key_Right: if modifiers == QtCore.Qt.ShiftModifier: self.dir_browse(10) else: self.dir_browse(1) elif event.key() == QtCore.Qt.Key_Left: if modifiers == QtCore.Qt.ShiftModifier: self.dir_browse(-10) else: self.dir_browse(-1) elif event.key() == QtCore.Qt.Key_Up: if modifiers == QtCore.Qt.ShiftModifier: scale = 1.2 else: scale = 1.05 for qview in self.qviews: qview.zoom_in(scale=scale) elif event.key() == QtCore.Qt.Key_Down: if modifiers == QtCore.Qt.ShiftModifier: scale = 1.2 else: scale = 1.05 for qview in self.qviews: qview.zoom_out(scale=scale) elif event.key() == QtCore.Qt.Key_F11: self.parent.switch_fullscreen() def show_video_info(self): if self.flag_show_info is True: # if not self.info_text: if self.player1.metaData('Resolution') is not None: resolution_str1 = ( f"Resolution : {self.player1.metaData('Resolution').width()} " f"x {self.player1.metaData('Resolution').height()}") else: resolution_str1 = ('Resolution : None') if self.player2.metaData('Resolution') is not None: resolution_str2 = ( f"Resolution : {self.player2.metaData('Resolution').width()} " f"x {self.player2.metaData('Resolution').height()}") else: resolution_str2 = ('Resolution : None') self.info_text = [ f'Title : {os.path.basename(self.video_file)}', resolution_str1, f"Duration : {str(self.player1.metaData('Duration'))}", f"FrameRate : {str(self.player1.metaData('VideoFrameRate'))}", f"BitRate : {str(self.player1.metaData('VideoBitRate'))}", f"Video Codec: {str(self.player1.metaData('VideoCodec'))}", '', f'Title : {os.path.basename(self.video_file2)}', resolution_str2, f"Duration : {str(self.player2.metaData('Duration'))}", f"FrameRate : {str(self.player2.metaData('VideoFrameRate'))}", f"BitRate : {str(self.player2.metaData('VideoBitRate'))}", f"Video Codec: {str(self.player2.metaData('VideoCodec'))}", ] self.qviews[0].set_shown_text(self.info_text) self.flag_show_info = False else: self.qviews[0].set_shown_text( ['Click InfoButtion to show video information']) self.flag_show_info = True self.qscenes[0].update() # update the shown text def clear_players(self): if hasattr(self, 'player1'): self.player1.stop() self.player2.stop() self.qscenes[0].clear() del self.videoitem1 del self.videoitem2 del self.player1 del self.player2 gc.collect() self.playButton.setEnabled(False) self.syncButton.setEnabled(False) self.clearButton.setEnabled(False) self.infoButton.setEnabled(False) self.positionSlider.setRange(0, 0) # clear the shown text self.qviews[0].set_shown_text([]) self.qscenes[0].update() def sync_two_players(self): position = self.player1.position() self.player1.setPosition(position) self.player2.setPosition(position) def play(self): """only control player 1""" if self.player1.state() == QMediaPlayer.PlayingState: self.pause_pos = self.player1.position() print(self.pause_pos, self.player1.duration(), self.player2.duration()) self.player1.pause() self.player2.pause() else: self.player1.play() self.player2.play() def positionChanged(self, position): self.positionSlider.setValue(position) def durationChanged(self, duration): self.positionSlider.setRange(0, duration) def setPosition(self, position): self.player1.setPosition(position) self.player2.setPosition(position) def mediaStateChanged(self, state): if self.player1.state() == QMediaPlayer.PlayingState: self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPause)) else: self.playButton.setIcon(self.style().standardIcon( QStyle.SP_MediaPlay)) def compare_folders(self, step): self.show_video() def show_video(self, init=False): if hasattr(self, 'player1'): if self.flag_front_player == '1': self.scene_text.setPlainText(os.path.basename(self.video_file)) self.flag_front_player = '2' self.qscenes[0].setFocusItem(self.videoitem2) self.videoitem2.stackBefore(self.videoitem1) # refresh frame in pause state # TODO: still have problem if self.player1.state() != QMediaPlayer.PlayingState: self.setPosition(self.pause_pos) else: self.scene_text.setPlainText(os.path.basename( self.video_file2)) self.flag_front_player = '1' self.qscenes[0].setFocusItem(self.videoitem1) self.videoitem1.stackBefore(self.videoitem2) # refresh frame in pause state # TODO: still have problem if self.player1.state() != QMediaPlayer.PlayingState: self.setPosition(self.pause_pos) self.qviews[0].set_transform() def dir_browse(self, step): self.show_video() def toggle_bg_color(self): if self.qview_bg_color == 'white': self.qview_bg_color = 'lightgray' for qscene in self.qscenes: qscene.setBackgroundBrush(QColor(211, 211, 211)) else: self.qview_bg_color = 'white' for qscene in self.qscenes: qscene.setBackgroundBrush(QtCore.Qt.white)
class Player(QWidget): fullScreenChanged = pyqtSignal(bool) def __init__(self, playlist, parent=None): super(Player, self).__init__(parent) self.setStyleSheet(stylesheet(self)) self.colorDialog = None self.trackInfo = "" self.statusInfo = "" self.duration = 0 self.url = "" self. settings = QSettings("QAudioPlayer", "QAudioPlayer") self.player = QMediaPlayer() self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.metaDataChanged.connect(self.metaDataChanged) self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) self.player.mediaStatusChanged.connect(self.statusChanged) self.player.bufferStatusChanged.connect(self.bufferingProgress) self.player.error.connect(self.displayErrorMessage) self.playlistModel = PlaylistModel() self.playlistModel.setPlaylist(self.playlist) self.playlistView = QListView() # self.playlistView.setSpacing(1) self.playlistView.setStyleSheet(stylesheet(self)) self.playlistView.setModel(self.playlistModel) self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) self.playlistView.activated.connect(self.jump) self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, self.player.duration() / 1000) self.slider.setStyleSheet(stylesheet(self)) self.labelDuration = QLabel() self.slider.sliderMoved.connect(self.seek) openButton = QPushButton("Open", clicked=self.open) openButton.setIcon(openButton.style().standardIcon(QStyle.SP_DialogOpenButton)) clearButton = QPushButton("", clicked=self.clearList) clearButton.setFixedWidth(36) clearButton.setIcon(QIcon.fromTheme("edit-delete")) clearButton.setToolTip("clear List") controls = PlayerControls() controls.setState(self.player.state()) controls.setVolume(self.player.volume()) controls.setMuted(controls.isMuted()) controls.play.connect(self.player.play) controls.pause.connect(self.player.pause) controls.stop.connect(self.player.stop) controls.next.connect(self.playlist.next) controls.previous.connect(self.previousClicked) controls.changeVolume.connect(self.player.setVolume) controls.changeMuting.connect(self.player.setMuted) controls.changeRate.connect(self.player.setPlaybackRate) self.player.stateChanged.connect(controls.setState) self.player.volumeChanged.connect(controls.setVolume) self.player.mutedChanged.connect(controls.setMuted) displayLayout = QHBoxLayout() displayLayout.addWidget(self.playlistView) controlLayout = QHBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) controlLayout.addWidget(openButton) controlLayout.addWidget(clearButton) # controlLayout.addStretch(1) controlLayout.addWidget(controls) # controlLayout.addStretch(1) layout = QVBoxLayout() layout.addLayout(displayLayout) hLayout = QHBoxLayout() hLayout.addWidget(self.slider) hLayout.addWidget(self.labelDuration) layout.addLayout(hLayout) layout.addLayout(controlLayout) self.statusBar = QStatusBar() vlayout = QVBoxLayout() vlayout.addWidget(self.statusBar) layout.addLayout(vlayout) self.statusBar.showMessage("Welcome") self.setWindowTitle("QAudioPlayer") self.setMinimumSize(300, 200) # self.setBackgroundRole(QPalette.Window) self.setContentsMargins(0,0,0,0) self.setLayout(layout) self.readSettings() if not self.player.isAvailable(): QMessageBox.warning(self, "Service not available", "The QMediaPlayer object does not have a valid service.\n" "Please check the media service plugins are installed.") controls.setEnabled(False) self.playlistView.setEnabled(False) openButton.setEnabled(False) self.colorButton.setEnabled(False) self.fullScreenButton.setEnabled(False) self.metaDataChanged() self.addToPlaylist(playlist) def readSettings(self): if self.settings.contains("url"): self.url = self.settings.value("url") self.addToPlaylist(self.url) def writeSettings(self): self.settings.setValue("url", self.url) def closeEvent(self, event): print("writing settings") self.writeSettings() print("goodbye ...") event.accept() def open(self): fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files", "/home", "Audio Files *.mp3 *.m4a *.ogg *.wav *.m3u") if fileNames: self.url = fileNames self.addToPlaylist(fileNames) print("added Files to playlist") def openOnStart(self, name): fileInfo = QFileInfo(name) if fileInfo.exists(): url = QUrl.fromLocalFile(fileInfo.absoluteFilePath()) if fileInfo.suffix().lower() == 'm3u': self.playlist.load(url) else: self.playlist.addMedia(QMediaContent(url)) else: url = QUrl(name) if url.isValid(): self.playlist.addMedia(QMediaContent(url)) print("added Files to playlist") def clearList(self): self.playlist.clear() def addToPlaylist(self, fileNames): for name in fileNames: fileInfo = QFileInfo(name) if fileInfo.exists(): url = QUrl.fromLocalFile(fileInfo.absoluteFilePath()) if fileInfo.suffix().lower() == 'm3u': self.playlist.load(url) else: self.playlist.addMedia(QMediaContent(url)) else: url = QUrl(name) if url.isValid(): self.playlist.addMedia(QMediaContent(url)) def durationChanged(self, duration): duration /= 1000 self.duration = duration self.slider.setMaximum(duration) def positionChanged(self, progress): progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) self.updateDurationInfo(progress) def metaDataChanged(self): if self.player.isMetaDataAvailable(): self.setTrackInfo("%s - %s" % ( self.player.metaData(QMediaMetaData.AlbumArtist), self.player.metaData(QMediaMetaData.Title))) def previousClicked(self): # Go to the previous track if we are within the first 5 seconds of # playback. Otherwise, seek to the beginning. if self.player.position() <= 5000: self.playlist.previous() else: self.player.setPosition(0) def jump(self, index): if index.isValid(): self.playlist.setCurrentIndex(index.row()) self.player.play() def playlistPositionChanged(self, position): self.playlistView.setCurrentIndex( self.playlistModel.index(position, 0)) def seek(self, seconds): self.player.setPosition(seconds * 1000) def statusChanged(self, status): self.handleCursor(status) if status == QMediaPlayer.LoadingMedia: self.setStatusInfo("Loading...") elif status == QMediaPlayer.StalledMedia: self.setStatusInfo("Media Stalled") elif status == QMediaPlayer.EndOfMedia: QApplication.alert(self) elif status == QMediaPlayer.InvalidMedia: self.displayErrorMessage() else: self.setStatusInfo("") def handleCursor(self, status): if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia): self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def bufferingProgress(self, progress): self.setStatusInfo("Buffering %d%" % progress) def setTrackInfo(self, info): self.trackInfo = info if self.statusInfo != "": self.statusBar.showMessage("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.statusBar.showMessage(self.trackInfo) def setStatusInfo(self, info): self.statusInfo = info if self.statusInfo != "": self.statusBar.showMessage("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.statusBar.showMessage(self.trackInfo) def displayErrorMessage(self): self.setStatusInfo(self.player.errorString()) def updateDurationInfo(self, currentInfo): duration = self.duration if currentInfo or duration: currentTime = QTime((currentInfo/3600)%60, (currentInfo/60)%60, currentInfo%60, (currentInfo*1000)%1000) totalTime = QTime((duration/3600)%60, (duration/60)%60, duration%60, (duration*1000)%1000); format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' tStr = currentTime.toString(format) + " / " + totalTime.toString(format) else: tStr = "" self.labelDuration.setText(tStr)
class Player(QWidget): fullScreenChanged = pyqtSignal(bool) def __init__(self, playlist, parent=None): super(Player, self).__init__(parent) self.colorDialog = None self.trackInfo = "" self.statusInfo = "" self.duration = 0 self.player = QMediaPlayer() self.playlist = QMediaPlaylist() self.player.setPlaylist(self.playlist) self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.metaDataChanged.connect(self.metaDataChanged) self.playlist.currentIndexChanged.connect(self.playlistPositionChanged) self.player.mediaStatusChanged.connect(self.statusChanged) self.player.bufferStatusChanged.connect(self.bufferingProgress) self.player.videoAvailableChanged.connect(self.videoAvailableChanged) self.player.error.connect(self.displayErrorMessage) self.videoWidget = VideoWidget() self.player.setVideoOutput(self.videoWidget) self.playlistModel = PlaylistModel() self.playlistModel.setPlaylist(self.playlist) self.playlistView = QListView() self.playlistView.setModel(self.playlistModel) self.playlistView.setCurrentIndex( self.playlistModel.index(self.playlist.currentIndex(), 0)) self.playlistView.activated.connect(self.jump) self.slider = QSlider(Qt.Horizontal) self.slider.setRange(0, self.player.duration() / 1000) self.labelDuration = QLabel() self.slider.sliderMoved.connect(self.seek) self.labelHistogram = QLabel() self.labelHistogram.setText("Histogram:") self.histogram = HistogramWidget() histogramLayout = QHBoxLayout() histogramLayout.addWidget(self.labelHistogram) histogramLayout.addWidget(self.histogram, 1) self.probe = QVideoProbe() self.probe.videoFrameProbed.connect(self.histogram.processFrame) self.probe.setSource(self.player) openButton = QPushButton("Open", clicked=self.open) controls = PlayerControls() controls.setState(self.player.state()) controls.setVolume(self.player.volume()) controls.setMuted(controls.isMuted()) controls.play.connect(self.player.play) controls.pause.connect(self.player.pause) controls.stop.connect(self.player.stop) controls.next.connect(self.playlist.next) controls.previous.connect(self.previousClicked) controls.changeVolume.connect(self.player.setVolume) controls.changeMuting.connect(self.player.setMuted) controls.changeRate.connect(self.player.setPlaybackRate) controls.stop.connect(self.videoWidget.update) self.player.stateChanged.connect(controls.setState) self.player.volumeChanged.connect(controls.setVolume) self.player.mutedChanged.connect(controls.setMuted) self.fullScreenButton = QPushButton("FullScreen") self.fullScreenButton.setCheckable(True) self.colorButton = QPushButton("Color Options...") self.colorButton.setEnabled(False) self.colorButton.clicked.connect(self.showColorDialog) displayLayout = QHBoxLayout() displayLayout.addWidget(self.videoWidget, 2) displayLayout.addWidget(self.playlistView) controlLayout = QHBoxLayout() controlLayout.setContentsMargins(0, 0, 0, 0) controlLayout.addWidget(openButton) controlLayout.addStretch(1) controlLayout.addWidget(controls) controlLayout.addStretch(1) controlLayout.addWidget(self.fullScreenButton) controlLayout.addWidget(self.colorButton) layout = QVBoxLayout() layout.addLayout(displayLayout) hLayout = QHBoxLayout() hLayout.addWidget(self.slider) hLayout.addWidget(self.labelDuration) layout.addLayout(hLayout) layout.addLayout(controlLayout) layout.addLayout(histogramLayout) self.setLayout(layout) if not self.player.isAvailable(): QMessageBox.warning(self, "Service not available", "The QMediaPlayer object does not have a valid service.\n" "Please check the media service plugins are installed.") controls.setEnabled(False) self.playlistView.setEnabled(False) openButton.setEnabled(False) self.colorButton.setEnabled(False) self.fullScreenButton.setEnabled(False) self.metaDataChanged() self.addToPlaylist(playlist) def open(self): fileNames, _ = QFileDialog.getOpenFileNames(self, "Open Files") self.addToPlaylist(fileNames) def addToPlaylist(self, fileNames): for name in fileNames: fileInfo = QFileInfo(name) if fileInfo.exists(): url = QUrl.fromLocalFile(fileInfo.absoluteFilePath()) if fileInfo.suffix().lower() == 'm3u': self.playlist.load(url) else: self.playlist.addMedia(QMediaContent(url)) else: url = QUrl(name) if url.isValid(): self.playlist.addMedia(QMediaContent(url)) def durationChanged(self, duration): duration /= 1000 self.duration = duration self.slider.setMaximum(duration) def positionChanged(self, progress): progress /= 1000 if not self.slider.isSliderDown(): self.slider.setValue(progress) self.updateDurationInfo(progress) def metaDataChanged(self): if self.player.isMetaDataAvailable(): self.setTrackInfo("%s - %s" % ( self.player.metaData(QMediaMetaData.AlbumArtist), self.player.metaData(QMediaMetaData.Title))) def previousClicked(self): # Go to the previous track if we are within the first 5 seconds of # playback. Otherwise, seek to the beginning. if self.player.position() <= 5000: self.playlist.previous() else: self.player.setPosition(0) def jump(self, index): if index.isValid(): self.playlist.setCurrentIndex(index.row()) self.player.play() def playlistPositionChanged(self, position): self.playlistView.setCurrentIndex( self.playlistModel.index(position, 0)) def seek(self, seconds): self.player.setPosition(seconds * 1000) def statusChanged(self, status): self.handleCursor(status) if status == QMediaPlayer.LoadingMedia: self.setStatusInfo("Loading...") elif status == QMediaPlayer.StalledMedia: self.setStatusInfo("Media Stalled") elif status == QMediaPlayer.EndOfMedia: QApplication.alert(self) elif status == QMediaPlayer.InvalidMedia: self.displayErrorMessage() else: self.setStatusInfo("") def handleCursor(self, status): if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia): self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def bufferingProgress(self, progress): self.setStatusInfo("Buffering %d%" % progress) def videoAvailableChanged(self, available): if available: self.fullScreenButton.clicked.connect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.connect( self.fullScreenButton.setChecked) if self.fullScreenButton.isChecked(): self.videoWidget.setFullScreen(True) else: self.fullScreenButton.clicked.disconnect( self.videoWidget.setFullScreen) self.videoWidget.fullScreenChanged.disconnect( self.fullScreenButton.setChecked) self.videoWidget.setFullScreen(False) self.colorButton.setEnabled(available) def setTrackInfo(self, info): self.trackInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def setStatusInfo(self, info): self.statusInfo = info if self.statusInfo != "": self.setWindowTitle("%s | %s" % (self.trackInfo, self.statusInfo)) else: self.setWindowTitle(self.trackInfo) def displayErrorMessage(self): self.setStatusInfo(self.player.errorString()) def updateDurationInfo(self, currentInfo): duration = self.duration if currentInfo or duration: currentTime = QTime((currentInfo/3600)%60, (currentInfo/60)%60, currentInfo%60, (currentInfo*1000)%1000) totalTime = QTime((duration/3600)%60, (duration/60)%60, duration%60, (duration*1000)%1000); format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' tStr = currentTime.toString(format) + " / " + totalTime.toString(format) else: tStr = "" self.labelDuration.setText(tStr) def showColorDialog(self): if self.colorDialog is None: brightnessSlider = QSlider(Qt.Horizontal) brightnessSlider.setRange(-100, 100) brightnessSlider.setValue(self.videoWidget.brightness()) brightnessSlider.sliderMoved.connect( self.videoWidget.setBrightness) self.videoWidget.brightnessChanged.connect( brightnessSlider.setValue) contrastSlider = QSlider(Qt.Horizontal) contrastSlider.setRange(-100, 100) contrastSlider.setValue(self.videoWidget.contrast()) contrastSlider.sliderMoved.connect(self.videoWidget.setContrast) self.videoWidget.contrastChanged.connect(contrastSlider.setValue) hueSlider = QSlider(Qt.Horizontal) hueSlider.setRange(-100, 100) hueSlider.setValue(self.videoWidget.hue()) hueSlider.sliderMoved.connect(self.videoWidget.setHue) self.videoWidget.hueChanged.connect(hueSlider.setValue) saturationSlider = QSlider(Qt.Horizontal) saturationSlider.setRange(-100, 100) saturationSlider.setValue(self.videoWidget.saturation()) saturationSlider.sliderMoved.connect( self.videoWidget.setSaturation) self.videoWidget.saturationChanged.connect( saturationSlider.setValue) layout = QFormLayout() layout.addRow("Brightness", brightnessSlider) layout.addRow("Contrast", contrastSlider) layout.addRow("Hue", hueSlider) layout.addRow("Saturation", saturationSlider) button = QPushButton("Close") layout.addRow(button) self.colorDialog = QDialog(self) self.colorDialog.setWindowTitle("Color Options") self.colorDialog.setLayout(layout) button.clicked.connect(self.colorDialog.close) self.colorDialog.show()