class AVPlayer(Q.QOpenGLWidget): pfl = Q.QOpenGLVersionProfile() pfl.setVersion(4, 1) pfl.setProfile(Q.QSurfaceFormat.CoreProfile) base_options = { 'input-default-bindings': True, 'input_vo_keyboard': True, 'gapless_audio': True, 'osc': False, 'load_scripts': True, 'ytdl': True, 'vo': 'opengl-cb', 'opengl-fbo-format': 'rgba32f', 'alpha': True # ,'opengl-es':True , 'opengl-swapinterval': 1, 'opengl-waitvsync': False, 'opengl-vsync-fences': 6, 'tscale-radius': 4.0, 'tscale-wparam': 1.0, 'tscale': 'gaussian', 'interpolation': True, 'opengl-backend': 'drm', 'video-sync': 'display-resample', 'display-fps': 60.0, 'interpolation-threshold': 0.0, 'interpolation': True # ,'vo-vaapi-scaling':'nla' # ,'vo-vaapi-scaled-osd':True # ,'vo-vdpau-hqscaling':5 # ,'vo-vdpau-deint':True # ,'vd-lavc-fast':True # ,'vd-lavc-show-all':True , 'hr-seek': 'yes' # ,'hr-seek-framedrop':True , 'hwdec-preload': True, 'hwdec': 'yes', 'opengl-hwdec-interop': 'drmprime-drm' } _reportFlip = False _reportedFlip = False _externalDrive = False _get_proc_address = 'ctypes' _get_proc_address_debug = True novid = Q.pyqtSignal() hasvid = Q.pyqtSignal() reconfig = Q.pyqtSignal(int, int) fullscreen = Q.pyqtSignal(bool) wakeup = Q.pyqtSignal() mpv_event = Q.pyqtSignal() logMessage = Q.pyqtSignal(object) just_die = Q.pyqtSignal() paintRateChanged = Q.pyqtSignal(object) eventRateChanged = Q.pyqtSignal(object) frameRateChanged = Q.pyqtSignal(object) swapRateChanged = Q.pyqtSignal(object) openglInitialized = Q.pyqtSignal(object) mpv = __import__('mpv') def get_property(self, prop): try: prop_name = self.m.attr_name(prop) except: return if not prop_name: return binding = self.findChild(AVProperty, prop_name) if binding: return binding prop_object = AVProperty(prop_name, ctx=self.m, parent=self) setattr(self, prop, prop_object) if not hasattr(self, prop_name): setattr(self, prop_name, prop_object) return prop_object def __getattr__(self, prop): try: prop_name = self.m.attr_name(prop) except: # return super().__getattr__(prop) raise AttributeError if not prop_name: raise AttributeError binding = self.findChild(AVProperty, prop_name) if binding: return binding prop_object = AVProperty(prop_name, ctx=self.m, parent=self) setattr(self, prop, prop_object) if not hasattr(self, prop_name): setattr(self, prop_name, prop_object) return prop_object def sizeHint(self): return Q.QSize(self.img_width, self.img_height) # return self.get_property(prop) @staticmethod def get_options(*args, **kwargs): options = kwargs media = list() for arg in args: if arg.startswith('--'): arg, _, value = arg[2:].partition('=') options[arg] = value or True continue media.append(arg) return options, media def __init__(self, *args, fp=None, **kwargs): super().__init__(*args, **kwargs) self.paintTimes = deque(maxlen=32) self.frameTimes = deque(maxlen=32) self.swapTimes = deque(maxlen=32) self.eventTimes = deque(maxlen=32) self.setMouseTracking(True) self._updated = False self.event_handler_cache = weakref.WeakValueDictionary() self.prop_bindings = dict() import locale locale.setlocale(locale.LC_NUMERIC, 'C') options = self.base_options.copy() new_options, media = self.get_options(*args, **kwargs) options.update(new_options) options[ 'msg-level'] = 'all=status,vd=debug,hwdec=debug,vo=debug,video=v,opengl=debug' options['af'] = 'rubberband=channels=apart:pitch=quality' self.new_frame = False mpv = self.mpv self.m = mpv.Context(**options) m = self.m self.just_die.connect(m.shutdown, Q.Qt.DirectConnection) self.destroyed.connect(self.just_die, Q.Qt.DirectConnection) self.m.set_log_level('terminal-default') self.m.set_wakeup_callback_thread(self.onEvent) # self.m.set_wakeup_callback(self.onEvent) self.m.request_event(self.mpv.EventType.property_change, True) self.m.request_event(self.mpv.EventType.video_reconfig, True) self.m.request_event(self.mpv.EventType.file_loaded, True) self.m.request_event(self.mpv.EventType.log_message, True) self.img_width = 64 self.img_height = 64 self.img_update = None self.tex_id = 0 self.fbo = None self._width = self.img_width self._height = self.img_height if isinstance(fp, pathlib.Path): fp = fp.resolve().absolute().as_posix() elif isinstance(fp, Q.QFileInfo): fp = fp.canonicalFilePath() elif isinstance(fp, Q.QUrl): fp = fp.toString() Q.QTimer.singleShot(0, self.update) if fp: Q.QTimer.singleShot(0, ( lambda: self.m.command('loadfile', fp, 'append', _async=True))) def command(self, *args, **kwargs): self.m.command(*args, **kwargs) def command_string(self, cmdlist): try: self.m.command_string(cmdlist) except: pass def try_command(self, *args, **kwargs): kwargs.setdefault('_async', True) try: self.m.command(*args) except self.mpv.MPVError: pass @staticmethod def get_options(*args, **kwargs): options = kwargs media = list() for arg in args: if arg.startswith('--'): arg, _, value = arg[2:].partition('=') options[arg] = value or True continue media.append(arg) return options, media @Q.pyqtProperty(float) def paintRate(self): if len(self.paintTimes) < 2: return 0 else: return 1e6 * (len(self.paintTimes) - 1) / (self.paintTimes[-1] - self.paintTimes[0]) def paintTimeAppend(self, time): self.paintTimes.append(time) self.paintRateChanged.emit(self.paintRate) @Q.pyqtProperty(float) def frameRate(self): if len(self.frameTimes) < 2: return 0 else: return 1e6 * (len(self.frameTimes) - 1) / (self.frameTimes[-1] - self.frameTimes[0]) def frameTimeAppend(self, time): self.frameTimes.append(time) self.frameRateChanged.emit(self.frameRate) @Q.pyqtProperty(float) def swapRate(self): if len(self.swapTimes) < 2: return 0 else: return 1e6 * (len(self.swapTimes) - 1) / (self.swapTimes[-1] - self.swapTimes[0]) def swapTimeAppend(self, time): self.swapTimes.append(time) self.swapRateChanged.emit(self.swapRate) @Q.pyqtProperty(float) def eventRate(self): if len(self.eventTimes) < 2: return 0 else: return 1e6 * (len(self.eventTimes) - 1) / (self.eventTimes[-1][0] - self.eventTimes[0][0]) def eventTimeAppend(self, time, type=None): self.eventTimes.append((time, type)) self.eventRateChanged.emit(self.eventRate) @Q.pyqtSlot(object) def onEventData(self, event): m = self.m if not m: return self.eventTimeAppend(self.m.time, event.id) if event.id is self.mpv.EventType.shutdown: print("on_event -> shutdown") self.just_die.emit() elif event.id is self.mpv.EventType.idle: self.novid.emit() elif event.id is self.mpv.EventType.start_file: self.hasvid.emit() elif event.id is self.mpv.EventType.file_loaded: ao = m.ao if ao and ao[0]['name'] in ('null', 'none'): m.aid = 0 else: try: if m.current_ao in ('null', 'none'): m.aid = 0 except self.mpv.MPVError as e: if e.code == self.mpv.Error.property_unavailable: m.aid = 0 if not m.aid: if m.af: self.af = m.af m.af = "" else: if self.af and not m.af: m.af = self.af # self.durationChanged.emit(m.duration) elif event.id is self.mpv.EventType.log_message: self.logMessage.emit(event.data) # print(event.data.text,) elif (event.id is self.mpv.EventType.end_file or event.id is self.mpv.EventType.video_reconfig): try: self.m.vid = 1 self.reconfig.emit(self.m.dwidth, -self.m.dheight) except self.mpv.MPVError as ex: self.reconfig.emit(None, None) elif event.id is self.mpv.EventType.property_change: oname = event.data.name data = event.data.data if event.reply_userdata: try: event.reply_userdata(data) except: pass elif event.data.name == 'fullscreen': pass @Q.pyqtSlot() def onEvent(self): m = self.m if not m: return while True: event = m.wait_event(0) if event is None: print("Warning, received a null event.") return elif event.id is self.mpv.EventType.none: return else: try: self.onEventData(event) except Exception as e: print(e) @Q.pyqtSlot() def onWakeup(self): # if not self._externalDrive: self.frameTimeAppend(self.m.time) self.update() def initializeGL(self): print('initialize GL') if self._get_proc_address is 'ctypes': import ctypes, ctypes.util lgl = ctypes.cdll.LoadLibrary(ctypes.util.find_library('GL')) get_proc_address = lgl.glXGetProcAddress get_proc_address.restype = ctypes.c_void_p get_proc_address.argtypes = [ctypes.c_char_p] _get_proc_address = lambda name: get_proc_address( name if isinstance(name, bytes) else name.encode('latin1')) else: qgl_get_proc_address = Q.QGLContext.currentContext().getProcAddress _get_proc_address = lambda name: int( qgl_get_proc_address( name.decode('latin1') if isinstance(name, bytes) else name)) if self._get_proc_address_debug: def getprocaddr(name): res = _get_proc_address(name) print('{} -> address {}'.format(name, res)) return res else: getprocaddr = _get_proc_address self.ogl = self.m.opengl_cb_context self.ogl.init_gl(getprocaddr, None) weakref.finalize(self, lambda: self.ogl.set_update_callback(None)) self.wakeup.connect(self.onWakeup, Q.Qt.QueuedConnection | Q.Qt.UniqueConnection) self.frameSwapped.connect(self.onFrameSwapped) self.ogl.set_update_callback(self.wakeup.emit) self.openglInitialized.emit(Q.QOpenGLContext.currentContext()) @Q.pyqtSlot() def onFrameSwapped(self): self.swapTimeAppend(self.m.time) if self._updated: self._updated = False if self.reportFlip: self.ogl.report_flip(self.m.time) self._reportedFlip = True @property def reportFlip(self): return self._reportFlip @reportFlip.setter def reportFlip(self, val): if self._reportedFlip: return self._reportFlip = bool(val) @property def externalDrive(self): return self._externalDrive @externalDrive.setter def externalDrive(self, val): self._externalDrive = bool(val) def resizeGL(self, w, h): self._width = w self._height = h def paintGL(self): self.paintTimeAppend(self.m.time) self.ogl.draw(self.defaultFramebufferObject(), self._width, -self._height) self._updated = True
class CmdLine(Q.QLineEdit): submitted = Q.pyqtSignal(str, bool) historyPosChanged = Q.pyqtSignal(int) historyChanged = Q.pyqtSignal() historyAppended = Q.pyqtSignal(str) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._history = collections.deque() self._history_pos = 0 self.historyAppended.connect(self.historyChanged, Q.Qt.UniqueConnection) self.returnPressed.connect(self.onReturnPressed, Q.Qt.UniqueConnection) def keyPressEvent(self, evt): if evt.modifiers() == Q.Qt.ControlModifier: if evt.key() == Q.Qt.Key_P or evt.key() == Q.Qt.Key_Up: if self.historyPos + 1 < len(self._history): self.historyPos += 1 self.setText(self._history[self.historyPos]) return elif evt.key() == Q.Qt.Key_N or evt.key() == Q.Qt.Key_Down: if self.historyPos > 0 and self._history: self.historyPos -= 1 self.setText(self._history[self.historyPos - 1]) else: self.historyPos = -1 return elif evt.key() in (Q.Qt.Key_C, Q.Qt.Key_U): self.historyPos = -1 self.clear() return super().keyPressEvent(evt) @property def history(self): return self._history @property def historyPos(self): return self._history_pos @historyPos.setter def historyPos(self, pos): new_pos = max(min(len(self._history) - 1, int(pos)), -1) if new_pos != self._history_pos: self._history_pos = new_pos if new_pos >= 0: self.setText(self.history[new_pos]) else: self.clear() self.historyPosChanged.emit(new_pos) def historyAppend(self, text): if (text): self._history.appendleft(text) self.historyAppended.emit(text) def onReturnPressed(self): text = self.text().strip() if self._history and self._history[0] == text: text = '' if text: self.historyAppend(text) self._history_pos = -1 self.clear() self.historyPosChanged.emit(-1) self.submitted.emit(text, False) elif self._history: self._history_pos = -1 self.clear() self.historyPosChanged.emit(-1) self.submitted.emit(self._history[0], False)
class AVProperty(Q.QObject): mpv = __import__('mpv') valueChanged = Q.pyqtSignal(object) _value = None @property def propertyName(self): return self.objectName() @property def context(self): return self.__context__ @Q.pyqtSlot() def value(self): return self._value # return getattr(self.context,self.objectName()) @Q.pyqtSlot(object) def setValue(self, value): if self._value != value: self._value = value setattr(self.context, self.objectName(), value) def _emitValueChanged(self, value): if self._value != value: self._value = value self.valueChanged.emit(value) def __index__(self): return int(self) def __str__(self): return str(self.value()) def __bytes__(self): return bytes(self.value()) def __init__(self, prop, ctx, *args, **kwargs): super().__init__(*args, **kwargs) self.__context__ = ctx prop = ctx.attr_name(prop) self.setObjectName(prop) # ctx.request_event(ctx.mpv.EventType.property_change,True) try: self._value = self.context.get_property(self.objectName()) except: self._value = None reply_userdata = lambda val: self._emitValueChanged(val) ctx_ref = weakref.ref(ctx) def unobserve_cb(val): ctx = ctx_ref() try: ctx.unobserve_property(val) except: pass self.reply_userdata = reply_userdata ctx.observe_property(prop, reply_userdata) self._finalizer = weakref.finalize(self, unobserve_cb, reply_userdata)