add exif support

This commit is contained in:
2022-03-02 18:33:04 +08:00
parent 23cbb89d10
commit 8f6fb37340
25 changed files with 1837 additions and 14 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/target
.vscode/
test/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "utils"]
path = utils
url = https://github.com/lifegpc/c-utils

248
Cargo.lock generated
View File

@@ -84,6 +84,30 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bindgen"
version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c72a978d268b1d70b0e963217e60fdabd9523a941457a6c42a7315d15c7e89e5"
dependencies = [
"bitflags",
"cexpr",
"cfg-if 0.1.10",
"clang-sys",
"clap",
"env_logger",
"lazy_static",
"lazycell",
"log",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"which",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -156,12 +180,36 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
[[package]]
name = "c_fixed_string"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe6833b1e1b632d1a4c167aa8dc2a78a774bd78e14bc14579505d209327b47cf"
dependencies = [
"memchr",
]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cexpr"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -181,6 +229,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "clang-sys"
version = "0.29.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe6837df1d5cba2397b835c8530f51723267e16abbf83892e9e5af4f0e5dd10a"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.34.0"
@@ -196,6 +255,15 @@ dependencies = [
"vec_map",
]
[[package]]
name = "cmake"
version = "0.1.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a"
dependencies = [
"cc",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
@@ -218,7 +286,7 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
]
[[package]]
@@ -318,7 +386,20 @@ version = "0.8.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
@@ -342,7 +423,7 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"crc32fast",
"libc",
"miniz_oxide",
@@ -459,6 +540,12 @@ dependencies = [
"encoding",
]
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "h2"
version = "0.3.11"
@@ -551,6 +638,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]]
name = "hyper"
version = "0.14.17"
@@ -628,7 +724,30 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
]
[[package]]
name = "int-enum"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b1428b2b1abe959e6eedb0a17d0ab12f6ba20e1106cc29fc4874e3ba393c177"
dependencies = [
"cfg-if 0.1.10",
"int-enum-impl",
]
[[package]]
name = "int-enum-impl"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c3cecaad8ca1a5020843500c696de2b9a07b63b624ddeef91f85f9bafb3671"
dependencies = [
"cfg-if 0.1.10",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -664,19 +783,35 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
[[package]]
name = "libloading"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753"
dependencies = [
"cc",
"winapi",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
]
[[package]]
@@ -753,6 +888,16 @@ dependencies = [
"tempfile",
]
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"memchr",
"version_check",
]
[[package]]
name = "ntapi"
version = "0.3.7"
@@ -810,7 +955,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
dependencies = [
"bitflags",
"cfg-if",
"cfg-if 1.0.0",
"foreign-types",
"libc",
"once_cell",
@@ -836,6 +981,12 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "percent-encoding"
version = "2.1.0"
@@ -901,18 +1052,23 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
name = "pixiv_downloader"
version = "0.0.1"
dependencies = [
"bindgen",
"c_fixed_string",
"chrono",
"cmake",
"dateparser",
"futures-util",
"getopts",
"gettext",
"html_parser",
"int-enum",
"json",
"lazy_static",
"regex",
"reqwest",
"spin_on",
"tokio",
"utf16string",
"winapi",
]
@@ -922,6 +1078,15 @@ version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
dependencies = [
"toml",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -955,6 +1120,12 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.15"
@@ -1058,6 +1229,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustls"
version = "0.20.4"
@@ -1183,6 +1360,12 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "shlex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
[[package]]
name = "slab"
version = "0.4.5"
@@ -1261,7 +1444,7 @@ version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"fastrand",
"libc",
"redox_syscall",
@@ -1269,6 +1452,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
@@ -1399,6 +1591,15 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]]
name = "tower-service"
version = "0.3.1"
@@ -1411,7 +1612,7 @@ version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"pin-project-lite",
"tracing-core",
]
@@ -1494,6 +1695,15 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "utf16string"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b62a1e85e12d5d712bf47a85f426b73d303e2d00a90de5f3004df3596e9d216"
dependencies = [
"byteorder",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
@@ -1534,7 +1744,7 @@ version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"wasm-bindgen-macro",
]
@@ -1559,7 +1769,7 @@ version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"js-sys",
"wasm-bindgen",
"web-sys",
@@ -1623,6 +1833,15 @@ dependencies = [
"webpki",
]
[[package]]
name = "which"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
dependencies = [
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
@@ -1639,6 +1858,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"

View File

@@ -6,18 +6,28 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
c_fixed_string = { version = "0.2", optional = true }
chrono = "0.4"
dateparser = "0.1.6"
futures-util = "0.3"
getopts = "0.2"
gettext = "0.4"
html_parser = "0.6.2"
int-enum = { version = "0.4", optional = true }
lazy_static = "1.4"
json = "0.12"
utf16string = { version= "0.2", optional = true }
regex = "1"
reqwest = { version = "0.11", features = ["brotli", "deflate", "gzip", "rustls-tls", "socks", "stream"] }
spin_on = "0.1.1"
tokio = { version = "1.17", features = ["rt", "macros", "rt-multi-thread", "time"] }
[build-dependencies]
cmake = "0.1"
bindgen = "0.53"
[features]
exif = ["c_fixed_string", "int-enum", "utf16string"]
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["winnls", "stringapiset"] }

94
build.rs Normal file
View File

@@ -0,0 +1,94 @@
extern crate bindgen;
extern crate cmake;
use std::env;
use std::fs::create_dir;
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
fn main() {
#[cfg(any(feature = "exif"))]
{
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let utils_build_path = out_path.join("utils");
if !utils_build_path.exists() {
create_dir(&utils_build_path).unwrap();
}
cmake::Config::new("utils")
.define("CMAKE_INSTALL_PREFIX", out_path.to_str().unwrap())
.out_dir(utils_build_path)
.define("ENABLE_ICONV", "OFF")
.define("ENABLE_STANDALONE", "ON")
.define("ENABLE_CXX17", "ON")
.define("CMAKE_BUILD_TYPE", "Release")
.define("INSTALL_DEP_FILES", "ON")
.build();
let dep_path = out_path.join("utils_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=exif");
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();
p.pop();
let pa = p.to_str().unwrap();
if pa != "" {
println!("cargo:rustc-link-search={}", pa);
}
println!("cargo:rustc-link-lib={}", file_name);
}
println!("cargo:rerun-if-changed=utils/");
}
#[cfg(feature = "exif")]
{
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let exif_build_path = out_path.join("exif");
let utils_build_path = out_path.join("utils");
if !exif_build_path.exists() {
create_dir(&exif_build_path).unwrap();
}
cmake::Config::new("exif")
.define("CMAKE_INSTALL_PREFIX", out_path.to_str().unwrap())
.out_dir(exif_build_path)
.define("UTILS_LIBRARY", utils_build_path.to_str().unwrap())
.define("CMAKE_BUILD_TYPE", "Release")
.build();
println!("cargo:rustc-link-search=native={}/lib", out_path.display());
let dep_path = out_path.join("exif_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=exif");
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();
p.pop();
println!("cargo:rustc-link-search={}", p.to_str().unwrap());
println!("cargo:rustc-link-lib={}", file_name);
}
println!("cargo:rerun-if-changed=exif/");
let bindings = bindgen::Builder::default()
// The input header we would like to generate
// bindings for.
.header("exif/exif.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("exif.rs"))
.expect("Couldn't write bindings!");
}
}

1
exif/.gitignore vendored Normal file
View File

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

41
exif/CMakeLists.txt Normal file
View File

@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.9)
project(exif)
if (MSVC)
add_compile_options(/utf-8)
add_compile_options(/EHsc)
endif()
include(GNUInstallDirs)
option(UTILS_LIBRARY "The path of utils of library." "")
if (UTILS_LIBRARY)
set(UTILS_TARGET "${UTILS_LIBRARY}")
else()
set(ENABLE_ICONV OFF CACHE BOOL "Libiconv is not needed.")
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../utils" "${CMAKE_BINARY_DIR}/utils")
set(UTILS_TARGET utils)
endif()
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../utils")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
find_package(Exiv2)
include_directories(${Exiv2_INCLUDE_DIRS})
include_directories("${CMAKE_CURRENT_SOURCE_DIR}")
add_library(exif exif.h src/exif_priv.h src/exif.cpp)
target_compile_features(exif PRIVATE cxx_std_17)
target_link_libraries(exif ${Exiv2_LIBRARIES})
target_link_libraries(exif ${UTILS_TARGET})
if (BUILD_SHARED_LIBS AND WIN32)
target_compile_definitions(exif PRIVATE -DBUILD_DLL -DWIN32_DLL)
endif()
get_target_property(OUT exif LINK_LIBRARIES)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/exif_dep.txt" "${OUT}")
install(TARGETS exif)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/exif_dep.txt" DESTINATION ${CMAKE_INSTALL_PREFIX})

View File

@@ -0,0 +1,34 @@
find_path(Exiv2_INCLUDE_DIR
NAMES exiv2/exiv2.hpp
)
find_library(Exiv2_LIBRARY
NAMES exiv2
)
if (Exiv2_INCLUDE_DIR)
if (EXISTS "${Exiv2_INCLUDE_DIR}/exiv2/exv_conf.h")
file(STRINGS "${Exiv2_INCLUDE_DIR}/exiv2/exv_conf.h" EXV_CONF_H)
foreach(LINE IN LISTS EXV_CONF_H)
string(REGEX MATCH "^#define EXV_PACKAGE_VERSION \"([^\"]+)\"" OUTPUT "${LINE}")
if (OUTPUT)
set(EXIV2_VERSION "${CMAKE_MATCH_1}")
endif()
endforeach()
endif()
endif()
if (Exiv2_INCLUDE_DIR AND Exiv2_LIBRARY)
set(Exiv2_FOUND TRUE)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Exiv2
FOUND_VAR Exiv2_FOUND
REQUIRED_VARS
Exiv2_LIBRARY
Exiv2_INCLUDE_DIR
VERSION_VAR
EXIV2_VERSION
)
set(Exiv2_INCLUDE_DIRS "${Exiv2_INCLUDE_DIR}")
set(Exiv2_LIBRARIES "${Exiv2_LIBRARY}")

65
exif/exif.h Normal file
View File

@@ -0,0 +1,65 @@
#ifndef _EXIF_H
#define _EXIF_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
/// <div rustbindgen opaque></div>
typedef struct ExifImage ExifImage;
/// <div rustbindgen opaque></div>
typedef struct ExifKey ExifKey;
/// <div rustbindgen opaque></div>
typedef struct ExifValue ExifValue;
/// <div rustbindgen opaque></div>
typedef struct ExifData ExifData;
/// <div rustbindgen opaque></div>
typedef struct ExifDatum ExifDatum;
#if defined _WIN32 && defined WIN32_DLL
#if BUILD_DLL
#define EXIF_API __declspec(dllexport)
#else
#define EXIF_API __declspec(dllimport)
#endif
#else
#define EXIF_API
#endif
EXIF_API ExifImage* create_exif_image(const char* path);
EXIF_API int exif_image_set_exif_data(ExifImage* image, ExifData* data);
EXIF_API int exif_image_write_metadata(ExifImage* image);
EXIF_API void free_exif_image(ExifImage* img);
EXIF_API ExifKey* exif_create_key_by_key(const char* key);
EXIF_API ExifKey* exif_create_key_by_id(uint16_t id, const char* group_name);
EXIF_API const char* exif_get_key_key(ExifKey* key);
EXIF_API const char* exif_get_key_family_name(ExifKey* key);
EXIF_API char* exif_get_key_group_name(ExifKey* key);
EXIF_API char* exif_get_key_tag_name(ExifKey* key);
EXIF_API uint16_t exif_get_key_tag_tag(ExifKey* key);
EXIF_API char* exif_get_key_tag_label(ExifKey* key);
EXIF_API char* exif_get_key_tag_desc(ExifKey* key);
EXIF_API int exif_get_key_default_type_id(ExifKey* key);
EXIF_API void exif_free(void* v);
EXIF_API void exif_free_key(ExifKey* key);
EXIF_API ExifValue* exif_create_value(int type_id);
EXIF_API int exif_get_value_type_id(ExifValue* value);
EXIF_API long exif_get_value_count(ExifValue* value);
EXIF_API long exif_get_value_size(ExifValue* value);
EXIF_API long exif_get_value_size_data_area(ExifValue *value);
EXIF_API int exif_value_read(ExifValue* value, const uint8_t* bytes, long len, int byte_order);
EXIF_API int exif_get_value_ok(ExifValue* value);
EXIF_API char* exif_value_to_string(ExifValue* value, size_t* len);
EXIF_API char* exif_value_to_string2(ExifValue* value, size_t* len, long i);
EXIF_API int64_t exif_value_to_int64(ExifValue* value, long i);
EXIF_API ExifData* exif_data_new();
EXIF_API int exif_data_add(ExifData* d, ExifKey* key, ExifValue* value);
EXIF_API int exif_data_clear(ExifData* d);
EXIF_API int exif_data_is_empty(ExifData* d);
EXIF_API long exif_data_get_count(ExifData* d);
EXIF_API void exif_free_value(ExifValue* value);
EXIF_API void exif_free_data(ExifData* d);
EXIF_API void exif_free_datum(ExifDatum* d);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,93 @@
diff --git a/src/basicio.cpp b/src/basicio.cpp
index 807d75c8..49841f36 100644
--- a/src/basicio.cpp
+++ b/src/basicio.cpp
@@ -87,6 +87,64 @@ namespace {
}
namespace Exiv2 {
+#if defined(__MINGW__) || (defined(WIN32) && !defined(__CYGWIN__))
+ /// Remove unsuportted options flags
+ unsigned long getMultiByteToWideCharOptions(const unsigned long ori_options, unsigned int cp) {
+ if (cp == CP_ACP) cp = GetACP();
+ if (cp == CP_OEMCP) cp = GetOEMCP();
+ switch (cp)
+ {
+ case 50220:
+ case 50221:
+ case 50222:
+ case 50225:
+ case 50227:
+ case 50229:
+ case CP_UTF7:
+ case 42:
+ return 0;
+ default:
+ break;
+ }
+ if (cp >= 57002 && cp <= 57011) return 0;
+ if (cp == CP_UTF8 || cp == 54936) {
+ return MB_ERR_INVALID_CHARS & ori_options;
+ }
+ return ori_options;
+ }
+ bool str_to_wstr(std::wstring& out, std::string inp, unsigned int cp) {
+ DWORD opt = getMultiByteToWideCharOptions(MB_ERR_INVALID_CHARS, cp);
+ int wlen = MultiByteToWideChar(cp, opt, inp.c_str(), inp.length(), nullptr, 0);
+ if (!wlen) {
+ return false;
+ }
+ wchar_t* wstr = (wchar_t*)malloc(wlen * sizeof(wchar_t));
+ if (wstr == nullptr) {
+ return false;
+ }
+ if (!MultiByteToWideChar(cp, opt, inp.c_str(), inp.length(), wstr, wlen)) {
+ free(wstr);
+ return false;
+ }
+ out = std::wstring(wstr, wlen);
+ free(wstr);
+ return true;
+ }
+ FILE* win32_fopen(const char* path, const char* mode) {
+ std::string p(path), m(mode);
+ FILE* f = nullptr;
+ unsigned long cps[] = { CP_UTF8, CP_ACP, CP_OEMCP };
+ for (int i = 0; i < 3; i++) {
+ std::wstring wp, wm;
+ if (str_to_wstr(wp, p, cps[i]) && str_to_wstr(wm, m, cps[i])) {
+ if ((f = _wfopen(wp.c_str(), wm.c_str()))) {
+ return f;
+ }
+ }
+ }
+ return std::fopen(path, mode);
+ }
+#endif
void BasicIo::readOrThrow(byte* buf, long rcount, ErrorCode err) {
const long nread = read(buf, rcount);
enforce(nread == rcount, err);
@@ -201,7 +259,11 @@ namespace Exiv2 {
}
openMode_ = "r+b";
opMode_ = opSeek;
+#if defined(__MINGW__) || (defined(WIN32) && !defined(__CYGWIN__))
+ fp_ = win32_fopen(path_.c_str(), openMode_.c_str());
+#else
fp_ = std::fopen(path_.c_str(), openMode_.c_str());
+#endif
if (!fp_) return 1;
return std::fseek(fp_, offset, SEEK_SET);
} // FileIo::Impl::switchMode
@@ -534,7 +596,11 @@ namespace Exiv2 {
close();
p_->openMode_ = mode;
p_->opMode_ = Impl::opSeek;
+#if defined(__MINGW__) || (defined(WIN32) && !defined(__CYGWIN__))
+ p_->fp_ = win32_fopen(path().c_str(), mode.c_str());
+#else
p_->fp_ = ::fopen(path().c_str(), mode.c_str());
+#endif
if (!p_->fp_)
return 1;
return 0;

250
exif/src/exif.cpp Normal file
View File

@@ -0,0 +1,250 @@
#include "exif.h"
#include "exif_priv.h"
#include "cpp2c.h"
#include <malloc.h>
#include <string.h>
#include "fileop.h"
#define string2char cpp2c::string2char
ExifImage* create_exif_image(const char* path) {
if (!path) return nullptr;
if (!fileop::exists(path)) return nullptr;
auto img = new ExifImage;
try {
img->image = Exiv2::ImageFactory::open(path);
} catch (std::exception& e) {
printf("%s\n", e.what());
goto end;
}
return img;
end:
delete img;
return nullptr;
}
int exif_image_set_exif_data(ExifImage* image, ExifData* data) {
if (!image || !data) return 1;
image->image->setExifData(data->data);
return 0;
}
int exif_image_write_metadata(ExifImage* image) {
if (!image) return 1;
try {
image->image->writeMetadata();
} catch (std::exception& e) {
printf("%s\n", e.what());
return 1;
}
return 0;
}
void free_exif_image(ExifImage* img) {
if (!img) return;
delete img;
}
ExifKey* exif_create_key_by_key(const char* key) {
if (!key) return nullptr;
ExifKey* k = new ExifKey;
try {
k->key = new Exiv2::ExifKey(key);
} catch (std::exception& e) {
printf("%s\n", e.what());
goto end;
}
return k;
end:
if (k->key) delete k->key;
delete k;
return nullptr;
}
ExifKey* exif_create_key_by_id(uint16_t id, const char* group_name) {
if (!group_name) return nullptr;
ExifKey* k = new ExifKey;
try {
k->key = new Exiv2::ExifKey(id, group_name);
} catch (std::exception& e) {
printf("%s\n", e.what());
goto end;
}
return k;
end:
if (k->key) delete k->key;
delete k;
return nullptr;
}
const char* exif_get_key_key(ExifKey* key) {
if (!key || !key->key) return nullptr;
return std::move(key->key->key().c_str());
}
const char* exif_get_key_family_name(ExifKey* key) {
if (!key || !key->key) return nullptr;
return key->key->familyName();
}
char* exif_get_key_group_name(ExifKey* key) {
if (!key || !key->key) return nullptr;
auto s = key->key->groupName();
char* re = nullptr;
if (!string2char(s, re)) return nullptr;
return re;
}
char* exif_get_key_tag_name(ExifKey* key) {
if (!key || !key->key) return nullptr;
auto s = key->key->tagName();
char* re = nullptr;
if (!string2char(s, re)) return nullptr;
return re;
}
uint16_t exif_get_key_tag_tag(ExifKey* key) {
if (!key || !key->key) return -1;
return key->key->tag();
}
char* exif_get_key_tag_label(ExifKey* key) {
if (!key || !key->key) return nullptr;
auto s = key->key->tagLabel();
char* re = nullptr;
if (!string2char(s, re)) return nullptr;
return re;
}
char* exif_get_key_tag_desc(ExifKey* key) {
if (!key || !key->key) return nullptr;
auto s = key->key->tagDesc();
char* re = nullptr;
if (!string2char(s, re)) return nullptr;
return re;
}
int exif_get_key_default_type_id(ExifKey* key) {
if (!key || !key->key) return -1;
return key->key->defaultTypeId();
}
void exif_free(void* v) {
free(v);
}
void exif_free_key(ExifKey* key) {
if (!key) return;
if (key->key) delete key->key;
delete key;
}
ExifValue* exif_create_value(int type_id) {
ExifValue* v = new ExifValue;
try {
auto t = static_cast<Exiv2::TypeId>(type_id);
v->value = Exiv2::Value::create(t);
} catch (std::exception& e) {
printf("%s\n", e.what());
goto end;
}
return v;
end:
if (v) delete v;
return nullptr;
}
int exif_get_value_type_id(ExifValue* value) {
if (!value) return -1;
return value->value->typeId();
}
long exif_get_value_count(ExifValue* value) {
if (!value) return -1;
return value->value->count();
}
long exif_get_value_size(ExifValue* value) {
if (!value) return -1;
return value->value->size();
}
long exif_get_value_size_data_area(ExifValue* value) {
if (!value) return -1;
return value->value->sizeDataArea();
}
int exif_value_read(ExifValue* value, const uint8_t* bytes, long len, int byte_order) {
if (!value || !bytes) return -1;
return value->value->read(bytes, len, static_cast<Exiv2::ByteOrder>(byte_order));
}
int exif_get_value_ok(ExifValue* value) {
if (!value) return 0;
return value->value->ok() ? 1 : 0;
}
char* exif_value_to_string(ExifValue* value, size_t* len) {
if (!value || !len) return nullptr;
auto s = value->value->toString();
*len = s.size();
char* tmp = nullptr;
if (!string2char(s, tmp)) return nullptr;
return tmp;
}
char* exif_value_to_string2(ExifValue* value, size_t* len, long i) {
if (!value || !len) return nullptr;
auto s = value->value->toString(i);
*len = s.size();
char* tmp = nullptr;
if (!string2char(s, tmp)) return nullptr;
return tmp;
}
int64_t exif_value_to_int64(ExifValue* value, long i) {
if (!value) return -1;
return value->value->toInt64(i);
}
ExifData* exif_data_new() {
return new ExifData;
}
int exif_data_add(ExifData* d, ExifKey* key, ExifValue* value) {
if (!d || !key || !value || !key->key) return 0;
d->data.add(*key->key, value->value.get());
return 1;
}
int exif_data_clear(ExifData* d) {
if (!d) return 0;
d->data.clear();
return 1;
}
int exif_data_is_empty(ExifData* d) {
if (!d) return -1;
return d->data.empty() ? 1 : 0;
}
long exif_data_get_count(ExifData* d) {
if (!d) return -1;
return d->data.count();
}
void exif_free_value(ExifValue* value) {
if (!value) return;
delete value;
}
void exif_free_data(ExifData* d) {
if (!d) return;
delete d;
}
void exif_free_datum(ExifDatum* d) {
if (!d) return;
if (d->data) delete d->data;
}

19
exif/src/exif_priv.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef _EXIF_EXIF_PRIV_H
#define _EXIF_EXIF_PRIV_H
#include "exiv2/exiv2.hpp"
typedef struct ExifImage {
Exiv2::Image::UniquePtr image;
} ExifImage;
typedef struct ExifKey {
Exiv2::ExifKey* key = nullptr;
} ExifKey;
typedef struct ExifValue {
Exiv2::Value::UniquePtr value;
} ExifValue;
typedef struct ExifData {
Exiv2::ExifData data;
} ExifData;
typedef struct ExifDatum {
Exiv2::Exifdatum* data;
} ExifDatum;
#endif

5
src/_exif.rs Normal file
View File

@@ -0,0 +1,5 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(dead_code)]
#![allow(deref_nullptr)]
include!(concat!(env!("OUT_DIR"), "/exif.rs"));

49
src/data/data.rs Normal file
View File

@@ -0,0 +1,49 @@
use crate::pixiv_link::ToPixivID;
use crate::pixiv_link::PixivID;
use json::JsonValue;
use std::convert::TryInto;
/// Pixiv's basic data
pub struct PixivData {
/// ID
pub id: PixivID,
/// The title
pub title: Option<String>,
/// The author
pub author: Option<String>,
}
impl PixivData {
pub fn new<T: ToPixivID>(id: T) -> Option<Self> {
let i = id.to_pixiv_id();
if i.is_none() {
return None;
}
Some(Self {
id: i.unwrap(),
title: None,
author: None,
})
}
/// Read data from JSON object.
/// The object is from https://www.pixiv.net/artworks/<id>
/// * `value` - The JSON object
/// * `allow_overwrite` - Allow overwrite the data existing.
pub fn from_web_page_data(&mut self, value: &JsonValue, allow_overwrite: bool) {
let id: u64 = (&self.id).try_into().unwrap();
let ids = format!("{}", id);
if self.title.is_none() || allow_overwrite {
let title = value["illust"][ids.as_str()]["illustTitle"].as_str();
if title.is_some() {
self.title = Some(String::from(title.unwrap()));
}
}
if self.author.is_none() || allow_overwrite {
let author = value["illust"][ids.as_str()]["userName"].as_str();
if author.is_some() {
self.author = Some(String::from(author.unwrap()));
}
}
}
}

60
src/data/exif.rs Normal file
View File

@@ -0,0 +1,60 @@
use crate::data::data::PixivData;
use crate::exif::ExifData;
use crate::exif::ExifImage;
use crate::exif::ExifKey;
use crate::exif::ExifTypeID;
use crate::exif::ExifValue;
use std::convert::TryFrom;
use std::ffi::OsStr;
use utf16string::LittleEndian;
use utf16string::WString;
fn add_image_id(data: &mut ExifData, d: &PixivData) -> Result<(), ()> {
let link = d.id.to_link();
let key = ExifKey::try_from("Exif.Image.ImageID")?;
let mut value = ExifValue::try_from(ExifTypeID::AsciiString)?;
value.read(link.as_bytes(), None)?;
data.add(&key, &value)?;
Ok(())
}
fn add_image_title(data: &mut ExifData, d: &PixivData) -> Result<(), ()> {
if d.title.is_none() {
return Ok(());
}
let title = d.title.as_ref().unwrap();
let key = ExifKey::try_from("Exif.Image.ImageDescription")?;
let mut value = ExifValue::try_from(ExifTypeID::AsciiString)?;
value.read(title.as_bytes(), None)?;
data.add(&key, &value)?;
let key = ExifKey::try_from("Exif.Image.XPTitle")?;
let mut value = ExifValue::try_from(ExifTypeID::BYTE)?;
let s: WString<LittleEndian> = WString::from(title);
value.read(s.as_bytes(), None)?;
data.add(&key, &value)?;
Ok(())
}
fn add_image_author(data: &mut ExifData, d: &PixivData) -> Result<(), ()> {
if d.author.is_none() {
return Ok(());
}
let author = d.author.as_ref().unwrap();
let key = ExifKey::try_from("Exif.Image.XPAuthor")?;
let mut value = ExifValue::try_from(ExifTypeID::BYTE)?;
let s: WString<LittleEndian> = WString::from(author);
value.read(s.as_bytes(), None)?;
data.add(&key, &value)?;
Ok(())
}
pub fn add_exifdata_to_image<S: AsRef<OsStr> + ?Sized>(file_name: &S, data: &PixivData) -> Result<(), ()> {
let mut f = ExifImage::new(file_name)?;
let mut d = ExifData::new()?;
add_image_id(&mut d, data)?;
add_image_title(&mut d, data)?;
add_image_author(&mut d, data)?;
f.set_exif_data(&d)?;
f.write_metadata()?;
Ok(())
}

View File

@@ -1,8 +1,10 @@
use crate::data::data::PixivData;
use crate::gettext;
use crate::pixiv_link::PixivID;
use crate::pixiv_link::ToPixivID;
use json::JsonValue;
use std::collections::HashMap;
use std::convert::From;
use std::ffi::OsStr;
use std::fs::File;
use std::fs::remove_file;
@@ -20,6 +22,7 @@ pub struct JSONDataFile {
}
impl JSONDataFile {
#[allow(dead_code)]
pub fn new<T: ToPixivID>(id: T) -> Option<Self> {
let i = id.to_pixiv_id();
if i.is_none() {
@@ -31,6 +34,16 @@ impl JSONDataFile {
})
}
pub fn add<T: ToJson>(&mut self, key: &str, value: T) -> Result<(), ()> {
let v = value.to_json();
if v.is_some() {
self.maps.insert(String::from(key), v.unwrap());
Ok(())
} else {
Err(())
}
}
pub fn save<S: AsRef<OsStr> + ?Sized>(&self, path: &S) -> bool {
let p = Path::new(path);
if p.exists() {
@@ -60,6 +73,28 @@ impl JSONDataFile {
}
}
impl From<PixivData> for JSONDataFile {
fn from(p: PixivData) -> Self {
JSONDataFile::from(&p)
}
}
impl From<&PixivData> for JSONDataFile {
fn from(p: &PixivData) -> Self {
let mut f = Self {
id: p.id.clone(),
maps: HashMap::new(),
};
if p.title.is_some() {
f.add("title", p.title.as_ref().unwrap()).unwrap();
}
if p.author.is_some() {
f.add("author", p.author.as_ref().unwrap()).unwrap();
}
f
}
}
impl ToJson for JSONDataFile {
fn to_json(&self) -> Option<JsonValue> {
let mut value = json::object! {};
@@ -74,3 +109,15 @@ impl ToJson for JSONDataFile {
Some(value)
}
}
impl ToJson for &str {
fn to_json(&self) -> Option<JsonValue> {
Some(JsonValue::String(String::from(*self)))
}
}
impl ToJson for &String {
fn to_json(&self) -> Option<JsonValue> {
Some(JsonValue::String((*self).to_string()))
}
}

View File

@@ -1 +1,4 @@
pub mod data;
#[cfg(feature = "exif")]
pub mod exif;
pub mod json;

View File

@@ -1,3 +1,6 @@
use crate::data::data::PixivData;
#[cfg(feature = "exif")]
use crate::data::exif::add_exifdata_to_image;
use crate::data::json::JSONDataFile;
use crate::gettext;
use crate::pixiv_link::PixivID;
@@ -40,7 +43,8 @@ impl Main {
if re.is_none() {
return 1;
}
let pages = (&(re.as_ref().unwrap())["illust"][format!("{}", id).as_str()]["sl"]).as_u64();
let re = re.unwrap();
let pages = (&re["illust"][format!("{}", id).as_str()]["pageCount"]).as_u64();
if pages.is_none() {
println!("{}", gettext("Failed to get page count."));
return 1;
@@ -56,7 +60,9 @@ impl Main {
}
let base = PathBuf::from(".");
let json_file = base.join(format!("{}.json", id));
let json_data = JSONDataFile::new(id).unwrap();
let mut datas = PixivData::new(id).unwrap();
datas.from_web_page_data(&re, true);
let json_data = JSONDataFile::from(&datas);
if !json_data.save(&json_file) {
println!("{}", gettext("Failed to save metadata to JSON file."));
return 1;
@@ -93,7 +99,58 @@ impl Main {
gettext("Downloaded image:"),
link,
file_name.to_str().unwrap_or("(null)")
)
);
#[cfg(feature = "exif")]
{
if add_exifdata_to_image(&file_name, &datas).is_err() {
println!(
"{} {}",
gettext("Failed to add exif data to image:"),
file_name.to_str().unwrap_or("(null)")
);
}
}
}
} else {
let link = (&re["illust"][format!("{}", id)]["urls"]["original"]).as_str();
if link.is_none() {
println!("{}", gettext("Failed to get original picture's link."));
return 1;
}
let link = link.unwrap();
let file_name = get_file_name_from_url(link);
if file_name.is_none() {
println!("{} {}", gettext("Failed to get file name from url:"), link);
return 1;
}
let file_name = file_name.unwrap();
let file_name = base.join(file_name);
let r = pw.download_image(link);
if r.is_none() {
println!("{} {}", gettext("Failed to download image:"), link);
return 1;
}
let r = r.unwrap();
let re = WebClient::download_stream(&file_name, r, pw.helper.overwrite());
if re.is_err() {
println!("{} {}", gettext("Failed to download image:"), link);
return 1;
}
println!(
"{} {} -> {}",
gettext("Downloaded image:"),
link,
file_name.to_str().unwrap_or("(null)")
);
#[cfg(feature = "exif")]
{
if add_exifdata_to_image(&file_name, &datas).is_err() {
println!(
"{} {}",
gettext("Failed to add exif data to image:"),
file_name.to_str().unwrap_or("(null)")
);
}
}
}
0

720
src/exif.rs Normal file
View File

@@ -0,0 +1,720 @@
use crate::_exif;
use c_fixed_string::CFixedStr;
use int_enum::IntEnum;
use std::convert::TryFrom;
use std::ffi::CStr;
use std::ffi::CString;
use std::ffi::OsStr;
#[cfg(test)]
use std::fs::copy;
#[cfg(test)]
use std::fs::create_dir;
use std::ops::Drop;
use std::os::raw::c_long;
use std::path::Path;
/// Used primarily as identifiers when creating ExifValue
#[repr(i32)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum)]
pub enum ExifTypeID {
/// Exif BYTE type, 8-bit unsigned integer.
BYTE = 1,
/// Exif ASCII type, 8-bit byte.
AsciiString = 2,
/// Exif SHORT type, 16-bit (2-byte) unsigned integer.
UShort = 3,
/// Exif LONG type, 32-bit (4-byte) unsigned integer.
ULong = 4,
/// Exif RATIONAL type, two LONGs: numerator and denumerator of a fraction.
URational = 5,
/// Exif SBYTE type, an 8-bit signed (twos-complement) integer.
SBYTE = 6,
/// Exif UNDEFINED type, an 8-bit byte that may contain anything.
Undefined = 7,
/// Exif SSHORT type, a 16-bit (2-byte) signed (twos-complement) integer.
SShort = 8,
/// Exif SLONG type, a 32-bit (4-byte) signed (twos-complement) integer.
SLong = 9,
/// Exif SRATIONAL type, two SLONGs: numerator and denominator of a fraction.
SRational = 10,
/// TIFF FLOAT type, single precision (4-byte) IEEE format.
TiffFloat = 11,
/// TIFF DOUBLE type, double precision (8-byte) IEEE format.
TiffDouble = 12,
/// TIFF IFD type, 32-bit (4-byte) unsigned integer.
TiffIfd = 13,
/// Exif LONG LONG type, 64-bit (8-byte) unsigned integer.
ULongLong = 16,
/// Exif LONG LONG type, 64-bit (8-byte) signed integer.
SLongLong = 17,
/// TIFF IFD type, 64-bit (8-byte) unsigned integer.
TiffIfd8 = 18,
/// IPTC string type.
String = 0x10000,
/// IPTC date type.
Date = 0x10001,
/// IPTC time type.
Time = 0x10002,
/// Exiv2 type for the Exif user comment.
Comment = 0x10003,
/// Exiv2 type for a CIFF directory.
Directory = 0x10004,
/// XMP text type.
XmpText = 0x10005,
/// XMP alternative type.
XmpAlt = 0x10006,
/// XMP bag type.
XmpBag = 0x10007,
/// XMP sequence type.
XmpSeq = 0x10008,
/// XMP language alternative type.
LangAlt = 0x10009,
/// Invalid type id.
InvalidTypeId = 0x1fffe,
/// Last type id.
LastTypeId = 0x1ffff,
}
/// Type to express the byte orde
#[repr(i32)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, IntEnum)]
pub enum ExifByteOrder {
Invalid = 0,
Little = 1,
Big = 2,
}
/// Exif Key<br>
/// See [all available keys](https://exiv2.org/tags.html).
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> {
let ptr = value.as_ptr();
let re = unsafe { _exif::exif_create_key_by_key(ptr) };
if re.is_null() {
return Err(());
}
Ok(Self { key: re })
}
}
impl TryFrom<&str> for ExifKey {
type Error = ();
fn try_from(value: &str) -> Result<Self, Self::Error> {
let p = CString::new(value);
if p.is_err() {
return Err(());
}
Self::try_from(p.unwrap())
}
}
impl Drop for ExifKey {
fn drop(&mut self) {
if !self.key.is_null() {
unsafe { _exif::exif_free_key(self.key) };
}
}
}
impl ToRawHandle<_exif::ExifKey> for ExifKey {
unsafe fn to_raw_handle(&self) -> *mut _exif::ExifKey {
self.key
}
}
#[allow(dead_code)]
impl ExifKey {
/// Create an Exif key from the tag number and group name
pub fn from_id(id: u16, group_name: &str) -> Result<Self, ()> {
let p = CString::new(group_name);
if p.is_err() {
return Err(());
}
let p = p.unwrap();
let ptr = p.as_ptr();
let re = unsafe { _exif::exif_create_key_by_id(id, ptr) };
if re.is_null() {
return Err(());
}
Ok(Self { key: re })
}
/// Return the key of the metadatum as a string.
/// The key is of the form `familyName.groupName.tagName`
/// # Note
/// However that the key is not necessarily unique, e.g., an ExifData may contain multiple metadata with the same key.
pub fn key(&self) -> Option<String> {
if self.key.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_key_key(self.key) };
if r.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(r) };
let s = s.to_str();
if s.is_err() {
return None;
}
let s = s.unwrap();
Some(s.to_owned())
}
/// Return an identifier for the type of metadata (the first part of the key)
pub fn family_name(&self) -> Option<String> {
if self.key.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_key_family_name(self.key) };
if r.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(r) };
let s = s.to_str();
if s.is_err() {
return None;
}
let s = s.unwrap();
Some(s.to_owned())
}
/// Return the name of the group (the second part of the key)
pub fn group_name(&self) -> Option<String> {
if self.key.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_key_group_name(self.key) };
if r.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(r) };
let s = s.to_owned();
unsafe { _exif::exif_free(r as *mut ::std::os::raw::c_void) };
let s = s.to_str();
if s.is_err() {
return None;
}
let s = s.unwrap();
Some(s.to_owned())
}
/// Return the name of the tag (which is also the third part of the key)
pub fn tag_name(&self) -> Option<String> {
if self.key.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_key_tag_name(self.key) };
if r.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(r) };
let s = s.to_owned();
unsafe { _exif::exif_free(r as *mut ::std::os::raw::c_void) };
let s = s.to_str();
if s.is_err() {
return None;
}
let s = s.unwrap();
Some(s.to_owned())
}
/// Return the tag number.
pub fn tag(&self) -> Option<u16> {
if self.key.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_key_tag_tag(self.key) };
if r == 65535 {
None
} else {
Some(r)
}
}
/// Return a label for the tag.
pub fn tag_label(&self) -> Option<String> {
if self.key.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_key_tag_label(self.key) };
if r.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(r) };
let s = s.to_owned();
unsafe { _exif::exif_free(r as *mut ::std::os::raw::c_void) };
let s = s.to_str();
if s.is_err() {
return None;
}
let s = s.unwrap();
Some(s.to_owned())
}
/// Return the tag description.
pub fn tag_desc(&self) -> Option<String> {
if self.key.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_key_tag_desc(self.key) };
if r.is_null() {
return None;
}
let s = unsafe { CStr::from_ptr(r) };
let s = s.to_owned();
unsafe { _exif::exif_free(r as *mut ::std::os::raw::c_void) };
let s = s.to_str();
if s.is_err() {
return None;
}
let s = s.unwrap();
Some(s.to_owned())
}
/// Return the default type id for this tag.
pub fn default_typeid(&self) -> Option<ExifTypeID> {
if self.key.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_key_default_type_id(self.key) };
if r == -1 {
return None;
}
let re = ExifTypeID::from_int(r);
if re.is_err() {
return None;
}
Some(re.unwrap())
}
}
/// Common interface for all types of values used with metadata.
pub struct ExifValue {
value: *mut _exif::ExifValue,
}
impl TryFrom<ExifTypeID> for ExifValue {
type Error = ();
fn try_from(value: ExifTypeID) -> Result<Self, Self::Error> {
let d = value.int_value();
let r = unsafe { _exif::exif_create_value(d) };
if r.is_null() {
return Err(());
}
Ok(Self { value: r })
}
}
impl TryFrom<i32> for ExifValue {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
let e = ExifTypeID::from_int(value);
if e.is_err() {
return Err(());
}
Self::try_from(e.unwrap())
}
}
impl Drop for ExifValue {
fn drop(&mut self) {
if !self.value.is_null() {
unsafe { _exif::exif_free_value(self.value) };
}
}
}
impl ToRawHandle<_exif::ExifValue> for ExifValue {
unsafe fn to_raw_handle(&self) -> *mut _exif::ExifValue {
self.value
}
}
#[allow(dead_code)]
impl ExifValue {
/// Return the type identifier (Exif data format type).
pub fn type_id(&self) -> Option<ExifTypeID> {
if self.value.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_value_type_id(self.value) };
if r == 0 {
return None;
}
let re = ExifTypeID::from_int(r);
if re.is_err() {
return None;
}
Some(re.unwrap())
}
/// Return the number of components of the value.
pub fn count(&self) -> Option<usize> {
if self.value.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_value_count(self.value) };
if r < 0 {
return None;
}
Some(r as usize)
}
/// Return the size of the value in bytes.
pub fn size(&self) -> Option<usize> {
if self.value.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_value_size(self.value) };
if r < 0 {
return None;
}
Some(r as usize)
}
/// Return the size of the data area, 0 if there is none.
pub fn size_data_area(&self) -> Option<usize> {
if self.value.is_null() {
return None;
}
let r = unsafe { _exif::exif_get_value_size_data_area(self.value) };
if r < 0 {
return None;
}
Some(r as usize)
}
/// Read the value from a character buffer.
/// * `buf` - Buffer
/// * `byte_order` - Applicable byte order (little or big endian). Default: invaild.
pub fn read(&mut self, buf: &[u8], byte_order: Option<ExifByteOrder>) -> Result<(), ()> {
if self.value.is_null() {
return Err(());
}
let buf_len = buf.len() as c_long;
let order = match byte_order {
Some(o) => o,
None => ExifByteOrder::Invalid,
};
let order = order.int_value();
let ptr = buf.as_ptr();
let r = unsafe { _exif::exif_value_read(self.value, ptr, buf_len, order) };
if r == 0 {
Ok(())
} else {
Err(())
}
}
/// Return the value / n-th component of the value as a string
/// * `n` - specify the component, if None, return whole value
pub fn to_string(&self, n: Option<usize>) -> Option<CString> {
if self.value.is_null() {
return None;
}
if n.is_some() {
let c = self.count();
if c.is_none() {
return None;
}
if n.as_ref().unwrap() >= c.as_ref().unwrap() {
return None;
}
}
let mut size: Vec<_exif::size_t> = vec![0];
let ptr = size.as_mut_ptr();
if ptr.is_null() {
return None;
}
let r = if n.is_none() {
unsafe { _exif::exif_value_to_string(self.value, ptr) }
} else {
unsafe { _exif::exif_value_to_string2(self.value, ptr, n.unwrap() as c_long) }
};
if r.is_null() {
return None;
}
let s = unsafe { CFixedStr::from_mut_ptr(r, size[0] as usize) };
let s = s.to_owned();
unsafe { _exif::exif_free(r as *mut ::std::os::raw::c_void) };
if !self.ok() {
return None;
}
Some(s.into_c_string())
}
/// Check the ok status indicator.
/// After a to<Type> conversion, this indicator shows whether the conversion was successful.
pub fn ok(&self) -> bool {
if self.value.is_null() {
return false;
}
let r = unsafe { _exif::exif_get_value_ok(self.value) };
r != 0
}
/// Convert the n-th component of the value to a int64
pub fn to_int64(&self, n: usize) -> Option<i64> {
if self.value.is_null() {
return None;
}
let c = self.count();
if c.is_none() {
return None;
}
let c = c.unwrap();
if n >= c {
return None;
}
let r = unsafe { _exif::exif_value_to_int64(self.value, n as i32) };
if !self.ok() {
return None;
}
Some(r)
}
}
/// A container for Exif data.
pub struct ExifData {
data: *mut _exif::ExifData,
}
#[allow(dead_code)]
impl ExifData {
/// Create a new container
pub fn new() -> Result<Self, ()> {
let d = unsafe { _exif::exif_data_new() };
if d.is_null() {
return Err(());
}
Ok(Self { data: d })
}
/// Add a data from the supplied key and value pair.
/// No duplicate checks are performed, i.e., it is possible to add multiple metadata with the same key.
pub fn add(&mut self, key: &ExifKey, value: &ExifValue) -> Result<(), ()> {
let k = unsafe { key.to_raw_handle() };
let v = unsafe { value.to_raw_handle() };
if k.is_null() || v.is_null() {
return Err(());
}
let r = unsafe { _exif::exif_data_add(self.data, k, v) };
if r == 0 {
Err(())
} else {
Ok(())
}
}
/// Delete all Exifdatum instances resulting in an empty container.
/// Note that this also removes thumbnails.
pub fn clear(&mut self) -> Result<(), ()> {
if self.data.is_null() {
return Err(());
}
let r = unsafe { _exif::exif_data_clear(self.data) };
if r == 0 {
Err(())
} else {
Ok(())
}
}
/// Get the number of metadata entries.
pub fn count(&self) -> Option<usize> {
if self.data.is_null() {
return None;
}
let r = unsafe { _exif::exif_data_get_count(self.data) };
if r == -1 {
return None;
}
Some(r as usize)
}
/// Return true if there is no Exif metadata.
pub fn empty(&self) -> Option<bool> {
if self.data.is_null() {
return None;
}
let r = unsafe { _exif::exif_data_is_empty(self.data) };
if r == -1 {
None
} else if r == 0 {
Some(false)
} else {
Some(true)
}
}
}
impl Drop for ExifData {
fn drop(&mut self) {
if !self.data.is_null() {
unsafe { _exif::exif_free_data(self.data) };
}
}
}
impl ToRawHandle<_exif::ExifData> for ExifData {
unsafe fn to_raw_handle(&self) -> *mut _exif::ExifData {
self.data
}
}
pub struct ExifImage {
img: *mut _exif::ExifImage,
}
#[allow(dead_code)]
impl ExifImage {
pub fn new<S: AsRef<OsStr> + ?Sized>(file_name: &S) -> Result<Self, ()> {
let p = Path::new(file_name);
if !p.exists() {
return Err(());
}
let p = p.to_str();
if p.is_none() {
return Err(());
}
let p = CString::new(p.unwrap());
if p.is_err() {
return Err(());
}
let p = p.unwrap();
let ptr = p.as_c_str().as_ptr();
let f = unsafe { _exif::create_exif_image(ptr) };
if f.is_null() {
return Err(());
}
Ok(Self { img: f })
}
pub fn set_exif_data(&mut self, data: &ExifData) -> Result<(), ()> {
if self.img.is_null() {
return Err(());
}
let d = unsafe { data.to_raw_handle() };
if d.is_null() {
return Err(());
}
let d = unsafe { _exif::exif_image_set_exif_data(self.img, d) };
if d == 0 {
Ok(())
} else {
Err(())
}
}
pub fn write_metadata(&mut self) -> Result<(), ()> {
if self.img.is_null() {
return Err(());
}
let d = unsafe { _exif::exif_image_write_metadata(self.img) };
if d == 0 {
Ok(())
} else {
Err(())
}
}
}
impl Drop for ExifImage {
fn drop(&mut self) {
if !self.img.is_null() {
unsafe { _exif::free_exif_image(self.img) };
}
}
}
#[test]
fn test_exif_key() {
let k = ExifKey::try_from("Exif.Image.XPTitle");
assert!(k.is_ok());
let k = k.unwrap();
assert_eq!(Some(String::from("Exif.Image.XPTitle")), k.key());
assert_eq!(Some(String::from("Exif")), k.family_name());
assert_eq!(Some(String::from("Image")), k.group_name());
assert_eq!(Some(String::from("XPTitle")), k.tag_name());
assert_eq!(Some(40091), k.tag());
assert_eq!(Some(String::from("Windows Title")), k.tag_label());
assert_eq!(
Some(String::from("Title tag used by Windows, encoded in UCS2")),
k.tag_desc()
);
assert_eq!(Some(ExifTypeID::BYTE), k.default_typeid());
let k2 = ExifKey::from_id(40091, "Image");
assert!(k2.is_ok());
let k2 = k2.unwrap();
assert_eq!(Some(String::from("Exif.Image.XPTitle")), k2.key());
}
#[test]
fn test_exif_value() {
let v = ExifValue::try_from(ExifTypeID::BYTE);
assert!(v.is_ok());
let mut v = v.unwrap();
assert_eq!(Some(ExifTypeID::BYTE), v.type_id());
assert_eq!(Some(0), v.count());
assert_eq!(Some(0), v.size());
assert_eq!(Some(0), v.size_data_area());
assert!(v.read("test".as_bytes(), None).is_ok());
assert_eq!(Some(4), v.count());
assert_eq!(Some(4), v.size());
let c = CString::new("116 101 115 116").unwrap();
assert_eq!(Some(c), v.to_string(None));
assert_eq!(Some(4), v.count());
assert_eq!(Some(4), v.size());
assert_eq!(Some(CString::new("116").unwrap()), v.to_string(Some(0)));
let mut v2 = ExifValue::try_from(ExifTypeID::SLongLong).unwrap();
v2.read(&(102345 as i64).to_le_bytes(), Some(ExifByteOrder::Little))
.unwrap();
assert_eq!(Some(8), v2.count());
}
#[test]
fn test_exif_data() {
let mut d = ExifData::new().unwrap();
assert_eq!(Some(0), d.count());
assert_eq!(Some(true), d.empty());
let k = ExifKey::try_from("Exif.Image.XPTitle").unwrap();
let mut v = ExifValue::try_from(ExifTypeID::BYTE).unwrap();
v.read("test".as_bytes(), None).unwrap();
d.add(&k, &v).unwrap();
assert_eq!(Some(1), d.count());
assert_eq!(Some(false), d.empty());
d.clear().unwrap();
assert_eq!(Some(0), d.count());
assert_eq!(Some(true), d.empty());
}
#[test]
fn test_exif_image() {
let p = Path::new("./test");
if !p.exists() {
create_dir("./test").unwrap();
}
let target = "./test/夏のチマメ隊🏖️_91055644_p0.jpg";
copy("./testdata/夏のチマメ隊🏖️_91055644_p0.jpg", target).unwrap();
let mut d = ExifData::new().unwrap();
let k = ExifKey::try_from("Exif.Image.ImageDescription").unwrap();
let mut v = ExifValue::try_from(ExifTypeID::AsciiString).unwrap();
v.read("夏のチマメ隊🏖️".as_bytes(), None).unwrap();
d.add(&k, &v).unwrap();
{
let mut img = ExifImage::new(target).unwrap();
img.set_exif_data(&d).unwrap();
img.write_metadata().unwrap();
}
}

View File

@@ -1,16 +1,27 @@
#[cfg(feature = "c_fixed_string")]
extern crate c_fixed_string;
extern crate chrono;
extern crate dateparser;
extern crate futures_util;
extern crate json;
#[cfg(feature = "int-enum")]
extern crate int_enum;
#[macro_use]
extern crate lazy_static;
extern crate tokio;
extern crate regex;
extern crate reqwest;
#[cfg(feature = "utf16string")]
extern crate utf16string;
#[cfg(feature = "exif")]
#[doc(hidden)]
mod _exif;
mod cookies;
mod data;
mod download;
#[cfg(feature = "exif")]
mod exif;
mod i18n;
mod opthelper;
mod opts;

View File

@@ -1,6 +1,7 @@
use crate::data::json::ToJson;
use json::JsonValue;
use regex::Regex;
use std::convert::TryInto;
lazy_static! {
#[doc(hidden)]
@@ -36,13 +37,21 @@ impl PixivID {
}
None
}
pub fn to_link(&self) -> String {
match self {
Self::Artwork(id) => {
format!("https://www.pixiv.net/artworks/{}", id)
}
}
}
}
impl ToJson for PixivID {
fn to_json(&self) -> Option<JsonValue> {
match *self {
PixivID::Artwork(id) => {
Some(json::value!({"type": "artwork", "id": id}))
Some(json::value!({"type": "artwork", "id": id, "link": self.to_link()}))
}
}
}
@@ -71,3 +80,21 @@ impl ToPixivID for u64 {
Some(PixivID::Artwork(*self))
}
}
impl TryInto<u64> for PixivID {
type Error = ();
fn try_into(self) -> Result<u64, Self::Error> {
match self {
Self::Artwork(id) => { Ok(id) }
}
}
}
impl TryInto<u64> for &PixivID {
type Error = ();
fn try_into(self) -> Result<u64, Self::Error> {
match *self {
PixivID::Artwork(id) => { Ok(id) }
}
}
}

View File

@@ -1,6 +1,7 @@
use crate::gettext;
use reqwest::IntoUrl;
use std::env;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
@@ -28,6 +29,7 @@ pub fn check_file_exists(path: &str) -> bool {
pub fn ask_need_overwrite(path: &str) -> bool {
let s = gettext("Do you want to delete file \"<file>\"?").replace("<file>", path);
print!("{}(y/n)", s.as_str());
std::io::stdout().flush().unwrap();
let mut d = String::from("");
loop {
let re = std::io::stdin().read_line(&mut d);

2
testdata/README.md vendored Normal file
View File

@@ -0,0 +1,2 @@
# Source
[夏のチマメ隊🏖️_91055644_p0.jpg](夏のチマメ隊🏖️_91055644_p0.jpg): [Pixiv](https://www.pixiv.net/artworks/91055644)

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

1
utils Submodule

Submodule utils added at 3fe5fa507b