8112bfeb65
Build and Push Docker Image / build (push) Successful in 3m3s
adds stream_decisions.custom_language (ISO 639-2 code or null) so the user can correct a mislabeled audio track — e.g. a Spanish dub tagged "und" in the container — without going through Jellyfin. the override wins over stream.language everywhere it matters: the analyzer reads it for keep/remove decisions and track ordering, the ffmpeg command builder writes it as both the language metadata tag and the harmonized track title, and reanalyze preserves it across reruns and rescans. on the audio detail page, each pending audio row swaps its language cell for an inline <select> populated from LANG_NAMES. picking the raw file language clears the override; anything else sets it and triggers a server-side reanalyze so keep/remove + target_index update immediately. a small ✎ hint marks overridden tracks. rebuilt commands tag the output accordingly so Jellyfin reads the corrected language. PATCH /api/review/:id/stream/:streamId/language validates the code against LANG_NAMES (accepts ISO 639-1/2/2B aliases, rejects garbage) and runs reanalyze inside. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
145 lines
4.7 KiB
TypeScript
145 lines
4.7 KiB
TypeScript
export const SCHEMA = `
|
|
PRAGMA journal_mode = WAL;
|
|
PRAGMA foreign_keys = ON;
|
|
|
|
CREATE TABLE IF NOT EXISTS config (
|
|
key TEXT PRIMARY KEY NOT NULL,
|
|
value TEXT
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS media_items (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
jellyfin_id TEXT NOT NULL UNIQUE,
|
|
type TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
original_title TEXT,
|
|
series_name TEXT,
|
|
series_jellyfin_id TEXT,
|
|
season_number INTEGER,
|
|
episode_number INTEGER,
|
|
year INTEGER,
|
|
file_path TEXT NOT NULL,
|
|
file_size INTEGER,
|
|
container TEXT,
|
|
runtime_ticks INTEGER,
|
|
date_last_refreshed TEXT,
|
|
original_language TEXT,
|
|
orig_lang_source TEXT,
|
|
needs_review INTEGER NOT NULL DEFAULT 1,
|
|
imdb_id TEXT,
|
|
tmdb_id TEXT,
|
|
tvdb_id TEXT,
|
|
jellyfin_raw TEXT,
|
|
external_raw TEXT,
|
|
scan_status TEXT NOT NULL DEFAULT 'pending',
|
|
scan_error TEXT,
|
|
last_scanned_at TEXT,
|
|
ingest_source TEXT NOT NULL DEFAULT 'scan',
|
|
last_executed_at TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS media_streams (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
item_id INTEGER NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
|
|
stream_index INTEGER NOT NULL,
|
|
type TEXT NOT NULL,
|
|
codec TEXT,
|
|
profile TEXT,
|
|
language TEXT,
|
|
language_display TEXT,
|
|
title TEXT,
|
|
is_default INTEGER NOT NULL DEFAULT 0,
|
|
is_forced INTEGER NOT NULL DEFAULT 0,
|
|
is_hearing_impaired INTEGER NOT NULL DEFAULT 0,
|
|
channels INTEGER,
|
|
channel_layout TEXT,
|
|
bit_rate INTEGER,
|
|
sample_rate INTEGER,
|
|
bit_depth INTEGER,
|
|
UNIQUE(item_id, stream_index)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS review_plans (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
item_id INTEGER NOT NULL UNIQUE REFERENCES media_items(id) ON DELETE CASCADE,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
is_noop INTEGER NOT NULL DEFAULT 0,
|
|
auto_class TEXT,
|
|
sorted INTEGER NOT NULL DEFAULT 0,
|
|
apple_compat TEXT,
|
|
job_type TEXT NOT NULL DEFAULT 'copy',
|
|
subs_extracted INTEGER NOT NULL DEFAULT 0,
|
|
notes TEXT,
|
|
reviewed_at TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS stream_decisions (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
plan_id INTEGER NOT NULL REFERENCES review_plans(id) ON DELETE CASCADE,
|
|
stream_id INTEGER NOT NULL REFERENCES media_streams(id) ON DELETE CASCADE,
|
|
action TEXT NOT NULL,
|
|
target_index INTEGER,
|
|
custom_title TEXT,
|
|
custom_language TEXT,
|
|
transcode_codec TEXT,
|
|
UNIQUE(plan_id, stream_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS jobs (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
item_id INTEGER NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
|
|
command TEXT NOT NULL,
|
|
job_type TEXT NOT NULL DEFAULT 'copy',
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
output TEXT,
|
|
exit_code INTEGER,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
started_at TEXT,
|
|
completed_at TEXT
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_review_plans_status ON review_plans(status);
|
|
CREATE INDEX IF NOT EXISTS idx_review_plans_is_noop ON review_plans(is_noop);
|
|
CREATE INDEX IF NOT EXISTS idx_stream_decisions_plan_id ON stream_decisions(plan_id);
|
|
CREATE INDEX IF NOT EXISTS idx_media_items_series_jf ON media_items(series_jellyfin_id);
|
|
CREATE INDEX IF NOT EXISTS idx_media_items_series_name ON media_items(series_name);
|
|
CREATE INDEX IF NOT EXISTS idx_media_items_type ON media_items(type);
|
|
CREATE INDEX IF NOT EXISTS idx_media_streams_item_id ON media_streams(item_id);
|
|
CREATE INDEX IF NOT EXISTS idx_media_streams_type ON media_streams(type);
|
|
CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
|
|
CREATE INDEX IF NOT EXISTS idx_jobs_item_id ON jobs(item_id);
|
|
`;
|
|
|
|
export const DEFAULT_CONFIG: Record<string, string> = {
|
|
setup_complete: "0",
|
|
jellyfin_url: "",
|
|
jellyfin_api_key: "",
|
|
jellyfin_user_id: "",
|
|
radarr_url: "",
|
|
radarr_api_key: "",
|
|
radarr_enabled: "0",
|
|
sonarr_url: "",
|
|
sonarr_api_key: "",
|
|
sonarr_enabled: "0",
|
|
audio_languages: "[]",
|
|
auto_processing: "0",
|
|
auto_process_queue: "0",
|
|
|
|
scan_running: "0",
|
|
job_sleep_seconds: "0",
|
|
scan_schedule_enabled: "0",
|
|
scan_schedule_start: "01:00",
|
|
scan_schedule_end: "07:00",
|
|
process_schedule_enabled: "0",
|
|
process_schedule_start: "01:00",
|
|
process_schedule_end: "07:00",
|
|
|
|
mqtt_enabled: "0",
|
|
mqtt_url: "",
|
|
mqtt_topic: "jellyfin/events",
|
|
mqtt_username: "",
|
|
mqtt_password: "",
|
|
};
|