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