From d1adf4e5dcea99b9cfd6040a60e7e631f7c3b032 Mon Sep 17 00:00:00 2001 From: csf123321 Date: Fri, 15 May 2026 00:10:07 +0800 Subject: [PATCH] Rename project to JAMS (Just A simple intelligent Media Server), replace lms- prefix with jams- throughout --- ARCHITECTURE.md | 86 ++++++++++++++++++++++---------------------- ARCHITECTURE.zh.md | 90 +++++++++++++++++++++++----------------------- REQUIREMENTS.md | 21 +++++------ REQUIREMENTS.zh.md | 20 ++++++----- 4 files changed, 112 insertions(+), 105 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 589e9b9..234b531 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,4 +1,6 @@ -# LLM Media Server — Architecture Design +# JAMS — Architecture Design + +> **JAMS**: Just A simple intelligent Media Server > Patterns derived from Jellyfin's architecture, implemented in Rust. @@ -7,17 +9,17 @@ ## 1. Cargo Workspace Layout ``` -lms/ ← workspace root +jams/ ← workspace root ├── Cargo.toml ← workspace members ├── crates/ -│ ├── lms-server/ ← binary entry, Axum router, DI wiring -│ ├── lms-core/ ← shared traits, domain types, error types -│ ├── lms-library/ ← LibraryManager, FileWatcher, MediaResolver -│ ├── lms-metadata/ ← MetadataRouter + provider implementations -│ ├── lms-llm/ ← LlmRouter + LLM provider implementations -│ ├── lms-media/ ← ffmpeg wrapper, MediaProbe, thumbnail extractor -│ ├── lms-stream/ ← StreamBuilder, direct play, HLS handler -│ └── lms-db/ ← SQLite/sqlx, migrations, repositories +│ ├── jams-server/ ← binary entry, Axum router, DI wiring +│ ├── jams-core/ ← shared traits, domain types, error types +│ ├── jams-library/ ← LibraryManager, FileWatcher, MediaResolver +│ ├── jams-metadata/ ← MetadataRouter + provider implementations +│ ├── jams-llm/ ← LlmRouter + LLM provider implementations +│ ├── jams-media/ ← ffmpeg wrapper, MediaProbe, thumbnail extractor +│ ├── jams-stream/ ← StreamBuilder, direct play, HLS handler +│ └── jams-db/ ← SQLite/sqlx, migrations, repositories ├── docs/ └── docker/ ├── Dockerfile @@ -27,27 +29,27 @@ lms/ ← workspace root **Dependency direction** (one-way, no cycles): ``` -lms-server - ├── lms-library → lms-core, lms-db - ├── lms-metadata → lms-core, lms-llm - ├── lms-llm → lms-core - ├── lms-media → lms-core - ├── lms-stream → lms-core, lms-media, lms-db - └── lms-db → lms-core +jams-server + ├── jams-library → jams-core, jams-db + ├── jams-metadata → jams-core, jams-llm + ├── jams-llm → jams-core + ├── jams-media → jams-core + ├── jams-stream → jams-core, jams-media, jams-db + └── jams-db → jams-core ``` -`lms-core` has zero internal dependencies. All traits are defined here. +`jams-core` has zero internal dependencies. All traits are defined here. --- -## 2. Core Traits (lms-core) +## 2. Core Traits (jams-core) Mirroring Jellyfin's `IMetadataProvider` hierarchy using Rust trait objects. ### 2.1 Metadata Provider ```rust -// crates/lms-core/src/providers/metadata.rs +// crates/jams-core/src/providers/metadata.rs #[async_trait] pub trait MetadataProvider: Send + Sync { @@ -88,7 +90,7 @@ pub struct MetadataResult { ### 2.2 LLM Provider ```rust -// crates/lms-core/src/providers/llm.rs +// crates/jams-core/src/providers/llm.rs #[async_trait] pub trait LlmProvider: Send + Sync { @@ -109,7 +111,7 @@ pub struct LlmOptions { Inspired by Jellyfin's `BaseItem`, but using a Rust enum hierarchy instead of class inheritance. ```rust -// crates/lms-core/src/domain/item.rs +// crates/jams-core/src/domain/item.rs #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MediaItem { @@ -214,7 +216,7 @@ mpsc::channel ## 4. MediaResolver — Filename Parsing (ref: Emby.Naming) ```rust -// crates/lms-library/src/resolver.rs +// crates/jams-library/src/resolver.rs pub struct MediaResolver { episode_patterns: Vec, // compiled once at startup @@ -249,7 +251,7 @@ const EPISODE_PATTERNS: &[&str] = &[ ## 5. MetadataRouter — Priority Routing (ref: Jellyfin ProviderManager) ```rust -// crates/lms-metadata/src/router.rs +// crates/jams-metadata/src/router.rs pub struct MetadataRouter { providers: Vec>, // sorted by priority() @@ -289,7 +291,7 @@ impl MetadataRouter { ## 6. LlmRouter — Multi-Provider with Fallback ```rust -// crates/lms-llm/src/router.rs +// crates/jams-llm/src/router.rs pub struct LlmRouter { primary: Box, @@ -316,7 +318,7 @@ impl LlmRouter { } // Provider implementations: -// crates/lms-llm/src/providers/ +// crates/jams-llm/src/providers/ // ollama.rs → GET http://localhost:11434/api/generate // claude.rs → POST https://api.anthropic.com/v1/messages // openai.rs → POST https://api.openai.com/v1/chat/completions @@ -327,7 +329,7 @@ impl LlmRouter { ## 7. StreamBuilder — Decision Tree (ref: Jellyfin StreamBuilder.cs) ```rust -// crates/lms-stream/src/builder.rs +// crates/jams-stream/src/builder.rs pub enum StreamPlan { DirectPlay { path: PathBuf }, @@ -367,7 +369,7 @@ impl StreamBuilder { --- -## 8. Database Schema (lms-db) +## 8. Database Schema (jams-db) ```sql CREATE TABLE media_items ( @@ -513,11 +515,11 @@ base_url = "https://api.openai.com/v1" model = "gpt-4o" [streaming] -transcode_dir = "/tmp/lms-transcode" +transcode_dir = "/tmp/jams-transcode" max_concurrent_jobs = 2 [db] -path = "/data/lms.db" +path = "/data/jams.db" ``` --- @@ -534,10 +536,10 @@ services: volumes: - ./config:/config # config + SQLite DB - /your/media:/media:ro # media library (read-only) - - /tmp/lms-transcode:/transcode + - /tmp/jams-transcode:/transcode environment: - - LMS_DB_PATH=/config/lms.db - - LMS_CONFIG=/config/lms.toml + - JAMS_DB_PATH=/config/jams.db + - JAMS_CONFIG=/config/jams.toml restart: unless-stopped ollama: @@ -555,13 +557,13 @@ volumes: FROM rust:1.82-slim AS builder WORKDIR /app COPY . . -RUN cargo build --release --bin lms-server +RUN cargo build --release --bin jams-server FROM debian:bookworm-slim RUN apt-get update && apt-get install -y ffmpeg ca-certificates && rm -rf /var/lib/apt/lists/* -COPY --from=builder /app/target/release/lms-server /usr/local/bin/lms-server +COPY --from=builder /app/target/release/jams-server /usr/local/bin/jams-server EXPOSE 3000 -ENTRYPOINT ["lms-server"] +ENTRYPOINT ["jams-server"] ``` --- @@ -569,14 +571,14 @@ ENTRYPOINT ["lms-server"] ## 12. Key Crate Dependencies ```toml -# lms-server +# jams-server axum = "0.7" tokio = { version = "1", features = ["full"] } tower-http = { version = "0.5", features = ["cors", "trace"] } tracing = "0.1" tracing-subscriber = "0.3" -# lms-core +# jams-core serde = { version = "1", features = ["derive"] } serde_json = "1" uuid = { version = "1", features = ["v4"] } @@ -584,17 +586,17 @@ chrono = { version = "0.4", features = ["serde"] } async-trait = "0.1" thiserror = "1" -# lms-library +# jams-library notify = "6" regex = "1" -# lms-db +# jams-db sqlx = { version = "0.7", features = ["sqlite", "runtime-tokio", "migrate", "uuid", "chrono"] } -# lms-llm / lms-metadata +# jams-llm / jams-metadata reqwest = { version = "0.12", features = ["json"] } -# lms-media +# jams-media tokio-process = "1" ``` diff --git a/ARCHITECTURE.zh.md b/ARCHITECTURE.zh.md index 717e9cb..93b0936 100644 --- a/ARCHITECTURE.zh.md +++ b/ARCHITECTURE.zh.md @@ -1,4 +1,6 @@ -# LLM 媒体服务器 — 架构设计文档 +# JAMS — 架构设计文档 + +> **JAMS**: Just A simple intelligent Media Server > 参考 Jellyfin 核心架构模式,以 Rust 实现。 @@ -7,17 +9,17 @@ ## 1. Cargo Workspace 结构 ``` -lms/ ← workspace 根目录 +jams/ ← workspace 根目录 ├── Cargo.toml ← workspace 成员声明 ├── crates/ -│ ├── lms-server/ ← 二进制入口,Axum 路由,依赖注入组装 -│ ├── lms-core/ ← 共享 Trait、领域类型、错误类型 -│ ├── lms-library/ ← LibraryManager、FileWatcher、MediaResolver -│ ├── lms-metadata/ ← MetadataRouter 及各 Provider 实现 -│ ├── lms-llm/ ← LlmRouter 及各 LLM Provider 实现 -│ ├── lms-media/ ← ffmpeg 封装、MediaProbe、缩略图提取 -│ ├── lms-stream/ ← StreamBuilder、直接播放、HLS 处理 -│ └── lms-db/ ← SQLite/sqlx、迁移、Repository 实现 +│ ├── jams-server/ ← 二进制入口,Axum 路由,依赖注入组装 +│ ├── jams-core/ ← 共享 Trait、领域类型、错误类型 +│ ├── jams-library/ ← LibraryManager、FileWatcher、MediaResolver +│ ├── jams-metadata/ ← MetadataRouter 及各 Provider 实现 +│ ├── jams-llm/ ← LlmRouter 及各 LLM Provider 实现 +│ ├── jams-media/ ← ffmpeg 封装、MediaProbe、缩略图提取 +│ ├── jams-stream/ ← StreamBuilder、直接播放、HLS 处理 +│ └── jams-db/ ← SQLite/sqlx、迁移、Repository 实现 ├── docs/ └── docker/ ├── Dockerfile @@ -27,27 +29,27 @@ lms/ ← workspace 根目录 **依赖方向**(单向,无环): ``` -lms-server - ├── lms-library → lms-core, lms-db - ├── lms-metadata → lms-core, lms-llm - ├── lms-llm → lms-core - ├── lms-media → lms-core - ├── lms-stream → lms-core, lms-media, lms-db - └── lms-db → lms-core +jams-server + ├── jams-library → jams-core, jams-db + ├── jams-metadata → jams-core, jams-llm + ├── jams-llm → jams-core + ├── jams-media → jams-core + ├── jams-stream → jams-core, jams-media, jams-db + └── jams-db → jams-core ``` -`lms-core` 不依赖任何其他 crate,所有 Trait 定义在此。 +`jams-core` 不依赖任何其他 crate,所有 Trait 定义在此。 --- -## 2. 核心 Trait 定义(lms-core) +## 2. 核心 Trait 定义(jams-core) 参考 Jellyfin 的 `IMetadataProvider` 层次,用 Rust Trait 对象实现同等抽象。 ### 2.1 元数据 Provider ```rust -// crates/lms-core/src/providers/metadata.rs +// crates/jams-core/src/providers/metadata.rs #[async_trait] pub trait MetadataProvider: Send + Sync { @@ -89,7 +91,7 @@ pub struct MetadataResult { ### 2.2 LLM Provider ```rust -// crates/lms-core/src/providers/llm.rs +// crates/jams-core/src/providers/llm.rs #[async_trait] pub trait LlmProvider: Send + Sync { @@ -113,7 +115,7 @@ pub struct LlmOptions { 参考 Jellyfin `BaseItem`,但用 Rust enum 替代继承层次。 ```rust -// crates/lms-core/src/domain/item.rs +// crates/jams-core/src/domain/item.rs #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MediaItem { @@ -148,7 +150,7 @@ pub struct ClassificationResult { ### 2.4 流媒体能力描述(参考 Jellyfin DeviceProfile) ```rust -// crates/lms-core/src/stream/profile.rs +// crates/jams-core/src/stream/profile.rs pub struct ClientCapabilities { pub supported_containers: Vec, // ["mp4", "mkv", "webm"] @@ -224,7 +226,7 @@ pub struct ClientCapabilities { ## 4. MediaResolver — 文件名解析(参考 Emby.Naming) ```rust -// crates/lms-library/src/resolver.rs +// crates/jams-library/src/resolver.rs pub struct MediaResolver { episode_patterns: Vec, // 编译期初始化,启动时只编译一次 @@ -264,7 +266,7 @@ const EPISODE_PATTERNS: &[&str] = &[ ## 5. MetadataRouter — 优先级路由(参考 Jellyfin ProviderManager) ```rust -// crates/lms-metadata/src/router.rs +// crates/jams-metadata/src/router.rs pub struct MetadataRouter { providers: Vec>, // 按 priority() 排序 @@ -294,7 +296,7 @@ impl MetadataRouter { } } -// Provider 注册(lms-server 组装) +// Provider 注册(jams-server 组装) fn build_metadata_router(cfg: &Config) -> MetadataRouter { let mut providers: Vec> = vec![ Box::new(TmdbProvider::new(&cfg.metadata.tmdb_api_key)), // priority: 10 @@ -319,7 +321,7 @@ fn build_metadata_router(cfg: &Config) -> MetadataRouter { ## 6. LlmRouter — 多 Provider 路由(含降级) ```rust -// crates/lms-llm/src/router.rs +// crates/jams-llm/src/router.rs pub struct LlmRouter { primary: Box, @@ -348,7 +350,7 @@ impl LlmRouter { } // Provider 实现列表 -// crates/lms-llm/src/providers/ +// crates/jams-llm/src/providers/ // ollama.rs → GET http://localhost:11434/api/generate // claude.rs → POST https://api.anthropic.com/v1/messages // openai.rs → POST https://api.openai.com/v1/chat/completions @@ -359,7 +361,7 @@ impl LlmRouter { ## 7. StreamBuilder — 流媒体决策树(参考 Jellyfin StreamBuilder.cs) ```rust -// crates/lms-stream/src/builder.rs +// crates/jams-stream/src/builder.rs pub enum StreamPlan { DirectPlay { path: PathBuf }, @@ -441,7 +443,7 @@ async fn stream_handler( --- -## 8. 数据库 Schema(lms-db) +## 8. 数据库 Schema(jams-db) 参考 Jellyfin 从单体 `BaseItemRepository` 演进为服务化 Repository 的方向,我们从一开始就按职责拆分。 @@ -614,11 +616,11 @@ base_url = "https://api.openai.com/v1" # 支持自定义端点 model = "gpt-4o" [streaming] -transcode_dir = "/tmp/lms-transcode" +transcode_dir = "/tmp/jams-transcode" max_concurrent_jobs = 2 [db] -path = "/data/lms.db" +path = "/data/jams.db" ``` --- @@ -637,10 +639,10 @@ services: volumes: - ./config:/config # TOML 配置 + SQLite DB - /your/media:/media:ro # 媒体目录(只读挂载) - - /tmp/lms-transcode:/transcode # 转码临时目录(推荐高速盘) + - /tmp/jams-transcode:/transcode # 转码临时目录(推荐高速盘) environment: - - LMS_DB_PATH=/config/lms.db - - LMS_CONFIG=/config/lms.toml + - JAMS_DB_PATH=/config/jams.db + - JAMS_CONFIG=/config/jams.toml restart: unless-stopped ollama: @@ -667,13 +669,13 @@ volumes: FROM rust:1.82-slim AS builder WORKDIR /app COPY . . -RUN cargo build --release --bin lms-server +RUN cargo build --release --bin jams-server FROM debian:bookworm-slim RUN apt-get update && apt-get install -y ffmpeg ca-certificates && rm -rf /var/lib/apt/lists/* -COPY --from=builder /app/target/release/lms-server /usr/local/bin/lms-server +COPY --from=builder /app/target/release/jams-server /usr/local/bin/jams-server EXPOSE 3000 -ENTRYPOINT ["lms-server"] +ENTRYPOINT ["jams-server"] ``` --- @@ -681,14 +683,14 @@ ENTRYPOINT ["lms-server"] ## 12. 各 crate 关键依赖 ```toml -# lms-server +# jams-server axum = "0.7" tokio = { version = "1", features = ["full"] } tower-http = { version = "0.5", features = ["cors", "trace"] } tracing = "0.1" tracing-subscriber = "0.3" -# lms-core +# jams-core serde = { version = "1", features = ["derive"] } serde_json = "1" uuid = { version = "1", features = ["v4"] } @@ -696,17 +698,17 @@ chrono = { version = "0.4", features = ["serde"] } async-trait = "0.1" thiserror = "1" -# lms-library +# jams-library notify = "6" # 跨平台文件系统监听(inotify on Linux) regex = "1" -# lms-db +# jams-db sqlx = { version = "0.7", features = ["sqlite", "runtime-tokio", "migrate", "uuid", "chrono"] } -# lms-llm / lms-metadata +# jams-llm / jams-metadata reqwest = { version = "0.12", features = ["json"] } -# lms-media +# jams-media tokio-process = "1" # 异步子进程(ffmpeg/ffprobe) ``` diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 2884e57..55e8152 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -19,7 +19,6 @@ A self-hosted video media server that uses LLMs to automatically index, tag, cla - [ ] Identify content type (movie, TV episode, home video) from filename + video analysis - [ ] Match movies/TV shows against known titles (local heuristics + LLM reasoning) - [ ] Extract season/episode numbers for TV shows -- [ ] Tag home videos with inferred subjects, locations, events (via frame analysis + LLM) - [ ] Classify content genre, mood, rating (family-safe, etc.) - [ ] Confidence scoring for all LLM-generated tags; flag low-confidence for manual review @@ -142,33 +141,35 @@ LLM metadata is used **only when** the external source returns no match or parti | Concern | Choice | Rationale | |----------------------|---------------------|--------------------------------------------------| -| Backend language | Go | Single binary, excellent HTTP/concurrency, ffmpeg CGO optional | -| Database | SQLite (sqlx) | Zero-config, embeddable, enough for single-user | +| Backend language | **Rust** | Memory safety, zero-cost abstractions, ideal for media processing throughput | +| Web framework | Axum | Async, tower-compatible, ergonomic REST routing | +| Async runtime | Tokio | Industry-standard async runtime for Rust | +| Database | SQLite (sqlx) | Zero-config, embeddable, async support via sqlx | | Media processing | ffmpeg (subprocess) | Industry standard, broad format support | | LLM (local) | Ollama REST API | Simple HTTP interface, model management built-in | -| LLM (cloud) | Anthropic SDK + OpenAI SDK | Dual-provider via abstraction layer | +| LLM (cloud) | Anthropic + OpenAI HTTP API | Via reqwest, provider abstraction layer | | Containerization | Docker + Compose | Multi-service: server + ollama + optional GPU | -| Config format | TOML | Human-friendly, Go ecosystem support (viper) | -| Web UI | HTMX + Tailwind | No JS framework needed, Go template rendering | +| Config format | TOML | Human-friendly, serde-compatible (toml crate) | +| Frontend | TBD | Pure backend for now; API-first design | -> **Rust alternative**: Rust is viable if performance is critical (transcoding pipeline), but Go is recommended for faster initial development and simpler deployment. +> **Frontend deferred**: The server exposes a clean REST API. Frontend tech will be decided after core backend is stable. --- ## 5. MVP Scope (Phase 1) -Goal: Working library scanner + LLM tagging + basic web UI + streaming +Goal: Working library scanner + LLM tagging + REST API + streaming (pure backend) - [ ] Directory watcher + file ingestion - [ ] Movie/TV classification (filename heuristics + LLM disambiguation) - [ ] Metadata fetch from TMDB API (primary); LLM fills unmatched/missing fields only - [ ] Thumbnail extraction -- [ ] SQLite metadata store +- [ ] SQLite metadata store (sqlx async) - [ ] REST API: list library, get item, trigger re-scan -- [ ] Basic web UI: grid view + video player - [ ] Direct-play HTTP streaming - [ ] Ollama integration (local LLM) - [ ] Docker Compose setup +- [ ] Frontend: TBD (API-first, no UI in MVP) --- diff --git a/REQUIREMENTS.zh.md b/REQUIREMENTS.zh.md index 17bc310..12dd11b 100644 --- a/REQUIREMENTS.zh.md +++ b/REQUIREMENTS.zh.md @@ -163,33 +163,35 @@ LLM 仅在以下情况使用: | 关注点 | 选型 | 理由 | |---|---|---| -| 后端语言 | Go | 单二进制、HTTP/并发优秀、部署简单 | -| 数据库 | SQLite (sqlx) | 零配置、可嵌入、单用户足够 | +| 后端语言 | **Rust** | 内存安全、零成本抽象、媒体处理吞吐量优秀 | +| Web 框架 | Axum | 异步、tower 兼容、REST 路由简洁 | +| 异步运行时 | Tokio | Rust 生态事实标准异步运行时 | +| 数据库 | SQLite (sqlx) | 零配置、可嵌入、sqlx 支持异步 | | 媒体处理 | ffmpeg(子进程) | 行业标准,格式支持广泛 | | LLM 本地 | Ollama REST API | HTTP 接口简单,内置模型管理 | -| LLM 云端 | Anthropic SDK + OpenAI SDK | 抽象层双提供商支持 | +| LLM 云端 | Anthropic + OpenAI HTTP API | 通过 reqwest,抽象层统一管理 | | 容器化 | Docker + Compose | 多服务:server + ollama + 可选 GPU | -| 配置格式 | TOML | 人类友好,Go 生态支持(viper) | -| Web UI | HTMX + Tailwind | 无 JS 框架,Go 模板渲染 | +| 配置格式 | TOML | 人类友好,serde 兼容(toml crate) | +| 前端 | 待定 | 纯后端阶段,API-First 设计 | -> **Rust 备选**:如果性能成为瓶颈(转码流水线),Rust 是可行选项,但 Go 开发速度更快,部署更简单,推荐作为首选。 +> **前端暂缓**:服务端提供完整 REST API,前端技术栈待后端核心稳定后再决策。 --- ## 5. MVP 范围(第一阶段) -目标:可用的库扫描 + LLM 辅助标签 + 基础 Web UI + 流媒体播放 +目标:可用的库扫描 + LLM 辅助标签 + REST API + 流媒体播放(纯后端) - [ ] 目录监听 + 文件摄入 - [ ] 电影/电视剧分类(文件名启发式 + LLM 辅助) - [ ] TMDB 元数据获取(主要来源);LLM 填补缺失字段 - [ ] 缩略图提取(TMDB 海报优先,ffmpeg 关键帧兜底) -- [ ] SQLite 元数据存储 +- [ ] SQLite 元数据存储(sqlx 异步) - [ ] REST API:库列表、获取条目、触发重扫 -- [ ] 基础 Web UI:网格视图 + 视频播放器 - [ ] HTTP 直接播放流媒体 - [ ] Ollama 集成(本地 LLM) - [ ] Docker Compose 配置 +- [ ] 前端:待定(API-First,MVP 阶段无 UI) ---