From 8ef00def87d93d19ad028a24456987914134980e Mon Sep 17 00:00:00 2001 From: lifegpc Date: Fri, 18 Mar 2022 12:37:04 +0800 Subject: [PATCH] add avdict --- Cargo.lock | 7 + Cargo.toml | 3 + avdict/.gitignore | 1 + avdict/CMakeLists.txt | 23 ++ avdict/avdict.h | 30 ++ avdict/src/avdict.c | 58 ++++ build.rs | 56 +++- cmake/FindAVUTIL.cmake | 29 ++ {exif/cmake => cmake}/FindExiv2.cmake | 0 cmake/GetLinkLibraries.cmake | 31 ++ exif/CMakeLists.txt | 4 +- src/_avdict.rs | 6 + src/avdict.rs | 389 ++++++++++++++++++++++++++ src/exif.rs | 7 +- src/ext/cstr.rs | 75 +++++ src/ext/flagset.rs | 30 ++ src/ext/mod.rs | 5 + src/ext/rawhandle.rs | 5 + src/main.rs | 9 + ugoira/.gitignore | 1 + 20 files changed, 756 insertions(+), 13 deletions(-) create mode 100644 avdict/.gitignore create mode 100644 avdict/CMakeLists.txt create mode 100644 avdict/avdict.h create mode 100644 avdict/src/avdict.c create mode 100644 cmake/FindAVUTIL.cmake rename {exif/cmake => cmake}/FindExiv2.cmake (100%) create mode 100644 cmake/GetLinkLibraries.cmake create mode 100644 src/_avdict.rs create mode 100644 src/avdict.rs create mode 100644 src/ext/cstr.rs create mode 100644 src/ext/flagset.rs create mode 100644 src/ext/mod.rs create mode 100644 src/ext/rawhandle.rs create mode 100644 ugoira/.gitignore diff --git a/Cargo.lock b/Cargo.lock index 8a10636..a0e23f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -441,6 +441,12 @@ dependencies = [ "instant", ] +[[package]] +name = "flagset" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda653ca797810c02f7ca4b804b40b8b95ae046eb989d356bce17919a8c25499" + [[package]] name = "flate2" version = "1.0.22" @@ -1096,6 +1102,7 @@ dependencies = [ "cmake", "dateparser", "derive_more", + "flagset", "futures-util", "getopts", "gettext", diff --git a/Cargo.toml b/Cargo.toml index 9bf26e3..6a0a1f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ c_fixed_string = { version = "0.2", optional = true } chrono = "0.4" dateparser = "0.1.6" derive_more = "0.99" +flagset = { version = "0.4", optional = true } futures-util = "0.3" getopts = "0.2" gettext = "0.4" @@ -30,7 +31,9 @@ bindgen = { version = "0.59", optional = true } cmake = { version = "0.1", optional = true } [features] +avdict = ["bindgen", "cmake", "flagset"] exif = ["bindgen", "c_fixed_string", "cmake", "link-cplusplus", "int-enum", "utf16string"] +ugoira = ["avdict", "bindgen", "cmake"] [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.9", features = ["winnls", "stringapiset"] } diff --git a/avdict/.gitignore b/avdict/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/avdict/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/avdict/CMakeLists.txt b/avdict/CMakeLists.txt new file mode 100644 index 0000000..f409c13 --- /dev/null +++ b/avdict/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.9) + +project(avdict) + +if (MSVC) + add_compile_options(/utf-8) +endif() + +include(GNUInstallDirs) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake") +include(GetLinkLibraries) +find_package(AVUTIL REQUIRED) + +add_library(avdict STATIC avdict.h src/avdict.c) +target_link_libraries(avdict AVUTIL::AVUTIL) +target_compile_definitions(avdict PRIVATE -DBUILD_AVDICT) + +get_link_libraries(OUT avdict) +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/avdict_dep.txt" "${OUT}") + +install(TARGETS avdict) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/avdict_dep.txt" DESTINATION ${CMAKE_INSTALL_PREFIX}) diff --git a/avdict/avdict.h b/avdict/avdict.h new file mode 100644 index 0000000..15fbeb4 --- /dev/null +++ b/avdict/avdict.h @@ -0,0 +1,30 @@ +#ifndef _AVDICT_H +#define _AVDICT_H +#include +#ifndef BUILD_AVDICT +#define AV_DICT_MATCH_CASE 1 +#define AV_DICT_IGNORE_SUFFIX 2 +#define AV_DICT_DONT_STRDUP_KEY 4 +#define AV_DICT_DONT_STRDUP_VAL 8 +#define AV_DICT_DONT_OVERWRITE 16 +#define AV_DICT_APPEND 32 +#define AV_DICT_MULTIKEY 64 +#endif +///
+typedef struct AVDict AVDict; +typedef struct AVDictEntry { + char* key; + char* value; +} AVDictEntry; +AVDictEntry* avdict_get(const AVDict* m, const char* key, const AVDictEntry* prev, int flags); +int avdict_count(const AVDict* m); +int avdict_set(AVDict** pm, const char* key, const char* value, int flags); +int avdict_copy(AVDict** dst, const AVDict* src, int flags); +void avdict_free(AVDict** m); +void avdict_mfree(void* data); +char* avdict_get_errmsg(int code); +int avdict_set_int(AVDict** pm, const char* key, int64_t value, int flags); +int avdict_parse_string(AVDict** pm, const char* str, const char* key_val_sep, const char* pairs_sep, int flags); +void avdict_avfree(void* data); +int avdict_get_string(const AVDict* m, char** buffer, const char key_val_sep, const char pairs_sep); +#endif diff --git a/avdict/src/avdict.c b/avdict/src/avdict.c new file mode 100644 index 0000000..a1b5c50 --- /dev/null +++ b/avdict/src/avdict.c @@ -0,0 +1,58 @@ +#include "../avdict.h" +#include "libavutil/avutil.h" +#include "libavutil/dict.h" + +#include + +struct AVDictionary { + int count; + AVDictionaryEntry* elems; +}; + +AVDictEntry* avdict_get(const AVDict* m, const char* key, const AVDictEntry* prev, int flags) { + return (AVDictEntry*)av_dict_get((const AVDictionary*)m, key, (const AVDictionaryEntry*)prev, flags); +} + +int avdict_count(const AVDict* m) { + return av_dict_count((const AVDictionary*)m); +} + +int avdict_set(AVDict** pm, const char* key, const char* value, int flags) { + return av_dict_set((AVDictionary**)pm, key, value, flags); +} + +int avdict_copy(AVDict** dst, const AVDict* src, int flags) { + return av_dict_copy((AVDictionary**)dst, (const AVDictionary*)src, flags); +} + +void avdict_free(AVDict** m) { + av_dict_free((AVDictionary**)m); +} + +void avdict_mfree(void* data) { + if (!data) return; + free(data); +} + +char* avdict_get_errmsg(int code) { + char* msg = malloc(AV_ERROR_MAX_STRING_SIZE); + if (!msg) return NULL; + return av_make_error_string(msg, AV_ERROR_MAX_STRING_SIZE, code); +} + +int avdict_set_int(AVDict** pm, const char* key, int64_t value, int flags) { + return av_dict_set_int((AVDictionary**)pm, key, value, flags); +} + +int avdict_parse_string(AVDict** pm, const char* str, const char* key_val_sep, const char* pairs_sep, int flags) { + return av_dict_parse_string((AVDictionary**)pm, str, key_val_sep, pairs_sep, flags); +} + +void avdict_avfree(void* data) { + if (!data) return; + av_free(data); +} + +int avdict_get_string(const AVDict* m, char** buffer, const char key_val_sep, const char pairs_sep) { + return av_dict_get_string((const AVDictionary*)m, buffer, key_val_sep, pairs_sep); +} diff --git a/build.rs b/build.rs index 37586e0..6f943a6 100644 --- a/build.rs +++ b/build.rs @@ -3,15 +3,15 @@ extern crate bindgen; #[cfg(feature = "cmake")] extern crate cmake; -#[cfg(any(feature = "exif"))] +#[cfg(any(feature = "avdict", feature = "exif"))] use std::env; -#[cfg(any(feature = "exif"))] +#[cfg(any(feature = "avdict", feature = "exif"))] use std::fs::create_dir; -#[cfg(any(feature = "exif"))] +#[cfg(any(feature = "avdict", feature = "exif"))] use std::fs::File; -#[cfg(any(feature = "exif"))] +#[cfg(any(feature = "avdict", feature = "exif"))] use std::io::Read; -#[cfg(any(feature = "exif"))] +#[cfg(any(feature = "avdict", feature = "exif"))] use std::path::PathBuf; fn main() { @@ -51,6 +51,52 @@ fn main() { } println!("cargo:rerun-if-changed=utils/"); } + #[cfg(feature = "avdict")] + { + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let avdict_build_path = out_path.join("avdict"); + if !avdict_build_path.exists() { + create_dir(&avdict_build_path).unwrap(); + } + cmake::Config::new("avdict") + .define("CMAKE_INSTALL_PREFIX", out_path.to_str().unwrap()) + .out_dir(avdict_build_path) + .define("CMAKE_BUILD_TYPE", "Release") + .build(); + println!("cargo:rustc-link-search=native={}/lib", out_path.display()); + let dep_path = out_path.join("avdict_dep.txt"); + let mut f = File::open(dep_path).unwrap(); + let mut s = String::from(""); + f.read_to_string(&mut s).unwrap(); + println!("cargo:rustc-link-lib=static=avdict"); + let l: Vec<&str> = s.split(";").collect(); + for i in l.iter() { + let mut p = PathBuf::from(i); + let p2 = p.clone(); + let file_name = p2.file_stem().unwrap(); + let file_name = file_name.to_str().unwrap(); + let file_name = file_name.trim_start_matches("lib"); + p.pop(); + println!("cargo:rustc-link-search={}", p.to_str().unwrap()); + println!("cargo:rustc-link-lib={}", file_name); + } + println!("cargo:rerun-if-changed=avdict/"); + let bindings = bindgen::Builder::default() + // The input header we would like to generate + // bindings for. + .header("avdict/avdict.h") + // Tell cargo to invalidate the built crate whenever any of the + // included header files changed. + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + // Finish the builder and generate the bindings. + .generate() + // Unwrap the Result and panic on failure. + .expect("Unable to generate bindings"); + + bindings + .write_to_file(out_path.join("avdict.rs")) + .expect("Couldn't write bindings!"); + } #[cfg(feature = "exif")] { let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); diff --git a/cmake/FindAVUTIL.cmake b/cmake/FindAVUTIL.cmake new file mode 100644 index 0000000..d2d5363 --- /dev/null +++ b/cmake/FindAVUTIL.cmake @@ -0,0 +1,29 @@ +find_package(PkgConfig) +if (PkgConfig_FOUND) + pkg_check_modules(PC_AVUTIL QUIET IMPORTED_TARGET GLOBAL libavutil) +endif() + +if (PC_AVUTIL_FOUND) + set(AVUTIL_FOUND TRUE) + set(AVUTIL_VERSION ${PC_AVUTIL_VERSION}) + set(AVUTIL_VERSION_STRING ${PC_AVUTIL_STRING}) + set(AVUTIL_LIBRARYS ${PC_AVUTIL_LIBRARIES}) + if (USE_STATIC_LIBS) + set(AVUTIL_INCLUDE_DIRS ${PC_AVUTIL_STATIC_INCLUDE_DIRS}) + else() + set(AVUTIL_INCLUDE_DIRS ${PC_AVUTIL_INCLUDE_DIRS}) + endif() + if (NOT TARGET AVUTIL::AVUTIL) + add_library(AVUTIL::AVUTIL ALIAS PkgConfig::PC_AVUTIL) + endif() +else() + message(FATAL_ERROR "failed to find libavutil.") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(AVUTIL + FOUND_VAR AVUTIL_FOUND + REQUIRED_VARS + AVUTIL_LIBRARYS + VERSION_VAR AVUTIL_VERSION +) diff --git a/exif/cmake/FindExiv2.cmake b/cmake/FindExiv2.cmake similarity index 100% rename from exif/cmake/FindExiv2.cmake rename to cmake/FindExiv2.cmake diff --git a/cmake/GetLinkLibraries.cmake b/cmake/GetLinkLibraries.cmake new file mode 100644 index 0000000..50c746a --- /dev/null +++ b/cmake/GetLinkLibraries.cmake @@ -0,0 +1,31 @@ +function(get_link_libraries OUTPUT_LIST TARGET) + get_target_property(IMPORTED ${TARGET} IMPORTED) + list(APPEND VISITED_TARGETS ${TARGET}) + if (IMPORTED) + get_target_property(LIBS ${TARGET} INTERFACE_LINK_LIBRARIES) + else() + get_target_property(LIBS ${TARGET} LINK_LIBRARIES) + endif() + set(LIB_FILES "") + foreach(LIB ${LIBS}) + if (TARGET ${LIB}) + list(FIND VISITED_TARGETS ${LIB} VISITED) + if (${VISITED} EQUAL -1) + get_target_property(LIB_FILE ${LIB} LOCATION) + if (NOT LIB_FILE) + get_target_property(LIB_FILE ${LIB} IMPORTED_LOCATION) + endif() + if (NOT LIB_FILE) + get_target_property(LIB_FILE ${LIB} IMPORTED_IMPLIB) + endif() + if (NOT LIB_FILE) + get_target_property(LIB_FILE ${LIB} INTERFACE_LINK_LIBRARIES) + endif() + get_link_libraries(LINK_LIB_FILES ${LIB}) + list(APPEND LIB_FILES ${LIB_FILE} ${LINK_LIB_FILES}) + endif() + endif() + endforeach() + set(VISITED_TARGETS ${VISITED_TARGETS} PARENT_SCOPE) + set(${OUTPUT_LIST} ${LIB_FILES} PARENT_SCOPE) +endfunction() diff --git a/exif/CMakeLists.txt b/exif/CMakeLists.txt index 0217189..6c0966c 100644 --- a/exif/CMakeLists.txt +++ b/exif/CMakeLists.txt @@ -20,8 +20,8 @@ endif() include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../utils") -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -find_package(Exiv2) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../cmake") +find_package(Exiv2 REQUIRED) include_directories(${Exiv2_INCLUDE_DIRS}) include_directories("${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/src/_avdict.rs b/src/_avdict.rs new file mode 100644 index 0000000..7f76176 --- /dev/null +++ b/src/_avdict.rs @@ -0,0 +1,6 @@ +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(dead_code)] +#![allow(deref_nullptr)] +#![allow(non_upper_case_globals)] +include!(concat!(env!("OUT_DIR"), "/avdict.rs")); diff --git a/src/avdict.rs b/src/avdict.rs new file mode 100644 index 0000000..c271975 --- /dev/null +++ b/src/avdict.rs @@ -0,0 +1,389 @@ +use crate::_avdict; +use crate::ext::cstr::{ToCStr, ToCStrError}; +use crate::ext::flagset::ToFlagSet; +use crate::ext::rawhandle::ToRawHandle; +use crate::gettext; +use std::collections::HashMap; +use std::convert::AsMut; +use std::convert::AsRef; +use std::convert::TryFrom; +use std::default::Default; +use std::ffi::CStr; +use std::ffi::CString; +use std::fmt::Display; +use std::iter::Iterator; +use std::ops::Drop; +use std::os::raw::c_char; +use std::os::raw::c_int; +use std::str::Utf8Error; + +#[derive(Debug, derive_more::From, PartialEq)] +pub enum AVDictError { + String(String), + Utf8Error(Utf8Error), + CodeError(AVDictCodeError), + ToCstr(ToCStrError) +} + +impl From<&str> for AVDictError { + fn from(s: &str) -> Self { + Self::String(String::from(s)) + } +} + +impl From for AVDictError { + fn from(i: c_int) -> Self { + Self::CodeError(AVDictCodeError::from(i)) + } +} + +impl Display for AVDictError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::String(s) => { f.write_str(s) } + Self::Utf8Error(s) => { f.write_fmt(format_args!("{} {}", gettext("Failed to decode string with UTF-8:"), s)) } + Self::CodeError(s) => { s.fmt(f) } + Self::ToCstr(s) => { s.fmt(f) } + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +pub struct AVDictCodeError { + err: c_int, +} + +impl AVDictCodeError { + pub fn to_str(&self) -> Result { + let s = unsafe { _avdict::avdict_get_errmsg(self.err) }; + if s.is_null() { + Err(gettext("Out of memory."))?; + } + let ss = unsafe { CStr::from_ptr(s) }; + let ss = ss.to_owned(); + unsafe { _avdict::avdict_mfree(s as *mut std::os::raw::c_void) }; + let re = ss.to_str()?; + Ok(String::from(re)) + } +} + +impl Display for AVDictCodeError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.to_str() { + Ok(s) => { + f.write_str(s.as_str()) + } + Err(e) => { + f.write_fmt(format_args!("{} {}", gettext("Failed to get error message:"), e)) + } + } + } +} + +impl From for AVDictCodeError { + fn from(i: c_int) -> Self { + Self { + err: i, + } + } +} + +flagset::flags! { + pub enum AVDictFlags: c_int { + /// Only get an entry with exact-case key match. + MatchCase = _avdict::AV_DICT_MATCH_CASE as c_int, + /// Return first entry in a dictionary whose first part corresponds to the search key, + /// ignoring the suffix of the found key string. + IgnoreSuffix = _avdict::AV_DICT_IGNORE_SUFFIX as c_int, + /// Take ownership of a key that's been allocated + DontStrdupKey = _avdict::AV_DICT_DONT_STRDUP_KEY as c_int, + /// Take ownership of a value that's been allocated + DontStrdupVal = _avdict::AV_DICT_DONT_STRDUP_VAL as c_int, + /// Don't overwrite existing entries. + DontOverwrite = _avdict::AV_DICT_DONT_OVERWRITE as c_int, + /// If the entry already exists, append to it. + Append = _avdict::AV_DICT_APPEND as c_int, + /// Allow to store several equal keys in the dictionary. + Multikey = _avdict::AV_DICT_MULTIKEY as c_int, + } +} + +pub struct AVDict { + m: *mut _avdict::AVDict, +} + +#[allow(dead_code)] +impl AVDict { + pub fn new() -> Self { + Self { + m: 0 as *mut _avdict::AVDict, + } + } + + pub fn copy>(&self, flags: T) -> Result { + if self.m.is_null() { + return Ok(Self::new()); + } + let mut m = 0 as *mut _avdict::AVDict; + let pm: *mut *mut _avdict::AVDict = &mut m; + let re = unsafe { _avdict::avdict_copy(pm, self.m, flags.to_bits()) }; + if re != 0 { + Err(re)?; + } + Ok(Self { + m, + }) + } + + pub fn from_map>(&mut self, maps: &HashMap, flags: F) -> Result<(), AVDictError> { + let flags = flags.to_flag_set(); + for (k, v) in maps { + self.set(k, v, flags)?; + } + Ok(()) + } + + pub fn get>(&self, key: K, flags: F) -> Result, AVDictError> { + if self.m.is_null() { + return Ok(None); + } + let k = key.to_cstr()?; + let re = unsafe { _avdict::avdict_get(self.m, k.as_ptr(), 0 as *mut _avdict::AVDictEntry, flags.to_bits()) }; + if !re.is_null() && unsafe { !(*re).value.is_null() } { + let s = unsafe { CStr::from_ptr((*re).value) }; + let s = s.to_owned(); + return Ok(Some(s)); + } + Ok(None) + } + + pub fn get_all>(&self, key: K, flags: F) -> Result>, AVDictError> { + if self.m.is_null() { + return Ok(None); + } + let k = key.to_cstr()?; + let mut re = unsafe { _avdict::avdict_get(self.m, k.as_ptr(), 0 as *mut _avdict::AVDictEntry, flags.to_bits()) }; + let mut l = Vec::new(); + while !re.is_null() { + if unsafe { (*re).value.is_null() } { + Err(gettext("Failed to get value for entry."))?; + } + let s = unsafe { CStr::from_ptr((*re).value) }; + let s = s.to_owned(); + l.push(s); + re = unsafe { _avdict::avdict_get(self.m, k.as_ptr(), re, flags.to_bits()) }; + } + if l.len() > 0 { + return Ok(Some(l)); + } + Ok(None) + } + + /// Get dictionary entries as a string. + /// + /// Return a string containing dictionary's entries. + /// * `key_val_sep` - character used to separate key from value + /// * `pairs_sep` - character used to separate two pairs from each other + /// # Note + /// String is escaped with backslashes (`\`). + /// # Warning + /// Separators cannot be neither `\` nor `\0`. They also cannot be the same. + pub fn get_string(&self, key_val_sep: char, pairs_sep: char) -> Result { + let mut buf = 0 as *mut c_char; + let pbuf: *mut *mut c_char = &mut buf; + let re = unsafe { _avdict::avdict_get_string(self.m, pbuf, key_val_sep as c_char, pairs_sep as c_char) }; + if re != 0 { + Err(re)?; + } + let s = unsafe { CStr::from_ptr(buf) }; + let s = s.to_owned(); + unsafe { _avdict::avdict_avfree(buf as *mut std::os::raw::c_void) }; + Ok(s) + } + + pub fn iter<'a>(&'a self) -> AVDictItor<'a> { + AVDictItor { + d: self, + cur: 0 as *mut _avdict::AVDictEntry, + started: false, + } + } + + pub fn len(&self) -> usize { + if self.m.is_null() { + 0 + } else { + unsafe { _avdict::avdict_count(self.m) as usize } + } + } + + /// Parse the key/value pairs list and add the parsed entries to a dictionary. + /// + /// In case of failure, all the successfully set entries are stored. + /// * `s` - string + /// * `key_val_sep` - a list of characters used to separate key from value + /// * `pairs_sep` - a list of characters used to separate two pairs from each other + /// * `flags` - flags to use when adding to dictionary. + /// StrdupKey and StrdipValue are ignored since the key/value tokens will always be duplicated. + pub fn parse_string>(&mut self, s: K, key_val_sep: V, pairs_sep: S, flags: F) -> Result<(), AVDictError> { + let pm: *mut *mut _avdict::AVDict = &mut self.m; + let s = s.to_cstr()?; + let k = key_val_sep.to_cstr()?; + let p = pairs_sep.to_cstr()?; + let re = unsafe { _avdict::avdict_parse_string(pm, s.as_ptr(), k.as_ptr(), p.as_ptr(), flags.to_bits()) }; + if re != 0 { + Err(re)?; + } + Ok(()) + } + + pub fn set>(&mut self, key: K, value: V, flags: F) -> Result<(), AVDictError> { + let pm: *mut *mut _avdict::AVDict = &mut self.m; + let k = key.to_cstr()?; + let v = value.to_cstr()?; + let re = unsafe { _avdict::avdict_set(pm, k.as_ptr(), v.as_ptr(), flags.to_bits()) }; + if re != 0 { + Err(re)?; + } + Ok(()) + } + + pub fn set_int>(&mut self, key: K, value: i64, flags: F) -> Result<(), AVDictError> { + let pm: *mut *mut _avdict::AVDict = &mut self.m; + let k = key.to_cstr()?; + let re = unsafe { _avdict::avdict_set_int(pm, k.as_ptr(), value, flags.to_bits()) }; + if re != 0 { + Err(re)?; + } + Ok(()) + } +} + +impl AsMut for AVDict { + fn as_mut(&mut self) -> &mut Self { + self + } +} + +impl AsRef for AVDict { + fn as_ref(&self) -> &Self { + self + } +} + +impl Default for AVDict { + fn default() -> Self { + Self::new() + } +} + +impl Drop for AVDict { + fn drop(&mut self) { + if !self.m.is_null() { + let ptr: *mut *mut _avdict::AVDict = &mut self.m; + unsafe { _avdict::avdict_free(ptr) }; + self.m = 0 as *mut _avdict::AVDict; + } + } +} + +impl TryFrom> for AVDict { + type Error = AVDictError; + fn try_from(value: HashMap) -> Result { + Self::try_from(&value) + } +} + +impl TryFrom<&HashMap> for AVDict { + type Error = AVDictError; + fn try_from(value: &HashMap) -> Result { + let mut d = Self::new(); + for (k, v) in value { + d.set(k, v, AVDictFlags::MatchCase | AVDictFlags::Multikey)?; + } + Ok(d) + } +} + +impl ToRawHandle<_avdict::AVDict> for AVDict { + unsafe fn to_raw_handle(&self) -> *mut _avdict::AVDict { + self.m + } +} + +pub struct AVDictItor<'a> { + d: &'a AVDict, + cur: *mut _avdict::AVDictEntry, + started: bool, +} + +lazy_static! { + #[doc(hidden)] + static ref NULLSTR: CString = CString::new("").unwrap(); +} + +impl<'a> Iterator for AVDictItor<'a> { + type Item = (CString, CString); + fn next(&mut self) -> Option { + if self.started && self.cur.is_null() { + return None; + } + self.started = true; + let m = unsafe { self.d.to_raw_handle() }; + let re = unsafe { _avdict::avdict_get(m, NULLSTR.as_ptr(), self.cur as *const _avdict::AVDictEntry, AVDictFlags::IgnoreSuffix.to_bits()) }; + self.cur = re; + if re.is_null() { + return None; + } + let k = unsafe { CStr::from_ptr((*re).key) }; + let k = k.to_owned(); + let v = unsafe { CStr::from_ptr((*re).value) }; + let v = v.to_owned(); + Some((k, v)) + } +} + +#[test] +fn test_avdict() { + let e = AVDictCodeError::from(-1); + assert_eq!(Ok(String::from("Operation not permitted")), e.to_str()); + let mut d = AVDict::new(); + assert_eq!(0, d.len()); + let d2 = d.copy(None).unwrap(); + assert_eq!(0, d2.len()); + d.set("a", String::from("s"), None).unwrap(); + assert_eq!(1, d.len()); + assert_eq!(0, d2.len()); + let mut d2 = d.copy(None).unwrap(); + assert_eq!(1, d2.len()); + assert_eq!(Ok(None), d.get("f", None)); + assert_eq!(Ok(Some(CString::new("s").unwrap())), d.get("a", None)); + d2.set("b", "ok", AVDictFlags::Multikey).unwrap(); + d2.set("b", "test", AVDictFlags::Multikey).unwrap(); + assert_eq!(Ok(Some(CString::new("ok").unwrap())), d2.get("b", None)); + assert_eq!(Ok(Some(vec![ + CString::new("ok").unwrap(), + CString::new("test").unwrap(), + ])), d2.get_all("b", None)); + d.set_int("i", 17, None).unwrap(); + assert_eq!(Ok(Some(CString::new("17").unwrap())), d.get("i", None)); + d.set("c", "test", AVDictFlags::Append).unwrap(); + d.set("c", "test2", AVDictFlags::Append).unwrap(); + assert_eq!(Ok(Some(CString::new("testtest2").unwrap())), d.get("c", None)); + assert_eq!(3, d.len()); + assert_eq!(3, d2.len()); + let mut m = HashMap::new(); + m.insert("s", "f"); + m.insert("f", "dd"); + let mut d3 = AVDict::new(); + d3.from_map(&m, None).unwrap(); + assert_eq!(Ok(Some(CString::new("dd").unwrap())), d3.get("f", None)); + let d3 = AVDict::try_from(&m).unwrap(); + assert_eq!(Ok(Some(CString::new("dd").unwrap())), d3.get("f", None)); + let mut d4 = AVDict::new(); + d4.parse_string("a=b b=c", "=", " ", None).unwrap(); + assert_eq!(2, d4.len()); + assert_eq!(Ok(CString::new("a=b b=c").unwrap()), d4.get_string('=', ' ')); + let mut it = d4.iter(); + assert_eq!(Some((CString::new("a").unwrap(), CString::new("b").unwrap())), it.next()); +} diff --git a/src/exif.rs b/src/exif.rs index 33175d1..9a94dcd 100644 --- a/src/exif.rs +++ b/src/exif.rs @@ -1,4 +1,5 @@ use crate::_exif; +use crate::ext::rawhandle::ToRawHandle; use c_fixed_string::CFixedStr; use int_enum::IntEnum; use std::convert::TryFrom; @@ -90,12 +91,6 @@ pub struct ExifKey { key: *mut _exif::ExifKey, } -/// Return raw pointer of the handle -pub trait ToRawHandle { - /// Return raw pointer of the handle - unsafe fn to_raw_handle(&self) -> *mut T; -} - impl TryFrom for ExifKey { type Error = (); fn try_from(value: CString) -> Result { diff --git a/src/ext/cstr.rs b/src/ext/cstr.rs new file mode 100644 index 0000000..599ffb1 --- /dev/null +++ b/src/ext/cstr.rs @@ -0,0 +1,75 @@ +#[cfg(feature = "c_fixed_string")] +use c_fixed_string::CFixedStr; +#[cfg(feature = "c_fixed_string")] +use c_fixed_string::CFixedString; +use std::ffi::NulError; +use std::ffi::CStr; +use std::ffi::CString; +use std::fmt::Display; + +#[derive(Debug, derive_more::From, PartialEq)] +pub enum ToCStrError { + Null(NulError), +} + +impl Display for ToCStrError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Null(e) => { e.fmt(f) } + } + } +} + +pub trait ToCStr { + fn to_cstr(&self) -> Result; +} + +impl ToCStr for CString { + fn to_cstr(&self) -> Result { + Ok(self.clone()) + } +} + +impl ToCStr for CStr { + fn to_cstr(&self) -> Result { + Ok(self.to_owned()) + } +} + +impl ToCStr for [u8] { + fn to_cstr(&self) -> Result { + Ok(CString::new(self)?) + } +} + +impl ToCStr for &str { + fn to_cstr(&self) -> Result { + (*self).as_bytes().to_cstr() + } +} + +impl ToCStr for String { + fn to_cstr(&self) -> Result { + self.as_bytes().to_cstr() + } +} + +#[cfg(feature = "c_fixed_string")] +impl ToCStr for CFixedStr { + fn to_cstr(&self) -> Result { + Ok(self.to_c_str().into_owned()) + } +} + +#[cfg(feature = "c_fixed_string")] +impl ToCStr for CFixedString { + fn to_cstr(&self) -> Result { + Ok(self.to_c_str().into_owned()) + } +} + +impl<'a, T: ToCStr> ToCStr for &'a T { + fn to_cstr(&self) -> Result { + (*self).to_cstr() + } +} diff --git a/src/ext/flagset.rs b/src/ext/flagset.rs new file mode 100644 index 0000000..0b89189 --- /dev/null +++ b/src/ext/flagset.rs @@ -0,0 +1,30 @@ +use flagset::Flags; +use flagset::FlagSet; + +pub trait ToFlagSet { + fn to_flag_set(&self) -> FlagSet; + fn to_bits(&self) -> T::Type { + self.to_flag_set().bits() + } +} + +impl ToFlagSet for T where FlagSet: From { + fn to_flag_set(&self) -> FlagSet { + FlagSet::from(self.clone()) + } +} + +impl ToFlagSet for FlagSet { + fn to_flag_set(&self) -> FlagSet { + self.clone() + } +} + +impl ToFlagSet for Option where FlagSet: From { + fn to_flag_set(&self) -> FlagSet { + match self { + Some(s) => { FlagSet::from(s.clone()) } + None => { FlagSet::default() } + } + } +} diff --git a/src/ext/mod.rs b/src/ext/mod.rs new file mode 100644 index 0000000..1255dca --- /dev/null +++ b/src/ext/mod.rs @@ -0,0 +1,5 @@ +pub mod cstr; +#[cfg(feature = "flagset")] +pub mod flagset; +#[cfg(any(feature = "exif", feature = "avdict"))] +pub mod rawhandle; diff --git a/src/ext/rawhandle.rs b/src/ext/rawhandle.rs new file mode 100644 index 0000000..ac5e670 --- /dev/null +++ b/src/ext/rawhandle.rs @@ -0,0 +1,5 @@ +/// Return raw pointer of the handle +pub trait ToRawHandle { + /// Return raw pointer of the handle + unsafe fn to_raw_handle(&self) -> *mut T; +} diff --git a/src/main.rs b/src/main.rs index 85bea78..a5e9e18 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ extern crate c_fixed_string; extern crate chrono; extern crate dateparser; extern crate derive_more; +#[cfg(feature = "flagset")] +extern crate flagset; extern crate futures_util; extern crate json; #[cfg(feature = "int-enum")] @@ -19,16 +21,23 @@ extern crate urlparse; extern crate utf16string; extern crate xml; +#[cfg(feature = "avdict")] +#[doc(hidden)] +mod _avdict; #[cfg(feature = "exif")] #[doc(hidden)] mod _exif; mod author_name_filter; +#[cfg(feature = "avdict")] +mod avdict; mod cookies; mod data; mod download; mod dur; #[cfg(feature = "exif")] mod exif; +/// Used to extend some thirdparty library +mod ext; mod i18n; mod list; mod opthelper; diff --git a/ugoira/.gitignore b/ugoira/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/ugoira/.gitignore @@ -0,0 +1 @@ +build/