boris-behav-obs 8.12__py3-none-any.whl → 9.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of boris-behav-obs might be problematic. Click here for more details.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +28 -39
- boris/add_modifier.py +122 -109
- boris/add_modifier_ui.py +239 -135
- boris/advanced_event_filtering.py +81 -45
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +228 -229
- boris/behavior_binary_table.py +33 -50
- boris/behaviors_coding_map.py +17 -18
- boris/boris_cli.py +6 -25
- boris/cmd_arguments.py +12 -1
- boris/coding_pad.py +42 -49
- boris/config.py +141 -65
- boris/config_file.py +58 -67
- boris/connections.py +107 -61
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2373 -1786
- boris/core_qrc.py +15895 -10743
- boris/core_ui.py +943 -798
- boris/db_functions.py +17 -42
- boris/dev.py +109 -8
- boris/dialog.py +482 -236
- boris/duration_widget.py +9 -14
- boris/edit_event.py +61 -31
- boris/edit_event_ui.py +208 -97
- boris/event_operations.py +408 -293
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +184 -223
- boris/export_observation.py +74 -100
- boris/external_processes.py +123 -98
- boris/geometric_measurement.py +644 -290
- boris/gui_utilities.py +91 -14
- boris/image_overlay.py +4 -4
- boris/import_observations.py +190 -98
- boris/ipc_mpv.py +325 -0
- boris/irr.py +20 -57
- boris/latency.py +31 -24
- boris/measurement_widget.py +14 -18
- boris/media_file.py +17 -19
- boris/menu_options.py +17 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv.py +1 -0
- boris/mpv2.py +732 -705
- boris/observation.py +533 -221
- boris/observation_operations.py +1025 -390
- boris/observation_ui.py +572 -362
- boris/observations_list.py +71 -53
- boris/otx_parser.py +74 -68
- boris/param_panel.py +31 -16
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +90 -60
- boris/plot_data_module.py +25 -33
- boris/plot_events.py +127 -90
- boris/plot_events_rt.py +17 -31
- boris/plot_spectrogram_rt.py +95 -30
- boris/plot_waveform_rt.py +32 -21
- boris/plugins.py +431 -0
- boris/portion/__init__.py +18 -8
- boris/portion/const.py +35 -18
- boris/portion/dict.py +5 -5
- boris/portion/func.py +2 -2
- boris/portion/interval.py +21 -41
- boris/portion/io.py +41 -32
- boris/preferences.py +306 -83
- boris/preferences_ui.py +684 -227
- boris/project.py +448 -293
- boris/project_functions.py +671 -238
- boris/project_import_export.py +213 -222
- boris/project_ui.py +674 -438
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +74 -48
- boris/select_observations.py +20 -198
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +52 -35
- boris/subjects_pad.py +6 -9
- boris/synthetic_time_budget.py +45 -28
- boris/time_budget_functions.py +171 -171
- boris/time_budget_widget.py +84 -114
- boris/transitions.py +41 -47
- boris/utilities.py +627 -236
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +95 -29
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.6.dist-info/entry_points.txt +2 -0
- boris/README.TXT +0 -22
- boris/add_modifier.ui +0 -323
- boris/converters.ui +0 -289
- boris/core.qrc +0 -36
- boris/core.ui +0 -1556
- boris/edit_event.ui +0 -233
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -850
- boris/observation.ui +0 -814
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1069
- boris/project_server.py +0 -236
- boris/vlc.py +0 -10343
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.12.dist-info/METADATA +0 -128
- boris_behav_obs-8.12.dist-info/RECORD +0 -108
- boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
- {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
boris/mpv2.py
CHANGED
|
@@ -2,23 +2,29 @@
|
|
|
2
2
|
# vim: ts=4 sw=4 et
|
|
3
3
|
#
|
|
4
4
|
# Python MPV library module
|
|
5
|
-
# Copyright (C) 2017-
|
|
5
|
+
# Copyright (C) 2017-2024 Sebastian Götte <code@jaseg.net>
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# version.
|
|
7
|
+
# python-mpv inherits the underlying libmpv's license, which can be either GPLv2 or later (default) or LGPLv2.1 or
|
|
8
|
+
# later. For details, see the mpv copyright page here: https://github.com/mpv-player/mpv/blob/master/Copyright
|
|
10
9
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
10
|
+
# You may copy, modify, and redistribute this file under the terms of the GNU General Public License version 2 (or, at
|
|
11
|
+
# your option, any later version), or the GNU Lesser General Public License as published by the Free Software
|
|
12
|
+
# Foundation; either version 2.1 of the License, or (at your option) any later version.
|
|
13
13
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
14
|
+
# This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
|
15
|
+
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License and the GNU
|
|
16
|
+
# Lesser General Public License for more details.
|
|
16
17
|
#
|
|
18
|
+
# You can find copies of the GPLv2 and LGPLv2.1 licenses in the project repository's LICENSE.GPL and LICENSE.LGPL files.
|
|
19
|
+
|
|
20
|
+
__version__ = '1.0.7'
|
|
17
21
|
|
|
18
22
|
from ctypes import *
|
|
19
23
|
import ctypes.util
|
|
20
24
|
import threading
|
|
25
|
+
import queue
|
|
21
26
|
import os
|
|
27
|
+
import os.path
|
|
22
28
|
import sys
|
|
23
29
|
from warnings import warn
|
|
24
30
|
from functools import partial, wraps
|
|
@@ -28,36 +34,42 @@ import collections
|
|
|
28
34
|
import re
|
|
29
35
|
import traceback
|
|
30
36
|
|
|
31
|
-
if os.name ==
|
|
37
|
+
if os.name == 'nt':
|
|
32
38
|
# Note: mpv-2.dll with API version 2 corresponds to mpv v0.35.0. Most things should work with the fallback, too.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
names = ['mpv-2.dll', 'libmpv-2.dll', 'mpv-1.dll']
|
|
40
|
+
for name in names:
|
|
41
|
+
dll = ctypes.util.find_library(name)
|
|
42
|
+
if dll:
|
|
43
|
+
break
|
|
44
|
+
else:
|
|
45
|
+
for name in names:
|
|
46
|
+
dll = os.path.join(os.path.dirname(__file__), name)
|
|
47
|
+
if os.path.isfile(dll):
|
|
48
|
+
break
|
|
49
|
+
else:
|
|
50
|
+
raise OSError('Cannot find mpv-1.dll, mpv-2.dll or libmpv-2.dll in your system %PATH%. One way to deal with this is to ship the dll with your script and put the directory your script is in into %PATH% before "import mpv": os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"] If mpv-1.dll is located elsewhere, you can add that path to os.environ["PATH"].')
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
# flags argument: LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
|
|
54
|
+
# cf. https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexa
|
|
55
|
+
backend = CDLL(dll, 0x00001000 | 0x00000100)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
if not os.path.isabs(dll): # can only be find_library, not the "look next to mpv.py" thing
|
|
58
|
+
raise OSError(f'ctypes.find_library found mpv.dll at {dll}, but ctypes.CDLL could not load it. It looks like find_library found mpv.dll under a relative path entry in %PATH%. Please make sure all paths in %PATH% are absolute. Instead of trying to load mpv.dll from the current working directory, put it somewhere next to your script and add that path to %PATH% using os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"]') from e
|
|
59
|
+
else:
|
|
60
|
+
raise OSError(f'ctypes.find_library found mpv.dll at {dll}, but ctypes.CDLL could not load it.') from e
|
|
61
|
+
fs_enc = 'utf-8'
|
|
62
|
+
|
|
44
63
|
else:
|
|
45
64
|
import locale
|
|
46
|
-
|
|
47
65
|
lc, enc = locale.getlocale(locale.LC_NUMERIC)
|
|
48
66
|
# libmpv requires LC_NUMERIC to be set to "C". Since messing with global variables everyone else relies upon is
|
|
49
67
|
# still better than segfaulting, we are setting LC_NUMERIC to "C".
|
|
50
|
-
locale.setlocale(locale.LC_NUMERIC,
|
|
51
|
-
|
|
52
|
-
sofile = ctypes.util.find_library("mpv")
|
|
68
|
+
locale.setlocale(locale.LC_NUMERIC, 'C')
|
|
53
69
|
|
|
70
|
+
sofile = ctypes.util.find_library('mpv')
|
|
54
71
|
if sofile is None:
|
|
55
|
-
raise OSError(
|
|
56
|
-
"Cannot find libmpv in the usual places. Depending on your distro, you may try installing an "
|
|
57
|
-
"mpv-devel or mpv-libs package. If you have libmpv around but this script can't find it, consult "
|
|
58
|
-
"the documentation for ctypes.util.find_library which this script uses to look up the library "
|
|
59
|
-
"filename."
|
|
60
|
-
)
|
|
72
|
+
raise OSError("Cannot find libmpv in the usual places. Depending on your distro, you may try installing an mpv-devel or mpv-libs package. If you have libmpv around but this script can't find it, consult the documentation for ctypes.util.find_library which this script uses to look up the library filename.")
|
|
61
73
|
backend = CDLL(sofile)
|
|
62
74
|
fs_enc = sys.getfilesystemencoding()
|
|
63
75
|
|
|
@@ -65,83 +77,72 @@ else:
|
|
|
65
77
|
class ShutdownError(SystemError):
|
|
66
78
|
pass
|
|
67
79
|
|
|
68
|
-
|
|
69
80
|
class EventOverflowError(SystemError):
|
|
70
81
|
pass
|
|
71
82
|
|
|
72
|
-
|
|
73
83
|
class MpvHandle(c_void_p):
|
|
74
84
|
pass
|
|
75
85
|
|
|
76
|
-
|
|
77
86
|
class MpvRenderCtxHandle(c_void_p):
|
|
78
87
|
pass
|
|
79
88
|
|
|
80
|
-
|
|
81
89
|
class PropertyUnavailableError(AttributeError):
|
|
82
90
|
pass
|
|
83
91
|
|
|
84
|
-
|
|
85
92
|
class ErrorCode(object):
|
|
86
93
|
"""For documentation on these, see mpv's libmpv/client.h."""
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
GENERIC = -20
|
|
94
|
+
SUCCESS = 0
|
|
95
|
+
EVENT_QUEUE_FULL = -1
|
|
96
|
+
NOMEM = -2
|
|
97
|
+
UNINITIALIZED = -3
|
|
98
|
+
INVALID_PARAMETER = -4
|
|
99
|
+
OPTION_NOT_FOUND = -5
|
|
100
|
+
OPTION_FORMAT = -6
|
|
101
|
+
OPTION_ERROR = -7
|
|
102
|
+
PROPERTY_NOT_FOUND = -8
|
|
103
|
+
PROPERTY_FORMAT = -9
|
|
104
|
+
PROPERTY_UNAVAILABLE = -10
|
|
105
|
+
PROPERTY_ERROR = -11
|
|
106
|
+
COMMAND = -12
|
|
107
|
+
LOADING_FAILED = -13
|
|
108
|
+
AO_INIT_FAILED = -14
|
|
109
|
+
VO_INIT_FAILED = -15
|
|
110
|
+
NOTHING_TO_PLAY = -16
|
|
111
|
+
UNKNOWN_FORMAT = -17
|
|
112
|
+
UNSUPPORTED = -18
|
|
113
|
+
NOT_IMPLEMENTED = -19
|
|
114
|
+
GENERIC = -20
|
|
109
115
|
|
|
110
116
|
EXCEPTION_DICT = {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
),
|
|
137
|
-
-18: lambda *a: ValueError("Generic error for signaling that certain system requirements are not fulfilled"),
|
|
138
|
-
-19: lambda *a: NotImplementedError("The API function which was called is a stub only"),
|
|
139
|
-
-20: lambda *a: RuntimeError("Unspecified error"),
|
|
140
|
-
}
|
|
117
|
+
0: None,
|
|
118
|
+
-1: lambda *a: MemoryError('mpv event queue full', *a),
|
|
119
|
+
-2: lambda *a: MemoryError('mpv cannot allocate memory', *a),
|
|
120
|
+
-3: lambda *a: ValueError('Uninitialized mpv handle used', *a),
|
|
121
|
+
-4: lambda *a: ValueError('Invalid value for mpv parameter', *a),
|
|
122
|
+
-5: lambda *a: AttributeError('mpv option does not exist', *a),
|
|
123
|
+
-6: lambda *a: TypeError('Tried to set mpv option using wrong format', *a),
|
|
124
|
+
-7: lambda *a: ValueError('Invalid value for mpv option', *a),
|
|
125
|
+
-8: lambda *a: AttributeError('mpv property does not exist', *a),
|
|
126
|
+
# Currently (mpv 0.18.1) there is a bug causing a PROPERTY_FORMAT error to be returned instead of
|
|
127
|
+
# INVALID_PARAMETER when setting a property-mapped option to an invalid value.
|
|
128
|
+
-9: lambda *a: TypeError('Tried to get/set mpv property using wrong format, or passed invalid value', *a),
|
|
129
|
+
-10: lambda *a: PropertyUnavailableError('mpv property is not available', *a),
|
|
130
|
+
-11: lambda *a: RuntimeError('Generic error getting or setting mpv property', *a),
|
|
131
|
+
-12: lambda *a: SystemError('Error running mpv command', *a),
|
|
132
|
+
-14: lambda *a: RuntimeError('Initializing the audio output failed', *a),
|
|
133
|
+
-15: lambda *a: RuntimeError('Initializing the video output failed'),
|
|
134
|
+
-16: lambda *a: RuntimeError('There was no audio or video data to play. This also happens if the file '
|
|
135
|
+
'was recognized, but did not contain any audio or video streams, or no '
|
|
136
|
+
'streams were selected.'),
|
|
137
|
+
-17: lambda *a: RuntimeError('When trying to load the file, the file format could not be determined, '
|
|
138
|
+
'or the file was too broken to open it'),
|
|
139
|
+
-18: lambda *a: ValueError('Generic error for signaling that certain system requirements are not fulfilled'),
|
|
140
|
+
-19: lambda *a: NotImplementedError('The API function which was called is a stub only'),
|
|
141
|
+
-20: lambda *a: RuntimeError('Unspecified error') }
|
|
141
142
|
|
|
142
143
|
@staticmethod
|
|
143
144
|
def human_readable(ec):
|
|
144
|
-
return _mpv_error_string(ec).decode(
|
|
145
|
+
return _mpv_error_string(ec).decode('utf-8')
|
|
145
146
|
|
|
146
147
|
@staticmethod
|
|
147
148
|
def default_error_handler(ec, *args):
|
|
@@ -160,61 +161,52 @@ class ErrorCode(object):
|
|
|
160
161
|
if ex:
|
|
161
162
|
raise ex
|
|
162
163
|
|
|
163
|
-
|
|
164
164
|
MpvGlGetProcAddressFn = CFUNCTYPE(c_void_p, c_void_p, c_char_p)
|
|
165
|
-
|
|
166
|
-
|
|
167
165
|
class MpvOpenGLInitParams(Structure):
|
|
168
|
-
_fields_ = [
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
("extra_exts", c_void_p),
|
|
172
|
-
]
|
|
166
|
+
_fields_ = [('get_proc_address', MpvGlGetProcAddressFn),
|
|
167
|
+
('get_proc_address_ctx', c_void_p),
|
|
168
|
+
('extra_exts', c_void_p)]
|
|
173
169
|
|
|
174
170
|
def __init__(self, get_proc_address):
|
|
175
171
|
self.get_proc_address = get_proc_address
|
|
176
172
|
self.get_proc_address_ctx = None
|
|
177
173
|
self.extra_exts = None
|
|
178
174
|
|
|
179
|
-
|
|
180
175
|
class MpvOpenGLFBO(Structure):
|
|
181
|
-
_fields_ = [(
|
|
176
|
+
_fields_ = [('fbo', c_int),
|
|
177
|
+
('w', c_int),
|
|
178
|
+
('h', c_int),
|
|
179
|
+
('internal_format', c_int)]
|
|
182
180
|
|
|
183
181
|
def __init__(self, w, h, fbo=0, internal_format=0):
|
|
184
182
|
self.w, self.h = w, h
|
|
185
183
|
self.fbo = fbo
|
|
186
184
|
self.internal_format = internal_format
|
|
187
185
|
|
|
188
|
-
|
|
189
186
|
class MpvRenderFrameInfo(Structure):
|
|
190
|
-
_fields_ = [(
|
|
187
|
+
_fields_ = [('flags', c_int64),
|
|
188
|
+
('target_time', c_int64)]
|
|
191
189
|
|
|
192
190
|
def as_dict(self):
|
|
193
|
-
return {
|
|
194
|
-
|
|
191
|
+
return {'flags': self.flags,
|
|
192
|
+
'target_time': self.target_time}
|
|
195
193
|
|
|
196
194
|
class MpvOpenGLDRMParams(Structure):
|
|
197
|
-
_fields_ = [
|
|
198
|
-
(
|
|
199
|
-
(
|
|
200
|
-
(
|
|
201
|
-
(
|
|
202
|
-
("render_fd", c_int),
|
|
203
|
-
]
|
|
204
|
-
|
|
195
|
+
_fields_ = [('fd', c_int),
|
|
196
|
+
('crtc_id', c_int),
|
|
197
|
+
('connector_id', c_int),
|
|
198
|
+
('atomic_request_ptr', c_void_p),
|
|
199
|
+
('render_fd', c_int)]
|
|
205
200
|
|
|
206
201
|
class MpvOpenGLDRMDrawSurfaceSize(Structure):
|
|
207
|
-
_fields_ = [(
|
|
208
|
-
|
|
202
|
+
_fields_ = [('width', c_int), ('height', c_int)]
|
|
209
203
|
|
|
210
204
|
class MpvOpenGLDRMParamsV2(Structure):
|
|
211
|
-
_fields_ = [
|
|
212
|
-
(
|
|
213
|
-
(
|
|
214
|
-
(
|
|
215
|
-
(
|
|
216
|
-
("render_fd", c_int),
|
|
217
|
-
]
|
|
205
|
+
_fields_ = [('fd', c_int),
|
|
206
|
+
('crtc_id', c_int),
|
|
207
|
+
('connector_id', c_int),
|
|
208
|
+
('atomic_request_ptr', c_void_p),
|
|
209
|
+
('render_fd', c_int)]
|
|
218
210
|
|
|
219
211
|
def __init__(self, crtc_id, connector_id, atomic_request_ptr, fd=-1, render_fd=-1):
|
|
220
212
|
self.crtc_id, self.connector_id = crtc_id, connector_id
|
|
@@ -223,29 +215,28 @@ class MpvOpenGLDRMParamsV2(Structure):
|
|
|
223
215
|
|
|
224
216
|
|
|
225
217
|
class MpvRenderParam(Structure):
|
|
226
|
-
_fields_ = [(
|
|
218
|
+
_fields_ = [('type_id', c_int),
|
|
219
|
+
('data', c_void_p)]
|
|
227
220
|
|
|
228
221
|
# maps human-readable type name to (type_id, argtype) tuple.
|
|
229
222
|
# The type IDs come from libmpv/render.h
|
|
230
|
-
TYPES = {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
"drm_display_v2": (16, MpvOpenGLDRMParamsV2),
|
|
248
|
-
}
|
|
223
|
+
TYPES = {"invalid" :(0, None),
|
|
224
|
+
"api_type" :(1, str),
|
|
225
|
+
"opengl_init_params" :(2, MpvOpenGLInitParams),
|
|
226
|
+
"opengl_fbo" :(3, MpvOpenGLFBO),
|
|
227
|
+
"flip_y" :(4, bool),
|
|
228
|
+
"depth" :(5, int),
|
|
229
|
+
"icc_profile" :(6, bytes),
|
|
230
|
+
"ambient_light" :(7, int),
|
|
231
|
+
"x11_display" :(8, c_void_p),
|
|
232
|
+
"wl_display" :(9, c_void_p),
|
|
233
|
+
"advanced_control" :(10, bool),
|
|
234
|
+
"next_frame_info" :(11, MpvRenderFrameInfo),
|
|
235
|
+
"block_for_target_time" :(12, bool),
|
|
236
|
+
"skip_rendering" :(13, bool),
|
|
237
|
+
"drm_display" :(14, MpvOpenGLDRMParams),
|
|
238
|
+
"drm_draw_surface_size" :(15, MpvOpenGLDRMDrawSurfaceSize),
|
|
239
|
+
"drm_display_v2" :(16, MpvOpenGLDRMParamsV2)}
|
|
249
240
|
|
|
250
241
|
def __init__(self, name, value=None):
|
|
251
242
|
if name not in self.TYPES:
|
|
@@ -256,7 +247,7 @@ class MpvRenderParam(Structure):
|
|
|
256
247
|
self.data = c_void_p()
|
|
257
248
|
elif cons is str:
|
|
258
249
|
self.value = value
|
|
259
|
-
self.data = cast(c_char_p(value.encode(
|
|
250
|
+
self.data = cast(c_char_p(value.encode('utf-8')), c_void_p)
|
|
260
251
|
elif cons is bytes:
|
|
261
252
|
self.value = MpvByteArray(value)
|
|
262
253
|
self.data = cast(pointer(self.value), c_void_p)
|
|
@@ -270,110 +261,82 @@ class MpvRenderParam(Structure):
|
|
|
270
261
|
self.value = cons(**value)
|
|
271
262
|
self.data = cast(pointer(self.value), c_void_p)
|
|
272
263
|
|
|
273
|
-
|
|
274
264
|
def kwargs_to_render_param_array(kwargs):
|
|
275
|
-
t = MpvRenderParam * (len(kwargs)
|
|
276
|
-
return t(*kwargs.items(), (
|
|
277
|
-
|
|
265
|
+
t = MpvRenderParam * (len(kwargs)+1)
|
|
266
|
+
return t(*kwargs.items(), ('invalid', None))
|
|
278
267
|
|
|
279
268
|
class MpvFormat(c_int):
|
|
280
|
-
NONE
|
|
281
|
-
STRING
|
|
282
|
-
OSD_STRING
|
|
283
|
-
FLAG
|
|
284
|
-
INT64
|
|
285
|
-
DOUBLE
|
|
286
|
-
NODE
|
|
287
|
-
NODE_ARRAY
|
|
288
|
-
NODE_MAP
|
|
289
|
-
BYTE_ARRAY
|
|
269
|
+
NONE = 0
|
|
270
|
+
STRING = 1
|
|
271
|
+
OSD_STRING = 2
|
|
272
|
+
FLAG = 3
|
|
273
|
+
INT64 = 4
|
|
274
|
+
DOUBLE = 5
|
|
275
|
+
NODE = 6
|
|
276
|
+
NODE_ARRAY = 7
|
|
277
|
+
NODE_MAP = 8
|
|
278
|
+
BYTE_ARRAY = 9
|
|
290
279
|
|
|
291
280
|
def __eq__(self, other):
|
|
292
281
|
return self is other or self.value == other or self.value == int(other)
|
|
293
282
|
|
|
294
283
|
def __repr__(self):
|
|
295
|
-
return [
|
|
296
|
-
|
|
297
|
-
"STRING",
|
|
298
|
-
"OSD_STRING",
|
|
299
|
-
"FLAG",
|
|
300
|
-
"INT64",
|
|
301
|
-
"DOUBLE",
|
|
302
|
-
"NODE",
|
|
303
|
-
"NODE_ARRAY",
|
|
304
|
-
"NODE_MAP",
|
|
305
|
-
"BYTE_ARRAY",
|
|
306
|
-
][self.value]
|
|
284
|
+
return ['NONE', 'STRING', 'OSD_STRING', 'FLAG', 'INT64', 'DOUBLE', 'NODE', 'NODE_ARRAY', 'NODE_MAP',
|
|
285
|
+
'BYTE_ARRAY'][self.value]
|
|
307
286
|
|
|
308
287
|
def __hash__(self):
|
|
309
288
|
return self.value
|
|
310
289
|
|
|
311
290
|
|
|
312
291
|
class MpvEventID(c_int):
|
|
313
|
-
NONE
|
|
314
|
-
SHUTDOWN
|
|
315
|
-
LOG_MESSAGE
|
|
316
|
-
GET_PROPERTY_REPLY
|
|
317
|
-
SET_PROPERTY_REPLY
|
|
318
|
-
COMMAND_REPLY
|
|
319
|
-
START_FILE
|
|
320
|
-
END_FILE
|
|
321
|
-
FILE_LOADED
|
|
322
|
-
CLIENT_MESSAGE
|
|
323
|
-
VIDEO_RECONFIG
|
|
324
|
-
AUDIO_RECONFIG
|
|
325
|
-
SEEK
|
|
326
|
-
PLAYBACK_RESTART
|
|
327
|
-
PROPERTY_CHANGE
|
|
328
|
-
QUEUE_OVERFLOW
|
|
329
|
-
HOOK
|
|
330
|
-
|
|
331
|
-
ANY = (
|
|
332
|
-
|
|
333
|
-
LOG_MESSAGE,
|
|
334
|
-
GET_PROPERTY_REPLY,
|
|
335
|
-
SET_PROPERTY_REPLY,
|
|
336
|
-
COMMAND_REPLY,
|
|
337
|
-
START_FILE,
|
|
338
|
-
END_FILE,
|
|
339
|
-
FILE_LOADED,
|
|
340
|
-
CLIENT_MESSAGE,
|
|
341
|
-
VIDEO_RECONFIG,
|
|
342
|
-
AUDIO_RECONFIG,
|
|
343
|
-
SEEK,
|
|
344
|
-
PLAYBACK_RESTART,
|
|
345
|
-
PROPERTY_CHANGE,
|
|
346
|
-
)
|
|
292
|
+
NONE = 0
|
|
293
|
+
SHUTDOWN = 1
|
|
294
|
+
LOG_MESSAGE = 2
|
|
295
|
+
GET_PROPERTY_REPLY = 3
|
|
296
|
+
SET_PROPERTY_REPLY = 4
|
|
297
|
+
COMMAND_REPLY = 5
|
|
298
|
+
START_FILE = 6
|
|
299
|
+
END_FILE = 7
|
|
300
|
+
FILE_LOADED = 8
|
|
301
|
+
CLIENT_MESSAGE = 16
|
|
302
|
+
VIDEO_RECONFIG = 17
|
|
303
|
+
AUDIO_RECONFIG = 18
|
|
304
|
+
SEEK = 20
|
|
305
|
+
PLAYBACK_RESTART = 21
|
|
306
|
+
PROPERTY_CHANGE = 22
|
|
307
|
+
QUEUE_OVERFLOW = 24
|
|
308
|
+
HOOK = 25
|
|
309
|
+
|
|
310
|
+
ANY = ( SHUTDOWN, LOG_MESSAGE, GET_PROPERTY_REPLY, SET_PROPERTY_REPLY, COMMAND_REPLY, START_FILE, END_FILE,
|
|
311
|
+
FILE_LOADED, CLIENT_MESSAGE, VIDEO_RECONFIG, AUDIO_RECONFIG, SEEK, PLAYBACK_RESTART, PROPERTY_CHANGE)
|
|
347
312
|
|
|
348
313
|
def __repr__(self):
|
|
349
314
|
return f'<MpvEventID {self.value} {_mpv_event_name(self.value).decode("utf-8")}>'
|
|
350
315
|
|
|
351
316
|
@classmethod
|
|
352
317
|
def from_str(kls, s):
|
|
353
|
-
return getattr(kls, s.upper().replace(
|
|
318
|
+
return getattr(kls, s.upper().replace('-', '_'))
|
|
354
319
|
|
|
355
320
|
|
|
356
321
|
identity_decoder = lambda b: b
|
|
357
|
-
strict_decoder = lambda b: b.decode(
|
|
358
|
-
|
|
359
|
-
|
|
322
|
+
strict_decoder = lambda b: b.decode('utf-8')
|
|
360
323
|
def lazy_decoder(b):
|
|
361
324
|
try:
|
|
362
|
-
return b.decode(
|
|
325
|
+
return b.decode('utf-8')
|
|
363
326
|
except UnicodeDecodeError:
|
|
364
327
|
return b
|
|
365
328
|
|
|
366
|
-
|
|
367
329
|
class MpvNodeList(Structure):
|
|
368
330
|
def array_value(self, decoder=identity_decoder):
|
|
369
|
-
return [self.values[i].node_value(decoder) for i in range(self.num)]
|
|
331
|
+
return [ self.values[i].node_value(decoder) for i in range(self.num) ]
|
|
370
332
|
|
|
371
333
|
def dict_value(self, decoder=identity_decoder):
|
|
372
|
-
return {self.keys[i].decode(
|
|
373
|
-
|
|
334
|
+
return { self.keys[i].decode('utf-8'):
|
|
335
|
+
self.values[i].node_value(decoder) for i in range(self.num) }
|
|
374
336
|
|
|
375
337
|
class MpvByteArray(Structure):
|
|
376
|
-
_fields_ = [(
|
|
338
|
+
_fields_ = [('data', c_void_p),
|
|
339
|
+
('size', c_size_t)]
|
|
377
340
|
|
|
378
341
|
def __init__(self, value):
|
|
379
342
|
self._value = value
|
|
@@ -381,8 +344,7 @@ class MpvByteArray(Structure):
|
|
|
381
344
|
self.size = len(value)
|
|
382
345
|
|
|
383
346
|
def bytes_value(self):
|
|
384
|
-
return cast(self.data, POINTER(c_char))[:
|
|
385
|
-
|
|
347
|
+
return cast(self.data, POINTER(c_char))[:self.size]
|
|
386
348
|
|
|
387
349
|
class MpvNode(Structure):
|
|
388
350
|
def node_value(self, decoder=identity_decoder):
|
|
@@ -395,7 +357,7 @@ class MpvNode(Structure):
|
|
|
395
357
|
elif fmt == MpvFormat.STRING:
|
|
396
358
|
return decoder(v.string)
|
|
397
359
|
elif fmt == MpvFormat.OSD_STRING:
|
|
398
|
-
return v.string.decode(
|
|
360
|
+
return v.string.decode('utf-8')
|
|
399
361
|
elif fmt == MpvFormat.FLAG:
|
|
400
362
|
return bool(v.flag)
|
|
401
363
|
elif fmt == MpvFormat.INT64:
|
|
@@ -403,7 +365,7 @@ class MpvNode(Structure):
|
|
|
403
365
|
elif fmt == MpvFormat.DOUBLE:
|
|
404
366
|
return v.double
|
|
405
367
|
else:
|
|
406
|
-
if not v.node:
|
|
368
|
+
if not v.node: # Check for null pointer
|
|
407
369
|
return None
|
|
408
370
|
if fmt == MpvFormat.NODE:
|
|
409
371
|
return v.node.contents.node_value(decoder)
|
|
@@ -414,42 +376,43 @@ class MpvNode(Structure):
|
|
|
414
376
|
elif fmt == MpvFormat.BYTE_ARRAY:
|
|
415
377
|
return v.byte_array.contents.bytes_value()
|
|
416
378
|
else:
|
|
417
|
-
raise TypeError(
|
|
418
|
-
|
|
379
|
+
raise TypeError('Unknown MPV node format {}. Please submit a bug report.'.format(fmt))
|
|
419
380
|
|
|
420
381
|
class MpvNodeUnion(Union):
|
|
421
|
-
_fields_ = [
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
382
|
+
_fields_ = [('string', c_char_p),
|
|
383
|
+
('flag', c_int),
|
|
384
|
+
('int64', c_int64),
|
|
385
|
+
('double', c_double),
|
|
386
|
+
('node', POINTER(MpvNode)),
|
|
387
|
+
('list', POINTER(MpvNodeList)),
|
|
388
|
+
('map', POINTER(MpvNodeList)),
|
|
389
|
+
('byte_array', POINTER(MpvByteArray))]
|
|
390
|
+
|
|
391
|
+
MpvNode._fields_ = [('val', MpvNodeUnion),
|
|
392
|
+
('format', MpvFormat)]
|
|
393
|
+
|
|
394
|
+
MpvNodeList._fields_ = [('num', c_int),
|
|
395
|
+
('values', POINTER(MpvNode)),
|
|
396
|
+
('keys', POINTER(c_char_p))]
|
|
437
397
|
|
|
438
398
|
class MpvEvent(Structure):
|
|
439
|
-
_fields_ = [(
|
|
399
|
+
_fields_ = [('event_id', MpvEventID),
|
|
400
|
+
('error', c_int),
|
|
401
|
+
('reply_userdata', c_ulonglong),
|
|
402
|
+
('_data', c_void_p)]
|
|
440
403
|
|
|
441
404
|
@property
|
|
442
405
|
def data(self):
|
|
443
406
|
dtype = {
|
|
444
|
-
MpvEventID.GET_PROPERTY_REPLY:
|
|
445
|
-
MpvEventID.PROPERTY_CHANGE:
|
|
446
|
-
MpvEventID.LOG_MESSAGE:
|
|
447
|
-
MpvEventID.CLIENT_MESSAGE:
|
|
448
|
-
MpvEventID.START_FILE:
|
|
449
|
-
MpvEventID.END_FILE:
|
|
450
|
-
MpvEventID.HOOK:
|
|
451
|
-
MpvEventID.COMMAND_REPLY:
|
|
452
|
-
|
|
407
|
+
MpvEventID.GET_PROPERTY_REPLY: MpvEventProperty,
|
|
408
|
+
MpvEventID.PROPERTY_CHANGE: MpvEventProperty,
|
|
409
|
+
MpvEventID.LOG_MESSAGE: MpvEventLogMessage,
|
|
410
|
+
MpvEventID.CLIENT_MESSAGE: MpvEventClientMessage,
|
|
411
|
+
MpvEventID.START_FILE: MpvEventStartFile,
|
|
412
|
+
MpvEventID.END_FILE: MpvEventEndFile,
|
|
413
|
+
MpvEventID.HOOK: MpvEventHook,
|
|
414
|
+
MpvEventID.COMMAND_REPLY: MpvEventCommand,
|
|
415
|
+
}.get(self.event_id.value)
|
|
453
416
|
return cast(self._data, POINTER(dtype)).contents if dtype else None
|
|
454
417
|
|
|
455
418
|
def as_dict(self, decoder=identity_decoder):
|
|
@@ -461,11 +424,12 @@ class MpvEvent(Structure):
|
|
|
461
424
|
|
|
462
425
|
def __str__(self):
|
|
463
426
|
d = self.data
|
|
464
|
-
return f
|
|
465
|
-
|
|
427
|
+
return f'<{type(d).__name__} ({self.event_id.value}) err={self.error} p={self.reply_userdata:016x} d={self.as_dict()}>'
|
|
466
428
|
|
|
467
429
|
class MpvEventProperty(Structure):
|
|
468
|
-
_fields_ = [(
|
|
430
|
+
_fields_ = [('_name', c_char_p),
|
|
431
|
+
('format', MpvFormat),
|
|
432
|
+
('data', MpvNodeUnion)]
|
|
469
433
|
|
|
470
434
|
@property
|
|
471
435
|
def name(self):
|
|
@@ -475,9 +439,10 @@ class MpvEventProperty(Structure):
|
|
|
475
439
|
def value(self):
|
|
476
440
|
return MpvNode.node_cast_value(self.data, self.format.value, decoder=lazy_decoder)
|
|
477
441
|
|
|
478
|
-
|
|
479
442
|
class MpvEventLogMessage(Structure):
|
|
480
|
-
_fields_ = [(
|
|
443
|
+
_fields_ = [('_prefix', c_char_p),
|
|
444
|
+
('_level', c_char_p),
|
|
445
|
+
('_text', c_char_p)]
|
|
481
446
|
|
|
482
447
|
@property
|
|
483
448
|
def prefix(self):
|
|
@@ -491,34 +456,35 @@ class MpvEventLogMessage(Structure):
|
|
|
491
456
|
def text(self):
|
|
492
457
|
return lazy_decoder(self._text)
|
|
493
458
|
|
|
494
|
-
|
|
495
459
|
class MpvEventEndFile(Structure):
|
|
496
|
-
_fields_ = [("reason", c_int), ("error", c_int)]
|
|
497
|
-
|
|
498
|
-
EOF = 0
|
|
499
|
-
RESTARTED = 1
|
|
500
|
-
ABORTED = 2
|
|
501
|
-
QUIT = 3
|
|
502
|
-
ERROR = 4
|
|
503
|
-
REDIRECT = 5
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
class MpvEventStartFile(Structure):
|
|
507
460
|
_fields_ = [
|
|
508
|
-
(
|
|
461
|
+
('reason', c_int),
|
|
462
|
+
('error', c_int),
|
|
463
|
+
('playlist_entry_id', c_ulonglong),
|
|
464
|
+
('playlist_insert_id', c_ulonglong),
|
|
465
|
+
('playlist_insert_num_entries', c_int),
|
|
509
466
|
]
|
|
467
|
+
|
|
468
|
+
EOF = 0
|
|
469
|
+
RESTARTED = 1
|
|
470
|
+
ABORTED = 2
|
|
471
|
+
QUIT = 3
|
|
472
|
+
ERROR = 4
|
|
473
|
+
REDIRECT = 5
|
|
510
474
|
|
|
475
|
+
class MpvEventStartFile(Structure):
|
|
476
|
+
_fields_ = [('playlist_entry_id', c_ulonglong),]
|
|
511
477
|
|
|
512
478
|
class MpvEventClientMessage(Structure):
|
|
513
|
-
_fields_ = [(
|
|
479
|
+
_fields_ = [('_num_args', c_int),
|
|
480
|
+
('_args', POINTER(c_char_p))]
|
|
514
481
|
|
|
515
482
|
@property
|
|
516
483
|
def args(self):
|
|
517
|
-
return [self._args[i] for i in range(self._num_args)]
|
|
518
|
-
|
|
484
|
+
return [ self._args[i] for i in range(self._num_args) ]
|
|
519
485
|
|
|
520
486
|
class MpvEventCommand(Structure):
|
|
521
|
-
_fields_ = [(
|
|
487
|
+
_fields_ = [('_result', MpvNode)]
|
|
522
488
|
|
|
523
489
|
def unpack(self, decoder=identity_decoder):
|
|
524
490
|
return self._result.node_value(decoder=decoder)
|
|
@@ -527,35 +493,28 @@ class MpvEventCommand(Structure):
|
|
|
527
493
|
def result(self):
|
|
528
494
|
return self.unpack()
|
|
529
495
|
|
|
530
|
-
|
|
531
496
|
class MpvEventHook(Structure):
|
|
532
|
-
_fields_ = [
|
|
533
|
-
|
|
534
|
-
("id", c_ulonglong),
|
|
535
|
-
]
|
|
497
|
+
_fields_ = [('_name', c_char_p),
|
|
498
|
+
('id', c_ulonglong),]
|
|
536
499
|
|
|
500
|
+
|
|
537
501
|
@property
|
|
538
502
|
def name(self):
|
|
539
503
|
return self._name.decode("utf-8")
|
|
540
504
|
|
|
541
|
-
|
|
542
505
|
StreamReadFn = CFUNCTYPE(c_int64, c_void_p, POINTER(c_char), c_uint64)
|
|
543
506
|
StreamSeekFn = CFUNCTYPE(c_int64, c_void_p, c_int64)
|
|
544
507
|
StreamSizeFn = CFUNCTYPE(c_int64, c_void_p)
|
|
545
508
|
StreamCloseFn = CFUNCTYPE(None, c_void_p)
|
|
546
509
|
StreamCancelFn = CFUNCTYPE(None, c_void_p)
|
|
547
510
|
|
|
548
|
-
|
|
549
511
|
class StreamCallbackInfo(Structure):
|
|
550
|
-
_fields_ = [
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
("cancel", StreamCancelFn),
|
|
557
|
-
]
|
|
558
|
-
|
|
512
|
+
_fields_ = [('cookie', c_void_p),
|
|
513
|
+
('read', StreamReadFn),
|
|
514
|
+
('seek', StreamSeekFn),
|
|
515
|
+
('size', StreamSizeFn),
|
|
516
|
+
('close', StreamCloseFn),
|
|
517
|
+
('cancel', StreamCancelFn)]
|
|
559
518
|
|
|
560
519
|
StreamOpenFn = CFUNCTYPE(c_int, c_void_p, c_char_p, POINTER(StreamCallbackInfo))
|
|
561
520
|
|
|
@@ -563,7 +522,6 @@ WakeupCallback = CFUNCTYPE(None, c_void_p)
|
|
|
563
522
|
|
|
564
523
|
RenderUpdateFn = CFUNCTYPE(None, c_void_p)
|
|
565
524
|
|
|
566
|
-
|
|
567
525
|
def _handle_func(name, args, restype, errcheck, ctx=MpvHandle, deprecated=False):
|
|
568
526
|
func = getattr(backend, name)
|
|
569
527
|
func.argtypes = [ctx] + args if ctx else args
|
|
@@ -572,20 +530,17 @@ def _handle_func(name, args, restype, errcheck, ctx=MpvHandle, deprecated=False)
|
|
|
572
530
|
if errcheck is not None:
|
|
573
531
|
func.errcheck = errcheck
|
|
574
532
|
if deprecated:
|
|
575
|
-
|
|
576
533
|
@wraps(func)
|
|
577
534
|
def wrapper(*args, **kwargs):
|
|
578
|
-
if not wrapper.warned:
|
|
535
|
+
if not wrapper.warned: # Only warn on first invocation to prevent spamming
|
|
579
536
|
warn("Backend C api has been deprecated: " + name, DeprecationWarning, stacklevel=2)
|
|
580
537
|
wrapper.warned = True
|
|
581
538
|
return func(*args, **kwargs)
|
|
582
|
-
|
|
583
539
|
wrapper.warned = False
|
|
584
540
|
|
|
585
|
-
globals()[
|
|
541
|
+
globals()['_'+name] = wrapper
|
|
586
542
|
else:
|
|
587
|
-
globals()[
|
|
588
|
-
|
|
543
|
+
globals()['_'+name] = func
|
|
589
544
|
|
|
590
545
|
def bytes_free_errcheck(res, func, *args):
|
|
591
546
|
notnull_errcheck(res, func, *args)
|
|
@@ -593,32 +548,23 @@ def bytes_free_errcheck(res, func, *args):
|
|
|
593
548
|
_mpv_free(res)
|
|
594
549
|
return rv
|
|
595
550
|
|
|
596
|
-
|
|
597
551
|
def notnull_errcheck(res, func, *args):
|
|
598
552
|
if res is None:
|
|
599
|
-
raise RuntimeError(
|
|
600
|
-
|
|
601
|
-
"Please consult your local debugger.".format(func.__name__, args)
|
|
602
|
-
)
|
|
553
|
+
raise RuntimeError('Underspecified error in MPV when calling {} with args {!r}: NULL pointer returned.'\
|
|
554
|
+
'Please consult your local debugger.'.format(func.__name__, args))
|
|
603
555
|
return res
|
|
604
556
|
|
|
605
|
-
|
|
606
557
|
ec_errcheck = ErrorCode.raise_for_ec
|
|
607
558
|
|
|
608
559
|
backend.mpv_client_api_version.restype = c_ulong
|
|
609
|
-
|
|
610
|
-
|
|
611
560
|
def _mpv_client_api_version():
|
|
612
561
|
ver = backend.mpv_client_api_version()
|
|
613
|
-
return ver
|
|
614
|
-
|
|
562
|
+
return ver>>16, ver&0xFFFF
|
|
615
563
|
|
|
616
564
|
MPV_VERSION = _mpv_client_api_version()
|
|
617
565
|
if MPV_VERSION < (1, 108):
|
|
618
|
-
ver =
|
|
619
|
-
raise RuntimeError(
|
|
620
|
-
f"python-mpv requires libmpv with an API version of 1.108 or higher (libmpv >= 0.33), but you have an older version ({ver})."
|
|
621
|
-
)
|
|
566
|
+
ver = '.'.join(str(num) for num in MPV_VERSION)
|
|
567
|
+
raise RuntimeError(f"python-mpv requires libmpv with an API version of 1.108 or higher (libmpv >= 0.33), but you have an older version ({ver}).")
|
|
622
568
|
|
|
623
569
|
backend.mpv_free.argtypes = [c_void_p]
|
|
624
570
|
_mpv_free = backend.mpv_free
|
|
@@ -629,72 +575,67 @@ _mpv_free_node_contents = backend.mpv_free_node_contents
|
|
|
629
575
|
backend.mpv_create.restype = MpvHandle
|
|
630
576
|
_mpv_create = backend.mpv_create
|
|
631
577
|
|
|
632
|
-
_handle_func(
|
|
633
|
-
_handle_func(
|
|
634
|
-
_handle_func(
|
|
635
|
-
_handle_func(
|
|
636
|
-
_handle_func(
|
|
637
|
-
_handle_func(
|
|
638
|
-
_handle_func(
|
|
639
|
-
_handle_func(
|
|
640
|
-
|
|
641
|
-
_handle_func(
|
|
642
|
-
_handle_func(
|
|
643
|
-
|
|
644
|
-
_handle_func(
|
|
645
|
-
_handle_func(
|
|
646
|
-
_handle_func(
|
|
647
|
-
_handle_func(
|
|
648
|
-
_handle_func(
|
|
649
|
-
_handle_func(
|
|
650
|
-
|
|
651
|
-
_handle_func(
|
|
652
|
-
_handle_func(
|
|
653
|
-
_handle_func(
|
|
654
|
-
_handle_func(
|
|
655
|
-
_handle_func(
|
|
656
|
-
_handle_func(
|
|
657
|
-
_handle_func(
|
|
658
|
-
_handle_func(
|
|
659
|
-
_handle_func(
|
|
660
|
-
|
|
661
|
-
_handle_func(
|
|
662
|
-
_handle_func(
|
|
663
|
-
_handle_func(
|
|
664
|
-
|
|
665
|
-
_handle_func(
|
|
666
|
-
_handle_func(
|
|
667
|
-
_handle_func(
|
|
668
|
-
_handle_func(
|
|
669
|
-
_handle_func(
|
|
670
|
-
|
|
671
|
-
_handle_func(
|
|
672
|
-
|
|
673
|
-
_handle_func(
|
|
674
|
-
|
|
675
|
-
)
|
|
676
|
-
_handle_func(
|
|
677
|
-
_handle_func(
|
|
678
|
-
_handle_func(
|
|
679
|
-
|
|
680
|
-
)
|
|
681
|
-
_handle_func("mpv_render_context_update", [], c_int64, errcheck=None, ctx=MpvRenderCtxHandle)
|
|
682
|
-
_handle_func("mpv_render_context_render", [POINTER(MpvRenderParam)], c_int, ec_errcheck, ctx=MpvRenderCtxHandle)
|
|
683
|
-
_handle_func("mpv_render_context_report_swap", [], None, errcheck=None, ctx=MpvRenderCtxHandle)
|
|
684
|
-
_handle_func("mpv_render_context_free", [], None, errcheck=None, ctx=MpvRenderCtxHandle)
|
|
578
|
+
_handle_func('mpv_create_client', [c_char_p], MpvHandle, notnull_errcheck)
|
|
579
|
+
_handle_func('mpv_create_weak_client', [c_char_p], MpvHandle, notnull_errcheck)
|
|
580
|
+
_handle_func('mpv_client_name', [], c_char_p, errcheck=None)
|
|
581
|
+
_handle_func('mpv_initialize', [], c_int, ec_errcheck)
|
|
582
|
+
_handle_func('mpv_destroy', [], None, errcheck=None)
|
|
583
|
+
_handle_func('mpv_terminate_destroy', [], None, errcheck=None)
|
|
584
|
+
_handle_func('mpv_load_config_file', [c_char_p], c_int, ec_errcheck)
|
|
585
|
+
_handle_func('mpv_get_time_us', [], c_ulonglong, errcheck=None)
|
|
586
|
+
|
|
587
|
+
_handle_func('mpv_set_option', [c_char_p, MpvFormat, c_void_p], c_int, ec_errcheck)
|
|
588
|
+
_handle_func('mpv_set_option_string', [c_char_p, c_char_p], c_int, ec_errcheck)
|
|
589
|
+
|
|
590
|
+
_handle_func('mpv_command', [POINTER(c_char_p)], c_int, ec_errcheck)
|
|
591
|
+
_handle_func('mpv_command_string', [c_char_p, c_char_p], c_int, ec_errcheck)
|
|
592
|
+
_handle_func('mpv_command_async', [c_ulonglong, POINTER(c_char_p)], c_int, ec_errcheck)
|
|
593
|
+
_handle_func('mpv_command_node', [POINTER(MpvNode), POINTER(MpvNode)], c_int, ec_errcheck)
|
|
594
|
+
_handle_func('mpv_command_node_async', [c_ulonglong, POINTER(MpvNode)], c_int, ec_errcheck)
|
|
595
|
+
_handle_func('mpv_abort_async_command', [c_ulonglong], None, errcheck=None)
|
|
596
|
+
|
|
597
|
+
_handle_func('mpv_set_property', [c_char_p, MpvFormat, c_void_p], c_int, ec_errcheck)
|
|
598
|
+
_handle_func('mpv_set_property_string', [c_char_p, c_char_p], c_int, ec_errcheck)
|
|
599
|
+
_handle_func('mpv_set_property_async', [c_ulonglong, c_char_p, MpvFormat,c_void_p],c_int, ec_errcheck)
|
|
600
|
+
_handle_func('mpv_get_property', [c_char_p, MpvFormat, c_void_p], c_int, ec_errcheck)
|
|
601
|
+
_handle_func('mpv_get_property_string', [c_char_p], c_void_p, bytes_free_errcheck)
|
|
602
|
+
_handle_func('mpv_get_property_osd_string', [c_char_p], c_void_p, bytes_free_errcheck)
|
|
603
|
+
_handle_func('mpv_get_property_async', [c_ulonglong, c_char_p, MpvFormat], c_int, ec_errcheck)
|
|
604
|
+
_handle_func('mpv_observe_property', [c_ulonglong, c_char_p, MpvFormat], c_int, ec_errcheck)
|
|
605
|
+
_handle_func('mpv_unobserve_property', [c_ulonglong], c_int, ec_errcheck)
|
|
606
|
+
|
|
607
|
+
_handle_func('mpv_event_name', [c_int], c_char_p, errcheck=None, ctx=None)
|
|
608
|
+
_handle_func('mpv_event_to_node', [POINTER(MpvNode), POINTER(MpvEvent)], c_int, ec_errcheck, ctx=None)
|
|
609
|
+
_handle_func('mpv_error_string', [c_int], c_char_p, errcheck=None, ctx=None)
|
|
610
|
+
|
|
611
|
+
_handle_func('mpv_request_event', [MpvEventID, c_int], c_int, ec_errcheck)
|
|
612
|
+
_handle_func('mpv_request_log_messages', [c_char_p], c_int, ec_errcheck)
|
|
613
|
+
_handle_func('mpv_wait_event', [c_double], POINTER(MpvEvent), errcheck=None)
|
|
614
|
+
_handle_func('mpv_wakeup', [], None, errcheck=None)
|
|
615
|
+
_handle_func('mpv_set_wakeup_callback', [WakeupCallback, c_void_p], None, errcheck=None)
|
|
616
|
+
|
|
617
|
+
_handle_func('mpv_stream_cb_add_ro', [c_char_p, c_void_p, StreamOpenFn], c_int, ec_errcheck)
|
|
618
|
+
|
|
619
|
+
_handle_func('mpv_render_context_create', [MpvRenderCtxHandle, MpvHandle, POINTER(MpvRenderParam)], c_int, ec_errcheck, ctx=None)
|
|
620
|
+
_handle_func('mpv_render_context_set_parameter', [MpvRenderParam], c_int, ec_errcheck, ctx=MpvRenderCtxHandle)
|
|
621
|
+
_handle_func('mpv_render_context_get_info', [MpvRenderParam], c_int, ec_errcheck, ctx=MpvRenderCtxHandle)
|
|
622
|
+
_handle_func('mpv_render_context_set_update_callback', [RenderUpdateFn, c_void_p], None, errcheck=None, ctx=MpvRenderCtxHandle)
|
|
623
|
+
_handle_func('mpv_render_context_update', [], c_int64, errcheck=None, ctx=MpvRenderCtxHandle)
|
|
624
|
+
_handle_func('mpv_render_context_render', [POINTER(MpvRenderParam)], c_int, ec_errcheck, ctx=MpvRenderCtxHandle)
|
|
625
|
+
_handle_func('mpv_render_context_report_swap', [], None, errcheck=None, ctx=MpvRenderCtxHandle)
|
|
626
|
+
_handle_func('mpv_render_context_free', [], None, errcheck=None, ctx=MpvRenderCtxHandle)
|
|
685
627
|
|
|
686
628
|
|
|
687
629
|
def _mpv_coax_proptype(value, proptype=str):
|
|
688
630
|
"""Intelligently coax the given python value into something that can be understood as a proptype property."""
|
|
689
631
|
if type(value) is bytes:
|
|
690
|
-
return value
|
|
632
|
+
return value;
|
|
691
633
|
elif type(value) is bool:
|
|
692
|
-
return b
|
|
634
|
+
return b'yes' if value else b'no'
|
|
693
635
|
elif proptype in (str, int, float):
|
|
694
|
-
return str(proptype(value)).encode(
|
|
636
|
+
return str(proptype(value)).encode('utf-8')
|
|
695
637
|
else:
|
|
696
|
-
raise TypeError(
|
|
697
|
-
|
|
638
|
+
raise TypeError('Cannot coax value of type {} into property type {}'.format(type(value), proptype))
|
|
698
639
|
|
|
699
640
|
def _make_node_str_list(l):
|
|
700
641
|
"""Take a list of python objects and make a MPV string node array from it.
|
|
@@ -714,25 +655,32 @@ def _make_node_str_list(l):
|
|
|
714
655
|
}
|
|
715
656
|
}
|
|
716
657
|
"""
|
|
717
|
-
char_ps = [c_char_p(_mpv_coax_proptype(e, str)) for e in l]
|
|
658
|
+
char_ps = [ c_char_p(_mpv_coax_proptype(e, str)) for e in l ]
|
|
718
659
|
node_list = MpvNodeList(
|
|
719
660
|
num=len(l),
|
|
720
661
|
keys=None,
|
|
721
|
-
values=(MpvNode * len(l))(*[MpvNode(
|
|
722
|
-
|
|
723
|
-
|
|
662
|
+
values=( MpvNode * len(l))( *[ MpvNode(
|
|
663
|
+
format=MpvFormat.STRING,
|
|
664
|
+
val=MpvNodeUnion(string=p))
|
|
665
|
+
for p in char_ps ]))
|
|
666
|
+
node = MpvNode(
|
|
667
|
+
format=MpvFormat.NODE_ARRAY,
|
|
668
|
+
val=MpvNodeUnion(list=pointer(node_list)))
|
|
724
669
|
return char_ps, node_list, node, cast(pointer(node), c_void_p)
|
|
725
670
|
|
|
726
|
-
|
|
727
671
|
def _make_node_str_map(d):
|
|
728
|
-
"""Take a dict of python objects and make a MPV string node map from it."""
|
|
729
|
-
char_ps = [(c_char_p(k.encode(
|
|
672
|
+
"""Take a dict of python objects and make a MPV string node map from it. """
|
|
673
|
+
char_ps = [ (c_char_p(k.encode('utf-8')), c_char_p(_mpv_coax_proptype(v, str))) for k, v in d.items() ]
|
|
730
674
|
node_list = MpvNodeList(
|
|
731
675
|
num=len(d),
|
|
732
|
-
keys=(c_char_p * len(d))(*[k for k, v in char_ps]),
|
|
733
|
-
values=(MpvNode * len(d))(*[MpvNode(
|
|
734
|
-
|
|
735
|
-
|
|
676
|
+
keys=( c_char_p * len(d))( *[k for k, v in char_ps] ),
|
|
677
|
+
values=( MpvNode * len(d))( *[ MpvNode(
|
|
678
|
+
format=MpvFormat.STRING,
|
|
679
|
+
val=MpvNodeUnion(string=v))
|
|
680
|
+
for k, v in char_ps ]))
|
|
681
|
+
node = MpvNode(
|
|
682
|
+
format=MpvFormat.NODE_MAP,
|
|
683
|
+
val=MpvNodeUnion(map=pointer(node_list)))
|
|
736
684
|
return char_ps, node_list, node, cast(pointer(node), c_void_p)
|
|
737
685
|
|
|
738
686
|
|
|
@@ -745,29 +693,23 @@ def _event_generator(handle):
|
|
|
745
693
|
|
|
746
694
|
|
|
747
695
|
def _create_null_term_cmd_arg_array(name, args):
|
|
748
|
-
args = (
|
|
749
|
-
|
|
750
|
-
+ [(arg if type(arg) is bytes else str(arg).encode("utf-8")) for arg in args if arg is not None]
|
|
751
|
-
+ [None]
|
|
752
|
-
)
|
|
696
|
+
args = [name.encode('utf-8')] + [(arg if type(arg) is bytes else str(arg).encode('utf-8'))
|
|
697
|
+
for arg in args if arg is not None] + [None]
|
|
753
698
|
return (c_char_p * len(args))(*args)
|
|
754
699
|
|
|
755
700
|
|
|
756
|
-
_py_to_mpv = lambda name: name.replace(
|
|
757
|
-
_mpv_to_py = lambda name: name.replace(
|
|
758
|
-
|
|
759
|
-
_drop_nones = lambda *args: [arg for arg in args if arg is not None]
|
|
701
|
+
_py_to_mpv = lambda name: name.replace('_', '-')
|
|
702
|
+
_mpv_to_py = lambda name: name.replace('-', '_')
|
|
760
703
|
|
|
704
|
+
_drop_nones = lambda *args: [ arg for arg in args if arg is not None ]
|
|
761
705
|
|
|
762
706
|
class _Proxy:
|
|
763
707
|
def __init__(self, mpv):
|
|
764
|
-
super().__setattr__(
|
|
765
|
-
|
|
708
|
+
super().__setattr__('mpv', mpv)
|
|
766
709
|
|
|
767
710
|
class _PropertyProxy(_Proxy):
|
|
768
711
|
def __dir__(self):
|
|
769
|
-
return super().__dir__() + [name.replace(
|
|
770
|
-
|
|
712
|
+
return super().__dir__() + [ name.replace('-', '_') for name in self.mpv.property_list ]
|
|
771
713
|
|
|
772
714
|
class _FileLocalProxy(_Proxy):
|
|
773
715
|
def __getitem__(self, name):
|
|
@@ -779,19 +721,17 @@ class _FileLocalProxy(_Proxy):
|
|
|
779
721
|
def __iter__(self):
|
|
780
722
|
return iter(self.mpv)
|
|
781
723
|
|
|
782
|
-
|
|
783
724
|
class _OSDPropertyProxy(_PropertyProxy):
|
|
784
725
|
def __getattr__(self, name):
|
|
785
726
|
return self.mpv._get_property(_py_to_mpv(name), fmt=MpvFormat.OSD_STRING)
|
|
786
727
|
|
|
787
728
|
def __setattr__(self, _name, _value):
|
|
788
|
-
raise AttributeError(
|
|
789
|
-
|
|
729
|
+
raise AttributeError('OSD properties are read-only. Please use the regular property API for writing.')
|
|
790
730
|
|
|
791
731
|
class _DecoderPropertyProxy(_PropertyProxy):
|
|
792
732
|
def __init__(self, mpv, decoder):
|
|
793
733
|
super().__init__(mpv)
|
|
794
|
-
super().__setattr__(
|
|
734
|
+
super().__setattr__('_decoder', decoder)
|
|
795
735
|
|
|
796
736
|
def __getattr__(self, name):
|
|
797
737
|
return self.mpv._get_property(_py_to_mpv(name), decoder=self._decoder)
|
|
@@ -799,7 +739,6 @@ class _DecoderPropertyProxy(_PropertyProxy):
|
|
|
799
739
|
def __setattr__(self, name, value):
|
|
800
740
|
setattr(self.mpv, _py_to_mpv(name), value)
|
|
801
741
|
|
|
802
|
-
|
|
803
742
|
class GeneratorStream:
|
|
804
743
|
"""Transform a python generator into an mpv-compatible stream object. The total size of the file can be indicated to
|
|
805
744
|
mpv using the size argument to __init__. Seeking is not supported.
|
|
@@ -811,8 +750,8 @@ class GeneratorStream:
|
|
|
811
750
|
|
|
812
751
|
def seek(self, offset):
|
|
813
752
|
self._read_iter = iter(self._generator_fun())
|
|
814
|
-
self._read_chunk = b
|
|
815
|
-
return 0
|
|
753
|
+
self._read_chunk = b''
|
|
754
|
+
return 0 # We only support seeking to the first byte atm
|
|
816
755
|
# implementation in case seeking to arbitrary offsets would be necessary
|
|
817
756
|
# while offset > 0:
|
|
818
757
|
# offset -= len(self.read(offset))
|
|
@@ -823,15 +762,15 @@ class GeneratorStream:
|
|
|
823
762
|
try:
|
|
824
763
|
self._read_chunk += next(self._read_iter)
|
|
825
764
|
except StopIteration:
|
|
826
|
-
return b
|
|
765
|
+
return b''
|
|
827
766
|
rv, self._read_chunk = self._read_chunk[:size], self._read_chunk[size:]
|
|
828
767
|
return rv
|
|
829
768
|
|
|
830
769
|
def close(self):
|
|
831
|
-
self._read_iter = iter([])
|
|
770
|
+
self._read_iter = iter([]) # make next read() call return EOF
|
|
832
771
|
|
|
833
772
|
def cancel(self):
|
|
834
|
-
self._read_iter = iter([])
|
|
773
|
+
self._read_iter = iter([]) # make next read() call return EOF
|
|
835
774
|
|
|
836
775
|
|
|
837
776
|
class ImageOverlay:
|
|
@@ -845,38 +784,37 @@ class ImageOverlay:
|
|
|
845
784
|
|
|
846
785
|
def update(self, img=None, pos=None):
|
|
847
786
|
from PIL import Image
|
|
848
|
-
|
|
849
787
|
if img is not None:
|
|
850
788
|
self.img = img
|
|
851
789
|
img = self.img
|
|
852
790
|
|
|
853
791
|
w, h = img.size
|
|
854
|
-
stride = w
|
|
792
|
+
stride = w*4
|
|
855
793
|
|
|
856
794
|
if pos is not None:
|
|
857
795
|
self.pos = pos
|
|
858
796
|
x, y = self.pos
|
|
859
797
|
|
|
860
798
|
# Pre-multiply alpha channel
|
|
861
|
-
bg = Image.new(
|
|
799
|
+
bg = Image.new('RGBA', (w, h), (0, 0, 0, 0))
|
|
862
800
|
out = Image.alpha_composite(bg, img)
|
|
863
801
|
|
|
864
802
|
# Copy image to ctypes buffer
|
|
865
803
|
if img.size != self._size:
|
|
866
|
-
self._buf = create_string_buffer(w
|
|
804
|
+
self._buf = create_string_buffer(w*h*4)
|
|
867
805
|
self._size = img.size
|
|
868
806
|
|
|
869
|
-
ctypes.memmove(self._buf, out.tobytes(
|
|
870
|
-
source =
|
|
807
|
+
ctypes.memmove(self._buf, out.tobytes('raw', 'BGRA'), w*h*4)
|
|
808
|
+
source = '&' + str(addressof(self._buf))
|
|
871
809
|
|
|
872
|
-
self.m.overlay_add(self.overlay_id, x, y, source, 0,
|
|
810
|
+
self.m.overlay_add(self.overlay_id, x, y, source, 0, 'bgra', w, h, stride)
|
|
873
811
|
|
|
874
812
|
def remove(self):
|
|
875
813
|
self.m.remove_overlay(self.overlay_id)
|
|
876
814
|
|
|
877
815
|
|
|
878
816
|
class FileOverlay:
|
|
879
|
-
def __init__(self, m, overlay_id, filename=None, size=None, stride=None, pos=(0,
|
|
817
|
+
def __init__(self, m, overlay_id, filename=None, size=None, stride=None, pos=(0,0)):
|
|
880
818
|
self.m = m
|
|
881
819
|
self.overlay_id = overlay_id
|
|
882
820
|
self.pos = pos
|
|
@@ -900,9 +838,9 @@ class FileOverlay:
|
|
|
900
838
|
|
|
901
839
|
x, y = self.pos
|
|
902
840
|
w, h = self.size
|
|
903
|
-
stride = self.stride or 4
|
|
841
|
+
stride = self.stride or 4*w
|
|
904
842
|
|
|
905
|
-
self.m.overlay_add(self, self.overlay_id, x, y, self.filename, 0,
|
|
843
|
+
self.m.overlay_add(self, self.overlay_id, x, y, self.filename, 0, 'bgra', w, h, stride)
|
|
906
844
|
|
|
907
845
|
def remove(self):
|
|
908
846
|
self.m.remove_overlay(self.overlay_id)
|
|
@@ -922,7 +860,7 @@ class MPV(object):
|
|
|
922
860
|
underscore_names exposed on the python object.
|
|
923
861
|
|
|
924
862
|
To make your program not barf hard the first time its used on a weird file system **always** access properties
|
|
925
|
-
containing file names or file tags through ``MPV.raw``."""
|
|
863
|
+
containing file names or file tags through ``MPV.raw``. """
|
|
926
864
|
|
|
927
865
|
def __init__(self, *extra_mpv_flags, log_handler=None, start_event_thread=True, loglevel=None, **extra_mpv_opts):
|
|
928
866
|
"""Create an MPV instance.
|
|
@@ -934,21 +872,21 @@ class MPV(object):
|
|
|
934
872
|
self._event_thread = None
|
|
935
873
|
self._core_shutdown = False
|
|
936
874
|
|
|
937
|
-
_mpv_set_option_string(self.handle, b
|
|
938
|
-
istr = lambda o: (
|
|
875
|
+
_mpv_set_option_string(self.handle, b'audio-display', b'no')
|
|
876
|
+
istr = lambda o: ('yes' if o else 'no') if type(o) is bool else str(o)
|
|
939
877
|
try:
|
|
940
878
|
for flag in extra_mpv_flags:
|
|
941
|
-
_mpv_set_option_string(self.handle, flag.encode(
|
|
942
|
-
for k,
|
|
943
|
-
_mpv_set_option_string(self.handle, k.replace(
|
|
879
|
+
_mpv_set_option_string(self.handle, flag.encode('utf-8'), b'')
|
|
880
|
+
for k,v in extra_mpv_opts.items():
|
|
881
|
+
_mpv_set_option_string(self.handle, k.replace('_', '-').encode('utf-8'), istr(v).encode('utf-8'))
|
|
944
882
|
finally:
|
|
945
883
|
_mpv_initialize(self.handle)
|
|
946
884
|
|
|
947
885
|
self.osd = _OSDPropertyProxy(self)
|
|
948
886
|
self.file_local = _FileLocalProxy(self)
|
|
949
|
-
self.raw
|
|
887
|
+
self.raw = _DecoderPropertyProxy(self, identity_decoder)
|
|
950
888
|
self.strict = _DecoderPropertyProxy(self, strict_decoder)
|
|
951
|
-
self.lazy
|
|
889
|
+
self.lazy = _DecoderPropertyProxy(self, lazy_decoder)
|
|
952
890
|
|
|
953
891
|
self._event_callbacks = []
|
|
954
892
|
self._command_reply_callbacks = {}
|
|
@@ -957,23 +895,40 @@ class MPV(object):
|
|
|
957
895
|
self._quit_handlers = set()
|
|
958
896
|
self._message_handlers = {}
|
|
959
897
|
self._key_binding_handlers = {}
|
|
960
|
-
self._event_handle = _mpv_create_client(self.handle, b
|
|
898
|
+
self._event_handle = _mpv_create_client(self.handle, b'py_event_handler')
|
|
961
899
|
self._log_handler = log_handler
|
|
962
900
|
self._stream_protocol_cbs = {}
|
|
963
901
|
self._stream_protocol_frontends = collections.defaultdict(lambda: {})
|
|
964
|
-
self.register_stream_protocol(
|
|
902
|
+
self.register_stream_protocol('python', self._python_stream_open)
|
|
965
903
|
self._python_streams = {}
|
|
966
904
|
self._python_stream_catchall = None
|
|
905
|
+
self._exception_futures = set()
|
|
967
906
|
self.overlay_ids = set()
|
|
968
907
|
self.overlays = {}
|
|
969
908
|
if loglevel is not None or log_handler is not None:
|
|
970
|
-
self.set_loglevel(loglevel or
|
|
909
|
+
self.set_loglevel(loglevel or 'terminal-default')
|
|
971
910
|
if start_event_thread:
|
|
972
|
-
self._event_thread = threading.Thread(target=self._loop, name=
|
|
911
|
+
self._event_thread = threading.Thread(target=self._loop, name='MPVEventHandlerThread')
|
|
973
912
|
self._event_thread.daemon = True
|
|
974
913
|
self._event_thread.start()
|
|
975
914
|
else:
|
|
976
915
|
self._event_thread = None
|
|
916
|
+
if (m := re.search(r'(\d+)\.(\d+)\.(\d+)', self.mpv_version)):
|
|
917
|
+
self.mpv_version_tuple = tuple(map(int, m.groups()))
|
|
918
|
+
|
|
919
|
+
@contextmanager
|
|
920
|
+
def _enqueue_exceptions(self):
|
|
921
|
+
try:
|
|
922
|
+
yield
|
|
923
|
+
except Exception as e:
|
|
924
|
+
for fut in self._exception_futures:
|
|
925
|
+
try:
|
|
926
|
+
fut.set_exception(e)
|
|
927
|
+
break
|
|
928
|
+
except InvalidStateError:
|
|
929
|
+
pass
|
|
930
|
+
else:
|
|
931
|
+
warn(f'Unhandled exception on python-mpv event loop: {e}\n{traceback.format_exc()}', RuntimeWarning)
|
|
977
932
|
|
|
978
933
|
def _loop(self):
|
|
979
934
|
for event in _event_generator(self._event_handle):
|
|
@@ -985,50 +940,51 @@ class MPV(object):
|
|
|
985
940
|
self._core_shutdown = True
|
|
986
941
|
|
|
987
942
|
for callback in self._event_callbacks:
|
|
988
|
-
|
|
943
|
+
with self._enqueue_exceptions():
|
|
944
|
+
callback(event)
|
|
989
945
|
|
|
990
946
|
if eid == MpvEventID.PROPERTY_CHANGE:
|
|
991
947
|
pc = event.data
|
|
992
948
|
name, value, _fmt = pc.name, pc.value, pc.format
|
|
993
949
|
for handler in self._property_handlers[name]:
|
|
994
|
-
|
|
950
|
+
with self._enqueue_exceptions():
|
|
951
|
+
handler(name, value)
|
|
995
952
|
|
|
996
953
|
if eid == MpvEventID.LOG_MESSAGE and self._log_handler is not None:
|
|
997
954
|
ev = event.data
|
|
998
|
-
self.
|
|
955
|
+
with self._enqueue_exceptions():
|
|
956
|
+
self._log_handler(ev.level, ev.prefix, ev.text)
|
|
999
957
|
|
|
1000
958
|
if eid == MpvEventID.CLIENT_MESSAGE:
|
|
1001
959
|
# {'event': {'args': ['key-binding', 'foo', 'u-', 'g']}, 'reply_userdata': 0, 'error': 0, 'event_id': 16}
|
|
1002
960
|
target, *args = event.data.args
|
|
1003
961
|
target = target.decode("utf-8")
|
|
1004
962
|
if target in self._message_handlers:
|
|
1005
|
-
self.
|
|
963
|
+
with self._enqueue_exceptions():
|
|
964
|
+
self._message_handlers[target](*args)
|
|
1006
965
|
|
|
1007
966
|
if eid == MpvEventID.COMMAND_REPLY:
|
|
1008
967
|
key = event.reply_userdata
|
|
1009
968
|
callback = self._command_reply_callbacks.pop(key, None)
|
|
1010
969
|
if callback:
|
|
1011
|
-
|
|
970
|
+
with self._enqueue_exceptions():
|
|
971
|
+
callback(ErrorCode.exception_for_ec(event.error), event.data)
|
|
1012
972
|
|
|
1013
973
|
if eid == MpvEventID.QUEUE_OVERFLOW:
|
|
1014
974
|
# cache list, since error handlers will unregister themselves
|
|
1015
975
|
for cb in list(self._command_reply_callbacks.values()):
|
|
1016
|
-
|
|
1017
|
-
EventOverflowError(
|
|
1018
|
-
"libmpv event queue has flown over because events have not been processed fast enough"
|
|
1019
|
-
),
|
|
1020
|
-
None,
|
|
1021
|
-
)
|
|
976
|
+
with self._enqueue_exceptions():
|
|
977
|
+
cb(EventOverflowError('libmpv event queue has flown over because events have not been processed fast enough'), None)
|
|
1022
978
|
|
|
1023
979
|
if eid == MpvEventID.SHUTDOWN:
|
|
1024
980
|
_mpv_destroy(self._event_handle)
|
|
1025
981
|
for cb in list(self._command_reply_callbacks.values()):
|
|
1026
|
-
|
|
982
|
+
with self._enqueue_exceptions():
|
|
983
|
+
cb(ShutdownError('libmpv core has been shutdown'), None)
|
|
1027
984
|
return
|
|
1028
985
|
|
|
1029
986
|
except Exception as e:
|
|
1030
|
-
|
|
1031
|
-
traceback.print_exc()
|
|
987
|
+
warn(f'Unhandled {e} inside python-mpv event loop!\n{traceback.format_exc()}', RuntimeWarning)
|
|
1032
988
|
|
|
1033
989
|
@property
|
|
1034
990
|
def core_shutdown(self):
|
|
@@ -1037,63 +993,58 @@ class MPV(object):
|
|
|
1037
993
|
return self._core_shutdown
|
|
1038
994
|
|
|
1039
995
|
def check_core_alive(self):
|
|
1040
|
-
"""This method can be used as a sanity check to tests whether the core is still alive at the time it is
|
|
996
|
+
""" This method can be used as a sanity check to tests whether the core is still alive at the time it is
|
|
1041
997
|
called."""
|
|
1042
998
|
if self._core_shutdown:
|
|
1043
|
-
raise ShutdownError(
|
|
999
|
+
raise ShutdownError('libmpv core has been shutdown')
|
|
1044
1000
|
|
|
1045
|
-
def wait_until_paused(self, timeout=None):
|
|
1001
|
+
def wait_until_paused(self, timeout=None, catch_errors=True):
|
|
1046
1002
|
"""Waits until playback of the current title is paused or done. Raises a ShutdownError if the core is shutdown while
|
|
1047
1003
|
waiting."""
|
|
1048
|
-
self.wait_for_property(
|
|
1004
|
+
self.wait_for_property('core-idle', timeout=timeout, catch_errors=catch_errors)
|
|
1049
1005
|
|
|
1050
|
-
def wait_for_playback(self, timeout=None):
|
|
1006
|
+
def wait_for_playback(self, timeout=None, catch_errors=True):
|
|
1051
1007
|
"""Waits until playback of the current title is finished. Raises a ShutdownError if the core is shutdown while
|
|
1052
1008
|
waiting.
|
|
1053
1009
|
"""
|
|
1054
|
-
self.wait_for_event(
|
|
1010
|
+
self.wait_for_event('end_file', timeout=timeout, catch_errors=catch_errors)
|
|
1055
1011
|
|
|
1056
|
-
def wait_until_playing(self, timeout=None):
|
|
1012
|
+
def wait_until_playing(self, timeout=None, catch_errors=True):
|
|
1057
1013
|
"""Waits until playback of the current title has started. Raises a ShutdownError if the core is shutdown while
|
|
1058
1014
|
waiting."""
|
|
1059
|
-
self.wait_for_property(
|
|
1015
|
+
self.wait_for_property('core-idle', lambda idle: not idle, timeout=timeout, catch_errors=catch_errors)
|
|
1060
1016
|
|
|
1061
|
-
def wait_for_property(self, name, cond=lambda val: val, level_sensitive=True, timeout=None):
|
|
1017
|
+
def wait_for_property(self, name, cond=lambda val: val, level_sensitive=True, timeout=None, catch_errors=True):
|
|
1062
1018
|
"""Waits until ``cond`` evaluates to a truthy value on the named property. This can be used to wait for
|
|
1063
1019
|
properties such as ``idle_active`` indicating the player is done with regular playback and just idling around.
|
|
1064
1020
|
Raises a ShutdownError when the core is shutdown while waiting.
|
|
1065
1021
|
"""
|
|
1066
|
-
with self.prepare_and_wait_for_property(name, cond, level_sensitive, timeout=timeout) as result:
|
|
1022
|
+
with self.prepare_and_wait_for_property(name, cond, level_sensitive, timeout=timeout, catch_errors=catch_errors) as result:
|
|
1067
1023
|
pass
|
|
1068
1024
|
return result.result()
|
|
1069
1025
|
|
|
1070
|
-
def wait_for_shutdown(self, timeout=None):
|
|
1071
|
-
|
|
1026
|
+
def wait_for_shutdown(self, timeout=None, catch_errors=True):
|
|
1027
|
+
'''Wait for core to shutdown (e.g. through quit() or terminate()).'''
|
|
1072
1028
|
try:
|
|
1073
|
-
self.wait_for_event(None, timeout=timeout)
|
|
1029
|
+
self.wait_for_event(None, timeout=timeout, catch_errors=catch_errors)
|
|
1074
1030
|
except ShutdownError:
|
|
1075
1031
|
return
|
|
1076
1032
|
|
|
1077
1033
|
def _set_error_handler(self, future):
|
|
1078
|
-
@self.event_callback(
|
|
1034
|
+
@self.event_callback('shutdown', 'queue-overflow')
|
|
1079
1035
|
def shutdown_handler(event):
|
|
1080
1036
|
nonlocal future
|
|
1081
1037
|
try:
|
|
1082
1038
|
if event.event_id.value == MpvEventID.SHUTDOWN:
|
|
1083
|
-
future.set_exception(ShutdownError(
|
|
1039
|
+
future.set_exception(ShutdownError('libmpv core has been shutdown'))
|
|
1084
1040
|
else:
|
|
1085
|
-
future.set_exception(
|
|
1086
|
-
EventOverflowError(
|
|
1087
|
-
"libmpv event queue has flown over because events have not been processed fast enough"
|
|
1088
|
-
)
|
|
1089
|
-
)
|
|
1041
|
+
future.set_exception(EventOverflowError('libmpv event queue has flown over because events have not been processed fast enough'))
|
|
1090
1042
|
except InvalidStateError:
|
|
1091
1043
|
pass
|
|
1092
|
-
|
|
1093
1044
|
return shutdown_handler.unregister_mpv_events
|
|
1094
1045
|
|
|
1095
1046
|
@contextmanager
|
|
1096
|
-
def prepare_and_wait_for_property(self, name, cond=lambda val: val, level_sensitive=True, timeout=None):
|
|
1047
|
+
def prepare_and_wait_for_property(self, name, cond=lambda val: val, level_sensitive=True, timeout=None, catch_errors=True):
|
|
1097
1048
|
"""Context manager that waits until ``cond`` evaluates to a truthy value on the named property. See
|
|
1098
1049
|
prepare_and_wait_for_event for usage.
|
|
1099
1050
|
Raises a ShutdownError when the core is shutdown while waiting. Re-raises any errors inside ``cond``.
|
|
@@ -1105,43 +1056,54 @@ class MPV(object):
|
|
|
1105
1056
|
rv = cond(val)
|
|
1106
1057
|
if rv:
|
|
1107
1058
|
result.set_result(rv)
|
|
1059
|
+
|
|
1060
|
+
except InvalidStateError:
|
|
1061
|
+
pass
|
|
1062
|
+
|
|
1108
1063
|
except Exception as e:
|
|
1109
1064
|
try:
|
|
1110
1065
|
result.set_exception(e)
|
|
1111
|
-
except
|
|
1066
|
+
except:
|
|
1112
1067
|
pass
|
|
1113
|
-
except InvalidStateError:
|
|
1114
|
-
pass
|
|
1115
|
-
|
|
1116
|
-
self.observe_property(name, observer)
|
|
1117
|
-
err_unregister = self._set_error_handler(result)
|
|
1118
1068
|
|
|
1119
1069
|
try:
|
|
1120
1070
|
result.set_running_or_notify_cancel()
|
|
1071
|
+
|
|
1072
|
+
self.observe_property(name, observer)
|
|
1073
|
+
err_unregister = self._set_error_handler(result)
|
|
1074
|
+
if catch_errors:
|
|
1075
|
+
self._exception_futures.add(result)
|
|
1076
|
+
|
|
1121
1077
|
yield result
|
|
1122
1078
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1079
|
+
if level_sensitive:
|
|
1080
|
+
rv = cond(getattr(self, name.replace('-', '_')))
|
|
1081
|
+
if rv:
|
|
1082
|
+
result.set_result(rv)
|
|
1083
|
+
return
|
|
1084
|
+
|
|
1085
|
+
self.check_core_alive()
|
|
1086
|
+
result.result(timeout)
|
|
1087
|
+
|
|
1088
|
+
except InvalidStateError:
|
|
1089
|
+
pass
|
|
1126
1090
|
|
|
1127
|
-
else:
|
|
1128
|
-
self.check_core_alive()
|
|
1129
|
-
result.result(timeout)
|
|
1130
1091
|
finally:
|
|
1131
1092
|
err_unregister()
|
|
1132
1093
|
self.unobserve_property(name, observer)
|
|
1094
|
+
self._exception_futures.discard(result)
|
|
1133
1095
|
|
|
1134
|
-
def wait_for_event(self, *event_types, cond=lambda evt: True, timeout=None):
|
|
1096
|
+
def wait_for_event(self, *event_types, cond=lambda evt: True, timeout=None, catch_errors=True):
|
|
1135
1097
|
"""Waits for the indicated event(s). If cond is given, waits until cond(event) is true. Raises a ShutdownError
|
|
1136
1098
|
if the core is shutdown while waiting. This also happens when 'shutdown' is in event_types. Re-raises any error
|
|
1137
1099
|
inside ``cond``.
|
|
1138
1100
|
"""
|
|
1139
|
-
with self.prepare_and_wait_for_event(*event_types, cond=cond, timeout=timeout) as result:
|
|
1101
|
+
with self.prepare_and_wait_for_event(*event_types, cond=cond, timeout=timeout, catch_errors=catch_errors) as result:
|
|
1140
1102
|
pass
|
|
1141
1103
|
return result.result()
|
|
1142
1104
|
|
|
1143
1105
|
@contextmanager
|
|
1144
|
-
def prepare_and_wait_for_event(self, *event_types, cond=lambda evt: True, timeout=None):
|
|
1106
|
+
def prepare_and_wait_for_event(self, *event_types, cond=lambda evt: True, timeout=None, catch_errors=True):
|
|
1145
1107
|
"""Context manager that waits for the indicated event(s) like wait_for_event after running. If cond is given,
|
|
1146
1108
|
waits until cond(event) is true. Raises a ShutdownError if the core is shutdown while waiting. This also happens
|
|
1147
1109
|
when 'shutdown' is in event_types. Re-raises any error inside ``cond``.
|
|
@@ -1159,7 +1121,6 @@ class MPV(object):
|
|
|
1159
1121
|
|
|
1160
1122
|
@self.event_callback(*event_types)
|
|
1161
1123
|
def target_handler(evt):
|
|
1162
|
-
|
|
1163
1124
|
try:
|
|
1164
1125
|
rv = cond(evt)
|
|
1165
1126
|
if rv:
|
|
@@ -1176,13 +1137,18 @@ class MPV(object):
|
|
|
1176
1137
|
|
|
1177
1138
|
try:
|
|
1178
1139
|
result.set_running_or_notify_cancel()
|
|
1140
|
+
if catch_errors:
|
|
1141
|
+
self._exception_futures.add(result)
|
|
1142
|
+
|
|
1179
1143
|
yield result
|
|
1144
|
+
|
|
1180
1145
|
self.check_core_alive()
|
|
1181
1146
|
result.result(timeout)
|
|
1182
1147
|
|
|
1183
1148
|
finally:
|
|
1184
1149
|
err_unregister()
|
|
1185
1150
|
target_handler.unregister_mpv_events()
|
|
1151
|
+
self._exception_futures.discard(result)
|
|
1186
1152
|
|
|
1187
1153
|
def __del__(self):
|
|
1188
1154
|
if self.handle:
|
|
@@ -1196,12 +1162,10 @@ class MPV(object):
|
|
|
1196
1162
|
"""
|
|
1197
1163
|
self.handle, handle = None, self.handle
|
|
1198
1164
|
if threading.current_thread() is self._event_thread:
|
|
1199
|
-
raise UserWarning(
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
"This call has been transformed into a call to quit()."
|
|
1204
|
-
)
|
|
1165
|
+
raise UserWarning('terminate() should not be called from event thread (e.g. from a callback function). If '
|
|
1166
|
+
'you want to terminate mpv from here, please call quit() instead, then sync the main thread '
|
|
1167
|
+
'against the event thread using e.g. wait_for_shutdown(), then terminate() from the main thread. '
|
|
1168
|
+
'This call has been transformed into a call to quit().')
|
|
1205
1169
|
self.quit()
|
|
1206
1170
|
else:
|
|
1207
1171
|
_mpv_terminate_destroy(handle)
|
|
@@ -1216,7 +1180,7 @@ class MPV(object):
|
|
|
1216
1180
|
Valid log levels are "no", "fatal", "error", "warn", "info", "v" "debug" and "trace". For details see your mpv's
|
|
1217
1181
|
client.h header file.
|
|
1218
1182
|
"""
|
|
1219
|
-
_mpv_request_log_messages(self._event_handle, level.encode(
|
|
1183
|
+
_mpv_request_log_messages(self._event_handle, level.encode('utf-8'))
|
|
1220
1184
|
|
|
1221
1185
|
def string_command(self, name, *args):
|
|
1222
1186
|
"""Execute a raw command."""
|
|
@@ -1241,7 +1205,6 @@ class MPV(object):
|
|
|
1241
1205
|
future.set_running_or_notify_cancel()
|
|
1242
1206
|
|
|
1243
1207
|
if callback is None:
|
|
1244
|
-
|
|
1245
1208
|
def callback(error, result):
|
|
1246
1209
|
if error:
|
|
1247
1210
|
raise error
|
|
@@ -1260,17 +1223,14 @@ class MPV(object):
|
|
|
1260
1223
|
def abort():
|
|
1261
1224
|
_mpv_abort_async_command(self._event_handle, id(future))
|
|
1262
1225
|
del self._command_reply_callbacks[id(future)]
|
|
1263
|
-
|
|
1264
1226
|
future.cancel = abort
|
|
1265
1227
|
|
|
1266
1228
|
self._command_reply_callbacks[id(future)] = wrapper
|
|
1267
1229
|
|
|
1268
1230
|
if kwargs:
|
|
1269
1231
|
if args:
|
|
1270
|
-
raise ValueError(
|
|
1271
|
-
|
|
1272
|
-
)
|
|
1273
|
-
kwargs["name"] = name
|
|
1232
|
+
raise ValueError('Can only call mpv commands either using positional or using named arguments, not a mix of both.')
|
|
1233
|
+
kwargs['name'] = name
|
|
1274
1234
|
_1, _2, _3, pointer = _make_node_str_map(kwargs)
|
|
1275
1235
|
else:
|
|
1276
1236
|
_1, _2, _3, pointer = _make_node_str_list([name, *args])
|
|
@@ -1279,16 +1239,15 @@ class MPV(object):
|
|
|
1279
1239
|
_mpv_command_node_async(self._event_handle, id(future), ppointer)
|
|
1280
1240
|
return future
|
|
1281
1241
|
|
|
1242
|
+
|
|
1282
1243
|
def node_command(self, name, *args, decoder=strict_decoder):
|
|
1283
1244
|
self.command(name, *args, decoder=decoder)
|
|
1284
1245
|
|
|
1285
1246
|
def command(self, name, *args, decoder=strict_decoder, **kwargs):
|
|
1286
1247
|
if kwargs:
|
|
1287
1248
|
if args:
|
|
1288
|
-
raise ValueError(
|
|
1289
|
-
|
|
1290
|
-
)
|
|
1291
|
-
kwargs["name"] = name
|
|
1249
|
+
raise ValueError('Can only call mpv commands either using positional or using named arguments, not a mix of both.')
|
|
1250
|
+
kwargs['name'] = name
|
|
1292
1251
|
_1, _2, _3, pointer = _make_node_str_map(kwargs)
|
|
1293
1252
|
else:
|
|
1294
1253
|
_1, _2, _3, pointer = _make_node_str_list([name, *args])
|
|
@@ -1302,61 +1261,59 @@ class MPV(object):
|
|
|
1302
1261
|
|
|
1303
1262
|
def seek(self, amount, reference="relative", precision="keyframes"):
|
|
1304
1263
|
"""Mapped mpv seek command, see man mpv(1)."""
|
|
1305
|
-
self.command(
|
|
1264
|
+
self.command('seek', amount, reference, precision)
|
|
1306
1265
|
|
|
1307
1266
|
def revert_seek(self):
|
|
1308
1267
|
"""Mapped mpv revert_seek command, see man mpv(1)."""
|
|
1309
|
-
self.command(
|
|
1268
|
+
self.command('revert_seek');
|
|
1310
1269
|
|
|
1311
1270
|
def frame_step(self):
|
|
1312
1271
|
"""Mapped mpv frame-step command, see man mpv(1)."""
|
|
1313
|
-
self.command(
|
|
1272
|
+
self.command('frame-step')
|
|
1314
1273
|
|
|
1315
1274
|
def frame_back_step(self):
|
|
1316
1275
|
"""Mapped mpv frame_back_step command, see man mpv(1)."""
|
|
1317
|
-
self.command(
|
|
1276
|
+
self.command('frame_back_step')
|
|
1318
1277
|
|
|
1319
1278
|
def property_add(self, name, value=1):
|
|
1320
1279
|
"""Add the given value to the property's value. On overflow or underflow, clamp the property to the maximum. If
|
|
1321
1280
|
``value`` is omitted, assume ``1``.
|
|
1322
1281
|
"""
|
|
1323
|
-
self.command(
|
|
1282
|
+
self.command('add', name, value)
|
|
1324
1283
|
|
|
1325
1284
|
def property_multiply(self, name, factor):
|
|
1326
1285
|
"""Multiply the value of a property with a numeric factor."""
|
|
1327
|
-
self.command(
|
|
1286
|
+
self.command('multiply', name, factor)
|
|
1328
1287
|
|
|
1329
|
-
def cycle(self, name, direction=
|
|
1288
|
+
def cycle(self, name, direction='up'):
|
|
1330
1289
|
"""Cycle the given property. ``up`` and ``down`` set the cycle direction. On overflow, set the property back to
|
|
1331
1290
|
the minimum, on underflow set it to the maximum. If ``up`` or ``down`` is omitted, assume ``up``.
|
|
1332
1291
|
"""
|
|
1333
|
-
self.command(
|
|
1292
|
+
self.command('cycle', name, direction)
|
|
1334
1293
|
|
|
1335
|
-
def screenshot(self, includes=
|
|
1294
|
+
def screenshot(self, includes='subtitles', mode='single'):
|
|
1336
1295
|
"""Mapped mpv screenshot command, see man mpv(1)."""
|
|
1337
|
-
self.command(
|
|
1296
|
+
self.command('screenshot', includes, mode)
|
|
1338
1297
|
|
|
1339
|
-
def screenshot_to_file(self, filename, includes=
|
|
1298
|
+
def screenshot_to_file(self, filename, includes='subtitles'):
|
|
1340
1299
|
"""Mapped mpv screenshot_to_file command, see man mpv(1)."""
|
|
1341
|
-
self.command(
|
|
1300
|
+
self.command('screenshot_to_file', filename.encode(fs_enc), includes)
|
|
1342
1301
|
|
|
1343
|
-
def screenshot_raw(self, includes=
|
|
1302
|
+
def screenshot_raw(self, includes='subtitles'):
|
|
1344
1303
|
"""Mapped mpv screenshot_raw command, see man mpv(1). Returns a pillow Image object."""
|
|
1345
1304
|
from PIL import Image
|
|
1346
|
-
|
|
1347
|
-
res
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
b, g, r, a = img.split()
|
|
1354
|
-
return Image.merge("RGB", (r, g, b))
|
|
1305
|
+
res = self.command('screenshot-raw', includes)
|
|
1306
|
+
if res['format'] != 'bgr0':
|
|
1307
|
+
raise ValueError('Screenshot in unknown format "{}". Currently, only bgr0 is supported.'
|
|
1308
|
+
.format(res['format']))
|
|
1309
|
+
img = Image.frombytes('RGBA', (res['stride']//4, res['h']), res['data'])
|
|
1310
|
+
b,g,r,a = img.split()
|
|
1311
|
+
return Image.merge('RGB', (r,g,b))
|
|
1355
1312
|
|
|
1356
1313
|
def allocate_overlay_id(self):
|
|
1357
1314
|
free_ids = set(range(64)) - self.overlay_ids
|
|
1358
1315
|
if not free_ids:
|
|
1359
|
-
raise IndexError(
|
|
1316
|
+
raise IndexError('All overlay IDs are in use')
|
|
1360
1317
|
next_id, *_ = sorted(free_ids)
|
|
1361
1318
|
self.overlay_ids.add(next_id)
|
|
1362
1319
|
return next_id
|
|
@@ -1364,13 +1321,13 @@ class MPV(object):
|
|
|
1364
1321
|
def free_overlay_id(self, overlay_id):
|
|
1365
1322
|
self.overlay_ids.remove(overlay_id)
|
|
1366
1323
|
|
|
1367
|
-
def create_file_overlay(self, filename=None, size=None, stride=None, pos=(0,
|
|
1324
|
+
def create_file_overlay(self, filename=None, size=None, stride=None, pos=(0,0)):
|
|
1368
1325
|
overlay_id = self.allocate_overlay_id()
|
|
1369
1326
|
overlay = FileOverlay(self, overlay_id, filename, size, stride, pos)
|
|
1370
1327
|
self.overlays[overlay_id] = overlay
|
|
1371
1328
|
return overlay
|
|
1372
1329
|
|
|
1373
|
-
def create_image_overlay(self, img=None, pos=(0,
|
|
1330
|
+
def create_image_overlay(self, img=None, pos=(0,0)):
|
|
1374
1331
|
overlay_id = self.allocate_overlay_id()
|
|
1375
1332
|
overlay = ImageOverlay(self, overlay_id, img, pos)
|
|
1376
1333
|
self.overlays[overlay_id] = overlay
|
|
@@ -1381,207 +1338,219 @@ class MPV(object):
|
|
|
1381
1338
|
self.free_overlay_id(overlay_id)
|
|
1382
1339
|
del self.overlays[overlay_id]
|
|
1383
1340
|
|
|
1384
|
-
def playlist_next(self, mode=
|
|
1341
|
+
def playlist_next(self, mode='weak'):
|
|
1385
1342
|
"""Mapped mpv playlist_next command, see man mpv(1)."""
|
|
1386
|
-
self.command(
|
|
1343
|
+
self.command('playlist_next', mode)
|
|
1387
1344
|
|
|
1388
|
-
def playlist_prev(self, mode=
|
|
1345
|
+
def playlist_prev(self, mode='weak'):
|
|
1389
1346
|
"""Mapped mpv playlist_prev command, see man mpv(1)."""
|
|
1390
|
-
self.command(
|
|
1347
|
+
self.command('playlist_prev', mode)
|
|
1391
1348
|
|
|
1392
1349
|
def playlist_play_index(self, idx):
|
|
1393
1350
|
"""Mapped mpv playlist-play-index command, see man mpv(1)."""
|
|
1394
|
-
self.command(
|
|
1351
|
+
self.command('playlist-play-index', idx)
|
|
1395
1352
|
|
|
1396
1353
|
@staticmethod
|
|
1397
1354
|
def _encode_options(options):
|
|
1398
|
-
return
|
|
1355
|
+
return ','.join('{}={}'.format(_py_to_mpv(str(key)), str(val)) for key, val in options.items())
|
|
1399
1356
|
|
|
1400
|
-
def loadfile(self, filename, mode=
|
|
1357
|
+
def loadfile(self, filename, mode='replace', index=None, **options):
|
|
1401
1358
|
"""Mapped mpv loadfile command, see man mpv(1)."""
|
|
1402
|
-
self.
|
|
1359
|
+
if self.mpv_version_tuple >= (0, 38, 0):
|
|
1360
|
+
if index is None:
|
|
1361
|
+
index = -1
|
|
1362
|
+
self.command('loadfile', filename.encode(fs_enc), mode, index, MPV._encode_options(options))
|
|
1363
|
+
else:
|
|
1364
|
+
if index is not None:
|
|
1365
|
+
warn(f'The index argument to the loadfile command is only supported on mpv >= 0.38.0')
|
|
1366
|
+
self.command('loadfile', filename.encode(fs_enc), mode, MPV._encode_options(options))
|
|
1403
1367
|
|
|
1404
|
-
def loadlist(self, playlist, mode=
|
|
1368
|
+
def loadlist(self, playlist, mode='replace'):
|
|
1405
1369
|
"""Mapped mpv loadlist command, see man mpv(1)."""
|
|
1406
|
-
self.command(
|
|
1370
|
+
self.command('loadlist', playlist.encode(fs_enc), mode)
|
|
1407
1371
|
|
|
1408
1372
|
def playlist_clear(self):
|
|
1409
1373
|
"""Mapped mpv playlist_clear command, see man mpv(1)."""
|
|
1410
|
-
self.command(
|
|
1374
|
+
self.command('playlist_clear')
|
|
1411
1375
|
|
|
1412
|
-
def playlist_remove(self, index=
|
|
1376
|
+
def playlist_remove(self, index='current'):
|
|
1413
1377
|
"""Mapped mpv playlist_remove command, see man mpv(1)."""
|
|
1414
|
-
self.command(
|
|
1378
|
+
self.command('playlist_remove', index)
|
|
1415
1379
|
|
|
1416
1380
|
def playlist_move(self, index1, index2):
|
|
1417
1381
|
"""Mapped mpv playlist_move command, see man mpv(1)."""
|
|
1418
|
-
self.command(
|
|
1382
|
+
self.command('playlist_move', index1, index2)
|
|
1419
1383
|
|
|
1420
1384
|
def playlist_shuffle(self):
|
|
1421
1385
|
"""Mapped mpv playlist-shuffle command, see man mpv(1)."""
|
|
1422
|
-
self.command(
|
|
1386
|
+
self.command('playlist-shuffle')
|
|
1423
1387
|
|
|
1424
1388
|
def playlist_unshuffle(self):
|
|
1425
1389
|
"""Mapped mpv playlist-unshuffle command, see man mpv(1)."""
|
|
1426
|
-
self.command(
|
|
1390
|
+
self.command('playlist-unshuffle')
|
|
1427
1391
|
|
|
1428
1392
|
def run(self, command, *args):
|
|
1429
1393
|
"""Mapped mpv run command, see man mpv(1)."""
|
|
1430
|
-
self.command(
|
|
1394
|
+
self.command('run', command, *args)
|
|
1431
1395
|
|
|
1432
1396
|
def quit(self, code=None):
|
|
1433
1397
|
"""Mapped mpv quit command, see man mpv(1)."""
|
|
1434
|
-
|
|
1398
|
+
if code is not None:
|
|
1399
|
+
self.command('quit', code)
|
|
1400
|
+
else:
|
|
1401
|
+
self.command('quit')
|
|
1435
1402
|
|
|
1436
1403
|
def quit_watch_later(self, code=None):
|
|
1437
1404
|
"""Mapped mpv quit_watch_later command, see man mpv(1)."""
|
|
1438
|
-
|
|
1405
|
+
if code is not None:
|
|
1406
|
+
self.command('quit_watch_later', code)
|
|
1407
|
+
else:
|
|
1408
|
+
self.command('quit_watch_later')
|
|
1439
1409
|
|
|
1440
1410
|
def stop(self, keep_playlist=False):
|
|
1441
1411
|
"""Mapped mpv stop command, see man mpv(1)."""
|
|
1442
1412
|
if keep_playlist:
|
|
1443
|
-
self.command(
|
|
1413
|
+
self.command('stop', 'keep-playlist')
|
|
1444
1414
|
else:
|
|
1445
|
-
self.command(
|
|
1415
|
+
self.command('stop')
|
|
1446
1416
|
|
|
1447
|
-
def audio_add(self, url, flags=
|
|
1417
|
+
def audio_add(self, url, flags='select', title=None, lang=None):
|
|
1448
1418
|
"""Mapped mpv audio_add command, see man mpv(1)."""
|
|
1449
|
-
self.command(
|
|
1419
|
+
self.command('audio_add', url.encode(fs_enc), *_drop_nones(flags, title, lang))
|
|
1450
1420
|
|
|
1451
1421
|
def audio_remove(self, audio_id=None):
|
|
1452
1422
|
"""Mapped mpv audio_remove command, see man mpv(1)."""
|
|
1453
|
-
self.command(
|
|
1423
|
+
self.command('audio_remove', audio_id)
|
|
1454
1424
|
|
|
1455
1425
|
def audio_reload(self, audio_id=None):
|
|
1456
1426
|
"""Mapped mpv audio_reload command, see man mpv(1)."""
|
|
1457
|
-
self.command(
|
|
1427
|
+
self.command('audio_reload', audio_id)
|
|
1458
1428
|
|
|
1459
|
-
def video_add(self, url, flags=
|
|
1429
|
+
def video_add(self, url, flags='select', title=None, lang=None, albumart=None):
|
|
1460
1430
|
"""Mapped mpv video_add command, see man mpv(1)."""
|
|
1461
|
-
self.command(
|
|
1431
|
+
self.command('video_add', url.encode(fs_enc), *_drop_nones(flags, title, lang, albumart))
|
|
1462
1432
|
|
|
1463
1433
|
def video_remove(self, video_id=None):
|
|
1464
1434
|
"""Mapped mpv video_remove command, see man mpv(1)."""
|
|
1465
|
-
self.command(
|
|
1435
|
+
self.command('video_remove', video_id)
|
|
1466
1436
|
|
|
1467
1437
|
def video_reload(self, video_id=None):
|
|
1468
1438
|
"""Mapped mpv video_reload command, see man mpv(1)."""
|
|
1469
|
-
self.command(
|
|
1439
|
+
self.command('video_reload', video_id)
|
|
1470
1440
|
|
|
1471
|
-
def sub_add(self, url, flags=
|
|
1441
|
+
def sub_add(self, url, flags='select', title=None, lang=None):
|
|
1472
1442
|
"""Mapped mpv sub_add command, see man mpv(1)."""
|
|
1473
|
-
self.command(
|
|
1443
|
+
self.command('sub_add', url.encode(fs_enc), *_drop_nones(flags, title, lang))
|
|
1474
1444
|
|
|
1475
1445
|
def sub_remove(self, sub_id=None):
|
|
1476
1446
|
"""Mapped mpv sub_remove command, see man mpv(1)."""
|
|
1477
|
-
self.command(
|
|
1447
|
+
self.command('sub_remove', sub_id)
|
|
1478
1448
|
|
|
1479
1449
|
def sub_reload(self, sub_id=None):
|
|
1480
1450
|
"""Mapped mpv sub_reload command, see man mpv(1)."""
|
|
1481
|
-
self.command(
|
|
1451
|
+
self.command('sub_reload', sub_id)
|
|
1482
1452
|
|
|
1483
1453
|
def sub_step(self, skip):
|
|
1484
1454
|
"""Mapped mpv sub_step command, see man mpv(1)."""
|
|
1485
|
-
self.command(
|
|
1455
|
+
self.command('sub_step', skip)
|
|
1486
1456
|
|
|
1487
1457
|
def sub_seek(self, skip):
|
|
1488
1458
|
"""Mapped mpv sub_seek command, see man mpv(1)."""
|
|
1489
|
-
self.command(
|
|
1459
|
+
self.command('sub_seek', skip)
|
|
1490
1460
|
|
|
1491
1461
|
def toggle_osd(self):
|
|
1492
1462
|
"""Mapped mpv osd command, see man mpv(1)."""
|
|
1493
|
-
self.command(
|
|
1463
|
+
self.command('osd')
|
|
1494
1464
|
|
|
1495
1465
|
def print_text(self, text):
|
|
1496
1466
|
"""Mapped mpv print-text command, see man mpv(1)."""
|
|
1497
|
-
self.command(
|
|
1467
|
+
self.command('print-text', text)
|
|
1498
1468
|
|
|
1499
|
-
def show_text(self, string, duration=
|
|
1469
|
+
def show_text(self, string, duration='-1', level=0):
|
|
1500
1470
|
"""Mapped mpv show_text command, see man mpv(1)."""
|
|
1501
|
-
self.command(
|
|
1471
|
+
self.command('show_text', string, duration, level)
|
|
1502
1472
|
|
|
1503
1473
|
def expand_text(self, text):
|
|
1504
1474
|
"""Mapped mpv expand-text command, see man mpv(1)."""
|
|
1505
|
-
return self.command(
|
|
1475
|
+
return self.command('expand-text', text)
|
|
1506
1476
|
|
|
1507
1477
|
def expand_path(self, path):
|
|
1508
1478
|
"""Mapped mpv expand-path command, see man mpv(1)."""
|
|
1509
|
-
return self.command(
|
|
1479
|
+
return self.command('expand-path', path)
|
|
1510
1480
|
|
|
1511
1481
|
def show_progress(self):
|
|
1512
1482
|
"""Mapped mpv show_progress command, see man mpv(1)."""
|
|
1513
|
-
self.command(
|
|
1483
|
+
self.command('show_progress')
|
|
1514
1484
|
|
|
1515
|
-
def rescan_external_files(self, mode=
|
|
1485
|
+
def rescan_external_files(self, mode='reselect'):
|
|
1516
1486
|
"""Mapped mpv rescan-external-files command, see man mpv(1)."""
|
|
1517
|
-
self.command(
|
|
1487
|
+
self.command('rescan-external-files', mode)
|
|
1518
1488
|
|
|
1519
1489
|
def discnav(self, command):
|
|
1520
1490
|
"""Mapped mpv discnav command, see man mpv(1)."""
|
|
1521
|
-
self.command(
|
|
1491
|
+
self.command('discnav', command)
|
|
1522
1492
|
|
|
1523
|
-
def mouse(x, y, button=None, mode=
|
|
1493
|
+
def mouse(self, x, y, button=None, mode='single'):
|
|
1524
1494
|
"""Mapped mpv mouse command, see man mpv(1)."""
|
|
1525
1495
|
if button is None:
|
|
1526
|
-
self.command(
|
|
1496
|
+
self.command('mouse', x, y, mode)
|
|
1527
1497
|
else:
|
|
1528
|
-
self.command(
|
|
1498
|
+
self.command('mouse', x, y, button, mode)
|
|
1529
1499
|
|
|
1530
1500
|
def keypress(self, name):
|
|
1531
1501
|
"""Mapped mpv keypress command, see man mpv(1)."""
|
|
1532
|
-
self.command(
|
|
1502
|
+
self.command('keypress', name)
|
|
1533
1503
|
|
|
1534
1504
|
def keydown(self, name):
|
|
1535
1505
|
"""Mapped mpv keydown command, see man mpv(1)."""
|
|
1536
|
-
self.command(
|
|
1506
|
+
self.command('keydown', name)
|
|
1537
1507
|
|
|
1538
1508
|
def keyup(self, name=None):
|
|
1539
1509
|
"""Mapped mpv keyup command, see man mpv(1)."""
|
|
1540
1510
|
if name is None:
|
|
1541
|
-
self.command(
|
|
1511
|
+
self.command('keyup')
|
|
1542
1512
|
else:
|
|
1543
|
-
self.command(
|
|
1513
|
+
self.command('keyup', name)
|
|
1544
1514
|
|
|
1545
1515
|
def keybind(self, name, command):
|
|
1546
1516
|
"""Mapped mpv keybind command, see man mpv(1)."""
|
|
1547
|
-
self.command(
|
|
1517
|
+
self.command('keybind', name, command)
|
|
1548
1518
|
|
|
1549
1519
|
def write_watch_later_config(self):
|
|
1550
1520
|
"""Mapped mpv write_watch_later_config command, see man mpv(1)."""
|
|
1551
|
-
self.command(
|
|
1521
|
+
self.command('write_watch_later_config')
|
|
1552
1522
|
|
|
1553
1523
|
def overlay_add(self, overlay_id, x, y, file_or_fd, offset, fmt, w, h, stride):
|
|
1554
1524
|
"""Mapped mpv overlay_add command, see man mpv(1)."""
|
|
1555
|
-
self.command(
|
|
1525
|
+
self.command('overlay_add', overlay_id, x, y, file_or_fd, offset, fmt, w, h, stride)
|
|
1556
1526
|
|
|
1557
1527
|
def overlay_remove(self, overlay_id):
|
|
1558
1528
|
"""Mapped mpv overlay_remove command, see man mpv(1)."""
|
|
1559
|
-
self.command(
|
|
1529
|
+
self.command('overlay_remove', overlay_id)
|
|
1560
1530
|
|
|
1561
1531
|
def osd_overlay(self, overlay_id, data, res_x=0, res_y=720, z=0, hidden=False):
|
|
1562
|
-
self.command(
|
|
1563
|
-
|
|
1564
|
-
)
|
|
1532
|
+
self.command('osd_overlay', id=overlay_id, data=data, res_x=res_x, res_y=res_Y, z=z, hidden=hidden,
|
|
1533
|
+
format='ass-events')
|
|
1565
1534
|
|
|
1566
1535
|
def osd_overlay_remove(self, overlay_id):
|
|
1567
|
-
self.command(
|
|
1536
|
+
self.command('osd_overlay', id=overlay_id, format='none')
|
|
1568
1537
|
|
|
1569
1538
|
def script_message(self, *args):
|
|
1570
1539
|
"""Mapped mpv script_message command, see man mpv(1)."""
|
|
1571
|
-
self.command(
|
|
1540
|
+
self.command('script_message', *args)
|
|
1572
1541
|
|
|
1573
1542
|
def script_message_to(self, target, *args):
|
|
1574
1543
|
"""Mapped mpv script_message_to command, see man mpv(1)."""
|
|
1575
|
-
self.command(
|
|
1544
|
+
self.command('script_message_to', target, *args)
|
|
1576
1545
|
|
|
1577
1546
|
def drop_buffers(self):
|
|
1578
|
-
self.command(
|
|
1547
|
+
self.command('drop_buffers')
|
|
1579
1548
|
|
|
1580
1549
|
def vf_command(self, label, command, argument):
|
|
1581
|
-
self.command(
|
|
1550
|
+
self.command('vf_command', label, command, argument)
|
|
1582
1551
|
|
|
1583
1552
|
def af_command(self, label, command, argument):
|
|
1584
|
-
self.command(
|
|
1553
|
+
self.command('af_command', label, command, argument)
|
|
1585
1554
|
|
|
1586
1555
|
def observe_property(self, name, handler):
|
|
1587
1556
|
"""Register an observer on the named property. An observer is a function that is called with the new property
|
|
@@ -1602,16 +1571,14 @@ class MPV(object):
|
|
|
1602
1571
|
from calling MPV.terminate() or issuing a "quit" input command).
|
|
1603
1572
|
"""
|
|
1604
1573
|
self._property_handlers[name].append(handler)
|
|
1605
|
-
_mpv_observe_property(self._event_handle, hash(name)
|
|
1574
|
+
_mpv_observe_property(self._event_handle, hash(name)&0xffffffffffffffff, name.encode('utf-8'), MpvFormat.NODE)
|
|
1606
1575
|
|
|
1607
1576
|
def property_observer(self, name):
|
|
1608
1577
|
"""Function decorator to register a property observer. See ``MPV.observe_property`` for details."""
|
|
1609
|
-
|
|
1610
1578
|
def wrapper(fun):
|
|
1611
1579
|
self.observe_property(name, fun)
|
|
1612
1580
|
fun.unobserve_mpv_properties = lambda: self.unobserve_property(name, fun)
|
|
1613
1581
|
return fun
|
|
1614
|
-
|
|
1615
1582
|
return wrapper
|
|
1616
1583
|
|
|
1617
1584
|
def unobserve_property(self, name, handler):
|
|
@@ -1621,7 +1588,7 @@ class MPV(object):
|
|
|
1621
1588
|
"""
|
|
1622
1589
|
self._property_handlers[name].remove(handler)
|
|
1623
1590
|
if not self._property_handlers[name]:
|
|
1624
|
-
_mpv_unobserve_property(self._event_handle, hash(name)
|
|
1591
|
+
_mpv_unobserve_property(self._event_handle, hash(name)&0xffffffffffffffff)
|
|
1625
1592
|
|
|
1626
1593
|
def unobserve_all_properties(self, handler):
|
|
1627
1594
|
"""Unregister a property observer from *all* observed properties."""
|
|
@@ -1675,12 +1642,10 @@ class MPV(object):
|
|
|
1675
1642
|
|
|
1676
1643
|
my_handler.unregister_mpv_messages()
|
|
1677
1644
|
"""
|
|
1678
|
-
|
|
1679
1645
|
def register(handler):
|
|
1680
1646
|
self._register_message_handler_internal(target, handler)
|
|
1681
1647
|
handler.unregister_mpv_messages = lambda: self.unregister_message_handler(handler)
|
|
1682
1648
|
return handler
|
|
1683
|
-
|
|
1684
1649
|
return register
|
|
1685
1650
|
|
|
1686
1651
|
def register_event_callback(self, callback):
|
|
@@ -1716,28 +1681,24 @@ class MPV(object):
|
|
|
1716
1681
|
|
|
1717
1682
|
my_handler.unregister_mpv_events()
|
|
1718
1683
|
"""
|
|
1719
|
-
|
|
1720
1684
|
def register(callback):
|
|
1721
1685
|
with self._event_handler_lock:
|
|
1722
1686
|
self.check_core_alive()
|
|
1723
1687
|
types = [MpvEventID.from_str(t) if isinstance(t, str) else t for t in event_types] or MpvEventID.ANY
|
|
1724
|
-
|
|
1725
1688
|
@wraps(callback)
|
|
1726
1689
|
def wrapper(event, *args, **kwargs):
|
|
1727
1690
|
if event.event_id.value in types:
|
|
1728
1691
|
callback(event, *args, **kwargs)
|
|
1729
|
-
|
|
1730
1692
|
self._event_callbacks.append(wrapper)
|
|
1731
1693
|
wrapper.unregister_mpv_events = partial(self.unregister_event_callback, wrapper)
|
|
1732
1694
|
return wrapper
|
|
1733
|
-
|
|
1734
1695
|
return register
|
|
1735
1696
|
|
|
1736
1697
|
@staticmethod
|
|
1737
1698
|
def _binding_name(callback_or_cmd):
|
|
1738
|
-
return
|
|
1699
|
+
return 'py_kb_{:016x}'.format(hash(callback_or_cmd)&0xffffffffffffffff)
|
|
1739
1700
|
|
|
1740
|
-
def on_key_press(self, keydef, mode=
|
|
1701
|
+
def on_key_press(self, keydef, mode='force'):
|
|
1741
1702
|
"""Function decorator to register a simplified key binding. The callback is called whenever the key given is
|
|
1742
1703
|
*pressed*.
|
|
1743
1704
|
|
|
@@ -1756,19 +1717,16 @@ class MPV(object):
|
|
|
1756
1717
|
|
|
1757
1718
|
The BIG FAT WARNING regarding untrusted keydefs from the key_binding method applies here as well.
|
|
1758
1719
|
"""
|
|
1759
|
-
|
|
1760
1720
|
def register(fun):
|
|
1761
1721
|
@self.key_binding(keydef, mode)
|
|
1762
1722
|
@wraps(fun)
|
|
1763
|
-
def wrapper(state=
|
|
1764
|
-
if state[0] in (
|
|
1723
|
+
def wrapper(state='p-', name=None, char=None):
|
|
1724
|
+
if state[0] in ('d', 'p'):
|
|
1765
1725
|
fun()
|
|
1766
|
-
|
|
1767
1726
|
return wrapper
|
|
1768
|
-
|
|
1769
1727
|
return register
|
|
1770
1728
|
|
|
1771
|
-
def key_binding(self, keydef, mode=
|
|
1729
|
+
def key_binding(self, keydef, mode='force'):
|
|
1772
1730
|
"""Function decorator to register a low-level key binding.
|
|
1773
1731
|
|
|
1774
1732
|
The callback function signature is ``fun(key_state, key_name)`` where ``key_state`` is either ``'U'`` for "key
|
|
@@ -1796,98 +1754,88 @@ class MPV(object):
|
|
|
1796
1754
|
completely fine--but, if you are about to pass untrusted input into this parameter, better double-check whether
|
|
1797
1755
|
this is secure in your case.
|
|
1798
1756
|
"""
|
|
1799
|
-
|
|
1800
1757
|
def register(fun):
|
|
1801
|
-
fun.mpv_key_bindings = getattr(fun,
|
|
1802
|
-
|
|
1758
|
+
fun.mpv_key_bindings = getattr(fun, 'mpv_key_bindings', []) + [keydef]
|
|
1803
1759
|
def unregister_all():
|
|
1804
1760
|
for keydef in fun.mpv_key_bindings:
|
|
1805
1761
|
self.unregister_key_binding(keydef)
|
|
1806
|
-
|
|
1807
1762
|
fun.unregister_mpv_key_bindings = unregister_all
|
|
1808
1763
|
|
|
1809
1764
|
self.register_key_binding(keydef, fun, mode)
|
|
1810
1765
|
return fun
|
|
1811
|
-
|
|
1812
1766
|
return register
|
|
1813
1767
|
|
|
1814
|
-
def register_key_binding(self, keydef, callback_or_cmd, mode=
|
|
1768
|
+
def register_key_binding(self, keydef, callback_or_cmd, mode='force'):
|
|
1815
1769
|
"""Register a key binding. This takes an mpv keydef and either a string containing a mpv command or a python
|
|
1816
1770
|
callback function. See ``MPV.key_binding`` for details.
|
|
1817
1771
|
"""
|
|
1818
|
-
if not re.match(r
|
|
1819
|
-
raise ValueError(
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
"symbolic name (as printed by --input-keylist"
|
|
1823
|
-
)
|
|
1772
|
+
if not re.match(r'(Shift+)?(Ctrl+)?(Alt+)?(Meta+)?(.|\w+)', keydef):
|
|
1773
|
+
raise ValueError('Invalid keydef. Expected format: [Shift+][Ctrl+][Alt+][Meta+]<key>\n'
|
|
1774
|
+
'<key> is either the literal character the key produces (ASCII or Unicode character), or a '
|
|
1775
|
+
'symbolic name (as printed by --input-keylist')
|
|
1824
1776
|
binding_name = MPV._binding_name(keydef)
|
|
1825
1777
|
if callable(callback_or_cmd):
|
|
1826
1778
|
self._key_binding_handlers[binding_name] = callback_or_cmd
|
|
1827
|
-
self.register_message_handler(
|
|
1828
|
-
self.command(
|
|
1829
|
-
|
|
1830
|
-
binding_name,
|
|
1831
|
-
"{} script-binding py_event_handler/{}".format(keydef, binding_name),
|
|
1832
|
-
mode,
|
|
1833
|
-
)
|
|
1779
|
+
self.register_message_handler('key-binding', self._handle_key_binding_message)
|
|
1780
|
+
self.command('define-section',
|
|
1781
|
+
binding_name, '{} script-binding py_event_handler/{}'.format(keydef, binding_name), mode)
|
|
1834
1782
|
elif isinstance(callback_or_cmd, str):
|
|
1835
|
-
self.command(
|
|
1783
|
+
self.command('define-section', binding_name, '{} {}'.format(keydef, callback_or_cmd), mode)
|
|
1836
1784
|
else:
|
|
1837
|
-
raise TypeError(
|
|
1838
|
-
self.command(
|
|
1785
|
+
raise TypeError('register_key_binding expects either an str with an mpv command or a python callable.')
|
|
1786
|
+
self.command('enable-section', binding_name, 'allow-hide-cursor+allow-vo-dragging')
|
|
1839
1787
|
|
|
1840
1788
|
def _handle_key_binding_message(self, binding_name, key_state, key_name=None, key_char=None):
|
|
1841
|
-
binding_name = binding_name.decode(
|
|
1842
|
-
key_state = key_state.decode(
|
|
1843
|
-
key_name = key_name.decode(
|
|
1844
|
-
key_char = key_char.decode(
|
|
1789
|
+
binding_name = binding_name.decode('utf-8')
|
|
1790
|
+
key_state = key_state.decode('utf-8')
|
|
1791
|
+
key_name = key_name.decode('utf-8') if key_name is not None else None
|
|
1792
|
+
key_char = key_char.decode('utf-8') if key_char is not None else None
|
|
1845
1793
|
self._key_binding_handlers[binding_name](key_state, key_name, key_char)
|
|
1846
1794
|
|
|
1847
1795
|
def unregister_key_binding(self, keydef):
|
|
1848
1796
|
"""Unregister a key binding by keydef."""
|
|
1849
1797
|
binding_name = MPV._binding_name(keydef)
|
|
1850
|
-
self.command(
|
|
1851
|
-
self.command(
|
|
1798
|
+
self.command('disable-section', binding_name)
|
|
1799
|
+
self.command('define-section', binding_name, '')
|
|
1852
1800
|
if binding_name in self._key_binding_handlers:
|
|
1853
1801
|
del self._key_binding_handlers[binding_name]
|
|
1854
1802
|
if not self._key_binding_handlers:
|
|
1855
|
-
self.unregister_message_handler(
|
|
1803
|
+
self.unregister_message_handler('key-binding')
|
|
1856
1804
|
|
|
1857
1805
|
def register_stream_protocol(self, proto, open_fn=None):
|
|
1858
|
-
"""Register a custom stream protocol as documented in libmpv/stream_cb.h:
|
|
1859
|
-
|
|
1806
|
+
""" Register a custom stream protocol as documented in libmpv/stream_cb.h:
|
|
1807
|
+
https://github.com/mpv-player/mpv/blob/master/libmpv/stream_cb.h
|
|
1860
1808
|
|
|
1861
|
-
|
|
1809
|
+
proto is the protocol scheme, e.g. "foo" for "foo://" urls.
|
|
1862
1810
|
|
|
1863
|
-
|
|
1864
|
-
|
|
1811
|
+
This function can either be used with two parameters or it can be used as a decorator on the target
|
|
1812
|
+
function.
|
|
1865
1813
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1814
|
+
open_fn is a function taking an URI string and returning an mpv stream object.
|
|
1815
|
+
open_fn may raise a ValueError to signal libmpv the URI could not be opened.
|
|
1868
1816
|
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1817
|
+
The mpv stream protocol is as follows:
|
|
1818
|
+
class Stream:
|
|
1819
|
+
@property
|
|
1820
|
+
def size(self):
|
|
1821
|
+
return None # unknown size
|
|
1822
|
+
return size # int with size in bytes
|
|
1875
1823
|
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1824
|
+
def read(self, size):
|
|
1825
|
+
...
|
|
1826
|
+
return read # non-empty bytes object with input
|
|
1827
|
+
return b'' # empty byte object signals permanent EOF
|
|
1880
1828
|
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1829
|
+
def seek(self, pos): # optional
|
|
1830
|
+
return new_offset # integer with new byte offset. The new offset may be before the requested offset
|
|
1831
|
+
in case an exact seek is inconvenient.
|
|
1884
1832
|
|
|
1885
|
-
|
|
1886
|
-
|
|
1833
|
+
def close(self): # optional
|
|
1834
|
+
...
|
|
1887
1835
|
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1836
|
+
def cancel(self): # optional
|
|
1837
|
+
Abort a running read() or seek() operation
|
|
1838
|
+
...
|
|
1891
1839
|
|
|
1892
1840
|
"""
|
|
1893
1841
|
|
|
@@ -1895,37 +1843,70 @@ class MPV(object):
|
|
|
1895
1843
|
@StreamOpenFn
|
|
1896
1844
|
def open_backend(_userdata, uri, cb_info):
|
|
1897
1845
|
try:
|
|
1898
|
-
frontend = open_fn(uri.decode(
|
|
1846
|
+
frontend = open_fn(uri.decode('utf-8'))
|
|
1899
1847
|
except ValueError:
|
|
1900
1848
|
return ErrorCode.LOADING_FAILED
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1849
|
+
except Exception as e:
|
|
1850
|
+
for fut in self._exception_futures:
|
|
1851
|
+
try:
|
|
1852
|
+
fut.set_exception(e)
|
|
1853
|
+
break
|
|
1854
|
+
except InvalidStateError:
|
|
1855
|
+
pass
|
|
1856
|
+
else:
|
|
1857
|
+
warnings.warn(f'Unhandled exception {e} inside stream open callback for URI {uri}\n{traceback.format_exc()}')
|
|
1858
|
+
return ErrorCode.LOADING_FAILED
|
|
1907
1859
|
|
|
1908
1860
|
cb_info.contents.cookie = None
|
|
1861
|
+
|
|
1862
|
+
def read_backend(_userdata, buf, bufsize):
|
|
1863
|
+
with self._enqueue_exceptions():
|
|
1864
|
+
data = frontend.read(bufsize)
|
|
1865
|
+
for i in range(len(data)):
|
|
1866
|
+
buf[i] = data[i]
|
|
1867
|
+
return len(data)
|
|
1868
|
+
return -1
|
|
1909
1869
|
read = cb_info.contents.read = StreamReadFn(read_backend)
|
|
1910
|
-
|
|
1870
|
+
|
|
1871
|
+
def close_backend(_userdata):
|
|
1872
|
+
with self._enqueue_exceptions():
|
|
1873
|
+
del self._stream_protocol_frontends[proto][uri]
|
|
1874
|
+
if hasattr(frontend, 'close'):
|
|
1875
|
+
frontend.close()
|
|
1876
|
+
close = cb_info.contents.close = StreamCloseFn(close_backend)
|
|
1911
1877
|
|
|
1912
1878
|
seek, size, cancel = None, None, None
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1879
|
+
|
|
1880
|
+
if hasattr(frontend, 'seek'):
|
|
1881
|
+
def seek_backend(_userdata, offx):
|
|
1882
|
+
with self._enqueue_exceptions():
|
|
1883
|
+
return frontend.seek(offx)
|
|
1884
|
+
return ErrorCode.GENERIC
|
|
1885
|
+
seek = cb_info.contents.seek = StreamSeekFn(seek_backend)
|
|
1886
|
+
|
|
1887
|
+
if hasattr(frontend, 'size') and frontend.size is not None:
|
|
1888
|
+
def size_backend(_userdata):
|
|
1889
|
+
with self._enqueue_exceptions():
|
|
1890
|
+
return frontend.size
|
|
1891
|
+
return 0
|
|
1892
|
+
size = cb_info.contents.size = StreamSizeFn(size_backend)
|
|
1893
|
+
|
|
1894
|
+
if hasattr(frontend, 'cancel'):
|
|
1895
|
+
def cancel_backend(_userdata):
|
|
1896
|
+
with self._enqueue_exceptions():
|
|
1897
|
+
frontend.cancel()
|
|
1898
|
+
cancel = cb_info.contents.cancel = StreamCancelFn(cancel_backend)
|
|
1899
|
+
|
|
1900
|
+
# keep frontend and callbacks in memory until closed
|
|
1921
1901
|
frontend._registered_callbacks = [read, close, seek, size, cancel]
|
|
1922
1902
|
self._stream_protocol_frontends[proto][uri] = frontend
|
|
1923
1903
|
return 0
|
|
1924
1904
|
|
|
1925
1905
|
if proto in self._stream_protocol_cbs:
|
|
1926
|
-
raise KeyError(
|
|
1906
|
+
raise KeyError('Stream protocol already registered')
|
|
1907
|
+
# keep backend in memory forever
|
|
1927
1908
|
self._stream_protocol_cbs[proto] = [open_backend]
|
|
1928
|
-
_mpv_stream_cb_add_ro(self.handle, proto.encode(
|
|
1909
|
+
_mpv_stream_cb_add_ro(self.handle, proto.encode('utf-8'), c_void_p(), open_backend)
|
|
1929
1910
|
|
|
1930
1911
|
return open_fn
|
|
1931
1912
|
|
|
@@ -1941,12 +1922,12 @@ class MPV(object):
|
|
|
1941
1922
|
@property
|
|
1942
1923
|
def playlist_filenames(self):
|
|
1943
1924
|
"""Return all playlist item file names/URLs as a list of strs."""
|
|
1944
|
-
return [element[
|
|
1925
|
+
return [element['filename'] for element in self.playlist]
|
|
1945
1926
|
|
|
1946
1927
|
def playlist_append(self, filename, **options):
|
|
1947
1928
|
"""Append a path or URL to the playlist. This does not start playing the file automatically. To do that, use
|
|
1948
1929
|
``MPV.loadfile(filename, 'append-play')``."""
|
|
1949
|
-
self.loadfile(filename,
|
|
1930
|
+
self.loadfile(filename, 'append', **options)
|
|
1950
1931
|
|
|
1951
1932
|
# "Python stream" logic. This is some porcelain for directly playing data from python generators.
|
|
1952
1933
|
|
|
@@ -1954,7 +1935,7 @@ class MPV(object):
|
|
|
1954
1935
|
"""Internal handler for python:// protocol streams registered through @python_stream(...) and
|
|
1955
1936
|
@python_stream_catchall
|
|
1956
1937
|
"""
|
|
1957
|
-
|
|
1938
|
+
name, = re.fullmatch('python://(.*)', uri).groups()
|
|
1958
1939
|
|
|
1959
1940
|
if name in self._python_streams:
|
|
1960
1941
|
generator_fun, size = self._python_streams[name]
|
|
@@ -1962,7 +1943,7 @@ class MPV(object):
|
|
|
1962
1943
|
if self._python_stream_catchall is not None:
|
|
1963
1944
|
generator_fun, size = self._python_stream_catchall(name)
|
|
1964
1945
|
else:
|
|
1965
|
-
raise ValueError(
|
|
1946
|
+
raise ValueError('Python stream name not found and no catch-all defined')
|
|
1966
1947
|
|
|
1967
1948
|
return GeneratorStream(generator_fun, size)
|
|
1968
1949
|
|
|
@@ -1990,26 +1971,70 @@ class MPV(object):
|
|
|
1990
1971
|
mpv.wait_for_playback()
|
|
1991
1972
|
reader.unregister()
|
|
1992
1973
|
"""
|
|
1993
|
-
|
|
1994
1974
|
def register(cb):
|
|
1995
1975
|
if name in self._python_streams:
|
|
1996
1976
|
raise KeyError('Python stream name "{}" is already registered'.format(name))
|
|
1997
1977
|
self._python_streams[name] = (cb, size)
|
|
1998
|
-
|
|
1999
1978
|
def unregister():
|
|
2000
|
-
if
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
raise RuntimeError("Python stream has already been unregistered")
|
|
1979
|
+
if name not in self._python_streams or\
|
|
1980
|
+
self._python_streams[name][0] is not cb: # This is just a basic sanity check
|
|
1981
|
+
raise RuntimeError('Python stream has already been unregistered')
|
|
2004
1982
|
del self._python_streams[name]
|
|
2005
|
-
|
|
2006
1983
|
cb.unregister = unregister
|
|
2007
1984
|
return cb
|
|
2008
|
-
|
|
2009
1985
|
return register
|
|
2010
1986
|
|
|
1987
|
+
@contextmanager
|
|
1988
|
+
def play_context(self):
|
|
1989
|
+
""" Context manager for streaming bytes straight into libmpv.
|
|
1990
|
+
|
|
1991
|
+
This is a convenience wrapper around python_stream. play_context returns a write method, which you can use in
|
|
1992
|
+
the body of the context manager to feed libmpv bytes. All bytes you feed in with write() in the body of a single
|
|
1993
|
+
call of this context manager are treated as one single file. A queue is used internally, so this function is
|
|
1994
|
+
thread-safe. The queue is unlimited, so it cannot block and is safe to call from async code. You can use this
|
|
1995
|
+
function to stream chunked data, e.g. from the network.
|
|
1996
|
+
|
|
1997
|
+
Use it like this:
|
|
1998
|
+
|
|
1999
|
+
with m.play_context() as write:
|
|
2000
|
+
with open(TESTVID, 'rb') as f:
|
|
2001
|
+
while (chunk := f.read(65536)): # Get some chunks of bytes
|
|
2002
|
+
write(chunk)
|
|
2003
|
+
"""
|
|
2004
|
+
q = queue.Queue()
|
|
2005
|
+
|
|
2006
|
+
frame = sys._getframe()
|
|
2007
|
+
stream_name = f'__python_mpv_play_generator_{hash(frame)}'
|
|
2008
|
+
EOF = frame # Get some unique object as EOF marker
|
|
2009
|
+
@self.python_stream(stream_name)
|
|
2010
|
+
def reader():
|
|
2011
|
+
while (chunk := q.get()) is not EOF:
|
|
2012
|
+
if chunk:
|
|
2013
|
+
yield chunk
|
|
2014
|
+
reader.unregister()
|
|
2015
|
+
|
|
2016
|
+
def write(chunk):
|
|
2017
|
+
q.put(chunk)
|
|
2018
|
+
|
|
2019
|
+
# Start playback before yielding, the first call to reader() will block until write is called at least once.
|
|
2020
|
+
self.play(f'python://{stream_name}')
|
|
2021
|
+
yield write
|
|
2022
|
+
q.put(EOF)
|
|
2023
|
+
|
|
2024
|
+
def play_bytes(self, data):
|
|
2025
|
+
""" Play the given bytes object as a single file. """
|
|
2026
|
+
frame = sys._getframe()
|
|
2027
|
+
stream_name = f'__python_mpv_play_generator_{hash(frame)}'
|
|
2028
|
+
|
|
2029
|
+
@self.python_stream(stream_name)
|
|
2030
|
+
def reader():
|
|
2031
|
+
yield data
|
|
2032
|
+
reader.unregister() # unregister itself
|
|
2033
|
+
|
|
2034
|
+
self.play(f'python://{stream_name}')
|
|
2035
|
+
|
|
2011
2036
|
def python_stream_catchall(self, cb):
|
|
2012
|
-
"""Register a catch-all python stream to be called when no name matches can be found. Use this decorator on a
|
|
2037
|
+
""" Register a catch-all python stream to be called when no name matches can be found. Use this decorator on a
|
|
2013
2038
|
function that takes a name argument and returns a (generator, size) tuple (with size being None if unknown).
|
|
2014
2039
|
|
|
2015
2040
|
An invalid URI can be signalled to libmpv by raising a ValueError inside the callback.
|
|
@@ -2034,15 +2059,13 @@ class MPV(object):
|
|
|
2034
2059
|
catchall.unregister()
|
|
2035
2060
|
"""
|
|
2036
2061
|
if self._python_stream_catchall is not None:
|
|
2037
|
-
raise KeyError(
|
|
2062
|
+
raise KeyError('A catch-all python stream is already registered')
|
|
2038
2063
|
|
|
2039
2064
|
self._python_stream_catchall = cb
|
|
2040
|
-
|
|
2041
2065
|
def unregister():
|
|
2042
2066
|
if self._python_stream_catchall is not cb:
|
|
2043
|
-
|
|
2067
|
+
raise RuntimeError('This catch-all python stream has already been unregistered')
|
|
2044
2068
|
self._python_stream_catchall = None
|
|
2045
|
-
|
|
2046
2069
|
cb.unregister = unregister
|
|
2047
2070
|
return cb
|
|
2048
2071
|
|
|
@@ -2051,23 +2074,26 @@ class MPV(object):
|
|
|
2051
2074
|
self.check_core_alive()
|
|
2052
2075
|
out = create_string_buffer(sizeof(MpvNode))
|
|
2053
2076
|
try:
|
|
2054
|
-
cval = _mpv_get_property(self.handle, name.encode(
|
|
2077
|
+
cval = _mpv_get_property(self.handle, name.encode('utf-8'), fmt, out)
|
|
2055
2078
|
|
|
2056
2079
|
if fmt is MpvFormat.OSD_STRING:
|
|
2057
|
-
return cast(out, POINTER(c_char_p)).contents.value.decode(
|
|
2080
|
+
return cast(out, POINTER(c_char_p)).contents.value.decode('utf-8')
|
|
2058
2081
|
elif fmt is MpvFormat.NODE:
|
|
2059
2082
|
rv = cast(out, POINTER(MpvNode)).contents.node_value(decoder=decoder)
|
|
2060
2083
|
_mpv_free_node_contents(out)
|
|
2061
2084
|
return rv
|
|
2062
2085
|
else:
|
|
2063
|
-
raise TypeError(
|
|
2086
|
+
raise TypeError('_get_property only supports NODE and OSD_STRING formats.')
|
|
2064
2087
|
except PropertyUnavailableError as ex:
|
|
2065
2088
|
return None
|
|
2066
2089
|
|
|
2067
2090
|
def _set_property(self, name, value):
|
|
2068
2091
|
self.check_core_alive()
|
|
2069
|
-
ename = name.encode(
|
|
2070
|
-
if isinstance(value,
|
|
2092
|
+
ename = name.encode('utf-8')
|
|
2093
|
+
if isinstance(value, dict):
|
|
2094
|
+
_1, _2, _3, pointer = _make_node_str_map(value)
|
|
2095
|
+
_mpv_set_property(self.handle, ename, MpvFormat.NODE, pointer)
|
|
2096
|
+
elif isinstance(value, (list, set)):
|
|
2071
2097
|
_1, _2, _3, pointer = _make_node_str_list(value)
|
|
2072
2098
|
_mpv_set_property(self.handle, ename, MpvFormat.NODE, pointer)
|
|
2073
2099
|
else:
|
|
@@ -2077,31 +2103,31 @@ class MPV(object):
|
|
|
2077
2103
|
return self._get_property(_py_to_mpv(name), lazy_decoder)
|
|
2078
2104
|
|
|
2079
2105
|
def __setattr__(self, name, value):
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2106
|
+
try:
|
|
2107
|
+
if name != 'handle' and not name.startswith('_'):
|
|
2108
|
+
self._set_property(_py_to_mpv(name), value)
|
|
2109
|
+
else:
|
|
2110
|
+
super().__setattr__(name, value)
|
|
2111
|
+
except AttributeError:
|
|
2084
2112
|
super().__setattr__(name, value)
|
|
2085
|
-
except AttributeError:
|
|
2086
|
-
super().__setattr__(name, value)
|
|
2087
2113
|
|
|
2088
2114
|
def __dir__(self):
|
|
2089
|
-
return super().__dir__() + [name.replace(
|
|
2115
|
+
return super().__dir__() + [ name.replace('-', '_') for name in self.property_list ]
|
|
2090
2116
|
|
|
2091
2117
|
@property
|
|
2092
2118
|
def properties(self):
|
|
2093
|
-
return {name: self.option_info(name) for name in self.property_list}
|
|
2119
|
+
return { name: self.option_info(name) for name in self.property_list }
|
|
2094
2120
|
|
|
2095
2121
|
# Dict-like option access
|
|
2096
2122
|
def __getitem__(self, name, file_local=False):
|
|
2097
2123
|
"""Get an option value."""
|
|
2098
|
-
prefix =
|
|
2099
|
-
return self._get_property(prefix
|
|
2124
|
+
prefix = 'file-local-options/' if file_local else 'options/'
|
|
2125
|
+
return self._get_property(prefix+name, lazy_decoder)
|
|
2100
2126
|
|
|
2101
2127
|
def __setitem__(self, name, value, file_local=False):
|
|
2102
2128
|
"""Set an option value."""
|
|
2103
|
-
prefix =
|
|
2104
|
-
return self._set_property(prefix
|
|
2129
|
+
prefix = 'file-local-options/' if file_local else 'options/'
|
|
2130
|
+
return self._set_property(prefix+name, value)
|
|
2105
2131
|
|
|
2106
2132
|
def __iter__(self):
|
|
2107
2133
|
"""Iterate over all option names."""
|
|
@@ -2110,7 +2136,7 @@ class MPV(object):
|
|
|
2110
2136
|
def option_info(self, name):
|
|
2111
2137
|
"""Get information on the given option."""
|
|
2112
2138
|
try:
|
|
2113
|
-
return self._get_property(
|
|
2139
|
+
return self._get_property('option-info/'+name)
|
|
2114
2140
|
except AttributeError:
|
|
2115
2141
|
return None
|
|
2116
2142
|
|
|
@@ -2118,7 +2144,7 @@ class MPV(object):
|
|
|
2118
2144
|
class MpvRenderContext:
|
|
2119
2145
|
def __init__(self, mpv, api_type, **kwargs):
|
|
2120
2146
|
self._mpv = mpv
|
|
2121
|
-
kwargs[
|
|
2147
|
+
kwargs['api_type'] = api_type
|
|
2122
2148
|
|
|
2123
2149
|
buf = cast(create_string_buffer(sizeof(MpvRenderCtxHandle)), POINTER(MpvRenderCtxHandle))
|
|
2124
2150
|
_mpv_render_context_create(buf, mpv.handle, kwargs_to_render_param_array(kwargs))
|
|
@@ -2128,10 +2154,10 @@ class MpvRenderContext:
|
|
|
2128
2154
|
_mpv_render_context_free(self._handle)
|
|
2129
2155
|
|
|
2130
2156
|
def __setattr__(self, name, value):
|
|
2131
|
-
if name.startswith(
|
|
2157
|
+
if name.startswith('_'):
|
|
2132
2158
|
super().__setattr__(name, value)
|
|
2133
2159
|
|
|
2134
|
-
elif name ==
|
|
2160
|
+
elif name == 'update_cb':
|
|
2135
2161
|
func = value if value else (lambda: None)
|
|
2136
2162
|
self._update_cb = value
|
|
2137
2163
|
self._update_fn_wrapper = RenderUpdateFn(lambda _userdata: func())
|
|
@@ -2142,10 +2168,10 @@ class MpvRenderContext:
|
|
|
2142
2168
|
_mpv_render_context_set_parameter(self._handle, param)
|
|
2143
2169
|
|
|
2144
2170
|
def __getattr__(self, name):
|
|
2145
|
-
if name ==
|
|
2171
|
+
if name == 'update_cb':
|
|
2146
2172
|
return self._update_cb
|
|
2147
2173
|
|
|
2148
|
-
elif name ==
|
|
2174
|
+
elif name == 'handle':
|
|
2149
2175
|
return self._handle
|
|
2150
2176
|
|
|
2151
2177
|
param = MpvRenderParam(name)
|
|
@@ -2156,7 +2182,7 @@ class MpvRenderContext:
|
|
|
2156
2182
|
return buf.contents.as_dict()
|
|
2157
2183
|
|
|
2158
2184
|
def update(self):
|
|
2159
|
-
"""Calls mpv_render_context_update and returns the MPV_RENDER_UPDATE_FRAME flag (see render.h)"""
|
|
2185
|
+
""" Calls mpv_render_context_update and returns the MPV_RENDER_UPDATE_FRAME flag (see render.h) """
|
|
2160
2186
|
return bool(_mpv_render_context_update(self._handle) & 1)
|
|
2161
2187
|
|
|
2162
2188
|
def render(self, **kwargs):
|
|
@@ -2164,3 +2190,4 @@ class MpvRenderContext:
|
|
|
2164
2190
|
|
|
2165
2191
|
def report_swap(self):
|
|
2166
2192
|
_mpv_render_context_report_swap(self._handle)
|
|
2193
|
+
|