mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-11-20 11:21:14 +00:00
Replace all bindings with calls to external binaries
This commit is contained in:
parent
7fd707c8df
commit
edd6bb4a60
16 changed files with 479 additions and 1283 deletions
377
Cargo.lock
generated
377
Cargo.lock
generated
|
@ -261,9 +261,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.15"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
@ -368,66 +368,12 @@ version = "0.13.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.56.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr 0.4.0",
|
||||
"clang-sys",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453c49e5950bb0eb63bb3df640e31618846c89d5b7faa54040d76e98e0134375"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr 0.5.0",
|
||||
"clang-sys",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
"peeking_take_while",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex 1.0.0",
|
||||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "0.19.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
|
@ -464,30 +410,6 @@ dependencies = [
|
|||
"bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
|
||||
dependencies = [
|
||||
"nom 5.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db507a7679252d2276ed0dd8113c6875ec56d3089f9225b2b42c30cc1f8e5c89"
|
||||
dependencies = [
|
||||
"nom 6.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -506,17 +428,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81cf2cc85830eae84823884db23c5306442a6c3d5bfd3beb2f2a2c829faa1816"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.3"
|
||||
|
@ -546,9 +457,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
|||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.1.5"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -628,44 +539,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-next"
|
||||
version = "4.4.0-dev"
|
||||
source = "git+https://github.com/jwiesler/rust-ffmpeg#1966244b303a365d22eca0e33160e58b3d8ce903"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ffmpeg-sys-next",
|
||||
"libc",
|
||||
"log",
|
||||
"vsprintf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ffmpeg-sys-next"
|
||||
version = "4.3.5"
|
||||
source = "git+https://github.com/jwiesler/rust-ffmpeg-sys#5d819d172f116a8a6d960df35e4d5bc8b4a82924"
|
||||
dependencies = [
|
||||
"bindgen 0.56.0",
|
||||
"cc",
|
||||
"libc",
|
||||
"num_cpus",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
@ -692,12 +565,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.16"
|
||||
|
@ -822,22 +689,6 @@ dependencies = [
|
|||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gexiv2-sys"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3e66ac4aab8b401ca63838116d080b98e12fb5056e1c2a44e35c93da3526d13"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[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.4"
|
||||
|
@ -898,12 +749,6 @@ version = "1.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
|
@ -952,28 +797,12 @@ 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.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "local-channel"
|
||||
version = "0.1.2"
|
||||
|
@ -994,9 +823,9 @@ checksum = "84f9a2d3e27ce99ce2c3aad0b09b1a7b916293ea9b2bf624c13fe646fadd8da4"
|
|||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.4"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
|
||||
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
@ -1010,17 +839,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "magick_rust"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13584c9d754ca069ee43d1ee155c4a14a87e7e68faf7b61255f2d3133ee259c4"
|
||||
dependencies = [
|
||||
"bindgen 0.59.1",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.0.1"
|
||||
|
@ -1038,9 +856,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
|||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
|
@ -1079,28 +897,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[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 = "nom"
|
||||
version = "6.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c5c51b9083a3c620fa67a2a635d1ce7d95b897e957d6b28ff9a5da960a103a6"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"funty",
|
||||
"memchr",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.3.6"
|
||||
|
@ -1120,17 +916,6 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
|
@ -1164,9 +949,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
|
@ -1175,9 +960,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.3"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
|
||||
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
|
@ -1193,12 +978,6 @@ version = "1.0.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
|
||||
|
||||
[[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"
|
||||
|
@ -1226,15 +1005,11 @@ dependencies = [
|
|||
"async-stream",
|
||||
"awc",
|
||||
"base64",
|
||||
"ffmpeg-next",
|
||||
"ffmpeg-sys-next",
|
||||
"futures",
|
||||
"magick_rust",
|
||||
"mime",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"rexiv2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
@ -1281,12 +1056,6 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.10"
|
||||
|
@ -1347,12 +1116,6 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.4"
|
||||
|
@ -1404,9 +1167,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.4.6"
|
||||
version = "1.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -1428,23 +1191,6 @@ version = "0.6.25"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "rexiv2"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab34307334dcf90c2f9afeda08e0c52310f4d9f282cf6e8ec6a6ffb0b09826a5"
|
||||
dependencies = [
|
||||
"gexiv2-sys",
|
||||
"libc",
|
||||
"num-rational",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
|
@ -1510,18 +1256,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.129"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.129"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1530,9 +1276,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.66"
|
||||
version = "1.0.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
|
||||
checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -1553,9 +1299,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.9.7"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a0c8611594e2ab4ebbf06ec7cbbf0a99450b8570e96cbf5188b5d5f6ef18d81"
|
||||
checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if",
|
||||
|
@ -1572,9 +1318,9 @@ checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
|
|||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.5"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
|
||||
checksum = "9204c41a1597a8c5af23c82d1c921cb01ec0a4c59e07a9c7306062829a3903f3"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if",
|
||||
|
@ -1592,18 +1338,6 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.0"
|
||||
|
@ -1750,21 +1484,6 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[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"
|
||||
|
@ -1776,18 +1495,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.26"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
|
||||
checksum = "e1c319f97498ee34e17e1d7813fcd28a0ec1aaf350a4c44883d2fe741edb1c70"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.26"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
|
||||
checksum = "8bf955fbafde33573fd32e90312488fa2ea68f7a220a5faab1809fa90690224f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -2067,12 +1786,6 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
|
@ -2085,16 +1798,6 @@ version = "0.9.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "vsprintf"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aec2f81b75ca063294776b4f7e8da71d1d5ae81c2b1b149c8d89969230265d63"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
|
@ -2155,15 +1858,6 @@ version = "0.2.76"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdb075a845574a1fa5f09fd77e43f7747599301ea3417a9fbffdeedfc1f4a29"
|
||||
|
||||
[[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"
|
||||
|
@ -2180,23 +1874,8 @@ 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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -20,12 +20,10 @@ async-stream = "0.3.0"
|
|||
awc = { version = "3.0.0-beta.7", default-features = false }
|
||||
base64 = "0.13.0"
|
||||
futures = "0.3.4"
|
||||
magick_rust = { version = "0.15.0" }
|
||||
mime = "0.3.1"
|
||||
num_cpus = "1"
|
||||
once_cell = "1.4.0"
|
||||
rand = "0.8.0"
|
||||
rexiv2 = "0.9.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sha2 = "0.9.0"
|
||||
|
@ -33,18 +31,8 @@ sled = { version = "0.34.6" }
|
|||
structopt = "0.3.14"
|
||||
thiserror = "1.0"
|
||||
time = { version = "0.2.23", features = ["serde"] }
|
||||
tokio = { version = "1", default-features = false, features = ["sync", "process"] }
|
||||
tokio = { version = "1", default-features = false, features = ["io-util", "process", "sync"] }
|
||||
tracing = "0.1.15"
|
||||
tracing-futures = "0.2.4"
|
||||
tracing-subscriber = { version = "0.2.5", features = ["fmt", "tracing-log"] }
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
|
||||
[dependencies.ffmpeg-next]
|
||||
version = "4.4.0-dev"
|
||||
default-features = false
|
||||
features = ["codec", "filter", "device", "format", "resampling", "postprocessing", "software-resampling", "software-scaling"]
|
||||
git = "https://github.com/jwiesler/rust-ffmpeg"
|
||||
|
||||
[dependencies.ffmpeg-sys-next]
|
||||
version = "4.3.5"
|
||||
git = "https://github.com/jwiesler/rust-ffmpeg-sys"
|
||||
|
|
|
@ -78,7 +78,7 @@ RUN \
|
|||
--disable-static \
|
||||
--disable-docs \
|
||||
--prefix=/usr/local \
|
||||
--with-utilities=no \
|
||||
--with-utilities=yes \
|
||||
--with-magick-plus-plus=no \
|
||||
--without-perl \
|
||||
--with-xml=yes \
|
||||
|
@ -121,7 +121,7 @@ FROM cross-build as pict-rs-builder
|
|||
|
||||
RUN \
|
||||
apt-get install -y \
|
||||
libgexiv2-dev:$ARCH \
|
||||
exiv2:$ARCH \
|
||||
libxml2:$ARCH \
|
||||
libltdl7:$ARCH \
|
||||
llvm-dev \
|
||||
|
@ -133,15 +133,7 @@ RUN \
|
|||
libwebpdemux2:$ARCH \
|
||||
libwebpmux3:$ARCH \
|
||||
libgomp1:$ARCH \
|
||||
libavcodec-dev:$ARCH \
|
||||
libavfilter-dev:$ARCH \
|
||||
libavdevice-dev:$ARCH \
|
||||
libavformat-dev:$ARCH \
|
||||
libavresample-dev:$ARCH \
|
||||
libavutil-dev:$ARCH \
|
||||
libswscale-dev:$ARCH \
|
||||
libswresample-dev:$ARCH \
|
||||
ffmpeg
|
||||
ffmpeg:$ARCH
|
||||
|
||||
ENV \
|
||||
PATH=$PATH:/opt/build/.cargo/bin \
|
||||
|
|
|
@ -78,7 +78,7 @@ RUN \
|
|||
--disable-static \
|
||||
--disable-docs \
|
||||
--prefix=/usr/local \
|
||||
--with-utilities=no \
|
||||
--with-utilities=yes \
|
||||
--with-magick-plus-plus=no \
|
||||
--without-perl \
|
||||
--with-xml=yes \
|
||||
|
@ -121,7 +121,7 @@ FROM cross-build as pict-rs-builder
|
|||
|
||||
RUN \
|
||||
apt-get install -y \
|
||||
libgexiv2-dev:$ARCH \
|
||||
exiv2:$ARCH \
|
||||
libxml2:$ARCH \
|
||||
libltdl7:$ARCH \
|
||||
llvm-dev \
|
||||
|
@ -133,15 +133,7 @@ RUN \
|
|||
libwebpdemux2:$ARCH \
|
||||
libwebpmux3:$ARCH \
|
||||
libgomp1:$ARCH \
|
||||
libavcodec-dev:$ARCH \
|
||||
libavfilter-dev:$ARCH \
|
||||
libavdevice-dev:$ARCH \
|
||||
libavformat-dev:$ARCH \
|
||||
libavresample-dev:$ARCH \
|
||||
libavutil-dev:$ARCH \
|
||||
libswscale-dev:$ARCH \
|
||||
libswresample-dev:$ARCH \
|
||||
ffmpeg && \
|
||||
ffmpeg:$ARCH && \
|
||||
rm -rf /usr/include/x86_64-linux-gnu
|
||||
|
||||
ENV \
|
||||
|
|
|
@ -78,7 +78,7 @@ RUN \
|
|||
--disable-static \
|
||||
--disable-docs \
|
||||
--prefix=/usr/local \
|
||||
--with-utilities=no \
|
||||
--with-utilities=yes \
|
||||
--with-magick-plus-plus=no \
|
||||
--without-perl \
|
||||
--with-xml=yes \
|
||||
|
@ -121,7 +121,7 @@ FROM cross-build as pict-rs-builder
|
|||
|
||||
RUN \
|
||||
apt-get install -y \
|
||||
libgexiv2-dev:$ARCH \
|
||||
exiv2:$ARCH \
|
||||
libxml2:$ARCH \
|
||||
libltdl7:$ARCH \
|
||||
llvm-dev \
|
||||
|
@ -133,14 +133,6 @@ RUN \
|
|||
libwebpdemux2:$ARCH \
|
||||
libwebpmux3:$ARCH \
|
||||
libgomp1:$ARCH \
|
||||
libavcodec-dev:$ARCH \
|
||||
libavfilter-dev:$ARCH \
|
||||
libavdevice-dev:$ARCH \
|
||||
libavformat-dev:$ARCH \
|
||||
libavresample-dev:$ARCH \
|
||||
libavutil-dev:$ARCH \
|
||||
libswscale-dev:$ARCH \
|
||||
libswresample-dev:$ARCH \
|
||||
ffmpeg && \
|
||||
rm -rf /usr/include/x86_64-linux-gnu
|
||||
|
||||
|
|
27
src/error.rs
27
src/error.rs
|
@ -1,4 +1,4 @@
|
|||
use crate::validate::GifError;
|
||||
use crate::{exiv2::Exvi2Error, ffmpeg::VideoError, magick::MagickError};
|
||||
use actix_web::{http::StatusCode, HttpResponse, ResponseError};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
@ -57,26 +57,23 @@ pub(crate) enum UploadError {
|
|||
#[error("Tried to save an image with an already-taken name")]
|
||||
DuplicateAlias,
|
||||
|
||||
#[error("Error validating Gif file, {0}")]
|
||||
Gif(#[from] GifError),
|
||||
|
||||
#[error("Error transcoding, {0}")]
|
||||
Transcode(crate::validate::transcode::Error),
|
||||
|
||||
#[error("Tried to create file, but file already exists")]
|
||||
FileExists,
|
||||
|
||||
#[error("File metadata could not be parsed, {0}")]
|
||||
Validate(#[from] rexiv2::Rexiv2Error),
|
||||
|
||||
#[error("Error in MagickWand, {0}")]
|
||||
Wand(String),
|
||||
|
||||
#[error("{0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
#[error("Range header not satisfiable")]
|
||||
Range,
|
||||
|
||||
#[error("{0}")]
|
||||
VideoError(#[from] VideoError),
|
||||
|
||||
#[error("{0}")]
|
||||
Exvi2Error(#[from] Exvi2Error),
|
||||
|
||||
#[error("{0}")]
|
||||
MagickError(#[from] MagickError),
|
||||
}
|
||||
|
||||
impl From<awc::error::SendRequestError> for UploadError {
|
||||
|
@ -109,7 +106,9 @@ impl From<actix_web::error::BlockingError> for UploadError {
|
|||
impl ResponseError for UploadError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
UploadError::Gif(_)
|
||||
UploadError::VideoError(_)
|
||||
| UploadError::Exvi2Error(_)
|
||||
| UploadError::MagickError(_)
|
||||
| UploadError::DuplicateAlias
|
||||
| UploadError::NoFiles
|
||||
| UploadError::Upload(_)
|
||||
|
|
127
src/exiv2.rs
127
src/exiv2.rs
|
@ -1,10 +1,10 @@
|
|||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum FormatError {
|
||||
pub(crate) enum Exvi2Error {
|
||||
#[error("Failed to interface with exiv2")]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
#[error("Failed to identify file")]
|
||||
Status,
|
||||
#[error("Mime Parse: {0}")]
|
||||
Mime(#[from] mime::FromStrError),
|
||||
|
||||
#[error("Identify semaphore is closed")]
|
||||
Closed,
|
||||
|
@ -12,6 +12,9 @@ pub(crate) enum FormatError {
|
|||
#[error("Requested information is not present")]
|
||||
Missing,
|
||||
|
||||
#[error("Exiv2 command failed")]
|
||||
Status,
|
||||
|
||||
#[error("Requested information was present, but not supported")]
|
||||
Unsupported,
|
||||
}
|
||||
|
@ -21,6 +24,13 @@ pub(crate) enum ValidInputType {
|
|||
Gif,
|
||||
Png,
|
||||
Jpeg,
|
||||
Webp,
|
||||
}
|
||||
|
||||
pub(crate) struct Details {
|
||||
pub(crate) mime_type: mime::Mime,
|
||||
pub(crate) width: usize,
|
||||
pub(crate) height: usize,
|
||||
}
|
||||
|
||||
static MAX_READS: once_cell::sync::OnceCell<tokio::sync::Semaphore> =
|
||||
|
@ -30,48 +40,125 @@ fn semaphore() -> &'static tokio::sync::Semaphore {
|
|||
MAX_READS.get_or_init(|| tokio::sync::Semaphore::new(num_cpus::get() * 4))
|
||||
}
|
||||
|
||||
pub(crate) async fn format<P>(file: P) -> Result<ValidInputType, FormatError>
|
||||
pub(crate) async fn clear_metadata<P>(file: P) -> Result<(), Exvi2Error>
|
||||
where
|
||||
P: AsRef<std::path::Path>,
|
||||
{
|
||||
let permit = semaphore().acquire().await?;
|
||||
|
||||
let status = tokio::process::Command::new("exiv2")
|
||||
.arg(&"rm")
|
||||
.arg(&file.as_ref())
|
||||
.spawn()?
|
||||
.wait()
|
||||
.await?;
|
||||
|
||||
drop(permit);
|
||||
|
||||
if !status.success() {
|
||||
return Err(Exvi2Error::Status);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn input_type<P>(file: P) -> Result<ValidInputType, Exvi2Error>
|
||||
where
|
||||
P: AsRef<std::path::Path>,
|
||||
{
|
||||
let permit = semaphore().acquire().await?;
|
||||
|
||||
let output = tokio::process::Command::new("exiv2")
|
||||
.args([
|
||||
&AsRef::<std::ffi::OsStr>::as_ref(&"pr"),
|
||||
&file.as_ref().as_ref(),
|
||||
])
|
||||
.arg(&"pr")
|
||||
.arg(&file.as_ref())
|
||||
.output()
|
||||
.await?;
|
||||
drop(permit);
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(FormatError::Status);
|
||||
}
|
||||
|
||||
let s = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
let line = s
|
||||
let mime_line = s
|
||||
.lines()
|
||||
.find(|line| line.starts_with("MIME"))
|
||||
.ok_or_else(|| FormatError::Missing)?;
|
||||
.ok_or_else(|| Exvi2Error::Missing)?;
|
||||
|
||||
let mut segments = line.rsplit(':');
|
||||
let mime_type = segments.next().ok_or_else(|| FormatError::Missing)?;
|
||||
let mut segments = mime_line.rsplit(':');
|
||||
let mime_type = segments.next().ok_or_else(|| Exvi2Error::Missing)?;
|
||||
|
||||
let input_type = match mime_type.trim() {
|
||||
"video/mp4" => ValidInputType::Mp4,
|
||||
"video/quicktime" => ValidInputType::Mp4,
|
||||
"image/gif" => ValidInputType::Gif,
|
||||
"image/png" => ValidInputType::Png,
|
||||
"image/jpeg" => ValidInputType::Jpeg,
|
||||
_ => return Err(FormatError::Unsupported),
|
||||
"image/webp" => ValidInputType::Webp,
|
||||
_ => return Err(Exvi2Error::Unsupported),
|
||||
};
|
||||
|
||||
Ok(input_type)
|
||||
}
|
||||
|
||||
impl From<tokio::sync::AcquireError> for FormatError {
|
||||
fn from(_: tokio::sync::AcquireError) -> FormatError {
|
||||
FormatError::Closed
|
||||
pub(crate) async fn details<P>(file: P) -> Result<Details, Exvi2Error>
|
||||
where
|
||||
P: AsRef<std::path::Path>,
|
||||
{
|
||||
let permit = semaphore().acquire().await?;
|
||||
|
||||
let output = tokio::process::Command::new("exiv2")
|
||||
.arg(&"pr")
|
||||
.arg(&file.as_ref())
|
||||
.output()
|
||||
.await?;
|
||||
drop(permit);
|
||||
|
||||
let s = String::from_utf8_lossy(&output.stdout);
|
||||
parse_output(s)
|
||||
}
|
||||
|
||||
fn parse_output(s: std::borrow::Cow<'_, str>) -> Result<Details, Exvi2Error> {
|
||||
let mime_line = s
|
||||
.lines()
|
||||
.find(|line| line.starts_with("MIME"))
|
||||
.ok_or_else(|| Exvi2Error::Missing)?;
|
||||
|
||||
let mut segments = mime_line.rsplit(':');
|
||||
let mime_type = segments.next().ok_or_else(|| Exvi2Error::Missing)?.trim();
|
||||
|
||||
let resolution_line = s
|
||||
.lines()
|
||||
.find(|line| line.starts_with("Image size"))
|
||||
.ok_or_else(|| Exvi2Error::Missing)?;
|
||||
|
||||
let mut segments = resolution_line.rsplit(':');
|
||||
let resolution = segments.next().ok_or_else(|| Exvi2Error::Missing)?;
|
||||
let mut resolution_segments = resolution.split('x');
|
||||
let width_str = resolution_segments
|
||||
.next()
|
||||
.ok_or_else(|| Exvi2Error::Missing)?
|
||||
.trim();
|
||||
let height_str = resolution_segments
|
||||
.next()
|
||||
.ok_or_else(|| Exvi2Error::Missing)?
|
||||
.trim();
|
||||
|
||||
let width = width_str.parse()?;
|
||||
let height = height_str.parse()?;
|
||||
|
||||
Ok(Details {
|
||||
mime_type: mime_type.parse()?,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
impl From<tokio::sync::AcquireError> for Exvi2Error {
|
||||
fn from(_: tokio::sync::AcquireError) -> Exvi2Error {
|
||||
Exvi2Error::Closed
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::num::ParseIntError> for Exvi2Error {
|
||||
fn from(_: std::num::ParseIntError) -> Exvi2Error {
|
||||
Exvi2Error::Unsupported
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,27 @@ pub(crate) enum VideoError {
|
|||
Closed,
|
||||
}
|
||||
|
||||
pub(crate) enum ThumbnailFormat {
|
||||
Jpeg,
|
||||
Webp,
|
||||
}
|
||||
|
||||
impl ThumbnailFormat {
|
||||
fn as_codec(&self) -> &'static str {
|
||||
match self {
|
||||
ThumbnailFormat::Jpeg => "mjpeg",
|
||||
ThumbnailFormat::Webp => "webp",
|
||||
}
|
||||
}
|
||||
|
||||
fn as_format(&self) -> &'static str {
|
||||
match self {
|
||||
ThumbnailFormat::Jpeg => "singlejpeg",
|
||||
ThumbnailFormat::Webp => "webp",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static MAX_TRANSCODES: once_cell::sync::OnceCell<tokio::sync::Semaphore> =
|
||||
once_cell::sync::OnceCell::new();
|
||||
|
||||
|
@ -18,22 +39,6 @@ fn semaphore() -> &'static tokio::sync::Semaphore {
|
|||
.get_or_init(|| tokio::sync::Semaphore::new(num_cpus::get().saturating_sub(1).max(1)))
|
||||
}
|
||||
|
||||
pub(crate) async fn thumbnail_jpeg<P1, P2>(from: P1, to: P2) -> Result<(), VideoError>
|
||||
where
|
||||
P1: AsRef<std::path::Path>,
|
||||
P2: AsRef<std::path::Path>,
|
||||
{
|
||||
thumbnail(from, to, "mjpeg").await
|
||||
}
|
||||
|
||||
pub(crate) async fn thumbnail_png<P1, P2>(from: P1, to: P2) -> Result<(), VideoError>
|
||||
where
|
||||
P1: AsRef<std::path::Path>,
|
||||
P2: AsRef<std::path::Path>,
|
||||
{
|
||||
thumbnail(from, to, "png").await
|
||||
}
|
||||
|
||||
pub(crate) async fn to_mp4<P1, P2>(from: P1, to: P2) -> Result<(), VideoError>
|
||||
where
|
||||
P1: AsRef<std::path::Path>,
|
||||
|
@ -42,20 +47,22 @@ where
|
|||
let permit = semaphore().acquire().await?;
|
||||
|
||||
let mut child = tokio::process::Command::new("ffmpeg")
|
||||
.arg(&"-i")
|
||||
.arg(&from.as_ref())
|
||||
.args([
|
||||
&AsRef::<std::ffi::OsStr>::as_ref(&"-i"),
|
||||
&from.as_ref().as_ref(),
|
||||
&"-movflags".as_ref(),
|
||||
&"faststart".as_ref(),
|
||||
&"-pix_fmt".as_ref(),
|
||||
&"yuv420p".as_ref(),
|
||||
&"-vf".as_ref(),
|
||||
&"scale=trunc(iw/2)*2:truc(ih/2)*2".as_ref(),
|
||||
&"-an".as_ref(),
|
||||
&"-codec".as_ref(),
|
||||
&"h264".as_ref(),
|
||||
&to.as_ref().as_ref(),
|
||||
&"-movflags",
|
||||
&"faststart",
|
||||
&"-pix_fmt",
|
||||
&"yuv420p",
|
||||
&"-vf",
|
||||
&"scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||
&"-an",
|
||||
&"-codec",
|
||||
&"h264",
|
||||
&"-f",
|
||||
&"mp4",
|
||||
])
|
||||
.arg(&to.as_ref())
|
||||
.spawn()?;
|
||||
|
||||
let status = child.wait().await?;
|
||||
|
@ -68,7 +75,11 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn thumbnail<P1, P2>(from: P1, to: P2, codec: &str) -> Result<(), VideoError>
|
||||
pub(crate) async fn thumbnail<P1, P2>(
|
||||
from: P1,
|
||||
to: P2,
|
||||
format: ThumbnailFormat,
|
||||
) -> Result<(), VideoError>
|
||||
where
|
||||
P1: AsRef<std::path::Path>,
|
||||
P2: AsRef<std::path::Path>,
|
||||
|
@ -76,17 +87,19 @@ where
|
|||
let permit = semaphore().acquire().await?;
|
||||
|
||||
let mut child = tokio::process::Command::new("ffmpeg")
|
||||
.arg(&"-i")
|
||||
.arg(&from.as_ref())
|
||||
.args([
|
||||
&AsRef::<std::ffi::OsStr>::as_ref(&"-i"),
|
||||
&from.as_ref().as_ref(),
|
||||
&"-ss".as_ref(),
|
||||
&"00:00:01.000".as_ref(),
|
||||
&"-vframes".as_ref(),
|
||||
&"1".as_ref(),
|
||||
&"-codec".as_ref(),
|
||||
&codec.as_ref(),
|
||||
&to.as_ref().as_ref(),
|
||||
&"-ss",
|
||||
&"00:00:01.000",
|
||||
&"-vframes",
|
||||
&"1",
|
||||
&"-codec",
|
||||
&format.as_codec(),
|
||||
&"-f",
|
||||
&format.as_format(),
|
||||
])
|
||||
.arg(&to.as_ref())
|
||||
.spawn()?;
|
||||
|
||||
let status = child.wait().await?;
|
||||
|
|
149
src/magick.rs
149
src/magick.rs
|
@ -1,28 +1,135 @@
|
|||
fn thumbnail_args(max_dimension: usize) -> [String; 2] {
|
||||
[
|
||||
"-sample".to_string(),
|
||||
format!("{}x{}>", max_dimension, max_dimension),
|
||||
]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum MagickError {
|
||||
#[error("{0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
#[error("Magick command failed")]
|
||||
Status,
|
||||
|
||||
#[error("Magick semaphore is closed")]
|
||||
Closed,
|
||||
|
||||
#[error("Invalid format")]
|
||||
Format,
|
||||
}
|
||||
|
||||
fn resize_args(max_dimension: usize) -> [String; 4] {
|
||||
[
|
||||
"-filter".to_string(),
|
||||
"Lanczos".to_string(),
|
||||
"-resize".to_string(),
|
||||
format!("{}x{}>", max_dimension, max_dimension),
|
||||
]
|
||||
pub(crate) enum ValidFormat {
|
||||
Jpeg,
|
||||
Png,
|
||||
Webp,
|
||||
}
|
||||
|
||||
fn crop_args(width: usize, height: usize) -> [String; 4] {
|
||||
[
|
||||
"-gravity".to_string(),
|
||||
"center".to_string(),
|
||||
"-crop".to_string(),
|
||||
format!("{}x{}>", width, height),
|
||||
]
|
||||
impl ValidFormat {
|
||||
fn as_magic_type(&self) -> &'static str {
|
||||
match self {
|
||||
ValidFormat::Jpeg => "JPEG",
|
||||
ValidFormat::Png => "PNG",
|
||||
ValidFormat::Webp => "WEBP",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn blur_args(radius: f64) -> [String; 2] {
|
||||
["-gaussian-blur".to_string(), radius.to_string()]
|
||||
static MAX_CONVERSIONS: once_cell::sync::OnceCell<tokio::sync::Semaphore> =
|
||||
once_cell::sync::OnceCell::new();
|
||||
|
||||
fn semaphore() -> &'static tokio::sync::Semaphore {
|
||||
MAX_CONVERSIONS
|
||||
.get_or_init(|| tokio::sync::Semaphore::new(num_cpus::get().saturating_sub(1).max(1)))
|
||||
}
|
||||
|
||||
pub(crate) async fn convert_file<P1, P2>(
|
||||
from: P1,
|
||||
to: P2,
|
||||
format: crate::config::Format,
|
||||
) -> Result<(), MagickError>
|
||||
where
|
||||
P1: AsRef<std::path::Path>,
|
||||
P2: AsRef<std::path::Path>,
|
||||
{
|
||||
let mut output_file = std::ffi::OsString::new();
|
||||
output_file.extend([format.to_magick_format().as_ref(), ":".as_ref()]);
|
||||
output_file.extend([to.as_ref().as_ref()]);
|
||||
|
||||
let permit = semaphore().acquire().await?;
|
||||
|
||||
let status = tokio::process::Command::new("magick")
|
||||
.arg("convert")
|
||||
.arg(&from.as_ref())
|
||||
.arg(&output_file)
|
||||
.spawn()?
|
||||
.wait()
|
||||
.await?;
|
||||
|
||||
drop(permit);
|
||||
|
||||
if !status.success() {
|
||||
return Err(MagickError::Status);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn validate_format<P>(file: &P, format: ValidFormat) -> Result<(), MagickError>
|
||||
where
|
||||
P: AsRef<std::path::Path>,
|
||||
{
|
||||
let permit = semaphore().acquire().await?;
|
||||
|
||||
let output = tokio::process::Command::new("magick")
|
||||
.args([&"identify", &"-ping", &"-format", &"%m\n"])
|
||||
.arg(&file.as_ref())
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
drop(permit);
|
||||
|
||||
let s = String::from_utf8_lossy(&output.stdout);
|
||||
|
||||
if s.lines()
|
||||
.all(|item| item.is_empty() || item == format.as_magic_type())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(MagickError::Format)
|
||||
}
|
||||
|
||||
pub(crate) async fn process_image<P1, P2>(
|
||||
input: P1,
|
||||
output: P2,
|
||||
args: Vec<String>,
|
||||
format: crate::config::Format,
|
||||
) -> Result<(), MagickError>
|
||||
where
|
||||
P1: AsRef<std::path::Path>,
|
||||
P2: AsRef<std::path::Path>,
|
||||
{
|
||||
let mut output_file = std::ffi::OsString::new();
|
||||
output_file.extend([format!("{}:", format.to_magick_format()).as_ref()]);
|
||||
output_file.extend([output.as_ref().as_ref()]);
|
||||
|
||||
let permit = semaphore().acquire().await?;
|
||||
|
||||
let status = tokio::process::Command::new("magick")
|
||||
.arg(&"convert")
|
||||
.arg(&input.as_ref())
|
||||
.args(args)
|
||||
.arg(&output.as_ref())
|
||||
.spawn()?
|
||||
.wait()
|
||||
.await?;
|
||||
|
||||
drop(permit);
|
||||
|
||||
if !status.success() {
|
||||
return Err(MagickError::Status);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl From<tokio::sync::AcquireError> for MagickError {
|
||||
fn from(_: tokio::sync::AcquireError) -> MagickError {
|
||||
MagickError::Closed
|
||||
}
|
||||
}
|
||||
|
|
100
src/main.rs
100
src/main.rs
|
@ -6,11 +6,9 @@ use actix_web::{
|
|||
web, App, HttpResponse, HttpResponseBuilder, HttpServer,
|
||||
};
|
||||
use awc::Client;
|
||||
use futures::stream::{once, Stream, TryStreamExt};
|
||||
use futures::stream::{Stream, TryStreamExt};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{
|
||||
collections::HashSet, future::ready, path::PathBuf, pin::Pin, sync::Once, time::SystemTime,
|
||||
};
|
||||
use std::{collections::HashSet, path::PathBuf, pin::Pin, time::SystemTime};
|
||||
use structopt::StructOpt;
|
||||
use tracing::{debug, error, info, instrument, Span};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
@ -19,6 +17,7 @@ mod config;
|
|||
mod error;
|
||||
mod exiv2;
|
||||
mod ffmpeg;
|
||||
mod magick;
|
||||
mod middleware;
|
||||
mod migrate;
|
||||
mod processor;
|
||||
|
@ -30,7 +29,6 @@ use self::{
|
|||
config::{Config, Format},
|
||||
error::UploadError,
|
||||
middleware::{Internal, Tracing},
|
||||
processor::process_image,
|
||||
upload_manager::{Details, UploadManager},
|
||||
validate::{image_webp, video_mp4},
|
||||
};
|
||||
|
@ -58,7 +56,6 @@ static TMP_DIR: Lazy<PathBuf> = Lazy::new(|| {
|
|||
path
|
||||
});
|
||||
static CONFIG: Lazy<Config> = Lazy::new(Config::from_args);
|
||||
static MAGICK_INIT: Once = Once::new();
|
||||
|
||||
// try moving a file
|
||||
#[instrument]
|
||||
|
@ -83,8 +80,11 @@ async fn safe_move_file(from: PathBuf, to: PathBuf) -> Result<(), UploadError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn safe_create_parent(path: PathBuf) -> Result<(), UploadError> {
|
||||
if let Some(path) = path.parent() {
|
||||
async fn safe_create_parent<P>(path: P) -> Result<(), UploadError>
|
||||
where
|
||||
P: AsRef<std::path::Path>,
|
||||
{
|
||||
if let Some(path) = path.as_ref().parent() {
|
||||
debug!("Creating directory {:?}", path);
|
||||
actix_fs::create_dir_all(path.to_owned()).await?;
|
||||
}
|
||||
|
@ -285,7 +285,7 @@ async fn prepare_process(
|
|||
ext: &str,
|
||||
manager: &UploadManager,
|
||||
whitelist: &Option<HashSet<String>>,
|
||||
) -> Result<(processor::ProcessChain, Format, String, PathBuf), UploadError> {
|
||||
) -> Result<(Format, String, PathBuf, Vec<String>), UploadError> {
|
||||
let (alias, operations) =
|
||||
query
|
||||
.into_inner()
|
||||
|
@ -322,8 +322,9 @@ async fn prepare_process(
|
|||
let processed_name = format!("{}.{}", name, ext);
|
||||
let base = manager.image_dir();
|
||||
let thumbnail_path = self::processor::build_path(base, &chain, processed_name);
|
||||
let thumbnail_args = self::processor::build_args(&chain);
|
||||
|
||||
Ok((chain, format, name, thumbnail_path))
|
||||
Ok((format, name, thumbnail_path, thumbnail_args))
|
||||
}
|
||||
|
||||
async fn process_details(
|
||||
|
@ -332,7 +333,7 @@ async fn process_details(
|
|||
manager: web::Data<UploadManager>,
|
||||
whitelist: web::Data<Option<HashSet<String>>>,
|
||||
) -> Result<HttpResponse, UploadError> {
|
||||
let (_, _, name, thumbnail_path) =
|
||||
let (_, name, thumbnail_path, _) =
|
||||
prepare_process(query, ext.as_str(), &manager, &whitelist).await?;
|
||||
|
||||
let details = manager.variant_details(thumbnail_path, name).await?;
|
||||
|
@ -351,7 +352,7 @@ async fn process(
|
|||
manager: web::Data<UploadManager>,
|
||||
whitelist: web::Data<Option<HashSet<String>>>,
|
||||
) -> Result<HttpResponse, UploadError> {
|
||||
let (chain, format, name, thumbnail_path) =
|
||||
let (format, name, thumbnail_path, thumbnail_args) =
|
||||
prepare_process(query, ext.as_str(), &manager, &whitelist).await?;
|
||||
|
||||
// If the thumbnail doesn't exist, we need to create it
|
||||
|
@ -396,78 +397,25 @@ async fn process(
|
|||
}
|
||||
}
|
||||
|
||||
safe_create_parent(&thumbnail_path).await?;
|
||||
|
||||
// apply chain to the provided image
|
||||
let img_bytes = process_image(original_path.clone(), chain, format).await?;
|
||||
magick::process_image(&original_path, &thumbnail_path, thumbnail_args, format).await?;
|
||||
|
||||
let path2 = thumbnail_path.clone();
|
||||
let img_bytes2 = img_bytes.clone();
|
||||
|
||||
let store_details = details.is_none();
|
||||
let details = if let Some(details) = details {
|
||||
details
|
||||
} else {
|
||||
let details = Details::from_bytes(&img_bytes)?;
|
||||
let details = Details::from_path(&thumbnail_path).await?;
|
||||
manager
|
||||
.store_variant_details(path2.clone(), name.clone(), &details)
|
||||
.store_variant_details(thumbnail_path.clone(), name.clone(), &details)
|
||||
.await?;
|
||||
manager
|
||||
.store_variant(thumbnail_path.clone(), name.clone())
|
||||
.await?;
|
||||
details
|
||||
};
|
||||
|
||||
// Save the file in another task, we want to return the thumbnail now
|
||||
debug!("Spawning storage task");
|
||||
let span = Span::current();
|
||||
let details2 = details.clone();
|
||||
actix_rt::spawn(async move {
|
||||
let entered = span.enter();
|
||||
if store_details {
|
||||
debug!("Storing details");
|
||||
if let Err(e) = manager
|
||||
.store_variant_details(path2.clone(), name.clone(), &details2)
|
||||
.await
|
||||
{
|
||||
error!("Error storing details, {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Err(e) = manager.store_variant(path2.clone(), name).await {
|
||||
error!("Error storing variant, {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(e) = safe_save_file(path2, img_bytes2).await {
|
||||
error!("Error saving file, {}", e);
|
||||
}
|
||||
drop(entered);
|
||||
});
|
||||
|
||||
let (builder, stream) = match range {
|
||||
Some(range_header) => {
|
||||
if !range_header.is_bytes() {
|
||||
return Err(UploadError::Range);
|
||||
}
|
||||
|
||||
if range_header.is_empty() {
|
||||
return Err(UploadError::Range);
|
||||
} else if range_header.len() == 1 {
|
||||
let range = range_header.ranges().next().unwrap();
|
||||
|
||||
let mut builder = HttpResponse::PartialContent();
|
||||
builder.insert_header(range.to_content_range(img_bytes.len() as u64));
|
||||
(builder, range.chop_bytes(img_bytes))
|
||||
} else {
|
||||
return Err(UploadError::Range);
|
||||
}
|
||||
}
|
||||
None => (HttpResponse::Ok(), once(ready(Ok(img_bytes)))),
|
||||
};
|
||||
|
||||
return Ok(srv_response(
|
||||
builder,
|
||||
stream,
|
||||
details.content_type(),
|
||||
7 * DAYS,
|
||||
details.system_time(),
|
||||
));
|
||||
return ranged_file_resp(thumbnail_path, range, details).await;
|
||||
}
|
||||
|
||||
let details = if let Some(details) = details {
|
||||
|
@ -670,10 +618,6 @@ async fn filename_by_alias(
|
|||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> Result<(), anyhow::Error> {
|
||||
MAGICK_INIT.call_once(|| {
|
||||
magick_rust::magick_wand_genesis();
|
||||
});
|
||||
|
||||
if std::env::var("RUST_LOG").is_err() {
|
||||
std::env::set_var("RUST_LOG", "info");
|
||||
}
|
||||
|
|
174
src/processor.rs
174
src/processor.rs
|
@ -1,12 +1,10 @@
|
|||
use crate::{
|
||||
config::Format,
|
||||
error::UploadError,
|
||||
validate::{ptos, Op},
|
||||
};
|
||||
use actix_web::web;
|
||||
use magick_rust::MagickWand;
|
||||
use crate::{error::UploadError, ffmpeg::ThumbnailFormat};
|
||||
use std::path::PathBuf;
|
||||
use tracing::{debug, error, instrument, Span};
|
||||
use tracing::{debug, error, instrument};
|
||||
|
||||
fn ptos(path: &PathBuf) -> Result<String, UploadError> {
|
||||
Ok(path.to_str().ok_or(UploadError::Path)?.to_owned())
|
||||
}
|
||||
|
||||
pub(crate) trait Processor {
|
||||
fn name() -> &'static str
|
||||
|
@ -22,7 +20,7 @@ pub(crate) trait Processor {
|
|||
Self: Sized;
|
||||
|
||||
fn path(&self, path: PathBuf) -> PathBuf;
|
||||
fn process(&self, wand: &mut MagickWand) -> Result<(), UploadError>;
|
||||
fn command(&self, args: Vec<String>) -> Vec<String>;
|
||||
}
|
||||
|
||||
pub(crate) struct Identity;
|
||||
|
@ -46,7 +44,6 @@ impl Processor for Identity {
|
|||
where
|
||||
Self: Sized,
|
||||
{
|
||||
debug!("Identity");
|
||||
Some(Box::new(Identity))
|
||||
}
|
||||
|
||||
|
@ -54,8 +51,8 @@ impl Processor for Identity {
|
|||
path
|
||||
}
|
||||
|
||||
fn process(&self, _: &mut MagickWand) -> Result<(), UploadError> {
|
||||
Ok(())
|
||||
fn command(&self, args: Vec<String>) -> Vec<String> {
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,25 +87,10 @@ impl Processor for Thumbnail {
|
|||
path
|
||||
}
|
||||
|
||||
fn process(&self, wand: &mut MagickWand) -> Result<(), UploadError> {
|
||||
debug!("Thumbnail");
|
||||
let width = wand.get_image_width();
|
||||
let height = wand.get_image_height();
|
||||
fn command(&self, mut args: Vec<String>) -> Vec<String> {
|
||||
args.extend(["-sample".to_string(), format!("{}x{}>", self.0, self.0)]);
|
||||
|
||||
if width > self.0 || height > self.0 {
|
||||
let width_ratio = width as f64 / self.0 as f64;
|
||||
let height_ratio = height as f64 / self.0 as f64;
|
||||
|
||||
let (new_width, new_height) = if width_ratio < height_ratio {
|
||||
(width as f64 / height_ratio, self.0 as f64)
|
||||
} else {
|
||||
(self.0 as f64, height as f64 / width_ratio)
|
||||
};
|
||||
|
||||
wand.op(|w| w.sample_image(new_width as usize, new_height as usize))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,29 +125,15 @@ impl Processor for Resize {
|
|||
path
|
||||
}
|
||||
|
||||
fn process(&self, wand: &mut MagickWand) -> Result<(), UploadError> {
|
||||
debug!("Resize");
|
||||
let width = wand.get_image_width();
|
||||
let height = wand.get_image_height();
|
||||
fn command(&self, mut args: Vec<String>) -> Vec<String> {
|
||||
args.extend([
|
||||
"-filter".to_string(),
|
||||
"-Lanczos2".to_string(),
|
||||
"-resize".to_string(),
|
||||
format!("{}x{}>", self.0, self.0),
|
||||
]);
|
||||
|
||||
if width > self.0 || height > self.0 {
|
||||
let width_ratio = width as f64 / self.0 as f64;
|
||||
let height_ratio = height as f64 / self.0 as f64;
|
||||
|
||||
let (new_width, new_height) = if width_ratio < height_ratio {
|
||||
(width as f64 / height_ratio, self.0 as f64)
|
||||
} else {
|
||||
(self.0 as f64, height as f64 / width_ratio)
|
||||
};
|
||||
|
||||
wand.resize_image(
|
||||
new_width as usize,
|
||||
new_height as usize,
|
||||
magick_rust::bindings::FilterType_Lanczos2Filter,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -208,40 +176,15 @@ impl Processor for Crop {
|
|||
path
|
||||
}
|
||||
|
||||
fn process(&self, wand: &mut MagickWand) -> Result<(), UploadError> {
|
||||
let width = wand.get_image_width();
|
||||
let height = wand.get_image_height();
|
||||
fn command(&self, mut args: Vec<String>) -> Vec<String> {
|
||||
args.extend([
|
||||
"-gravity".to_string(),
|
||||
"center".to_string(),
|
||||
"-crop".to_string(),
|
||||
format!("{}:{}+0+0", self.0, self.1),
|
||||
]);
|
||||
|
||||
// 16x9 becomes 16/9, which is bigger than 16/10. a bigger number means a wider image
|
||||
//
|
||||
// Crop ratios bigger than Image ratios mean cropping the image's height and leaving the
|
||||
// width alone.
|
||||
let img_ratio = width as f64 / height as f64;
|
||||
let crop_ratio = self.0 as f64 / self.1 as f64;
|
||||
|
||||
let final_width;
|
||||
let final_height;
|
||||
|
||||
let x_offset;
|
||||
let y_offset;
|
||||
|
||||
if crop_ratio > img_ratio {
|
||||
final_height = (width as f64 / self.0 as f64 * self.1 as f64) as usize;
|
||||
final_width = width;
|
||||
|
||||
x_offset = 0;
|
||||
y_offset = ((height - final_height) as f64 / 2.0) as isize;
|
||||
} else {
|
||||
final_height = height;
|
||||
final_width = (height as f64 / self.1 as f64 * self.0 as f64) as usize;
|
||||
|
||||
x_offset = ((width - final_width) as f64 / 2.0) as isize;
|
||||
y_offset = 0;
|
||||
}
|
||||
|
||||
wand.op(|w| w.crop_image(final_width, final_height, x_offset, y_offset))?;
|
||||
|
||||
Ok(())
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,13 +213,10 @@ impl Processor for Blur {
|
|||
path
|
||||
}
|
||||
|
||||
fn process(&self, wand: &mut MagickWand) -> Result<(), UploadError> {
|
||||
debug!("Blur");
|
||||
if self.0 > 0.0 {
|
||||
wand.op(|w| w.gaussian_blur_image(0.0, self.0))?;
|
||||
}
|
||||
fn command(&self, mut args: Vec<String>) -> Vec<String> {
|
||||
args.extend(["-gaussian-blur".to_string(), self.0.to_string()]);
|
||||
|
||||
Ok(())
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -333,6 +273,13 @@ pub(crate) fn build_path(base: PathBuf, chain: &ProcessChain, filename: String)
|
|||
path
|
||||
}
|
||||
|
||||
pub(crate) fn build_args(chain: &ProcessChain) -> Vec<String> {
|
||||
chain
|
||||
.inner
|
||||
.iter()
|
||||
.fold(Vec::new(), |acc, processor| processor.command(acc))
|
||||
}
|
||||
|
||||
fn is_motion(s: &str) -> bool {
|
||||
s.ends_with(".gif") || s.ends_with(".mp4")
|
||||
}
|
||||
|
@ -363,23 +310,17 @@ pub(crate) async fn prepare_image(
|
|||
let orig_path = original_path_str.clone();
|
||||
|
||||
let tmpfile = crate::tmp_file();
|
||||
crate::safe_create_parent(tmpfile.clone()).await?;
|
||||
let tmpfile2 = tmpfile.clone();
|
||||
crate::safe_create_parent(&tmpfile).await?;
|
||||
|
||||
let res = web::block(move || {
|
||||
use crate::validate::transcode::{transcode, Target};
|
||||
|
||||
transcode(orig_path, tmpfile, Target::Jpeg).map_err(UploadError::Transcode)
|
||||
})
|
||||
.await?;
|
||||
let res = crate::ffmpeg::thumbnail(orig_path, &tmpfile, ThumbnailFormat::Jpeg).await;
|
||||
|
||||
if let Err(e) = res {
|
||||
error!("transcode error: {:?}", e);
|
||||
actix_fs::remove_file(tmpfile2).await?;
|
||||
actix_fs::remove_file(tmpfile.clone()).await?;
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
return match crate::safe_move_file(tmpfile2, jpg_path.clone()).await {
|
||||
return match crate::safe_move_file(tmpfile, jpg_path.clone()).await {
|
||||
Err(UploadError::FileExists) => Ok(Some((jpg_path, Exists::Exists))),
|
||||
Err(e) => Err(e),
|
||||
_ => Ok(Some((jpg_path, Exists::New))),
|
||||
|
@ -388,34 +329,3 @@ pub(crate) async fn prepare_image(
|
|||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub(crate) async fn process_image(
|
||||
original_file: PathBuf,
|
||||
chain: ProcessChain,
|
||||
format: Format,
|
||||
) -> Result<web::Bytes, UploadError> {
|
||||
let original_path_str = ptos(&original_file)?;
|
||||
|
||||
let span = Span::current();
|
||||
let bytes = web::block(move || {
|
||||
let entered = span.enter();
|
||||
|
||||
let mut wand = MagickWand::new();
|
||||
debug!("Reading image");
|
||||
wand.op(|w| w.read_image(&original_path_str))?;
|
||||
|
||||
debug!("Processing image");
|
||||
for processor in chain.inner.into_iter() {
|
||||
debug!("Step");
|
||||
processor.process(&mut wand)?;
|
||||
}
|
||||
|
||||
let vec = wand.op(|w| w.write_image_blob(format.to_magick_format()))?;
|
||||
drop(entered);
|
||||
Ok(web::Bytes::from(vec)) as Result<web::Bytes, UploadError>
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
|
17
src/range.rs
17
src/range.rs
|
@ -8,12 +8,9 @@ use actix_web::{
|
|||
web::Bytes,
|
||||
FromRequest, HttpRequest,
|
||||
};
|
||||
use futures::stream::{once, Once, Stream, StreamExt, TryStreamExt};
|
||||
use futures::stream::{Stream, StreamExt, TryStreamExt};
|
||||
use std::{fs, io};
|
||||
use std::{
|
||||
future::{ready, Ready},
|
||||
pin::Pin,
|
||||
};
|
||||
use std::{future::ready, pin::Pin};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Range {
|
||||
|
@ -46,16 +43,6 @@ impl Range {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn chop_bytes(&self, bytes: Bytes) -> Once<Ready<Result<Bytes, UploadError>>> {
|
||||
match self {
|
||||
Range::RangeStart(start) => once(ready(Ok(bytes.slice(*start as usize..)))),
|
||||
Range::SuffixLength(from_start) => once(ready(Ok(bytes.slice(..*from_start as usize)))),
|
||||
Range::Segment(start, end) => {
|
||||
once(ready(Ok(bytes.slice(*start as usize..*end as usize))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn chop_file(
|
||||
&self,
|
||||
file: fs::File,
|
||||
|
|
|
@ -96,47 +96,18 @@ pub(crate) struct Details {
|
|||
created_at: time::OffsetDateTime,
|
||||
}
|
||||
|
||||
fn mime_from_media_type(media_type: rexiv2::MediaType) -> mime::Mime {
|
||||
match media_type {
|
||||
rexiv2::MediaType::Jpeg => mime::IMAGE_JPEG,
|
||||
rexiv2::MediaType::Png => mime::IMAGE_PNG,
|
||||
rexiv2::MediaType::Gif => mime::IMAGE_GIF,
|
||||
rexiv2::MediaType::Other(s) if s == "image/webp" => s.parse::<mime::Mime>().unwrap(),
|
||||
rexiv2::MediaType::Other(s) if s == "video/mp4" || s == "video/quicktime" => {
|
||||
"video/mp4".parse::<mime::Mime>().unwrap()
|
||||
}
|
||||
_ => mime::APPLICATION_OCTET_STREAM,
|
||||
}
|
||||
}
|
||||
|
||||
impl Details {
|
||||
pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, UploadError> {
|
||||
let metadata = rexiv2::Metadata::new_from_buffer(bytes)?;
|
||||
let mime_type = mime_from_media_type(metadata.get_media_type()?);
|
||||
let width = metadata.get_pixel_width();
|
||||
let height = metadata.get_pixel_height();
|
||||
let details = Details::now(width as usize, height as usize, mime_type);
|
||||
Ok(details)
|
||||
}
|
||||
pub(crate) async fn from_path<P>(path: P) -> Result<Self, UploadError>
|
||||
where
|
||||
P: AsRef<std::path::Path>,
|
||||
{
|
||||
let details = crate::exiv2::details(&path).await?;
|
||||
|
||||
pub(crate) async fn from_path(path: PathBuf) -> Result<Self, UploadError> {
|
||||
let (mime_type, width, height) = web::block(move || {
|
||||
rexiv2::Metadata::new_from_path(&path).and_then(|metadata| {
|
||||
metadata
|
||||
.get_media_type()
|
||||
.map(mime_from_media_type)
|
||||
.map(|mime_type| {
|
||||
(
|
||||
mime_type,
|
||||
metadata.get_pixel_width(),
|
||||
metadata.get_pixel_height(),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Details::now(width as usize, height as usize, mime_type))
|
||||
Ok(Details::now(
|
||||
details.width,
|
||||
details.height,
|
||||
details.mime_type,
|
||||
))
|
||||
}
|
||||
|
||||
fn now(width: usize, height: usize, content_type: mime::Mime) -> Self {
|
||||
|
|
67
src/validate.rs
Normal file
67
src/validate.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use crate::{
|
||||
config::Format, error::UploadError, exiv2::ValidInputType, magick::ValidFormat, tmp_file,
|
||||
};
|
||||
|
||||
pub(crate) fn image_webp() -> mime::Mime {
|
||||
"image/webp".parse().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn video_mp4() -> mime::Mime {
|
||||
"video/mp4".parse().unwrap()
|
||||
}
|
||||
|
||||
// import & export image using the image crate
|
||||
#[tracing::instrument]
|
||||
pub(crate) async fn validate_image(
|
||||
tmpfile: std::path::PathBuf,
|
||||
prescribed_format: Option<Format>,
|
||||
) -> Result<mime::Mime, UploadError> {
|
||||
let input_type = crate::exiv2::input_type(&tmpfile).await?;
|
||||
|
||||
match (prescribed_format, input_type) {
|
||||
(_, ValidInputType::Gif) | (_, ValidInputType::Mp4) => {
|
||||
let newfile = tmp_file();
|
||||
crate::safe_create_parent(&newfile).await?;
|
||||
crate::ffmpeg::to_mp4(&tmpfile, &newfile).await?;
|
||||
actix_fs::rename(newfile, tmpfile).await?;
|
||||
|
||||
Ok(video_mp4())
|
||||
}
|
||||
(Some(Format::Jpeg), ValidInputType::Jpeg) | (None, ValidInputType::Jpeg) => {
|
||||
tracing::debug!("Validating format");
|
||||
crate::magick::validate_format(&tmpfile, ValidFormat::Jpeg).await?;
|
||||
tracing::debug!("Clearing metadata");
|
||||
crate::exiv2::clear_metadata(&tmpfile).await?;
|
||||
tracing::debug!("Validated");
|
||||
|
||||
Ok(mime::IMAGE_JPEG)
|
||||
}
|
||||
(Some(Format::Png), ValidInputType::Png) | (None, ValidInputType::Png) => {
|
||||
tracing::debug!("Validating format");
|
||||
crate::magick::validate_format(&tmpfile, ValidFormat::Png).await?;
|
||||
tracing::debug!("Clearing metadata");
|
||||
crate::exiv2::clear_metadata(&tmpfile).await?;
|
||||
tracing::debug!("Validated");
|
||||
|
||||
Ok(mime::IMAGE_PNG)
|
||||
}
|
||||
(Some(Format::Webp), ValidInputType::Webp) | (None, ValidInputType::Webp) => {
|
||||
tracing::debug!("Validating format");
|
||||
crate::magick::validate_format(&tmpfile, ValidFormat::Webp).await?;
|
||||
tracing::debug!("Clearing metadata");
|
||||
crate::exiv2::clear_metadata(&tmpfile).await?;
|
||||
tracing::debug!("Validated");
|
||||
|
||||
Ok(image_webp())
|
||||
}
|
||||
(Some(format), _) => {
|
||||
let newfile = tmp_file();
|
||||
crate::safe_create_parent(&newfile).await?;
|
||||
crate::magick::convert_file(&tmpfile, &newfile, format.clone()).await?;
|
||||
|
||||
actix_fs::rename(newfile, tmpfile).await?;
|
||||
|
||||
Ok(format.to_mime())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,218 +0,0 @@
|
|||
use crate::{config::Format, error::UploadError, tmp_file};
|
||||
use actix_web::web;
|
||||
use magick_rust::MagickWand;
|
||||
use rexiv2::{MediaType, Metadata};
|
||||
use std::path::PathBuf;
|
||||
use tracing::{debug, error, instrument, warn, Span};
|
||||
|
||||
pub(crate) mod transcode;
|
||||
|
||||
use self::transcode::{Error as TranscodeError, Target};
|
||||
|
||||
pub(crate) trait Op {
|
||||
fn op<F, T>(&self, f: F) -> Result<T, UploadError>
|
||||
where
|
||||
F: Fn(&Self) -> Result<T, &'static str>;
|
||||
|
||||
fn op_mut<F, T>(&mut self, f: F) -> Result<T, UploadError>
|
||||
where
|
||||
F: Fn(&mut Self) -> Result<T, &'static str>;
|
||||
}
|
||||
|
||||
impl Op for MagickWand {
|
||||
fn op<F, T>(&self, f: F) -> Result<T, UploadError>
|
||||
where
|
||||
F: Fn(&Self) -> Result<T, &'static str>,
|
||||
{
|
||||
match f(self) {
|
||||
Ok(t) => Ok(t),
|
||||
Err(e) => {
|
||||
if let Ok(e) = self.get_exception() {
|
||||
error!("WandError: {}", e.0);
|
||||
Err(UploadError::Wand(e.0))
|
||||
} else {
|
||||
Err(UploadError::Wand(e.to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn op_mut<F, T>(&mut self, f: F) -> Result<T, UploadError>
|
||||
where
|
||||
F: Fn(&mut Self) -> Result<T, &'static str>,
|
||||
{
|
||||
match f(self) {
|
||||
Ok(t) => Ok(t),
|
||||
Err(e) => {
|
||||
if let Ok(e) = self.get_exception() {
|
||||
error!("WandError: {}", e.0);
|
||||
Err(UploadError::Wand(e.0))
|
||||
} else {
|
||||
Err(UploadError::Wand(e.to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum GifError {
|
||||
#[error("{0}")]
|
||||
Decode(#[from] TranscodeError),
|
||||
|
||||
#[error("{0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
pub(crate) fn image_webp() -> mime::Mime {
|
||||
"image/webp".parse().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn video_mp4() -> mime::Mime {
|
||||
"video/mp4".parse().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn ptos(p: &PathBuf) -> Result<String, UploadError> {
|
||||
Ok(p.to_str().ok_or(UploadError::Path)?.to_owned())
|
||||
}
|
||||
|
||||
fn validate_format(file: &str, format: &str) -> Result<(), UploadError> {
|
||||
let wand = MagickWand::new();
|
||||
debug!("reading");
|
||||
wand.op(|w| w.read_image(file))?;
|
||||
|
||||
if wand.op(|w| w.get_image_format())? != format {
|
||||
return Err(UploadError::UnsupportedFormat);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn safe_create_parent(path: &PathBuf) -> Result<(), UploadError> {
|
||||
if let Some(path) = path.parent() {
|
||||
std::fs::create_dir_all(path)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// import & export image using the image crate
|
||||
#[instrument]
|
||||
pub(crate) async fn validate_image(
|
||||
tmpfile: PathBuf,
|
||||
prescribed_format: Option<Format>,
|
||||
) -> Result<mime::Mime, UploadError> {
|
||||
let tmpfile_str = ptos(&tmpfile)?;
|
||||
let span = Span::current();
|
||||
|
||||
let content_type = web::block(move || {
|
||||
let entered = span.enter();
|
||||
|
||||
let meta = Metadata::new_from_path(&tmpfile)?;
|
||||
|
||||
let content_type = match (prescribed_format, meta.get_media_type()?) {
|
||||
(_, MediaType::Gif) => {
|
||||
let newfile = tmp_file();
|
||||
safe_create_parent(&newfile)?;
|
||||
validate_frames(&tmpfile, &newfile)?;
|
||||
|
||||
video_mp4()
|
||||
}
|
||||
(Some(Format::Jpeg), MediaType::Jpeg) | (None, MediaType::Jpeg) => {
|
||||
validate_format(&tmpfile_str, "JPEG")?;
|
||||
|
||||
meta.clear();
|
||||
meta.save_to_file(&tmpfile)?;
|
||||
|
||||
mime::IMAGE_JPEG
|
||||
}
|
||||
(Some(Format::Png), MediaType::Png) | (None, MediaType::Png) => {
|
||||
validate_format(&tmpfile_str, "PNG")?;
|
||||
|
||||
meta.clear();
|
||||
meta.save_to_file(&tmpfile)?;
|
||||
|
||||
mime::IMAGE_PNG
|
||||
}
|
||||
(Some(Format::Webp), MediaType::Other(webp)) | (None, MediaType::Other(webp))
|
||||
if webp == "image/webp" =>
|
||||
{
|
||||
let newfile = tmp_file();
|
||||
safe_create_parent(&newfile)?;
|
||||
let newfile_str = ptos(&newfile)?;
|
||||
// clean metadata by writing new webp, since exiv2 doesn't support webp yet
|
||||
{
|
||||
let wand = MagickWand::new();
|
||||
|
||||
debug!("reading");
|
||||
wand.op(|w| w.read_image(&tmpfile_str))?;
|
||||
|
||||
if wand.op(|w| w.get_image_format())? != "WEBP" {
|
||||
return Err(UploadError::UnsupportedFormat);
|
||||
}
|
||||
|
||||
if let Err(e) = wand.op(|w| w.write_image(&newfile_str)) {
|
||||
std::fs::remove_file(&newfile_str)?;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::rename(&newfile, &tmpfile)?;
|
||||
|
||||
image_webp()
|
||||
}
|
||||
(Some(format), _) => {
|
||||
let newfile = tmp_file();
|
||||
safe_create_parent(&newfile)?;
|
||||
let newfile_str = ptos(&newfile)?;
|
||||
{
|
||||
let mut wand = MagickWand::new();
|
||||
|
||||
debug!("reading: {}", tmpfile_str);
|
||||
wand.op(|w| w.read_image(&tmpfile_str))?;
|
||||
|
||||
wand.op_mut(|w| w.set_image_format(format.to_magick_format()))?;
|
||||
|
||||
debug!("writing: {}", newfile_str);
|
||||
if let Err(e) = wand.op(|w| w.write_image(&newfile_str)) {
|
||||
std::fs::remove_file(&newfile_str)?;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
std::fs::rename(&newfile, &tmpfile)?;
|
||||
|
||||
format.to_mime()
|
||||
}
|
||||
(_, MediaType::Other(mp4)) if mp4 == "video/mp4" || mp4 == "video/quicktime" => {
|
||||
let newfile = tmp_file();
|
||||
safe_create_parent(&newfile)?;
|
||||
validate_frames(&tmpfile, &newfile)?;
|
||||
|
||||
video_mp4()
|
||||
}
|
||||
(_, media_type) => {
|
||||
warn!("Unsupported media type, {}", media_type);
|
||||
return Err(UploadError::UnsupportedFormat);
|
||||
}
|
||||
};
|
||||
|
||||
drop(entered);
|
||||
Ok(content_type) as Result<mime::Mime, UploadError>
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(content_type)
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn validate_frames(from: &PathBuf, to: &PathBuf) -> Result<(), GifError> {
|
||||
debug!("Transmuting GIF");
|
||||
|
||||
if let Err(e) = self::transcode::transcode(from, to, Target::Mp4) {
|
||||
std::fs::remove_file(to)?;
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
std::fs::rename(to, from)?;
|
||||
Ok(())
|
||||
}
|
|
@ -1,314 +0,0 @@
|
|||
use ffmpeg_next::{
|
||||
self, codec, filter, format, frame, media,
|
||||
util::{format::pixel::Pixel, rational::Rational},
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub(crate) enum Error {
|
||||
#[error("No video stream present")]
|
||||
MissingVideo,
|
||||
|
||||
#[error("Input format is not supported")]
|
||||
UnsupportedFormat,
|
||||
|
||||
#[error("No frame-rate present in input video")]
|
||||
FrameRate,
|
||||
|
||||
#[error("Filter {0} should have been set up by now")]
|
||||
MissingFilter(&'static str),
|
||||
|
||||
#[error("{0}")]
|
||||
Transcode(#[from] ffmpeg_next::Error),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum Target {
|
||||
Mp4,
|
||||
Jpeg,
|
||||
#[allow(dead_code)]
|
||||
Png,
|
||||
}
|
||||
|
||||
impl Target {
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Target::Mp4 => "mp4",
|
||||
Target::Jpeg => "image2",
|
||||
Target::Png => "image2",
|
||||
}
|
||||
}
|
||||
|
||||
fn codec(&self) -> codec::id::Id {
|
||||
match self {
|
||||
Target::Mp4 => codec::id::Id::H264,
|
||||
Target::Jpeg => codec::id::Id::MJPEG,
|
||||
Target::Png => codec::id::Id::PNG,
|
||||
}
|
||||
}
|
||||
|
||||
fn frames(&self) -> Option<usize> {
|
||||
match self {
|
||||
Target::Mp4 => None,
|
||||
Target::Jpeg => Some(1),
|
||||
Target::Png => Some(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pixel_value(pixel: Pixel) -> i32 {
|
||||
let av_px_fmt: ffmpeg_sys_next::AVPixelFormat = pixel.into();
|
||||
unsafe { std::mem::transmute::<_, i32>(av_px_fmt) }
|
||||
}
|
||||
|
||||
fn to_even(num: u32) -> u32 {
|
||||
if num % 2 == 0 {
|
||||
num
|
||||
} else {
|
||||
num - 1
|
||||
}
|
||||
}
|
||||
|
||||
fn filter(
|
||||
decoder: &codec::decoder::Video,
|
||||
encoder: &codec::encoder::Video,
|
||||
) -> Result<filter::Graph, Error> {
|
||||
let mut filter = filter::Graph::new();
|
||||
|
||||
let aspect = Rational::new(
|
||||
to_even(decoder.width()) as i32,
|
||||
to_even(decoder.height()) as i32,
|
||||
)
|
||||
.reduce();
|
||||
|
||||
let av_px_fmt = pixel_value(decoder.format());
|
||||
|
||||
let args = format!(
|
||||
"video_size={width}x{height}:pix_fmt={pix_fmt}:time_base={time_base_num}/{time_base_den}:pixel_aspect={pix_aspect_num}/{pix_aspect_den}",
|
||||
width=to_even(decoder.width()),
|
||||
height=to_even(decoder.height()),
|
||||
pix_fmt=av_px_fmt,
|
||||
time_base_num=decoder.time_base().numerator(),
|
||||
time_base_den=decoder.time_base().denominator(),
|
||||
pix_aspect_num=aspect.numerator(),
|
||||
pix_aspect_den=aspect.denominator(),
|
||||
);
|
||||
|
||||
let buffer = filter::find("buffer").ok_or(Error::MissingFilter("buffer"))?;
|
||||
filter.add(&buffer, "in", &args)?;
|
||||
|
||||
let buffersink = filter::find("buffersink").ok_or(Error::MissingFilter("buffersink"))?;
|
||||
let mut out = filter.add(&buffersink, "out", "")?;
|
||||
out.set_pixel_format(encoder.format());
|
||||
|
||||
filter.output("in", 0)?.input("out", 0)?.parse("null")?;
|
||||
filter.validate()?;
|
||||
|
||||
println!("{}", filter.dump());
|
||||
|
||||
if let Some(codec) = encoder.codec() {
|
||||
if !codec
|
||||
.capabilities()
|
||||
.contains(codec::capabilities::Capabilities::VARIABLE_FRAME_SIZE)
|
||||
{
|
||||
filter
|
||||
.get("out")
|
||||
.ok_or(Error::MissingFilter("out"))?
|
||||
.sink()
|
||||
.set_frame_size(encoder.frame_size());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(filter)
|
||||
}
|
||||
|
||||
struct Transcoder {
|
||||
stream: usize,
|
||||
filter: filter::Graph,
|
||||
decoder: codec::decoder::Video,
|
||||
encoder: codec::encoder::Video,
|
||||
}
|
||||
|
||||
impl Transcoder {
|
||||
fn decode(
|
||||
&mut self,
|
||||
packet: &ffmpeg_next::Packet,
|
||||
decoded: &mut frame::Video,
|
||||
) -> Result<bool, ffmpeg_next::Error> {
|
||||
self.decoder.decode(packet, decoded)
|
||||
}
|
||||
|
||||
fn add_frame(&mut self, decoded: &frame::Video) -> Result<(), Error> {
|
||||
self.filter
|
||||
.get("in")
|
||||
.ok_or(Error::MissingFilter("out"))?
|
||||
.source()
|
||||
.add(decoded)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode(
|
||||
&mut self,
|
||||
decoded: &mut frame::Video,
|
||||
encoded: &mut ffmpeg_next::Packet,
|
||||
octx: &mut format::context::Output,
|
||||
in_time_base: Rational,
|
||||
out_time_base: Rational,
|
||||
) -> Result<(), Error> {
|
||||
while let Ok(()) = self
|
||||
.filter
|
||||
.get("out")
|
||||
.ok_or(Error::MissingFilter("out"))?
|
||||
.sink()
|
||||
.frame(decoded)
|
||||
{
|
||||
if let Ok(true) = self.encoder.encode(decoded, encoded) {
|
||||
encoded.set_stream(0);
|
||||
encoded.rescale_ts(in_time_base, out_time_base);
|
||||
encoded.write_interleaved(octx)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush(
|
||||
&mut self,
|
||||
decoded: &mut frame::Video,
|
||||
encoded: &mut ffmpeg_next::Packet,
|
||||
octx: &mut format::context::Output,
|
||||
in_time_base: Rational,
|
||||
out_time_base: Rational,
|
||||
) -> Result<(), Error> {
|
||||
self.filter
|
||||
.get("in")
|
||||
.ok_or(Error::MissingFilter("in"))?
|
||||
.source()
|
||||
.flush()?;
|
||||
|
||||
self.encode(decoded, encoded, octx, in_time_base, out_time_base)?;
|
||||
|
||||
while let Ok(true) = self.encoder.flush(encoded) {
|
||||
encoded.set_stream(0);
|
||||
encoded.rescale_ts(in_time_base, out_time_base);
|
||||
encoded.write_interleaved(octx)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn transcoder(
|
||||
ictx: &mut format::context::Input,
|
||||
octx: &mut format::context::Output,
|
||||
target: Target,
|
||||
) -> Result<Transcoder, Error> {
|
||||
let input = ictx
|
||||
.streams()
|
||||
.best(media::Type::Video)
|
||||
.ok_or(Error::MissingVideo)?;
|
||||
let mut decoder = input.codec().decoder().video()?;
|
||||
let codec_id = target.codec();
|
||||
let codec = ffmpeg_next::encoder::find(codec_id)
|
||||
.ok_or(Error::UnsupportedFormat)?
|
||||
.video()?;
|
||||
let global = octx
|
||||
.format()
|
||||
.flags()
|
||||
.contains(format::flag::Flags::GLOBAL_HEADER);
|
||||
|
||||
decoder.set_parameters(input.parameters())?;
|
||||
|
||||
let mut output = octx.add_stream(codec)?;
|
||||
let mut encoder = output.codec().encoder().video()?;
|
||||
|
||||
if global {
|
||||
encoder.set_flags(codec::flag::Flags::GLOBAL_HEADER);
|
||||
}
|
||||
|
||||
encoder.set_format(
|
||||
codec
|
||||
.formats()
|
||||
.ok_or(Error::UnsupportedFormat)?
|
||||
.next()
|
||||
.ok_or(Error::UnsupportedFormat)?,
|
||||
);
|
||||
encoder.set_bit_rate(decoder.bit_rate());
|
||||
encoder.set_max_bit_rate(decoder.max_bit_rate());
|
||||
|
||||
encoder.set_width(to_even(decoder.width()));
|
||||
encoder.set_height(to_even(decoder.height()));
|
||||
encoder.set_bit_rate(decoder.bit_rate());
|
||||
encoder.set_max_bit_rate(decoder.max_bit_rate());
|
||||
encoder.set_time_base(decoder.frame_rate().ok_or(Error::FrameRate)?.invert());
|
||||
output.set_time_base(decoder.time_base());
|
||||
|
||||
let encoder = encoder.open_as(codec)?;
|
||||
output.set_parameters(&encoder);
|
||||
|
||||
let filter = filter(&decoder, &encoder)?;
|
||||
|
||||
Ok(Transcoder {
|
||||
stream: input.index(),
|
||||
filter,
|
||||
decoder,
|
||||
encoder,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn transcode<P, Q>(input: P, output: Q, target: Target) -> Result<(), Error>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
let mut ictx = format::input(&input)?;
|
||||
let mut octx = format::output_as(&output, target.name())?;
|
||||
let mut transcoder = transcoder(&mut ictx, &mut octx, target)?;
|
||||
|
||||
octx.write_header()?;
|
||||
|
||||
let in_time_base = transcoder.decoder.time_base();
|
||||
let out_time_base = octx.stream(0).ok_or(Error::MissingVideo)?.time_base();
|
||||
|
||||
let mut decoded = frame::Video::empty();
|
||||
let mut encoded = ffmpeg_next::Packet::empty();
|
||||
let mut count = 0;
|
||||
|
||||
for (stream, mut packet) in ictx.packets() {
|
||||
if stream.index() == transcoder.stream {
|
||||
packet.rescale_ts(stream.time_base(), in_time_base);
|
||||
|
||||
if let Ok(true) = transcoder.decode(&packet, &mut decoded) {
|
||||
let timestamp = decoded.timestamp();
|
||||
decoded.set_pts(timestamp);
|
||||
|
||||
transcoder.add_frame(&decoded)?;
|
||||
|
||||
transcoder.encode(
|
||||
&mut decoded,
|
||||
&mut encoded,
|
||||
&mut octx,
|
||||
in_time_base,
|
||||
out_time_base,
|
||||
)?;
|
||||
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if target.frames().map(|f| count >= f).unwrap_or(false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
transcoder.flush(
|
||||
&mut decoded,
|
||||
&mut encoded,
|
||||
&mut octx,
|
||||
in_time_base,
|
||||
out_time_base,
|
||||
)?;
|
||||
|
||||
octx.write_trailer()?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in a new issue