+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "INSERT INTO subtitle_attempts (media_file_id, language)\n SELECT id, ? FROM media_files WHERE probed IS NOT NULL\n ON CONFLICT (media_file_id, language) DO NOTHING",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "35ace0417f613340f82522549d70d0dffeb680afb57511cb17fa15b3a571b974"
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT probed FROM media_files WHERE id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "probed",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "media_files",
|
||||||
|
"name": "probed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "af10ccd655f39235f525a531e38850b2e60ff713a038ee1e7c04c616b4613b52"
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT wanted_languages AS \"wanted_languages!: String\",\n providers_enabled AS \"providers_enabled!: String\",\n translation_engine AS \"translation_engine: String\"\n FROM subtitle_settings WHERE id = 1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "wanted_languages!: String",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "subtitle_settings",
|
||||||
|
"name": "wanted_languages"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "providers_enabled!: String",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "subtitle_settings",
|
||||||
|
"name": "providers_enabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "translation_engine: String",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text",
|
||||||
|
"origin": {
|
||||||
|
"Table": {
|
||||||
|
"table": "subtitle_settings",
|
||||||
|
"name": "translation_engine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 0
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "caa03a8daf9064de5c4724fcfecf70cc13f9a188468395bf5558aa3068c8a4e4"
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ mod reaper;
|
|||||||
pub mod reconcile;
|
pub mod reconcile;
|
||||||
mod rss;
|
mod rss;
|
||||||
mod series_refresh;
|
mod series_refresh;
|
||||||
|
mod subtitles;
|
||||||
mod tv_grab;
|
mod tv_grab;
|
||||||
mod web;
|
mod web;
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ use reaper::ReaperAction;
|
|||||||
use reconcile::{ReconcileLoop, Tick};
|
use reconcile::{ReconcileLoop, Tick};
|
||||||
use rss::RssAction;
|
use rss::RssAction;
|
||||||
use series_refresh::SeriesRefreshAction;
|
use series_refresh::SeriesRefreshAction;
|
||||||
|
use subtitles::SubtitleAction;
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tv_grab::TvGrabAction;
|
use tv_grab::TvGrabAction;
|
||||||
|
|
||||||
@@ -331,6 +333,9 @@ fn reconcile_loop(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// §15: subtitle gaps are reconciled from the same rows the API writes.
|
||||||
|
reconcile = reconcile.register(Tick::Reconcile, subtitle_action(config, notifier)?);
|
||||||
|
|
||||||
// §9.5 *needs a decision* and *broken* both go to the operator alone;
|
// §9.5 *needs a decision* and *broken* both go to the operator alone;
|
||||||
// without a topic configured there is nowhere to send them.
|
// without a topic configured there is nowhere to send them.
|
||||||
if let Some(operator_topic) = &config.ntfy_operator_topic {
|
if let Some(operator_topic) = &config.ntfy_operator_topic {
|
||||||
@@ -474,6 +479,30 @@ fn jellyfin_client(config: &Config) -> Result<arr_api::jellyfin::JellyfinClient,
|
|||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The §15 reconcile lane: closes subtitle gaps from the attempt rows.
|
||||||
|
///
|
||||||
|
/// Translation backends are passed empty: none is constructed at startup yet
|
||||||
|
/// — the compiled-in backends (#191–#193) still need their bootstrap wiring
|
||||||
|
/// (model names, base URLs) — so the translate step records "not compiled"
|
||||||
|
/// and backs off rather than failing obscurely.
|
||||||
|
fn subtitle_action(config: &Config, notifier: &Notifier) -> Result<SubtitleAction, Error> {
|
||||||
|
let action = SubtitleAction::new(
|
||||||
|
subtitle_providers(
|
||||||
|
config.opensubtitles_api_key.clone(),
|
||||||
|
config.opensubtitles_username.clone(),
|
||||||
|
config.opensubtitles_password.clone(),
|
||||||
|
),
|
||||||
|
Vec::new(),
|
||||||
|
arr_subs::Syncer::new().with_binary(config.alass_path.clone()),
|
||||||
|
arr_probe::Extractor::new().with_binary(config.ffmpeg_path.clone()),
|
||||||
|
jellyfin_client(config)?,
|
||||||
|
);
|
||||||
|
Ok(match &config.ntfy_operator_topic {
|
||||||
|
Some(topic) => action.with_notifier(notifier.clone(), topic.clone()),
|
||||||
|
None => action,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// The subtitle providers this deployment can reach (DESIGN.md §15).
|
/// The subtitle providers this deployment can reach (DESIGN.md §15).
|
||||||
///
|
///
|
||||||
/// Credentials are bootstrap config and never reach the database (§10), so
|
/// Credentials are bootstrap config and never reach the database (§10), so
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user