예제 #1
0
파일: av_player.py 프로젝트: gdkar/qtmpv
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
예제 #2
0
파일: av_player.py 프로젝트: gdkar/qtmpv
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)
예제 #3
0
파일: av_player.py 프로젝트: gdkar/qtmpv
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)