add avdict

This commit is contained in:
2022-03-18 12:37:04 +08:00
parent e348a29c28
commit 8ef00def87
20 changed files with 756 additions and 13 deletions

7
Cargo.lock generated
View File

@@ -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",

View File

@@ -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"] }

1
avdict/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build/

23
avdict/CMakeLists.txt Normal file
View File

@@ -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})

30
avdict/avdict.h Normal file
View File

@@ -0,0 +1,30 @@
#ifndef _AVDICT_H
#define _AVDICT_H
#include <stdint.h>
#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
/// <div rustbindgen opaque></div>
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

58
avdict/src/avdict.c Normal file
View File

@@ -0,0 +1,58 @@
#include "../avdict.h"
#include "libavutil/avutil.h"
#include "libavutil/dict.h"
#include <malloc.h>
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);
}

View File

@@ -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());

29
cmake/FindAVUTIL.cmake Normal file
View File

@@ -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
)

View File

@@ -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()

View File

@@ -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}")

6
src/_avdict.rs Normal file
View File

@@ -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"));

389
src/avdict.rs Normal file
View File

@@ -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<c_int> 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<String, AVDictError> {
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<c_int> 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<T: ToFlagSet<AVDictFlags>>(&self, flags: T) -> Result<Self, AVDictError> {
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<K: ToCStr, V: ToCStr, F: ToFlagSet<AVDictFlags>>(&mut self, maps: &HashMap<K, V>, flags: F) -> Result<(), AVDictError> {
let flags = flags.to_flag_set();
for (k, v) in maps {
self.set(k, v, flags)?;
}
Ok(())
}
pub fn get<K: ToCStr, F: ToFlagSet<AVDictFlags>>(&self, key: K, flags: F) -> Result<Option<CString>, 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<K: ToCStr, F: ToFlagSet<AVDictFlags>>(&self, key: K, flags: F) -> Result<Option<Vec<CString>>, 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<CString, AVDictError> {
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<K: ToCStr, V: ToCStr, S: ToCStr, F: ToFlagSet<AVDictFlags>>(&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<K: ToCStr, V: ToCStr, F: ToFlagSet<AVDictFlags>>(&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<K: ToCStr, F: ToFlagSet<AVDictFlags>>(&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<Self> for AVDict {
fn as_mut(&mut self) -> &mut Self {
self
}
}
impl AsRef<Self> 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 <K: ToCStr, V: ToCStr> TryFrom<HashMap<K, V>> for AVDict {
type Error = AVDictError;
fn try_from(value: HashMap<K, V>) -> Result<Self, Self::Error> {
Self::try_from(&value)
}
}
impl <K: ToCStr, V: ToCStr> TryFrom<&HashMap<K, V>> for AVDict {
type Error = AVDictError;
fn try_from(value: &HashMap<K, V>) -> Result<Self, Self::Error> {
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<Self::Item> {
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());
}

View File

@@ -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<T> {
/// Return raw pointer of the handle
unsafe fn to_raw_handle(&self) -> *mut T;
}
impl TryFrom<CString> for ExifKey {
type Error = ();
fn try_from(value: CString) -> Result<Self, Self::Error> {

75
src/ext/cstr.rs Normal file
View File

@@ -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<CString, ToCStrError>;
}
impl ToCStr for CString {
fn to_cstr(&self) -> Result<CString, ToCStrError> {
Ok(self.clone())
}
}
impl ToCStr for CStr {
fn to_cstr(&self) -> Result<CString, ToCStrError> {
Ok(self.to_owned())
}
}
impl ToCStr for [u8] {
fn to_cstr(&self) -> Result<CString, ToCStrError> {
Ok(CString::new(self)?)
}
}
impl ToCStr for &str {
fn to_cstr(&self) -> Result<CString, ToCStrError> {
(*self).as_bytes().to_cstr()
}
}
impl ToCStr for String {
fn to_cstr(&self) -> Result<CString, ToCStrError> {
self.as_bytes().to_cstr()
}
}
#[cfg(feature = "c_fixed_string")]
impl ToCStr for CFixedStr {
fn to_cstr(&self) -> Result<CString, ToCStrError> {
Ok(self.to_c_str().into_owned())
}
}
#[cfg(feature = "c_fixed_string")]
impl ToCStr for CFixedString {
fn to_cstr(&self) -> Result<CString, ToCStrError> {
Ok(self.to_c_str().into_owned())
}
}
impl<'a, T: ToCStr> ToCStr for &'a T {
fn to_cstr(&self) -> Result<CString, ToCStrError> {
(*self).to_cstr()
}
}

30
src/ext/flagset.rs Normal file
View File

@@ -0,0 +1,30 @@
use flagset::Flags;
use flagset::FlagSet;
pub trait ToFlagSet<T: Flags> {
fn to_flag_set(&self) -> FlagSet<T>;
fn to_bits(&self) -> T::Type {
self.to_flag_set().bits()
}
}
impl<T: Flags> ToFlagSet<T> for T where FlagSet<T>: From<T> {
fn to_flag_set(&self) -> FlagSet<T> {
FlagSet::from(self.clone())
}
}
impl<T: Flags> ToFlagSet<T> for FlagSet<T> {
fn to_flag_set(&self) -> FlagSet<T> {
self.clone()
}
}
impl<T: Flags> ToFlagSet<T> for Option<T> where FlagSet<T>: From<T> {
fn to_flag_set(&self) -> FlagSet<T> {
match self {
Some(s) => { FlagSet::from(s.clone()) }
None => { FlagSet::default() }
}
}
}

5
src/ext/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod cstr;
#[cfg(feature = "flagset")]
pub mod flagset;
#[cfg(any(feature = "exif", feature = "avdict"))]
pub mod rawhandle;

5
src/ext/rawhandle.rs Normal file
View File

@@ -0,0 +1,5 @@
/// Return raw pointer of the handle
pub trait ToRawHandle<T> {
/// Return raw pointer of the handle
unsafe fn to_raw_handle(&self) -> *mut T;
}

View File

@@ -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;

1
ugoira/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
build/