1
0
mirror of https://github.com/mpv-player/mpv synced 2025-04-18 13:16:43 +00:00

python: optimize source, add feature

- lint
- better error message through check_error when mpv api(s) return with an error
- add function hook_add
- add function hook_continue
- add event handler for MPV_EVENT_HOOK
This commit is contained in:
8lurry 2025-04-09 13:43:16 +06:00
parent 6d67e31328
commit fea5db26bb
No known key found for this signature in database
GPG Key ID: 173C569D35B6B4E2
11 changed files with 165 additions and 79 deletions

View File

@ -60,11 +60,11 @@ def parse_value(value):
return None
def format_value(value, dB):
return f"{value}{dB}"
def format_value(value, decibel):
return f"{value}{decibel}"
def show_osd(filter):
def show_osd(filter): # noqa: A002
global o
if not o["show_osd"]:
return
@ -102,7 +102,8 @@ def get_filter():
))
for param in params:
af[len(af) - 1]["params"][param["name"]] = format_value(o["default_" + param["name"]], param["dB"]) # type: ignore
af[len(af) - 1]["params"][param["name"]] = format_value(
o["default_" + param["name"]], param["dB"]) # type: ignore
return af, len(af)

View File

@ -83,7 +83,7 @@ def is_cropable(time_needed):
def remove_cropdetect():
for filter in mpv.get_property_node("vf"):
for filter in mpv.get_property_node("vf"): # noqa: A001
if filter["label"] == cropdetect_label:
mpv.command_string(f"{command_prefix} vf remove @{filter["label"]}")
return
@ -123,8 +123,8 @@ def apply_crop(meta):
return
# Apply crop.
mpv.command_string("%s set file-local-options/video-crop %sx%s+%s+%s" %
(command_prefix, meta["w"], meta["h"], meta["x"], meta["y"]))
mpv.command_string("{} set file-local-options/video-crop {}x{}+{}+{}".format(
command_prefix, meta["w"], meta["h"], meta["x"], meta["y"]))
def detect_end():
@ -182,14 +182,10 @@ def detect_crop():
hwdec_current = mpv.get_property_string("hwdec-current")
if not hwdec_current.endswith("-copy") and hwdec_current not in ["no", "crystalhd", "rkmpp"]:
hwdec_backup = mpv.get_property_string("hwdec")
mpv.set_property_string("hwdec", "no")
# Insert the cropdetect filter.
limit = options["detect_limit"]
round = options["detect_round"]
mpv.command_string(f"{command_prefix} vf pre @{cropdetect_label}:cropdetect=limit={limit}:round={round}:reset=0")
mpv.command_string(f"{command_prefix} vf pre @{cropdetect_label}:"
"cropdetect=limit={limit}:round={round}:reset=0")
# Wait to gather data.
mpv.add_timeout(time_needed, detect_end, name="detect_crop")
@ -236,7 +232,7 @@ def on_toggle():
# Cropped => Remove it.
if mpv.get_property_string("video-crop") != "":
mpv.command_string("%s set file-local-options/video-crop ''" % command_prefix)
mpv.command_string(f"{command_prefix} set file-local-options/video-crop ''")
return
# Detecting => Leave it.

View File

@ -27,16 +27,16 @@
from mpvclient import mpv # type: ignore
script_name = mpv.name
detect_label = "%s-detect" % script_name
pullup_label = "%s" % script_name
dominance_label = "%s-dominance" % script_name
ivtc_detect_label = "%s-ivtc-detect" % script_name
detect_label = f"{script_name}-detect"
pullup_label = script_name
dominance_label = f"{script_name}-dominance"
ivtc_detect_label = f"{script_name}-ivtc-detect"
progressive, interlaced_tff, interlaced_bff, interlaced = 0, 1, 2, 3
# number of seconds to gather cropdetect data
try:
detect_seconds = float(mpv.get_opt("%s.detect_seconds" % script_name, 4))
detect_seconds = float(mpv.get_opt(f"{script_name}.detect_seconds", 4))
except ValueError:
detect_seconds = 4
@ -54,8 +54,8 @@ def del_filter_if_present(label):
return False
def add_vf(label, filter):
return mpv.command_string("vf add @%s:%s" % (label, filter))
def add_vf(label, filter): # noqa: A002
return mpv.command_string(f"vf add @{label}:{filter}")
def stop_detect():
@ -65,7 +65,7 @@ def stop_detect():
def judge(label):
# get the metadata
result = mpv.get_property_node("vf-metadata/%s" % label)
result = mpv.get_property_node(f"vf-metadata/{label}")
num_tff = float(result["lavfi.idet.multiple.tff"])
num_bff = float(result["lavfi.idet.multiple.bff"])
num_progressive = float(result["lavfi.idet.multiple.progressive"])
@ -114,7 +114,7 @@ def select_filter():
# handle the ivtc detection filter results
if ivtc_verdict == progressive:
mpv.info("telecined with %s field dominance: using pullup" % dominance)
mpv.info(f"telecined with {dominance} field dominance: using pullup")
stop_detect()
else:
mpv.info("interlaced with " + dominance +

View File

@ -1,6 +1,6 @@
# Test script for some command API details.
from mpvclient import mpv
from mpvclient import mpv # type: ignore
@mpv.observe_property("vo-configured", mpv.MPV_FORMAT_FLAG)

View File

@ -15,10 +15,11 @@
# TODO: It might make sense to use hardware assisted vdpaupp=pullup,
# if available, but I don't have hardware to test it. Patch welcome.
from mpvclient import mpv
from mpvclient import mpv # type: ignore
script_name = mpv.name
pullup_label = "%s-pullup" % script_name
pullup_label = f"{script_name}-pullup"
def pullup_on():
for vf in mpv.get_property_node("vf"):
@ -26,15 +27,16 @@ def pullup_on():
return "yes"
return "no"
def do_cycle():
if pullup_on() == "yes":
# if pullup is on remove it
mpv.command_string("vf remove @%s:pullup" % pullup_label)
mpv.command_string(f"vf remove @{pullup_label}:pullup")
return
elif mpv.get_property_string("deinterlace") == "yes":
# if deinterlace is on, turn it off and insert pullup filter
mpv.set_property_string("deinterlace", "no")
mpv.command_string("vf add @%s:pullup" % pullup_label)
mpv.command_string(f"vf add @{pullup_label}:pullup")
return
else:
# if neither is on, turn on deinterlace
@ -46,5 +48,5 @@ def do_cycle():
def cycle_deinterlace_pullup_handler():
do_cycle()
# independently determine current state and give user feedback
mpv.osd_message("deinterlace: %s\npullup: %s" % (
mpv.osd_message("deinterlace: {}\npullup: {}".format(
mpv.get_property_string("deinterlace"), pullup_on()))

View File

@ -1,5 +1,5 @@
import math
from mpvclient import mpv
from mpvclient import mpv # type: ignore
def lux_to_gamma(lmin, lmax, rmin, rmax, lux):

View File

@ -4,7 +4,7 @@
# remote files), so you should in general only watch properties you
# are interested in.
from mpvclient import mpv
from mpvclient import mpv #type: ignore
def observe(name):

View File

@ -1,7 +1,7 @@
# makes mpv disable ontop when pausing and re-enable it again when resuming playback
# please note that this won't do anything if ontop was not enabled before pausing
from mpvclient import mpv
from mpvclient import mpv # type: ignore
was_ontop = False

View File

@ -1,10 +1,10 @@
from mpvclient import mpv
from mpvclient import mpv # type: ignore
things = []
for _ in range(2):
things.append({
"osd1": mpv.create_osd_overlay("ass-events"),
"osd2": mpv.create_osd_overlay("ass-events")
"osd2": mpv.create_osd_overlay("ass-events"),
})
things[0]["text"] = "{\\an5}hello\\Nworld"
@ -19,7 +19,7 @@ def the_do_hickky_thing():
res = thing["osd1"].update()
thing["osd2"].hidden = True
if res != None and res["x0"] != None:
if res is not None and res["x0"] is not None:
draw = mpv.ass_new()
draw.append("{\\alpha&H80}")
draw.draw_start()

View File

@ -205,23 +205,24 @@ print_parse_error(const char *msg)
{
PyErr_PrintEx(1);
char message[200];
snprintf(message, sizeof(message), "%s\n%s",
char *message = talloc_asprintf(NULL, "%s\n%s",
"Below is the line describing the above exception location", msg);
PyErr_SetString(PyExc_Exception, message);
talloc_free(message);
PyErr_PrintEx(1);
}
static PyObject *check_error(int err)
static PyObject *check_error(int err, char *suffix)
{
if (err >= 0) {
Py_RETURN_TRUE;
}
const char *errstr = mpv_error_string(err);
// printf("%s\n", errstr);
PyErr_SetString(PyExc_Exception, errstr);
char *msg = talloc_asprintf(NULL, "%s %s", suffix, errstr);
PyErr_SetString(PyExc_Exception, msg);
talloc_free(msg);
PyErr_PrintEx(1); // clearing it out lets python to continue (or use: PyErr_Clear())
Py_RETURN_NONE;
}
@ -371,7 +372,7 @@ py_mpv_enable_messages(PyObject *self, PyObject *args)
PyErr_SetString(PyExc_Exception, "Invalid Log Error");
Py_RETURN_NONE;
}
return check_error(res);
return check_error(res, "");
}
@ -383,13 +384,13 @@ py_mpv_enable_messages(PyObject *self, PyObject *args)
static PyObject *
py_mpv_get_property(PyObject *self, PyObject *args)
{
const char **name = talloc(NULL, const char *);
mpv_format *format = talloc(NULL, mpv_format);
void *tmp = talloc_new(NULL);
const char **name = talloc(tmp, const char *);
mpv_format *format = talloc(tmp, mpv_format);
PyObject *mpv;
if (!PyArg_ParseTuple(args, "Osi", &mpv, name, format)) {
talloc_free(name);
talloc_free(format);
talloc_free(tmp);
print_parse_error("Failed to parse args (mpv.get_property)\n");
Py_RETURN_NONE;
}
@ -397,30 +398,28 @@ py_mpv_get_property(PyObject *self, PyObject *args)
PyClientCtx *cctx = get_client_context(mpv);
if (*format == MPV_FORMAT_NONE) {
talloc_free(name);
talloc_free(format);
talloc_free(tmp);
Py_DECREF(cctx);
Py_RETURN_NONE;
}
void *out = fmt_talloc(NULL, *format);
void *out = fmt_talloc(tmp, *format);
int err;
if (*format == MPV_FORMAT_STRING || *format == MPV_FORMAT_OSD_STRING) {
err = mpv_get_property(cctx->client, *name, *format, &out);
} else {
err = mpv_get_property(cctx->client, *name, *format, out);
}
talloc_free(name);
Py_DECREF(cctx);
if (err >= 0) {
PyObject *ret = unmakedata(*format, out);
talloc_free(out);
talloc_free(format);
talloc_free(tmp);
return ret;
}
talloc_free(out);
talloc_free(format);
return check_error(err);
char *suffix = talloc_asprintf(tmp, "(mpv.get_property) property_name: '%s' (format: '%d')::", *name, *format);
PyObject *ret = check_error(err, suffix);
talloc_free(tmp);
return ret;
}
@ -481,7 +480,7 @@ py_mpv_set_property(PyObject *self, PyObject *args)
res = mpv_set_property(cctx->client, *name, *format, data);
talloc_free(tctx);
Py_DECREF(cctx);
return check_error(res);
return check_error(res, "");
}
@ -507,7 +506,7 @@ py_mpv_del_property(PyObject *self, PyObject *args)
talloc_free(p);
Py_DECREF(cctx);
return check_error(res);
return check_error(res, "");
}
@ -538,7 +537,7 @@ py_mpv_observe_property(PyObject *self, PyObject *args)
int err = mpv_observe_property(ctx->client, *reply_userdata, *name, *format);
talloc_free(tctx);
Py_DECREF(ctx);
return check_error(err);
return check_error(err, "");
}
@ -561,7 +560,7 @@ py_mpv_unobserve_property(PyObject *self, PyObject *args)
talloc_free(reply_userdata);
Py_DECREF(ctx);
return check_error(err);
return check_error(err, "");
}
@ -573,7 +572,7 @@ py_mpv_command(PyObject *self, PyObject *args)
struct mpv_node *cmd = talloc(tmp, struct mpv_node);
makenode(tmp, PyTuple_GetItem(args, 1), cmd);
struct mpv_node *result = talloc(tmp, struct mpv_node);
if (!PyObject_IsTrue(check_error(mpv_command_node(cctx->client, cmd, result)))) {
if (!PyObject_IsTrue(check_error(mpv_command_node(cctx->client, cmd, result), ""))) {
MP_ERR(cctx, "failed to run node command\n");
talloc_free(tmp);
Py_DECREF(cctx);
@ -604,7 +603,7 @@ py_mpv_commandv(PyObject *self, PyObject *args)
int ret = mpv_command(cctx->client, argv);
Py_DECREF(cctx);
talloc_free(argv);
return check_error(ret);
return check_error(ret, "");
}
@ -626,7 +625,7 @@ py_mpv_command_string(PyObject *self, PyObject *args)
int res = mpv_command_string(cctx->client, *s);
talloc_free(s);
Py_DECREF(cctx);
return check_error(res);
return check_error(res, "");
}
@ -653,7 +652,7 @@ py_mpv_command_node_async(PyObject *self, PyObject *args)
talloc_free(command_id);
talloc_free(tmp);
Py_DECREF(cctx);
return check_error(res);
return check_error(res, "");
}
@ -675,6 +674,65 @@ py_mpv_abort_async_command(PyObject *self, PyObject *args)
}
static PyObject *
py_mpv_hook_add(PyObject *self, PyObject *args)
{
PyObject *mpv;
void *tmp = talloc_new(NULL);
uint64_t *reply_userdata = talloc(tmp, uint64_t);
char **name = talloc(tmp, char *);
int *priority = talloc(tmp, int);
if (!PyArg_ParseTuple(args, "OKsi", &mpv, reply_userdata, name, priority)) {
talloc_free(tmp);
print_parse_error("Failed to parse args (mpv.hook_add)\n");
Py_RETURN_NONE;
}
PyClientCtx *ctx = get_client_context(mpv);
int err = mpv_hook_add(ctx->client, *reply_userdata, *name, *priority);
Py_DECREF(ctx);
if (err >= 0) {
talloc_free(tmp);
Py_RETURN_TRUE;
}
char *suffix = talloc_asprintf(tmp, "(mpv.hook_add) (%ld) '%s' '%d':",
*reply_userdata, *name, *priority);
PyObject *ret = check_error(err, suffix);
talloc_free(tmp);
return ret;
}
static PyObject *
py_mpv_hook_continue(PyObject *self, PyObject *args)
{
PyObject *mpv;
void *tmp = talloc_new(NULL);
uint64_t *id = talloc(tmp, uint64_t);
if (!PyArg_ParseTuple(args, "OK", &mpv, id)) {
talloc_free(tmp);
print_parse_error("Failed to parse args (mpv.hook_continue)\n");
Py_RETURN_NONE;
}
PyClientCtx *ctx = get_client_context(mpv);
int err = mpv_hook_continue(ctx->client, *id);
Py_DECREF(ctx);
if (err >= 0) {
talloc_free(tmp);
Py_RETURN_TRUE;
}
char *suffix = talloc_asprintf(tmp, "(mpv.hook_continue) '%ld':", *id);
PyObject *ret = check_error(err, suffix);
talloc_free(tmp);
return ret;
}
/**
* @param args:
* :param int event_id:
@ -698,7 +756,7 @@ py_mpv_request_event(PyObject *self, PyObject *args)
talloc_free(args_);
Py_DECREF(cctx);
return check_error(err);
return check_error(err, "");
}
@ -786,6 +844,12 @@ static PyObject *py_mpv_run_event_loop(PyObject *self, PyObject *args)
} else if (event->event_id == MPV_EVENT_COMMAND_REPLY) {
mpv_event_command *result_node = (mpv_event_command *)event->data;
PyDict_SetItemString(ed, "data", deconstructnode(&result_node->result));
} else if (event->event_id == MPV_EVENT_HOOK) {
mpv_event_hook *hook = (mpv_event_hook *)event->data;
PyObject *data = PyDict_New();
PyDict_SetItemString(data, "name", PyUnicode_DecodeFSDefault(hook->name));
PyDict_SetItemString(data, "id", PyLong_FromUnsignedLongLong(hook->id));
PyDict_SetItemString(ed, "data", data);
} else PyDict_SetItemString(ed, "data", Py_None);
if (PyObject_CallMethod(mpv, "handle_event", "O", ed) == Py_False) {
@ -837,6 +901,10 @@ static PyMethodDef py_mpv_methods[] = {
PyDoc_STR("runs mpv_command_node_async given a command_id and py structure(s, as in list) convertible to mpv_node.")},
{"abort_async_command", (PyCFunction)py_mpv_abort_async_command, METH_VARARGS,
PyDoc_STR("given an async command id, aborts it.")},
{"hook_add", (PyCFunction)py_mpv_hook_add, METH_VARARGS,
PyDoc_STR("")},
{"hook_continue", (PyCFunction)py_mpv_hook_continue, METH_VARARGS,
PyDoc_STR("")},
{"wait_event", (PyCFunction)py_mpv_wait_event, METH_VARARGS,
PyDoc_STR("Listens for mpv_event and returns event_id and event_data")},
{NULL, NULL, 0, NULL} /* Sentinel */

View File

@ -151,13 +151,13 @@ class Ass:
self.text += s
def draw_start(self):
self.text = "%s{\\p%d}" % (self.text, self.scale)
self.text = "%s{\\p%d}" % (self.text, self.scale) # noqa: UP031
def draw_stop(self):
self.text += "{\\p0}"
def pos(self, x, y):
self.append("{\\pos(%f,%f)}" % (x, y))
self.append("{\\pos(%f,%f)}" % (x, y)) # noqa: UP031
def rect_cw(self, x0, y0, x1, y1):
self.move_to(x0, y0)
@ -177,12 +177,12 @@ class Ass:
scale = 2 ** (self.scale - 1)
ix = math.ceil(x * scale)
iy = math.ceil(y * scale)
self.text = "%s %d %d" % (self.text, ix, iy)
self.text = f"{self.text} {ix} {iy}"
class OSDOverlay:
next_assid = 1
def __init__(self, format):
def __init__(self, format): # noqa: A002
self.format = format
OSDOverlay.next_assid += 1
self.id = OSDOverlay.next_assid
@ -196,7 +196,7 @@ class OSDOverlay:
cmd["name"] = "osd-overlay"
cmd["res_x"] = round(cmd["res_x"])
cmd["res_y"] = round(cmd["res_y"])
return mpv.sanitize(mpv.command(cmd))
def remove(self):
@ -205,7 +205,7 @@ class OSDOverlay:
"name": "osd-overlay",
"format": "none",
"id": self.id,
"data": ""
"data": "",
})
except Exception:
return False
@ -241,7 +241,7 @@ class Mpv:
options = Options()
def create_osd_overlay(self, format = "ass-events"):
def create_osd_overlay(self, format = "ass-events"): # noqa: A002
return OSDOverlay(format)
def ass_new(self):
@ -264,9 +264,9 @@ class Mpv:
def decorate(fn):
self.request_event(name)
l = self.event_handlers.get(name, [])
l.append(fn)
self.event_handlers[name] = l
handler_list = self.event_handlers.get(name, [])
handler_list.append(fn)
self.event_handlers[name] = handler_list
return decorate
@ -287,7 +287,7 @@ class Mpv:
name = f"timer{self.next_bid}"
self.periodic_timer_meta[name] = {
"sec": sec,
"disabled": False
"disabled": False,
}
def do(*a, **kw):
func(*a, **kw)
@ -384,8 +384,8 @@ class Mpv:
:param typing.Callable func:
:param tuple[typing.Any] args:
:param dict[str, typing.Any] kwargs:
:param typing.Any default: return value when func fails with exception.
:param int log_level: log level to register the message at when func fails with an exception.
:param typing.Any default: return value when func fails with exception
:param int log_level: log level to register the message at when func fails with an exception
Executes :param:`func` passing in the given :param:`args` and
:param:`kwargs` and returns the :param:`func` 's return value. In case
@ -420,6 +420,10 @@ class Mpv:
elif event_id == self.MPV_EVENT_PROPERTY_CHANGE:
self.notify_observer(event)
elif event_id == self.MPV_EVENT_HOOK:
self.run_hook(data["name"], event["reply_userdata"])
_mpv.hook_continue(self, data["id"])
elif event_id in self.enabled_events:
for cb in self.event_handlers.get(event_id, []):
self.call_catch_ex(cb, event)
@ -458,8 +462,8 @@ class Mpv:
return decorate
def abort_async_command(self, t):
if id := t["id"]:
_mpv.abort_async_command(self, id)
if command_id := t["id"]:
_mpv.abort_async_command(self, command_id)
def find_config_file(self, filename):
return _mpv.find_config_file(self, filename)
@ -472,6 +476,21 @@ class Mpv:
finally:
self.enabled_events.append(name)
hooks: dict = {}
hook_id = 0
def add_hook(self, name, priority):
def decorate(func):
self.hook_id += 1
if name not in self.hooks:
self.hooks[name] = {}
self.hooks[name][self.hook_id] = func
_mpv.hook_add(self, self.hook_id, name, priority)
return decorate
def run_hook(self, hook_name, reply_userdata):
self.call_catch_ex(self.hooks[hook_name][reply_userdata])
def enable_messages(self, level):
return _mpv.enable_messages(self, level)
@ -485,7 +504,7 @@ class Mpv:
return decorate
def unobserve_property(self, id):
def unobserve_property(self, id): # noqa: A002
if id not in self.observe_properties:
self.error(f"Unknown property observer id: {id}")
return