2022-03-28 00:10:06 +00:00
|
|
|
use crate::{
|
2023-07-13 22:42:21 +00:00
|
|
|
config::primitives::{Filesystem, LogFormat, Targets},
|
|
|
|
formats::{AnimationFormat, AudioCodec, ImageFormat, VideoCodec},
|
2022-03-28 00:10:06 +00:00
|
|
|
serde_str::Serde,
|
|
|
|
};
|
2022-09-25 20:17:33 +00:00
|
|
|
use once_cell::sync::OnceCell;
|
2022-03-29 01:47:46 +00:00
|
|
|
use std::{collections::BTreeSet, net::SocketAddr, path::PathBuf};
|
2022-03-28 00:10:06 +00:00
|
|
|
use url::Url;
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2022-03-28 00:10:06 +00:00
|
|
|
pub(crate) struct ConfigFile {
|
|
|
|
pub(crate) server: Server,
|
|
|
|
|
2023-07-17 18:44:31 +00:00
|
|
|
pub(crate) client: Client,
|
|
|
|
|
2022-03-28 00:10:06 +00:00
|
|
|
pub(crate) tracing: Tracing,
|
|
|
|
|
2022-03-28 04:27:07 +00:00
|
|
|
pub(crate) old_db: OldDb,
|
2022-03-28 00:10:06 +00:00
|
|
|
|
|
|
|
pub(crate) media: Media,
|
|
|
|
|
|
|
|
pub(crate) repo: Repo,
|
|
|
|
|
|
|
|
pub(crate) store: Store,
|
|
|
|
}
|
|
|
|
|
2023-07-11 18:01:58 +00:00
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
#[serde(tag = "type")]
|
|
|
|
// allow large enum variant - this is an instantiated-once config
|
|
|
|
#[allow(clippy::large_enum_variant)]
|
|
|
|
pub(crate) enum Store {
|
|
|
|
Filesystem(Filesystem),
|
|
|
|
ObjectStorage(ObjectStorage),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
|
|
#[serde(rename_all = "snake_case")]
|
|
|
|
pub(crate) struct ObjectStorage {
|
|
|
|
/// The base endpoint for the object storage
|
|
|
|
///
|
|
|
|
/// Examples:
|
|
|
|
/// - `http://localhost:9000`
|
|
|
|
/// - `https://s3.dualstack.eu-west-1.amazonaws.com`
|
|
|
|
pub(crate) endpoint: Url,
|
|
|
|
|
|
|
|
/// Determines whether to use path style or virtualhost style for accessing objects
|
|
|
|
///
|
|
|
|
/// When this is true, objects will be fetched from {endpoint}/{bucket_name}/{object}
|
|
|
|
/// When false, objects will be fetched from {bucket_name}.{endpoint}/{object}
|
|
|
|
pub(crate) use_path_style: bool,
|
|
|
|
|
|
|
|
/// The bucket in which to store media
|
|
|
|
pub(crate) bucket_name: String,
|
|
|
|
|
|
|
|
/// The region the bucket is located in
|
|
|
|
pub(crate) region: String,
|
|
|
|
|
|
|
|
/// The Access Key for the user accessing the bucket
|
|
|
|
pub(crate) access_key: String,
|
|
|
|
|
|
|
|
/// The secret key for the user accessing the bucket
|
|
|
|
pub(crate) secret_key: String,
|
|
|
|
|
|
|
|
/// The session token for accessing the bucket
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) session_token: Option<String>,
|
|
|
|
|
|
|
|
/// How long signatures for object storage requests are valid (in seconds)
|
|
|
|
///
|
|
|
|
/// This defaults to 15 seconds
|
|
|
|
pub(crate) signature_duration: u64,
|
|
|
|
|
|
|
|
/// How long a client can wait on an object storage request before giving up (in seconds)
|
|
|
|
///
|
|
|
|
/// This defaults to 30 seconds
|
|
|
|
pub(crate) client_timeout: u64,
|
2023-07-14 19:53:37 +00:00
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) public_endpoint: Option<Url>,
|
2023-07-11 18:01:58 +00:00
|
|
|
}
|
|
|
|
|
2022-03-28 00:10:06 +00:00
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2022-03-28 00:10:06 +00:00
|
|
|
#[serde(tag = "type")]
|
|
|
|
pub(crate) enum Repo {
|
|
|
|
Sled(Sled),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2022-03-28 00:10:06 +00:00
|
|
|
pub(crate) struct Server {
|
|
|
|
pub(crate) address: SocketAddr,
|
|
|
|
|
2022-03-29 17:51:16 +00:00
|
|
|
pub(crate) worker_id: String,
|
|
|
|
|
2022-03-28 00:10:06 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) api_key: Option<String>,
|
2023-07-17 19:24:49 +00:00
|
|
|
|
|
|
|
pub(crate) read_only: bool,
|
2023-07-17 18:44:31 +00:00
|
|
|
}
|
2023-06-23 16:20:20 +00:00
|
|
|
|
2023-07-17 18:44:31 +00:00
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
|
|
pub(crate) struct Client {
|
|
|
|
pub(crate) pool_size: usize,
|
|
|
|
|
|
|
|
pub(crate) timeout: u64,
|
2022-03-28 00:10:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2022-03-28 00:10:06 +00:00
|
|
|
pub(crate) struct Tracing {
|
2022-03-28 04:27:07 +00:00
|
|
|
pub(crate) logging: Logging,
|
2022-03-28 00:10:06 +00:00
|
|
|
|
2022-03-28 04:27:07 +00:00
|
|
|
pub(crate) console: Console,
|
2022-03-28 00:10:06 +00:00
|
|
|
|
2022-03-28 04:27:07 +00:00
|
|
|
pub(crate) opentelemetry: OpenTelemetry,
|
2022-03-28 00:10:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2022-03-28 00:10:06 +00:00
|
|
|
pub(crate) struct Logging {
|
|
|
|
pub(crate) format: LogFormat,
|
|
|
|
|
|
|
|
pub(crate) targets: Serde<Targets>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2022-03-28 00:10:06 +00:00
|
|
|
pub(crate) struct OpenTelemetry {
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) url: Option<Url>,
|
2022-03-28 00:10:06 +00:00
|
|
|
|
|
|
|
pub(crate) service_name: String,
|
|
|
|
|
|
|
|
pub(crate) targets: Serde<Targets>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2022-03-28 00:10:06 +00:00
|
|
|
pub(crate) struct Console {
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) address: Option<SocketAddr>,
|
|
|
|
|
2022-03-28 00:10:06 +00:00
|
|
|
pub(crate) buffer_capacity: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2022-03-28 00:10:06 +00:00
|
|
|
pub(crate) struct OldDb {
|
|
|
|
pub(crate) path: PathBuf,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2022-03-28 00:10:06 +00:00
|
|
|
pub(crate) struct Media {
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) max_file_size: usize,
|
|
|
|
|
2022-09-25 20:17:33 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) preprocess_steps: Option<String>,
|
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) filters: BTreeSet<String>,
|
2022-03-28 00:10:06 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) image: Image,
|
2022-03-28 00:10:06 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) animation: Animation,
|
2022-03-28 00:10:06 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) video: Video,
|
|
|
|
}
|
2022-09-25 22:36:07 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
|
|
pub(crate) struct Image {
|
|
|
|
pub(crate) max_width: u16,
|
2023-02-04 23:32:36 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) max_height: u16,
|
2022-03-28 00:10:06 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) max_area: u32,
|
2022-09-25 22:36:07 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) max_file_size: usize,
|
2022-10-01 00:38:11 +00:00
|
|
|
|
2023-02-04 23:32:36 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) format: Option<ImageFormat>,
|
2023-07-17 22:45:26 +00:00
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) quality: Option<ImageQuality>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Image {
|
|
|
|
pub(crate) fn quality_for(&self, format: ImageFormat) -> Option<u8> {
|
|
|
|
self.quality
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|quality| match format {
|
|
|
|
ImageFormat::Avif => quality.avif,
|
|
|
|
ImageFormat::Jpeg => quality.jpeg,
|
|
|
|
ImageFormat::Jxl => quality.jxl,
|
|
|
|
ImageFormat::Png => quality.png,
|
|
|
|
ImageFormat::Webp => quality.webp,
|
|
|
|
})
|
|
|
|
.map(|quality| quality.min(100))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
|
|
pub(crate) struct ImageQuality {
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) avif: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) png: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) jpeg: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) jxl: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) webp: Option<u8>,
|
2023-07-13 22:42:21 +00:00
|
|
|
}
|
2022-10-01 00:38:11 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
|
|
pub(crate) struct Animation {
|
|
|
|
pub(crate) max_width: u16,
|
2022-03-28 00:10:06 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) max_height: u16,
|
|
|
|
|
|
|
|
pub(crate) max_area: u32,
|
|
|
|
|
|
|
|
pub(crate) max_file_size: usize,
|
2022-03-28 00:10:06 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) max_frame_count: u32,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) format: Option<AnimationFormat>,
|
2023-07-17 22:45:26 +00:00
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) quality: Option<AnimationQuality>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Animation {
|
|
|
|
pub(crate) fn quality_for(&self, format: AnimationFormat) -> Option<u8> {
|
|
|
|
self.quality
|
|
|
|
.as_ref()
|
|
|
|
.and_then(|quality| match format {
|
|
|
|
AnimationFormat::Apng => quality.apng,
|
|
|
|
AnimationFormat::Avif => quality.avif,
|
|
|
|
AnimationFormat::Gif => None,
|
|
|
|
AnimationFormat::Webp => quality.webp,
|
|
|
|
})
|
|
|
|
.map(|quality| quality.min(100))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
|
|
|
|
pub(crate) struct AnimationQuality {
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
apng: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
avif: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
webp: Option<u8>,
|
2022-03-28 00:10:06 +00:00
|
|
|
}
|
|
|
|
|
2023-02-04 23:32:36 +00:00
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) struct Video {
|
|
|
|
pub(crate) enable: bool,
|
|
|
|
|
|
|
|
pub(crate) allow_audio: bool,
|
|
|
|
|
|
|
|
pub(crate) max_width: u16,
|
2023-02-04 23:32:36 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) max_height: u16,
|
2023-02-04 23:32:36 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) max_area: u32,
|
2023-02-04 23:52:23 +00:00
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
pub(crate) max_file_size: usize,
|
|
|
|
|
|
|
|
pub(crate) max_frame_count: u32,
|
|
|
|
|
|
|
|
pub(crate) video_codec: VideoCodec,
|
|
|
|
|
2023-07-17 22:45:26 +00:00
|
|
|
pub(crate) quality: VideoQuality,
|
|
|
|
|
2023-07-13 22:42:21 +00:00
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
pub(crate) audio_codec: Option<AudioCodec>,
|
2023-02-04 23:32:36 +00:00
|
|
|
}
|
|
|
|
|
2023-07-17 22:45:26 +00:00
|
|
|
impl Video {
|
|
|
|
pub(crate) fn crf_for(&self, width: u16, height: u16) -> u8 {
|
|
|
|
let smaller_dimension = width.min(height);
|
|
|
|
|
|
|
|
let dimension_cutoffs = [240, 360, 480, 720, 1080, 1440, 2160];
|
|
|
|
let crfs = [
|
|
|
|
self.quality.crf_240,
|
|
|
|
self.quality.crf_360,
|
|
|
|
self.quality.crf_480,
|
|
|
|
self.quality.crf_720,
|
|
|
|
self.quality.crf_1080,
|
|
|
|
self.quality.crf_1440,
|
|
|
|
self.quality.crf_2160,
|
|
|
|
];
|
|
|
|
|
|
|
|
let index = dimension_cutoffs
|
|
|
|
.into_iter()
|
|
|
|
.enumerate()
|
|
|
|
.find_map(|(index, dim)| {
|
|
|
|
if smaller_dimension <= dim {
|
|
|
|
Some(index)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.unwrap_or(crfs.len());
|
|
|
|
|
|
|
|
crfs.into_iter()
|
|
|
|
.skip(index)
|
|
|
|
.find_map(|opt| opt)
|
|
|
|
.unwrap_or(self.quality.crf_max)
|
|
|
|
.min(63)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
|
|
pub(crate) struct VideoQuality {
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
crf_240: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
crf_360: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
crf_480: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
crf_720: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
crf_1080: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
crf_1440: Option<u8>,
|
|
|
|
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
|
|
crf_2160: Option<u8>,
|
|
|
|
|
|
|
|
crf_max: u8,
|
|
|
|
}
|
|
|
|
|
2022-09-25 20:17:33 +00:00
|
|
|
impl Media {
|
|
|
|
pub(crate) fn preprocess_steps(&self) -> Option<&[(String, String)]> {
|
|
|
|
static PREPROCESS_STEPS: OnceCell<Vec<(String, String)>> = OnceCell::new();
|
|
|
|
|
|
|
|
if let Some(steps) = &self.preprocess_steps {
|
|
|
|
let steps = PREPROCESS_STEPS
|
|
|
|
.get_or_try_init(|| {
|
|
|
|
serde_urlencoded::from_str(steps) as Result<Vec<(String, String)>, _>
|
|
|
|
})
|
|
|
|
.expect("Invalid preprocess_steps configuration")
|
|
|
|
.as_slice();
|
|
|
|
|
|
|
|
Some(steps)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-28 00:10:06 +00:00
|
|
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
2022-03-28 04:27:07 +00:00
|
|
|
#[serde(rename_all = "snake_case")]
|
2022-03-28 00:10:06 +00:00
|
|
|
pub(crate) struct Sled {
|
|
|
|
pub(crate) path: PathBuf,
|
|
|
|
|
|
|
|
pub(crate) cache_capacity: u64,
|
2023-07-08 22:35:57 +00:00
|
|
|
|
|
|
|
pub(crate) export_path: PathBuf,
|
2022-03-28 00:10:06 +00:00
|
|
|
}
|