class QgsFmvPlayer(QMainWindow, Ui_PlayerWindow): """ Video Player Class """ def __init__(self, iface, path=None, parent=None): """ Constructor """ super(QgsFmvPlayer, self).__init__(parent) self.setupUi(self) self.parent = parent self.iface = iface self.fileName = None self.metadataDlg = None self.createingMosaic = False self.currentInfo = 0.0 self.RecGIF = QMovie(":/imgFMV/images/record.gif") self.videoWidget.customContextMenuRequested[QPoint].connect( self.contextMenuRequested) self.duration = 0 self.playerMuted = False self.HasFileAudio = False self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.player.setNotifyInterval(1000) # One second self.pass_time = 0.1 self.playlist = QMediaPlaylist() # self.player.setVideoOutput( # self.videoWidget) # Standar Surface self.player.setVideoOutput( self.videoWidget.videoSurface()) # Custom Surface self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.mediaStatusChanged.connect(self.statusChanged) self.player.stateChanged.connect(self.setCurrentState) self.playerState = QMediaPlayer.StoppedState self.playFile(path) self.sliderDuration.setRange(0, self.player.duration() / 1000) self.volumeSlider.setValue(self.player.volume()) self.volumeSlider.enterEvent = self.showVolumeTip if self.metadataDlg is None: self.metadataDlg = QgsFmvMetadata(parent=self, player=self) self.addDockWidget(Qt.RightDockWidgetArea, self.metadataDlg) self.metadataDlg.setMinimumWidth(500) self.metadataDlg.hide() def HasMetadata(self, videoPath): """ Check if video have Metadata or not """ try: p = _spawn([ '-i', videoPath, '-map', 'data-re', '-codec', 'copy', '-f', 'data', '-' ]) stdout_data, _ = p.communicate() if stdout_data == b'': qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", "This video don't have Metadata ! : "), level=QGis.Info) return False return True except Exception as e: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", "Metadata Callback Failed! : "), str(e), level=QGis.Info) def HasAudio(self, videoPath): """ Check if video have Metadata or not """ try: p = _spawn([ '-i', videoPath, '-show_streams', '-select_streams', 'a', '-loglevel', 'error' ], type="ffprobe") stdout_data, _ = p.communicate() if stdout_data == b'': qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", "This video don't have Audio ! : "), level=QGis.Info) return False return True except Exception as e: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", "Audio check Failed! : "), str(e), level=QGis.Info) def callBackMetadata(self, currentTime, nextTime): """ Metadata CallBack """ try: # TODO : Speed this function # stdout_data = _check_output(['-i', self.fileName, # '-ss', currentTime, # '-to', nextTime, # '-f', 'data', '-']) t = callBackMetadataThread(cmds=[ '-i', self.fileName, '-ss', currentTime, '-to', nextTime, '-map', 'data-re', '-f', 'data', '-' ]) t.start() t.join(1) if t.is_alive(): t.p.terminate() t.join() if t.stdout == b'': return for packet in StreamParser(t.stdout): try: self.addMetadata(packet.MetadataList()) UpdateLayers(packet, parent=self, mosaic=self.createingMosaic) self.iface.mapCanvas().refresh() QApplication.processEvents() return except Exception as e: None except Exception as e: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", "Metadata Callback Failed! : "), str(e), level=QGis.Info) def addMetadata(self, packet): ''' Add Metadata to List ''' self.clearMetadata() row = 0 for key in sorted(packet.keys()): self.metadataDlg.VManager.insertRow(row) self.metadataDlg.VManager.setItem(row, 0, QTableWidgetItem(str(key))) self.metadataDlg.VManager.setItem( row, 1, QTableWidgetItem(str(packet[key][0]))) self.metadataDlg.VManager.setItem( row, 2, QTableWidgetItem(str(packet[key][1]))) row += 1 self.metadataDlg.VManager.setVisible(False) self.metadataDlg.VManager.resizeColumnsToContents() self.metadataDlg.VManager.setVisible(True) self.metadataDlg.VManager.verticalScrollBar().setSliderPosition( self.sliderPosition) def clearMetadata(self): ''' Clear Metadata List ''' try: self.sliderPosition = self.metadataDlg.VManager.verticalScrollBar( ).sliderPosition() self.metadataDlg.VManager.setRowCount(0) except: None def saveInfoToJson(self): """ Save video Info to json """ if not self.KillAllProcessors(): return out_json, _ = QFileDialog.getSaveFileName(self, "Save File", "", "Json Files (*.json)") if out_json == "": return try: self.VPProbeToJson = Converter() self.VPTProbeToJson = QThread() self.VPProbeToJson.moveToThread(self.VPTProbeToJson) self.VPProbeToJson.finished.connect(self.QThreadFinished) self.VPProbeToJson.error.connect(self.QThreadError) self.VPProbeToJson.progress.connect( self.progressBarProcessor.setValue) self.VPTProbeToJson.start(QThread.LowPriority) QMetaObject.invokeMethod(self.VPProbeToJson, 'probeToJson', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, out_json)) except Exception as e: qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Error saving Json")) self.QThreadFinished("probeToJson", "Closing ProbeToJson") def showVideoInfo(self): ''' Show default probe info ''' try: self.VPProbe = Converter() self.VPTProbe = QThread() self.VPProbe.moveToThread(self.VPTProbe) self.VPProbe.finishedJson.connect(self.QThreadFinished) self.VPProbe.error.connect(self.QThreadError) self.VPProbe.progress.connect(self.progressBarProcessor.setValue) self.VPTProbe.start(QThread.LowPriority) QMetaObject.invokeMethod(self.VPProbe, 'probeShow', Qt.QueuedConnection, Q_ARG(str, self.fileName)) except Exception as e: qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Error Info Show")) self.QThreadFinished("probeShow", "Closing Probe") return def state(self): ''' Return Current State ''' return self.playerState def setCurrentState(self, state): ''' Set Current State ''' if state != self.playerState: self.playerState = state if state == QMediaPlayer.StoppedState: self.btn_play.setIcon(QIcon(":/imgFMV/images/play-arrow.png")) return def showColorDialog(self): ''' Show Color dialog ''' self.ColorDialog = ColorDialog(parent=self) self.ColorDialog.setWindowFlags(Qt.Window | Qt.WindowCloseButtonHint) # Fail if not uncheked self.actionMagnifying_glass.setChecked(False) self.actionZoom_Rectangle.setChecked(False) self.ColorDialog.exec_() return def createMosaic(self, value): ''' Function for create Video Mosaic ''' home = os.path.expanduser("~") qgsu.createFolderByName(home, "QGIS_FMV") homefmv = os.path.join(home, "QGIS_FMV") root, ext = os.path.splitext(os.path.basename(self.fileName)) qgsu.createFolderByName(homefmv, root) self.createingMosaic = value # Create Group CreateGroupByName() return def contextMenuRequested(self, point): ''' Context Menu Video ''' menu = QMenu() # actionColors = menu.addAction( # QCoreApplication.translate("QgsFmvPlayer", "Color Options")) # actionColors.setShortcut("Ctrl+May+C") # actionColors.triggered.connect(self.showColorDialog) actionMute = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Mute/Unmute")) actionMute.setShortcut("Ctrl+May+U") actionMute.triggered.connect(self.setMuted) menu.addSeparator() actionAllFrames = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Extract All Frames")) actionAllFrames.setShortcut("Ctrl+May+A") actionAllFrames.triggered.connect(self.ExtractAllFrames) actionCurrentFrames = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Extract Current Frame")) actionCurrentFrames.setShortcut("Ctrl+May+Q") actionCurrentFrames.triggered.connect(self.ExtractCurrentFrame) menu.addSeparator() actionShowMetadata = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Show Metadata")) actionShowMetadata.setShortcut("Ctrl+May+M") actionShowMetadata.triggered.connect(self.OpenQgsFmvMetadata) menu.exec_(self.mapToGlobal(point)) # Start Snnipet FILTERS def grayFilter(self, value): self.UncheckFilters(self.sender(), value) self.videoWidget.SetGray(value) self.videoWidget.UpdateSurface() return def edgeFilter(self, value): self.UncheckFilters(self.sender(), value) self.videoWidget.SetEdgeDetection(value) self.videoWidget.UpdateSurface() return def invertColorFilter(self, value): self.UncheckFilters(self.sender(), value) self.videoWidget.SetInvertColor(value) self.videoWidget.UpdateSurface() return def autoContrastFilter(self, value): self.UncheckFilters(self.sender(), value) self.videoWidget.SetAutoContrastFilter(value) self.videoWidget.UpdateSurface() return def monoFilter(self, value): self.UncheckFilters(self.sender(), value) self.videoWidget.SetMonoFilter(value) self.videoWidget.UpdateSurface() return def magnifier(self, value): self.UncheckUtils(self.sender(), value) self.videoWidget.SetMagnifier(value) self.videoWidget.UpdateSurface() return def zoomRect(self, value): self.UncheckUtils(self.sender(), value) self.videoWidget.SetZoomRect(value) self.videoWidget.UpdateSurface() return def UncheckUtils(self, sender, value): # p = self.player.position() # self.player.setVideoOutput( # self.videoWidget.videoSurface()) # Custom surface # self.player.setPosition(p) QApplication.processEvents() name = sender.objectName() self.actionMagnifying_glass.setChecked( True if name == "actionMagnifying_glass" else False) self.actionZoom_Rectangle.setChecked(True if name == "actionZoom_Rectangle" else False) sender.setChecked(value) return def UncheckFilters(self, sender, value): # p = self.player.position() # self.player.setVideoOutput( # self.videoWidget.videoSurface()) # Custom surface # self.player.setPosition(p) # QApplication.processEvents() name = sender.objectName() self.actionGray.setChecked(True if name == "actionGray" else False) self.actionInvert_Color.setChecked(True if name == "actionInvert_Color" else False) self.actionMono_Filter.setChecked(True if name == "actionMono_Filter" else False) self.actionCanny_edge_detection.setChecked( True if name == "actionCanny_edge_detection" else False) self.actionAuto_Contrast_Filter.setChecked( True if name == "actionAuto_Contrast_Filter" else False) self.videoWidget.SetGray(True if name == "actionGray" else False) self.videoWidget.SetEdgeDetection( True if name == "actionCanny_edge_detection" else False) self.videoWidget.SetInvertColor(True if name == "actionInvert_Color" else False) self.videoWidget.SetMonoFilter(True if name == "actionMono_Filter" else False) self.videoWidget.SetAutoContrastFilter( True if name == "actionAuto_Contrast_Filter" else False) sender.setChecked(value) return # End Snnipet FILTERS def isMuted(self): ''' Is muted video property''' return self.playerMuted def setMuted(self): ''' Muted video ''' if self.player.isMuted(): self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_up.png")) self.player.setMuted(False) self.volumeSlider.setEnabled(True) else: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_off.png")) self.player.setMuted(True) self.volumeSlider.setEnabled(False) return def stop(self): ''' Stop video''' self.player.stop() self.videoWidget.update() return def volume(self): ''' Volume Slider ''' return self.volumeSlider.value() def setVolume(self, volume): ''' Tooltip and set value''' self.player.setVolume(volume) self.showVolumeTip(volume) if 0 < volume <= 30: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_30.png")) elif 30 < volume <= 60: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_60.png")) elif 60 < volume <= 100: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_up.png")) elif volume == 0: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_off.png")) def EndMedia(self): ''' Button end video position ''' if self.player.isVideoAvailable(): self.player.setPosition(self.player.duration()) self.videoWidget.update() return def StartMedia(self): ''' Button start video position ''' if self.player.isVideoAvailable(): self.player.setPosition(0) self.videoWidget.update() return def forwardMedia(self): ''' Button forward Video ''' forwardTime = int(self.player.position()) + 10 * 1000 if forwardTime > int(self.player.duration()): forwardTime = int(self.player.duration()) self.player.setPosition(forwardTime) def rewindMedia(self): ''' Button rewind Video ''' rewindTime = int(self.player.position()) - 10 * 1000 if rewindTime < 0: rewindTime = 0 self.player.setPosition(rewindTime) def AutoRepeat(self, checked): ''' Button AutoRepeat Video ''' if checked: self.playlist.setPlaybackMode(QMediaPlaylist.Loop) else: self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) return def showVolumeTip(self, _): ''' Volume Slider Tooltip Trick ''' self.style = self.volumeSlider.style() self.opt = QStyleOptionSlider() self.volumeSlider.initStyleOption(self.opt) rectHandle = self.style.subControlRect(self.style.CC_Slider, self.opt, self.style.SC_SliderHandle) self.tip_offset = QPoint(5, 15) pos_local = rectHandle.topLeft() + self.tip_offset pos_global = self.volumeSlider.mapToGlobal(pos_local) QToolTip.showText(pos_global, str(self.volumeSlider.value()) + " %", self) def showMoveTip(self, currentInfo): ''' Player Silder Move Tooptip Trick ''' self.style = self.sliderDuration.style() self.opt = QStyleOptionSlider() self.sliderDuration.initStyleOption(self.opt) rectHandle = self.style.subControlRect(self.style.CC_Slider, self.opt, self.style.SC_SliderHandle) self.tip_offset = QPoint(5, 15) pos_local = rectHandle.topLeft() + self.tip_offset pos_global = self.sliderDuration.mapToGlobal(pos_local) tStr = _seconds_to_time(currentInfo) QToolTip.showText(pos_global, tStr, self) def durationChanged(self, duration): ''' Duration video change signal ''' duration /= 1000 self.duration = duration self.sliderDuration.setMaximum(duration) def positionChanged(self, progress): ''' Current Video position change ''' progress /= 1000 if not self.sliderDuration.isSliderDown(): self.sliderDuration.setValue(progress) self.updateDurationInfo(progress) def updateDurationInfo(self, currentInfo): ''' Update labels duration Info and CallBack Metadata ''' duration = self.duration self.currentInfo = currentInfo if currentInfo or duration: totalTime = _seconds_to_time(duration) currentTime = _seconds_to_time(currentInfo) tStr = currentTime + " / " + totalTime nextTime = currentInfo + self.pass_time currentTimeInfo = _seconds_to_time_frac(currentInfo) nextTimeInfo = _seconds_to_time_frac(nextTime) # Metadata CallBack self.callBackMetadata(currentTimeInfo, nextTimeInfo) else: tStr = "" self.labelDuration.setText(tStr) def handleCursor(self, status): ''' Change cursor ''' if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia): self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def statusChanged(self, status): ''' Signal Status video change ''' self.handleCursor(status) if status == QMediaPlayer.LoadingMedia: self.videoAvailableChanged(False) elif status == QMediaPlayer.StalledMedia: self.videoAvailableChanged(False) if status == QMediaPlayer.EndOfMedia: self.videoAvailableChanged(True) elif status == QMediaPlayer.InvalidMedia: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", self.player.errorString()), level=QGis.Warning) self.videoAvailableChanged(False) else: self.videoAvailableChanged(True) def playFile(self, videoPath): ''' Play file from path ''' try: RemoveVideoLayers() RemoveGroupByName() self.fileName = videoPath self.playlist = QMediaPlaylist() url = QUrl.fromLocalFile(videoPath) self.playlist.addMedia(QMediaContent(url)) self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) self.player.setPlaylist(self.playlist) self.setWindowTitle("Playing : " + os.path.basename(os.path.normpath(videoPath))) if self.HasMetadata(videoPath): CreateVideoLayers() self.clearMetadata() self.lb_cursor_coord.setText( "<span style='font-size:10pt; font-weight:bold;'>Lon :</span>" + "<span style='font-size:9pt; font-weight:normal;'>Null</span>" + "<span style='font-size:10pt; font-weight:bold;'> Lat :</span>" + "<span style='font-size:9pt; font-weight:normal;'>Null</span>" ) else: self.btn_GeoReferencing.setEnabled(False) self.HasFileAudio = True if not self.HasAudio(videoPath): self.actionAudio.setEnabled(False) self.actionSave_Audio.setEnabled(False) self.HasFileAudio = False self.playClicked(True) except Exception as e: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", 'Open Video File : '), str(e), level=QGis.Warning) def ReciconUpdate(self, frame): self.btn_Rec.setIcon(QIcon(self.RecGIF.currentPixmap())) def RecordVideo(self, value): ''' Cut Video ''' currentTime = _seconds_to_time(self.currentInfo) if value is False: self.endRecord = currentTime _, file_extension = os.path.splitext(self.fileName) out, _ = QFileDialog.getSaveFileName(self, "Save As", "", file_extension) if not out: self.RecGIF.frameChanged.disconnect(self.ReciconUpdate) self.RecGIF.stop() self.btn_Rec.setIcon(QIcon(":/imgFMV/images/record.png")) return False lfn = out.lower() if not lfn.endswith((file_extension)): out += file_extension p = _spawn([ '-i', self.fileName, '-ss', self.startRecord, '-to', self.endRecord, '-c', 'copy', out ]) p.communicate() qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Save file succesfully!")) self.RecGIF.frameChanged.disconnect(self.ReciconUpdate) self.RecGIF.stop() self.btn_Rec.setIcon(QIcon(":/imgFMV/images/record.png")) else: self.startRecord = currentTime self.RecGIF.frameChanged.connect(self.ReciconUpdate) self.RecGIF.start() return def videoAvailableChanged(self, available): ''' Buttons for video available ''' # self.btn_Color.setEnabled(available) self.btn_CaptureFrame.setEnabled(available) self.gb_PlayerControls.setEnabled(available) return def toggleGroup(self, state): ''' Toggle GroupBox ''' sender = self.sender() if state: sender.setFixedHeight(sender.sizeHint().height()) else: sender.setFixedHeight(15) def playClicked(self, state): ''' Stop and Play video ''' if self.playerState in (QMediaPlayer.StoppedState, QMediaPlayer.PausedState): self.btn_play.setIcon(QIcon(":/imgFMV/images/pause.png")) self.player.play() elif self.playerState == QMediaPlayer.PlayingState: self.btn_play.setIcon(QIcon(":/imgFMV/images/play-arrow.png")) self.player.pause() def seek(self, seconds): '''Slider Move''' self.player.setPosition(seconds * 1000) self.showMoveTip(seconds) def convertVideo(self): '''Convert Video To Other Format ''' if not self.KillAllProcessors(): return sel = "mp4 Files (*.mp4)" out, _ = QFileDialog.getSaveFileName( self, "Save Video as...", None, "ogg files (*.ogg);;avi Files (*.avi);;mkv Files (*.mkv);;webm Files (*.webm);;flv Files (*.flv);;mov Files (*.mov);;mp4 Files (*.mp4);;mpg Files (*.mpg);;mp3 Files (*.mp3)", sel) if not out: return False lfn = out.lower() if not lfn.endswith(('.ogg', '.avi', '.mkv', '.webm', '.flv', '.mov', '.mp4', '.mp3', '.mpg')): # The default. out += '.mp4' try: self.VPConverter = Converter() self.VPTConverter = QThread() self.VPConverter.moveToThread(self.VPTConverter) self.VPConverter.finished.connect(self.QThreadFinished) self.VPConverter.error.connect(self.QThreadError) self.VPConverter.progress.connect( self.progressBarProcessor.setValue) self.VPTConverter.start(QThread.LowPriority) # TODO : Make Correct format Conversion and embebed metadata info = self.VPConverter.probeInfo(self.fileName) if info is not None: if self.HasFileAudio: audio_codec = info.audio.codec audio_samplerate = info.audio.audio_samplerate audio_channels = info.audio.audio_channels video_codec = info.video.codec video_width = info.video.video_width video_height = info.video.video_height video_fps = info.video.video_fps _, out_ext = os.path.splitext(out) if self.HasFileAudio: options = { 'format': out_ext[1:], 'audio': { 'codec': audio_codec, 'samplerate': audio_samplerate, 'channels': audio_channels }, 'video': { 'codec': video_codec, 'width': video_width, 'height': video_height, 'fps': video_fps } } else: options = { 'format': out_ext[1:], 'video': { 'codec': video_codec, 'width': video_width, 'height': video_height, 'fps': video_fps } } QMetaObject.invokeMethod(self.VPConverter, 'convert', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, out), Q_ARG(dict, options), Q_ARG(bool, False)) except Exception as e: qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Error converting video ")) self.QThreadFinished("convert", "Closing convert") def ShowPlot(self, bitrate_data, frame_count, output=None): ''' Show plot,because show not work using threading ''' matplot.figure().canvas.set_window_title(self.fileName) matplot.title("Stream Bitrate vs Time") matplot.xlabel("Time (sec)") matplot.ylabel("Frame Bitrate (kbit/s)") matplot.grid(True) # map frame type to color frame_type_color = { # audio 'A': 'yellow', # video 'I': 'red', 'P': 'green', 'B': 'blue' } global_peak_bitrate = 0.0 global_mean_bitrate = 0.0 # render charts in order of expected decreasing size for frame_type in ['I', 'P', 'B', 'A']: # skip frame type if missing if frame_type not in bitrate_data: continue # convert list of tuples to numpy 2d array frame_list = bitrate_data[frame_type] frame_array = numpy.array(frame_list) # update global peak bitrate peak_bitrate = frame_array.max(0)[1] if peak_bitrate > global_peak_bitrate: global_peak_bitrate = peak_bitrate # update global mean bitrate (using piecewise mean) mean_bitrate = frame_array.mean(0)[1] global_mean_bitrate += mean_bitrate * \ (len(frame_list) / frame_count) # plot chart using gnuplot-like impulses matplot.vlines(frame_array[:, 0], [0], frame_array[:, 1], color=frame_type_color[frame_type], label="{} Frames".format(frame_type)) self.progressBarProcessor.setValue(90) # calculate peak line position (left 15%, above line) peak_text_x = matplot.xlim()[1] * 0.15 peak_text_y = global_peak_bitrate + \ ((matplot.ylim()[1] - matplot.ylim()[0]) * 0.015) peak_text = "peak ({:.0f})".format(global_peak_bitrate) # draw peak as think black line w/ text matplot.axhline(global_peak_bitrate, linewidth=2, color='black') matplot.text(peak_text_x, peak_text_y, peak_text, horizontalalignment='center', fontweight='bold', color='black') # calculate mean line position (right 85%, above line) mean_text_x = matplot.xlim()[1] * 0.85 mean_text_y = global_mean_bitrate + \ ((matplot.ylim()[1] - matplot.ylim()[0]) * 0.015) mean_text = "mean ({:.0f})".format(global_mean_bitrate) # draw mean as think black line w/ text matplot.axhline(global_mean_bitrate, linewidth=2, color='black') matplot.text(mean_text_x, mean_text_y, mean_text, horizontalalignment='center', fontweight='bold', color='black') matplot.legend() if output != "": matplot.savefig(output) else: matplot.show() self.progressBarProcessor.setValue(100) def CreateBitratePlot(self): ''' Create video Plot Bitrate Thread ''' if not self.KillAllProcessors(): return try: self.VPBitratePlot = CreatePlotsBitrate() self.VPTBitratePlot = QThread() self.VPBitratePlot.moveToThread(self.VPTBitratePlot) self.VPBitratePlot.finished.connect(self.QThreadFinished) self.VPBitratePlot.return_fig.connect(self.ShowPlot) self.VPBitratePlot.error.connect(self.QThreadError) self.VPBitratePlot.progress.connect( self.progressBarProcessor.setValue) self.VPTBitratePlot.start(QThread.LowPriority) sender = self.sender().objectName() if sender == "actionAudio": QMetaObject.invokeMethod(self.VPBitratePlot, 'CreatePlot', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, None), Q_ARG(str, 'audio')) elif sender == "actionVideo": QMetaObject.invokeMethod(self.VPBitratePlot, 'CreatePlot', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, None), Q_ARG(str, 'video')) elif sender == "actionSave_Audio": selfilter = "Portable Network Graphics (*.png)" fileaudio, _ = QFileDialog.getSaveFileName( self, "Save Audio Bitrate Plot", "", "EPS Encapsulated Postscript (*.eps);;" "PGF code for LaTex (*.pgf);;" "Portable document format(*pdf);;" "Portable Network Graphics (*.png);;" "Postscript (*.ps);;" "Raw RGBA bitmap (*.raw*.rgba);;" "Scalable vector graphics (*.svg*.svgz)", selfilter) if fileaudio == "": return QMetaObject.invokeMethod(self.VPBitratePlot, 'CreatePlot', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, fileaudio), Q_ARG(str, 'audio')) elif sender == "actionSave_Video": selfilter = "Portable Network Graphics (*.png)" filevideo, _ = QFileDialog.getSaveFileName( self, "Save Video Bitrate Plot", "", "EPS Encapsulated Postscript (*.eps);;" "PGF code for LaTex (*.pgf);;" "Portable document format(*pdf);;" "Portable Network Graphics (*.png);;" "Postscript (*.ps);;" "Raw RGBA bitmap (*.raw*.rgba);;" "Scalable vector graphics (*.svg*.svgz)", selfilter) if filevideo == "": return QMetaObject.invokeMethod(self.VPBitratePlot, 'CreatePlot', Qt.QueuedConnection, Q_ARG(str, self.fileName), Q_ARG(str, filevideo), Q_ARG(str, 'video')) except Exception as e: qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Failed creating Plot Bitrate")) def ExtractAllFrames(self): """ Extract All Video Frames Thread """ if not self.KillAllProcessors(): return options = QFileDialog.DontResolveSymlinks | QFileDialog.ShowDirsOnly directory = QFileDialog.getExistingDirectory( self, QCoreApplication.translate("QgsFmvPlayer", "Save images"), '', options=options) if directory: self.VPExtractFrames = ExtractFramesProcessor() self.VPTExtractAllFrames = QThread() self.VPExtractFrames.moveToThread(self.VPTExtractAllFrames) self.VPExtractFrames.finished.connect(self.QThreadFinished) self.VPExtractFrames.error.connect(self.QThreadError) self.VPExtractFrames.progress.connect( self.progressBarProcessor.setValue) self.VPTExtractAllFrames.start(QThread.LowPriority) QMetaObject.invokeMethod(self.VPExtractFrames, 'ExtractFrames', Qt.QueuedConnection, Q_ARG(str, directory), Q_ARG(str, self.fileName)) return def ExtractCurrentFrame(self): """ Extract Current Frame Thread """ image = self.videoWidget.GetCurrentFrame() out_image, _ = QFileDialog.getSaveFileName( self, "Save Current Frame", "", "Image File (*.png *.jpg *.bmp *.tiff)") if out_image == "": return if out_image: t = threading.Thread(target=self.SaveCapture, args=( image, out_image, )) t.start() return def SaveCapture(self, image, output): ''' Save Current Image ''' image.save(output) QApplication.processEvents() return def QThreadFinished(self, process, msg, outjson=None): ''' Finish Threads ''' if process == "ExtractFramesProcessor": self.VPExtractFrames.deleteLater() self.VPTExtractAllFrames.terminate() self.VPTExtractAllFrames.deleteLater() elif process == "CreatePlotsBitrate": self.VPBitratePlot.deleteLater() self.VPTBitratePlot.terminate() self.VPTBitratePlot.deleteLater() elif process == "convert": self.VPConverter.deleteLater() self.VPTConverter.terminate() self.VPTConverter.deleteLater() elif process == "probeToJson": self.VPProbeToJson.deleteLater() self.VPTProbeToJson.terminate() self.VPTProbeToJson.deleteLater() elif process == "probeShow": self.VPProbe.deleteLater() self.VPTProbe.terminate() self.VPTProbe.deleteLater() self.showVideoInfoDialog(outjson) QApplication.processEvents() self.progressBarProcessor.setValue(0) return def QThreadError(self, processor, e, exception_string): """ Threads Errors""" qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", processor), 'Failed!\n'.format(exception_string), level=QGis.Warning) self.QThreadFinished(processor, "Closing Processor") return def OpenQgsFmvMetadata(self): """ Open Metadata Dock """ if self.metadataDlg is None: self.metadataDlg = QgsFmvMetadata(parent=self, player=self) self.addDockWidget(Qt.RightDockWidgetArea, self.metadataDlg) self.metadataDlg.show() else: self.metadataDlg.show() return def KillAllProcessors(self): """Kill All Processors""" """ Extract all frames Processors """ try: if self.VPTExtractAllFrames.isRunning(): ret = qgsu.CustomMessage( QCoreApplication.translate( "QgsFmvPlayer", "HEY...Active background process!"), QCoreApplication.translate("QgsFmvPlayer", "Do you really want close?")) if ret == QMessageBox.Yes: self.QThreadFinished("ExtractFramesProcessor", "Closing Extract Frames Processor") else: return False except: None """ Bitrates Processors""" try: if self.VPTBitratePlot.isRunning(): ret = qgsu.CustomMessage( QCoreApplication.translate( "QgsFmvPlayer", "HEY...Active background process!"), QCoreApplication.translate("QgsFmvPlayer", "Do you really want close?")) if ret == QMessageBox.Yes: self.QThreadFinished("CreatePlotsBitrate", "Closing Plot Bitrate") else: return False except: None """ Converter Processors """ try: if self.VPTConverter.isRunning(): ret = qgsu.CustomMessage( QCoreApplication.translate( "QgsFmvPlayer", "HEY...Active background process!"), QCoreApplication.translate("QgsFmvPlayer", "Do you really want close?")) if ret == QMessageBox.Yes: self.QThreadFinished("convert", "Closing convert") else: return False except: None """ probeToJson Processors """ try: if self.VPTProbeToJson.isRunning(): ret = qgsu.CustomMessage( QCoreApplication.translate( "QgsFmvPlayer", "HEY...Active background process!"), QCoreApplication.translate("QgsFmvPlayer", "Do you really want close?")) if ret == QMessageBox.Yes: self.QThreadFinished("probeToJson", "Closing Info to Json") else: return False except: None """ probeShow Processors """ try: if self.VPTProbe.isRunning(): ret = qgsu.CustomMessage( QCoreApplication.translate( "QgsFmvPlayer", "HEY...Active background process!"), QCoreApplication.translate("QgsFmvPlayer", "Do you really want close?")) if ret == QMessageBox.Yes: self.QThreadFinished("probeShow", "Closing Show Video Info") else: return False except: None return True def showVideoInfoDialog(self, outjson): """ Show Video Information Dialog """ view = QTreeView() model = QJsonModel() view.setModel(model) model.loadJsonFromConsole(outjson) self.VideoInfoDialog = QDialog(self) self.VideoInfoDialog.setWindowTitle("Video Information : " + self.fileName) self.VideoInfoDialog.setWindowIcon( QIcon(":/imgFMV/images/video_information.png")) self.verticalLayout = QVBoxLayout(self.VideoInfoDialog) self.verticalLayout.addWidget(view) view.expandAll() view.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.VideoInfoDialog.setWindowFlags(Qt.Window | Qt.WindowCloseButtonHint) self.VideoInfoDialog.setObjectName("VideoInfoDialog") self.VideoInfoDialog.resize(500, 400) self.VideoInfoDialog.show() def closeEvent(self, evt): """ Close Event """ if self.KillAllProcessors() is False: evt.ignore() return self.player.stop() self.parent._PlayerDlg = None self.parent.ToggleActiveFromTitle() RemoveVideoLayers() RemoveGroupByName() # Restore Filters State self.videoWidget.RestoreFilters() # QApplication.processEvents() del self.player
class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(1200, 720) Form.setMinimumSize(QtCore.QSize(200, 199)) Form.setStyleSheet("background-color:black;") self.isOnline = False self.isMini = False self.isOnTop = True self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.verticalLayout_2 = QtWidgets.QVBoxLayout(Form) self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout_2.setSpacing(0) self.verticalLayout_2.setObjectName("verticalLayout_2") self.mainFrame = QtWidgets.QFrame(Form) self.mainFrame.setMinimumSize(QtCore.QSize(200, 82)) self.mainFrame.setFrameShape(QtWidgets.QFrame.StyledPanel) self.mainFrame.setFrameShadow(QtWidgets.QFrame.Raised) self.mainFrame.setObjectName("mainFrame") self.verticalLayout = QtWidgets.QVBoxLayout(self.mainFrame) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setSpacing(0) self.verticalLayout.setObjectName("verticalLayout") self.frame_3 = QtWidgets.QFrame(self.mainFrame) sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.frame_3.sizePolicy().hasHeightForWidth()) self.frame_3.setSizePolicy(sizePolicy) self.frame_3.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_3.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_3.setObjectName("frame_3") self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.frame_3) self.horizontalLayout_6.setContentsMargins(0, 0, 0, 0) self.horizontalLayout_6.setObjectName("horizontalLayout_6") self.Player_name = QtWidgets.QLabel(self.frame_3) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.Player_name.sizePolicy().hasHeightForWidth()) self.Player_name.setSizePolicy(sizePolicy) self.Player_name.setStyleSheet("QLabel\n" " {\n" " font: 12pt \"Helvetica\";\n" " color: white;\n" " border: 0px solid #076100;\n" " }") self.Player_name.setObjectName("status_label") self.horizontalLayout_6.addWidget(self.Player_name) spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_6.addItem(spacerItem) self.minimize_button = QtWidgets.QPushButton(self.frame_3) self.minimize_button.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) self.minimize_button.setText("") icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap("icon_sets/minimize.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.minimize_button.setIconSize(QSize(25, 17)) self.minimize_button.setMaximumSize(QSize(25, 17)) self.minimize_button.setIcon(icon) self.minimize_button.setIconSize(QtCore.QSize(27, 20)) # self.minimize_button.setFlat(True) self.minimize_button.setObjectName("minimize_button") self.horizontalLayout_6.addWidget(self.minimize_button) self.maximize_button = QtWidgets.QPushButton(self.frame_3) self.maximize_button.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) self.maximize_button.setText("") icon1 = QtGui.QIcon() icon1.addPixmap(QtGui.QPixmap("icon_sets/maximize.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.maximize_button.setIcon(icon1) self.maximize_button.setIconSize(QSize(25, 17)) self.maximize_button.setMaximumSize(QSize(25, 17)) self.maximize_button.setIconSize(QtCore.QSize(27, 20)) # self.maximize_button.setFlat(True) self.maximize_button.setObjectName("maximize_button") self.horizontalLayout_6.addWidget(self.maximize_button) self.cross_button = QtWidgets.QPushButton(self.frame_3) self.cross_button.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) self.cross_button.setText("") self.cross_button.setIconSize(QSize(25, 17)) self.cross_button.setMaximumSize(QSize(25, 17)) icon2 = QtGui.QIcon() icon2.addPixmap(QtGui.QPixmap("icon_sets/cross.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.cross_button.setIcon(icon2) self.cross_button.setIconSize(QtCore.QSize(27, 20)) # self.cross_button.setFlat(True) self.cross_button.setObjectName("cross_button") self.horizontalLayout_6.addWidget(self.cross_button) self.verticalLayout.addWidget(self.frame_3) self.video_playback = Videowidget(self.frame_3) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(200) sizePolicy.setHeightForWidth( self.video_playback.sizePolicy().hasHeightForWidth()) self.video_playback.setSizePolicy(sizePolicy) self.video_playback.setMinimumSize(QtCore.QSize(200, 100)) self.video_playback.setMouseTracking(False) self.video_playback.setTabletTracking(False) self.video_playback.setAcceptDrops(False) self.video_playback.setAutoFillBackground(False) self.video_playback.setObjectName("video_playback") self.video_playback.setStyleSheet("background-color:grey") self.verticalLayout.addWidget(self.video_playback) self.pos_frame = QtWidgets.QFrame(self.mainFrame) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(10) sizePolicy.setHeightForWidth( self.pos_frame.sizePolicy().hasHeightForWidth()) self.pos_frame.setSizePolicy(sizePolicy) self.pos_frame.setFrameShape(QtWidgets.QFrame.StyledPanel) self.pos_frame.setFrameShadow(QtWidgets.QFrame.Raised) self.pos_frame.setObjectName("pos_frame") self.horizontalLayout = QtWidgets.QHBoxLayout(self.pos_frame) self.horizontalLayout.setObjectName("horizontalLayout") self.position_slider = PosSlider(self.pos_frame) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(100) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.position_slider.sizePolicy().hasHeightForWidth()) self.position_slider.setSizePolicy(sizePolicy) self.position_slider.setMouseTracking(True) self.position_slider.setAutoFillBackground(False) self.position_slider.setStyleSheet( "QSlider::handle:horizontal \n" " {\n" " background: transparent;\n" " width: 8px;\n" " }\n" "QSlider::groove:horizontal {\n" " border: 1px solid white;\n" " height: 8px;\n" " background: qlineargradient(y1: 0, y2: 1,stop: 0 #2e3436, stop: 1.0 #000000);\n" "}\n" " QSlider::sub-page:horizontal {\n" " background:qlineargradient( y1: 0, y2: 1,\n" " stop: 0 #42a6db, stop: 1 #0074e0); \n" " border: 1px solid white;\n" " height: 8px;\n" "}\n" "QSlider::handle:horizontal:hover {\n" " background: black;\n" " height: 8px;\n" " width: 8px;\n" " border: 1px solid white;\n" " }\n" "") self.position_slider.setOrientation(QtCore.Qt.Horizontal) self.position_slider.setInvertedAppearance(False) self.position_slider.setInvertedControls(False) self.position_slider.setObjectName("position_slider") self.horizontalLayout.addWidget(self.position_slider) self.time_status = QtWidgets.QLabel(self.pos_frame) font = QtGui.QFont() font.setFamily("Arial Rounded MT Bold") font.setPointSize(8) font.setBold(False) font.setItalic(False) font.setWeight(50) self.time_status.setFont(font) self.time_status.setStyleSheet( "QLabel\n" " {\n" " \n" " font: 8pt \"Arial Rounded MT Bold\";\n" " color: white;\n" " border: 0px solid #076100;\n" "\n" " }") self.time_status.setObjectName("time_status") self.horizontalLayout.addWidget(self.time_status) self.backslash = QtWidgets.QLabel(self.pos_frame) font = QtGui.QFont() font.setFamily("Arial Rounded MT Bold") font.setPointSize(8) font.setBold(False) font.setItalic(False) font.setWeight(50) self.backslash.setFont(font) self.backslash.setStyleSheet( "QLabel\n" " {\n" " \n" " font: 8pt \"Arial Rounded MT Bold\";\n" " color: white;\n" " border: 0px solid #076100;\n" "\n" " }") self.backslash.setObjectName("backslash") self.horizontalLayout.addWidget(self.backslash) self.duration_status = QtWidgets.QLabel(self.pos_frame) font = QtGui.QFont() font.setFamily("Arial Rounded MT Bold") font.setPointSize(8) font.setBold(False) font.setItalic(False) font.setWeight(50) self.duration_status.setFont(font) self.duration_status.setStyleSheet( "QLabel\n" " {\n" " \n" " font: 8pt \"Arial Rounded MT Bold\";\n" " color: white;\n" " border: 0px solid #076100;\n" "\n" " }") self.duration_status.setObjectName("duration_status") self.horizontalLayout.addWidget(self.duration_status) self.verticalLayout.addWidget(self.pos_frame) self.frame_2 = QtWidgets.QFrame(self.mainFrame) sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(22) # sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth()) self.frame_2.setSizePolicy(sizePolicy) self.frame_2.setContentsMargins(0, 0, 0, 0) self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) self.frame_2.setObjectName("frame_2") self.frame_2.setStyleSheet("") self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.frame_2) self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.play_button = QtWidgets.QPushButton(self.frame_2) self.play_button.setEnabled(False) self.play_button.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) self.play_button.setStyleSheet( "QPushButton[play=true]{image:url(icon_sets/play/play.png);width:22px;height:22px}\n" "QPushButton[play=false]{image:url(icon_sets/pause/pause.png);width:22px;height:22px }\n" ) self.play_button.setProperty("play", True) self.play_button.setText("") self.play_button.setObjectName("play_button") self.horizontalLayout_4.addWidget(self.play_button) self.playback_button = QtWidgets.QPushButton(self.frame_2) self.playback_button.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) self.playback_button.setStyleSheet( "QPushButton{image:url(icon_sets/playback/playback.png);width:22px;height:22px }\n" ) self.playback_button.setText("") self.playback_button.setObjectName("playback_button") self.horizontalLayout_4.addWidget(self.playback_button) self.always_on_top_button = QtWidgets.QPushButton(self.frame_2) self.always_on_top_button.setCursor( QtGui.QCursor(QtCore.Qt.ArrowCursor)) self.always_on_top_button.setStyleSheet( "QPushButton[top=false]{image : url(icon_sets/always_on_top/top_off.png) }\n" "QPushButton[top=true]{image : url(icon_sets/always_on_top/top_on.png) }\n" ) self.always_on_top_button.setProperty("top", True) self.always_on_top_button.setText("") self.always_on_top_button.setObjectName("always_on_top_button") self.horizontalLayout_4.addWidget(self.always_on_top_button) self.miniplayer_button = QtWidgets.QPushButton(self.frame_2) self.miniplayer_button.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) self.miniplayer_button.setStyleSheet( "QPushButton[mini=false]{image : url(icon_sets/standard_player/standard.png) }\n" # "QPushButton[mini=false]:hover{ image:url(icon_sets/standard_player/.png) }\n" "QPushButton[mini=true]{image : url(icon_sets/mini_player/mini.png) }\n" # "QPushButton[mini=true]:hover{ image:url(icon_sets/mini_player/.png) }\n" ) self.miniplayer_button.setProperty("mini", False) self.miniplayer_button.setContentsMargins(0, 0, 0, 0) self.miniplayer_button.setText("") self.miniplayer_button.setObjectName("miniplayer_button") self.horizontalLayout_4.addWidget(self.miniplayer_button) self.open_File_button = QtWidgets.QPushButton(self.frame_2) self.open_File_button.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) self.open_File_button.setStyleSheet( "QPushButton{image:url(icon_sets/new_file/new_file.png);width:22px;height:22px }\n" ) self.open_File_button.setText("") self.open_File_button.setObjectName("playback_button") self.horizontalLayout_4.addWidget(self.open_File_button) spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.horizontalLayout_4.addItem(spacerItem1) self.screenshot_button = QtWidgets.QPushButton(self.frame_2) self.screenshot_button.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) self.screenshot_button.setStyleSheet( "QPushButton{image : url(icon_sets/snapshot/snapshot.png);width:22px;height:22px} \n" ) self.screenshot_button.setText("") self.screenshot_button.setObjectName("screenshot_button") self.horizontalLayout_4.addWidget(self.screenshot_button) ''' Video Setting button for Video subs and dubs''' # self.video_setting_button = QtWidgets.QPushButton(self.frame_2) # self.video_setting_button.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) # self.video_setting_button.setText("") # self.video_setting_button.setIconSize(QtCore.QSize(22, 22)) # self.video_setting_button.setStyleSheet("QPushButton{image : url(icon_sets/video_setting/video_settings.png) }\n" # ) # self.video_setting_button.setObjectName("video_setting_button") # self.horizontalLayout_4.addWidget(self.video_setting_button) self.setting_button = QtWidgets.QPushButton(self.frame_2) self.setting_button.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) self.setting_button.setStyleSheet( "QPushButton{image : url(icon_sets/settings/settings.png) }\n") self.setting_button.setText("") self.setting_button.setObjectName("setting_button") self.horizontalLayout_4.addWidget(self.setting_button) self.Quality_box = QtWidgets.QComboBox(self.frame_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.Quality_box.sizePolicy().hasHeightForWidth()) self.Quality_box.setSizePolicy(sizePolicy) self.Quality_box.setStyleSheet( "QComboBox\n" " {\n" " border-image :url(icon_sets/quality/border.png);\n" " color: #fcffff;\n" " font-size: 8pt;\n" " font-weight: bold;\n" # " width: 41px;\n" " background-color: #000000;\n" " }\n" " QComboBox QAbstractItemView \n" " {\n" " background: #fcffff;\n" " border: 2px solid darkgray;\n" " selection-background-color: #000000;\n" " }\n" "QComboBox::drop-down {\n" " border: 0px;\n" " subcontrol-origin: padding;\n" " subcontrol-position: top right;\n" "\n" " border-top-right-radius: 3px;\n" " border-bottom-right-radius: 3px;\n" "}\n") self.Quality_box.setIconSize(QtCore.QSize(0, 0)) self.Quality_box.setDuplicatesEnabled(False) self.Quality_box.setObjectName("comboBox") self.Quality_box.addItem(" -") self.horizontalLayout_4.addWidget(self.Quality_box) self.volume_button = QtWidgets.QPushButton(self.frame_2) self.volume_button.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) self.volume_button.setText("") icon9 = QtGui.QIcon() icon9.addPixmap(QtGui.QPixmap("icon_sets/volume/volume1.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.volume_button.setIcon(icon9) self.volume_button.setIconSize(QtCore.QSize(22, 22)) self.volume_button.setCheckable(True) # self.volume_button.setFlat(True) self.volume_button.setObjectName("volume_button") self.horizontalLayout_4.addWidget(self.volume_button) self.volumeslider = VolSlider(self.frame_2) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.volumeslider.sizePolicy().hasHeightForWidth()) self.volumeslider.setSizePolicy(sizePolicy) self.volumeslider.setAutoFillBackground(False) self.volumeslider.setStyleSheet( "QSlider::handle:horizontal \n" " {\n" " background: transparent;\n" " width: 5px;\n" " }\n" "QSlider::groove:horizontal {\n" " border: 1px solid #444444;\n" " height: 5px;\n" " background: qlineargradient(y1: 0, y2: 1,stop: 0 grey, stop: 1.0 grey);\n" "}\n" " QSlider::sub-page:horizontal {\n" " background:qlineargradient( y1: 0, y2: 1,\n" " stop: 0 #42a6db, stop: 1 #0074e0); \n" " border: 1px solid #777;\n" " height: 5px;\n" "}\n" "QSlider::handle:horizontal:hover {\n" " background: black;\n" " height: 5px;\n" " width: 5px;\n" " border: 1px solid #0074e0;\n" " }\n" "QSlider::sub-page:horizontal:disabled{background:qlineargradient( y1: 0, y2: 1,\n" " stop: 0 #909090, stop: 1 #A8A8A8 );}\n" "") self.volumeslider.setOrientation(QtCore.Qt.Horizontal) self.volumeslider.setObjectName("volume_slider") self.horizontalLayout_4.addWidget(self.volumeslider) self.volume_percentage = QtWidgets.QLabel(self.frame_2) self.volume_percentage.setStyleSheet( "QLabel\n" " {\n" " font: 7pt \"Arial Rounded MT Bold\";\n" " color: white;\n" " border: 0px solid #076100;\n" " }") self.volume_percentage.setObjectName("volume_status") self.volume_percentage.setMinimumWidth(35) self.volume_percentage.setText(" 75 %") self.horizontalLayout_4.addWidget(self.volume_percentage) self.verticalLayout.addWidget(self.frame_2) self.frame = QtWidgets.QFrame(self.mainFrame) font = QtGui.QFont() font.setFamily("Lucida Console") self.frame.setFont(font) self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) self.frame.setFrameShadow(QtWidgets.QFrame.Raised) self.frame.setObjectName("frame") self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.frame) self.horizontalLayout_5.setObjectName("horizontalLayout_5") sizegrip_2 = QtWidgets.QSizeGrip(Form) sizegrip_2.setStyleSheet("image:url(icon_sets/.png)") self.horizontalLayout_5.addWidget( sizegrip_2, 0, QtCore.Qt.AlignBottom | QtCore.Qt.AlignLeft) # self.enter_url_label = QtWidgets.QLabel(self.frame) # font = QtGui.QFont() # font.setFamily("Arial Rounded MT Bold") # font.setPointSize(8) # font.setBold(False) # font.setItalic(False) # font.setWeight(50) # self.enter_url_label.setFont(font) # self.enter_url_label.setStyleSheet("QLabel\n" # " {\n" # " \n" # " font: 8pt \"Arial Rounded MT Bold\";\n" # " color: white;\n" # " border: 0px solid #076100;\n" # " }") # self.enter_url_label.setObjectName("enter_url_label") # self.horizontalLayout_5.addWidget(self.enter_url_label) self.url_box = QtWidgets.QComboBox(self.frame) sizePolicy = QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(200) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.url_box.sizePolicy().hasHeightForWidth()) self.url_box.setSizePolicy(sizePolicy) self.url_box.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.url_box.setMouseTracking(False) self.url_box.setAcceptDrops(False) self.url_box.setWhatsThis("") self.url_box.setAccessibleDescription("") self.url_box.setAutoFillBackground(True) self.url_box.setStyleSheet("QComboBox\n" " {\n" " border: 2px solid #0074e0;\n" " color: #fcffff;\n" " font-size: 8pt;\n" " font-weight: bold;\n" " background-color: #000000;\n" " border-radius: 6px;\n" " }\n" " QComboBox QAbstractItemView \n" " {\n" " background: #fcffff;\n" " border: 2px solid darkgray;\n" " selection-background-color: #000000;\n" " }\n" "QComboBox::down-arrow {\n" " image: url(icon_sets/url/url4.png)\n" "}\n" "QComboBox::drop-down {\n" " subcontrol-origin: padding;\n" " subcontrol-position: top right;\n" " width: 20px;\n" " \n" " border-top-right-radius: 3px;\n" " border-bottom-right-radius: 3px;\n" "}") self.url_box.setInputMethodHints(QtCore.Qt.ImhUrlCharactersOnly) self.url_box.setEditable(True) self.url_box.setCurrentText("") self.url_box.setMaxVisibleItems(100) self.url_box.setMaxCount(100) self.url_box.setInsertPolicy(QtWidgets.QComboBox.InsertAtCurrent) self.url_box.setSizeAdjustPolicy( QtWidgets.QComboBox.AdjustToContentsOnFirstShow) self.url_box.setMinimumContentsLength(2) self.url_box.setIconSize(QtCore.QSize(20, 20)) self.url_box.setDuplicatesEnabled(False) self.url_box.setFrame(True) self.url_box.setObjectName("url_box") self.horizontalLayout_5.addWidget(self.url_box) self.verticalLayout.addWidget(self.frame) sizegrip_1 = QtWidgets.QSizeGrip(Form) sizegrip_1.setStyleSheet( "image:url(icon_sets/size.png);width:15; height:18;") self.horizontalLayout_5.addWidget( sizegrip_1, 0, QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight) self.verticalLayout_2.addWidget(self.mainFrame) self.retranslateUi(Form) self.url_box.setCurrentIndex(-1) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Q-Stream Player")) self.Player_name.setText(_translate("Form", "Q-Stream Player")) self.time_status.setText(_translate("Form", "00:00:00")) self.backslash.setText(_translate("Form", "/")) self.duration_status.setText(_translate("Form", "00:00:00")) # self.enter_url_label.setText(_translate("Form", statusList[4])) # Set intial Volume self.volumeslider.setRange(0, 100) self.volumeslider.setValue(75) self.mediaPlayer.setVolume(75) self.position_slider.setRange(0, 100) # connecting buttons self.miniplayer_button.clicked.connect(self.setupMiniPlayer) self.position_slider.sliderMoved.connect(self.setPosition) self.position_slider.sliderMoved.connect(self.handleLabel) self.volume_button.clicked.connect(self.mute) self.volumeslider.valueChanged.connect(self.setVolume) self.screenshot_button.clicked.connect(self.screenshot) self.playback_button.clicked.connect(self.stopplayback) self.always_on_top_button.clicked.connect(self.checkOnTop) self.play_button.clicked.connect(self.play) self.open_File_button.clicked.connect(self.openFile) # self.video_setting_button.clicked.connect(self.handleQuality) self.setting_button.clicked.connect(self.handleSetting) self.Quality_box.currentTextChanged.connect(self.handleQuality) self.cross_button.clicked.connect(self.exit) self.maximize_button.clicked.connect(self.max) self.minimize_button.clicked.connect(self.min) # Shortcuts shortcut = QShortcut(QKeySequence('Esc'), self.video_playback) shortcut.activated.connect(self.EscFun) shortcut = QShortcut(QKeySequence('Space'), self.video_playback) shortcut.activated.connect(self.play) shortcut = QShortcut(QKeySequence('f'), self.video_playback) shortcut.activated.connect(self.fullscreen_video) shortcut = QShortcut(QKeySequence('c'), self.video_playback) shortcut.activated.connect(self.setupMiniPlayer) shortcut = QShortcut(QKeySequence('o'), self.video_playback) shortcut.activated.connect(self.openFile) shortcut = QShortcut(QKeySequence('a'), self.video_playback) shortcut.activated.connect(self.checkOnTop) shortcut = QShortcut(QKeySequence("Return"), self.video_playback) shortcut.activated.connect(self.playOnline) shortcut = QShortcut(QKeySequence('m'), self.video_playback) shortcut.activated.connect(self.mute) shortcut = QShortcut(QKeySequence(Qt.Key_Right), self.video_playback) shortcut.activated.connect(self.forwardSlider) shortcut = QShortcut(QKeySequence(Qt.Key_Left), self.video_playback) shortcut.activated.connect(self.backSlider) self.volupshortcut = QShortcut(QKeySequence(Qt.Key_Up), self.video_playback) self.volupshortcut.activated.connect(self.volumeUp) self.voldownshortcut = QShortcut(QKeySequence(Qt.Key_Down), self.video_playback) self.voldownshortcut.activated.connect(self.volumeDown) shortcut = QShortcut(QKeySequence(Qt.ControlModifier + Qt.Key_Right), self.video_playback) shortcut.activated.connect(self.forwardSlider10) shortcut = QShortcut(QKeySequence(Qt.ControlModifier + Qt.Key_Left), self.video_playback) shortcut.activated.connect(self.backSlider10) shortcut = QShortcut(QKeySequence(Qt.AltModifier + Qt.Key_Left), self.video_playback) shortcut.activated.connect(self.backSlider5) shortcut = QShortcut(QKeySequence(Qt.AltModifier + Qt.Key_Right), self.video_playback) shortcut.activated.connect(self.forwardSlider5) # loading history to Url Box items = self.load() self.url_box.addItems(items) self.url_box.setCurrentIndex(-1) # icon size # iconSize = QSize(5,5) # self.play_button.setIconSize(iconSize) # self.playback_button.setIconSize(iconSize) # self.screenshot_button.setIconSize(iconSize) # self.always_on_top_button.setIconSize(iconSize) # self.miniplayer_button.setIconSize(iconSize) # self.setting_button.setIconSize(iconSize) # self.volume_button.setIconSize(iconSize) # button size btnSize = QSize(22, 22) self.play_button.setMaximumSize(btnSize) self.playback_button.setMaximumSize(btnSize) self.screenshot_button.setMaximumSize(btnSize) self.always_on_top_button.setMaximumSize(btnSize) self.miniplayer_button.setMaximumSize(btnSize) # self.video_setting_button.setMaximumSize(btnSize) self.setting_button.setMaximumSize(btnSize) self.volume_button.setMaximumSize(btnSize) # set margin self.horizontalLayout.setContentsMargins(10, 5, 9, 0) self.horizontalLayout_4.setContentsMargins(9, 0, 9, 0) self.horizontalLayout_4.setSpacing(4) self.horizontalLayout_5.setContentsMargins(0, 5, 5, 5) self.horizontalLayout_6.setContentsMargins(9, 0, 9, 0) # Media player settings self.mediaPlayer.setVideoOutput(self.video_playback) self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.positionChanged.connect(self.handleLabel) self.mediaPlayer.durationChanged.connect(self.durationChanged) def handleLabel(self): self.time_status.clear() mtime = QtCore.QTime(0, 0, 0, 0) self.time = mtime.addMSecs(self.mediaPlayer.position()) self.time_status.setText(self.time.toString()) def hide_all(self): self.frame_3.close() # self.frame.close() self.url_box.close() self.playback_button.close() self.screenshot_button.close() self.Quality_box.close() self.volume_button.close() # self.video_setting_button.close() # self.setting_button.close() def show_all(self): self.frame_3.show() # self.frame.show() self.url_box.show() self.playback_button.show() self.screenshot_button.show() self.Quality_box.show() self.volume_button.show() # self.video_setting_button.show() self.setting_button.show() def checkOnTop(self): self.isOnTop = not self.isOnTop if self.isOnTop: self.always_on_top_button.setProperty("top", True) self.always_on_top_button.setStyle( self.always_on_top_button.style()) Form.setWindowFlags(Form.windowFlags() | QtCore.Qt.WindowStaysOnTopHint) else: self.always_on_top_button.setProperty("top", False) self.always_on_top_button.setStyle( self.always_on_top_button.style()) Form.setWindowFlags(Form.windowFlags() & ~QtCore.Qt.WindowStaysOnTopHint) Form.show() def miniProperty(self): self.video_playback.setMinimumSize(QSize(200, 100)) self.video_playback.resize(QSize(550, 270)) Form.resize(QSize(550, 270)) Form.setMinimumSize(QSize(200, 175)) self.mainFrame.setMinimumSize(QSize(200, 60)) self.mainFrame.resize(QSize(200, 53)) def standardProperty(self): self.video_playback.setMinimumSize(QSize(200, 100)) self.mainFrame.setMinimumSize(QSize(200, 82)) Form.setMinimumSize(QSize(200, 202)) Form.resize(550, 369) def setupMiniPlayer(self): self.isMini = not self.isMini if self.isMini: self.miniplayer_button.setProperty("mini", True) self.miniplayer_button.setStyle(self.miniplayer_button.style()) self.hide_all() self.miniProperty() else: self.miniplayer_button.setProperty("mini", False) self.miniplayer_button.setStyle(self.miniplayer_button.style()) self.standardProperty() self.show_all() def load(self): scorefile = "db.bat" if os.path.exists(scorefile): with open(scorefile, 'rb') as sf: scores = pickle.load(sf) else: scores = [] with open(scorefile, "wb") as sf: pickle.dump(scores, sf) return scores def scor_func(self, url): scorefile = "db.bat" if os.path.exists(scorefile): with open(scorefile, 'rb') as sf: scores = pickle.load(sf) else: scores = [] scores.append(url) with open(scorefile, "wb") as sf: if len(scores) > 100: print("here", scores) scores = scores[1:] pickle.dump(scores, sf) return scores def mute(self): if self.mediaPlayer.isMuted(): print('[ ! Full Volume]') self.mediaPlayer.setMuted(False) icon9 = QtGui.QIcon() icon9.addPixmap(QtGui.QPixmap("icon_sets/volume/volume1.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.volume_button.setIcon(icon9) self.volume_button.setIconSize(QSize(22, 22)) self.volumeslider.setEnabled(True) # shortcut Enable self.volupshortcut.setEnabled(True) self.voldownshortcut.setEnabled(True) else: print('[ ! Mute Volume]') self.mediaPlayer.setMuted(True) icon9 = QtGui.QIcon() icon9.addPixmap(QtGui.QPixmap("icon_sets/volume/volume2.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.volume_button.setIcon(icon9) self.volume_button.setIconSize(QSize(22, 22)) self.volumeslider.setEnabled(False) # shrotcut disable self.volupshortcut.setEnabled(False) self.voldownshortcut.setEnabled(False) def play(self): if self.mediaPlayer.isVideoAvailable(): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: print("[ ! PAUSE PRESSED ]") self.mediaPlayer.pause() else: print("[ ! PLAY PRESSED ]") self.mediaPlayer.play() def playOnline(self): if self.url_box.currentText() != '': print('[ ! GETTING VIDEO ONLINE ]') fileName = self.url_box.currentText() res = requests.get('https://mediaplayerserver.herokuapp.com/', params={"key": fileName}) try: self.streams = json.loads(res.text) try: self.mediaPlayer.setMedia( QMediaContent(QUrl(self.streams['best']))) self.play_video() self.isOnline = True self.addQuality() if self.url_box.findText(fileName, Qt.MatchExactly) < 0: self.url_box.addItem(fileName) self.scor_func(fileName) except KeyError: print("[ ! Error Video Not Supported By platform]") except json.JSONDecodeError: print("[ ! Error NoPluginError]") finally: self.url_box.setCurrentText("") def openFile(self): print('[ ! OPEN FILE ]') username = getpass.getuser() if sys.platform == 'win32': path = 'C:/Users/' + username + '/Videos/' elif sys.platform == 'linux' or sys.platform == 'Darwin': path = '/home/' + username + '/Videos/' formats = str.join(' ', [ '*.%s' % str(fmt).strip('b').strip("'") for fmt in QtGui.QMovie.supportedFormats() ]) fileName, _ = QFileDialog.getOpenFileName( self.video_playback, "Select media file", path, "Video Files (*.mp4 *.flv *.ts *.mts *.avi *.mkv)") if fileName != '': self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile(fileName))) self.play_video() # def decodeLink(self,url): # try: # streams = streamlink.streams(url) # keys = [k for k in streams.keys()] # data = dict() # for k in keys: # data[k] = streams[k].url # return data # except streamlink.NoPluginError: # return None # def playOnline(self): # if self.url_box.currentText() != '': # print('[ ! GETTING VIDEO ONLINE ]') # fileName = self.url_box.currentText() # self.streams = self.decodeLink(fileName) # try: # self.mediaPlayer.setMedia(QMediaContent(QUrl(self.streams['best']))) # self.play_video() # self.isOnline = True # self.addQuality() # if self.url_box.findText(fileName, Qt.MatchExactly) < 0: # self.url_box.addItem(fileName) # self.scor_func(fileName) # except KeyError: # print("[ ! Error Video Not Supported By platform]") # finally: # self.url_box.setCurrentText("") def play_video(self): print('[ ! PLAYING VIDEO ]') self.play_button.setEnabled(True) if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() else: self.mediaPlayer.play() def mediaStateChanged(self, state): print('[ ! CHANGING MEDIA STATE ]') if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.play_button.setProperty('play', False) self.play_button.setStyle(self.play_button.style()) else: self.play_button.setProperty('play', True) self.play_button.setStyle(self.play_button.style()) def stopplayback(self): print('[ ! STOP PLAYBACK VIDEO ]') self.mediaPlayer.stop() self.play_video() def positionChanged(self, position): print('[ ! POSITION CHANGED ]') self.position_slider.setValue(position) def durationChanged(self, duration): print('[ ! DURATION CHANGED ]') self.position_slider.setRange(0, duration) self.duration_status.clear() mtime = QtCore.QTime(0, 0, 0, 0) time = mtime.addMSecs(self.mediaPlayer.duration()) self.duration_status.setText(time.toString()) def setPosition(self, position): print('[ ! POSITION SET ]') self.mediaPlayer.setPosition(position) def setVolumePos(self, remain): print('[ ! REMANING VOLUME ]') print(remain) self.volumeslider.setRange(remain, 100) def setVolume(self, vol): print('[ ! SET VOLUME ]') print("set volume = " + str(vol)) if vol >= 0 and vol <= 100: self.volume_percentage.setText(" " + str(vol) + " %") if vol <= 0: icon9 = QtGui.QIcon() icon9.addPixmap(QtGui.QPixmap("icon_sets/volume/volume2.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.volume_button.setIcon(icon9) else: icon9 = QtGui.QIcon() icon9.addPixmap(QtGui.QPixmap("icon_sets/volume/volume1.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.volume_button.setIcon(icon9) self.volumeslider.setValue(vol) self.mediaPlayer.setVolume(vol) def screenshot(self): print('[ ! SCREENSHOT ]') wincen = Form.geometry() topX = wincen.topLeft().x() topY = wincen.topLeft().y() geo = self.video_playback.geometry() image = pyautogui.screenshot(region=(topX, topY + 38, geo.width(), geo.height() - 35)) filename = "screenshot" + str(uuid.uuid4()) + ".png" username = getpass.getuser() if sys.platform == 'win32': path = 'C:/Users/' + username + '/Pictures/' + filename elif sys.platform == 'linux' or sys.platform == 'Darwin': path = '/home/' + username + '/Pictures/' + filename image.save(path) def EscFun(self): if self.video_playback.isFullScreen(): Form.show() self.video_playback.setFullScreen(False) def fullscreen_video(self): if self.mediaPlayer.isVideoAvailable(): if self.video_playback.isFullScreen(): self.video_playback.setFullScreen(False) print('[ ! Normal Screen ]') Form.show() else: print('[ ! Full Screen ]') self.video_playback.setFullScreen(True) Form.hide() def getFormat(self): li = [k for k in self.streams.keys()] for q in li: if q.startswith('audio'): li.remove(q) try: li.remove('audio_opus') except ValueError: pass return li def changeQuality(self, quality): pos = self.mediaPlayer.position() try: self.mediaPlayer.setMedia( QMediaContent(QUrl(self.streams[quality]))) except KeyError: pass self.setPosition(pos) self.mediaPlayer.play() def handleSetting(self): from setting import SettingDialog dlg = SettingDialog() dlg.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) dlg.exec_() def addQuality(self): self.Quality_box.clear() self.Quality_box.addItems(self.getFormat()) def handleQuality(self): if self.isOnline: self.changeQuality(self.Quality_box.currentText()) def forwardSlider(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() + 1000) def forwardSlider10(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() + 10000) def forwardSlider5(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() + 5000) def backSlider(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() - 1000) def backSlider10(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() - 10000) def backSlider5(self): self.mediaPlayer.setPosition(self.mediaPlayer.position() - 5000) def volumeUp(self): self.setVolume(self.mediaPlayer.volume() + 10) print("Volume: " + str(self.mediaPlayer.volume())) def volumeDown(self): self.setVolume(self.mediaPlayer.volume() - 10) print("Volume: " + str(self.mediaPlayer.volume())) def max(self): if not Form.isMaximized(): print("[! Window is Maximized]") Form.showMaximized() else: print("[! Window is Normal]") Form.showNormal() def min(self): if not Form.isMinimized(): print("[! Window is Minimized]") Form.showMinimized() else: print("[! Window is Normal]") Form.showNormal() def exit(self): self.mediaPlayer.stop() sys.exit()
class QgsFmvPlayer(QMainWindow, Ui_PlayerWindow): """ Video Player Class """ def __init__(self, iface, path, parent=None, meta_reader=None, pass_time=None, initialPt=None, isStreaming=False): """ Constructor """ super(QgsFmvPlayer, self).__init__(parent) self.setupUi(self) self.parent = parent self.iface = iface self.fileName = path self.initialPt = initialPt self.meta_reader = meta_reader self.isStreaming = isStreaming self.createingMosaic = False self.currentInfo = 0.0 self.data = None # Create Draw Toolbar self.DrawToolBar.addAction(self.actionMagnifying_glass) self.DrawToolBar.addSeparator() # Draw Polygon QToolButton self.toolBtn_DPolygon.setDefaultAction(self.actionDraw_Polygon) self.DrawToolBar.addWidget(self.toolBtn_DPolygon) # Draw Point QToolButton self.toolBtn_DPoint.setDefaultAction(self.actionDraw_Pinpoint) self.DrawToolBar.addWidget(self.toolBtn_DPoint) # Draw Point QToolButton self.toolBtn_DLine.setDefaultAction(self.actionDraw_Line) self.DrawToolBar.addWidget(self.toolBtn_DLine) self.DrawToolBar.addAction(self.actionRuler) self.DrawToolBar.addSeparator() # # Censure QToolButton # self.toolBtn_Cesure.setDefaultAction(self.actionCensure) # self.DrawToolBar.addWidget(self.toolBtn_Cesure) # self.DrawToolBar.addSeparator() # # # Object Tracking # self.DrawToolBar.addAction(self.actionObject_Tracking) self.toolBtn_Cesure.setVisible(False) # Hide Color Button self.btn_Color.hide() self.RecGIF = QMovie(":/imgFMV/images/record.gif") self.videoWidget.customContextMenuRequested[QPoint].connect( self.contextMenuRequested) self.menubarwidget.customContextMenuRequested[QPoint].connect( self.contextMenuBarRequested) self.duration = 0 self.playerMuted = False self.HasFileAudio = False self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.pass_time = pass_time self.player.setNotifyInterval(700) # Metadata Callback Interval self.playlist = QMediaPlaylist() self.player.setVideoOutput( self.videoWidget.videoSurface()) # Abstract Surface self.player.durationChanged.connect(self.durationChanged) self.player.positionChanged.connect(self.positionChanged) self.player.mediaStatusChanged.connect(self.statusChanged) self.player.stateChanged.connect(self.setCurrentState) self.playerState = QMediaPlayer.LoadingMedia self.playFile(path) self.sliderDuration.setRange(0, self.player.duration() / 1000) self.volumeSlider.setValue(self.player.volume()) self.volumeSlider.enterEvent = self.showVolumeTip self.metadataDlg = QgsFmvMetadata(parent=self, player=self) self.addDockWidget(Qt.RightDockWidgetArea, self.metadataDlg) self.metadataDlg.setMinimumWidth(500) self.metadataDlg.hide() self.converter = Converter() self.BitratePlot = CreatePlotsBitrate() def HasAudio(self, videoPath): """ Check if video have Metadata or not """ try: p = _spawn([ '-i', videoPath, '-show_streams', '-select_streams', 'a', '-preset', 'ultrafast', '-loglevel', 'error' ], t="probe") stdout_data, _ = p.communicate() if stdout_data == b'': qgsu.showUserAndLogMessage( QCoreApplication.translate( "QgsFmvPlayer", "This video doesn't have Audio ! ")) self.actionAudio.setEnabled(False) self.actionSave_Audio.setEnabled(False) return False return True except Exception as e: qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Audio check Failed! : "), str(e)) self.actionAudio.setEnabled(False) self.actionSave_Audio.setEnabled(False) def get_metadata_from_buffer(self, currentTime): """ Metadata CallBack """ try: # There is no way to spawn a thread and call after join() without blocking the video UI thread. # callBackMetadata can be as fast as possible, it will always create a small video lag every time meta are read. # To get rid of this, we fill a buffer (BufferedMetaReader) in the QManager with some Metadata in advance, # and hope they'll be ready to read here in a totaly non-blocking # way (increase the buffer size if needed in QManager). stdout_data = self.meta_reader.get(currentTime) # qgsu.showUserAndLogMessage( # "", "stdout_data: " + str(stdout_data) + " currentTime: " + str(currentTime), onlyLog=True) if stdout_data == 'NOT_READY': self.metadataDlg.menuSave.setEnabled(False) qgsu.showUserAndLogMessage( "", "Buffer value read but is not ready, increase buffer size. : ", onlyLog=True) return #Values need to be read, pause the video a short while elif stdout_data == 'BUFFERING': qgsu.showUserAndLogMessage("Buffering metadata...", "", duration=4, level=QGis.Info) self.player.pause() QTimer.singleShot(2500, lambda: self.player.play()) return elif stdout_data == b'' or len(stdout_data) == 0: self.metadataDlg.menuSave.setEnabled(False) qgsu.showUserAndLogMessage( "", "Buffer returned empty metadata, check pass_time. : ", onlyLog=True) return self.packetStreamParser(stdout_data) except Exception as inst: qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Metadata Buffer Failed! : "), str(inst)) def packetStreamParser(self, stdout_data): ''' Common packet process''' for packet in StreamParser(stdout_data): try: if isinstance(packet, UnknownElement): qgsu.showUserAndLogMessage( "Error interpreting klv data, metadata cannot be read.", "the parser did not recognize KLV data", level=QGis.Warning, onlyLog=True) continue data = packet.MetadataList() self.data = data if self.metadataDlg.isVisible( ): # Only add metada to table if this QDockWidget is visible (speed plugin) self.metadataDlg.menuSave.setEnabled(True) self.addMetadata(data) UpdateLayers(packet, parent=self, mosaic=self.createingMosaic) QApplication.processEvents() return except Exception: None # qgsu.showUserAndLogMessage(QCoreApplication.translate( # "QgsFmvPlayer", "Meta update failed! "), " Packet:" + str(packet) + ", error:" + str(inst), level=QGis.Warning) def callBackMetadata(self, currentTime, nextTime): """ Metadata CallBack """ try: port = int(self.fileName.split(':')[2]) t = callBackMetadataThread(cmds=[ '-i', self.fileName.replace(str(port), str( port + 1)), '-ss', currentTime, '-to', nextTime, '-map', 'data-re', '-preset', 'ultrafast', '-f', 'data', '-' ]) t.start() t.join(1) if t.is_alive(): t.p.terminate() t.join() qgsu.showUserAndLogMessage("", "callBackMetadataThread self.stdout: " + str(t.stdout), onlyLog=True) if t.stdout == b'': return self.packetStreamParser(t.stdout) except Exception as e: qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Metadata Callback Failed! : "), str(e)) def GetPacketData(self): ''' Return Current Packet data ''' return self.data def addMetadata(self, packet): ''' Add Metadata to List ''' self.clearMetadata() row = 0 for key in sorted(packet.keys()): self.metadataDlg.VManager.insertRow(row) self.metadataDlg.VManager.setItem(row, 0, QTableWidgetItem(str(key))) self.metadataDlg.VManager.setItem( row, 1, QTableWidgetItem(str(packet[key][0]))) self.metadataDlg.VManager.setItem( row, 2, QTableWidgetItem(str(packet[key][1]))) row += 1 self.metadataDlg.VManager.setVisible(False) self.metadataDlg.VManager.resizeColumnsToContents() self.metadataDlg.VManager.setVisible(True) self.metadataDlg.VManager.verticalScrollBar().setSliderPosition( self.sliderPosition) def clearMetadata(self): ''' Clear Metadata List ''' try: self.sliderPosition = self.metadataDlg.VManager.verticalScrollBar( ).sliderPosition() self.metadataDlg.VManager.setRowCount(0) except Exception: None def saveInfoToJson(self): """ Save video Info to json """ out_json, _ = askForFiles(self, QCoreApplication.translate( "QgsFmvPlayer", "Save Json"), isSave=True, exts="json") if not out_json: return taskSaveInfoToJson = QgsTask.fromFunction( 'Save Video Info to Json Task', self.converter.probeToJson, fname=self.fileName, output=out_json, on_finished=self.finishedTask, flags=QgsTask.CanCancel) QgsApplication.taskManager().addTask(taskSaveInfoToJson) return def showVideoInfo(self): ''' Show default probe info ''' taskSaveInfoToJson = QgsTask.fromFunction( 'Show Video Info Task', self.converter.probeShow, fname=self.fileName, on_finished=self.finishedTask, flags=QgsTask.CanCancel) QgsApplication.taskManager().addTask(taskSaveInfoToJson) return def state(self): ''' Return Current State ''' return self.playerState def setCurrentState(self, state): ''' Set Current State ''' if state != self.playerState: self.playerState = state if state == QMediaPlayer.StoppedState: self.btn_play.setIcon(QIcon(":/imgFMV/images/play-arrow.png")) return def showColorDialog(self): ''' Show Color dialog ''' self.ColorDialog = ColorDialog(parent=self) self.ColorDialog.setWindowFlags(Qt.Window | Qt.WindowCloseButtonHint) # Fail if not uncheked self.actionMagnifying_glass.setChecked(False) self.ColorDialog.exec_() QApplication.processEvents() self.ColorDialog.contrastSlider.setValue(80) self.ColorDialog.contrastSlider.triggerAction( QAbstractSlider.SliderMove) return def createMosaic(self, value): ''' Function for create Video Mosaic ''' home = os.path.expanduser("~") qgsu.createFolderByName(home, "QGIS_FMV") homefmv = os.path.join(home, "QGIS_FMV") root, _ = os.path.splitext(os.path.basename(self.fileName)) qgsu.createFolderByName(homefmv, root) self.createingMosaic = value # Create Group CreateGroupByName() return def contextMenuBarRequested(self, point): ''' Context Menu Menu Bar ''' menu = QMenu('ToolBars') toolbars = self.findChildren(QToolBar) for toolbar in toolbars: action = menu.addAction(toolbar.windowTitle()) action.setCheckable(True) action.setChecked(toolbar.isVisible()) action.setObjectName(toolbar.windowTitle()) action.triggered.connect(lambda _: self.ToggleQToolBar()) menu.exec_(self.mapToGlobal(point)) return def ToggleQToolBar(self): ''' Toggle ToolBar ''' toolbars = self.findChildren(QToolBar) for toolbar in toolbars: if self.sender().objectName() == toolbar.windowTitle(): toolbar.toggleViewAction().trigger() def contextMenuRequested(self, point): ''' Context Menu Video ''' menu = QMenu('Video') # actionColors = menu.addAction( # QCoreApplication.translate("QgsFmvPlayer", "Color Options")) # actionColors.setShortcut("Ctrl+May+C") # actionColors.triggered.connect(self.showColorDialog) actionMute = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Mute/Unmute")) actionMute.setShortcut("Ctrl+Shift+U") actionMute.triggered.connect(self.setMuted) menu.addSeparator() actionAllFrames = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Extract All Frames")) actionAllFrames.setShortcut("Ctrl+Shift+A") actionAllFrames.triggered.connect(self.ExtractAllFrames) actionCurrentFrames = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Extract Current Frame")) actionCurrentFrames.setShortcut("Ctrl+Shift+Q") actionCurrentFrames.triggered.connect(self.ExtractCurrentFrame) menu.addSeparator() actionShowMetadata = menu.addAction( QCoreApplication.translate("QgsFmvPlayer", "Show Metadata")) actionShowMetadata.setShortcut("Ctrl+Shift+M") actionShowMetadata.triggered.connect(self.OpenQgsFmvMetadata) menu.exec_(self.mapToGlobal(point)) # Start Snnipet FILTERS def grayFilter(self, value): ''' Gray Video Filter ''' self.UncheckFilters(self.sender(), value) self.videoWidget.SetGray(value) self.videoWidget.UpdateSurface() return def MirrorHorizontalFilter(self, value): ''' Mirror Horizontal Video Filter ''' self.UncheckFilters(self.sender(), value) self.videoWidget.SetMirrorH(value) self.videoWidget.UpdateSurface() return def edgeFilter(self, value): ''' Edge Detection Video Filter ''' self.UncheckFilters(self.sender(), value) self.videoWidget.SetEdgeDetection(value) self.videoWidget.UpdateSurface() return def invertColorFilter(self, value): ''' Invert Color Video Filter ''' self.UncheckFilters(self.sender(), value) self.videoWidget.SetInvertColor(value) self.videoWidget.UpdateSurface() return def autoContrastFilter(self, value): ''' Auto Contrast Video Filter ''' self.UncheckFilters(self.sender(), value) self.videoWidget.SetAutoContrastFilter(value) self.videoWidget.UpdateSurface() return def monoFilter(self, value): ''' Filter Mono Video ''' self.UncheckFilters(self.sender(), value) self.videoWidget.SetMonoFilter(value) self.videoWidget.UpdateSurface() return def magnifier(self, value): ''' Magnifier Glass Utils ''' self.UncheckUtils(self.sender(), value) self.videoWidget.SetMagnifier(value) self.videoWidget.UpdateSurface() return def pointDrawer(self, value): ''' Draw Point ''' self.UncheckUtils(self.sender(), value) self.videoWidget.SetPointDrawer(value) self.videoWidget.UpdateSurface() def lineDrawer(self, value): ''' Draw Line ''' self.UncheckUtils(self.sender(), value) self.videoWidget.SetLineDrawer(value) self.videoWidget.UpdateSurface() def polygonDrawer(self, value): ''' Draw Polygon ''' self.UncheckUtils(self.sender(), value) self.videoWidget.SetPolygonDrawer(value) self.videoWidget.UpdateSurface() def ojectTracking(self, value): ''' Object Tracking ''' self.UncheckUtils(self.sender(), value) self.videoWidget.SetObjectTracking(value) self.videoWidget.UpdateSurface() def VideoRuler(self, value): ''' Video Ruler ''' self.UncheckUtils(self.sender(), value) self.videoWidget.SetRuler(value) if value: self.player.pause() self.btn_play.setIcon(QIcon(":/imgFMV/images/play-arrow.png")) else: self.videoWidget.ResetDrawRuler() self.player.play() self.btn_play.setIcon(QIcon(":/imgFMV/images/pause.png")) self.videoWidget.UpdateSurface() def VideoCensure(self, value): ''' Censure Video Parts''' self.UncheckUtils(self.sender(), value) self.videoWidget.SetCensure(value) self.videoWidget.UpdateSurface() return def UncheckUtils(self, sender, value): ''' Uncheck Utils Video ''' self.actionMagnifying_glass.setChecked(False) self.actionDraw_Pinpoint.setChecked(False) self.actionDraw_Line.setChecked(False) self.actionDraw_Polygon.setChecked(False) self.actionObject_Tracking.setChecked(False) self.actionRuler.setChecked(False) self.actionCensure.setChecked(False) self.videoWidget.RestoreDrawer() sender.setChecked(value) return def UncheckFilters(self, sender, value): ''' Uncheck Filters Video ''' self.actionGray.setChecked(False) self.actionInvert_Color.setChecked(False) self.actionMono_Filter.setChecked(False) self.actionCanny_edge_detection.setChecked(False) self.actionAuto_Contrast_Filter.setChecked(False) self.actionMirroredH.setChecked(False) self.videoWidget.RestoreFilters() sender.setChecked(value) return # End Snnipet FILTERS def isMuted(self): ''' Is muted video property''' return self.playerMuted def setMuted(self): ''' Muted video ''' if self.player.isMuted(): self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_up.png")) self.player.setMuted(False) self.volumeSlider.setEnabled(True) else: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_off.png")) self.player.setMuted(True) self.volumeSlider.setEnabled(False) return def stop(self): ''' Stop video''' # Prevent Error in a Video Utils.Disable Magnifier if self.actionMagnifying_glass.isChecked(): self.actionMagnifying_glass.trigger() # Stop Video self.fakeStop() return def volume(self): ''' Volume Slider ''' return self.volumeSlider.value() def setVolume(self, volume): ''' Tooltip and set Volume value and icon ''' self.player.setVolume(volume) self.showVolumeTip(volume) if 0 < volume <= 30: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_30.png")) elif 30 < volume <= 60: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_60.png")) elif 60 < volume <= 100: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_up.png")) elif volume == 0: self.btn_volume.setIcon(QIcon(":/imgFMV/images/volume_off.png")) def EndMedia(self): ''' Button end video position ''' if self.player.isVideoAvailable(): self.player.setPosition(self.player.duration()) self.videoWidget.update() return def StartMedia(self): ''' Button start video position ''' if self.player.isVideoAvailable(): self.player.setPosition(0) self.videoWidget.update() return def forwardMedia(self): ''' Button forward Video ''' forwardTime = int(self.player.position()) + 10 * 1000 if forwardTime > int(self.player.duration()): forwardTime = int(self.player.duration()) self.player.setPosition(forwardTime) def rewindMedia(self): ''' Button rewind Video ''' rewindTime = int(self.player.position()) - 10 * 1000 if rewindTime < 0: rewindTime = 0 self.player.setPosition(rewindTime) def AutoRepeat(self, checked): ''' Button AutoRepeat Video ''' if checked: self.playlist.setPlaybackMode(QMediaPlaylist.Loop) else: self.playlist.setPlaybackMode(QMediaPlaylist.Sequential) return def showVolumeTip(self, _): ''' Volume Slider Tooltip Trick ''' self.style = self.volumeSlider.style() self.opt = QStyleOptionSlider() self.volumeSlider.initStyleOption(self.opt) rectHandle = self.style.subControlRect(self.style.CC_Slider, self.opt, self.style.SC_SliderHandle) self.tip_offset = QPoint(5, 15) pos_local = rectHandle.topLeft() + self.tip_offset pos_global = self.volumeSlider.mapToGlobal(pos_local) QToolTip.showText(pos_global, str(self.volumeSlider.value()) + " %", self) def showMoveTip(self, currentInfo): ''' Player Silder Move Tooptip Trick ''' self.style = self.sliderDuration.style() self.opt = QStyleOptionSlider() self.sliderDuration.initStyleOption(self.opt) rectHandle = self.style.subControlRect(self.style.CC_Slider, self.opt, self.style.SC_SliderHandle) self.tip_offset = QPoint(5, 15) pos_local = rectHandle.topLeft() + self.tip_offset pos_global = self.sliderDuration.mapToGlobal(pos_local) tStr = _seconds_to_time(currentInfo) QToolTip.showText(pos_global, tStr, self) def durationChanged(self, duration): ''' Duration video change signal ''' duration /= 1000 self.duration = duration self.sliderDuration.setMaximum(duration) def positionChanged(self, progress): ''' Current Video position change ''' progress /= 1000 if not self.sliderDuration.isSliderDown(): self.sliderDuration.setValue(progress) self.updateDurationInfo(progress) def updateDurationInfo(self, currentInfo): ''' Update labels duration Info and CallBack Metadata ''' duration = self.duration self.currentInfo = currentInfo if currentInfo or duration: totalTime = _seconds_to_time(duration) currentTime = _seconds_to_time(currentInfo) tStr = currentTime + " / " + totalTime currentTimeInfo = _seconds_to_time_frac(currentInfo) # Get Metadata from buffer if not self.isStreaming: self.get_metadata_from_buffer(currentTimeInfo) else: qgsu.showUserAndLogMessage("", "Streaming on ", onlyLog=True) nextTime = currentInfo + self.pass_time / 1000 nextTimeInfo = _seconds_to_time_frac(nextTime) self.callBackMetadata(currentTimeInfo, nextTimeInfo) else: tStr = "" self.labelDuration.setText(tStr) def handleCursor(self, status): ''' Change cursor ''' if status in (QMediaPlayer.LoadingMedia, QMediaPlayer.BufferingMedia, QMediaPlayer.StalledMedia): self.setCursor(Qt.BusyCursor) else: self.unsetCursor() def statusChanged(self, status): ''' Signal Status video change ''' self.handleCursor(status) if status is QMediaPlayer.LoadingMedia or status is QMediaPlayer.StalledMedia or status is QMediaPlayer.InvalidMedia: self.videoAvailableChanged(False) elif status == QMediaPlayer.InvalidMedia: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", self.player.errorString()), level=QGis.Warning) self.videoAvailableChanged(False) else: self.videoAvailableChanged(True) def playFile(self, videoPath): ''' Play file from path ''' try: RemoveVideoLayers() RemoveGroupByName() # if "udp://" in videoPath: # host, port = videoPath.split("://")[1].split(":") # receiver = UDPClient(host, int(port), type="udp") # receiver.show() # self.close() # return # if "tcp://" in videoPath: # host, port = videoPath.split("://")[1].split(":") # receiver = UDPClient(host, port, type="tcp") # receiver.show() # self.close() # return self.fileName = videoPath self.playlist = QMediaPlaylist() if self.isStreaming: url = QUrl(videoPath) else: url = QUrl.fromLocalFile(videoPath) qgsu.showUserAndLogMessage("", "Added: " + str(url), onlyLog=True) self.playlist.addMedia(QMediaContent(url)) self.player.setPlaylist(self.playlist) self.setWindowTitle( QCoreApplication.translate("QgsFmvPlayer", 'Playing : ') + os.path.basename(os.path.normpath(videoPath))) CreateVideoLayers() self.clearMetadata() self.HasFileAudio = True if not self.HasAudio(videoPath): self.actionAudio.setEnabled(False) self.actionSave_Audio.setEnabled(False) self.HasFileAudio = False # Recenter map on video initial point if self.initialPt: rect = QgsRectangle(self.initialPt[1], self.initialPt[0], self.initialPt[1], self.initialPt[0]) self.iface.mapCanvas().setExtent(rect) self.iface.mapCanvas().refresh() self.playClicked(True) except Exception as e: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", 'Open Video File : '), str(e), level=QGis.Warning) def ReciconUpdate(self, _): ''' Record Button Icon Effect ''' self.btn_Rec.setIcon(QIcon(self.RecGIF.currentPixmap())) def StopRecordAnimation(self): '''Stop record gif animation''' self.RecGIF.frameChanged.disconnect(self.ReciconUpdate) self.RecGIF.stop() self.btn_Rec.setIcon(QIcon(":/imgFMV/images/record.png")) # TODO: Make in other thread def RecordVideo(self, value): ''' Cut Video ''' currentTime = _seconds_to_time(self.currentInfo) if value is False: self.endRecord = currentTime _, file_extension = os.path.splitext(self.fileName) out, _ = askForFiles(self, QCoreApplication.translate( "QgsFmvPlayer", "Save video record"), isSave=True, exts=file_extension[1:]) if not out: self.StopRecordAnimation() return p = _spawn([ '-i', self.fileName, '-ss', self.startRecord, '-to', self.endRecord, '-preset', 'ultrafast', '-c', 'copy', out ]) p.communicate() qgsu.showUserAndLogMessage( QCoreApplication.translate("QgsFmvPlayer", "Save file succesfully!")) self.StopRecordAnimation() else: self.startRecord = currentTime self.RecGIF.frameChanged.connect(self.ReciconUpdate) self.RecGIF.start() return def videoAvailableChanged(self, available): ''' Buttons for video available ''' # self.btn_Color.setEnabled(available) self.btn_CaptureFrame.setEnabled(available) self.gb_PlayerControls.setEnabled(available) return def toggleGroup(self, state): ''' Toggle GroupBox ''' sender = self.sender() if state: sender.setFixedHeight(sender.sizeHint().height()) else: sender.setFixedHeight(15) def fakeStop(self): '''self.player.stop() make a black screen and not reproduce it again''' self.player.pause() self.StartMedia() self.btn_play.setIcon(QIcon(":/imgFMV/images/play-arrow.png")) def playClicked(self, _): ''' Stop and Play video ''' if self.playerState in (QMediaPlayer.StoppedState, QMediaPlayer.PausedState): self.btn_play.setIcon(QIcon(":/imgFMV/images/pause.png")) # Uncheck Ruler self.videoWidget.ResetDrawRuler() self.actionRuler.setChecked(False) self.videoWidget.SetRuler(False) # Play Video self.player.play() elif self.playerState == QMediaPlayer.PlayingState: self.btn_play.setIcon(QIcon(":/imgFMV/images/play-arrow.png")) self.player.pause() def seek(self, seconds): '''Slider Move''' self.player.setPosition(seconds * 1000) self.showMoveTip(seconds) def convertVideo(self): '''Convert Video To Other Format ''' out, _ = askForFiles(self, QCoreApplication.translate( "QgsFmvPlayer", "Save Video as..."), isSave=True, exts=[ "mp4", "ogg", "avi", "mkv", "webm", "flv", "mov", "mpg", "mp3" ]) if not out: return # TODO : Make Correct format Conversion and embebed metadata info = self.converter.probeInfo(self.fileName) if info is not None: if self.HasFileAudio: audio_codec = info.audio.codec audio_samplerate = info.audio.audio_samplerate audio_channels = info.audio.audio_channels video_codec = info.video.codec video_width = info.video.video_width video_height = info.video.video_height video_fps = info.video.video_fps _, out_ext = os.path.splitext(out) if self.HasFileAudio: options = { 'format': out_ext[1:], 'audio': { 'codec': audio_codec, 'samplerate': audio_samplerate, 'channels': audio_channels }, 'video': { 'codec': video_codec, 'width': video_width, 'height': video_height, 'fps': video_fps } } else: options = { 'format': out_ext[1:], 'video': { 'codec': video_codec, 'width': video_width, 'height': video_height, 'fps': video_fps } } taskConvertVideo = QgsTask.fromFunction('Converting Video Task', self.converter.convert, infile=self.fileName, outfile=out, options=options, twopass=False, on_finished=self.finishedTask, flags=QgsTask.CanCancel) QgsApplication.taskManager().addTask(taskConvertVideo) def CreateBitratePlot(self): ''' Create video Plot Bitrate Thread ''' sender = self.sender().objectName() if sender == "actionAudio": taskactionAudio = QgsTask.fromFunction( 'Show Audio Bitrate', self.BitratePlot.CreatePlot, fileName=self.fileName, output=None, t='audio', on_finished=self.finishedTask, flags=QgsTask.CanCancel) QgsApplication.taskManager().addTask(taskactionAudio) elif sender == "actionVideo": taskactionVideo = QgsTask.fromFunction( 'Show Video Bitrate', self.BitratePlot.CreatePlot, fileName=self.fileName, output=None, t='video', on_finished=self.finishedTask, flags=QgsTask.CanCancel) QgsApplication.taskManager().addTask(taskactionVideo) elif sender == "actionSave_Audio": fileaudio, _ = askForFiles(self, QCoreApplication.translate( "QgsFmvPlayer", "Save Audio Bitrate Plot"), isSave=True, exts=[ "png", "pdf", "pgf", "eps", "ps", "raw", "rgba", "svg", "svgz" ]) if not fileaudio: return taskactionSave_Audio = QgsTask.fromFunction( 'Save Action Audio Bitrate', self.BitratePlot.CreatePlot, fileName=self.fileName, output=fileaudio, t='audio', on_finished=self.finishedTask, flags=QgsTask.CanCancel) QgsApplication.taskManager().addTask(taskactionSave_Audio) elif sender == "actionSave_Video": filevideo, _ = askForFiles(self, QCoreApplication.translate( "QgsFmvPlayer", "Save Video Bitrate Plot"), isSave=True, exts=[ "png", "pdf", "pgf", "eps", "ps", "raw", "rgba", "svg", "svgz" ]) if not filevideo: return taskactionSave_Video = QgsTask.fromFunction( 'Save Action Video Bitrate', self.BitratePlot.CreatePlot, fileName=self.fileName, output=filevideo, t='video', on_finished=self.finishedTask, flags=QgsTask.CanCancel) QgsApplication.taskManager().addTask(taskactionSave_Video) def finishedTask(self, e, result=None): """ Common finish task function """ if e is None: if result is None: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", 'Completed with no exception and no result '\ '(probably manually canceled by the user)'), level=QGis.Warning) else: if "Georeferencing" in result['task']: return qgsu.showUserAndLogMessage( QCoreApplication.translate( "QgsFmvPlayer", "Succesfully " + result['task'] + "!")) if "Bitrate" in result['task']: self.matplot = ShowPlot(self.BitratePlot.bitrate_data, self.BitratePlot.frame_count, self.fileName, self.BitratePlot.output) if result['task'] == 'Show Video Info Task': self.showVideoInfoDialog(self.converter.bytes_value) else: qgsu.showUserAndLogMessage(QCoreApplication.translate( "QgsFmvPlayer", "Failed " + result['task'] + "!"), level=QGis.Warning) raise e def ExtractAllFrames(self): """ Extract All Video Frames Task """ directory = askForFolder( self, QCoreApplication.translate("QgsFmvPlayer", "Save all Frames"), options=QFileDialog.DontResolveSymlinks | QFileDialog.ShowDirsOnly) if directory: taskExtractAllFrames = QgsTask.fromFunction( 'Save All Frames Task', self.SaveAllFrames, fileName=self.fileName, directory=directory, on_finished=self.finishedTask, flags=QgsTask.CanCancel) QgsApplication.taskManager().addTask(taskExtractAllFrames) return def SaveAllFrames(self, task, fileName, directory): vidcap = cv2.VideoCapture(fileName) length = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT)) count = 0 while not task.isCanceled(): _, image = vidcap.read() cv2.imwrite(directory + "\\frame_%d.jpg" % count, image) # save frame as JPEG file task.setProgress(count * 100 / length) count += 1 vidcap.release() cv2.destroyAllWindows() if task.isCanceled(): return None return {'task': task.description()} def ExtractCurrentFrame(self): """ Extract Current Frame Task """ image = self.videoWidget.GetCurrentFrame() output, _ = askForFiles(self, QCoreApplication.translate( "QgsFmvPlayer", "Save Current Frame"), isSave=True, exts=["png", "jpg", "bmp", "tiff"]) if not output: return taskCurrentFrame = QgsTask.fromFunction('Save Current Frame Task', self.SaveCapture, image=image, output=output, on_finished=self.finishedTask, flags=QgsTask.CanCancel) QgsApplication.taskManager().addTask(taskCurrentFrame) return def SaveCapture(self, task, image, output): ''' Save Current Frame ''' image.save(output) if task.isCanceled(): return None return {'task': task.description()} def OpenQgsFmvMetadata(self): """ Open Metadata Dock """ if self.metadataDlg is None: self.metadataDlg = QgsFmvMetadata(parent=self, player=self) self.addDockWidget(Qt.RightDockWidgetArea, self.metadataDlg) self.metadataDlg.show() else: self.metadataDlg.show() return def showVideoInfoDialog(self, outjson): """ Show Video Information Dialog """ view = QTreeView() model = QJsonModel() view.setModel(model) model.loadJsonFromConsole(outjson) self.VideoInfoDialog = QDialog(self) self.VideoInfoDialog.setWindowTitle( QCoreApplication.translate("QgsFmvPlayer", "Video Information : ") + self.fileName) self.VideoInfoDialog.setWindowIcon( QIcon(":/imgFMV/images/video-info.png")) self.verticalLayout = QVBoxLayout(self.VideoInfoDialog) self.verticalLayout.addWidget(view) view.expandAll() view.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.VideoInfoDialog.setWindowFlags(Qt.Window | Qt.WindowCloseButtonHint) self.VideoInfoDialog.setObjectName("VideoInfoDialog") self.VideoInfoDialog.resize(500, 400) self.VideoInfoDialog.show() def closeEvent(self, _): """ Close Event """ self.stop() self.parent._PlayerDlg = None self.parent.ToggleActiveFromTitle() RemoveVideoLayers() RemoveGroupByName() ResetData() try: self.metadataDlg.hide() except Exception: None try: self.matplot.close() except Exception: None # Restore Filters State self.videoWidget.RestoreFilters()
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 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 MediaPlayer(QMainWindow, Form): def __init__(self): Form.__init__(self) QMainWindow.__init__(self) self.setupUi(self) # Video Part self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface) self.player.setVideoOutput(self.videowidget) self.setWindowTitle(" Media Player") self.setAcceptDrops(True) # Define some Events self.videowidget.mouseDoubleClickEvent = self.Doubleclick_mouse self.wheelEvent = self.Scroll_mouse self.mouseReleaseEvent = self.MainWindow_Event self.videowidget.mouseMoveEvent = self.MousePos self.resizeEvent = self.main_size_Change self.lineEdit_Bookmark.setVisible(False) self.lineEdit_Bookmark.returnPressed.connect(self.save_Bookmarks) # threads part self.tag_thread = None self.search_Thread = None self.SearchAnimation = None self.BookMarkAnimation = None # Create Tags self.allTag = {} self.tag_Path = None # create Volume Slide self.Slider_Volume = Slider(self) self.Slider_Volume.setOrientation(Qt.Horizontal) self.horizontalLayout_3.addWidget(self.Slider_Volume, 0) self.Slider_Volume.setMaximumWidth(100) self.Slider_Volume.setMaximumHeight(30) # create Play Slider self.Slider_Play = Slider(self) self.Slider_Play.setOrientation(Qt.Horizontal) self.Slider_Play.resize(644, 22) self.horizontalLayout_4.addWidget(self.label_Time) self.horizontalLayout_4.addWidget(self.Slider_Play) self.horizontalLayout_4.addWidget(self.label_Duration) # To Apply initial Theme self.Setting = Setting.SettingWindow(self) Theme_module.Theme_apply(self.Setting) #Create Help self.helpW = Help.helpWindow(self) # PushButttons self.pushButton_Start.setEnabled(False) self.pushButton_Start.clicked.connect(self.start) self.pushButton_volume.setEnabled(False) self.pushButton_volume.clicked.connect(self.volumeOnOff) self.pushButton_stop.setEnabled(False) self.pushButton_stop.clicked.connect(self.stop) self.pushButton_stop.setToolTip("Stop (Ctrl+Z)") self.pushButton_next.setEnabled(False) self.pushButton_next.clicked.connect(self.next) self.pushButton_next.setToolTip("Next (PgUp)") self.pushButton_previous.setEnabled(False) self.pushButton_previous.clicked.connect(self.previous) self.pushButton_previous.setToolTip("Previous (PgDn)") self.pushButton_open.clicked.connect(self.Load_video) self.pushButton_open.setToolTip("Open (Ctrl+O)") self.pushButton_Search.clicked.connect(self.sch_icon_Event) self.pushButton_Search.setToolTip("Search (Ctrl+S)") self.pushButton_BookMark.setVisible(False) self.pushButton_BookMark.clicked.connect(self.add_BookMarks) self.pushButton_BookMark.setToolTip("Add BookMark (Ctrl+B)") self.pushButton_Setting.clicked.connect(self.Settingshow) self.pushButton_Setting.setToolTip("Setting") self.pushButton_Playlist.clicked.connect(self.Play_list) self.pushButton_Playlist.setToolTip("Play list") self.PlaylistW = Playlist.PlaylistWindow(self) # Create Tags of file Dockwidget self.pushButton_Tag_of_file.clicked.connect(self.Show_Tags_of_file) self.pushButton_Tag_of_file.setToolTip("Tags of file") self.DockWidget_Tags_of_file = QDockWidget("Tags of file", self) self.DockWidget_Tags_of_file.setVisible(False) self.DockWidget_Tags_of_file.setMinimumWidth(150) self.ComboBox_Tags_of_file = QComboBox(self) self.ListWidget_Tags_of_file = QListWidget() widget = QWidget() # Create Widget for Tags of file DockWidget layout = QVBoxLayout() # Create Layout for Tags of file DockWidget # Add Listwiget and ComboBox to layout layout.addWidget(self.ComboBox_Tags_of_file) layout.addWidget(self.ListWidget_Tags_of_file) widget.setLayout(layout) # Set layout on the widget # set Widget on Tags of file DockWidget self.DockWidget_Tags_of_file.setWidget(widget) self.addDockWidget(Qt.RightDockWidgetArea, self.DockWidget_Tags_of_file) self.ComboBox_Tags_of_file.activated.connect(self.ListWidget_Tag) self.ListWidget_Tags_of_file.itemActivated.connect(self.GoToTagtime) # Slider Play self.Slider_Play.setRange(0, 0) self.player.positionChanged.connect(self.Position_changed) self.player.durationChanged.connect(self.Duration_changed) self.Slider_Play.setUP_Slider.connect(self.Set_Position) self.Slider_Play.moveMent_Position.connect(self.slider_play_pos) # Label Time self.Slider_play_label = QLabel("Time", parent=self) self.Slider_play_label.resize(50, 20) self.Slider_play_label.setAlignment(Qt.AlignCenter) self.Slider_play_label.setVisible(False) # Slider Volume self.Slider_Volume.setRange(0, 0) self.Slider_Volume.setUP_Slider.connect(self.Set_volume) self.Slider_Volume.moveMent_Position.connect(self.slider_volume_pos) # Label Volume self.Slider_Volume_label = QLabel("Volume", parent=self) self.Slider_Volume_label.resize(30, 20) self.Slider_Volume_label.setAlignment(Qt.AlignCenter) self.Slider_Volume_label.setVisible(False) # Create listWidget for search part self.sch_listWidget = QListWidget(self) self.sch_listWidget.setVisible(False) self.search_lineEdit.setVisible(False) # Search signals self.sch_listWidget.itemDoubleClicked.connect( self.item_searchlist_Event) self.search_lineEdit.textChanged.connect(self.search_Tag) # Define Action for Tool Bar self.actionOpen.triggered.connect(self.Load_video) self.actionChange_Password.triggered.connect(self.menuBarAccout) self.actionDelete_Account.triggered.connect(self.menuBarAccout) self.actionHelp.triggered.connect(self.Help) self.actionLogOut.triggered.connect(self.Logout) self.actionExit.triggered.connect(lambda: self.close()) self.actionFullScreen.triggered.connect(self.fullscreen) self.actionSpeed.triggered.connect(self.menuBarSpeed) self.actionThme.triggered.connect(self.menuBarTheme) self.actionCreate_Tag.triggered.connect(self.menuBarCreateTag) self.actionOpen_Tags.triggered.connect(self.openTags) self.actionEdit_Tag.triggered.connect(self.menuBarEditTag) self.actionClose_Tag.triggered.connect(self.menuBarCloseTag) # Shortcut QShortcut(QKeySequence('Ctrl+B'), self).activated.connect(self.add_BookMarks) QShortcut(QKeySequence('Ctrl+Z'), self).activated.connect(self.stop) QShortcut(QKeySequence('Ctrl+M'), self).activated.connect(self.volumeOnOff) QShortcut(QKeySequence('Ctrl+S'), self).activated.connect(self.sch_icon_Event) QShortcut(QKeySequence('Ctrl+O'), self).activated.connect(self.Load_video) # For FullScreen part self.firstTime_fullscreen = True # close event to stop running thread self.closeEvent = self.Close # KeyPress Event def keyPressEvent(self, event): if event.key() == Qt.Key_PageDown and self.pushButton_next.isEnabled(): self.next() if event.key() == Qt.Key_PageUp and self.pushButton_previous.isEnabled( ): self.previous() if event.key() == Qt.Key_F5: self.fullscreen() if event.key() == Qt.Key_6: self.Move_Slider_play(self.width() * 4500 / self.player.duration()) if event.key() == Qt.Key_4: self.Move_Slider_play( (-1 * self.width() * 4500 / self.player.duration())) if event.key() == Qt.Key_8: self.Move_Slider_volume(+1) if event.key() == Qt.Key_2: self.Move_Slider_volume(-1) def menuBarTheme(self): # Open Theme Tab of Setting Window self.Setting.Account_changing = False self.Settingshow() self.Setting.Tab.setCurrentIndex(0) def Help(self): self.helpW.show() def menuBarSpeed(self): # Open Speed Tab of Setting Window self.Setting.Account_changing = False self.Settingshow() self.Setting.Tab.setCurrentIndex(1) def menuBarEditTag(self): # Open Tag Tab of Setting Window self.Setting.Account_changing = False self.Settingshow() self.Setting.Tab.setCurrentIndex(2) def menuBarAccout(self): # Open Account Tab of Setting Window self.Setting.Account_changing = False self.Settingshow() self.Setting.Tab.setCurrentIndex(3) def menuBarCreateTag(self): # create new Tag file if self.tag_Path: self.confirmCloseTag = confrimWin( self, Title="Create Tag", Text="Are you sure to close current tag and create new one") self.actionCreate_Tag.setEnabled(False) self.confirmCloseTag.show() else: dialog = QFileDialog(self, 'File tag', directory=os.getcwd()) _path = dialog.getSaveFileName(filter="*.csv")[0] try: if _path: self.tag_Path = _path open(self.tag_Path, "w") except: pass def menuBarCloseTag(self): # close current tags if self.tag_Path: self.confirmCloseTag = confrimWin( self, Title="Close Tag", Text="Are you sure to close current tag") self.confirmCloseTag.show() self.actionClose_Tag.setEnabled(False) else: self.confirmCloseTag = confrimWin(self, Title="Warning", Text="There is no tag to close") self.confirmCloseTag.show() self.actionClose_Tag.setEnabled(False) def fullscreen(self): self.DockWidget_Tags_of_file.setVisible(False) self.Slider_play_label.setVisible(False) self.Slider_Volume_label.setVisible(False) Full_screen.fullscreen(self) # Define some Events def MousePos(self, position): if self.isFullScreen(): Full_screen.MousePosition(self, position) self.Slider_play_label.setVisible(False) self.Slider_Volume_label.setVisible(False) def Doubleclick_mouse(self, position): if self.pushButton_Start.isEnabled(): self.start() def Scroll_mouse(self, event): if self.player.isAudioAvailable() or self.player.isVideoAvailable(): self.Set_volume( int(self.player.volume() + event.angleDelta().y() / 120)) def Move_Slider_play(self, move): if self.player.isAudioAvailable() or self.player.isVideoAvailable(): Now = self.player.position() * self.Slider_Play.width( ) / self.player.duration() self.Set_Position(Now + move) def Move_Slider_volume(self, move): if self.player.isAudioAvailable() or self.player.isVideoAvailable(): self.Set_volume(self.player.volume() + move) def Settingshow(self): # To show Setting window self.Setting.Tab.setCurrentIndex(0) self.Setting.lineEdit_CurrentPass.clear() self.Setting.lineEdit_NewPass.clear() self.Setting.lineEdit_ReNewpass.clear() self.Setting.label_finish.setVisible(False) self.Setting.label_NotMatch.setVisible(False) self.Setting.label_PassLong.setVisible(False) self.Setting.label_OldPass.setVisible(False) self.Setting.label_Wait.setVisible(False) self.Setting.label_Error.setVisible(False) self.Setting.Account_changing = False self.Setting.show() def start(self): # Start and Pause the player if self.player.state() == QMediaPlayer.PlayingState: # Stop self.player.pause() self.pushButton_Start.setEnabled(True) self.pushButton_Start.setIcon(QIcon('./Icons/play.png')) self.pushButton_Start.setToolTip("Play") else: # Start self.player.play() self.pushButton_Start.setEnabled(True) self.pushButton_Start.setIcon(QIcon('./Icons/pause.png')) self.pushButton_Start.setToolTip("Pause") self.pushButton_Start.setFocus(True) def stop(self): # Stop player if self.player.isAudioAvailable() or self.player.isVideoAvailable(): self.player.stop() self.pushButton_next.setEnabled(False) self.pushButton_previous.setEnabled(False) self.pushButton_volume.setEnabled(False) self.pushButton_Start.setEnabled(False) self.pushButton_stop.setEnabled(False) self.pushButton_BookMark.setVisible(False) self.label_Duration.setText("00:00:00") self.label_Time.setText("00:00:00") self.Slider_Volume.setEnabled(False) def next(self): # Play next file from playlist # If the playing file is in the playlist if self.windowTitle()[16:] in self.PlaylistW.Files.keys(): self.PlaylistW.listWidget_Playlist.setCurrentRow( list(self.PlaylistW.Files.keys()).index(self.windowTitle() [16:]) + 1) else: # If the playing file is not in the playlist self.PlaylistW.listWidget_Playlist.setCurrentRow( self.PlaylistW.listWidget_Playlist.currentRow() + 1) self.PlaylistW.spliter = len( str(self.PlaylistW.listWidget_Playlist.currentRow() + 1)) + 3 self.player.setMedia( QMediaContent( QUrl.fromLocalFile(self.PlaylistW.Files[ self.PlaylistW.listWidget_Playlist.currentItem().text() [self.PlaylistW.spliter:]]))) # Change title self.setWindowTitle( f" Media Player - {self.PlaylistW.listWidget_Playlist.currentItem().text()[self.PlaylistW.spliter:]}" ) currentText = self.PlaylistW.listWidget_Playlist.currentItem().text( )[self.PlaylistW.spliter:] index = self.ComboBox_Tags_of_file.findText(currentText) self.ComboBox_Tags_of_file.setCurrentIndex(index) self.set_TagonListwidget(".".join(currentText.split(".")[:-1])) self.start() # To handle Next button if self.PlaylistW.listWidget_Playlist.currentRow( ) == self.PlaylistW.listWidget_Playlist.count() - 1: self.pushButton_next.setEnabled(False) self.pushButton_previous.setEnabled(True) def previous(self): # Play previous file from playlist # If the playing file is in the playlist if self.windowTitle()[16:] in self.PlaylistW.Files.keys(): self.PlaylistW.listWidget_Playlist.setCurrentRow( list(self.PlaylistW.Files.keys()).index(self.windowTitle() [16:]) - 1) else: # If the playing file is not in the playlist self.PlaylistW.listWidget_Playlist.setCurrentRow( self.PlaylistW.listWidget_Playlist.currentRow()) self.PlaylistW.spliter = len( str(self.PlaylistW.listWidget_Playlist.currentRow() + 1)) + 3 self.player.setMedia( QMediaContent( QUrl.fromLocalFile(self.PlaylistW.Files[ self.PlaylistW.listWidget_Playlist.currentItem().text() [self.PlaylistW.spliter:]]))) self.setWindowTitle( f" Media Player - {self.PlaylistW.listWidget_Playlist.currentItem().text()[self.PlaylistW.spliter:]}" ) currentText = self.PlaylistW.listWidget_Playlist.currentItem().text( )[self.PlaylistW.spliter:] index = self.ComboBox_Tags_of_file.findText(currentText) self.ComboBox_Tags_of_file.setCurrentIndex(index) self.set_TagonListwidget(".".join(currentText.split(".")[:-1])) self.start() # To handle previous button if not self.PlaylistW.listWidget_Playlist.currentRow(): self.pushButton_previous.setEnabled(False) self.pushButton_next.setEnabled(True) def Load_video(self, filepath=None): # Load Files of directory # just mp4 ,mkv , mp3 if not filepath: try: # To read initial Directory from csvfile with open("./PlayListPart/Filepath.csv") as file: initial_FilePath = file.read() file_path, _ = QFileDialog.getOpenFileName( self, "Open video", directory=initial_FilePath, filter='*.mp4 *.mkv *.mp3') except: initial_FilePath = os.getcwd() file_path, _ = QFileDialog.getOpenFileName( self, "Open video", directory=initial_FilePath, filter='*.mp4 *.mkv *.mp3') else: initial_FilePath = None file_path = filepath if file_path: # To write initial Directory in csvfile if file_path.replace(file_path.split("/")[-1], "") != initial_FilePath: with open("./PlayListPart/Filepath.csv", 'w') as file: file.write(file_path.replace(file_path.split("/")[-1], "")) self.player.setMedia(QMediaContent(QUrl.fromLocalFile(file_path))) self.start() self.pushButton_volume.setEnabled(True) self.pushButton_volume.setIcon(QIcon('./Icons/unmute.png')) self.pushButton_volume.setToolTip("Mute (Ctrl+M)") if not self.isFullScreen(): self.pushButton_BookMark.setVisible(True) self.pushButton_stop.setEnabled(True) # Handle Volume Slider self.Slider_Volume.setEnabled(True) self.Slider_Volume.setRange(0, self.player.volume()) self.Slider_Volume.setValue(60) self.player.setVolume(60) # Create Playlist self.Setting.comboBox_Tag.clear() self.ComboBox_Tags_of_file.clear() self.PlaylistW.Create_Playlist(file_path) self.set_TagonListwidget(".".join( self.windowTitle()[16:].split(".")[:-1])) def Position_changed(self, position): if position > self.player.duration(): position = self.player.duration() if self.player.isAudioAvailable() or self.player.isVideoAvailable(): hour = position // (1000 * 3600) minute = (position % (1000 * 3600)) // (60 * 1000) second = ((position % (1000 * 3600)) % (60 * 1000)) // 1000 # Show Time self.label_Time.setText( f'{str(hour).zfill(2)}:{str(minute).zfill(2)}:{str(second).zfill(2)}' ) # Automatic Next if self.player.duration() and position == self.player.duration(): if self.pushButton_next.isEnabled(): self.next() else: self.stop() self.Slider_Play.setValue(position) def Duration_changed(self, duration): # To set file duration self.Slider_Play.setRange(0, duration) # convert millsec to xx:xx:xx format hour = duration // (1000 * 3600) minute = (duration % (1000 * 3600)) // (60 * 1000) second = ((duration % (1000 * 3600)) % (60 * 1000)) // 1000 self.label_Duration.setText( f'{str(hour).zfill(2)}:{str(minute).zfill(2)}:{str(second).zfill(2)}' ) self.Slider_Volume.setEnabled(True) def Set_Position(self, position): if self.Slider_Play.width() - 1 <= position: position = self.Slider_Play.width() - 1 self.player.pause() self.pushButton_Start.setEnabled(True) self.pushButton_Start.setIcon(QIcon('./Icons/play.png')) self.pushButton_Start.setToolTip("Paly") # if not (position < 0 and position > self.Slider_Play.width()): position = int(self.player.duration() * (position / self.Slider_Play.width())) self.Slider_Play.setValue(position) self.player.setPosition(position) def slider_play_pos(self, position): if self.player.isAudioAvailable() or self.player.isVideoAvailable(): # Convert Width of Slider to time Time = int(self.player.duration() * position / self.Slider_Play.width()) # convert millsec to xx:xx:xx format hour = Time // (1000 * 3600) minute = (Time % (1000 * 3600)) // (60 * 1000) second = ((Time % (1000 * 3600)) % (60 * 1000)) // 1000 self.Slider_play_label.setText( f'{str(hour).zfill(2)}:{str(minute).zfill(2)}:{str(second).zfill(2)}' ) if self.isFullScreen(): self.Slider_play_label.move(position + self.Slider_Play.x(), self.height() - 65) else: self.Slider_play_label.move(position + self.Slider_Play.x(), self.height() - 80) self.Slider_play_label.setVisible(True) def slider_volume_pos(self, position): if self.player.isAudioAvailable() or self.player.isVideoAvailable(): # Convert Width of Slider to volume volume = int(100 * position / self.Slider_Volume.width()) self.Slider_Volume_label.setText(f'{volume}%') if self.isFullScreen(): self.Slider_Volume_label.move( position + self.Slider_Volume.x() - 5, self.height() - 37) else: self.Slider_Volume_label.move( position + self.Slider_Volume.x() - 5, self.height() - 48) self.Slider_Volume_label.setVisible(True) def Set_volume(self, volume): if self.player.isAudioAvailable() or self.player.isVideoAvailable(): # Volume must be between 0 , 100 if 100 <= volume: volume = 100 elif volume <= 0: volume = 0 if not volume: self.player.setMuted(True) self.pushButton_volume.setIcon(QIcon('./Icons/mute.png')) self.pushButton_volume.setToolTip("UnMute (Ctrl+M)") else: self.player.setMuted(False) self.pushButton_volume.setIcon(QIcon('./Icons/unmute.png')) self.pushButton_volume.setToolTip("Mute (Ctrl+M)") self.Slider_Volume.setValue(volume) self.player.setVolume(volume) def volumeOnOff(self): """Mute and unmute""" if self.player.isAudioAvailable() or self.player.isVideoAvailable(): if self.player.isMuted(): self.player.setMuted(False) self.pushButton_volume.setIcon(QIcon('./Icons/unmute.png')) self.pushButton_volume.setToolTip("Mute (Ctrl+M)") else: self.player.setMuted(True) self.pushButton_volume.setIcon(QIcon('./Icons/mute.png')) self.pushButton_volume.setToolTip("UnMute (Ctrl+M)") # save bookmarks and updatae tag list widget def save_Bookmarks(self): if not self.tag_Path: error_addBookmark = confrimWin( self, Title="Warning", Text="First select or create a tag file and try again") error_addBookmark.show() return False try: # use add bookmark function to add bookmarks in tag part add_Bookmark( self.lineEdit_Bookmark.text() + "#" + tc.millis_to_format(self.player.position()), ".".join(self.windowTitle()[16:].split(".")[:-1]), self.tag_Path) # there isn't any tags for movie we want to add bookmark it if not ".".join( self.windowTitle()[16:].split(".")[:-1]) in self.allTag: self.allTag.update( {".".join(self.windowTitle()[16:].split(".")[:-1]): {}}) self.allTag[".".join( self.windowTitle()[16:].split(".")[:-1])].update({ self.lineEdit_Bookmark.text(): tc.millis_to_format(self.player.position()) }) self.set_TagonListwidget( self.windowTitle()[16:].split(".")[0]) # update tag listwidget # update combo box of edit and Main window self.pushButton_Start.setFocus(True) index = self.ComboBox_Tags_of_file.findText( self.windowTitle()[16:]) self.ComboBox_Tags_of_file.setCurrentIndex(index) index = self.Setting.comboBox_Tag.findText(self.windowTitle()[16:]) self.Setting.comboBox_Tag.setCurrentIndex(index) # **** except: pass self.lineEdit_Bookmark.clear() self.lineEdit_Bookmark.setVisible(False) def sch_icon_Event(self): # Show search lineEdit to search self.search_lineEdit.setFixedWidth(0) # Start Animation of search lineEdit self.SearchAnimation = Search_Animation(self) self.SearchAnimation.update_Animation.connect( self.Update_Search_Animation) self.pushButton_Search.setEnabled(False) self.SearchAnimation.start() self.search_lineEdit.setVisible(True) self.search_lineEdit.setFocus(True) def Update_Search_Animation(self, size): if size == -1: # Animation is finished self.pushButton_Search.setEnabled(True) else: # Animation is running self.search_lineEdit.setFixedWidth(size) def add_BookMarks(self): # Show Bookmark lineEdit to search if self.player.isVideoAvailable() or self.player.isAudioAvailable(): self.lineEdit_Bookmark.setFixedWidth(0) # Start Animation of search lineEdit self.BookMarkAnimation = BookMark_Animation(self) self.BookMarkAnimation.update_Animation.connect( self.Update_BookMark_Animation) self.pushButton_BookMark.setEnabled(False) self.BookMarkAnimation.start() self.lineEdit_Bookmark.setVisible(True) self.lineEdit_Bookmark.setFocus(True) def Update_BookMark_Animation(self, size): if size == -1: # Animation is finished self.pushButton_BookMark.setEnabled(True) else: # Animation is running self.lineEdit_Bookmark.setFixedWidth(size) def MainWindow_Event(self, type): # Click on MainWindow self.search_lineEdit.setText("") self.search_lineEdit.setVisible(False) self.lineEdit_Bookmark.setText("") self.lineEdit_Bookmark.setVisible(False) self.sch_listWidget.setVisible(False) self.sch_listWidget.clear() # handle main size change to set correct size to # search and bookmark line edits and searchlistwidgetd def main_size_Change(self, val): self.lineEdit_Bookmark.setFixedWidth(int(self.size().width() / 4)) self.search_lineEdit.setFixedWidth(int(self.size().width() / 4)) self.sch_listWidget.resize( # Handle size of search listwidget int(self.size().width() / 4), int((200 / 600) * self.size().height())) self.sch_listWidget.move( # Move search listwidget self.size().width() - self.pushButton_Search.geometry().width() - self.search_lineEdit.geometry().width() - 15, self.search_lineEdit.geometry().height() + self.search_lineEdit.pos().y() + self.menubar.geometry().height()) # Item clicked event in search tag part def item_searchlist_Event(self, item): session, tag = re.split(" -> ", item.text()) # get all session to compare that is selected tag in playlist or user select tag wrong all_sessions = list(self.PlaylistW.Files.keys()) # there is bug here if mp3 and mp4 has same name all_sessions = [ ".".join(item.split(".")[:-1]) for item in all_sessions ] if session in all_sessions: if session != ".".join(self.windowTitle()[16:].split(".")[:-1]): # Show confirm window to get accept user for change video self.confirmWin = confrimWin( self, session=session, tag_Text=tag, Text= f"Are you sure to change video to {session} from search") self.confirmWin.show() else: try: # concert time format to second for using in change position time_second = tc.to_second(self.allTag[session][tag]) self.change_Position(time_second) except: # handle unexcepted error! pass else: Warning_user_wrongTags = confrimWin( self, Title="Warning", Text="You have opened wrong tag files") Warning_user_wrongTags.show() # Create search listwidget and running thread to starting search def search_Tag(self, val): # Create QListWidget self.sch_listWidget.resize(int((200 / 800) * self.size().width()), int((200 / 600) * self.size().height())) self.sch_listWidget.move( self.search_lineEdit.x(), self.search_lineEdit.geometry().height() + self.search_lineEdit.pos().y() + self.menubar.geometry().height()) self.sch_listWidget.setVisible(True) if val == "": self.sch_listWidget.clear() # start search thread else: self.search_Thread = search_thread(self, self.allTag, val) self.search_Thread.update_schTag.connect(self.update_searchTags) self.search_Thread.start() # Update searchtags function to update search widget by searching instantly def update_searchTags(self, tagsDict): self.sch_listWidget.clear() # clear search list widget for session, Tags in tagsDict.items( ): # writing data on search listwidget for text in Tags: self.sch_listWidget.addItem(session + " -> " + text) def Logout(self): os.remove("LoginPart/User.csv") self.close() self.player.stop() self.Setting.close() self.PlaylistW.close() self.helpW.close() self.DockWidget_Tags_of_file.close() subprocess.call(['python', 'MediaPlayer.py']) # Start again def Play_list(self): # Open playlist # To show current Row for every time it is opened try: if self.PlaylistW.listWidget_Playlist.count(): self.PlaylistW.listWidget_Playlist.setCurrentRow( list(self.PlaylistW.Files.keys()).index( self.windowTitle()[16:])) except Exception as e: pass self.PlaylistW.show() # setPosition of playlist when it is opened self.PlaylistW.move( QtGui.QCursor().pos().x(), QtGui.QCursor().pos().y() - self.PlaylistW.size().height() - 25) def Show_Tags_of_file(self): self.sch_listWidget.setVisible(False) # To show Tags of file in DockWidget self.DockWidget_Tags_of_file.setVisible( not self.DockWidget_Tags_of_file.isVisible()) index = self.ComboBox_Tags_of_file.findText(self.windowTitle()[16:]) self.ComboBox_Tags_of_file.setCurrentIndex(index) # To Change features of the Dockwidget according Fullscreen if self.isFullScreen(): self.DockWidget_Tags_of_file.setFeatures( QDockWidget.DockWidgetClosable) Full_screen.Set_visible(self, False) else: # To Change features of the Dockwidget according Normalscreen self.DockWidget_Tags_of_file.setFeatures( QDockWidget.DockWidgetFloatable | QDockWidget.DockWidgetMovable) def ListWidget_Tag(self, index): """Tag combo box item clicked function""" videoName = ".".join( list(self.PlaylistW.Files.keys())[index].split(".")[:-1]) self.set_TagonListwidget(videoName, Setting_Tags=False) def openTags(self): """OpenTag in csv, pptx, docx format and start tag thread for reading data""" try: # To read initial Tag Directory from csvfile # if there is FilePath_Tag.csv with open("./tagPart/Filepath_Tag.csv") as file: initial_FilePath_Tag = file.read() _tag_Path, _ = QFileDialog.getOpenFileName( self, "Open Tag", directory=initial_FilePath_Tag, filter='*.csv *.docx *.pptx') except Exception as e: initial_FilePath_Tag = os.path.join(os.getcwd(), 'Tags') _tag_Path, _ = QFileDialog.getOpenFileName( self, "Open Tag", directory=initial_FilePath_Tag, filter='*.csv *.docx *.pptx') # if tagpath is correct we starting tag_thread to read tags from path if _tag_Path: # save filepath in csv file self.tag_Path = _tag_Path try: with open("./tagPart/Filepath_Tag.csv", 'w') as file: file.write( self.tag_Path.replace( self.tag_Path.split("/")[-1], "")) except Exception as e: pass Tagname = self.tag_Path.split("/")[-1] fileFormat = Tagname.split(".")[-1] self.tag_thread = read_Tag(self, self.tag_Path, fileFormat) self.tag_thread.Tag_Ready.connect(self.getTag) self.tag_thread.start() # Tag is ready to use # if tags ready we shoud save them in self.allTag variable to use them properly def getTag(self, tags): self.allTag = tags index = self.ComboBox_Tags_of_file.findText(self.windowTitle()[16:]) self.ComboBox_Tags_of_file.setCurrentIndex(index) self.set_TagonListwidget(".".join( self.windowTitle()[16:].split(".")[:-1])) # set tags on tag listwidget using vedio name # update tags def set_TagonListwidget(self, videoName, Setting_Tags=True, Media_Tags=True): if Media_Tags: self.ListWidget_Tags_of_file.clear() if Setting_Tags: self.Setting.Edit_tag_Listwidget.clear() try: if videoName in self.allTag: sessionTag = self.allTag[videoName] # sorted tags by time sessionTag = { text: time for text, time in sorted( sessionTag.items(), key=lambda item: tc.to_second(item[1])) } for text in sessionTag: # setting part tags by qtreewidget if Setting_Tags: item = QTreeWidgetItem( self.Setting.Edit_tag_Listwidget, [text, sessionTag[text]]) # media part tags by qlistwidget if Media_Tags: self.ListWidget_Tags_of_file.addItem( f'{self.ListWidget_Tags_of_file.count()+1} . {text}' ) except: pass # item clicked event to go to time correlate clicked tag in video # change time when clicking on tags in search part and main listwidget if tags def GoToTagtime(self, item): spliter = len(str(self.ListWidget_Tags_of_file.currentRow() + 1)) + 3 tag_Text = item.text()[spliter:] # change time if clicked on tags that belong to playing session if self.windowTitle()[16:] == self.ComboBox_Tags_of_file.currentText(): session = ".".join(self.windowTitle()[16:].split(".")[:-1]) try: # convert time to seconds using tc mudole(write by own) time_second = tc.to_second(self.allTag[session][tag_Text]) # using change position function to handle sliders and time self.change_Position(time_second) except: pass else: session = ".".join( self.ComboBox_Tags_of_file.currentText().split(".")[:-1]) self.confirmWin = confrimWin( self, session=session, tag_Text=tag_Text, Text=f"Are you sure to change video to {session}") self.confirmWin.show() def change_Position(self, time_second): # change slider position using item time self.Slider_Play.setValue(int(time_second) * 1000) # change video position using item time self.player.setPosition(int(time_second) * 1000) # change video function to change video when clicked on # tags that there is not in current movie's tags def change_Video(self, session): # there is bug here if user select mp3 file that has same name with mp4 similar file for key in self.PlaylistW.Files: if session + ".mp4" == key: return self._change_Video(key) elif session + ".mp3" == key: return self._change_Video(key) elif session + ".mkv" == key: return self._change_Video(key) return False def _change_Video(self, key): self.player.setMedia( QMediaContent(QUrl.fromLocalFile( self.PlaylistW.Files[key]))) # set video self.start() self.setWindowTitle(f" Media Player - {key}") # change title # update combo box of session in MediaPlayer index = self.ComboBox_Tags_of_file.findText(key) self.ComboBox_Tags_of_file.setCurrentIndex(index) # update combo box # update tags on setting and MediaPlayer window self.set_TagonListwidget(".".join(key.split(".")[:-1])) return True # close Event to stop runnig thread and preventing error def Close(self, val): if self.tag_thread: self.tag_thread.stop() if self.search_Thread: self.search_Thread.stop() if self.SearchAnimation: self.SearchAnimation.stop() if self.BookMarkAnimation: self.BookMarkAnimation.stop() # dragEnterEvent to hanle drag and drop option def dragEnterEvent(self, event): # pass if event.mimeData().hasUrls: event.accept() else: event.ignore() # dragMoveEvent to hanle drag and drop option def dragMoveEvent(self, event): if event.mimeData().hasUrls: event.accept() else: event.ignore() # dropEvent to hanle drag and drop option def dropEvent(self, event): if event.mimeData().hasUrls: event.setDropAction(Qt.CopyAction) # get droppen file path file_path = event.mimeData().urls()[0].toLocalFile() file_format = (file_path.split("/")[-1]).split(".")[-1] desired_format = ['mp4', 'mkv', 'mp3'] # desired path to open if file_format in desired_format: # check file format self.Load_video(filepath=file_path)