feat(arr): wire translation backends into daemon
Forward each translate-* feature from arr-daemon to arr-subs, add the missing OpenAI model bootstrap key, and construct the compiled and credentialed backends at startup so the translate endpoint stops answering 503 unconditionally.
This commit is contained in:
@@ -10,6 +10,16 @@ publish = false
|
|||||||
name = "arr"
|
name = "arr"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
# Forwards straight to `arr-subs`' own features (DESIGN.md §15): which
|
||||||
|
# translators a build ships is a compile-time choice, off by default, same as
|
||||||
|
# the crate that implements them.
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
translate-openai = ["arr-subs/translate-openai"]
|
||||||
|
translate-deepl = ["arr-subs/translate-deepl"]
|
||||||
|
translate-google = ["arr-subs/translate-google"]
|
||||||
|
translate-command = ["arr-subs/translate-command"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arr-api = { workspace = true }
|
arr-api = { workspace = true }
|
||||||
arr-compat = { workspace = true }
|
arr-compat = { workspace = true }
|
||||||
|
|||||||
+103
-30
@@ -40,6 +40,7 @@ pub const ENV_PODNAPISI_USERNAME: &str = "ARR_PODNAPISI_USERNAME";
|
|||||||
pub const ENV_PODNAPISI_PASSWORD: &str = "ARR_PODNAPISI_PASSWORD";
|
pub const ENV_PODNAPISI_PASSWORD: &str = "ARR_PODNAPISI_PASSWORD";
|
||||||
pub const ENV_TRANSLATE_OPENAI_API_KEY: &str = "ARR_TRANSLATE_OPENAI_API_KEY";
|
pub const ENV_TRANSLATE_OPENAI_API_KEY: &str = "ARR_TRANSLATE_OPENAI_API_KEY";
|
||||||
pub const ENV_TRANSLATE_OPENAI_BASE_URL: &str = "ARR_TRANSLATE_OPENAI_BASE_URL";
|
pub const ENV_TRANSLATE_OPENAI_BASE_URL: &str = "ARR_TRANSLATE_OPENAI_BASE_URL";
|
||||||
|
pub const ENV_TRANSLATE_OPENAI_MODEL: &str = "ARR_TRANSLATE_OPENAI_MODEL";
|
||||||
pub const ENV_TRANSLATE_DEEPL_API_KEY: &str = "ARR_TRANSLATE_DEEPL_API_KEY";
|
pub const ENV_TRANSLATE_DEEPL_API_KEY: &str = "ARR_TRANSLATE_DEEPL_API_KEY";
|
||||||
pub const ENV_TRANSLATE_DEEPL_BASE_URL: &str = "ARR_TRANSLATE_DEEPL_BASE_URL";
|
pub const ENV_TRANSLATE_DEEPL_BASE_URL: &str = "ARR_TRANSLATE_DEEPL_BASE_URL";
|
||||||
pub const ENV_TRANSLATE_GOOGLE_API_KEY: &str = "ARR_TRANSLATE_GOOGLE_API_KEY";
|
pub const ENV_TRANSLATE_GOOGLE_API_KEY: &str = "ARR_TRANSLATE_GOOGLE_API_KEY";
|
||||||
@@ -64,6 +65,10 @@ pub const DEFAULT_JELLYFIN_URL: &str = "http://localhost:8096";
|
|||||||
pub const DEFAULT_NTFY_URL: &str = "http://localhost";
|
pub const DEFAULT_NTFY_URL: &str = "http://localhost";
|
||||||
pub const DEFAULT_ALASS_PATH: &str = "alass";
|
pub const DEFAULT_ALASS_PATH: &str = "alass";
|
||||||
pub const DEFAULT_FFMPEG_PATH: &str = "ffmpeg";
|
pub const DEFAULT_FFMPEG_PATH: &str = "ffmpeg";
|
||||||
|
/// #191 made the model an `OpenAi::new` argument rather than an
|
||||||
|
/// `OpenAiConfig` field; no other bootstrap key covers it, so this is that
|
||||||
|
/// key's default.
|
||||||
|
pub const DEFAULT_TRANSLATE_OPENAI_MODEL: &str = "gpt-4o-mini";
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ConfigError {
|
pub enum ConfigError {
|
||||||
@@ -114,6 +119,8 @@ struct ConfigFile {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
translate_openai_base_url: Option<String>,
|
translate_openai_base_url: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
translate_openai_model: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
translate_deepl_base_url: Option<String>,
|
translate_deepl_base_url: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
translate_google_base_url: Option<String>,
|
translate_google_base_url: Option<String>,
|
||||||
@@ -169,6 +176,7 @@ pub struct EnvOverrides {
|
|||||||
pub podnapisi_password: Option<String>,
|
pub podnapisi_password: Option<String>,
|
||||||
pub translate_openai_api_key: Option<String>,
|
pub translate_openai_api_key: Option<String>,
|
||||||
pub translate_openai_base_url: Option<String>,
|
pub translate_openai_base_url: Option<String>,
|
||||||
|
pub translate_openai_model: Option<String>,
|
||||||
pub translate_deepl_api_key: Option<String>,
|
pub translate_deepl_api_key: Option<String>,
|
||||||
pub translate_deepl_base_url: Option<String>,
|
pub translate_deepl_base_url: Option<String>,
|
||||||
pub translate_google_api_key: Option<String>,
|
pub translate_google_api_key: Option<String>,
|
||||||
@@ -204,6 +212,7 @@ impl EnvOverrides {
|
|||||||
podnapisi_password: std::env::var(ENV_PODNAPISI_PASSWORD).ok(),
|
podnapisi_password: std::env::var(ENV_PODNAPISI_PASSWORD).ok(),
|
||||||
translate_openai_api_key: std::env::var(ENV_TRANSLATE_OPENAI_API_KEY).ok(),
|
translate_openai_api_key: std::env::var(ENV_TRANSLATE_OPENAI_API_KEY).ok(),
|
||||||
translate_openai_base_url: std::env::var(ENV_TRANSLATE_OPENAI_BASE_URL).ok(),
|
translate_openai_base_url: std::env::var(ENV_TRANSLATE_OPENAI_BASE_URL).ok(),
|
||||||
|
translate_openai_model: std::env::var(ENV_TRANSLATE_OPENAI_MODEL).ok(),
|
||||||
translate_deepl_api_key: std::env::var(ENV_TRANSLATE_DEEPL_API_KEY).ok(),
|
translate_deepl_api_key: std::env::var(ENV_TRANSLATE_DEEPL_API_KEY).ok(),
|
||||||
translate_deepl_base_url: std::env::var(ENV_TRANSLATE_DEEPL_BASE_URL).ok(),
|
translate_deepl_base_url: std::env::var(ENV_TRANSLATE_DEEPL_BASE_URL).ok(),
|
||||||
translate_google_api_key: std::env::var(ENV_TRANSLATE_GOOGLE_API_KEY).ok(),
|
translate_google_api_key: std::env::var(ENV_TRANSLATE_GOOGLE_API_KEY).ok(),
|
||||||
@@ -252,6 +261,9 @@ pub struct Config {
|
|||||||
/// §15. No default: an OpenAI-compatible endpoint has no universal
|
/// §15. No default: an OpenAI-compatible endpoint has no universal
|
||||||
/// address the way `DeepL` or Google Translate do.
|
/// address the way `DeepL` or Google Translate do.
|
||||||
pub translate_openai_base_url: Option<String>,
|
pub translate_openai_base_url: Option<String>,
|
||||||
|
/// §15. Unlike the base URL, a model does have a usable default —
|
||||||
|
/// see [`DEFAULT_TRANSLATE_OPENAI_MODEL`].
|
||||||
|
pub translate_openai_model: String,
|
||||||
pub translate_deepl_api_key: Option<String>,
|
pub translate_deepl_api_key: Option<String>,
|
||||||
/// `None` means the backend's own built-in default when it lands (#192).
|
/// `None` means the backend's own built-in default when it lands (#192).
|
||||||
pub translate_deepl_base_url: Option<String>,
|
pub translate_deepl_base_url: Option<String>,
|
||||||
@@ -268,6 +280,75 @@ pub struct Config {
|
|||||||
pub ffmpeg_path: PathBuf,
|
pub ffmpeg_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// §15/§10 subtitle bootstrap: provider and translator credentials, base
|
||||||
|
/// URLs, the remote-command template, and binary paths. Resolved separately
|
||||||
|
/// from [`Config::resolve`] for the same reason `arr-daemon::api_state` is
|
||||||
|
/// split out of `run` — one field per provider or backend, and `resolve` is
|
||||||
|
/// already at the too-many-lines limit.
|
||||||
|
struct SubtitleBootstrap {
|
||||||
|
opensubtitles_api_key: Option<String>,
|
||||||
|
opensubtitles_username: Option<String>,
|
||||||
|
opensubtitles_password: Option<String>,
|
||||||
|
podnapisi_username: Option<String>,
|
||||||
|
podnapisi_password: Option<String>,
|
||||||
|
translate_openai_api_key: Option<String>,
|
||||||
|
translate_openai_base_url: Option<String>,
|
||||||
|
translate_openai_model: String,
|
||||||
|
translate_deepl_api_key: Option<String>,
|
||||||
|
translate_deepl_base_url: Option<String>,
|
||||||
|
translate_google_api_key: Option<String>,
|
||||||
|
translate_google_base_url: Option<String>,
|
||||||
|
translate_command_template: Option<String>,
|
||||||
|
alass_path: PathBuf,
|
||||||
|
ffmpeg_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_subtitle_bootstrap(env: &EnvOverrides, file: &ConfigFile) -> SubtitleBootstrap {
|
||||||
|
SubtitleBootstrap {
|
||||||
|
opensubtitles_api_key: env.opensubtitles_api_key.clone(),
|
||||||
|
opensubtitles_username: env.opensubtitles_username.clone(),
|
||||||
|
opensubtitles_password: env.opensubtitles_password.clone(),
|
||||||
|
podnapisi_username: env.podnapisi_username.clone(),
|
||||||
|
podnapisi_password: env.podnapisi_password.clone(),
|
||||||
|
translate_openai_api_key: env.translate_openai_api_key.clone(),
|
||||||
|
translate_openai_base_url: env
|
||||||
|
.translate_openai_base_url
|
||||||
|
.clone()
|
||||||
|
.or_else(|| file.translate_openai_base_url.clone()),
|
||||||
|
translate_openai_model: env
|
||||||
|
.translate_openai_model
|
||||||
|
.clone()
|
||||||
|
.or_else(|| file.translate_openai_model.clone())
|
||||||
|
.unwrap_or_else(|| DEFAULT_TRANSLATE_OPENAI_MODEL.to_string()),
|
||||||
|
translate_deepl_api_key: env.translate_deepl_api_key.clone(),
|
||||||
|
translate_deepl_base_url: env
|
||||||
|
.translate_deepl_base_url
|
||||||
|
.clone()
|
||||||
|
.or_else(|| file.translate_deepl_base_url.clone()),
|
||||||
|
translate_google_api_key: env.translate_google_api_key.clone(),
|
||||||
|
translate_google_base_url: env
|
||||||
|
.translate_google_base_url
|
||||||
|
.clone()
|
||||||
|
.or_else(|| file.translate_google_base_url.clone()),
|
||||||
|
translate_command_template: env
|
||||||
|
.translate_command_template
|
||||||
|
.clone()
|
||||||
|
.or_else(|| file.translate_command_template.clone()),
|
||||||
|
alass_path: env
|
||||||
|
.alass_path
|
||||||
|
.clone()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.or_else(|| file.alass_path.clone())
|
||||||
|
.unwrap_or_else(|| PathBuf::from(DEFAULT_ALASS_PATH)),
|
||||||
|
ffmpeg_path: env
|
||||||
|
.ffmpeg_path
|
||||||
|
.clone()
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.or_else(|| file.ffmpeg_path.clone())
|
||||||
|
.unwrap_or_else(|| PathBuf::from(DEFAULT_FFMPEG_PATH)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
/// Load from the process environment, and an optional TOML file named by
|
/// Load from the process environment, and an optional TOML file named by
|
||||||
/// `$ARR_CONFIG_FILE`. No file is not an error — every non-secret field
|
/// `$ARR_CONFIG_FILE`. No file is not an error — every non-secret field
|
||||||
@@ -281,6 +362,7 @@ impl Config {
|
|||||||
Some(path) => ConfigFile::load(Path::new(path))?,
|
Some(path) => ConfigFile::load(Path::new(path))?,
|
||||||
None => ConfigFile::default(),
|
None => ConfigFile::default(),
|
||||||
};
|
};
|
||||||
|
let subtitles = resolve_subtitle_bootstrap(&env, &file);
|
||||||
|
|
||||||
let bind_addr = match &env.bind_addr {
|
let bind_addr = match &env.bind_addr {
|
||||||
Some(raw) => parse_bind_addr(raw, ENV_BIND_ADDR)?,
|
Some(raw) => parse_bind_addr(raw, ENV_BIND_ADDR)?,
|
||||||
@@ -347,36 +429,21 @@ impl Config {
|
|||||||
.or(file.ntfy_url)
|
.or(file.ntfy_url)
|
||||||
.unwrap_or_else(|| DEFAULT_NTFY_URL.to_string()),
|
.unwrap_or_else(|| DEFAULT_NTFY_URL.to_string()),
|
||||||
ntfy_operator_topic: env.ntfy_operator_topic.or(file.ntfy_operator_topic),
|
ntfy_operator_topic: env.ntfy_operator_topic.or(file.ntfy_operator_topic),
|
||||||
opensubtitles_api_key: env.opensubtitles_api_key,
|
opensubtitles_api_key: subtitles.opensubtitles_api_key,
|
||||||
opensubtitles_username: env.opensubtitles_username,
|
opensubtitles_username: subtitles.opensubtitles_username,
|
||||||
opensubtitles_password: env.opensubtitles_password,
|
opensubtitles_password: subtitles.opensubtitles_password,
|
||||||
podnapisi_username: env.podnapisi_username,
|
podnapisi_username: subtitles.podnapisi_username,
|
||||||
podnapisi_password: env.podnapisi_password,
|
podnapisi_password: subtitles.podnapisi_password,
|
||||||
translate_openai_api_key: env.translate_openai_api_key,
|
translate_openai_api_key: subtitles.translate_openai_api_key,
|
||||||
translate_openai_base_url: env
|
translate_openai_base_url: subtitles.translate_openai_base_url,
|
||||||
.translate_openai_base_url
|
translate_openai_model: subtitles.translate_openai_model,
|
||||||
.or(file.translate_openai_base_url),
|
translate_deepl_api_key: subtitles.translate_deepl_api_key,
|
||||||
translate_deepl_api_key: env.translate_deepl_api_key,
|
translate_deepl_base_url: subtitles.translate_deepl_base_url,
|
||||||
translate_deepl_base_url: env
|
translate_google_api_key: subtitles.translate_google_api_key,
|
||||||
.translate_deepl_base_url
|
translate_google_base_url: subtitles.translate_google_base_url,
|
||||||
.or(file.translate_deepl_base_url),
|
translate_command_template: subtitles.translate_command_template,
|
||||||
translate_google_api_key: env.translate_google_api_key,
|
alass_path: subtitles.alass_path,
|
||||||
translate_google_base_url: env
|
ffmpeg_path: subtitles.ffmpeg_path,
|
||||||
.translate_google_base_url
|
|
||||||
.or(file.translate_google_base_url),
|
|
||||||
translate_command_template: env
|
|
||||||
.translate_command_template
|
|
||||||
.or(file.translate_command_template),
|
|
||||||
alass_path: env
|
|
||||||
.alass_path
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.or(file.alass_path)
|
|
||||||
.unwrap_or_else(|| PathBuf::from(DEFAULT_ALASS_PATH)),
|
|
||||||
ffmpeg_path: env
|
|
||||||
.ffmpeg_path
|
|
||||||
.map(PathBuf::from)
|
|
||||||
.or(file.ffmpeg_path)
|
|
||||||
.unwrap_or_else(|| PathBuf::from(DEFAULT_FFMPEG_PATH)),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,6 +492,10 @@ mod tests {
|
|||||||
assert_eq!(config.opensubtitles_api_key, None);
|
assert_eq!(config.opensubtitles_api_key, None);
|
||||||
assert_eq!(config.podnapisi_username, None);
|
assert_eq!(config.podnapisi_username, None);
|
||||||
assert_eq!(config.translate_command_template, None);
|
assert_eq!(config.translate_command_template, None);
|
||||||
|
assert_eq!(
|
||||||
|
config.translate_openai_model,
|
||||||
|
DEFAULT_TRANSLATE_OPENAI_MODEL
|
||||||
|
);
|
||||||
assert_eq!(config.alass_path, PathBuf::from(DEFAULT_ALASS_PATH));
|
assert_eq!(config.alass_path, PathBuf::from(DEFAULT_ALASS_PATH));
|
||||||
assert_eq!(config.ffmpeg_path, PathBuf::from(DEFAULT_FFMPEG_PATH));
|
assert_eq!(config.ffmpeg_path, PathBuf::from(DEFAULT_FFMPEG_PATH));
|
||||||
}
|
}
|
||||||
@@ -547,6 +618,7 @@ prowlarr_url = "http://prowlarr.internal:9696"
|
|||||||
&path,
|
&path,
|
||||||
r#"
|
r#"
|
||||||
translate_openai_base_url = "http://llm.internal/v1"
|
translate_openai_base_url = "http://llm.internal/v1"
|
||||||
|
translate_openai_model = "local-model"
|
||||||
translate_command_template = "ssh box claude -p"
|
translate_command_template = "ssh box claude -p"
|
||||||
alass_path = "/usr/local/bin/alass"
|
alass_path = "/usr/local/bin/alass"
|
||||||
"#,
|
"#,
|
||||||
@@ -561,6 +633,7 @@ alass_path = "/usr/local/bin/alass"
|
|||||||
config.translate_openai_base_url.as_deref(),
|
config.translate_openai_base_url.as_deref(),
|
||||||
Some("http://llm.internal/v1")
|
Some("http://llm.internal/v1")
|
||||||
);
|
);
|
||||||
|
assert_eq!(config.translate_openai_model, "local-model");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
config.translate_command_template.as_deref(),
|
config.translate_command_template.as_deref(),
|
||||||
Some("ssh box claude -p")
|
Some("ssh box claude -p")
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ fn api_state(
|
|||||||
config.opensubtitles_username.clone(),
|
config.opensubtitles_username.clone(),
|
||||||
config.opensubtitles_password.clone(),
|
config.opensubtitles_password.clone(),
|
||||||
))
|
))
|
||||||
|
.with_translation_backends(translation_backends(config))
|
||||||
.with_jellyfin(jellyfin)
|
.with_jellyfin(jellyfin)
|
||||||
.with_syncer(arr_subs::Syncer::new().with_binary(config.alass_path.clone())))
|
.with_syncer(arr_subs::Syncer::new().with_binary(config.alass_path.clone())))
|
||||||
}
|
}
|
||||||
@@ -480,10 +481,6 @@ fn jellyfin_client(config: &Config) -> Result<arr_api::jellyfin::JellyfinClient,
|
|||||||
/// search *runs* is the `providers_enabled` setting the API reads per
|
/// search *runs* is the `providers_enabled` setting the API reads per
|
||||||
/// request. OpenSubtitles.com needs a registered API key to be called at all,
|
/// request. OpenSubtitles.com needs a registered API key to be called at all,
|
||||||
/// so without one it is not offered; Podnapisi is anonymous and always is.
|
/// so without one it is not offered; Podnapisi is anonymous and always is.
|
||||||
///
|
|
||||||
/// Translation backends are not wired: none is implemented yet (#191-#193),
|
|
||||||
/// and `AppState` defaults to an empty registry, which makes a manual
|
|
||||||
/// translation answer "no such engine" rather than fail obscurely.
|
|
||||||
fn subtitle_providers(
|
fn subtitle_providers(
|
||||||
opensubtitles_api_key: Option<String>,
|
opensubtitles_api_key: Option<String>,
|
||||||
username: Option<String>,
|
username: Option<String>,
|
||||||
@@ -511,3 +508,95 @@ fn subtitle_providers(
|
|||||||
|
|
||||||
providers
|
providers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The translation backends this deployment can offer (DESIGN.md §15,
|
||||||
|
/// issue #216).
|
||||||
|
///
|
||||||
|
/// Which cargo features this binary was built with decides what could ever
|
||||||
|
/// be here (`compiled_engines`); credentials decide what actually is, same
|
||||||
|
/// split `subtitle_providers` makes for search. Which one of these a
|
||||||
|
/// translation *uses* is the `translation_engine` database setting, read per
|
||||||
|
/// request (#198) — this only decides which ids exist to be picked.
|
||||||
|
#[cfg_attr(
|
||||||
|
not(any(
|
||||||
|
feature = "translate-openai",
|
||||||
|
feature = "translate-deepl",
|
||||||
|
feature = "translate-google",
|
||||||
|
feature = "translate-command"
|
||||||
|
)),
|
||||||
|
allow(unused_variables, unused_mut)
|
||||||
|
)]
|
||||||
|
fn translation_backends(config: &Config) -> Vec<std::sync::Arc<dyn arr_subs::Backend>> {
|
||||||
|
let mut backends: Vec<std::sync::Arc<dyn arr_subs::Backend>> = Vec::new();
|
||||||
|
|
||||||
|
#[cfg(feature = "translate-openai")]
|
||||||
|
{
|
||||||
|
let openai_config = arr_subs::OpenAiConfig {
|
||||||
|
api_key: config.translate_openai_api_key.clone(),
|
||||||
|
};
|
||||||
|
let backend = match &config.translate_openai_base_url {
|
||||||
|
Some(base_url) => arr_subs::OpenAi::with_base_url(
|
||||||
|
config.translate_openai_model.clone(),
|
||||||
|
openai_config,
|
||||||
|
base_url,
|
||||||
|
),
|
||||||
|
None => arr_subs::OpenAi::new(config.translate_openai_model.clone(), openai_config),
|
||||||
|
};
|
||||||
|
match backend {
|
||||||
|
Ok(backend) => backends.push(std::sync::Arc::new(backend)),
|
||||||
|
Err(error) => tracing::warn!(%error, "OpenAI-compatible translator not available"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "translate-deepl")]
|
||||||
|
{
|
||||||
|
if let Some(auth_key) = config.translate_deepl_api_key.clone() {
|
||||||
|
let deepl_config = arr_subs::DeepLConfig { auth_key };
|
||||||
|
let backend = match &config.translate_deepl_base_url {
|
||||||
|
Some(base_url) => arr_subs::DeepL::with_base_url(deepl_config, base_url),
|
||||||
|
None => arr_subs::DeepL::new(deepl_config),
|
||||||
|
};
|
||||||
|
match backend {
|
||||||
|
Ok(backend) => backends.push(std::sync::Arc::new(backend)),
|
||||||
|
Err(error) => tracing::warn!(%error, "DeepL not available"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::info!("no DeepL auth key configured; that translator is off");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "translate-google")]
|
||||||
|
{
|
||||||
|
if let Some(api_key) = config.translate_google_api_key.clone() {
|
||||||
|
let google_config = arr_subs::GoogleConfig { api_key };
|
||||||
|
let backend = match &config.translate_google_base_url {
|
||||||
|
Some(base_url) => arr_subs::Google::with_base_url(google_config, base_url),
|
||||||
|
None => arr_subs::Google::new(google_config),
|
||||||
|
};
|
||||||
|
match backend {
|
||||||
|
Ok(backend) => backends.push(std::sync::Arc::new(backend)),
|
||||||
|
Err(error) => tracing::warn!(%error, "Google Translate not available"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::info!("no Google Translate API key configured; that translator is off");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "translate-command")]
|
||||||
|
{
|
||||||
|
if let Some(template) = config.translate_command_template.clone() {
|
||||||
|
let command_config = arr_subs::CommandConfig {
|
||||||
|
template,
|
||||||
|
timeout: arr_subs::COMMAND_DEFAULT_TIMEOUT,
|
||||||
|
};
|
||||||
|
match arr_subs::Command::new(command_config) {
|
||||||
|
Ok(backend) => backends.push(std::sync::Arc::new(backend)),
|
||||||
|
Err(error) => tracing::warn!(%error, "remote-command translator not available"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::info!("no remote-command template configured; that translator is off");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backends
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user