use std::{ffi::c_void, path::Path, sync::Arc};
use num_rational::Rational32;
use parking_lot::lock_api::RawRwLock;
use crate::{common::CWString, filter::RgbaPixel};
struct InternalCacheHandle(*mut aviutl2_sys::cache2::CACHE_HANDLE);
unsafe impl Send for InternalCacheHandle {}
static CACHE_HANDLE: std::sync::OnceLock<std::sync::Mutex<InternalCacheHandle>> =
std::sync::OnceLock::new();
static CACHE_LOCKS: std::sync::LazyLock<dashmap::DashMap<usize, Arc<parking_lot::RawRwLock>>> =
std::sync::LazyLock::new(dashmap::DashMap::new);
#[doc(hidden)]
pub struct GlobalCacheHandle;
pub const GLOBAL_CACHE_HANDLE: GlobalCacheHandle = GlobalCacheHandle;
impl AsCacheIdentifier for GlobalCacheHandle {
fn as_cache_identifier(&self) -> *mut c_void {
with_cache_handle(|handle| handle as *mut c_void).expect("Cache handle is not initialized")
}
}
#[doc(hidden)]
pub fn __initialize_cache(handle: *mut aviutl2_sys::cache2::CACHE_HANDLE) {
CACHE_HANDLE
.set(std::sync::Mutex::new(InternalCacheHandle(handle)))
.ok()
.expect("Cache handle is already initialized");
}
#[doc(hidden)]
pub fn __initialize_cache_unwind(handle: *mut aviutl2_sys::cache2::CACHE_HANDLE) {
if let Err(panic_info) = crate::__catch_unwind_with_panic_info(|| __initialize_cache(handle)) {
tracing::error!("Panic occurred during InitializeCache: {}", panic_info);
let _ = crate::logger::write_error_log(&panic_info);
}
}
#[derive(Debug, thiserror::Error)]
pub enum CacheError {
#[error("api call failed")]
ApiCallFailed,
#[error("input utf-16 string contains null byte")]
InputCwstrContainsNull(#[from] crate::common::NullByteError),
#[error("value is out of range")]
ValueOutOfRange,
#[error("invalid audio channel")]
InvalidAudioChannel,
}
pub trait AsCacheIdentifier {
fn as_cache_identifier(&self) -> *mut c_void;
}
impl AsCacheIdentifier for *mut c_void {
fn as_cache_identifier(&self) -> *mut c_void {
*self
}
}
impl AsCacheIdentifier for *const c_void {
fn as_cache_identifier(&self) -> *mut c_void {
*self as *mut c_void
}
}
impl AsCacheIdentifier for usize {
fn as_cache_identifier(&self) -> *mut c_void {
*self as *mut c_void
}
}
enum CacheAccessMode {
Read,
Write,
}
struct CacheAccessGuard {
lock: Arc<parking_lot::RawRwLock>,
mode: CacheAccessMode,
}
impl CacheAccessGuard {
fn read(key: usize) -> Self {
let lock = CACHE_LOCKS
.entry(key)
.or_insert_with(|| Arc::new(parking_lot::RawRwLock::INIT))
.clone();
lock.lock_shared();
Self {
lock,
mode: CacheAccessMode::Read,
}
}
fn write(key: usize) -> Self {
let lock = CACHE_LOCKS
.entry(key)
.or_insert_with(|| Arc::new(parking_lot::RawRwLock::INIT))
.clone();
lock.lock_exclusive();
Self {
lock,
mode: CacheAccessMode::Write,
}
}
}
impl Drop for CacheAccessGuard {
fn drop(&mut self) {
unsafe {
match self.mode {
CacheAccessMode::Read => self.lock.unlock_shared(),
CacheAccessMode::Write => self.lock.unlock_exclusive(),
}
}
}
}
pub struct CacheImageReadGuard {
_access: CacheAccessGuard,
raw: aviutl2_sys::cache2::CACHE_IMAGE,
len: usize,
}
pub struct CacheImageWriteGuard {
_access: CacheAccessGuard,
raw: aviutl2_sys::cache2::CACHE_IMAGE,
len: usize,
}
pub struct CacheAudioReadGuard {
_access: CacheAccessGuard,
raw: aviutl2_sys::cache2::CACHE_AUDIO,
len: usize,
}
pub struct CacheAudioWriteGuard {
_access: CacheAccessGuard,
raw: aviutl2_sys::cache2::CACHE_AUDIO,
len: usize,
}
pub struct CacheFileImageReadGuard {
_access: CacheAccessGuard,
raw: aviutl2_sys::cache2::CACHE_FILE_IMAGE,
byte_len: usize,
}
unsafe impl Send for CacheImageReadGuard {}
unsafe impl Send for CacheImageWriteGuard {}
unsafe impl Send for CacheAudioReadGuard {}
unsafe impl Send for CacheAudioWriteGuard {}
unsafe impl Send for CacheFileImageReadGuard {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CacheImagePixelFormat {
Rgba,
Bgra,
Bgr,
Pa64,
Hf64,
Yuy2,
Yc48,
}
impl From<aviutl2_sys::filter2::INPUT_PIXEL_FORMAT> for CacheImagePixelFormat {
fn from(value: aviutl2_sys::filter2::INPUT_PIXEL_FORMAT) -> Self {
match value {
aviutl2_sys::filter2::INPUT_PIXEL_FORMAT::RGBA => Self::Rgba,
aviutl2_sys::filter2::INPUT_PIXEL_FORMAT::BGRA => Self::Bgra,
aviutl2_sys::filter2::INPUT_PIXEL_FORMAT::BGR => Self::Bgr,
aviutl2_sys::filter2::INPUT_PIXEL_FORMAT::PA64 => Self::Pa64,
aviutl2_sys::filter2::INPUT_PIXEL_FORMAT::HF64 => Self::Hf64,
aviutl2_sys::filter2::INPUT_PIXEL_FORMAT::YUY2 => Self::Yuy2,
aviutl2_sys::filter2::INPUT_PIXEL_FORMAT::YC48 => Self::Yc48,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct VideoFileInfo {
pub total_time: f64,
pub frame_num: usize,
pub track_num: usize,
pub width: usize,
pub height: usize,
pub fps: Rational32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[non_exhaustive]
pub struct AudioFileInfo {
pub total_time: f64,
pub sample_num: usize,
pub track_num: usize,
pub rate: i32,
pub channel: usize,
}
impl CacheImageReadGuard {
pub fn width(&self) -> usize {
self.raw.width as usize
}
pub fn height(&self) -> usize {
self.raw.height as usize
}
pub fn as_slice(&self) -> &[RgbaPixel] {
unsafe { std::slice::from_raw_parts(self.raw.buffer as *const RgbaPixel, self.len) }
}
pub fn as_ptr(&self) -> *const RgbaPixel {
self.raw.buffer as *const RgbaPixel
}
pub fn as_u8_slice(&self) -> &[u8] {
unsafe {
std::slice::from_raw_parts(
self.raw.buffer as *const u8,
self.len * std::mem::size_of::<RgbaPixel>(),
)
}
}
pub fn as_u8_ptr(&self) -> *const u8 {
self.raw.buffer as *const u8
}
}
impl CacheImageWriteGuard {
pub fn width(&self) -> usize {
self.raw.width as usize
}
pub fn height(&self) -> usize {
self.raw.height as usize
}
pub fn as_slice(&self) -> &[RgbaPixel] {
unsafe { std::slice::from_raw_parts(self.raw.buffer as *const RgbaPixel, self.len) }
}
pub fn as_slice_mut(&mut self) -> &mut [RgbaPixel] {
unsafe { std::slice::from_raw_parts_mut(self.raw.buffer as *mut RgbaPixel, self.len) }
}
pub fn as_mut_ptr(&mut self) -> *mut RgbaPixel {
self.raw.buffer as *mut RgbaPixel
}
pub fn as_u8_slice(&self) -> &[u8] {
unsafe {
std::slice::from_raw_parts(
self.raw.buffer as *const u8,
self.len * std::mem::size_of::<RgbaPixel>(),
)
}
}
pub fn as_u8_slice_mut(&mut self) -> &mut [u8] {
unsafe {
std::slice::from_raw_parts_mut(
self.raw.buffer as *mut u8,
self.len * std::mem::size_of::<RgbaPixel>(),
)
}
}
pub fn as_mut_u8_ptr(&mut self) -> *mut u8 {
self.raw.buffer as *mut u8
}
}
impl CacheAudioReadGuard {
pub fn sample_num(&self) -> usize {
self.len
}
pub fn channel_num(&self) -> usize {
self.raw.channel_num as usize
}
pub fn channel(&self, channel: crate::filter::AudioChannel) -> Option<&[f32]> {
let buffer = self.channel_ptr(channel)?;
Some(unsafe { std::slice::from_raw_parts(buffer, self.len) })
}
fn channel_ptr(&self, channel: crate::filter::AudioChannel) -> Option<*const f32> {
match i32::from(channel) {
0 if !self.raw.buffer0.is_null() => Some(self.raw.buffer0),
1 if self.raw.channel_num >= 2 && !self.raw.buffer1.is_null() => Some(self.raw.buffer1),
_ => None,
}
}
}
impl CacheAudioWriteGuard {
pub fn sample_num(&self) -> usize {
self.len
}
pub fn channel_num(&self) -> usize {
self.raw.channel_num as usize
}
pub fn channel(&self, channel: crate::filter::AudioChannel) -> Option<&[f32]> {
let buffer = self.channel_ptr(channel)?;
Some(unsafe { std::slice::from_raw_parts(buffer, self.len) })
}
pub fn channel_mut(&mut self, channel: crate::filter::AudioChannel) -> Option<&mut [f32]> {
let buffer = self.channel_ptr_mut(channel)?;
Some(unsafe { std::slice::from_raw_parts_mut(buffer, self.len) })
}
fn channel_ptr(&self, channel: crate::filter::AudioChannel) -> Option<*const f32> {
match i32::from(channel) {
0 if !self.raw.buffer0.is_null() => Some(self.raw.buffer0),
1 if self.raw.channel_num >= 2 && !self.raw.buffer1.is_null() => Some(self.raw.buffer1),
_ => None,
}
}
fn channel_ptr_mut(&mut self, channel: crate::filter::AudioChannel) -> Option<*mut f32> {
match i32::from(channel) {
0 if !self.raw.buffer0.is_null() => Some(self.raw.buffer0),
1 if self.raw.channel_num >= 2 && !self.raw.buffer1.is_null() => Some(self.raw.buffer1),
_ => None,
}
}
}
impl CacheFileImageReadGuard {
pub fn width(&self) -> usize {
self.raw.width as usize
}
pub fn height(&self) -> usize {
self.raw.height as usize
}
pub fn pitch(&self) -> usize {
self.raw.pitch as usize
}
pub fn format(&self) -> CacheImagePixelFormat {
self.raw.format.into()
}
pub fn as_u8_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.raw.buffer as *const u8, self.byte_len) }
}
pub fn as_ptr(&self) -> *const c_void {
self.raw.buffer
}
}
pub fn get_image_cache(
identifier: &impl AsCacheIdentifier,
name: &str,
) -> Result<Option<CacheImageReadGuard>, CacheError> {
let wide_name = CWString::new(name)?;
let identifier = identifier.as_cache_identifier();
let raw = with_cache_handle(|handle| unsafe {
((*handle).get_image_cache)(identifier, wide_name.as_ptr())
})?;
if raw.buffer.is_null() {
Ok(None)
} else {
Ok(Some(image_read_guard(raw)?))
}
}
pub fn get_image_cache_mut(
identifier: &impl AsCacheIdentifier,
name: &str,
) -> Result<Option<CacheImageWriteGuard>, CacheError> {
let wide_name = CWString::new(name)?;
let identifier = identifier.as_cache_identifier();
let raw = with_cache_handle(|handle| unsafe {
((*handle).get_image_cache)(identifier, wide_name.as_ptr())
})?;
if raw.buffer.is_null() {
Ok(None)
} else {
Ok(Some(image_write_guard(raw)?))
}
}
pub fn create_image_cache(
identifier: &impl AsCacheIdentifier,
name: &str,
width: usize,
height: usize,
) -> Result<CacheImageWriteGuard, CacheError> {
let wide_name = CWString::new(name)?;
let width = i32::try_from(width).map_err(|_| CacheError::ValueOutOfRange)?;
let height = i32::try_from(height).map_err(|_| CacheError::ValueOutOfRange)?;
let identifier = identifier.as_cache_identifier();
let raw = with_cache_handle(|handle| unsafe {
((*handle).create_image_cache)(identifier, wide_name.as_ptr(), width, height)
})?;
image_write_guard(raw)
}
pub fn get_audio_cache(
identifier: &impl AsCacheIdentifier,
name: &str,
) -> Result<Option<CacheAudioReadGuard>, CacheError> {
let wide_name = CWString::new(name)?;
let identifier = identifier.as_cache_identifier();
let raw = with_cache_handle(|handle| unsafe {
((*handle).get_audio_cache)(identifier, wide_name.as_ptr())
})?;
if raw.buffer0.is_null() {
Ok(None)
} else {
Ok(Some(audio_read_guard(raw)?))
}
}
pub fn get_audio_cache_mut(
identifier: &impl AsCacheIdentifier,
name: &str,
) -> Result<Option<CacheAudioWriteGuard>, CacheError> {
let wide_name = CWString::new(name)?;
let identifier = identifier.as_cache_identifier();
let raw = with_cache_handle(|handle| unsafe {
((*handle).get_audio_cache)(identifier, wide_name.as_ptr())
})?;
if raw.buffer0.is_null() {
Ok(None)
} else {
Ok(Some(audio_write_guard(raw)?))
}
}
pub fn create_audio_cache(
identifier: &impl AsCacheIdentifier,
name: &str,
sample_num: usize,
channel_num: usize,
) -> Result<CacheAudioWriteGuard, CacheError> {
if !(1..=2).contains(&channel_num) {
return Err(CacheError::InvalidAudioChannel);
}
let wide_name = CWString::new(name)?;
let sample_num = i32::try_from(sample_num).map_err(|_| CacheError::ValueOutOfRange)?;
let channel_num = i32::try_from(channel_num).map_err(|_| CacheError::ValueOutOfRange)?;
let identifier = identifier.as_cache_identifier();
let raw = with_cache_handle(|handle| unsafe {
((*handle).create_audio_cache)(identifier, wide_name.as_ptr(), sample_num, channel_num)
})?;
audio_write_guard(raw)
}
pub fn clear_image_cache(
identifier: &impl AsCacheIdentifier,
name: &str,
) -> Result<(), CacheError> {
let wide_name = CWString::new(name)?;
let identifier = identifier.as_cache_identifier();
with_cache_handle(|handle| unsafe {
((*handle).clear_image_cache)(identifier, wide_name.as_ptr())
})?;
Ok(())
}
pub fn clear_audio_cache(
identifier: &impl AsCacheIdentifier,
name: &str,
) -> Result<(), CacheError> {
let wide_name = CWString::new(name)?;
let identifier = identifier.as_cache_identifier();
with_cache_handle(|handle| unsafe {
((*handle).clear_audio_cache)(identifier, wide_name.as_ptr())
})?;
Ok(())
}
pub fn get_video_file_info(path: impl AsRef<Path>) -> Result<Option<VideoFileInfo>, CacheError> {
let file = CWString::new(&path.as_ref().to_string_lossy())?;
let mut raw = std::mem::MaybeUninit::<aviutl2_sys::cache2::VIDEO_INFO>::uninit();
let success = with_cache_handle(|handle| unsafe {
((*handle).get_video_file_info)(
file.as_ptr(),
raw.as_mut_ptr(),
std::mem::size_of::<aviutl2_sys::cache2::VIDEO_INFO>() as i32,
)
})?;
if !success {
return Ok(None);
}
let raw = unsafe { raw.assume_init() };
Ok(Some(VideoFileInfo {
total_time: raw.total_time,
frame_num: usize::try_from(raw.frame_num).map_err(|_| CacheError::ValueOutOfRange)?,
track_num: usize::try_from(raw.track_num).map_err(|_| CacheError::ValueOutOfRange)?,
width: usize::try_from(raw.width).map_err(|_| CacheError::ValueOutOfRange)?,
height: usize::try_from(raw.height).map_err(|_| CacheError::ValueOutOfRange)?,
fps: Rational32::new(raw.rate, raw.scale),
}))
}
pub fn get_audio_file_info(path: impl AsRef<Path>) -> Result<Option<AudioFileInfo>, CacheError> {
let file = CWString::new(&path.as_ref().to_string_lossy())?;
let mut raw = std::mem::MaybeUninit::<aviutl2_sys::cache2::AUDIO_INFO>::uninit();
let success = with_cache_handle(|handle| unsafe {
((*handle).get_audio_file_info)(
file.as_ptr(),
raw.as_mut_ptr(),
std::mem::size_of::<aviutl2_sys::cache2::AUDIO_INFO>() as i32,
)
})?;
if !success {
return Ok(None);
}
let raw = unsafe { raw.assume_init() };
Ok(Some(AudioFileInfo {
total_time: raw.total_time,
sample_num: usize::try_from(raw.sample_num).map_err(|_| CacheError::ValueOutOfRange)?,
track_num: usize::try_from(raw.track_num).map_err(|_| CacheError::ValueOutOfRange)?,
rate: raw.rate,
channel: usize::try_from(raw.channel).map_err(|_| CacheError::ValueOutOfRange)?,
}))
}
pub fn get_image_file_cache(
path: impl AsRef<Path>,
) -> Result<Option<CacheFileImageReadGuard>, CacheError> {
let file = CWString::new(&path.as_ref().to_string_lossy())?;
let raw =
with_cache_handle(|handle| unsafe { ((*handle).get_image_file_cache)(file.as_ptr()) })?;
if raw.buffer.is_null() {
Ok(None)
} else {
Ok(Some(file_image_read_guard(raw)?))
}
}
pub fn get_video_file_cache(
path: impl AsRef<Path>,
track: usize,
frame: usize,
) -> Result<Option<CacheFileImageReadGuard>, CacheError> {
let file = CWString::new(&path.as_ref().to_string_lossy())?;
let track = i32::try_from(track).map_err(|_| CacheError::ValueOutOfRange)?;
let frame = i32::try_from(frame).map_err(|_| CacheError::ValueOutOfRange)?;
let raw = with_cache_handle(|handle| unsafe {
((*handle).get_video_file_cache)(file.as_ptr(), track, frame)
})?;
if raw.buffer.is_null() {
Ok(None)
} else {
Ok(Some(file_image_read_guard(raw)?))
}
}
pub fn get_video_file_cache_by_time(
path: impl AsRef<Path>,
track: usize,
time: f64,
) -> Result<Option<CacheFileImageReadGuard>, CacheError> {
let file = CWString::new(&path.as_ref().to_string_lossy())?;
let track = i32::try_from(track).map_err(|_| CacheError::ValueOutOfRange)?;
let raw = with_cache_handle(|handle| unsafe {
((*handle).get_video_file_cache_by_time)(file.as_ptr(), track, time)
})?;
if raw.buffer.is_null() {
Ok(None)
} else {
Ok(Some(file_image_read_guard(raw)?))
}
}
pub fn get_audio_file_data(
path: impl AsRef<Path>,
track: usize,
sample_index: usize,
buffer0: &mut [f32],
buffer1: &mut [f32],
) -> Result<usize, CacheError> {
assert_eq!(
buffer0.len(),
buffer1.len(),
"Audio buffers must have the same length"
);
let file = CWString::new(&path.as_ref().to_string_lossy())?;
let track = i32::try_from(track).map_err(|_| CacheError::ValueOutOfRange)?;
let sample_index = i64::try_from(sample_index).map_err(|_| CacheError::ValueOutOfRange)?;
let sample_num = i32::try_from(buffer0.len()).map_err(|_| CacheError::ValueOutOfRange)?;
let read = with_cache_handle(|handle| unsafe {
((*handle).get_audio_file_data)(
file.as_ptr(),
track,
sample_index,
sample_num,
buffer0.as_mut_ptr(),
buffer1.as_mut_ptr(),
)
})?;
usize::try_from(read).map_err(|_| CacheError::ValueOutOfRange)
}
fn image_read_guard(
raw: aviutl2_sys::cache2::CACHE_IMAGE,
) -> Result<CacheImageReadGuard, CacheError> {
let len = image_len(&raw)?;
let access = CacheAccessGuard::read(raw.buffer as usize);
Ok(CacheImageReadGuard {
_access: access,
raw,
len,
})
}
fn image_write_guard(
raw: aviutl2_sys::cache2::CACHE_IMAGE,
) -> Result<CacheImageWriteGuard, CacheError> {
if raw.buffer.is_null() {
return Err(CacheError::ApiCallFailed);
}
let len = image_len(&raw)?;
let access = CacheAccessGuard::write(raw.buffer as usize);
Ok(CacheImageWriteGuard {
_access: access,
raw,
len,
})
}
fn audio_read_guard(
raw: aviutl2_sys::cache2::CACHE_AUDIO,
) -> Result<CacheAudioReadGuard, CacheError> {
let len = audio_len(&raw)?;
let access = CacheAccessGuard::read(raw.buffer0 as usize);
Ok(CacheAudioReadGuard {
_access: access,
raw,
len,
})
}
fn audio_write_guard(
raw: aviutl2_sys::cache2::CACHE_AUDIO,
) -> Result<CacheAudioWriteGuard, CacheError> {
if raw.buffer0.is_null() {
return Err(CacheError::ApiCallFailed);
}
let len = audio_len(&raw)?;
let access = CacheAccessGuard::write(raw.buffer0 as usize);
Ok(CacheAudioWriteGuard {
_access: access,
raw,
len,
})
}
fn file_image_read_guard(
raw: aviutl2_sys::cache2::CACHE_FILE_IMAGE,
) -> Result<CacheFileImageReadGuard, CacheError> {
let byte_len = file_image_byte_len(&raw)?;
let access = CacheAccessGuard::read(raw.buffer as usize);
Ok(CacheFileImageReadGuard {
_access: access,
raw,
byte_len,
})
}
fn image_len(raw: &aviutl2_sys::cache2::CACHE_IMAGE) -> Result<usize, CacheError> {
let width = usize::try_from(raw.width).map_err(|_| CacheError::ValueOutOfRange)?;
let height = usize::try_from(raw.height).map_err(|_| CacheError::ValueOutOfRange)?;
width.checked_mul(height).ok_or(CacheError::ValueOutOfRange)
}
fn file_image_byte_len(raw: &aviutl2_sys::cache2::CACHE_FILE_IMAGE) -> Result<usize, CacheError> {
let pitch = usize::try_from(raw.pitch).map_err(|_| CacheError::ValueOutOfRange)?;
let height = usize::try_from(raw.height).map_err(|_| CacheError::ValueOutOfRange)?;
pitch.checked_mul(height).ok_or(CacheError::ValueOutOfRange)
}
fn audio_len(raw: &aviutl2_sys::cache2::CACHE_AUDIO) -> Result<usize, CacheError> {
usize::try_from(raw.sample_num).map_err(|_| CacheError::ValueOutOfRange)
}
fn with_cache_handle<T>(
f: impl FnOnce(*mut aviutl2_sys::cache2::CACHE_HANDLE) -> T,
) -> Result<T, CacheError> {
let handle = CACHE_HANDLE.get().expect("Cache handle is not initialized");
let handle = handle.lock().unwrap();
if handle.0.is_null() {
panic!("Cache handle raw pointer is null");
}
Ok(f(handle.0))
}