class PlayerApp(App): def __init__(self, **kwargs): super(PlayerApp, self).__init__(**kwargs) self.texture = None self.size = (0, 0) self.next_frame = None self._done = False self._lock = RLock() self._thread = Thread(target=self._next_frame, name='Next frame') self._trigger = Clock.create_trigger(self.redraw) self._force_refresh = False def build(self): self.root = Root() return self.root def on_start(self): self.callback_ref = WeakMethod(self.callback) filename = sys.argv[1] logging.info('ffpyplayer: Playing file "{}"'.format(filename)) # try ff_opts = {'vf':'edgedetect'} http://ffmpeg.org/ffmpeg-filters.html ff_opts = {} self.ffplayer = MediaPlayer(filename, callback=self.callback_ref, loglevel=log_level, ff_opts=ff_opts) self._thread.start() self.keyboard = Window.request_keyboard(None, self.root) self.keyboard.bind(on_key_down=self.on_keyboard_down) def resize(self): if self.ffplayer: w, h = self.ffplayer.get_metadata()['src_vid_size'] if not h: return lock = self._lock lock.acquire() if self.root.image.width < self.root.image.height * w / float(h): self.ffplayer.set_size(-1, self.root.image.height) else: self.ffplayer.set_size(self.root.image.width, -1) lock.release() logging.debug('ffpyplayer: Resized video.') def update_pts(self, *args): if self.ffplayer: self.root.seek.value = self.ffplayer.get_pts() def on_keyboard_down(self, keyboard, keycode, text, modifiers): if not self.ffplayer: return False lock = self._lock ctrl = 'ctrl' in modifiers if keycode[1] == 'p' or keycode[1] == 'spacebar': logging.info('Toggled pause.') self.ffplayer.toggle_pause() elif keycode[1] == 'r': logging.debug('ffpyplayer: Forcing a refresh.') self._force_refresh = True elif keycode[1] == 'v': logging.debug('ffpyplayer: Changing video stream.') lock.acquire() self.ffplayer.request_channel('video', 'close' if ctrl else 'cycle') lock.release() Clock.unschedule(self.update_pts) if ctrl: # need to continue updating pts, since video is disabled. Clock.schedule_interval(self.update_pts, 0.05) elif keycode[1] == 'a': logging.debug('ffpyplayer: Changing audio stream.') lock.acquire() self.ffplayer.request_channel('audio', 'close' if ctrl else 'cycle') lock.release() elif keycode[1] == 't': logging.debug('ffpyplayer: Changing subtitle stream.') lock.acquire() self.ffplayer.request_channel('subtitle', 'close' if ctrl else 'cycle') lock.release() elif keycode[1] == 'right': logging.debug('ffpyplayer: Seeking forward by 10s.') self.ffplayer.seek(10.) elif keycode[1] == 'left': logging.debug('ffpyplayer: Seeking back by 10s.') self.ffplayer.seek(-10.) elif keycode[1] == 'up': logging.debug('ffpyplayer: Increasing volume.') self.ffplayer.set_volume(self.ffplayer.get_volume() + 0.01) self.root.volume.value = self.ffplayer.get_volume() elif keycode[1] == 'down': logging.debug('ffpyplayer: Decreasing volume.') self.ffplayer.set_volume(self.ffplayer.get_volume() - 0.01) self.root.volume.value = self.ffplayer.get_volume() return True def touch_down(self, touch): if self.root.seek.collide_point(*touch.pos) and self.ffplayer: pts = ((touch.pos[0] - self.root.volume.width) / self.root.seek.width * self.ffplayer.get_metadata()['duration']) logging.debug('ffpyplayer: Seeking to {}.'.format(pts)) self.ffplayer.seek(pts, relative=False) self._force_refresh = True return True return False def callback(self, selector, value): if self.ffplayer is None: return if selector == 'quit': logging.debug('ffpyplayer: Quitting.') def close(*args): self._done = True self.ffplayer = None Clock.schedule_once(close, 0) # called from internal thread, it typically reads forward elif selector == 'display_sub': self.display_subtitle(*value) def _next_frame(self): ffplayer = self.ffplayer sleep = time.sleep trigger = self._trigger while not self._done: force = self._force_refresh if force: self._force_refresh = False frame, val = ffplayer.get_frame(force_refresh=force) if val == 'eof': logging.debug('ffpyplayer: Got eof.') sleep(1 / 30.) elif val == 'paused': logging.debug('ffpyplayer: Got paused.') sleep(1 / 30.) else: if frame: logging.debug('ffpyplayer: Next frame: {}.'.format(val)) sleep(val) self.next_frame = frame trigger() else: val = val if val else (1 / 30.) logging.debug('ffpyplayer: Schedule next frame check: {}.' .format(val)) sleep(val) def redraw(self, dt=0, force_refresh=False): if not self.ffplayer: return if self.next_frame: img, pts = self.next_frame if img.get_size() != self.size or self.texture is None: self.root.image.canvas.remove_group(str(self)+'_display') self.texture = Texture.create(size=img.get_size(), colorfmt='rgb') # by adding 'vf':'vflip' to the player initialization ffmpeg # will do the flipping self.texture.flip_vertical() self.texture.add_reload_observer(self.reload_buffer) self.size = img.get_size() logging.debug('ffpyplayer: Creating new image texture of ' 'size: {}.'.format(self.size)) self.texture.blit_buffer(img.to_memoryview()[0]) self.root.image.texture = None self.root.image.texture = self.texture self.root.seek.value = pts logging.debug('ffpyplayer: Blitted new frame with time: {}.' .format(pts)) if self.root.seek.value: self.root.seek.max = self.ffplayer.get_metadata()['duration'] def display_subtitle(self, text, fmt, pts, t_start, t_end): pass # fmt is text (unformatted), or ass (formatted subs) def reload_buffer(self, *args): logging.debug('ffpyplayer: Reloading buffer.') frame = self.next_frame if not frame: return self.texture.blit_buffer(frame[0].to_memoryview()[0], colorfmt='rgb', bufferfmt='ubyte')
class Window(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.Listadd() self.step = 0 self.loop = 1 self.flag = True def Listadd(self): if os.path.isfile('CPlayerlist.txt'): with open('CPlayerlist.txt') as f: for filelist in f: filelist = filelist.strip() self.list.addItem(filelist) def Add(self): filelists, _ = QFileDialog.getOpenFileNames(self, '添加到播放列表', '.', '媒体文件(*)') self.list.addItems(filelists) self.Listchanged() def Remove(self): self.list.takeItem(self.list.currentRow()) self.Listchanged() def Clear(self): self.list.clear() os.remove('CPlayerlist.txt') def Listchanged(self): with open('CPlayerlist.txt', 'w') as f: for i in range(self.list.count()): f.write(self.list.item(i).text() + '\n') def Loop(self): if self.loop == 0: self.loop = 1 self.bloop.setIcon(QIcon(r'img\withloop.png')) self.bloop.setToolTip('循环播放') else: self.loop = 0 self.bloop.setIcon(QIcon(r'img\withoutloop.png')) self.bloop.setToolTip('取消循环') def Play(self): try: if self.flag: self.playitem = self.list.currentItem().text() self.player = MediaPlayer("%s" % self.playitem) self.timer = QTimer() self.timer.start(50) self.timer.timeout.connect(self.Show) self.steptimer = QTimer() self.steptimer.start(1000) self.steptimer.timeout.connect(self.Step) self.flag = False self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停') else: if self.list.currentItem().text() == self.playitem: self.player.toggle_pause() if self.player.get_pause(): self.timer.stop() self.steptimer.stop() self.bplay.setIcon(QIcon(r'img\play.png')) self.bplay.setToolTip('播放') else: self.timer.start() self.steptimer.start() self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停') else: self.step = 0 self.stime.setValue(0) self.playitem = self.list.currentItem().text() self.player = MediaPlayer("%s" % self.playitem) self.timer.start() self.steptimer.start() except: QMessageBox.warning(self, '错误', '找不到要播放的文件!') def Show(self): frame, self.val = self.player.get_frame() self.lmedia.setPixmap(QPixmap('')) if self.val != 'eof' and frame is not None: img, t = frame data = img.to_bytearray()[0] width, height = img.get_size() qimg = QImage(data, width, height, QImage.Format_RGB888) self.lmedia.setPixmap(QPixmap.fromImage(qimg)) self.mediatime = self.player.get_metadata()['duration'] self.stime.setMaximum(int(self.mediatime)) mediamin, mediasec = divmod(self.mediatime, 60) mediahour, mediamin = divmod(mediamin, 60) playmin, playsec = divmod(self.step, 60) playhour, playmin = divmod(playmin, 60) self.ltime.setText( '%02d:%02d:%02d/%02d:%02d:%02d' % (playhour, playmin, playsec, mediahour, mediamin, mediasec)) def Stop(self): if self.flag == False: self.player.close_player() self.timer.stop() self.steptimer.stop() self.step = 0 self.loop = 1 self.flag = True self.stime.setValue(0) self.ltime.setText('') self.bplay.setIcon(QIcon(r'img\play.png')) self.bplay.setToolTip('播放') self.lmedia.setPixmap(QPixmap('')) def Curvol(self): self.curvol = self.svolume.value() def Mute(self): if self.flag == False: if self.player.get_volume() != 0: self.player.set_volume(0) self.bmute.setIcon(QIcon(r'img\withoutvolume.png')) self.bmute.setToolTip('取消静音') else: if self.svolume.value() != 0: self.player.set_volume(self.svolume.value()) else: self.player.set_volume(self.curvol / 100) self.svolume.setValue(self.curvol) self.bmute.setIcon(QIcon(r'img\withvolume.png')) self.bmute.setToolTip('静音') def Volume(self): if self.flag == False: if self.svolume.value() == 0: self.bmute.setIcon(QIcon(r'img\withoutvolume.png')) self.bmute.setToolTip('取消静音') else: self.bmute.setIcon(QIcon(r'img\withvolume.png')) self.bmute.setToolTip('静音') self.player.set_volume(self.svolume.value() / 100) def Step(self): if self.step >= int(self.mediatime): self.step = int(self.mediatime) if self.loop == 0: self.step = 0 self.flag = True self.Play() else: if self.val == 'eof': self.timer.stop() self.steptimer.stop() self.step = 0 self.loop = 1 self.flag = True self.stime.setValue(0) self.player.close_player() self.bplay.setIcon(QIcon(r'img\play.png')) self.bplay.setToolTip('播放') else: self.step += 1 self.stime.setValue(self.step) def Slidechanged(self): self.step = self.stime.value() def Slidemoved(self): self.timer.start() self.steptimer.start() self.player = MediaPlayer("%s" % self.playitem, ff_opts={'ss': self.step}) self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停') def Fastforward(self): self.step += 10 if self.step >= int(self.mediatime): self.stime.setValue(int(self.mediatime)) self.timer.start() self.steptimer.start() self.player = MediaPlayer("%s" % self.playitem, ff_opts={'ss': self.step}) self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停') def Fastback(self): self.step -= 10 if self.step <= 0: self.step = 0 self.timer.start() self.steptimer.start() self.player = MediaPlayer("%s" % self.playitem, ff_opts={'ss': self.step}) self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停')
class Window(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.Listadd() self.step = 0 self.loop = 1 self.tag = self.flag = self.listtag = self.fulltag = True screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move(int((screen.width() - size.width()) / 2), int((screen.height() - size.height()) / 2)) def keyPressEvent(self, event): if event.key() == Qt.Key_P: self.Listhide() if event.key() == Qt.Key_T: self.Fastback() if event.key() == Qt.Key_L: self.Loop() if event.key() == Qt.Key_Space: self.Play() if event.key() == Qt.Key_S: self.Stop() if event.key() == Qt.Key_F: self.Full() if event.key() == Qt.Key_J: self.Fastforward() if event.key() == Qt.Key_M: self.Mute() if event.key() == Qt.Key_A: self.svolume.setValue(self.svolume.value() + 1) if event.key() == Qt.Key_R: self.svolume.setValue(self.svolume.value() - 1) def eventFilter(self, sender, event): if (event.type() == event.ChildRemoved): self.Moved() return False def Listmenu(self, position): lm = QMenu() addact = QAction("添加到播放列表", self, triggered=self.Add) removeact = QAction("从播放列表移除", self, triggered=self.Remove) renameact = QAction('重命名', self, triggered=self.Rename) clearact = QAction('清空播放列表', self, triggered=self.Clear) saveact = QAction('保存当前播放列表', self, triggered=self.Saved) lm.addAction(addact) if self.list.itemAt(position): lm.addAction(removeact) lm.addAction(renameact) lm.addAction(clearact) lm.addAction(saveact) lm.exec_(self.list.mapToGlobal(position)) def Listadd(self): self.l = [] self.list.installEventFilter(self) if os.path.isfile('CPlayerlist.txt'): with open('CPlayerlist.txt') as f: for i in f: i = i.strip() name = i[0:i.find(',')] filelist = i[i.find(',') + 1:len(i)] self.list.addItem(name) self.l.append(filelist) def Add(self): filelists, _ = QFileDialog.getOpenFileNames(self, '添加到播放列表', '.', '媒体文件(*)') for filelist in filelists: name = filelist[filelist.rfind('/') + 1:filelist.rfind('.')] self.list.addItem(name) self.l.append(filelist) def Remove(self): ltmp = [] for i in self.list.selectedIndexes(): ltmp.append(i.row()) ltmp.sort(reverse=True) for j in ltmp: self.list.takeItem(j) self.l.pop(j) def Rename(self): item = self.list.item(self.list.currentRow()) item.setFlags(item.flags() | Qt.ItemIsEditable) self.list.editItem(item) def Clear(self): self.l = [] self.list.clear() if os.path.isfile('CPlayerlist.txt'): os.remove('CPlayerlist.txt') def Drag(self): self.tmp1 = [] self.tmp2 = self.l[:] for i in range(self.list.count()): self.tmp1.append(self.list.item(i).text()) def Moved(self): for i in range(self.list.count()): if self.list.item(i).text() == self.tmp1[i]: continue else: self.l[i] = self.tmp2[self.tmp1.index( self.list.item(i).text())] def Saved(self): with open('CPlayerlist.txt', 'w') as f: for i in range(self.list.count()): f.write('%s,%s\n' % (self.list.item(i).text(), self.l[i])) QMessageBox.information(self, '保存', '播放列表保存成功!') def Listhide(self): if self.listtag: self.frame.hide() self.listtag = False else: self.frame.show() self.listtag = True def Loop(self): if self.loop == 0: self.loop = 1 self.bloop.setIcon(QIcon(r'img\withloop.png')) self.bloop.setToolTip('循环播放,快捷键“l”') else: self.loop = 0 self.bloop.setIcon(QIcon(r'img\withoutloop.png')) self.bloop.setToolTip('取消循环,快捷键“l”') def Play(self): if self.flag: try: self.playitem = self.l[self.list.currentRow()] if os.path.isfile("%s" % self.playitem): self.player = MediaPlayer("%s" % self.playitem) self.timer = QTimer() self.timer.start(50) self.timer.timeout.connect(self.Show) self.steptimer = QTimer() self.steptimer.start(1000) self.steptimer.timeout.connect(self.Step) self.flag = False self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停,快捷键“Space”') else: QMessageBox.warning(self, '错误', '找不到要播放的文件!') except: QMessageBox.warning(self, '错误', '找不到要播放的文件!') else: if self.l[self.list.currentRow()] == self.playitem: self.player.toggle_pause() if self.player.get_pause(): self.timer.stop() self.steptimer.stop() self.bplay.setIcon(QIcon(r'img\play.png')) self.bplay.setToolTip('播放,快捷键“Space”') else: self.timer.start() self.steptimer.start() self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停,快捷键“Space”') else: self.playitem = self.l[self.list.currentRow()] if os.path.isfile("%s" % self.playitem): self.step = 0 self.stime.setValue(0) self.player = MediaPlayer("%s" % self.playitem) self.timer.start() self.steptimer.start() self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停,快捷键“Space”') else: QMessageBox.warning(self, '错误', '找不到要播放的文件!') def Show(self): if self.tag: self.player.set_volume(self.svolume.value() / 100) else: self.player.set_volume(0) frame, self.val = self.player.get_frame() self.lmedia.setPixmap(QPixmap('')) if self.val != 'eof' and frame is not None: img, t = frame data = img.to_bytearray()[0] width, height = img.get_size() qimg = QImage(data, width, height, QImage.Format_RGB888) self.lmedia.setPixmap(QPixmap.fromImage(qimg)) self.mediatime = self.player.get_metadata()['duration'] if self.mediatime: self.stime.setMaximum(int(self.mediatime)) mediamin, mediasec = divmod(self.mediatime, 60) mediahour, mediamin = divmod(mediamin, 60) playmin, playsec = divmod(self.step, 60) playhour, playmin = divmod(playmin, 60) self.ltime.setText( '%02d:%02d:%02d/%02d:%02d:%02d' % (playhour, playmin, playsec, mediahour, mediamin, mediasec)) def Stop(self): if self.flag == False: self.player.close_player() self.timer.stop() self.steptimer.stop() self.step = 0 self.loop = 1 self.flag = True self.stime.setValue(0) self.ltime.setText('') self.bplay.setIcon(QIcon(r'img\play.png')) self.bplay.setToolTip('播放,快捷键“Space”') self.lmedia.setPixmap(QPixmap('')) def Full(self): if self.fulltag: self.frame.hide() self.frame_2.hide() self.showFullScreen() self.bfull.setIcon(QIcon(r'img\exitfullscreen.png')) self.bfull.setToolTip('退出全屏,快捷键“f”') self.fulltag = False else: self.frame.show() self.frame_2.show() self.showNormal() self.bfull.setIcon(QIcon(r'img\expandfullscreen.png')) self.bfull.setToolTip('全屏,快捷键“f”') self.fulltag = True def Curvol(self): self.curvol = self.svolume.value() def Mute(self): if self.flag == False: if self.player.get_volume() != 0: self.player.set_volume(0) self.bmute.setIcon(QIcon(r'img\withoutvolume.png')) self.bmute.setToolTip('取消静音,快捷键“m”') self.tag = False else: if self.svolume.value() != 0: self.player.set_volume(self.svolume.value() / 100) else: self.player.set_volume(self.curvol / 100) self.svolume.setValue(self.curvol) self.bmute.setIcon(QIcon(r'img\withvolume.png')) self.bmute.setToolTip('静音,快捷键“m”') self.tag = True def Volume(self): if self.flag == False: if self.svolume.value() == 0: self.bmute.setIcon(QIcon(r'img\withoutvolume.png')) self.bmute.setToolTip('取消静音,快捷键“m”') else: self.bmute.setIcon(QIcon(r'img\withvolume.png')) self.bmute.setToolTip('静音,快捷键“m”') self.player.set_volume(self.svolume.value() / 100) def Step(self): if self.step >= int(self.mediatime): self.step = int(self.mediatime) if self.loop == 0: self.step = 0 self.stime.setValue(0) self.flag = True self.Play() else: if self.val == 'eof': self.Stop() else: self.step += 1 self.stime.setValue(self.step) def Slidechanged(self): self.step = self.stime.value() def Slidemoved(self): if self.flag == False: self.timer.start() self.steptimer.start() self.player = MediaPlayer("%s" % self.playitem, ff_opts={'ss': self.step}) self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停,快捷键“Space”') def Fastforward(self): if self.flag == False: self.step += 10 if self.step >= int(self.mediatime): self.stime.setValue(int(self.mediatime)) self.timer.start() self.steptimer.start() self.player = MediaPlayer("%s" % self.playitem, ff_opts={'ss': self.step}) self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停,快捷键“Space”') def Fastback(self): if self.flag == False: self.step -= 10 if self.step <= 0: self.step = 0 self.stime.setValue(0) self.timer.start() self.steptimer.start() self.player = MediaPlayer("%s" % self.playitem, ff_opts={'ss': self.step}) self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停,快捷键“Space”')
class Window(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) self.Listadd() self.step = 0 self.loop = 1 self.tag = True self.flag = True self.hidetag = True screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move(int((screen.width() - size.width()) / 2), int((screen.height() - size.height()) / 2)) def keyPressEvent(self, event): if event.key() == Qt.Key_P: self.Listhide() if event.key() == Qt.Key_T: self.Fastback() if event.key() == Qt.Key_L: self.Loop() if event.key() == Qt.Key_Space: self.Play() if event.key() == Qt.Key_S: self.Stop() if event.key() == Qt.Key_F: self.Full() if event.key() == Qt.Key_J: self.Fastforward() if event.key() == Qt.Key_M: self.Mute() if event.key() == Qt.Key_A: self.svolume.setValue(self.svolume.value() + 1) if event.key() == Qt.Key_R: self.svolume.setValue(self.svolume.value() - 1) def Listadd(self): if os.path.isfile('CPlayerlist.txt'): with open('CPlayerlist.txt') as f: for filelist in f: filelist = filelist.strip() self.list.addItem(filelist) def Add(self): filelists, _ = QFileDialog.getOpenFileNames(self, '添加到播放列表', '.', '媒体文件(*)') self.list.addItems(filelists) self.Listchanged() def Remove(self): self.list.takeItem(self.list.currentRow()) self.Listchanged() def Clear(self): self.list.clear() os.remove('CPlayerlist.txt') def Listchanged(self): with open('CPlayerlist.txt', 'w') as f: for i in range(self.list.count()): f.write(self.list.item(i).text() + '\n') def Listhide(self): if self.hidetag: self.frame.hide() self.hidetag = False else: self.frame.show() self.hidetag = True def Loop(self): if self.loop == 0: self.loop = 1 self.bloop.setIcon(QIcon(r'img\withloop.png')) self.bloop.setToolTip('循环播放,快捷键“l”') else: self.loop = 0 self.bloop.setIcon(QIcon(r'img\withoutloop.png')) self.bloop.setToolTip('取消循环,快捷键“l”') def Play(self): if self.flag: try: self.playitem = self.list.currentItem().text() if os.path.isfile("%s" % self.playitem): self.player = MediaPlayer("%s" % self.playitem) self.timer = QTimer() self.timer.start(50) self.timer.timeout.connect(self.Show) self.steptimer = QTimer() self.steptimer.start(1000) self.steptimer.timeout.connect(self.Step) self.flag = False self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停,快捷键“Space”') else: QMessageBox.warning(self, '错误', '找不到要播放的文件!') except: QMessageBox.warning(self, '错误', '找不到要播放的文件!') else: if self.list.currentItem().text() == self.playitem: self.player.toggle_pause() if self.player.get_pause(): self.timer.stop() self.steptimer.stop() self.bplay.setIcon(QIcon(r'img\play.png')) self.bplay.setToolTip('播放,快捷键“Space”') else: self.timer.start() self.steptimer.start() self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停,快捷键“Space”') else: self.playitem = self.list.currentItem().text() if os.path.isfile("%s" % self.playitem): self.step = 0 self.stime.setValue(0) self.player = MediaPlayer("%s" % self.playitem) self.timer.start() self.steptimer.start() else: QMessageBox.warning(self, '错误', '找不到要播放的文件!') def Show(self): if self.tag: self.player.set_volume(self.svolume.value() / 100) else: self.player.set_volume(0) frame, self.val = self.player.get_frame() self.lmedia.setPixmap(QPixmap('')) if self.val != 'eof' and frame is not None: img, t = frame data = img.to_bytearray()[0] width, height = img.get_size() qimg = QImage(data, width, height, QImage.Format_RGB888) self.lmedia.setPixmap(QPixmap.fromImage(qimg)) self.mediatime = self.player.get_metadata()['duration'] if self.mediatime: self.stime.setMaximum(int(self.mediatime)) mediamin, mediasec = divmod(self.mediatime, 60) mediahour, mediamin = divmod(mediamin, 60) playmin, playsec = divmod(self.step, 60) playhour, playmin = divmod(playmin, 60) self.ltime.setText( '%02d:%02d:%02d/%02d:%02d:%02d' % (playhour, playmin, playsec, mediahour, mediamin, mediasec)) def Stop(self): if self.flag == False: self.player.close_player() self.timer.stop() self.steptimer.stop() self.step = 0 self.loop = 1 self.flag = True self.stime.setValue(0) self.ltime.setText('') self.bplay.setIcon(QIcon(r'img\play.png')) self.bplay.setToolTip('播放,快捷键“Space”') self.lmedia.setPixmap(QPixmap('')) def Full(self): if self.hidetag: self.setWindowFlags(Qt.FramelessWindowHint) rect = QApplication.desktop().geometry() self.setGeometry(rect) self.frame.hide() self.frame_2.hide() self.show() self.bfull.setIcon(QIcon(r'img\exitfullscreen.png')) self.bfull.setToolTip('退出全屏,快捷键“f”') self.hidetag = False else: self.setWindowFlags(Qt.Widget) self.setGeometry(0, 0, 1144, 705) self.frame.show() self.frame_2.show() screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move(int((screen.width() - size.width()) / 2), int((screen.height() - size.height()) / 2)) self.show() self.bfull.setIcon(QIcon(r'img\expandfullscreen.png')) self.bfull.setToolTip('全屏,快捷键“f”') self.hidetag = True def Curvol(self): self.curvol = self.svolume.value() def Mute(self): if self.flag == False: if self.player.get_volume() != 0: self.player.set_volume(0) self.bmute.setIcon(QIcon(r'img\withoutvolume.png')) self.bmute.setToolTip('取消静音,快捷键“m”') self.tag = False else: if self.svolume.value() != 0: self.player.set_volume(self.svolume.value() / 100) else: self.player.set_volume(self.curvol / 100) self.svolume.setValue(self.curvol) self.bmute.setIcon(QIcon(r'img\withvolume.png')) self.bmute.setToolTip('静音,快捷键“m”') self.tag = True def Volume(self): if self.flag == False: if self.svolume.value() == 0: self.bmute.setIcon(QIcon(r'img\withoutvolume.png')) self.bmute.setToolTip('取消静音,快捷键“m”') else: self.bmute.setIcon(QIcon(r'img\withvolume.png')) self.bmute.setToolTip('静音,快捷键“m”') self.player.set_volume(self.svolume.value() / 100) def Step(self): if self.step >= int(self.mediatime): self.step = int(self.mediatime) if self.loop == 0: self.step = 0 self.flag = True self.Play() else: if self.val == 'eof': self.timer.stop() self.steptimer.stop() self.step = 0 self.loop = 1 self.flag = True self.stime.setValue(0) self.player.close_player() self.bplay.setIcon(QIcon(r'img\play.png')) self.bplay.setToolTip('播放,快捷键“Space”') else: self.step += 1 self.stime.setValue(self.step) def Slidechanged(self): self.step = self.stime.value() def Slidemoved(self): self.timer.start() self.steptimer.start() self.player = MediaPlayer("%s" % self.playitem, ff_opts={'ss': self.step}) self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停,快捷键“Space”') def Fastforward(self): if self.flag == False: self.step += 10 if self.step >= int(self.mediatime): self.stime.setValue(int(self.mediatime)) self.timer.start() self.steptimer.start() self.player = MediaPlayer("%s" % self.playitem, ff_opts={'ss': self.step}) self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停,快捷键“Space”') def Fastback(self): if self.flag == False: self.step -= 10 if self.step <= 0: self.step = 0 self.timer.start() self.steptimer.start() self.player = MediaPlayer("%s" % self.playitem, ff_opts={'ss': self.step}) self.bplay.setIcon(QIcon(r'img\pause.png')) self.bplay.setToolTip('暂停,快捷键“Space”')
class FFPyPlayer(BaseMoviePlayer): """Interface class for the FFPyPlayer library for use with `MovieStim`. This class also serves as the reference implementation for classes which interface with movie codec libraries for use with `MovieStim`. Creating new player classes which closely replicate the behaviour of this one should allow them to smoothly plug into `MovieStim`. """ _movieLib = 'ffpyplayer' def __init__(self, parent): self._filename = u"" self.parent = parent # handle to `ffpyplayer` self._handle = None # thread for reading frames asynchronously self._tStream = None # data from stream thread self._lastFrame = NULL_MOVIE_FRAME_INFO self._frameIndex = -1 self._loopCount = 0 self._metadata = None # metadata from the stream self._lastPlayerOpts = DEFAULT_FF_OPTS.copy() # options from the parent if self.parent.loop: # infinite loop self._lastPlayerOpts['loop'] = 0 else: self._lastPlayerOpts['loop'] = 1 # play once if hasattr(self.parent, '_noAudio'): self._lastPlayerOpts['an'] = self.parent._noAudio # status flags self._status = NOT_STARTED def start(self, log=True): """Initialize and start the decoder. This method will return when a valid frame is made available. """ # clear queued data from previous streams self._lastFrame = None self._frameIndex = -1 # open the media player self._handle = MediaPlayer(self._filename, ff_opts=self._lastPlayerOpts) self._handle.set_pause(True) # Pull the first frame to get metadata. NB - `_enqueueFrame` should be # able to do this but the logic in there depends on having access to # metadata first. That may be rewritten at some point to reduce all of # this to just a single `_enqeueFrame` call. # self._status = NOT_STARTED # hand off the player interface to the thread self._tStream = MovieStreamThreadFFPyPlayer(self._handle) self._tStream.begin() # make sure we have metadata self.update() def load(self, pathToMovie): """Load a movie file from disk. Parameters ---------- pathToMovie : str Path to movie file, stream (URI) or camera. Must be a format that FFMPEG supports. """ # set the file path self._filename = pathToString(pathToMovie) # Check if the player is already started. Close it and load a new # instance if so. if self._handle is not None: # player already started # make sure it's the correct type if not isinstance(self._handle, MediaPlayer): raise TypeError( 'Incorrect type for `FFMovieStim._player`, expected ' '`ffpyplayer.player.MediaPlayer`. Got type `{}` ' 'instead.'.format(type(self._handle).__name__)) # close the player and reset self.unload() # self._selectWindow(self.win) # free buffers here !!! self.start() self._status = NOT_STARTED def unload(self): """Unload the video stream and reset. """ self._handle.close_player() self._filename = u"" self._frameIndex = -1 self._handle = None # reset @property def handle(self): """Handle to the `MediaPlayer` object exposed by FFPyPlayer. If `None`, no media player object has yet been initialized. """ return self._handle @property def isLoaded(self): return self._handle is not None @property def metadata(self): """Most recent metadata (`MovieMetadata`). """ return self.getMetadata() def getMetadata(self): """Get metadata from the movie stream. Returns ------- MovieMetadata Movie metadata object. If no movie is loaded, `NULL_MOVIE_METADATA` is returned. At a minimum, fields `duration`, `size`, and `frameRate` are populated if a valid movie has been previously loaded. """ self._assertMediaPlayer() metadata = self._metadata # write metadata to the fields of a `MovieMetadata` object toReturn = MovieMetadata(mediaPath=self._filename, title=metadata['title'], duration=metadata['duration'], frameRate=metadata['frame_rate'], size=metadata['src_vid_size'], pixelFormat=metadata['src_pix_fmt'], movieLib=self._movieLib, userData=None) return toReturn def _assertMediaPlayer(self): """Ensure the media player instance is available. Raises a `RuntimeError` if no movie is loaded. """ if isinstance(self._handle, MediaPlayer): return # nop if we're good raise RuntimeError( "Calling this class method requires a successful call to " "`load` first.") @property def status(self): """Player status flag (`int`). """ return self._status @property def isPlaying(self): """`True` if the video is presently playing (`bool`).""" # Status flags as properties are pretty useful for users since they are # self documenting and prevent the user from touching the status flag # attribute directly. # return self.status == PLAYING @property def isNotStarted(self): """`True` if the video has not be started yet (`bool`). This status is given after a video is loaded and play has yet to be called. """ return self.status == NOT_STARTED @property def isStopped(self): """`True` if the movie has been stopped. """ return self.status == STOPPED @property def isPaused(self): """`True` if the movie has been paused. """ self._assertMediaPlayer() return self._handle.get_pause() @property def isFinished(self): """`True` if the video is finished (`bool`). """ # why is this the same as STOPPED? return self.status == FINISHED def play(self, log=False): """Start or continue a paused movie from current position. Parameters ---------- log : bool Log the play event. Returns ------- int or None Frame index playback started at. Should always be `0` if starting at the beginning of the video. Returns `None` if the player has not been initialized. """ self._assertMediaPlayer() self._tStream.play() self._status = PLAYING def stop(self, log=False): """Stop the current point in the movie (sound will stop, current frame will not advance). Once stopped the movie cannot be restarted - it must be loaded again. Use `pause()` instead if you may need to restart the movie. Parameters ---------- log : bool Log the stop event. """ if self._tStream is None: raise RuntimeError("Cannot close stream, not opened yet.") # close the thread if not self._tStream.isDone(): self._tStream.shutdown() self._tStream.join() # wait until thread exits self._tStream = None if self._handle is not None: self._handle.close_player() self._handle = None # reset def pause(self, log=False): """Pause the current point in the movie. The image of the last frame will persist on-screen until `play()` or `stop()` are called. Parameters ---------- log : bool Log this event. """ self._assertMediaPlayer() self._tStream.pause() return False def seek(self, timestamp, log=False): """Seek to a particular timestamp in the movie. Parameters ---------- timestamp : float Time in seconds. log : bool Log the seek event. """ raise NotImplementedError( "This feature is not available for the current backend.") def rewind(self, seconds=5, log=False): """Rewind the video. Parameters ---------- seconds : float Time in seconds to rewind from the current position. Default is 5 seconds. log : bool Log this event. Returns ------- float Timestamp after rewinding the video. """ raise NotImplementedError( "This feature is not available for the current backend.") def fastForward(self, seconds=5, log=False): """Fast-forward the video. Parameters ---------- seconds : float Time in seconds to fast forward from the current position. Default is 5 seconds. log : bool Log this event. Returns ------- float Timestamp at new position after fast forwarding the video. """ raise NotImplementedError( "This feature is not available for the current backend.") def replay(self, autoStart=True, log=False): """Replay the movie from the beginning. Parameters ---------- autoStart : bool Start playback immediately. If `False`, you must call `play()` afterwards to initiate playback. log : bool Log this event. Notes ----- * This tears down the current media player instance and creates a new one. Similar to calling `stop()` and `loadMovie()`. Use `seek(0.0)` if you would like to restart the movie without reloading. """ lastMovieFile = self._filename self.stop() # stop the movie # self._autoStart = autoStart self.load(lastMovieFile) # will play if auto start # -------------------------------------------------------------------------- # Audio stream control methods # @property def muted(self): """`True` if the stream audio is muted (`bool`). """ return self._handle.get_mute() # thread-safe? @muted.setter def muted(self, value): self._tStream.setMute(value) def volumeUp(self, amount): """Increase the volume by a fixed amount. Parameters ---------- amount : float or int Amount to increase the volume relative to the current volume. """ self._assertMediaPlayer() # get the current volume from the player self.volume = self.volume + amount return self.volume def volumeDown(self, amount): """Decrease the volume by a fixed amount. Parameters ---------- amount : float or int Amount to decrease the volume relative to the current volume. """ self._assertMediaPlayer() # get the current volume from the player self.volume = self.volume - amount return self.volume @property def volume(self): """Volume for the audio track for this movie (`int` or `float`). """ self._assertMediaPlayer() return self._handle.get_volume() # thread-safe? @volume.setter def volume(self, value): self._assertMediaPlayer() self._tStream.setVolume(max(min(value, 1.0), 0.0)) @property def loopCount(self): """Number of loops completed since playback started (`int`). This value is reset when either `stop` or `loadMovie` is called. """ return self._loopCount # -------------------------------------------------------------------------- # Timing related methods # # The methods here are used to handle timing, such as converting between # movie and experiment timestamps. # @property def pts(self): """Presentation timestamp for the current movie frame in seconds (`float`). The value for this either comes from the decoder or some other time source. This should be synchronized to the start of the audio track. A value of `-1.0` is invalid. """ if self._handle is None: return -1.0 return self._lastFrame.absTime def getStartAbsTime(self): """Get the absolute experiment time in seconds the movie starts at (`float`). This value reflects the time which the movie would have started if played continuously from the start. Seeking and pausing the movie causes this value to change. Returns ------- float Start time of the movie in absolute experiment time. """ self._assertMediaPlayer() return getTime() - self._lastFrame.absTime def movieToAbsTime(self, movieTime): """Convert a movie timestamp to absolute experiment timestamp. Parameters ---------- movieTime : float Movie timestamp to convert to absolute experiment time. Returns ------- float Timestamp in experiment time which is coincident with the provided `movieTime` timestamp. The returned value should usually be precise down to about five decimal places. """ self._assertMediaPlayer() # type checks on parameters if not isinstance(movieTime, float): raise TypeError( "Value for parameter `movieTime` must have type `float` or " "`int`.") return self.getStartAbsTime() + movieTime def absToMovieTime(self, absTime): """Convert absolute experiment timestamp to a movie timestamp. Parameters ---------- absTime : float Absolute experiment time to convert to movie time. Returns ------- float Movie time referenced to absolute experiment time. If the value is negative then provided `absTime` happens before the beginning of the movie from the current time stamp. The returned value should usually be precise down to about five decimal places. """ self._assertMediaPlayer() # type checks on parameters if not isinstance(absTime, float): raise TypeError( "Value for parameter `absTime` must have type `float` or " "`int`.") return absTime - self.getStartAbsTime() def movieTimeFromFrameIndex(self, frameIdx): """Get the movie time a specific a frame with a given index is scheduled to be presented. This is used to handle logic for seeking through a video feed (if permitted by the player). Parameters ---------- frameIdx : int Frame index. Negative values are accepted but they will return negative timestamps. """ self._assertMediaPlayer() return frameIdx * self._metadata.frameInterval def frameIndexFromMovieTime(self, movieTime): """Get the frame index of a given movie time. Parameters ---------- movieTime : float Timestamp in movie time to convert to a frame index. Returns ------- int Frame index that should be presented at the specified movie time. """ self._assertMediaPlayer() return math.floor(movieTime / self._metadata.frameInterval) @property def isSeekable(self): """Is seeking allowed for the video stream (`bool`)? If `False` then `frameIndex` will increase monotonically. """ return False # fixed for now @property def frameInterval(self): """Duration a single frame is to be presented in seconds (`float`). This is derived from the framerate information in the metadata. If not movie is loaded, the returned value will be invalid. """ return self.metadata.frameInterval @property def frameIndex(self): """Current frame index (`int`). Index of the current frame in the stream. If playing from a file or any other seekable source, this value may not increase monotonically with time. A value of `-1` is invalid, meaning either the video is not started or there is some issue with the stream. """ return self._lastFrame.frameIndex def getPercentageComplete(self): """Provides a value between 0.0 and 100.0, indicating the amount of the movie that has been already played (`float`). """ duration = self.metadata.duration return (self.pts / duration) * 100.0 # -------------------------------------------------------------------------- # Methods for getting video frames from the encoder # def _enqueueFrame(self): """Grab the latest frame from the stream. Returns ------- bool `True` if a frame has been enqueued. Returns `False` if the camera is not ready or if the stream was closed. """ self._assertMediaPlayer() # If the queue is empty, the decoder thread has not yielded a new frame # since the last call. enqueuedFrame = self._tStream.getRecentFrame() if enqueuedFrame is None: return False # Unpack the data we got back ... # Note - Bit messy here, we should just hold onto the `enqueuedFrame` # instance and reference its fields from properties. Keeping like this # for now. frameImage = enqueuedFrame.frameImage streamStatus = enqueuedFrame.streamStatus self._metadata = enqueuedFrame.metadata self.parent.status = self._status = streamStatus.status self._frameIndex = streamStatus.frameIndex self._loopCount = streamStatus.loopCount # status information self._streamTime = streamStatus.streamTime # stream time for the camera # if we have a new frame, update the frame information videoBuffer = frameImage.to_bytearray()[0] videoFrameArray = np.frombuffer(videoBuffer, dtype=np.uint8) # provide the last frame self._lastFrame = MovieFrame( frameIndex=self._frameIndex, absTime=self._streamTime, displayTime=self.metadata.frameInterval, size=frameImage.get_size(), colorData=videoFrameArray, audioChannels=0, # not populated yet ... audioSamples=None, metadata=self.metadata, movieLib=u'ffpyplayer', userData=None) return True def update(self): """Update this player. This get the latest data from the video stream and updates the player accordingly. This should be called at a higher frequency than the frame rate of the movie to avoid frame skips. """ self._assertMediaPlayer() # check if the stream reader thread is present and alive, if not the # movie is finished if not self._tStream.isDone(): self._enqueueFrame() else: self.parent.status = self._status = FINISHED def getMovieFrame(self): """Get the movie frame scheduled to be displayed at the current time. Returns ------- `~psychopy.visual.movies.frame.MovieFrame` Current movie frame. """ self.update() return self._lastFrame def __del__(self): """Cleanup when unloading. """ if hasattr(self, '_tStream'): if self._tStream is not None: if not self._tStream.isDone(): self._tStream.shutdown() self._tStream.join() if hasattr(self, '_handle'): if self._handle is not None: self._handle.close_player()
class VideoStream: def __init__(self, video_source=None): ff_opts = {'paused': True, 'autoexit': False} # Audio options self.video_surce = video_source # Open the video source self.player = MediaPlayer(video_source, ff_opts=ff_opts) # TODO: colocar pausa de tiempo para cargas mediaplayer y obtener los datos # conseguir el frame rate para la sincronizacion self.dalay while self.player.get_metadata()['src_vid_size'] == (0, 0): time.sleep(0.01) data = self.player.get_metadata() print('data -->', data) self.f_rate = data['frame_rate'] print('delay -> ', self.f_rate) self.w, self.h = data['src_vid_size'] print('WxH -> ', self.w, self.h) self.pts = self.player.get_pts( ) # Returns the elapsed play time. float print('pts ->', self.pts) self.duration = data['duration'] print('duration', self.duration) self.pause = self.player.get_pause( ) # Returns whether the player is paused. print('pause ->', self.pause) self.volume = self.player.get_volume( ) # Returns the volume of the audio. loat: A value between 0.0 - 1.0 print('volume ->', self.volume) self.player.toggle_pause( ) # Toggles -alterna- the player’s pause state # self.player.set_pause(False) # auses or un-pauses the file. state: bool cond = True while cond: self.l_frame, self.val = self.player.get_frame() if self.val == 'eof': print('can not open source: ', video_source) break elif self.l_frame is None: time.sleep(0.01) else: self._imagen, self.pts = self.l_frame print('pts ->', self.pts) # arr = self._imagen.to_memoryview()[0] # array image # self.imagen = Image.frombytes("RGB", self.original_size, arr.memview) # self.imagen.show() cond = False # propierties. @property def f_rate(self): return self.__f_rate @f_rate.setter def f_rate(self, val): import math vn = val[0] vd = val[1] if vd <= 1: self.__f_rate = vn elif vd > 1: self.__f_rate = int(round(vn / vd)) else: self.__f_rate = 30 # end properties. def get_frame(self): ''' Return valores: val : 'eof' or 'pause' pts : time location aduio imagen. imagen : frame image Return (val, t, imagen) ''' self.l_frame, self.val = self.player.get_frame() if self.val == 'eof': # condicion final fichero, salimos if and while # self.player.toggle_pause() # ponemos en pause return self.val, None, None elif self.l_frame is None: time.sleep(0.01) return self.val, None, None else: # import math self._imagen, self.pts = self.l_frame return self.val, self.pts, self._imagen # w, h = self._imagen.get_size() # linesize = [int(math.ceil(w * 3 / 32.) * 32)] # self._imagen = pic.Image(plane_buffers=[bytes(b' ') * (h * linesize[0])], # pix_fmt=self._imagen.get_pixel_format(), size=(w, h), linesize=linesize) # self._imagen.get_linesizes(keep_align=True) # if self.new_size is not None: # sws = None # n_w , n_h = self.new_size # if n_w > n_h: # sws = pic.SWScale(w, h, self._imagen.get_pixel_format(), oh=n_h) # else: # sws = pic.SWScale(w, h, self._imagen.get_pixel_format(), ow=n_w) # self._imagen = sws.scale(self._imagen) # size = self._imagen.get_size() # arr = self._imagen.to_memoryview()[0] # array image # self.imagen = Image.frombytes("RGB", size, arr.memview) # print('>>> videostream::get_frame()::self.pts ->', self.pts) def toggle_pause(self): ''' Function: toggle_pause ''' try: # Stopping audio self.player.toggle_pause() # self.player = None except: pass def seek(self, pts=None, relative=False, accurate=False): if not pts: return self.player.seek(pts, relative=False, accurate=False) def snapshot(self, road=None): ''' get current frame ''' img = self.l_frame[0] if img is not None: size = img.get_size() arr = img.to_memoryview()[0] # array image img = Image.frombytes("RGB", size, arr.memview) # vamos a guardar esto. time_str = time.strftime("%d-%m-%Y-%H-%M-%S") frame_name = f"frame-{time_str}.jpg" if not road: ruta = os.path.dirname(self.video_surce) name_out = os.path.join(ruta, frame_name) else: name_out = os.path.join(road, frame_name) img.save(name_out) # Release the video source when the object is destroyed def __del__(self): self.player.close_player() print('__del__')