diff --git a/Cargo.lock b/Cargo.lock
index 6c9c45d..e176cc2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -61,6 +61,16 @@ version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+[[package]]
+name = "assert-json-diff"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "atk"
version = "0.18.2"
@@ -323,6 +333,23 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+[[package]]
+name = "cfg_aliases"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
+
+[[package]]
+name = "chacha20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.1",
+ "rand_core 0.10.1",
+]
+
[[package]]
name = "chrono"
version = "0.4.45"
@@ -335,6 +362,15 @@ dependencies = [
"windows-link 0.2.1",
]
+[[package]]
+name = "colored"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "combine"
version = "4.6.8"
@@ -424,6 +460,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "cpufeatures"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "crc32fast"
version = "1.5.1"
@@ -1066,8 +1111,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"wasi",
+ "wasm-bindgen",
]
[[package]]
@@ -1089,8 +1136,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"r-efi 6.0.0",
+ "rand_core 0.10.1",
+ "wasm-bindgen",
]
[[package]]
@@ -1241,6 +1291,25 @@ dependencies = [
"syn 2.0.119",
]
+[[package]]
+name = "h2"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap 2.14.1",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -1320,6 +1389,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
[[package]]
name = "hyper"
version = "1.11.1"
@@ -1330,9 +1405,11 @@ dependencies = [
"bytes",
"futures-channel",
"futures-core",
+ "h2",
"http",
"http-body",
"httparse",
+ "httpdate",
"itoa",
"pin-project-lite",
"smallvec",
@@ -1340,6 +1417,22 @@ dependencies = [
"want",
]
+[[package]]
+name = "hyper-rustls"
+version = "0.27.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+ "webpki-roots",
+]
+
[[package]]
name = "hyper-util"
version = "0.1.20"
@@ -1794,6 +1887,12 @@ version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
[[package]]
name = "markup5ever"
version = "0.38.0"
@@ -1857,6 +1956,31 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "mockito"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90820618712cab19cfc46b274c6c22546a82affcb3c3bdf0f29e3db8e1bb92c0"
+dependencies = [
+ "assert-json-diff",
+ "bytes",
+ "colored",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "log",
+ "pin-project-lite",
+ "rand 0.9.5",
+ "regex",
+ "serde_json",
+ "serde_urlencoded",
+ "similar",
+ "tokio",
+]
+
[[package]]
name = "muda"
version = "0.19.3"
@@ -2353,6 +2477,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
[[package]]
name = "precomputed-hash"
version = "0.1.1"
@@ -2430,6 +2563,62 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "quinn"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror 2.0.20",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83"
+dependencies = [
+ "bytes",
+ "getrandom 0.4.3",
+ "lru-slab",
+ "rand 0.10.2",
+ "rand_pcg",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.20",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "quote"
version = "1.0.47"
@@ -2451,6 +2640,61 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+[[package]]
+name = "rand"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
+dependencies = [
+ "rand_chacha",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.3",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.9.5",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
+dependencies = [
+ "getrandom 0.3.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
+name = "rand_pcg"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
+dependencies = [
+ "rand_core 0.10.1",
+]
+
[[package]]
name = "raw-window-handle"
version = "0.6.2"
@@ -2544,11 +2788,52 @@ dependencies = [
name = "renamr-core"
version = "0.1.0"
dependencies = [
+ "mockito",
"regex",
+ "reqwest 0.12.28",
"rhai",
"serde",
"serde_json",
"thiserror 1.0.69",
+ "tokio",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.12.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webpki-roots",
]
[[package]]
@@ -2638,6 +2923,20 @@ dependencies = [
"syn 2.0.119",
]
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
[[package]]
name = "rustc-hash"
version = "2.1.3"
@@ -2653,12 +2952,53 @@ dependencies = [
"semver",
]
+[[package]]
+name = "rustls"
+version = "0.23.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
[[package]]
name = "rustversion"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+[[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
[[package]]
name = "same-file"
version = "1.0.6"
@@ -2849,6 +3189,18 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
[[package]]
name = "serde_with"
version = "3.22.0"
@@ -2920,7 +3272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"digest",
]
@@ -2936,6 +3288,12 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
+[[package]]
+name = "similar"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
+
[[package]]
name = "siphasher"
version = "1.0.3"
@@ -3071,6 +3429,12 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
[[package]]
name = "swift-rs"
version = "1.0.8"
@@ -3234,7 +3598,7 @@ dependencies = [
"percent-encoding",
"plist",
"raw-window-handle",
- "reqwest",
+ "reqwest 0.13.4",
"serde",
"serde_json",
"serde_repr",
@@ -3603,11 +3967,34 @@ dependencies = [
"bytes",
"libc",
"mio",
+ "parking_lot",
"pin-project-lite",
"socket2",
+ "tokio-macros",
"windows-sys 0.61.2",
]
+[[package]]
+name = "tokio-macros"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.4",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
[[package]]
name = "tokio-util"
version = "0.7.19"
@@ -3617,6 +4004,7 @@ dependencies = [
"bytes",
"futures-core",
"futures-sink",
+ "libc",
"pin-project-lite",
"tokio",
]
@@ -3898,6 +4286,12 @@ version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
[[package]]
name = "url"
version = "2.5.8"
@@ -4151,6 +4545,15 @@ dependencies = [
"system-deps",
]
+[[package]]
+name = "webpki-roots"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
+dependencies = [
+ "rustls-pki-types",
+]
+
[[package]]
name = "webview2-com"
version = "0.38.2"
@@ -4381,6 +4784,15 @@ dependencies = [
"windows-targets 0.42.2",
]
+[[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.6",
+]
+
[[package]]
name = "windows-sys"
version = "0.59.0"
@@ -4787,6 +5199,12 @@ dependencies = [
"synstructure",
]
+[[package]]
+name = "zeroize"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
+
[[package]]
name = "zerotrie"
version = "0.2.5"
diff --git a/README.md b/README.md
index 17ef39a..a886929 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,12 @@ fork), built from scratch as its own project: new stack, new architecture, new b
**Status: early scaffold, but the core loop works end to end.** The naming-expression
engine is ported and tested, the faithfully-recreated two-pane UI is wired to real
native file/folder loading and drag-and-drop, local filename-based season/episode
-detection ("Match") is working, and the actual on-disk rename operation is wired up
-(same-directory rename, skip-on-conflict, with a confirmation step showing the full
-old-name/new-name plan before anything touches disk — there is no undo yet). Full
-media-metadata bindings, the fuzzy similarity/matching engine, and online metadata
-lookups are not yet ported. See "Porting roadmap" below.
+detection ("Match") is working, real online metadata lookups against TMDB ("Fetch
+Data") are working, and the actual on-disk rename operation is wired up (same-directory
+rename, skip-on-conflict, with a confirmation step showing the full old-name/new-name
+plan before anything touches disk — there is no undo yet). Full media-metadata
+bindings and the fuzzy similarity/matching engine are not yet ported. See "Porting
+roadmap" below.
## Why a rewrite, and why this stack
@@ -50,19 +51,24 @@ translate directly.
```
crates/renamr-core/ Pure Rust core: naming-expression engine (ported), local
- filename-based season/episode matching (ported, `matching/`), and
- the on-disk rename engine (ported for the same-directory case,
+ filename-based season/episode matching (ported, `matching/`), a
+ TMDB metadata client (`web/tmdb.rs` — TV search + episode lookup,
+ tested against a mocked HTTP server via `mockito`), and the
+ on-disk rename engine (ported for the same-directory case,
`rename/`) — skip-on-conflict, no move/organize-into-folders yet.
Not yet ported: the fuzzy similarity engine, full media metadata
- bindings, web datasource clients (TMDB/TVDB/AniDB/etc). No Tauri
+ bindings, other web datasource clients (TVDB/AniDB/etc). No Tauri
dependency, so it builds and unit-tests standalone.
src-tauri/ Tauri app shell: exposes renamr-core as commands to the frontend
- (naming-scheme rendering, filename matching, executing a rename
- plan, native file/folder dialogs, drag-and-drop expansion), app
- metadata/icons/window config (tauri.conf.json).
+ (naming-scheme rendering, filename matching, TMDB metadata fetch,
+ executing a rename plan, native file/folder dialogs, drag-and-drop
+ expansion), plus `settings.rs` (TMDB token, stored as JSON in the
+ OS app-config directory), app metadata/icons/window config
+ (tauri.conf.json).
src/ React + TypeScript UI (Vite). Faithful recreation of the original
two-pane rename screen (Aurora theme) wired to real file loading,
- drag-and-drop, live naming-scheme preview, local matching, and a
+ drag-and-drop, live naming-scheme preview, local matching, TMDB
+ fetch (behind a Settings modal for the API token), and a
confirm-before-touching-disk modal for the actual rename.
scripts/gen_icons.py Regenerates src-tauri/icons/ (placeholder branding — swap the
colors/glyph in the script, or replace the icons directly, to
@@ -89,11 +95,24 @@ npm run tauri build
```
Verified in the environment this was built in: `cargo test -p renamr-core` (all
-passing), `cargo check -p renamr` (compiles clean against real Tauri + GTK/WebKit
-system libraries), and `npm run build` (TypeScript type-checks, Vite bundles). The
-actual GUI window has **not** been visually verified anywhere yet — that sandbox has no
-display. Run `npm run tauri dev` on your machine to see and click through it for the
-first time.
+passing, including the TMDB client tests — those run against a local mock HTTP server
+via `mockito`, no live network or API key needed to build/test), `cargo check -p
+renamr` (compiles clean against real Tauri + GTK/WebKit system libraries), and `npm run
+build` (TypeScript type-checks, Vite bundles). The actual GUI window has **not** been
+visually verified anywhere yet — that sandbox has no display. Run `npm run tauri dev`
+on your machine to see and click through it for the first time.
+
+### Setting up TMDB (for "Fetch Data")
+
+Everything else in the app works fully offline; "Fetch Data" is the one feature that
+needs a (free) [TMDB](https://www.themoviedb.org/) account:
+
+1. Create a TMDB account, then go to Settings → API in your account and generate an
+ **API Read Access Token** (not the older numeric "API Key" — Renamr uses the Bearer
+ token method TMDB documents as the default).
+2. In the app, click the key icon ("TMDB Settings") next to Fetch Data and paste the
+ token in. It's saved locally in the OS app-config directory and only ever sent to
+ `api.themoviedb.org`.
## Porting roadmap (not yet done)
@@ -111,13 +130,21 @@ ported from, for anyone picking this up:
3. **Matching/similarity engine** — `net.filebot.similarity.*`. Local filename-based
season/episode detection is done (`crates/renamr-core/src/matching`, ported from the
pattern-matching half of `SeasonEpisodeMatcher.java`, adapted to Rust's `regex` crate
- which lacks lookahead/lookbehind — wired to the "Match" button). Still not ported:
- `EpisodeMetrics.java` (849 lines, the fuzzy title-matching score cascade used once
- there's an actual metadata source to score candidates against) and `DateMatcher`.
+ which lacks lookahead/lookbehind — wired to the "Match" button). Now that TMDB is
+ wired up (see item 5 below), `EpisodeMetrics.java` (849 lines, the fuzzy title-matching
+ score cascade) is the next thing this actually blocks on: today "Fetch Data" just
+ takes TMDB's first (TMDB-ranked) search result, which is right most of the time but
+ has no way to catch a wrong pick among several similarly-named shows. `DateMatcher`
+ also still not ported.
4. **Media detection** — `net.filebot.media.MediaDetection` (1356 lines): ties file-name
parsing + matching together to auto-identify what a file is.
5. **Web datasource clients** — `net.filebot.web.*` (~40 files): TheMovieDB, TheTVDB,
- AniDB, OMDb, TVmaze, OpenSubtitles, etc. Use `reqwest` + `serde_json`/`serde`.
+ AniDB, OMDb, TVmaze, OpenSubtitles, etc. TMDB is done for TV (`crates/renamr-core/src/web/tmdb.rs`,
+ `reqwest` + `serde_json`, Bearer-token auth) — search-by-name plus per-season episode
+ lookup, wired to "Fetch Data". Picks the first (TMDB-ranked) search result rather
+ than disambiguating between multiple plausible shows — a real fix needs the
+ `EpisodeMetrics` fuzzy-matching port above. TMDB movie lookup and all other sources
+ (TheTVDB, AniDB, OMDb, TVmaze, OpenSubtitles) are not yet started.
6. **Rename engine + CLI** — `net.filebot.cli.CmdlineOperations` (1235 lines) and
`ArgumentBean` (415 lines, the full flag surface: `-rename`, `--db`, `--format`,
`--action`, `--conflict`, `-get-subtitles`, `-script`, etc.). The core same-directory
diff --git a/crates/renamr-core/Cargo.toml b/crates/renamr-core/Cargo.toml
index d52098e..506981c 100644
--- a/crates/renamr-core/Cargo.toml
+++ b/crates/renamr-core/Cargo.toml
@@ -20,4 +20,12 @@ serde_json = "1"
# are adapted to rely on \b word boundaries instead).
regex = "1"
+# TMDB metadata client ("Fetch Data"). rustls instead of the default openssl-backed TLS
+# so this doesn't need system OpenSSL dev headers on every platform we target.
+reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
+tokio = { version = "1", features = ["rt", "macros"] }
+
[dev-dependencies]
+# Local HTTP mock server so the TMDB client's request/response handling can be unit
+# tested without live network access or a real API key.
+mockito = "1"
diff --git a/crates/renamr-core/src/lib.rs b/crates/renamr-core/src/lib.rs
index b15b594..8e8d8c5 100644
--- a/crates/renamr-core/src/lib.rs
+++ b/crates/renamr-core/src/lib.rs
@@ -8,6 +8,7 @@
pub mod format;
pub mod matching;
pub mod rename;
+pub mod web;
use rhai::Dynamic;
use std::collections::HashMap;
diff --git a/crates/renamr-core/src/web/mod.rs b/crates/renamr-core/src/web/mod.rs
new file mode 100644
index 0000000..b3b8dd5
--- /dev/null
+++ b/crates/renamr-core/src/web/mod.rs
@@ -0,0 +1,5 @@
+//! Online metadata clients ("Fetch Data"). Currently just TMDB; the original FileBot
+//! supports ~40 sources (`net.filebot.web.*`) - TMDB was picked first since it's the
+//! most widely useful for both TV and movies and has a simple, well-documented API.
+
+pub mod tmdb;
diff --git a/crates/renamr-core/src/web/tmdb.rs b/crates/renamr-core/src/web/tmdb.rs
new file mode 100644
index 0000000..32980f7
--- /dev/null
+++ b/crates/renamr-core/src/web/tmdb.rs
@@ -0,0 +1,307 @@
+//! A small client for [TMDB](https://www.themoviedb.org/)'s v3 API: search for a TV
+//! show by name, then look up a specific episode's real title/air date.
+//!
+//! Ports the *lookup* half of what `net.filebot.web.TheMovieDBClient` did in the
+//! original; this is intentionally scoped to TV episodes only for v1 (movies are a
+//! straightforward follow-up using the parallel `/search/movie` + `/movie/{id}`
+//! endpoints, not yet added). Authenticates with a TMDB **API Read Access Token**
+//! (the Bearer-token method TMDB documents as the current default) rather than the
+//! older `api_key` query parameter.
+
+use serde::Deserialize;
+use thiserror::Error;
+
+const DEFAULT_BASE_URL: &str = "https://api.themoviedb.org/3";
+
+#[derive(Debug, Error)]
+pub enum TmdbError {
+ #[error("network error talking to TMDB: {0}")]
+ Network(String),
+ #[error("TMDB API error (status {status}): {message}")]
+ Api { status: u16, message: String },
+ #[error("no TMDB match found for \"{0}\"")]
+ NotFound(String),
+}
+
+#[derive(Debug, Clone, Deserialize)]
+struct SearchTvResponse {
+ #[serde(default)]
+ results: Vec
+ Fetch Data uses{" "} + + TheMovieDB + {" "} + to resolve local guesses into real episode titles and air dates. Get a free{" "} + API Read Access Token from your TMDB account under Settings → API, then paste it below. + The token is stored locally on this machine and never leaves it except to call TMDB directly. +
+ + setTmdbTokenInput(e.target.value)} + placeholder={hasTmdbToken ? "•••••••••• (already saved — enter a new token to replace it)" : "Paste your TMDB token here"} + /> +