mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-05-06 19:07:49 +08:00
添加python插件
This commit is contained in:
parent
a54a0b35c7
commit
9420f25b73
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -10,3 +10,6 @@
|
||||
[submodule "www/webassist"]
|
||||
path = www/webassist
|
||||
url = https://gitee.com/victor1002/zlm_webassist
|
||||
[submodule "3rdpart/pybind11"]
|
||||
path = 3rdpart/pybind11
|
||||
url = https://gitee.com/mirrors/pybind11.git
|
||||
|
||||
@ -9,4 +9,7 @@
|
||||
url = https://github.com/open-source-parsers/jsoncpp.git
|
||||
[submodule "www/webassist"]
|
||||
path = www/webassist
|
||||
url = https://github.com/1002victor/zlm_webassist
|
||||
url = https://github.com/1002victor/zlm_webassist
|
||||
[submodule "3rdpart/pybind11"]
|
||||
path = 3rdpart/pybind11
|
||||
url = https://github.com/pybind/pybind11.git
|
||||
@ -120,4 +120,14 @@ add_subdirectory(ZLToolKit)
|
||||
# 添加库别名
|
||||
add_library(ZLMediaKit::ToolKit ALIAS ZLToolKit)
|
||||
# 添加依赖
|
||||
update_cached_list(MK_LINK_LIBRARIES ZLMediaKit::ToolKit)
|
||||
update_cached_list(MK_LINK_LIBRARIES ZLMediaKit::ToolKit)
|
||||
|
||||
##############################################################################
|
||||
|
||||
if (ENABLE_PYTHON)
|
||||
# ============ pybind11 lib ============
|
||||
add_subdirectory(pybind11)
|
||||
update_cached_list(MK_LINK_LIBRARIES pybind11::embed)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/pybind11/include)
|
||||
update_cached_list(MK_COMPILE_DEFINITIONS ENABLE_PYTHON)
|
||||
endif ()
|
||||
1
3rdpart/pybind11
Submodule
1
3rdpart/pybind11
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ed5057ded698e305210269dafa57574ecf964483
|
||||
@ -65,6 +65,7 @@ option(USE_SOLUTION_FOLDERS "Enable solution dir supported" ON)
|
||||
option(ENABLE_OBJCOPY "Enable use objcopy to generate debug info file" ON)
|
||||
# 编译静态库
|
||||
option(BUILD_SHARED_LIBS "Build shared instead of static" OFF)
|
||||
option(ENABLE_PYTHON "Enable python plugin" OFF)
|
||||
|
||||
##############################################################################
|
||||
# 设置socket默认缓冲区大小为256k.如果设置为0则不设置socket的默认缓冲区大小,使用系统内核默认值(设置为0仅对linux有效)
|
||||
@ -603,6 +604,10 @@ endif ()
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/www" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf/config.ini" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/default.pem" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
if (ENABLE_PYTHON)
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/python" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
endif ()
|
||||
|
||||
if (ENABLE_FFMPEG)
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/DejaVuSans.ttf" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||
endif ()
|
||||
|
||||
27
python/mk_logger.py
Normal file
27
python/mk_logger.py
Normal file
@ -0,0 +1,27 @@
|
||||
import inspect
|
||||
|
||||
try:
|
||||
import mk_loader
|
||||
USE_PLUGIN_LOGGER = True
|
||||
except ImportError:
|
||||
USE_PLUGIN_LOGGER = False
|
||||
|
||||
def _do_log(level: int, *args):
|
||||
frame_info = inspect.stack()[2]
|
||||
filename = frame_info.filename
|
||||
lineno = frame_info.lineno
|
||||
funcname = frame_info.function
|
||||
|
||||
# 把所有参数转成字符串后用空格拼接
|
||||
msg = " ".join(str(arg) for arg in args)
|
||||
|
||||
if USE_PLUGIN_LOGGER:
|
||||
mk_loader.log(level, filename, lineno, funcname, msg)
|
||||
else:
|
||||
print(f"[{filename}:{lineno}] {funcname} | {msg}")
|
||||
|
||||
def log_trace(*args): _do_log(0, *args)
|
||||
def log_debug(*args): _do_log(1, *args)
|
||||
def log_info(*args): _do_log(2, *args)
|
||||
def log_warn(*args): _do_log(3, *args)
|
||||
def log_error(*args): _do_log(4, *args)
|
||||
21
python/mk_plugin.py
Normal file
21
python/mk_plugin.py
Normal file
@ -0,0 +1,21 @@
|
||||
import mk_logger
|
||||
import mk_loader
|
||||
|
||||
def on_start():
|
||||
mk_logger.log_info("on_start")
|
||||
|
||||
|
||||
def on_exit():
|
||||
mk_logger.log_info("on_exit")
|
||||
|
||||
|
||||
def on_publish(type: str, info, invoker, sender) -> bool:
|
||||
mk_logger.log_info(f"on_publish: {type}")
|
||||
# opt 控制转协议,请参考配置文件[protocol]下字段
|
||||
opt = {
|
||||
"enable_rtmp": "1"
|
||||
}
|
||||
# 响应推流鉴权结果
|
||||
mk_loader.mk_publish_auth_invoker_do(invoker, "", opt);
|
||||
# 返回True代表此事件被python拦截
|
||||
return True
|
||||
@ -21,6 +21,10 @@
|
||||
#include "WebHook.h"
|
||||
#include "WebApi.h"
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
#include "pyinvoker.h"
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace Json;
|
||||
using namespace toolkit;
|
||||
@ -358,6 +362,12 @@ void installWebHook() {
|
||||
GET_CONFIG(bool, hook_enable, Hook::kEnable);
|
||||
|
||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
|
||||
#if defined(ENABLE_PYTHON)
|
||||
if (PythonInvoker::Instance().on_publish(type, args, invoker, sender)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
GET_CONFIG(string, hook_publish, Hook::kOnPublish);
|
||||
if (!hook_enable || hook_publish.empty()) {
|
||||
invoker("", ProtocolOption());
|
||||
|
||||
@ -43,6 +43,10 @@
|
||||
#include "ZLMVersion.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
#include "pyinvoker.h"
|
||||
#endif
|
||||
|
||||
#include "System.h"
|
||||
|
||||
using namespace std;
|
||||
@ -107,6 +111,14 @@ onceToken token1([](){
|
||||
},nullptr);
|
||||
} //namespace RtpProxy
|
||||
|
||||
namespace Python {
|
||||
#define Python_FIELD "python."
|
||||
const string kPlugin = Python_FIELD"plugin";
|
||||
onceToken token1([](){
|
||||
mINI::Instance()[kPlugin] = "mk_plugin";
|
||||
},nullptr);
|
||||
} //namespace RtpProxy
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
|
||||
@ -494,6 +506,13 @@ int start_main(int argc,char *argv[]) {
|
||||
g_reload_certificates();
|
||||
});
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
auto py_plugin = mINI::Instance()[Python::kPlugin];
|
||||
if (!py_plugin.empty()) {
|
||||
PythonInvoker::Instance().load(py_plugin);
|
||||
}
|
||||
#endif
|
||||
sem.wait();
|
||||
}
|
||||
unInstallWebApi();
|
||||
|
||||
155
server/pyinvoker.cpp
Normal file
155
server/pyinvoker.cpp
Normal file
@ -0,0 +1,155 @@
|
||||
#if defined(ENABLE_PYTHON)
|
||||
|
||||
#include "pyinvoker.h"
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
using namespace toolkit;
|
||||
using namespace mediakit;
|
||||
|
||||
template <typename T>
|
||||
auto to_python(const T &obj) -> typename std::enable_if<std::is_copy_constructible<T>::value, py::capsule>::type {
|
||||
static auto name_str = toolkit::demangle(typeid(T).name());
|
||||
auto p = new toolkit::Any(std::make_shared<T>(obj));
|
||||
return py::capsule(p, name_str.data(), [](PyObject *capsule) {
|
||||
auto p = reinterpret_cast<toolkit::Any *>(PyCapsule_GetPointer(capsule, name_str.data()));
|
||||
delete p;
|
||||
TraceL << "delete " << name_str << "(" << p << ")";
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto to_python(const T &obj) -> typename std::enable_if<!std::is_copy_constructible<T>::value, py::capsule>::type {
|
||||
static auto name_str = toolkit::demangle(typeid(T).name());
|
||||
auto p = new toolkit::Any(std::shared_ptr<T>(const_cast<T *>(&obj), [](T *) {}));
|
||||
return py::capsule(p, name_str.data(), [](PyObject *capsule) {
|
||||
auto p = reinterpret_cast<toolkit::Any *>(PyCapsule_GetPointer(capsule, name_str.data()));
|
||||
delete p;
|
||||
TraceL << "unref " << name_str << "(" << p << ")";
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T &to_native(const py::capsule &cap) {
|
||||
static auto name_str = toolkit::demangle(typeid(T).name());
|
||||
if (std::string(cap.name()) != name_str) {
|
||||
throw std::runtime_error("Invalid capsule name!");
|
||||
}
|
||||
auto any = reinterpret_cast<toolkit::Any *>(cap.get_pointer());
|
||||
return any->get<T>();
|
||||
}
|
||||
|
||||
mINI to_native(const py::dict &opt) {
|
||||
mINI ret;
|
||||
for (auto &item : opt) {
|
||||
// 转换为字符串(允许 int/float/bool 等)
|
||||
ret.emplace(py::str(item.first).cast<std::string>(), py::str(item.second).cast<std::string>());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
PYBIND11_EMBEDDED_MODULE(mk_loader, m) {
|
||||
m.def("log", [](int lev, const char *file, int line, const char *func, const char *content) {
|
||||
py::gil_scoped_release release;
|
||||
LoggerWrapper::printLog(::toolkit::getLogger(), lev, file, func, line, content);
|
||||
});
|
||||
m.def("mk_publish_auth_invoker_do", [](const py::capsule &cap, const std::string &err, const py::dict &opt) {
|
||||
ProtocolOption option;
|
||||
option.load(to_native(opt));
|
||||
// 执行c++代码时释放gil锁
|
||||
py::gil_scoped_release release;
|
||||
auto &invoker = to_native<Broadcast::PublishAuthInvoker>(cap);
|
||||
invoker(err, option);
|
||||
});
|
||||
}
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
inline bool set_env(const char *name, const char *value) {
|
||||
#if defined(_WIN32)
|
||||
std::string env_str = std::string(name) + "=" + value;
|
||||
return _putenv(env_str.c_str()) == 0;
|
||||
#else
|
||||
return setenv(name, value, 1) == 0; // overwrite = 1
|
||||
#endif
|
||||
}
|
||||
|
||||
bool set_python_path() {
|
||||
const char *env_var = std::getenv("PYTHONPATH");
|
||||
if (env_var && *env_var) {
|
||||
PrintI("PYTHONPATH is already set to: %s", env_var);
|
||||
return false;
|
||||
}
|
||||
auto default_path = exeDir() + "/python";
|
||||
// 1 表示覆盖已存在的值
|
||||
if (!set_env("PYTHONPATH", default_path.data())) {
|
||||
PrintW("Failed to set PYTHONPATH");
|
||||
return false;
|
||||
}
|
||||
PrintI("PYTHONPATH was not set. Set to default: %s", default_path.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
PythonInvoker &PythonInvoker::Instance() {
|
||||
static std::shared_ptr<PythonInvoker> instance(new PythonInvoker);
|
||||
return *instance;
|
||||
}
|
||||
|
||||
PythonInvoker::PythonInvoker() {
|
||||
// 确保日志一直可用
|
||||
_logger = Logger::Instance().shared_from_this();
|
||||
set_python_path(); // 确保 PYTHONPATH 在第一次调用时设置
|
||||
_interpreter = new py::scoped_interpreter;
|
||||
_rel = new py::gil_scoped_release;
|
||||
}
|
||||
|
||||
PythonInvoker::~PythonInvoker() {
|
||||
{
|
||||
py::gil_scoped_acquire gil; // 加锁
|
||||
if (_on_exit) {
|
||||
_on_exit();
|
||||
}
|
||||
_on_exit = py::object();
|
||||
_on_publish = py::object();
|
||||
_module = py::module();
|
||||
}
|
||||
|
||||
delete _rel;
|
||||
delete _interpreter;
|
||||
}
|
||||
|
||||
void PythonInvoker::load(const std::string &module_name) {
|
||||
try {
|
||||
py::gil_scoped_acquire gil; // 加锁
|
||||
_module = py::module::import(module_name.c_str());
|
||||
if (hasattr(_module, "on_start")) {
|
||||
py::object on_start = _module.attr("on_start");
|
||||
if (on_start) {
|
||||
on_start();
|
||||
}
|
||||
}
|
||||
if (hasattr(_module, "on_exit")) {
|
||||
_on_exit = _module.attr("on_exit");
|
||||
}
|
||||
if (hasattr(_module, "on_publish")) {
|
||||
_on_publish = _module.attr("on_publish");
|
||||
}
|
||||
} catch (py::error_already_set &e) {
|
||||
PrintE("Python exception:%s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool PythonInvoker::on_publish(BroadcastMediaPublishArgs) {
|
||||
py::gil_scoped_acquire gil; // 确保在 Python 调用期间持有 GIL
|
||||
if (!_on_publish) {
|
||||
return false;
|
||||
}
|
||||
return _on_publish(getOriginTypeString(type), to_python(args), to_python(invoker), to_python(sender)).cast<bool>();
|
||||
}
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
#endif
|
||||
46
server/pyinvoker.h
Normal file
46
server/pyinvoker.h
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
#ifndef PYINVOKER_H
|
||||
#define PYINVOKER_H
|
||||
|
||||
#if defined(ENABLE_PYTHON)
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <pybind11/embed.h>
|
||||
#include <pybind11/numpy.h>
|
||||
#include "Util/logger.h"
|
||||
#include "Common/config.h"
|
||||
#include "Common/MediaSource.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace mediakit {
|
||||
|
||||
class PythonInvoker : public std::enable_shared_from_this<PythonInvoker>{
|
||||
public:
|
||||
~PythonInvoker();
|
||||
|
||||
static PythonInvoker& Instance();
|
||||
|
||||
void load(const std::string &module_name);
|
||||
bool on_publish(BroadcastMediaPublishArgs);
|
||||
|
||||
private:
|
||||
PythonInvoker();
|
||||
|
||||
private:
|
||||
py::gil_scoped_release *_rel;
|
||||
py::scoped_interpreter *_interpreter;
|
||||
std::shared_ptr<toolkit::Logger> _logger;
|
||||
py::module _module;
|
||||
|
||||
// 程序退出
|
||||
py::object _on_exit;
|
||||
// 推流鉴权
|
||||
py::object _on_publish;
|
||||
};
|
||||
|
||||
} // namespace mediakit
|
||||
|
||||
#endif
|
||||
#endif // PYINVOKER_H
|
||||
Loading…
Reference in New Issue
Block a user