[workspace] resolver = "3" members = [ "crates/ferrofin-util", "crates/ferrofin-keyframes", "crates/ferrofin-model", "crates/ferrofin-common", "crates/ferrofin-naming", "crates/ferrofin-networking", "crates/ferrofin-health", "crates/ferrofin-db", "crates/ferrofin-traits", "crates/ferrofin-mediaencoding", "crates/ferrofin-hls", "crates/ferrofin-drawing", "crates/ferrofin-providers", "crates/ferrofin-livetv", "crates/ferrofin-chromaprint", "crates/ferrofin-core", "crates/ferrofin-extensions", "crates/ferrofin-wasm", "crates/ferrofin-metrics", "crates/ferrofin-api", "apps/ferrofin-server", # crates are added here as each PortJob's SCAFFOLD stage lands them. # Dependency-ordered (bottom-up port waves): # Wave 0: ferrofin-util, ferrofin-keyframes # Wave 1: ferrofin-model # Wave 2: ferrofin-common, ferrofin-naming, ferrofin-networking, ferrofin-health # Wave 3: ferrofin-db Wave 4: ferrofin-traits # Wave 5: ferrofin-mediaencoding, ferrofin-hls, ferrofin-drawing, ferrofin-providers, ferrofin-livetv(stub) # Wave 6: ferrofin-core Wave 7: ferrofin-api Wave 8: apps/ferrofin-server ] exclude = ["vendor/icu_collator"] [workspace.package] version = "0.4.1" edition = "2024" license = "GPL-3.0-only" # matches the GPL-3.0-only package metadata on the Jellyfin crates being ported publish = false repository = "https://github.com/mangoleaf/ferrofin" [workspace.lints.rust] missing_docs = "warn" # `coverage_nightly` is set by cargo-llvm-cov (nightly) to gate the # `#[coverage(off)]` carve-outs on un-mockable I/O modules (e.g. the real # ffmpeg spawn in ferrofin-mediaencoding). Register it so stable builds don't warn. unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } [workspace.lints.clippy] pedantic = { level = "warn", priority = -1 } # product names in docs (SQLite, ffmpeg, DLNA, …) trip doc_markdown — allow it doc_markdown = "allow" # modules with children are self-named `foo.rs` beside `foo/`, never `foo/mod.rs` # (integration-test helpers at `tests//mod.rs` are not flagged) mod_module_files = "warn" # nursery allocation lints (gl-1017): a clone whose original is never used again, a # collect only iterated, counted or searched once, an eagerly built `or`/`unwrap_or` default redundant_clone = "warn" needless_collect = "warn" or_fun_call = "warn" [workspace.dependencies] if-addrs = { version = "0.15.0", features = ["link-local"] } # ── intra-workspace (path deps added as crates land) ─────────────── ferrofin-util = { path = "crates/ferrofin-util" } ferrofin-keyframes = { path = "crates/ferrofin-keyframes" } ferrofin-model = { path = "crates/ferrofin-model" } ferrofin-common = { path = "crates/ferrofin-common" } ferrofin-naming = { path = "crates/ferrofin-naming" } ferrofin-networking = { path = "crates/ferrofin-networking" } ferrofin-health = { path = "crates/ferrofin-health" } ferrofin-db = { path = "crates/ferrofin-db" } ferrofin-traits = { path = "crates/ferrofin-traits" } ferrofin-mediaencoding = { path = "crates/ferrofin-mediaencoding" } ferrofin-hls = { path = "crates/ferrofin-hls" } ferrofin-drawing = { path = "crates/ferrofin-drawing" } ferrofin-providers = { path = "crates/ferrofin-providers" } ferrofin-livetv = { path = "crates/ferrofin-livetv" } ferrofin-chromaprint = { path = "crates/ferrofin-chromaprint" } ferrofin-core = { path = "crates/ferrofin-core" } ferrofin-extensions = { path = "crates/ferrofin-extensions" } ferrofin-wasm = { path = "crates/ferrofin-wasm" } ferrofin-metrics = { path = "crates/ferrofin-metrics" } ferrofin-api = { path = "crates/ferrofin-api" } # ── http / async ─────────────────────────────────────────────────── axum = "0.8" tower = "0.5" tower-http = { version = "0.7.1", features = ["fs", "trace", "cors", "compression-gzip", "compression-br"] } httpdate = "1.0.3" # Decoders for the response-compression tests only (the server never decodes a # response it just encoded). Streaming readers validate the wire response. flate2 = "1.1.10" zip = { version = "9", default-features = false, features = ["deflate"] } # .cbz/.epub archives (ferrofin-providers books); backup archives (ferrofin-api, which adds `time`) tar = { version = "0.4.46", default-features = false } # .cbt comic covers (ferrofin-providers books); read-only, so no `xattr` sevenz-rust2 = { version = "0.24.1", default-features = false, features = ["bzip2", "deflate", "ppmd"] } # .cb7 comic covers (ferrofin-providers books) brotli = "9.0.0" tokio = { version = "1", features = ["full"] } socket2 = "0.6" http-body = "1.0" # The `Stream` trait `axum::body::Body::from_stream` takes — the progressive # (tail-following) response bodies the Live TV live-stream/recording routes serve. futures-core = "0.3.34" # WebSocket client for the /socket integration tests only. Its version is # independent of the WebSocket implementation used internally by axum. tokio-tungstenite = "0.30.0" futures-util = "0.3.34" notify = "8.2" # ── persistence: sqlx + SQLite (speed-first; see plan) ───────────── # SQLite has no network transport, so no TLS; `macros` stays only because it carries `migrate!`. sqlx = { version = "0.9.0", default-features = false, features = ["sqlite", "runtime-tokio", "macros", "migrate", "chrono", "uuid"] } # Direct handle on the SQLite C API, pinned to the exact version sqlx resolves so # cargo unifies them to ONE linked copy — a second copy would mean a second, # unconfigured SQLite. Used only to call sqlite3_config() before any connection. libsqlite3-sys = "0.37.0" # ── serde / errors ───────────────────────────────────────────────── serde = { version = "1", features = ["derive"] } serde_json = "1" serde_urlencoded = "0.7" # A map that keeps its insertion order on the wire: BaseItemDto.Trickplay lists # its media sources in GetMediaSources order (ferrofin-model). indexmap = { version = "2.14", features = ["serde"] } # Edits config.toml in place, keeping its comments and layout: the settings # page's save (ferrofin-server `settings::save`). toml_edit = { version = "0.25.17", default-features = false, features = ["parse", "display"] } # The `$.Field[0]` path in a body-binder rejection: serde's own error names the # type, not the member that failed, and ASP.NET's ValidationProblemDetails keys # its `errors` dictionary by path. ferrofin-api's JsonBody extractor only. serde_path_to_error = "0.1" form_urlencoded = "1.2.2" secrecy = { version = "0.10", features = ["serde"] } thiserror = "2.0.21" anyhow = "1" # ── allocator (server-only: glibc malloc convoyed under 64 threads) ─ # `background_threads` is load-bearing for memory, not speed: without it jemalloc # only purges dirty pages while it is *being called*, so an idle server never # returns anything and peak RSS becomes a permanent ratchet. See the comment on # the `#[global_allocator]` in `apps/ferrofin-server/src/main.rs`. tikv-jemallocator = { version = "0.7", features = ["unprefixed_malloc_on_supported_platforms", "background_threads"] } # ── observability / net ──────────────────────────────────────────── tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } tracing-appender = "0.2" reqwest = { version = "0.13.5", default-features = false, features = ["json", "rustls", "stream", "query"] } # The HTTPS listener (server only): rustls with the aws-lc-rs provider reqwest # already builds, and Jellyfin's PKCS#12 (`.pfx`) certificates, read with both # the modern (PBES2) and the legacy (PBES1, `pbes1`) encryptions. rustls = { version = "0.23.46", default-features = false, features = ["aws_lc_rs", "logging", "std", "tls12"] } tokio-rustls = { version = "0.26.6", default-features = false, features = ["aws_lc_rs", "logging", "tls12"] } p12-keystore = "0.4.1" # The LDAP-Auth extension's directory client: LDAP, LDAPS and StartTLS over the # same rustls + aws-lc-rs stack (no default features, so neither native-tls nor # ring comes in). ldap3 = { version = "0.12.1", default-features = false, features = ["tls-rustls-aws-lc-rs"] } # The system's trusted roots for an LDAP-Auth TLS configuration Ferrofin builds # itself (a client certificate, or the default checks): what ldap3 reads too. rustls-native-certs = "0.8.5" # A certificate's validity period, for LDAP-Auth's check of a server certificate # given as its own root CA (already built for ldap3 and p12-keystore). x509-parser = "0.18.1" # Self-signed certificates for the HTTPS tests (dev-dependency only). rcgen = { version = "0.14.10", default-features = false, features = ["aws_lc_rs", "pem"] } # ── openapi ──────────────────────────────────────────────────────── utoipa = "6.0.0" # ── metrics + traces: OpenTelemetry ──────────────────────────────── # metrics → prometheus exposition (ferrofin-metrics only); traces → OTLP gRPC # (ferrofin-server only). The 0.34 tracing-opentelemetry pairs with otel 0.33 # (the expected +1 offset); bump both together if otel ever moves. opentelemetry = { version = "0.33.0", default-features = false, features = ["metrics", "trace"] } opentelemetry_sdk = { version = "0.33.0", default-features = false, features = ["metrics", "trace"] } opentelemetry-prometheus = "0.33.0" # TLS for an https collector: aws-lc as everywhere else, roots from the system store. opentelemetry-otlp = { version = "0.33.0", default-features = false, features = ["grpc-tonic", "trace", "tls-aws-lc", "tls-roots"] } tracing-opentelemetry = "0.34.0" # encode-only; ferrofin-metrics ONLY — the 0.32 exporter exposes no render helper, # so `Registry` + `TextEncoder` come from a direct dep (dedupes with the exporter's ^0.14). prometheus = { version = "0.14", default-features = false } # ── WASM plugin host (Tier 1b — see docs/EXTENSIONS.md) ── # Component-model runtime for sandboxed third-party plugins. Always compiled # in (decision 2026-08-07); MSRV 1.94 ≤ our pinned toolchain. # No default features: a guest proposal is on whenever its crate feature is, # so the list is the sandbox's grant (`threads`, `gc` with exceptions, # `component-model-async` and `stack-switching` stay off, and so do the # profiling, cache, coredump and pooling extras). Only WASIp2 is linked. wasmtime = { version = "49.0.5", default-features = false, features = [ "addr2line", "anyhow", "backtrace", "component-model", "cranelift", "demangle", "parallel-compilation", "runtime", "std", ] } # Locked-down empty WASI ctx (no preopens/sockets) so std guests link. wasmtime-wasi = { version = "49.0.5", default-features = false, features = ["p2"] } wat = "1.259.0" # dev-only in practice: inline-WAT test fixtures (no committed .wasm) # ── domain helpers ───────────────────────────────────────────────── uuid = { version = "1.26.1", features = ["v4", "serde"] } url = "2.5.8" chrono = { version = "0.4", features = ["serde"] } unicode-normalization = "0.1.25" rand = "0.10.3" # ShuffleExtensions (ferrofin-util `shuffle`), password salts (ferrofin-common) # Legacy text encodings (subtitles, playlist files) — C# `UtfUnknown`. encoding_rs = "0.8.35" chardetng = "0.1.17" shlex = "2" # ffmpeg command strings → argv (ferrofin-mediaencoding `split_args`) async-trait = "0.1.92" libc = "0.2" windows-sys = { version = "0.61.2", features = [ "Win32_Foundation", "Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_Ndis", "Win32_Networking_WinSock", "Win32_System_Threading", ] } pbkdf2 = "0.13.0" # password hashing — matches Jellyfin's Rfc2898DeriveBytes.Pbkdf2 sha2 = "0.11.0" # PBKDF2-HMAC-SHA512 (default hash id) sha1 = "0.11.0" # PBKDF2-HMAC-SHA1 (legacy "PBKDF2" hash id) image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp", "gif", "bmp", "ico"] } kamadak-exif = "0.6.2" # photo EXIF (ferrofin-drawing PhotoProvider) webp = { version = "0.3", default-features = false } # LOSSY WebP encode via libwebp (BSD-3) — image-webp is lossless-only; Skia parity needs Google's encoder jpeg-encoder = "0.7" # JPEG encode with 4:2:0 chroma subsampling — the `image` crate's encoder is hardcoded 4:4:4; Skia's SkJpegEncoder defaults to 4:2:0 jpeg-decoder = { version = "0.3.2", default-features = false } # DCT-scaled JPEG decode (1/2, 1/4, 1/8) for blur hashes and thumbnails, like Skia's GetScaledDimensions; zune-jpeg (the `image` crate's decoder) cannot scale. In maintenance mode upstream. No rayon: without it a decode wider than 128 px runs one short-lived thread per colour component, inside the encode permits that bound concurrent decodes to the cores (a failed spawn falls back to the full decode) blurhash = "0.2" # ImageBlurHashes — functionally-valid hashes (not byte-identical to Jellyfin's Skia) config = { version = "0.15.26", default-features = false, features = ["toml"] } # config.toml is TOML only clap = { version = "4.6.7", features = ["derive"] } clap_mangen = "0.3.3" # renders the .deb man page from the clap definition (test-maintained) bitflags = "2.13.2" # CRC-32 (reflected zlib polynomial) for the HDHomeRun control protocol's packet # checksum — a port of `MediaBrowser.Common/Crc32.cs`, whose own tests pin # 0x414fa339 for "The quick brown fox...". Already in the lock file as a # transitive dependency of flate2/zip, so it adds no new compilation. The WASM # host also uses it to tell whether a plugin's state file still holds the bytes # its kept map came from. crc32fast = "1.5.2" regex = "1" percent-encoding = "2.3.2" # Base64 with .NET's rules (`Base64.IsValid`, `Convert.FromBase64String`) for # LDAP-Auth's profile images. Already in the lock file as a transitive dependency. base64 = "0.23.1" # CLDR root collation — the port of .NET `StringComparison.InvariantCulture` # used by the Live TV name sort. ICU4X 2.x is already in the tree (icu_normalizer # 2.2 via idna), so this shares icu_collections/icu_provider/zerovec. icu_collator = "2.3.1" icu_casemap = "2.3.0" icu_locale_core = "2.3.0" icu_collections = "2.3.0" icu_locale_fallback = "2.3.0" icu_normalizer = "2.3.0" icu_provider = "2.3.1" zerovec = "0.11.8" fancy-regex = "0.19.2" # for C# regexes that use lookaround quick-xml = { version = "0.42.0", features = ["serialize"] } # NFO/XBMC metadata parsing (ferrofin-providers) # Embedded lyric tags read from the file (ID3v2 SYLT, frame order), which # ffprobe's tag map cannot give — the scan's port of ATL's `Track.Lyrics`. lofty = "0.25.4" # ── dev / test ───────────────────────────────────────────────────── rstest = "0.27.0" # [Theory]/[InlineData] → #[case] transliteration tempfile = "3" # temp SQLite files for ferrofin-db tests # ── build profiles ───────────────────────────────────────────────── # Release-speed binary with full debug info, for samply/perf/heaptrack # symbolization (PLAN_OPTIMIZE_HOTSPOTS phase 0). Never shipped: release # stays lean; this exists so profiling is one flag, not a Cargo.toml edit. [profile.profiling] inherits = "release" debug = true strip = false # Exact ICU4X2.3.1 with a reviewed Jamo dispatch correction for search data. [patch.crates-io] icu_collator = { path = "vendor/icu_collator" }