1pub use anyhow::Result as AnyResult;
2use zerocopy::{Immutable, IntoBytes};
3
4pub use half::{self, f16};
5pub use num_rational::{self, Rational32};
6pub use raw_window_handle::{self, Win32WindowHandle};
7
8#[derive(Debug, Clone)]
10pub struct AviUtl2Info {
11 pub version: AviUtl2Version,
13}
14
15pub const MINIMUM_AVIUTL2_VERSION: AviUtl2Version = AviUtl2Version(2010800);
17
18pub fn ensure_minimum_aviutl2_version(version: AviUtl2Version) -> AnyResult<()> {
20 anyhow::ensure!(
21 version >= MINIMUM_AVIUTL2_VERSION,
22 "AviUtl2 version {version} is not supported. {MINIMUM_AVIUTL2_VERSION} or higher is required.",
23 );
24 Ok(())
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
41pub struct AviUtl2Version(pub u32);
42impl From<u32> for AviUtl2Version {
43 fn from(value: u32) -> Self {
44 Self(value)
45 }
46}
47impl From<AviUtl2Version> for u32 {
48 fn from(value: AviUtl2Version) -> Self {
49 value.0
50 }
51}
52impl AviUtl2Version {
53 pub fn new(major: u8, minor: u8, patch: u8, build: u8) -> Self {
59 assert!(minor < 100, "minor version must be less than 100");
60 assert!(patch < 100, "patch version must be less than 100");
61 assert!(build < 100, "build version must be less than 100");
62 Self(
63 (major as u32) * 1_000_000
64 + (minor as u32) * 10_000
65 + (patch as u32) * 100
66 + (build as u32),
67 )
68 }
69
70 pub fn major(self) -> u32 {
72 self.0 / 1000000
73 }
74
75 pub fn minor(self) -> u32 {
77 (self.0 / 10000) % 100
78 }
79
80 pub fn patch(self) -> u32 {
82 (self.0 / 100) % 100
83 }
84
85 pub fn build(self) -> u32 {
87 self.0 % 100
88 }
89}
90impl std::fmt::Display for AviUtl2Version {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 if self.0 <= 2005301 {
93 write!(
94 f,
95 "{}.{:0>2}beta{}{}",
96 self.major(),
97 self.minor(),
98 self.patch(),
99 int_to_a_to_z(self.build() as u8).unwrap_or_default()
100 )
101 } else {
102 write!(
103 f,
104 "v{}.{}.{}{}",
105 self.major(),
106 self.minor(),
107 self.patch(),
108 int_to_a_to_z(self.build() as u8).unwrap_or_default()
109 )
110 }
111 }
112}
113
114fn int_to_a_to_z(n: u8) -> Option<String> {
118 let mut n = n;
119 if n == 0 {
120 return None;
121 }
122
123 let mut result = String::new();
124 while n > 0 {
125 n -= 1;
126 result.insert(0, (b'a' + (n % 26)) as char);
127 n /= 26;
128 }
129 Some(result)
130}
131
132#[cfg(feature = "aviutl2-alias")]
133impl aviutl2_alias::FromTableValue for AviUtl2Version {
134 type Err = std::num::ParseIntError;
135
136 fn from_table_value(value: &str) -> Result<Self, Self::Err> {
137 let v: u32 = value.parse()?;
138 Ok(Self::from(v))
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct FileFilter {
145 pub name: String,
147 pub extensions: Vec<String>,
149}
150
151#[macro_export]
162macro_rules! file_filters {
163 ($($name:expr => [$($ext:expr),* $(,)?] ),* $(,)?) => {
164 vec![
165 $(
166 $crate::FileFilter {
167 name: $name.to_string(),
168 extensions: vec![$($ext.to_string()),*],
169 }
170 ),*
171 ]
172 };
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, IntoBytes, Immutable)]
180#[repr(C)]
181pub struct Yc48 {
182 pub y: i16,
185 pub cb: i16,
188 pub cr: i16,
191}
192impl Yc48 {
193 pub fn from_yuy2(yuy2: (u8, u8, u8, u8)) -> (Self, Self) {
195 let (y1, u, y2, v) = yuy2;
196 let ny1 = ((y1 as u16 * 1197) >> 6) - 299;
197 let ny2 = ((y2 as u16 * 1197) >> 6) - 299;
198 let ncb = ((u as u16 - 128) * 4681 + 164) >> 8;
199 let ncr = ((v as u16 - 128) * 4681 + 164) >> 8;
200 (
201 Self {
202 y: ny1 as i16,
203 cb: ncb as i16,
204 cr: ncr as i16,
205 },
206 Self {
207 y: ((ny1 + ny2) >> 1) as i16,
208 cb: ncb as i16,
209 cr: ncr as i16,
210 },
211 )
212 }
213
214 pub fn to_yuy2(self, other: Yc48) -> (u8, u8, u8, u8) {
216 let y1 = ((self.y * 219 + 383) >> 12) + 16;
217 let y2 = ((other.y * 219 + 383) >> 12) + 16;
218 let u = (((self.cb + 2048) * 7 + 66) >> 7) + 16;
219 let v = (((self.cr + 2048) * 7 + 66) >> 7) + 16;
220 let y1 = y1.min(255) as u8;
221 let y2 = y2.min(255) as u8;
222 let u = u.min(255) as u8;
223 let v = v.min(255) as u8;
224 (y1, u, y2, v)
225 }
226
227 pub fn from_rgb(self, rgb: (u8, u8, u8)) -> Self {
229 let (r, g, b) = rgb;
230 let r = i16::from(r);
231 let g = i16::from(g);
232 let b = i16::from(b);
233 let y = ((4918 * r + 354) >> 10) + ((9655 * g + 585) >> 10) + ((1875 * b + 523) >> 10);
234 let cb = ((-2775 * r + 240) >> 10) + ((-5449 * g + 515) >> 10) + ((8224 * b + 256) >> 10);
235 let cr = ((8224 * r + 256) >> 10) + ((-6887 * g + 110) >> 10) + ((-1337 * b + 646) >> 10);
236
237 Yc48 { y, cb, cr }
238 }
239
240 pub fn to_rgb(self) -> (u8, u8, u8) {
242 let y = self.y as i32;
243 let cr = self.cr as i32;
244 let cb = self.cr as i32;
245 let r = (255 * y + ((((22881 * cr) >> 16) + 3) << 10)) >> 12;
246 let g = (255 * y + ((((-5616 * cb) >> 16) + ((-11655 * cr) >> 16) + 3) << 10)) >> 12;
247 let b = (255 * y + ((((28919 * cb) >> 16) + 3) << 10)) >> 12;
248
249 let r = r.min(255) as u8;
250 let g = g.min(255) as u8;
251 let b = b.min(255) as u8;
252 (r, g, b)
253 }
254}
255
256pub(crate) fn format_file_filters(file_filters: &[FileFilter]) -> String {
257 let mut file_filter = String::new();
258 for filter in file_filters {
259 let display = format!(
260 "{} ({})",
261 filter.name,
262 if filter.extensions.is_empty() {
263 "*".to_string()
264 } else {
265 filter
266 .extensions
267 .iter()
268 .map(|ext| format!(".{ext}"))
269 .collect::<Vec<_>>()
270 .join(", ")
271 }
272 );
273 file_filter.push_str(&display);
274 file_filter.push('\x00');
275 if filter.extensions.is_empty() {
276 file_filter.push('*');
277 } else {
278 file_filter.push_str(
279 &filter
280 .extensions
281 .iter()
282 .map(|ext| format!("*.{ext}"))
283 .collect::<Vec<_>>()
284 .join(";"),
285 );
286 }
287 file_filter.push('\x00');
288 }
289
290 file_filter
291}
292
293pub(crate) struct KillablePointer<T> {
294 kill_switch: std::sync::Arc<std::sync::atomic::AtomicBool>,
295 inner: *mut T,
296}
297unsafe impl<T> Send for KillablePointer<T> {}
298unsafe impl<T> Sync for KillablePointer<T> {}
299impl<T> Drop for KillablePointer<T> {
300 fn drop(&mut self) {
301 self.kill_switch
302 .store(true, std::sync::atomic::Ordering::SeqCst);
303 }
304}
305impl<T> KillablePointer<T> {
306 pub fn new(inner: T) -> Self {
307 Self {
308 kill_switch: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
309 inner: Box::into_raw(Box::new(inner)),
310 }
311 }
312
313 pub fn create_child(&self) -> ChildKillablePointer<T> {
314 ChildKillablePointer {
315 kill_switch: std::sync::Arc::clone(&self.kill_switch),
316 inner: self.inner,
317 }
318 }
319}
320
321pub(crate) struct ChildKillablePointer<T> {
322 kill_switch: std::sync::Arc<std::sync::atomic::AtomicBool>,
323 inner: *mut T,
324}
325unsafe impl<T> Send for ChildKillablePointer<T> {}
326unsafe impl<T> Sync for ChildKillablePointer<T> {}
327impl<T> ChildKillablePointer<T> {
328 pub fn is_killed(&self) -> bool {
329 self.kill_switch.load(std::sync::atomic::Ordering::SeqCst)
330 }
331
332 pub unsafe fn as_mut(&mut self) -> &mut T {
333 if self.is_killed() {
334 panic!("parent KillablePointer has been dropped");
335 }
336 unsafe { &mut *self.inner }
337 }
338}
339
340pub(crate) enum LeakType {
341 WideString,
342 ValueVector { len: usize, name: String },
343 Null,
344 Other(String),
345}
346
347pub(crate) struct LeakManager {
348 ptrs: std::sync::Mutex<Vec<(LeakType, usize)>>,
349}
350
351pub(crate) trait IntoLeakedPtr {
352 fn into_leaked_ptr(self) -> (LeakType, usize);
353}
354pub(crate) trait LeakableValue {}
355
356impl LeakManager {
357 pub fn new() -> Self {
358 Self {
359 ptrs: std::sync::Mutex::new(Vec::new()),
360 }
361 }
362
363 pub fn leak<T: IntoLeakedPtr>(&self, value: T) -> *const T {
364 tracing::debug!("Leaking memory for type {}", std::any::type_name::<T>());
365 let mut ptrs = self.ptrs.lock().unwrap();
366 let leaked = value.into_leaked_ptr();
367 let ptr = leaked.1;
368 ptrs.push(leaked);
369 ptr as *const T
370 }
371
372 pub fn leak_as_wide_string(&self, s: &str) -> *const u16 {
373 tracing::debug!("Leaking wide string: {}", s);
374 let mut wide: Vec<u16> = s.encode_utf16().collect();
375 wide.push(0); let boxed = wide.into_boxed_slice();
377 let ptr = Box::into_raw(boxed) as *mut u16 as usize;
378 let mut ptrs = self.ptrs.lock().unwrap();
379 ptrs.push((LeakType::WideString, ptr));
380 ptr as *const u16
381 }
382
383 pub fn leak_value_vec<T: LeakableValue>(&self, vec: Vec<T>) -> *const T {
397 tracing::debug!(
398 "Leaking value vector of type {}",
399 std::any::type_name::<T>()
400 );
401 let len = vec.len();
402 let boxed = vec.into_boxed_slice();
403 let ptr = Box::into_raw(boxed) as *mut T as usize;
404 let mut ptrs = self.ptrs.lock().unwrap();
405 ptrs.push((
406 LeakType::ValueVector {
407 len,
408 name: std::any::type_name::<T>().to_string(),
409 },
410 ptr,
411 ));
412 ptr as *const T
413 }
414
415 pub fn free_leaked_memory(&self) {
416 let mut ptrs = self.ptrs.lock().unwrap();
417 while let Some((ptr_type, ptr)) = ptrs.pop() {
418 match ptr_type {
419 LeakType::WideString => unsafe {
420 let _ = Box::from_raw(ptr as *mut u16);
421 },
422 LeakType::ValueVector { len, name } => {
423 Self::free_leaked_memory_leakable_value(&name, ptr, len);
424 }
425 LeakType::Null => {
426 assert!(ptr == 0);
427 }
428 LeakType::Other(ref type_name) => {
429 Self::free_leaked_memory_other_ptr(type_name, ptr);
430 }
431 }
432 }
433 }
434}
435macro_rules! impl_leak_ptr {
436 ($($t:ty),* $(,)?) => {
437 $(
438 impl IntoLeakedPtr for $t {
439 fn into_leaked_ptr(self) -> (LeakType, usize) {
440 let boxed = Box::new(self);
441 let ptr = Box::into_raw(boxed) as usize;
442 (LeakType::Other(std::any::type_name::<$t>().to_string()), ptr)
443 }
444 }
445 )*
446
447 impl LeakManager {
448 fn free_leaked_memory_other_ptr(ptr_type: &str, ptr: usize) {
449 unsafe {
450 match ptr_type {
451 $(
452 t if t == std::any::type_name::<$t>() => {
453 let _ = Box::from_raw(ptr as *mut $t);
454 },
455 )*
456 _ => {
457 unreachable!("Unknown leaked pointer type: {}", ptr_type);
458 }
459 }
460 }
461 }
462 }
463 };
464}
465macro_rules! impl_leakable_value {
466 ($($t:ty),* $(,)?) => {
467 $(
468 impl LeakableValue for $t {}
469 )*
470 impl LeakManager {
471 fn free_leaked_memory_leakable_value(type_name: &str, ptr: usize, len: usize) {
472 unsafe {
473 match type_name {
474 $(
475 t if t == std::any::type_name::<$t>() => {
476 let _ = Box::from_raw(std::slice::from_raw_parts_mut(ptr as *mut $t, len));
477 },
478 )*
479 _ => {
480 unreachable!("Unknown leaked value vector type: {}", type_name);
481 }
482 }
483 }
484 }
485 }
486 };
487}
488impl_leak_ptr!(
489 aviutl2_sys::input2::WAVEFORMATEX,
490 aviutl2_sys::output2::BITMAPINFOHEADER,
491 aviutl2_sys::filter2::FILTER_ITEM,
492);
493impl_leakable_value!(
494 aviutl2_sys::filter2::FILTER_ITEM_SELECT_ITEM,
495 aviutl2_sys::module2::SCRIPT_MODULE_FUNCTION,
496 *const aviutl2_sys::filter2::FILTER_ITEM,
497 usize
498);
499
500impl<T: IntoLeakedPtr> IntoLeakedPtr for Option<T> {
501 fn into_leaked_ptr(self) -> (LeakType, usize) {
502 match self {
503 Some(value) => value.into_leaked_ptr(),
504 None => (LeakType::Null, 0),
505 }
506 }
507}
508
509impl Drop for LeakManager {
510 fn drop(&mut self) {
511 self.free_leaked_memory();
512 }
513}
514
515pub(crate) unsafe fn load_wide_string(ptr: *const u16) -> String {
520 if ptr.is_null() {
521 return String::new();
522 }
523
524 let mut len = 0;
525 while unsafe { *ptr.add(len) } != 0 {
526 len += 1;
527 }
528
529 unsafe { String::from_utf16_lossy(std::slice::from_raw_parts(ptr, len)) }
530}
531
532#[doc(hidden)]
533#[expect(private_bounds)]
534pub fn __output_log_if_error<T: MenuCallbackReturn>(result: T) {
535 if let Some(err_msg) = result.into_optional_error() {
536 let _ = crate::logger::write_error_log(&err_msg);
537 }
538}
539
540#[doc(hidden)]
541#[expect(private_bounds)]
542pub fn __log_and_beep_if_error<T: MenuCallbackReturn>(result: T) {
543 if let Some(err_msg) = result.into_optional_error() {
544 let _ = crate::logger::write_error_log(&err_msg);
545
546 let _ = unsafe {
547 windows::Win32::System::Diagnostics::Debug::MessageBeep(
548 windows::Win32::UI::WindowsAndMessaging::MB_ICONEXCLAMATION,
549 )
550 };
551 }
552}
553
554trait MenuCallbackReturn {
555 fn into_optional_error(self) -> Option<String>;
556}
557impl<E> MenuCallbackReturn for Result<(), E>
558where
559 Box<dyn std::error::Error>: From<E>,
560{
561 fn into_optional_error(self) -> Option<String> {
562 match self {
563 Ok(_) => None,
564 Err(e) => {
565 let boxed: Box<dyn std::error::Error> = e.into();
566 Some(format!("{}", boxed))
567 }
568 }
569 }
570}
571impl MenuCallbackReturn for () {
572 fn into_optional_error(self) -> Option<String> {
573 None
574 }
575}
576
577#[derive(Debug, Clone, PartialEq, Eq)]
579pub(crate) struct CWString(Vec<u16>);
580
581#[derive(thiserror::Error, Debug, Clone)]
583#[error("String contains null byte")]
584pub struct NullByteError {
585 position: usize,
586 u16_seq: Vec<u16>,
587}
588impl NullByteError {
589 pub fn nul_position(&self) -> usize {
590 self.position
591 }
592
593 pub fn into_vec(self) -> Vec<u16> {
594 self.u16_seq
595 }
596}
597
598impl CWString {
599 pub fn new(string: &str) -> Result<Self, NullByteError> {
605 let mut wide: Vec<u16> = string.encode_utf16().collect();
606 if let Some((pos, _)) = wide.iter().enumerate().find(|&(_, &c)| c == 0) {
607 return Err(NullByteError {
608 position: pos,
609 u16_seq: wide,
610 });
611 }
612 wide.push(0); Ok(Self(wide))
614 }
615
616 pub fn as_ptr(&self) -> *const u16 {
626 self.0.as_ptr()
627 }
628}
629impl std::fmt::Display for CWString {
630 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631 let s = String::from_utf16_lossy(&self.0[..self.0.len() - 1]);
632 write!(f, "{}", s)
633 }
634}
635
636#[cfg(test)]
637mod tests {
638 use super::*;
639
640 #[test]
641 fn test_format_file_filters() {
642 let filters = vec![
643 FileFilter {
644 name: "Image Files".to_string(),
645 extensions: vec!["png".to_string(), "jpg".to_string()],
646 },
647 FileFilter {
648 name: "All Files".to_string(),
649 extensions: vec![],
650 },
651 ];
652 let formatted = format_file_filters(&filters);
653 let expected = "Image Files (.png, .jpg)\x00*.png;*.jpg\x00All Files (*)\x00*\x00";
654 assert_eq!(formatted, expected);
655 }
656
657 #[test]
658 fn test_file_filters_macro() {
659 let filters = file_filters! {
660 "Image Files" => ["png", "jpg"],
661 "All Files" => []
662 };
663 assert_eq!(filters.len(), 2);
664 assert_eq!(filters[0].name, "Image Files");
665 assert_eq!(
666 filters[0].extensions,
667 vec!["png".to_string(), "jpg".to_string()]
668 );
669 assert_eq!(filters[1].name, "All Files");
670 assert_eq!(filters[1].extensions, Vec::<String>::new());
671 }
672
673 #[test]
674 fn test_aviutl2_version() {
675 let version = AviUtl2Version::new(2, 0, 15, 0);
676 assert_eq!(version.0, 2001500);
677 assert_eq!(version.major(), 2);
678 assert_eq!(version.minor(), 0);
679 assert_eq!(version.patch(), 15);
680 assert_eq!(version.build(), 0);
681
682 let version_from_u32: AviUtl2Version = 2001500u32.into();
683 assert_eq!(version_from_u32, version);
684
685 let u32_from_version: u32 = version.into();
686 assert_eq!(u32_from_version, 2001500);
687
688 assert_eq!(version.to_string(), "2.00beta15");
689
690 let version2 = AviUtl2Version::new(2, 0, 15, 1);
691 assert_eq!(version2.to_string(), "2.00beta15a");
692
693 let version3 = AviUtl2Version::new(2, 0, 54, 0);
694 assert_eq!(version3.to_string(), "v2.0.54");
695
696 let version4 = AviUtl2Version::new(2, 0, 54, 1);
697 assert_eq!(version4.to_string(), "v2.0.54a");
698
699 let version5 = AviUtl2Version::new(2, 0, 54, 26);
700 assert_eq!(version5.to_string(), "v2.0.54z");
701
702 let version6 = AviUtl2Version::new(2, 0, 54, 27);
703 assert_eq!(version6.to_string(), "v2.0.54aa");
704
705 let version7 = AviUtl2Version::new(2, 0, 54, 52);
706 assert_eq!(version7.to_string(), "v2.0.54az");
707 }
708
709 #[test]
710 fn test_cwstring_new() {
711 let s = "Hello, world!";
712 let cwstring = CWString::new(s).unwrap();
713 assert_eq!(unsafe { load_wide_string(cwstring.as_ptr()) }, s);
714
715 let s_with_nul = "Hello\0world!";
716 let err = CWString::new(s_with_nul).unwrap_err();
717 assert_eq!(err.nul_position(), 5);
718 }
719}