Post-grab async background swap, upgrade_locked flag to prevent the upgrade loop from re-inflating a locally-transcoded file, and a transcode-library CLI backfill for the existing catalog. Bitrate model calibrated against Silicon Valley's real HEVC bitrate, scaled by resolution and AV1's encoding efficiency. HDR/2160p and anime excluded from this first pass. Jellyfin session polling throttles batch encoding back to 1 stream during active playback.
267 lines
7.3 KiB
Rust
267 lines
7.3 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MediaItemSummary {
|
|
pub id: i64,
|
|
pub kind: String,
|
|
pub title: String,
|
|
pub year: Option<i64>,
|
|
pub monitored: bool,
|
|
pub episode_count: i64,
|
|
pub missing_count: i64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct EpisodeSummary {
|
|
pub id: i64,
|
|
pub season_number: i64,
|
|
pub episode_number: i64,
|
|
pub title: Option<String>,
|
|
pub air_date: Option<String>,
|
|
pub monitored: bool,
|
|
pub has_file: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MediaItemDetail {
|
|
pub id: i64,
|
|
pub kind: String,
|
|
pub title: String,
|
|
pub year: Option<i64>,
|
|
pub monitored: bool,
|
|
pub root_folder: String,
|
|
pub episodes: Vec<EpisodeSummary>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ReleaseSummary {
|
|
pub id: i64,
|
|
pub media_title: String,
|
|
pub raw_title: String,
|
|
pub score: Option<f64>,
|
|
pub status: String,
|
|
pub grabbed_at: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ReviewQueueEntry {
|
|
pub id: i64,
|
|
pub raw_release_title: String,
|
|
pub candidate_media_title: Option<String>,
|
|
pub confidence: f64,
|
|
pub status: String,
|
|
pub created_at: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct StalledGrab {
|
|
pub release_id: i64,
|
|
pub media_item_id: i64,
|
|
pub media_title: String,
|
|
pub raw_title: String,
|
|
pub grabbed_at: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MaxedSearchTarget {
|
|
pub media_item_id: i64,
|
|
pub media_title: String,
|
|
pub search_count: i64,
|
|
pub last_searched_at: Option<String>,
|
|
}
|
|
|
|
/// The daily "why don't I have this yet" answer — grabs that have been
|
|
/// sitting without importing longer than expected, how deep the review
|
|
/// queue has backed up, and search targets that have been failing every
|
|
/// attempt for so long their backoff has hit its ceiling.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct StuckReport {
|
|
pub stalled_grabs: Vec<StalledGrab>,
|
|
pub review_queue_depth: i64,
|
|
pub maxed_out_search_targets: Vec<MaxedSearchTarget>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SearchResult {
|
|
pub external_id: String,
|
|
pub title: String,
|
|
pub year: Option<i64>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AddSeriesRequest {
|
|
pub tvdb_id: String,
|
|
pub title: String,
|
|
pub year: Option<i64>,
|
|
pub aliases: Vec<String>,
|
|
pub root_folder: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AddSeriesResponse {
|
|
pub media_item_id: i64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AddMovieRequest {
|
|
pub tmdb_id: String,
|
|
pub title: String,
|
|
pub year: Option<i64>,
|
|
pub root_folder: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AddMovieResponse {
|
|
pub media_item_id: i64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SearchNowResult {
|
|
pub targets: usize,
|
|
pub searched: usize,
|
|
pub grabbed: usize,
|
|
pub errors: usize,
|
|
pub source_exhausted: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CalendarEntry {
|
|
pub media_item_id: i64,
|
|
pub media_title: String,
|
|
pub season_number: i64,
|
|
pub episode_number: i64,
|
|
pub title: Option<String>,
|
|
pub air_date: String,
|
|
pub monitored: bool,
|
|
pub has_file: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct HealthDetail {
|
|
pub status: String,
|
|
pub last_grab_cycle: Option<CycleInfo>,
|
|
pub last_import_cycle: Option<CycleInfo>,
|
|
pub last_search_cycle: Option<CycleInfo>,
|
|
pub last_upgrade_cycle: Option<CycleInfo>,
|
|
pub last_transcode_cycle: Option<CycleInfo>,
|
|
pub search_halted: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CycleInfo {
|
|
pub at: chrono::DateTime<chrono::Utc>,
|
|
pub ok: bool,
|
|
pub detail: String,
|
|
}
|
|
|
|
/// One file flagged by a `media_file_probe` category — reused across every
|
|
/// flag list in `LibraryHealthReport` so a client can render them all with
|
|
/// the same widget.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FlaggedFile {
|
|
pub episode_file_id: i64,
|
|
pub media_title: String,
|
|
pub episode_label: Option<String>,
|
|
pub path: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct DuplicateGroup {
|
|
pub media_title: String,
|
|
pub episode_label: Option<String>,
|
|
pub paths: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CodecCount {
|
|
pub codec: String,
|
|
pub count: i64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct LibrarySummary {
|
|
pub total_files: i64,
|
|
pub total_size_bytes: i64,
|
|
pub probed_files: i64,
|
|
pub by_video_codec: Vec<CodecCount>,
|
|
pub sd_count: i64,
|
|
pub hd_720p_count: i64,
|
|
pub full_hd_1080p_count: i64,
|
|
pub uhd_4k_count: i64,
|
|
pub pct_with_subtitles: f64,
|
|
}
|
|
|
|
/// One search result scored (or gate-rejected) for manual review — the
|
|
/// same candidate a search cycle would evaluate automatically, surfaced
|
|
/// before a grab decision is made instead of after. `link`/`guid`/
|
|
/// `source_id` are carried through so a chosen candidate can be grabbed
|
|
/// directly without re-searching.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ReleaseCandidate {
|
|
pub raw_title: String,
|
|
pub link: String,
|
|
pub guid: String,
|
|
pub source_id: i64,
|
|
pub source_name: String,
|
|
pub seeders: Option<u32>,
|
|
pub leechers: Option<u32>,
|
|
pub size_bytes: Option<u64>,
|
|
/// `None` when gate-rejected — `rejected_reason` explains why.
|
|
pub score: Option<f32>,
|
|
pub rejected_reason: Option<String>,
|
|
pub resolution: Option<u32>,
|
|
pub is_repack: bool,
|
|
pub is_season_pack: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct GrabCandidateRequest {
|
|
pub raw_title: String,
|
|
pub link: String,
|
|
pub guid: String,
|
|
pub source_id: i64,
|
|
}
|
|
|
|
/// Every scoring axis `QualityProfile::Weights` has, mirrored 1:1 for the
|
|
/// wire — used both to show the currently-effective (defaults-plus-stored-
|
|
/// override) values and to submit a full replacement set when the user
|
|
/// edits one.
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
pub struct WeightsDto {
|
|
pub seeder: f32,
|
|
pub resolution_tier: f32,
|
|
pub source_tier: f32,
|
|
pub codec_tier: f32,
|
|
pub bit_depth: f32,
|
|
pub container: f32,
|
|
pub group_allowlist: f32,
|
|
pub repack: f32,
|
|
pub hdr: f32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct QualityProfileSummary {
|
|
pub id: i64,
|
|
pub name: String,
|
|
pub kind: String,
|
|
/// Always the fully-resolved values (built-in defaults with any stored
|
|
/// override already applied) — never a partial/sparse object, so the
|
|
/// TUI always has something concrete to display and re-submit.
|
|
pub weights: WeightsDto,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct UpdateQualityProfileWeightsRequest {
|
|
pub weights: WeightsDto,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct LibraryHealthReport {
|
|
pub corrupt_files: Vec<FlaggedFile>,
|
|
pub under_quality_files: Vec<FlaggedFile>,
|
|
pub no_subtitle_files: Vec<FlaggedFile>,
|
|
pub no_english_audio_files: Vec<FlaggedFile>,
|
|
pub non_english_default_audio_files: Vec<FlaggedFile>,
|
|
pub duplicate_groups: Vec<DuplicateGroup>,
|
|
pub summary: LibrarySummary,
|
|
}
|