Compare commits
27 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
6095fd69fc | |
|
|
8f1c0876c5 | |
|
|
6e2cf357f4 | |
|
|
4aede62dbb | |
|
|
c3d6cd1545 | |
|
|
bf7d09dde0 | |
|
|
d13266dd73 | |
|
|
ccc4d7fbd2 | |
|
|
48961fee2a | |
|
|
dfbdba81a6 | |
|
|
a38e39b808 | |
|
|
9e053c96d6 | |
|
|
10ef89f0e9 | |
|
|
32a652519a | |
|
|
a8ba8fc97d | |
|
|
5bd118704d | |
|
|
daf554ee83 | |
|
|
6513dc09b4 | |
|
|
68dd89ffc9 | |
|
|
c5b2945c55 | |
|
|
11437bc848 | |
|
|
ddfae7f86d | |
|
|
92b9765ad1 | |
|
|
89142d69b1 | |
|
|
96369424b3 | |
|
|
1a1d979ebd | |
|
|
47339a01f9 |
|
|
@ -2,6 +2,457 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener 4.0.1",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7"
|
||||
dependencies = [
|
||||
"async-lock 3.2.0",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix",
|
||||
"slab",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
|
||||
dependencies = [
|
||||
"event-listener 2.5.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c"
|
||||
dependencies = [
|
||||
"event-listener 4.0.1",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15c1cd5d253ecac3d3cf15e390fd96bd92a13b1d14497d81abf077304794fb04"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-io",
|
||||
"async-lock 3.2.0",
|
||||
"async-signal",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener 4.0.1",
|
||||
"futures-lite",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock 2.8.0",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d90cd0b264dfdd8eb5bad0a2c217c1f88fa96a8573f40e7b12de23fb468f46"
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-lock 3.2.0",
|
||||
"async-task",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"piper",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84f2cdcf274580f2d63697192d744727b3198894b1bf02923643bf59e2c26712"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
|
||||
dependencies = [
|
||||
"event-listener 4.0.1",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lib"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-process",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
|
|
|||
|
|
@ -6,3 +6,12 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-process = "2.0.1"
|
||||
|
||||
[lib]
|
||||
name = "lib"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "cli"
|
||||
path = "src/cli/main.rs"
|
||||
|
|
|
|||
375
src-lib/_all
375
src-lib/_all
|
|
@ -1,375 +0,0 @@
|
|||
use std::str::FromStr;
|
||||
use core::cmp::Ordering;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::io::Write;
|
||||
|
||||
pub fn screenshotify(input: &String) -> Result<Vec<String>, String> {
|
||||
let output_d = format!("{}.d", input);
|
||||
let ext = "jpg".to_string();
|
||||
let mut result = vec![];
|
||||
let _ = ify(input, |content_span| {
|
||||
let output = format!("{}/{}.{}", output_d, result.len(), ext);
|
||||
let _ = screenshot(&output, input, (content_span.stop + content_span.start) / 2.0)?;
|
||||
result.push(output);
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn clipify(input: &String) -> Result<Vec<String>, String> {
|
||||
let output_d = format!("{}.d", input);
|
||||
let ext = input.split(".").last().unwrap();
|
||||
let mut result = vec![];
|
||||
let _ = ify(input, |content_span| {
|
||||
let output = format!("{}/{}.{}", output_d, result.len(), ext);
|
||||
let _ = clip(&output, input, content_span)?;
|
||||
result.push(output);
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn ify(input: &String, mut cb: impl FnMut(ContentSpan) -> Result<(), String>) -> Result<(), String> {
|
||||
match inspect(input) {
|
||||
Ok(inspection) => {
|
||||
match inspection.iter()
|
||||
.map(|x| cb(*x))
|
||||
.filter(|x| x.is_err())
|
||||
.map(|x| x.err())
|
||||
.filter(|x| x.is_some())
|
||||
.map(|x| x.unwrap())
|
||||
.nth(0) {
|
||||
Some(err) => Err(format!("callback failed: {}", err)),
|
||||
None => Ok(()),
|
||||
}
|
||||
},
|
||||
Err(msg) => Err(msg),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clip(output: &String, input: &String, content_span: ContentSpan) -> Result<(), String> {
|
||||
fs::create_dir_all(Path::new(output).parent().unwrap()).unwrap();
|
||||
match std::process::Command::new("ffmpeg")
|
||||
.args([
|
||||
"-y",
|
||||
"-ss", &content_span.start.to_string(),
|
||||
"-i", input,
|
||||
"-t", &(content_span.stop - content_span.start).to_string(),
|
||||
output,
|
||||
])
|
||||
.output() {
|
||||
Ok(output) => match output.status.success() {
|
||||
true => Ok(()),
|
||||
false => Err(format!("failed to ffmpeg clip {}: {}", input, String::from_utf8(output.stderr).unwrap())),
|
||||
},
|
||||
Err(msg) => Err(format!("failed to ffmpeg clip {}: {}", input, msg)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn screenshot(output: &String, input: &String, ts: f32) -> Result<(), String> {
|
||||
match screenshot_jpg(input, ts) {
|
||||
Ok(jpg) => {
|
||||
match std::fs::File::create(output) {
|
||||
Ok(mut f) => {
|
||||
f.write_all(jpg).unwrap();
|
||||
Ok(())
|
||||
},
|
||||
Err(msg) => Err(format!("failed to open {} for writing: {}", output, msg)),
|
||||
}
|
||||
},
|
||||
Err(msg) => Err(msg),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn screenshot_jpg(input: &String, ts: f32) -> Result<Vec<u8>, String> {
|
||||
match std::process::Command::new("ffmpeg")
|
||||
.args([
|
||||
"-y",
|
||||
"-ss", &ts.to_string(),
|
||||
"-i", input,
|
||||
"-frames:v", "1",
|
||||
"-q:v", "2",
|
||||
"-f", "jpg",
|
||||
"-",
|
||||
])
|
||||
.output() {
|
||||
Ok(output) => Ok(output.stdout),
|
||||
Err(msg) => Err(format!("failed to ffmpeg screenshot {}: {}", input, msg)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inspect(file: &String) -> Result<Vec<ContentSpan>, String> {
|
||||
match _inspect(file) {
|
||||
Ok(inspection) => Ok(inspection.content_spans()),
|
||||
Err(msg) => Err(msg)
|
||||
}
|
||||
}
|
||||
|
||||
fn _inspect(file: &String) -> Result<Inspection, String> {
|
||||
match std::process::Command::new("ffmpeg")
|
||||
.args([
|
||||
"-i", file,
|
||||
"-vf", "mpdecimate",
|
||||
"-af", "silencedetect=n=-50dB:d=1",
|
||||
"-loglevel", "debug",
|
||||
"-f", "null",
|
||||
"-",
|
||||
])
|
||||
.output() {
|
||||
Ok(output) => {
|
||||
let stderr = String::from_utf8(output.stderr).unwrap();
|
||||
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||
let line_iter = stderr.split("\n").chain(stdout.split("\n"));
|
||||
Ok(Inspection{
|
||||
lines: line_iter.map(|x| x.to_string()).collect(),
|
||||
})
|
||||
},
|
||||
Err(msg) => Err(format!("failed to ffmpeg inspect {}: {}", file, msg)),
|
||||
}
|
||||
}
|
||||
|
||||
struct Inspection {
|
||||
lines: Vec<String>,
|
||||
}
|
||||
|
||||
impl Inspection {
|
||||
fn content_spans(&self) -> Vec<ContentSpan> {
|
||||
self.overlap_spans(
|
||||
&self.visual_spans(),
|
||||
&self.audible_spans(),
|
||||
)
|
||||
}
|
||||
|
||||
fn overlap_spans(&self, a: &Vec<ContentSpan>, b: &Vec<ContentSpan>) -> Vec<ContentSpan> {
|
||||
self.merge_unsorted_spans(a.iter().chain(b.iter()).map(|x| *x).collect())
|
||||
}
|
||||
|
||||
fn audible_spans(&self) -> Vec<ContentSpan> {
|
||||
self.spans_from_transitions(self.audible_transitions())
|
||||
}
|
||||
|
||||
fn visual_spans(&self) -> Vec<ContentSpan> {
|
||||
self.spans_from_transitions(self.visual_transitions())
|
||||
}
|
||||
|
||||
fn visual_transitions(&self) -> Vec<f32> {
|
||||
let lines: Vec<_> = self.lines.iter()
|
||||
.filter(|x| x.contains("Parsed_mpdecimate_0"))
|
||||
.filter(|x| x.contains("keep pts:") || x.contains("drop pts:"))
|
||||
.collect();
|
||||
let mut lines_with_transitions = vec![];
|
||||
for i in 0..lines.len() {
|
||||
if lines_with_transitions.len() == 0 {
|
||||
lines_with_transitions.push(lines[i]);
|
||||
} else {
|
||||
let this_is_keep = lines[i].contains("keep");
|
||||
let prev_was_keep = lines_with_transitions.last().unwrap().contains("keep");
|
||||
if this_is_keep != prev_was_keep {
|
||||
lines_with_transitions.push(lines[i-1]);
|
||||
lines_with_transitions.push(lines[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
lines_with_transitions.iter()
|
||||
.skip(2)
|
||||
.filter(|x| x.contains("keep"))
|
||||
.map(|x| x.split("pts_time:").nth(1).unwrap().split(" ").nth(0).unwrap())
|
||||
.map(|x| f32::from_str(x).unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn audible_transitions(&self) -> Vec<f32> {
|
||||
let lines: Vec<_> = self.lines.iter()
|
||||
.filter(|x| x.contains("[silencedetect @"))
|
||||
.map(|x| x.split(" ").nth(4).unwrap())
|
||||
.map(|x| f32::from_str(x).unwrap())
|
||||
.collect();
|
||||
let mut lines_with_transitions = vec![];
|
||||
lines_with_transitions.push(0.0);
|
||||
lines.iter().for_each(|x| lines_with_transitions.push(*x));
|
||||
lines_with_transitions.push(self.duration());
|
||||
lines_with_transitions
|
||||
}
|
||||
|
||||
fn duration(&self) -> f32 {
|
||||
let ts = self.lines.iter()
|
||||
.filter(|x| (*x).contains("Duration: "))
|
||||
.filter(|x| (*x).contains("start: "))
|
||||
.filter(|x| (*x).contains("bitrate: "))
|
||||
.nth(0)
|
||||
.expect("did not find duration from ffmpeg")
|
||||
.split(",").nth(0).unwrap()
|
||||
.split(": ").nth(1).unwrap();
|
||||
let pieces: Vec<_> = ts.split(":")
|
||||
.map(|x| f32::from_str(x).unwrap())
|
||||
.collect();
|
||||
assert_eq!(3, pieces.len());
|
||||
let hours = pieces[0] * 60.0 * 60.0;
|
||||
let minutes = pieces[1] * 60.0;
|
||||
let seconds = pieces[2];
|
||||
hours + minutes + seconds
|
||||
}
|
||||
|
||||
fn spans_from_transitions(&self, transitions: Vec<f32>) -> Vec<ContentSpan> {
|
||||
let spans: Vec<_> = self.raw_spans_from_transitions(transitions).iter()
|
||||
.filter(|x| x.stop - x.start > 0.25) // TODO const
|
||||
.map(|x| ContentSpan{
|
||||
start: (x.start - 1.0).clamp(0.0, self.duration()),
|
||||
stop: (x.stop + 1.0).clamp(0.0, self.duration()), // TODO const
|
||||
})
|
||||
.collect();
|
||||
self.merge_spans(spans)
|
||||
}
|
||||
|
||||
fn merge_unsorted_spans(&self, spans: Vec<ContentSpan>) -> Vec<ContentSpan> {
|
||||
let mut spans: Vec<_> = spans.iter().map(|x| *x).collect();
|
||||
spans.sort_by(|x, y| match x.start.partial_cmp(&y.start).unwrap() {
|
||||
Ordering::Less => Ordering::Less,
|
||||
Ordering::Equal => x.stop.partial_cmp(&y.stop).unwrap(),
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
});
|
||||
self.merge_spans(spans)
|
||||
}
|
||||
|
||||
fn merge_spans(&self, spans: Vec<ContentSpan>) -> Vec<ContentSpan> {
|
||||
let mut result: Vec<_> = vec![];
|
||||
let mut cur = *spans.iter().nth(0).or(Some(&ContentSpan{start: 0.0, stop: 0.0})).unwrap();
|
||||
for i in 1..spans.len() {
|
||||
if spans[i].start - cur.stop > 5.0 { // TODO const
|
||||
if !cur.empty() {
|
||||
result.push(cur);
|
||||
}
|
||||
cur = spans[i];
|
||||
} else if spans[i].stop > cur.stop {
|
||||
cur.stop = spans[i].stop
|
||||
}
|
||||
}
|
||||
result.push(cur);
|
||||
result
|
||||
}
|
||||
|
||||
fn raw_spans_from_transitions(&self, transitions: Vec<f32>) -> Vec<ContentSpan> {
|
||||
let mut result = vec![];
|
||||
for i in (0..transitions.len()).step_by(2) {
|
||||
let start = transitions[i];
|
||||
let mut stop = self.duration();
|
||||
if transitions.len() > i+1 {
|
||||
stop = transitions[i+1];
|
||||
}
|
||||
result.push(ContentSpan{start: start, stop: stop});
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_inspection {
|
||||
use super::*;
|
||||
const FILE: &str = "/Users/breel/Movies/bel_1_1.mp4";
|
||||
|
||||
#[test]
|
||||
fn test_screenshotify() {
|
||||
let result = screenshotify(&FILE.to_string()).unwrap();
|
||||
for i in result.iter() {
|
||||
eprintln!("{}", i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clipify() {
|
||||
let result = clipify(&FILE.to_string()).unwrap();
|
||||
for i in result.iter() {
|
||||
eprintln!("{}", i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_screenshot() {
|
||||
let output = format!("{}.clipped.jpg", FILE);
|
||||
screenshot(
|
||||
&output,
|
||||
&FILE.to_string(),
|
||||
3.0,
|
||||
).unwrap();
|
||||
|
||||
let inspection = _inspect(&output);
|
||||
assert_eq!(true, inspection.is_ok());
|
||||
let inspection = inspection.unwrap();
|
||||
assert_eq!(0.04, inspection.duration());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clip() {
|
||||
let output = format!("{}.clipped.mp4", FILE);
|
||||
clip(
|
||||
&output,
|
||||
&FILE.to_string(),
|
||||
ContentSpan{start: 3.0, stop: 5.0},
|
||||
).unwrap();
|
||||
|
||||
let inspection = _inspect(&output);
|
||||
assert_eq!(true, inspection.is_ok());
|
||||
let inspection = inspection.unwrap();
|
||||
assert_eq!(2.0, inspection.duration());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inspect() {
|
||||
let inspection = _inspect(&FILE.to_string());
|
||||
assert_eq!(true, inspection.is_ok());
|
||||
let inspection = inspection.unwrap();
|
||||
|
||||
assert_eq!(28.14, inspection.duration());
|
||||
|
||||
assert_eq!(4, inspection.visual_transitions().len());
|
||||
assert_eq!(
|
||||
vec![ContentSpan{start: 1.0520501, stop: 22.0043}],
|
||||
inspection.visual_spans(),
|
||||
);
|
||||
|
||||
assert_eq!(8, inspection.audible_transitions().len());
|
||||
assert_eq!(
|
||||
vec![ContentSpan{start: 3.3723102, stop: 20.8207}],
|
||||
inspection.audible_spans(),
|
||||
);
|
||||
|
||||
assert_eq!(1, inspection.content_spans().len());
|
||||
assert_eq!(
|
||||
vec![ContentSpan{start: 1.0520501, stop: 22.0043}],
|
||||
inspection.content_spans(),
|
||||
);
|
||||
|
||||
let lines = inspection.lines;
|
||||
assert!(lines.iter().filter(|x| x.contains("Parsed_mpdecimate_0")).count() > 0);
|
||||
assert!(lines.iter().filter(|x| x.contains("Duration: 00")).count() > 0);
|
||||
assert!(lines.iter().filter(|x| x.contains("silence_end: ")).count() > 0);
|
||||
assert!(lines.iter().filter(|x| x.contains("silence_start: ")).count() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, PartialOrd)]
|
||||
pub struct ContentSpan {
|
||||
pub start: f32,
|
||||
pub stop: f32,
|
||||
}
|
||||
|
||||
impl ContentSpan {
|
||||
pub fn duration(&self) -> f32 {
|
||||
self.stop - self.start
|
||||
}
|
||||
|
||||
fn empty(&self) -> bool {
|
||||
*self == (ContentSpan{start: 0.0, stop: 0.0})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_content_span {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_duration() {
|
||||
assert_eq!(1.0, ContentSpan{start: 1.0, stop: 2.0}.duration());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
use lib;
|
||||
use std::str::FromStr;
|
||||
|
||||
fn main() {
|
||||
let cmd = std::env::args().nth(1).expect("first argument must be [inspect] or [clip]");
|
||||
let file = std::env::args().nth(2).expect("second argument must be [path to file]");
|
||||
match cmd.as_ref() {
|
||||
"clip" => {
|
||||
let content_span = lib::video::ContentSpan{
|
||||
start: f32::from_str(&std::env::args().nth(3).expect("third argument must be [start time as f32 seconds]")).unwrap(),
|
||||
stop: f32::from_str(&std::env::args().nth(4).expect("fourth argument must be [stop time as f32 seconds]")).unwrap(),
|
||||
};
|
||||
let output = format!("{}.clipped.mp4", &file);
|
||||
eprintln!("clipping {} from {} to {} as {}...", &file, &content_span.start, &content_span.stop, &output);
|
||||
lib::video::clip(&output, &file, content_span).unwrap();
|
||||
},
|
||||
"inspect" => {
|
||||
eprintln!("inspecting {}...", &file);
|
||||
let content_spans = lib::video::inspect(&file).unwrap();
|
||||
content_spans.iter()
|
||||
.for_each(|x| {
|
||||
println!("{}..{}", x.start, x.stop);
|
||||
});
|
||||
},
|
||||
_ => {
|
||||
panic!("unrecognized command {}", cmd);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -67,6 +67,25 @@ pub fn clip(output: &String, input: &String, content_span: ContentSpan) -> Resul
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn clip_async(output: &String, input: &String, content_span: ContentSpan) -> Result<(), String> {
|
||||
fs::create_dir_all(Path::new(output).parent().unwrap()).unwrap();
|
||||
match async_process::Command::new("ffmpeg")
|
||||
.args([
|
||||
"-y",
|
||||
"-ss", &content_span.start.to_string(),
|
||||
"-i", input,
|
||||
"-t", &(content_span.stop - content_span.start).to_string(),
|
||||
output,
|
||||
])
|
||||
.output().await {
|
||||
Ok(output) => match output.status.success() {
|
||||
true => Ok(()),
|
||||
false => Err(format!("failed to ffmpeg clip {}: {}", input, String::from_utf8(output.stderr).unwrap())),
|
||||
},
|
||||
Err(msg) => Err(format!("failed to ffmpeg clip {}: {}", input, msg)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn screenshot(output: &String, input: &String, ts: f32) -> Result<(), String> {
|
||||
match screenshot_png(input, ts) {
|
||||
Ok(png) => {
|
||||
|
|
@ -111,7 +130,7 @@ fn _inspect(file: &String) -> Result<Inspection, String> {
|
|||
match std::process::Command::new("ffmpeg")
|
||||
.args([
|
||||
"-i", file,
|
||||
"-vf", "mpdecimate",
|
||||
"-vf", "mpdecimate,select='gt(scene,0.0)'",
|
||||
"-af", "silencedetect=n=-50dB:d=1",
|
||||
"-loglevel", "debug",
|
||||
"-f", "null",
|
||||
|
|
@ -126,7 +145,38 @@ fn _inspect(file: &String) -> Result<Inspection, String> {
|
|||
lines: line_iter.map(|x| x.to_string()).collect(),
|
||||
})
|
||||
},
|
||||
Err(msg) => Err(format!("failed to ffmpeg inspect {}: {}", file, msg)),
|
||||
Err(msg) => Err(format!("failed to inspect with ffmpeg: {}", msg)),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub async fn inspect_async(file: &String) -> Result<Vec<ContentSpan>, String> {
|
||||
match _inspect_async(file).await {
|
||||
Ok(inspection) => Ok(inspection.content_spans()),
|
||||
Err(msg) => Err(msg)
|
||||
}
|
||||
}
|
||||
|
||||
async fn _inspect_async(file: &String) -> Result<Inspection, String> {
|
||||
match async_process::Command::new("ffmpeg")
|
||||
.args([
|
||||
"-i", file,
|
||||
"-vf", "mpdecimate,select='gt(scene,0.0)'",
|
||||
"-af", "silencedetect=n=-50dB:d=1",
|
||||
"-loglevel", "debug",
|
||||
"-f", "null",
|
||||
"-",
|
||||
])
|
||||
.output().await {
|
||||
Ok(output) => {
|
||||
let stderr = String::from_utf8(output.stderr).unwrap();
|
||||
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||
let line_iter = stderr.split("\n").chain(stdout.split("\n"));
|
||||
Ok(Inspection{
|
||||
lines: line_iter.map(|x| x.to_string()).collect(),
|
||||
})
|
||||
},
|
||||
Err(msg) => Err(format!("failed to inspect with ffmpeg: {}", msg)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,10 +201,36 @@ impl Inspection {
|
|||
}
|
||||
|
||||
fn visual_spans(&self) -> Vec<ContentSpan> {
|
||||
self.spans_from_transitions(self.visual_transitions())
|
||||
let unstuck_spans = self.spans_from_transitions(self.visual_transitions_unstuck_frames());
|
||||
let scene_splits = self.visual_transitions_scene_splits();
|
||||
let mut result = vec![];
|
||||
for i in 0..unstuck_spans.len() {
|
||||
let mut span = unstuck_spans[i];
|
||||
for split in scene_splits.iter() {
|
||||
if &(span.start + 5.0) < split && split < &(span.stop - 5.0) { // TODO const
|
||||
result.push(ContentSpan{start: span.start, stop: *split});
|
||||
span.start = *split;
|
||||
}
|
||||
}
|
||||
if span.stop - span.start > 2.0 {
|
||||
result.push(span);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn visual_transitions(&self) -> Vec<f32> {
|
||||
fn visual_transitions_scene_splits(&self) -> Vec<f32> {
|
||||
self.lines.iter()
|
||||
.filter(|x| x.contains("[Parsed_select_1") && x.contains(" scene:"))
|
||||
.map(|x| f32::from_str(x
|
||||
.split(" scene:").nth(1).unwrap()
|
||||
.split(" ").nth(0).unwrap()
|
||||
).unwrap())
|
||||
.filter(|x| *x > 0.3) // TODO const
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn visual_transitions_unstuck_frames(&self) -> Vec<f32> {
|
||||
let lines: Vec<_> = self.lines.iter()
|
||||
.filter(|x| x.contains("Parsed_mpdecimate_0"))
|
||||
.filter(|x| x.contains("keep pts:") || x.contains("drop pts:"))
|
||||
|
|
@ -327,7 +403,11 @@ mod test_inspection {
|
|||
|
||||
assert_eq!(28.14, inspection.duration());
|
||||
|
||||
assert_eq!(4, inspection.visual_transitions().len());
|
||||
assert_eq!(2, inspection.visual_transitions_scene_splits().len()); // TODO
|
||||
assert_eq!(0.307763, inspection.visual_transitions_scene_splits()[0]); // TODO
|
||||
assert_eq!(0.65986, inspection.visual_transitions_scene_splits()[1]); // TODO
|
||||
|
||||
assert_eq!(4, inspection.visual_transitions_unstuck_frames().len());
|
||||
assert_eq!(
|
||||
vec![ContentSpan{start: 1.0520501, stop: 22.0043}],
|
||||
inspection.visual_spans(),
|
||||
|
|
|
|||
|
|
@ -69,13 +69,32 @@ dependencies = [
|
|||
"futures-lite 1.13.0",
|
||||
"log",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix",
|
||||
"polling 2.8.0",
|
||||
"rustix 0.37.27",
|
||||
"slab",
|
||||
"socket2",
|
||||
"waker-fn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7"
|
||||
dependencies = [
|
||||
"async-lock 3.2.0",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite 2.1.0",
|
||||
"parking",
|
||||
"polling 3.3.1",
|
||||
"rustix 0.38.28",
|
||||
"slab",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "2.8.0"
|
||||
|
|
@ -96,6 +115,42 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15c1cd5d253ecac3d3cf15e390fd96bd92a13b1d14497d81abf077304794fb04"
|
||||
dependencies = [
|
||||
"async-channel 2.1.1",
|
||||
"async-io 2.2.2",
|
||||
"async-lock 3.2.0",
|
||||
"async-signal",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener 4.0.1",
|
||||
"futures-lite 2.1.0",
|
||||
"rustix 0.38.28",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5"
|
||||
dependencies = [
|
||||
"async-io 2.2.2",
|
||||
"async-lock 2.8.0",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix 0.38.28",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.6.0"
|
||||
|
|
@ -948,7 +1003,10 @@ version = "2.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143"
|
||||
dependencies = [
|
||||
"fastrand 2.0.1",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
|
|
@ -1291,6 +1349,17 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home-video-blue-extractinator"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"base64 0.21.5",
|
||||
"dioxus",
|
||||
"dioxus-desktop",
|
||||
"lib",
|
||||
"opener",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.25.2"
|
||||
|
|
@ -1522,6 +1591,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
[[package]]
|
||||
name = "lib"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-process",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
|
|
@ -1535,6 +1607,12 @@ version = "0.3.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.11"
|
||||
|
|
@ -1993,6 +2071,20 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"pin-project-lite",
|
||||
"rustix 0.38.28",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
|
|
@ -2204,7 +2296,7 @@ version = "0.11.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fe664af397d2b6a13a8ba1d172a2b5c87c6c5149039edbf8fa122b98c9ed96f"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-io 1.13.0",
|
||||
"block",
|
||||
"dispatch",
|
||||
"futures-util",
|
||||
|
|
@ -2254,10 +2346,23 @@ dependencies = [
|
|||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"linux-raw-sys 0.3.8",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.12",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.16"
|
||||
|
|
@ -2387,6 +2492,15 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
|
|
@ -2467,17 +2581,6 @@ dependencies = [
|
|||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "src"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.21.5",
|
||||
"dioxus",
|
||||
"dioxus-desktop",
|
||||
"lib",
|
||||
"opener",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "src"
|
||||
version = "0.1.0"
|
||||
name = "home-video-blue-extractinator"
|
||||
version = "0.1.6"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
|
@ -10,5 +10,13 @@ dioxus = "0.4.3"
|
|||
lib = { path = "../src-lib" }
|
||||
base64 = "0.21.5"
|
||||
opener = "0.6.1"
|
||||
#dioxus-web = "0.4.3"
|
||||
dioxus-desktop = "0.4.3"
|
||||
|
||||
[package.metadata.bundle]
|
||||
name = "home-video-blue-extractinator"
|
||||
identifier = "com.breel.home-video-blue-extractinator"
|
||||
version = "0.1.6"
|
||||
copyright = "Copyright (c) breel.dev 2023. All rights reserved."
|
||||
long_description = """
|
||||
Tool to turn long home movies into clips with less noise.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
[application]
|
||||
name = "home-video-blue-extractinator"
|
||||
default_platform = "desktop"
|
||||
|
||||
[web.app]
|
||||
title = "home-video-blue-extractinator"
|
||||
|
||||
[web.watcher]
|
||||
|
||||
[web.resource]
|
||||
|
||||
[web.resource.dev]
|
||||
180
src/src/main.rs
180
src/src/main.rs
|
|
@ -3,79 +3,90 @@
|
|||
use dioxus::prelude::*;
|
||||
use lib;
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use core::cmp::Ordering;
|
||||
use std::str::FromStr;
|
||||
|
||||
fn main() {
|
||||
// launch the dioxus app in a webview
|
||||
dioxus_desktop::launch(App); // TODO desktop
|
||||
//dioxus_web::launch(App);
|
||||
dioxus_desktop::launch(App);
|
||||
dioxus_desktop::launch_cfg(App, dioxus_desktop::Config::new()
|
||||
.with_window(dioxus_desktop::WindowBuilder::new()
|
||||
.with_title("home-video-blue-extractinator")
|
||||
));
|
||||
}
|
||||
|
||||
// define a component that renders a div with the text "Hello, world!"
|
||||
fn App(cx: Scope) -> Element {
|
||||
let file = use_state(cx, String::new);
|
||||
let analysis = use_state(cx, Analysis::new);
|
||||
let status = use_state(cx, String::new);
|
||||
let file = use_state(cx, || String::new());
|
||||
let analyze_status = use_state(cx, || String::new());
|
||||
let clipify_status = use_state(cx, || String::new());
|
||||
let processing = use_state(cx, || false);
|
||||
let analysis = use_state(cx, || Analysis::new());
|
||||
|
||||
let a_css = String::from_utf8_lossy(include_bytes!("./style.css"));
|
||||
let processing_css = || {
|
||||
match *processing.get() {
|
||||
true => "body { background-color: lightgray !important; opacity: 0.75 !important; }".to_string(),
|
||||
false => "".to_string(),
|
||||
}
|
||||
};
|
||||
|
||||
cx.render(rsx! {
|
||||
header {
|
||||
style { "
|
||||
body {{
|
||||
max-width: 600pt;
|
||||
margin: auto;
|
||||
}}
|
||||
label {{
|
||||
display: block;
|
||||
}}
|
||||
label:has(input[type=checkbox]:checked) {{
|
||||
background-color: lightgreen;
|
||||
}}
|
||||
" }
|
||||
title { "home-video-blue-extractinator" }
|
||||
style { "{a_css} {processing_css()}" }
|
||||
}
|
||||
main {
|
||||
rsx! {
|
||||
h1 { "home-video-blue-extractinator" }
|
||||
h3 { "1. Choose your movie" }
|
||||
div {
|
||||
input {
|
||||
r#type: "file",
|
||||
disabled: *processing.get(),
|
||||
onchange: |evt| {
|
||||
to_owned![file];
|
||||
if let Some(file_engine) = &evt.files {
|
||||
for f in &file_engine.files() {
|
||||
file.set(f.clone());
|
||||
analyze_status.set(String::new());
|
||||
clipify_status.set(String::new());
|
||||
processing.set(false);
|
||||
analysis.set(Analysis::new());
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
p { file.get().clone() }
|
||||
}
|
||||
h3 { "2. Analyze your movie" }
|
||||
div {
|
||||
input { r#type: "button", value: "analyze", disabled: file.get().len() == 0, onclick: |evt| {
|
||||
to_owned![file];
|
||||
to_owned![analysis];
|
||||
to_owned![status];
|
||||
async move {
|
||||
let analyzed = analyze(file.get().clone());
|
||||
if analyzed.err.len() > 0 {
|
||||
status.set(analyzed.err.clone());
|
||||
} else {
|
||||
status.set(format!(
|
||||
"found {} clips to keep and {} clips to drop",
|
||||
analyzed.result.iter().filter(|x| x.has_content).count(),
|
||||
analyzed.result.iter().filter(|x| !x.has_content).count(),
|
||||
));
|
||||
input { r#type: "button", value: "analyze", disabled: file.get().len() == 0 || *processing.get(), onclick: move |_| {
|
||||
cx.spawn({
|
||||
let file = file.to_owned();
|
||||
let analyze_status = analyze_status.to_owned();
|
||||
let processing = processing.to_owned();
|
||||
let analysis = analysis.to_owned();
|
||||
|
||||
async move {
|
||||
processing.set(true);
|
||||
analyze_status.set(format!("analyzing {file}... (this may take a while, like 5 minutes, but I promise I'm working on it)"));
|
||||
let analyzed = analyze(file.get().clone()).await;
|
||||
if analyzed.err.len() > 0 {
|
||||
analyze_status.set(analyzed.err.clone());
|
||||
} else {
|
||||
analyze_status.set(format!(
|
||||
"found {} clips to keep and {} clips to drop",
|
||||
analyzed.result.iter().filter(|x| x.has_content).count(),
|
||||
analyzed.result.iter().filter(|x| !x.has_content).count(),
|
||||
));
|
||||
}
|
||||
analysis.set(analyzed);
|
||||
processing.set(false);
|
||||
}
|
||||
analysis.set(analyzed);
|
||||
}
|
||||
});
|
||||
}}
|
||||
br {}
|
||||
div { analyze_status.get().clone() }
|
||||
}
|
||||
div {
|
||||
br {}
|
||||
status.get().clone()
|
||||
br {}
|
||||
}
|
||||
div {
|
||||
h3 { "Content Spans" }
|
||||
form {
|
||||
onsubmit: |evt| {
|
||||
let content_spans: Vec<_> = evt.values.iter()
|
||||
|
|
@ -89,16 +100,19 @@ fn App(cx: Scope) -> Element {
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
status.set(format!("clipifying {:?}", content_spans));
|
||||
clipify_status.set(format!("clipifying {:?}...", content_spans));
|
||||
let file = file.get().clone();
|
||||
to_owned![status];
|
||||
to_owned![clipify_status];
|
||||
to_owned![processing];
|
||||
async move {
|
||||
processing.set(true);
|
||||
let f = clipify(file, content_spans).await;
|
||||
status.set(f);
|
||||
clipify_status.set(f);
|
||||
processing.set(false);
|
||||
}
|
||||
},
|
||||
input { r#type: "submit", value: "clipify selected spans", disabled: file.get().len() == 0 }
|
||||
hr {}
|
||||
h3 { "3. Double check the scenes to keep (green) are okay" }
|
||||
analysis.get().result.iter().map(|a| {
|
||||
rsx! {
|
||||
div {
|
||||
|
|
@ -108,13 +122,18 @@ fn App(cx: Scope) -> Element {
|
|||
checked: a.has_content,
|
||||
name: "{a.start}..{a.stop}",
|
||||
}
|
||||
"{a.start}..{a.stop}: "
|
||||
"{a.pretty_range()}"
|
||||
br {}
|
||||
img { src: "data:image/png;base64, {a.screenshot}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
hr {}
|
||||
h3 { "4. Submit your re-clip" }
|
||||
div { clipify_status.get().clone() }
|
||||
br {}
|
||||
input { r#type: "submit", value: "clipify selected spans", disabled: file.get().len() == 0 || *processing.get() || analysis.get().result.len() == 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -138,15 +157,35 @@ impl Analysis {
|
|||
|
||||
#[derive(Clone)]
|
||||
struct Analyzed {
|
||||
_start: f32,
|
||||
_stop: f32,
|
||||
start: String,
|
||||
stop: String,
|
||||
screenshot: String,
|
||||
has_content: bool,
|
||||
}
|
||||
|
||||
fn analyze(file: String) -> Analysis {
|
||||
let content_spans = lib::video::inspect(&file); // TODO desktop
|
||||
//let content_spans: Result<Vec<lib::video::ContentSpan>, String> = Ok(vec![lib::video::ContentSpan{start: 0.5, stop: 20.2}]);
|
||||
impl Analyzed {
|
||||
fn pretty_range(&self) -> String {
|
||||
format!("{} - {}", self.pretty_t(self._start), self.pretty_t(self._stop))
|
||||
}
|
||||
|
||||
fn pretty_t(&self, t: f32) -> String {
|
||||
if t < 60.0 {
|
||||
return format!(":{:02}", t as i32);
|
||||
} else if t < 60.0 * 60.0 {
|
||||
return format!("{:02}:{:02}", (t / 60.0) as i32, (t % 60.0) as i32);
|
||||
} else {
|
||||
return format!("{:02}:{:02}:{:02}",
|
||||
(t / 60.0 / 60.0) as i32,
|
||||
(t / 60.0) as i32,
|
||||
(t % 60.0) as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn analyze(file: String) -> Analysis {
|
||||
let content_spans = lib::video::inspect_async(&file).await;
|
||||
if content_spans.is_err() {
|
||||
return Analysis{
|
||||
result: vec![],
|
||||
|
|
@ -169,11 +208,13 @@ fn analyze(file: String) -> Analysis {
|
|||
let screenshot = |content_span: &lib::video::ContentSpan| -> Analyzed {
|
||||
let ts = (content_span.start + content_span.stop) / 2.0;
|
||||
Analyzed {
|
||||
_start: content_span.start,
|
||||
_stop: content_span.stop,
|
||||
start: content_span.start.to_string(),
|
||||
stop: content_span.stop.to_string(),
|
||||
screenshot: match lib::video::screenshot_png(&file, ts) {
|
||||
Ok(png) => general_purpose::STANDARD.encode(&png),
|
||||
Err(_) => a_png.to_string(),
|
||||
Err(_) => A_PNG.to_string(),
|
||||
},
|
||||
has_content: false,
|
||||
}
|
||||
|
|
@ -201,25 +242,24 @@ fn analyze(file: String) -> Analysis {
|
|||
|
||||
async fn clipify(file: String, content_spans: Vec<lib::video::ContentSpan>) -> String {
|
||||
let d = format!("{}.d", &file);
|
||||
match content_spans.iter()
|
||||
.map(|content_span| {
|
||||
lib::video::clip(
|
||||
&format!("{}/{}.mp4", &d, content_span.start),
|
||||
&file,
|
||||
*content_span,
|
||||
)
|
||||
})
|
||||
.filter(|x| x.is_err())
|
||||
.map(|x| x.err().unwrap())
|
||||
.nth(0) {
|
||||
Some(err_msg) => err_msg,
|
||||
None => {
|
||||
match opener::open(d.clone()) {
|
||||
Ok(_) => d,
|
||||
Err(msg) => format!("failed to open clipify result: {}", msg),
|
||||
}
|
||||
},
|
||||
let mut statuses = vec![];
|
||||
for content_span in content_spans.iter() {
|
||||
let output = format!("{}/{}.mp4", &d, content_span.start);
|
||||
match std::path::Path::new(&output).exists() {
|
||||
true => { statuses.push(format!("skipping {} as it already exists", &output)); },
|
||||
false => {
|
||||
let cur = lib::video::clip_async(&output, &file, *content_span).await;
|
||||
if cur.is_err() {
|
||||
statuses.push(format!("failed to clipify {} at {}..{}: {}", &file, &content_span.start, &content_span.stop, cur.err().unwrap()));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
let _ = opener::open(d.clone());
|
||||
match statuses.len() {
|
||||
0 => d,
|
||||
_ => statuses.join(", "),
|
||||
}
|
||||
}
|
||||
|
||||
const a_png: &str = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
|
||||
const A_PNG: &str = r"iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue