Use ffprobe to load per-file metadata.

Build single metadata.json for whole library.
Have basic load into struct, a subset of useful fields fromt metadata.json.
This commit is contained in:
Bill Thiede 2019-11-02 22:09:50 -07:00
parent 86fbf78a73
commit bf9d5b7c11
5 changed files with 226798 additions and 53 deletions

217
Cargo.lock generated
View File

@ -16,6 +16,14 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "arrayvec"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.13"
@ -30,11 +38,36 @@ name = "autocfg"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "backtrace"
version = "0.3.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "backtrace-sys"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.10"
@ -65,6 +98,70 @@ dependencies = [
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-deque"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-epoch"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-queue"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "crossbeam-utils"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "either"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "failure"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "failure_derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "glob"
version = "0.3.0"
@ -78,6 +175,14 @@ dependencies = [
"unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hermit-abi"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "human_format"
version = "1.0.2"
@ -111,6 +216,19 @@ name = "memchr"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memoffset"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nodrop"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "num-integer"
version = "0.1.41"
@ -128,6 +246,15 @@ dependencies = [
"autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num_cpus"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro-error"
version = "0.2.6"
@ -154,6 +281,28 @@ dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rayon-core"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
@ -175,11 +324,42 @@ name = "regex-syntax"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustc-demangle"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ryu"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "scopeguard"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "serde"
version = "1.0.102"
@ -249,9 +429,12 @@ dependencies = [
name = "superdeduper"
version = "0.1.0"
dependencies = [
"failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
"glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"human_format 1.0.2 (git+https://github.com/wathiede/human-format-rs)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)",
@ -269,6 +452,17 @@ dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "synstructure"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termcolor"
version = "0.3.6"
@ -353,29 +547,51 @@ dependencies = [
[metadata]
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum cc 1.0.46 (registry+https://github.com/rust-lang/crates.io-index)" = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71"
"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9"
"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b"
"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
"checksum human_format 1.0.2 (git+https://github.com/wathiede/human-format-rs)" = "<none>"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f"
"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
"checksum num_cpus 1.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "155394f924cdddf08149da25bfb932d226b4a593ca7468b08191ff6335941af5"
"checksum proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097"
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83a27732a533a1be0a0035a111fe76db89ad312f6f0347004c220c57f209a123"
"checksum rayon-core 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98dcf634205083b17d0861252431eb2acbfb698ab7478a2d20de07954f47ec7b"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd"
"checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0"
"checksum serde_derive 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)" = "ca13fc1a832f793322228923fbb3aba9f3f44444898f835d31ad1b74fa0a2bf8"
"checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2"
@ -384,6 +600,7 @@ dependencies = [
"checksum structopt 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4f66a4c0ddf7aee4677995697366de0749b0139057342eccbb609b12d0affc"
"checksum structopt-derive 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8fe0c13e476b4e21ff7f5c4ace3818b6d7bdc16897c31c73862471bc1663acae"
"checksum syn 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7bedb3320d0f3035594b0b723c8a28d7d336a3eda3881db79e61d676fb644c"
"checksum synstructure 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203"
"checksum termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "adc4587ead41bf016f11af03e55a624c06568b5a19db4e90fde573d805074f83"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"

View File

@ -7,11 +7,14 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
failure = "0.1"
glob = "0.3"
human_format = { git ="https://github.com/wathiede/human-format-rs" }
lazy_static = "1.4"
log = "0.4"
rayon = "1.2"
regex = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
human_format = { git ="https://github.com/wathiede/human-format-rs" }
stderrlog = "0.4"
log = "0.4"
regex = "1"
structopt = "0.3"

View File

@ -1,33 +1,126 @@
use std::collections::HashMap;
use std::env;
use std::error::Error;
use std::ffi::OsStr;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fs::File;
use std::io::BufReader;
use std::io::BufWriter;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::str::FromStr;
use failure::bail;
use failure::Error;
use failure::ResultExt;
use glob::glob;
use lazy_static::lazy_static;
use log::error;
use log::info;
use rayon::iter::ParallelBridge;
use rayon::prelude::ParallelIterator;
use serde::de;
use serde::de::Deserializer;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
#[derive(Clone, Deserialize, Debug)]
#[derive(Clone, Deserialize, Debug, Serialize)]
pub struct Resolution(usize, usize);
impl fmt::Display for Resolution {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
impl Display for Resolution {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let v = format!("{}x{}", self.0, self.1);
f.pad(&v)
}
}
#[derive(Clone, Deserialize, Debug)]
pub struct Metadata {
pub size: usize,
pub dimension: Resolution,
pub duration_text: String,
pub duration: f32,
fn option_from_str<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
T::from_str(&s).map(Some).map_err(de::Error::custom)
}
#[derive(Deserialize, Debug)]
fn from_str<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
T::from_str(&s).map_err(de::Error::custom)
}
#[derive(Clone, Deserialize, Debug, Serialize)]
pub struct Format {
#[serde(default, deserialize_with = "option_from_str")]
bit_rate: Option<usize>,
#[serde(deserialize_with = "from_str")]
duration: f32,
filename: String,
format_name: String,
#[serde(deserialize_with = "from_str")]
size: usize,
}
// TODO(wathiede): make strem an enum with the tag type stored in codec_type?
#[derive(Clone, Deserialize, Debug, Serialize)]
#[serde(tag = "codec_type")]
pub enum Stream {
#[serde(rename = "video")]
Video {
#[serde(default, deserialize_with = "option_from_str")]
bit_rate: Option<usize>,
codec_name: String,
codec_long_name: String,
coded_height: usize,
coded_width: usize,
display_aspect_ratio: String,
#[serde(default, deserialize_with = "from_str")]
duration: f32,
height: usize,
width: usize,
},
#[serde(rename = "audio")]
Audio {},
#[serde(rename = "subtitle")]
Subtitle {},
#[serde(rename = "attachment")]
Attachment {},
#[serde(rename = "data")]
Data {},
}
impl Stream {
pub fn dimension(&self) -> Option<Resolution> {
None
}
}
#[derive(Clone, Deserialize, Debug, Serialize)]
pub struct Metadata {
format: Format,
streams: Vec<Stream>,
}
impl Metadata {
pub fn dimension(&self) -> Option<Resolution> {
None
}
pub fn duration(&self) -> f32 {
self.format.duration
}
pub fn size(&self) -> usize {
self.format.size
}
}
#[derive(Deserialize, Debug, Serialize)]
pub struct MetadataFile {
#[serde(flatten)]
pub metadata: HashMap<String, Metadata>,
@ -37,37 +130,132 @@ pub struct MovieLibrary {
root: String,
}
fn json_metadata_for_path<P: AsRef<OsStr>>(path: P) -> Result<String, Error> {
let mut cmd = Command::new("ffprobe");
// TODO(wathiede): maybe add "-select_streams v"
cmd.args(&[
"-v",
"quiet",
"-print_format",
"json",
"-show_format",
"-show_error",
"-show_streams",
])
.arg(path);
info!(target: "json", "cmd {:?}", cmd);
let output = cmd.output()?;
if output.status.success() {
return Ok(String::from_utf8(output.stdout)?);
}
bail!(
"{:?} exit status {}:\nSTDOUT: {}\nSTDERR: {}",
cmd,
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
)
}
lazy_static! {
static ref MOVIE_EXTS: Vec<&'static str> = vec!["avi", "m4v", "mkv", "mov", "mp4"];
}
impl MovieLibrary {
pub fn new<S: Into<String>>(root: S) -> MovieLibrary {
MovieLibrary { root: root.into() }
}
pub fn movies(
&self,
include_stale: bool,
) -> Result<(HashMap<String, Metadata>), Box<dyn Error>> {
let mut movies = HashMap::new();
for md in glob(&format!("{}/*/metadata.json", self.root))? {
match md {
Ok(path) => {
let mdf = read_metadata_from_file(&path)?;
for (name, md) in mdf.metadata {
if include_stale {
movies.insert(name, md);
} else {
// Filter out files that don't exist
dbg!(&self.root, &name);
let mut p = PathBuf::from(&self.root);
p.push(&name);
dbg!(&p);
if p.is_file() {
movies.insert(name, md);
}
}
pub fn compact_metadata(&self) -> Result<(), Error> {
let mdf = read_metadata_from_file(Path::new(&self.root).join("metadata.json"))?;
info!("Read metadata, {} videos found", mdf.metadata.len());
Ok(())
}
pub fn update_metadata(&self) -> Result<(), Error> {
let path = Path::new(&self.root).join("metadata.json");
// Open the file in read-only mode with buffer.
let f = File::open(&path).context(format!("open {}", path.display()))?;
let r = BufReader::new(f);
// Read the JSON contents of the file as an instance of `User`.
let old_metadata: HashMap<String, Value> = serde_json::from_reader(r)
.context(format!("serde_json::from_reader {}", path.display()))?;
info!("Read metadata, {} videos found", old_metadata.len());
let mut metadata: HashMap<_, _> = self
.iter_video_files()
.filter(|r| r.is_ok())
.filter(|r| {
let path = r
.as_ref()
.unwrap()
.strip_prefix(&self.root)
.unwrap()
.to_str()
.unwrap()
.to_owned();
!old_metadata.contains_key(&path)
})
.par_bridge()
.filter_map(move |path| {
env::set_current_dir(&self.root).unwrap();
let path: PathBuf = path.unwrap().into();
let path = path.strip_prefix(&self.root).unwrap();
match json_metadata_for_path(&path) {
Ok(json) => {
info!("{}", path.display());
Some((path.to_string_lossy().into_owned(), json))
}
Err(e) => {
error!("{}", e);
None
}
}
Err(e) => {
return Err(e.into());
})
.map(|(path, json)| (path, serde_json::from_str::<Value>(&json).unwrap()))
.collect();
info!("Adding {} new videos", metadata.len());
metadata.extend(old_metadata);
let f = File::create(Path::new(&self.root).join("metadata.json"))?;
let f = BufWriter::new(f);
serde_json::ser::to_writer_pretty(f, &metadata)?;
Ok(())
}
fn iter_video_files(&self) -> impl Send + Iterator<Item = Result<PathBuf, glob::GlobError>> {
glob(&format!("{}/*/*", self.root)).unwrap().filter(|path| {
let path = path.as_ref().unwrap();
match path.extension() {
Some(ext) => {
let ext: &str = &ext.to_str().unwrap().to_lowercase();
if !MOVIE_EXTS.contains(&ext) {
return false;
}
}
None => return false,
}
return true;
})
}
pub fn movies(&self, include_stale: bool) -> Result<(HashMap<String, Metadata>), Error> {
let mut movies = HashMap::new();
for md in glob(&format!("{}/*/metadata.json", self.root))? {
let path = md?;
let mdf = read_metadata_from_file(&path)?;
for (name, md) in mdf.metadata {
if include_stale {
movies.insert(name, md);
} else {
// Filter out files that don't exist
let mut p = PathBuf::from(&self.root);
p.push(&name);
if p.is_file() {
movies.insert(name, md);
}
}
}
}
@ -75,13 +263,15 @@ impl MovieLibrary {
}
}
fn read_metadata_from_file<P: AsRef<Path>>(path: P) -> Result<MetadataFile, Box<dyn Error>> {
fn read_metadata_from_file<P: AsRef<Path>>(path: P) -> Result<MetadataFile, Error> {
let path = path.as_ref();
// Open the file in read-only mode with buffer.
let file = File::open(path)?;
let reader = BufReader::new(file);
let f = File::open(path).context(format!("open {}", path.display()))?;
let r = BufReader::new(f);
// Read the JSON contents of the file as an instance of `User`.
let md = serde_json::from_reader(reader)?;
let md = serde_json::from_reader(r)
.context(format!("serde_json::from_reader {}", path.display()))?;
// Return the `User`.
Ok(md)
@ -95,6 +285,14 @@ mod tests {
format!("{}/testdata", env::var("CARGO_MANIFEST_DIR").unwrap())
}
#[test]
fn test_read_full_metadata() {
let mdf = read_metadata_from_file(Path::new(&testdata_dir()).join("Movies/metadata.json"))
.expect("failed to read metadata");
assert_eq!(mdf.metadata.len(), 1214);
}
/*
#[test]
fn test_movies() {
let lib = MovieLibrary::new(format!("{}/Movies", testdata_dir()));
@ -116,7 +314,9 @@ mod tests {
assert_eq!(got, want);
}
*/
/*
#[test]
fn test_filter_stale() {
let lib = MovieLibrary::new(format!("{}/Movies", testdata_dir()));
@ -137,4 +337,5 @@ mod tests {
assert_eq!(got, want);
}
*/
}

View File

@ -25,19 +25,29 @@ fn clean_path_parent<P: AsRef<Path>>(path: P) -> PathBuf {
PathBuf::from(path)
}
fn print_movie_groups(movie_groups: &HashMap<PathBuf, Vec<String>>) {
fn print_movie_groups(movie_groups: &HashMap<PathBuf, Vec<(String, Metadata)>>) {
let mut names = movie_groups.keys().collect::<Vec<_>>();
names.sort();
let mut fmtr = Formatter::new();
fmtr.with_separator("");
fmtr.with_scales(Scales::Binary());
for name in names {
let paths = &movie_groups[name];
if paths.len() < 2 {
continue;
}
let dir = name.to_str().unwrap();
println!("{}:", &dir[MOVIE_DIR.len() + 1..]);
for p in paths {
println!(" {}", &p[p.rfind("/").unwrap() + 1..]);
let mut file: Vec<_> = movie_groups[name].iter().collect();
file.sort_by(|(n1, _), (n2, _)| n1.partial_cmp(n2).unwrap());
println!("{}:", name.display());
for (p, md) in file {
println!(
" {:>9} {:>9} {} {}",
md.dimension().unwrap(),
fmtr.format(md.size() as f64),
md.duration(),
&p[p.rfind("/").unwrap() + 1..]
);
}
}
}
@ -57,9 +67,9 @@ fn print_movies(movies: &HashMap<String, Metadata>, filter: Option<&Regex>) {
let md = &movies[name];
info!(
"{:>9} {:>8} {} {}",
md.dimension,
fmtr.format(md.size as f64),
md.duration_text,
md.dimension().unwrap(),
fmtr.format(md.size() as f64),
md.duration(),
&name[MOVIE_DIR.len() + 1..]
);
println!("mv '{}' '{}'", name, TO_BE_REMOVED_DIR);
@ -72,6 +82,13 @@ enum Command {
Samples,
#[structopt(name = "groups", about = "Print movies grouped by root name")]
Groups,
#[structopt(
name = "compact-metadata",
about = "Read full metadata file and write compact file."
)]
CompactMetadata,
#[structopt(name = "update-metadata", about = "Write metadata files")]
UpdateMetadata,
}
#[derive(StructOpt)]
@ -86,15 +103,22 @@ struct SuperDeduper {
parse(from_occurrences)
)]
verbose: usize,
#[structopt(long = "module", help = "Additional log target to enable")]
module: Option<String>,
#[structopt(subcommand)] // Note that we mark a field as a subcommand
cmd: Command,
}
fn main() -> Result<(), Box<dyn Error>> {
let app = SuperDeduper::from_args();
let mut modules = vec![module_path!().to_string()];
if let Some(module) = app.module {
modules.push(module);
}
stderrlog::new()
.verbosity(app.verbose)
.timestamp(stderrlog::Timestamp::Millisecond)
.modules(modules)
.init()
.unwrap();
@ -110,15 +134,23 @@ fn main() -> Result<(), Box<dyn Error>> {
let lib = MovieLibrary::new(MOVIE_DIR);
let movies = lib.movies(false)?;
let mut movie_groups: HashMap<PathBuf, Vec<String>> = HashMap::new();
for name in movies.keys() {
let mut movie_groups: HashMap<PathBuf, Vec<(String, Metadata)>> = HashMap::new();
for (name, md) in movies.into_iter() {
let clean_name = clean_path_parent(&name);
let paths = movie_groups.entry(clean_name).or_insert(Vec::new());
paths.push(name.to_string())
paths.push((name.to_string(), md));
}
print_movie_groups(&movie_groups);
}
Command::CompactMetadata => {
let lib = MovieLibrary::new(MOVIE_DIR);
lib.compact_metadata()?;
}
Command::UpdateMetadata => {
let lib = MovieLibrary::new(MOVIE_DIR);
lib.update_metadata()?;
}
}
Ok(())
}

226292
testdata/Movies/metadata.json vendored Normal file

File diff suppressed because it is too large Load Diff