use crate::error::{MediaInfoError, Result};
use libloading::{Library, Symbol};
use std::ffi::c_void;
use std::path::Path;
use std::sync::Arc;
#[cfg(unix)]
fn ensure_locale_initialized() {
use std::sync::OnceLock;
static INIT: OnceLock<()> = OnceLock::new();
INIT.get_or_init(|| unsafe {
libc::setlocale(libc::LC_CTYPE, c"".as_ptr());
});
}
#[cfg(not(unix))]
fn ensure_locale_initialized() {}
#[cfg(windows)]
use widestring::{U16CStr, U16CString};
#[cfg(not(windows))]
use widestring::{U32CStr, U32CString};
#[cfg(windows)]
type WCharPtr = *const u16;
#[cfg(not(windows))]
type WCharPtr = *const u32;
#[cfg(windows)]
type MediaInfoNew = unsafe extern "system" fn() -> *mut c_void;
#[cfg(not(windows))]
type MediaInfoNew = unsafe extern "C" fn() -> *mut c_void;
#[cfg(windows)]
type MediaInfoDelete = unsafe extern "system" fn(handle: *mut c_void);
#[cfg(not(windows))]
type MediaInfoDelete = unsafe extern "C" fn(handle: *mut c_void);
#[cfg(windows)]
type MediaInfoClose = unsafe extern "system" fn(handle: *mut c_void);
#[cfg(not(windows))]
type MediaInfoClose = unsafe extern "C" fn(handle: *mut c_void);
#[cfg(windows)]
type MediaInfoOpen = unsafe extern "system" fn(handle: *mut c_void, file: WCharPtr) -> usize;
#[cfg(not(windows))]
type MediaInfoOpen = unsafe extern "C" fn(handle: *mut c_void, file: WCharPtr) -> usize;
#[cfg(windows)]
type MediaInfoOption =
unsafe extern "system" fn(handle: *mut c_void, option: WCharPtr, value: WCharPtr) -> WCharPtr;
#[cfg(not(windows))]
type MediaInfoOption =
unsafe extern "C" fn(handle: *mut c_void, option: WCharPtr, value: WCharPtr) -> WCharPtr;
#[cfg(windows)]
type MediaInfoInform = unsafe extern "system" fn(handle: *mut c_void, reserved: usize) -> WCharPtr;
#[cfg(not(windows))]
type MediaInfoInform = unsafe extern "C" fn(handle: *mut c_void, reserved: usize) -> WCharPtr;
#[cfg(windows)]
type MediaInfoOpenBufferInit =
unsafe extern "system" fn(handle: *mut c_void, file_size: u64, file_offset: u64) -> usize;
#[cfg(not(windows))]
type MediaInfoOpenBufferInit =
unsafe extern "C" fn(handle: *mut c_void, file_size: u64, file_offset: u64) -> usize;
#[cfg(windows)]
type MediaInfoOpenBufferContinue =
unsafe extern "system" fn(handle: *mut c_void, buffer: *const u8, buffer_size: usize) -> usize;
#[cfg(not(windows))]
type MediaInfoOpenBufferContinue =
unsafe extern "C" fn(handle: *mut c_void, buffer: *const u8, buffer_size: usize) -> usize;
#[cfg(windows)]
type MediaInfoOpenBufferContinueGoToGet = unsafe extern "system" fn(handle: *mut c_void) -> u64;
#[cfg(not(windows))]
type MediaInfoOpenBufferContinueGoToGet = unsafe extern "C" fn(handle: *mut c_void) -> u64;
#[cfg(windows)]
type MediaInfoOpenBufferFinalize = unsafe extern "system" fn(handle: *mut c_void) -> usize;
#[cfg(not(windows))]
type MediaInfoOpenBufferFinalize = unsafe extern "C" fn(handle: *mut c_void) -> usize;
pub struct MediaInfoLib {
#[allow(dead_code)]
library: Library,
new: MediaInfoNew,
delete: MediaInfoDelete,
close: MediaInfoClose,
open: MediaInfoOpen,
option: MediaInfoOption,
inform: MediaInfoInform,
open_buffer_init: MediaInfoOpenBufferInit,
open_buffer_continue: MediaInfoOpenBufferContinue,
open_buffer_continue_goto_get: MediaInfoOpenBufferContinueGoToGet,
open_buffer_finalize: MediaInfoOpenBufferFinalize,
}
impl MediaInfoLib {
pub fn load<P: AsRef<Path>>(library_path: P) -> Result<Self> {
ensure_locale_initialized();
let path = library_path.as_ref();
let library = unsafe { Library::new(path) }.map_err(|e| {
MediaInfoError::library_not_found(&[path.display().to_string()], &[e.to_string()])
})?;
macro_rules! load_symbol {
($lib:expr, $name:literal, $type:ty) => {{
let sym: Symbol<$type> = unsafe { $lib.get($name) }.map_err(|e| {
MediaInfoError::library_not_found(
&[path.display().to_string()],
&[e.to_string()],
)
})?;
*sym
}};
}
let new_fn = load_symbol!(library, b"MediaInfo_New\0", MediaInfoNew);
let delete_fn = load_symbol!(library, b"MediaInfo_Delete\0", MediaInfoDelete);
let close_fn = load_symbol!(library, b"MediaInfo_Close\0", MediaInfoClose);
let open_fn = load_symbol!(library, b"MediaInfo_Open\0", MediaInfoOpen);
let option_fn = load_symbol!(library, b"MediaInfo_Option\0", MediaInfoOption);
let inform_fn = load_symbol!(library, b"MediaInfo_Inform\0", MediaInfoInform);
let open_buffer_init_fn = load_symbol!(
library,
b"MediaInfo_Open_Buffer_Init\0",
MediaInfoOpenBufferInit
);
let open_buffer_continue_fn = load_symbol!(
library,
b"MediaInfo_Open_Buffer_Continue\0",
MediaInfoOpenBufferContinue
);
let open_buffer_continue_goto_get_fn = load_symbol!(
library,
b"MediaInfo_Open_Buffer_Continue_GoTo_Get\0",
MediaInfoOpenBufferContinueGoToGet
);
let open_buffer_finalize_fn = load_symbol!(
library,
b"MediaInfo_Open_Buffer_Finalize\0",
MediaInfoOpenBufferFinalize
);
Ok(MediaInfoLib {
library,
new: new_fn,
delete: delete_fn,
close: close_fn,
open: open_fn,
option: option_fn,
inform: inform_fn,
open_buffer_init: open_buffer_init_fn,
open_buffer_continue: open_buffer_continue_fn,
open_buffer_continue_goto_get: open_buffer_continue_goto_get_fn,
open_buffer_finalize: open_buffer_finalize_fn,
})
}
pub fn load_from_paths(paths: &[std::path::PathBuf]) -> Result<Self> {
let mut attempted_paths = Vec::new();
let mut errors = Vec::new();
for path in paths {
match Self::load(path) {
Ok(lib) => return Ok(lib),
Err(e) => {
attempted_paths.push(path.display().to_string());
let message = match e {
MediaInfoError::LibraryNotFound {
errors: error_message,
..
} => error_message,
other => other.to_string(),
};
errors.push(message);
}
}
}
Err(MediaInfoError::library_not_found(&attempted_paths, &errors))
}
pub fn new_handle(&self) -> *mut c_void {
unsafe { (self.new)() }
}
pub fn delete_handle(&self, handle: *mut c_void) {
unsafe { (self.delete)(handle) }
}
pub fn close_handle(&self, handle: *mut c_void) {
unsafe { (self.close)(handle) }
}
pub fn open(&self, handle: *mut c_void, filename: &str) -> usize {
let wchar_filename = str_to_wchar(filename);
unsafe { (self.open)(handle, wchar_filename.as_ptr()) }
}
pub fn option(&self, handle: *mut c_void, option_name: &str, option_value: &str) -> String {
let wchar_option = str_to_wchar(option_name);
let wchar_value = str_to_wchar(option_value);
let result = unsafe { (self.option)(handle, wchar_option.as_ptr(), wchar_value.as_ptr()) };
wchar_ptr_to_string(result)
}
pub fn inform(&self, handle: *mut c_void) -> String {
let result = unsafe { (self.inform)(handle, 0) };
wchar_ptr_to_string(result)
}
pub fn open_buffer_init(&self, handle: *mut c_void, file_size: u64, file_offset: u64) -> usize {
unsafe { (self.open_buffer_init)(handle, file_size, file_offset) }
}
pub fn open_buffer_continue(&self, handle: *mut c_void, buffer: &[u8]) -> usize {
unsafe { (self.open_buffer_continue)(handle, buffer.as_ptr(), buffer.len()) }
}
pub fn open_buffer_continue_goto_get(&self, handle: *mut c_void) -> u64 {
unsafe { (self.open_buffer_continue_goto_get)(handle) }
}
pub fn open_buffer_finalize(&self, handle: *mut c_void) -> usize {
unsafe { (self.open_buffer_finalize)(handle) }
}
}
pub struct MediaInfoHandle {
lib: Arc<MediaInfoLib>,
handle: *mut c_void,
}
impl MediaInfoHandle {
pub fn new(lib: Arc<MediaInfoLib>) -> Self {
let handle = lib.new_handle();
MediaInfoHandle { lib, handle }
}
#[allow(dead_code)]
pub fn as_ptr(&self) -> *mut c_void {
self.handle
}
pub fn open(&self, filename: &str) -> usize {
self.lib.open(self.handle, filename)
}
pub fn option(&self, option_name: &str, option_value: &str) -> String {
self.lib.option(self.handle, option_name, option_value)
}
pub fn inform(&self) -> String {
self.lib.inform(self.handle)
}
pub fn open_buffer_init(&self, file_size: u64, file_offset: u64) -> usize {
self.lib
.open_buffer_init(self.handle, file_size, file_offset)
}
pub fn open_buffer_continue(&self, buffer: &[u8]) -> usize {
self.lib.open_buffer_continue(self.handle, buffer)
}
pub fn open_buffer_continue_goto_get(&self) -> u64 {
self.lib.open_buffer_continue_goto_get(self.handle)
}
pub fn open_buffer_finalize(&self) -> usize {
self.lib.open_buffer_finalize(self.handle)
}
#[allow(dead_code)]
pub fn close(&self) {
self.lib.close_handle(self.handle);
}
}
impl Drop for MediaInfoHandle {
fn drop(&mut self) {
self.lib.close_handle(self.handle);
self.lib.delete_handle(self.handle);
}
}
#[cfg(windows)]
fn str_to_wchar(s: &str) -> U16CString {
U16CString::from_str(s).unwrap_or_else(|_| U16CString::default())
}
#[cfg(not(windows))]
fn str_to_wchar(s: &str) -> U32CString {
U32CString::from_str(s).unwrap_or_else(|_| U32CString::default())
}
#[cfg(windows)]
fn wchar_ptr_to_string(ptr: WCharPtr) -> String {
if ptr.is_null() {
return String::new();
}
unsafe { U16CStr::from_ptr_str(ptr).to_string_lossy() }
}
#[cfg(not(windows))]
fn wchar_ptr_to_string(ptr: WCharPtr) -> String {
if ptr.is_null() {
return String::new();
}
unsafe { U32CStr::from_ptr_str(ptr).to_string_lossy() }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_str_to_wchar_empty() {
let wchar = str_to_wchar("");
assert_eq!(wchar.len(), 0);
}
#[test]
fn test_str_to_wchar_ascii() {
let wchar = str_to_wchar("hello");
assert_eq!(wchar.len(), 5);
}
#[test]
fn test_str_to_wchar_unicode() {
let wchar = str_to_wchar("accentué");
assert!(!wchar.is_empty());
}
#[test]
fn test_wchar_ptr_null() {
let result = wchar_ptr_to_string(std::ptr::null());
assert!(result.is_empty());
}
}