class MyMainWindow(QMainWindow, Ui_musicPlayer): def __init__(self, parent=None): super(MyMainWindow, self).__init__(parent) self.setupUi(self) self.player = QMediaPlayer() # --计时器 self.timer = QTimer(self) self.timer.start(1000) self.songs_list_select = [] self.song_formats = ['mp3', 'm4a', 'flac', 'wav', 'ogg'] self.settingfilename = 'setting.ini' self.cur_path = os.path.abspath(os.path.dirname(__file__)) self.cur_playing_song = '' self.is_switching = False self.is_pause = True self.openDir.clicked.connect(self.open_Dir) self.playStop.clicked.connect(self.playMusic) self.horizontalSlider.sliderMoved[int].connect( lambda: self.player.setPosition(self.horizontalSlider.value())) self.songUp.clicked.connect(self.previewMusic) self.songDn.clicked.connect(self.nextMusic) self.listWidget.doubleClicked.connect(self.doubleClicked) self.timer.timeout.connect(self.playByMode) def loadsetting(self): if os.path.isfile(self.settingfilename): config = configparser.ConfigParser() config.read(self.settingfilename) self.cur_path = config.get('MusicPlayer', 'PATH') self.showMusicList() def savesetting(self): config = configparser.ConfigParser() config.read(self.settingfilename) if not os.path.isfile(self.settingfilename): config.add_section('MusicPlayer') config.set('MusicPlayer', 'PATH', self.cur_path) config.write(open(self.settingfilename, 'w')) '''提示''' def Tips(self, message): QMessageBox.about(self, "提示", message) def open_Dir(self): self.cur_path = QFileDialog.getExistingDirectory( self, "选取文件夹", self.cur_path) if self.cur_path: self.showMusicList() self.cur_playing_song = '' self.setCurPlaying() self.label.setText('00:00') self.label_2.setText('00:00') self.horizontalSlider.setSliderPosition(0) self.is_pause = True self.playStop.setText('播放') def showMusicList(self): self.listWidget.clear() # self.updateSetting() for song in os.listdir(self.cur_path): if song.split('.')[-1] in self.song_formats: self.songs_list_select.append([ song, os.path.join(self.cur_path, song).replace('\\', '/') ]) self.listWidget.addItem(song) self.listWidget.setCurrentRow(0) if self.songs_list_select: self.cur_playing_song = self.songs_list_select[ self.listWidget.currentRow()][-1] '''根据播放模式播放音乐''' def playByMode(self): if (not self.is_pause) and (not self.is_switching): self.horizontalSlider.setMinimum(0) self.horizontalSlider.setMaximum(self.player.duration()) self.horizontalSlider.setValue(self.horizontalSlider.value() + 1000) self.label.setText( time.strftime('%M:%S', time.localtime(self.player.position() / 1000))) self.label_2.setText( time.strftime('%M:%S', time.localtime(self.player.duration() / 1000))) if (self.playType.currentIndex() == 0) and (not self.is_pause) and (not self.is_switching): if self.listWidget.count() == 0: return if self.player.position() == self.player.duration(): self.nextMusic() elif (self.playType.currentIndex() == 1) and (not self.is_pause) and (not self.is_switching): if self.listWidget.count() == 0: return if self.player.position() == self.player.duration(): self.is_switching = True self.setCurPlaying() self.horizontalSlider.setValue(0) self.playMusic() self.is_switching = False elif (self.playType.currentIndex() == 2) and (not self.is_pause) and (not self.is_switching): if self.listWidget.count() == 0: return if self.player.position() == self.player.duration(): self.is_switching = True self.listWidget.setCurrentRow( random.randint(0, self.qlist.count() - 1)) self.setCurPlaying() self.horizontalSlider.setValue(0) self.playMusic() self.is_switching = False '''双击播放音乐''' def doubleClicked(self): self.horizontalSlider.setValue(0) self.is_switching = True self.setCurPlaying() self.playMusic() self.is_switching = False '''设置当前播放的音乐''' def setCurPlaying(self): self.cur_playing_song = self.songs_list_select[ self.listWidget.currentRow()][-1] self.player.setMedia(QMediaContent(QUrl(self.cur_playing_song))) '''播放音乐''' def playMusic(self): if self.listWidget.count() == 0: self.Tips('当前路径内无可播放的音乐文件') return if not self.player.isAudioAvailable(): self.setCurPlaying() if self.is_switching or self.is_pause: self.player.play() self.is_pause = False self.playStop.setText('暂停') elif (not self.is_pause) and (not self.is_switching): self.player.pause() self.is_pause = True self.playStop.setText('播放') '''上一首''' def previewMusic(self): self.horizontalSlider.setValue(0) if self.listWidget.count() == 0: self.Tips('当前路径内无可播放的音乐文件') return pre_row = self.listWidget.currentRow( ) - 1 if self.listWidget.currentRow( ) != 0 else self.listWidget.count() - 1 self.listWidget.setCurrentRow(pre_row) self.is_switching = True self.setCurPlaying() self.playMusic() self.is_switching = False '''下一首''' def nextMusic(self): self.horizontalSlider.setValue(0) if self.listWidget.count() == 0: self.Tips('当前路径内无可播放的音乐文件') return next_row = self.listWidget.currentRow( ) + 1 if self.listWidget.currentRow( ) != self.listWidget.count() - 1 else 0 self.listWidget.setCurrentRow(next_row) self.is_switching = True self.setCurPlaying() self.playMusic() self.is_switching = False
class videoPlayer(QVideoWidget): def __init__(self, parent): super(QVideoWidget, self).__init__(parent) self.player = QMediaPlayer(self) self.player.setVolume(50) self.player.setVideoOutput(self) self.player.mediaStatusChanged.connect(self.mediaStatusChanged) def update(self, path): if path: path = QUrl.fromLocalFile(path) self.player.setMedia(QMediaContent(path)) self.player.play() def rotate(self, path, sign): self.update(None) clip = VideoFileClip(path) clip.rotate(90 * sign) if path.endswith(('gif')): clip.write_gif(path) else: clip.write_videofile(path) clip.close() def pause(self): status = self.player.state() if status == QMediaPlayer.PlayingState: self.player.pause() elif status == QMediaPlayer.PausedState: self.player.play() def position(self, delta): self.player.setPosition(self.player.position() + delta) def volume(self, delta): if self.player.isAudioAvailable(): self.player.setVolume(self.player.volume() + delta) def mute(self): if self.player.isAudioAvailable(): self.player.setMuted(not self.player.isMuted()) def stop(self): self.player.stop() def mediaStatusChanged(self, status): if status == QMediaPlayer.EndOfMedia: self.player.play() elif status not in (2, 1): self.parent().setCurrentIndex(1) def wheelEvent(self, event): self.volume(event.angleDelta().y() // 12)
class MP3Player(QWidget): def __init__(self): super().__init__() self.startTimeLabel = QLabel('00:00') self.endTimeLabel = QLabel('00:00') self.slider = QSlider(Qt.Horizontal, self) self.PlayModeBtn = QPushButton(self) self.playBtn = QPushButton(self) self.prevBtn = QPushButton(self) self.nextBtn = QPushButton(self) self.openBtn = QPushButton(self) self.musicList = QListWidget() self.song_formats = ['mp3', 'm4a', 'flac', 'wav', 'ogg'] self.songs_list = [] self.cur_playing_song = '' self.is_pause = True self.player = QMediaPlayer() self.is_switching = False self.playMode = 0 self.settingfilename = 'config.ini' self.textLable = QLabel('前进的路上,也要记得欣赏沿途的风景呀!') self.infoLabel = QLabel('Mculover666 v2.0.0') self.playBtn.setStyleSheet( "QPushButton{border-image: url(resource/image/play.png)}") self.playBtn.setFixedSize(48, 48) self.nextBtn.setStyleSheet( "QPushButton{border-image: url(resource/image/next.png)}") self.nextBtn.setFixedSize(48, 48) self.prevBtn.setStyleSheet( "QPushButton{border-image: url(resource/image/prev.png)}") self.prevBtn.setFixedSize(48, 48) self.openBtn.setStyleSheet( "QPushButton{border-image: url(resource/image/open.png)}") self.openBtn.setFixedSize(24, 24) self.PlayModeBtn.setStyleSheet( "QPushButton{border-image: url(resource/image/sequential.png)}") self.PlayModeBtn.setFixedSize(24, 24) self.timer = QTimer(self) self.timer.start(1000) self.timer.timeout.connect(self.playByMode) self.hBoxSlider = QHBoxLayout() self.hBoxSlider.addWidget(self.startTimeLabel) self.hBoxSlider.addWidget(self.slider) self.hBoxSlider.addWidget(self.endTimeLabel) self.hBoxButton = QHBoxLayout() self.hBoxButton.addWidget(self.PlayModeBtn) self.hBoxButton.addStretch(1) self.hBoxButton.addWidget(self.prevBtn) self.hBoxButton.addWidget(self.playBtn) self.hBoxButton.addWidget(self.nextBtn) self.hBoxButton.addStretch(1) self.hBoxButton.addWidget(self.openBtn) self.vBoxControl = QVBoxLayout() self.vBoxControl.addLayout(self.hBoxSlider) self.vBoxControl.addLayout(self.hBoxButton) self.hBoxAbout = QHBoxLayout() self.hBoxAbout.addWidget(self.textLable) self.hBoxAbout.addStretch(1) self.hBoxAbout.addWidget(self.infoLabel) self.vboxMain = QVBoxLayout() self.vboxMain.addWidget(self.musicList) self.vboxMain.addLayout(self.vBoxControl) self.vboxMain.addLayout(self.hBoxAbout) self.setLayout(self.vboxMain) self.openBtn.clicked.connect(self.openMusicFloder) self.playBtn.clicked.connect(self.playMusic) self.prevBtn.clicked.connect(self.prevMusic) self.nextBtn.clicked.connect(self.nextMusic) self.musicList.itemDoubleClicked.connect(self.doubleClicked) self.slider.sliderMoved[int].connect( lambda: self.player.setPosition(self.slider.value())) self.PlayModeBtn.clicked.connect(self.playModeSet) self.loadingSetting() self.initUI() # 初始化界面 def initUI(self): self.resize(600, 400) self.center() self.setWindowTitle('音乐播放器') self.setWindowIcon(QIcon('resource/image/favicon.ico')) self.show() # 窗口显示居中 def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) # 打开文件夹 def openMusicFloder(self): self.cur_path = QFileDialog.getExistingDirectory(self, "选取音乐文件夹", './') if self.cur_path: self.showMusicList() self.cur_playing_song = '' self.startTimeLabel.setText('00:00') self.endTimeLabel.setText('00:00') self.slider.setSliderPosition(0) self.updateSetting() self.is_pause = True self.playBtn.setStyleSheet( "QPushButton{border-image: url(resource/image/play.png)}") # 显示音乐列表 def showMusicList(self): self.musicList.clear() for song in os.listdir(self.cur_path): if song.split('.')[-1] in self.song_formats: self.songs_list.append([ song, os.path.join(self.cur_path, song).replace('\\', '/') ]) self.musicList.addItem(song) self.musicList.setCurrentRow(0) if self.songs_list: self.cur_playing_song = self.songs_list[ self.musicList.currentRow()][-1] # 提示 def Tips(self, message): QMessageBox.about(self, "提示", message) # 设置当前播放的音乐 def setCurPlaying(self): self.cur_playing_song = self.songs_list[ self.musicList.currentRow()][-1] self.player.setMedia(QMediaContent(QUrl(self.cur_playing_song))) # 播放/暂停播放 def playMusic(self): if self.musicList.count() == 0: self.Tips('当前路径内无可播放的音乐文件') return if not self.player.isAudioAvailable(): self.setCurPlaying() if self.is_pause or self.is_switching: self.player.play() self.is_pause = False self.playBtn.setStyleSheet( "QPushButton{border-image: url(resource/image/pause.png)}") elif (not self.is_pause) and (not self.is_switching): self.player.pause() self.is_pause = True self.playBtn.setStyleSheet( "QPushButton{border-image: url(resource/image/play.png)}") # 上一曲 def prevMusic(self): self.slider.setValue(0) if self.musicList.count() == 0: self.Tips('当前路径内无可播放的音乐文件') return pre_row = self.musicList.currentRow() - 1 if self.musicList.currentRow( ) != 0 else self.musicList.count() - 1 self.musicList.setCurrentRow(pre_row) self.is_switching = True self.setCurPlaying() self.playMusic() self.is_switching = False # 下一曲 def nextMusic(self): self.slider.setValue(0) if self.musicList.count() == 0: self.Tips('当前路径内无可播放的音乐文件') return next_row = self.musicList.currentRow( ) + 1 if self.musicList.currentRow( ) != self.musicList.count() - 1 else 0 self.musicList.setCurrentRow(next_row) self.is_switching = True self.setCurPlaying() self.playMusic() self.is_switching = False # 双击歌曲名称播放音乐 def doubleClicked(self): self.slider.setValue(0) self.is_switching = True self.setCurPlaying() self.playMusic() self.is_switching = False # 根据播放模式自动播放,并刷新进度条 def playByMode(self): # 刷新进度条 if (not self.is_pause) and (not self.is_switching): self.slider.setMinimum(0) self.slider.setMaximum(self.player.duration()) self.slider.setValue(self.slider.value() + 1000) self.startTimeLabel.setText( time.strftime('%M:%S', time.localtime(self.player.position() / 1000))) self.endTimeLabel.setText( time.strftime('%M:%S', time.localtime(self.player.duration() / 1000))) # 顺序播放 if (self.playMode == 0) and (not self.is_pause) and (not self.is_switching): if self.musicList.count() == 0: return if self.player.position() == self.player.duration(): self.nextMusic() # 单曲循环 elif (self.playMode == 1) and (not self.is_pause) and (not self.is_switching): if self.musicList.count() == 0: return if self.player.position() == self.player.duration(): self.is_switching = True self.setCurPlaying() self.slider.setValue(0) self.playMusic() self.is_switching = False # 随机播放 elif (self.playMode == 2) and (not self.is_pause) and (not self.is_switching): if self.musicList.count() == 0: return if self.player.position() == self.player.duration(): self.is_switching = True self.musicList.setCurrentRow( random.randint(0, self.musicList.count() - 1)) self.setCurPlaying() self.slider.setValue(0) self.playMusic() self.is_switching = False # 更新配置文件 def updateSetting(self): config = configparser.ConfigParser() config.read(self.settingfilename) if not os.path.isfile(self.settingfilename): config.add_section('MP3Player') config.set('MP3Player', 'PATH', self.cur_path) config.write(open(self.settingfilename, 'w')) # 加载配置文件 def loadingSetting(self): config = configparser.ConfigParser() config.read(self.settingfilename) if not os.path.isfile(self.settingfilename): return self.cur_path = config.get('MP3Player', 'PATH') self.showMusicList() # 播放模式设置 def playModeSet(self): # 设置为单曲循环模式 if self.playMode == 0: self.playMode = 1 self.PlayModeBtn.setStyleSheet( "QPushButton{border-image: url(resource/image/circulation.png)}" ) # 设置为随机播放模式 elif self.playMode == 1: self.playMode = 2 self.PlayModeBtn.setStyleSheet( "QPushButton{border-image: url(resource/image/random.png)}") # 设置为顺序播放模式 elif self.playMode == 2: self.playMode = 0 self.PlayModeBtn.setStyleSheet( "QPushButton{border-image: url(resource/image/sequential.png)}" ) # 确认用户是否要真正退出 def closeEvent(self, event): reply = QMessageBox.question(self, 'Message', "确定要退出吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: event.accept() else: event.ignore()
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)