mirror of
https://gitee.com/xia-chu/ZLMediaKit.git
synced 2026-05-20 16:57:48 +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"]
|
[submodule "www/webassist"]
|
||||||
path = www/webassist
|
path = www/webassist
|
||||||
url = https://gitee.com/victor1002/zlm_webassist
|
url = https://gitee.com/victor1002/zlm_webassist
|
||||||
|
[submodule "3rdpart/pybind11"]
|
||||||
|
path = 3rdpart/pybind11
|
||||||
|
url = https://gitee.com/mirrors/pybind11.git
|
||||||
|
|||||||
@ -10,3 +10,6 @@
|
|||||||
[submodule "www/webassist"]
|
[submodule "www/webassist"]
|
||||||
path = 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
|
||||||
@ -121,3 +121,13 @@ add_subdirectory(ZLToolKit)
|
|||||||
add_library(ZLMediaKit::ToolKit ALIAS 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(ENABLE_OBJCOPY "Enable use objcopy to generate debug info file" ON)
|
||||||
# 编译静态库
|
# 编译静态库
|
||||||
option(BUILD_SHARED_LIBS "Build shared instead of static" OFF)
|
option(BUILD_SHARED_LIBS "Build shared instead of static" OFF)
|
||||||
|
option(ENABLE_PYTHON "Enable python plugin" OFF)
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# 设置socket默认缓冲区大小为256k.如果设置为0则不设置socket的默认缓冲区大小,使用系统内核默认值(设置为0仅对linux有效)
|
# 设置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}/www" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/conf/config.ini" 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})
|
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)
|
if (ENABLE_FFMPEG)
|
||||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/DejaVuSans.ttf" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/DejaVuSans.ttf" DESTINATION ${EXECUTABLE_OUTPUT_PATH})
|
||||||
endif ()
|
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 "WebHook.h"
|
||||||
#include "WebApi.h"
|
#include "WebApi.h"
|
||||||
|
|
||||||
|
#if defined(ENABLE_PYTHON)
|
||||||
|
#include "pyinvoker.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Json;
|
using namespace Json;
|
||||||
using namespace toolkit;
|
using namespace toolkit;
|
||||||
@ -358,6 +362,12 @@ void installWebHook() {
|
|||||||
GET_CONFIG(bool, hook_enable, Hook::kEnable);
|
GET_CONFIG(bool, hook_enable, Hook::kEnable);
|
||||||
|
|
||||||
NoticeCenter::Instance().addListener(&web_hook_tag, Broadcast::kBroadcastMediaPublish, [](BroadcastMediaPublishArgs) {
|
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);
|
GET_CONFIG(string, hook_publish, Hook::kOnPublish);
|
||||||
if (!hook_enable || hook_publish.empty()) {
|
if (!hook_enable || hook_publish.empty()) {
|
||||||
invoker("", ProtocolOption());
|
invoker("", ProtocolOption());
|
||||||
|
|||||||
@ -43,6 +43,10 @@
|
|||||||
#include "ZLMVersion.h"
|
#include "ZLMVersion.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(ENABLE_PYTHON)
|
||||||
|
#include "pyinvoker.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "System.h"
|
#include "System.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -107,6 +111,14 @@ onceToken token1([](){
|
|||||||
},nullptr);
|
},nullptr);
|
||||||
} //namespace RtpProxy
|
} //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
|
} // namespace mediakit
|
||||||
|
|
||||||
|
|
||||||
@ -494,6 +506,13 @@ int start_main(int argc,char *argv[]) {
|
|||||||
g_reload_certificates();
|
g_reload_certificates();
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(ENABLE_PYTHON)
|
||||||
|
auto py_plugin = mINI::Instance()[Python::kPlugin];
|
||||||
|
if (!py_plugin.empty()) {
|
||||||
|
PythonInvoker::Instance().load(py_plugin);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
sem.wait();
|
sem.wait();
|
||||||
}
|
}
|
||||||
unInstallWebApi();
|
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