pub use anyhow::Result as AnyResult;
use zerocopy::{Immutable, IntoBytes};
pub use half::{self, f16};
pub use num_rational::{self, Rational32};
pub use raw_window_handle::{self, Win32WindowHandle};
#[derive(Debug, Clone)]
pub struct AviUtl2Info {
pub version: AviUtl2Version,
}
pub const MINIMUM_AVIUTL2_VERSION: AviUtl2Version = AviUtl2Version(2010500);
pub fn ensure_minimum_aviutl2_version(version: AviUtl2Version) -> AnyResult<()> {
anyhow::ensure!(
version >= MINIMUM_AVIUTL2_VERSION,
"AviUtl2 version {version} is not supported. {MINIMUM_AVIUTL2_VERSION} or higher is required.",
);
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct AviUtl2Version(pub u32);
impl From<u32> for AviUtl2Version {
fn from(value: u32) -> Self {
Self(value)
}
}
impl From<AviUtl2Version> for u32 {
fn from(value: AviUtl2Version) -> Self {
value.0
}
}
impl AviUtl2Version {
pub fn new(major: u8, minor: u8, patch: u8, build: u8) -> Self {
assert!(minor < 100, "minor version must be less than 100");
assert!(patch < 100, "patch version must be less than 100");
assert!(build < 100, "build version must be less than 100");
Self(
(major as u32) * 1_000_000
+ (minor as u32) * 10_000
+ (patch as u32) * 100
+ (build as u32),
)
}
pub fn major(self) -> u32 {
self.0 / 1000000
}
pub fn minor(self) -> u32 {
(self.0 / 10000) % 100
}
pub fn patch(self) -> u32 {
(self.0 / 100) % 100
}
pub fn build(self) -> u32 {
self.0 % 100
}
}
impl std::fmt::Display for AviUtl2Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0 <= 2005301 {
write!(
f,
"{}.{:0>2}beta{}{}",
self.major(),
self.minor(),
self.patch(),
int_to_a_to_z(self.build() as u8).unwrap_or_default()
)
} else {
write!(
f,
"v{}.{}.{}{}",
self.major(),
self.minor(),
self.patch(),
int_to_a_to_z(self.build() as u8).unwrap_or_default()
)
}
}
}
fn int_to_a_to_z(n: u8) -> Option<String> {
let mut n = n;
if n == 0 {
return None;
}
let mut result = String::new();
while n > 0 {
n -= 1;
result.insert(0, (b'a' + (n % 26)) as char);
n /= 26;
}
Some(result)
}
#[cfg(feature = "aviutl2-alias")]
impl aviutl2_alias::FromTableValue for AviUtl2Version {
type Err = std::num::ParseIntError;
fn from_table_value(value: &str) -> Result<Self, Self::Err> {
let v: u32 = value.parse()?;
Ok(Self::from(v))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileFilter {
pub name: String,
pub extensions: Vec<String>,
}
#[macro_export]
macro_rules! file_filters {
($($name:expr => [$($ext:expr),* $(,)?] ),* $(,)?) => {
vec![
$(
$crate::FileFilter {
name: $name.to_string(),
extensions: vec![$($ext.to_string()),*],
}
),*
]
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoBytes, Immutable)]
#[repr(C)]
pub struct Yc48 {
pub y: i16,
pub cb: i16,
pub cr: i16,
}
impl Yc48 {
pub fn from_yuy2(yuy2: (u8, u8, u8, u8)) -> (Self, Self) {
let (y1, u, y2, v) = yuy2;
let ny1 = ((y1 as u16 * 1197) >> 6) - 299;
let ny2 = ((y2 as u16 * 1197) >> 6) - 299;
let ncb = ((u as u16 - 128) * 4681 + 164) >> 8;
let ncr = ((v as u16 - 128) * 4681 + 164) >> 8;
(
Self {
y: ny1 as i16,
cb: ncb as i16,
cr: ncr as i16,
},
Self {
y: ((ny1 + ny2) >> 1) as i16,
cb: ncb as i16,
cr: ncr as i16,
},
)
}
pub fn to_yuy2(self, other: Yc48) -> (u8, u8, u8, u8) {
let y1 = ((self.y * 219 + 383) >> 12) + 16;
let y2 = ((other.y * 219 + 383) >> 12) + 16;
let u = (((self.cb + 2048) * 7 + 66) >> 7) + 16;
let v = (((self.cr + 2048) * 7 + 66) >> 7) + 16;
let y1 = y1.min(255) as u8;
let y2 = y2.min(255) as u8;
let u = u.min(255) as u8;
let v = v.min(255) as u8;
(y1, u, y2, v)
}
pub fn from_rgb(self, rgb: (u8, u8, u8)) -> Self {
let (r, g, b) = rgb;
let r = i16::from(r);
let g = i16::from(g);
let b = i16::from(b);
let y = ((4918 * r + 354) >> 10) + ((9655 * g + 585) >> 10) + ((1875 * b + 523) >> 10);
let cb = ((-2775 * r + 240) >> 10) + ((-5449 * g + 515) >> 10) + ((8224 * b + 256) >> 10);
let cr = ((8224 * r + 256) >> 10) + ((-6887 * g + 110) >> 10) + ((-1337 * b + 646) >> 10);
Yc48 { y, cb, cr }
}
pub fn to_rgb(self) -> (u8, u8, u8) {
let y = self.y as i32;
let cr = self.cr as i32;
let cb = self.cr as i32;
let r = (255 * y + ((((22881 * cr) >> 16) + 3) << 10)) >> 12;
let g = (255 * y + ((((-5616 * cb) >> 16) + ((-11655 * cr) >> 16) + 3) << 10)) >> 12;
let b = (255 * y + ((((28919 * cb) >> 16) + 3) << 10)) >> 12;
let r = r.min(255) as u8;
let g = g.min(255) as u8;
let b = b.min(255) as u8;
(r, g, b)
}
}
pub(crate) fn format_file_filters(file_filters: &[FileFilter]) -> String {
let mut file_filter = String::new();
for filter in file_filters {
let display = format!(
"{} ({})",
filter.name,
if filter.extensions.is_empty() {
"*".to_string()
} else {
filter
.extensions
.iter()
.map(|ext| format!(".{ext}"))
.collect::<Vec<_>>()
.join(", ")
}
);
file_filter.push_str(&display);
file_filter.push('\x00');
if filter.extensions.is_empty() {
file_filter.push('*');
} else {
file_filter.push_str(
&filter
.extensions
.iter()
.map(|ext| format!("*.{ext}"))
.collect::<Vec<_>>()
.join(";"),
);
}
file_filter.push('\x00');
}
file_filter
}
pub(crate) struct KillablePointer<T> {
kill_switch: std::sync::Arc<std::sync::atomic::AtomicBool>,
inner: *mut T,
}
unsafe impl<T> Send for KillablePointer<T> {}
unsafe impl<T> Sync for KillablePointer<T> {}
impl<T> Drop for KillablePointer<T> {
fn drop(&mut self) {
self.kill_switch
.store(true, std::sync::atomic::Ordering::SeqCst);
}
}
impl<T> KillablePointer<T> {
pub fn new(inner: T) -> Self {
Self {
kill_switch: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
inner: Box::into_raw(Box::new(inner)),
}
}
pub fn create_child(&self) -> ChildKillablePointer<T> {
ChildKillablePointer {
kill_switch: std::sync::Arc::clone(&self.kill_switch),
inner: self.inner,
}
}
}
pub(crate) struct ChildKillablePointer<T> {
kill_switch: std::sync::Arc<std::sync::atomic::AtomicBool>,
inner: *mut T,
}
unsafe impl<T> Send for ChildKillablePointer<T> {}
unsafe impl<T> Sync for ChildKillablePointer<T> {}
impl<T> ChildKillablePointer<T> {
pub fn is_killed(&self) -> bool {
self.kill_switch.load(std::sync::atomic::Ordering::SeqCst)
}
pub unsafe fn as_mut(&mut self) -> &mut T {
if self.is_killed() {
panic!("parent KillablePointer has been dropped");
}
unsafe { &mut *self.inner }
}
}
pub(crate) enum LeakType {
WideString,
ValueVector { len: usize, name: String },
Null,
Other(String),
}
pub(crate) struct LeakManager {
ptrs: std::sync::Mutex<Vec<(LeakType, usize)>>,
}
pub(crate) trait IntoLeakedPtr {
fn into_leaked_ptr(self) -> (LeakType, usize);
}
pub(crate) trait LeakableValue {}
impl LeakManager {
pub fn new() -> Self {
Self {
ptrs: std::sync::Mutex::new(Vec::new()),
}
}
pub fn leak<T: IntoLeakedPtr>(&self, value: T) -> *const T {
tracing::debug!("Leaking memory for type {}", std::any::type_name::<T>());
let mut ptrs = self.ptrs.lock().unwrap();
let leaked = value.into_leaked_ptr();
let ptr = leaked.1;
ptrs.push(leaked);
ptr as *const T
}
pub fn leak_as_wide_string(&self, s: &str) -> *const u16 {
tracing::debug!("Leaking wide string: {}", s);
let mut wide: Vec<u16> = s.encode_utf16().collect();
wide.push(0); let boxed = wide.into_boxed_slice();
let ptr = Box::into_raw(boxed) as *mut u16 as usize;
let mut ptrs = self.ptrs.lock().unwrap();
ptrs.push((LeakType::WideString, ptr));
ptr as *const u16
}
pub fn leak_value_vec<T: LeakableValue>(&self, vec: Vec<T>) -> *const T {
tracing::debug!(
"Leaking value vector of type {}",
std::any::type_name::<T>()
);
let len = vec.len();
let boxed = vec.into_boxed_slice();
let ptr = Box::into_raw(boxed) as *mut T as usize;
let mut ptrs = self.ptrs.lock().unwrap();
ptrs.push((
LeakType::ValueVector {
len,
name: std::any::type_name::<T>().to_string(),
},
ptr,
));
ptr as *const T
}
pub fn free_leaked_memory(&self) {
let mut ptrs = self.ptrs.lock().unwrap();
while let Some((ptr_type, ptr)) = ptrs.pop() {
match ptr_type {
LeakType::WideString => unsafe {
let _ = Box::from_raw(ptr as *mut u16);
},
LeakType::ValueVector { len, name } => {
Self::free_leaked_memory_leakable_value(&name, ptr, len);
}
LeakType::Null => {
assert!(ptr == 0);
}
LeakType::Other(ref type_name) => {
Self::free_leaked_memory_other_ptr(type_name, ptr);
}
}
}
}
}
macro_rules! impl_leak_ptr {
($($t:ty),* $(,)?) => {
$(
impl IntoLeakedPtr for $t {
fn into_leaked_ptr(self) -> (LeakType, usize) {
let boxed = Box::new(self);
let ptr = Box::into_raw(boxed) as usize;
(LeakType::Other(std::any::type_name::<$t>().to_string()), ptr)
}
}
)*
impl LeakManager {
fn free_leaked_memory_other_ptr(ptr_type: &str, ptr: usize) {
unsafe {
match ptr_type {
$(
t if t == std::any::type_name::<$t>() => {
let _ = Box::from_raw(ptr as *mut $t);
},
)*
_ => {
unreachable!("Unknown leaked pointer type: {}", ptr_type);
}
}
}
}
}
};
}
macro_rules! impl_leakable_value {
($($t:ty),* $(,)?) => {
$(
impl LeakableValue for $t {}
)*
impl LeakManager {
fn free_leaked_memory_leakable_value(type_name: &str, ptr: usize, len: usize) {
unsafe {
match type_name {
$(
t if t == std::any::type_name::<$t>() => {
let _ = Box::from_raw(std::slice::from_raw_parts_mut(ptr as *mut $t, len));
},
)*
_ => {
unreachable!("Unknown leaked value vector type: {}", type_name);
}
}
}
}
}
};
}
impl_leak_ptr!(
aviutl2_sys::input2::WAVEFORMATEX,
aviutl2_sys::output2::BITMAPINFOHEADER,
aviutl2_sys::filter2::FILTER_ITEM,
);
impl_leakable_value!(
aviutl2_sys::filter2::FILTER_ITEM_SELECT_ITEM,
aviutl2_sys::module2::SCRIPT_MODULE_FUNCTION,
*const aviutl2_sys::filter2::FILTER_ITEM,
usize
);
impl<T: IntoLeakedPtr> IntoLeakedPtr for Option<T> {
fn into_leaked_ptr(self) -> (LeakType, usize) {
match self {
Some(value) => value.into_leaked_ptr(),
None => (LeakType::Null, 0),
}
}
}
impl Drop for LeakManager {
fn drop(&mut self) {
self.free_leaked_memory();
}
}
pub(crate) unsafe fn load_wide_string(ptr: *const u16) -> String {
if ptr.is_null() {
return String::new();
}
let mut len = 0;
while unsafe { *ptr.add(len) } != 0 {
len += 1;
}
unsafe { String::from_utf16_lossy(std::slice::from_raw_parts(ptr, len)) }
}
#[doc(hidden)]
#[expect(private_bounds)]
pub fn __output_log_if_error<T: MenuCallbackReturn>(result: T) {
if let Some(err_msg) = result.into_optional_error() {
let _ = crate::logger::write_error_log(&err_msg);
}
}
#[doc(hidden)]
#[expect(private_bounds)]
pub fn __log_and_beep_if_error<T: MenuCallbackReturn>(result: T) {
if let Some(err_msg) = result.into_optional_error() {
let _ = crate::logger::write_error_log(&err_msg);
let _ = unsafe {
windows::Win32::System::Diagnostics::Debug::MessageBeep(
windows::Win32::UI::WindowsAndMessaging::MB_ICONEXCLAMATION,
)
};
}
}
trait MenuCallbackReturn {
fn into_optional_error(self) -> Option<String>;
}
impl<E> MenuCallbackReturn for Result<(), E>
where
Box<dyn std::error::Error>: From<E>,
{
fn into_optional_error(self) -> Option<String> {
match self {
Ok(_) => None,
Err(e) => {
let boxed: Box<dyn std::error::Error> = e.into();
Some(format!("{}", boxed))
}
}
}
}
impl MenuCallbackReturn for () {
fn into_optional_error(self) -> Option<String> {
None
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CWString(Vec<u16>);
#[derive(thiserror::Error, Debug, Clone)]
#[error("String contains null byte")]
pub struct NullByteError {
position: usize,
u16_seq: Vec<u16>,
}
impl NullByteError {
pub fn nul_position(&self) -> usize {
self.position
}
pub fn into_vec(self) -> Vec<u16> {
self.u16_seq
}
}
impl CWString {
pub fn new(string: &str) -> Result<Self, NullByteError> {
let mut wide: Vec<u16> = string.encode_utf16().collect();
if let Some((pos, _)) = wide.iter().enumerate().find(|&(_, &c)| c == 0) {
return Err(NullByteError {
position: pos,
u16_seq: wide,
});
}
wide.push(0); Ok(Self(wide))
}
pub fn as_ptr(&self) -> *const u16 {
self.0.as_ptr()
}
}
impl std::fmt::Display for CWString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = String::from_utf16_lossy(&self.0[..self.0.len() - 1]);
write!(f, "{}", s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_file_filters() {
let filters = vec![
FileFilter {
name: "Image Files".to_string(),
extensions: vec!["png".to_string(), "jpg".to_string()],
},
FileFilter {
name: "All Files".to_string(),
extensions: vec![],
},
];
let formatted = format_file_filters(&filters);
let expected = "Image Files (.png, .jpg)\x00*.png;*.jpg\x00All Files (*)\x00*\x00";
assert_eq!(formatted, expected);
}
#[test]
fn test_file_filters_macro() {
let filters = file_filters! {
"Image Files" => ["png", "jpg"],
"All Files" => []
};
assert_eq!(filters.len(), 2);
assert_eq!(filters[0].name, "Image Files");
assert_eq!(
filters[0].extensions,
vec!["png".to_string(), "jpg".to_string()]
);
assert_eq!(filters[1].name, "All Files");
assert_eq!(filters[1].extensions, Vec::<String>::new());
}
#[test]
fn test_aviutl2_version() {
let version = AviUtl2Version::new(2, 0, 15, 0);
assert_eq!(version.0, 2001500);
assert_eq!(version.major(), 2);
assert_eq!(version.minor(), 0);
assert_eq!(version.patch(), 15);
assert_eq!(version.build(), 0);
let version_from_u32: AviUtl2Version = 2001500u32.into();
assert_eq!(version_from_u32, version);
let u32_from_version: u32 = version.into();
assert_eq!(u32_from_version, 2001500);
assert_eq!(version.to_string(), "2.00beta15");
let version2 = AviUtl2Version::new(2, 0, 15, 1);
assert_eq!(version2.to_string(), "2.00beta15a");
let version3 = AviUtl2Version::new(2, 0, 54, 0);
assert_eq!(version3.to_string(), "v2.0.54");
let version4 = AviUtl2Version::new(2, 0, 54, 1);
assert_eq!(version4.to_string(), "v2.0.54a");
let version5 = AviUtl2Version::new(2, 0, 54, 26);
assert_eq!(version5.to_string(), "v2.0.54z");
let version6 = AviUtl2Version::new(2, 0, 54, 27);
assert_eq!(version6.to_string(), "v2.0.54aa");
let version7 = AviUtl2Version::new(2, 0, 54, 52);
assert_eq!(version7.to_string(), "v2.0.54az");
}
#[test]
fn test_cwstring_new() {
let s = "Hello, world!";
let cwstring = CWString::new(s).unwrap();
assert_eq!(unsafe { load_wide_string(cwstring.as_ptr()) }, s);
let s_with_nul = "Hello\0world!";
let err = CWString::new(s_with_nul).unwrap_err();
assert_eq!(err.nul_position(), 5);
}
}