mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-06-06 05:49:01 +08:00
add exif support
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/target
|
||||
.vscode/
|
||||
test/
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "utils"]
|
||||
path = utils
|
||||
url = https://github.com/lifegpc/c-utils
|
||||
248
Cargo.lock
generated
248
Cargo.lock
generated
@@ -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"
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -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
94
build.rs
Normal 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
1
exif/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build/
|
||||
41
exif/CMakeLists.txt
Normal file
41
exif/CMakeLists.txt
Normal 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})
|
||||
34
exif/cmake/FindExiv2.cmake
Normal file
34
exif/cmake/FindExiv2.cmake
Normal 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
65
exif/exif.h
Normal 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
|
||||
93
exif/patchs/basicio.cpp.patch
Normal file
93
exif/patchs/basicio.cpp.patch
Normal 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
250
exif/src/exif.cpp
Normal 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
19
exif/src/exif_priv.h
Normal 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
5
src/_exif.rs
Normal 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
49
src/data/data.rs
Normal 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
60
src/data/exif.rs
Normal 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(())
|
||||
}
|
||||
@@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
pub mod data;
|
||||
#[cfg(feature = "exif")]
|
||||
pub mod exif;
|
||||
pub mod json;
|
||||
|
||||
@@ -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
720
src/exif.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
11
src/main.rs
11
src/main.rs
@@ -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;
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
2
testdata/README.md
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Source
|
||||
[夏のチマメ隊🏖️_91055644_p0.jpg](夏のチマメ隊🏖️_91055644_p0.jpg): [Pixiv](https://www.pixiv.net/artworks/91055644)
|
||||
BIN
testdata/夏のチマメ隊🏖️_91055644_p0.jpg
vendored
Normal file
BIN
testdata/夏のチマメ隊🏖️_91055644_p0.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 MiB |
1
utils
Submodule
1
utils
Submodule
Submodule utils added at 3fe5fa507b
Reference in New Issue
Block a user