use std::num::NonZeroIsize;
use std::sync::Mutex;
use crate::common::{ChildKillablePointer, KillablePointer};
use crate::filter::RgbaPixel;
use crate::generic::{EditSection, ReadSection};
#[derive(Debug)]
pub struct EditHandle {
pub(crate) internal: *mut aviutl2_sys::plugin2::EDIT_HANDLE,
pub(crate) is_ready: std::sync::Arc<std::sync::atomic::AtomicBool>,
}
unsafe impl Send for EditHandle {}
unsafe impl Sync for EditHandle {}
#[derive(thiserror::Error, Debug)]
pub enum EditHandleError {
#[error("api call failed")]
ApiCallFailed,
#[error("effect does not exist")]
EffectNotFound,
#[error("input utf-16 string contains null byte")]
InputCwstrContainsNull(#[from] crate::common::NullByteError),
#[error("unknown edit state: {0}")]
UnknownEditState(i32),
#[error("value is out of range")]
ValueOutOfRange,
}
#[derive(Debug, Clone, Copy)]
pub struct RenderingSceneVideo<'a> {
pub frame: u32,
pub width: u32,
pub height: u32,
pub pitch: u32,
pub buffer: &'a [u8],
}
impl RenderingSceneVideo<'_> {
pub fn as_rgba_pixels(&self) -> Option<&[RgbaPixel]> {
let expected_pitch = self.width as usize * std::mem::size_of::<RgbaPixel>();
if self.pitch as usize != expected_pitch {
return None;
}
Some(unsafe {
std::slice::from_raw_parts(
self.buffer.as_ptr() as *const RgbaPixel,
self.width as usize * self.height as usize,
)
})
}
}
#[derive(Debug, Clone, Copy)]
pub struct RenderingSceneAudio<'a> {
pub frame: u32,
pub buffer0: &'a [f32],
pub buffer1: &'a [f32],
}
impl EditHandle {
pub(crate) unsafe fn new(
internal: *mut aviutl2_sys::plugin2::EDIT_HANDLE,
is_ready: std::sync::Arc<std::sync::atomic::AtomicBool>,
) -> Self {
Self { internal, is_ready }
}
pub fn is_ready(&self) -> bool {
self.is_ready.load(std::sync::atomic::Ordering::Acquire)
}
pub fn call_edit_section<'a, T, F>(&self, callback: F) -> Result<T, EditHandleError>
where
T: Send + 'static,
F: FnOnce(&mut EditSection) -> T + Send + 'a,
{
assert!(
self.is_ready(),
"call_edit_section cannot be called before register_plugin is done"
);
type CallbackParam<'a, F, T> = (ChildKillablePointer<Option<F>>, &'a mut Option<T>);
let closure = Some(callback);
let param = KillablePointer::new(closure);
let child_param = param.create_child();
extern "C" fn trampoline<F, T>(
param: *mut std::ffi::c_void,
edit_section: *mut aviutl2_sys::plugin2::EDIT_SECTION,
) where
T: Send + 'static,
F: FnOnce(&mut EditSection) -> T,
{
unsafe {
let (child_param, result_ptr) = &mut *(param as *mut CallbackParam<F, T>);
let callback = child_param.as_mut().take().expect("Callback already taken");
let mut edit_section = EditSection::from_raw(edit_section);
let res = callback(&mut edit_section);
result_ptr.replace(res);
}
}
let trampoline_static = trampoline::<F, T>
as extern "C" fn(*mut std::ffi::c_void, *mut aviutl2_sys::plugin2::EDIT_SECTION);
let mut result = None;
let param = Box::<CallbackParam<F, T>>::new((child_param, &mut result));
let param_ptr = Box::into_raw(param);
let success = unsafe {
((*self.internal).call_edit_section_param)(
param_ptr as *mut std::ffi::c_void,
trampoline_static,
)
};
drop(unsafe { Box::from_raw(param_ptr) });
if success {
Ok(result.expect("Callback did not set result"))
} else {
Err(EditHandleError::ApiCallFailed)
}
}
pub fn call_read_section<'a, T, F>(&self, callback: F) -> Result<T, EditHandleError>
where
T: Send + 'static,
F: FnOnce(&ReadSection) -> T + Send + 'a,
{
assert!(
self.is_ready(),
"call_read_section cannot be called before register_plugin is done"
);
type CallbackParam<'a, F, T> = (ChildKillablePointer<Option<F>>, &'a mut Option<T>);
let closure = Some(callback);
let param = KillablePointer::new(closure);
let child_param = param.create_child();
extern "C" fn trampoline<F, T>(
param: *mut std::ffi::c_void,
read_section: *mut aviutl2_sys::plugin2::EDIT_SECTION,
) where
T: Send + 'static,
F: FnOnce(&ReadSection) -> T,
{
unsafe {
let (child_param, result_ptr) = &mut *(param as *mut CallbackParam<F, T>);
let callback = child_param.as_mut().take().expect("Callback already taken");
let read_section = ReadSection::from_raw(read_section);
let res = callback(&read_section);
result_ptr.replace(res);
}
}
let trampoline_static = trampoline::<F, T>
as extern "C" fn(*mut std::ffi::c_void, *mut aviutl2_sys::plugin2::EDIT_SECTION);
let mut result = None;
let param = Box::<CallbackParam<F, T>>::new((child_param, &mut result));
let param_ptr = Box::into_raw(param);
let success = unsafe {
((*self.internal).call_read_section_param)(
param_ptr as *mut std::ffi::c_void,
trampoline_static,
)
};
drop(unsafe { Box::from_raw(param_ptr) });
if success {
Ok(result.expect("Callback did not set result"))
} else {
Err(EditHandleError::ApiCallFailed)
}
}
pub fn get_edit_info(&self) -> crate::generic::EditInfo {
assert!(
self.is_ready(),
"get_edit_info cannot be called before register_plugin is done"
);
let mut raw_info = std::mem::MaybeUninit::<aviutl2_sys::plugin2::EDIT_INFO>::uninit();
unsafe {
((*self.internal).get_edit_info)(
raw_info.as_mut_ptr(),
std::mem::size_of::<aviutl2_sys::plugin2::EDIT_INFO>() as _,
);
let edit_info = raw_info.assume_init();
crate::generic::EditInfo::from_raw(&edit_info)
}
}
pub fn restart_host_app(&self) {
assert!(
self.is_ready(),
"restart_host_app cannot be called before register_plugin is done"
);
unsafe {
((*self.internal).restart_host_app)();
}
}
pub fn enumerate_effects<F>(&self, callback: F)
where
F: FnMut(Effect),
{
assert!(
self.is_ready(),
"enumerate_effects cannot be called before register_plugin is done"
);
type CallbackParam<F> = ChildKillablePointer<F>;
extern "C" fn trampoline<F>(
param: *mut std::ffi::c_void,
name: aviutl2_sys::common::LPCWSTR,
r#type: i32,
flag: i32,
) where
F: FnMut(Effect),
{
let callback = unsafe { &mut *(param as *mut CallbackParam<F>) };
let callback = unsafe { callback.as_mut() };
let name_str = unsafe { crate::common::load_wide_string(name) };
if let Ok(effect_type) = EffectType::try_from(r#type) {
let effect = Effect {
name: name_str,
effect_type,
flag: EffectFlag::from_bits(flag),
};
callback(effect);
} else {
tracing::warn!("Unknown effect type: {}", r#type);
}
}
let trampoline_static = trampoline::<F>
as extern "C" fn(*mut std::ffi::c_void, aviutl2_sys::common::LPCWSTR, i32, i32);
let callback_guard = KillablePointer::new(callback);
let child_param = callback_guard.create_child();
let param = Box::new(child_param);
let param_ptr = Box::into_raw(param);
unsafe {
((*self.internal).enum_effect_name)(
param_ptr as *mut std::ffi::c_void,
trampoline_static,
);
}
drop(unsafe { Box::from_raw(param_ptr) });
}
pub fn get_effects(&self) -> Vec<Effect> {
assert!(
self.is_ready(),
"get_effects cannot be called before register_plugin is done"
);
let mut effects = Vec::new();
self.enumerate_effects(|effect| {
effects.push(effect);
});
effects
}
pub fn enumerate_effect_items<F>(
&self,
effect: &str,
callback: F,
) -> Result<(), EditHandleError>
where
F: FnMut(EffectItemInfo),
{
assert!(
self.is_ready(),
"enumerate_effect_items cannot be called before register_plugin is done"
);
type CallbackParam<F> = ChildKillablePointer<F>;
unsafe extern "C" fn trampoline<F>(
param: *mut std::ffi::c_void,
name: aviutl2_sys::common::LPCWSTR,
r#type: i32,
) where
F: FnMut(EffectItemInfo),
{
let callback = unsafe { &mut *(param as *mut CallbackParam<F>) };
let callback = unsafe { callback.as_mut() };
let name = unsafe { crate::common::load_wide_string(name) };
if let Some(info) = effect_item_info_from_raw(name, r#type) {
callback(info);
}
}
let effect = crate::common::CWString::new(effect)?;
let trampoline_static = trampoline::<F>
as unsafe extern "C" fn(*mut std::ffi::c_void, aviutl2_sys::common::LPCWSTR, i32);
let callback_guard = KillablePointer::new(callback);
let child_param = callback_guard.create_child();
let param = Box::new(child_param);
let param_ptr = Box::into_raw(param);
let success = unsafe {
((*self.internal).enum_effect_item)(
effect.as_ptr(),
param_ptr as *mut std::ffi::c_void,
trampoline_static,
)
};
drop(unsafe { Box::from_raw(param_ptr) });
if success {
Ok(())
} else {
Err(EditHandleError::EffectNotFound)
}
}
pub fn get_effect_items(&self, effect: &str) -> Result<Vec<EffectItemInfo>, EditHandleError> {
assert!(
self.is_ready(),
"get_effect_items cannot be called before register_plugin is done"
);
let mut items = Vec::new();
self.enumerate_effect_items(effect, |item| {
items.push(item);
})?;
Ok(items)
}
pub fn enumerate_modules<F>(&self, callback: F)
where
F: FnMut(ModuleInfo),
{
assert!(
self.is_ready(),
"enumerate_modules cannot be called before register_plugin is done"
);
type CallbackParam<F> = ChildKillablePointer<F>;
extern "C" fn trampoline<F>(
param: *mut std::ffi::c_void,
module: *mut aviutl2_sys::plugin2::MODULE_INFO,
) where
F: FnMut(ModuleInfo),
{
let callback = unsafe { &mut *(param as *mut CallbackParam<F>) };
let callback = unsafe { callback.as_mut() };
if let Some(module_info) = module_info_from_raw(module) {
callback(module_info);
}
}
let trampoline_static = trampoline::<F>
as unsafe extern "C" fn(*mut std::ffi::c_void, *mut aviutl2_sys::plugin2::MODULE_INFO);
let callback_guard = KillablePointer::new(callback);
let child_param = callback_guard.create_child();
let param = Box::new(child_param);
let param_ptr = Box::into_raw(param);
unsafe {
((*self.internal).enum_module_info)(
param_ptr as *mut std::ffi::c_void,
trampoline_static,
);
}
drop(unsafe { Box::from_raw(param_ptr) });
}
pub fn get_modules(&self) -> Vec<ModuleInfo> {
assert!(
self.is_ready(),
"get_modules cannot be called before register_plugin is done"
);
let mut modules = Vec::new();
self.enumerate_modules(|module| {
modules.push(module);
});
modules
}
pub fn get_host_app_window_raw(&self) -> Option<raw_window_handle::Win32WindowHandle> {
let hwnd = unsafe { ((*self.internal).get_host_app_window)() };
NonZeroIsize::new(hwnd as isize).map(raw_window_handle::Win32WindowHandle::new)
}
pub unsafe fn get_host_app_window(&'_ self) -> Option<raw_window_handle::WindowHandle<'_>> {
self.get_host_app_window_raw().map(|handle| unsafe {
raw_window_handle::WindowHandle::borrow_raw(raw_window_handle::RawWindowHandle::Win32(
handle,
))
})
}
pub fn get_edit_state(&self) -> Result<EditState, EditHandleError> {
assert!(
self.is_ready(),
"get_edit_state cannot be called before register_plugin is done"
);
let state = unsafe { ((*self.internal).get_edit_state)() };
EditState::try_from(state).map_err(|_| EditHandleError::UnknownEditState(state))
}
pub fn rendering_scene_video<F>(&self, frame: u32, callback: F) -> Result<(), EditHandleError>
where
F: FnMut(RenderingSceneVideo<'_>) + Send + 'static,
{
assert!(
self.is_ready(),
"rendering_scene_video cannot be called before register_plugin is done"
);
type CallbackParam<F> = Mutex<Option<F>>;
unsafe extern "C" fn trampoline<F>(
param: *mut std::ffi::c_void,
frame: i32,
buffer: *const std::ffi::c_void,
width: i32,
height: i32,
pitch: i32,
) where
F: FnMut(RenderingSceneVideo<'_>),
{
let callback = unsafe { Box::from_raw(param as *mut CallbackParam<F>) };
let len = usize::try_from(pitch)
.ok()
.and_then(|pitch| {
usize::try_from(height)
.ok()
.and_then(|height| pitch.checked_mul(height))
})
.unwrap_or(0);
let buffer = if buffer.is_null() || len == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(buffer as *const u8, len) }
};
let video = RenderingSceneVideo {
frame: frame as u32,
width: width as u32,
height: height as u32,
pitch: pitch as u32,
buffer,
};
let mut callback = callback
.lock()
.unwrap()
.take()
.expect("Callback already taken");
callback(video);
}
let frame = i32::try_from(frame).map_err(|_| EditHandleError::ValueOutOfRange)?;
let param = Box::into_raw(Box::new(Mutex::new(Some(callback))));
let success = unsafe {
((*self.internal).rendering_scene_video)(
frame,
param as *mut std::ffi::c_void,
trampoline::<F>,
)
};
if success {
Ok(())
} else {
unsafe {
drop(Box::from_raw(param));
}
Err(EditHandleError::ApiCallFailed)
}
}
pub fn rendering_scene_audio<F>(&self, frame: u32, callback: F) -> Result<(), EditHandleError>
where
F: FnMut(RenderingSceneAudio<'_>) + Send + 'static,
{
assert!(
self.is_ready(),
"rendering_scene_audio cannot be called before register_plugin is done"
);
type CallbackParam<F> = Mutex<Option<F>>;
unsafe extern "C" fn trampoline<F>(
param: *mut std::ffi::c_void,
frame: i32,
buffer0: *const f32,
buffer1: *const f32,
sample_num: i32,
) where
F: FnMut(RenderingSceneAudio<'_>),
{
let callback = unsafe { Box::from_raw(param as *mut CallbackParam<F>) };
let len = usize::try_from(sample_num).unwrap_or(0);
let buffer0 = if buffer0.is_null() || len == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(buffer0, len) }
};
let buffer1 = if buffer1.is_null() || len == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(buffer1, len) }
};
let audio = RenderingSceneAudio {
frame: frame as u32,
buffer0,
buffer1,
};
let mut callback = callback
.lock()
.unwrap()
.take()
.expect("Callback already taken");
callback(audio);
}
let frame = i32::try_from(frame).map_err(|_| EditHandleError::ValueOutOfRange)?;
let param = Box::into_raw(Box::new(Mutex::new(Some(callback))));
let success = unsafe {
((*self.internal).rendering_scene_audio)(
frame,
param as *mut std::ffi::c_void,
trampoline::<F>,
)
};
if success {
Ok(())
} else {
unsafe {
drop(Box::from_raw(param));
}
Err(EditHandleError::ApiCallFailed)
}
}
pub fn wait_rendering_task(&self) {
assert!(
self.is_ready(),
"wait_rendering_task cannot be called before register_plugin is done"
);
unsafe {
((*self.internal).wait_rendering_task)();
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Effect {
pub name: String,
pub effect_type: EffectType,
pub flag: EffectFlag,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EffectItemInfo {
pub name: String,
pub item_type: EffectItemType,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EffectType {
Filter,
Input,
SceneChange,
Control,
Output,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EffectItemType {
Integer,
Number,
Check,
Text,
String,
File,
Color,
Select,
Scene,
Range,
Combo,
Mask,
Font,
Figure,
Data,
Folder,
}
define_bitflag! {
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct EffectFlag: i32 {
video: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_VIDEO,
audio: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_AUDIO,
as_filter: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_FILTER,
camera: aviutl2_sys::plugin2::EDIT_HANDLE::EFFECT_FLAG_CAMERA,
}
}
impl TryFrom<i32> for EffectType {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
1 => Ok(EffectType::Filter),
2 => Ok(EffectType::Input),
3 => Ok(EffectType::SceneChange),
4 => Ok(EffectType::Control),
5 => Ok(EffectType::Output),
_ => Err(()),
}
}
}
impl From<EffectType> for i32 {
fn from(value: EffectType) -> Self {
match value {
EffectType::Filter => 1,
EffectType::Input => 2,
EffectType::SceneChange => 3,
EffectType::Control => 4,
EffectType::Output => 5,
}
}
}
impl TryFrom<i32> for EffectItemType {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
1 => Ok(EffectItemType::Integer),
2 => Ok(EffectItemType::Number),
3 => Ok(EffectItemType::Check),
4 => Ok(EffectItemType::Text),
5 => Ok(EffectItemType::String),
6 => Ok(EffectItemType::File),
7 => Ok(EffectItemType::Color),
8 => Ok(EffectItemType::Select),
9 => Ok(EffectItemType::Scene),
10 => Ok(EffectItemType::Range),
11 => Ok(EffectItemType::Combo),
12 => Ok(EffectItemType::Mask),
13 => Ok(EffectItemType::Font),
14 => Ok(EffectItemType::Figure),
15 => Ok(EffectItemType::Data),
16 => Ok(EffectItemType::Folder),
_ => Err(()),
}
}
}
impl From<EffectItemType> for i32 {
fn from(value: EffectItemType) -> Self {
match value {
EffectItemType::Integer => 1,
EffectItemType::Number => 2,
EffectItemType::Check => 3,
EffectItemType::Text => 4,
EffectItemType::String => 5,
EffectItemType::File => 6,
EffectItemType::Color => 7,
EffectItemType::Select => 8,
EffectItemType::Scene => 9,
EffectItemType::Range => 10,
EffectItemType::Combo => 11,
EffectItemType::Mask => 12,
EffectItemType::Font => 13,
EffectItemType::Figure => 14,
EffectItemType::Data => 15,
EffectItemType::Folder => 16,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModuleInfo {
pub module_type: ModuleType,
pub name: String,
pub information: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModuleType {
ScriptFilter,
ScriptObject,
ScriptCamera,
ScriptTrack,
ScriptModule,
PluginInput,
PluginOutput,
PluginFilter,
PluginGeneric,
}
impl TryFrom<i32> for ModuleType {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
1 => Ok(ModuleType::ScriptFilter),
2 => Ok(ModuleType::ScriptObject),
3 => Ok(ModuleType::ScriptCamera),
4 => Ok(ModuleType::ScriptTrack),
5 => Ok(ModuleType::ScriptModule),
6 => Ok(ModuleType::PluginInput),
7 => Ok(ModuleType::PluginOutput),
8 => Ok(ModuleType::PluginFilter),
9 => Ok(ModuleType::PluginGeneric),
_ => Err(()),
}
}
}
impl From<ModuleType> for i32 {
fn from(value: ModuleType) -> Self {
match value {
ModuleType::ScriptFilter => 1,
ModuleType::ScriptObject => 2,
ModuleType::ScriptCamera => 3,
ModuleType::ScriptTrack => 4,
ModuleType::ScriptModule => 5,
ModuleType::PluginInput => 6,
ModuleType::PluginOutput => 7,
ModuleType::PluginFilter => 8,
ModuleType::PluginGeneric => 9,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EditState {
Edit,
Preview,
Save,
}
impl TryFrom<i32> for EditState {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(EditState::Edit),
1 => Ok(EditState::Preview),
2 => Ok(EditState::Save),
_ => Err(()),
}
}
}
impl From<EditState> for i32 {
fn from(value: EditState) -> Self {
match value {
EditState::Edit => 0,
EditState::Preview => 1,
EditState::Save => 2,
}
}
}
#[derive(Debug)]
pub struct GlobalEditHandle {
edit_handle: std::sync::OnceLock<crate::generic::EditHandle>,
}
impl GlobalEditHandle {
pub const fn new() -> Self {
Self {
edit_handle: std::sync::OnceLock::new(),
}
}
pub fn init(&self, edit_handle: crate::generic::EditHandle) {
let _ = self
.edit_handle
.set(edit_handle)
.map_err(|_| tracing::warn!("GlobalEditHandle was already initialized"));
}
pub fn is_ready(&self) -> bool {
self.edit_handle
.get()
.is_some_and(|handle| handle.is_ready())
}
}
impl Default for GlobalEditHandle {
fn default() -> Self {
Self::new()
}
}
impl std::ops::Deref for GlobalEditHandle {
type Target = crate::generic::EditHandle;
fn deref(&self) -> &Self::Target {
self.edit_handle
.get()
.expect("GlobalEditHandle is not initialized")
}
}
fn effect_item_info_from_raw(name: String, item_type: i32) -> Option<EffectItemInfo> {
if let Ok(item_type) = EffectItemType::try_from(item_type) {
Some(EffectItemInfo { name, item_type })
} else {
tracing::warn!("Unknown effect item type: {}", item_type);
None
}
}
fn module_info_from_raw(raw: *mut aviutl2_sys::plugin2::MODULE_INFO) -> Option<ModuleInfo> {
let module_type = unsafe { (*raw).r#type };
if let Ok(module_type) = ModuleType::try_from(module_type) {
Some(ModuleInfo {
module_type,
name: unsafe { crate::common::load_wide_string((*raw).name) },
information: unsafe { crate::common::load_wide_string((*raw).information) },
})
} else {
tracing::warn!("Unknown module type: {}", module_type);
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn effect_item_type_try_from_known_values() {
assert_eq!(EffectItemType::try_from(1), Ok(EffectItemType::Integer));
assert_eq!(EffectItemType::try_from(8), Ok(EffectItemType::Select));
assert_eq!(EffectItemType::try_from(16), Ok(EffectItemType::Folder));
}
#[test]
fn effect_item_type_try_from_unknown_value_fails() {
assert_eq!(EffectItemType::try_from(999), Err(()));
}
#[test]
fn effect_item_type_into_i32() {
assert_eq!(i32::from(EffectItemType::Integer), 1);
assert_eq!(i32::from(EffectItemType::Combo), 11);
assert_eq!(i32::from(EffectItemType::Folder), 16);
}
#[test]
fn effect_item_info_from_raw_returns_none_for_unknown_type() {
assert_eq!(effect_item_info_from_raw("test".to_string(), 999), None);
}
#[test]
fn effect_item_info_from_raw_builds_info_for_known_type() {
assert_eq!(
effect_item_info_from_raw("test".to_string(), 4),
Some(EffectItemInfo {
name: "test".to_string(),
item_type: EffectItemType::Text,
})
);
}
#[test]
fn module_type_try_from_unknown_value_fails() {
assert_eq!(ModuleType::try_from(999), Err(()));
}
#[test]
fn edit_state_try_from_unknown_value_fails() {
assert_eq!(EditState::try_from(999), Err(()));
}
}