From fe42e47729d8068f18b286b2cf0ad0e21f69cf34 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Sun, 14 Jan 2024 18:05:05 +0000 Subject: [PATCH 01/21] feat: categories fix: minor fixes feat: igdb image processor fix: proto formatting feat: handle image callback fix: lint fix: push --- .../api/src/api/v1/gql/models/image_upload.rs | 2 - .../api/src/api/v1/upload/profile_picture.rs | 17 +- platform/api/src/igdb_cron.rs | 15 +- platform/api/src/image_processor_callback.rs | 257 -------- platform/api/src/image_upload_callback.rs | 236 +++++++ platform/api/src/lib.rs | 2 +- platform/api/src/main.rs | 6 +- .../image_processor/src/processor/job/mod.rs | 4 +- .../src/processor/job/process.rs | 116 ++-- .../src/processor/job/resize.rs | 4 +- .../src/processor/job/scaling.rs | 584 ++++++++++++++++++ .../platform/internal/image_processor.proto | 35 +- .../types/processed_image_variant.proto | 5 +- 13 files changed, 944 insertions(+), 339 deletions(-) delete mode 100644 platform/api/src/image_processor_callback.rs create mode 100644 platform/api/src/image_upload_callback.rs create mode 100644 platform/image_processor/src/processor/job/scaling.rs diff --git a/platform/api/src/api/v1/gql/models/image_upload.rs b/platform/api/src/api/v1/gql/models/image_upload.rs index 0d0c61cf..d67d73c4 100644 --- a/platform/api/src/api/v1/gql/models/image_upload.rs +++ b/platform/api/src/api/v1/gql/models/image_upload.rs @@ -23,7 +23,6 @@ pub struct ImageUpload { pub struct ImageUploadVariant { pub width: u32, pub height: u32, - pub scale: u32, pub url: String, pub format: ImageUploadFormat, pub byte_size: u32, @@ -76,7 +75,6 @@ impl From for Ima Self { width: value.width, height: value.height, - scale: value.scale, format: value.format().into(), byte_size: value.byte_size, url: value.path, diff --git a/platform/api/src/api/v1/upload/profile_picture.rs b/platform/api/src/api/v1/upload/profile_picture.rs index 8bce1717..c404b015 100644 --- a/platform/api/src/api/v1/upload/profile_picture.rs +++ b/platform/api/src/api/v1/upload/profile_picture.rs @@ -23,8 +23,11 @@ use crate::global::ApiGlobal; fn create_task(file_id: Ulid, input_path: &str, config: &ImageUploaderConfig, owner_id: Ulid) -> image_processor::Task { image_processor::Task { input_path: input_path.to_string(), - base_height: 128, // 128, 256, 384, 512 - base_width: 128, // 128, 256, 384, 512 + aspect_ratio: Some(image_processor::task::Ratio { + numerator: 1, + denominator: 1, + }), + clamp_aspect_ratio: true, formats: vec![ ImageFormat::PngStatic as i32, ImageFormat::AvifStatic as i32, @@ -42,8 +45,14 @@ fn create_task(file_id: Ulid, input_path: &str, config: &ImageUploaderConfig, ow max_processing_time_ms: 60 * 1000, // 60 seconds }), resize_algorithm: image_processor::task::ResizeAlgorithm::Lanczos3 as i32, - upscale: true, // For profile pictures we want to have a consistent size - scales: vec![1, 2, 3, 4], + upscale: image_processor::task::Upscale::NoPreserveSource as i32, + input_image_scaling: true, + scales: vec![ + 64, + 128, + 256, + 384, + ], resize_method: image_processor::task::ResizeMethod::PadCenter as i32, output_prefix: format!("{owner_id}/{file_id}"), } diff --git a/platform/api/src/igdb_cron.rs b/platform/api/src/igdb_cron.rs index e1672cea..d054eff7 100644 --- a/platform/api/src/igdb_cron.rs +++ b/platform/api/src/igdb_cron.rs @@ -587,9 +587,13 @@ fn create_task( ) -> image_processor::Task { image_processor::Task { callback_subject: config.callback_subject.clone(), - upscale: false, + upscale: image_processor::task::Upscale::NoPreserveSource as i32, output_prefix: format!("categories/{category_id}/{id}"), - scales: vec![1], + scales: vec![ + 720, + 1080, + ], + input_image_scaling: true, limits: Some(image_processor::task::Limits { max_processing_time_ms: 60000, ..Default::default() @@ -601,8 +605,11 @@ fn create_task( ], input_path: path, resize_method: image_processor::task::ResizeMethod::Fit as i32, - base_height: 1080, - base_width: 1920, + clamp_aspect_ratio: false, + aspect_ratio: Some(image_processor::task::Ratio { + numerator: 1, + denominator: 1, + }), resize_algorithm: image_processor::task::ResizeAlgorithm::Lanczos3 as i32, } } diff --git a/platform/api/src/image_processor_callback.rs b/platform/api/src/image_processor_callback.rs deleted file mode 100644 index ab5d245c..00000000 --- a/platform/api/src/image_processor_callback.rs +++ /dev/null @@ -1,257 +0,0 @@ -use std::sync::Arc; -use std::time::Duration; - -use anyhow::Context; -use async_nats::jetstream::consumer::pull::MessagesErrorKind; -use async_nats::jetstream::stream::RetentionPolicy; -use async_nats::jetstream::AckKind; -use futures_util::StreamExt; -use pb::ext::UlidExt; -use pb::scuffle::platform::internal::events::{processed_image, ProcessedImage}; -use pb::scuffle::platform::internal::types::{uploaded_file_metadata, UploadedFileMetadata}; -use prost::Message; -use utils::context::ContextExt; - -use crate::config::ImageUploaderConfig; -use crate::database::{FileType, UploadedFile, UploadedFileStatus}; -use crate::global::ApiGlobal; -use crate::subscription::SubscriptionTopic; - -pub async fn run(global: Arc) -> anyhow::Result<()> { - let config = global.config::(); - - // It can't contain dots for some reason - let stream_name = config.callback_subject.replace('.', "-"); - - let stream = global - .jetstream() - .get_or_create_stream(async_nats::jetstream::stream::Config { - name: stream_name.clone(), - subjects: vec![config.callback_subject.clone()], - max_consumers: 1, - retention: RetentionPolicy::WorkQueue, - ..Default::default() - }) - .await - .context("stream")?; - - let consumer = stream - .get_or_create_consumer( - &stream_name, - async_nats::jetstream::consumer::pull::Config { - name: Some(stream_name.clone()), - ..Default::default() - }, - ) - .await - .context("consumer")?; - - let mut messages = consumer.messages().await.context("messages")?; - - while let Ok(message) = messages.next().context(global.ctx()).await { - handle_message(&global, message).await?; - } - - Ok(()) -} - -async fn handle_message( - global: &Arc, - message: Option>>, -) -> anyhow::Result<()> { - let message = match message { - Some(Ok(message)) => message, - Some(Err(err)) if matches!(err.kind(), MessagesErrorKind::MissingHeartbeat) => { - tracing::warn!("missing heartbeat"); - return Ok(()); - } - Some(Err(err)) => { - anyhow::bail!("message: {:#}", err) - } - None => { - anyhow::bail!("stream closed"); - } - }; - - let (job_id, job_result) = match ProcessedImage::decode(message.payload.as_ref()) { - Ok(ProcessedImage { - job_id, - result: Some(result), - }) => (job_id, result), - err => { - if let Err(err) = err { - tracing::warn!(error = %err, "failed to decode image upload job result"); - } else { - tracing::warn!("malformed image upload job result"); - } - message - .ack() - .await - .map_err(|err| anyhow::anyhow!(err)) - .context("failed to ack")?; - return Ok(()); - } - }; - tracing::trace!("received image upload job result: {:?}", job_result); - - let mut client = global.db().get().await.context("failed to get db connection")?; - let tx = client.transaction().await.context("failed to start transaction")?; - - let uploaded_file: UploadedFile = match utils::database::query("UPDATE uploaded_files SET status = $1, failed = $2, metadata = $3, updated_at = NOW() WHERE id = $4 AND status = 'queued' RETURNING *") - .bind(if matches!(job_result, processed_image::Result::Success(_)) { - UploadedFileStatus::Completed - } else { - UploadedFileStatus::Failed - }) - .bind(match &job_result { - processed_image::Result::Success(_) => None, - processed_image::Result::Failure(processed_image::Failure { reason, .. }) => { - Some(reason) - } - }) - .bind(utils::database::Protobuf(UploadedFileMetadata { - metadata: Some(uploaded_file_metadata::Metadata::Image(uploaded_file_metadata::Image { - versions: match &job_result { - processed_image::Result::Success(processed_image::Success { variants }) => variants.clone(), - processed_image::Result::Failure(_) => Vec::new(), - }, - })), - })) - .bind(job_id.into_ulid()) - .build_query_as() - .fetch_optional(&tx) - .await - .context("failed to get uploaded file")? { - Some(uploaded_file) => uploaded_file, - None => { - tracing::warn!("uploaded file not found"); - message.ack().await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; - return Ok(()); - } - }; - - match job_result { - processed_image::Result::Success(_) => { - global - .nats() - .publish( - SubscriptionTopic::UploadedFileStatus(uploaded_file.id), - pb::scuffle::platform::internal::events::UploadedFileStatus { - file_id: Some(uploaded_file.id.into()), - status: Some( - pb::scuffle::platform::internal::events::uploaded_file_status::Status::Success( - pb::scuffle::platform::internal::events::uploaded_file_status::Success {}, - ), - ), - } - .encode_to_vec() - .into(), - ) - .await - .context("failed to publish file update event")?; - - let updated = match uploaded_file.ty { - FileType::ProfilePicture => { - let owner_id = uploaded_file - .owner_id - .ok_or_else(|| anyhow::anyhow!("uploaded file owner id is null"))?; - - if utils::database::query("UPDATE users SET profile_picture_id = $1, pending_profile_picture_id = NULL, updated_at = NOW() WHERE id = $2 AND pending_profile_picture_id = $3") - .bind(uploaded_file.id) - .bind(owner_id) - .bind(uploaded_file.id) - .build() - .execute(&tx) - .await - .context("failed to update user")? == 1 { - Some(( - SubscriptionTopic::UserProfilePicture(uploaded_file.owner_id.unwrap()), - pb::scuffle::platform::internal::events::UserProfilePicture { - user_id: Some(uploaded_file.owner_id.unwrap().into()), - profile_picture_id: Some(uploaded_file.id.into()), - } - .encode_to_vec() - .into(), - )) - } else { - None - } - } - FileType::CategoryCover => None, - FileType::CategoryArtwork => None, - }; - - if let Some((topic, payload)) = updated { - global - .nats() - .publish(topic, payload) - .await - .context("failed to publish image upload update event")?; - } - } - processed_image::Result::Failure(processed_image::Failure { - reason, - friendly_message, - }) => { - global - .nats() - .publish( - SubscriptionTopic::UploadedFileStatus(uploaded_file.id), - pb::scuffle::platform::internal::events::UploadedFileStatus { - file_id: Some(uploaded_file.id.into()), - status: Some( - pb::scuffle::platform::internal::events::uploaded_file_status::Status::Failure( - pb::scuffle::platform::internal::events::uploaded_file_status::Failure { - reason, - friendly_message, - }, - ), - ), - } - .encode_to_vec() - .into(), - ) - .await - .context("failed to publish file update event")?; - - match uploaded_file.ty { - FileType::ProfilePicture => { - let owner_id = uploaded_file - .owner_id - .ok_or_else(|| anyhow::anyhow!("uploaded file owner id is null"))?; - - utils::database::query( - "UPDATE users SET pending_profile_picture_id = NULL, updated_at = NOW() WHERE id = $1 AND pending_profile_picture_id = $2", - ) - .bind(owner_id) - .bind(uploaded_file.id) - .build() - .execute(&tx) - .await - .context("failed to update user")?; - } - FileType::CategoryCover => {} - FileType::CategoryArtwork => {} - } - } - } - - if let Err(err) = tx.commit().await.context("failed to commit transaction") { - tracing::warn!(error = %err, "failed to commit transaction"); - message - .ack_with(AckKind::Nak(Some(Duration::from_secs(5)))) - .await - .map_err(|err| anyhow::anyhow!(err)) - .context("failed to ack")?; - return Ok(()); - } - - message - .ack() - .await - .map_err(|err| anyhow::anyhow!(err)) - .context("failed to ack")?; - - tracing::debug!("processed image upload job result"); - Ok(()) -} diff --git a/platform/api/src/image_upload_callback.rs b/platform/api/src/image_upload_callback.rs new file mode 100644 index 00000000..698eda05 --- /dev/null +++ b/platform/api/src/image_upload_callback.rs @@ -0,0 +1,236 @@ +use std::sync::Arc; +use std::time::Duration; + +use anyhow::Context; +use async_nats::jetstream::stream::RetentionPolicy; +use async_nats::jetstream::AckKind; +use futures_util::StreamExt; +use pb::ext::UlidExt; +use pb::scuffle::platform::internal::events::{processed_image, ProcessedImage}; +use pb::scuffle::platform::internal::types::{uploaded_file_metadata, ProcessedImageVariant, UploadedFileMetadata}; +use prost::Message; +use utils::context::ContextExt; + +use crate::config::ImageUploaderConfig; +use crate::database::{FileType, UploadedFile}; +use crate::global::ApiGlobal; +use crate::subscription::SubscriptionTopic; + +const CONSUMER_NAME: &str = "image-upload-consumer"; + +pub async fn run(global: Arc) -> anyhow::Result<()> { + let config = global.config::(); + + let image_upload_callback = global + .jetstream() + .get_or_create_stream(async_nats::jetstream::stream::Config { + name: config.callback_subject.clone(), + subjects: vec![config.callback_subject.clone()], + max_consumers: 1, + retention: RetentionPolicy::WorkQueue, + ..Default::default() + }) + .await + .context("failed to create profile picture stream")?; + + let image_upload_callback = image_upload_callback + .get_or_create_consumer(CONSUMER_NAME, async_nats::jetstream::consumer::pull::Config { + name: Some(CONSUMER_NAME.to_owned()), + ack_wait: Duration::from_secs(30), + ..Default::default() + }) + .await + .context("failed to create profile picture consumer")?; + + let mut image_upload_consumer = image_upload_callback + .messages() + .await + .context("failed to get profile picture consumer messages")?; + + while let Ok(message) = image_upload_consumer.next().context(global.ctx()).await { + let message = message.ok_or_else(|| anyhow::anyhow!("profile picture consumer closed"))?.context("failed to get profile picture consumer message")?; + let (job_id, job_result) = match ProcessedImage::decode(message.payload.as_ref()) { + Ok(ProcessedImage { job_id, result: Some(result) }) => (job_id, result), + err => { + if let Err(err) = err { + tracing::warn!(error = %err, "failed to decode profile picture job result"); + } else { + tracing::warn!("malformed profile picture job result"); + } + message.ack().await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + continue; + }, + }; + tracing::debug!("received profile picture job result: {:?}", job_result); + + match job_result { + processed_image::Result::Success(processed_image::Success { variants }) => { + if let Err(err) = handle_success(&global, job_id.into_ulid(), variants).await { + tracing::warn!(error = %err, "failed to handle profile picture job success"); + message.ack_with(AckKind::Nak(Some(Duration::from_secs(5)))).await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + } else { + message.ack().await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + } + }, + processed_image::Result::Failure(processed_image::Failure { reason, friendly_message }) => { + if let Err(err) = handle_failure(&global, job_id.into_ulid(), reason, friendly_message).await { + tracing::warn!(error = %err, "failed to handle profile picture job failure"); + message.ack_with(AckKind::Nak(Some(Duration::from_secs(5)))).await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + } else { + message.ack().await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + } + }, + } + + message.ack().await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + } + + Ok(()) +} + + +async fn handle_success( + global: &Arc, + job_id: ulid::Ulid, + variants: Vec, +) -> anyhow::Result<()> { + let mut client = global.db().get().await.context("failed to get db connection")?; + let tx = client.transaction().await.context("failed to start transaction")?; + + let uploaded_file: UploadedFile = match utils::database::query("UPDATE uploaded_files SET status = 'completed', metadata = $1, updated_at = NOW() WHERE id = $2 AND status = 'queued' RETURNING *") + .bind(utils::database::Protobuf(UploadedFileMetadata { + metadata: Some(uploaded_file_metadata::Metadata::Image(uploaded_file_metadata::Image { + versions: variants, + })), + })) + .bind(job_id) + .build_query_as() + .fetch_optional(&tx) + .await + .context("failed to get uploaded file")? { + Some(uploaded_file) => uploaded_file, + None => { + anyhow::bail!("uploaded file not found"); + } + }; + + global + .nats() + .publish( + SubscriptionTopic::UploadedFileStatus(uploaded_file.id), + pb::scuffle::platform::internal::events::UploadedFileStatus { + file_id: Some(uploaded_file.id.into()), + status: Some(pb::scuffle::platform::internal::events::uploaded_file_status::Status::Success(pb::scuffle::platform::internal::events::uploaded_file_status::Success {})), + }.encode_to_vec().into(), + ) + .await + .context("failed to publish file update event")?; + + match uploaded_file.ty { + FileType::CategoryArtwork | FileType::CategoryCover => { + + }, + FileType::ProfilePicture => { + let user_updated = utils::database::query("UPDATE users SET profile_picture_id = $1, pending_profile_picture_id = NULL, updated_at = NOW() WHERE id = $2 AND pending_profile_picture_id = $1") + .bind(uploaded_file.id) + .bind(uploaded_file.owner_id) + .build() + .execute(&tx) + .await + .context("failed to update user")? == 1; + + tx.commit().await.context("failed to commit transaction")?; + + let owner_id = uploaded_file.owner_id.ok_or_else(|| anyhow::anyhow!("uploaded file owner id is null"))?; + + if user_updated { + global + .nats() + .publish( + SubscriptionTopic::UserProfilePicture(owner_id), + pb::scuffle::platform::internal::events::UserProfilePicture { + user_id: Some(owner_id.into()), + profile_picture_id: Some(uploaded_file.id.into()), + }.encode_to_vec().into(), + ) + .await + .context("failed to publish profile picture update event")?; + } + } + } + + Ok(()) +} + +async fn handle_failure( + global: &Arc, + job_id: ulid::Ulid, + reason: String, + friendly_message: String, +) -> anyhow::Result<()> { + let mut client = global.db().get().await.context("failed to get db connection")?; + let tx = client.transaction().await.context("failed to start transaction")?; + + let uploaded_file: UploadedFile = match utils::database::query("UPDATE uploaded_files SET status = 'failed', failed = $1, updated_at = NOW() WHERE id = $2 AND status = 'queued' RETURNING *") + .bind(reason.clone()) + .bind(job_id) + .build_query_as() + .fetch_optional(&tx) + .await + .context("failed to get uploaded file")? { + Some(uploaded_file) => uploaded_file, + None => { + anyhow::bail!("uploaded file not found"); + } + }; + + global + .nats() + .publish( + SubscriptionTopic::UploadedFileStatus(uploaded_file.id), + pb::scuffle::platform::internal::events::UploadedFileStatus { + file_id: Some(uploaded_file.id.into()), + status: Some(pb::scuffle::platform::internal::events::uploaded_file_status::Status::Failure(pb::scuffle::platform::internal::events::uploaded_file_status::Failure { + reason, + friendly_message, + })), + }.encode_to_vec().into(), + ) + .await + .context("failed to publish file update event")?; + + let update_count = match uploaded_file.ty { + FileType::CategoryArtwork | FileType::CategoryCover => false, + FileType::ProfilePicture => { + utils::database::query("UPDATE users SET pending_profile_picture_id = NULL, updated_at = NOW() WHERE id = $1 AND pending_profile_picture_id = $2") + .bind(uploaded_file.owner_id) + .bind(uploaded_file.id) + .build() + .execute(&tx) + .await + .context("failed to update user")? == 1 + } + }; + + tx.commit().await.context("failed to commit transaction")?; + + match (uploaded_file.ty, update_count) { + (FileType::CategoryArtwork | FileType::CategoryCover, _) => {}, + (FileType::ProfilePicture, true) => { + global + .nats() + .publish( + SubscriptionTopic::UserProfilePicture(uploaded_file.owner_id.unwrap()), + pb::scuffle::platform::internal::events::UserProfilePicture { + user_id: Some(uploaded_file.owner_id.unwrap().into()), + profile_picture_id: None, + }.encode_to_vec().into(), + ) + .await + .context("failed to publish profile picture update event")?; + }, + (FileType::ProfilePicture, false) => {}, + } + + Ok(()) +} \ No newline at end of file diff --git a/platform/api/src/lib.rs b/platform/api/src/lib.rs index bcb18296..4a4ff94a 100644 --- a/platform/api/src/lib.rs +++ b/platform/api/src/lib.rs @@ -5,7 +5,7 @@ pub mod dataloader; pub mod global; pub mod grpc; pub mod igdb_cron; -pub mod image_processor_callback; +pub mod image_upload_callback; pub mod subscription; pub mod turnstile; pub mod video_api; diff --git a/platform/api/src/main.rs b/platform/api/src/main.rs index ae61ba3d..58d2c467 100644 --- a/platform/api/src/main.rs +++ b/platform/api/src/main.rs @@ -18,7 +18,7 @@ use platform_api::video_api::{ load_playback_keypair_private_key, setup_video_events_client, setup_video_playback_session_client, setup_video_room_client, VideoEventsClient, VideoPlaybackSessionClient, VideoRoomClient, }; -use platform_api::{igdb_cron, image_processor_callback, video_event_handler}; +use platform_api::{igdb_cron, image_upload_callback, video_event_handler}; use tokio::select; use utils::context::Context; use utils::dataloader::DataLoader; @@ -321,7 +321,7 @@ pub async fn main() { let api_future = platform_api::api::run(global.clone()); let subscription_manager = global.subscription_manager.run(global.ctx.clone(), global.nats.clone()); let video_event_handler = video_event_handler::run(global.clone()); - let image_processor_callback = image_processor_callback::run(global.clone()); + let image_upload_callback = image_upload_callback::run(global.clone()); let igdb_cron = igdb_cron::run(global.clone()); select! { @@ -329,7 +329,7 @@ pub async fn main() { r = api_future => r.context("api server stopped unexpectedly")?, r = subscription_manager => r.context("subscription manager stopped unexpectedly")?, r = video_event_handler => r.context("video event handler stopped unexpectedly")?, - r = image_processor_callback => r.context("image processor callback handler stopped unexpectedly")?, + r = image_upload_callback => r.context("image processor callback handler stopped unexpectedly")?, r = igdb_cron => r.context("igdb cron stopped unexpectedly")?, } diff --git a/platform/image_processor/src/processor/job/mod.rs b/platform/image_processor/src/processor/job/mod.rs index 128676c1..f4d6a41a 100644 --- a/platform/image_processor/src/processor/job/mod.rs +++ b/platform/image_processor/src/processor/job/mod.rs @@ -28,6 +28,7 @@ pub(crate) mod libwebp; pub(crate) mod process; pub(crate) mod resize; pub(crate) mod smart_object; +pub(crate) mod scaling; pub(crate) struct Job<'a, G: ImageProcessorGlobal> { pub(crate) global: &'a Arc, @@ -230,11 +231,10 @@ impl<'a, G: ImageProcessorGlobal> Job<'a, G> { .iter() .map(|image| pb::scuffle::platform::internal::types::ProcessedImageVariant { path: image.url(&self.job.task.output_prefix), - format: image.request.1.into(), + format: image.request.into(), width: image.width as u32, height: image.height as u32, byte_size: image.data.len() as u32, - scale: image.request.0 as u32, }) .collect(), }, diff --git a/platform/image_processor/src/processor/job/process.rs b/platform/image_processor/src/processor/job/process.rs index 020ba7b8..09d3118f 100644 --- a/platform/image_processor/src/processor/job/process.rs +++ b/platform/image_processor/src/processor/job/process.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use bytes::Bytes; +use pb::scuffle::platform::internal::image_processor::task; use pb::scuffle::platform::internal::types::ImageFormat; use rgb::ComponentBytes; use sha2::Digest; @@ -11,6 +12,7 @@ use super::encoder::{AnyEncoder, Encoder, EncoderFrontend, EncoderSettings}; use super::resize::{ImageResizer, ImageResizerTarget}; use crate::database::Job; use crate::processor::error::{ProcessorError, Result}; +use crate::processor::job::scaling::{ScalingOptions, Ratio}; #[derive(Debug)] #[allow(dead_code)] @@ -22,12 +24,12 @@ pub struct Image { pub encoder: EncoderFrontend, pub data: Bytes, pub loop_count: LoopCount, - pub request: (usize, ImageFormat), + pub request: ImageFormat, } impl Image { pub fn file_extension(&self) -> &'static str { - match self.request.1 { + match self.request { ImageFormat::Avif | ImageFormat::AvifStatic => "avif", ImageFormat::Webp | ImageFormat::WebpStatic => "webp", ImageFormat::Gif => "gif", @@ -36,7 +38,7 @@ impl Image { } pub fn content_type(&self) -> &'static str { - match self.request.1 { + match self.request { ImageFormat::Avif | ImageFormat::AvifStatic => "image/avif", ImageFormat::Webp | ImageFormat::WebpStatic => "image/webp", ImageFormat::Gif => "image/gif", @@ -46,18 +48,19 @@ impl Image { pub fn is_static(&self) -> bool { matches!( - self.request.1, + self.request, ImageFormat::AvifStatic | ImageFormat::WebpStatic | ImageFormat::PngStatic ) } pub fn url(&self, prefix: &str) -> String { format!( - "{}/{}{}x.{}", - prefix.trim_end_matches('/'), - self.is_static().then_some("static_").unwrap_or_default(), - self.request.0, - self.file_extension() + "{prefix}/{static_prefix}{width}x{height}.{ext}", + prefix = prefix.trim_end_matches('/'), + static_prefix = self.is_static().then_some("static_").unwrap_or_default(), + width = self.width, + height = self.height, + ext = self.file_extension() ) } } @@ -73,7 +76,10 @@ pub fn process_job(backend: DecoderBackend, job: &Job, data: Cow<'_, [u8]>) -> R let info = decoder.info(); let formats = job.task.formats().collect::>(); - let scales = job.task.scales.iter().map(|s| *s as usize).collect::>(); + let mut scales = job.task.scales.iter().cloned().map(|s| s as usize).collect::>(); + + // Sorts the scales from smallest to largest. + scales.sort(); if formats.is_empty() || scales.is_empty() { tracing::debug!("no formats or scales specified"); @@ -119,32 +125,50 @@ pub fn process_job(backend: DecoderBackend, job: &Job, data: Cow<'_, [u8]>) -> R static_image: true, }; - let (base_width, base_height) = if job.task.upscale { - (job.task.base_width as f64, job.task.base_height as f64) - } else { - let largest_scale = scales.iter().max().copied().unwrap_or(1); - - let width = info.width as f64 / largest_scale as f64; - let height = info.height as f64 / largest_scale as f64; - - if width > job.task.base_width as f64 && height > job.task.base_height as f64 { - (job.task.base_width as f64, job.task.base_height as f64) - } else { - (width, height) - } + let (preserve_aspect_height, preserve_aspect_width) = match job.task.resize_method() { + task::ResizeMethod::Fit => (true, true), + task::ResizeMethod::Stretch => (false, false), + task::ResizeMethod::PadBottomLeft => (false, false), + task::ResizeMethod::PadBottomRight => (false, false), + task::ResizeMethod::PadTopLeft => (false, false), + task::ResizeMethod::PadTopRight => (false, false), + task::ResizeMethod::PadCenter => (false, false), + task::ResizeMethod::PadCenterLeft => (false, false), + task::ResizeMethod::PadCenterRight => (false, false), + task::ResizeMethod::PadTopCenter => (false, false), + task::ResizeMethod::PadBottomCenter => (false, false), + task::ResizeMethod::PadTop => (false, true), + task::ResizeMethod::PadBottom => (false, true), + task::ResizeMethod::PadLeft => (true, false), + task::ResizeMethod::PadRight => (true, false), }; - + + let upscale = job.task.upscale().into(); + + let scales = ScalingOptions { + input_height: info.height, + input_width: info.width, + input_image_scaling: job.task.input_image_scaling, + clamp_aspect_ratio: job.task.clamp_aspect_ratio, + scales, + aspect_ratio: job.task.aspect_ratio.as_ref().map(|r| Ratio::new(r.numerator as usize, r.denominator as usize)).unwrap_or(Ratio::ONE), + upscale, + preserve_aspect_height, + preserve_aspect_width, + }.compute(); + + // let base_width = input_width as f64 / job.task.aspect_width as f64; let mut resizers = scales .iter() .map(|scale| { ( - *scale, + scale.clone(), ImageResizer::new(ImageResizerTarget { - height: base_height.ceil() as usize * scale, - width: base_width.ceil() as usize * scale, + height: scale.height, + width: scale.width, algorithm: job.task.resize_algorithm(), method: job.task.resize_method(), - upscale: job.task.upscale, + upscale: upscale.is_yes(), }), Vec::with_capacity(info.frame_count), ) @@ -186,16 +210,14 @@ pub fn process_job(backend: DecoderBackend, job: &Job, data: Cow<'_, [u8]>) -> R drop(decoder); struct Stack { - scale: usize, static_encoders: Vec, animation_encoders: Vec, } let mut stacks = resizers - .iter_mut() - .map(|(scale, _, frames)| { + .iter() + .map(|(_, _, frames)| { Ok(Stack { - scale: *scale, static_encoders: static_formats .iter() .map(|&frontend| frontend.build(static_settings)) @@ -243,15 +265,12 @@ pub fn process_job(backend: DecoderBackend, job: &Job, data: Cow<'_, [u8]>) -> R encoder: info.frontend, data: output.into(), loop_count: info.loop_count, - request: ( - stack.scale, - match info.frontend { - EncoderFrontend::Gifski => ImageFormat::Gif, - EncoderFrontend::LibAvif => ImageFormat::Avif, - EncoderFrontend::LibWebp => ImageFormat::Webp, - EncoderFrontend::Png => unreachable!(), - }, - ), + request: match info.frontend { + EncoderFrontend::Gifski => ImageFormat::Gif, + EncoderFrontend::LibAvif => ImageFormat::Avif, + EncoderFrontend::LibWebp => ImageFormat::Webp, + EncoderFrontend::Png => unreachable!(), + }, }); } @@ -266,15 +285,12 @@ pub fn process_job(backend: DecoderBackend, job: &Job, data: Cow<'_, [u8]>) -> R encoder: info.frontend, data: output.into(), loop_count: info.loop_count, - request: ( - stack.scale, - match info.frontend { - EncoderFrontend::LibAvif => ImageFormat::AvifStatic, - EncoderFrontend::LibWebp => ImageFormat::WebpStatic, - EncoderFrontend::Png => ImageFormat::PngStatic, - EncoderFrontend::Gifski => unreachable!(), - }, - ), + request: match info.frontend { + EncoderFrontend::LibAvif => ImageFormat::AvifStatic, + EncoderFrontend::LibWebp => ImageFormat::WebpStatic, + EncoderFrontend::Png => ImageFormat::PngStatic, + EncoderFrontend::Gifski => unreachable!(), + }, }); } } diff --git a/platform/image_processor/src/processor/job/resize.rs b/platform/image_processor/src/processor/job/resize.rs index df5b910f..3b4dac75 100644 --- a/platform/image_processor/src/processor/job/resize.rs +++ b/platform/image_processor/src/processor/job/resize.rs @@ -50,7 +50,7 @@ impl ImageResizer { pub fn resize(&mut self, frame: &Frame) -> Result { let _abort_guard = utils::task::AbortGuard::new(); - let (width, height) = if self.target.method == ResizeMethod::Exact { + let (width, height) = if self.target.method == ResizeMethod::Stretch { (self.target.width, self.target.height) } else { let (mut width, mut height) = if frame.image.width() > frame.image.height() { @@ -117,7 +117,7 @@ impl ImageResizer { ResizeMethod::PadBottom => (0, height_delta, 0, 0), ResizeMethod::PadLeft => (0, 0, width_delta, 0), ResizeMethod::PadRight => (0, 0, 0, width_delta), - ResizeMethod::Exact => unreachable!(), + ResizeMethod::Stretch => unreachable!(), ResizeMethod::Fit => unreachable!(), }; diff --git a/platform/image_processor/src/processor/job/scaling.rs b/platform/image_processor/src/processor/job/scaling.rs new file mode 100644 index 00000000..e0e3c99c --- /dev/null +++ b/platform/image_processor/src/processor/job/scaling.rs @@ -0,0 +1,584 @@ +use std::ops::MulAssign; + +#[derive(Debug, Clone)] +pub struct ScalingOptions { + pub input_width: usize, + pub input_height: usize, + pub aspect_ratio: Ratio, + pub clamp_aspect_ratio: bool, + pub preserve_aspect_width: bool, + pub preserve_aspect_height: bool, + pub upscale: Upscale, + pub input_image_scaling: bool, + pub scales: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Upscale { + Yes, + No, + NoPreserveSource, +} + +impl From for Upscale { + fn from(value: pb::scuffle::platform::internal::image_processor::task::Upscale) -> Self { + match value { + pb::scuffle::platform::internal::image_processor::task::Upscale::Yes => Upscale::Yes, + pb::scuffle::platform::internal::image_processor::task::Upscale::No => Upscale::No, + pb::scuffle::platform::internal::image_processor::task::Upscale::NoPreserveSource => Upscale::NoPreserveSource, + } + } +} + +impl Upscale { + pub fn is_yes(&self) -> bool { + matches!(self, Upscale::Yes) + } + + pub fn is_no(&self) -> bool { + matches!(self, Upscale::No | Upscale::NoPreserveSource) + } + + pub fn preserve_source(&self) -> bool { + matches!(self, Upscale::NoPreserveSource) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Size { + pub width: T, + pub height: T, +} + +#[derive(Debug, Clone, Copy)] +pub struct Ratio { + n: usize, + d: usize, +} + +impl Ratio { + pub const ONE: Self = Self::new(1, 1); + + pub const fn new(n: usize, d: usize) -> Self { + Self { n, d }.simplify() + } + + const fn gcd(&self) -> usize { + let mut a = self.n; + let mut b = self.d; + + while b != 0 { + let t = b; + b = a % b; + a = t; + } + + a + } + + const fn simplify(mut self) -> Self { + let gcd = self.gcd(); + + self.n /= gcd; + self.d /= gcd; + + self + } + + fn as_f64(&self) -> f64 { + self.n as f64 / self.d as f64 + } +} + +impl std::ops::Div for Ratio { + type Output = Ratio; + + fn div(self, rhs: usize) -> Self::Output { + Self { + n: self.n, + d: self.d * rhs, + }.simplify() + } +} + +impl std::ops::Mul for Ratio { + type Output = Ratio; + + fn mul(self, rhs: usize) -> Self::Output { + Self { + n: self.n * rhs, + d: self.d, + }.simplify() + } +} + +impl std::ops::Div for Ratio { + type Output = Ratio; + + fn div(self, rhs: Ratio) -> Self::Output { + Self { + n: self.n * rhs.d, + d: self.d * rhs.n, + }.simplify() + } +} + +impl std::ops::Mul for Ratio { + type Output = Ratio; + + fn mul(self, rhs: Ratio) -> Self::Output { + Self { + n: self.n * rhs.n, + d: self.d * rhs.d, + }.simplify() + } +} + +impl PartialEq for Ratio { + fn eq(&self, other: &Self) -> bool { + let this = self.simplify(); + let other = other.simplify(); + + this.n == other.n && this.d == other.d + } +} + +impl Eq for Ratio {} + +impl PartialOrd for Ratio { + fn partial_cmp(&self, other: &Self) -> Option { + let this = self.simplify(); + let other = other.simplify(); + + Some((this.n * other.d).cmp(&(this.d * other.n))) + } +} + +impl Ord for Ratio { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let this = self.simplify(); + let other = other.simplify(); + + (this.n * other.d).cmp(&(this.d * other.n)) + } +} + +impl MulAssign for Ratio { + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +impl std::ops::DivAssign for Ratio { + fn div_assign(&mut self, rhs: Self) { + *self = *self / rhs; + } +} + +impl std::ops::Div for Size +where + T: std::ops::Div + Copy, +{ + type Output = Self; + + fn div(self, rhs: T) -> Self::Output { + Self { + width: self.width / rhs, + height: self.height / rhs, + } + } +} + +impl std::ops::Mul for Size +where + T: std::ops::Mul + Copy, +{ + type Output = Self; + + fn mul(self, rhs: T) -> Self::Output { + Self { + width: self.width * rhs, + height: self.height * rhs, + } + } +} + +impl ScalingOptions { + pub fn compute(&mut self) -> Vec> { + // Sorts the scales from smallest to largest. + self.scales.sort_by(|a, b| a.partial_cmp(&b).unwrap()); + + let mut scales = self.compute_scales(); + let padded_size = self.padded_size(); + + let (best_idx, input_scale_factor) = scales.iter().position(|(size, _)| { + size.width >= padded_size.width || size.height >= padded_size.height + }).map(|idx| (idx, Ratio::ONE)).unwrap_or_else(|| { + let size = scales.last().unwrap().0; + + // Since its the padded size, the aspect ratio is the same as the target aspect ratio. + let input_scale_factor = padded_size.width / size.width; + + (scales.len() - 1, input_scale_factor) + }); + + dbg!(&scales); + + if self.input_image_scaling { + let scaled_width = padded_size.width / scales[best_idx].1 / input_scale_factor; + let scaled_height = padded_size.height / scales[best_idx].1 / input_scale_factor; + scales.iter_mut().for_each(|(size, scale)| { + size.width = *scale * scaled_width; + size.height = *scale * scaled_height; + }); + }; + + + if self.upscale.preserve_source() { + let padded_size = padded_size / input_scale_factor; + + dbg!(&padded_size); + + let size = scales[best_idx].0; + + if size.width > padded_size.width || size.height > padded_size.height { + scales[best_idx].0 = padded_size; + } + } + + if self.clamp_aspect_ratio { + scales.iter_mut().for_each(|(size, scale)| { + let scale = *scale; + + if self.aspect_ratio < Ratio::ONE && size.height > scale / self.aspect_ratio { + let height = scale / self.aspect_ratio; + size.width *= height / size.height; + size.height = height; + } else if self.aspect_ratio > Ratio::ONE && size.width > scale * self.aspect_ratio { + let width = scale * self.aspect_ratio; + size.height *= width / size.width; + size.width = width; + } else if self.aspect_ratio == Ratio::ONE && size.width > scale { + size.height *= scale / size.width; + size.width = scale; + } else if self.aspect_ratio == Ratio::ONE && size.height > scale { + size.width *= scale / size.height; + size.height = scale; + } + + size.width = size.width.max(Ratio::ONE); + size.height = size.height.max(Ratio::ONE); + }); + } + + if self.upscale.is_no() { + scales.retain(|(size, _)| { + size.width <= padded_size.width && size.height <= padded_size.height + }); + } + + scales.into_iter().map(|(mut size, _)| { + let input_aspect_ratio = self.input_aspect_ratio(); + + if self.preserve_aspect_height && self.aspect_ratio <= Ratio::ONE { + let height = size.height * self.aspect_ratio / input_aspect_ratio; + // size.width *= size.height / height; + size.height = height; + } else if self.preserve_aspect_width && self.aspect_ratio >= Ratio::ONE { + let width = size.width * input_aspect_ratio / self.aspect_ratio; + // size.height *= size.width / width; + size.width = width; + } + + Size { + width: size.width.as_f64().round() as usize, + height: size.height.as_f64().round() as usize, + } + }).collect() + } + + fn compute_scales(&self) -> Vec<(Size, Ratio)> { + self.scales.iter().copied().map(|scale| { + let scale = Ratio::new(scale, 1); + + let (width, height) = if self.aspect_ratio > Ratio::ONE { + (scale * self.aspect_ratio, scale) + } else { + (scale, scale / self.aspect_ratio) + }; + + (Size { width, height }, scale) + }).collect() + } + + fn input_aspect_ratio(&self) -> Ratio { + Ratio { + n: self.input_width, + d: self.input_height, + }.simplify() + } + + fn padded_size(&self) -> Size { + let width = Ratio::new(self.input_width, 1); + let height = Ratio::new(self.input_height, 1); + + let (width, height) = if self.aspect_ratio < Ratio::ONE { + (width, width / self.aspect_ratio) + } else { + (height * self.aspect_ratio, height) + }; + + Size { width, height } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_compute_scales_same_aspect() { + let mut options = ScalingOptions { + input_width: 100, + input_height: 100, + aspect_ratio: Ratio::new(1, 1), + preserve_aspect_width: false, + preserve_aspect_height: false, + upscale: Upscale::Yes, + input_image_scaling: false, + clamp_aspect_ratio: true, + scales: vec![ + 32, + 64, + 96, + 128, + ], + }; + + assert_eq!(options.compute(), vec![ + Size { width: 32, height: 32 }, + Size { width: 64, height: 64 }, + Size { width: 96, height: 96 }, + Size { width: 128, height: 128 }, + ]); + + options.upscale = Upscale::No; + + assert_eq!(options.compute(), vec![ + Size { width: 32, height: 32 }, + Size { width: 64, height: 64 }, + Size { width: 96, height: 96 }, + ]); + + options.upscale = Upscale::NoPreserveSource; + + assert_eq!(options.compute(), vec![ + Size { width: 32, height: 32 }, + Size { width: 64, height: 64 }, + Size { width: 96, height: 96 }, + Size { width: 100, height: 100 }, + ]); + + options.input_height = 112; + options.input_width = 112; + options.input_image_scaling = true; + options.upscale = Upscale::No; + + assert_eq!(options.compute(), vec![ + Size { width: 28, height: 28 }, + Size { width: 56, height: 56 }, + Size { width: 84, height: 84 }, + Size { width: 112, height: 112 }, + ]); + } + + #[test] + fn test_compute_scales_different_aspect() { + let mut options = ScalingOptions { + input_width: 100, + input_height: 100, + aspect_ratio: Ratio::new(16, 9), + preserve_aspect_width: false, + preserve_aspect_height: false, + upscale: Upscale::Yes, + input_image_scaling: false, + clamp_aspect_ratio: true, + scales: vec![ + 360, + 720, + 1080, + ], + }; + + assert_eq!(options.compute(), vec![ + Size { width: 640, height: 360 }, + Size { width: 1280, height: 720 }, + Size { width: 1920, height: 1080 }, + ]); + + options.upscale = Upscale::No; + assert_eq!(options.compute(), vec![]); + + options.upscale = Upscale::NoPreserveSource; + assert_eq!(options.compute(), vec![ + Size { width: 178, height: 100 }, + ]); + + options.aspect_ratio = Ratio::new(9, 16); + options.upscale = Upscale::Yes; + + assert_eq!(options.compute(), vec![ + Size { width: 360, height: 640 }, + Size { width: 720, height: 1280 }, + Size { width: 1080, height: 1920 }, + ]); + + options.upscale = Upscale::No; + assert_eq!(options.compute(), vec![]); + + options.upscale = Upscale::NoPreserveSource; + assert_eq!(options.compute(), vec![ + Size { width: 100, height: 178 }, + ]); + + options.input_width = 1920; + options.input_height = 1080; + options.upscale = Upscale::Yes; + + assert_eq!(options.compute(), vec![ + Size { width: 360, height: 640 }, + Size { width: 720, height: 1280 }, + Size { width: 1080, height: 1920 }, + ]); + + options.upscale = Upscale::No; + assert_eq!(options.compute(), vec![ + Size { width: 360, height: 640 }, + Size { width: 720, height: 1280 }, + Size { width: 1080, height: 1920 }, + ]); + + options.upscale = Upscale::NoPreserveSource; + assert_eq!(options.compute(), vec![ + Size { width: 360, height: 640 }, + Size { width: 720, height: 1280 }, + Size { width: 1080, height: 1920 }, + ]); + } + + #[test] + fn test_compute_scales_image_scaling() { + let mut options = ScalingOptions { + input_width: 112, + input_height: 112, + aspect_ratio: Ratio::new(3, 1), + preserve_aspect_width: true, + preserve_aspect_height: true, + upscale: Upscale::NoPreserveSource, + input_image_scaling: true, + clamp_aspect_ratio: true, + scales: vec![ + 32, + 64, + 96, + 128, + ], + }; + + assert_eq!(options.compute(), vec![ + Size { width: 28, height: 28 }, + Size { width: 56, height: 56 }, + Size { width: 84, height: 84 }, + Size { width: 112, height: 112 }, + ]); + + options.input_width = 112 * 2; + assert_eq!(options.compute(), vec![ + Size { width: 28 * 2, height: 28 }, + Size { width: 56 * 2, height: 56 }, + Size { width: 84 * 2, height: 84 }, + Size { width: 112 * 2, height: 112 }, + ]); + + options.input_width = 112 * 3; + assert_eq!(options.compute(), vec![ + Size { width: 28 * 3, height: 28 }, + Size { width: 56 * 3, height: 56 }, + Size { width: 84 * 3, height: 84 }, + Size { width: 112 * 3, height: 112 }, + ]); + + options.input_width = 112 * 4; + assert_eq!(options.compute(), vec![ + Size { width: 32 * 3, height: 24 }, + Size { width: 64 * 3, height: 48 }, + Size { width: 96 * 3, height: 72 }, + Size { width: 128 * 3, height: 96 }, + ]); + + options.input_width = 112 / 2; + assert_eq!(options.compute(), vec![ + Size { width: 28 / 2, height: 28 }, + Size { width: 56 / 2, height: 56 }, + Size { width: 84 / 2, height: 84 }, + Size { width: 112 / 2, height: 112 }, + ]); + + options.input_width = 112 / 3; + assert_eq!(options.compute(), vec![ + Size { width: 9, height: 28 }, + Size { width: 19, height: 56 }, + Size { width: 28, height: 84 }, + Size { width: 37, height: 112 }, + ]); + } + + #[test] + fn test_compute_scales_any_scale() { + let mut options = ScalingOptions { + input_width: 245, + input_height: 1239, + aspect_ratio: Ratio::new(1, 1), + preserve_aspect_width: true, + preserve_aspect_height: true, + upscale: Upscale::NoPreserveSource, + input_image_scaling: true, + clamp_aspect_ratio: true, + scales: vec![ + 720, + 1080, + ], + }; + + assert_eq!(options.compute(), vec![ + Size { width: 142, height: 720 }, + Size { width: 214, height: 1080 }, + ]); + + options.input_width = 1239; + options.input_height = 245; + + assert_eq!(options.compute(), vec![ + Size { width: 720, height: 142 }, + Size { width: 1080, height: 214 }, + ]); + + options.clamp_aspect_ratio = false; + options.input_image_scaling = false; + options.input_height = 1239; + options.input_width = 245; + + assert_eq!(options.compute(), vec![ + Size { width: 142, height: 720 }, + Size { width: 214, height: 1080 }, + ]); + + options.input_height = 245; + options.input_width = 1239; + + assert_eq!(options.compute(), vec![ + Size { width: 720, height: 142 }, + Size { width: 1080, height: 214 }, + ]); + } +} diff --git a/proto/scuffle/platform/internal/image_processor.proto b/proto/scuffle/platform/internal/image_processor.proto index 7356908b..87ce9a8b 100644 --- a/proto/scuffle/platform/internal/image_processor.proto +++ b/proto/scuffle/platform/internal/image_processor.proto @@ -7,7 +7,7 @@ import "scuffle/platform/internal/types/image_format.proto"; message Task { enum ResizeMethod { Fit = 0; - Exact = 1; + Stretch = 1; PadBottomLeft = 2; PadBottomRight = 3; PadTopLeft = 4; @@ -35,17 +35,30 @@ message Task { string input_path = 1; - uint32 base_width = 2; - uint32 base_height = 3; + message Ratio { + uint32 numerator = 1; + uint32 denominator = 2; + } + + Ratio aspect_ratio = 2; + bool clamp_aspect_ratio = 3; + + enum Upscale { + Yes = 0; + No = 1; + NoPreserveSource = 2; + } - repeated scuffle.platform.internal.types.ImageFormat formats = 4; - ResizeMethod resize_method = 5; - ResizeAlgorithm resize_algorithm = 6; - repeated uint32 scales = 7; + Upscale upscale = 4; - bool upscale = 8; + repeated scuffle.platform.internal.types.ImageFormat formats = 5; + ResizeMethod resize_method = 6; + ResizeAlgorithm resize_algorithm = 7; + + bool input_image_scaling = 8; + repeated uint32 scales = 9; - string output_prefix = 9; + string output_prefix = 10; message Limits { uint32 max_processing_time_ms = 1; @@ -55,7 +68,7 @@ message Task { uint32 max_input_duration_ms = 5; } - optional Limits limits = 10; + optional Limits limits = 11; - string callback_subject = 11; + string callback_subject = 12; } diff --git a/proto/scuffle/platform/internal/types/processed_image_variant.proto b/proto/scuffle/platform/internal/types/processed_image_variant.proto index d522251b..d234a013 100644 --- a/proto/scuffle/platform/internal/types/processed_image_variant.proto +++ b/proto/scuffle/platform/internal/types/processed_image_variant.proto @@ -8,7 +8,6 @@ message ProcessedImageVariant { uint32 width = 1; uint32 height = 2; ImageFormat format = 3; - uint32 scale = 4; - uint32 byte_size = 5; - string path = 6; + uint32 byte_size = 4; + string path = 5; } From b4d0544f3d24537bced8a2c4f2acf5e0d986e4d6 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Wed, 31 Jan 2024 00:09:34 +0000 Subject: [PATCH 02/21] fix: formatting --- .../api/src/api/v1/upload/profile_picture.rs | 7 +- platform/api/src/igdb_cron.rs | 5 +- platform/api/src/image_upload_callback.rs | 143 ++- .../image_processor/src/processor/job/mod.rs | 2 +- .../src/processor/job/process.rs | 16 +- .../src/processor/job/scaling.rs | 1137 +++++++++-------- 6 files changed, 748 insertions(+), 562 deletions(-) diff --git a/platform/api/src/api/v1/upload/profile_picture.rs b/platform/api/src/api/v1/upload/profile_picture.rs index c404b015..173404cd 100644 --- a/platform/api/src/api/v1/upload/profile_picture.rs +++ b/platform/api/src/api/v1/upload/profile_picture.rs @@ -47,12 +47,7 @@ fn create_task(file_id: Ulid, input_path: &str, config: &ImageUploaderConfig, ow resize_algorithm: image_processor::task::ResizeAlgorithm::Lanczos3 as i32, upscale: image_processor::task::Upscale::NoPreserveSource as i32, input_image_scaling: true, - scales: vec![ - 64, - 128, - 256, - 384, - ], + scales: vec![64, 128, 256, 384], resize_method: image_processor::task::ResizeMethod::PadCenter as i32, output_prefix: format!("{owner_id}/{file_id}"), } diff --git a/platform/api/src/igdb_cron.rs b/platform/api/src/igdb_cron.rs index d054eff7..9dfb82e7 100644 --- a/platform/api/src/igdb_cron.rs +++ b/platform/api/src/igdb_cron.rs @@ -589,10 +589,7 @@ fn create_task( callback_subject: config.callback_subject.clone(), upscale: image_processor::task::Upscale::NoPreserveSource as i32, output_prefix: format!("categories/{category_id}/{id}"), - scales: vec![ - 720, - 1080, - ], + scales: vec![720, 1080], input_image_scaling: true, limits: Some(image_processor::task::Limits { max_processing_time_ms: 60000, diff --git a/platform/api/src/image_upload_callback.rs b/platform/api/src/image_upload_callback.rs index 698eda05..cee82bb4 100644 --- a/platform/api/src/image_upload_callback.rs +++ b/platform/api/src/image_upload_callback.rs @@ -34,11 +34,14 @@ pub async fn run(global: Arc) -> anyhow::Result<()> { .context("failed to create profile picture stream")?; let image_upload_callback = image_upload_callback - .get_or_create_consumer(CONSUMER_NAME, async_nats::jetstream::consumer::pull::Config { - name: Some(CONSUMER_NAME.to_owned()), - ack_wait: Duration::from_secs(30), - ..Default::default() - }) + .get_or_create_consumer( + CONSUMER_NAME, + async_nats::jetstream::consumer::pull::Config { + name: Some(CONSUMER_NAME.to_owned()), + ack_wait: Duration::from_secs(30), + ..Default::default() + }, + ) .await .context("failed to create profile picture consumer")?; @@ -48,18 +51,27 @@ pub async fn run(global: Arc) -> anyhow::Result<()> { .context("failed to get profile picture consumer messages")?; while let Ok(message) = image_upload_consumer.next().context(global.ctx()).await { - let message = message.ok_or_else(|| anyhow::anyhow!("profile picture consumer closed"))?.context("failed to get profile picture consumer message")?; + let message = message + .ok_or_else(|| anyhow::anyhow!("profile picture consumer closed"))? + .context("failed to get profile picture consumer message")?; let (job_id, job_result) = match ProcessedImage::decode(message.payload.as_ref()) { - Ok(ProcessedImage { job_id, result: Some(result) }) => (job_id, result), + Ok(ProcessedImage { + job_id, + result: Some(result), + }) => (job_id, result), err => { if let Err(err) = err { tracing::warn!(error = %err, "failed to decode profile picture job result"); } else { tracing::warn!("malformed profile picture job result"); } - message.ack().await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + message + .ack() + .await + .map_err(|err| anyhow::anyhow!(err)) + .context("failed to ack")?; continue; - }, + } }; tracing::debug!("received profile picture job result: {:?}", job_result); @@ -67,28 +79,50 @@ pub async fn run(global: Arc) -> anyhow::Result<()> { processed_image::Result::Success(processed_image::Success { variants }) => { if let Err(err) = handle_success(&global, job_id.into_ulid(), variants).await { tracing::warn!(error = %err, "failed to handle profile picture job success"); - message.ack_with(AckKind::Nak(Some(Duration::from_secs(5)))).await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + message + .ack_with(AckKind::Nak(Some(Duration::from_secs(5)))) + .await + .map_err(|err| anyhow::anyhow!(err)) + .context("failed to ack")?; } else { - message.ack().await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + message + .ack() + .await + .map_err(|err| anyhow::anyhow!(err)) + .context("failed to ack")?; } - }, - processed_image::Result::Failure(processed_image::Failure { reason, friendly_message }) => { + } + processed_image::Result::Failure(processed_image::Failure { + reason, + friendly_message, + }) => { if let Err(err) = handle_failure(&global, job_id.into_ulid(), reason, friendly_message).await { tracing::warn!(error = %err, "failed to handle profile picture job failure"); - message.ack_with(AckKind::Nak(Some(Duration::from_secs(5)))).await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + message + .ack_with(AckKind::Nak(Some(Duration::from_secs(5)))) + .await + .map_err(|err| anyhow::anyhow!(err)) + .context("failed to ack")?; } else { - message.ack().await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + message + .ack() + .await + .map_err(|err| anyhow::anyhow!(err)) + .context("failed to ack")?; } - }, + } } - message.ack().await.map_err(|err| anyhow::anyhow!(err)).context("failed to ack")?; + message + .ack() + .await + .map_err(|err| anyhow::anyhow!(err)) + .context("failed to ack")?; } Ok(()) } - async fn handle_success( global: &Arc, job_id: ulid::Ulid, @@ -120,16 +154,20 @@ async fn handle_success( SubscriptionTopic::UploadedFileStatus(uploaded_file.id), pb::scuffle::platform::internal::events::UploadedFileStatus { file_id: Some(uploaded_file.id.into()), - status: Some(pb::scuffle::platform::internal::events::uploaded_file_status::Status::Success(pb::scuffle::platform::internal::events::uploaded_file_status::Success {})), - }.encode_to_vec().into(), + status: Some( + pb::scuffle::platform::internal::events::uploaded_file_status::Status::Success( + pb::scuffle::platform::internal::events::uploaded_file_status::Success {}, + ), + ), + } + .encode_to_vec() + .into(), ) .await .context("failed to publish file update event")?; match uploaded_file.ty { - FileType::CategoryArtwork | FileType::CategoryCover => { - - }, + FileType::CategoryArtwork | FileType::CategoryCover => {} FileType::ProfilePicture => { let user_updated = utils::database::query("UPDATE users SET profile_picture_id = $1, pending_profile_picture_id = NULL, updated_at = NOW() WHERE id = $2 AND pending_profile_picture_id = $1") .bind(uploaded_file.id) @@ -138,11 +176,13 @@ async fn handle_success( .execute(&tx) .await .context("failed to update user")? == 1; - + tx.commit().await.context("failed to commit transaction")?; - - let owner_id = uploaded_file.owner_id.ok_or_else(|| anyhow::anyhow!("uploaded file owner id is null"))?; - + + let owner_id = uploaded_file + .owner_id + .ok_or_else(|| anyhow::anyhow!("uploaded file owner id is null"))?; + if user_updated { global .nats() @@ -151,7 +191,9 @@ async fn handle_success( pb::scuffle::platform::internal::events::UserProfilePicture { user_id: Some(owner_id.into()), profile_picture_id: Some(uploaded_file.id.into()), - }.encode_to_vec().into(), + } + .encode_to_vec() + .into(), ) .await .context("failed to publish profile picture update event")?; @@ -190,11 +232,17 @@ async fn handle_failure( SubscriptionTopic::UploadedFileStatus(uploaded_file.id), pb::scuffle::platform::internal::events::UploadedFileStatus { file_id: Some(uploaded_file.id.into()), - status: Some(pb::scuffle::platform::internal::events::uploaded_file_status::Status::Failure(pb::scuffle::platform::internal::events::uploaded_file_status::Failure { - reason, - friendly_message, - })), - }.encode_to_vec().into(), + status: Some( + pb::scuffle::platform::internal::events::uploaded_file_status::Status::Failure( + pb::scuffle::platform::internal::events::uploaded_file_status::Failure { + reason, + friendly_message, + }, + ), + ), + } + .encode_to_vec() + .into(), ) .await .context("failed to publish file update event")?; @@ -202,20 +250,23 @@ async fn handle_failure( let update_count = match uploaded_file.ty { FileType::CategoryArtwork | FileType::CategoryCover => false, FileType::ProfilePicture => { - utils::database::query("UPDATE users SET pending_profile_picture_id = NULL, updated_at = NOW() WHERE id = $1 AND pending_profile_picture_id = $2") - .bind(uploaded_file.owner_id) - .bind(uploaded_file.id) - .build() - .execute(&tx) - .await - .context("failed to update user")? == 1 + utils::database::query( + "UPDATE users SET pending_profile_picture_id = NULL, updated_at = NOW() WHERE id = $1 AND pending_profile_picture_id = $2", + ) + .bind(uploaded_file.owner_id) + .bind(uploaded_file.id) + .build() + .execute(&tx) + .await + .context("failed to update user")? + == 1 } }; tx.commit().await.context("failed to commit transaction")?; match (uploaded_file.ty, update_count) { - (FileType::CategoryArtwork | FileType::CategoryCover, _) => {}, + (FileType::CategoryArtwork | FileType::CategoryCover, _) => {} (FileType::ProfilePicture, true) => { global .nats() @@ -224,13 +275,15 @@ async fn handle_failure( pb::scuffle::platform::internal::events::UserProfilePicture { user_id: Some(uploaded_file.owner_id.unwrap().into()), profile_picture_id: None, - }.encode_to_vec().into(), + } + .encode_to_vec() + .into(), ) .await .context("failed to publish profile picture update event")?; - }, - (FileType::ProfilePicture, false) => {}, + } + (FileType::ProfilePicture, false) => {} } Ok(()) -} \ No newline at end of file +} diff --git a/platform/image_processor/src/processor/job/mod.rs b/platform/image_processor/src/processor/job/mod.rs index f4d6a41a..ec624ec3 100644 --- a/platform/image_processor/src/processor/job/mod.rs +++ b/platform/image_processor/src/processor/job/mod.rs @@ -27,8 +27,8 @@ pub(crate) mod libavif; pub(crate) mod libwebp; pub(crate) mod process; pub(crate) mod resize; -pub(crate) mod smart_object; pub(crate) mod scaling; +pub(crate) mod smart_object; pub(crate) struct Job<'a, G: ImageProcessorGlobal> { pub(crate) global: &'a Arc, diff --git a/platform/image_processor/src/processor/job/process.rs b/platform/image_processor/src/processor/job/process.rs index 09d3118f..bc09e1ee 100644 --- a/platform/image_processor/src/processor/job/process.rs +++ b/platform/image_processor/src/processor/job/process.rs @@ -12,7 +12,7 @@ use super::encoder::{AnyEncoder, Encoder, EncoderFrontend, EncoderSettings}; use super::resize::{ImageResizer, ImageResizerTarget}; use crate::database::Job; use crate::processor::error::{ProcessorError, Result}; -use crate::processor::job::scaling::{ScalingOptions, Ratio}; +use crate::processor::job::scaling::{Ratio, ScalingOptions}; #[derive(Debug)] #[allow(dead_code)] @@ -142,7 +142,7 @@ pub fn process_job(backend: DecoderBackend, job: &Job, data: Cow<'_, [u8]>) -> R task::ResizeMethod::PadLeft => (true, false), task::ResizeMethod::PadRight => (true, false), }; - + let upscale = job.task.upscale().into(); let scales = ScalingOptions { @@ -151,18 +151,24 @@ pub fn process_job(backend: DecoderBackend, job: &Job, data: Cow<'_, [u8]>) -> R input_image_scaling: job.task.input_image_scaling, clamp_aspect_ratio: job.task.clamp_aspect_ratio, scales, - aspect_ratio: job.task.aspect_ratio.as_ref().map(|r| Ratio::new(r.numerator as usize, r.denominator as usize)).unwrap_or(Ratio::ONE), + aspect_ratio: job + .task + .aspect_ratio + .as_ref() + .map(|r| Ratio::new(r.numerator as usize, r.denominator as usize)) + .unwrap_or(Ratio::ONE), upscale, preserve_aspect_height, preserve_aspect_width, - }.compute(); + } + .compute(); // let base_width = input_width as f64 / job.task.aspect_width as f64; let mut resizers = scales .iter() .map(|scale| { ( - scale.clone(), + *scale, ImageResizer::new(ImageResizerTarget { height: scale.height, width: scale.width, diff --git a/platform/image_processor/src/processor/job/scaling.rs b/platform/image_processor/src/processor/job/scaling.rs index e0e3c99c..7122863c 100644 --- a/platform/image_processor/src/processor/job/scaling.rs +++ b/platform/image_processor/src/processor/job/scaling.rs @@ -1,584 +1,719 @@ -use std::ops::MulAssign; +use std::ops::{Mul, MulAssign}; #[derive(Debug, Clone)] pub struct ScalingOptions { - pub input_width: usize, - pub input_height: usize, - pub aspect_ratio: Ratio, - pub clamp_aspect_ratio: bool, - pub preserve_aspect_width: bool, - pub preserve_aspect_height: bool, - pub upscale: Upscale, - pub input_image_scaling: bool, - pub scales: Vec, + pub input_width: usize, + pub input_height: usize, + pub aspect_ratio: Ratio, + pub clamp_aspect_ratio: bool, + pub preserve_aspect_width: bool, + pub preserve_aspect_height: bool, + pub upscale: Upscale, + pub input_image_scaling: bool, + pub scales: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Upscale { - Yes, - No, - NoPreserveSource, + Yes, + No, + NoPreserveSource, } impl From for Upscale { - fn from(value: pb::scuffle::platform::internal::image_processor::task::Upscale) -> Self { - match value { - pb::scuffle::platform::internal::image_processor::task::Upscale::Yes => Upscale::Yes, - pb::scuffle::platform::internal::image_processor::task::Upscale::No => Upscale::No, - pb::scuffle::platform::internal::image_processor::task::Upscale::NoPreserveSource => Upscale::NoPreserveSource, - } - } + fn from(value: pb::scuffle::platform::internal::image_processor::task::Upscale) -> Self { + match value { + pb::scuffle::platform::internal::image_processor::task::Upscale::Yes => Upscale::Yes, + pb::scuffle::platform::internal::image_processor::task::Upscale::No => Upscale::No, + pb::scuffle::platform::internal::image_processor::task::Upscale::NoPreserveSource => Upscale::NoPreserveSource, + } + } } impl Upscale { - pub fn is_yes(&self) -> bool { - matches!(self, Upscale::Yes) - } + pub fn is_yes(&self) -> bool { + matches!(self, Upscale::Yes) + } - pub fn is_no(&self) -> bool { - matches!(self, Upscale::No | Upscale::NoPreserveSource) - } + pub fn is_no(&self) -> bool { + matches!(self, Upscale::No | Upscale::NoPreserveSource) + } - pub fn preserve_source(&self) -> bool { - matches!(self, Upscale::NoPreserveSource) - } + pub fn preserve_source(&self) -> bool { + matches!(self, Upscale::NoPreserveSource) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Size { - pub width: T, - pub height: T, + pub width: T, + pub height: T, } #[derive(Debug, Clone, Copy)] pub struct Ratio { - n: usize, - d: usize, + n: usize, + d: usize, } impl Ratio { - pub const ONE: Self = Self::new(1, 1); + pub const ONE: Self = Self::new(1, 1); - pub const fn new(n: usize, d: usize) -> Self { - Self { n, d }.simplify() - } + pub const fn new(n: usize, d: usize) -> Self { + Self { n, d }.simplify() + } - const fn gcd(&self) -> usize { - let mut a = self.n; - let mut b = self.d; + const fn gcd(&self) -> usize { + let mut a = self.n; + let mut b = self.d; - while b != 0 { - let t = b; - b = a % b; - a = t; - } + while b != 0 { + let t = b; + b = a % b; + a = t; + } - a - } + a + } - const fn simplify(mut self) -> Self { - let gcd = self.gcd(); + const fn simplify(mut self) -> Self { + let gcd = self.gcd(); - self.n /= gcd; - self.d /= gcd; + self.n /= gcd; + self.d /= gcd; - self - } + self + } - fn as_f64(&self) -> f64 { - self.n as f64 / self.d as f64 - } + fn as_f64(&self) -> f64 { + self.n as f64 / self.d as f64 + } } impl std::ops::Div for Ratio { - type Output = Ratio; - - fn div(self, rhs: usize) -> Self::Output { - Self { - n: self.n, - d: self.d * rhs, - }.simplify() - } + type Output = Ratio; + + fn div(self, rhs: usize) -> Self::Output { + Self { + n: self.n, + d: self.d.mul(rhs), + } + .simplify() + } } impl std::ops::Mul for Ratio { - type Output = Ratio; - - fn mul(self, rhs: usize) -> Self::Output { - Self { - n: self.n * rhs, - d: self.d, - }.simplify() - } + type Output = Ratio; + + fn mul(self, rhs: usize) -> Self::Output { + Self { + n: self.n.mul(rhs), + d: self.d, + } + .simplify() + } } impl std::ops::Div for Ratio { - type Output = Ratio; - - fn div(self, rhs: Ratio) -> Self::Output { - Self { - n: self.n * rhs.d, - d: self.d * rhs.n, - }.simplify() - } + type Output = Ratio; + + fn div(self, rhs: Ratio) -> Self::Output { + Self { + n: self.n * rhs.d, + d: self.d * rhs.n, + } + .simplify() + } } impl std::ops::Mul for Ratio { - type Output = Ratio; - - fn mul(self, rhs: Ratio) -> Self::Output { - Self { - n: self.n * rhs.n, - d: self.d * rhs.d, - }.simplify() - } + type Output = Ratio; + + fn mul(self, rhs: Ratio) -> Self::Output { + Self { + n: self.n * rhs.n, + d: self.d * rhs.d, + } + .simplify() + } } impl PartialEq for Ratio { - fn eq(&self, other: &Self) -> bool { - let this = self.simplify(); - let other = other.simplify(); + fn eq(&self, other: &Self) -> bool { + let this = self.simplify(); + let other = other.simplify(); - this.n == other.n && this.d == other.d - } + this.n == other.n && this.d == other.d + } } impl Eq for Ratio {} impl PartialOrd for Ratio { - fn partial_cmp(&self, other: &Self) -> Option { - let this = self.simplify(); - let other = other.simplify(); - - Some((this.n * other.d).cmp(&(this.d * other.n))) - } + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } } impl Ord for Ratio { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - let this = self.simplify(); - let other = other.simplify(); + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let this = self.simplify(); + let other = other.simplify(); - (this.n * other.d).cmp(&(this.d * other.n)) - } + (this.n * other.d).cmp(&(this.d * other.n)) + } } impl MulAssign for Ratio { - fn mul_assign(&mut self, rhs: Self) { - *self = *self * rhs; - } + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } } impl std::ops::DivAssign for Ratio { - fn div_assign(&mut self, rhs: Self) { - *self = *self / rhs; - } + fn div_assign(&mut self, rhs: Self) { + *self = *self / rhs; + } } impl std::ops::Div for Size where - T: std::ops::Div + Copy, + T: std::ops::Div + Copy, { - type Output = Self; - - fn div(self, rhs: T) -> Self::Output { - Self { - width: self.width / rhs, - height: self.height / rhs, - } - } + type Output = Self; + + fn div(self, rhs: T) -> Self::Output { + Self { + width: self.width / rhs, + height: self.height / rhs, + } + } } impl std::ops::Mul for Size where - T: std::ops::Mul + Copy, + T: std::ops::Mul + Copy, { - type Output = Self; - - fn mul(self, rhs: T) -> Self::Output { - Self { - width: self.width * rhs, - height: self.height * rhs, - } - } + type Output = Self; + + fn mul(self, rhs: T) -> Self::Output { + Self { + width: self.width * rhs, + height: self.height * rhs, + } + } } impl ScalingOptions { - pub fn compute(&mut self) -> Vec> { - // Sorts the scales from smallest to largest. - self.scales.sort_by(|a, b| a.partial_cmp(&b).unwrap()); - - let mut scales = self.compute_scales(); - let padded_size = self.padded_size(); - - let (best_idx, input_scale_factor) = scales.iter().position(|(size, _)| { - size.width >= padded_size.width || size.height >= padded_size.height - }).map(|idx| (idx, Ratio::ONE)).unwrap_or_else(|| { - let size = scales.last().unwrap().0; - - // Since its the padded size, the aspect ratio is the same as the target aspect ratio. - let input_scale_factor = padded_size.width / size.width; - - (scales.len() - 1, input_scale_factor) - }); - - dbg!(&scales); - - if self.input_image_scaling { - let scaled_width = padded_size.width / scales[best_idx].1 / input_scale_factor; - let scaled_height = padded_size.height / scales[best_idx].1 / input_scale_factor; - scales.iter_mut().for_each(|(size, scale)| { - size.width = *scale * scaled_width; - size.height = *scale * scaled_height; - }); - }; - - - if self.upscale.preserve_source() { - let padded_size = padded_size / input_scale_factor; - - dbg!(&padded_size); - - let size = scales[best_idx].0; - - if size.width > padded_size.width || size.height > padded_size.height { - scales[best_idx].0 = padded_size; - } - } - - if self.clamp_aspect_ratio { - scales.iter_mut().for_each(|(size, scale)| { - let scale = *scale; - - if self.aspect_ratio < Ratio::ONE && size.height > scale / self.aspect_ratio { - let height = scale / self.aspect_ratio; - size.width *= height / size.height; - size.height = height; - } else if self.aspect_ratio > Ratio::ONE && size.width > scale * self.aspect_ratio { - let width = scale * self.aspect_ratio; - size.height *= width / size.width; - size.width = width; - } else if self.aspect_ratio == Ratio::ONE && size.width > scale { - size.height *= scale / size.width; - size.width = scale; - } else if self.aspect_ratio == Ratio::ONE && size.height > scale { - size.width *= scale / size.height; - size.height = scale; - } - - size.width = size.width.max(Ratio::ONE); - size.height = size.height.max(Ratio::ONE); - }); - } - - if self.upscale.is_no() { - scales.retain(|(size, _)| { - size.width <= padded_size.width && size.height <= padded_size.height - }); - } - - scales.into_iter().map(|(mut size, _)| { - let input_aspect_ratio = self.input_aspect_ratio(); - - if self.preserve_aspect_height && self.aspect_ratio <= Ratio::ONE { - let height = size.height * self.aspect_ratio / input_aspect_ratio; - // size.width *= size.height / height; - size.height = height; - } else if self.preserve_aspect_width && self.aspect_ratio >= Ratio::ONE { - let width = size.width * input_aspect_ratio / self.aspect_ratio; - // size.height *= size.width / width; - size.width = width; - } - - Size { - width: size.width.as_f64().round() as usize, - height: size.height.as_f64().round() as usize, - } - }).collect() - } - - fn compute_scales(&self) -> Vec<(Size, Ratio)> { - self.scales.iter().copied().map(|scale| { - let scale = Ratio::new(scale, 1); - - let (width, height) = if self.aspect_ratio > Ratio::ONE { - (scale * self.aspect_ratio, scale) - } else { - (scale, scale / self.aspect_ratio) - }; - - (Size { width, height }, scale) - }).collect() - } - - fn input_aspect_ratio(&self) -> Ratio { - Ratio { - n: self.input_width, - d: self.input_height, - }.simplify() - } - - fn padded_size(&self) -> Size { - let width = Ratio::new(self.input_width, 1); - let height = Ratio::new(self.input_height, 1); - - let (width, height) = if self.aspect_ratio < Ratio::ONE { - (width, width / self.aspect_ratio) - } else { - (height * self.aspect_ratio, height) - }; - - Size { width, height } - } + pub fn compute(&mut self) -> Vec> { + // Sorts the scales from smallest to largest. + self.scales.sort_by(|a, b| a.partial_cmp(b).unwrap()); + + let mut scales = self.compute_scales(); + let padded_size = self.padded_size(); + + let (best_idx, input_scale_factor) = scales + .iter() + .position(|(size, _)| size.width >= padded_size.width || size.height >= padded_size.height) + .map(|idx| (idx, Ratio::ONE)) + .unwrap_or_else(|| { + let size = scales.last().unwrap().0; + + // Since its the padded size, the aspect ratio is the same as the target aspect + // ratio. + let input_scale_factor = padded_size.width / size.width; + + (scales.len() - 1, input_scale_factor) + }); + + dbg!(&scales); + + if self.input_image_scaling { + let scaled_width = padded_size.width / scales[best_idx].1 / input_scale_factor; + let scaled_height = padded_size.height / scales[best_idx].1 / input_scale_factor; + scales.iter_mut().for_each(|(size, scale)| { + size.width = *scale * scaled_width; + size.height = *scale * scaled_height; + }); + }; + + if self.upscale.preserve_source() { + let padded_size = padded_size / input_scale_factor; + + dbg!(&padded_size); + + let size = scales[best_idx].0; + + if size.width > padded_size.width || size.height > padded_size.height { + scales[best_idx].0 = padded_size; + } + } + + if self.clamp_aspect_ratio { + scales.iter_mut().for_each(|(size, scale)| { + let scale = *scale; + + if self.aspect_ratio < Ratio::ONE && size.height > scale / self.aspect_ratio { + let height = scale / self.aspect_ratio; + size.width *= height / size.height; + size.height = height; + } else if self.aspect_ratio > Ratio::ONE && size.width > scale * self.aspect_ratio { + let width = scale * self.aspect_ratio; + size.height *= width / size.width; + size.width = width; + } else if self.aspect_ratio == Ratio::ONE && size.width > scale { + size.height *= scale / size.width; + size.width = scale; + } else if self.aspect_ratio == Ratio::ONE && size.height > scale { + size.width *= scale / size.height; + size.height = scale; + } + + size.width = size.width.max(Ratio::ONE); + size.height = size.height.max(Ratio::ONE); + }); + } + + if self.upscale.is_no() { + scales.retain(|(size, _)| size.width <= padded_size.width && size.height <= padded_size.height); + } + + scales + .into_iter() + .map(|(mut size, _)| { + let input_aspect_ratio = self.input_aspect_ratio(); + + if self.preserve_aspect_height && self.aspect_ratio <= Ratio::ONE { + let height = size.height * self.aspect_ratio / input_aspect_ratio; + // size.width *= size.height / height; + size.height = height; + } else if self.preserve_aspect_width && self.aspect_ratio >= Ratio::ONE { + let width = size.width * input_aspect_ratio / self.aspect_ratio; + // size.height *= size.width / width; + size.width = width; + } + + Size { + width: size.width.as_f64().round() as usize, + height: size.height.as_f64().round() as usize, + } + }) + .collect() + } + + fn compute_scales(&self) -> Vec<(Size, Ratio)> { + self.scales + .iter() + .copied() + .map(|scale| { + let scale = Ratio::new(scale, 1); + + let (width, height) = if self.aspect_ratio > Ratio::ONE { + (scale * self.aspect_ratio, scale) + } else { + (scale, scale / self.aspect_ratio) + }; + + (Size { width, height }, scale) + }) + .collect() + } + + fn input_aspect_ratio(&self) -> Ratio { + Ratio { + n: self.input_width, + d: self.input_height, + } + .simplify() + } + + fn padded_size(&self) -> Size { + let width = Ratio::new(self.input_width, 1); + let height = Ratio::new(self.input_height, 1); + + let (width, height) = if self.aspect_ratio < Ratio::ONE { + (width, width / self.aspect_ratio) + } else { + (height * self.aspect_ratio, height) + }; + + Size { width, height } + } } #[cfg(test)] mod tests { - use super::*; - - #[test] - fn test_compute_scales_same_aspect() { - let mut options = ScalingOptions { - input_width: 100, - input_height: 100, - aspect_ratio: Ratio::new(1, 1), - preserve_aspect_width: false, - preserve_aspect_height: false, - upscale: Upscale::Yes, - input_image_scaling: false, - clamp_aspect_ratio: true, - scales: vec![ - 32, - 64, - 96, - 128, - ], - }; - - assert_eq!(options.compute(), vec![ - Size { width: 32, height: 32 }, - Size { width: 64, height: 64 }, - Size { width: 96, height: 96 }, - Size { width: 128, height: 128 }, - ]); - - options.upscale = Upscale::No; - - assert_eq!(options.compute(), vec![ - Size { width: 32, height: 32 }, - Size { width: 64, height: 64 }, - Size { width: 96, height: 96 }, - ]); - - options.upscale = Upscale::NoPreserveSource; - - assert_eq!(options.compute(), vec![ - Size { width: 32, height: 32 }, - Size { width: 64, height: 64 }, - Size { width: 96, height: 96 }, - Size { width: 100, height: 100 }, - ]); - - options.input_height = 112; - options.input_width = 112; - options.input_image_scaling = true; - options.upscale = Upscale::No; - - assert_eq!(options.compute(), vec![ - Size { width: 28, height: 28 }, - Size { width: 56, height: 56 }, - Size { width: 84, height: 84 }, - Size { width: 112, height: 112 }, - ]); - } - - #[test] - fn test_compute_scales_different_aspect() { - let mut options = ScalingOptions { - input_width: 100, - input_height: 100, - aspect_ratio: Ratio::new(16, 9), - preserve_aspect_width: false, - preserve_aspect_height: false, - upscale: Upscale::Yes, - input_image_scaling: false, - clamp_aspect_ratio: true, - scales: vec![ - 360, - 720, - 1080, - ], - }; - - assert_eq!(options.compute(), vec![ - Size { width: 640, height: 360 }, - Size { width: 1280, height: 720 }, - Size { width: 1920, height: 1080 }, - ]); - - options.upscale = Upscale::No; - assert_eq!(options.compute(), vec![]); - - options.upscale = Upscale::NoPreserveSource; - assert_eq!(options.compute(), vec![ - Size { width: 178, height: 100 }, - ]); - - options.aspect_ratio = Ratio::new(9, 16); - options.upscale = Upscale::Yes; - - assert_eq!(options.compute(), vec![ - Size { width: 360, height: 640 }, - Size { width: 720, height: 1280 }, - Size { width: 1080, height: 1920 }, - ]); - - options.upscale = Upscale::No; - assert_eq!(options.compute(), vec![]); - - options.upscale = Upscale::NoPreserveSource; - assert_eq!(options.compute(), vec![ - Size { width: 100, height: 178 }, - ]); - - options.input_width = 1920; - options.input_height = 1080; - options.upscale = Upscale::Yes; - - assert_eq!(options.compute(), vec![ - Size { width: 360, height: 640 }, - Size { width: 720, height: 1280 }, - Size { width: 1080, height: 1920 }, - ]); - - options.upscale = Upscale::No; - assert_eq!(options.compute(), vec![ - Size { width: 360, height: 640 }, - Size { width: 720, height: 1280 }, - Size { width: 1080, height: 1920 }, - ]); - - options.upscale = Upscale::NoPreserveSource; - assert_eq!(options.compute(), vec![ - Size { width: 360, height: 640 }, - Size { width: 720, height: 1280 }, - Size { width: 1080, height: 1920 }, - ]); - } - - #[test] - fn test_compute_scales_image_scaling() { - let mut options = ScalingOptions { - input_width: 112, - input_height: 112, - aspect_ratio: Ratio::new(3, 1), - preserve_aspect_width: true, - preserve_aspect_height: true, - upscale: Upscale::NoPreserveSource, - input_image_scaling: true, - clamp_aspect_ratio: true, - scales: vec![ - 32, - 64, - 96, - 128, - ], - }; - - assert_eq!(options.compute(), vec![ - Size { width: 28, height: 28 }, - Size { width: 56, height: 56 }, - Size { width: 84, height: 84 }, - Size { width: 112, height: 112 }, - ]); - - options.input_width = 112 * 2; - assert_eq!(options.compute(), vec![ - Size { width: 28 * 2, height: 28 }, - Size { width: 56 * 2, height: 56 }, - Size { width: 84 * 2, height: 84 }, - Size { width: 112 * 2, height: 112 }, - ]); - - options.input_width = 112 * 3; - assert_eq!(options.compute(), vec![ - Size { width: 28 * 3, height: 28 }, - Size { width: 56 * 3, height: 56 }, - Size { width: 84 * 3, height: 84 }, - Size { width: 112 * 3, height: 112 }, - ]); - - options.input_width = 112 * 4; - assert_eq!(options.compute(), vec![ - Size { width: 32 * 3, height: 24 }, - Size { width: 64 * 3, height: 48 }, - Size { width: 96 * 3, height: 72 }, - Size { width: 128 * 3, height: 96 }, - ]); - - options.input_width = 112 / 2; - assert_eq!(options.compute(), vec![ - Size { width: 28 / 2, height: 28 }, - Size { width: 56 / 2, height: 56 }, - Size { width: 84 / 2, height: 84 }, - Size { width: 112 / 2, height: 112 }, - ]); - - options.input_width = 112 / 3; - assert_eq!(options.compute(), vec![ - Size { width: 9, height: 28 }, - Size { width: 19, height: 56 }, - Size { width: 28, height: 84 }, - Size { width: 37, height: 112 }, - ]); - } - - #[test] - fn test_compute_scales_any_scale() { - let mut options = ScalingOptions { - input_width: 245, - input_height: 1239, - aspect_ratio: Ratio::new(1, 1), - preserve_aspect_width: true, - preserve_aspect_height: true, - upscale: Upscale::NoPreserveSource, - input_image_scaling: true, - clamp_aspect_ratio: true, - scales: vec![ - 720, - 1080, - ], - }; - - assert_eq!(options.compute(), vec![ - Size { width: 142, height: 720 }, - Size { width: 214, height: 1080 }, - ]); - - options.input_width = 1239; - options.input_height = 245; - - assert_eq!(options.compute(), vec![ - Size { width: 720, height: 142 }, - Size { width: 1080, height: 214 }, - ]); - - options.clamp_aspect_ratio = false; - options.input_image_scaling = false; - options.input_height = 1239; - options.input_width = 245; - - assert_eq!(options.compute(), vec![ - Size { width: 142, height: 720 }, - Size { width: 214, height: 1080 }, - ]); - - options.input_height = 245; - options.input_width = 1239; - - assert_eq!(options.compute(), vec![ - Size { width: 720, height: 142 }, - Size { width: 1080, height: 214 }, - ]); - } + use super::*; + + #[test] + fn test_compute_scales_same_aspect() { + let mut options = ScalingOptions { + input_width: 100, + input_height: 100, + aspect_ratio: Ratio::new(1, 1), + preserve_aspect_width: false, + preserve_aspect_height: false, + upscale: Upscale::Yes, + input_image_scaling: false, + clamp_aspect_ratio: true, + scales: vec![32, 64, 96, 128], + }; + + assert_eq!( + options.compute(), + vec![ + Size { width: 32, height: 32 }, + Size { width: 64, height: 64 }, + Size { width: 96, height: 96 }, + Size { width: 128, height: 128 }, + ] + ); + + options.upscale = Upscale::No; + + assert_eq!( + options.compute(), + vec![ + Size { width: 32, height: 32 }, + Size { width: 64, height: 64 }, + Size { width: 96, height: 96 }, + ] + ); + + options.upscale = Upscale::NoPreserveSource; + + assert_eq!( + options.compute(), + vec![ + Size { width: 32, height: 32 }, + Size { width: 64, height: 64 }, + Size { width: 96, height: 96 }, + Size { width: 100, height: 100 }, + ] + ); + + options.input_height = 112; + options.input_width = 112; + options.input_image_scaling = true; + options.upscale = Upscale::No; + + assert_eq!( + options.compute(), + vec![ + Size { width: 28, height: 28 }, + Size { width: 56, height: 56 }, + Size { width: 84, height: 84 }, + Size { width: 112, height: 112 }, + ] + ); + } + + #[test] + fn test_compute_scales_different_aspect() { + let mut options = ScalingOptions { + input_width: 100, + input_height: 100, + aspect_ratio: Ratio::new(16, 9), + preserve_aspect_width: false, + preserve_aspect_height: false, + upscale: Upscale::Yes, + input_image_scaling: false, + clamp_aspect_ratio: true, + scales: vec![360, 720, 1080], + }; + + assert_eq!( + options.compute(), + vec![ + Size { width: 640, height: 360 }, + Size { + width: 1280, + height: 720 + }, + Size { + width: 1920, + height: 1080 + }, + ] + ); + + options.upscale = Upscale::No; + assert_eq!(options.compute(), vec![]); + + options.upscale = Upscale::NoPreserveSource; + assert_eq!(options.compute(), vec![Size { width: 178, height: 100 },]); + + options.aspect_ratio = Ratio::new(9, 16); + options.upscale = Upscale::Yes; + + assert_eq!( + options.compute(), + vec![ + Size { width: 360, height: 640 }, + Size { + width: 720, + height: 1280 + }, + Size { + width: 1080, + height: 1920 + }, + ] + ); + + options.upscale = Upscale::No; + assert_eq!(options.compute(), vec![]); + + options.upscale = Upscale::NoPreserveSource; + assert_eq!(options.compute(), vec![Size { width: 100, height: 178 },]); + + options.input_width = 1920; + options.input_height = 1080; + options.upscale = Upscale::Yes; + + assert_eq!( + options.compute(), + vec![ + Size { width: 360, height: 640 }, + Size { + width: 720, + height: 1280 + }, + Size { + width: 1080, + height: 1920 + }, + ] + ); + + options.upscale = Upscale::No; + assert_eq!( + options.compute(), + vec![ + Size { width: 360, height: 640 }, + Size { + width: 720, + height: 1280 + }, + Size { + width: 1080, + height: 1920 + }, + ] + ); + + options.upscale = Upscale::NoPreserveSource; + assert_eq!( + options.compute(), + vec![ + Size { width: 360, height: 640 }, + Size { + width: 720, + height: 1280 + }, + Size { + width: 1080, + height: 1920 + }, + ] + ); + } + + #[test] + fn test_compute_scales_image_scaling() { + let mut options = ScalingOptions { + input_width: 112, + input_height: 112, + aspect_ratio: Ratio::new(3, 1), + preserve_aspect_width: true, + preserve_aspect_height: true, + upscale: Upscale::NoPreserveSource, + input_image_scaling: true, + clamp_aspect_ratio: true, + scales: vec![32, 64, 96, 128], + }; + + assert_eq!( + options.compute(), + vec![ + Size { width: 28, height: 28 }, + Size { width: 56, height: 56 }, + Size { width: 84, height: 84 }, + Size { width: 112, height: 112 }, + ] + ); + + options.input_width = 112 * 2; + assert_eq!( + options.compute(), + vec![ + Size { + width: 28 * 2, + height: 28 + }, + Size { + width: 56 * 2, + height: 56 + }, + Size { + width: 84 * 2, + height: 84 + }, + Size { + width: 112 * 2, + height: 112 + }, + ] + ); + + options.input_width = 112 * 3; + assert_eq!( + options.compute(), + vec![ + Size { + width: 28 * 3, + height: 28 + }, + Size { + width: 56 * 3, + height: 56 + }, + Size { + width: 84 * 3, + height: 84 + }, + Size { + width: 112 * 3, + height: 112 + }, + ] + ); + + options.input_width = 112 * 4; + assert_eq!( + options.compute(), + vec![ + Size { + width: 32 * 3, + height: 24 + }, + Size { + width: 64 * 3, + height: 48 + }, + Size { + width: 96 * 3, + height: 72 + }, + Size { + width: 128 * 3, + height: 96 + }, + ] + ); + + options.input_width = 112 / 2; + assert_eq!( + options.compute(), + vec![ + Size { + width: 28 / 2, + height: 28 + }, + Size { + width: 56 / 2, + height: 56 + }, + Size { + width: 84 / 2, + height: 84 + }, + Size { + width: 112 / 2, + height: 112 + }, + ] + ); + + options.input_width = 112 / 3; + assert_eq!( + options.compute(), + vec![ + Size { width: 9, height: 28 }, + Size { width: 19, height: 56 }, + Size { width: 28, height: 84 }, + Size { width: 37, height: 112 }, + ] + ); + } + + #[test] + fn test_compute_scales_any_scale() { + let mut options = ScalingOptions { + input_width: 245, + input_height: 1239, + aspect_ratio: Ratio::new(1, 1), + preserve_aspect_width: true, + preserve_aspect_height: true, + upscale: Upscale::NoPreserveSource, + input_image_scaling: true, + clamp_aspect_ratio: true, + scales: vec![720, 1080], + }; + + assert_eq!( + options.compute(), + vec![ + Size { width: 142, height: 720 }, + Size { + width: 214, + height: 1080 + }, + ] + ); + + options.input_width = 1239; + options.input_height = 245; + + assert_eq!( + options.compute(), + vec![ + Size { width: 720, height: 142 }, + Size { + width: 1080, + height: 214 + }, + ] + ); + + options.clamp_aspect_ratio = false; + options.input_image_scaling = false; + options.input_height = 1239; + options.input_width = 245; + + assert_eq!( + options.compute(), + vec![ + Size { width: 142, height: 720 }, + Size { + width: 214, + height: 1080 + }, + ] + ); + + options.input_height = 245; + options.input_width = 1239; + + assert_eq!( + options.compute(), + vec![ + Size { width: 720, height: 142 }, + Size { + width: 1080, + height: 214 + }, + ] + ); + } } From 1bd3a2c62777a71e4be64a822ab07fbac8c2d0a7 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Fri, 2 Feb 2024 16:43:16 +0000 Subject: [PATCH 03/21] fix: scaling calc --- .../src/processor/job/scaling.rs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/platform/image_processor/src/processor/job/scaling.rs b/platform/image_processor/src/processor/job/scaling.rs index 7122863c..e8d47fd4 100644 --- a/platform/image_processor/src/processor/job/scaling.rs +++ b/platform/image_processor/src/processor/job/scaling.rs @@ -226,8 +226,7 @@ impl ScalingOptions { (scales.len() - 1, input_scale_factor) }); - dbg!(&scales); - + if self.input_image_scaling { let scaled_width = padded_size.width / scales[best_idx].1 / input_scale_factor; let scaled_height = padded_size.height / scales[best_idx].1 / input_scale_factor; @@ -240,8 +239,6 @@ impl ScalingOptions { if self.upscale.preserve_source() { let padded_size = padded_size / input_scale_factor; - dbg!(&padded_size); - let size = scales[best_idx].0; if size.width > padded_size.width || size.height > padded_size.height { @@ -280,7 +277,7 @@ impl ScalingOptions { scales .into_iter() - .map(|(mut size, _)| { + .map(|(mut size, scale)| { let input_aspect_ratio = self.input_aspect_ratio(); if self.preserve_aspect_height && self.aspect_ratio <= Ratio::ONE { @@ -293,6 +290,23 @@ impl ScalingOptions { size.width = width; } + if self.preserve_aspect_height || self.preserve_aspect_width { + // need to make sure the new sizes are not larger than the max for this scale + let (width, height) = if self.aspect_ratio > Ratio::ONE { + (scale * self.aspect_ratio, scale) + } else { + (scale, scale / self.aspect_ratio) + }; + + if size.width > width { + size.height *= width / size.width; + size.width = width; + } else if size.height > height { + size.width *= height / size.height; + size.height = height; + } + } + Size { width: size.width.as_f64().round() as usize, height: size.height.as_f64().round() as usize, @@ -333,8 +347,11 @@ impl ScalingOptions { let (width, height) = if self.aspect_ratio < Ratio::ONE { (width, width / self.aspect_ratio) - } else { + } else if self.aspect_ratio > Ratio::ONE { (height * self.aspect_ratio, height) + } else { + let max = width.max(height); + (max, max) }; Size { width, height } From a01bbd36866e108cf80650e5bdf9c0fe0f155988 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Fri, 2 Feb 2024 16:48:38 +0000 Subject: [PATCH 04/21] fix: into_iter --- platform/image_processor/src/processor/job/process.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/image_processor/src/processor/job/process.rs b/platform/image_processor/src/processor/job/process.rs index bc09e1ee..0dc0d26c 100644 --- a/platform/image_processor/src/processor/job/process.rs +++ b/platform/image_processor/src/processor/job/process.rs @@ -165,10 +165,10 @@ pub fn process_job(backend: DecoderBackend, job: &Job, data: Cow<'_, [u8]>) -> R // let base_width = input_width as f64 / job.task.aspect_width as f64; let mut resizers = scales - .iter() + .into_iter() .map(|scale| { ( - *scale, + scale, ImageResizer::new(ImageResizerTarget { height: scale.height, width: scale.width, From 9f6c27faa926fcff3c27396c9b461747805d9c87 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Fri, 12 Apr 2024 18:36:35 +0000 Subject: [PATCH 05/21] chore: move imageprocessor --- Cargo.lock | 816 +++++++++++------- Cargo.toml | 2 +- binary-helper/Cargo.toml | 9 +- binary-helper/src/global.rs | 14 +- ffmpeg/Cargo.toml | 2 +- image_processor/APACHE2_LICENSE | 1 + .../Cargo.toml | 4 +- image_processor/MIT_LICENSE | 1 + .../migrations/0001_initial.down.sql | 1 + .../migrations/0001_initial.up.sql | 10 + .../src/config.rs | 0 .../src/database.rs | 0 .../src/global.rs | 0 .../src/grpc.rs | 0 .../src/lib.rs | 1 + .../src/main.rs | 0 image_processor/src/migration/0001_initial.rs | 54 ++ image_processor/src/migration/mod.rs | 94 ++ .../src/processor/error.rs | 0 .../src/processor/job/decoder/ffmpeg.rs | 0 .../src/processor/job/decoder/libavif.rs | 0 .../src/processor/job/decoder/libwebp.rs | 0 .../src/processor/job/decoder/mod.rs | 0 .../src/processor/job/encoder/gifski.rs | 0 .../src/processor/job/encoder/libavif.rs | 0 .../src/processor/job/encoder/libwebp.rs | 0 .../src/processor/job/encoder/mod.rs | 0 .../src/processor/job/encoder/png.rs | 0 .../src/processor/job/frame.rs | 0 .../src/processor/job/libavif.rs | 0 .../src/processor/job/libwebp.rs | 0 .../src/processor/job/mod.rs | 0 .../src/processor/job/process.rs | 0 .../src/processor/job/resize.rs | 0 .../src/processor/job/scaling.rs | 1 - .../src/processor/job/smart_object.rs | 0 .../src/processor/mod.rs | 0 .../src/processor/utils.rs | 0 .../src/tests/global.rs | 0 .../src/tests/mod.rs | 0 .../src/tests/processor/decoder.rs | 0 .../src/tests/processor/encoder.rs | 0 .../src/tests/processor/mod.rs | 0 .../src/tests/processor/resize.rs | 0 .../src/tests/utils.rs | 0 platform/api/Cargo.toml | 10 +- platform/image_processor/APACHE2_LICENSE | 1 - platform/image_processor/MIT_LICENSE | 1 - utils/Cargo.toml | 2 +- video/api/Cargo.toml | 4 +- video/cli/Cargo.toml | 4 +- video/common/Cargo.toml | 2 +- video/edge/Cargo.toml | 6 +- video/ingest/Cargo.toml | 8 +- video/transcoder/Cargo.toml | 4 +- .../src/transcoder/job/screenshot.rs | 2 +- 56 files changed, 725 insertions(+), 329 deletions(-) create mode 120000 image_processor/APACHE2_LICENSE rename {platform/image_processor => image_processor}/Cargo.toml (93%) create mode 120000 image_processor/MIT_LICENSE create mode 100644 image_processor/migrations/0001_initial.down.sql create mode 100644 image_processor/migrations/0001_initial.up.sql rename {platform/image_processor => image_processor}/src/config.rs (100%) rename {platform/image_processor => image_processor}/src/database.rs (100%) rename {platform/image_processor => image_processor}/src/global.rs (100%) rename {platform/image_processor => image_processor}/src/grpc.rs (100%) rename {platform/image_processor => image_processor}/src/lib.rs (85%) rename {platform/image_processor => image_processor}/src/main.rs (100%) create mode 100644 image_processor/src/migration/0001_initial.rs create mode 100644 image_processor/src/migration/mod.rs rename {platform/image_processor => image_processor}/src/processor/error.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/decoder/ffmpeg.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/decoder/libavif.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/decoder/libwebp.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/decoder/mod.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/encoder/gifski.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/encoder/libavif.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/encoder/libwebp.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/encoder/mod.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/encoder/png.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/frame.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/libavif.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/libwebp.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/mod.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/process.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/resize.rs (100%) rename {platform/image_processor => image_processor}/src/processor/job/scaling.rs (99%) rename {platform/image_processor => image_processor}/src/processor/job/smart_object.rs (100%) rename {platform/image_processor => image_processor}/src/processor/mod.rs (100%) rename {platform/image_processor => image_processor}/src/processor/utils.rs (100%) rename {platform/image_processor => image_processor}/src/tests/global.rs (100%) rename {platform/image_processor => image_processor}/src/tests/mod.rs (100%) rename {platform/image_processor => image_processor}/src/tests/processor/decoder.rs (100%) rename {platform/image_processor => image_processor}/src/tests/processor/encoder.rs (100%) rename {platform/image_processor => image_processor}/src/tests/processor/mod.rs (100%) rename {platform/image_processor => image_processor}/src/tests/processor/resize.rs (100%) rename {platform/image_processor => image_processor}/src/tests/utils.rs (100%) delete mode 120000 platform/image_processor/APACHE2_LICENSE delete mode 120000 platform/image_processor/MIT_LICENSE diff --git a/Cargo.lock b/Cargo.lock index 074822f2..6106e199 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,7 +56,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", "once_cell", "version_check", "zerocopy", @@ -79,9 +78,9 @@ checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "amf0" @@ -111,47 +110,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -186,7 +186,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -264,7 +264,7 @@ dependencies = [ "proc-macro2", "quote", "strum", - "syn 2.0.58", + "syn 2.0.60", "thiserror", ] @@ -294,25 +294,24 @@ dependencies = [ [[package]] name = "async-nats" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc1f1a75fd07f0f517322d103211f12d757658e91676def9a2e688774656c60" +checksum = "eea7b126ebfa4db78e9e788b2a792b6329f35b4f2fdd56dbc646dedc2beec7a5" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", "futures", - "http 0.2.12", "memchr", "nkeys", "nuid", "once_cell", + "portable-atomic", "rand", "regex", "ring 0.17.8", - "rustls 0.21.12", - "rustls-native-certs 0.6.3", - "rustls-pemfile 1.0.4", - "rustls-webpki 0.101.7", + "rustls-native-certs 0.7.0", + "rustls-pemfile 2.1.2", + "rustls-webpki 0.102.3", "serde", "serde_json", "serde_nanos", @@ -320,9 +319,9 @@ dependencies = [ "thiserror", "time", "tokio", - "tokio-retry", - "tokio-rustls 0.24.1", + "tokio-rustls 0.25.0", "tracing", + "tryhard", "url", ] @@ -345,7 +344,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -356,7 +355,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -388,11 +387,20 @@ dependencies = [ "v_frame", ] +[[package]] +name = "avif-serialize" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +dependencies = [ + "arrayvec", +] + [[package]] name = "aws-config" -version = "1.1.10" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48730d0b4c3d91c43d0d37168831d9fd0e065ad4a889a2ee9faf8d34c3d2804d" +checksum = "baaa0be6ee7d90b775ae6ccb6d2ba182b91219ec2001f92338773a094246af1d" dependencies = [ "aws-credential-types", "aws-runtime", @@ -421,9 +429,9 @@ dependencies = [ [[package]] name = "aws-credential-types" -version = "1.1.8" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8587ae17c8e967e4b05a62d495be2fb7701bec52a97f7acfe8a29f938384c8" +checksum = "e16838e6c9e12125face1c1eff1343c75e3ff540de98ff7ebd61874a89bcfeb9" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", @@ -431,11 +439,38 @@ dependencies = [ "zeroize", ] +[[package]] +name = "aws-lc-rs" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8487b59d62764df8231cb371c459314df895b41756df457a1fb1243d65c89195" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c15eb61145320320eb919d9bab524617a7aa4216c78d342fae3a758bc33073e4" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "aws-runtime" -version = "1.1.9" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4ee6903f9d0197510eb6b44c4d86b493011d08b4992938f7b9be0333b6685aa" +checksum = "f4963ac9ff2d33a4231b3806c1c69f578f221a9cabb89ad2bde62ce2b442c8a7" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -457,9 +492,9 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.22.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644c5939c1b78097d37f3341708978d68490070d4b0f8fa91f0878678c06a7ef" +checksum = "cedc97499da49c3e36cde578340f9925284685073cb3e512aaf9ab16cd9a2541" dependencies = [ "ahash 0.8.11", "aws-credential-types", @@ -492,9 +527,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.19.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2be5ba83b077b67a6f7a1927eb6b212bf556e33bd74b5eaa5aa6e421910803a" +checksum = "ca3d6c4cba4e009391b72b0fcf12aff04ea3c9c3aa2ecaafa330326a8bd7e601" dependencies = [ "aws-credential-types", "aws-runtime", @@ -514,9 +549,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.19.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022ca669825f841aef17b12d4354ef2b8651e4664be49f2d9ea13e4062a80c9f" +checksum = "73400dc239d14f63d932f4ca7b55af5e9ef1f857f7d70655249ccc287adb2570" dependencies = [ "aws-credential-types", "aws-runtime", @@ -536,9 +571,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.19.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e4a5f5cb007347c1ab34a6d56456301dfada921fc9e57d687ecb08baddd11ff" +checksum = "10f8858308af76fba3e5ffcf1bb56af5471574d2bdfaf0159470c25bc2f760e5" dependencies = [ "aws-credential-types", "aws-runtime", @@ -559,9 +594,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d6f29688a4be9895c0ba8bef861ad0c0dac5c15e9618b9b7a6c233990fc263" +checksum = "58b56f1cbe6fd4d0c2573df72868f20ab1c125ca9c9dbce17927a463433a2e57" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -631,9 +666,9 @@ dependencies = [ [[package]] name = "aws-smithy-http" -version = "0.60.7" +version = "0.60.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f10fa66956f01540051b0aa7ad54574640f748f9839e843442d99b970d3aff9" +checksum = "4a7de001a1b9a25601016d8057ea16e31a45fdca3751304c8edf4ad72e706c08" dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", @@ -671,9 +706,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de34bcfa1fb3c82a80e252a753db34a6658e07f23d3a5b3fc96919518fa7a3f5" +checksum = "1cf64e73ef8d4dac6c933230d56d136b75b252edcf82ed36e37d603090cd7348" dependencies = [ "aws-smithy-async", "aws-smithy-http", @@ -686,7 +721,7 @@ dependencies = [ "http-body 0.4.6", "http-body 1.0.0", "hyper 0.14.28", - "hyper-rustls", + "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", "pin-utils", @@ -697,9 +732,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cc56a5c96ec741de6c5e6bf1ce6948be969d6506dfa9c39cffc284e31e4979b" +checksum = "8c19fdae6e3d5ac9cd01f2d6e6c359c5f5a3e028c2d148a8f5b90bf3399a18a7" dependencies = [ "aws-smithy-async", "aws-smithy-types", @@ -740,18 +775,18 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.7" +version = "0.60.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872c68cf019c0e4afc5de7753c4f7288ce4b71663212771bf5e4542eb9346ca9" +checksum = "d123fbc2a4adc3c301652ba8e149bf4bc1d1725affb9784eb20c953ace06bf55" dependencies = [ "xmlparser", ] [[package]] name = "aws-types" -version = "1.1.9" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb278e322f16f59630a83b6b2dc992a0b48aa74ed47b4130f193fae0053d713" +checksum = "5a43b56df2c529fe44cb4d92bd64d0479883fb9608ff62daede4df5405381814" dependencies = [ "aws-credential-types", "aws-smithy-async", @@ -803,7 +838,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "hyper-util", "itoa", "matchit", @@ -909,9 +944,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64-simd" @@ -956,14 +991,14 @@ dependencies = [ "fred", "futures-util", "http-body 1.0.0", - "hyper 1.2.0", + "hyper 1.3.1", "once_cell", "pb", "pin-project", "postgres-from-row", "postgres-types", "prost 0.12.4", - "rustls 0.22.3", + "rustls 0.23.5", "rustls-pemfile 2.1.2", "scuffle-config", "scuffle-utils", @@ -972,6 +1007,7 @@ dependencies = [ "tokio", "tokio-postgres", "tokio-postgres-rustls", + "tokio-rustls 0.25.0", "tonic", "tower-layer", "tracing", @@ -981,22 +1017,25 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.64.0" +version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "cexpr", "clang-sys", + "itertools 0.12.1", "lazy_static", "lazycell", - "peeking_take_while", + "log", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", - "syn 1.0.109", + "syn 2.0.60", + "which", ] [[package]] @@ -1024,7 +1063,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9990737a6d5740ff51cdbbc0f0503015cb30c390f6623968281eb214a520cfc0" dependencies = [ "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -1075,6 +1114,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.6.0" @@ -1109,12 +1154,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.92" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -1144,9 +1190,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1154,7 +1200,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1199,7 +1245,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -1225,9 +1271,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "console_error_panic_hook" @@ -1460,7 +1506,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -1484,7 +1530,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -1495,22 +1541,21 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "deadpool" -version = "0.10.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490" +checksum = "ff0fc28638c21092aba483136debc6e177fff3dace8c835d715866923b03323e" dependencies = [ - "async-trait", "deadpool-runtime", "num_cpus", "tokio", @@ -1518,9 +1563,9 @@ dependencies = [ [[package]] name = "deadpool-postgres" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda39fa1cfff190d8924d447ad04fd22772c250438ca5ce1dfb3c80621c05aaa" +checksum = "4aa08f5c838496cbabb672e3614534444145fc6632995f102e13d30a29a25a13" dependencies = [ "deadpool", "tokio", @@ -1685,9 +1730,9 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "elliptic-curve" @@ -1748,7 +1793,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -1776,7 +1821,7 @@ dependencies = [ "h3 0.0.4", "h3-quinn", "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "hyper-util", "opentelemetry", "quinn", @@ -1851,9 +1896,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdeflate" @@ -1899,9 +1944,9 @@ dependencies = [ [[package]] name = "ffmpeg-sys-next" -version = "6.1.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2529ad916d08c3562c754c21bc9b17a26c7882c0f5706cc2cd69472175f1620" +checksum = "972a460dd8e901b737ce0482bf71a837e1751e3dd7c8f8b0a4ead808e7f174a5" dependencies = [ "bindgen", "cc", @@ -1913,9 +1958,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" +checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" [[package]] name = "file-format" @@ -1961,9 +2006,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -2035,9 +2080,9 @@ dependencies = [ "parking_lot", "rand", "redis-protocol", - "rustls 0.22.3", + "rustls 0.22.4", "rustls-native-certs 0.7.0", - "rustls-webpki 0.102.2", + "rustls-webpki 0.102.3", "semver", "socket2", "tokio", @@ -2049,6 +2094,12 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.30" @@ -2105,7 +2156,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -2185,9 +2236,9 @@ dependencies = [ [[package]] name = "gifski" -version = "1.14.4" +version = "1.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a6c5eab296009821c25867a4eaa9fca77df08bade4eed27bc5c211b3e6466f" +checksum = "fa3aeeed337aa658d1c2d90cb21b6db6172d1b8a84dfb462ade81f48eb0fd5eb" dependencies = [ "clap", "crossbeam-channel", @@ -2207,6 +2258,8 @@ dependencies = [ "resize", "rgb", "wild", + "y4m", + "yuv", ] [[package]] @@ -2430,9 +2483,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.11", "allocator-api2", @@ -2480,6 +2533,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "hostname" version = "0.3.1" @@ -2601,9 +2663,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", @@ -2636,6 +2698,23 @@ dependencies = [ "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tower-service", +] + [[package]] name = "hyper-timeout" version = "0.4.1" @@ -2655,7 +2734,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a343d17fe7885302ed7252767dc7bb83609a874b6ff581142241ec4b73957ad" dependencies = [ "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "hyper-util", "pin-project-lite", "tokio", @@ -2670,13 +2749,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ "bytes", + "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.0", - "hyper 1.2.0", + "hyper 1.3.1", "pin-project-lite", "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -2733,24 +2816,52 @@ name = "image" version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", +] + +[[package]] +name = "image" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" dependencies = [ "bytemuck", "byteorder", "color_quant", "exr", "gif", - "jpeg-decoder", + "image-webp", "num-traits", "png", "qoi", + "ravif", + "rayon", + "rgb", "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" +dependencies = [ + "byteorder-lite", + "thiserror", ] [[package]] name = "imagequant" -version = "4.3.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a7f142d232ccbdc00cbef49d17f45639aeb07d9bfe28e17c21dea3efac64e5" +checksum = "09db32417831053bf246bc74fc7c139a05458552d2d98a9f58ff5744d8dea8d3" dependencies = [ "arrayvec", "once_cell", @@ -2782,7 +2893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "serde", ] @@ -2794,7 +2905,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -2806,7 +2917,7 @@ dependencies = [ "socket2", "widestring", "windows-sys 0.48.0", - "winreg", + "winreg 0.50.0", ] [[package]] @@ -2815,6 +2926,12 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -2860,9 +2977,9 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.29" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f08474e32172238f2827bd160c67871cdb2801430f65c3979184dc362e3ca118" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -2872,9 +2989,6 @@ name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" -dependencies = [ - "rayon", -] [[package]] name = "js-sys" @@ -2941,9 +3055,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libdav1d-sys" @@ -2972,7 +3086,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -3008,9 +3122,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -3059,7 +3173,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -3099,6 +3213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", + "rayon", ] [[package]] @@ -3165,6 +3280,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + [[package]] name = "mp4" version = "0.0.1" @@ -3288,11 +3409,10 @@ dependencies = [ [[package]] name = "nkeys" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad178aad32087b19042ee36dfd450b73f5f934fbfb058b59b198684dfec4c47" +checksum = "bc522a19199a0795776406619aa6aa78e1e55690fbeb3181b8db5265fd0e89ce" dependencies = [ - "byteorder", "data-encoding", "ed25519", "ed25519-dalek", @@ -3402,7 +3522,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -3654,9 +3774,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -3664,15 +3784,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -3710,7 +3830,7 @@ dependencies = [ "prost 0.12.4", "prost-build", "quote", - "syn 2.0.58", + "syn 2.0.60", "tonic", "tonic-build", "ulid", @@ -3729,19 +3849,13 @@ dependencies = [ "winapi", ] -[[package]] -name = "peeking_take_while" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" - [[package]] name = "pem" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "serde", ] @@ -3762,9 +3876,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", @@ -3773,9 +3887,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" dependencies = [ "pest", "pest_generator", @@ -3783,22 +3897,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] name = "pest_meta" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" dependencies = [ "once_cell", "pest", @@ -3850,7 +3964,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -3915,7 +4029,7 @@ dependencies = [ "async-trait", "aws-config", "aws-sdk-s3", - "base64 0.21.7", + "base64 0.22.1", "binary-helper", "bitmask-enum", "bytes", @@ -3927,7 +4041,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.0", "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "hyper-tungstenite", "hyper-util", "jwt-next", @@ -3940,7 +4054,7 @@ dependencies = [ "prost 0.12.4", "rand", "reqwest", - "rustls 0.22.3", + "rustls 0.23.5", "rustls-pemfile 2.1.2", "scuffle-config", "scuffle-utils", @@ -3950,7 +4064,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.0", "tokio-stream", "tonic", "totp-rs", @@ -4018,6 +4132,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "portpicker" version = "0.1.1" @@ -4036,7 +4156,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -4056,7 +4176,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -4130,12 +4250,12 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" +checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" dependencies = [ "proc-macro2", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -4159,9 +4279,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -4182,7 +4302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -4205,7 +4325,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -4245,7 +4365,7 @@ dependencies = [ "prost 0.12.4", "prost-types", "regex", - "syn 2.0.58", + "syn 2.0.60", "tempfile", ] @@ -4272,7 +4392,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -4305,8 +4425,8 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f59a7c7ddb94c99fa3942dd761dbb305bca462b71d7bd9bcb3f9ff4d454d5736" dependencies = [ - "base64 0.22.0", - "image", + "base64 0.22.1", + "image 0.24.9", "qrcodegen", ] @@ -4445,6 +4565,22 @@ dependencies = [ "system-deps", "thiserror", "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc13288f5ab39e6d7c9d501759712e6969fcc9734220846fc9ed26cae2cc4234" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error 2.0.1", + "rav1e", + "rayon", + "rgb", ] [[package]] @@ -4490,6 +4626,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "regex" version = "1.10.4" @@ -4542,20 +4687,20 @@ checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", - "encoding_rs", "futures-core", "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.28", - "hyper-rustls", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls 0.26.0", + "hyper-util", "ipnet", "js-sys", "log", @@ -4563,22 +4708,22 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", + "rustls 0.22.4", + "rustls-pemfile 2.1.2", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls 0.25.0", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "webpki-roots", - "winreg", + "winreg 0.52.0", ] [[package]] @@ -4728,9 +4873,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -4753,14 +4898,29 @@ dependencies = [ [[package]] name = "rustls" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring 0.17.8", "rustls-pki-types", - "rustls-webpki 0.102.2", + "rustls-webpki 0.102.3", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afabcee0551bd1aa3e18e5adbf2c0544722014b899adb31bd186ec638d3da97e" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.3", "subtle", "zeroize", ] @@ -4805,15 +4965,15 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" [[package]] name = "rustls-webpki" @@ -4827,10 +4987,11 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" dependencies = [ + "aws-lc-rs", "ring 0.17.8", "rustls-pki-types", "untrusted 0.9.0", @@ -4865,9 +5026,9 @@ checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248" [[package]] name = "scc" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96560eea317a9cc4e0bb1f6a2c93c09a19b8c4fc5cb3fcc0ec1c094cd783e2" +checksum = "76ad2bbb0ae5100a07b7a6f2ed7ab5fd0045551a4c507989b7a620046ea3efdc" dependencies = [ "sdd", ] @@ -4937,7 +5098,7 @@ dependencies = [ "http-body 1.0.0", "humantime", "humantime-serde", - "hyper 1.2.0", + "hyper 1.3.1", "hyper-util", "itertools 0.12.1", "jemalloc_pprof", @@ -4982,7 +5143,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5001,7 +5162,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "path-tree", "pin-project", "portpicker", @@ -5028,7 +5189,7 @@ version = "0.0.1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5096,9 +5257,9 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -5126,13 +5287,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5143,7 +5304,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5157,9 +5318,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -5193,7 +5354,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5269,9 +5430,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -5346,9 +5507,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -5443,7 +5604,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5488,9 +5649,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.58" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", @@ -5563,22 +5724,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5716,7 +5877,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5747,47 +5908,46 @@ dependencies = [ [[package]] name = "tokio-postgres-rustls" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea13f22eda7127c827983bdaf0d7fff9df21c8817bab02815ac277a21143677" +checksum = "04fb792ccd6bbcd4bba408eb8a292f70fc4a3589e5d793626f45190e6454b6ab" dependencies = [ - "futures", "ring 0.17.8", - "rustls 0.22.3", + "rustls 0.23.5", "tokio", "tokio-postgres", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.0", "x509-certificate", ] [[package]] -name = "tokio-retry" -version = "0.3.0" +name = "tokio-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "pin-project", - "rand", + "rustls 0.21.12", "tokio", ] [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.21.12", + "rustls 0.22.4", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.22.3", + "rustls 0.23.5", "rustls-pki-types", "tokio", ] @@ -5840,7 +6000,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.9", + "toml_edit 0.22.12", ] [[package]] @@ -5865,15 +6025,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.9" +version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" +checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.6", + "winnow 0.6.7", ] [[package]] @@ -5916,7 +6076,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -5987,7 +6147,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -6125,6 +6285,17 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tryhard" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9f0a709784e86923586cff0d872dba54cd2d2e116b3bc57587d15737cfce9d" +dependencies = [ + "futures", + "pin-project-lite", + "tokio", +] + [[package]] name = "tsify" version = "0.4.5" @@ -6145,7 +6316,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -6328,7 +6499,7 @@ dependencies = [ "async-nats", "async-stream", "async-trait", - "base64 0.21.7", + "base64 0.22.1", "binary-helper", "bytes", "chrono", @@ -6370,7 +6541,7 @@ dependencies = [ "anyhow", "async-nats", "async-trait", - "base64 0.21.7", + "base64 0.22.1", "binary-helper", "chrono", "clap", @@ -6428,14 +6599,14 @@ dependencies = [ "futures-util", "hmac", "http-body-util", - "hyper 1.2.0", + "hyper 1.3.1", "hyper-util", "itertools 0.12.1", "jwt-next", "pb", "postgres-from-row", "prost 0.12.4", - "rustls 0.22.3", + "rustls 0.23.5", "rustls-pemfile 2.1.2", "scuffle-config", "scuffle-utils", @@ -6444,7 +6615,7 @@ dependencies = [ "sha2", "thiserror", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.0", "tokio-stream", "tokio-util", "tonic", @@ -6465,7 +6636,7 @@ dependencies = [ "async-nats", "async-stream", "async-trait", - "base64 0.21.7", + "base64 0.22.1", "binary-helper", "bytes", "bytesio", @@ -6475,21 +6646,21 @@ dependencies = [ "flv", "futures", "futures-util", - "hyper 1.2.0", + "hyper 1.3.1", "mp4", "pb", "portpicker", "postgres-from-row", "prost 0.12.4", "rtmp", - "rustls 0.22.3", + "rustls 0.23.5", "rustls-pemfile 2.1.2", "scuffle-config", "scuffle-utils", "serde", "serde_json", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.0", "tokio-stream", "tonic", "tracing", @@ -6556,8 +6727,8 @@ dependencies = [ "flv", "futures", "futures-util", - "hyper 1.2.0", - "image", + "hyper 1.3.1", + "image 0.25.1", "mp4", "pb", "portpicker", @@ -6638,7 +6809,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", "wasm-bindgen-shared", ] @@ -6672,7 +6843,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6705,9 +6876,12 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.4" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "weezl" @@ -6715,13 +6889,25 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "whoami" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ - "redox_syscall", + "redox_syscall 0.4.1", "wasite", "web-sys", ] @@ -6759,11 +6945,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -6787,7 +6973,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -6805,7 +6991,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -6825,17 +7011,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -6846,9 +7033,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -6858,9 +7045,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -6870,9 +7057,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -6882,9 +7075,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -6894,9 +7087,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -6906,9 +7099,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -6918,9 +7111,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" @@ -6933,9 +7126,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" dependencies = [ "memchr", ] @@ -6950,6 +7143,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "x509-certificate" version = "0.23.1" @@ -6975,6 +7178,22 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yuv" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7933ddf59021f0147c02986654b971ce2fbf04c5a7f1c92cd9ff738578b182" +dependencies = [ + "num-traits", + "rgb", +] + [[package]] name = "zerocopy" version = "0.7.32" @@ -6992,7 +7211,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] [[package]] @@ -7012,9 +7231,15 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.58", + "syn 2.0.60", ] +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + [[package]] name = "zune-inflate" version = "0.2.54" @@ -7023,3 +7248,12 @@ checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] + +[[package]] +name = "zune-jpeg" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +dependencies = [ + "zune-core", +] diff --git a/Cargo.toml b/Cargo.toml index 145c041a..a89f8bf2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ members = [ "platform/api", - "platform/image_processor", + "image_processor", "video/edge", "video/ingest", "video/transcoder", diff --git a/binary-helper/Cargo.toml b/binary-helper/Cargo.toml index e1aa0d7f..e6d17bcd 100644 --- a/binary-helper/Cargo.toml +++ b/binary-helper/Cargo.toml @@ -9,7 +9,7 @@ tracing = "0.1" thiserror = "1.0" tokio = { version = "1.36", features = ["full"] } serde = { version = "1.0.1", features = ["derive"] } -async-nats = "0.33" +async-nats = "0.34" ulid = "1.1" async-trait = "0.1" tonic = { version = "0.11", features = ["tls"] } @@ -17,10 +17,10 @@ anyhow = "1.0" tower-layer = "0.3" async-stream = "0.3" futures-util = "0.3" -rustls = "0.22" +rustls = "0.23" rustls-pemfile = "2.0" fred = { version = "8.0.0", features = ["enable-rustls", "sentinel-client", "dns"] } -tokio-postgres-rustls = "0.11" +tokio-postgres-rustls = "0.12" tracing-subscriber = { features = ["env-filter", "fmt", "json"], version = "0.3" } once_cell = "1.19" aws-config = { version = "1.1" } @@ -31,10 +31,11 @@ http-body = { version = "1.0.0"} hyper = "1" bytes = "1.0" pin-project = "1" +tokio-rustls = "0.25" tokio-postgres = { version = "0.7" } postgres-types = { version = "0.2", features = ["with-serde_json-1", "with-chrono-0_4", "derive"] } -deadpool-postgres = { version = "0.12" } +deadpool-postgres = { version = "0.13" } postgres-from-row = { version = "0.5" } prost = { version = "0.12" } diff --git a/binary-helper/src/global.rs b/binary-helper/src/global.rs index ed6a44b8..a2b64469 100644 --- a/binary-helper/src/global.rs +++ b/binary-helper/src/global.rs @@ -230,7 +230,7 @@ pub async fn setup_redis(config: &RedisConfig) -> anyhow::Result, _>>()?; - let mut cert_store = RootCertStore::empty(); + let mut cert_store = tokio_rustls::rustls::RootCertStore::empty(); if let Some(ca_cert) = &tls.ca_cert { let ca_cert = tokio::fs::read(ca_cert).await.context("failed to read redis ca cert")?; let ca_certs = @@ -240,11 +240,13 @@ pub async fn setup_redis(config: &RedisConfig) -> anyhow::Result Migration for InitialMigration { + fn name(&self) -> &'static str { + "InitialMigration" + } + + fn version(&self) -> i32 { + 1 + } + + async fn up(&self, _: &Arc, tx: &Transaction<'_>) -> anyhow::Result<()> { + utils::database::query( + "CREATE TABLE image_processor_job ( + id UUID PRIMARY KEY, + hold_until TIMESTAMP WITH TIME ZONE, + priority INTEGER NOT NULL, + claimed_by_id UUID, + task bytea NOT NULL + );", + ) + .build() + .execute(tx) + .await?; + + utils::database::query("CREATE INDEX image_processor_job_hold_until_index ON image_processor_job (hold_until ASC);") + .build() + .execute(tx) + .await?; + + utils::database::query( + "CREATE INDEX image_processor_job_priority_index ON image_processor_job (priority DESC, id DESC);", + ) + .build() + .execute(tx) + .await?; + + Ok(()) + } + + async fn down(&self, _: &Arc, tx: &Transaction<'_>) -> anyhow::Result<()> { + utils::database::query("DROP TABLE image_jobs").build().execute(tx).await?; + + Ok(()) + } +} diff --git a/image_processor/src/migration/mod.rs b/image_processor/src/migration/mod.rs new file mode 100644 index 00000000..497aafed --- /dev/null +++ b/image_processor/src/migration/mod.rs @@ -0,0 +1,94 @@ +use std::sync::Arc; + +use anyhow::Context; +use utils::database::deadpool_postgres::Transaction; + +use crate::global::ImageProcessorGlobal; + +#[path = "0001_initial.rs"] +mod initial; + +#[async_trait::async_trait] +trait Migration { + fn name(&self) -> &'static str; + fn version(&self) -> i32; + + async fn up(&self, global: &Arc, tx: &Transaction<'_>) -> anyhow::Result<()>; + async fn down(&self, global: &Arc, tx: &Transaction<'_>) -> anyhow::Result<()>; +} + +const fn migrations() -> &'static [&'static dyn Migration] { + &[&initial::InitialMigration] +} + +#[tracing::instrument(skip(global))] +async fn get_migrations(global: &Arc) -> anyhow::Result>> { + let migrations = migrations::(); + + let migration_version = match utils::database::query("SELECT version FROM image_processor_migrations") + .build_query_single_scalar::() + .fetch_one(global.db()) + .await + { + Ok(version) => version as usize, + Err(err) => { + tracing::info!("Initializing database: {}", err); + utils::database::query("CREATE TABLE image_processor_migrations (version INTEGER NOT NULL)") + .build() + .execute(global.db()) + .await + .context("Failed to create migration table")?; + + utils::database::query("INSERT INTO image_processor_migrations (version) VALUES (0)") + .build() + .execute(global.db()) + .await + .context("Failed to insert initial migration version")?; + + 0 + } + }; + + if migration_version > migrations.len() { + anyhow::bail!( + "Database is at version {}, but only {} migrations are available", + migration_version, + migrations.len() + ); + } + + Ok(migrations.iter().skip(migration_version).copied().collect()) +} + +#[tracing::instrument(skip(global, migration), fields(name = migration.name(), version = migration.version()))] +async fn run_migration( + global: &Arc, + migration: &'static dyn Migration, +) -> anyhow::Result<()> { + let mut client = global.db().get().await.context("Failed to get database connection")?; + let tx = client.transaction().await.context("Failed to start transaction")?; + + migration.up(global, &tx).await.context("Failed to apply migration")?; + + utils::database::query("UPDATE image_processor_migrations SET version = ") + .push_bind(migration.version() as i32) + .build() + .execute(&tx) + .await + .context("Failed to update migration version")?; + + tx.commit().await.context("Failed to commit transaction")?; + + Ok(()) +} + +#[tracing::instrument(skip(global))] +pub async fn run_migrations(global: &Arc) -> anyhow::Result<()> { + let migrations = get_migrations(global).await?; + + for migration in migrations { + run_migration(global, migration).await?; + } + + Ok(()) +} diff --git a/platform/image_processor/src/processor/error.rs b/image_processor/src/processor/error.rs similarity index 100% rename from platform/image_processor/src/processor/error.rs rename to image_processor/src/processor/error.rs diff --git a/platform/image_processor/src/processor/job/decoder/ffmpeg.rs b/image_processor/src/processor/job/decoder/ffmpeg.rs similarity index 100% rename from platform/image_processor/src/processor/job/decoder/ffmpeg.rs rename to image_processor/src/processor/job/decoder/ffmpeg.rs diff --git a/platform/image_processor/src/processor/job/decoder/libavif.rs b/image_processor/src/processor/job/decoder/libavif.rs similarity index 100% rename from platform/image_processor/src/processor/job/decoder/libavif.rs rename to image_processor/src/processor/job/decoder/libavif.rs diff --git a/platform/image_processor/src/processor/job/decoder/libwebp.rs b/image_processor/src/processor/job/decoder/libwebp.rs similarity index 100% rename from platform/image_processor/src/processor/job/decoder/libwebp.rs rename to image_processor/src/processor/job/decoder/libwebp.rs diff --git a/platform/image_processor/src/processor/job/decoder/mod.rs b/image_processor/src/processor/job/decoder/mod.rs similarity index 100% rename from platform/image_processor/src/processor/job/decoder/mod.rs rename to image_processor/src/processor/job/decoder/mod.rs diff --git a/platform/image_processor/src/processor/job/encoder/gifski.rs b/image_processor/src/processor/job/encoder/gifski.rs similarity index 100% rename from platform/image_processor/src/processor/job/encoder/gifski.rs rename to image_processor/src/processor/job/encoder/gifski.rs diff --git a/platform/image_processor/src/processor/job/encoder/libavif.rs b/image_processor/src/processor/job/encoder/libavif.rs similarity index 100% rename from platform/image_processor/src/processor/job/encoder/libavif.rs rename to image_processor/src/processor/job/encoder/libavif.rs diff --git a/platform/image_processor/src/processor/job/encoder/libwebp.rs b/image_processor/src/processor/job/encoder/libwebp.rs similarity index 100% rename from platform/image_processor/src/processor/job/encoder/libwebp.rs rename to image_processor/src/processor/job/encoder/libwebp.rs diff --git a/platform/image_processor/src/processor/job/encoder/mod.rs b/image_processor/src/processor/job/encoder/mod.rs similarity index 100% rename from platform/image_processor/src/processor/job/encoder/mod.rs rename to image_processor/src/processor/job/encoder/mod.rs diff --git a/platform/image_processor/src/processor/job/encoder/png.rs b/image_processor/src/processor/job/encoder/png.rs similarity index 100% rename from platform/image_processor/src/processor/job/encoder/png.rs rename to image_processor/src/processor/job/encoder/png.rs diff --git a/platform/image_processor/src/processor/job/frame.rs b/image_processor/src/processor/job/frame.rs similarity index 100% rename from platform/image_processor/src/processor/job/frame.rs rename to image_processor/src/processor/job/frame.rs diff --git a/platform/image_processor/src/processor/job/libavif.rs b/image_processor/src/processor/job/libavif.rs similarity index 100% rename from platform/image_processor/src/processor/job/libavif.rs rename to image_processor/src/processor/job/libavif.rs diff --git a/platform/image_processor/src/processor/job/libwebp.rs b/image_processor/src/processor/job/libwebp.rs similarity index 100% rename from platform/image_processor/src/processor/job/libwebp.rs rename to image_processor/src/processor/job/libwebp.rs diff --git a/platform/image_processor/src/processor/job/mod.rs b/image_processor/src/processor/job/mod.rs similarity index 100% rename from platform/image_processor/src/processor/job/mod.rs rename to image_processor/src/processor/job/mod.rs diff --git a/platform/image_processor/src/processor/job/process.rs b/image_processor/src/processor/job/process.rs similarity index 100% rename from platform/image_processor/src/processor/job/process.rs rename to image_processor/src/processor/job/process.rs diff --git a/platform/image_processor/src/processor/job/resize.rs b/image_processor/src/processor/job/resize.rs similarity index 100% rename from platform/image_processor/src/processor/job/resize.rs rename to image_processor/src/processor/job/resize.rs diff --git a/platform/image_processor/src/processor/job/scaling.rs b/image_processor/src/processor/job/scaling.rs similarity index 99% rename from platform/image_processor/src/processor/job/scaling.rs rename to image_processor/src/processor/job/scaling.rs index e8d47fd4..b7c8ed52 100644 --- a/platform/image_processor/src/processor/job/scaling.rs +++ b/image_processor/src/processor/job/scaling.rs @@ -226,7 +226,6 @@ impl ScalingOptions { (scales.len() - 1, input_scale_factor) }); - if self.input_image_scaling { let scaled_width = padded_size.width / scales[best_idx].1 / input_scale_factor; let scaled_height = padded_size.height / scales[best_idx].1 / input_scale_factor; diff --git a/platform/image_processor/src/processor/job/smart_object.rs b/image_processor/src/processor/job/smart_object.rs similarity index 100% rename from platform/image_processor/src/processor/job/smart_object.rs rename to image_processor/src/processor/job/smart_object.rs diff --git a/platform/image_processor/src/processor/mod.rs b/image_processor/src/processor/mod.rs similarity index 100% rename from platform/image_processor/src/processor/mod.rs rename to image_processor/src/processor/mod.rs diff --git a/platform/image_processor/src/processor/utils.rs b/image_processor/src/processor/utils.rs similarity index 100% rename from platform/image_processor/src/processor/utils.rs rename to image_processor/src/processor/utils.rs diff --git a/platform/image_processor/src/tests/global.rs b/image_processor/src/tests/global.rs similarity index 100% rename from platform/image_processor/src/tests/global.rs rename to image_processor/src/tests/global.rs diff --git a/platform/image_processor/src/tests/mod.rs b/image_processor/src/tests/mod.rs similarity index 100% rename from platform/image_processor/src/tests/mod.rs rename to image_processor/src/tests/mod.rs diff --git a/platform/image_processor/src/tests/processor/decoder.rs b/image_processor/src/tests/processor/decoder.rs similarity index 100% rename from platform/image_processor/src/tests/processor/decoder.rs rename to image_processor/src/tests/processor/decoder.rs diff --git a/platform/image_processor/src/tests/processor/encoder.rs b/image_processor/src/tests/processor/encoder.rs similarity index 100% rename from platform/image_processor/src/tests/processor/encoder.rs rename to image_processor/src/tests/processor/encoder.rs diff --git a/platform/image_processor/src/tests/processor/mod.rs b/image_processor/src/tests/processor/mod.rs similarity index 100% rename from platform/image_processor/src/tests/processor/mod.rs rename to image_processor/src/tests/processor/mod.rs diff --git a/platform/image_processor/src/tests/processor/resize.rs b/image_processor/src/tests/processor/resize.rs similarity index 100% rename from platform/image_processor/src/tests/processor/resize.rs rename to image_processor/src/tests/processor/resize.rs diff --git a/platform/image_processor/src/tests/utils.rs b/image_processor/src/tests/utils.rs similarity index 100% rename from platform/image_processor/src/tests/utils.rs rename to image_processor/src/tests/utils.rs diff --git a/platform/api/Cargo.toml b/platform/api/Cargo.toml index 8b5449dd..21b00d9a 100644 --- a/platform/api/Cargo.toml +++ b/platform/api/Cargo.toml @@ -12,12 +12,12 @@ tokio = { version = "1.36", features = ["full"] } serde = { version = "1.0", features = ["derive"] } hyper = { version = "1.1", features = ["full"] } utils = { workspace = true, features = ["all"] } -rustls = "0.22" +rustls = "0.23" rustls-pemfile = "2.0" -tokio-rustls = "0.25" +tokio-rustls = "0.26" path-tree = "0.7" serde_json = "1.0" -reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features = false} +reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false} chrono = { version = "0.4", default-features = false, features = ["serde", "clock"] } async-graphql = { version = "7.0.1", features = ["apollo_tracing", "apollo_persisted_queries", "tracing", "string_number"] } hyper-tungstenite = "0" @@ -36,7 +36,7 @@ argon2 = "0.5" ulid = { version = "1.1", features = ["uuid"] } rand = "0.8" tokio-stream = { version = "0.1", features = ["sync"] } -async-nats = "0.33" +async-nats = "0.34" async-trait = "0.1" bytes = "1.5" totp-rs = { version = "5.4", features = ["qr"] } @@ -49,7 +49,7 @@ http-body = "1.0" http-body-util = "0.1" hyper-util = "0.1" pin-project = "1.1" -base64 = "0.21" +base64 = "0.22" postgres-from-row = "0.5" postgres-types = "0.2" fred = { version = "8.0", features = ["enable-rustls", "sentinel-client", "dns"] } diff --git a/platform/image_processor/APACHE2_LICENSE b/platform/image_processor/APACHE2_LICENSE deleted file mode 120000 index 4eaff731..00000000 --- a/platform/image_processor/APACHE2_LICENSE +++ /dev/null @@ -1 +0,0 @@ -../../licenses/APACHE2_LICENSE \ No newline at end of file diff --git a/platform/image_processor/MIT_LICENSE b/platform/image_processor/MIT_LICENSE deleted file mode 120000 index 9b50c52e..00000000 --- a/platform/image_processor/MIT_LICENSE +++ /dev/null @@ -1 +0,0 @@ -../../licenses/MIT_LICENSE \ No newline at end of file diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 0461f476..59da4696 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -54,7 +54,7 @@ const_format = "0.2" tokio-postgres = { version = "0.7", optional = true } postgres-types = { version = "0.2", optional = true, features = ["with-serde_json-1", "with-chrono-0_4", "derive"] } -deadpool-postgres = { version = "0.12", optional = true } +deadpool-postgres = { version = "0.13", optional = true } postgres-from-row = { version = "0.5", optional = true } prost = { version = "0.12", optional = true } ulid = { version = "1.1", optional = true } diff --git a/video/api/Cargo.toml b/video/api/Cargo.toml index 4aa79b5c..fab4c56e 100644 --- a/video/api/Cargo.toml +++ b/video/api/Cargo.toml @@ -27,10 +27,10 @@ jwt-next = "0.17" hmac = "0.12" sha2 = "0.10" rand = "0.8" -async-nats = "0.33" +async-nats = "0.34" ulid = "1.1" hex = "0.4" -base64 = "0.21" +base64 = "0.22" serde_json = "1.0" fred = { version = "8.0.0", features = ["enable-rustls", "sentinel-client", "dns"] } url = "2.5" diff --git a/video/cli/Cargo.toml b/video/cli/Cargo.toml index 05aaea45..4fa3331b 100644 --- a/video/cli/Cargo.toml +++ b/video/cli/Cargo.toml @@ -10,7 +10,7 @@ clap = { version = "4.4", features = ["derive", "env"] } ulid = "1.1" chrono = { version = "0.4", features = ["serde"] } fred = { version = "8.0.0", features = ["enable-rustls", "sentinel-client", "dns"] } -async-nats = "0.33" +async-nats = "0.34" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" async-trait = "0.1" @@ -19,7 +19,7 @@ tonic = "0.11" futures = "0.3" futures-util = "0.3" serde_yaml = "0.9" -base64 = "0.21" +base64 = "0.22" pb = { workspace = true } config = { workspace = true } diff --git a/video/common/Cargo.toml b/video/common/Cargo.toml index b8d46934..f171fb7f 100644 --- a/video/common/Cargo.toml +++ b/video/common/Cargo.toml @@ -19,7 +19,7 @@ futures = "0.3" futures-util = "0.3" bytes = "1.5" async-trait = "0.1" -async-nats = "0.33" +async-nats = "0.34" pb = { workspace = true } utils = { workspace = true, features = ["all"] } diff --git a/video/edge/Cargo.toml b/video/edge/Cargo.toml index eb986360..4f43d8da 100644 --- a/video/edge/Cargo.toml +++ b/video/edge/Cargo.toml @@ -11,9 +11,9 @@ path = "src/main.rs" [dependencies] anyhow = "1.0" tracing = "0.1" -rustls = "0.22" +rustls = "0.23" rustls-pemfile = "2.0" -tokio-rustls = "0.25" +tokio-rustls = "0.26" postgres-from-row = "0.5" tokio = { version = "1.36", features = ["full"] } serde = { version = "1.0", features = ["derive"] } @@ -32,7 +32,7 @@ tokio-stream = "0.1" serde_json = "1.0" uuid = { version = "1.6", features = ["v4"] } url = "2.5" -async-nats = "0.33" +async-nats = "0.34" hmac = "0.12" jwt-next = "0.17" ulid = { version = "1.1", features = ["uuid", "serde"] } diff --git a/video/ingest/Cargo.toml b/video/ingest/Cargo.toml index fb008875..30f6cb05 100644 --- a/video/ingest/Cargo.toml +++ b/video/ingest/Cargo.toml @@ -11,9 +11,9 @@ path = "src/main.rs" [dependencies] anyhow = "1.0" tracing = "0.1" -rustls = "0.22" +rustls = "0.23" rustls-pemfile = "2.0" -tokio-rustls = "0.25" +tokio-rustls = "0.26" async-trait = "0.1" tokio = { version = "1.36", features = ["full"] } serde = { version = "1.0", features = ["derive"] } @@ -28,8 +28,8 @@ serde_json = "1.0" uuid = "1.6" ulid = { version = "1.1", features = ["uuid"] } async-stream = "0.3" -async-nats = "0.33" -base64 = "0.21" +async-nats = "0.34" +base64 = "0.22" tokio-stream = "0.1" default-net = "0.22" postgres-from-row = "0.5" diff --git a/video/transcoder/Cargo.toml b/video/transcoder/Cargo.toml index 24d76420..bade5f46 100644 --- a/video/transcoder/Cargo.toml +++ b/video/transcoder/Cargo.toml @@ -27,11 +27,11 @@ tokio-util = { version = "0.7", features = ["compat"] } tokio-stream = "0.1" ulid = { version = "1.1", features = ["uuid"] } uuid = { version = "1.6", features = ["serde", "v4"] } -async-nats = "0.33" +async-nats = "0.34" thiserror = "1.0" aws-config = "1.1" aws-sdk-s3 = { version = "1.12", features = ["behavior-version-latest"] } -image = "0.24" +image = "0.25" aac = { workspace = true } mp4 = { workspace = true } diff --git a/video/transcoder/src/transcoder/job/screenshot.rs b/video/transcoder/src/transcoder/job/screenshot.rs index d930a657..85c33783 100644 --- a/video/transcoder/src/transcoder/job/screenshot.rs +++ b/video/transcoder/src/transcoder/job/screenshot.rs @@ -22,7 +22,7 @@ pub fn screenshot_task(mut recv: mpsc::Receiver, send: mpsc::Sender<(Byte } encoder - .encode(data, width, height, image::ColorType::Rgba8) + .encode(data, width, height, image::ExtendedColorType::Rgba8) .context("failed to encode jpeg")?; let data = Bytes::from(writer); From 3bcfe152f8e5464d1992ebc887e526fc5ee30784 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Fri, 12 Apr 2024 18:38:00 +0000 Subject: [PATCH 06/21] chore: logs --- image_processor/migrations/0001_initial.down.sql | 1 - image_processor/migrations/0001_initial.up.sql | 10 ---------- image_processor/src/migration/mod.rs | 4 ++++ 3 files changed, 4 insertions(+), 11 deletions(-) delete mode 100644 image_processor/migrations/0001_initial.down.sql delete mode 100644 image_processor/migrations/0001_initial.up.sql diff --git a/image_processor/migrations/0001_initial.down.sql b/image_processor/migrations/0001_initial.down.sql deleted file mode 100644 index ce786812..00000000 --- a/image_processor/migrations/0001_initial.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE image_processor_job; diff --git a/image_processor/migrations/0001_initial.up.sql b/image_processor/migrations/0001_initial.up.sql deleted file mode 100644 index 1be9cd25..00000000 --- a/image_processor/migrations/0001_initial.up.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE image_processor_job ( - id UUID PRIMARY KEY, - hold_until TIMESTAMP WITH TIME ZONE, - priority INTEGER NOT NULL, - claimed_by_id UUID, - task bytea NOT NULL -); - -CREATE INDEX image_processor_job_hold_until_index ON image_processor_job (hold_until ASC); -CREATE INDEX image_processor_job_priority_index ON image_processor_job (priority DESC, id DESC); diff --git a/image_processor/src/migration/mod.rs b/image_processor/src/migration/mod.rs index 497aafed..0f17526d 100644 --- a/image_processor/src/migration/mod.rs +++ b/image_processor/src/migration/mod.rs @@ -65,6 +65,8 @@ async fn run_migration( global: &Arc, migration: &'static dyn Migration, ) -> anyhow::Result<()> { + tracing::info!("Applying migration"); + let mut client = global.db().get().await.context("Failed to get database connection")?; let tx = client.transaction().await.context("Failed to start transaction")?; @@ -79,6 +81,8 @@ async fn run_migration( tx.commit().await.context("Failed to commit transaction")?; + tracing::info!("Migration applied"); + Ok(()) } From eaae16e2f1c00539df48758ac024654a0f2982a3 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Mon, 29 Apr 2024 18:29:01 +0000 Subject: [PATCH 07/21] wd --- Cargo.lock | 462 ++++++++++++++++-- Cargo.toml | 4 +- binary-helper/Cargo.toml | 2 +- binary-helper/src/global.rs | 32 +- binary-helper/src/lib.rs | 4 +- binary-helper/src/traits.rs | 2 +- config/src/sources/cli.rs | 2 +- config/src/sources/env.rs | 2 +- config/src/sources/file/mod.rs | 2 +- config/src/sources/manual.rs | 2 +- ffmpeg/Cargo.toml | 6 +- ffmpeg/src/decoder.rs | 6 +- ffmpeg/src/encoder.rs | 10 +- ffmpeg/src/filter_graph.rs | 12 +- ffmpeg/src/io/internal.rs | 4 +- ffmpeg/src/io/output.rs | 14 +- ffmpeg/src/packet.rs | 2 +- ffmpeg/src/scalar.rs | 2 +- image_processor/Cargo.toml | 13 +- image_processor/build.rs | 6 + .../scuffle/image_processor/service.proto | 51 ++ .../proto/scuffle/image_processor/types.proto | 249 ++++++++++ image_processor/src/config.rs | 206 ++++++-- image_processor/src/database.rs | 15 +- image_processor/src/global.rs | 48 +- image_processor/src/grpc.rs | 2 +- image_processor/src/lib.rs | 2 +- image_processor/src/main.rs | 64 +-- image_processor/src/migration/0001_initial.rs | 54 -- image_processor/src/migration/mod.rs | 98 ---- image_processor/src/pb.rs | 1 + image_processor/src/processor/error.rs | 4 +- .../src/processor/job/decoder/ffmpeg.rs | 20 +- .../src/processor/job/decoder/libavif.rs | 2 +- .../src/processor/job/decoder/libwebp.rs | 2 +- .../src/processor/job/encoder/gifski.rs | 6 +- .../src/processor/job/encoder/libavif.rs | 4 +- .../src/processor/job/encoder/libwebp.rs | 6 +- .../src/processor/job/encoder/png.rs | 2 +- image_processor/src/processor/job/mod.rs | 36 +- image_processor/src/processor/job/process.rs | 35 +- image_processor/src/processor/job/resize.rs | 5 +- image_processor/src/processor/job/scaling.rs | 19 +- image_processor/src/processor/utils.rs | 11 +- image_processor/src/tests/global.rs | 9 +- image_processor/src/tests/utils.rs | 2 +- platform/api/Cargo.toml | 2 +- platform/api/src/api/auth.rs | 2 +- platform/api/src/api/error.rs | 6 +- platform/api/src/api/middleware/auth.rs | 8 +- platform/api/src/api/mod.rs | 12 +- platform/api/src/api/v1/gql/error.rs | 2 +- platform/api/src/api/v1/gql/handlers.rs | 8 +- platform/api/src/api/v1/gql/mod.rs | 6 +- platform/api/src/api/v1/gql/models/channel.rs | 4 +- platform/api/src/api/v1/gql/mutations/auth.rs | 14 +- .../api/src/api/v1/gql/mutations/channel.rs | 2 +- platform/api/src/api/v1/gql/mutations/chat.rs | 2 +- platform/api/src/api/v1/gql/mutations/user.rs | 12 +- .../src/api/v1/gql/mutations/user/two_fa.rs | 6 +- .../api/src/api/v1/gql/queries/category.rs | 2 +- platform/api/src/api/v1/gql/queries/mod.rs | 2 +- platform/api/src/api/v1/gql/queries/user.rs | 6 +- .../src/api/v1/gql/subscription/channel.rs | 2 +- .../api/src/api/v1/gql/subscription/chat.rs | 2 +- .../api/src/api/v1/gql/subscription/user.rs | 2 +- platform/api/src/api/v1/mod.rs | 6 +- platform/api/src/api/v1/upload/mod.rs | 12 +- .../api/src/api/v1/upload/profile_picture.rs | 12 +- platform/api/src/database/channel.rs | 2 +- platform/api/src/database/two_fa_request.rs | 14 +- platform/api/src/database/uploaded_file.rs | 2 +- platform/api/src/dataloader/category.rs | 4 +- platform/api/src/dataloader/global_state.rs | 4 +- platform/api/src/dataloader/role.rs | 4 +- platform/api/src/dataloader/session.rs | 4 +- platform/api/src/dataloader/uploaded_file.rs | 4 +- platform/api/src/dataloader/user.rs | 6 +- platform/api/src/global.rs | 2 +- platform/api/src/igdb_cron.rs | 58 +-- platform/api/src/image_upload_callback.rs | 10 +- platform/api/src/main.rs | 8 +- platform/api/src/subscription.rs | 2 +- platform/api/src/video_event_handler.rs | 4 +- .../internal/events/processed_image.proto | 24 - .../platform/internal/image_processor.proto | 74 --- .../internal/types/image_format.proto | 12 - .../types/processed_image_variant.proto | 13 - .../types/uploaded_file_metadata.proto | 15 - video/api/Cargo.toml | 2 +- video/api/src/api/access_token/create.rs | 2 +- video/api/src/api/access_token/delete.rs | 2 +- video/api/src/api/access_token/get.rs | 2 +- video/api/src/api/mod.rs | 2 +- video/api/src/api/playback_key_pair/create.rs | 2 +- video/api/src/api/playback_key_pair/delete.rs | 2 +- video/api/src/api/playback_key_pair/get.rs | 2 +- video/api/src/api/playback_key_pair/modify.rs | 2 +- video/api/src/api/playback_session/count.rs | 2 +- video/api/src/api/playback_session/get.rs | 2 +- video/api/src/api/playback_session/revoke.rs | 4 +- video/api/src/api/recording/delete.rs | 14 +- video/api/src/api/recording/get.rs | 2 +- video/api/src/api/recording/modify.rs | 6 +- video/api/src/api/recording_config/create.rs | 8 +- video/api/src/api/recording_config/delete.rs | 4 +- video/api/src/api/recording_config/get.rs | 2 +- video/api/src/api/recording_config/modify.rs | 6 +- video/api/src/api/room/create.rs | 8 +- video/api/src/api/room/delete.rs | 4 +- video/api/src/api/room/get.rs | 2 +- video/api/src/api/room/modify.rs | 8 +- video/api/src/api/room/reset_key.rs | 2 +- video/api/src/api/s3_bucket/create.rs | 2 +- video/api/src/api/s3_bucket/delete.rs | 4 +- video/api/src/api/s3_bucket/get.rs | 2 +- video/api/src/api/s3_bucket/modify.rs | 2 +- .../api/src/api/transcoding_config/create.rs | 2 +- .../api/src/api/transcoding_config/delete.rs | 4 +- video/api/src/api/transcoding_config/get.rs | 2 +- .../api/src/api/transcoding_config/modify.rs | 2 +- video/api/src/api/utils/get.rs | 6 +- video/api/src/api/utils/ratelimit.rs | 14 +- video/api/src/api/utils/tags.rs | 10 +- video/api/src/dataloaders/access_token.rs | 4 +- video/api/src/dataloaders/recording_state.rs | 4 +- video/api/src/dataloaders/room.rs | 23 +- video/api/src/global.rs | 2 +- video/api/src/main.rs | 6 +- video/api/src/tests/api/access_token.rs | 48 +- video/api/src/tests/api/events.rs | 4 +- video/api/src/tests/api/playback_key_pair.rs | 50 +- video/api/src/tests/api/playback_session.rs | 28 +- video/api/src/tests/api/recording.rs | 26 +- video/api/src/tests/api/recording_config.rs | 50 +- video/api/src/tests/api/room.rs | 58 +-- video/api/src/tests/api/s3_bucket.rs | 50 +- video/api/src/tests/api/transcoding_config.rs | 50 +- video/api/src/tests/api/utils.rs | 34 +- video/api/src/tests/global.rs | 12 +- video/api/src/tests/utils.rs | 26 +- video/cli/Cargo.toml | 2 +- video/cli/src/invoker/direct.rs | 20 +- video/cli/src/invoker/grpc.rs | 2 +- video/cli/src/invoker/mod.rs | 2 +- video/cli/src/main.rs | 4 +- video/common/Cargo.toml | 2 +- video/common/src/database/access_token.rs | 2 +- video/common/src/database/organization.rs | 2 +- .../common/src/database/playback_key_pair.rs | 2 +- video/common/src/database/recording.rs | 2 +- video/common/src/database/recording_config.rs | 2 +- video/common/src/database/room.rs | 2 +- video/common/src/database/s3_bucket.rs | 2 +- .../common/src/database/transcoding_config.rs | 2 +- video/edge/Cargo.toml | 2 +- video/edge/src/edge/error.rs | 6 +- video/edge/src/edge/mod.rs | 10 +- video/edge/src/edge/stream/hls_config.rs | 4 +- video/edge/src/edge/stream/mod.rs | 30 +- video/edge/src/edge/stream/playlist.rs | 10 +- video/edge/src/edge/stream/tokens.rs | 8 +- video/edge/src/main.rs | 2 +- video/edge/src/subscription/mod.rs | 2 +- video/ingest/Cargo.toml | 2 +- video/ingest/src/grpc/ingest.rs | 2 +- video/ingest/src/ingest/connection.rs | 12 +- video/ingest/src/ingest/mod.rs | 4 +- video/ingest/src/ingest/update.rs | 4 +- video/ingest/src/main.rs | 2 +- video/ingest/src/tests/global.rs | 6 +- video/ingest/src/tests/ingest.rs | 20 +- video/lib/bytesio/Cargo.toml | 4 +- video/lib/bytesio/src/bytesio.rs | 2 +- video/lib/rtmp/Cargo.toml | 2 +- video/lib/rtmp/src/session/server_session.rs | 2 +- video/lib/rtmp/src/tests/rtmp.rs | 2 +- video/transcoder/Cargo.toml | 4 +- video/transcoder/src/global.rs | 2 +- video/transcoder/src/main.rs | 4 +- video/transcoder/src/tests/global.rs | 10 +- video/transcoder/src/tests/transcoder/mod.rs | 12 +- .../src/transcoder/job/ffmpeg/audio.rs | 26 +- .../src/transcoder/job/ffmpeg/mod.rs | 57 +-- .../src/transcoder/job/ffmpeg/video.rs | 22 +- video/transcoder/src/transcoder/job/mod.rs | 6 +- .../src/transcoder/job/recording.rs | 30 +- .../src/transcoder/job/screenshot.rs | 6 +- .../src/transcoder/job/sql_operations.rs | 10 +- .../src/transcoder/job/task/generic.rs | 2 +- .../src/transcoder/job/task/recording.rs | 4 +- video/transcoder/src/transcoder/mod.rs | 2 +- 192 files changed, 1692 insertions(+), 1248 deletions(-) create mode 100644 image_processor/build.rs create mode 100644 image_processor/proto/scuffle/image_processor/service.proto create mode 100644 image_processor/proto/scuffle/image_processor/types.proto delete mode 100644 image_processor/src/migration/0001_initial.rs delete mode 100644 image_processor/src/migration/mod.rs create mode 100644 image_processor/src/pb.rs delete mode 100644 proto/scuffle/platform/internal/events/processed_image.proto delete mode 100644 proto/scuffle/platform/internal/image_processor.proto delete mode 100644 proto/scuffle/platform/internal/types/image_format.proto delete mode 100644 proto/scuffle/platform/internal/types/processed_image_variant.proto delete mode 100644 proto/scuffle/platform/internal/types/uploaded_file_metadata.proto diff --git a/Cargo.lock b/Cargo.lock index 6106e199..7b9863a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", "version_check", "zerocopy", @@ -259,7 +260,7 @@ checksum = "3188809947798ea6db736715a60cf645ba3b87ea031c710130e1476b48e45967" dependencies = [ "Inflector", "async-graphql-parser", - "darling", + "darling 0.20.8", "proc-macro-crate", "proc-macro2", "quote", @@ -793,7 +794,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "http 0.2.12", - "rustc_version", + "rustc_version 0.4.0", "tracing", ] @@ -936,6 +937,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -1072,6 +1079,18 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06c9989a51171e2e81038ab168b6ae22886fe9ded214430dbb4f41c28cf176da" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.10.6" @@ -1090,6 +1109,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bson" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d43b38e074cc0de2957f10947e376a1d88b9c4dbab340b590800cc1b2e066b2" +dependencies = [ + "ahash 0.8.11", + "base64 0.13.1", + "bitvec", + "hex", + "indexmap 2.2.6", + "js-sys", + "once_cell", + "rand", + "serde", + "serde_bytes", + "serde_json", + "time", + "uuid", +] + [[package]] name = "built" version = "0.7.2" @@ -1323,6 +1363,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "convert_case" version = "0.6.0" @@ -1387,7 +1433,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89254598aa9b9fa608de44b3ae54c810f0f06d755e24c50177f1f8f31ff50ce2" dependencies = [ - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -1494,7 +1540,7 @@ dependencies = [ "digest", "fiat-crypto", "platforms", - "rustc_version", + "rustc_version 0.4.0", "subtle", ] @@ -1509,14 +1555,38 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + [[package]] name = "darling" version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.8", + "darling_macro 0.20.8", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", ] [[package]] @@ -1533,13 +1603,24 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core 0.13.4", + "quote", + "syn 1.0.109", +] + [[package]] name = "darling_macro" version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ - "darling_core", + "darling_core 0.20.8", "quote", "syn 2.0.60", ] @@ -1639,6 +1720,30 @@ dependencies = [ "serde", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn 1.0.109", +] + [[package]] name = "digest" version = "0.10.7" @@ -1784,6 +1889,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-as-inner" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "enum-as-inner" version = "0.6.0" @@ -1829,7 +1946,7 @@ dependencies = [ "rustls 0.21.12", "rustls-pemfile 1.0.4", "scuffle-foundations", - "socket2", + "socket2 0.5.7", "tokio", "tower", "tracing", @@ -1929,19 +2046,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ffmpeg" -version = "0.1.0" -dependencies = [ - "bytes", - "crossbeam-channel", - "ffmpeg-sys-next", - "libc", - "scuffle-utils", - "tokio", - "tracing", -] - [[package]] name = "ffmpeg-sys-next" version = "7.0.0" @@ -2083,13 +2187,13 @@ dependencies = [ "rustls 0.22.4", "rustls-native-certs 0.7.0", "rustls-webpki 0.102.3", - "semver", - "socket2", + "semver 1.0.22", + "socket2 0.5.7", "tokio", "tokio-rustls 0.25.0", "tokio-stream", "tokio-util", - "trust-dns-resolver", + "trust-dns-resolver 0.23.2", "url", "urlencoding", ] @@ -2100,6 +2204,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.30" @@ -2654,7 +2764,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -2755,7 +2865,7 @@ dependencies = [ "http-body 1.0.0", "hyper 1.3.1", "pin-project-lite", - "socket2", + "socket2 0.5.7", "tokio", "tower", "tower-service", @@ -2791,6 +2901,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.4.0" @@ -2914,7 +3035,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2", + "socket2 0.5.7", "widestring", "windows-sys 0.48.0", "winreg 0.50.0", @@ -3200,6 +3321,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "matchit" version = "0.7.3" @@ -3286,6 +3413,53 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" +[[package]] +name = "mongodb" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef206acb1b72389b49bc9985efe7eb1f8a9bb18e5680d262fac26c07f44025f1" +dependencies = [ + "async-trait", + "base64 0.13.1", + "bitflags 1.3.2", + "bson", + "chrono", + "derivative", + "derive_more", + "futures-core", + "futures-executor", + "futures-io", + "futures-util", + "hex", + "hmac", + "lazy_static", + "md-5", + "pbkdf2", + "percent-encoding", + "rand", + "rustc_version_runtime", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_bytes", + "serde_with", + "sha-1", + "sha2", + "socket2 0.4.10", + "stringprep", + "strsim 0.10.0", + "take_mut", + "thiserror", + "tokio", + "tokio-rustls 0.24.1", + "tokio-util", + "trust-dns-proto 0.21.2", + "trust-dns-resolver 0.21.2", + "typed-builder", + "uuid", + "webpki-roots 0.25.4", +] + [[package]] name = "mp4" version = "0.0.1" @@ -3838,6 +4012,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", +] + [[package]] name = "pbr" version = "1.1.1" @@ -4082,26 +4265,25 @@ dependencies = [ "async-trait", "aws-config", "aws-sdk-s3", - "binary-helper", "byteorder", "bytes", + "chrono", "fast_image_resize", - "ffmpeg", "file-format", "futures", "gifski", "imgref", "libavif-sys", "libwebp-sys2", + "mongodb", "num_cpus", - "pb", "png", "postgres-from-row", "prost 0.12.4", "reqwest", "rgb", "scopeguard", - "scuffle-config", + "scuffle-ffmpeg", "scuffle-utils", "serde", "serde_json", @@ -4109,6 +4291,7 @@ dependencies = [ "thiserror", "tokio", "tonic", + "tonic-build", "tracing", "ulid", ] @@ -4173,7 +4356,7 @@ name = "postgres-from-row-derive" version = "0.5.2" source = "git+https://github.com/ScuffleTV/postgres-from-row.git?branch=troy/from_fn#3a775f225aae7c0f54e404f3f07aa13fcec2cc9b" dependencies = [ - "darling", + "darling 0.20.8", "proc-macro2", "quote", "syn 2.0.60", @@ -4486,7 +4669,7 @@ checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" dependencies = [ "bytes", "libc", - "socket2", + "socket2 0.5.7", "tracing", "windows-sys 0.48.0", ] @@ -4500,6 +4683,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -4722,7 +4911,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.26.1", "winreg 0.52.0", ] @@ -4862,13 +5051,32 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.22", +] + +[[package]] +name = "rustc_version_runtime" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d31b7153270ebf48bf91c65ae5b0c00e749c4cfad505f66530ac74950249582f" +dependencies = [ + "rustc_version 0.2.3", + "semver 0.9.0", ] [[package]] @@ -5063,7 +5271,7 @@ name = "scuffle-config" version = "0.0.1" dependencies = [ "clap", - "convert_case", + "convert_case 0.6.0", "humantime", "num-order", "scuffle_config_derive", @@ -5080,6 +5288,19 @@ dependencies = [ "uuid", ] +[[package]] +name = "scuffle-ffmpeg" +version = "0.1.0" +dependencies = [ + "bytes", + "crossbeam-channel", + "ffmpeg-sys-next", + "libc", + "scuffle-utils", + "tokio", + "tracing", +] + [[package]] name = "scuffle-foundations" version = "0.0.0" @@ -5121,7 +5342,7 @@ dependencies = [ "scuffle-foundations-macros", "serde", "serde_yaml", - "socket2", + "socket2 0.5.7", "spin 0.9.8", "thiserror", "thread_local", @@ -5139,8 +5360,8 @@ dependencies = [ name = "scuffle-foundations-macros" version = "0.0.0" dependencies = [ - "convert_case", - "darling", + "convert_case 0.6.0", + "darling 0.20.8", "proc-macro2", "quote", "syn 2.0.60", @@ -5179,7 +5400,7 @@ dependencies = [ "tonic-build", "tower", "tracing", - "trust-dns-resolver", + "trust-dns-resolver 0.23.2", "ulid", ] @@ -5249,12 +5470,27 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.200" @@ -5285,6 +5521,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.200" @@ -5322,6 +5567,7 @@ version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -5378,6 +5624,28 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling 0.13.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -5391,6 +5659,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha1" version = "0.10.6" @@ -5505,6 +5784,16 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.7" @@ -5704,6 +5993,18 @@ dependencies = [ "version-compare", ] +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.14" @@ -5854,7 +6155,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] @@ -5900,7 +6201,7 @@ dependencies = [ "postgres-protocol", "postgres-types", "rand", - "socket2", + "socket2 0.5.7", "tokio", "tokio-util", "whoami", @@ -6233,6 +6534,31 @@ dependencies = [ "serde_json", ] +[[package]] +name = "trust-dns-proto" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner 0.4.0", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "log", + "rand", + "smallvec", + "thiserror", + "tinyvec", + "tokio", + "url", +] + [[package]] name = "trust-dns-proto" version = "0.23.2" @@ -6242,7 +6568,7 @@ dependencies = [ "async-trait", "cfg-if", "data-encoding", - "enum-as-inner", + "enum-as-inner 0.6.0", "futures-channel", "futures-io", "futures-util", @@ -6258,6 +6584,26 @@ dependencies = [ "url", ] +[[package]] +name = "trust-dns-resolver" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "parking_lot", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "trust-dns-proto 0.21.2", +] + [[package]] name = "trust-dns-resolver" version = "0.23.2" @@ -6276,7 +6622,7 @@ dependencies = [ "thiserror", "tokio", "tracing", - "trust-dns-proto", + "trust-dns-proto 0.23.2", ] [[package]] @@ -6338,6 +6684,17 @@ dependencies = [ "utf-8", ] +[[package]] +name = "typed-builder" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "typenum" version = "1.17.0" @@ -6723,7 +7080,6 @@ dependencies = [ "bytesio", "chrono", "dotenvy", - "ffmpeg", "flv", "futures", "futures-util", @@ -6734,6 +7090,7 @@ dependencies = [ "portpicker", "prost 0.12.4", "scuffle-config", + "scuffle-ffmpeg", "scuffle-utils", "serde", "serde_json", @@ -6874,6 +7231,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "webpki-roots" version = "0.26.1" @@ -7153,6 +7516,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x509-certificate" version = "0.23.1" diff --git a/Cargo.toml b/Cargo.toml index a89f8bf2..0ecc4c35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,7 @@ h265 = { path = "video/lib/h265" } mp4 = { path = "video/lib/mp4" } rtmp = { path = "video/lib/rtmp" } transmuxer = { path = "video/lib/transmuxer" } -utils = { path = "utils", default-features = false, package = "scuffle-utils" } +scuffle-utils = { path = "utils", default-features = false } config = { path = "config", package = "scuffle-config" } pb = { path = "proto" } video-common = { path = "video/common" } @@ -62,7 +62,7 @@ video-edge = { path = "video/edge" } video-ingest = { path = "video/ingest" } video-transcoder = { path = "video/transcoder" } binary-helper = { path = "binary-helper" } -ffmpeg = { path = "ffmpeg" } +scuffle-ffmpeg = { path = "ffmpeg" } # These patches are pending PRs to the upstream crates # TODO: Remove these once the PRs are merged diff --git a/binary-helper/Cargo.toml b/binary-helper/Cargo.toml index e6d17bcd..f4d264b1 100644 --- a/binary-helper/Cargo.toml +++ b/binary-helper/Cargo.toml @@ -40,5 +40,5 @@ postgres-from-row = { version = "0.5" } prost = { version = "0.12" } config = { workspace = true } -utils = { workspace = true, features = ["all"] } +scuffle-utils = { workspace = true, features = ["all"] } pb = { workspace = true } diff --git a/binary-helper/src/global.rs b/binary-helper/src/global.rs index a2b64469..2a964708 100644 --- a/binary-helper/src/global.rs +++ b/binary-helper/src/global.rs @@ -9,10 +9,10 @@ use fred::interfaces::ClientLike; use fred::types::ServerConfig; use hyper::StatusCode; use rustls::RootCertStore; -use utils::database::deadpool_postgres::{ManagerConfig, PoolConfig, RecyclingMethod, Runtime}; -use utils::database::tokio_postgres::NoTls; -use utils::database::Pool; -use utils::http::RouteError; +use scuffle_utils::database::deadpool_postgres::{ManagerConfig, PoolConfig, RecyclingMethod, Runtime}; +use scuffle_utils::database::tokio_postgres::NoTls; +use scuffle_utils::database::Pool; +use scuffle_utils::http::RouteError; use crate::config::{DatabaseConfig, NatsConfig, RedisConfig}; @@ -40,7 +40,7 @@ macro_rules! impl_global_traits { impl binary_helper::global::GlobalDb for $struct { #[inline(always)] - fn db(&self) -> &Arc { + fn db(&self) -> &Arc { &self.db } } @@ -50,7 +50,7 @@ macro_rules! impl_global_traits { } pub trait GlobalCtx { - fn ctx(&self) -> &utils::context::Context; + fn ctx(&self) -> &scuffle_utils::context::Context; } pub trait GlobalConfig { @@ -124,16 +124,16 @@ pub async fn setup_nats( Ok((nats, jetstream)) } -pub async fn setup_database(config: &DatabaseConfig) -> anyhow::Result> { +pub async fn setup_database(config: &DatabaseConfig) -> anyhow::Result> { let mut pg_config = config .uri - .parse::() + .parse::() .context("invalid database uri")?; pg_config.ssl_mode(if config.tls.is_some() { - utils::database::tokio_postgres::config::SslMode::Require + scuffle_utils::database::tokio_postgres::config::SslMode::Require } else { - utils::database::tokio_postgres::config::SslMode::Disable + scuffle_utils::database::tokio_postgres::config::SslMode::Disable }); let manager = if let Some(tls) = &config.tls { @@ -164,7 +164,7 @@ pub async fn setup_database(config: &DatabaseConfig) -> anyhow::Result anyhow::Result anyhow::Result anyhow::Result diff --git a/config/src/sources/cli.rs b/config/src/sources/cli.rs index d0f49e0d..a384965b 100644 --- a/config/src/sources/cli.rs +++ b/config/src/sources/cli.rs @@ -447,6 +447,6 @@ impl CliSource { impl Source for CliSource { fn get_key(&self, path: &KeyPath) -> Result> { - utils::get_key::(&self.value, path).map_err(|e| e.with_source(ErrorSource::Cli)) + scuffle_utilsget_key::(&self.value, path).map_err(|e| e.with_source(ErrorSource::Cli)) } } diff --git a/config/src/sources/env.rs b/config/src/sources/env.rs index 4039343b..66390c5f 100644 --- a/config/src/sources/env.rs +++ b/config/src/sources/env.rs @@ -174,6 +174,6 @@ fn extract_keys( impl Source for EnvSource { fn get_key(&self, path: &KeyPath) -> Result> { - utils::get_key::(&self.value, path).map_err(|e| e.with_source(ErrorSource::Env)) + scuffle_utilsget_key::(&self.value, path).map_err(|e| e.with_source(ErrorSource::Env)) } } diff --git a/config/src/sources/file/mod.rs b/config/src/sources/file/mod.rs index cc5f598b..5f43a0d9 100644 --- a/config/src/sources/file/mod.rs +++ b/config/src/sources/file/mod.rs @@ -145,6 +145,6 @@ impl FileSource { impl Source for FileSource { fn get_key(&self, path: &KeyPath) -> Result> { - utils::get_key::(&self.content, path).map_err(|e| e.with_source(ErrorSource::File(self.location.clone()))) + scuffle_utilsget_key::(&self.content, path).map_err(|e| e.with_source(ErrorSource::File(self.location.clone()))) } } diff --git a/config/src/sources/manual.rs b/config/src/sources/manual.rs index 25c457b1..e2496fe1 100644 --- a/config/src/sources/manual.rs +++ b/config/src/sources/manual.rs @@ -92,7 +92,7 @@ impl ManualSource { impl Source for ManualSource { fn get_key(&self, path: &crate::KeyPath) -> crate::Result> { match &self.value { - Some(value) => utils::get_key::(value, path).map_err(|e| e.with_source(ErrorSource::Manual)), + Some(value) => scuffle_utilsget_key::(value, path).map_err(|e| e.with_source(ErrorSource::Manual)), None => Ok(None), } } diff --git a/ffmpeg/Cargo.toml b/ffmpeg/Cargo.toml index 278af74a..c8b980d7 100644 --- a/ffmpeg/Cargo.toml +++ b/ffmpeg/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ffmpeg" +name = "scuffle-ffmpeg" version = "0.1.0" edition = "2021" license = "MIT OR Apache-2.0" @@ -11,11 +11,11 @@ bytes = { optional = true, version = "1" } tokio = { optional = true, version = "1" } crossbeam-channel = { optional = true, version = "0.5" } tracing = { optional = true, version = "0.1" } -utils = { workspace = true, optional = true } +scuffle-utils = { path = "../utils", version = "*", optional = true, features = ["task"]} [features] default = [] -task-abort = ["dep:utils"] +task-abort = ["dep:scuffle-utils"] channel = ["dep:bytes"] tokio-channel = ["channel", "dep:tokio"] crossbeam-channel = ["channel", "dep:crossbeam-channel"] diff --git a/ffmpeg/src/decoder.rs b/ffmpeg/src/decoder.rs index 57add9ad..3e58adff 100644 --- a/ffmpeg/src/decoder.rs +++ b/ffmpeg/src/decoder.rs @@ -153,7 +153,7 @@ impl GenericDecoder { pub fn send_packet(&mut self, packet: &Packet) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _guard = utils::task::AbortGuard::new(); + let _guard = scuffle_utils::task::AbortGuard::new(); // Safety: `packet` is a valid pointer, and `self.decoder` is a valid pointer. let ret = unsafe { avcodec_send_packet(self.decoder.as_mut_ptr(), packet.as_ptr()) }; @@ -166,7 +166,7 @@ impl GenericDecoder { pub fn send_eof(&mut self) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _guard = utils::task::AbortGuard::new(); + let _guard = scuffle_utils::task::AbortGuard::new(); // Safety: `self.decoder` is a valid pointer. let ret = unsafe { avcodec_send_packet(self.decoder.as_mut_ptr(), std::ptr::null()) }; @@ -179,7 +179,7 @@ impl GenericDecoder { pub fn receive_frame(&mut self) -> Result, FfmpegError> { #[cfg(feature = "task-abort")] - let _guard = utils::task::AbortGuard::new(); + let _guard = scuffle_utils::task::AbortGuard::new(); let mut frame = Frame::new()?; diff --git a/ffmpeg/src/encoder.rs b/ffmpeg/src/encoder.rs index 5c055066..238eae47 100644 --- a/ffmpeg/src/encoder.rs +++ b/ffmpeg/src/encoder.rs @@ -427,7 +427,7 @@ impl Encoder { settings: impl Into, ) -> Result { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); if codec.as_ptr().is_null() { return Err(FfmpegError::NoEncoder); @@ -490,7 +490,7 @@ impl Encoder { pub fn send_eof(&mut self) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); // Safety: `self.encoder` is a valid pointer. let ret = unsafe { avcodec_send_frame(self.encoder.as_mut_ptr(), std::ptr::null()) }; @@ -503,7 +503,7 @@ impl Encoder { pub fn send_frame(&mut self, frame: &Frame) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); // Safety: `self.encoder` and `frame` are valid pointers. let ret = unsafe { avcodec_send_frame(self.encoder.as_mut_ptr(), frame.as_ptr()) }; @@ -516,7 +516,7 @@ impl Encoder { pub fn receive_packet(&mut self) -> Result, FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); let mut packet = Packet::new()?; @@ -632,7 +632,7 @@ impl MuxerEncoder { pub fn send_eof(&mut self) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); self.encoder.send_eof()?; self.handle_packets()?; diff --git a/ffmpeg/src/filter_graph.rs b/ffmpeg/src/filter_graph.rs index 0db49873..65d7f116 100644 --- a/ffmpeg/src/filter_graph.rs +++ b/ffmpeg/src/filter_graph.rs @@ -15,7 +15,7 @@ unsafe impl Send for FilterGraph {} impl FilterGraph { pub fn new() -> Result { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); // Safety: the pointer returned from avfilter_graph_alloc is valid unsafe { Self::wrap(avfilter_graph_alloc()) } @@ -24,7 +24,7 @@ impl FilterGraph { /// Safety: `ptr` must be a valid pointer to an `AVFilterGraph`. unsafe fn wrap(ptr: *mut AVFilterGraph) -> Result { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); Ok(Self( SmartPtr::wrap_non_null(ptr, |ptr| unsafe { avfilter_graph_free(ptr) }).ok_or(FfmpegError::Alloc)?, @@ -41,7 +41,7 @@ impl FilterGraph { pub fn add(&mut self, filter: Filter, name: &str, args: &str) -> Result, FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); let name = CString::new(name).expect("failed to convert name to CString"); let args = CString::new(args).expect("failed to convert args to CString"); @@ -239,7 +239,7 @@ unsafe impl Send for FilterContextSource<'_> {} impl FilterContextSource<'_> { pub fn send_frame(&mut self, frame: &Frame) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); // Safety: `frame` is a valid pointer, and `self.0` is a valid pointer. unsafe { @@ -252,7 +252,7 @@ impl FilterContextSource<'_> { pub fn send_eof(&mut self, pts: Option) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); // Safety: `self.0` is a valid pointer. unsafe { @@ -276,7 +276,7 @@ unsafe impl Send for FilterContextSink<'_> {} impl FilterContextSink<'_> { pub fn receive_frame(&mut self) -> Result, FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); let mut frame = Frame::new()?; diff --git a/ffmpeg/src/io/internal.rs b/ffmpeg/src/io/internal.rs index 5d6cb418..33b08ad1 100644 --- a/ffmpeg/src/io/internal.rs +++ b/ffmpeg/src/io/internal.rs @@ -113,7 +113,7 @@ impl Default for InnerOptions { impl Inner { pub fn new(data: T, options: InnerOptions) -> Result { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); // Safety: av_malloc is safe to call let buffer = unsafe { @@ -228,7 +228,7 @@ impl Inner<()> { pub fn open_output(path: &str) -> Result { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); let path = std::ffi::CString::new(path).expect("Failed to convert path to CString"); diff --git a/ffmpeg/src/io/output.rs b/ffmpeg/src/io/output.rs index ddbfa212..c16e06a7 100644 --- a/ffmpeg/src/io/output.rs +++ b/ffmpeg/src/io/output.rs @@ -150,7 +150,7 @@ impl Output { pub fn add_stream(&mut self, codec: Option<*const AVCodec>) -> Option> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); // Safety: `avformat_new_stream` is safe to call. let stream = unsafe { avformat_new_stream(self.as_mut_ptr(), codec.unwrap_or_else(std::ptr::null)) }; @@ -168,7 +168,7 @@ impl Output { pub fn copy_stream<'a>(&'a mut self, stream: &Stream<'_>) -> Option> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); let codec_param = stream.codec_parameters()?; @@ -196,7 +196,7 @@ impl Output { pub fn write_header(&mut self) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); if self.witten_header { return Err(FfmpegError::Arguments("header already written")); @@ -217,7 +217,7 @@ impl Output { pub fn write_header_with_options(&mut self, options: &mut Dictionary) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); if self.witten_header { return Err(FfmpegError::Arguments("header already written")); @@ -238,7 +238,7 @@ impl Output { pub fn write_trailer(&mut self) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); if !self.witten_header { return Err(FfmpegError::Arguments("header not written")); @@ -255,7 +255,7 @@ impl Output { pub fn write_interleaved_packet(&mut self, mut packet: Packet) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); if !self.witten_header { return Err(FfmpegError::Arguments("header not written")); @@ -273,7 +273,7 @@ impl Output { pub fn write_packet(&mut self, packet: &Packet) -> Result<(), FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); if !self.witten_header { return Err(FfmpegError::Arguments("header not written")); diff --git a/ffmpeg/src/packet.rs b/ffmpeg/src/packet.rs index b6060c36..dcc0c6a7 100644 --- a/ffmpeg/src/packet.rs +++ b/ffmpeg/src/packet.rs @@ -18,7 +18,7 @@ impl<'a> Packets<'a> { pub fn receive(&mut self) -> Result, FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); let mut packet = Packet::new()?; diff --git a/ffmpeg/src/scalar.rs b/ffmpeg/src/scalar.rs index 8ab06906..feade65c 100644 --- a/ffmpeg/src/scalar.rs +++ b/ffmpeg/src/scalar.rs @@ -88,7 +88,7 @@ impl Scalar { pub fn process<'a>(&'a mut self, frame: &Frame) -> Result<&'a VideoFrame, FfmpegError> { #[cfg(feature = "task-abort")] - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); // Safety: `frame` is a valid pointer, and `self.ptr` is a valid pointer. let ret = unsafe { diff --git a/image_processor/Cargo.toml b/image_processor/Cargo.toml index 96b8fd8d..ff8e4819 100644 --- a/image_processor/Cargo.toml +++ b/image_processor/Cargo.toml @@ -36,9 +36,12 @@ num_cpus = "1.16" bytes = "1.0" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } fast_image_resize = "3.0.4" +chrono = "0.4" -utils = { workspace = true, features = ["all"] } -config = { workspace = true } -pb = { workspace = true } -binary-helper = { workspace = true } -ffmpeg = { workspace = true, features = ["task-abort", "tracing"] } +scuffle-utils = { version = "*", path = "../utils", features = ["all"] } +scuffle-ffmpeg = { version = "*", path = "../ffmpeg", features = ["task-abort", "tracing"] } + +mongodb = { version = "2.0", features = ["tokio-runtime"] } + +[build-dependencies] +tonic-build = "0.11" diff --git a/image_processor/build.rs b/image_processor/build.rs new file mode 100644 index 00000000..c41c6e69 --- /dev/null +++ b/image_processor/build.rs @@ -0,0 +1,6 @@ +fn main() -> Result<(), Box> { + tonic_build::configure() + .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]") + .compile(&["proto/scuffle/image_processor/service.proto"], &["proto/"])?; + Ok(()) +} diff --git a/image_processor/proto/scuffle/image_processor/service.proto b/image_processor/proto/scuffle/image_processor/service.proto new file mode 100644 index 00000000..47ed2dce --- /dev/null +++ b/image_processor/proto/scuffle/image_processor/service.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +package scuffle.image_processor; + +import "scuffle/image_processor/types.proto"; + +// The ImageProcessor service provides methods to process images +service ImageProcessor { + // Submit a task to process an image + rpc ProcessImage(ProcessImageRequest) returns (ProcessImageResponse) {} + // Cancel a task + rpc CancelTask(CancelTaskRequest) returns (CancelTaskResponse) {} +} + +// The Payload for a ImageProcessor.ProcessImage request +message ProcessImageRequest { + // The task to process + Task task = 1; + + // The priority of the task + // The higher the priority, the sooner the task will be processed + uint32 priority = 2; + + // The time-to-live of the task in seconds + // If the task has not started processing within the TTL, it will be removed. + optional uint32 ttl = 3; + + // Optionally provide an image to process + // Providing an image will override the input image path in the task + optional InputUpload input_upload = 4; +} + +// The Payload for a ImageProcessor.ProcessImage response +message ProcessImageResponse { + // A unique identifier for the task + string id = 1; + // Pre-errors that occurred when creating the task. + repeated Error errors = 2; +} + +// The Payload for a ImageProcessor.CancelTask request +message CancelTaskRequest { + // The unique identifier of the task to cancel + string id = 1; +} + +// The Payload for a ImageProcessor.CancelTask response +message CancelTaskResponse { + // The status of the task + optional string error = 1; +} diff --git a/image_processor/proto/scuffle/image_processor/types.proto b/image_processor/proto/scuffle/image_processor/types.proto new file mode 100644 index 00000000..eac27b6d --- /dev/null +++ b/image_processor/proto/scuffle/image_processor/types.proto @@ -0,0 +1,249 @@ +syntax = "proto3"; + +package scuffle.image_processor; + +// When submitting a task these formats are used to determine what the image processor should do. +// If the image processor is unable to generate a requested format it will not hard fail unless the task is set to hard fail. +// Otherwise it will generate as many formats as it can and return the results with any errors in the response. +enum ImageFormat { + WEBP_ANIM = 0; + AVIF_ANIM = 1; + GIF_ANIM = 2; + WEBP_STATIC = 3; + AVIF_STATIC = 4; + PNG_STATIC = 5; +} + +// The resize method determines how the image processor should resize the image. +enum ResizeMethod { + // Fit will resize the image to fit within the desired dimensions without changing the aspect ratio. + Fit = 0; + // Stretch will stretch the image to fit the desired dimensions. (This will change the aspect ratio of the image.) + Stretch = 1; + // Pad will resize the image to fit the desired dimentions and pad the bottom left of the image with the background color if necessary. + PadBottomLeft = 2; + // Pad will resize the image to fit the desired dimentions and pad the bottom right of the image with the background color if necessary. + PadBottomRight = 3; + // Pad will resize the image to fit the desired dimentions and pad the top left of the image with the background color if necessary. + PadTopLeft = 4; + // Pad will resize the image to fit the desired dimentions and pad the top right of the image with the background color if necessary. + PadTopRight = 5; + // Pad will resize the image to fit the desired dimentions and pad the center of the image with the background color if necessary. + PadCenter = 6; + // Pad will resize the image to fit the desired dimentions and pad the center of the image with the background color if necessary. + PadCenterRight = 7; + // Pad will resize the image to fit the desired dimentions and pad the center of the image with the background color if necessary. + PadCenterLeft = 8; + // Pad will resize the image to fit the desired dimentions and pad the top center of the image with the background color if necessary. + PadTopCenter = 9; + // Pad will resize the image to fit the desired dimentions and pad the bottom center of the image with the background color if necessary. + PadBottomCenter = 10; + // Pad will resize the image to fit the desired dimentions and pad the top of the image with the background color if necessary, the left and right will be unchanged. + PadTop = 11; + // Pad will resize the image to fit the desired dimentions and pad the bottom of the image with the background color if necessary, the left and right will be unchanged. + PadBottom = 12; + // Pad will resize the image to fit the desired dimentions and pad the left of the image with the background color if necessary, the top and bottom will be unchanged. + PadLeft = 13; + // Pad will resize the image to fit the desired dimentions and pad the right of the image with the background color if necessary, the top and bottom will be unchanged. + PadRight = 14; +} + +// The resize algorithm determines the algorithm used to resize the image. +enum ResizeAlgorithm { + Nearest = 0; + Box = 1; + Bilinear = 2; + Hamming = 3; + CatmullRom = 4; + Mitchell = 5; + Lanczos3 = 6; +} + +// Limits are used to determine how much processing time and resources the image processor should use. +message Limits { + // The maximum amount of time the image processor should spend processing the image. + optional uint32 max_processing_time_ms = 1; + // The maximum input frame count the image processor should accept. + optional uint32 max_input_frame_count = 2; + // The maximum input width the image processor should accept. + optional uint32 max_input_width = 3; + // The maximum input height the image processor should accept. + optional uint32 max_input_height = 4; + // The maximum input file duration the image processor should accept. (if the input is a video or animated image) + optional uint32 max_input_duration_ms = 5; +} + +message Ratio { + // The width of the ratio. + uint32 width = 1; + // The height of the ratio. + uint32 height = 2; +} + +// Crop is used to determine what part of the image the image processor should crop. +// The processor will crop the image before resizing it. +message Crop { + // The x coordinate of the top left corner of the crop. + uint32 x = 1; + // The y coordinate of the top left corner of the crop. + uint32 y = 2; + // The width of the crop. + uint32 width = 3; + // The height of the crop. + uint32 height = 4; +} + +// Upscale is used to determine if the image processor should upscale the image. +enum Upscale { + Yes = 0; + No = 1; + NoPreserveSource = 2; +} + +// Provide extra information about the input to the image processor. +message InputMetadata { + // If the input is not animated, this will generate a fatal error. If there are not enough frames this will generate a fatal error. + // Otherwise this will be the frame used for static variants. + optional uint32 static_frame_index = 1; + // If this is different from the actual frame count the image processor will generate a fatal error. + optional uint32 frame_count = 2; + // If this is different from the actual width the image processor will generate a fatal error. + optional uint32 width = 3; + // If this is different from the actual height the image processor will generate a fatal error. + optional uint32 height = 4; +} + +// Scale is used to determine what the output image size should be. +message Scale { + // The width of the output image. (in pixels, use -1 to keep the aspect ratio) + int32 width = 1; + // The height of the output image. (in pixels, use -1 to keep the aspect ratio) + int32 height = 2; + // Name of the scale. ALlows for template arguments to be passed in. + // For example if the name is "thumbnail_{width}x{height}" and the width is 100 and the height is 200 the name will be "thumbnail_100x200". + // If not set will be "{width}x{height}" + // If multiple scales have the same name the processor will generate a fatal error. + optional string name = 3; + + // Allow upscale for this scale. + // If NoPreserveSource is set and this scale is larger than the input image we will just use the source dimensions. + // If Yes, we will upscale the image. + // If No, we will ignore this scale. + Upscale upscale = 4; +} + +message InputUpload { + // The input image as a binary blob. + bytes binary = 1; + + // The path to upload the image to. + // Must be in the format :// where drive is a drive defined in the image processor config. + // Allows for template arguments to be passed in. For example if the path is "images/{id}.png" and the id is 123 the path will be "images/123.png". + string path = 2; +} + +message Input { + // The path to the input image. + // Must be in the format :// where drive is a drive defined in the image processor config. + // This can be used in combination with the ImageUpload message to upload the image to a specific path. + // Allows for template arguments to be passed in. For example if the path is "images/{id}.png" and the id is 123 the path will be "images/123.png". + string path = 1; + // Extra information about the input image. + optional InputMetadata metadata = 2; +} + +message OutputFormat { + message Webp { + bool static = 1; + } + message Avif { + bool static = 1; + } + message Gif {} + message Png {} + + // The name is used in the template argument for the output path. + // By default the name is the same as the format. + // Webp (static) -> webp_static + // Webp (animated) -> webp_animated + // Avif (static) -> avif_static + // Avif (animated) -> avif_animated + // Gif -> gif + // Png -> png + string name = 1; + + oneof format { + Webp webp = 2; + Avif avif = 3; + Gif gif = 4; + Png png = 5; + } +} + +message Output { + // The image processor will save the results to this path. + // Must either be a format '://' where drive is a drive defined in the image processor config. + // Allows for template arguments to be passed in. For example if the path is "images/{id}/{scale}_{format}.{ext}" and the id is 123 the path will be "images/123/100x100_webp_static.webp". + // If multiple outputs resolve to the same path the processor will generate a fatal error. + string path = 1; + // The desired formats to encode the output image. + repeated OutputFormat formats = 2; + // The resize method used to resize the image. + ResizeMethod resize_method = 3; + // The resize algorithm used to resize the image. + ResizeAlgorithm resize_algorithm = 4; + // The crop used to crop the image before resizing. If the crop settings are not possible the processor will generate a fatal error. + optional Crop crop = 5; + // The minimum and maximum ratios for the scaled image. Used to prevent upscaling too much on wide or tall images. + // If the image does not fit into the min and max ratios the processor will generate a fatal error. If unset the processor will not check the ratios. + // These checks are done after the crop. If the resize method allows for padding or stretching we will use the padded or stretched dimentions to perform the check. + // If scales are provided that are not within the min and max ratios the processor will generate a fatal error. + optional Ratio min_ratio = 6; + optional Ratio max_ratio = 7; + // The target ratio for the scale image, if unset the processor will use the input ratio (after crop but before resize). + // The min and max ratios will be used to detemine if padding or stretching is needed to reach the target ratio. + optional Ratio target_ratio = 8; + // The desired scales of the output image. + repeated Scale scales = 9; +} + +// Events must be in the format +// :// where event_queue is a queue defined in the image processor config. +// The topic argument is used in the template for the event queue settings defined in the image processor config. +// Setting any of the events to an empty string will disable the event. +message Events { + // The event to trigger when the task is completed successfully + string on_success = 1; + // The event to trigger when the task fails + string on_fail = 2; + // The event to trigger when the task is cancelled + string on_cancel = 3; + // The event to trigger when the task is started + string on_start = 4; +} + +message Task { + // The input image to process. + Input input = 1; + // The output image to generate. + Output output = 2; + // Result output + Events events = 3; + // The limits for the image processor. + optional Limits limits = 4; +} + +message Error { + // The error message. + string message = 1; + // The error code. + ErrorCode code = 2; +} + +enum ErrorCode { + Unknown = 0; +} + +message EventPayload { + string id = 1; +} diff --git a/image_processor/src/config.rs b/image_processor/src/config.rs index adaeeb7a..41e66f86 100644 --- a/image_processor/src/config.rs +++ b/image_processor/src/config.rs @@ -1,49 +1,189 @@ -use binary_helper::config::{S3BucketConfig, S3CredentialsConfig}; -use ulid::Ulid; +use std::collections::HashMap; -#[derive(Debug, Clone, PartialEq, config::Config, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] #[serde(default)] pub struct ImageProcessorConfig { - /// The S3 Bucket which contains the source images - pub source_bucket: S3BucketConfig, - - /// The S3 Bucket which will contain the target images - pub target_bucket: S3BucketConfig, - + /// MongoDB database configuration + pub database: DatabaseConfig, + /// The disk configurations for the image processor + pub disks: Vec, + /// The event queues for the image processor + pub event_queues: Vec, /// Concurrency limit, defaults to number of CPUs pub concurrency: usize, +} - /// Instance ID (defaults to a random ULID) - pub instance_id: Ulid, +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +pub struct DatabaseConfig { + pub uri: String, +} - /// Allow http downloads - pub allow_http: bool, +impl Default for DatabaseConfig { + fn default() -> Self { + Self { + uri: "mongodb://localhost:27017".to_string(), + } + } } impl Default for ImageProcessorConfig { fn default() -> Self { Self { - source_bucket: S3BucketConfig { - name: "scuffle-image-processor".to_owned(), - endpoint: Some("http://localhost:9000".to_owned()), - region: "us-east-1".to_owned(), - credentials: S3CredentialsConfig { - access_key: Some("root".to_owned()), - secret_key: Some("scuffle123".to_owned()), - }, - }, - target_bucket: S3BucketConfig { - name: "scuffle-image-processor-public".to_owned(), - endpoint: Some("http://localhost:9000".to_owned()), - region: "us-east-1".to_owned(), - credentials: S3CredentialsConfig { - access_key: Some("root".to_owned()), - secret_key: Some("scuffle123".to_owned()), - }, - }, + database: DatabaseConfig::default(), + disks: vec![], + event_queues: vec![], concurrency: num_cpus::get(), - instance_id: Ulid::new(), - allow_http: true, } } } + +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +#[serde(tag = "kind")] +pub enum DiskConfig { + /// Local disk + Local(LocalDiskConfig), + /// S3 bucket + S3(S3DiskConfig), + /// Memory disk + Memory(MemoryDiskConfig), + /// HTTP disk + Http(HttpDiskConfig), + /// Public web http disk + PublicHttp(PublicHttpDiskConfig), +} + +#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] +#[serde(default)] +pub struct LocalDiskConfig { + /// The name of the disk + pub name: String, + /// The path to the local disk + pub path: std::path::PathBuf, + /// The disk mode + pub mode: DiskMode, +} + +#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] +#[serde(default)] +pub struct S3DiskConfig { + /// The name of the disk + pub name: String, + /// The S3 bucket name + pub bucket: String, + /// The S3 region + pub region: String, + /// The S3 access key + pub access_key: String, + /// The S3 secret key + pub secret_key: String, + /// The S3 endpoint + pub endpoint: Option, + /// The S3 bucket prefix path + pub path: Option, + /// Use path style + pub path_style: bool, + /// The disk mode + pub mode: DiskMode, + /// The maximum number of concurrent connections + pub max_connections: Option, +} + +#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] +#[serde(default)] +pub struct MemoryDiskConfig { + /// The name of the disk + pub name: String, + /// The maximum capacity of the memory disk + pub capacity: Option, + /// Global, shared memory disk for all tasks otherwise each task gets its + /// own memory disk + pub global: bool, + /// The disk mode + pub mode: DiskMode, +} + +#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] +#[serde(default)] +pub struct HttpDiskConfig { + /// The name of the disk + pub name: String, + /// The base URL for the HTTP disk + pub base_url: String, + /// The timeout for the HTTP disk + pub timeout: Option, + /// Allow insecure TLS + pub allow_insecure: bool, + /// The disk mode + pub mode: DiskMode, + /// The maximum number of concurrent connections + pub max_connections: Option, + /// Additional headers for the HTTP disk + pub headers: HashMap, + /// Write Method + pub write_method: String, +} + +#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] +pub enum DiskMode { + /// Read only + Read, + #[default] + /// Read and write + ReadWrite, + /// Write only + Write, +} + + +#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] +#[serde(default)] +/// Public http disks do not have a name because they will be invoked if the input path is a URL +/// that starts with 'http' or 'https'. Public http disks can only be read-only. +/// If you do not have a public http disk, the image processor will not be able to download images using HTTP. +pub struct PublicHttpDiskConfig { + /// The timeout for the HTTP disk + pub timeout: Option, + /// Allow insecure TLS + pub allow_insecure: bool, + /// The maximum number of concurrent connections + pub max_connections: Option, + /// Additional headers for the HTTP disk + pub headers: HashMap, + /// Whitelist of allowed domains or IPs can be subnets or CIDR ranges + /// IPs are compared after resolving the domain name + pub whitelist: Vec, + /// Blacklist of disallowed domains or IPs can be subnets or CIDR ranges + /// IPs are compared after resolving the domain name + pub blacklist: Vec, +} + +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +pub enum EventQueue { + Nats(NatsEventQueue), + Http(HttpEventQueue), + Redis(RedisEventQueue), +} + +#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] +#[serde(default)] +pub struct NatsEventQueue { + pub name: String, +} + +#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] +#[serde(default)] +pub struct HttpEventQueue { + pub name: String, + pub url: String, + pub timeout: Option, + pub allow_insecure: bool, + pub method: String, + pub headers: HashMap, +} + +#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] +#[serde(default)] +pub struct RedisEventQueue { + pub name: String, + pub url: String, +} diff --git a/image_processor/src/database.rs b/image_processor/src/database.rs index 1298a2b6..3b1d3f13 100644 --- a/image_processor/src/database.rs +++ b/image_processor/src/database.rs @@ -1,13 +1,14 @@ -use pb::scuffle::platform::internal::image_processor::Task; +use mongodb::bson::oid::ObjectId; use ulid::Ulid; -use utils::database::protobuf; -// The actual table has more columns but we only need id and task to process a -// job +use crate::pb::Task; -#[derive(Debug, Clone, Default, postgres_from_row::FromRow)] +#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)] pub struct Job { - pub id: Ulid, - #[from_row(from_fn = "protobuf")] + #[serde(rename = "_id")] + pub id: ObjectId, + pub priority: i32, + pub hold_until: Option>, pub task: Task, + pub claimed_by_id: Option, } diff --git a/image_processor/src/global.rs b/image_processor/src/global.rs index 8dd7af01..a4e458e6 100644 --- a/image_processor/src/global.rs +++ b/image_processor/src/global.rs @@ -1,35 +1,29 @@ -use binary_helper::s3::Bucket; +use scuffle_utils::context::Context; use crate::config::ImageProcessorConfig; -pub trait ImageProcessorState { - fn s3_source_bucket(&self) -> &Bucket; - fn s3_target_bucket(&self) -> &Bucket; - fn http_client(&self) -> &reqwest::Client; +pub struct ImageProcessorGlobalImpl { + ctx: Context, + config: ImageProcessorConfig, + http_client: reqwest::Client, } -pub trait ImageProcessorGlobal: - binary_helper::global::GlobalCtx - + binary_helper::global::GlobalConfigProvider - + binary_helper::global::GlobalNats - + binary_helper::global::GlobalDb - + binary_helper::global::GlobalConfig - + ImageProcessorState - + Send - + Sync - + 'static -{ +pub trait ImageProcessorGlobal: Send + Sync + 'static { + fn ctx(&self) -> &Context; + fn config(&self) -> &ImageProcessorConfig; + fn http_client(&self) -> &reqwest::Client; } -impl ImageProcessorGlobal for T where - T: binary_helper::global::GlobalCtx - + binary_helper::global::GlobalConfigProvider - + binary_helper::global::GlobalNats - + binary_helper::global::GlobalDb - + binary_helper::global::GlobalConfig - + ImageProcessorState - + Send - + Sync - + 'static -{ +impl ImageProcessorGlobal for ImageProcessorGlobalImpl { + fn ctx(&self) -> &Context { + &self.ctx + } + + fn config(&self) -> &ImageProcessorConfig { + &self.config + } + + fn http_client(&self) -> &reqwest::Client { + &self.http_client + } } diff --git a/image_processor/src/grpc.rs b/image_processor/src/grpc.rs index f770cd14..3e2ab358 100644 --- a/image_processor/src/grpc.rs +++ b/image_processor/src/grpc.rs @@ -4,6 +4,6 @@ use tonic::transport::server::Router; use crate::global::ImageProcessorGlobal; -pub fn add_routes(_: &Arc, router: Router) -> Router { +pub fn add_routes(_: &Arc, router: Router) -> Router { router } diff --git a/image_processor/src/lib.rs b/image_processor/src/lib.rs index 900f10f8..c7d8ee31 100644 --- a/image_processor/src/lib.rs +++ b/image_processor/src/lib.rs @@ -2,7 +2,7 @@ pub mod config; pub mod database; pub mod global; pub mod grpc; -pub mod migration; +pub mod pb; pub mod processor; #[cfg(test)] diff --git a/image_processor/src/main.rs b/image_processor/src/main.rs index 51620261..95c38824 100644 --- a/image_processor/src/main.rs +++ b/image_processor/src/main.rs @@ -6,8 +6,8 @@ use anyhow::Context as _; use binary_helper::global::{setup_database, setup_nats, GlobalCtx, GlobalDb, GlobalNats}; use binary_helper::{bootstrap, grpc_health, grpc_server, impl_global_traits}; use platform_image_processor::config::ImageProcessorConfig; +use scuffle_utils::context::Context; use tokio::select; -use utils::context::Context; #[derive(Debug, Clone, Default, config::Config, serde::Deserialize)] #[serde(default)] @@ -22,68 +22,6 @@ impl binary_helper::config::ConfigExtention for ExtConfig { // TODO: We don't need grpc and nats type AppConfig = binary_helper::config::AppConfig; -struct GlobalState { - ctx: Context, - db: Arc, - config: AppConfig, - nats: async_nats::Client, - jetstream: async_nats::jetstream::Context, - s3_source_bucket: binary_helper::s3::Bucket, - s3_target_bucket: binary_helper::s3::Bucket, - http_client: reqwest::Client, -} - -impl_global_traits!(GlobalState); - -impl binary_helper::global::GlobalConfigProvider for GlobalState { - #[inline(always)] - fn provide_config(&self) -> &ImageProcessorConfig { - &self.config.extra.image_processor - } -} - -impl platform_image_processor::global::ImageProcessorState for GlobalState { - #[inline(always)] - fn s3_source_bucket(&self) -> &binary_helper::s3::Bucket { - &self.s3_source_bucket - } - - #[inline(always)] - fn s3_target_bucket(&self) -> &binary_helper::s3::Bucket { - &self.s3_target_bucket - } - - #[inline(always)] - fn http_client(&self) -> &reqwest::Client { - &self.http_client - } -} - -impl binary_helper::Global for GlobalState { - async fn new(ctx: Context, config: AppConfig) -> anyhow::Result { - let db = setup_database(&config.database).await?; - let s3_source_bucket = config.extra.image_processor.source_bucket.setup(); - let s3_target_bucket = config.extra.image_processor.target_bucket.setup(); - - let (nats, jetstream) = setup_nats(&config.name, &config.nats).await?; - - let http_client = reqwest::Client::builder() - .user_agent(concat!("scuffle-image-processor/", env!("CARGO_PKG_VERSION"))) - .build()?; - - Ok(Self { - ctx, - db, - nats, - jetstream, - config, - s3_source_bucket, - s3_target_bucket, - http_client, - }) - } -} - pub fn main() { tokio::runtime::Builder::new_multi_thread() .enable_all() diff --git a/image_processor/src/migration/0001_initial.rs b/image_processor/src/migration/0001_initial.rs deleted file mode 100644 index ef04f173..00000000 --- a/image_processor/src/migration/0001_initial.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::sync::Arc; - -use utils::database::deadpool_postgres::Transaction; - -use super::Migration; -use crate::global::ImageProcessorGlobal; - -pub struct InitialMigration; - -#[async_trait::async_trait] -impl Migration for InitialMigration { - fn name(&self) -> &'static str { - "InitialMigration" - } - - fn version(&self) -> i32 { - 1 - } - - async fn up(&self, _: &Arc, tx: &Transaction<'_>) -> anyhow::Result<()> { - utils::database::query( - "CREATE TABLE image_processor_job ( - id UUID PRIMARY KEY, - hold_until TIMESTAMP WITH TIME ZONE, - priority INTEGER NOT NULL, - claimed_by_id UUID, - task bytea NOT NULL - );", - ) - .build() - .execute(tx) - .await?; - - utils::database::query("CREATE INDEX image_processor_job_hold_until_index ON image_processor_job (hold_until ASC);") - .build() - .execute(tx) - .await?; - - utils::database::query( - "CREATE INDEX image_processor_job_priority_index ON image_processor_job (priority DESC, id DESC);", - ) - .build() - .execute(tx) - .await?; - - Ok(()) - } - - async fn down(&self, _: &Arc, tx: &Transaction<'_>) -> anyhow::Result<()> { - utils::database::query("DROP TABLE image_jobs").build().execute(tx).await?; - - Ok(()) - } -} diff --git a/image_processor/src/migration/mod.rs b/image_processor/src/migration/mod.rs deleted file mode 100644 index 0f17526d..00000000 --- a/image_processor/src/migration/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::sync::Arc; - -use anyhow::Context; -use utils::database::deadpool_postgres::Transaction; - -use crate::global::ImageProcessorGlobal; - -#[path = "0001_initial.rs"] -mod initial; - -#[async_trait::async_trait] -trait Migration { - fn name(&self) -> &'static str; - fn version(&self) -> i32; - - async fn up(&self, global: &Arc, tx: &Transaction<'_>) -> anyhow::Result<()>; - async fn down(&self, global: &Arc, tx: &Transaction<'_>) -> anyhow::Result<()>; -} - -const fn migrations() -> &'static [&'static dyn Migration] { - &[&initial::InitialMigration] -} - -#[tracing::instrument(skip(global))] -async fn get_migrations(global: &Arc) -> anyhow::Result>> { - let migrations = migrations::(); - - let migration_version = match utils::database::query("SELECT version FROM image_processor_migrations") - .build_query_single_scalar::() - .fetch_one(global.db()) - .await - { - Ok(version) => version as usize, - Err(err) => { - tracing::info!("Initializing database: {}", err); - utils::database::query("CREATE TABLE image_processor_migrations (version INTEGER NOT NULL)") - .build() - .execute(global.db()) - .await - .context("Failed to create migration table")?; - - utils::database::query("INSERT INTO image_processor_migrations (version) VALUES (0)") - .build() - .execute(global.db()) - .await - .context("Failed to insert initial migration version")?; - - 0 - } - }; - - if migration_version > migrations.len() { - anyhow::bail!( - "Database is at version {}, but only {} migrations are available", - migration_version, - migrations.len() - ); - } - - Ok(migrations.iter().skip(migration_version).copied().collect()) -} - -#[tracing::instrument(skip(global, migration), fields(name = migration.name(), version = migration.version()))] -async fn run_migration( - global: &Arc, - migration: &'static dyn Migration, -) -> anyhow::Result<()> { - tracing::info!("Applying migration"); - - let mut client = global.db().get().await.context("Failed to get database connection")?; - let tx = client.transaction().await.context("Failed to start transaction")?; - - migration.up(global, &tx).await.context("Failed to apply migration")?; - - utils::database::query("UPDATE image_processor_migrations SET version = ") - .push_bind(migration.version() as i32) - .build() - .execute(&tx) - .await - .context("Failed to update migration version")?; - - tx.commit().await.context("Failed to commit transaction")?; - - tracing::info!("Migration applied"); - - Ok(()) -} - -#[tracing::instrument(skip(global))] -pub async fn run_migrations(global: &Arc) -> anyhow::Result<()> { - let migrations = get_migrations(global).await?; - - for migration in migrations { - run_migration(global, migration).await?; - } - - Ok(()) -} diff --git a/image_processor/src/pb.rs b/image_processor/src/pb.rs new file mode 100644 index 00000000..bb3de442 --- /dev/null +++ b/image_processor/src/pb.rs @@ -0,0 +1 @@ +tonic::include_proto!("scuffle.image_processor"); diff --git a/image_processor/src/processor/error.rs b/image_processor/src/processor/error.rs index c393684c..a1684a56 100644 --- a/image_processor/src/processor/error.rs +++ b/image_processor/src/processor/error.rs @@ -22,10 +22,10 @@ pub enum ProcessorError { SemaphoreAcquire(#[from] tokio::sync::AcquireError), #[error("database: {0}")] - Database(#[from] utils::database::tokio_postgres::Error), + Database(#[from] scuffle_utils::database::tokio_postgres::Error), #[error("database pool: {0}")] - DatabasePool(#[from] utils::database::deadpool_postgres::PoolError), + DatabasePool(#[from] scuffle_utils::database::deadpool_postgres::PoolError), #[error("lost job")] LostJob, diff --git a/image_processor/src/processor/job/decoder/ffmpeg.rs b/image_processor/src/processor/job/decoder/ffmpeg.rs index 72190320..e674bc1c 100644 --- a/image_processor/src/processor/job/decoder/ffmpeg.rs +++ b/image_processor/src/processor/job/decoder/ffmpeg.rs @@ -10,9 +10,9 @@ use crate::processor::error::{DecoderError, ProcessorError, Result}; use crate::processor::job::frame::Frame; pub struct FfmpegDecoder<'data> { - input: ffmpeg::io::Input>>, - decoder: ffmpeg::decoder::VideoDecoder, - scaler: ffmpeg::scalar::Scalar, + input: scuffle_ffmpeg::io::Input>>, + decoder: scuffle_ffmpeg::decoder::VideoDecoder, + scaler: scuffle_ffmpeg::scalar::Scalar, info: DecoderInfo, input_stream_index: i32, average_frame_duration_ts: u64, @@ -30,17 +30,17 @@ static FFMPEG_LOGGING_INITIALIZED: std::sync::Once = std::sync::Once::new(); impl<'data> FfmpegDecoder<'data> { pub fn new(job: &Job, data: Cow<'data, [u8]>) -> Result { FFMPEG_LOGGING_INITIALIZED.call_once(|| { - ffmpeg::log::log_callback_tracing(); + scuffle_ffmpeg::log::log_callback_tracing(); }); - let input = ffmpeg::io::Input::seekable(std::io::Cursor::new(data)) + let input = scuffle_ffmpeg::io::Input::seekable(std::io::Cursor::new(data)) .context("input") .map_err(DecoderError::Other) .map_err(ProcessorError::FfmpegDecode)?; let input_stream = input .streams() - .best(ffmpeg::ffi::AVMediaType::AVMEDIA_TYPE_VIDEO) + .best(scuffle_ffmpeg::ffi::AVMediaType::AVMEDIA_TYPE_VIDEO) .ok_or_else(|| ProcessorError::FfmpegDecode(DecoderError::Other(anyhow!("no video stream"))))?; let input_stream_index = input_stream.index(); @@ -58,12 +58,12 @@ impl<'data> FfmpegDecoder<'data> { )))); } - let decoder = match ffmpeg::decoder::Decoder::new(&input_stream) + let decoder = match scuffle_ffmpeg::decoder::Decoder::new(&input_stream) .context("video decoder") .map_err(DecoderError::Other) .map_err(ProcessorError::FfmpegDecode)? { - ffmpeg::decoder::Decoder::Video(decoder) => decoder, + scuffle_ffmpeg::decoder::Decoder::Video(decoder) => decoder, _ => { return Err(ProcessorError::FfmpegDecode(DecoderError::Other(anyhow!( "not a video decoder" @@ -97,13 +97,13 @@ impl<'data> FfmpegDecoder<'data> { return Err(ProcessorError::FfmpegDecode(DecoderError::TooLong(duration))); } - let scaler = ffmpeg::scalar::Scalar::new( + let scaler = scuffle_ffmpeg::scalar::Scalar::new( decoder.width(), decoder.height(), decoder.pixel_format(), decoder.width(), decoder.height(), - ffmpeg::ffi::AVPixelFormat::AV_PIX_FMT_RGBA, + scuffle_ffmpeg::ffi::AVPixelFormat::AV_PIX_FMT_RGBA, ) .context("scaler") .map_err(DecoderError::Other) diff --git a/image_processor/src/processor/job/decoder/libavif.rs b/image_processor/src/processor/job/decoder/libavif.rs index b6fa116d..bc2a5dd0 100644 --- a/image_processor/src/processor/job/decoder/libavif.rs +++ b/image_processor/src/processor/job/decoder/libavif.rs @@ -114,7 +114,7 @@ impl Decoder for AvifDecoder<'_> { } fn decode(&mut self) -> Result> { - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); if AvifError::from_code(unsafe { libavif_sys::avifDecoderNextImage(self.decoder.as_ptr()) }).is_err() { return Ok(None); diff --git a/image_processor/src/processor/job/decoder/libwebp.rs b/image_processor/src/processor/job/decoder/libwebp.rs index 24f2bae7..201999e0 100644 --- a/image_processor/src/processor/job/decoder/libwebp.rs +++ b/image_processor/src/processor/job/decoder/libwebp.rs @@ -102,7 +102,7 @@ impl Decoder for WebpDecoder<'_> { } fn decode(&mut self) -> Result> { - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); let mut buf = std::ptr::null_mut(); let previous_timestamp = self.timestamp; diff --git a/image_processor/src/processor/job/encoder/gifski.rs b/image_processor/src/processor/job/encoder/gifski.rs index 2dac130b..642e768a 100644 --- a/image_processor/src/processor/job/encoder/gifski.rs +++ b/image_processor/src/processor/job/encoder/gifski.rs @@ -1,5 +1,5 @@ use anyhow::Context; -use utils::task::Task; +use scuffle_utils::task::Task; use super::{Encoder, EncoderFrontend, EncoderInfo, EncoderSettings}; use crate::processor::error::{ProcessorError, Result}; @@ -59,7 +59,7 @@ impl Encoder for GifskiEncoder { } fn add_frame(&mut self, frame: &Frame) -> Result<()> { - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); let frame = frame.to_owned(); self.info.height = frame.image.height(); @@ -74,7 +74,7 @@ impl Encoder for GifskiEncoder { } fn finish(self) -> Result> { - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); drop(self.collector); diff --git a/image_processor/src/processor/job/encoder/libavif.rs b/image_processor/src/processor/job/encoder/libavif.rs index 8c7254a1..009adad7 100644 --- a/image_processor/src/processor/job/encoder/libavif.rs +++ b/image_processor/src/processor/job/encoder/libavif.rs @@ -85,7 +85,7 @@ impl Encoder for AvifEncoder { } fn add_frame(&mut self, frame: &Frame) -> Result<()> { - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); if self.rgb.is_none() { self.image.as_mut().width = frame.image.width() as u32; @@ -136,7 +136,7 @@ impl Encoder for AvifEncoder { } fn finish(mut self) -> Result> { - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); if self.rgb.is_none() { return Err(ProcessorError::AvifEncode(anyhow::anyhow!("no frames added"))); diff --git a/image_processor/src/processor/job/encoder/libwebp.rs b/image_processor/src/processor/job/encoder/libwebp.rs index 8ffce5a8..b63281a6 100644 --- a/image_processor/src/processor/job/encoder/libwebp.rs +++ b/image_processor/src/processor/job/encoder/libwebp.rs @@ -78,7 +78,7 @@ impl WebpEncoder { } fn flush_frame(&mut self, duration: u64) -> Result<()> { - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); // Safety: The picture is valid. wrap_error( @@ -106,7 +106,7 @@ impl Encoder for WebpEncoder { } fn add_frame(&mut self, frame: &Frame) -> Result<()> { - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); if self.first_duration.is_none() && self.encoder.is_none() { self.picture.width = frame.image.width() as _; @@ -178,7 +178,7 @@ impl Encoder for WebpEncoder { } fn finish(mut self) -> Result> { - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); let timestamp = self.timestamp(); diff --git a/image_processor/src/processor/job/encoder/png.rs b/image_processor/src/processor/job/encoder/png.rs index bbcad0da..4d4e15dc 100644 --- a/image_processor/src/processor/job/encoder/png.rs +++ b/image_processor/src/processor/job/encoder/png.rs @@ -33,7 +33,7 @@ impl Encoder for PngEncoder { } fn add_frame(&mut self, frame: &Frame) -> Result<()> { - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); if self.result.is_some() { return Err(ProcessorError::PngEncode(anyhow::anyhow!("encoder already finished"))); diff --git a/image_processor/src/processor/job/mod.rs b/image_processor/src/processor/job/mod.rs index ec624ec3..bd7e4a42 100644 --- a/image_processor/src/processor/job/mod.rs +++ b/image_processor/src/processor/job/mod.rs @@ -2,10 +2,9 @@ use std::borrow::Cow; use std::sync::Arc; use std::time::Duration; -use ::utils::prelude::FutureTimeout; -use ::utils::task::AsyncTask; +use scuffle_utils::prelude::FutureTimeout; +use scuffle_utils::task::AsyncTask; use aws_sdk_s3::types::ObjectCannedAcl; -use binary_helper::s3::PutObjectOptions; use bytes::Bytes; use file_format::FileFormat; use futures::FutureExt; @@ -16,7 +15,7 @@ use tracing::Instrument; use self::decoder::DecoderBackend; use super::error::{ProcessorError, Result}; use super::utils; -use crate::database; +use crate::{database, pb}; use crate::global::ImageProcessorGlobal; use crate::processor::utils::refresh_job; @@ -94,14 +93,8 @@ impl<'a, G: ImageProcessorGlobal> Job<'a, G> { .nats() .publish( self.job.task.callback_subject.clone(), - pb::scuffle::platform::internal::events::ProcessedImage { - job_id: Some(self.job.id.into()), - result: Some(pb::scuffle::platform::internal::events::processed_image::Result::Failure( - pb::scuffle::platform::internal::events::processed_image::Failure { - reason: e.to_string(), - friendly_message: e.friendly_message(), - }, - )), + pb::EventPayload { + id: todo!(), } .encode_to_vec() .into(), @@ -222,23 +215,8 @@ impl<'a, G: ImageProcessorGlobal> Job<'a, G> { .nats() .publish( self.job.task.callback_subject.clone(), - pb::scuffle::platform::internal::events::ProcessedImage { - job_id: Some(self.job.id.into()), - result: Some(pb::scuffle::platform::internal::events::processed_image::Result::Success( - pb::scuffle::platform::internal::events::processed_image::Success { - variants: images - .images - .iter() - .map(|image| pb::scuffle::platform::internal::types::ProcessedImageVariant { - path: image.url(&self.job.task.output_prefix), - format: image.request.into(), - width: image.width as u32, - height: image.height as u32, - byte_size: image.data.len() as u32, - }) - .collect(), - }, - )), + pb::EventPayload { + id: todo!(), } .encode_to_vec() .into(), diff --git a/image_processor/src/processor/job/process.rs b/image_processor/src/processor/job/process.rs index 0dc0d26c..d34230d4 100644 --- a/image_processor/src/processor/job/process.rs +++ b/image_processor/src/processor/job/process.rs @@ -2,8 +2,6 @@ use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use bytes::Bytes; -use pb::scuffle::platform::internal::image_processor::task; -use pb::scuffle::platform::internal::types::ImageFormat; use rgb::ComponentBytes; use sha2::Digest; @@ -11,6 +9,7 @@ use super::decoder::{Decoder, DecoderBackend, LoopCount}; use super::encoder::{AnyEncoder, Encoder, EncoderFrontend, EncoderSettings}; use super::resize::{ImageResizer, ImageResizerTarget}; use crate::database::Job; +use crate::pb::{ImageFormat, ResizeMethod}; use crate::processor::error::{ProcessorError, Result}; use crate::processor::job::scaling::{Ratio, ScalingOptions}; @@ -126,21 +125,21 @@ pub fn process_job(backend: DecoderBackend, job: &Job, data: Cow<'_, [u8]>) -> R }; let (preserve_aspect_height, preserve_aspect_width) = match job.task.resize_method() { - task::ResizeMethod::Fit => (true, true), - task::ResizeMethod::Stretch => (false, false), - task::ResizeMethod::PadBottomLeft => (false, false), - task::ResizeMethod::PadBottomRight => (false, false), - task::ResizeMethod::PadTopLeft => (false, false), - task::ResizeMethod::PadTopRight => (false, false), - task::ResizeMethod::PadCenter => (false, false), - task::ResizeMethod::PadCenterLeft => (false, false), - task::ResizeMethod::PadCenterRight => (false, false), - task::ResizeMethod::PadTopCenter => (false, false), - task::ResizeMethod::PadBottomCenter => (false, false), - task::ResizeMethod::PadTop => (false, true), - task::ResizeMethod::PadBottom => (false, true), - task::ResizeMethod::PadLeft => (true, false), - task::ResizeMethod::PadRight => (true, false), + ResizeMethod::Fit => (true, true), + ResizeMethod::Stretch => (false, false), + ResizeMethod::PadBottomLeft => (false, false), + ResizeMethod::PadBottomRight => (false, false), + ResizeMethod::PadTopLeft => (false, false), + ResizeMethod::PadTopRight => (false, false), + ResizeMethod::PadCenter => (false, false), + ResizeMethod::PadCenterLeft => (false, false), + ResizeMethod::PadCenterRight => (false, false), + ResizeMethod::PadTopCenter => (false, false), + ResizeMethod::PadBottomCenter => (false, false), + ResizeMethod::PadTop => (false, true), + ResizeMethod::PadBottom => (false, true), + ResizeMethod::PadLeft => (true, false), + ResizeMethod::PadRight => (true, false), }; let upscale = job.task.upscale().into(); @@ -172,7 +171,7 @@ pub fn process_job(backend: DecoderBackend, job: &Job, data: Cow<'_, [u8]>) -> R ImageResizer::new(ImageResizerTarget { height: scale.height, width: scale.width, - algorithm: job.task.resize_algorithm(), + algorithm: job.task.output(), method: job.task.resize_method(), upscale: upscale.is_yes(), }), diff --git a/image_processor/src/processor/job/resize.rs b/image_processor/src/processor/job/resize.rs index 3b4dac75..2c1bbbb1 100644 --- a/image_processor/src/processor/job/resize.rs +++ b/image_processor/src/processor/job/resize.rs @@ -1,11 +1,10 @@ use anyhow::Context; use fast_image_resize as fr; use imgref::Img; -use pb::scuffle::platform::internal::image_processor::task::{ResizeAlgorithm, ResizeMethod}; use rgb::{ComponentBytes, RGBA}; use super::frame::Frame; -use crate::processor::error::{ProcessorError, Result}; +use crate::{pb::{ResizeAlgorithm, ResizeMethod}, processor::error::{ProcessorError, Result}}; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct ImageResizerTarget { @@ -48,7 +47,7 @@ impl ImageResizer { /// resized frame. After this function returns original frame can be /// dropped, the returned frame is valid for the lifetime of the Resizer. pub fn resize(&mut self, frame: &Frame) -> Result { - let _abort_guard = utils::task::AbortGuard::new(); + let _abort_guard = scuffle_utils::task::AbortGuard::new(); let (width, height) = if self.target.method == ResizeMethod::Stretch { (self.target.width, self.target.height) diff --git a/image_processor/src/processor/job/scaling.rs b/image_processor/src/processor/job/scaling.rs index b7c8ed52..13b190f6 100644 --- a/image_processor/src/processor/job/scaling.rs +++ b/image_processor/src/processor/job/scaling.rs @@ -1,5 +1,7 @@ use std::ops::{Mul, MulAssign}; +use crate::pb::Upscale; + #[derive(Debug, Clone)] pub struct ScalingOptions { pub input_width: usize, @@ -13,23 +15,6 @@ pub struct ScalingOptions { pub scales: Vec, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Upscale { - Yes, - No, - NoPreserveSource, -} - -impl From for Upscale { - fn from(value: pb::scuffle::platform::internal::image_processor::task::Upscale) -> Self { - match value { - pb::scuffle::platform::internal::image_processor::task::Upscale::Yes => Upscale::Yes, - pb::scuffle::platform::internal::image_processor::task::Upscale::No => Upscale::No, - pb::scuffle::platform::internal::image_processor::task::Upscale::NoPreserveSource => Upscale::NoPreserveSource, - } - } -} - impl Upscale { pub fn is_yes(&self) -> bool { matches!(self, Upscale::Yes) diff --git a/image_processor/src/processor/utils.rs b/image_processor/src/processor/utils.rs index c6a58b76..c4e4edce 100644 --- a/image_processor/src/processor/utils.rs +++ b/image_processor/src/processor/utils.rs @@ -8,7 +8,7 @@ use crate::global::ImageProcessorGlobal; use crate::processor::error::Result; pub async fn query_job(global: &Arc, limit: usize) -> Result> { - Ok(utils::database::query( + Ok(scuffle_utils::database::query( "UPDATE image_jobs SET claimed_by = $1, hold_until = NOW() + INTERVAL '30 seconds' @@ -23,7 +23,7 @@ pub async fn query_job(global: &Arc, limit: usize) -> WHERE image_jobs.id = job.id RETURNING image_jobs.id, image_jobs.task", ) - .bind(global.config().instance_id) + .bind(global.instance_id()) .bind(limit as i64) .build_query_as() .fetch_all(global.db()) @@ -31,13 +31,13 @@ pub async fn query_job(global: &Arc, limit: usize) -> } pub async fn refresh_job(global: &Arc, job_id: Ulid) -> Result<()> { - let result = utils::database::query( + let result = scuffle_utils::database::query( "UPDATE image_jobs SET hold_until = NOW() + INTERVAL '30 seconds' WHERE image_jobs.id = $1 AND image_jobs.claimed_by = $2", ) .bind(job_id) - .bind(global.config().instance_id) + .bind(global.instance_id()) .build() .execute(global.db()) .await?; @@ -46,8 +46,9 @@ pub async fn refresh_job(global: &Arc, job_id: Ulid) } pub async fn delete_job(global: &Arc, job_id: Ulid) -> Result<()> { - utils::database::query("DELETE FROM image_jobs WHERE id = $1") + scuffle_utils::database::query("DELETE FROM image_jobs WHERE id = $1 AND claimed_by = $2") .bind(job_id) + .bind(global.instance_id()) .build() .execute(global.db()) .await?; diff --git a/image_processor/src/tests/global.rs b/image_processor/src/tests/global.rs index 19d6e019..4f5920c4 100644 --- a/image_processor/src/tests/global.rs +++ b/image_processor/src/tests/global.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use utils::context::Context; +use scuffle_utils::context::Context; use crate::config::ImageProcessorConfig; @@ -13,6 +13,7 @@ pub struct GlobalState { s3_source_bucket: binary_helper::s3::Bucket, s3_target_bucket: binary_helper::s3::Bucket, http_client: reqwest::Client, + instance_id: ulid::Ulid, } impl binary_helper::global::GlobalCtx for GlobalState { @@ -57,6 +58,10 @@ impl crate::global::ImageProcessorState for GlobalState { fn http_client(&self) -> &reqwest::Client { &self.http_client } + + fn instance_id(&self) -> ulid::Ulid { + self.instance_id + } } // pub async fn mock_global_state(config: ImageProcessorConfig) -> @@ -79,7 +84,7 @@ impl crate::global::ImageProcessorState for GlobalState { // nats"); let jetstream = async_nats::jetstream::new(nats.clone()); // let db = Arc::new( -// utils::database::Pool::connect(&database_uri) +// scuffle_utils::database::Pool::connect(&database_uri) // .await // .expect("failed to connect to database"), // ); diff --git a/image_processor/src/tests/utils.rs b/image_processor/src/tests/utils.rs index 31e20936..b65ab2b9 100644 --- a/image_processor/src/tests/utils.rs +++ b/image_processor/src/tests/utils.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; // use std::sync::Arc; -// use utils::context::Handler; +// use scuffle_utils::context::Handler; // use super::global::GlobalState; diff --git a/platform/api/Cargo.toml b/platform/api/Cargo.toml index 21b00d9a..014d9ae0 100644 --- a/platform/api/Cargo.toml +++ b/platform/api/Cargo.toml @@ -11,7 +11,7 @@ tracing = "0.1" tokio = { version = "1.36", features = ["full"] } serde = { version = "1.0", features = ["derive"] } hyper = { version = "1.1", features = ["full"] } -utils = { workspace = true, features = ["all"] } +scuffle-utils = { workspace = true, features = ["all"] } rustls = "0.23" rustls-pemfile = "2.0" tokio-rustls = "0.26" diff --git a/platform/api/src/api/auth.rs b/platform/api/src/api/auth.rs index f944055b..7801b831 100644 --- a/platform/api/src/api/auth.rs +++ b/platform/api/src/api/auth.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use std::sync::Arc; use hyper::StatusCode; +use scuffle_utils::http::RouteError; use ulid::Ulid; -use utils::http::RouteError; use super::error::ApiError; use crate::database::{Role, RolePermission, Session, User}; diff --git a/platform/api/src/api/error.rs b/platform/api/src/api/error.rs index c485f333..a4fda5d1 100644 --- a/platform/api/src/api/error.rs +++ b/platform/api/src/api/error.rs @@ -1,4 +1,4 @@ -use utils::http::RouteError; +use scuffle_utils::http::RouteError; use super::auth::AuthError; use crate::turnstile::TurnstileError; @@ -18,11 +18,11 @@ pub enum ApiError { #[error("failed to query turnstile: {0}")] Turnstile(#[from] TurnstileError), #[error("failed to query database: {0}")] - Database(#[from] utils::database::deadpool_postgres::PoolError), + Database(#[from] scuffle_utils::database::deadpool_postgres::PoolError), } impl From for ApiError { - fn from(value: utils::database::tokio_postgres::Error) -> Self { + fn from(value: scuffle_utils::database::tokio_postgres::Error) -> Self { Self::Database(value.into()) } } diff --git a/platform/api/src/api/middleware/auth.rs b/platform/api/src/api/middleware/auth.rs index 30fd791f..272a8673 100644 --- a/platform/api/src/api/middleware/auth.rs +++ b/platform/api/src/api/middleware/auth.rs @@ -3,10 +3,10 @@ use std::sync::Arc; use binary_helper::global::RequestGlobalExt; use hyper::body::Incoming; use hyper::http::header; -use utils::http::ext::*; -use utils::http::router::ext::RequestExt; -use utils::http::router::middleware::{middleware_fn, Middleware}; -use utils::http::RouteError; +use scuffle_utils::http::ext::*; +use scuffle_utils::http::router::ext::RequestExt; +use scuffle_utils::http::router::middleware::{middleware_fn, Middleware}; +use scuffle_utils::http::RouteError; use crate::api::auth::{AuthData, AuthError}; use crate::api::error::ApiError; diff --git a/platform/api/src/api/mod.rs b/platform/api/src/api/mod.rs index 9ac04356..3329b06d 100644 --- a/platform/api/src/api/mod.rs +++ b/platform/api/src/api/mod.rs @@ -9,14 +9,14 @@ use hyper::body::Incoming; use hyper::server::conn::http1; use hyper::service::service_fn; use hyper_util::rt::TokioIo; +use scuffle_utils::context::ContextExt; +use scuffle_utils::http::router::middleware::{CorsMiddleware, CorsOptions, ResponseHeadersMiddleware}; +use scuffle_utils::http::router::Router; +use scuffle_utils::http::RouteError; +use scuffle_utils::prelude::FutureTimeout; +use scuffle_utilsmake_response; use serde_json::json; use tokio::net::TcpSocket; -use utils::context::ContextExt; -use utils::http::router::middleware::{CorsMiddleware, CorsOptions, ResponseHeadersMiddleware}; -use utils::http::router::Router; -use utils::http::RouteError; -use utils::make_response; -use utils::prelude::FutureTimeout; use self::error::ApiError; use crate::config::ApiConfig; diff --git a/platform/api/src/api/v1/gql/error.rs b/platform/api/src/api/v1/gql/error.rs index 5ea50f17..d4352831 100644 --- a/platform/api/src/api/v1/gql/error.rs +++ b/platform/api/src/api/v1/gql/error.rs @@ -74,7 +74,7 @@ pub enum GqlError { } impl From for GqlError { - fn from(value: utils::database::tokio_postgres::Error) -> Self { + fn from(value: scuffle_utils::database::tokio_postgres::Error) -> Self { Self::Database(Arc::new(value.into())) } } diff --git a/platform/api/src/api/v1/gql/handlers.rs b/platform/api/src/api/v1/gql/handlers.rs index b463e19b..caa38da9 100644 --- a/platform/api/src/api/v1/gql/handlers.rs +++ b/platform/api/src/api/v1/gql/handlers.rs @@ -14,11 +14,11 @@ use hyper_tungstenite::tungstenite::protocol::frame::coding::CloseCode; use hyper_tungstenite::tungstenite::protocol::CloseFrame; use hyper_tungstenite::tungstenite::Message; use hyper_tungstenite::HyperWebsocket; +use scuffle_utils::context::ContextExt; +use scuffle_utils::http::ext::*; +use scuffle_utils::http::router::compat::BodyExt as _; +use scuffle_utils::http::router::ext::RequestExt; use serde_json::json; -use utils::context::ContextExt; -use utils::http::ext::*; -use utils::http::router::compat::BodyExt as _; -use utils::http::router::ext::RequestExt; use super::error::GqlError; use super::ext::RequestExt as _; diff --git a/platform/api/src/api/v1/gql/mod.rs b/platform/api/src/api/v1/gql/mod.rs index cfe0d594..68f3f33f 100644 --- a/platform/api/src/api/v1/gql/mod.rs +++ b/platform/api/src/api/v1/gql/mod.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use async_graphql::{extensions, Schema}; use hyper::body::Incoming; use hyper::Response; -use utils::http::router::builder::RouterBuilder; -use utils::http::router::Router; -use utils::http::RouteError; +use scuffle_utils::http::router::builder::RouterBuilder; +use scuffle_utils::http::router::Router; +use scuffle_utils::http::RouteError; use crate::api::error::ApiError; use crate::api::Body; diff --git a/platform/api/src/api/v1/gql/models/channel.rs b/platform/api/src/api/v1/gql/models/channel.rs index b39f70a5..158b53e7 100644 --- a/platform/api/src/api/v1/gql/models/channel.rs +++ b/platform/api/src/api/v1/gql/models/channel.rs @@ -57,7 +57,7 @@ impl Channel { async fn followers_count(&self, ctx: &Context<'_>) -> Result { let global = ctx.get_global::(); - let followers = utils::database::query( + let followers = scuffle_utils::database::query( r#" SELECT COUNT(*) @@ -125,7 +125,7 @@ impl ChannelLive { .await .map_err_gql("failed to fetch playback session count")?; - utils::database::query( + scuffle_utils::database::query( "UPDATE users SET channel_live_viewer_count = $1, channel_live_viewer_count_updated_at = NOW() WHERE id = $2", ) .bind(live_viewer_count) diff --git a/platform/api/src/api/v1/gql/mutations/auth.rs b/platform/api/src/api/v1/gql/mutations/auth.rs index aad6c8bf..5ad6d0cc 100644 --- a/platform/api/src/api/v1/gql/mutations/auth.rs +++ b/platform/api/src/api/v1/gql/mutations/auth.rs @@ -90,7 +90,7 @@ impl AuthMutation { if user.totp_enabled { let request_id = ulid::Ulid::new(); - utils::database::query( + scuffle_utils::database::query( r#" INSERT INTO two_fa_requests ( id, @@ -149,7 +149,7 @@ impl AuthMutation { let request_context = ctx.get_req_context(); // TODO: Make this a dataloader - let request: database::TwoFaRequest = utils::database::query( + let request: database::TwoFaRequest = scuffle_utils::database::query( r#" SELECT * @@ -180,7 +180,7 @@ impl AuthMutation { .into()); } - utils::database::query( + scuffle_utils::database::query( r#" DELETE FROM two_fa_requests @@ -242,7 +242,7 @@ impl AuthMutation { })?; // TODO: maybe look to batch this - let session: database::Session = utils::database::query( + let session: database::Session = scuffle_utils::database::query( r#" UPDATE user_sessions @@ -355,7 +355,7 @@ impl AuthMutation { let tx = client.transaction().await?; // TODO: maybe look to batch this - let user: database::User = utils::database::query( + let user: database::User = scuffle_utils::database::query( r#" INSERT INTO users ( id, @@ -394,7 +394,7 @@ impl AuthMutation { let expires_at = Utc::now() + Duration::seconds(login_duration as i64); // TODO: maybe look to batch this - let session: database::Session = utils::database::query( + let session: database::Session = scuffle_utils::database::query( r#" INSERT INTO user_sessions ( id, @@ -476,7 +476,7 @@ impl AuthMutation { }; // TODO: maybe look to batch this - utils::database::query( + scuffle_utils::database::query( r#" DELETE FROM user_sessions diff --git a/platform/api/src/api/v1/gql/mutations/channel.rs b/platform/api/src/api/v1/gql/mutations/channel.rs index 4224d8ea..52879bf6 100644 --- a/platform/api/src/api/v1/gql/mutations/channel.rs +++ b/platform/api/src/api/v1/gql/mutations/channel.rs @@ -29,7 +29,7 @@ impl ChannelMutation { .await? .map_err_gql(GqlError::Auth(AuthError::NotLoggedIn))?; - let user: database::User = utils::database::query( + let user: database::User = scuffle_utils::database::query( r#" UPDATE users SET diff --git a/platform/api/src/api/v1/gql/mutations/chat.rs b/platform/api/src/api/v1/gql/mutations/chat.rs index ac1c1507..df49f260 100644 --- a/platform/api/src/api/v1/gql/mutations/chat.rs +++ b/platform/api/src/api/v1/gql/mutations/chat.rs @@ -41,7 +41,7 @@ impl ChatMutation { // TODO: Check if the user is allowed to send messages in this chat let message_id = Ulid::new(); - let chat_message: database::ChatMessage = utils::database::query( + let chat_message: database::ChatMessage = scuffle_utils::database::query( r#" INSERT INTO chat_messages ( id, diff --git a/platform/api/src/api/v1/gql/mutations/user.rs b/platform/api/src/api/v1/gql/mutations/user.rs index e4be6e23..d4aa2880 100644 --- a/platform/api/src/api/v1/gql/mutations/user.rs +++ b/platform/api/src/api/v1/gql/mutations/user.rs @@ -50,7 +50,7 @@ impl UserMutation { .await? .map_err_gql(GqlError::Auth(AuthError::NotLoggedIn))?; - let user: database::User = utils::database::query( + let user: database::User = scuffle_utils::database::query( r#" UPDATE users SET @@ -102,7 +102,7 @@ impl UserMutation { .into()); } - let user: database::User = utils::database::query( + let user: database::User = scuffle_utils::database::query( r#" UPDATE users SET @@ -152,7 +152,7 @@ impl UserMutation { .await? .ok_or(GqlError::Auth(AuthError::NotLoggedIn))?; - let user: database::User = utils::database::query( + let user: database::User = scuffle_utils::database::query( r#" UPDATE users SET @@ -196,7 +196,7 @@ impl UserMutation { .await? .ok_or(GqlError::Auth(AuthError::NotLoggedIn))?; - let user: database::User = utils::database::query( + let user: database::User = scuffle_utils::database::query( "UPDATE users SET profile_picture_id = NULL, pending_profile_picture_id = NULL WHERE id = $1 RETURNING *", ) .bind(auth.session.user_id) @@ -257,7 +257,7 @@ impl UserMutation { if user.totp_enabled { let request_id = ulid::Ulid::new(); - utils::database::query( + scuffle_utils::database::query( r#" INSERT INTO two_fa_requests ( id, @@ -311,7 +311,7 @@ impl UserMutation { .into()); } - utils::database::query( + scuffle_utils::database::query( r#" UPSERT INTO channel_user ( user_id, diff --git a/platform/api/src/api/v1/gql/mutations/user/two_fa.rs b/platform/api/src/api/v1/gql/mutations/user/two_fa.rs index 5d629f41..14ca6946 100644 --- a/platform/api/src/api/v1/gql/mutations/user/two_fa.rs +++ b/platform/api/src/api/v1/gql/mutations/user/two_fa.rs @@ -67,7 +67,7 @@ impl TwoFaMutation { let hex_backup_codes = backup_codes.iter().map(|c| format!("{:08x}", c)).collect(); // Save secret and backup codes to database. - utils::database::query( + scuffle_utils::database::query( r#" UPDATE users @@ -130,7 +130,7 @@ impl TwoFaMutation { } // Enable 2fa - let user: database::User = utils::database::query( + let user: database::User = scuffle_utils::database::query( r#" UPDATE users @@ -179,7 +179,7 @@ impl TwoFaMutation { } // Disable 2fa, remove secret and backup codes. - let user: database::User = utils::database::query( + let user: database::User = scuffle_utils::database::query( r#" UPDATE users SET diff --git a/platform/api/src/api/v1/gql/queries/category.rs b/platform/api/src/api/v1/gql/queries/category.rs index 50860906..a1cc681c 100644 --- a/platform/api/src/api/v1/gql/queries/category.rs +++ b/platform/api/src/api/v1/gql/queries/category.rs @@ -61,7 +61,7 @@ impl CategoryQuery { ) -> Result { let global = ctx.get_global::(); - let categories: Vec> = utils::database::query("SELECT categories.*, similarity(name, $1), COUNT(*) OVER() AS total_count FROM categories WHERE name % $1 ORDER BY similarity DESC LIMIT $2 OFFSET $3") + let categories: Vec> = scuffle_utils::database::query("SELECT categories.*, similarity(name, $1), COUNT(*) OVER() AS total_count FROM categories WHERE name % $1 ORDER BY similarity DESC LIMIT $2 OFFSET $3") .bind(query) .bind(limit.unwrap_or(5)) .bind(offset.unwrap_or(0)) diff --git a/platform/api/src/api/v1/gql/queries/mod.rs b/platform/api/src/api/v1/gql/queries/mod.rs index 7bf34cbe..2699dd58 100644 --- a/platform/api/src/api/v1/gql/queries/mod.rs +++ b/platform/api/src/api/v1/gql/queries/mod.rs @@ -49,7 +49,7 @@ impl Query { ) -> Result> { let global = ctx.get_global::(); - let query_results: Vec = utils::database::query( + let query_results: Vec = scuffle_utils::database::query( r#" WITH CombinedResults AS ( SELECT diff --git a/platform/api/src/api/v1/gql/queries/user.rs b/platform/api/src/api/v1/gql/queries/user.rs index e2cefa95..758d5114 100644 --- a/platform/api/src/api/v1/gql/queries/user.rs +++ b/platform/api/src/api/v1/gql/queries/user.rs @@ -98,7 +98,7 @@ impl UserQuery { ) -> Result> { let global = ctx.get_global::(); - let users: Vec> = utils::database::query("SELECT users.*, similarity(username, $1), COUNT(*) OVER() AS total_count FROM users WHERE username % $1 ORDER BY similarity DESC LIMIT $2 OFFSET $3") + let users: Vec> = scuffle_utils::database::query("SELECT users.*, similarity(username, $1), COUNT(*) OVER() AS total_count FROM users WHERE username % $1 ORDER BY similarity DESC LIMIT $2 OFFSET $3") .bind(query) .bind(limit.unwrap_or(5)) .bind(offset.unwrap_or(0)) @@ -120,7 +120,7 @@ impl UserQuery { .await? .ok_or(GqlError::Auth(AuthError::NotLoggedIn))?; - let is_following = utils::database::query( + let is_following = scuffle_utils::database::query( r#" SELECT following @@ -161,7 +161,7 @@ impl UserQuery { } // This query is not very good, we should have some paging mechinsm with ids. - let channels: Vec = utils::database::query( + let channels: Vec = scuffle_utils::database::query( r#" SELECT users.* diff --git a/platform/api/src/api/v1/gql/subscription/channel.rs b/platform/api/src/api/v1/gql/subscription/channel.rs index a23ab6ec..5870edc3 100644 --- a/platform/api/src/api/v1/gql/subscription/channel.rs +++ b/platform/api/src/api/v1/gql/subscription/channel.rs @@ -88,7 +88,7 @@ impl ChannelSubscription { let stream = self.channel_follows(ctx, channel_id).await?; - let mut followers = utils::database::query( + let mut followers = scuffle_utils::database::query( r#" SELECT COUNT(*) diff --git a/platform/api/src/api/v1/gql/subscription/chat.rs b/platform/api/src/api/v1/gql/subscription/chat.rs index c8f792a3..85090fe9 100644 --- a/platform/api/src/api/v1/gql/subscription/chat.rs +++ b/platform/api/src/api/v1/gql/subscription/chat.rs @@ -52,7 +52,7 @@ impl ChatSubscription { // load old messages not older than 10 minutes, max 100 messages let not_older_than = chrono::Utc::now() - chrono::Duration::minutes(10); let not_older_than = ulid::Ulid::from_parts(not_older_than.timestamp() as u64, u128::MAX); - let messages: Vec = utils::database::query( + let messages: Vec = scuffle_utils::database::query( "SELECT * FROM chat_messages WHERE channel_id = $1 AND deleted_at IS NULL AND id >= $2 ORDER BY id LIMIT 100", ) .bind(channel_id.to_ulid()) diff --git a/platform/api/src/api/v1/gql/subscription/user.rs b/platform/api/src/api/v1/gql/subscription/user.rs index e02cf68f..92e29b35 100644 --- a/platform/api/src/api/v1/gql/subscription/user.rs +++ b/platform/api/src/api/v1/gql/subscription/user.rs @@ -231,7 +231,7 @@ impl UserSubscription { Ok(async_stream::stream!({ if let Some(channel_id) = channel_id { - let is_following = utils::database::query( + let is_following = scuffle_utils::database::query( r#" SELECT following diff --git a/platform/api/src/api/v1/mod.rs b/platform/api/src/api/v1/mod.rs index f9577edd..65e94cca 100644 --- a/platform/api/src/api/v1/mod.rs +++ b/platform/api/src/api/v1/mod.rs @@ -1,9 +1,9 @@ use std::sync::Arc; use hyper::body::Incoming; -use utils::http::router::builder::RouterBuilder; -use utils::http::router::Router; -use utils::http::RouteError; +use scuffle_utils::http::router::builder::RouterBuilder; +use scuffle_utils::http::router::Router; +use scuffle_utils::http::RouteError; use super::error::ApiError; use super::Body; diff --git a/platform/api/src/api/v1/upload/mod.rs b/platform/api/src/api/v1/upload/mod.rs index eb6c3699..cadd6933 100644 --- a/platform/api/src/api/v1/upload/mod.rs +++ b/platform/api/src/api/v1/upload/mod.rs @@ -5,12 +5,12 @@ use bytes::Bytes; use hyper::body::Incoming; use hyper::{Request, Response, StatusCode}; use multer::{Constraints, SizeLimit}; -use utils::http::ext::{OptionExt, ResultExt}; -use utils::http::router::builder::RouterBuilder; -use utils::http::router::compat::BodyExt; -use utils::http::router::ext::RequestExt; -use utils::http::router::Router; -use utils::http::RouteError; +use scuffle_utils::http::ext::{OptionExt, ResultExt}; +use scuffle_utils::http::router::builder::RouterBuilder; +use scuffle_utils::http::router::compat::BodyExt; +use scuffle_utils::http::router::ext::RequestExt; +use scuffle_utils::http::router::Router; +use scuffle_utils::http::RouteError; use self::profile_picture::ProfilePicture; use crate::api::auth::AuthData; diff --git a/platform/api/src/api/v1/upload/profile_picture.rs b/platform/api/src/api/v1/upload/profile_picture.rs index 173404cd..5e037494 100644 --- a/platform/api/src/api/v1/upload/profile_picture.rs +++ b/platform/api/src/api/v1/upload/profile_picture.rs @@ -6,11 +6,11 @@ use bytes::Bytes; use hyper::{Response, StatusCode}; use pb::scuffle::platform::internal::image_processor; use pb::scuffle::platform::internal::types::{uploaded_file_metadata, ImageFormat, UploadedFileMetadata}; +use scuffle_utils::http::ext::ResultExt; +use scuffle_utils::http::RouteError; +use scuffle_utilsmake_response; use serde_json::json; use ulid::Ulid; -use utils::http::ext::ResultExt; -use utils::http::RouteError; -use utils::make_response; use super::UploadType; use crate::api::auth::AuthData; @@ -187,7 +187,7 @@ impl UploadType for ProfilePicture { .await .map_err_route((StatusCode::INTERNAL_SERVER_ERROR, "failed to start transaction"))?; - utils::database::query("INSERT INTO image_jobs (id, priority, task) VALUES ($1, $2, $3)") + scuffle_utils::database::query("INSERT INTO image_jobs (id, priority, task) VALUES ($1, $2, $3)") .bind(file_id) .bind(config.profile_picture_task_priority) .bind(utils::database::Protobuf(create_task( @@ -201,7 +201,7 @@ impl UploadType for ProfilePicture { .await .map_err_route((StatusCode::INTERNAL_SERVER_ERROR, "failed to insert image job"))?; - utils::database::query("INSERT INTO uploaded_files(id, owner_id, uploader_id, name, type, metadata, total_size, path, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)") + scuffle_utils::database::query("INSERT INTO uploaded_files(id, owner_id, uploader_id, name, type, metadata, total_size, path, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)") .bind(file_id) // id .bind(auth.session.user_id) // owner_id .bind(auth.session.user_id) // uploader_id @@ -221,7 +221,7 @@ impl UploadType for ProfilePicture { .map_err_route((StatusCode::INTERNAL_SERVER_ERROR, "failed to insert uploaded file"))?; if self.set_active { - utils::database::query("UPDATE users SET pending_profile_picture_id = $1 WHERE id = $2") + scuffle_utils::database::query("UPDATE users SET pending_profile_picture_id = $1 WHERE id = $2") .bind(file_id) .bind(auth.session.user_id) .build() diff --git a/platform/api/src/database/channel.rs b/platform/api/src/database/channel.rs index 9fb592da..6d440f54 100644 --- a/platform/api/src/database/channel.rs +++ b/platform/api/src/database/channel.rs @@ -1,7 +1,7 @@ use async_graphql::SimpleObject; use chrono::{DateTime, Utc}; +use scuffle_utils::database::json; use ulid::Ulid; -use utils::database::json; #[derive(Debug, Clone, Default, postgres_from_row::FromRow)] pub struct Channel { diff --git a/platform/api/src/database/two_fa_request.rs b/platform/api/src/database/two_fa_request.rs index 7ffbecdd..6b921ef1 100644 --- a/platform/api/src/database/two_fa_request.rs +++ b/platform/api/src/database/two_fa_request.rs @@ -4,8 +4,8 @@ use chrono::{Duration, Utc}; use pb::ext::UlidExt; use pb::scuffle::platform::internal::two_fa::two_fa_request_action::{ChangePassword, Login}; use pb::scuffle::platform::internal::two_fa::TwoFaRequestAction; +use scuffle_utils::database::protobuf; use ulid::Ulid; -use utils::database::protobuf; use super::{Session, User}; use crate::global::ApiGlobal; @@ -27,7 +27,7 @@ pub trait TwoFaRequestActionTrait { } impl TwoFaRequestActionTrait for Login { - type Result = Result; + type Result = Result; async fn execute(self, global: &Arc, user_id: Ulid) -> Self::Result { let expires_at = Utc::now() + Duration::seconds(self.login_duration as i64); @@ -36,7 +36,7 @@ impl TwoFaRequestActionTrait for Login { let mut client = global.db().get().await?; let tx = client.transaction().await?; - let session = utils::database::query( + let session = scuffle_utils::database::query( r#" INSERT INTO user_sessions ( id, @@ -56,7 +56,7 @@ impl TwoFaRequestActionTrait for Login { .fetch_one(&tx) .await?; - utils::database::query( + scuffle_utils::database::query( r#" UPDATE users SET @@ -76,13 +76,13 @@ impl TwoFaRequestActionTrait for Login { } impl TwoFaRequestActionTrait for ChangePassword { - type Result = Result<(), utils::database::deadpool_postgres::PoolError>; + type Result = Result<(), scuffle_utils::database::deadpool_postgres::PoolError>; async fn execute(self, global: &Arc, user_id: Ulid) -> Self::Result { let mut client = global.db().get().await?; let tx = client.transaction().await?; - let user: User = utils::database::query( + let user: User = scuffle_utils::database::query( r#" UPDATE users @@ -100,7 +100,7 @@ impl TwoFaRequestActionTrait for ChangePassword { .await?; // Delete all sessions except current - utils::database::query( + scuffle_utils::database::query( r#" DELETE FROM user_sessions diff --git a/platform/api/src/database/uploaded_file.rs b/platform/api/src/database/uploaded_file.rs index 57fd25ba..b29fcc37 100644 --- a/platform/api/src/database/uploaded_file.rs +++ b/platform/api/src/database/uploaded_file.rs @@ -1,5 +1,5 @@ +use scuffle_utils::database::protobuf; use ulid::Ulid; -use utils::database::protobuf; use super::{FileType, UploadedFileStatus}; diff --git a/platform/api/src/dataloader/category.rs b/platform/api/src/dataloader/category.rs index 0b1f8877..a0124a53 100644 --- a/platform/api/src/dataloader/category.rs +++ b/platform/api/src/dataloader/category.rs @@ -1,7 +1,7 @@ use std::sync::Arc; +use scuffle_utilsdataloader::{DataLoader, Loader, LoaderOutput}; use ulid::Ulid; -use utils::dataloader::{DataLoader, Loader, LoaderOutput}; use crate::database::Category; @@ -21,7 +21,7 @@ impl Loader for CategoryByIdLoader { type Value = Category; async fn load(&self, keys: &[Self::Key]) -> LoaderOutput { - let results: Vec = utils::database::query("SELECT * FROM categories WHERE id = ANY($1)") + let results: Vec = scuffle_utils::database::query("SELECT * FROM categories WHERE id = ANY($1)") .bind(keys) .build_query_as() .fetch_all(&self.db) diff --git a/platform/api/src/dataloader/global_state.rs b/platform/api/src/dataloader/global_state.rs index 9aa84e23..636e350a 100644 --- a/platform/api/src/dataloader/global_state.rs +++ b/platform/api/src/dataloader/global_state.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::sync::Arc; -use utils::dataloader::{DataLoader, Loader, LoaderOutput}; +use scuffle_utilsdataloader::{DataLoader, Loader, LoaderOutput}; use crate::database::GlobalState; @@ -21,7 +21,7 @@ impl Loader for GlobalStateLoader { type Value = GlobalState; async fn load(&self, _: &[Self::Key]) -> LoaderOutput { - let state = utils::database::query("SELECT * FROM global_state") + let state = scuffle_utils::database::query("SELECT * FROM global_state") .build_query_as() .fetch_one(&self.db) .await diff --git a/platform/api/src/dataloader/role.rs b/platform/api/src/dataloader/role.rs index 11c3f083..6b73b8c1 100644 --- a/platform/api/src/dataloader/role.rs +++ b/platform/api/src/dataloader/role.rs @@ -1,7 +1,7 @@ use std::sync::Arc; +use scuffle_utilsdataloader::{DataLoader, Loader, LoaderOutput}; use ulid::Ulid; -use utils::dataloader::{DataLoader, Loader, LoaderOutput}; use crate::database::Role; @@ -21,7 +21,7 @@ impl Loader for RoleByIdLoader { type Value = Role; async fn load(&self, keys: &[Self::Key]) -> LoaderOutput { - let results: Vec = utils::database::query("SELECT * FROM roles WHERE id = ANY($1)") + let results: Vec = scuffle_utils::database::query("SELECT * FROM roles WHERE id = ANY($1)") .bind(keys) .build_query_as() .fetch_all(self.db.as_ref()) diff --git a/platform/api/src/dataloader/session.rs b/platform/api/src/dataloader/session.rs index 00ef276c..8247fc45 100644 --- a/platform/api/src/dataloader/session.rs +++ b/platform/api/src/dataloader/session.rs @@ -1,7 +1,7 @@ use std::sync::Arc; +use scuffle_utilsdataloader::{DataLoader, Loader, LoaderOutput}; use ulid::Ulid; -use utils::dataloader::{DataLoader, Loader, LoaderOutput}; use crate::database::Session; @@ -21,7 +21,7 @@ impl Loader for SessionByIdLoader { type Value = Session; async fn load(&self, keys: &[Self::Key]) -> LoaderOutput { - let results: Vec = utils::database::query("SELECT * FROM user_sessions WHERE id = ANY($1)") + let results: Vec = scuffle_utils::database::query("SELECT * FROM user_sessions WHERE id = ANY($1)") .bind(keys) .build_query_as() .fetch_all(self.db.as_ref()) diff --git a/platform/api/src/dataloader/uploaded_file.rs b/platform/api/src/dataloader/uploaded_file.rs index 82c2996d..8fef1dbc 100644 --- a/platform/api/src/dataloader/uploaded_file.rs +++ b/platform/api/src/dataloader/uploaded_file.rs @@ -1,7 +1,7 @@ use std::sync::Arc; +use scuffle_utilsdataloader::{DataLoader, Loader, LoaderOutput}; use ulid::Ulid; -use utils::dataloader::{DataLoader, Loader, LoaderOutput}; use crate::database::UploadedFile; @@ -21,7 +21,7 @@ impl Loader for UploadedFileByIdLoader { type Value = UploadedFile; async fn load(&self, keys: &[Self::Key]) -> LoaderOutput { - let results: Vec = utils::database::query("SELECT * FROM uploaded_files WHERE id = ANY($1)") + let results: Vec = scuffle_utils::database::query("SELECT * FROM uploaded_files WHERE id = ANY($1)") .bind(keys) .build_query_as() .fetch_all(self.db.as_ref()) diff --git a/platform/api/src/dataloader/user.rs b/platform/api/src/dataloader/user.rs index 7121e3a6..2c511744 100644 --- a/platform/api/src/dataloader/user.rs +++ b/platform/api/src/dataloader/user.rs @@ -1,7 +1,7 @@ use std::sync::Arc; +use scuffle_utilsdataloader::{DataLoader, Loader, LoaderOutput}; use ulid::Ulid; -use utils::dataloader::{DataLoader, Loader, LoaderOutput}; use crate::database::User; @@ -21,7 +21,7 @@ impl Loader for UserByUsernameLoader { type Value = User; async fn load(&self, keys: &[Self::Key]) -> LoaderOutput { - let results: Vec = utils::database::query("SELECT * FROM users WHERE username = ANY($1)") + let results: Vec = scuffle_utils::database::query("SELECT * FROM users WHERE username = ANY($1)") .bind(keys) .build_query_as() .fetch_all(self.db.as_ref()) @@ -50,7 +50,7 @@ impl Loader for UserByIdLoader { type Value = User; async fn load(&self, keys: &[Self::Key]) -> LoaderOutput { - let results: Vec = utils::database::query("SELECT * FROM users WHERE id = ANY($1)") + let results: Vec = scuffle_utils::database::query("SELECT * FROM users WHERE id = ANY($1)") .bind(keys) .build_query_as() .fetch_all(self.db.as_ref()) diff --git a/platform/api/src/global.rs b/platform/api/src/global.rs index 4def750b..ce02686c 100644 --- a/platform/api/src/global.rs +++ b/platform/api/src/global.rs @@ -1,4 +1,4 @@ -use utils::dataloader::DataLoader; +use scuffle_utilsdataloader::DataLoader; use crate::config::{ApiConfig, IgDbConfig, ImageUploaderConfig, JwtConfig, TurnstileConfig, VideoApiConfig}; use crate::dataloader::category::CategoryByIdLoader; diff --git a/platform/api/src/igdb_cron.rs b/platform/api/src/igdb_cron.rs index 9dfb82e7..b95951ef 100644 --- a/platform/api/src/igdb_cron.rs +++ b/platform/api/src/igdb_cron.rs @@ -314,7 +314,7 @@ async fn refresh_igdb(global: &Arc, config: &IgDbConfig) -> any uploaded_file_id: Ulid, } - utils::database::query("INSERT INTO igdb_image (uploaded_file_id, image_id)") + scuffle_utils::database::query("INSERT INTO igdb_image (uploaded_file_id, image_id)") .push_values(&image_ids, |mut sep, item| { sep.push_bind(item.0); sep.push_bind(item.1); @@ -325,13 +325,14 @@ async fn refresh_igdb(global: &Arc, config: &IgDbConfig) -> any .await .context("insert igdb_image")?; - let image_ids = - utils::database::query("SELECT image_id, uploaded_file_id FROM igdb_image WHERE image_id = ANY($1::TEXT[])") - .bind(image_ids.iter().map(|x| x.1).collect::>()) - .build_query_as::() - .fetch_all(&tx) - .await - .context("select igdb_image")?; + let image_ids = scuffle_utils::database::query( + "SELECT image_id, uploaded_file_id FROM igdb_image WHERE image_id = ANY($1::TEXT[])", + ) + .bind(image_ids.iter().map(|x| x.1).collect::>()) + .build_query_as::() + .fetch_all(&tx) + .await + .context("select igdb_image")?; let image_ids = image_ids .into_iter() @@ -387,22 +388,23 @@ async fn refresh_igdb(global: &Arc, config: &IgDbConfig) -> any }) .collect::>(); - let uploaded_files_ids = - utils::database::query("INSERT INTO uploaded_files (id, name, type, metadata, total_size, path, status) ") - .push_values(&uploaded_files, |mut sep, item| { - sep.push_bind(item.id); - sep.push_bind(&item.name); - sep.push_bind(item.ty); - sep.push_bind(utils::database::Protobuf(item.metadata.clone())); - sep.push_bind(item.total_size); - sep.push_bind(&item.path); - sep.push_bind(item.status); - }) - .push("ON CONFLICT (id) DO NOTHING RETURNING id;") - .build_query_single_scalar::() - .fetch_all(&tx) - .await - .context("insert uploaded_files")?; + let uploaded_files_ids = scuffle_utils::database::query( + "INSERT INTO uploaded_files (id, name, type, metadata, total_size, path, status) ", + ) + .push_values(&uploaded_files, |mut sep, item| { + sep.push_bind(item.id); + sep.push_bind(&item.name); + sep.push_bind(item.ty); + sep.push_bind(utils::database::Protobuf(item.metadata.clone())); + sep.push_bind(item.total_size); + sep.push_bind(&item.path); + sep.push_bind(item.status); + }) + .push("ON CONFLICT (id) DO NOTHING RETURNING id;") + .build_query_single_scalar::() + .fetch_all(&tx) + .await + .context("insert uploaded_files")?; let resp = resp .into_iter() @@ -433,7 +435,7 @@ async fn refresh_igdb(global: &Arc, config: &IgDbConfig) -> any offset += resp.len(); let count = resp.len(); - let categories = utils::database::query("INSERT INTO categories (id, igdb_id, name, aliases, keywords, storyline, summary, over_18, cover_id, rating, updated_at, artwork_ids, igdb_similar_game_ids, websites) ") + let categories = scuffle_utils::database::query("INSERT INTO categories (id, igdb_id, name, aliases, keywords, storyline, summary, over_18, cover_id, rating, updated_at, artwork_ids, igdb_similar_game_ids, websites) ") .push_values(&resp, |mut sep, item| { sep.push_bind(item.id); sep.push_bind(item.igdb_id); @@ -480,7 +482,7 @@ async fn refresh_igdb(global: &Arc, config: &IgDbConfig) -> any }) .collect::>(); - utils::database::query("WITH updated(id, category) AS (") + scuffle_utils::database::query("WITH updated(id, category) AS (") .push_values(categories.iter().collect::>(), |mut sep, item| { sep.push_bind(item.0).push_unseparated("::UUID"); sep.push_bind(item.1).push_unseparated("::UUID"); @@ -505,7 +507,7 @@ async fn refresh_igdb(global: &Arc, config: &IgDbConfig) -> any .await .context("start transaction image_jobs")?; - let unqueued = utils::database::query( + let unqueued = scuffle_utils::database::query( "UPDATE uploaded_files SET status = 'queued' WHERE id = ANY($1::UUID[]) AND status = 'unqueued' RETURNING id, path;", ) .bind(uploaded_files_ids) @@ -515,7 +517,7 @@ async fn refresh_igdb(global: &Arc, config: &IgDbConfig) -> any .context("update uploaded_files")?; if !unqueued.is_empty() { - utils::database::query("INSERT INTO image_jobs (id, priority, task) ") + scuffle_utils::database::query("INSERT INTO image_jobs (id, priority, task) ") .bind(image_processor_config.igdb_image_task_priority as i64) .push_values(unqueued, |mut sep, (id, path)| { sep.push_bind(id).push("$1").push_bind(utils::database::Protobuf(create_task( diff --git a/platform/api/src/image_upload_callback.rs b/platform/api/src/image_upload_callback.rs index cee82bb4..087f5914 100644 --- a/platform/api/src/image_upload_callback.rs +++ b/platform/api/src/image_upload_callback.rs @@ -9,7 +9,7 @@ use pb::ext::UlidExt; use pb::scuffle::platform::internal::events::{processed_image, ProcessedImage}; use pb::scuffle::platform::internal::types::{uploaded_file_metadata, ProcessedImageVariant, UploadedFileMetadata}; use prost::Message; -use utils::context::ContextExt; +use scuffle_utils::context::ContextExt; use crate::config::ImageUploaderConfig; use crate::database::{FileType, UploadedFile}; @@ -131,7 +131,7 @@ async fn handle_success( let mut client = global.db().get().await.context("failed to get db connection")?; let tx = client.transaction().await.context("failed to start transaction")?; - let uploaded_file: UploadedFile = match utils::database::query("UPDATE uploaded_files SET status = 'completed', metadata = $1, updated_at = NOW() WHERE id = $2 AND status = 'queued' RETURNING *") + let uploaded_file: UploadedFile = match scuffle_utils::database::query("UPDATE uploaded_files SET status = 'completed', metadata = $1, updated_at = NOW() WHERE id = $2 AND status = 'queued' RETURNING *") .bind(utils::database::Protobuf(UploadedFileMetadata { metadata: Some(uploaded_file_metadata::Metadata::Image(uploaded_file_metadata::Image { versions: variants, @@ -169,7 +169,7 @@ async fn handle_success( match uploaded_file.ty { FileType::CategoryArtwork | FileType::CategoryCover => {} FileType::ProfilePicture => { - let user_updated = utils::database::query("UPDATE users SET profile_picture_id = $1, pending_profile_picture_id = NULL, updated_at = NOW() WHERE id = $2 AND pending_profile_picture_id = $1") + let user_updated = scuffle_utils::database::query("UPDATE users SET profile_picture_id = $1, pending_profile_picture_id = NULL, updated_at = NOW() WHERE id = $2 AND pending_profile_picture_id = $1") .bind(uploaded_file.id) .bind(uploaded_file.owner_id) .build() @@ -213,7 +213,7 @@ async fn handle_failure( let mut client = global.db().get().await.context("failed to get db connection")?; let tx = client.transaction().await.context("failed to start transaction")?; - let uploaded_file: UploadedFile = match utils::database::query("UPDATE uploaded_files SET status = 'failed', failed = $1, updated_at = NOW() WHERE id = $2 AND status = 'queued' RETURNING *") + let uploaded_file: UploadedFile = match scuffle_utils::database::query("UPDATE uploaded_files SET status = 'failed', failed = $1, updated_at = NOW() WHERE id = $2 AND status = 'queued' RETURNING *") .bind(reason.clone()) .bind(job_id) .build_query_as() @@ -250,7 +250,7 @@ async fn handle_failure( let update_count = match uploaded_file.ty { FileType::CategoryArtwork | FileType::CategoryCover => false, FileType::ProfilePicture => { - utils::database::query( + scuffle_utils::database::query( "UPDATE users SET pending_profile_picture_id = NULL, updated_at = NOW() WHERE id = $1 AND pending_profile_picture_id = $2", ) .bind(uploaded_file.owner_id) diff --git a/platform/api/src/main.rs b/platform/api/src/main.rs index 58d2c467..e328c557 100644 --- a/platform/api/src/main.rs +++ b/platform/api/src/main.rs @@ -19,10 +19,10 @@ use platform_api::video_api::{ setup_video_room_client, VideoEventsClient, VideoPlaybackSessionClient, VideoRoomClient, }; use platform_api::{igdb_cron, image_upload_callback, video_event_handler}; +use scuffle_utils::context::Context; +use scuffle_utilsdataloader::DataLoader; +use scuffle_utilsgrpc::TlsSettings; use tokio::select; -use utils::context::Context; -use utils::dataloader::DataLoader; -use utils::grpc::TlsSettings; #[derive(Debug, Clone, Default, config::Config, serde::Deserialize)] #[serde(default)] @@ -256,7 +256,7 @@ impl binary_helper::Global for GlobalState { None }; - let video_api_channel = utils::grpc::make_channel( + let video_api_channel = scuffle_utilsgrpc::make_channel( vec![config.extra.video_api.address.clone()], Duration::from_secs(30), video_api_tls, diff --git a/platform/api/src/subscription.rs b/platform/api/src/subscription.rs index 0fd7208f..6c333e7e 100644 --- a/platform/api/src/subscription.rs +++ b/platform/api/src/subscription.rs @@ -2,12 +2,12 @@ use std::collections::HashMap; use std::ops::{Deref, DerefMut}; use async_nats::Message; +use scuffle_utils::context::Context; use tokio::select; use tokio::sync::{broadcast, mpsc, oneshot, Mutex}; use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose}; use tracing::{debug, error, warn}; use ulid::Ulid; -use utils::context::Context; #[derive(thiserror::Error, Debug)] pub enum SubscriptionManagerError { diff --git a/platform/api/src/video_event_handler.rs b/platform/api/src/video_event_handler.rs index ef7818ea..15ce3a2b 100644 --- a/platform/api/src/video_event_handler.rs +++ b/platform/api/src/video_event_handler.rs @@ -62,7 +62,7 @@ async fn handle_room_event(global: &Arc, event: event::Room, ti .await .context("failed to fetch playback session count")?; - let channel_id = utils::database::query("UPDATE users SET channel_active_connection_id = $1, channel_live_viewer_count = $2, channel_live_viewer_count_updated_at = NOW(), channel_last_live_at = $3 WHERE channel_room_id = $4 RETURNING id") + let channel_id = scuffle_utils::database::query("UPDATE users SET channel_active_connection_id = $1, channel_live_viewer_count = $2, channel_live_viewer_count_updated_at = NOW(), channel_last_live_at = $3 WHERE channel_room_id = $4 RETURNING id") .bind(connection_id.into_ulid()) .bind(live_viewer_count) .bind(chrono::DateTime::from_timestamp_millis(timestamp)) @@ -89,7 +89,7 @@ async fn handle_room_event(global: &Arc, event: event::Room, ti connection_id: Some(connection_id), .. }) => { - let res = utils::database::query("UPDATE users SET channel_active_connection_id = NULL, channel_live_viewer_count = 0, channel_live_viewer_count_updated_at = NOW() WHERE channel_room_id = $1 AND channel_active_connection_id = $2 RETURNING id") + let res = scuffle_utils::database::query("UPDATE users SET channel_active_connection_id = NULL, channel_live_viewer_count = 0, channel_live_viewer_count_updated_at = NOW() WHERE channel_room_id = $1 AND channel_active_connection_id = $2 RETURNING id") .bind(room_id.into_ulid()) .bind(connection_id.into_ulid()) .build_query_single_scalar() diff --git a/proto/scuffle/platform/internal/events/processed_image.proto b/proto/scuffle/platform/internal/events/processed_image.proto deleted file mode 100644 index a2ab6f41..00000000 --- a/proto/scuffle/platform/internal/events/processed_image.proto +++ /dev/null @@ -1,24 +0,0 @@ -syntax = "proto3"; - -package scuffle.platform.internal.events; - -import "scuffle/types/ulid.proto"; -import "scuffle/platform/internal/types/processed_image_variant.proto"; - -message ProcessedImage { - message Success { - repeated scuffle.platform.internal.types.ProcessedImageVariant variants = 1; - } - - message Failure { - string reason = 1; - string friendly_message = 2; - } - - scuffle.types.Ulid job_id = 1; - - oneof result { - Success success = 2; - Failure failure = 3; - } -} diff --git a/proto/scuffle/platform/internal/image_processor.proto b/proto/scuffle/platform/internal/image_processor.proto deleted file mode 100644 index 87ce9a8b..00000000 --- a/proto/scuffle/platform/internal/image_processor.proto +++ /dev/null @@ -1,74 +0,0 @@ -syntax = "proto3"; - -package scuffle.platform.internal.image_processor; - -import "scuffle/platform/internal/types/image_format.proto"; - -message Task { - enum ResizeMethod { - Fit = 0; - Stretch = 1; - PadBottomLeft = 2; - PadBottomRight = 3; - PadTopLeft = 4; - PadTopRight = 5; - PadCenter = 6; - PadCenterRight = 7; - PadCenterLeft = 8; - PadTopCenter = 9; - PadBottomCenter = 10; - PadTop = 11; - PadBottom = 12; - PadLeft = 13; - PadRight = 14; - } - - enum ResizeAlgorithm { - Nearest = 0; - Box = 1; - Bilinear = 2; - Hamming = 3; - CatmullRom = 4; - Mitchell = 5; - Lanczos3 = 6; - } - - string input_path = 1; - - message Ratio { - uint32 numerator = 1; - uint32 denominator = 2; - } - - Ratio aspect_ratio = 2; - bool clamp_aspect_ratio = 3; - - enum Upscale { - Yes = 0; - No = 1; - NoPreserveSource = 2; - } - - Upscale upscale = 4; - - repeated scuffle.platform.internal.types.ImageFormat formats = 5; - ResizeMethod resize_method = 6; - ResizeAlgorithm resize_algorithm = 7; - - bool input_image_scaling = 8; - repeated uint32 scales = 9; - - string output_prefix = 10; - - message Limits { - uint32 max_processing_time_ms = 1; - uint32 max_input_frame_count = 2; - uint32 max_input_width = 3; - uint32 max_input_height = 4; - uint32 max_input_duration_ms = 5; - } - - optional Limits limits = 11; - - string callback_subject = 12; -} diff --git a/proto/scuffle/platform/internal/types/image_format.proto b/proto/scuffle/platform/internal/types/image_format.proto deleted file mode 100644 index 619e6695..00000000 --- a/proto/scuffle/platform/internal/types/image_format.proto +++ /dev/null @@ -1,12 +0,0 @@ -syntax = "proto3"; - -package scuffle.platform.internal.types; - -enum ImageFormat { - WEBP = 0; - AVIF = 1; - GIF = 2; - WEBP_STATIC = 3; - AVIF_STATIC = 4; - PNG_STATIC = 5; -} diff --git a/proto/scuffle/platform/internal/types/processed_image_variant.proto b/proto/scuffle/platform/internal/types/processed_image_variant.proto deleted file mode 100644 index d234a013..00000000 --- a/proto/scuffle/platform/internal/types/processed_image_variant.proto +++ /dev/null @@ -1,13 +0,0 @@ -syntax = "proto3"; - -package scuffle.platform.internal.types; - -import "scuffle/platform/internal/types/image_format.proto"; - -message ProcessedImageVariant { - uint32 width = 1; - uint32 height = 2; - ImageFormat format = 3; - uint32 byte_size = 4; - string path = 5; -} diff --git a/proto/scuffle/platform/internal/types/uploaded_file_metadata.proto b/proto/scuffle/platform/internal/types/uploaded_file_metadata.proto deleted file mode 100644 index 8221c5ab..00000000 --- a/proto/scuffle/platform/internal/types/uploaded_file_metadata.proto +++ /dev/null @@ -1,15 +0,0 @@ -syntax = "proto3"; - -import "scuffle/platform/internal/types/processed_image_variant.proto"; - -package scuffle.platform.internal.types; - -message UploadedFileMetadata { - message Image { - repeated ProcessedImageVariant versions = 1; - } - - oneof metadata { - Image image = 1; - } -} diff --git a/video/api/Cargo.toml b/video/api/Cargo.toml index fab4c56e..c21d9d1c 100644 --- a/video/api/Cargo.toml +++ b/video/api/Cargo.toml @@ -40,7 +40,7 @@ http = "=0.2" hyper = "=0.14" postgres-from-row = "0.5" -utils = { workspace = true, features = ["all"] } +scuffle-utils = { workspace = true, features = ["all"] } config = { workspace = true } pb = { workspace = true } video-common = { workspace = true } diff --git a/video/api/src/api/access_token/create.rs b/video/api/src/api/access_token/create.rs index b3390554..8f7fb7db 100644 --- a/video/api/src/api/access_token/create.rs +++ b/video/api/src/api/access_token/create.rs @@ -48,7 +48,7 @@ pub fn build_query( access_token: &AccessToken, permissions: RequiredScope, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("INSERT INTO ") .push(::Table::NAME) diff --git a/video/api/src/api/access_token/delete.rs b/video/api/src/api/access_token/delete.rs index a9b11b52..f4bfef4a 100644 --- a/video/api/src/api/access_token/delete.rs +++ b/video/api/src/api/access_token/delete.rs @@ -63,7 +63,7 @@ impl ApiRequest for tonic::Request = utils::database::query("DELETE FROM ") + let deleted_ids: Vec = scuffle_utils::database::query("DELETE FROM ") .push(::Table::NAME) .push(" WHERE id = ANY(") .push_bind(ids_to_delete.iter().copied().collect::>()) diff --git a/video/api/src/api/access_token/get.rs b/video/api/src/api/access_token/get.rs index 6d3b6101..6dad9b34 100644 --- a/video/api/src/api/access_token/get.rs +++ b/video/api/src/api/access_token/get.rs @@ -21,7 +21,7 @@ pub fn build_query( req: &AccessTokenGetRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT * FROM ") .push(::Table::NAME) .push(" WHERE "); diff --git a/video/api/src/api/mod.rs b/video/api/src/api/mod.rs index 25989fb2..6fc26f23 100644 --- a/video/api/src/api/mod.rs +++ b/video/api/src/api/mod.rs @@ -21,7 +21,7 @@ pub(crate) mod s3_bucket; pub(crate) mod transcoding_config; pub(crate) mod utils; -pub use utils::{ApiRequest, RequiredScope, ResourcePermission}; +pub use scuffle_utils::{ApiRequest, RequiredScope, ResourcePermission}; fn global_middleware( global: &Arc, diff --git a/video/api/src/api/playback_key_pair/create.rs b/video/api/src/api/playback_key_pair/create.rs index 816ba9da..fabf2f87 100644 --- a/video/api/src/api/playback_key_pair/create.rs +++ b/video/api/src/api/playback_key_pair/create.rs @@ -33,7 +33,7 @@ pub fn build_query( ) -> tonic::Result> { let (cert, fingerprint) = jwt; - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("INSERT INTO ") .push(::Table::NAME) diff --git a/video/api/src/api/playback_key_pair/delete.rs b/video/api/src/api/playback_key_pair/delete.rs index 03bf4578..7100d341 100644 --- a/video/api/src/api/playback_key_pair/delete.rs +++ b/video/api/src/api/playback_key_pair/delete.rs @@ -43,7 +43,7 @@ impl ApiRequest for tonic::Request>(); - let deleted_ids: Vec = utils::database::query("DELETE FROM ") + let deleted_ids: Vec = scuffle_utils::database::query("DELETE FROM ") .push(::Table::NAME) .push(" WHERE id = ANY(") .push_bind(ids_to_delete.iter().copied().collect::>()) diff --git a/video/api/src/api/playback_key_pair/get.rs b/video/api/src/api/playback_key_pair/get.rs index a7c48c8f..ea6f2072 100644 --- a/video/api/src/api/playback_key_pair/get.rs +++ b/video/api/src/api/playback_key_pair/get.rs @@ -21,7 +21,7 @@ pub fn build_query( req: &PlaybackKeyPairGetRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT * FROM ") .push(::Table::NAME) .push(" WHERE "); diff --git a/video/api/src/api/playback_key_pair/modify.rs b/video/api/src/api/playback_key_pair/modify.rs index 52bed5c0..510db76d 100644 --- a/video/api/src/api/playback_key_pair/modify.rs +++ b/video/api/src/api/playback_key_pair/modify.rs @@ -30,7 +30,7 @@ pub fn build_query( req: &PlaybackKeyPairModifyRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("UPDATE ") .push(::Table::NAME) diff --git a/video/api/src/api/playback_session/count.rs b/video/api/src/api/playback_session/count.rs index 2751d33b..328e0673 100644 --- a/video/api/src/api/playback_session/count.rs +++ b/video/api/src/api/playback_session/count.rs @@ -26,7 +26,7 @@ pub fn build_query<'a>( req: &'a PlaybackSessionCountRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); let filter = req .filter diff --git a/video/api/src/api/playback_session/get.rs b/video/api/src/api/playback_session/get.rs index fdec3dd3..942ed15f 100644 --- a/video/api/src/api/playback_session/get.rs +++ b/video/api/src/api/playback_session/get.rs @@ -20,7 +20,7 @@ pub fn build_query<'a>( req: &'a PlaybackSessionGetRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT * FROM ") .push(::Table::NAME) .push(" WHERE "); diff --git a/video/api/src/api/playback_session/revoke.rs b/video/api/src/api/playback_session/revoke.rs index d965544c..c0b567a7 100644 --- a/video/api/src/api/playback_session/revoke.rs +++ b/video/api/src/api/playback_session/revoke.rs @@ -25,7 +25,7 @@ impl ApiRequest for tonic::Request, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); let req = self.get_ref(); @@ -114,7 +114,7 @@ impl ApiRequest for tonic::Request chrono::Utc::now() - chrono::Duration::minutes(10) }) { - utils::database::query("INSERT INTO playback_session_revocations(organization_id, room_id, recording_id, user_id, revoke_before) VALUES ($1, $2, $3, $4, $5)") + scuffle_utils::database::query("INSERT INTO playback_session_revocations(organization_id, room_id, recording_id, user_id, revoke_before) VALUES ($1, $2, $3, $4, $5)") .bind(access_token.organization_id) .bind(req.target.and_then(|t| match t.target { Some(playback_session_target::Target::RoomId(room_id)) => Some(room_id.into_ulid()), diff --git a/video/api/src/api/recording/delete.rs b/video/api/src/api/recording/delete.rs index 5e1181be..b77c9f9b 100644 --- a/video/api/src/api/recording/delete.rs +++ b/video/api/src/api/recording/delete.rs @@ -8,9 +8,9 @@ use pb::scuffle::video::v1::types::access_token_scope::Permission; use pb::scuffle::video::v1::types::{FailedResource, Resource}; use pb::scuffle::video::v1::{RecordingDeleteRequest, RecordingDeleteResponse}; use prost::Message; +use scuffle_utils::database::ClientLike; use tonic::Status; use ulid::Ulid; -use utils::database::ClientLike; use video_common::database::{AccessToken, DatabaseTable, Rendition}; use crate::api::utils::{impl_request_scopes, ApiRequest, TonicRequest}; @@ -161,7 +161,7 @@ async fn handle_query( client: impl ClientLike, deleted_recordings: &HashMap, batch: &mut RecordingDeleteBatchTask, - qb: &mut utils::database::QueryBuilder<'_>, + qb: &mut scuffle_utils::database::QueryBuilder<'_>, ) -> Option<()> where B: UpdateBatch + postgres_from_row::FromRow + Send + Unpin, @@ -227,7 +227,7 @@ impl ApiRequest for tonic::Request = utils::database::query("UPDATE ") + let deleted_recordings: Vec = scuffle_utils::database::query("UPDATE ") .push(::Table::NAME) .push(" SET deleted_at = NOW(), room_id = NULL, recording_config_id = NULL") .push(" WHERE id = ANY(") @@ -258,7 +258,7 @@ impl ApiRequest for tonic::Request::NAME) .push(" WHERE recording_id = ANY(") .push_bind(&deleted_ids) @@ -269,7 +269,7 @@ impl ApiRequest for tonic::Request::FRIENDLY_NAME)) })?; - utils::database::query("DELETE FROM ") + scuffle_utils::database::query("DELETE FROM ") .push(::NAME) .push(" WHERE recording_id = ANY(") .push_bind(&deleted_ids) @@ -302,7 +302,7 @@ impl ApiRequest for tonic::Request::NAME) .push(" WHERE recording_id = ANY(") .push_bind(&deleted_ids) @@ -319,7 +319,7 @@ impl ApiRequest for tonic::Request::NAME) .push(" WHERE recording_id = ANY(") .push_bind(&deleted_ids) diff --git a/video/api/src/api/recording/get.rs b/video/api/src/api/recording/get.rs index 8ce4d8ee..2f139f01 100644 --- a/video/api/src/api/recording/get.rs +++ b/video/api/src/api/recording/get.rs @@ -25,7 +25,7 @@ impl ApiRequest for tonic::Request { ) -> tonic::Result> { let req = self.get_ref(); - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT * FROM ") .push(::Table::NAME) .push(" WHERE "); diff --git a/video/api/src/api/recording/modify.rs b/video/api/src/api/recording/modify.rs index 57e7a48f..a282372e 100644 --- a/video/api/src/api/recording/modify.rs +++ b/video/api/src/api/recording/modify.rs @@ -31,7 +31,7 @@ impl ApiRequest for tonic::Request::Table::NAME) @@ -45,7 +45,7 @@ impl ApiRequest for tonic::Request for tonic::Request tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("INSERT INTO ") .push(::Table::NAME) @@ -62,14 +62,14 @@ pub async fn build_query( } let bucket: S3Bucket = if let Some(s3_bucket_id) = &req.s3_bucket_id { - utils::database::query("SELECT * FROM s3_buckets WHERE id = $1 AND organization_id = $2") + scuffle_utils::database::query("SELECT * FROM s3_buckets WHERE id = $1 AND organization_id = $2") .bind(s3_bucket_id.into_ulid()) .bind(access_token.organization_id) .build_query_as() .fetch_optional(client) .await } else { - utils::database::query("SELECT * FROM s3_buckets WHERE organization_id = $1 AND managed = TRUE LIMIT 1") + scuffle_utils::database::query("SELECT * FROM s3_buckets WHERE organization_id = $1 AND managed = TRUE LIMIT 1") .bind(access_token.organization_id) .build_query_as() .fetch_optional(client) diff --git a/video/api/src/api/recording_config/delete.rs b/video/api/src/api/recording_config/delete.rs index a892b674..4b93ca2b 100644 --- a/video/api/src/api/recording_config/delete.rs +++ b/video/api/src/api/recording_config/delete.rs @@ -27,7 +27,7 @@ impl ApiRequest for tonic::Request tonic::Result> { // Check if any rooms are using the recording config - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); let req = self.get_ref(); @@ -78,7 +78,7 @@ impl ApiRequest for tonic::Request::Table::NAME) diff --git a/video/api/src/api/recording_config/get.rs b/video/api/src/api/recording_config/get.rs index 7a16e08c..e981ed88 100644 --- a/video/api/src/api/recording_config/get.rs +++ b/video/api/src/api/recording_config/get.rs @@ -20,7 +20,7 @@ pub fn build_query( req: &RecordingConfigGetRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT * FROM ") .push(::Table::NAME) .push(" WHERE "); diff --git a/video/api/src/api/recording_config/modify.rs b/video/api/src/api/recording_config/modify.rs index bf7344d6..4a4c6de0 100644 --- a/video/api/src/api/recording_config/modify.rs +++ b/video/api/src/api/recording_config/modify.rs @@ -6,8 +6,8 @@ use pb::scuffle::video::v1::events_fetch_request::Target; use pb::scuffle::video::v1::types::access_token_scope::Permission; use pb::scuffle::video::v1::types::{event, Resource}; use pb::scuffle::video::v1::{RecordingConfigModifyRequest, RecordingConfigModifyResponse}; +use scuffle_utils::database::ClientLike; use tonic::Status; -use utils::database::ClientLike; use video_common::database::{AccessToken, DatabaseTable, Rendition}; use crate::api::errors::MODIFY_NO_FIELDS; @@ -32,7 +32,7 @@ pub async fn build_query<'a>( client: impl ClientLike, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("UPDATE ") .push(::Table::NAME) @@ -74,7 +74,7 @@ pub async fn build_query<'a>( } if let Some(s3_bucket_id) = &req.s3_bucket_id { - utils::database::query("SELECT * FROM s3_buckets WHERE id = $1 AND organization_id = $2") + scuffle_utils::database::query("SELECT * FROM s3_buckets WHERE id = $1 AND organization_id = $2") .bind(s3_bucket_id.into_ulid()) .bind(access_token.organization_id) .build() diff --git a/video/api/src/api/room/create.rs b/video/api/src/api/room/create.rs index 41ad9c3d..61d2f474 100644 --- a/video/api/src/api/room/create.rs +++ b/video/api/src/api/room/create.rs @@ -4,9 +4,9 @@ use pb::scuffle::video::v1::events_fetch_request::Target; use pb::scuffle::video::v1::types::access_token_scope::Permission; use pb::scuffle::video::v1::types::{event, Resource}; use pb::scuffle::video::v1::{RoomCreateRequest, RoomCreateResponse}; +use scuffle_utils::database::ClientLike; use tonic::Status; use ulid::Ulid; -use utils::database::ClientLike; use video_common::database::{AccessToken, DatabaseTable, Visibility}; use super::utils::create_stream_key; @@ -31,7 +31,7 @@ pub async fn build_query( client: impl ClientLike, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("INSERT INTO ") .push(::Table::NAME) @@ -50,7 +50,7 @@ pub async fn build_query( qb.push(") VALUES ("); let transcoding_config_id = if let Some(transcoding_config_id) = &req.transcoding_config_id { - utils::database::query("SELECT * FROM transcoding_configs WHERE id = $1 AND organization_id = $2") + scuffle_utils::database::query("SELECT * FROM transcoding_configs WHERE id = $1 AND organization_id = $2") .bind(transcoding_config_id.into_ulid()) .bind(access_token.organization_id) .build() @@ -68,7 +68,7 @@ pub async fn build_query( }; let recording_config_id = if let Some(recording_config_id) = &req.recording_config_id { - utils::database::query("SELECT * FROM recording_configs WHERE id = $1 AND organization_id = $2") + scuffle_utils::database::query("SELECT * FROM recording_configs WHERE id = $1 AND organization_id = $2") .bind(recording_config_id.into_ulid()) .bind(access_token.organization_id) .build() diff --git a/video/api/src/api/room/delete.rs b/video/api/src/api/room/delete.rs index b998f4aa..1e7ac612 100644 --- a/video/api/src/api/room/delete.rs +++ b/video/api/src/api/room/delete.rs @@ -43,7 +43,7 @@ impl ApiRequest for tonic::Request { .map(pb::scuffle::types::Ulid::into_ulid) .collect::>(); - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT DISTINCT room_id AS id FROM ") .push(::NAME) @@ -71,7 +71,7 @@ impl ApiRequest for tonic::Request { .collect::>(); let deleted_ids = if !ids_to_delete.is_empty() { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("DELETE FROM ") .push(::Table::NAME) diff --git a/video/api/src/api/room/get.rs b/video/api/src/api/room/get.rs index 27c9102f..5577944e 100644 --- a/video/api/src/api/room/get.rs +++ b/video/api/src/api/room/get.rs @@ -21,7 +21,7 @@ pub fn build_query( req: &RoomGetRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT * FROM ") .push(::Table::NAME) .push(" WHERE "); diff --git a/video/api/src/api/room/modify.rs b/video/api/src/api/room/modify.rs index 330e348d..beb28f20 100644 --- a/video/api/src/api/room/modify.rs +++ b/video/api/src/api/room/modify.rs @@ -5,8 +5,8 @@ use pb::scuffle::video::v1::events_fetch_request::Target; use pb::scuffle::video::v1::types::access_token_scope::Permission; use pb::scuffle::video::v1::types::{event, Resource}; use pb::scuffle::video::v1::{RoomModifyRequest, RoomModifyResponse}; +use scuffle_utils::database::ClientLike; use tonic::Status; -use utils::database::ClientLike; use video_common::database::{AccessToken, DatabaseTable, Visibility}; use crate::api::errors::MODIFY_NO_FIELDS; @@ -31,7 +31,7 @@ pub async fn build_query<'a>( client: impl ClientLike, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("UPDATE ") .push(::Table::NAME) @@ -44,7 +44,7 @@ pub async fn build_query<'a>( if transcoding_config_id.is_nil() { seperated.push("transcoding_config_id = NULL"); } else { - utils::database::query("SELECT 1 FROM transcoding_configs WHERE id = $1 AND organization_id = $2") + scuffle_utils::database::query("SELECT 1 FROM transcoding_configs WHERE id = $1 AND organization_id = $2") .bind(transcoding_config_id) .bind(access_token.organization_id) .build() @@ -67,7 +67,7 @@ pub async fn build_query<'a>( if recording_config_id.is_nil() { seperated.push("recording_config_id = NULL"); } else { - utils::database::query("SELECT 1 FROM recording_configs WHERE id = $1 AND organization_id = $2") + scuffle_utils::database::query("SELECT 1 FROM recording_configs WHERE id = $1 AND organization_id = $2") .bind(recording_config_id) .bind(access_token.organization_id) .build() diff --git a/video/api/src/api/room/reset_key.rs b/video/api/src/api/room/reset_key.rs index a743b623..559c9a91 100644 --- a/video/api/src/api/room/reset_key.rs +++ b/video/api/src/api/room/reset_key.rs @@ -52,7 +52,7 @@ impl ApiRequest for tonic::Request { let data = ids_to_reset.iter().copied().map(|id| (id, create_stream_key())); - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("WITH updated_values AS (SELECT * FROM (") .push_values(data.clone(), |mut b, data| { diff --git a/video/api/src/api/s3_bucket/create.rs b/video/api/src/api/s3_bucket/create.rs index 26476abc..b834fdab 100644 --- a/video/api/src/api/s3_bucket/create.rs +++ b/video/api/src/api/s3_bucket/create.rs @@ -32,7 +32,7 @@ pub fn build_query<'a>( req: &'a S3BucketCreateRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("INSERT INTO ") .push(::Table::NAME) diff --git a/video/api/src/api/s3_bucket/delete.rs b/video/api/src/api/s3_bucket/delete.rs index f7f837f1..c424c90d 100644 --- a/video/api/src/api/s3_bucket/delete.rs +++ b/video/api/src/api/s3_bucket/delete.rs @@ -43,7 +43,7 @@ impl ApiRequest for tonic::Request>(); - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("(SELECT DISTINCT s3_bucket_id AS id FROM ") .push(::NAME) @@ -77,7 +77,7 @@ impl ApiRequest for tonic::Request>(); let deleted_ids = if !ids_to_delete.is_empty() { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("DELETE FROM ") .push(::Table::NAME) diff --git a/video/api/src/api/s3_bucket/get.rs b/video/api/src/api/s3_bucket/get.rs index 5362d3f0..f0856b2c 100644 --- a/video/api/src/api/s3_bucket/get.rs +++ b/video/api/src/api/s3_bucket/get.rs @@ -20,7 +20,7 @@ pub fn build_query( req: &S3BucketGetRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT * FROM ") .push(::Table::NAME) .push(" WHERE "); diff --git a/video/api/src/api/s3_bucket/modify.rs b/video/api/src/api/s3_bucket/modify.rs index 560f624c..65cbd078 100644 --- a/video/api/src/api/s3_bucket/modify.rs +++ b/video/api/src/api/s3_bucket/modify.rs @@ -32,7 +32,7 @@ pub fn build_query<'a>( req: &'a S3BucketModifyRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("UPDATE ") .push(::Table::NAME) diff --git a/video/api/src/api/transcoding_config/create.rs b/video/api/src/api/transcoding_config/create.rs index 89195e0a..e66fee9a 100644 --- a/video/api/src/api/transcoding_config/create.rs +++ b/video/api/src/api/transcoding_config/create.rs @@ -29,7 +29,7 @@ pub fn build_query( req: &TranscodingConfigCreateRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("INSERT INTO ") .push(::Table::NAME) diff --git a/video/api/src/api/transcoding_config/delete.rs b/video/api/src/api/transcoding_config/delete.rs index e6711cd1..061421fb 100644 --- a/video/api/src/api/transcoding_config/delete.rs +++ b/video/api/src/api/transcoding_config/delete.rs @@ -43,7 +43,7 @@ impl ApiRequest for tonic::Request>(); - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT DISTINCT transcoding_config_id AS id FROM ") .push(::NAME) @@ -71,7 +71,7 @@ impl ApiRequest for tonic::Request>(); let deleted_ids = if !ids_to_delete.is_empty() { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("DELETE FROM ") .push(::Table::NAME) diff --git a/video/api/src/api/transcoding_config/get.rs b/video/api/src/api/transcoding_config/get.rs index c1433288..a5917d75 100644 --- a/video/api/src/api/transcoding_config/get.rs +++ b/video/api/src/api/transcoding_config/get.rs @@ -20,7 +20,7 @@ pub fn build_query( req: &TranscodingConfigGetRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT * FROM ") .push(::Table::NAME) .push(" WHERE "); diff --git a/video/api/src/api/transcoding_config/modify.rs b/video/api/src/api/transcoding_config/modify.rs index 6c13e0e9..bdd191eb 100644 --- a/video/api/src/api/transcoding_config/modify.rs +++ b/video/api/src/api/transcoding_config/modify.rs @@ -30,7 +30,7 @@ pub fn build_query<'a>( req: &'a TranscodingConfigModifyRequest, access_token: &AccessToken, ) -> tonic::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("UPDATE ") .push(::Table::NAME) diff --git a/video/api/src/api/utils/get.rs b/video/api/src/api/utils/get.rs index 5decd203..d84e55c0 100644 --- a/video/api/src/api/utils/get.rs +++ b/video/api/src/api/utils/get.rs @@ -4,12 +4,12 @@ use ulid::Ulid; use super::tags::validate_tags; -pub fn organization_id(seperated: &mut utils::database::Separated<'_, '_>, organization_id: Ulid) { +pub fn organization_id(seperated: &mut scuffle_utils::database::Separated<'_, '_>, organization_id: Ulid) { seperated.push("organization_id = "); seperated.push_bind_unseparated(organization_id); } -pub fn ids(seperated: &mut utils::database::Separated<'_, '_>, ids: &[pb::scuffle::types::Ulid]) { +pub fn ids(seperated: &mut scuffle_utils::database::Separated<'_, '_>, ids: &[pb::scuffle::types::Ulid]) { if !ids.is_empty() { seperated.push("id = ANY("); seperated.push_bind_unseparated( @@ -23,7 +23,7 @@ pub fn ids(seperated: &mut utils::database::Separated<'_, '_>, ids: &[pb::scuffl } pub fn search_options( - seperated: &mut utils::database::Separated<'_, '_>, + seperated: &mut scuffle_utils::database::Separated<'_, '_>, search_options: Option<&SearchOptions>, ) -> tonic::Result<()> { if let Some(options) = search_options { diff --git a/video/api/src/api/utils/ratelimit.rs b/video/api/src/api/utils/ratelimit.rs index 3bd4a6b5..341b7e03 100644 --- a/video/api/src/api/utils/ratelimit.rs +++ b/video/api/src/api/utils/ratelimit.rs @@ -3,11 +3,11 @@ use std::time::Duration; use fred::interfaces::KeysInterface; use futures_util::Future; +use scuffle_utils::prelude::FutureTimeout; +use scuffle_utilsratelimiter::{RateLimitResponse, RateLimiterOptions}; use tonic::metadata::AsciiMetadataValue; use tonic::{Response, Status}; use ulid::Ulid; -use utils::prelude::FutureTimeout; -use utils::ratelimiter::{RateLimitResponse, RateLimiterOptions}; use super::RequiredScope; use crate::config::ApiConfig; @@ -109,10 +109,12 @@ pub async fn ratelimit_scoped(global: &Arc, options: &RateLimiterOptions) -> tonic::Result { let redis = global.redis(); - let resp = utils::ratelimiter::ratelimit(redis.as_ref(), options).await.map_err(|err| { - tracing::error!(err = %err, "failed to rate limit"); - Status::internal("Unable to process request, failed to rate limit") - })?; + let resp = scuffle_utilsratelimiter::ratelimit(redis.as_ref(), options) + .await + .map_err(|err| { + tracing::error!(err = %err, "failed to rate limit"); + Status::internal("Unable to process request, failed to rate limit") + })?; if resp.banned || resp.remaining == -1 { let mut status = Status::resource_exhausted("rate limit exceeded"); diff --git a/video/api/src/api/utils/tags.rs b/video/api/src/api/utils/tags.rs index a7af9cc5..d71559f2 100644 --- a/video/api/src/api/utils/tags.rs +++ b/video/api/src/api/utils/tags.rs @@ -68,7 +68,7 @@ pub fn validate_tags_array(tags: &[String]) -> tonic::Result<()> { #[derive(postgres_from_row::FromRow)] pub struct TagExt { - pub tags: utils::database::Json>, + pub tags: scuffle_utils::database::Json>, pub status: i64, } @@ -97,8 +97,8 @@ pub fn add_tag_query( tags: &HashMap, id: Ulid, organization_id: Option, -) -> utils::database::QueryBuilder<'_> { - let mut qb = utils::database::QueryBuilder::default(); +) -> scuffle_utils::database::QueryBuilder<'_> { + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("WITH mt AS (SELECT id, tags || ") .push_bind(utils::database::Json(tags)) @@ -126,8 +126,8 @@ pub fn remove_tag_query( tags: &[String], id: Ulid, organization_id: Option, -) -> utils::database::QueryBuilder<'_> { - let mut qb = utils::database::QueryBuilder::default(); +) -> scuffle_utils::database::QueryBuilder<'_> { + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("WITH rt AS (SELECT id, tags - ") .push_bind(tags) diff --git a/video/api/src/dataloaders/access_token.rs b/video/api/src/dataloaders/access_token.rs index 3537d62e..0bb86f65 100644 --- a/video/api/src/dataloaders/access_token.rs +++ b/video/api/src/dataloaders/access_token.rs @@ -1,7 +1,7 @@ use std::sync::Arc; +use scuffle_utilsdataloader::{DataLoader, Loader, LoaderOutput}; use ulid::Ulid; -use utils::dataloader::{DataLoader, Loader, LoaderOutput}; pub struct AccessTokenLoader { db: Arc, @@ -20,7 +20,7 @@ impl Loader for AccessTokenLoader { async fn load(&self, keys: &[Self::Key]) -> LoaderOutput { let results: Vec = - utils::database::query("SELECT * FROM access_tokens WHERE (organization_id, id) IN ") + scuffle_utils::database::query("SELECT * FROM access_tokens WHERE (organization_id, id) IN ") .push_tuples(keys, |mut qb, (organization_id, access_token_id)| { qb.push_bind(organization_id).push_bind(access_token_id); }) diff --git a/video/api/src/dataloaders/recording_state.rs b/video/api/src/dataloaders/recording_state.rs index 453cb585..63d77bf4 100644 --- a/video/api/src/dataloaders/recording_state.rs +++ b/video/api/src/dataloaders/recording_state.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use itertools::Itertools; +use scuffle_utilsdataloader::{DataLoader, Loader, LoaderOutput}; use ulid::Ulid; -use utils::dataloader::{DataLoader, Loader, LoaderOutput}; use video_common::database::{Recording, Rendition}; pub struct RecordingStateLoader { @@ -53,7 +53,7 @@ impl Loader for RecordingStateLoader { type Value = RecordingState; async fn load(&self, keys: &[Self::Key]) -> LoaderOutput { - let results: Vec = utils::database::query("SELECT organization_id, recording_id, rendition, COUNT(size_bytes) AS size_bytes, MAX(end_time) AS end_time, MAX(start_time) AS start_time FROM recording_rendition_segments WHERE (organization_id, recording_id) IN ") + let results: Vec = scuffle_utils::database::query("SELECT organization_id, recording_id, rendition, COUNT(size_bytes) AS size_bytes, MAX(end_time) AS end_time, MAX(start_time) AS start_time FROM recording_rendition_segments WHERE (organization_id, recording_id) IN ") .push_tuples(keys, |mut qb, (organization_id, recording_id)| { qb.push_bind(organization_id).push_bind(recording_id); }).push(" GROUP BY organization_id, recording_id, rendition ORDER BY organization_id, recording_id").build_query_as().fetch_all(&self.db).await.map_err(|err| { diff --git a/video/api/src/dataloaders/room.rs b/video/api/src/dataloaders/room.rs index 7bb3eb22..099e3836 100644 --- a/video/api/src/dataloaders/room.rs +++ b/video/api/src/dataloaders/room.rs @@ -1,7 +1,7 @@ use std::sync::Arc; +use scuffle_utilsdataloader::{DataLoader, Loader, LoaderOutput}; use ulid::Ulid; -use utils::dataloader::{DataLoader, Loader, LoaderOutput}; pub struct RoomLoader { db: Arc, @@ -19,16 +19,17 @@ impl Loader for RoomLoader { type Value = video_common::database::Room; async fn load(&self, keys: &[Self::Key]) -> LoaderOutput { - let results: Vec = utils::database::query("SELECT * FROM rooms WHERE (organization_id, id) IN ") - .push_tuples(keys, |mut qb, (organization_id, room_id)| { - qb.push_bind(organization_id).push_bind(room_id); - }) - .build_query_as() - .fetch_all(&self.db) - .await - .map_err(|err| { - tracing::error!(error = %err, "failed to load rooms"); - })?; + let results: Vec = + scuffle_utils::database::query("SELECT * FROM rooms WHERE (organization_id, id) IN ") + .push_tuples(keys, |mut qb, (organization_id, room_id)| { + qb.push_bind(organization_id).push_bind(room_id); + }) + .build_query_as() + .fetch_all(&self.db) + .await + .map_err(|err| { + tracing::error!(error = %err, "failed to load rooms"); + })?; Ok(results.into_iter().map(|v| ((v.organization_id, v.id), v)).collect()) } diff --git a/video/api/src/global.rs b/video/api/src/global.rs index 7c48972e..c8e57f5c 100644 --- a/video/api/src/global.rs +++ b/video/api/src/global.rs @@ -1,4 +1,4 @@ -use utils::dataloader::DataLoader; +use scuffle_utilsdataloader::DataLoader; use crate::config::ApiConfig; use crate::dataloaders; diff --git a/video/api/src/main.rs b/video/api/src/main.rs index ec3de8a7..072d82f7 100644 --- a/video/api/src/main.rs +++ b/video/api/src/main.rs @@ -5,9 +5,9 @@ use async_nats::jetstream::stream::{self, RetentionPolicy}; use binary_helper::config::RedisConfig; use binary_helper::global::{setup_database, setup_nats, setup_redis, GlobalCtx, GlobalDb, GlobalNats}; use binary_helper::{bootstrap, grpc_health, grpc_server, impl_global_traits}; +use scuffle_utils::context::Context; +use scuffle_utilsdataloader::DataLoader; use tokio::select; -use utils::context::Context; -use utils::dataloader::DataLoader; use video_api::config::ApiConfig; use video_api::dataloaders; @@ -88,7 +88,7 @@ impl binary_helper::Global for GlobalState { let recording_state_loader = dataloaders::RecordingStateLoader::new(db.clone()); let room_loader = dataloaders::RoomLoader::new(db.clone()); - utils::ratelimiter::load_rate_limiter_script(&*redis) + scuffle_utilsratelimiter::load_rate_limiter_script(&*redis) .await .context("failed to load rate limiter script")?; diff --git a/video/api/src/tests/api/access_token.rs b/video/api/src/tests/api/access_token.rs index 70ef1b8c..b73c2de6 100644 --- a/video/api/src/tests/api/access_token.rs +++ b/video/api/src/tests/api/access_token.rs @@ -17,7 +17,7 @@ use crate::tests::utils; #[tokio::test] async fn test_access_token_get_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![ ( @@ -48,12 +48,12 @@ async fn test_access_token_get_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_access_token_create_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( AccessTokenCreateRequest { @@ -78,12 +78,12 @@ async fn test_access_token_create_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_access_token_tag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( AccessTokenTagRequest { @@ -105,12 +105,12 @@ async fn test_access_token_tag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_access_token_untag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( AccessTokenUntagRequest { @@ -128,12 +128,12 @@ async fn test_access_token_untag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_access_token_tag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let tag_request = AccessTokenTagRequest { id: Some(access_token.id.into()), @@ -148,12 +148,12 @@ async fn test_access_token_tag() { let tags = response.tags.unwrap(); assert_eq!(tags.tags.get("key").unwrap(), &"value"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_access_token_untag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; // Tag the token first let tag_request = AccessTokenTagRequest { @@ -179,12 +179,12 @@ async fn test_access_token_untag() { let tags = response.tags.unwrap(); assert!(tags.tags.is_empty(), "Tags should be empty after untagging"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_access_token_create() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; // Test case: Create a basic access token let req = AccessTokenCreateRequest { @@ -231,16 +231,16 @@ async fn test_access_token_create() { "tag_value" ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_access_token_get() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; // Create multiple access tokens with different tags for testing let created_tokens = vec![ - utils::create_access_token( + scuffle_utilscreate_access_token( &global, &main_access_token.organization_id, vec![], @@ -252,7 +252,7 @@ async fn test_access_token_get() { .collect(), ) .await, - utils::create_access_token( + scuffle_utilscreate_access_token( &global, &main_access_token.organization_id, vec![], @@ -335,16 +335,16 @@ async fn test_access_token_get() { // Assertions for limit and reverse options assert_eq!(limited_tokens.len(), 1, "Should fetch only one token due to limit"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_access_token_delete() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; // Create access tokens to be deleted let token_to_delete = - utils::create_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; + scuffle_utilscreate_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; // Delete request with a token the caller should have permission to delete let delete_request = AccessTokenDeleteRequest { @@ -390,15 +390,15 @@ async fn test_access_token_delete() { "Failed deletion reason should be correct" ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_access_token_boiler_plate() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let no_scopes_token = - utils::create_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; + scuffle_utilscreate_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; let server = AccessTokenServer::::new(); @@ -579,5 +579,5 @@ async fn test_access_token_boiler_plate() { assert_eq!(response.code(), tonic::Code::PermissionDenied); assert_eq!(response.message(), "missing required scope: access_token:delete"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } diff --git a/video/api/src/tests/api/events.rs b/video/api/src/tests/api/events.rs index 56736da6..2f443240 100644 --- a/video/api/src/tests/api/events.rs +++ b/video/api/src/tests/api/events.rs @@ -14,7 +14,7 @@ use crate::tests::utils; #[tokio::test] async fn test_events() { - let (global, handler, access_token) = utils::setup(ApiConfig { + let (global, handler, access_token) = scuffle_utilssetup(ApiConfig { events: EventsConfig { stream_name: Ulid::new().to_string(), fetch_request_min_delay: Duration::from_secs(0), @@ -87,5 +87,5 @@ async fn test_events() { .await .expect("failed to process request"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } diff --git a/video/api/src/tests/api/playback_key_pair.rs b/video/api/src/tests/api/playback_key_pair.rs index 4ca878af..87fa2471 100644 --- a/video/api/src/tests/api/playback_key_pair.rs +++ b/video/api/src/tests/api/playback_key_pair.rs @@ -19,7 +19,7 @@ use crate::tests::utils; #[tokio::test] async fn test_playback_key_pair_get_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let playback_key_pair = create_playback_keypair( &global, @@ -57,12 +57,12 @@ async fn test_playback_key_pair_get_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_key_pair_create_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( PlaybackKeyPairCreateRequest { @@ -80,12 +80,12 @@ async fn test_playback_key_pair_create_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_key_pair_modify_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let playback_key_pair = create_playback_keypair( &global, @@ -135,12 +135,12 @@ async fn test_playback_key_pair_modify_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_key_pair_tag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let playback_key_pair = create_playback_keypair( &global, @@ -169,12 +169,12 @@ async fn test_playback_key_pair_tag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_key_pair_untag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let playback_key_pair = create_playback_keypair( &global, @@ -199,12 +199,12 @@ async fn test_playback_key_pair_untag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_key_pair_tag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let playback_key_pair = create_playback_keypair( &global, @@ -228,12 +228,12 @@ async fn test_playback_key_pair_tag() { assert_eq!(tags.tags.get("key").unwrap(), &"value"); assert_eq!(tags.tags.get("key2").unwrap(), &"value2"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_key_pair_untag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let playback_key_pair = create_playback_keypair( &global, @@ -260,12 +260,12 @@ async fn test_playback_key_pair_untag() { assert_eq!(tags.tags.len(), 1, "Only 1 tag should be left"); assert_eq!(tags.tags.get("key2").unwrap(), &"value2"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_key_pair_create() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let (_, fingerprint) = validate_public_key(include_str!("../certs/ec384/public.pem")).unwrap(); @@ -293,12 +293,12 @@ async fn test_playback_key_pair_create() { let created = response.playback_key_pair.as_ref().unwrap(); assert_eq!(created.tags.as_ref().unwrap().tags.get("tag_key").unwrap(), "tag_value"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_key_pair_modify() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let playback_key_pair = create_playback_keypair( &global, @@ -357,12 +357,12 @@ async fn test_playback_key_pair_modify() { "Fingerprint should not change" ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_key_pair_get() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; // Create multiple playback key pair with different tags for testing let created = vec![ @@ -447,12 +447,12 @@ async fn test_playback_key_pair_get() { // Assertions for limit and reverse options assert_eq!(fetched.len(), 1, "Should fetch only one playback key pair due to limit"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_key_pair_delete() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; // Create access tokens to be deleted let keypair_to_delete = create_playback_keypair( @@ -479,15 +479,15 @@ async fn test_playback_key_pair_delete() { ); assert!(failed_deletions.is_empty(), "No deletions should fail in this scenario"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_key_pair_boiler_plate() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let no_scopes_token = - utils::create_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; + scuffle_utilscreate_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; let server = PlaybackKeyPairServer::::new(); @@ -721,5 +721,5 @@ async fn test_playback_key_pair_boiler_plate() { assert_eq!(response.code(), tonic::Code::PermissionDenied); assert_eq!(response.message(), "missing required scope: playback_key_pair:delete"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } diff --git a/video/api/src/tests/api/playback_session.rs b/video/api/src/tests/api/playback_session.rs index 95c3439e..34a79af1 100644 --- a/video/api/src/tests/api/playback_session.rs +++ b/video/api/src/tests/api/playback_session.rs @@ -22,7 +22,7 @@ use crate::tests::utils::{self, teardown}; #[tokio::test] async fn test_playback_session_count_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![ ( @@ -60,12 +60,12 @@ async fn test_playback_session_count_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_session_get_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![ ( @@ -204,12 +204,12 @@ async fn test_playback_session_get_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_session_count() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording = create_recording( @@ -295,12 +295,12 @@ async fn test_playback_session_count() { assert_eq!(response.count, 300); assert_eq!(response.deduplicated_count, 200); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_session_revoke() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording = create_recording( @@ -416,12 +416,12 @@ async fn test_playback_session_revoke() { "revoke_before should be within 5 seconds of now" ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_session_revoke_2() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording = create_recording( @@ -478,12 +478,12 @@ async fn test_playback_session_revoke_2() { // Half of them are authorized, so 50 should be revoked assert_eq!(response.revoked, 50); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_playback_session_get() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording = create_recording( @@ -564,10 +564,10 @@ async fn test_playback_session_get() { #[tokio::test] async fn test_playback_session_boiler_plate() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let no_scopes_token = - utils::create_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; + scuffle_utilscreate_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; let server = PlaybackSessionServer::::new(); @@ -703,5 +703,5 @@ async fn test_playback_session_boiler_plate() { assert_eq!(response.code(), tonic::Code::PermissionDenied); assert_eq!(response.message(), "missing required scope: playback_session:delete"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } diff --git a/video/api/src/tests/api/recording.rs b/video/api/src/tests/api/recording.rs index 62626994..1b417e75 100644 --- a/video/api/src/tests/api/recording.rs +++ b/video/api/src/tests/api/recording.rs @@ -25,7 +25,7 @@ use crate::tests::utils; #[tokio::test] async fn test_recording_get() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let room = create_room(&global, access_token.organization_id).await; @@ -89,12 +89,12 @@ async fn test_recording_get() { .unwrap(); assert_eq!(resp.recordings.len(), 0, "expected 0 recording"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_modify() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let room = create_room(&global, access_token.organization_id).await; @@ -202,12 +202,12 @@ async fn test_recording_modify() { "expected tag to match" ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_tag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let room = create_room(&global, access_token.organization_id).await; @@ -267,12 +267,12 @@ async fn test_recording_tag() { "expected 1 tags" ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_untag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let room = create_room(&global, access_token.organization_id).await; @@ -323,14 +323,14 @@ async fn test_recording_untag() { assert_eq!(resp.tags.as_ref().unwrap().tags.len(), 0, "expected 0 tags"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_delete() { let recording_delete_stream = Ulid::new().to_string(); - let (global, handler, access_token) = utils::setup(ApiConfig { + let (global, handler, access_token) = scuffle_utilssetup(ApiConfig { recording_delete_stream: recording_delete_stream.clone(), ..Default::default() }) @@ -433,15 +433,15 @@ async fn test_recording_delete() { assert!(thumbnails.is_empty(), "expected all thumbnails to be deleted"); assert!(segments.is_empty(), "expected all segments to be deleted"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_boiler_plate() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let no_scopes_token = - utils::create_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; + scuffle_utilscreate_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; let server = RecordingServer::::new(); @@ -630,5 +630,5 @@ async fn test_recording_boiler_plate() { assert_eq!(response.code(), tonic::Code::PermissionDenied); assert_eq!(response.message(), "missing required scope: recording:delete"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } diff --git a/video/api/src/tests/api/recording_config.rs b/video/api/src/tests/api/recording_config.rs index 9603c56c..360f98f9 100644 --- a/video/api/src/tests/api/recording_config.rs +++ b/video/api/src/tests/api/recording_config.rs @@ -20,7 +20,7 @@ use crate::tests::utils; #[tokio::test] async fn test_recording_config_get_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![ ( @@ -55,12 +55,12 @@ async fn test_recording_config_get_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_config_create_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; @@ -85,12 +85,12 @@ async fn test_recording_config_create_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_config_modify_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; @@ -174,12 +174,12 @@ async fn test_recording_config_modify_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_config_tag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( RecordingConfigTagRequest { @@ -201,12 +201,12 @@ async fn test_recording_config_tag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_config_untag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( RecordingConfigUntagRequest { @@ -224,12 +224,12 @@ async fn test_recording_config_untag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_config_tag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording_config = create_recording_config( @@ -257,12 +257,12 @@ async fn test_recording_config_tag() { assert_eq!(tags.tags.get("key").unwrap(), &"value"); assert_eq!(tags.tags.get("key2").unwrap(), &"value2"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_config_untag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording_config = create_recording_config( @@ -292,12 +292,12 @@ async fn test_recording_config_untag() { assert_eq!(tags.tags.len(), 1, "Only 1 tag should be left"); assert_eq!(tags.tags.get("key2").unwrap(), &"value2"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_config_create() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; @@ -359,12 +359,12 @@ async fn test_recording_config_create() { } ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_config_modify() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording_config = create_recording_config( @@ -440,12 +440,12 @@ async fn test_recording_config_modify() { } ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_config_get() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, main_access_token.organization_id, HashMap::new()).await; @@ -546,12 +546,12 @@ async fn test_recording_config_get() { // Assertions for limit and reverse options assert_eq!(fetched.len(), 1, "Should fetch only one playback key pair due to limit"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_config_delete() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, main_access_token.organization_id, HashMap::new()).await; @@ -583,15 +583,15 @@ async fn test_recording_config_delete() { ); assert!(failed_deletions.is_empty(), "No deletions should fail in this scenario"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_recording_config_boiler_plate() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let no_scopes_token = - utils::create_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; + scuffle_utilscreate_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; let server = RecordingConfigServer::::new(); @@ -837,5 +837,5 @@ async fn test_recording_config_boiler_plate() { assert_eq!(response.code(), tonic::Code::PermissionDenied); assert_eq!(response.message(), "missing required scope: recording_config:delete"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } diff --git a/video/api/src/tests/api/room.rs b/video/api/src/tests/api/room.rs index 1fcbd8fb..5dd48b81 100644 --- a/video/api/src/tests/api/room.rs +++ b/video/api/src/tests/api/room.rs @@ -22,7 +22,7 @@ use crate::tests::utils; #[tokio::test] async fn test_room_get_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![ ( @@ -116,12 +116,12 @@ async fn test_room_get_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_create_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording_config = @@ -159,12 +159,12 @@ async fn test_room_create_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_modify_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording_config = @@ -209,12 +209,12 @@ async fn test_room_modify_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_pair_tag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( RoomTagRequest { @@ -236,12 +236,12 @@ async fn test_room_pair_tag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_pair_untag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( RoomUntagRequest { @@ -259,12 +259,12 @@ async fn test_room_pair_untag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_create() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording_config = @@ -341,12 +341,12 @@ async fn test_room_create() { "tags should be empty" ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_get() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording_config = @@ -518,12 +518,12 @@ async fn test_room_get() { assert_eq!(resp.rooms.len(), 1, "should return 1 room"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_modify() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; let recording_config = @@ -619,12 +619,12 @@ async fn test_room_modify() { "tags should be empty" ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_tag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let room = create_room(&global, access_token.organization_id).await; @@ -704,12 +704,12 @@ async fn test_room_tag() { "tags should match" ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_untag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let room = create_room(&global, access_token.organization_id).await; @@ -748,12 +748,12 @@ async fn test_room_untag() { assert_eq!(resp.tags.as_ref().unwrap().tags.len(), 0, "tags should match"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_delete() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let room = create_room(&global, access_token.organization_id).await; @@ -785,12 +785,12 @@ async fn test_room_delete() { assert_eq!(resp.failed_deletes[0].id, Some(room.id.into()), "failed delete should match"); assert_eq!(resp.failed_deletes[0].reason, "room not found", "failed delete should match"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_disconnect() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let room = create_room(&global, access_token.organization_id).await; @@ -841,12 +841,12 @@ async fn test_room_disconnect() { ); assert!(msg.payload.is_empty(), "payload should be empty"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_reset_keys() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let room = create_room(&global, access_token.organization_id).await; @@ -875,15 +875,15 @@ async fn test_room_reset_keys() { assert_eq!(resp.rooms[0].id, Some(room.id.into()), "room should match"); assert_eq!(resp.rooms[0].key, key, "room should match"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_room_boilerplate() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let no_scopes_token = - utils::create_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; + scuffle_utilscreate_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; let room = create_room(&global, main_access_token.organization_id).await; @@ -1189,5 +1189,5 @@ async fn test_room_boilerplate() { assert_eq!(response.code(), tonic::Code::PermissionDenied); assert_eq!(response.message(), "missing required scope: room:delete"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } diff --git a/video/api/src/tests/api/s3_bucket.rs b/video/api/src/tests/api/s3_bucket.rs index 8a2e7e6b..a83723a2 100644 --- a/video/api/src/tests/api/s3_bucket.rs +++ b/video/api/src/tests/api/s3_bucket.rs @@ -17,7 +17,7 @@ use crate::tests::utils; #[tokio::test] async fn test_s3_bucket_get_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![ ( @@ -50,12 +50,12 @@ async fn test_s3_bucket_get_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_s3_bucket_create_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( S3BucketCreateRequest { @@ -78,12 +78,12 @@ async fn test_s3_bucket_create_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_s3_bucket_modify_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![ ( @@ -119,12 +119,12 @@ async fn test_s3_bucket_modify_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_s3_bucket_tag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( S3BucketTagRequest { @@ -146,12 +146,12 @@ async fn test_s3_bucket_tag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_s3_bucket_untag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( S3BucketUntagRequest { @@ -169,12 +169,12 @@ async fn test_s3_bucket_untag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_s3_bucket_tag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket( &global, @@ -200,12 +200,12 @@ async fn test_s3_bucket_tag() { assert_eq!(tags.tags.get("key").unwrap(), &"value"); assert_eq!(tags.tags.get("key2").unwrap(), &"value2"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_s3_bucket_untag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket( &global, @@ -230,12 +230,12 @@ async fn test_s3_bucket_untag() { assert_eq!(tags.tags.len(), 1, "Only 1 tag should be left"); assert_eq!(tags.tags.get("key2").unwrap(), &"value2"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_s3_bucket_create() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let response: S3BucketCreateResponse = process_request( &global, @@ -281,12 +281,12 @@ async fn test_s3_bucket_create() { assert_eq!(created.endpoint, None); assert_eq!(created.public_url, None); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_s3_bucket_modify() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, access_token.organization_id, HashMap::new()).await; @@ -342,12 +342,12 @@ async fn test_s3_bucket_modify() { assert_eq!(created.endpoint, Some("https://endpoint.com".to_string())); assert_eq!(created.public_url, Some("https://public_url.com".to_string())); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_s3_bucket_get() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let created = vec![ create_s3_bucket( @@ -443,12 +443,12 @@ async fn test_s3_bucket_get() { // Assertions for limit and reverse options assert_eq!(fetched.len(), 1, "Should fetch only one s3 bucket due to limit"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_s3_bucket_delete() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let s3_bucket = create_s3_bucket(&global, main_access_token.organization_id, HashMap::new()).await; @@ -472,15 +472,15 @@ async fn test_s3_bucket_delete() { ); assert!(failed_deletions.is_empty(), "No deletions should fail in this scenario"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_s3_bucket_boilerplate() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let no_scopes_token = - utils::create_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; + scuffle_utilscreate_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; let server = S3BucketServer::::new(); @@ -700,5 +700,5 @@ async fn test_s3_bucket_boilerplate() { assert_eq!(response.code(), tonic::Code::PermissionDenied); assert_eq!(response.message(), "missing required scope: s3_bucket:delete"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } diff --git a/video/api/src/tests/api/transcoding_config.rs b/video/api/src/tests/api/transcoding_config.rs index d8de88c1..5302cc21 100644 --- a/video/api/src/tests/api/transcoding_config.rs +++ b/video/api/src/tests/api/transcoding_config.rs @@ -19,7 +19,7 @@ use crate::tests::utils; #[tokio::test] async fn test_transcoding_config_get_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![ ( @@ -54,12 +54,12 @@ async fn test_transcoding_config_get_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_transcoding_config_create_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( TranscodingConfigCreateRequest { @@ -78,12 +78,12 @@ async fn test_transcoding_config_create_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_transcoding_config_modify_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![ ( @@ -150,12 +150,12 @@ async fn test_transcoding_config_modify_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_transcoding_config_tag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( TranscodingConfigTagRequest { @@ -177,12 +177,12 @@ async fn test_transcoding_config_tag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_transcoding_config_untag_qb() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let test_cases = vec![( TranscodingConfigUntagRequest { @@ -200,12 +200,12 @@ async fn test_transcoding_config_untag_qb() { assert_query_matches(result, expected); } - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_transcoding_config_tag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let transcoding_config = create_transcoding_config( &global, @@ -231,12 +231,12 @@ async fn test_transcoding_config_tag() { assert_eq!(tags.tags.get("key").unwrap(), &"value"); assert_eq!(tags.tags.get("key2").unwrap(), &"value2"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_transcoding_config_untag() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let transcoding_config = create_transcoding_config( &global, @@ -264,12 +264,12 @@ async fn test_transcoding_config_untag() { assert_eq!(tags.tags.len(), 1, "Only 1 tag should be left"); assert_eq!(tags.tags.get("key2").unwrap(), &"value2"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_transcoding_config_create() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let response: TranscodingConfigCreateResponse = process_request( &global, @@ -314,12 +314,12 @@ async fn test_transcoding_config_create() { ] ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_transcoding_config_modify() { - let (global, handler, access_token) = utils::setup(Default::default()).await; + let (global, handler, access_token) = scuffle_utilssetup(Default::default()).await; let transcoding_config = create_transcoding_config( &global, @@ -381,12 +381,12 @@ async fn test_transcoding_config_modify() { ] ); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_transcoding_config_get() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let created = vec![ create_transcoding_config( @@ -482,12 +482,12 @@ async fn test_transcoding_config_get() { // Assertions for limit and reverse options assert_eq!(fetched.len(), 1, "Should fetch only one playback key pair due to limit"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_transcoding_config_delete() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let transcoding_config = create_transcoding_config( &global, @@ -516,15 +516,15 @@ async fn test_transcoding_config_delete() { ); assert!(failed_deletions.is_empty(), "No deletions should fail in this scenario"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } #[tokio::test] async fn test_transcoding_config_boiler_plate() { - let (global, handler, main_access_token) = utils::setup(Default::default()).await; + let (global, handler, main_access_token) = scuffle_utilssetup(Default::default()).await; let no_scopes_token = - utils::create_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; + scuffle_utilscreate_access_token(&global, &main_access_token.organization_id, vec![], HashMap::new()).await; let server = TranscodingConfigServer::::new(); @@ -771,5 +771,5 @@ async fn test_transcoding_config_boiler_plate() { assert_eq!(response.code(), tonic::Code::PermissionDenied); assert_eq!(response.message(), "missing required scope: transcoding_config:delete"); - utils::teardown(global, handler).await; + scuffle_utilsteardown(global, handler).await; } diff --git a/video/api/src/tests/api/utils.rs b/video/api/src/tests/api/utils.rs index fb3725e8..4b143bb8 100644 --- a/video/api/src/tests/api/utils.rs +++ b/video/api/src/tests/api/utils.rs @@ -44,7 +44,7 @@ pub async fn create_playback_session( let client = global.db().get().await.unwrap(); for inserts in &inserts.chunks(u16::MAX as usize / 5) { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("INSERT INTO playback_sessions (id, organization_id, room_id, recording_id, user_id, ip_address) "); @@ -66,7 +66,7 @@ pub async fn create_playback_session( } pub async fn create_room(global: &Arc, organization_id: Ulid) -> video_common::database::Room { - utils::database::query("INSERT INTO rooms (id, organization_id, stream_key) VALUES ($1, $2, $3) RETURNING *") + scuffle_utils::database::query("INSERT INTO rooms (id, organization_id, stream_key) VALUES ($1, $2, $3) RETURNING *") .bind(Ulid::new()) .bind(organization_id) .bind(create_stream_key()) @@ -84,7 +84,7 @@ pub async fn create_recording( recording_config_id: Option, tags: HashMap, ) -> video_common::database::Recording { - utils::database::query("INSERT INTO recordings (id, organization_id, s3_bucket_id, room_id, recording_config_id, tags) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *").bind(Ulid::new()).bind(organization_id).bind(s3_bucket_id).bind(room_id).bind(recording_config_id).bind(utils::database::Json(tags)).build_query_as().fetch_one(global.db()).await.unwrap() + scuffle_utils::database::query("INSERT INTO recordings (id, organization_id, s3_bucket_id, room_id, recording_config_id, tags) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *").bind(Ulid::new()).bind(organization_id).bind(s3_bucket_id).bind(room_id).bind(recording_config_id).bind(utils::database::Json(tags)).build_query_as().fetch_one(global.db()).await.unwrap() } pub async fn create_recording_thumbnail( @@ -98,7 +98,7 @@ pub async fn create_recording_thumbnail( let client = global.db().get().await.unwrap(); for inserts in &inserts.chunks(u16::MAX as usize / 5) { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("INSERT INTO recording_thumbnails (organization_id, recording_id, idx, id, start_time) "); @@ -129,7 +129,7 @@ pub async fn create_recording_segment( let client = global.db().get().await.unwrap(); for inserts in &inserts.chunks(u16::MAX as usize / 14) { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push( "INSERT INTO recording_rendition_segments (organization_id, recording_id, rendition, idx, id, start_time, end_time) ", @@ -159,7 +159,7 @@ pub async fn create_recording_config( s3_bucket_id: Ulid, tags: HashMap, ) -> video_common::database::RecordingConfig { - utils::database::query( + scuffle_utils::database::query( "INSERT INTO recording_configs (id, organization_id, s3_bucket_id, tags) VALUES ($1, $2, $3, $4) RETURNING *", ) .bind(Ulid::new()) @@ -177,14 +177,16 @@ pub async fn create_transcoding_config( organization_id: Ulid, tags: HashMap, ) -> video_common::database::TranscodingConfig { - utils::database::query("INSERT INTO transcoding_configs (id, organization_id, tags) VALUES ($1, $2, $3) RETURNING *") - .bind(Ulid::new()) - .bind(organization_id) - .bind(utils::database::Json(tags)) - .build_query_as() - .fetch_one(global.db()) - .await - .unwrap() + scuffle_utils::database::query( + "INSERT INTO transcoding_configs (id, organization_id, tags) VALUES ($1, $2, $3) RETURNING *", + ) + .bind(Ulid::new()) + .bind(organization_id) + .bind(utils::database::Json(tags)) + .build_query_as() + .fetch_one(global.db()) + .await + .unwrap() } pub async fn create_s3_bucket( @@ -192,7 +194,7 @@ pub async fn create_s3_bucket( organization_id: Ulid, tags: HashMap, ) -> video_common::database::S3Bucket { - utils::database::query( + scuffle_utils::database::query( "INSERT INTO s3_buckets (id, organization_id, name, region, access_key_id, secret_access_key, managed, tags) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *", ) .bind(Ulid::new()) @@ -216,7 +218,7 @@ pub async fn create_playback_keypair( ) -> video_common::database::PlaybackKeyPair { let (key, fingerprint) = validate_public_key(include_str!("../certs/ec384/public.pem")).unwrap(); - utils::database::query( + scuffle_utils::database::query( "INSERT INTO playback_key_pairs (id, organization_id, public_key, fingerprint, updated_at, tags) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *", ) .bind(Ulid::new()) diff --git a/video/api/src/tests/global.rs b/video/api/src/tests/global.rs index 34e06b9b..74f6a720 100644 --- a/video/api/src/tests/global.rs +++ b/video/api/src/tests/global.rs @@ -4,11 +4,11 @@ use async_nats::jetstream::stream::{self, RetentionPolicy}; use binary_helper::logging; use fred::interfaces::ClientLike; use postgres_from_row::tokio_postgres::NoTls; -use utils::context::{Context, Handler}; -use utils::database::deadpool_postgres::{ManagerConfig, PoolConfig, RecyclingMethod, Runtime}; -use utils::database::Pool; -use utils::dataloader::DataLoader; -use utils::prelude::FutureTimeout; +use scuffle_utils::context::{Context, Handler}; +use scuffle_utils::database::deadpool_postgres::{ManagerConfig, PoolConfig, RecyclingMethod, Runtime}; +use scuffle_utils::database::Pool; +use scuffle_utils::prelude::FutureTimeout; +use scuffle_utilsdataloader::DataLoader; use crate::config::ApiConfig; use crate::dataloaders; @@ -127,7 +127,7 @@ pub async fn mock_global_state(config: ApiConfig) -> (Arc, Handler) .expect("failed to connect to redis") .expect("failed to connect to redis"); - utils::ratelimiter::load_rate_limiter_script(&*redis) + scuffle_utilsratelimiter::load_rate_limiter_script(&*redis) .await .expect("failed to load rate limiter script"); diff --git a/video/api/src/tests/utils.rs b/video/api/src/tests/utils.rs index 751cace3..2e85c9f3 100644 --- a/video/api/src/tests/utils.rs +++ b/video/api/src/tests/utils.rs @@ -4,9 +4,9 @@ use std::sync::Arc; use std::time::Duration; use pb::scuffle::video::v1::types::{access_token_scope, AccessTokenScope}; +use scuffle_utils::context::Handler; +use scuffle_utils::prelude::FutureTimeout; use ulid::Ulid; -use utils::context::Handler; -use utils::prelude::FutureTimeout; use video_common::database::AccessToken; use super::global::{mock_global_state, GlobalState}; @@ -14,15 +14,17 @@ use crate::config::ApiConfig; use crate::global::ApiGlobal; pub async fn create_organization(global: &Arc) -> video_common::database::Organization { - utils::database::query("INSERT INTO organizations (id, name, updated_at, tags) VALUES ($1, $2, $3, $4) RETURNING *") - .bind(Ulid::new()) - .bind("test") - .bind(chrono::Utc::now()) - .bind(utils::database::Json(std::collections::HashMap::::default())) - .build_query_as() - .fetch_one(global.db()) - .await - .unwrap() + scuffle_utils::database::query( + "INSERT INTO organizations (id, name, updated_at, tags) VALUES ($1, $2, $3, $4) RETURNING *", + ) + .bind(Ulid::new()) + .bind("test") + .bind(chrono::Utc::now()) + .bind(utils::database::Json(std::collections::HashMap::::default())) + .build_query_as() + .fetch_one(global.db()) + .await + .unwrap() } pub async fn create_access_token( @@ -31,7 +33,7 @@ pub async fn create_access_token( scopes: Vec>, tags: std::collections::HashMap, ) -> video_common::database::AccessToken { - utils::database::query("INSERT INTO access_tokens (id, organization_id, secret_token, last_active_at, updated_at, expires_at, scopes, tags) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *") + scuffle_utils::database::query("INSERT INTO access_tokens (id, organization_id, secret_token, last_active_at, updated_at, expires_at, scopes, tags) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *") .bind(Ulid::new()) .bind(organization_id) .bind(Ulid::new()) diff --git a/video/cli/Cargo.toml b/video/cli/Cargo.toml index 4fa3331b..621a2976 100644 --- a/video/cli/Cargo.toml +++ b/video/cli/Cargo.toml @@ -23,7 +23,7 @@ base64 = "0.22" pb = { workspace = true } config = { workspace = true } -utils = { workspace = true, features = ["all"] } +scuffle-utils = { workspace = true, features = ["all"] } video-api = { workspace = true } video-common = { workspace = true } binary-helper = { workspace = true } diff --git a/video/cli/src/invoker/direct.rs b/video/cli/src/invoker/direct.rs index bfc3d179..0c240909 100644 --- a/video/cli/src/invoker/direct.rs +++ b/video/cli/src/invoker/direct.rs @@ -9,10 +9,10 @@ use binary_helper::{impl_global_traits, logging}; use futures_util::stream::BoxStream; use pb::scuffle::video::v1::types::{access_token_scope, AccessTokenScope}; pub use pb::scuffle::video::v1::*; +use scuffle_utils::context::Context; +use scuffle_utils::prelude::FutureTimeout; +use scuffle_utilsdataloader::DataLoader; use ulid::Ulid; -use utils::context::Context; -use utils::dataloader::DataLoader; -use utils::prelude::FutureTimeout; use video_api::api::ApiRequest; use video_api::config::ApiConfig; use video_api::dataloaders; @@ -34,7 +34,7 @@ impl DirectBackend { logging::init(&global.config.logging.level, global.config.logging.mode).expect("failed to init logging"); let access_token = if let Some(organization_id) = organization_id { - utils::database::query("SELECT * FROM organizations WHERE id = $1") + scuffle_utils::database::query("SELECT * FROM organizations WHERE id = $1") .bind(organization_id) .build() .fetch_optional(global.db()) @@ -76,7 +76,7 @@ impl DirectBackend { async fn create_organization(&self, req: OrganizationCreateRequest) -> anyhow::Result { let org: video_common::database::Organization = - utils::database::query("INSERT INTO organizations (id, name, tags) VALUES ($1, $2, $3) RETURNING *") + scuffle_utils::database::query("INSERT INTO organizations (id, name, tags) VALUES ($1, $2, $3) RETURNING *") .bind(Ulid::new()) .bind(req.name) .bind(utils::database::Json(req.tags)) @@ -130,7 +130,7 @@ impl DirectBackend { } async fn get_organization(&self, req: OrganizationGetRequest) -> anyhow::Result> { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT * FROM organizations"); @@ -183,7 +183,7 @@ impl DirectBackend { } async fn modify_organization(&self, req: OrganizationModifyRequest) -> anyhow::Result { - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("UPDATE organizations SET "); @@ -223,7 +223,7 @@ impl DirectBackend { async fn tag_organization(&self, req: OrganizationTagRequest) -> anyhow::Result { let org: video_common::database::Organization = - utils::database::query("UPDATE organizations SET tags = tags || $1 WHERE id = $2 RETURNING *") + scuffle_utils::database::query("UPDATE organizations SET tags = tags || $1 WHERE id = $2 RETURNING *") .bind(utils::database::Json(req.tags)) .bind(req.id) .build_query_as() @@ -239,7 +239,7 @@ impl DirectBackend { async fn untag_organization(&self, req: OrganizationUntagRequest) -> anyhow::Result { let org: video_common::database::Organization = - utils::database::query("UPDATE organizations SET tags = tags - $1::text[] WHERE id = $2 RETURNING *") + scuffle_utils::database::query("UPDATE organizations SET tags = tags - $1::text[] WHERE id = $2 RETURNING *") .bind(req.tags) .bind(req.id) .build_query_as() @@ -353,7 +353,7 @@ impl GlobalState { let recording_state_loader = dataloaders::RecordingStateLoader::new(db.clone()); let room_loader = dataloaders::RoomLoader::new(db.clone()); - utils::ratelimiter::load_rate_limiter_script(&*redis) + scuffle_utilsratelimiter::load_rate_limiter_script(&*redis) .await .context("failed to load rate limiter script")?; diff --git a/video/cli/src/invoker/grpc.rs b/video/cli/src/invoker/grpc.rs index a0904f12..f2ac2535 100644 --- a/video/cli/src/invoker/grpc.rs +++ b/video/cli/src/invoker/grpc.rs @@ -2,10 +2,10 @@ use anyhow::Context as _; use base64::Engine; use futures_util::stream::BoxStream; pub use pb::scuffle::video::v1::*; +use scuffle_utils::context::Context; use tonic::service::interceptor; use tonic::transport::Channel; use ulid::Ulid; -use utils::context::Context; use crate::cli::display::{DeleteResponse, TagResponse}; pub use crate::invoker::request::*; diff --git a/video/cli/src/invoker/mod.rs b/video/cli/src/invoker/mod.rs index 42f6739f..611af3c6 100644 --- a/video/cli/src/invoker/mod.rs +++ b/video/cli/src/invoker/mod.rs @@ -1,5 +1,5 @@ use anyhow::Context as _; -use utils::context::Context; +use scuffle_utils::context::Context; use self::direct::DirectBackend; use self::grpc::GrpcBackend; diff --git a/video/cli/src/main.rs b/video/cli/src/main.rs index 777c3846..e1706028 100644 --- a/video/cli/src/main.rs +++ b/video/cli/src/main.rs @@ -4,8 +4,8 @@ use anyhow::Context as _; use clap::Parser; use cli::Invokable; use invoker::Invoker; -use utils::context::Context; -use utils::prelude::FutureTimeout; +use scuffle_utils::context::Context; +use scuffle_utils::prelude::FutureTimeout; mod cli; mod invoker; diff --git a/video/common/Cargo.toml b/video/common/Cargo.toml index f171fb7f..667abdf9 100644 --- a/video/common/Cargo.toml +++ b/video/common/Cargo.toml @@ -22,4 +22,4 @@ async-trait = "0.1" async-nats = "0.34" pb = { workspace = true } -utils = { workspace = true, features = ["all"] } +scuffle-utils = { workspace = true, features = ["all"] } diff --git a/video/common/src/database/access_token.rs b/video/common/src/database/access_token.rs index 660e6a7f..14d6b76f 100644 --- a/video/common/src/database/access_token.rs +++ b/video/common/src/database/access_token.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use chrono::Utc; use pb::scuffle::video::v1::types::AccessTokenScope; use postgres_from_row::FromRow; +use scuffle_utils::database::{json, protobuf_vec}; use ulid::Ulid; -use utils::database::{json, protobuf_vec}; use super::DatabaseTable; diff --git a/video/common/src/database/organization.rs b/video/common/src/database/organization.rs index 394deede..075d20f3 100644 --- a/video/common/src/database/organization.rs +++ b/video/common/src/database/organization.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use postgres_from_row::FromRow; +use scuffle_utils::database::json; use ulid::Ulid; -use utils::database::json; use super::DatabaseTable; diff --git a/video/common/src/database/playback_key_pair.rs b/video/common/src/database/playback_key_pair.rs index 346e0bb7..30480744 100644 --- a/video/common/src/database/playback_key_pair.rs +++ b/video/common/src/database/playback_key_pair.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use postgres_from_row::FromRow; +use scuffle_utils::database::json; use ulid::Ulid; -use utils::database::json; use super::DatabaseTable; diff --git a/video/common/src/database/recording.rs b/video/common/src/database/recording.rs index a2f7e8b9..428fbbf3 100644 --- a/video/common/src/database/recording.rs +++ b/video/common/src/database/recording.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use postgres_from_row::FromRow; +use scuffle_utils::database::json; use ulid::Ulid; -use utils::database::json; use super::{DatabaseTable, Rendition, Visibility}; diff --git a/video/common/src/database/recording_config.rs b/video/common/src/database/recording_config.rs index dad5d71c..05d4d0d1 100644 --- a/video/common/src/database/recording_config.rs +++ b/video/common/src/database/recording_config.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use pb::scuffle::video::v1::types::{RecordingLifecyclePolicy, Rendition as PbRendition}; use postgres_from_row::FromRow; +use scuffle_utils::database::{json, protobuf_vec}; use ulid::Ulid; -use utils::database::{json, protobuf_vec}; use super::{DatabaseTable, Rendition}; diff --git a/video/common/src/database/room.rs b/video/common/src/database/room.rs index 3efb0427..62843920 100644 --- a/video/common/src/database/room.rs +++ b/video/common/src/database/room.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use pb::scuffle::video::v1::types::{AudioConfig, RecordingConfig, TranscodingConfig, VideoConfig}; use postgres_from_row::FromRow; +use scuffle_utils::database::{json, protobuf_opt, protobuf_vec_opt}; use ulid::Ulid; -use utils::database::{json, protobuf_opt, protobuf_vec_opt}; use super::{DatabaseTable, RoomStatus, Visibility}; diff --git a/video/common/src/database/s3_bucket.rs b/video/common/src/database/s3_bucket.rs index 7d4e5743..aba90a7e 100644 --- a/video/common/src/database/s3_bucket.rs +++ b/video/common/src/database/s3_bucket.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use postgres_from_row::FromRow; +use scuffle_utils::database::json; use ulid::Ulid; -use utils::database::json; use super::DatabaseTable; diff --git a/video/common/src/database/transcoding_config.rs b/video/common/src/database/transcoding_config.rs index 3258faf1..9a5b12af 100644 --- a/video/common/src/database/transcoding_config.rs +++ b/video/common/src/database/transcoding_config.rs @@ -2,8 +2,8 @@ use std::collections::HashMap; use pb::scuffle::video::v1::types::Rendition as PbRendition; use postgres_from_row::FromRow; +use scuffle_utils::database::json; use ulid::Ulid; -use utils::database::json; use super::{DatabaseTable, Rendition}; diff --git a/video/edge/Cargo.toml b/video/edge/Cargo.toml index 4f43d8da..0e39e673 100644 --- a/video/edge/Cargo.toml +++ b/video/edge/Cargo.toml @@ -41,7 +41,7 @@ thiserror = "1.0" http-body-util = "0.1" hyper-util = "0.1" -utils = { workspace = true, features = ["all"] } +scuffle-utils = { workspace = true, features = ["all"] } config = { workspace = true } pb = { workspace = true } video-common = { workspace = true } diff --git a/video/edge/src/edge/error.rs b/video/edge/src/edge/error.rs index be911e38..42fde5c3 100644 --- a/video/edge/src/edge/error.rs +++ b/video/edge/src/edge/error.rs @@ -1,4 +1,4 @@ -use utils::http::RouteError; +use scuffle_utils::http::RouteError; use crate::subscription::SubscriptionError; @@ -13,9 +13,9 @@ pub enum EdgeError { #[error("internal server error: {0}")] InternalServer(&'static str), #[error("database error: {0}")] - Database(#[from] utils::database::tokio_postgres::Error), + Database(#[from] scuffle_utils::database::tokio_postgres::Error), #[error("database pool error: {0}")] - DatabasePool(#[from] utils::database::deadpool_postgres::PoolError), + DatabasePool(#[from] scuffle_utils::database::deadpool_postgres::PoolError), #[error("json error: {0}")] ParseJson(#[from] serde_json::Error), #[error("prost error: {0}")] diff --git a/video/edge/src/edge/mod.rs b/video/edge/src/edge/mod.rs index 75ea2085..99631742 100644 --- a/video/edge/src/edge/mod.rs +++ b/video/edge/src/edge/mod.rs @@ -10,12 +10,12 @@ use hyper::server::conn::http1; use hyper::service::service_fn; use hyper::StatusCode; use hyper_util::rt::TokioIo; +use scuffle_utils::context::ContextExt; +use scuffle_utils::http::router::middleware::{CorsMiddleware, CorsOptions}; +use scuffle_utils::http::router::Router; +use scuffle_utils::http::RouteError; +use scuffle_utils::prelude::FutureTimeout; use tokio::net::TcpSocket; -use utils::context::ContextExt; -use utils::http::router::middleware::{CorsMiddleware, CorsOptions}; -use utils::http::router::Router; -use utils::http::RouteError; -use utils::prelude::FutureTimeout; use crate::config::EdgeConfig; use crate::global::EdgeGlobal; diff --git a/video/edge/src/edge/stream/hls_config.rs b/video/edge/src/edge/stream/hls_config.rs index 5f574863..709a44fb 100644 --- a/video/edge/src/edge/stream/hls_config.rs +++ b/video/edge/src/edge/stream/hls_config.rs @@ -1,7 +1,7 @@ use hyper::{Request, StatusCode}; use pb::scuffle::video::internal::live_rendition_manifest::RenditionInfo; -use utils::http::ext::*; -use utils::http::RouteError; +use scuffle_utils::http::ext::*; +use scuffle_utils::http::RouteError; use super::block_style::BlockStyle; use crate::edge::error::Result; diff --git a/video/edge/src/edge/stream/mod.rs b/video/edge/src/edge/stream/mod.rs index ded283d6..938cd7c6 100644 --- a/video/edge/src/edge/stream/mod.rs +++ b/video/edge/src/edge/stream/mod.rs @@ -10,17 +10,17 @@ use itertools::Itertools; use pb::scuffle::video::internal::{LiveManifest, LiveRenditionManifest}; use pb::scuffle::video::v1::types::{AudioConfig, VideoConfig}; use prost::Message; +use scuffle_utils::database::non_null_vec; +use scuffle_utils::http::ext::*; +use scuffle_utils::http::router::builder::RouterBuilder; +use scuffle_utils::http::router::ext::RequestExt; +use scuffle_utils::http::router::Router; +use scuffle_utils::http::RouteError; +use scuffle_utils::prelude::FutureTimeout; +use scuffle_utilsmake_response; use tokio::io::AsyncReadExt; use tokio::time::Instant; use ulid::Ulid; -use utils::database::non_null_vec; -use utils::http::ext::*; -use utils::http::router::builder::RouterBuilder; -use utils::http::router::ext::RequestExt; -use utils::http::router::Router; -use utils::http::RouteError; -use utils::make_response; -use utils::prelude::FutureTimeout; use video_common::database::{Rendition, Room, RoomStatus, Visibility}; use video_common::keys; use video_player_types::SessionRefresh; @@ -84,7 +84,7 @@ async fn room_playlist(req: Request) -> Result = utils::database::query( + let room: Option = scuffle_utils::database::query( r#" SELECT * @@ -145,7 +145,7 @@ async fn room_playlist(req: Request) -> Result(req: Request) -> Result = utils::database::query( + let recording: Option = scuffle_utils::database::query( r#" WITH filtered_recordings AS ( SELECT @@ -346,7 +346,7 @@ async fn recording_playlist(req: Request) -> Result(req: Request) -> Result(req: Request) -> Result(req: Request) -> Result = utils::database::query( + let room: Option = scuffle_utils::database::query( r#" SELECT * diff --git a/video/edge/src/edge/stream/playlist.rs b/video/edge/src/edge/stream/playlist.rs index eb9b202b..4e3059e0 100644 --- a/video/edge/src/edge/stream/playlist.rs +++ b/video/edge/src/edge/stream/playlist.rs @@ -4,9 +4,9 @@ use hyper::StatusCode; use pb::ext::UlidExt; use pb::scuffle::video::internal::LiveRenditionManifest; use pb::scuffle::video::v1::types::{AudioConfig, VideoConfig}; +use scuffle_utils::database::non_null_vec; +use scuffle_utils::http::ext::*; use ulid::Ulid; -use utils::database::non_null_vec; -use utils::http::ext::*; use video_common::database::{Recording, RecordingThumbnail, Rendition, Visibility}; use video_player_types::{ RenditionPlaylist, RenditionPlaylistRendition, RenditionPlaylistSegment, RenditionPlaylistSegmentPart, @@ -192,7 +192,7 @@ pub async fn rendition_playlist( }; let recording_data = if let Some((recording_id, skip, active_idx)) = recording_data { - utils::database::query( + scuffle_utils::database::query( r#" SELECT s.public_url, @@ -239,7 +239,7 @@ pub async fn rendition_playlist( ); if !*skip { - let recording_rendition: RecordingRenditionExt = utils::database::query( + let recording_rendition: RecordingRenditionExt = scuffle_utils::database::query( r#" WITH filtered_renditions AS ( SELECT recording_id, rendition @@ -271,7 +271,7 @@ pub async fn rendition_playlist( .map_err_route((StatusCode::INTERNAL_SERVER_ERROR, "failed to query database"))? .ok_or((StatusCode::NOT_FOUND, "recording no longer exists"))?; - let recording_thumbnails: Vec = utils::database::query( + let recording_thumbnails: Vec = scuffle_utils::database::query( r#" SELECT * diff --git a/video/edge/src/edge/stream/tokens.rs b/video/edge/src/edge/stream/tokens.rs index c340208f..052c3219 100644 --- a/video/edge/src/edge/stream/tokens.rs +++ b/video/edge/src/edge/stream/tokens.rs @@ -5,9 +5,9 @@ use hmac::{Hmac, Mac}; use hyper::StatusCode; use jwt_next::asymmetric::VerifyingKey; use jwt_next::{asymmetric, AlgorithmType, SignWithKey, Token, VerifyWithKey}; +use scuffle_utils::http::ext::*; use sha2::Sha256; use ulid::Ulid; -use utils::http::ext::*; use video_common::database::{PlaybackKeyPair, Rendition}; use crate::config::EdgeConfig; @@ -131,7 +131,7 @@ impl TokenClaims { return Err((StatusCode::BAD_REQUEST, "invalid token, iat is too far in the past").into()); } - let keypair: Option = utils::database::query( + let keypair: Option = scuffle_utils::database::query( r#" SELECT * @@ -162,7 +162,7 @@ impl TokenClaims { .verify_with_key(&verifier) .map_err(|_| (StatusCode::BAD_REQUEST, "invalid token, failed to verify"))?; - let mut qb = utils::database::QueryBuilder::default(); + let mut qb = scuffle_utils::database::QueryBuilder::default(); qb.push("SELECT 1 FROM playback_session_revocations WHERE organization_id = ") .push_bind(organization_id) @@ -201,7 +201,7 @@ impl TokenClaims { } if let Some(id) = token.claims().id.as_ref() { - if utils::database::query( + if scuffle_utils::database::query( "INSERT INTO playback_session_revocations(organization_id, sso_id) VALUES ($1, $2) ON CONFLICT DO NOTHING", ) .bind(organization_id) diff --git a/video/edge/src/main.rs b/video/edge/src/main.rs index 8d21e41a..9522e406 100644 --- a/video/edge/src/main.rs +++ b/video/edge/src/main.rs @@ -5,8 +5,8 @@ use anyhow::Context as _; use async_nats::jetstream::stream::StorageType; use binary_helper::global::{setup_database, setup_nats, GlobalCtx, GlobalDb, GlobalNats}; use binary_helper::{bootstrap, grpc_health, grpc_server, impl_global_traits}; +use scuffle_utils::context::Context; use tokio::select; -use utils::context::Context; use video_edge::config::EdgeConfig; use video_edge::global::EdgeState; use video_edge::subscription; diff --git a/video/edge/src/subscription/mod.rs b/video/edge/src/subscription/mod.rs index f13f29c0..ef986850 100644 --- a/video/edge/src/subscription/mod.rs +++ b/video/edge/src/subscription/mod.rs @@ -1,10 +1,10 @@ use std::sync::Arc; use async_nats::jetstream::kv::Entry; +use scuffle_utils::context::Context; use tokio::select; use tokio::sync::{broadcast, mpsc, oneshot, Mutex}; use tokio_stream::{StreamExt, StreamMap, StreamNotifyClose}; -use utils::context::Context; pub use self::recv::SubscriberReceiver; use self::topics::TopicMap; diff --git a/video/ingest/Cargo.toml b/video/ingest/Cargo.toml index 30f6cb05..ff7da6b9 100644 --- a/video/ingest/Cargo.toml +++ b/video/ingest/Cargo.toml @@ -34,7 +34,7 @@ tokio-stream = "0.1" default-net = "0.22" postgres-from-row = "0.5" -utils = { workspace = true, features = ["all"] } +scuffle-utils = { workspace = true, features = ["all"] } rtmp = { workspace = true } bytesio = { workspace = true } flv = { workspace = true } diff --git a/video/ingest/src/grpc/ingest.rs b/video/ingest/src/grpc/ingest.rs index 4b6b3f59..041627bb 100644 --- a/video/ingest/src/grpc/ingest.rs +++ b/video/ingest/src/grpc/ingest.rs @@ -6,8 +6,8 @@ use async_stream::try_stream; use futures_util::Stream; use pb::ext::UlidExt; use pb::scuffle::video::internal::{ingest_server, ingest_watch_request, IngestWatchRequest, IngestWatchResponse}; +use scuffle_utils::prelude::FutureTimeout; use tonic::{async_trait, Request, Response, Status, Streaming}; -use utils::prelude::FutureTimeout; use crate::global::{IncomingTranscoder, IngestGlobal}; diff --git a/video/ingest/src/ingest/connection.rs b/video/ingest/src/ingest/connection.rs index c16ca2d2..369595da 100644 --- a/video/ingest/src/ingest/connection.rs +++ b/video/ingest/src/ingest/connection.rs @@ -16,14 +16,14 @@ use pb::scuffle::video::v1::events_fetch_request::Target; use pb::scuffle::video::v1::types::{event, Rendition}; use prost::Message as _; use rtmp::{ChannelData, PublishRequest, Session, SessionError}; +use scuffle_utils::context::ContextExt; +use scuffle_utils::prelude::FutureTimeout; use tokio::select; use tokio::sync::mpsc; use tokio::time::Instant; use tonic::{Status, Streaming}; use transmuxer::{AudioSettings, MediaSegment, TransmuxResult, Transmuxer, VideoSettings}; use ulid::Ulid; -use utils::context::ContextExt; -use utils::prelude::FutureTimeout; use video_common::database::RoomStatus; use video_common::{events, keys}; @@ -176,7 +176,7 @@ impl Connection { let id = Ulid::new(); - let result: Option = utils::database::query( + let result: Option = scuffle_utils::database::query( r#" UPDATE rooms as new SET @@ -492,7 +492,7 @@ impl Connection { WhichTranscoder::Current => { self.current_transcoder = None; self.current_transcoder_id = Ulid::nil(); - match utils::database::query( + match scuffle_utils::database::query( r#" UPDATE rooms SET @@ -707,7 +707,7 @@ impl Connection { } .encode_to_vec(); - match utils::database::query( + match scuffle_utils::database::query( r#" UPDATE rooms SET @@ -1087,7 +1087,7 @@ impl Connection { ) .await; - utils::database::query( + scuffle_utils::database::query( r#" UPDATE rooms SET diff --git a/video/ingest/src/ingest/mod.rs b/video/ingest/src/ingest/mod.rs index 04e5bbef..a383a8cc 100644 --- a/video/ingest/src/ingest/mod.rs +++ b/video/ingest/src/ingest/mod.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use std::time::Duration; use anyhow::Result; +use scuffle_utils::context::ContextExt; +use scuffle_utils::prelude::FutureTimeout; use tokio::net::TcpSocket; -use utils::context::ContextExt; -use utils::prelude::FutureTimeout; use crate::config::IngestConfig; use crate::global::IngestGlobal; diff --git a/video/ingest/src/ingest/update.rs b/video/ingest/src/ingest/update.rs index 5a849f1f..336650fd 100644 --- a/video/ingest/src/ingest/update.rs +++ b/video/ingest/src/ingest/update.rs @@ -1,9 +1,9 @@ use std::sync::Arc; use std::time::Duration; +use scuffle_utils::prelude::FutureTimeout; use tokio::sync::mpsc; use ulid::Ulid; -use utils::prelude::FutureTimeout; use crate::global::IngestGlobal; @@ -22,7 +22,7 @@ pub async fn update_db( let mut success = false; for _ in 0..5 { - match utils::database::query( + match scuffle_utils::database::query( r#" UPDATE rooms SET diff --git a/video/ingest/src/main.rs b/video/ingest/src/main.rs index c2d63655..660dea99 100644 --- a/video/ingest/src/main.rs +++ b/video/ingest/src/main.rs @@ -5,10 +5,10 @@ use std::sync::Arc; use anyhow::Context as _; use binary_helper::global::{setup_database, setup_nats, GlobalCtx, GlobalDb, GlobalNats}; use binary_helper::{bootstrap, grpc_health, grpc_server, impl_global_traits}; +use scuffle_utils::context::Context; use tokio::select; use tokio::sync::{mpsc, Mutex}; use ulid::Ulid; -use utils::context::Context; use video_ingest::config::IngestConfig; use video_ingest::global::IncomingTranscoder; diff --git a/video/ingest/src/tests/global.rs b/video/ingest/src/tests/global.rs index fe9b9eef..48255a7f 100644 --- a/video/ingest/src/tests/global.rs +++ b/video/ingest/src/tests/global.rs @@ -3,11 +3,11 @@ use std::sync::Arc; use binary_helper::logging; use postgres_from_row::tokio_postgres::NoTls; +use scuffle_utils::context::{Context, Handler}; +use scuffle_utils::database::deadpool_postgres::{ManagerConfig, PoolConfig, RecyclingMethod, Runtime}; +use scuffle_utils::database::Pool; use tokio::sync::{mpsc, Mutex}; use ulid::Ulid; -use utils::context::{Context, Handler}; -use utils::database::deadpool_postgres::{ManagerConfig, PoolConfig, RecyclingMethod, Runtime}; -use utils::database::Pool; use crate::config::IngestConfig; use crate::global::IncomingTranscoder; diff --git a/video/ingest/src/tests/ingest.rs b/video/ingest/src/tests/ingest.rs index c0ea414f..fe746704 100644 --- a/video/ingest/src/tests/ingest.rs +++ b/video/ingest/src/tests/ingest.rs @@ -18,13 +18,13 @@ use pb::scuffle::video::internal::{ingest_watch_request, ingest_watch_response, use pb::scuffle::video::v1::events_fetch_request::Target; use pb::scuffle::video::v1::types::{event, Event, Rendition}; use prost::Message; +use scuffle_utils::context::ContextExt; +use scuffle_utils::prelude::FutureTimeout; use tokio::io::AsyncWriteExt; use tokio::process::Command; use tokio::sync::mpsc; use tokio::task::JoinHandle; use ulid::Ulid; -use utils::context::ContextExt; -use utils::prelude::FutureTimeout; use uuid::Uuid; use video_common::database::Room; use video_common::keys::{self, event_subject}; @@ -126,7 +126,7 @@ impl Watcher { tracing::info!("connecting to ingest server at {}", advertise_addr); - let channel = utils::grpc::make_channel(vec![advertise_addr], Duration::from_secs(30), None).unwrap(); + let channel = scuffle_utilsgrpc::make_channel(vec![advertise_addr], Duration::from_secs(30), None).unwrap(); let mut client = IngestClient::new(channel); @@ -153,7 +153,7 @@ struct TestState { pub org_id: Ulid, pub room_id: Ulid, pub global: Arc, - pub handler: utils::context::Handler, + pub handler: scuffle_utils::context::Handler, pub transcoder_requests: Pin>>, pub events: Pin>>, pub ingest_handle: JoinHandle>, @@ -237,7 +237,7 @@ impl TestState { }) }; - utils::database::query("INSERT INTO organizations (id, name) VALUES ($1, $2)") + scuffle_utils::database::query("INSERT INTO organizations (id, name) VALUES ($1, $2)") .bind(org_id) .bind("test") .build() @@ -247,7 +247,7 @@ impl TestState { let room_id = Ulid::new(); - utils::database::query("INSERT INTO rooms (organization_id, id, stream_key) VALUES ($1, $2, $3)") + scuffle_utils::database::query("INSERT INTO rooms (organization_id, id, stream_key) VALUES ($1, $2, $3)") .bind(org_id) .bind(room_id) .bind(room_id.to_string()) @@ -321,7 +321,7 @@ async fn test_ingest_stream() { } let room: video_common::database::Room = - utils::database::query("SELECT * FROM rooms WHERE organization_id = $1 AND id = $2") + scuffle_utils::database::query("SELECT * FROM rooms WHERE organization_id = $1 AND id = $2") .bind(state.org_id) .bind(state.room_id) .build_query_as() @@ -508,7 +508,7 @@ async fn test_ingest_stream() { tracing::info!("waiting for transcoder to exit"); - let room: Room = utils::database::query("SELECT * FROM rooms WHERE organization_id = $1 AND id = $2") + let room: Room = scuffle_utils::database::query("SELECT * FROM rooms WHERE organization_id = $1 AND id = $2") .bind(state.org_id) .bind(state.room_id) .build_query_as() @@ -710,7 +710,7 @@ async fn test_ingest_stream_shutdown() { _ => panic!("unexpected event"), } - let room: Room = utils::database::query("SELECT * FROM rooms WHERE organization_id = $1 AND id = $2") + let room: Room = scuffle_utils::database::query("SELECT * FROM rooms WHERE organization_id = $1 AND id = $2") .bind(state.org_id) .bind(state.room_id) .build_query_as() @@ -751,7 +751,7 @@ async fn test_ingest_stream_transcoder_full() { _ => panic!("unexpected event"), } - let room: Room = utils::database::query("SELECT * FROM rooms WHERE organization_id = $1 AND id = $2") + let room: Room = scuffle_utils::database::query("SELECT * FROM rooms WHERE organization_id = $1 AND id = $2") .bind(state.org_id) .bind(state.room_id) .build_query_as() diff --git a/video/lib/bytesio/Cargo.toml b/video/lib/bytesio/Cargo.toml index 39636ef7..355bef14 100644 --- a/video/lib/bytesio/Cargo.toml +++ b/video/lib/bytesio/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT OR Apache-2.0" [features] -tokio = ["dep:tokio-util", "dep:tokio-stream", "dep:tokio", "dep:futures", "dep:utils"] +tokio = ["dep:tokio-util", "dep:tokio-stream", "dep:tokio", "dep:futures", "dep:scuffle-utils"] default = ["tokio"] [dependencies] @@ -16,7 +16,7 @@ futures = { version = "0.3", optional = true } tokio-util = { version = "0.7", features = ["codec"], optional = true } tokio-stream = { version = "0.1", optional = true } tokio = { version = "1.36", optional = true } -utils = { workspace = true, default-features = false, features = ["prelude"], optional = true } +scuffle-utils = { workspace = true, default-features = false, features = ["prelude"], optional = true } [dev-dependencies] tokio = { version = "1.36", features = ["full"] } diff --git a/video/lib/bytesio/src/bytesio.rs b/video/lib/bytesio/src/bytesio.rs index 8ddffd61..4c2c7705 100644 --- a/video/lib/bytesio/src/bytesio.rs +++ b/video/lib/bytesio/src/bytesio.rs @@ -2,10 +2,10 @@ use std::time::Duration; use bytes::{Bytes, BytesMut}; use futures::SinkExt; +use scuffle_utils::prelude::FutureTimeout; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::StreamExt; use tokio_util::codec::{BytesCodec, Framed}; -use utils::prelude::FutureTimeout; use super::bytesio_errors::BytesIOError; diff --git a/video/lib/rtmp/Cargo.toml b/video/lib/rtmp/Cargo.toml index 0e57c82f..0544d23a 100644 --- a/video/lib/rtmp/Cargo.toml +++ b/video/lib/rtmp/Cargo.toml @@ -21,7 +21,7 @@ tracing = "0.1" bytesio = { workspace = true, features = ["default"] } amf0 = { workspace = true } -utils = { workspace = true } +scuffle-utils = { workspace = true } [dev-dependencies] tokio = { version = "1.36", features = ["full"] } diff --git a/video/lib/rtmp/src/session/server_session.rs b/video/lib/rtmp/src/session/server_session.rs index 000258a5..631949d1 100644 --- a/video/lib/rtmp/src/session/server_session.rs +++ b/video/lib/rtmp/src/session/server_session.rs @@ -6,8 +6,8 @@ use bytes::Bytes; use bytesio::bytes_writer::BytesWriter; use bytesio::bytesio::{AsyncReadWrite, BytesIO}; use bytesio::bytesio_errors::BytesIOError; +use scuffle_utils::prelude::FutureTimeout; use tokio::sync::oneshot; -use utils::prelude::FutureTimeout; use super::define::RtmpCommand; use super::errors::SessionError; diff --git a/video/lib/rtmp/src/tests/rtmp.rs b/video/lib/rtmp/src/tests/rtmp.rs index b20f525a..c9bcf028 100644 --- a/video/lib/rtmp/src/tests/rtmp.rs +++ b/video/lib/rtmp/src/tests/rtmp.rs @@ -1,9 +1,9 @@ use std::path::PathBuf; use std::time::Duration; +use scuffle_utils::prelude::FutureTimeout; use tokio::process::Command; use tokio::sync::mpsc; -use utils::prelude::FutureTimeout; use crate::channels::{ChannelData, UniqueID}; use crate::Session; diff --git a/video/transcoder/Cargo.toml b/video/transcoder/Cargo.toml index bade5f46..2143401d 100644 --- a/video/transcoder/Cargo.toml +++ b/video/transcoder/Cargo.toml @@ -35,13 +35,13 @@ image = "0.25" aac = { workspace = true } mp4 = { workspace = true } -utils = { workspace = true, features = ["all"] } +scuffle-utils = { workspace = true, features = ["all"] } bytesio = { workspace = true, features = ["default"] } config = { workspace = true } pb = { workspace = true } video-common = { workspace = true } binary-helper = { workspace = true } -ffmpeg = { workspace = true, features = ["tokio-channel", "tracing", "task-abort"] } +scuffle-ffmpeg = { workspace = true, features = ["tokio-channel", "tracing", "task-abort"] } [dev-dependencies] dotenvy = "0.15" diff --git a/video/transcoder/src/global.rs b/video/transcoder/src/global.rs index 909ed4cf..5f03c810 100644 --- a/video/transcoder/src/global.rs +++ b/video/transcoder/src/global.rs @@ -1,4 +1,4 @@ -use utils::grpc::TlsSettings; +use scuffle_utilsgrpc::TlsSettings; use crate::config::TranscoderConfig; diff --git a/video/transcoder/src/main.rs b/video/transcoder/src/main.rs index a814f362..43b827c2 100644 --- a/video/transcoder/src/main.rs +++ b/video/transcoder/src/main.rs @@ -5,9 +5,9 @@ use anyhow::Context as _; use async_nats::jetstream::stream::StorageType; use binary_helper::global::{setup_database, setup_nats, GlobalCtx, GlobalDb, GlobalNats}; use binary_helper::{bootstrap, grpc_health, grpc_server, impl_global_traits}; +use scuffle_utils::context::Context; +use scuffle_utilsgrpc::TlsSettings; use tokio::select; -use utils::context::Context; -use utils::grpc::TlsSettings; use video_transcoder::config::TranscoderConfig; #[derive(Debug, Clone, Default, serde::Deserialize, config::Config)] diff --git a/video/transcoder/src/tests/global.rs b/video/transcoder/src/tests/global.rs index 9d910010..26283e1b 100644 --- a/video/transcoder/src/tests/global.rs +++ b/video/transcoder/src/tests/global.rs @@ -1,11 +1,11 @@ use std::sync::Arc; use binary_helper::logging; -use utils::context::{Context, Handler}; -use utils::database::deadpool_postgres::{ManagerConfig, PoolConfig, RecyclingMethod, Runtime}; -use utils::database::tokio_postgres::NoTls; -use utils::database::Pool; -use utils::grpc::TlsSettings; +use scuffle_utils::context::{Context, Handler}; +use scuffle_utils::database::deadpool_postgres::{ManagerConfig, PoolConfig, RecyclingMethod, Runtime}; +use scuffle_utils::database::tokio_postgres::NoTls; +use scuffle_utils::database::Pool; +use scuffle_utilsgrpc::TlsSettings; use crate::config::TranscoderConfig; diff --git a/video/transcoder/src/tests/transcoder/mod.rs b/video/transcoder/src/tests/transcoder/mod.rs index c1aab61d..fc5c9b74 100644 --- a/video/transcoder/src/tests/transcoder/mod.rs +++ b/video/transcoder/src/tests/transcoder/mod.rs @@ -19,6 +19,7 @@ use pb::scuffle::video::internal::{ use pb::scuffle::video::v1::events_fetch_request::Target; use pb::scuffle::video::v1::types::{event, AudioConfig, Event, Rendition, VideoConfig}; use prost::Message; +use scuffle_utils::prelude::FutureTimeout; use tokio::process::Command; use tokio::sync::mpsc; use tokio_stream::wrappers::ReceiverStream; @@ -26,7 +27,6 @@ use tokio_stream::StreamExt; use tonic::Response; use transmuxer::{TransmuxResult, Transmuxer}; use ulid::Ulid; -use utils::prelude::FutureTimeout; use video_common::database::{Room, RoomStatus}; use video_common::ext::AsyncReadExt as _; @@ -114,7 +114,7 @@ async fn test_transcode() { let room_id = Ulid::new(); let connection_id = Ulid::new(); - utils::database::query( + scuffle_utils::database::query( r#" INSERT INTO organizations ( id, @@ -131,7 +131,7 @@ async fn test_transcode() { .await .unwrap(); - utils::database::query( + scuffle_utils::database::query( r#" INSERT INTO rooms ( id, @@ -545,7 +545,7 @@ async fn test_transcode() { assert_eq!(json["streams"][0]["duration_ts"], 48128); assert_eq!(json["streams"][0]["time_base"], "1/48000"); - let room: Room = utils::database::query( + let room: Room = scuffle_utils::database::query( "SELECT * FROM rooms WHERE organization_id = $1 AND id = $2 AND active_ingest_connection_id = $3", ) .bind(org_id) @@ -651,7 +651,7 @@ async fn test_transcode_reconnect() { let room_id = Ulid::new(); let connection_id = Ulid::new(); - utils::database::query( + scuffle_utils::database::query( r#" INSERT INTO organizations ( id, @@ -668,7 +668,7 @@ async fn test_transcode_reconnect() { .await .unwrap(); - utils::database::query( + scuffle_utils::database::query( r#" INSERT INTO rooms ( organization_id, diff --git a/video/transcoder/src/transcoder/job/ffmpeg/audio.rs b/video/transcoder/src/transcoder/job/ffmpeg/audio.rs index ba8755b9..25a4cea8 100644 --- a/video/transcoder/src/transcoder/job/ffmpeg/audio.rs +++ b/video/transcoder/src/transcoder/job/ffmpeg/audio.rs @@ -1,14 +1,14 @@ use anyhow::Context; -use ffmpeg::codec::EncoderCodec; -use ffmpeg::dict::Dictionary; -use ffmpeg::encoder::{AudioEncoderSettings, MuxerEncoder, MuxerSettings}; -use ffmpeg::error::FfmpegError; -use ffmpeg::ffi::{AVCodecID, AVPictureType}; -use ffmpeg::io::channel::ChannelCompatSend; -use ffmpeg::io::OutputOptions; -use ffmpeg::packet::Packet; use mp4::codec::AudioCodec; use pb::scuffle::video::v1::types::AudioConfig; +use scuffle_ffmpeg::codec::EncoderCodec; +use scuffle_ffmpeg::dict::Dictionary; +use scuffle_ffmpeg::encoder::{AudioEncoderSettings, MuxerEncoder, MuxerSettings}; +use scuffle_ffmpeg::error::FfmpegError; +use scuffle_ffmpeg::ffi::{AVCodecID, AVPictureType}; +use scuffle_ffmpeg::io::channel::ChannelCompatSend; +use scuffle_ffmpeg::io::OutputOptions; +use scuffle_ffmpeg::packet::Packet; use tokio::sync::mpsc; use super::{muxer_options, Transcoder}; @@ -16,8 +16,8 @@ use super::{muxer_options, Transcoder}; pub fn codec_options(codec: AudioCodec) -> anyhow::Result<(EncoderCodec, Dictionary)> { Ok(match codec { AudioCodec::Aac { object_type } => { - let codec = ffmpeg::codec::EncoderCodec::by_name("libfdk_aac") - .or_else(|| ffmpeg::codec::EncoderCodec::new(AVCodecID::AV_CODEC_ID_AAC)) + let codec = scuffle_ffmpeg::codec::EncoderCodec::by_name("libfdk_aac") + .or_else(|| scuffle_ffmpeg::codec::EncoderCodec::new(AVCodecID::AV_CODEC_ID_AAC)) .ok_or(FfmpegError::NoEncoder) .context("failed to find aac encoder")?; @@ -38,8 +38,8 @@ pub fn codec_options(codec: AudioCodec) -> anyhow::Result<(EncoderCodec, Diction ) } AudioCodec::Opus => { - let codec = ffmpeg::codec::EncoderCodec::by_name("libopus") - .or_else(|| ffmpeg::codec::EncoderCodec::new(AVCodecID::AV_CODEC_ID_OPUS)) + let codec = scuffle_ffmpeg::codec::EncoderCodec::by_name("libopus") + .or_else(|| scuffle_ffmpeg::codec::EncoderCodec::new(AVCodecID::AV_CODEC_ID_OPUS)) .ok_or(FfmpegError::NoEncoder) .context("failed to find opus encoder")?; @@ -56,7 +56,7 @@ impl Transcoder { encoder_codec: EncoderCodec, encoder_options: Dictionary, ) -> anyhow::Result<()> { - let output = ffmpeg::io::Output::new( + let output = scuffle_ffmpeg::io::Output::new( sender.into_compat(), OutputOptions { format_name: Some("mp4"), diff --git a/video/transcoder/src/transcoder/job/ffmpeg/mod.rs b/video/transcoder/src/transcoder/job/ffmpeg/mod.rs index 13db1584..8207fb5a 100644 --- a/video/transcoder/src/transcoder/job/ffmpeg/mod.rs +++ b/video/transcoder/src/transcoder/job/ffmpeg/mod.rs @@ -4,15 +4,15 @@ use std::time::{Duration, Instant}; use anyhow::Context; use bytes::Bytes; -use ffmpeg::decoder::Decoder; -use ffmpeg::dict::Dictionary; -use ffmpeg::error::FfmpegError; -use ffmpeg::ffi::{AVMediaType, AVPixelFormat}; -use ffmpeg::frame::Frame; -use ffmpeg::io::channel::{ChannelCompatRecv as _, ChannelCompatSend as _}; -use ffmpeg::io::OutputOptions; -use ffmpeg::log::LogLevel; use pb::scuffle::video::v1::types::{AudioConfig, VideoConfig}; +use scuffle_ffmpeg::decoder::Decoder; +use scuffle_ffmpeg::dict::Dictionary; +use scuffle_ffmpeg::error::FfmpegError; +use scuffle_ffmpeg::ffi::{AVMediaType, AVPixelFormat}; +use scuffle_ffmpeg::frame::Frame; +use scuffle_ffmpeg::io::channel::{ChannelCompatRecv as _, ChannelCompatSend as _}; +use scuffle_ffmpeg::io::OutputOptions; +use scuffle_ffmpeg::log::LogLevel; use tokio::sync::mpsc; use video_common::database::Rendition; @@ -23,16 +23,16 @@ mod video; const MP4_FLAGS: &str = "frag_keyframe+frag_every_frame+empty_moov+delay_moov+default_base_moof"; -type ChannelCompatRecv = ffmpeg::io::channel::ChannelCompat>; -type ChannelCompatSend = ffmpeg::io::channel::ChannelCompat>>; +type ChannelCompatRecv = scuffle_ffmpeg::io::channel::ChannelCompat>; +type ChannelCompatSend = scuffle_ffmpeg::io::channel::ChannelCompat>>; -type Input = ffmpeg::io::Input; -type Output = ffmpeg::io::Output; -type VideoDecoder = ffmpeg::decoder::VideoDecoder; -type AudioDecoder = ffmpeg::decoder::AudioDecoder; -type Encoder = ffmpeg::encoder::MuxerEncoder; -type Scalar = ffmpeg::scalar::Scalar; -type Limiter = ffmpeg::limiter::FrameRateLimiter; +type Input = scuffle_ffmpeg::io::Input; +type Output = scuffle_ffmpeg::io::Output; +type VideoDecoder = scuffle_ffmpeg::decoder::VideoDecoder; +type AudioDecoder = scuffle_ffmpeg::decoder::AudioDecoder; +type Encoder = scuffle_ffmpeg::encoder::MuxerEncoder; +type Scalar = scuffle_ffmpeg::scalar::Scalar; +type Limiter = scuffle_ffmpeg::limiter::FrameRateLimiter; static SETUP_LOGGING: std::sync::Once = std::sync::Once::new(); @@ -90,11 +90,11 @@ impl Transcoder { mut audio_outputs: Vec, ) -> anyhow::Result { SETUP_LOGGING.call_once(|| { - ffmpeg::log::set_log_level(LogLevel::Trace); - ffmpeg::log::log_callback_tracing(); + scuffle_ffmpeg::log::set_log_level(LogLevel::Trace); + scuffle_ffmpeg::log::log_callback_tracing(); }); - let input = ffmpeg::io::Input::new(input.into_compat()).context("failed to create input")?; + let input = scuffle_ffmpeg::io::Input::new(input.into_compat()).context("failed to create input")?; let video_stream = input .streams() @@ -108,14 +108,15 @@ impl Transcoder { .ok_or(FfmpegError::NoStream) .context("failed to find video stream")?; - let video_decoder = match ffmpeg::decoder::Decoder::new(&video_stream).context("failed to create h264 decoder")? { - Decoder::Video(decoder) => decoder, - _ => anyhow::bail!("expected video decoder"), - }; + let video_decoder = + match scuffle_ffmpeg::decoder::Decoder::new(&video_stream).context("failed to create h264 decoder")? { + Decoder::Video(decoder) => decoder, + _ => anyhow::bail!("expected video decoder"), + }; let (screenshot_width, screenshot_height) = screenshot_size(video_decoder.width(), video_decoder.height()); - let screenshot_scalar = ffmpeg::scalar::Scalar::new( + let screenshot_scalar = scuffle_ffmpeg::scalar::Scalar::new( video_decoder.width(), video_decoder.height(), video_decoder.pixel_format(), @@ -148,7 +149,7 @@ impl Transcoder { .remove(&Rendition::AudioSource) .ok_or_else(|| anyhow::anyhow!("missing audio source output"))?; - let mut output = ffmpeg::io::Output::new( + let mut output = scuffle_ffmpeg::io::Output::new( sender.into_compat(), OutputOptions { format_name: Some("mp4"), @@ -175,7 +176,7 @@ impl Transcoder { .remove(&Rendition::VideoSource) .ok_or_else(|| anyhow::anyhow!("missing video source output"))?; - let mut output = ffmpeg::io::Output::new( + let mut output = scuffle_ffmpeg::io::Output::new( sender.into_compat(), OutputOptions { format_name: Some("mp4"), @@ -227,7 +228,7 @@ impl Transcoder { .context("failed to find video stream")?; this.audio_decoder = Some( - match ffmpeg::decoder::Decoder::new(&audio_stream).context("failed to create aac decoder")? { + match scuffle_ffmpeg::decoder::Decoder::new(&audio_stream).context("failed to create aac decoder")? { Decoder::Audio(decoder) => decoder, _ => anyhow::bail!("expected audio decoder"), }, diff --git a/video/transcoder/src/transcoder/job/ffmpeg/video.rs b/video/transcoder/src/transcoder/job/ffmpeg/video.rs index ad2610b7..3a04c92a 100644 --- a/video/transcoder/src/transcoder/job/ffmpeg/video.rs +++ b/video/transcoder/src/transcoder/job/ffmpeg/video.rs @@ -1,13 +1,13 @@ use anyhow::Context; -use ffmpeg::codec::EncoderCodec; -use ffmpeg::dict::Dictionary; -use ffmpeg::encoder::{MuxerEncoder, MuxerSettings, VideoEncoderSettings}; -use ffmpeg::error::FfmpegError; -use ffmpeg::ffi::{AVCodecID, AVPictureType, AVRational}; -use ffmpeg::io::channel::ChannelCompatSend; -use ffmpeg::io::OutputOptions; use mp4::codec::VideoCodec; use pb::scuffle::video::v1::types::VideoConfig; +use scuffle_ffmpeg::codec::EncoderCodec; +use scuffle_ffmpeg::dict::Dictionary; +use scuffle_ffmpeg::encoder::{MuxerEncoder, MuxerSettings, VideoEncoderSettings}; +use scuffle_ffmpeg::error::FfmpegError; +use scuffle_ffmpeg::ffi::{AVCodecID, AVPictureType, AVRational}; +use scuffle_ffmpeg::io::channel::ChannelCompatSend; +use scuffle_ffmpeg::io::OutputOptions; use tokio::sync::mpsc; use super::{muxer_options, Limiter, Scalar, Transcoder}; @@ -59,8 +59,8 @@ pub fn codec_options(config: &TranscoderConfig, codec: VideoCodec) -> anyhow::Re config .h264_encoder .as_ref() - .map(|name| ffmpeg::codec::EncoderCodec::by_name(name)) - .unwrap_or_else(|| ffmpeg::codec::EncoderCodec::new(AVCodecID::AV_CODEC_ID_H264)) + .map(|name| scuffle_ffmpeg::codec::EncoderCodec::by_name(name)) + .unwrap_or_else(|| scuffle_ffmpeg::codec::EncoderCodec::new(AVCodecID::AV_CODEC_ID_H264)) .ok_or(FfmpegError::NoEncoder) .context("failed to find h264 encoder")?, options, @@ -83,7 +83,7 @@ impl Transcoder { encoder_codec: EncoderCodec, encoder_options: Dictionary, ) -> anyhow::Result<()> { - let output = ffmpeg::io::Output::new( + let output = scuffle_ffmpeg::io::Output::new( sender.into_compat(), OutputOptions { format_name: Some("mp4"), @@ -144,7 +144,7 @@ impl Transcoder { Ok(()) } - pub fn handle_video_packet(&mut self, mut packet: ffmpeg::packet::Packet) -> anyhow::Result<()> { + pub fn handle_video_packet(&mut self, mut packet: scuffle_ffmpeg::packet::Packet) -> anyhow::Result<()> { packet.set_pos(Some(-1)); for copy in self.video_copies.iter_mut() { copy.write_interleaved_packet(packet.clone()).context("copy")?; diff --git a/video/transcoder/src/transcoder/job/mod.rs b/video/transcoder/src/transcoder/job/mod.rs index f8c18920..6691c26a 100644 --- a/video/transcoder/src/transcoder/job/mod.rs +++ b/video/transcoder/src/transcoder/job/mod.rs @@ -19,12 +19,12 @@ use pb::scuffle::video::internal::{ use pb::scuffle::video::v1::events_fetch_request::Target; use pb::scuffle::video::v1::types::event; use prost::Message as _; +use scuffle_utils::prelude::FutureTimeout; +use scuffle_utils::task::AsyncTask; use tokio::sync::mpsc; use tokio::{select, try_join}; use tokio_util::sync::CancellationToken; use ulid::Ulid; -use utils::prelude::FutureTimeout; -use utils::task::AsyncTask; use video_common::database::Rendition; use self::recording::Recording; @@ -215,7 +215,7 @@ impl Job { let tls = global.ingest_tls(); - let channel = utils::grpc::make_channel(vec![message.grpc_endpoint], Duration::from_secs(30), tls)?; + let channel = scuffle_utilsgrpc::make_channel(vec![message.grpc_endpoint], Duration::from_secs(30), tls)?; let mut client = IngestClient::new(channel); diff --git a/video/transcoder/src/transcoder/job/recording.rs b/video/transcoder/src/transcoder/job/recording.rs index ecf5aa98..c5ad145c 100644 --- a/video/transcoder/src/transcoder/job/recording.rs +++ b/video/transcoder/src/transcoder/job/recording.rs @@ -9,10 +9,10 @@ use pb::ext::UlidExt; use pb::scuffle::video::internal::live_rendition_manifest::recording_data::RecordingThumbnail; use pb::scuffle::video::v1::types::{AudioConfig, RecordingConfig, Rendition as PbRendition, VideoConfig}; use prost::Message; +use scuffle_utils::database::tokio_postgres::Transaction; +use scuffle_utils::task::AsyncTask; use tokio::sync::mpsc; use ulid::Ulid; -use utils::database::tokio_postgres::Transaction; -use utils::task::AsyncTask; use video_common::database::{Rendition, S3Bucket, Visibility}; use super::task::recording::{recording_task, recording_thumbnail_task, RecordingTask, RecordingThumbnailTask}; @@ -68,7 +68,7 @@ impl Recording { let allow_dvr = recording_renditions.len() == video_outputs.len() + audio_outputs.len(); - utils::database::query( + scuffle_utils::database::query( r#" INSERT INTO recordings ( id, @@ -100,17 +100,19 @@ impl Recording { .execute(tx) .await?; - utils::database::query("INSERT INTO recording_renditions (organization_id, recording_id, rendition, config)") - .push_values(recording_renditions.iter(), |mut b, (rendition, config)| { - b.push_bind(organization_id); - b.push_bind(id); - b.push_bind(rendition); - b.push_bind(config); - }) - .push("ON CONFLICT DO NOTHING") - .build() - .execute(tx) - .await?; + scuffle_utils::database::query( + "INSERT INTO recording_renditions (organization_id, recording_id, rendition, config)", + ) + .push_values(recording_renditions.iter(), |mut b, (rendition, config)| { + b.push_bind(organization_id); + b.push_bind(id); + b.push_bind(rendition); + b.push_bind(config); + }) + .push("ON CONFLICT DO NOTHING") + .build() + .execute(tx) + .await?; let mut tasks = Vec::new(); let mut uploaders = HashMap::new(); diff --git a/video/transcoder/src/transcoder/job/screenshot.rs b/video/transcoder/src/transcoder/job/screenshot.rs index 85c33783..26c7bb9c 100644 --- a/video/transcoder/src/transcoder/job/screenshot.rs +++ b/video/transcoder/src/transcoder/job/screenshot.rs @@ -1,13 +1,13 @@ use anyhow::Context; use bytes::Bytes; -use ffmpeg::ffi::AVPixelFormat; -use ffmpeg::frame::Frame; use image::codecs::jpeg::JpegEncoder; +use scuffle_ffmpeg::ffi::AVPixelFormat; +use scuffle_ffmpeg::frame::Frame; use tokio::sync::mpsc; pub fn screenshot_task(mut recv: mpsc::Receiver, send: mpsc::Sender<(Bytes, f64)>) -> anyhow::Result<()> { while let Some(frame) = recv.blocking_recv() { - let _guard = utils::task::AbortGuard::new(); + let _guard = scuffle_utils::task::AbortGuard::new(); let frame = frame.video(); diff --git a/video/transcoder/src/transcoder/job/sql_operations.rs b/video/transcoder/src/transcoder/job/sql_operations.rs index 56cdaeb5..40226b52 100644 --- a/video/transcoder/src/transcoder/job/sql_operations.rs +++ b/video/transcoder/src/transcoder/job/sql_operations.rs @@ -29,7 +29,7 @@ pub async fn perform_sql_operations( ) -> anyhow::Result { let mut client = global.db().get().await.context("failed to get database connection")?; - let room: Option = match utils::database::query( + let room: Option = match scuffle_utils::database::query( r#" SELECT * @@ -69,7 +69,7 @@ pub async fn perform_sql_operations( Some(recording_config) } else if let Some(recording_config_id) = &room.recording_config_id { Some( - match utils::database::query( + match scuffle_utils::database::query( r#" SELECT * @@ -101,7 +101,7 @@ pub async fn perform_sql_operations( Some(( recording_config, - match utils::database::query( + match scuffle_utils::database::query( r#" SELECT * @@ -131,7 +131,7 @@ pub async fn perform_sql_operations( let transcoding_config = if let Some(transcoding_config) = room.active_transcoding_config { transcoding_config } else if let Some(transcoding_config_id) = &room.transcoding_config_id { - match utils::database::query( + match scuffle_utils::database::query( r#" SELECT * @@ -164,7 +164,7 @@ pub async fn perform_sql_operations( let tx = client.transaction().await.context("failed to start transaction")?; - utils::database::query( + scuffle_utils::database::query( r#" UPDATE rooms SET diff --git a/video/transcoder/src/transcoder/job/task/generic.rs b/video/transcoder/src/transcoder/job/task/generic.rs index 42084704..74f4eaa9 100644 --- a/video/transcoder/src/transcoder/job/task/generic.rs +++ b/video/transcoder/src/transcoder/job/task/generic.rs @@ -45,7 +45,7 @@ pub async fn generic_task( .context("upload manifest")?; } GenericTask::RoomReady {} => { - if utils::database::query( + if scuffle_utils::database::query( r#" UPDATE rooms SET diff --git a/video/transcoder/src/transcoder/job/task/recording.rs b/video/transcoder/src/transcoder/job/task/recording.rs index 6f278a3d..f30aed4e 100644 --- a/video/transcoder/src/transcoder/job/task/recording.rs +++ b/video/transcoder/src/transcoder/job/task/recording.rs @@ -73,7 +73,7 @@ pub async fn recording_task( .await .context("upload segment")?; - if utils::database::query( + if scuffle_utils::database::query( r#" INSERT INTO recording_rendition_segments ( organization_id, @@ -168,7 +168,7 @@ pub async fn recording_thumbnail_task( .await .context("upload thumbnail")?; - if utils::database::query( + if scuffle_utils::database::query( r#" INSERT INTO recording_thumbnails ( organization_id, diff --git a/video/transcoder/src/transcoder/mod.rs b/video/transcoder/src/transcoder/mod.rs index 699ce1d5..c1de7e05 100644 --- a/video/transcoder/src/transcoder/mod.rs +++ b/video/transcoder/src/transcoder/mod.rs @@ -6,8 +6,8 @@ use async_nats::jetstream::consumer::pull::Config; use async_nats::jetstream::consumer::DeliverPolicy; use async_nats::jetstream::stream::RetentionPolicy; use futures::StreamExt; +use scuffle_utils::context::ContextExt; use tokio_util::sync::CancellationToken; -use utils::context::ContextExt; use crate::config::TranscoderConfig; use crate::global::TranscoderGlobal; From 0cd00f555b906e12bfc8c960e3449ab7028d53ac Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Sat, 4 May 2024 01:57:21 +0000 Subject: [PATCH 08/21] feat: image processor redesign --- Cargo.lock | 159 ++++++++++------ Cargo.toml | 1 + ffmpeg/Cargo.toml | 2 - ffmpeg/src/decoder.rs | 9 - ffmpeg/src/encoder.rs | 15 -- ffmpeg/src/filter_graph.rs | 18 -- ffmpeg/src/io/internal.rs | 6 - ffmpeg/src/io/output.rs | 21 --- ffmpeg/src/packet.rs | 3 - ffmpeg/src/scalar.rs | 3 - foundations/examples/src/http-server.rs | 20 ++- foundations/src/http/server/mod.rs | 16 +- foundations/src/http/server/stream/mod.rs | 2 +- foundations/src/http/server/stream/quic.rs | 5 +- foundations/src/http/server/stream/tcp.rs | 6 +- foundations/src/http/server/stream/tls.rs | 5 +- foundations/src/telemetry/server.rs | 5 +- image_processor/Cargo.toml | 17 +- image_processor/build.rs | 6 - image_processor/proto/Cargo.toml | 23 +++ image_processor/proto/build.rs | 19 ++ .../scuffle/image_processor/events.proto | 20 +++ .../{src/pb.rs => proto/src/lib.rs} | 0 image_processor/src/config.rs | 170 +++++++++++------- image_processor/src/database.rs | 122 ++++++++++++- image_processor/src/disk/http.rs | 167 +++++++++++++++++ image_processor/src/disk/local.rs | 79 ++++++++ image_processor/src/disk/memory.rs | 151 ++++++++++++++++ image_processor/src/disk/mod.rs | 130 ++++++++++++++ image_processor/src/disk/public_http.rs | 96 ++++++++++ image_processor/src/disk/s3.rs | 134 ++++++++++++++ image_processor/src/event_queue/http.rs | 93 ++++++++++ image_processor/src/event_queue/mod.rs | 65 +++++++ image_processor/src/event_queue/nats.rs | 65 +++++++ image_processor/src/event_queue/redis.rs | 57 ++++++ image_processor/src/global.rs | 99 ++++++++-- image_processor/src/lib.rs | 9 - image_processor/src/main.rs | 81 +++------ video/transcoder/Cargo.toml | 2 +- 39 files changed, 1588 insertions(+), 313 deletions(-) delete mode 100644 image_processor/build.rs create mode 100644 image_processor/proto/Cargo.toml create mode 100644 image_processor/proto/build.rs create mode 100644 image_processor/proto/scuffle/image_processor/events.proto rename image_processor/{src/pb.rs => proto/src/lib.rs} (100%) create mode 100644 image_processor/src/disk/http.rs create mode 100644 image_processor/src/disk/local.rs create mode 100644 image_processor/src/disk/memory.rs create mode 100644 image_processor/src/disk/mod.rs create mode 100644 image_processor/src/disk/public_http.rs create mode 100644 image_processor/src/disk/s3.rs create mode 100644 image_processor/src/event_queue/http.rs create mode 100644 image_processor/src/event_queue/mod.rs create mode 100644 image_processor/src/event_queue/nats.rs create mode 100644 image_processor/src/event_queue/redis.rs delete mode 100644 image_processor/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7b9863a8..a4215e43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -995,7 +995,7 @@ dependencies = [ "aws-smithy-types", "bytes", "deadpool-postgres", - "fred", + "fred 8.0.6", "futures-util", "http-body 1.0.0", "hyper 1.3.1", @@ -1118,6 +1118,7 @@ dependencies = [ "ahash 0.8.11", "base64 0.13.1", "bitvec", + "chrono", "hex", "indexmap 2.2.6", "js-sys", @@ -1380,12 +1381,9 @@ dependencies = [ [[package]] name = "cookie-factory" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" -dependencies = [ - "futures", -] +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" [[package]] name = "core-foundation" @@ -2183,7 +2181,7 @@ dependencies = [ "log", "parking_lot", "rand", - "redis-protocol", + "redis-protocol 4.1.0", "rustls 0.22.4", "rustls-native-certs 0.7.0", "rustls-webpki 0.102.3", @@ -2198,6 +2196,32 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "fred" +version = "9.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915e065b377f6e16d5c01eae96bf31eeaf81e1e300b76f938761b3c21307cad8" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "bytes-utils", + "crossbeam-queue", + "float-cmp", + "futures", + "log", + "parking_lot", + "rand", + "redis-protocol 5.0.1", + "semver 1.0.22", + "socket2 0.5.7", + "tokio", + "tokio-stream", + "tokio-util", + "url", + "urlencoding", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -4217,7 +4241,7 @@ dependencies = [ "bitmask-enum", "bytes", "chrono", - "fred", + "fred 8.0.6", "futures", "futures-util", "hmac", @@ -4256,46 +4280,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "platform-image-processor" -version = "0.0.1" -dependencies = [ - "anyhow", - "async-nats", - "async-trait", - "aws-config", - "aws-sdk-s3", - "byteorder", - "bytes", - "chrono", - "fast_image_resize", - "file-format", - "futures", - "gifski", - "imgref", - "libavif-sys", - "libwebp-sys2", - "mongodb", - "num_cpus", - "png", - "postgres-from-row", - "prost 0.12.4", - "reqwest", - "rgb", - "scopeguard", - "scuffle-ffmpeg", - "scuffle-utils", - "serde", - "serde_json", - "sha2", - "thiserror", - "tokio", - "tonic", - "tonic-build", - "tracing", - "ulid", -] - [[package]] name = "platforms" version = "3.4.0" @@ -4806,6 +4790,20 @@ dependencies = [ "nom", ] +[[package]] +name = "redis-protocol" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65deb7c9501fbb2b6f812a30d59c0253779480853545153a51d8e9e444ddc99f" +dependencies = [ + "bytes", + "bytes-utils", + "cookie-factory", + "crc16", + "log", + "nom", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -5296,7 +5294,6 @@ dependencies = [ "crossbeam-channel", "ffmpeg-sys-next", "libc", - "scuffle-utils", "tokio", "tracing", ] @@ -5367,6 +5364,64 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "scuffle-image-processor" +version = "0.0.1" +dependencies = [ + "anyhow", + "async-nats", + "async-trait", + "aws-config", + "aws-sdk-s3", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bson", + "byteorder", + "bytes", + "chrono", + "fast_image_resize", + "file-format", + "fred 9.0.3", + "futures", + "gifski", + "http 1.1.0", + "imgref", + "libavif-sys", + "libwebp-sys2", + "mongodb", + "num_cpus", + "png", + "postgres-from-row", + "prost 0.12.4", + "reqwest", + "rgb", + "scopeguard", + "scuffle-ffmpeg", + "scuffle-foundations", + "scuffle-image-processor-proto", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tonic", + "tonic-build", + "tracing", + "ulid", + "url", +] + +[[package]] +name = "scuffle-image-processor-proto" +version = "0.0.0" +dependencies = [ + "prost 0.12.4", + "prost-build", + "serde", + "tonic", + "tonic-build", +] + [[package]] name = "scuffle-utils" version = "0.1.0" @@ -5377,7 +5432,7 @@ dependencies = [ "deadpool-postgres", "dotenvy", "fnv", - "fred", + "fred 8.0.6", "futures", "futures-channel", "futures-util", @@ -6861,7 +6916,7 @@ dependencies = [ "bytes", "chrono", "dotenvy", - "fred", + "fred 8.0.6", "futures", "futures-util", "hex", @@ -6902,7 +6957,7 @@ dependencies = [ "binary-helper", "chrono", "clap", - "fred", + "fred 8.0.6", "futures", "futures-util", "pb", diff --git a/Cargo.toml b/Cargo.toml index 0ecc4c35..369a9949 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "platform/api", "image_processor", + "image_processor/proto", "video/edge", "video/ingest", "video/transcoder", diff --git a/ffmpeg/Cargo.toml b/ffmpeg/Cargo.toml index c8b980d7..7fd53411 100644 --- a/ffmpeg/Cargo.toml +++ b/ffmpeg/Cargo.toml @@ -11,11 +11,9 @@ bytes = { optional = true, version = "1" } tokio = { optional = true, version = "1" } crossbeam-channel = { optional = true, version = "0.5" } tracing = { optional = true, version = "0.1" } -scuffle-utils = { path = "../utils", version = "*", optional = true, features = ["task"]} [features] default = [] -task-abort = ["dep:scuffle-utils"] channel = ["dep:bytes"] tokio-channel = ["channel", "dep:tokio"] crossbeam-channel = ["channel", "dep:crossbeam-channel"] diff --git a/ffmpeg/src/decoder.rs b/ffmpeg/src/decoder.rs index 3e58adff..f33e1b7c 100644 --- a/ffmpeg/src/decoder.rs +++ b/ffmpeg/src/decoder.rs @@ -152,9 +152,6 @@ impl GenericDecoder { } pub fn send_packet(&mut self, packet: &Packet) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _guard = scuffle_utils::task::AbortGuard::new(); - // Safety: `packet` is a valid pointer, and `self.decoder` is a valid pointer. let ret = unsafe { avcodec_send_packet(self.decoder.as_mut_ptr(), packet.as_ptr()) }; @@ -165,9 +162,6 @@ impl GenericDecoder { } pub fn send_eof(&mut self) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _guard = scuffle_utils::task::AbortGuard::new(); - // Safety: `self.decoder` is a valid pointer. let ret = unsafe { avcodec_send_packet(self.decoder.as_mut_ptr(), std::ptr::null()) }; @@ -178,9 +172,6 @@ impl GenericDecoder { } pub fn receive_frame(&mut self) -> Result, FfmpegError> { - #[cfg(feature = "task-abort")] - let _guard = scuffle_utils::task::AbortGuard::new(); - let mut frame = Frame::new()?; // Safety: `frame` is a valid pointer, and `self.decoder` is a valid pointer. diff --git a/ffmpeg/src/encoder.rs b/ffmpeg/src/encoder.rs index 238eae47..7b699c01 100644 --- a/ffmpeg/src/encoder.rs +++ b/ffmpeg/src/encoder.rs @@ -426,9 +426,6 @@ impl Encoder { outgoing_time_base: AVRational, settings: impl Into, ) -> Result { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - if codec.as_ptr().is_null() { return Err(FfmpegError::NoEncoder); } @@ -489,9 +486,6 @@ impl Encoder { } pub fn send_eof(&mut self) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - // Safety: `self.encoder` is a valid pointer. let ret = unsafe { avcodec_send_frame(self.encoder.as_mut_ptr(), std::ptr::null()) }; if ret == 0 { @@ -502,9 +496,6 @@ impl Encoder { } pub fn send_frame(&mut self, frame: &Frame) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - // Safety: `self.encoder` and `frame` are valid pointers. let ret = unsafe { avcodec_send_frame(self.encoder.as_mut_ptr(), frame.as_ptr()) }; if ret == 0 { @@ -515,9 +506,6 @@ impl Encoder { } pub fn receive_packet(&mut self) -> Result, FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - let mut packet = Packet::new()?; const AVERROR_EAGAIN: i32 = AVERROR(EAGAIN); @@ -631,9 +619,6 @@ impl MuxerEncoder { } pub fn send_eof(&mut self) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - self.encoder.send_eof()?; self.handle_packets()?; diff --git a/ffmpeg/src/filter_graph.rs b/ffmpeg/src/filter_graph.rs index 65d7f116..0bf180e7 100644 --- a/ffmpeg/src/filter_graph.rs +++ b/ffmpeg/src/filter_graph.rs @@ -14,18 +14,12 @@ unsafe impl Send for FilterGraph {} impl FilterGraph { pub fn new() -> Result { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - // Safety: the pointer returned from avfilter_graph_alloc is valid unsafe { Self::wrap(avfilter_graph_alloc()) } } /// Safety: `ptr` must be a valid pointer to an `AVFilterGraph`. unsafe fn wrap(ptr: *mut AVFilterGraph) -> Result { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - Ok(Self( SmartPtr::wrap_non_null(ptr, |ptr| unsafe { avfilter_graph_free(ptr) }).ok_or(FfmpegError::Alloc)?, )) @@ -40,9 +34,6 @@ impl FilterGraph { } pub fn add(&mut self, filter: Filter, name: &str, args: &str) -> Result, FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - let name = CString::new(name).expect("failed to convert name to CString"); let args = CString::new(args).expect("failed to convert args to CString"); @@ -238,9 +229,6 @@ unsafe impl Send for FilterContextSource<'_> {} impl FilterContextSource<'_> { pub fn send_frame(&mut self, frame: &Frame) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - // Safety: `frame` is a valid pointer, and `self.0` is a valid pointer. unsafe { match av_buffersrc_write_frame(self.0, frame.as_ptr()) { @@ -251,9 +239,6 @@ impl FilterContextSource<'_> { } pub fn send_eof(&mut self, pts: Option) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - // Safety: `self.0` is a valid pointer. unsafe { match if let Some(pts) = pts { @@ -275,9 +260,6 @@ unsafe impl Send for FilterContextSink<'_> {} impl FilterContextSink<'_> { pub fn receive_frame(&mut self) -> Result, FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - let mut frame = Frame::new()?; // Safety: `frame` is a valid pointer, and `self.0` is a valid pointer. diff --git a/ffmpeg/src/io/internal.rs b/ffmpeg/src/io/internal.rs index 33b08ad1..b9cedc2c 100644 --- a/ffmpeg/src/io/internal.rs +++ b/ffmpeg/src/io/internal.rs @@ -112,9 +112,6 @@ impl Default for InnerOptions { impl Inner { pub fn new(data: T, options: InnerOptions) -> Result { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - // Safety: av_malloc is safe to call let buffer = unsafe { SmartPtr::wrap_non_null(av_malloc(options.buffer_size), |ptr| { @@ -227,9 +224,6 @@ impl Inner<()> { } pub fn open_output(path: &str) -> Result { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - let path = std::ffi::CString::new(path).expect("Failed to convert path to CString"); // Safety: avformat_alloc_output_context2 is safe to call diff --git a/ffmpeg/src/io/output.rs b/ffmpeg/src/io/output.rs index c16e06a7..4eaad989 100644 --- a/ffmpeg/src/io/output.rs +++ b/ffmpeg/src/io/output.rs @@ -149,9 +149,6 @@ impl Output { } pub fn add_stream(&mut self, codec: Option<*const AVCodec>) -> Option> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - // Safety: `avformat_new_stream` is safe to call. let stream = unsafe { avformat_new_stream(self.as_mut_ptr(), codec.unwrap_or_else(std::ptr::null)) }; if stream.is_null() { @@ -167,9 +164,6 @@ impl Output { } pub fn copy_stream<'a>(&'a mut self, stream: &Stream<'_>) -> Option> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - let codec_param = stream.codec_parameters()?; // Safety: `avformat_new_stream` is safe to call. @@ -195,9 +189,6 @@ impl Output { } pub fn write_header(&mut self) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - if self.witten_header { return Err(FfmpegError::Arguments("header already written")); } @@ -216,9 +207,6 @@ impl Output { } pub fn write_header_with_options(&mut self, options: &mut Dictionary) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - if self.witten_header { return Err(FfmpegError::Arguments("header already written")); } @@ -237,9 +225,6 @@ impl Output { } pub fn write_trailer(&mut self) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - if !self.witten_header { return Err(FfmpegError::Arguments("header not written")); } @@ -254,9 +239,6 @@ impl Output { } pub fn write_interleaved_packet(&mut self, mut packet: Packet) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - if !self.witten_header { return Err(FfmpegError::Arguments("header not written")); } @@ -272,9 +254,6 @@ impl Output { } pub fn write_packet(&mut self, packet: &Packet) -> Result<(), FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - if !self.witten_header { return Err(FfmpegError::Arguments("header not written")); } diff --git a/ffmpeg/src/packet.rs b/ffmpeg/src/packet.rs index dcc0c6a7..b2ad2137 100644 --- a/ffmpeg/src/packet.rs +++ b/ffmpeg/src/packet.rs @@ -17,9 +17,6 @@ impl<'a> Packets<'a> { } pub fn receive(&mut self) -> Result, FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - let mut packet = Packet::new()?; // Safety: av_read_frame is safe to call, 'packet' is a valid pointer diff --git a/ffmpeg/src/scalar.rs b/ffmpeg/src/scalar.rs index feade65c..34bae5f7 100644 --- a/ffmpeg/src/scalar.rs +++ b/ffmpeg/src/scalar.rs @@ -87,9 +87,6 @@ impl Scalar { } pub fn process<'a>(&'a mut self, frame: &Frame) -> Result<&'a VideoFrame, FfmpegError> { - #[cfg(feature = "task-abort")] - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - // Safety: `frame` is a valid pointer, and `self.ptr` is a valid pointer. let ret = unsafe { sws_scale( diff --git a/foundations/examples/src/http-server.rs b/foundations/examples/src/http-server.rs index ac650465..bf209253 100644 --- a/foundations/examples/src/http-server.rs +++ b/foundations/examples/src/http-server.rs @@ -9,7 +9,7 @@ use scuffle_foundations::bootstrap::{bootstrap, Bootstrap, RuntimeSettings}; use scuffle_foundations::http::server::stream::{IncomingConnection, MakeService, ServiceHandler, SocketKind}; use scuffle_foundations::http::server::Server; use scuffle_foundations::settings::auto_settings; -use scuffle_foundations::settings::cli::{Matches, clap}; +use scuffle_foundations::settings::cli::{clap, Matches}; use scuffle_foundations::telemetry::settings::TelemetrySettings; use tokio::signal::unix::SignalKind; @@ -37,20 +37,22 @@ impl Bootstrap for HttpServerSettings { fn additional_args() -> Vec { vec![ - clap::Arg::new("tls-cert") - .long("tls-cert") - .value_name("FILE"), - clap::Arg::new("tls-key") - .long("tls-key") - .value_name("FILE"), + clap::Arg::new("tls-cert").long("tls-cert").value_name("FILE"), + clap::Arg::new("tls-key").long("tls-key").value_name("FILE"), ] } } #[bootstrap] async fn main(settings: Matches) { - let tls_cert = settings.args.get_one::("tls-cert").or(settings.settings.tls_cert.as_ref()); - let tls_key = settings.args.get_one::("tls-key").or(settings.settings.tls_key.as_ref()); + let tls_cert = settings + .args + .get_one::("tls-cert") + .or(settings.settings.tls_cert.as_ref()); + let tls_key = settings + .args + .get_one::("tls-key") + .or(settings.settings.tls_key.as_ref()); let Some((tls_cert, tls_key)) = tls_cert.zip(tls_key) else { panic!("TLS certificate and key are required"); diff --git a/foundations/src/http/server/mod.rs b/foundations/src/http/server/mod.rs index 6eadd26e..b1c3b028 100644 --- a/foundations/src/http/server/mod.rs +++ b/foundations/src/http/server/mod.rs @@ -5,7 +5,6 @@ mod builder; pub mod stream; pub use axum; - use hyper_util::rt::TokioExecutor; #[cfg(not(feature = "runtime"))] use tokio::spawn; @@ -145,7 +144,8 @@ impl Server { let make_service = self.make_service.clone(); let backend = TlsBackend::new(tcp_listener, acceptor.clone(), self.http1_2.clone(), &ctx); let span = tracing::info_span!("tls", addr = %self.bind, worker = i); - self.backends.push(AbortOnDrop::new(spawn(backend.serve(make_service).instrument(span)))); + self.backends + .push(AbortOnDrop::new(spawn(backend.serve(make_service).instrument(span)))); } } else if self.insecure_bind.is_none() { self.insecure_bind = Some(self.bind); @@ -162,7 +162,8 @@ impl Server { let make_service = self.make_service.clone(); let backend = TcpBackend::new(tcp_listener, self.http1_2.clone(), &ctx); let span = tracing::info_span!("tcp", addr = %addr, worker = i); - self.backends.push(AbortOnDrop::new(spawn(backend.serve(make_service).instrument(span)))); + self.backends + .push(AbortOnDrop::new(spawn(backend.serve(make_service).instrument(span)))); } } @@ -179,7 +180,8 @@ impl Server { let make_service = self.make_service.clone(); let backend = QuicBackend::new(endpoint, quic.h3.clone(), &ctx); let span = tracing::info_span!("quic", addr = %self.bind, worker = i); - self.backends.push(AbortOnDrop::new(spawn(backend.serve(make_service).instrument(span)))); + self.backends + .push(AbortOnDrop::new(spawn(backend.serve(make_service).instrument(span)))); } } @@ -202,7 +204,11 @@ impl Server { binds.push(format!("https+quic://{}", self.bind)); } - tracing::info!(worker_count = self.worker_count, "listening on {binds}", binds = binds.join(", ")); + tracing::info!( + worker_count = self.worker_count, + "listening on {binds}", + binds = binds.join(", ") + ); Ok(()) } diff --git a/foundations/src/http/server/stream/mod.rs b/foundations/src/http/server/stream/mod.rs index c7f24729..4e3aa743 100644 --- a/foundations/src/http/server/stream/mod.rs +++ b/foundations/src/http/server/stream/mod.rs @@ -6,9 +6,9 @@ pub mod tls; use std::convert::Infallible; +pub use axum::body::Body; pub use axum::extract::Request; pub use axum::response::{IntoResponse, Response}; -pub use axum::body::Body; use super::Error; diff --git a/foundations/src/http/server/stream/quic.rs b/foundations/src/http/server/stream/quic.rs index 5c74247e..122f014d 100644 --- a/foundations/src/http/server/stream/quic.rs +++ b/foundations/src/http/server/stream/quic.rs @@ -254,7 +254,6 @@ async fn serve_request(service: &impl ServiceHandler, request: Request, mut stre tracing::trace!(?parts, "sending response"); send.send_response(Response::from_parts(parts, ())).await?; - let mut body = std::pin::pin!(body); tracing::trace!("sending response body"); @@ -274,10 +273,10 @@ async fn serve_request(service: &impl ServiceHandler, request: Request, mut stre } None => { send.finish().await?; - }, + } } } - + tracing::trace!("response body finished"); Ok(()) diff --git a/foundations/src/http/server/stream/tcp.rs b/foundations/src/http/server/stream/tcp.rs index cd2c5c14..37698707 100644 --- a/foundations/src/http/server/stream/tcp.rs +++ b/foundations/src/http/server/stream/tcp.rs @@ -151,7 +151,8 @@ impl Connection { let resp = service.on_request(req.map(Body::new)).await.into_response(); drop(ctx); Ok::<_, Infallible>(resp) - }.instrument(span.clone()) + } + .instrument(span.clone()) }) }; @@ -165,11 +166,10 @@ impl Connection { } }; - if let Err(err) = r { self.service.on_error(err.into()).await; } - + self.service.on_close().await; tracing::trace!("connection closed"); } diff --git a/foundations/src/http/server/stream/tls.rs b/foundations/src/http/server/stream/tls.rs index ad7188df..a300a345 100644 --- a/foundations/src/http/server/stream/tls.rs +++ b/foundations/src/http/server/stream/tls.rs @@ -174,7 +174,8 @@ impl Connection { let resp = service.on_request(req.map(Body::new)).await.into_response(); drop(ctx); Ok::<_, Infallible>(resp) - }.instrument(span.clone()) + } + .instrument(span.clone()) }) }; @@ -191,7 +192,7 @@ impl Connection { if let Err(err) = r { self.service.on_error(err.into()).await; } - + self.service.on_close().await; tracing::trace!("connection closed"); } diff --git a/foundations/src/telemetry/server.rs b/foundations/src/telemetry/server.rs index 03ac023e..567bc212 100644 --- a/foundations/src/telemetry/server.rs +++ b/foundations/src/telemetry/server.rs @@ -302,10 +302,7 @@ pub async fn init(settings: ServerSettings) -> anyhow::Result<()> { router = router.fallback(axum::routing::any(not_found)); - let mut server = settings - .builder - .build(router) - .context("failed to build server")?; + let mut server = settings.builder.build(router).context("failed to build server")?; server.start_and_wait().await.context("failed to start server")?; diff --git a/image_processor/Cargo.toml b/image_processor/Cargo.toml index ff8e4819..ab7e5de4 100644 --- a/image_processor/Cargo.toml +++ b/image_processor/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "platform-image-processor" +name = "scuffle-image-processor" version = "0.0.1" edition = "2021" authors = ["Scuffle "] @@ -37,11 +37,20 @@ bytes = "1.0" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } fast_image_resize = "3.0.4" chrono = "0.4" +url = { version = "2", features = ["serde"] } +http = "1" -scuffle-utils = { version = "*", path = "../utils", features = ["all"] } -scuffle-ffmpeg = { version = "*", path = "../ffmpeg", features = ["task-abort", "tracing"] } +scuffle-foundations = { version = "*", path = "../foundations" } +scuffle-ffmpeg = { version = "*", path = "../ffmpeg", features = ["tracing"] } -mongodb = { version = "2.0", features = ["tokio-runtime"] } +scuffle-image-processor-proto = { version = "*", path = "./proto" } + +mongodb = { version = "2", features = ["tokio-runtime", "bson-chrono-0_4"] } +bson = { version = "2", features = ["chrono-0_4"] } + +aws-smithy-types = "1" +aws-smithy-runtime-api = "1" +fred = "9.0.3" [build-dependencies] tonic-build = "0.11" diff --git a/image_processor/build.rs b/image_processor/build.rs deleted file mode 100644 index c41c6e69..00000000 --- a/image_processor/build.rs +++ /dev/null @@ -1,6 +0,0 @@ -fn main() -> Result<(), Box> { - tonic_build::configure() - .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]") - .compile(&["proto/scuffle/image_processor/service.proto"], &["proto/"])?; - Ok(()) -} diff --git a/image_processor/proto/Cargo.toml b/image_processor/proto/Cargo.toml new file mode 100644 index 00000000..1c923bc3 --- /dev/null +++ b/image_processor/proto/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "scuffle-image-processor-proto" +version = "0.0.0" +edition = "2021" +authors = ["Scuffle "] +description = "Scuffle Image Processor Protocol Buffers" +license = "MIT OR Apache-2.0" + +[dependencies] +prost = "0.12.4" +tonic = "0.11.0" +serde = { version = "1", optional = true, features = ["derive"] } + +[build-dependencies] +prost-build = "0.12.4" +tonic-build = "0.11.0" + +[features] +server = [] +client = [] +serde = [ "dep:serde" ] + +default = ["server", "client", "serde"] diff --git a/image_processor/proto/build.rs b/image_processor/proto/build.rs new file mode 100644 index 00000000..6755bd88 --- /dev/null +++ b/image_processor/proto/build.rs @@ -0,0 +1,19 @@ +fn main() -> Result<(), Box> { + let config = tonic_build::configure() + .build_server(cfg!(feature = "server")) + .build_client(cfg!(feature = "client")); + + #[cfg(feature = "serde")] + let config = config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]"); + + config.compile( + &[ + "scuffle/image_processor/service.proto", + "scuffle/image_processor/types.proto", + "scuffle/image_processor/events.proto", + ], + &["./"], + )?; + + Ok(()) +} diff --git a/image_processor/proto/scuffle/image_processor/events.proto b/image_processor/proto/scuffle/image_processor/events.proto new file mode 100644 index 00000000..eea968e7 --- /dev/null +++ b/image_processor/proto/scuffle/image_processor/events.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package scuffle.image_processor; + +message EventCallback { + message Success {} + + message Fail {} + + message Cancel {} + + message Start {} + + oneof event { + Success success = 1; + Fail fail = 2; + Cancel cancel = 3; + Start start = 4; + } +} diff --git a/image_processor/src/pb.rs b/image_processor/proto/src/lib.rs similarity index 100% rename from image_processor/src/pb.rs rename to image_processor/proto/src/lib.rs diff --git a/image_processor/src/config.rs b/image_processor/src/config.rs index 41e66f86..3f40e656 100644 --- a/image_processor/src/config.rs +++ b/image_processor/src/config.rs @@ -1,6 +1,9 @@ use std::collections::HashMap; -#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +use scuffle_foundations::{bootstrap::RuntimeSettings, settings::auto_settings, telemetry::settings::TelemetrySettings}; +use url::Url; + +#[auto_settings] #[serde(default)] pub struct ImageProcessorConfig { /// MongoDB database configuration @@ -8,37 +11,27 @@ pub struct ImageProcessorConfig { /// The disk configurations for the image processor pub disks: Vec, /// The event queues for the image processor - pub event_queues: Vec, + pub event_queues: Vec, /// Concurrency limit, defaults to number of CPUs + /// 0 means all CPUs + #[settings(default = 0)] pub concurrency: usize, + + /// Telemetry configuration + pub telemetry: TelemetrySettings, + /// Runtime configuration + pub runtime: RuntimeSettings, } -#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +#[auto_settings] +#[serde(default)] pub struct DatabaseConfig { + #[settings(default = "mongodb://localhost:27017".into())] pub uri: String, } -impl Default for DatabaseConfig { - fn default() -> Self { - Self { - uri: "mongodb://localhost:27017".to_string(), - } - } -} - -impl Default for ImageProcessorConfig { - fn default() -> Self { - Self { - database: DatabaseConfig::default(), - disks: vec![], - event_queues: vec![], - concurrency: num_cpus::get(), - } - } -} - -#[derive(Debug, Clone, PartialEq, serde::Deserialize)] -#[serde(tag = "kind")] +#[auto_settings(impl_default = false)] +#[serde(tag = "kind", rename_all = "kebab-case")] pub enum DiskConfig { /// Local disk Local(LocalDiskConfig), @@ -52,138 +45,193 @@ pub enum DiskConfig { PublicHttp(PublicHttpDiskConfig), } -#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] -#[serde(default)] +#[auto_settings] pub struct LocalDiskConfig { /// The name of the disk pub name: String, /// The path to the local disk pub path: std::path::PathBuf, /// The disk mode + #[serde(default)] pub mode: DiskMode, } -#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] -#[serde(default)] +#[auto_settings] pub struct S3DiskConfig { /// The name of the disk pub name: String, /// The S3 bucket name pub bucket: String, - /// The S3 region - pub region: String, /// The S3 access key pub access_key: String, /// The S3 secret key pub secret_key: String, + /// The S3 region + #[serde(default = "default_region")] + pub region: String, /// The S3 endpoint + #[serde(default)] pub endpoint: Option, /// The S3 bucket prefix path + #[serde(default)] pub path: Option, /// Use path style + #[serde(default)] pub path_style: bool, /// The disk mode + #[serde(default)] pub mode: DiskMode, /// The maximum number of concurrent connections + #[serde(default)] pub max_connections: Option, } -#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] -#[serde(default)] +fn default_region() -> String { + "us-east-1".into() +} + +#[auto_settings] pub struct MemoryDiskConfig { /// The name of the disk pub name: String, /// The maximum capacity of the memory disk + #[serde(default)] pub capacity: Option, /// Global, shared memory disk for all tasks otherwise each task gets its /// own memory disk + #[serde(default = "default_true")] pub global: bool, /// The disk mode + #[serde(default)] pub mode: DiskMode, } -#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] -#[serde(default)] +fn default_true() -> bool { + true +} + +#[auto_settings(impl_default = false)] pub struct HttpDiskConfig { /// The name of the disk pub name: String, /// The base URL for the HTTP disk - pub base_url: String, + pub url: Url, /// The timeout for the HTTP disk + #[serde(default = "default_timeout")] pub timeout: Option, /// Allow insecure TLS + #[serde(default)] pub allow_insecure: bool, /// The disk mode + #[serde(default)] pub mode: DiskMode, /// The maximum number of concurrent connections + #[serde(default)] pub max_connections: Option, /// Additional headers for the HTTP disk + #[serde(default)] pub headers: HashMap, - /// Write Method - pub write_method: String, } -#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] +fn default_timeout() -> Option { + Some(std::time::Duration::from_secs(30)) +} + +#[auto_settings] +#[serde(rename_all = "kebab-case")] +#[derive(Copy, PartialEq, Eq, Hash)] pub enum DiskMode { /// Read only Read, - #[default] + #[settings(default)] /// Read and write ReadWrite, /// Write only Write, } - -#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] -#[serde(default)] -/// Public http disks do not have a name because they will be invoked if the input path is a URL -/// that starts with 'http' or 'https'. Public http disks can only be read-only. -/// If you do not have a public http disk, the image processor will not be able to download images using HTTP. +/// Public http disks do not have a name because they will be invoked if the +/// input path is a URL that starts with 'http' or 'https'. Public http disks +/// can only be read-only. If you do not have a public http disk, the image +/// processor will not be able to download images using HTTP. +#[auto_settings] pub struct PublicHttpDiskConfig { /// The timeout for the HTTP disk + #[serde(default = "default_timeout")] pub timeout: Option, /// Allow insecure TLS + #[serde(default)] pub allow_insecure: bool, /// The maximum number of concurrent connections + #[serde(default)] pub max_connections: Option, /// Additional headers for the HTTP disk + #[serde(default)] pub headers: HashMap, /// Whitelist of allowed domains or IPs can be subnets or CIDR ranges /// IPs are compared after resolving the domain name + #[serde(default)] pub whitelist: Vec, /// Blacklist of disallowed domains or IPs can be subnets or CIDR ranges /// IPs are compared after resolving the domain name + #[serde(default)] pub blacklist: Vec, } -#[derive(Debug, Clone, PartialEq, serde::Deserialize)] -pub enum EventQueue { - Nats(NatsEventQueue), - Http(HttpEventQueue), - Redis(RedisEventQueue), +#[auto_settings(impl_default = false)] +#[serde(tag = "kind", rename_all = "kebab-case")] +pub enum EventQueueConfig { + Nats(NatsEventQueueConfig), + Http(HttpEventQueueConfig), + Redis(RedisEventQueueConfig), } -#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] -#[serde(default)] -pub struct NatsEventQueue { +#[auto_settings(impl_default = false)] +pub struct NatsEventQueueConfig { + /// The name of the event queue pub name: String, + /// The Nats URL + /// For example: nats://localhost:4222 + pub url: String, + /// Allow Protobuf messages + #[serde(default)] + pub allow_protobuf: bool, } -#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] -#[serde(default)] -pub struct HttpEventQueue { +#[auto_settings(impl_default = false)] +pub struct HttpEventQueueConfig { + /// The name of the event queue pub name: String, - pub url: String, + /// The base URL for the HTTP event queue + pub url: Url, + /// The timeout for the HTTP event queue + /// Default is 30 seconds + #[serde(default = "default_timeout")] pub timeout: Option, + /// Allow insecure TLS (if the url is https, do not verify the certificate) + #[serde(default)] pub allow_insecure: bool, - pub method: String, + /// Additional headers for the HTTP event queue + /// Can be used to set the authorization header + /// Default is empty + #[serde(default)] pub headers: HashMap, + /// The maximum number of concurrent connections + /// Default is None + #[serde(default)] + pub max_connections: Option, + /// Allow Protobuf messages + #[serde(default)] + pub allow_protobuf: bool, } -#[derive(Debug, Clone, Default, PartialEq, serde::Deserialize)] -#[serde(default)] -pub struct RedisEventQueue { +#[auto_settings(impl_default = false)] +pub struct RedisEventQueueConfig { + /// The name of the event queue pub name: String, + /// The Redis URL, for example: redis://localhost:6379 pub url: String, + /// Allow Protobuf messages + #[serde(default)] + pub allow_protobuf: bool, } diff --git a/image_processor/src/database.rs b/image_processor/src/database.rs index 3b1d3f13..9f1a354b 100644 --- a/image_processor/src/database.rs +++ b/image_processor/src/database.rs @@ -1,7 +1,20 @@ -use mongodb::bson::oid::ObjectId; -use ulid::Ulid; +use std::sync::Arc; -use crate::pb::Task; +use bson::Bson; +use mongodb::{bson::oid::ObjectId, Database, IndexModel}; +use scuffle_image_processor_proto::Task; +use serde::{Deserialize, Serializer}; + +use crate::global::Global; + +fn serialize_protobuf(value: &T, serializer: S) -> Result { + serializer.serialize_bytes(&value.encode_to_vec()) +} + +fn deserialize_protobuf<'de, T: prost::Message + Default, D: serde::Deserializer<'de>>(deserializer: D) -> Result { + let bytes = Vec::::deserialize(deserializer)?; + T::decode(bytes.as_slice()).map_err(serde::de::Error::custom) +} #[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)] pub struct Job { @@ -9,6 +22,109 @@ pub struct Job { pub id: ObjectId, pub priority: i32, pub hold_until: Option>, + #[serde(serialize_with = "serialize_protobuf", deserialize_with = "deserialize_protobuf")] pub task: Task, pub claimed_by_id: Option, } + +impl Job { + fn collection(database: &Database) -> mongodb::Collection { + database.collection("jobs") + } + + pub async fn setup_collection(database: &Database) -> Result<(), mongodb::error::Error> { + let collection = Self::collection(database); + + collection.create_index( + IndexModel::builder() + .keys(bson::doc! { + "hold_until": 1, + "priority": -1, + }) + .build(), + None, + ).await?; + + Ok(()) + } + + pub async fn new(global: &Arc, task: Task, priority: i32) -> Result { + let job = Job { + id: ObjectId::new(), + priority, + hold_until: None, + task, + claimed_by_id: None, + }; + + Self::collection(global.database()).insert_one(&job, None).await?; + + Ok(job) + } + + pub async fn fetch(global: &Arc) -> Result, mongodb::error::Error> { + // Find with the highest priority and no hold_until or hold_until in the past + Self::collection(global.database()) + .find_one_and_update( + bson::doc! { + "$or": [ + bson::doc! { + "hold_until": Bson::Null, + }, + bson::doc! { + "hold_until": { + "$lt": chrono::Utc::now(), + }, + }, + ], + }, + bson::doc! { + "$set": { + "claimed_by_id": global.worker_id(), + "hold_until": chrono::Utc::now() + chrono::Duration::seconds(60), + }, + }, + Some( + mongodb::options::FindOneAndUpdateOptions::builder() + .sort(bson::doc! { + "priority": -1, + }) + .build(), + ), + ) + .await + } + + pub async fn refresh(&self, global: &Arc) -> Result { + let success = Self::collection(global.database()) + .update_one( + bson::doc! { + "_id": self.id.clone(), + "claimed_by_id": global.worker_id(), + }, + bson::doc! { + "$set": { + "hold_until": chrono::Utc::now() + chrono::Duration::seconds(60), + }, + }, + None, + ) + .await?; + + Ok(success.modified_count == 1) + } + + pub async fn complete(&self, global: &Arc) -> Result { + let success = Self::collection(global.database()) + .delete_one( + bson::doc! { + "_id": self.id.clone(), + "claimed_by_id": global.worker_id(), + }, + None, + ) + .await?; + + Ok(success.deleted_count == 1) + } +} diff --git a/image_processor/src/disk/http.rs b/image_processor/src/disk/http.rs new file mode 100644 index 00000000..cd313e5b --- /dev/null +++ b/image_processor/src/disk/http.rs @@ -0,0 +1,167 @@ +use bytes::Bytes; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; +use reqwest::Method; +use url::Url; + +use super::{Disk, DiskError, DiskWriteOptions}; +use crate::config::{DiskMode, HttpDiskConfig}; + +#[derive(Debug)] +pub struct HttpDisk { + name: String, + base_url: Url, + mode: DiskMode, + semaphore: Option, + client: reqwest::Client, +} + +#[derive(Debug, thiserror::Error)] +pub enum HttpDiskError { + #[error("invalid path")] + InvalidPath(#[from] url::ParseError), + #[error("reqwest: {0}")] + Reqwest(#[from] reqwest::Error), + #[error("invalid header name")] + InvalidHeaderName(#[from] reqwest::header::InvalidHeaderName), + #[error("invalid header value")] + InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue), +} + +impl HttpDisk { + #[tracing::instrument(skip(config), name = "HttpDisk::new", fields(name = %config.name), err)] + pub async fn new(config: &HttpDiskConfig) -> Result { + tracing::debug!("setting up http disk"); + Ok(Self { + name: config.name.clone(), + base_url: config.url.clone(), + mode: config.mode, + semaphore: config.max_connections.map(|max| tokio::sync::Semaphore::new(max)), + client: { + let mut builder = reqwest::Client::builder(); + + if let Some(timeout) = config.timeout { + builder = builder.timeout(timeout); + } + + if config.allow_insecure { + builder = builder.danger_accept_invalid_certs(true); + } + + let mut headers = HeaderMap::new(); + + for (key, value) in &config.headers { + headers.insert(key.parse::()?, value.parse::()?); + } + + builder = builder.default_headers(headers); + + builder.build().map_err(HttpDiskError::Reqwest)? + }, + }) + } +} + +impl Disk for HttpDisk { + fn name(&self) -> &str { + &self.name + } + + #[tracing::instrument(skip(self), name = "HttpDisk::read", fields(name = %self.name), err)] + async fn read(&self, path: &str) -> Result { + tracing::debug!("reading file"); + + if self.mode == DiskMode::Write { + return Err(DiskError::ReadOnly); + } + + let _permit = if let Some(semaphore) = &self.semaphore { + Some(semaphore.acquire().await) + } else { + None + }; + + let url = self.base_url.join(path).map_err(HttpDiskError::InvalidPath)?; + + let response = self.client.get(url).send().await.map_err(HttpDiskError::Reqwest)?; + + let response = response.error_for_status().map_err(HttpDiskError::Reqwest)?; + + Ok(response.bytes().await.map_err(HttpDiskError::Reqwest)?) + } + + #[tracing::instrument(skip(self, data), name = "HttpDisk::write", fields(name = %self.name, size = data.len()), err)] + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { + tracing::debug!("writing file"); + + if self.mode == DiskMode::Read { + return Err(DiskError::WriteOnly); + } + + let _permit = if let Some(semaphore) = &self.semaphore { + Some(semaphore.acquire().await) + } else { + None + }; + + let url = self.base_url.join(path).map_err(HttpDiskError::InvalidPath)?; + + let mut request = self + .client + .request(Method::POST, url) + .body(data) + .build() + .map_err(HttpDiskError::Reqwest)?; + + if let Some(options) = options { + if let Some(cache_control) = &options.cache_control { + request.headers_mut().insert( + reqwest::header::CACHE_CONTROL, + cache_control.parse().map_err(HttpDiskError::InvalidHeaderValue)?, + ); + } + + if let Some(content_type) = &options.content_type { + request.headers_mut().insert( + reqwest::header::CONTENT_TYPE, + content_type.parse().map_err(HttpDiskError::InvalidHeaderValue)?, + ); + } + + if let Some(acl) = &options.acl { + request.headers_mut().insert( + reqwest::header::HeaderName::from_static("x-amz-acl"), + acl.parse().map_err(HttpDiskError::InvalidHeaderValue)?, + ); + } + } + + let resp = self.client.execute(request).await.map_err(HttpDiskError::Reqwest)?; + + resp.error_for_status().map_err(HttpDiskError::Reqwest)?; + + Ok(()) + } + + #[tracing::instrument(skip(self), name = "HttpDisk::delete", fields(name = %self.name), err)] + async fn delete(&self, path: &str) -> Result<(), DiskError> { + tracing::debug!("deleting file"); + + if self.mode == DiskMode::Read { + return Err(DiskError::WriteOnly); + } + + let _permit = if let Some(semaphore) = &self.semaphore { + Some(semaphore.acquire().await) + } else { + None + }; + + let url = self.base_url.join(path).map_err(HttpDiskError::InvalidPath)?; + + let response = self.client.delete(url).send().await.map_err(HttpDiskError::Reqwest)?; + + response.error_for_status().map_err(HttpDiskError::Reqwest)?; + + Ok(()) + } +} diff --git a/image_processor/src/disk/local.rs b/image_processor/src/disk/local.rs new file mode 100644 index 00000000..3e9be4fe --- /dev/null +++ b/image_processor/src/disk/local.rs @@ -0,0 +1,79 @@ +use std::path::PathBuf; + +use bytes::Bytes; + +use super::{Disk, DiskError, DiskWriteOptions}; +use crate::config::{DiskMode, LocalDiskConfig}; + +#[derive(Debug)] +pub struct LocalDisk { + name: String, + mode: DiskMode, + path: PathBuf, +} + +#[derive(Debug, thiserror::Error)] +pub enum LocalDiskError { + #[error("io: {0}")] + Io(#[from] std::io::Error), +} + +impl LocalDisk { + #[tracing::instrument(skip(config), name = "LocalDisk::new", fields(name = %config.name), err)] + pub async fn new(config: &LocalDiskConfig) -> Result { + tracing::debug!("setting up local disk"); + + if !config.path.exists() { + tokio::fs::create_dir_all(&config.path).await.map_err(LocalDiskError::Io)?; + } + + Ok(Self { + name: config.name.clone(), + mode: config.mode, + path: config.path.clone(), + }) + } +} + +impl Disk for LocalDisk { + fn name(&self) -> &str { + &self.name + } + + #[tracing::instrument(skip(self), name = "LocalDisk::read", err)] + async fn read(&self, path: &str) -> Result { + tracing::debug!("reading file"); + + if self.mode == DiskMode::Write { + return Err(DiskError::ReadOnly); + } + + let path = self.path.join(path); + Ok(tokio::fs::read(path).await.map_err(LocalDiskError::Io)?.into()) + } + + #[tracing::instrument(skip(self, data), name = "LocalDisk::write", err, fields(size = data.len()))] + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { + tracing::debug!("writing file"); + + if self.mode == DiskMode::Read { + return Err(DiskError::WriteOnly); + } + + let path = self.path.join(path); + Ok(tokio::fs::write(path, data).await.map_err(LocalDiskError::Io)?) + } + + #[tracing::instrument(skip(self), name = "LocalDisk::delete", err)] + async fn delete(&self, path: &str) -> Result<(), DiskError> { + tracing::debug!("deleting file"); + + if self.mode == DiskMode::Read { + return Err(DiskError::WriteOnly); + } + + let path = self.path.join(path); + tokio::fs::remove_file(path).await.map_err(LocalDiskError::Io)?; + Ok(()) + } +} diff --git a/image_processor/src/disk/memory.rs b/image_processor/src/disk/memory.rs new file mode 100644 index 00000000..426278cc --- /dev/null +++ b/image_processor/src/disk/memory.rs @@ -0,0 +1,151 @@ +use std::collections::HashMap; + +use bytes::Bytes; +use tokio::sync::RwLock; + +use super::{Disk, DiskError, DiskWriteOptions}; +use crate::config::{DiskMode, MemoryDiskConfig}; + +#[derive(Debug)] +struct FileHolder { + remaining_capacity: usize, + files: HashMap, +} + +impl FileHolder { + fn get(&self, path: &str) -> Option<&MemoryFile> { + self.files.get(path) + } + + fn insert(&mut self, path: String, file: MemoryFile) -> Result, MemoryDiskError> { + if file.data.len() > self.remaining_capacity { + return Err(MemoryDiskError::NoSpaceLeft); + } + + self.remaining_capacity -= file.data.len(); + self.files + .insert(path, file) + .map(|file| { + self.remaining_capacity += file.data.len(); + Ok(file) + }) + .transpose() + } + + fn remove(&mut self, path: &str) -> Option { + let file = self.files.remove(path)?; + self.remaining_capacity += file.data.len(); + Some(file) + } +} + +#[derive(Debug)] +pub struct MemoryDisk { + name: String, + mode: DiskMode, + files: RwLock, + global: bool, +} + +#[derive(Debug, Clone)] +pub struct MemoryFile { + data: Bytes, + _options: DiskWriteOptions, +} + +#[derive(Debug, Clone, thiserror::Error)] +pub enum MemoryDiskError { + #[error("no space left on disk")] + NoSpaceLeft, +} + +impl MemoryDisk { + #[tracing::instrument(skip(config), name = "MemoryDisk::new", fields(name = %config.name), err)] + pub async fn new(config: &MemoryDiskConfig) -> Result { + tracing::debug!("setting up memory disk"); + Ok(Self { + name: config.name.clone(), + mode: config.mode, + global: config.global, + files: RwLock::new(FileHolder { + remaining_capacity: config.capacity.unwrap_or(usize::MAX), + files: HashMap::new(), + }), + }) + } +} + +impl Disk for MemoryDisk { + fn name(&self) -> &str { + &self.name + } + + #[tracing::instrument(skip(self), name = "MemoryDisk::read", err)] + async fn read(&self, path: &str) -> Result { + tracing::debug!("reading file"); + + if self.mode == DiskMode::Write { + return Err(DiskError::ReadOnly); + } + + Ok(self + .files + .read() + .await + .get(path) + .map(|file| file.data.clone()) + .ok_or(DiskError::NotFound)?) + } + + #[tracing::instrument(skip(self, data), name = "MemoryDisk::write", err, fields(size = data.len()))] + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { + tracing::debug!("writing file"); + + if self.mode == DiskMode::Read { + return Err(DiskError::WriteOnly); + } + + let mut files = self.files.write().await; + + files.insert( + path.to_owned(), + MemoryFile { + data, + _options: options.unwrap_or_default(), + }, + )?; + + Ok(()) + } + + #[tracing::instrument(skip(self), name = "MemoryDisk::delete", err)] + async fn delete(&self, path: &str) -> Result<(), DiskError> { + tracing::debug!("deleting file"); + + if self.mode == DiskMode::Read { + return Err(DiskError::WriteOnly); + } + + self.files.write().await.remove(path).ok_or(DiskError::NotFound)?; + Ok(()) + } + + fn scoped(&self) -> Option + where + Self: Sized, + { + if self.global { + return None; + } + + Some(Self { + name: self.name.clone(), + mode: self.mode, + global: false, + files: RwLock::new(FileHolder { + remaining_capacity: 0, + files: HashMap::new(), + }), + }) + } +} diff --git a/image_processor/src/disk/mod.rs b/image_processor/src/disk/mod.rs new file mode 100644 index 00000000..435bd6c4 --- /dev/null +++ b/image_processor/src/disk/mod.rs @@ -0,0 +1,130 @@ +use bytes::Bytes; + +use self::http::{HttpDisk, HttpDiskError}; +use self::local::{LocalDisk, LocalDiskError}; +use self::memory::{MemoryDisk, MemoryDiskError}; +use self::public_http::{PublicHttpDisk, PublicHttpDiskError}; +use self::s3::{S3Disk, S3DiskError}; +use crate::config::DiskConfig; + +pub mod http; +pub mod local; +pub mod memory; +pub mod public_http; +pub mod s3; + +#[derive(Debug, thiserror::Error)] +pub enum DiskError { + #[error("http: {0}")] + Http(#[from] HttpDiskError), + #[error("local: {0}")] + Local(#[from] LocalDiskError), + #[error("s3: {0}")] + S3(#[from] S3DiskError), + #[error("memory: {0}")] + Memory(#[from] MemoryDiskError), + #[error("public http: {0}")] + PublicHttp(#[from] PublicHttpDiskError), + #[error("not found")] + NotFound, + #[error("read only")] + ReadOnly, + #[error("write only")] + WriteOnly, +} + +#[derive(Debug, Clone, Default)] +pub struct DiskWriteOptions { + pub cache_control: Option, + pub content_type: Option, + pub acl: Option, + pub content_disposition: Option, +} + +pub trait Disk { + /// Get the name of the disk + fn name(&self) -> &str; + + /// Read data from a disk + fn read(&self, path: &str) -> impl std::future::Future> + Send; + + /// Write data to a disk + fn write( + &self, + path: &str, + data: Bytes, + options: Option, + ) -> impl std::future::Future> + Send; + + /// Delete data from a disk + fn delete(&self, path: &str) -> impl std::future::Future> + Send; + + /// Can be scoped to a specific request + fn scoped(&self) -> Option + where + Self: Sized, + { + None + } +} + +#[derive(Debug)] +pub enum AnyDisk { + Local(LocalDisk), + S3(S3Disk), + Memory(MemoryDisk), + Http(HttpDisk), + PublicHttp(PublicHttpDisk), +} + +impl Disk for AnyDisk { + fn name(&self) -> &str { + match self { + AnyDisk::Local(disk) => disk.name(), + AnyDisk::S3(disk) => disk.name(), + AnyDisk::Memory(disk) => disk.name(), + AnyDisk::Http(disk) => disk.name(), + AnyDisk::PublicHttp(disk) => disk.name(), + } + } + + async fn read(&self, path: &str) -> Result { + match self { + AnyDisk::Local(disk) => disk.read(path).await, + AnyDisk::S3(disk) => disk.read(path).await, + AnyDisk::Memory(disk) => disk.read(path).await, + AnyDisk::Http(disk) => disk.read(path).await, + AnyDisk::PublicHttp(disk) => disk.read(path).await, + } + } + + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { + match self { + AnyDisk::Local(disk) => disk.write(path, data, options).await, + AnyDisk::S3(disk) => disk.write(path, data, options).await, + AnyDisk::Memory(disk) => disk.write(path, data, options).await, + AnyDisk::Http(disk) => disk.write(path, data, options).await, + AnyDisk::PublicHttp(disk) => disk.write(path, data, options).await, + } + } + + async fn delete(&self, path: &str) -> Result<(), DiskError> { + match self { + AnyDisk::Local(disk) => disk.delete(path).await, + AnyDisk::S3(disk) => disk.delete(path).await, + AnyDisk::Memory(disk) => disk.delete(path).await, + AnyDisk::Http(disk) => disk.delete(path).await, + AnyDisk::PublicHttp(disk) => disk.delete(path).await, + } + } +} + +pub async fn build_disk(config: &DiskConfig) -> Result { + match config { + DiskConfig::Local(local) => Ok(AnyDisk::Local(LocalDisk::new(local).await?)), + DiskConfig::S3(s3) => Ok(AnyDisk::S3(S3Disk::new(s3).await?)), + DiskConfig::Memory(memory) => Ok(AnyDisk::Memory(MemoryDisk::new(memory).await?)), + DiskConfig::Http(http) => Ok(AnyDisk::Http(HttpDisk::new(http).await?)), + DiskConfig::PublicHttp(public_http) => Ok(AnyDisk::PublicHttp(PublicHttpDisk::new(public_http).await?)), + } +} diff --git a/image_processor/src/disk/public_http.rs b/image_processor/src/disk/public_http.rs new file mode 100644 index 00000000..7f09f2bc --- /dev/null +++ b/image_processor/src/disk/public_http.rs @@ -0,0 +1,96 @@ +use bytes::Bytes; +use http::{HeaderName, HeaderValue}; + +use super::{Disk, DiskError, DiskWriteOptions}; +use crate::config::PublicHttpDiskConfig; + +pub const PUBLIC_HTTP_DISK_NAME: &str = "__public_http"; + +#[derive(Debug)] +pub struct PublicHttpDisk { + client: reqwest::Client, + semaphore: Option, +} + +#[derive(Debug, thiserror::Error)] +pub enum PublicHttpDiskError { + #[error("reqwest: {0}")] + Reqwest(#[from] reqwest::Error), + #[error("invalid header name")] + InvalidHeaderName(#[from] reqwest::header::InvalidHeaderName), + #[error("invalid header value")] + InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue), + #[error("unsupported: {0}")] + Unsupported(&'static str), +} + +impl PublicHttpDisk { + #[tracing::instrument(skip(config), name = "PublicHttpDisk::new", err)] + pub async fn new(config: &PublicHttpDiskConfig) -> Result { + tracing::debug!("setting up public http disk"); + if !config.blacklist.is_empty() || !config.whitelist.is_empty() { + tracing::error!("blacklist and whitelist are not supported for public http disk"); + return Err(PublicHttpDiskError::Unsupported("blacklist and whitelist")); + } + + Ok(Self { + client: { + let mut builder = reqwest::Client::builder(); + + if let Some(timeout) = config.timeout { + builder = builder.timeout(timeout); + } + + if config.allow_insecure { + builder = builder.danger_accept_invalid_certs(true); + } + + let mut headers = reqwest::header::HeaderMap::new(); + + for (key, value) in &config.headers { + headers.insert(key.parse::()?, value.parse::()?); + } + + builder = builder.default_headers(headers); + + builder.build().map_err(|e| PublicHttpDiskError::Reqwest(e))? + }, + semaphore: config.max_connections.map(|max| tokio::sync::Semaphore::new(max)), + }) + } +} + +impl Disk for PublicHttpDisk { + fn name(&self) -> &str { + PUBLIC_HTTP_DISK_NAME + } + + #[tracing::instrument(skip(self), name = "PublicHttpDisk::read", err)] + async fn read(&self, path: &str) -> Result { + tracing::debug!("reading file"); + + let _permit = if let Some(semaphore) = &self.semaphore { + Some(semaphore.acquire().await) + } else { + None + }; + + let response = self.client.get(path).send().await.map_err(PublicHttpDiskError::Reqwest)?; + + let response = response.error_for_status().map_err(PublicHttpDiskError::Reqwest)?; + + Ok(response.bytes().await.map_err(PublicHttpDiskError::Reqwest)?) + } + + #[tracing::instrument(skip(self, data), name = "PublicHttpDisk::write", fields(size = data.len()), err)] + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { + tracing::error!("writing is not supported for public http disk"); + Err(DiskError::ReadOnly) + } + + #[tracing::instrument(skip(self), name = "PublicHttpDisk::delete", err)] + async fn delete(&self, path: &str) -> Result<(), DiskError> { + tracing::error!("deleting is not supported for public http disk"); + Err(DiskError::ReadOnly) + } +} diff --git a/image_processor/src/disk/s3.rs b/image_processor/src/disk/s3.rs new file mode 100644 index 00000000..0d09bbf4 --- /dev/null +++ b/image_processor/src/disk/s3.rs @@ -0,0 +1,134 @@ +use aws_config::{AppName, Region, SdkConfig}; +use aws_sdk_s3::config::{Credentials, SharedCredentialsProvider}; +use aws_sdk_s3::operation::delete_object::DeleteObjectError; +use aws_sdk_s3::operation::get_object::GetObjectError; +use aws_sdk_s3::operation::put_object::PutObjectError; +use aws_smithy_runtime_api::client::orchestrator::HttpResponse; +use aws_smithy_runtime_api::client::result::SdkError; +use bytes::Bytes; +use scuffle_foundations::service_info; + +use super::{Disk, DiskError, DiskWriteOptions}; +use crate::config::{DiskMode, S3DiskConfig}; + +#[derive(Debug)] +pub struct S3Disk { + name: String, + mode: DiskMode, + client: aws_sdk_s3::Client, + bucket: String, +} + +#[derive(Debug, thiserror::Error)] +pub enum S3DiskError { + #[error("s3: {0}")] + S3Error(#[from] aws_sdk_s3::Error), + #[error("byte stream: {0}")] + ByteStreamError(#[from] aws_smithy_types::byte_stream::error::Error), + #[error("read: {0}")] + ReadError(#[from] SdkError), + #[error("write: {0}")] + WriteError(#[from] SdkError), + #[error("delete: {0}")] + DeleteError(#[from] SdkError), +} + +impl S3Disk { + #[tracing::instrument(skip(config), name = "S3Disk::new", fields(name = %config.name), err)] + pub async fn new(config: &S3DiskConfig) -> Result { + tracing::debug!("setting up s3 disk"); + Ok(Self { + name: config.name.clone(), + mode: config.mode, + client: aws_sdk_s3::Client::new(&{ + let mut builder = SdkConfig::builder(); + + builder.set_app_name(Some(AppName::new(service_info!().name).unwrap())); + + builder.set_region(Some(Region::new(config.region.clone()))); + + builder.set_credentials_provider(Some(SharedCredentialsProvider::new(Credentials::new( + config.access_key.clone(), + config.secret_key.clone(), + None, + None, + "ConfiguredCredentialsProvider", + )))); + + builder.build() + }), + bucket: config.bucket.clone(), + }) + } +} + +impl Disk for S3Disk { + fn name(&self) -> &str { + &self.name + } + + #[tracing::instrument(skip(self), name = "S3Disk::read", err)] + async fn read(&self, path: &str) -> Result { + if self.mode == DiskMode::Write { + return Err(DiskError::ReadOnly); + } + + let result = self + .client + .get_object() + .bucket(&self.bucket) + .key(path) + .send() + .await + .map_err(S3DiskError::from)?; + + let bytes = result.body.collect().await.map_err(S3DiskError::from)?; + + Ok(bytes.into_bytes()) + } + + #[tracing::instrument(skip(self, data), name = "S3Disk::write", err, fields(size = data.len()))] + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { + if self.mode == DiskMode::Read { + return Err(DiskError::WriteOnly); + } + + let mut req = self.client.put_object().bucket(&self.bucket).key(path).body(data.into()); + + if let Some(options) = options { + if let Some(cache_control) = &options.cache_control { + req = req.cache_control(cache_control); + } + if let Some(content_type) = &options.content_type { + req = req.content_type(content_type); + } + if let Some(content_disposition) = &options.content_disposition { + req = req.content_disposition(content_disposition); + } + if let Some(acl) = &options.acl { + req = req.acl(acl.as_str().into()); + } + } + + req.send().await.map_err(S3DiskError::from)?; + + Ok(()) + } + + #[tracing::instrument(skip(self), name = "S3Disk::delete", err)] + async fn delete(&self, path: &str) -> Result<(), DiskError> { + if self.mode == DiskMode::Read { + return Err(DiskError::WriteOnly); + } + + self.client + .delete_object() + .bucket(&self.bucket) + .key(path) + .send() + .await + .map_err(S3DiskError::from)?; + + Ok(()) + } +} diff --git a/image_processor/src/event_queue/http.rs b/image_processor/src/event_queue/http.rs new file mode 100644 index 00000000..1a995438 --- /dev/null +++ b/image_processor/src/event_queue/http.rs @@ -0,0 +1,93 @@ +use prost::Message; +use scuffle_image_processor_proto::EventCallback; +use url::Url; + +use super::{EventQueue, EventQueueError, PROTOBUF_CONTENT_TYPE}; +use crate::config::HttpEventQueueConfig; + +#[derive(Debug)] +pub struct HttpEventQueue { + name: String, + url: Url, + client: reqwest::Client, + semaphore: Option, + allow_protobuf: bool, +} + +#[derive(Debug, thiserror::Error)] +pub enum HttpEventQueueError { + #[error("reqwest: {0}")] + Reqwest(#[from] reqwest::Error), + #[error("invalid header name")] + InvalidHeaderName(#[from] reqwest::header::InvalidHeaderName), + #[error("invalid header value")] + InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue), +} + +impl HttpEventQueue { + #[tracing::instrument(skip(config), name = "HttpEventQueue::new", fields(name = %config.name), err)] + pub async fn new(config: &HttpEventQueueConfig) -> Result { + tracing::debug!("setting up http event queue"); + Ok(Self { + name: config.name.clone(), + client: { + let mut builder = reqwest::Client::builder(); + + if let Some(timeout) = config.timeout { + builder = builder.timeout(timeout); + } + + if config.allow_insecure { + builder = builder.danger_accept_invalid_certs(true); + } + + let mut headers = reqwest::header::HeaderMap::new(); + + for (key, value) in &config.headers { + headers.insert( + key.parse::()?, + value.parse::()?, + ); + } + + builder = builder.default_headers(headers); + + builder.build().map_err(|e| HttpEventQueueError::Reqwest(e))? + }, + url: config.url.clone(), + allow_protobuf: config.allow_protobuf, + semaphore: config.max_connections.map(|max| tokio::sync::Semaphore::new(max)), + }) + } +} + +impl EventQueue for HttpEventQueue { + fn name(&self) -> &str { + &self.name + } + + #[tracing::instrument(skip(self), name = "HttpEventQueue::publish", fields(name = %self.name))] + async fn publish(&self, topic: &str, data: EventCallback) -> Result<(), EventQueueError> { + let _permit = if let Some(semaphore) = &self.semaphore { + Some(semaphore.acquire().await) + } else { + None + }; + + let mut req = self.client.post(self.url.clone()).header("X-Topic", topic); + + if self.allow_protobuf { + req = req.header("Content-Type", PROTOBUF_CONTENT_TYPE).body(data.encode_to_vec()); + } else { + req = req.json(&data); + } + + req.send() + .await + .map_err(HttpEventQueueError::Reqwest)? + .error_for_status() + .map_err(HttpEventQueueError::Reqwest)?; + + Ok(()) + } +} diff --git a/image_processor/src/event_queue/mod.rs b/image_processor/src/event_queue/mod.rs new file mode 100644 index 00000000..bf4765e9 --- /dev/null +++ b/image_processor/src/event_queue/mod.rs @@ -0,0 +1,65 @@ +use scuffle_image_processor_proto::EventCallback; + +use self::http::{HttpEventQueue, HttpEventQueueError}; +use self::nats::{NatsEventQueue, NatsEventQueueError}; +use self::redis::{RedisEventQueue, RedisEventQueueError}; +use crate::config::EventQueueConfig; + +pub mod http; +pub mod nats; +pub mod redis; + +#[derive(Debug, thiserror::Error)] +pub enum EventQueueError { + #[error("nats: {0}")] + Nats(#[from] NatsEventQueueError), + #[error("http: {0}")] + Http(#[from] HttpEventQueueError), + #[error("redis: {0}")] + Redis(#[from] RedisEventQueueError), +} + +const PROTOBUF_CONTENT_TYPE: &str = "application/protobuf; proto=scuffle.image_processor.EventCallback"; + +pub trait EventQueue { + fn name(&self) -> &str; + + fn publish( + &self, + topic: &str, + data: EventCallback, + ) -> impl std::future::Future> + Send; +} + +#[derive(Debug)] +pub enum AnyEventQueue { + Nats(NatsEventQueue), + Http(HttpEventQueue), + Redis(RedisEventQueue), +} + +impl EventQueue for AnyEventQueue { + fn name(&self) -> &str { + match self { + AnyEventQueue::Nats(queue) => queue.name(), + AnyEventQueue::Http(queue) => queue.name(), + AnyEventQueue::Redis(queue) => queue.name(), + } + } + + async fn publish(&self, topic: &str, data: EventCallback) -> Result<(), EventQueueError> { + match self { + AnyEventQueue::Nats(queue) => queue.publish(topic, data).await, + AnyEventQueue::Http(queue) => queue.publish(topic, data).await, + AnyEventQueue::Redis(queue) => queue.publish(topic, data).await, + } + } +} + +pub async fn build_event_queue(config: &EventQueueConfig) -> Result { + match config { + EventQueueConfig::Nats(nats) => Ok(AnyEventQueue::Nats(NatsEventQueue::new(nats).await?)), + EventQueueConfig::Redis(redis) => Ok(AnyEventQueue::Redis(RedisEventQueue::new(redis).await?)), + EventQueueConfig::Http(http) => Ok(AnyEventQueue::Http(HttpEventQueue::new(http).await?)), + } +} diff --git a/image_processor/src/event_queue/nats.rs b/image_processor/src/event_queue/nats.rs new file mode 100644 index 00000000..0d1754e7 --- /dev/null +++ b/image_processor/src/event_queue/nats.rs @@ -0,0 +1,65 @@ +use prost::Message; +use scuffle_image_processor_proto::EventCallback; + +use super::{EventQueue, EventQueueError, PROTOBUF_CONTENT_TYPE}; +use crate::config::NatsEventQueueConfig; + +#[derive(Debug)] +pub struct NatsEventQueue { + name: String, + allow_protobuf: bool, + nats: async_nats::Client, +} + +#[derive(Debug, thiserror::Error)] +pub enum NatsEventQueueError { + #[error("connect: {0}")] + Connect(#[from] async_nats::ConnectError), + #[error("encode json: {0}")] + EncodeJson(#[from] serde_json::Error), + #[error("publish: {0}")] + Publish(#[from] async_nats::PublishError), +} + +impl NatsEventQueue { + #[tracing::instrument(skip(config), name = "NatsEventQueue::new", fields(name = %config.name), err)] + pub async fn new(config: &NatsEventQueueConfig) -> Result { + tracing::debug!("setting up nats event queue"); + let nats = async_nats::connect(&config.url).await?; + + Ok(Self { + name: config.name.clone(), + allow_protobuf: config.allow_protobuf, + nats, + }) + } +} + +impl EventQueue for NatsEventQueue { + fn name(&self) -> &str { + &self.name + } + + #[tracing::instrument(skip(self), name = "NatsEventQueue::publish", err)] + async fn publish(&self, topic: &str, data: EventCallback) -> Result<(), EventQueueError> { + let mut header_map = async_nats::HeaderMap::new(); + + let payload = if self.allow_protobuf { + header_map.insert("Content-Type", PROTOBUF_CONTENT_TYPE); + data.encode_to_vec() + } else { + header_map.insert("Content-Type", "application/json"); + serde_json::to_string(&data) + .map_err(NatsEventQueueError::EncodeJson)? + .into_bytes() + } + .into(); + + self.nats + .publish_with_headers(topic.to_owned(), header_map, payload) + .await + .map_err(NatsEventQueueError::Publish)?; + + Ok(()) + } +} diff --git a/image_processor/src/event_queue/redis.rs b/image_processor/src/event_queue/redis.rs new file mode 100644 index 00000000..560ad6e5 --- /dev/null +++ b/image_processor/src/event_queue/redis.rs @@ -0,0 +1,57 @@ +use fred::interfaces::PubsubInterface; +use fred::types::RedisConfig; +use prost::Message; +use scuffle_image_processor_proto::EventCallback; + +use super::{EventQueue, EventQueueError}; +use crate::config::RedisEventQueueConfig; + +#[derive(Debug)] +pub struct RedisEventQueue { + client: fred::clients::RedisClient, + name: String, + allow_protobuf: bool, +} + +#[derive(Debug, thiserror::Error)] +pub enum RedisEventQueueError { + #[error("redis: {0}")] + Redis(#[from] fred::error::RedisError), + #[error("json encode: {0}")] + JsonEncode(#[from] serde_json::Error), +} + +impl RedisEventQueue { + #[tracing::instrument(skip(config), name = "RedisEventQueue::new", fields(name = %config.name), err)] + pub async fn new(config: &RedisEventQueueConfig) -> Result { + Ok(Self { + client: fred::clients::RedisClient::new(RedisConfig::from_url(&config.url)?, None, None, None), + name: config.name.clone(), + allow_protobuf: config.allow_protobuf, + }) + } +} + +impl EventQueue for RedisEventQueue { + fn name(&self) -> &str { + &self.name + } + + #[tracing::instrument(skip(self), name = "RedisEventQueue::publish", err)] + async fn publish(&self, topic: &str, data: EventCallback) -> Result<(), EventQueueError> { + let payload = if self.allow_protobuf { + data.encode_to_vec() + } else { + serde_json::to_string(&data) + .map_err(RedisEventQueueError::JsonEncode)? + .into_bytes() + }; + + self.client + .publish(topic, payload) + .await + .map_err(RedisEventQueueError::Redis)?; + + Ok(()) + } +} diff --git a/image_processor/src/global.rs b/image_processor/src/global.rs index a4e458e6..92f7e451 100644 --- a/image_processor/src/global.rs +++ b/image_processor/src/global.rs @@ -1,29 +1,98 @@ -use scuffle_utils::context::Context; +use std::collections::HashMap; + +use anyhow::Context; +use bson::oid::ObjectId; +use scuffle_foundations::BootstrapResult; use crate::config::ImageProcessorConfig; +use crate::database::Job; +use crate::disk::public_http::PUBLIC_HTTP_DISK_NAME; +use crate::disk::{build_disk, AnyDisk, Disk}; +use crate::event_queue::{build_event_queue, AnyEventQueue, EventQueue}; -pub struct ImageProcessorGlobalImpl { - ctx: Context, +pub struct Global { + worker_id: ObjectId, config: ImageProcessorConfig, - http_client: reqwest::Client, + client: mongodb::Client, + database: mongodb::Database, + disks: HashMap, + event_queues: HashMap, } -pub trait ImageProcessorGlobal: Send + Sync + 'static { - fn ctx(&self) -> &Context; - fn config(&self) -> &ImageProcessorConfig; - fn http_client(&self) -> &reqwest::Client; -} +impl Global { + pub async fn new(config: ImageProcessorConfig) -> BootstrapResult { + tracing::debug!("setting up mongo client"); + + let client = mongodb::Client::with_uri_str(&config.database.uri).await.context("mongodb")?; + let Some(database) = client.default_database() else { + anyhow::bail!("no default database") + }; + + tracing::debug!("setting up job collection"); + + Job::setup_collection(&database).await.context("setup job collection")?; + + tracing::debug!("setting up disks and event queues"); + + let mut disks = HashMap::new(); + + for disk in &config.disks { + let disk = build_disk(disk).await.context("disk")?; + let name = disk.name().to_string(); + if disks.insert(name.clone(), disk).is_some() { + anyhow::bail!("duplicate disk name: {name}"); + } + } + + if config.disks.is_empty() { + tracing::warn!("no disks configured"); + } + + let mut event_queues = HashMap::new(); -impl ImageProcessorGlobal for ImageProcessorGlobalImpl { - fn ctx(&self) -> &Context { - &self.ctx + for event_queue in &config.event_queues { + let event_queue = build_event_queue(event_queue).await.context("event queue")?; + let name = event_queue.name().to_string(); + if event_queues.insert(name.clone(), event_queue).is_some() { + anyhow::bail!("duplicate event queue name: {name}"); + } + } + + if config.event_queues.is_empty() { + tracing::warn!("no event queues configured"); + } + + Ok(Self { + worker_id: ObjectId::new(), + config, + client, + database, + disks, + event_queues, + }) } - fn config(&self) -> &ImageProcessorConfig { + pub fn worker_id(&self) -> ObjectId { + self.worker_id + } + + pub fn config(&self) -> &ImageProcessorConfig { &self.config } - fn http_client(&self) -> &reqwest::Client { - &self.http_client + pub fn disk(&self, name: &str) -> Option<&AnyDisk> { + self.disks.get(name) + } + + pub fn event_queue(&self, name: &str) -> Option<&AnyEventQueue> { + self.event_queues.get(name) + } + + pub fn public_http_disk(&self) -> Option<&AnyDisk> { + self.disk(PUBLIC_HTTP_DISK_NAME) + } + + pub fn database(&self) -> &mongodb::Database { + &self.database } } diff --git a/image_processor/src/lib.rs b/image_processor/src/lib.rs deleted file mode 100644 index c7d8ee31..00000000 --- a/image_processor/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod config; -pub mod database; -pub mod global; -pub mod grpc; -pub mod pb; -pub mod processor; - -#[cfg(test)] -mod tests; diff --git a/image_processor/src/main.rs b/image_processor/src/main.rs index 95c38824..dc43c184 100644 --- a/image_processor/src/main.rs +++ b/image_processor/src/main.rs @@ -1,69 +1,34 @@ -#![allow(dead_code)] - use std::sync::Arc; -use anyhow::Context as _; -use binary_helper::global::{setup_database, setup_nats, GlobalCtx, GlobalDb, GlobalNats}; -use binary_helper::{bootstrap, grpc_health, grpc_server, impl_global_traits}; -use platform_image_processor::config::ImageProcessorConfig; -use scuffle_utils::context::Context; -use tokio::select; +use scuffle_foundations::bootstrap::{bootstrap, Bootstrap}; +use scuffle_foundations::settings::cli::Matches; +use scuffle_foundations::BootstrapResult; -#[derive(Debug, Clone, Default, config::Config, serde::Deserialize)] -#[serde(default)] -struct ExtConfig { - image_processor: ImageProcessorConfig, -} +use self::config::ImageProcessorConfig; -impl binary_helper::config::ConfigExtention for ExtConfig { - const APP_NAME: &'static str = "scuffle-image-processor"; -} +impl Bootstrap for ImageProcessorConfig { + type Settings = Self; -// TODO: We don't need grpc and nats -type AppConfig = binary_helper::config::AppConfig; + fn runtime_mode(&self) -> scuffle_foundations::bootstrap::RuntimeSettings { + self.runtime.clone() + } -pub fn main() { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .max_blocking_threads( - std::env::var("TOKIO_MAX_BLOCKING_THREADS") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(2048), - ) - .build() - .expect("failed to create tokio runtime") - .block_on(async { - if let Err(err) = bootstrap::(|global| async move { - let grpc_future = { - let mut server = grpc_server(&global.config.grpc) - .await - .context("failed to create grpc server")?; - let router = server.add_service(grpc_health::HealthServer::new(&global, |global, _| async move { - !global.db().is_closed() - && global.nats().connection_state() == async_nats::connection::State::Connected - })); - - let router = platform_image_processor::grpc::add_routes(&global, router); + fn telemetry_config(&self) -> Option { + Some(self.telemetry.clone()) + } +} - router.serve_with_shutdown(global.config.grpc.bind_address, async { - global.ctx().done().await; - }) - }; +mod config; +mod database; +mod disk; +mod event_queue; +mod global; - let processor_future = platform_image_processor::processor::run(global.clone()); +#[bootstrap] +async fn main(cfg: Matches) -> BootstrapResult<()> { + tracing::info!("starting image processor"); - select! { - r = grpc_future => r.context("grpc server stopped unexpectedly")?, - r = processor_future => r.context("processor stopped unexpectedly")?, - } + let global = Arc::new(global::Global::new(cfg.settings).await?); - Ok(()) - }) - .await - { - tracing::error!("{:#}", err); - std::process::exit(1); - } - }) + Ok(()) } diff --git a/video/transcoder/Cargo.toml b/video/transcoder/Cargo.toml index 2143401d..b8502c78 100644 --- a/video/transcoder/Cargo.toml +++ b/video/transcoder/Cargo.toml @@ -41,7 +41,7 @@ config = { workspace = true } pb = { workspace = true } video-common = { workspace = true } binary-helper = { workspace = true } -scuffle-ffmpeg = { workspace = true, features = ["tokio-channel", "tracing", "task-abort"] } +scuffle-ffmpeg = { workspace = true, features = ["tokio-channel", "tracing"] } [dev-dependencies] dotenvy = "0.15" From 5b7a20f3b88812b1868693edc5d38b54d45cca97 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Sat, 4 May 2024 13:20:15 +0000 Subject: [PATCH 09/21] chore: rename --- Cargo.toml | 4 ++-- {image_processor => image-processor}/APACHE2_LICENSE | 0 {image_processor => image-processor}/Cargo.toml | 0 {image_processor => image-processor}/MIT_LICENSE | 0 {image_processor => image-processor}/proto/Cargo.toml | 0 {image_processor => image-processor}/proto/build.rs | 0 .../proto/scuffle/image_processor/events.proto | 0 .../proto/scuffle/image_processor/service.proto | 0 .../proto/scuffle/image_processor/types.proto | 0 {image_processor => image-processor}/proto/src/lib.rs | 0 {image_processor => image-processor}/src/config.rs | 0 {image_processor => image-processor}/src/database.rs | 0 {image_processor => image-processor}/src/disk/http.rs | 0 {image_processor => image-processor}/src/disk/local.rs | 0 {image_processor => image-processor}/src/disk/memory.rs | 0 {image_processor => image-processor}/src/disk/mod.rs | 0 {image_processor => image-processor}/src/disk/public_http.rs | 0 {image_processor => image-processor}/src/disk/s3.rs | 0 {image_processor => image-processor}/src/event_queue/http.rs | 0 {image_processor => image-processor}/src/event_queue/mod.rs | 0 {image_processor => image-processor}/src/event_queue/nats.rs | 0 {image_processor => image-processor}/src/event_queue/redis.rs | 0 {image_processor => image-processor}/src/global.rs | 0 {image_processor => image-processor}/src/grpc.rs | 0 {image_processor => image-processor}/src/main.rs | 0 {image_processor => image-processor}/src/processor/error.rs | 0 .../src/processor/job/decoder/ffmpeg.rs | 0 .../src/processor/job/decoder/libavif.rs | 0 .../src/processor/job/decoder/libwebp.rs | 0 .../src/processor/job/decoder/mod.rs | 0 .../src/processor/job/encoder/gifski.rs | 0 .../src/processor/job/encoder/libavif.rs | 0 .../src/processor/job/encoder/libwebp.rs | 0 .../src/processor/job/encoder/mod.rs | 0 .../src/processor/job/encoder/png.rs | 0 .../src/processor/job/frame.rs | 0 .../src/processor/job/libavif.rs | 0 .../src/processor/job/libwebp.rs | 0 {image_processor => image-processor}/src/processor/job/mod.rs | 0 .../src/processor/job/process.rs | 0 .../src/processor/job/resize.rs | 0 .../src/processor/job/scaling.rs | 0 .../src/processor/job/smart_object.rs | 0 {image_processor => image-processor}/src/processor/mod.rs | 0 {image_processor => image-processor}/src/processor/utils.rs | 0 {image_processor => image-processor}/src/tests/global.rs | 0 {image_processor => image-processor}/src/tests/mod.rs | 0 .../src/tests/processor/decoder.rs | 0 .../src/tests/processor/encoder.rs | 0 .../src/tests/processor/mod.rs | 0 .../src/tests/processor/resize.rs | 0 {image_processor => image-processor}/src/tests/utils.rs | 0 52 files changed, 2 insertions(+), 2 deletions(-) rename {image_processor => image-processor}/APACHE2_LICENSE (100%) rename {image_processor => image-processor}/Cargo.toml (100%) rename {image_processor => image-processor}/MIT_LICENSE (100%) rename {image_processor => image-processor}/proto/Cargo.toml (100%) rename {image_processor => image-processor}/proto/build.rs (100%) rename {image_processor => image-processor}/proto/scuffle/image_processor/events.proto (100%) rename {image_processor => image-processor}/proto/scuffle/image_processor/service.proto (100%) rename {image_processor => image-processor}/proto/scuffle/image_processor/types.proto (100%) rename {image_processor => image-processor}/proto/src/lib.rs (100%) rename {image_processor => image-processor}/src/config.rs (100%) rename {image_processor => image-processor}/src/database.rs (100%) rename {image_processor => image-processor}/src/disk/http.rs (100%) rename {image_processor => image-processor}/src/disk/local.rs (100%) rename {image_processor => image-processor}/src/disk/memory.rs (100%) rename {image_processor => image-processor}/src/disk/mod.rs (100%) rename {image_processor => image-processor}/src/disk/public_http.rs (100%) rename {image_processor => image-processor}/src/disk/s3.rs (100%) rename {image_processor => image-processor}/src/event_queue/http.rs (100%) rename {image_processor => image-processor}/src/event_queue/mod.rs (100%) rename {image_processor => image-processor}/src/event_queue/nats.rs (100%) rename {image_processor => image-processor}/src/event_queue/redis.rs (100%) rename {image_processor => image-processor}/src/global.rs (100%) rename {image_processor => image-processor}/src/grpc.rs (100%) rename {image_processor => image-processor}/src/main.rs (100%) rename {image_processor => image-processor}/src/processor/error.rs (100%) rename {image_processor => image-processor}/src/processor/job/decoder/ffmpeg.rs (100%) rename {image_processor => image-processor}/src/processor/job/decoder/libavif.rs (100%) rename {image_processor => image-processor}/src/processor/job/decoder/libwebp.rs (100%) rename {image_processor => image-processor}/src/processor/job/decoder/mod.rs (100%) rename {image_processor => image-processor}/src/processor/job/encoder/gifski.rs (100%) rename {image_processor => image-processor}/src/processor/job/encoder/libavif.rs (100%) rename {image_processor => image-processor}/src/processor/job/encoder/libwebp.rs (100%) rename {image_processor => image-processor}/src/processor/job/encoder/mod.rs (100%) rename {image_processor => image-processor}/src/processor/job/encoder/png.rs (100%) rename {image_processor => image-processor}/src/processor/job/frame.rs (100%) rename {image_processor => image-processor}/src/processor/job/libavif.rs (100%) rename {image_processor => image-processor}/src/processor/job/libwebp.rs (100%) rename {image_processor => image-processor}/src/processor/job/mod.rs (100%) rename {image_processor => image-processor}/src/processor/job/process.rs (100%) rename {image_processor => image-processor}/src/processor/job/resize.rs (100%) rename {image_processor => image-processor}/src/processor/job/scaling.rs (100%) rename {image_processor => image-processor}/src/processor/job/smart_object.rs (100%) rename {image_processor => image-processor}/src/processor/mod.rs (100%) rename {image_processor => image-processor}/src/processor/utils.rs (100%) rename {image_processor => image-processor}/src/tests/global.rs (100%) rename {image_processor => image-processor}/src/tests/mod.rs (100%) rename {image_processor => image-processor}/src/tests/processor/decoder.rs (100%) rename {image_processor => image-processor}/src/tests/processor/encoder.rs (100%) rename {image_processor => image-processor}/src/tests/processor/mod.rs (100%) rename {image_processor => image-processor}/src/tests/processor/resize.rs (100%) rename {image_processor => image-processor}/src/tests/utils.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 369a9949..fb665d9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,8 @@ members = [ "platform/api", - "image_processor", - "image_processor/proto", + "image-processor", + "image-processor/proto", "video/edge", "video/ingest", "video/transcoder", diff --git a/image_processor/APACHE2_LICENSE b/image-processor/APACHE2_LICENSE similarity index 100% rename from image_processor/APACHE2_LICENSE rename to image-processor/APACHE2_LICENSE diff --git a/image_processor/Cargo.toml b/image-processor/Cargo.toml similarity index 100% rename from image_processor/Cargo.toml rename to image-processor/Cargo.toml diff --git a/image_processor/MIT_LICENSE b/image-processor/MIT_LICENSE similarity index 100% rename from image_processor/MIT_LICENSE rename to image-processor/MIT_LICENSE diff --git a/image_processor/proto/Cargo.toml b/image-processor/proto/Cargo.toml similarity index 100% rename from image_processor/proto/Cargo.toml rename to image-processor/proto/Cargo.toml diff --git a/image_processor/proto/build.rs b/image-processor/proto/build.rs similarity index 100% rename from image_processor/proto/build.rs rename to image-processor/proto/build.rs diff --git a/image_processor/proto/scuffle/image_processor/events.proto b/image-processor/proto/scuffle/image_processor/events.proto similarity index 100% rename from image_processor/proto/scuffle/image_processor/events.proto rename to image-processor/proto/scuffle/image_processor/events.proto diff --git a/image_processor/proto/scuffle/image_processor/service.proto b/image-processor/proto/scuffle/image_processor/service.proto similarity index 100% rename from image_processor/proto/scuffle/image_processor/service.proto rename to image-processor/proto/scuffle/image_processor/service.proto diff --git a/image_processor/proto/scuffle/image_processor/types.proto b/image-processor/proto/scuffle/image_processor/types.proto similarity index 100% rename from image_processor/proto/scuffle/image_processor/types.proto rename to image-processor/proto/scuffle/image_processor/types.proto diff --git a/image_processor/proto/src/lib.rs b/image-processor/proto/src/lib.rs similarity index 100% rename from image_processor/proto/src/lib.rs rename to image-processor/proto/src/lib.rs diff --git a/image_processor/src/config.rs b/image-processor/src/config.rs similarity index 100% rename from image_processor/src/config.rs rename to image-processor/src/config.rs diff --git a/image_processor/src/database.rs b/image-processor/src/database.rs similarity index 100% rename from image_processor/src/database.rs rename to image-processor/src/database.rs diff --git a/image_processor/src/disk/http.rs b/image-processor/src/disk/http.rs similarity index 100% rename from image_processor/src/disk/http.rs rename to image-processor/src/disk/http.rs diff --git a/image_processor/src/disk/local.rs b/image-processor/src/disk/local.rs similarity index 100% rename from image_processor/src/disk/local.rs rename to image-processor/src/disk/local.rs diff --git a/image_processor/src/disk/memory.rs b/image-processor/src/disk/memory.rs similarity index 100% rename from image_processor/src/disk/memory.rs rename to image-processor/src/disk/memory.rs diff --git a/image_processor/src/disk/mod.rs b/image-processor/src/disk/mod.rs similarity index 100% rename from image_processor/src/disk/mod.rs rename to image-processor/src/disk/mod.rs diff --git a/image_processor/src/disk/public_http.rs b/image-processor/src/disk/public_http.rs similarity index 100% rename from image_processor/src/disk/public_http.rs rename to image-processor/src/disk/public_http.rs diff --git a/image_processor/src/disk/s3.rs b/image-processor/src/disk/s3.rs similarity index 100% rename from image_processor/src/disk/s3.rs rename to image-processor/src/disk/s3.rs diff --git a/image_processor/src/event_queue/http.rs b/image-processor/src/event_queue/http.rs similarity index 100% rename from image_processor/src/event_queue/http.rs rename to image-processor/src/event_queue/http.rs diff --git a/image_processor/src/event_queue/mod.rs b/image-processor/src/event_queue/mod.rs similarity index 100% rename from image_processor/src/event_queue/mod.rs rename to image-processor/src/event_queue/mod.rs diff --git a/image_processor/src/event_queue/nats.rs b/image-processor/src/event_queue/nats.rs similarity index 100% rename from image_processor/src/event_queue/nats.rs rename to image-processor/src/event_queue/nats.rs diff --git a/image_processor/src/event_queue/redis.rs b/image-processor/src/event_queue/redis.rs similarity index 100% rename from image_processor/src/event_queue/redis.rs rename to image-processor/src/event_queue/redis.rs diff --git a/image_processor/src/global.rs b/image-processor/src/global.rs similarity index 100% rename from image_processor/src/global.rs rename to image-processor/src/global.rs diff --git a/image_processor/src/grpc.rs b/image-processor/src/grpc.rs similarity index 100% rename from image_processor/src/grpc.rs rename to image-processor/src/grpc.rs diff --git a/image_processor/src/main.rs b/image-processor/src/main.rs similarity index 100% rename from image_processor/src/main.rs rename to image-processor/src/main.rs diff --git a/image_processor/src/processor/error.rs b/image-processor/src/processor/error.rs similarity index 100% rename from image_processor/src/processor/error.rs rename to image-processor/src/processor/error.rs diff --git a/image_processor/src/processor/job/decoder/ffmpeg.rs b/image-processor/src/processor/job/decoder/ffmpeg.rs similarity index 100% rename from image_processor/src/processor/job/decoder/ffmpeg.rs rename to image-processor/src/processor/job/decoder/ffmpeg.rs diff --git a/image_processor/src/processor/job/decoder/libavif.rs b/image-processor/src/processor/job/decoder/libavif.rs similarity index 100% rename from image_processor/src/processor/job/decoder/libavif.rs rename to image-processor/src/processor/job/decoder/libavif.rs diff --git a/image_processor/src/processor/job/decoder/libwebp.rs b/image-processor/src/processor/job/decoder/libwebp.rs similarity index 100% rename from image_processor/src/processor/job/decoder/libwebp.rs rename to image-processor/src/processor/job/decoder/libwebp.rs diff --git a/image_processor/src/processor/job/decoder/mod.rs b/image-processor/src/processor/job/decoder/mod.rs similarity index 100% rename from image_processor/src/processor/job/decoder/mod.rs rename to image-processor/src/processor/job/decoder/mod.rs diff --git a/image_processor/src/processor/job/encoder/gifski.rs b/image-processor/src/processor/job/encoder/gifski.rs similarity index 100% rename from image_processor/src/processor/job/encoder/gifski.rs rename to image-processor/src/processor/job/encoder/gifski.rs diff --git a/image_processor/src/processor/job/encoder/libavif.rs b/image-processor/src/processor/job/encoder/libavif.rs similarity index 100% rename from image_processor/src/processor/job/encoder/libavif.rs rename to image-processor/src/processor/job/encoder/libavif.rs diff --git a/image_processor/src/processor/job/encoder/libwebp.rs b/image-processor/src/processor/job/encoder/libwebp.rs similarity index 100% rename from image_processor/src/processor/job/encoder/libwebp.rs rename to image-processor/src/processor/job/encoder/libwebp.rs diff --git a/image_processor/src/processor/job/encoder/mod.rs b/image-processor/src/processor/job/encoder/mod.rs similarity index 100% rename from image_processor/src/processor/job/encoder/mod.rs rename to image-processor/src/processor/job/encoder/mod.rs diff --git a/image_processor/src/processor/job/encoder/png.rs b/image-processor/src/processor/job/encoder/png.rs similarity index 100% rename from image_processor/src/processor/job/encoder/png.rs rename to image-processor/src/processor/job/encoder/png.rs diff --git a/image_processor/src/processor/job/frame.rs b/image-processor/src/processor/job/frame.rs similarity index 100% rename from image_processor/src/processor/job/frame.rs rename to image-processor/src/processor/job/frame.rs diff --git a/image_processor/src/processor/job/libavif.rs b/image-processor/src/processor/job/libavif.rs similarity index 100% rename from image_processor/src/processor/job/libavif.rs rename to image-processor/src/processor/job/libavif.rs diff --git a/image_processor/src/processor/job/libwebp.rs b/image-processor/src/processor/job/libwebp.rs similarity index 100% rename from image_processor/src/processor/job/libwebp.rs rename to image-processor/src/processor/job/libwebp.rs diff --git a/image_processor/src/processor/job/mod.rs b/image-processor/src/processor/job/mod.rs similarity index 100% rename from image_processor/src/processor/job/mod.rs rename to image-processor/src/processor/job/mod.rs diff --git a/image_processor/src/processor/job/process.rs b/image-processor/src/processor/job/process.rs similarity index 100% rename from image_processor/src/processor/job/process.rs rename to image-processor/src/processor/job/process.rs diff --git a/image_processor/src/processor/job/resize.rs b/image-processor/src/processor/job/resize.rs similarity index 100% rename from image_processor/src/processor/job/resize.rs rename to image-processor/src/processor/job/resize.rs diff --git a/image_processor/src/processor/job/scaling.rs b/image-processor/src/processor/job/scaling.rs similarity index 100% rename from image_processor/src/processor/job/scaling.rs rename to image-processor/src/processor/job/scaling.rs diff --git a/image_processor/src/processor/job/smart_object.rs b/image-processor/src/processor/job/smart_object.rs similarity index 100% rename from image_processor/src/processor/job/smart_object.rs rename to image-processor/src/processor/job/smart_object.rs diff --git a/image_processor/src/processor/mod.rs b/image-processor/src/processor/mod.rs similarity index 100% rename from image_processor/src/processor/mod.rs rename to image-processor/src/processor/mod.rs diff --git a/image_processor/src/processor/utils.rs b/image-processor/src/processor/utils.rs similarity index 100% rename from image_processor/src/processor/utils.rs rename to image-processor/src/processor/utils.rs diff --git a/image_processor/src/tests/global.rs b/image-processor/src/tests/global.rs similarity index 100% rename from image_processor/src/tests/global.rs rename to image-processor/src/tests/global.rs diff --git a/image_processor/src/tests/mod.rs b/image-processor/src/tests/mod.rs similarity index 100% rename from image_processor/src/tests/mod.rs rename to image-processor/src/tests/mod.rs diff --git a/image_processor/src/tests/processor/decoder.rs b/image-processor/src/tests/processor/decoder.rs similarity index 100% rename from image_processor/src/tests/processor/decoder.rs rename to image-processor/src/tests/processor/decoder.rs diff --git a/image_processor/src/tests/processor/encoder.rs b/image-processor/src/tests/processor/encoder.rs similarity index 100% rename from image_processor/src/tests/processor/encoder.rs rename to image-processor/src/tests/processor/encoder.rs diff --git a/image_processor/src/tests/processor/mod.rs b/image-processor/src/tests/processor/mod.rs similarity index 100% rename from image_processor/src/tests/processor/mod.rs rename to image-processor/src/tests/processor/mod.rs diff --git a/image_processor/src/tests/processor/resize.rs b/image-processor/src/tests/processor/resize.rs similarity index 100% rename from image_processor/src/tests/processor/resize.rs rename to image-processor/src/tests/processor/resize.rs diff --git a/image_processor/src/tests/utils.rs b/image-processor/src/tests/utils.rs similarity index 100% rename from image_processor/src/tests/utils.rs rename to image-processor/src/tests/utils.rs From 1f7ce8bfee4d4aee92cf941487b62a02a40b4bbc Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Sun, 5 May 2024 17:58:24 +0000 Subject: [PATCH 10/21] wd --- Cargo.lock | 58 ++++ foundations/src/telemetry/server.rs | 21 +- image-processor/Cargo.toml | 9 +- image-processor/proto/Cargo.toml | 15 +- image-processor/proto/build.rs | 21 +- .../scuffle/image_processor/service.proto | 8 +- .../proto/scuffle/image_processor/types.proto | 48 +-- image-processor/proto/src/lib.rs | 3 + image-processor/src/config.rs | 166 ++++++---- image-processor/src/database.rs | 106 +++++- image-processor/src/disk/http.rs | 69 ++-- image-processor/src/disk/local.rs | 42 +-- image-processor/src/disk/memory.rs | 44 +-- image-processor/src/disk/mod.rs | 120 +++---- image-processor/src/disk/public_http.rs | 43 +-- image-processor/src/disk/s3.rs | 42 +-- image-processor/src/event_queue/http.rs | 17 +- image-processor/src/event_queue/mod.rs | 4 + image-processor/src/event_queue/nats.rs | 16 +- image-processor/src/event_queue/redis.rs | 23 +- image-processor/src/global.rs | 75 ++++- image-processor/src/grpc.rs | 9 - image-processor/src/main.rs | 76 ++++- image-processor/src/management/grpc.rs | 48 +++ image-processor/src/management/http.rs | 59 ++++ image-processor/src/management/mod.rs | 108 ++++++ image-processor/src/management/validation.rs | 307 ++++++++++++++++++ image-processor/src/worker.rs | 8 + 28 files changed, 1244 insertions(+), 321 deletions(-) delete mode 100644 image-processor/src/grpc.rs create mode 100644 image-processor/src/management/grpc.rs create mode 100644 image-processor/src/management/http.rs create mode 100644 image-processor/src/management/mod.rs create mode 100644 image-processor/src/management/validation.rs create mode 100644 image-processor/src/worker.rs diff --git a/Cargo.lock b/Cargo.lock index a4215e43..248509ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3086,6 +3086,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -4036,6 +4045,43 @@ dependencies = [ "walkdir", ] +[[package]] +name = "pbjson" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" +dependencies = [ + "heck 0.4.1", + "itertools 0.11.0", + "prost 0.12.4", + "prost-types", +] + +[[package]] +name = "pbjson-types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" +dependencies = [ + "bytes", + "chrono", + "pbjson", + "pbjson-build", + "prost 0.12.4", + "prost-build", + "serde", +] + [[package]] name = "pbkdf2" version = "0.11.0" @@ -5390,6 +5436,7 @@ dependencies = [ "libwebp-sys2", "mongodb", "num_cpus", + "once_cell", "png", "postgres-from-row", "prost 0.12.4", @@ -5402,6 +5449,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "strfmt", "thiserror", "tokio", "tonic", @@ -5409,12 +5457,16 @@ dependencies = [ "tracing", "ulid", "url", + "urlencoding", ] [[package]] name = "scuffle-image-processor-proto" version = "0.0.0" dependencies = [ + "pbjson", + "pbjson-build", + "pbjson-types", "prost 0.12.4", "prost-build", "serde", @@ -5906,6 +5958,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" +[[package]] +name = "strfmt" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8348af2d9fc3258c8733b8d9d8db2e56f54b2363a4b5b81585c7875ed65e65" + [[package]] name = "stringprep" version = "0.1.4" diff --git a/foundations/src/telemetry/server.rs b/foundations/src/telemetry/server.rs index 567bc212..1f857dfd 100644 --- a/foundations/src/telemetry/server.rs +++ b/foundations/src/telemetry/server.rs @@ -165,18 +165,19 @@ async fn metrics( #[cfg(feature = "health-check")] pub use health_check::{ - register as register_health_check, unregister as unregister_health_check, HealthCheck, HealthCheckFn, + register as register_health_check, require as require_health_check, unregister as unregister_health_check, HealthCheck, + HealthCheckFn, }; #[cfg(feature = "health-check")] mod health_check { use std::pin::Pin; - use std::sync::atomic::AtomicUsize; + use std::sync::atomic::{AtomicBool, AtomicUsize}; use futures::Future; use scc::HashMap; - pub struct HealthCheckFn(F); + pub struct HealthCheckFn(pub F); impl HealthCheck for HealthCheckFn where @@ -207,12 +208,14 @@ mod health_check { #[derive(Default)] struct HealthChecker { id: AtomicUsize, + require_check: AtomicBool, health_checks: HashMap>, } static HEALTH_CHECK: once_cell::sync::Lazy = once_cell::sync::Lazy::::new(|| HealthChecker::default()); + /// Register a health check and return an id pub fn register(check: impl HealthCheck) -> usize { let id = HEALTH_CHECK.id.fetch_add(1, std::sync::atomic::Ordering::Relaxed); HEALTH_CHECK @@ -223,11 +226,23 @@ mod health_check { id } + /// Unregister a health check by id pub fn unregister(id: usize) { HEALTH_CHECK.health_checks.remove(&id); } + /// Require a health check to be registered, if no health checks are + /// registered the server will always return 503 Service Unavailable This is + /// useful for ensuring that the server is healthy before accepting traffic + pub fn require() { + HEALTH_CHECK.require_check.store(true, std::sync::atomic::Ordering::Relaxed); + } + pub async fn is_healthy() -> bool { + if HEALTH_CHECK.require_check.load(std::sync::atomic::Ordering::Relaxed) && HEALTH_CHECK.health_checks.is_empty() { + return false; + } + let mut o_entry = HEALTH_CHECK.health_checks.first_entry_async().await; while let Some(entry) = o_entry { diff --git a/image-processor/Cargo.toml b/image-processor/Cargo.toml index ab7e5de4..ceb4aabe 100644 --- a/image-processor/Cargo.toml +++ b/image-processor/Cargo.toml @@ -34,16 +34,17 @@ gifski = "1.13" png = "0.17" num_cpus = "1.16" bytes = "1.0" -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } fast_image_resize = "3.0.4" -chrono = "0.4" +chrono = { version = "0.4", features = ["serde"] } url = { version = "2", features = ["serde"] } http = "1" +urlencoding = "2" scuffle-foundations = { version = "*", path = "../foundations" } scuffle-ffmpeg = { version = "*", path = "../ffmpeg", features = ["tracing"] } -scuffle-image-processor-proto = { version = "*", path = "./proto" } +scuffle-image-processor-proto = { version = "*", path = "./proto", features = ["server", "serde"]} mongodb = { version = "2", features = ["tokio-runtime", "bson-chrono-0_4"] } bson = { version = "2", features = ["chrono-0_4"] } @@ -51,6 +52,8 @@ bson = { version = "2", features = ["chrono-0_4"] } aws-smithy-types = "1" aws-smithy-runtime-api = "1" fred = "9.0.3" +strfmt = "0.2" +once_cell = "1.8" [build-dependencies] tonic-build = "0.11" diff --git a/image-processor/proto/Cargo.toml b/image-processor/proto/Cargo.toml index 1c923bc3..8e2cb445 100644 --- a/image-processor/proto/Cargo.toml +++ b/image-processor/proto/Cargo.toml @@ -7,17 +7,24 @@ description = "Scuffle Image Processor Protocol Buffers" license = "MIT OR Apache-2.0" [dependencies] -prost = "0.12.4" +prost = "0.12" tonic = "0.11.0" -serde = { version = "1", optional = true, features = ["derive"] } +pbjson = { version = "0.6.0", optional = true } +pbjson-types = { version = "0.6.0", optional = true } +serde = { version = "1.0", optional = true } [build-dependencies] prost-build = "0.12.4" tonic-build = "0.11.0" +pbjson-build = { version = "0.6.0", optional = true } [features] server = [] client = [] -serde = [ "dep:serde" ] +serde = [ + "dep:serde", + "pbjson-types", + "pbjson-build", + "pbjson", +] -default = ["server", "client", "serde"] diff --git a/image-processor/proto/build.rs b/image-processor/proto/build.rs index 6755bd88..57f757ad 100644 --- a/image-processor/proto/build.rs +++ b/image-processor/proto/build.rs @@ -1,10 +1,16 @@ +#[cfg(feature = "serde")] +use std::{env, path::PathBuf}; + fn main() -> Result<(), Box> { + #[cfg(feature = "serde")] + let descriptor_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("proto_descriptor.bin"); + let config = tonic_build::configure() .build_server(cfg!(feature = "server")) .build_client(cfg!(feature = "client")); #[cfg(feature = "serde")] - let config = config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]"); + let config = config.file_descriptor_set_path(&descriptor_path); config.compile( &[ @@ -15,5 +21,18 @@ fn main() -> Result<(), Box> { &["./"], )?; + #[cfg(feature = "serde")] + let descriptor_set = std::fs::read(&descriptor_path)?; + + #[cfg(feature = "serde")] + pbjson_build::Builder::new() + .register_descriptors(&descriptor_set)? + .build( + &[ + ".scuffle.image_processor", + ], + )?; + + Ok(()) } diff --git a/image-processor/proto/scuffle/image_processor/service.proto b/image-processor/proto/scuffle/image_processor/service.proto index 47ed2dce..354acd40 100644 --- a/image-processor/proto/scuffle/image_processor/service.proto +++ b/image-processor/proto/scuffle/image_processor/service.proto @@ -34,8 +34,8 @@ message ProcessImageRequest { message ProcessImageResponse { // A unique identifier for the task string id = 1; - // Pre-errors that occurred when creating the task. - repeated Error errors = 2; + // Errors that occurred when creating the task. + optional Error error = 2; } // The Payload for a ImageProcessor.CancelTask request @@ -46,6 +46,6 @@ message CancelTaskRequest { // The Payload for a ImageProcessor.CancelTask response message CancelTaskResponse { - // The status of the task - optional string error = 1; + // The status of the response + optional Error error = 1; } diff --git a/image-processor/proto/scuffle/image_processor/types.proto b/image-processor/proto/scuffle/image_processor/types.proto index eac27b6d..bfa8528d 100644 --- a/image-processor/proto/scuffle/image_processor/types.proto +++ b/image-processor/proto/scuffle/image_processor/types.proto @@ -14,6 +14,20 @@ enum ImageFormat { PNG_STATIC = 5; } +message DrivePath { + string drive = 1; + string path = 2; +} + +message InputPath { + oneof input_path { + // Drive path to the image. + DrivePath drive_path = 1; + // Public URL to the image. + string public_url = 2; + } +} + // The resize method determines how the image processor should resize the image. enum ResizeMethod { // Fit will resize the image to fit within the desired dimensions without changing the aspect ratio. @@ -135,19 +149,13 @@ message Scale { message InputUpload { // The input image as a binary blob. bytes binary = 1; - - // The path to upload the image to. - // Must be in the format :// where drive is a drive defined in the image processor config. - // Allows for template arguments to be passed in. For example if the path is "images/{id}.png" and the id is 123 the path will be "images/123.png". - string path = 2; + // The path to save the image to. + DrivePath path = 2; } message Input { // The path to the input image. - // Must be in the format :// where drive is a drive defined in the image processor config. - // This can be used in combination with the ImageUpload message to upload the image to a specific path. - // Allows for template arguments to be passed in. For example if the path is "images/{id}.png" and the id is 123 the path will be "images/123.png". - string path = 1; + InputPath path = 1; // Extra information about the input image. optional InputMetadata metadata = 2; } @@ -181,11 +189,7 @@ message OutputFormat { } message Output { - // The image processor will save the results to this path. - // Must either be a format '://' where drive is a drive defined in the image processor config. - // Allows for template arguments to be passed in. For example if the path is "images/{id}/{scale}_{format}.{ext}" and the id is 123 the path will be "images/123/100x100_webp_static.webp". - // If multiple outputs resolve to the same path the processor will generate a fatal error. - string path = 1; + DrivePath path = 1; // The desired formats to encode the output image. repeated OutputFormat formats = 2; // The resize method used to resize the image. @@ -210,16 +214,20 @@ message Output { // Events must be in the format // :// where event_queue is a queue defined in the image processor config. // The topic argument is used in the template for the event queue settings defined in the image processor config. -// Setting any of the events to an empty string will disable the event. message Events { // The event to trigger when the task is completed successfully - string on_success = 1; + optional EventQueue on_success = 1; // The event to trigger when the task fails - string on_fail = 2; + optional EventQueue on_fail = 2; // The event to trigger when the task is cancelled - string on_cancel = 3; + optional EventQueue on_cancel = 3; // The event to trigger when the task is started - string on_start = 4; + optional EventQueue on_start = 4; +} + +message EventQueue { + string name = 1; + string topic = 2; } message Task { @@ -242,6 +250,8 @@ message Error { enum ErrorCode { Unknown = 0; + InvalidInput = 1; + InternalError = 2; } message EventPayload { diff --git a/image-processor/proto/src/lib.rs b/image-processor/proto/src/lib.rs index bb3de442..13be0805 100644 --- a/image-processor/proto/src/lib.rs +++ b/image-processor/proto/src/lib.rs @@ -1 +1,4 @@ tonic::include_proto!("scuffle.image_processor"); + +#[cfg(feature = "serde")] +include!(concat!(env!("OUT_DIR"), "/scuffle.image_processor.serde.rs")); diff --git a/image-processor/src/config.rs b/image-processor/src/config.rs index 3f40e656..08bd014d 100644 --- a/image-processor/src/config.rs +++ b/image-processor/src/config.rs @@ -1,6 +1,9 @@ use std::collections::HashMap; +use std::net::SocketAddr; -use scuffle_foundations::{bootstrap::RuntimeSettings, settings::auto_settings, telemetry::settings::TelemetrySettings}; +use scuffle_foundations::bootstrap::RuntimeSettings; +use scuffle_foundations::settings::auto_settings; +use scuffle_foundations::telemetry::settings::TelemetrySettings; use url::Url; #[auto_settings] @@ -8,57 +11,99 @@ use url::Url; pub struct ImageProcessorConfig { /// MongoDB database configuration pub database: DatabaseConfig, - /// The disk configurations for the image processor - pub disks: Vec, + /// The drive configurations for the image processor + pub drives: Vec, /// The event queues for the image processor pub event_queues: Vec, - /// Concurrency limit, defaults to number of CPUs - /// 0 means all CPUs - #[settings(default = 0)] - pub concurrency: usize, - + /// The worker configuration + pub worker: WorkerConfig, + /// The management configuration + pub management: ManagementConfig, /// Telemetry configuration pub telemetry: TelemetrySettings, /// Runtime configuration pub runtime: RuntimeSettings, } +#[auto_settings] +#[serde(default)] +pub struct ManagementConfig { + /// The gRPC configuration + pub grpc: GrpcConfig, + /// The HTTP configuration + pub http: HttpConfig, +} + +#[auto_settings] +#[serde(default)] +pub struct GrpcConfig { + /// Enable the gRPC server + #[settings(default = true)] + pub enabled: bool, + /// The gRPC server address + #[settings(default = SocketAddr::from(([0, 0, 0, 0], 50051)))] + pub bind: SocketAddr, +} + +#[auto_settings] +#[serde(default)] +pub struct HttpConfig { + /// Enable the HTTP server + #[settings(default = true)] + pub enabled: bool, + /// The HTTP server address + #[settings(default = SocketAddr::from(([0, 0, 0, 0], 8080)))] + pub bind: SocketAddr, +} + +#[auto_settings] +#[serde(default)] +pub struct WorkerConfig { + /// Enable the worker server + pub enabled: bool, + /// The number of workers to start + /// Default is 0, which means the number of workers is equal to the number + /// of CPU cores + #[settings(default = 0)] + pub workers: usize, +} + #[auto_settings] #[serde(default)] pub struct DatabaseConfig { - #[settings(default = "mongodb://localhost:27017".into())] + #[settings(default = "mongodb://localhost:27017/scuffle-image-processor".into())] pub uri: String, } #[auto_settings(impl_default = false)] #[serde(tag = "kind", rename_all = "kebab-case")] -pub enum DiskConfig { - /// Local disk - Local(LocalDiskConfig), +pub enum DriveConfig { + /// Local drive + Local(LocalDriveConfig), /// S3 bucket - S3(S3DiskConfig), - /// Memory disk - Memory(MemoryDiskConfig), - /// HTTP disk - Http(HttpDiskConfig), - /// Public web http disk - PublicHttp(PublicHttpDiskConfig), + S3(S3DriveConfig), + /// Memory drive + Memory(MemoryDriveConfig), + /// HTTP drive + Http(HttpDriveConfig), + /// Public web http drive + PublicHttp(PublicHttpDriveConfig), } #[auto_settings] -pub struct LocalDiskConfig { - /// The name of the disk +pub struct LocalDriveConfig { + /// The name of the drive pub name: String, - /// The path to the local disk + /// The path to the local drive pub path: std::path::PathBuf, - /// The disk mode + /// The drive mode #[serde(default)] - pub mode: DiskMode, + pub mode: DriveMode, } #[auto_settings] -pub struct S3DiskConfig { - /// The name of the disk +pub struct S3DriveConfig { + /// The name of the drive pub name: String, /// The S3 bucket name pub bucket: String, @@ -78,9 +123,9 @@ pub struct S3DiskConfig { /// Use path style #[serde(default)] pub path_style: bool, - /// The disk mode + /// The drive mode #[serde(default)] - pub mode: DiskMode, + pub mode: DriveMode, /// The maximum number of concurrent connections #[serde(default)] pub max_connections: Option, @@ -91,19 +136,19 @@ fn default_region() -> String { } #[auto_settings] -pub struct MemoryDiskConfig { - /// The name of the disk +pub struct MemoryDriveConfig { + /// The name of the drive pub name: String, - /// The maximum capacity of the memory disk + /// The maximum capacity of the memory drive #[serde(default)] pub capacity: Option, - /// Global, shared memory disk for all tasks otherwise each task gets its - /// own memory disk + /// Global, shared memory drive for all tasks otherwise each task gets its + /// own memory drive #[serde(default = "default_true")] pub global: bool, - /// The disk mode + /// The drive mode #[serde(default)] - pub mode: DiskMode, + pub mode: DriveMode, } fn default_true() -> bool { @@ -111,24 +156,24 @@ fn default_true() -> bool { } #[auto_settings(impl_default = false)] -pub struct HttpDiskConfig { - /// The name of the disk +pub struct HttpDriveConfig { + /// The name of the drive pub name: String, - /// The base URL for the HTTP disk + /// The base URL for the HTTP drive pub url: Url, - /// The timeout for the HTTP disk + /// The timeout for the HTTP drive #[serde(default = "default_timeout")] pub timeout: Option, /// Allow insecure TLS #[serde(default)] pub allow_insecure: bool, - /// The disk mode + /// The drive mode #[serde(default)] - pub mode: DiskMode, + pub mode: DriveMode, /// The maximum number of concurrent connections #[serde(default)] pub max_connections: Option, - /// Additional headers for the HTTP disk + /// Additional headers for the HTTP drive #[serde(default)] pub headers: HashMap, } @@ -140,7 +185,7 @@ fn default_timeout() -> Option { #[auto_settings] #[serde(rename_all = "kebab-case")] #[derive(Copy, PartialEq, Eq, Hash)] -pub enum DiskMode { +pub enum DriveMode { /// Read only Read, #[settings(default)] @@ -150,13 +195,13 @@ pub enum DiskMode { Write, } -/// Public http disks do not have a name because they will be invoked if the -/// input path is a URL that starts with 'http' or 'https'. Public http disks -/// can only be read-only. If you do not have a public http disk, the image +/// Public http drives do not have a name because they will be invoked if the +/// input path is a URL that starts with 'http' or 'https'. Public http drives +/// can only be read-only. If you do not have a public http drive, the image /// processor will not be able to download images using HTTP. #[auto_settings] -pub struct PublicHttpDiskConfig { - /// The timeout for the HTTP disk +pub struct PublicHttpDriveConfig { + /// The timeout for the HTTP drive #[serde(default = "default_timeout")] pub timeout: Option, /// Allow insecure TLS @@ -165,7 +210,7 @@ pub struct PublicHttpDiskConfig { /// The maximum number of concurrent connections #[serde(default)] pub max_connections: Option, - /// Additional headers for the HTTP disk + /// Additional headers for the HTTP drive #[serde(default)] pub headers: HashMap, /// Whitelist of allowed domains or IPs can be subnets or CIDR ranges @@ -193,9 +238,9 @@ pub struct NatsEventQueueConfig { /// The Nats URL /// For example: nats://localhost:4222 pub url: String, - /// Allow Protobuf messages + /// The message encoding for the event queue #[serde(default)] - pub allow_protobuf: bool, + pub message_encoding: MessageEncoding, } #[auto_settings(impl_default = false)] @@ -220,9 +265,9 @@ pub struct HttpEventQueueConfig { /// Default is None #[serde(default)] pub max_connections: Option, - /// Allow Protobuf messages + /// The message encoding for the event queue #[serde(default)] - pub allow_protobuf: bool, + pub message_encoding: MessageEncoding, } #[auto_settings(impl_default = false)] @@ -231,7 +276,18 @@ pub struct RedisEventQueueConfig { pub name: String, /// The Redis URL, for example: redis://localhost:6379 pub url: String, - /// Allow Protobuf messages + /// The message encoding for the event queue #[serde(default)] - pub allow_protobuf: bool, + pub message_encoding: MessageEncoding, +} + +#[auto_settings] +#[derive(Copy, PartialEq, Eq, Hash)] +#[serde(rename_all = "lowercase")] +pub enum MessageEncoding { + /// JSON encoding + #[settings(default)] + Json, + /// Protobuf encoding + Protobuf, } diff --git a/image-processor/src/database.rs b/image-processor/src/database.rs index 9f1a354b..e2d96990 100644 --- a/image-processor/src/database.rs +++ b/image-processor/src/database.rs @@ -1,7 +1,10 @@ use std::sync::Arc; +use std::time::Duration; use bson::Bson; -use mongodb::{bson::oid::ObjectId, Database, IndexModel}; +use mongodb::bson::oid::ObjectId; +use mongodb::options::IndexOptions; +use mongodb::{Database, IndexModel}; use scuffle_image_processor_proto::Task; use serde::{Deserialize, Serializer}; @@ -11,7 +14,9 @@ fn serialize_protobuf(value: &T, serializer: S serializer.serialize_bytes(&value.encode_to_vec()) } -fn deserialize_protobuf<'de, T: prost::Message + Default, D: serde::Deserializer<'de>>(deserializer: D) -> Result { +fn deserialize_protobuf<'de, T: prost::Message + Default, D: serde::Deserializer<'de>>( + deserializer: D, +) -> Result { let bytes = Vec::::deserialize(deserializer)?; T::decode(bytes.as_slice()).map_err(serde::de::Error::custom) } @@ -19,11 +24,18 @@ fn deserialize_protobuf<'de, T: prost::Message + Default, D: serde::Deserializer #[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)] pub struct Job { #[serde(rename = "_id")] + /// The id of the job pub id: ObjectId, + /// The priority of the job, higher priority jobs are fetched first pub priority: i32, + /// The lease time of the job on a worker. pub hold_until: Option>, #[serde(serialize_with = "serialize_protobuf", deserialize_with = "deserialize_protobuf")] + /// The task to be performed pub task: Task, + /// The ttl of the job, after which it will be deleted + pub expires_at: Option>, + /// The id of the worker that claimed the job pub claimed_by_id: Option, } @@ -35,26 +47,56 @@ impl Job { pub async fn setup_collection(database: &Database) -> Result<(), mongodb::error::Error> { let collection = Self::collection(database); - collection.create_index( - IndexModel::builder() - .keys(bson::doc! { - "hold_until": 1, - "priority": -1, - }) - .build(), - None, - ).await?; + collection + .create_index( + IndexModel::builder() + .keys(bson::doc! { + "hold_until": 1, + "priority": -1, + }) + .build(), + None, + ) + .await?; + + collection + .create_index( + IndexModel::builder() + .keys(bson::doc! { + "expires_at": 1, + }) + .options(Some( + IndexOptions::builder().expire_after(Some(Duration::from_secs(0))).build(), + )) + .build(), + None, + ) + .await?; Ok(()) } - pub async fn new(global: &Arc, task: Task, priority: i32) -> Result { + /// Creates a new job in the database + /// # Arguments + /// * `global` - The global state + /// * `task` - The task to be performed + /// * `priority` - The priority of the job + /// * `ttl` - The time-to-live of the job in seconds + /// # Returns + /// The job that was created + pub async fn new( + global: &Arc, + task: Task, + priority: i32, + ttl: Option, + ) -> Result { let job = Job { id: ObjectId::new(), priority, hold_until: None, task, claimed_by_id: None, + expires_at: ttl.map(|ttl| chrono::Utc::now() + chrono::Duration::seconds(ttl as i64)), }; Self::collection(global.database()).insert_one(&job, None).await?; @@ -62,6 +104,14 @@ impl Job { Ok(job) } + /// Fetches a job from the database + /// The job is claimed by the worker and will be held for 60 seconds, after + /// which it will be released to refresh the hold time, call `refresh`. The + /// job returned is the one with the highest priority and no hold_until or + /// hold_until in the past # Arguments + /// * `global` - The global state + /// # Returns + /// The job that was fetched or None if no job was found pub async fn fetch(global: &Arc) -> Result, mongodb::error::Error> { // Find with the highest priority and no hold_until or hold_until in the past Self::collection(global.database()) @@ -95,6 +145,12 @@ impl Job { .await } + /// Refreshes the hold time of the job + /// # Arguments + /// * `global` - The global state + /// # Returns + /// Whether the job was successfully refreshed, if the job was reclaimed by + /// a different worker, it will not be refreshed and this will return false pub async fn refresh(&self, global: &Arc) -> Result { let success = Self::collection(global.database()) .update_one( @@ -114,6 +170,13 @@ impl Job { Ok(success.modified_count == 1) } + /// Completes the job + /// # Arguments + /// * `global` - The global state + /// # Returns + /// Whether the job was successfully completed or not, if the job was + /// reclaimed by a different worker, it will not be completed and this will + /// return false pub async fn complete(&self, global: &Arc) -> Result { let success = Self::collection(global.database()) .delete_one( @@ -127,4 +190,23 @@ impl Job { Ok(success.deleted_count == 1) } + + /// Cancels a job + /// # Arguments + /// * `global` - The global state + /// * `id` - The id of the job to cancel + /// # Returns + /// The job that was cancelled or None if no job was found + pub async fn cancel(global: &Arc, id: ObjectId) -> Result, mongodb::error::Error> { + let job = Self::collection(global.database()) + .find_one_and_delete( + bson::doc! { + "_id": id, + }, + None, + ) + .await?; + + Ok(job) + } } diff --git a/image-processor/src/disk/http.rs b/image-processor/src/disk/http.rs index cd313e5b..464f0ecd 100644 --- a/image-processor/src/disk/http.rs +++ b/image-processor/src/disk/http.rs @@ -3,20 +3,20 @@ use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; use reqwest::Method; use url::Url; -use super::{Disk, DiskError, DiskWriteOptions}; -use crate::config::{DiskMode, HttpDiskConfig}; +use super::{Drive, DriveError, DriveWriteOptions}; +use crate::config::{DriveMode, HttpDriveConfig}; #[derive(Debug)] -pub struct HttpDisk { +pub struct HttpDrive { name: String, base_url: Url, - mode: DiskMode, + mode: DriveMode, semaphore: Option, client: reqwest::Client, } #[derive(Debug, thiserror::Error)] -pub enum HttpDiskError { +pub enum HttpDriveError { #[error("invalid path")] InvalidPath(#[from] url::ParseError), #[error("reqwest: {0}")] @@ -27,9 +27,9 @@ pub enum HttpDiskError { InvalidHeaderValue(#[from] reqwest::header::InvalidHeaderValue), } -impl HttpDisk { +impl HttpDrive { #[tracing::instrument(skip(config), name = "HttpDisk::new", fields(name = %config.name), err)] - pub async fn new(config: &HttpDiskConfig) -> Result { + pub async fn new(config: &HttpDriveConfig) -> Result { tracing::debug!("setting up http disk"); Ok(Self { name: config.name.clone(), @@ -50,28 +50,31 @@ impl HttpDisk { let mut headers = HeaderMap::new(); for (key, value) in &config.headers { - headers.insert(key.parse::()?, value.parse::()?); + headers.insert( + key.parse::().map_err(HttpDriveError::InvalidHeaderName)?, + value.parse::().map_err(HttpDriveError::InvalidHeaderValue)?, + ); } builder = builder.default_headers(headers); - builder.build().map_err(HttpDiskError::Reqwest)? + builder.build().map_err(HttpDriveError::Reqwest)? }, }) } } -impl Disk for HttpDisk { +impl Drive for HttpDrive { fn name(&self) -> &str { &self.name } #[tracing::instrument(skip(self), name = "HttpDisk::read", fields(name = %self.name), err)] - async fn read(&self, path: &str) -> Result { + async fn read(&self, path: &str) -> Result { tracing::debug!("reading file"); - if self.mode == DiskMode::Write { - return Err(DiskError::ReadOnly); + if self.mode == DriveMode::Write { + return Err(DriveError::ReadOnly); } let _permit = if let Some(semaphore) = &self.semaphore { @@ -80,21 +83,21 @@ impl Disk for HttpDisk { None }; - let url = self.base_url.join(path).map_err(HttpDiskError::InvalidPath)?; + let url = self.base_url.join(path).map_err(HttpDriveError::InvalidPath)?; - let response = self.client.get(url).send().await.map_err(HttpDiskError::Reqwest)?; + let response = self.client.get(url).send().await.map_err(HttpDriveError::Reqwest)?; - let response = response.error_for_status().map_err(HttpDiskError::Reqwest)?; + let response = response.error_for_status().map_err(HttpDriveError::Reqwest)?; - Ok(response.bytes().await.map_err(HttpDiskError::Reqwest)?) + Ok(response.bytes().await.map_err(HttpDriveError::Reqwest)?) } #[tracing::instrument(skip(self, data), name = "HttpDisk::write", fields(name = %self.name, size = data.len()), err)] - async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DriveError> { tracing::debug!("writing file"); - if self.mode == DiskMode::Read { - return Err(DiskError::WriteOnly); + if self.mode == DriveMode::Read { + return Err(DriveError::WriteOnly); } let _permit = if let Some(semaphore) = &self.semaphore { @@ -103,51 +106,51 @@ impl Disk for HttpDisk { None }; - let url = self.base_url.join(path).map_err(HttpDiskError::InvalidPath)?; + let url = self.base_url.join(path).map_err(HttpDriveError::InvalidPath)?; let mut request = self .client .request(Method::POST, url) .body(data) .build() - .map_err(HttpDiskError::Reqwest)?; + .map_err(HttpDriveError::Reqwest)?; if let Some(options) = options { if let Some(cache_control) = &options.cache_control { request.headers_mut().insert( reqwest::header::CACHE_CONTROL, - cache_control.parse().map_err(HttpDiskError::InvalidHeaderValue)?, + cache_control.parse().map_err(HttpDriveError::InvalidHeaderValue)?, ); } if let Some(content_type) = &options.content_type { request.headers_mut().insert( reqwest::header::CONTENT_TYPE, - content_type.parse().map_err(HttpDiskError::InvalidHeaderValue)?, + content_type.parse().map_err(HttpDriveError::InvalidHeaderValue)?, ); } if let Some(acl) = &options.acl { request.headers_mut().insert( reqwest::header::HeaderName::from_static("x-amz-acl"), - acl.parse().map_err(HttpDiskError::InvalidHeaderValue)?, + acl.parse().map_err(HttpDriveError::InvalidHeaderValue)?, ); } } - let resp = self.client.execute(request).await.map_err(HttpDiskError::Reqwest)?; + let resp = self.client.execute(request).await.map_err(HttpDriveError::Reqwest)?; - resp.error_for_status().map_err(HttpDiskError::Reqwest)?; + resp.error_for_status().map_err(HttpDriveError::Reqwest)?; Ok(()) } #[tracing::instrument(skip(self), name = "HttpDisk::delete", fields(name = %self.name), err)] - async fn delete(&self, path: &str) -> Result<(), DiskError> { + async fn delete(&self, path: &str) -> Result<(), DriveError> { tracing::debug!("deleting file"); - if self.mode == DiskMode::Read { - return Err(DiskError::WriteOnly); + if self.mode == DriveMode::Read { + return Err(DriveError::WriteOnly); } let _permit = if let Some(semaphore) = &self.semaphore { @@ -156,11 +159,11 @@ impl Disk for HttpDisk { None }; - let url = self.base_url.join(path).map_err(HttpDiskError::InvalidPath)?; + let url = self.base_url.join(path).map_err(HttpDriveError::InvalidPath)?; - let response = self.client.delete(url).send().await.map_err(HttpDiskError::Reqwest)?; + let response = self.client.delete(url).send().await.map_err(HttpDriveError::Reqwest)?; - response.error_for_status().map_err(HttpDiskError::Reqwest)?; + response.error_for_status().map_err(HttpDriveError::Reqwest)?; Ok(()) } diff --git a/image-processor/src/disk/local.rs b/image-processor/src/disk/local.rs index 3e9be4fe..0b6076cb 100644 --- a/image-processor/src/disk/local.rs +++ b/image-processor/src/disk/local.rs @@ -2,29 +2,29 @@ use std::path::PathBuf; use bytes::Bytes; -use super::{Disk, DiskError, DiskWriteOptions}; -use crate::config::{DiskMode, LocalDiskConfig}; +use super::{Drive, DriveError, DriveWriteOptions}; +use crate::config::{DriveMode, LocalDriveConfig}; #[derive(Debug)] -pub struct LocalDisk { +pub struct LocalDrive { name: String, - mode: DiskMode, + mode: DriveMode, path: PathBuf, } #[derive(Debug, thiserror::Error)] -pub enum LocalDiskError { +pub enum LocalDriveError { #[error("io: {0}")] Io(#[from] std::io::Error), } -impl LocalDisk { +impl LocalDrive { #[tracing::instrument(skip(config), name = "LocalDisk::new", fields(name = %config.name), err)] - pub async fn new(config: &LocalDiskConfig) -> Result { + pub async fn new(config: &LocalDriveConfig) -> Result { tracing::debug!("setting up local disk"); if !config.path.exists() { - tokio::fs::create_dir_all(&config.path).await.map_err(LocalDiskError::Io)?; + tokio::fs::create_dir_all(&config.path).await.map_err(LocalDriveError::Io)?; } Ok(Self { @@ -35,45 +35,45 @@ impl LocalDisk { } } -impl Disk for LocalDisk { +impl Drive for LocalDrive { fn name(&self) -> &str { &self.name } #[tracing::instrument(skip(self), name = "LocalDisk::read", err)] - async fn read(&self, path: &str) -> Result { + async fn read(&self, path: &str) -> Result { tracing::debug!("reading file"); - if self.mode == DiskMode::Write { - return Err(DiskError::ReadOnly); + if self.mode == DriveMode::Write { + return Err(DriveError::ReadOnly); } let path = self.path.join(path); - Ok(tokio::fs::read(path).await.map_err(LocalDiskError::Io)?.into()) + Ok(tokio::fs::read(path).await.map_err(LocalDriveError::Io)?.into()) } #[tracing::instrument(skip(self, data), name = "LocalDisk::write", err, fields(size = data.len()))] - async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DriveError> { tracing::debug!("writing file"); - if self.mode == DiskMode::Read { - return Err(DiskError::WriteOnly); + if self.mode == DriveMode::Read { + return Err(DriveError::WriteOnly); } let path = self.path.join(path); - Ok(tokio::fs::write(path, data).await.map_err(LocalDiskError::Io)?) + Ok(tokio::fs::write(path, data).await.map_err(LocalDriveError::Io)?) } #[tracing::instrument(skip(self), name = "LocalDisk::delete", err)] - async fn delete(&self, path: &str) -> Result<(), DiskError> { + async fn delete(&self, path: &str) -> Result<(), DriveError> { tracing::debug!("deleting file"); - if self.mode == DiskMode::Read { - return Err(DiskError::WriteOnly); + if self.mode == DriveMode::Read { + return Err(DriveError::WriteOnly); } let path = self.path.join(path); - tokio::fs::remove_file(path).await.map_err(LocalDiskError::Io)?; + tokio::fs::remove_file(path).await.map_err(LocalDriveError::Io)?; Ok(()) } } diff --git a/image-processor/src/disk/memory.rs b/image-processor/src/disk/memory.rs index 426278cc..30d2754f 100644 --- a/image-processor/src/disk/memory.rs +++ b/image-processor/src/disk/memory.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use bytes::Bytes; use tokio::sync::RwLock; -use super::{Disk, DiskError, DiskWriteOptions}; -use crate::config::{DiskMode, MemoryDiskConfig}; +use super::{Drive, DriveError, DriveWriteOptions}; +use crate::config::{DriveMode, MemoryDriveConfig}; #[derive(Debug)] struct FileHolder { @@ -17,9 +17,9 @@ impl FileHolder { self.files.get(path) } - fn insert(&mut self, path: String, file: MemoryFile) -> Result, MemoryDiskError> { + fn insert(&mut self, path: String, file: MemoryFile) -> Result, DriveError> { if file.data.len() > self.remaining_capacity { - return Err(MemoryDiskError::NoSpaceLeft); + return Err(MemoryDriveError::NoSpaceLeft.into()); } self.remaining_capacity -= file.data.len(); @@ -40,9 +40,9 @@ impl FileHolder { } #[derive(Debug)] -pub struct MemoryDisk { +pub struct MemoryDrive { name: String, - mode: DiskMode, + mode: DriveMode, files: RwLock, global: bool, } @@ -50,18 +50,18 @@ pub struct MemoryDisk { #[derive(Debug, Clone)] pub struct MemoryFile { data: Bytes, - _options: DiskWriteOptions, + _options: DriveWriteOptions, } #[derive(Debug, Clone, thiserror::Error)] -pub enum MemoryDiskError { +pub enum MemoryDriveError { #[error("no space left on disk")] NoSpaceLeft, } -impl MemoryDisk { +impl MemoryDrive { #[tracing::instrument(skip(config), name = "MemoryDisk::new", fields(name = %config.name), err)] - pub async fn new(config: &MemoryDiskConfig) -> Result { + pub async fn new(config: &MemoryDriveConfig) -> Result { tracing::debug!("setting up memory disk"); Ok(Self { name: config.name.clone(), @@ -75,17 +75,17 @@ impl MemoryDisk { } } -impl Disk for MemoryDisk { +impl Drive for MemoryDrive { fn name(&self) -> &str { &self.name } #[tracing::instrument(skip(self), name = "MemoryDisk::read", err)] - async fn read(&self, path: &str) -> Result { + async fn read(&self, path: &str) -> Result { tracing::debug!("reading file"); - if self.mode == DiskMode::Write { - return Err(DiskError::ReadOnly); + if self.mode == DriveMode::Write { + return Err(DriveError::ReadOnly); } Ok(self @@ -94,15 +94,15 @@ impl Disk for MemoryDisk { .await .get(path) .map(|file| file.data.clone()) - .ok_or(DiskError::NotFound)?) + .ok_or(DriveError::NotFound)?) } #[tracing::instrument(skip(self, data), name = "MemoryDisk::write", err, fields(size = data.len()))] - async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DriveError> { tracing::debug!("writing file"); - if self.mode == DiskMode::Read { - return Err(DiskError::WriteOnly); + if self.mode == DriveMode::Read { + return Err(DriveError::WriteOnly); } let mut files = self.files.write().await; @@ -119,14 +119,14 @@ impl Disk for MemoryDisk { } #[tracing::instrument(skip(self), name = "MemoryDisk::delete", err)] - async fn delete(&self, path: &str) -> Result<(), DiskError> { + async fn delete(&self, path: &str) -> Result<(), DriveError> { tracing::debug!("deleting file"); - if self.mode == DiskMode::Read { - return Err(DiskError::WriteOnly); + if self.mode == DriveMode::Read { + return Err(DriveError::WriteOnly); } - self.files.write().await.remove(path).ok_or(DiskError::NotFound)?; + self.files.write().await.remove(path).ok_or(DriveError::NotFound)?; Ok(()) } diff --git a/image-processor/src/disk/mod.rs b/image-processor/src/disk/mod.rs index 435bd6c4..c48c70f4 100644 --- a/image-processor/src/disk/mod.rs +++ b/image-processor/src/disk/mod.rs @@ -1,11 +1,11 @@ use bytes::Bytes; -use self::http::{HttpDisk, HttpDiskError}; -use self::local::{LocalDisk, LocalDiskError}; -use self::memory::{MemoryDisk, MemoryDiskError}; -use self::public_http::{PublicHttpDisk, PublicHttpDiskError}; -use self::s3::{S3Disk, S3DiskError}; -use crate::config::DiskConfig; +use self::http::{HttpDrive, HttpDriveError}; +use self::local::{LocalDrive, LocalDriveError}; +use self::memory::{MemoryDrive, MemoryDriveError}; +use self::public_http::{PublicHttpDrive, PublicHttpDriveError}; +use self::s3::{S3Drive, S3DriveError}; +use crate::config::DriveConfig; pub mod http; pub mod local; @@ -14,17 +14,17 @@ pub mod public_http; pub mod s3; #[derive(Debug, thiserror::Error)] -pub enum DiskError { +pub enum DriveError { #[error("http: {0}")] - Http(#[from] HttpDiskError), + Http(#[from] HttpDriveError), #[error("local: {0}")] - Local(#[from] LocalDiskError), + Local(#[from] LocalDriveError), #[error("s3: {0}")] - S3(#[from] S3DiskError), + S3(#[from] S3DriveError), #[error("memory: {0}")] - Memory(#[from] MemoryDiskError), + Memory(#[from] MemoryDriveError), #[error("public http: {0}")] - PublicHttp(#[from] PublicHttpDiskError), + PublicHttp(#[from] PublicHttpDriveError), #[error("not found")] NotFound, #[error("read only")] @@ -34,30 +34,30 @@ pub enum DiskError { } #[derive(Debug, Clone, Default)] -pub struct DiskWriteOptions { +pub struct DriveWriteOptions { pub cache_control: Option, pub content_type: Option, pub acl: Option, pub content_disposition: Option, } -pub trait Disk { - /// Get the name of the disk +pub trait Drive { + /// Get the name of the drive fn name(&self) -> &str; - /// Read data from a disk - fn read(&self, path: &str) -> impl std::future::Future> + Send; + /// Read data from a drive + fn read(&self, path: &str) -> impl std::future::Future> + Send; - /// Write data to a disk + /// Write data to a drive fn write( &self, path: &str, data: Bytes, - options: Option, - ) -> impl std::future::Future> + Send; + options: Option, + ) -> impl std::future::Future> + Send; - /// Delete data from a disk - fn delete(&self, path: &str) -> impl std::future::Future> + Send; + /// Delete data from a drive + fn delete(&self, path: &str) -> impl std::future::Future> + Send; /// Can be scoped to a specific request fn scoped(&self) -> Option @@ -66,65 +66,69 @@ pub trait Disk { { None } + + fn healthy(&self) -> impl std::future::Future + Send { + async { true } + } } #[derive(Debug)] -pub enum AnyDisk { - Local(LocalDisk), - S3(S3Disk), - Memory(MemoryDisk), - Http(HttpDisk), - PublicHttp(PublicHttpDisk), +pub enum AnyDrive { + Local(LocalDrive), + S3(S3Drive), + Memory(MemoryDrive), + Http(HttpDrive), + PublicHttp(PublicHttpDrive), } -impl Disk for AnyDisk { +impl Drive for AnyDrive { fn name(&self) -> &str { match self { - AnyDisk::Local(disk) => disk.name(), - AnyDisk::S3(disk) => disk.name(), - AnyDisk::Memory(disk) => disk.name(), - AnyDisk::Http(disk) => disk.name(), - AnyDisk::PublicHttp(disk) => disk.name(), + AnyDrive::Local(drive) => drive.name(), + AnyDrive::S3(drive) => drive.name(), + AnyDrive::Memory(drive) => drive.name(), + AnyDrive::Http(drive) => drive.name(), + AnyDrive::PublicHttp(drive) => drive.name(), } } - async fn read(&self, path: &str) -> Result { + async fn read(&self, path: &str) -> Result { match self { - AnyDisk::Local(disk) => disk.read(path).await, - AnyDisk::S3(disk) => disk.read(path).await, - AnyDisk::Memory(disk) => disk.read(path).await, - AnyDisk::Http(disk) => disk.read(path).await, - AnyDisk::PublicHttp(disk) => disk.read(path).await, + AnyDrive::Local(drive) => drive.read(path).await, + AnyDrive::S3(drive) => drive.read(path).await, + AnyDrive::Memory(drvie) => drvie.read(path).await, + AnyDrive::Http(drive) => drive.read(path).await, + AnyDrive::PublicHttp(drive) => drive.read(path).await, } } - async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DriveError> { match self { - AnyDisk::Local(disk) => disk.write(path, data, options).await, - AnyDisk::S3(disk) => disk.write(path, data, options).await, - AnyDisk::Memory(disk) => disk.write(path, data, options).await, - AnyDisk::Http(disk) => disk.write(path, data, options).await, - AnyDisk::PublicHttp(disk) => disk.write(path, data, options).await, + AnyDrive::Local(drive) => drive.write(path, data, options).await, + AnyDrive::S3(drive) => drive.write(path, data, options).await, + AnyDrive::Memory(drive) => drive.write(path, data, options).await, + AnyDrive::Http(drive) => drive.write(path, data, options).await, + AnyDrive::PublicHttp(drive) => drive.write(path, data, options).await, } } - async fn delete(&self, path: &str) -> Result<(), DiskError> { + async fn delete(&self, path: &str) -> Result<(), DriveError> { match self { - AnyDisk::Local(disk) => disk.delete(path).await, - AnyDisk::S3(disk) => disk.delete(path).await, - AnyDisk::Memory(disk) => disk.delete(path).await, - AnyDisk::Http(disk) => disk.delete(path).await, - AnyDisk::PublicHttp(disk) => disk.delete(path).await, + AnyDrive::Local(drive) => drive.delete(path).await, + AnyDrive::S3(drive) => drive.delete(path).await, + AnyDrive::Memory(drive) => drive.delete(path).await, + AnyDrive::Http(drive) => drive.delete(path).await, + AnyDrive::PublicHttp(drive) => drive.delete(path).await, } } } -pub async fn build_disk(config: &DiskConfig) -> Result { +pub async fn build_drive(config: &DriveConfig) -> Result { match config { - DiskConfig::Local(local) => Ok(AnyDisk::Local(LocalDisk::new(local).await?)), - DiskConfig::S3(s3) => Ok(AnyDisk::S3(S3Disk::new(s3).await?)), - DiskConfig::Memory(memory) => Ok(AnyDisk::Memory(MemoryDisk::new(memory).await?)), - DiskConfig::Http(http) => Ok(AnyDisk::Http(HttpDisk::new(http).await?)), - DiskConfig::PublicHttp(public_http) => Ok(AnyDisk::PublicHttp(PublicHttpDisk::new(public_http).await?)), + DriveConfig::Local(local) => Ok(AnyDrive::Local(LocalDrive::new(local).await?)), + DriveConfig::S3(s3) => Ok(AnyDrive::S3(S3Drive::new(s3).await?)), + DriveConfig::Memory(memory) => Ok(AnyDrive::Memory(MemoryDrive::new(memory).await?)), + DriveConfig::Http(http) => Ok(AnyDrive::Http(HttpDrive::new(http).await?)), + DriveConfig::PublicHttp(public_http) => Ok(AnyDrive::PublicHttp(PublicHttpDrive::new(public_http).await?)), } } diff --git a/image-processor/src/disk/public_http.rs b/image-processor/src/disk/public_http.rs index 7f09f2bc..ea70174c 100644 --- a/image-processor/src/disk/public_http.rs +++ b/image-processor/src/disk/public_http.rs @@ -1,19 +1,19 @@ use bytes::Bytes; use http::{HeaderName, HeaderValue}; -use super::{Disk, DiskError, DiskWriteOptions}; -use crate::config::PublicHttpDiskConfig; +use super::{Drive, DriveError, DriveWriteOptions}; +use crate::config::PublicHttpDriveConfig; -pub const PUBLIC_HTTP_DISK_NAME: &str = "__public_http"; +pub const PUBLIC_HTTP_DRIVE_NAME: &str = "__public_http"; #[derive(Debug)] -pub struct PublicHttpDisk { +pub struct PublicHttpDrive { client: reqwest::Client, semaphore: Option, } #[derive(Debug, thiserror::Error)] -pub enum PublicHttpDiskError { +pub enum PublicHttpDriveError { #[error("reqwest: {0}")] Reqwest(#[from] reqwest::Error), #[error("invalid header name")] @@ -24,13 +24,13 @@ pub enum PublicHttpDiskError { Unsupported(&'static str), } -impl PublicHttpDisk { +impl PublicHttpDrive { #[tracing::instrument(skip(config), name = "PublicHttpDisk::new", err)] - pub async fn new(config: &PublicHttpDiskConfig) -> Result { + pub async fn new(config: &PublicHttpDriveConfig) -> Result { tracing::debug!("setting up public http disk"); if !config.blacklist.is_empty() || !config.whitelist.is_empty() { tracing::error!("blacklist and whitelist are not supported for public http disk"); - return Err(PublicHttpDiskError::Unsupported("blacklist and whitelist")); + return Err(PublicHttpDriveError::Unsupported("blacklist and whitelist").into()); } Ok(Self { @@ -48,25 +48,28 @@ impl PublicHttpDisk { let mut headers = reqwest::header::HeaderMap::new(); for (key, value) in &config.headers { - headers.insert(key.parse::()?, value.parse::()?); + headers.insert( + key.parse::().map_err(PublicHttpDriveError::from)?, + value.parse::().map_err(PublicHttpDriveError::from)?, + ); } builder = builder.default_headers(headers); - builder.build().map_err(|e| PublicHttpDiskError::Reqwest(e))? + builder.build().map_err(|e| PublicHttpDriveError::Reqwest(e))? }, semaphore: config.max_connections.map(|max| tokio::sync::Semaphore::new(max)), }) } } -impl Disk for PublicHttpDisk { +impl Drive for PublicHttpDrive { fn name(&self) -> &str { - PUBLIC_HTTP_DISK_NAME + PUBLIC_HTTP_DRIVE_NAME } #[tracing::instrument(skip(self), name = "PublicHttpDisk::read", err)] - async fn read(&self, path: &str) -> Result { + async fn read(&self, path: &str) -> Result { tracing::debug!("reading file"); let _permit = if let Some(semaphore) = &self.semaphore { @@ -75,22 +78,22 @@ impl Disk for PublicHttpDisk { None }; - let response = self.client.get(path).send().await.map_err(PublicHttpDiskError::Reqwest)?; + let response = self.client.get(path).send().await.map_err(PublicHttpDriveError::Reqwest)?; - let response = response.error_for_status().map_err(PublicHttpDiskError::Reqwest)?; + let response = response.error_for_status().map_err(PublicHttpDriveError::Reqwest)?; - Ok(response.bytes().await.map_err(PublicHttpDiskError::Reqwest)?) + Ok(response.bytes().await.map_err(PublicHttpDriveError::Reqwest)?) } #[tracing::instrument(skip(self, data), name = "PublicHttpDisk::write", fields(size = data.len()), err)] - async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DriveError> { tracing::error!("writing is not supported for public http disk"); - Err(DiskError::ReadOnly) + Err(DriveError::ReadOnly) } #[tracing::instrument(skip(self), name = "PublicHttpDisk::delete", err)] - async fn delete(&self, path: &str) -> Result<(), DiskError> { + async fn delete(&self, path: &str) -> Result<(), DriveError> { tracing::error!("deleting is not supported for public http disk"); - Err(DiskError::ReadOnly) + Err(DriveError::ReadOnly) } } diff --git a/image-processor/src/disk/s3.rs b/image-processor/src/disk/s3.rs index 0d09bbf4..aaa9987d 100644 --- a/image-processor/src/disk/s3.rs +++ b/image-processor/src/disk/s3.rs @@ -8,19 +8,19 @@ use aws_smithy_runtime_api::client::result::SdkError; use bytes::Bytes; use scuffle_foundations::service_info; -use super::{Disk, DiskError, DiskWriteOptions}; -use crate::config::{DiskMode, S3DiskConfig}; +use super::{Drive, DriveError, DriveWriteOptions}; +use crate::config::{DriveMode, S3DriveConfig}; #[derive(Debug)] -pub struct S3Disk { +pub struct S3Drive { name: String, - mode: DiskMode, + mode: DriveMode, client: aws_sdk_s3::Client, bucket: String, } #[derive(Debug, thiserror::Error)] -pub enum S3DiskError { +pub enum S3DriveError { #[error("s3: {0}")] S3Error(#[from] aws_sdk_s3::Error), #[error("byte stream: {0}")] @@ -33,9 +33,9 @@ pub enum S3DiskError { DeleteError(#[from] SdkError), } -impl S3Disk { +impl S3Drive { #[tracing::instrument(skip(config), name = "S3Disk::new", fields(name = %config.name), err)] - pub async fn new(config: &S3DiskConfig) -> Result { + pub async fn new(config: &S3DriveConfig) -> Result { tracing::debug!("setting up s3 disk"); Ok(Self { name: config.name.clone(), @@ -62,15 +62,15 @@ impl S3Disk { } } -impl Disk for S3Disk { +impl Drive for S3Drive { fn name(&self) -> &str { &self.name } #[tracing::instrument(skip(self), name = "S3Disk::read", err)] - async fn read(&self, path: &str) -> Result { - if self.mode == DiskMode::Write { - return Err(DiskError::ReadOnly); + async fn read(&self, path: &str) -> Result { + if self.mode == DriveMode::Write { + return Err(DriveError::ReadOnly); } let result = self @@ -80,17 +80,17 @@ impl Disk for S3Disk { .key(path) .send() .await - .map_err(S3DiskError::from)?; + .map_err(S3DriveError::from)?; - let bytes = result.body.collect().await.map_err(S3DiskError::from)?; + let bytes = result.body.collect().await.map_err(S3DriveError::from)?; Ok(bytes.into_bytes()) } #[tracing::instrument(skip(self, data), name = "S3Disk::write", err, fields(size = data.len()))] - async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DiskError> { - if self.mode == DiskMode::Read { - return Err(DiskError::WriteOnly); + async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DriveError> { + if self.mode == DriveMode::Read { + return Err(DriveError::WriteOnly); } let mut req = self.client.put_object().bucket(&self.bucket).key(path).body(data.into()); @@ -110,15 +110,15 @@ impl Disk for S3Disk { } } - req.send().await.map_err(S3DiskError::from)?; + req.send().await.map_err(S3DriveError::from)?; Ok(()) } #[tracing::instrument(skip(self), name = "S3Disk::delete", err)] - async fn delete(&self, path: &str) -> Result<(), DiskError> { - if self.mode == DiskMode::Read { - return Err(DiskError::WriteOnly); + async fn delete(&self, path: &str) -> Result<(), DriveError> { + if self.mode == DriveMode::Read { + return Err(DriveError::WriteOnly); } self.client @@ -127,7 +127,7 @@ impl Disk for S3Disk { .key(path) .send() .await - .map_err(S3DiskError::from)?; + .map_err(S3DriveError::from)?; Ok(()) } diff --git a/image-processor/src/event_queue/http.rs b/image-processor/src/event_queue/http.rs index 1a995438..dea43fdf 100644 --- a/image-processor/src/event_queue/http.rs +++ b/image-processor/src/event_queue/http.rs @@ -3,7 +3,7 @@ use scuffle_image_processor_proto::EventCallback; use url::Url; use super::{EventQueue, EventQueueError, PROTOBUF_CONTENT_TYPE}; -use crate::config::HttpEventQueueConfig; +use crate::config::{HttpEventQueueConfig, MessageEncoding}; #[derive(Debug)] pub struct HttpEventQueue { @@ -11,7 +11,7 @@ pub struct HttpEventQueue { url: Url, client: reqwest::Client, semaphore: Option, - allow_protobuf: bool, + message_encoding: MessageEncoding, } #[derive(Debug, thiserror::Error)] @@ -26,7 +26,7 @@ pub enum HttpEventQueueError { impl HttpEventQueue { #[tracing::instrument(skip(config), name = "HttpEventQueue::new", fields(name = %config.name), err)] - pub async fn new(config: &HttpEventQueueConfig) -> Result { + pub async fn new(config: &HttpEventQueueConfig) -> Result { tracing::debug!("setting up http event queue"); Ok(Self { name: config.name.clone(), @@ -45,8 +45,11 @@ impl HttpEventQueue { for (key, value) in &config.headers { headers.insert( - key.parse::()?, - value.parse::()?, + key.parse::() + .map_err(HttpEventQueueError::from)?, + value + .parse::() + .map_err(HttpEventQueueError::from)?, ); } @@ -55,7 +58,7 @@ impl HttpEventQueue { builder.build().map_err(|e| HttpEventQueueError::Reqwest(e))? }, url: config.url.clone(), - allow_protobuf: config.allow_protobuf, + message_encoding: config.message_encoding, semaphore: config.max_connections.map(|max| tokio::sync::Semaphore::new(max)), }) } @@ -76,7 +79,7 @@ impl EventQueue for HttpEventQueue { let mut req = self.client.post(self.url.clone()).header("X-Topic", topic); - if self.allow_protobuf { + if self.message_encoding == MessageEncoding::Protobuf { req = req.header("Content-Type", PROTOBUF_CONTENT_TYPE).body(data.encode_to_vec()); } else { req = req.json(&data); diff --git a/image-processor/src/event_queue/mod.rs b/image-processor/src/event_queue/mod.rs index bf4765e9..d09388a2 100644 --- a/image-processor/src/event_queue/mod.rs +++ b/image-processor/src/event_queue/mod.rs @@ -29,6 +29,10 @@ pub trait EventQueue { topic: &str, data: EventCallback, ) -> impl std::future::Future> + Send; + + fn healthy(&self) -> impl std::future::Future + Send { + async { true } + } } #[derive(Debug)] diff --git a/image-processor/src/event_queue/nats.rs b/image-processor/src/event_queue/nats.rs index 0d1754e7..1ce6f9c4 100644 --- a/image-processor/src/event_queue/nats.rs +++ b/image-processor/src/event_queue/nats.rs @@ -2,12 +2,12 @@ use prost::Message; use scuffle_image_processor_proto::EventCallback; use super::{EventQueue, EventQueueError, PROTOBUF_CONTENT_TYPE}; -use crate::config::NatsEventQueueConfig; +use crate::config::{MessageEncoding, NatsEventQueueConfig}; #[derive(Debug)] pub struct NatsEventQueue { name: String, - allow_protobuf: bool, + message_encoding: MessageEncoding, nats: async_nats::Client, } @@ -23,13 +23,13 @@ pub enum NatsEventQueueError { impl NatsEventQueue { #[tracing::instrument(skip(config), name = "NatsEventQueue::new", fields(name = %config.name), err)] - pub async fn new(config: &NatsEventQueueConfig) -> Result { + pub async fn new(config: &NatsEventQueueConfig) -> Result { tracing::debug!("setting up nats event queue"); - let nats = async_nats::connect(&config.url).await?; + let nats = async_nats::connect(&config.url).await.map_err(NatsEventQueueError::from)?; Ok(Self { name: config.name.clone(), - allow_protobuf: config.allow_protobuf, + message_encoding: config.message_encoding, nats, }) } @@ -44,7 +44,7 @@ impl EventQueue for NatsEventQueue { async fn publish(&self, topic: &str, data: EventCallback) -> Result<(), EventQueueError> { let mut header_map = async_nats::HeaderMap::new(); - let payload = if self.allow_protobuf { + let payload = if self.message_encoding == MessageEncoding::Protobuf { header_map.insert("Content-Type", PROTOBUF_CONTENT_TYPE); data.encode_to_vec() } else { @@ -62,4 +62,8 @@ impl EventQueue for NatsEventQueue { Ok(()) } + + async fn healthy(&self) -> bool { + matches!(self.nats.connection_state(), async_nats::connection::State::Connected) + } } diff --git a/image-processor/src/event_queue/redis.rs b/image-processor/src/event_queue/redis.rs index 560ad6e5..e998a57d 100644 --- a/image-processor/src/event_queue/redis.rs +++ b/image-processor/src/event_queue/redis.rs @@ -1,16 +1,16 @@ -use fred::interfaces::PubsubInterface; +use fred::interfaces::{ClientLike, PubsubInterface}; use fred::types::RedisConfig; use prost::Message; use scuffle_image_processor_proto::EventCallback; use super::{EventQueue, EventQueueError}; -use crate::config::RedisEventQueueConfig; +use crate::config::{MessageEncoding, RedisEventQueueConfig}; #[derive(Debug)] pub struct RedisEventQueue { client: fred::clients::RedisClient, name: String, - allow_protobuf: bool, + message_encoding: MessageEncoding, } #[derive(Debug, thiserror::Error)] @@ -23,11 +23,16 @@ pub enum RedisEventQueueError { impl RedisEventQueue { #[tracing::instrument(skip(config), name = "RedisEventQueue::new", fields(name = %config.name), err)] - pub async fn new(config: &RedisEventQueueConfig) -> Result { + pub async fn new(config: &RedisEventQueueConfig) -> Result { Ok(Self { - client: fred::clients::RedisClient::new(RedisConfig::from_url(&config.url)?, None, None, None), + client: fred::clients::RedisClient::new( + RedisConfig::from_url(&config.url).map_err(RedisEventQueueError::from)?, + None, + None, + None, + ), name: config.name.clone(), - allow_protobuf: config.allow_protobuf, + message_encoding: config.message_encoding, }) } } @@ -39,7 +44,7 @@ impl EventQueue for RedisEventQueue { #[tracing::instrument(skip(self), name = "RedisEventQueue::publish", err)] async fn publish(&self, topic: &str, data: EventCallback) -> Result<(), EventQueueError> { - let payload = if self.allow_protobuf { + let payload = if self.message_encoding == MessageEncoding::Protobuf { data.encode_to_vec() } else { serde_json::to_string(&data) @@ -54,4 +59,8 @@ impl EventQueue for RedisEventQueue { Ok(()) } + + async fn healthy(&self) -> bool { + self.client.ping::<()>().await.is_ok() + } } diff --git a/image-processor/src/global.rs b/image-processor/src/global.rs index 92f7e451..1ade9418 100644 --- a/image-processor/src/global.rs +++ b/image-processor/src/global.rs @@ -2,12 +2,13 @@ use std::collections::HashMap; use anyhow::Context; use bson::oid::ObjectId; +use scuffle_foundations::telemetry::server::HealthCheck; use scuffle_foundations::BootstrapResult; use crate::config::ImageProcessorConfig; use crate::database::Job; -use crate::disk::public_http::PUBLIC_HTTP_DISK_NAME; -use crate::disk::{build_disk, AnyDisk, Disk}; +use crate::disk::public_http::PUBLIC_HTTP_DRIVE_NAME; +use crate::disk::{build_drive, AnyDrive, Drive}; use crate::event_queue::{build_event_queue, AnyEventQueue, EventQueue}; pub struct Global { @@ -15,43 +16,58 @@ pub struct Global { config: ImageProcessorConfig, client: mongodb::Client, database: mongodb::Database, - disks: HashMap, + disks: HashMap, event_queues: HashMap, } impl Global { pub async fn new(config: ImageProcessorConfig) -> BootstrapResult { + const DEFAULT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3); tracing::debug!("setting up mongo client"); - let client = mongodb::Client::with_uri_str(&config.database.uri).await.context("mongodb")?; + let client = tokio::time::timeout(DEFAULT_TIMEOUT, mongodb::Client::with_uri_str(&config.database.uri)) + .await + .context("mongodb timeout")? + .context("mongodb")?; let Some(database) = client.default_database() else { anyhow::bail!("no default database") }; tracing::debug!("setting up job collection"); - Job::setup_collection(&database).await.context("setup job collection")?; + tokio::time::timeout(DEFAULT_TIMEOUT, Job::setup_collection(&database)) + .await + .context("job collection timeout")? + .context("job collection")?; tracing::debug!("setting up disks and event queues"); let mut disks = HashMap::new(); - for disk in &config.disks { - let disk = build_disk(disk).await.context("disk")?; + for disk in &config.drives { + let disk = tokio::time::timeout(DEFAULT_TIMEOUT, build_drive(disk)) + .await + .context("disk timeout")? + .context("disk")?; + let name = disk.name().to_string(); if disks.insert(name.clone(), disk).is_some() { anyhow::bail!("duplicate disk name: {name}"); } } - if config.disks.is_empty() { + if config.drives.is_empty() { tracing::warn!("no disks configured"); } let mut event_queues = HashMap::new(); for event_queue in &config.event_queues { - let event_queue = build_event_queue(event_queue).await.context("event queue")?; + let event_queue = tokio::time::timeout(DEFAULT_TIMEOUT, build_event_queue(event_queue)) + .await + .context("event queue timeout")? + .context("event queue")?; + let name = event_queue.name().to_string(); if event_queues.insert(name.clone(), event_queue).is_some() { anyhow::bail!("duplicate event queue name: {name}"); @@ -80,19 +96,54 @@ impl Global { &self.config } - pub fn disk(&self, name: &str) -> Option<&AnyDisk> { + pub fn drive(&self, name: &str) -> Option<&AnyDrive> { self.disks.get(name) } + pub fn drives(&self) -> &HashMap { + &self.disks + } + + pub fn event_queues(&self) -> &HashMap { + &self.event_queues + } + pub fn event_queue(&self, name: &str) -> Option<&AnyEventQueue> { self.event_queues.get(name) } - pub fn public_http_disk(&self) -> Option<&AnyDisk> { - self.disk(PUBLIC_HTTP_DISK_NAME) + pub fn public_http_drive(&self) -> Option<&AnyDrive> { + self.drive(PUBLIC_HTTP_DRIVE_NAME) } pub fn database(&self) -> &mongodb::Database { &self.database } } + +impl HealthCheck for Global { + fn check(&self) -> std::pin::Pin + Send + '_>> { + Box::pin(async { + if let Err(err) = self.database().run_command(bson::doc! { "ping": 1 }, None).await { + tracing::error!("database ping failed: {err}"); + return false; + } + + for disk in self.drive().values() { + if !disk.healthy().await { + tracing::error!(name = %disk.name(), "disk check failed"); + return false; + } + } + + for event_queue in self.event_queues().values() { + if !event_queue.healthy().await { + tracing::error!(name = %event_queue.name(), "event queue check failed"); + return false; + } + } + + true + }) + } +} diff --git a/image-processor/src/grpc.rs b/image-processor/src/grpc.rs deleted file mode 100644 index 3e2ab358..00000000 --- a/image-processor/src/grpc.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::sync::Arc; - -use tonic::transport::server::Router; - -use crate::global::ImageProcessorGlobal; - -pub fn add_routes(_: &Arc, router: Router) -> Router { - router -} diff --git a/image-processor/src/main.rs b/image-processor/src/main.rs index dc43c184..fa4c738b 100644 --- a/image-processor/src/main.rs +++ b/image-processor/src/main.rs @@ -1,8 +1,11 @@ use std::sync::Arc; +use anyhow::Context; use scuffle_foundations::bootstrap::{bootstrap, Bootstrap}; use scuffle_foundations::settings::cli::Matches; -use scuffle_foundations::BootstrapResult; +use scuffle_foundations::runtime; +use scuffle_image_processor_proto::{event_callback, EventCallback}; +use tokio::signal::unix::SignalKind; use self::config::ImageProcessorConfig; @@ -23,12 +26,77 @@ mod database; mod disk; mod event_queue; mod global; +mod management; +mod worker; #[bootstrap] -async fn main(cfg: Matches) -> BootstrapResult<()> { +async fn main(cfg: Matches) { tracing::info!("starting image processor"); - let global = Arc::new(global::Global::new(cfg.settings).await?); + // Require a health check to be registered + scuffle_foundations::telemetry::server::require_health_check(); - Ok(()) + let global = Arc::new({ + match global::Global::new(cfg.settings).await { + Ok(global) => global, + Err(err) => { + tracing::error!("error setting up global: {err}"); + std::process::exit(1); + } + } + }); + + scuffle_foundations::telemetry::server::register_health_check(global.clone()); + + let mut handles = Vec::new(); + + if global.config().management.grpc.enabled || global.config().management.http.enabled { + handles.push(runtime::spawn(management::start(global.clone()))); + } + + if global.config().worker.enabled { + handles.push(runtime::spawn(worker::start(global.clone()))); + } + + let mut signal = scuffle_foundations::signal::SignalHandler::new() + .with_signal(SignalKind::interrupt()) + .with_signal(SignalKind::terminate()); + + let handles = futures::future::try_join_all( + handles + .iter_mut() + .map(|handle| async move { handle.await.context("spawn task failed")? }), + ); + + tokio::select! { + _ = signal.recv() => { + tracing::info!("received signal, shutting down"); + } + result = handles => { + match result { + Ok(_) => { + tracing::warn!("handles completed unexpectedly without error"); + }, + Err(err) => tracing::error!("error in handle: {}", err), + } + } + } + + let handle = scuffle_foundations::context::Handler::global(); + + tokio::select! { + _ = signal.recv() => { + tracing::warn!("received signal again, forcing exit"); + }, + r = tokio::time::timeout(std::time::Duration::from_secs(60), handle.shutdown()) => { + if r.is_err() { + tracing::warn!("shutdown timed out, forcing exit"); + } else { + tracing::info!("image processor stopped"); + } + } + } + + + std::process::exit(0); } diff --git a/image-processor/src/management/grpc.rs b/image-processor/src/management/grpc.rs new file mode 100644 index 00000000..5b522dc4 --- /dev/null +++ b/image-processor/src/management/grpc.rs @@ -0,0 +1,48 @@ +use scuffle_image_processor_proto::{CancelTaskRequest, CancelTaskResponse, ProcessImageRequest, ProcessImageResponse}; +use tonic::{Request, Response}; + +use super::ManagementServer; + +impl ManagementServer { + pub async fn run_grpc(&self) -> Result<(), tonic::transport::Error> { + let addr = self.global.config().management.grpc.bind; + let server = tonic::transport::Server::builder() + .add_service(scuffle_image_processor_proto::image_processor_server::ImageProcessorServer::new(self.clone())) + .serve_with_shutdown(addr, scuffle_foundations::context::Context::global().into_done()); + + tracing::info!(%addr, "gRPC server listening"); + server.await + } +} + +#[async_trait::async_trait] +impl scuffle_image_processor_proto::image_processor_server::ImageProcessor for ManagementServer { + async fn process_image( + &self, + request: Request, + ) -> tonic::Result> { + let resp = match self.process_image(request.into_inner()).await { + Ok(resp) => resp, + Err(err) => ProcessImageResponse { + id: "".to_owned(), + error: Some(err), + }, + }; + + Ok(Response::new(resp)) + } + + async fn cancel_task( + &self, + request: Request, + ) -> tonic::Result> { + let resp = match self.cancel_task(request.into_inner()).await { + Ok(resp) => resp, + Err(err) => CancelTaskResponse { + error: Some(err), + }, + }; + + Ok(Response::new(resp)) + } +} diff --git a/image-processor/src/management/http.rs b/image-processor/src/management/http.rs new file mode 100644 index 00000000..298a590a --- /dev/null +++ b/image-processor/src/management/http.rs @@ -0,0 +1,59 @@ +use scuffle_foundations::http::server::axum::{extract::State, Json, Router, routing::post}; +use scuffle_image_processor_proto::{CancelTaskRequest, CancelTaskResponse, ErrorCode, ProcessImageRequest, ProcessImageResponse}; + +use super::ManagementServer; + +impl ManagementServer { + pub async fn run_http(&self) -> Result<(), scuffle_foundations::http::server::Error> { + let router = Router::new() + .route("/process_image", post(process_image)) + .route("/cancel_task", post(cancel_task)) + .with_state(self.clone()); + + let addr = self.global.config().management.http.bind; + scuffle_foundations::http::server::Server::builder() + .bind(addr) + .build(router)? + .start_and_wait() + .await + } +} + +async fn process_image( + State(server): State, + Json(request): Json, +) -> (http::StatusCode, Json) { + let resp = match server.process_image(request).await { + Ok(resp) => resp, + Err(err) => ProcessImageResponse { + id: "".to_owned(), + error: Some(err), + } + }; + + let status = resp.error.as_ref().map_or(http::StatusCode::OK, |err| map_error_code(err.code())); + (status, Json(resp)) +} + +async fn cancel_task( + State(server): State, + Json(request): Json, +) -> (http::StatusCode, Json) { + let resp = match server.cancel_task(request).await { + Ok(resp) => resp, + Err(err) => CancelTaskResponse { + error: Some(err), + } + }; + + let status = resp.error.as_ref().map_or(http::StatusCode::OK, |err| map_error_code(err.code())); + (status, Json(resp)) +} + +fn map_error_code(code: ErrorCode) -> http::StatusCode { + match code { + ErrorCode::InvalidInput => http::StatusCode::BAD_REQUEST, + ErrorCode::InternalError => http::StatusCode::INTERNAL_SERVER_ERROR, + ErrorCode::Unknown => http::StatusCode::INTERNAL_SERVER_ERROR, + } +} \ No newline at end of file diff --git a/image-processor/src/management/mod.rs b/image-processor/src/management/mod.rs new file mode 100644 index 00000000..897c0f70 --- /dev/null +++ b/image-processor/src/management/mod.rs @@ -0,0 +1,108 @@ +use std::sync::Arc; + +use anyhow::Context; +use scuffle_image_processor_proto::{CancelTaskRequest, CancelTaskResponse, Error, ErrorCode, ProcessImageRequest, ProcessImageResponse, input_path::InputPath}; +use url::Url; + +use crate::global::Global; + +pub mod grpc; +pub mod http; + +mod validation; + +#[derive(Clone)] +struct ManagementServer { + global: Arc, +} + +impl ManagementServer { + async fn process_image( + &self, + request: ProcessImageRequest, + ) -> Result { + let Some(task) = request.task.as_ref() else { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: "task is required".to_string(), + }); + }; + + let Some(input) = &task.input else { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: "input is required".to_string(), + }); + }; + + let Some(input_path) = input.path.as_ref().and_then(|path| path.input_path.as_ref()) else { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: "task.input.path is required".to_string(), + }); + }; + + + + if let Some(events) = &task.events { + let queues = [ + (&events.on_success, "task.events.on_success"), + (&events.on_fail, "task.events.on_fail"), + (&events.on_start, "task.events.on_start"), + (&events.on_cancel, "task.events.on_cancel"), + ]; + + for (queue, field) in queues { + if let Some(queue) = queue { + if self.global.event_queue(&queue.name).is_none() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{field}.name: event queue not found"), + }); + } + } + } + } + + // We need to do validation here. + if let Some(image) = request.input_upload.as_ref() { + + } + + todo!() + } + + async fn cancel_task( + &self, + request: CancelTaskRequest, + ) -> Result { + todo!() + } +} + + +pub async fn start(global: Arc) -> anyhow::Result<()> { + let server = ManagementServer { + global, + }; + + let http = async { + if server.global.config().management.http.enabled { + server.run_http().await.context("http") + } else { + Ok(()) + } + }; + let grpc = async { + if server.global.config().management.grpc.enabled { + server.run_grpc().await.context("grpc") + } else { + Ok(()) + } + }; + + futures::future::try_join(http, grpc).await.context("management")?; + + Ok(()) +} + diff --git a/image-processor/src/management/validation.rs b/image-processor/src/management/validation.rs new file mode 100644 index 00000000..e8bf8150 --- /dev/null +++ b/image-processor/src/management/validation.rs @@ -0,0 +1,307 @@ +use std::sync::Arc; + +use scuffle_image_processor_proto::{input_path::InputPath, DrivePath, Error, ErrorCode, Input, InputMetadata, Output, Task}; +use url::Url; + +use crate::global::Global; + + +#[derive(Debug, Default, Clone)] +pub struct Fragment { + path: Vec<&'static str>, +} + +impl std::fmt::Display for Fragment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.path.join(".")) + } +} + +impl Fragment { + pub fn new() -> Self { + Self::default() + } + + pub fn push(&mut self, path: &'static str) { + self.path.push(path); + } + + pub fn pop(&mut self) { + self.path.pop(); + } + + pub fn push_clone(&self, path: &'static str) -> Self { + let mut fragment = self.clone(); + fragment.push(path); + fragment + } + + pub fn push_str(&self, path: &str) -> String { + let mut builder = self.path.join("."); + if !builder.is_empty() { + builder.push('.'); + } + builder.push_str(path); + builder + } +} + +pub fn validate_task( + global: &Arc, + mut fragment: Fragment, + task: Option<&Task>, +) -> Result<(), Error> { + let task = task.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment} is required"), + })?; + + validate_input(global, fragment.push_clone("input"), task.input.as_ref())?; + + validate_output(global, fragment.push_clone("output"), task.output.as_ref())?; + + Ok(()) +} + +pub fn validate_output( + global: &Arc, + mut fragment: Fragment, + output: Option<&Output>, +) -> Result<(), Error> { + let output = output.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment} is required"), + })?; + + validate_drive_path(global, fragment.push_clone("path"), output.path.as_ref(), false)?; + + + Ok(()) +} + +pub fn validate_input( + global: &Arc, + mut fragment: Fragment, + input: Option<&Input> +) -> Result<(), Error> { + let input = input.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment} is required"), + })?; + + let path = input.path.as_ref().ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{} is required", fragment.push_str("path")), + })?; + + validate_input_path(global, fragment.push_clone("path"), path.input_path.as_ref())?; + + // Metadata is optional + if let Some(metadata) = &input.metadata { + validate_input_metadata(global, fragment.push_clone("metadata"), Some(metadata))?; + } + + Ok(()) +} + +pub fn validate_input_metadata( + global: &Arc, + mut fragment: Fragment, + metadata: Option<&InputMetadata>, +) -> Result<(), Error> { + let metadata = metadata.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{} is required", fragment), + })?; + + match (metadata.static_frame_index, metadata.frame_count) { + (None, Some(frame_count)) if frame_count == 0 => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: frame_count must be non 0", fragment), + }); + } + (Some(static_frame_index), Some(frame_count)) if static_frame_index >= frame_count => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: static_frame_index must be less than frame_count, {static_frame_index} >= {frame_count}", fragment), + }); + }, + (Some(_), None) => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: is required when static_frame_index is provided", fragment.push_str("frame_count")), + }); + }, + _ => {}, + } + + match (metadata.width, metadata.height) { + (Some(width), Some(height)) => { + let checks = [ + (width, "width"), + (height, "height"), + ]; + + for (value, field) in checks { + if value == 0 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: must be non 0", fragment.push_str(field)), + }); + } + + if value > u16::MAX as u32 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: must be less than {}", fragment.push_str(field), u16::MAX), + }); + } + } + }, + (None, None) => {}, + (Some(_), None) => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: is required when width is provided", fragment.push_str("height")), + }); + }, + (None, Some(_)) => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: height is required when width is provided", fragment.push_str("width")), + }); + } + } + + Ok(()) +} + +pub fn validate_input_path( + global: &Arc, + mut fragment: Fragment, + input_path: Option<&InputPath>, +) -> Result<(), Error> { + let input_path = input_path.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{} is required", fragment.push_str("input_path")), + })?; + + match input_path { + InputPath::DrivePath(drive_path) => { + validate_drive_path(global, fragment.push_clone("input_path.drive_path"), Some(drive_path), true)?; + }, + InputPath::PublicUrl(url) => { + validate_public_url(global, fragment.push_clone("input_path.public_url"), url)?; + }, + } + + Ok(()) +} + +pub fn validate_drive_path( + global: &Arc, + mut fragment: Fragment, + drive_path: Option<&DrivePath>, + is_input: bool, +) -> Result<(), Error> { + let drive_path = drive_path.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{} is required", fragment), + })?; + + if global.drive(&drive_path.drive).is_none() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: drive not found", fragment.push_str("drive")), + }); + } + + const INPUT_PATH_ALLOWED_VARS: &[&str] = &[ + "id", + ]; + + const OUTPUT_PATH_ALLOWED_VARS: &[&str] = &[ + "id", + "scale", + "ext", + "width", + "format", + "height", + ]; + + let allowed_vars = if is_input { + INPUT_PATH_ALLOWED_VARS + } else { + OUTPUT_PATH_ALLOWED_VARS + }; + + validate_template_string(allowed_vars, &drive_path.path).map_err(|err| { + match err { + strfmt::FmtError::KeyError(key) => Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: invalid variable '{}' allowed variables {:?}", fragment.push_str("path"), key, allowed_vars), + }, + strfmt::FmtError::TypeError(_) | strfmt::FmtError::Invalid(_) => Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: invalid template syntax", fragment.push_str("path")), + }, + } + })?; + + Ok(()) +} + +pub fn validate_public_url( + global: &Arc, + mut fragment: Fragment, + url: &str, +) -> Result<(), Error> { + if url.is_empty() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{} is required", fragment), + }); + } else if global.public_http_drive().is_none() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: public http drive not found", fragment), + }); + } + + let url = Url::parse(url).map_err(|e| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: {}", fragment, e), + })?; + + if url.scheme() != "http" && url.scheme() != "https" { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: scheme must be http or https", fragment), + }); + } + + if url.host().is_none() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: host is required", fragment), + }); + } + + Ok(()) +} + +fn validate_template_string( + allowed_vars: &[&str], + template: &str, +) -> Result { + let formatter = |fmt: strfmt::Formatter| { + let k: &str = fmt.key; + if !allowed_vars.contains(&k) { + return Err(strfmt::FmtError::KeyError(k.to_owned())); + } + Ok(()) + }; + + strfmt::strfmt_map(template, formatter) +} diff --git a/image-processor/src/worker.rs b/image-processor/src/worker.rs new file mode 100644 index 00000000..d87f5fd8 --- /dev/null +++ b/image-processor/src/worker.rs @@ -0,0 +1,8 @@ +use std::sync::Arc; + +use crate::global::Global; + +pub async fn start(global: Arc) -> anyhow::Result<()> { + std::future::pending::<()>().await; + Ok(()) +} From 0a45f1708255840ccf235e3fe782ba98e99cc6eb Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Mon, 6 May 2024 04:14:11 +0000 Subject: [PATCH 11/21] wd --- foundations/src/telemetry/settings.rs | 1 - image-processor/proto/build.rs | 7 +- .../scuffle/image_processor/service.proto | 1 + .../proto/scuffle/image_processor/types.proto | 300 +++++--- image-processor/src/config.rs | 4 +- image-processor/src/disk/s3.rs | 60 +- image-processor/src/global.rs | 2 +- image-processor/src/main.rs | 3 +- image-processor/src/management/grpc.rs | 14 +- image-processor/src/management/http.rs | 93 +-- image-processor/src/management/mod.rs | 118 +--- image-processor/src/management/validation.rs | 652 +++++++++++------- image-processor/src/worker.rs | 2 +- 13 files changed, 738 insertions(+), 519 deletions(-) diff --git a/foundations/src/telemetry/settings.rs b/foundations/src/telemetry/settings.rs index 1f10a0a7..9ccabe08 100644 --- a/foundations/src/telemetry/settings.rs +++ b/foundations/src/telemetry/settings.rs @@ -7,7 +7,6 @@ use std::net::SocketAddr; use opentelemetry_otlp::WithExportConfig; #[cfg(feature = "opentelemetry")] use opentelemetry_sdk::Resource; - #[cfg(feature = "logging")] use tracing_subscriber::fmt::time::{ChronoLocal, ChronoUtc}; diff --git a/image-processor/proto/build.rs b/image-processor/proto/build.rs index 57f757ad..8143c334 100644 --- a/image-processor/proto/build.rs +++ b/image-processor/proto/build.rs @@ -27,12 +27,7 @@ fn main() -> Result<(), Box> { #[cfg(feature = "serde")] pbjson_build::Builder::new() .register_descriptors(&descriptor_set)? - .build( - &[ - ".scuffle.image_processor", - ], - )?; - + .build(&[".scuffle.image_processor"])?; Ok(()) } diff --git a/image-processor/proto/scuffle/image_processor/service.proto b/image-processor/proto/scuffle/image_processor/service.proto index 354acd40..03a532ae 100644 --- a/image-processor/proto/scuffle/image_processor/service.proto +++ b/image-processor/proto/scuffle/image_processor/service.proto @@ -8,6 +8,7 @@ import "scuffle/image_processor/types.proto"; service ImageProcessor { // Submit a task to process an image rpc ProcessImage(ProcessImageRequest) returns (ProcessImageResponse) {} + // Cancel a task rpc CancelTask(CancelTaskRequest) returns (CancelTaskResponse) {} } diff --git a/image-processor/proto/scuffle/image_processor/types.proto b/image-processor/proto/scuffle/image_processor/types.proto index bfa8528d..499a20ee 100644 --- a/image-processor/proto/scuffle/image_processor/types.proto +++ b/image-processor/proto/scuffle/image_processor/types.proto @@ -2,32 +2,30 @@ syntax = "proto3"; package scuffle.image_processor; -// When submitting a task these formats are used to determine what the image processor should do. -// If the image processor is unable to generate a requested format it will not hard fail unless the task is set to hard fail. -// Otherwise it will generate as many formats as it can and return the results with any errors in the response. -enum ImageFormat { - WEBP_ANIM = 0; - AVIF_ANIM = 1; - GIF_ANIM = 2; - WEBP_STATIC = 3; - AVIF_STATIC = 4; - PNG_STATIC = 5; +// The output format type +enum OutputFormat { + // Animated WebP format + WebpAnim = 0; + // Animated AVIF format. + AvifAnim = 1; + // Animated GIF format. + GifAnim = 2; + // Static WebP format. + WebpStatic = 3; + // Static AVIF format. + AvifStatic = 4; + // Static PNG format. + PngStatic = 5; } +// DrivePath is used to determine where the image should be stored. message DrivePath { + // The drive to locate the image. string drive = 1; + // The path in the drive. string path = 2; } -message InputPath { - oneof input_path { - // Drive path to the image. - DrivePath drive_path = 1; - // Public URL to the image. - string public_url = 2; - } -} - // The resize method determines how the image processor should resize the image. enum ResizeMethod { // Fit will resize the image to fit within the desired dimensions without changing the aspect ratio. @@ -87,13 +85,6 @@ message Limits { optional uint32 max_input_duration_ms = 5; } -message Ratio { - // The width of the ratio. - uint32 width = 1; - // The height of the ratio. - uint32 height = 2; -} - // Crop is used to determine what part of the image the image processor should crop. // The processor will crop the image before resizing it. message Crop { @@ -107,13 +98,6 @@ message Crop { uint32 height = 4; } -// Upscale is used to determine if the image processor should upscale the image. -enum Upscale { - Yes = 0; - No = 1; - NoPreserveSource = 2; -} - // Provide extra information about the input to the image processor. message InputMetadata { // If the input is not animated, this will generate a fatal error. If there are not enough frames this will generate a fatal error. @@ -122,93 +106,207 @@ message InputMetadata { // If this is different from the actual frame count the image processor will generate a fatal error. optional uint32 frame_count = 2; // If this is different from the actual width the image processor will generate a fatal error. - optional uint32 width = 3; + uint32 width = 3; // If this is different from the actual height the image processor will generate a fatal error. - optional uint32 height = 4; + uint32 height = 4; } -// Scale is used to determine what the output image size should be. -message Scale { - // The width of the output image. (in pixels, use -1 to keep the aspect ratio) - int32 width = 1; - // The height of the output image. (in pixels, use -1 to keep the aspect ratio) - int32 height = 2; - // Name of the scale. ALlows for template arguments to be passed in. - // For example if the name is "thumbnail_{width}x{height}" and the width is 100 and the height is 200 the name will be "thumbnail_100x200". - // If not set will be "{width}x{height}" - // If multiple scales have the same name the processor will generate a fatal error. - optional string name = 3; - - // Allow upscale for this scale. - // If NoPreserveSource is set and this scale is larger than the input image we will just use the source dimensions. - // If Yes, we will upscale the image. - // If No, we will ignore this scale. - Upscale upscale = 4; +// Confine the aspect ratio of the image to a specific range. +// For example if you want to allow all images that are 3:1 to 1:3 you would set min_ratio to 1/3 and max_ratio to 3. +// Setting the min and max to the same value will restrict the aspect ratio to that value. +// Setting both values to 0 will use the input image's aspect ratio. +// Setting one of the values to 0 will allow any aspect ratio that is less than or greater than the other value. +message AspectRatio { + // The minimum ratio of the image. + // Set to 0 to have no minimum ratio. + // An aspect ratio is the ratio of the width to the height of the image. + double min_ratio = 1; + // The maximum ratio of the image. + // Set to 0 to have no maximum ratio. + // An aspect ratio is the ratio of the width to the height of the image. + double max_ratio = 2; } +// InputUpload is used to upload an image to a drive configured in the image processor config. message InputUpload { // The input image as a binary blob. bytes binary = 1; - // The path to save the image to. + // A prefix to use for the folder the image will be stored in. DrivePath path = 2; } +// Input is used to determine the input image to process. message Input { // The path to the input image. - InputPath path = 1; + oneof path { + // Drive path to the image. + // The image processor will download the image from the drive. + DrivePath drive_path = 1; + // Public URL to the image. + // If public downloads is disabled this will generate a fatal error. + string public_url = 2; + } + // Extra information about the input image. - optional InputMetadata metadata = 2; + optional InputMetadata metadata = 3; +} + +// How to handle scales that are larger than the input image. +enum Upscale { + // Do not upscale the image. + UpscaleNo = 0; + // Upscale the image to the desired dimensions. + UpscaleYes = 1; + // Do not upscale the image, however if not all variants are generated due to the image being too small + // generate a single last variant that has the input image dimensions. + UpscaleNoPreserveSource = 2; } -message OutputFormat { - message Webp { - bool static = 1; +// Scaling is used to specify a linear scaling factor for the various dimensions of the image. +// For example, if you have an image that is 100x100 (and use this as the base) and you want to generate 1x, 2x, and 3x images you would set the scales to [1, 2, 3]. +// The sizes of the output images would be [100x100, 200x200, 300x300]. +message Scaling { + oneof base { + // This is the scale for the input image (after cropping or aspect ratio adjustments are made). + uint32 fixed = 1; + // This is used to automatically determine the scale of the input image based on the width. + // We know what aspect ratio to use based on the aspect ratio adjustments made to the input image. + // We can then use that to determine the (input_width / base_width) scale. + // The scale would be the largest integer that is less than or equal to (input_width / base_width), + // or 1 if the input width is less than base_width. + uint32 base_width = 2; + // Functionally the same as base_width but allows you to specify in terms of height instead. + uint32 base_height = 3; } - message Avif { - bool static = 1; + + // The various scales. + // For example to generate a 1x, 2x, and 3x image you would set scales to [1, 2, 3]. + IntegerList scales = 4; +} + +// A list of integers. +message IntegerList { + repeated uint32 values = 1; +} + +message AnimationConfig { + // Specify an animation loop count for animated images. + // If this is set to -1 the image will loop indefinitely. + // If this is set to 0 the image will not loop. + // If this is set to a positive number the image will loop that many times. + // If this is unset the image will be encoded with the loop value the input image has. + optional int32 loop_count = 1; + + oneof frame_rate { + // Specify the frame duration for every frame in the output image. + // This can be used to specify a constant frame rate for the output image. + // frame_rate = 1000 / frame_duration_ms + uint32 duration_ms = 2; + // Frame durations for each frame in the output image. + // Specify the frame duration for each frame in the output image. + // If this number does not match the number of frames in the output image the processor will generate a fatal error. + IntegerList durations_ms = 3; + // Factor to multiply the frame duration by. + // This can be used to speed up or slow down the animation. + // The frame duration will be multiplied by this value. + // Each frame has a minimum duration of 1ms, if the factor creates some frames that are less than 1ms the processor will, + // drop frames and adjust timings of others to ensure that the total duration of the animation is correctly multiplied. + // This rule only applies for when the factor is greater than 1. + double factor = 4; } - message Gif {} - message Png {} - - // The name is used in the template argument for the output path. - // By default the name is the same as the format. - // Webp (static) -> webp_static - // Webp (animated) -> webp_animated - // Avif (static) -> avif_static - // Avif (animated) -> avif_animated - // Gif -> gif - // Png -> png - string name = 1; - oneof format { - Webp webp = 2; - Avif avif = 3; - Gif gif = 4; - Png png = 5; + // Remove frames idx's from the input image. + // This can be used to reduce the size of the output image. + // If you specify an index that is out of bounds the processor will generate a fatal error. + // If you specify the same index multiple times the processor will ignore the duplicates. + repeated uint32 remove_frame_idxs = 5; +} + +enum OutputQuality { + // Auto quality output. (default) + Auto = 0; + // High quality output. (large file size) + High = 1; + // Medium quality output. (medium file size) + Medium = 2; + // Low quality output. (smaller file size) + Low = 3; + // Lossless output. (very large file size) + Lossless = 4; +} + +message OutputFormatOptions { + // The format of the output image. + OutputFormat format = 1; + // The quality of the output image. + OutputQuality quality = 2; +} + +message OutputVariants { + // A suffix that will be appended to the output.drive_path.path to create the output path. + // This suffix is special as it allows for template arguments. + // Possible template argument values are: + // - {id} - The id of the task. + // - {format} - The format of the output image. (e.g 'webp_anim', 'avif_static', 'png_static', etc.) + // - {scale} - The scale of the output image. (if scaling is used, otherwise empty) + // - {width} - The resulting width of the output image. + // - {height} - The resulting height of the output image. + // - {format_idx} - The index of the output format in the list. + // - {resize_idx} - The index of the resize operation, if the operation is width or height its the index of the value in the list. + // If its scaling its the index of the scale in the list. + // - {static} - '_static' if the input image is static, otherwise empty. + // - {ext} - The extension of the output image. (e.g. 'webp', 'avif', etc.) + string suffix = 1; + + // The desired format to encode the output image. + repeated OutputFormatOptions formats = 2; + + // Allow upscaling if the determined dimensions are larger than the input image. + Upscale upscale = 3; + + // Sometimes we might specify that we want 'WebpAnim' but the input image is a static image. + // In this case we would typically fatally error because we can't generate an animated image from a static image. + // However if this is set to true the processor will ignore these errors and skip the format. + bool skip_impossible_formats = 4; + + // Skips resizing if the resize operation is impossible. + // For example if the resize results in a width or height of less than 1. + // If this is set to true the processor will ignore these errors and skip the resize operation. + bool skip_impossible_resizes = 5; + + // There must be at least one element in the list. + oneof resize { + // Resize to a specific width, the height will be determined by the aspect ratio. + IntegerList width = 6; + // Resize to a specific height, the width will be determined by the aspect ratio. + IntegerList height = 7; + // A scaling config to determine how each dimension should be scaled. + Scaling scaling = 8; } } message Output { - DrivePath path = 1; + // The drive path to store the output image. + // This is a prefix and the processor will append the suffix to this path to determine the final path. + // The suffix is defined in the OutputVariantsConfig. + DrivePath drive_path = 1; + // The desired formats to encode the output image. - repeated OutputFormat formats = 2; + OutputVariants variants = 2; // The resize method used to resize the image. ResizeMethod resize_method = 3; // The resize algorithm used to resize the image. ResizeAlgorithm resize_algorithm = 4; - // The crop used to crop the image before resizing. If the crop settings are not possible the processor will generate a fatal error. - optional Crop crop = 5; - // The minimum and maximum ratios for the scaled image. Used to prevent upscaling too much on wide or tall images. - // If the image does not fit into the min and max ratios the processor will generate a fatal error. If unset the processor will not check the ratios. - // These checks are done after the crop. If the resize method allows for padding or stretching we will use the padded or stretched dimentions to perform the check. - // If scales are provided that are not within the min and max ratios the processor will generate a fatal error. - optional Ratio min_ratio = 6; - optional Ratio max_ratio = 7; - // The target ratio for the scale image, if unset the processor will use the input ratio (after crop but before resize). - // The min and max ratios will be used to detemine if padding or stretching is needed to reach the target ratio. - optional Ratio target_ratio = 8; - // The desired scales of the output image. - repeated Scale scales = 9; + + // The animation configuration for the output image. + optional AnimationConfig animation_config = 5; + + // A crop is applied to the image before resizing and before an aspect ratio change.s + optional Crop crop = 6; + + // Choose one of the following options to determine the aspect ratio of the image. + // An aspect ratio is the ratio of the width to the height of the image. + AspectRatio aspect_ratio = 7; } // Events must be in the format @@ -218,29 +316,37 @@ message Events { // The event to trigger when the task is completed successfully optional EventQueue on_success = 1; // The event to trigger when the task fails - optional EventQueue on_fail = 2; + optional EventQueue on_failure = 2; // The event to trigger when the task is cancelled optional EventQueue on_cancel = 3; // The event to trigger when the task is started optional EventQueue on_start = 4; + + // Metadata to send with the event. + map metadata = 5; } +// EventQueue is used to determine where the image processor should send events. message EventQueue { + // The name of the event queue. string name = 1; + // The topic of the event queue. string topic = 2; } +// A task to process an image. message Task { // The input image to process. Input input = 1; // The output image to generate. - Output output = 2; + Output output = 2; // Result output Events events = 3; // The limits for the image processor. optional Limits limits = 4; } +// A error message. message Error { // The error message. string message = 1; @@ -248,10 +354,12 @@ message Error { ErrorCode code = 2; } +// ErrorCode is used to determine the type of error that occurred. enum ErrorCode { - Unknown = 0; + // Internal error occurred, please file a bug report. + InternalError = 0; + // Invalid input error. Please refer to the error message for more information, and resubmit the task with valid input. InvalidInput = 1; - InternalError = 2; } message EventPayload { diff --git a/image-processor/src/config.rs b/image-processor/src/config.rs index 08bd014d..768ce588 100644 --- a/image-processor/src/config.rs +++ b/image-processor/src/config.rs @@ -119,10 +119,10 @@ pub struct S3DriveConfig { pub endpoint: Option, /// The S3 bucket prefix path #[serde(default)] - pub path: Option, + pub prefix_path: Option, /// Use path style #[serde(default)] - pub path_style: bool, + pub force_path_style: Option, /// The drive mode #[serde(default)] pub mode: DriveMode, diff --git a/image-processor/src/disk/s3.rs b/image-processor/src/disk/s3.rs index aaa9987d..810fb6eb 100644 --- a/image-processor/src/disk/s3.rs +++ b/image-processor/src/disk/s3.rs @@ -1,4 +1,4 @@ -use aws_config::{AppName, Region, SdkConfig}; +use aws_config::{AppName, Region}; use aws_sdk_s3::config::{Credentials, SharedCredentialsProvider}; use aws_sdk_s3::operation::delete_object::DeleteObjectError; use aws_sdk_s3::operation::get_object::GetObjectError; @@ -17,6 +17,8 @@ pub struct S3Drive { mode: DriveMode, client: aws_sdk_s3::Client, bucket: String, + path_prefix: Option, + semaphore: Option, } #[derive(Debug, thiserror::Error)] @@ -38,15 +40,19 @@ impl S3Drive { pub async fn new(config: &S3DriveConfig) -> Result { tracing::debug!("setting up s3 disk"); Ok(Self { - name: config.name.clone(), + name: config.bucket.clone(), mode: config.mode, - client: aws_sdk_s3::Client::new(&{ - let mut builder = SdkConfig::builder(); + client: aws_sdk_s3::Client::from_conf({ + let mut builder = aws_sdk_s3::Config::builder(); + + builder.set_endpoint_url(config.endpoint.clone()); builder.set_app_name(Some(AppName::new(service_info!().name).unwrap())); builder.set_region(Some(Region::new(config.region.clone()))); + builder.set_force_path_style(config.force_path_style); + builder.set_credentials_provider(Some(SharedCredentialsProvider::new(Credentials::new( config.access_key.clone(), config.secret_key.clone(), @@ -57,7 +63,9 @@ impl S3Drive { builder.build() }), + path_prefix: config.prefix_path.clone(), bucket: config.bucket.clone(), + semaphore: config.max_connections.map(tokio::sync::Semaphore::new), }) } } @@ -73,11 +81,22 @@ impl Drive for S3Drive { return Err(DriveError::ReadOnly); } + let _permit = if let Some(semaphore) = &self.semaphore { + Some(semaphore.acquire().await) + } else { + None + }; + + let path = self + .path_prefix + .as_ref() + .map_or_else(|| path.to_string(), |prefix| format!("{}/{}", prefix, path)); + let result = self .client .get_object() .bucket(&self.bucket) - .key(path) + .key(path.trim_start_matches('/')) .send() .await .map_err(S3DriveError::from)?; @@ -93,7 +112,23 @@ impl Drive for S3Drive { return Err(DriveError::WriteOnly); } - let mut req = self.client.put_object().bucket(&self.bucket).key(path).body(data.into()); + let _permit = if let Some(semaphore) = &self.semaphore { + Some(semaphore.acquire().await) + } else { + None + }; + + let path = self + .path_prefix + .as_ref() + .map_or_else(|| path.to_string(), |prefix| format!("{}/{}", prefix, path)); + + let mut req = self + .client + .put_object() + .bucket(&self.bucket) + .key(path.trim_start_matches('/')) + .body(data.into()); if let Some(options) = options { if let Some(cache_control) = &options.cache_control { @@ -121,10 +156,21 @@ impl Drive for S3Drive { return Err(DriveError::WriteOnly); } + let _permit = if let Some(semaphore) = &self.semaphore { + Some(semaphore.acquire().await) + } else { + None + }; + + let path = self + .path_prefix + .as_ref() + .map_or_else(|| path.to_string(), |prefix| format!("{}/{}", prefix, path)); + self.client .delete_object() .bucket(&self.bucket) - .key(path) + .key(path.trim_start_matches('/')) .send() .await .map_err(S3DriveError::from)?; diff --git a/image-processor/src/global.rs b/image-processor/src/global.rs index 1ade9418..b707d728 100644 --- a/image-processor/src/global.rs +++ b/image-processor/src/global.rs @@ -129,7 +129,7 @@ impl HealthCheck for Global { return false; } - for disk in self.drive().values() { + for disk in self.drives().values() { if !disk.healthy().await { tracing::error!(name = %disk.name(), "disk check failed"); return false; diff --git a/image-processor/src/main.rs b/image-processor/src/main.rs index fa4c738b..fbca385e 100644 --- a/image-processor/src/main.rs +++ b/image-processor/src/main.rs @@ -2,8 +2,8 @@ use std::sync::Arc; use anyhow::Context; use scuffle_foundations::bootstrap::{bootstrap, Bootstrap}; -use scuffle_foundations::settings::cli::Matches; use scuffle_foundations::runtime; +use scuffle_foundations::settings::cli::Matches; use scuffle_image_processor_proto::{event_callback, EventCallback}; use tokio::signal::unix::SignalKind; @@ -97,6 +97,5 @@ async fn main(cfg: Matches) { } } - std::process::exit(0); } diff --git a/image-processor/src/management/grpc.rs b/image-processor/src/management/grpc.rs index 5b522dc4..7a83ed83 100644 --- a/image-processor/src/management/grpc.rs +++ b/image-processor/src/management/grpc.rs @@ -17,10 +17,7 @@ impl ManagementServer { #[async_trait::async_trait] impl scuffle_image_processor_proto::image_processor_server::ImageProcessor for ManagementServer { - async fn process_image( - &self, - request: Request, - ) -> tonic::Result> { + async fn process_image(&self, request: Request) -> tonic::Result> { let resp = match self.process_image(request.into_inner()).await { Ok(resp) => resp, Err(err) => ProcessImageResponse { @@ -32,15 +29,10 @@ impl scuffle_image_processor_proto::image_processor_server::ImageProcessor for M Ok(Response::new(resp)) } - async fn cancel_task( - &self, - request: Request, - ) -> tonic::Result> { + async fn cancel_task(&self, request: Request) -> tonic::Result> { let resp = match self.cancel_task(request.into_inner()).await { Ok(resp) => resp, - Err(err) => CancelTaskResponse { - error: Some(err), - }, + Err(err) => CancelTaskResponse { error: Some(err) }, }; Ok(Response::new(resp)) diff --git a/image-processor/src/management/http.rs b/image-processor/src/management/http.rs index 298a590a..54cc2b7e 100644 --- a/image-processor/src/management/http.rs +++ b/image-processor/src/management/http.rs @@ -1,59 +1,66 @@ -use scuffle_foundations::http::server::axum::{extract::State, Json, Router, routing::post}; -use scuffle_image_processor_proto::{CancelTaskRequest, CancelTaskResponse, ErrorCode, ProcessImageRequest, ProcessImageResponse}; +use scuffle_foundations::http::server::axum::extract::State; +use scuffle_foundations::http::server::axum::routing::post; +use scuffle_foundations::http::server::axum::{Json, Router}; +use scuffle_image_processor_proto::{ + CancelTaskRequest, CancelTaskResponse, ErrorCode, ProcessImageRequest, ProcessImageResponse, +}; use super::ManagementServer; impl ManagementServer { pub async fn run_http(&self) -> Result<(), scuffle_foundations::http::server::Error> { - let router = Router::new() - .route("/process_image", post(process_image)) - .route("/cancel_task", post(cancel_task)) - .with_state(self.clone()); - - let addr = self.global.config().management.http.bind; - scuffle_foundations::http::server::Server::builder() - .bind(addr) - .build(router)? - .start_and_wait() - .await - } + let router = Router::new() + .route("/process_image", post(process_image)) + .route("/cancel_task", post(cancel_task)) + .with_state(self.clone()); + + let addr = self.global.config().management.http.bind; + scuffle_foundations::http::server::Server::builder() + .bind(addr) + .build(router)? + .start_and_wait() + .await + } } async fn process_image( - State(server): State, - Json(request): Json, + State(server): State, + Json(request): Json, ) -> (http::StatusCode, Json) { - let resp = match server.process_image(request).await { - Ok(resp) => resp, - Err(err) => ProcessImageResponse { - id: "".to_owned(), - error: Some(err), - } - }; - - let status = resp.error.as_ref().map_or(http::StatusCode::OK, |err| map_error_code(err.code())); - (status, Json(resp)) + let resp = match server.process_image(request).await { + Ok(resp) => resp, + Err(err) => ProcessImageResponse { + id: "".to_owned(), + error: Some(err), + }, + }; + + let status = resp + .error + .as_ref() + .map_or(http::StatusCode::OK, |err| map_error_code(err.code())); + (status, Json(resp)) } async fn cancel_task( - State(server): State, - Json(request): Json, + State(server): State, + Json(request): Json, ) -> (http::StatusCode, Json) { - let resp = match server.cancel_task(request).await { - Ok(resp) => resp, - Err(err) => CancelTaskResponse { - error: Some(err), - } - }; - - let status = resp.error.as_ref().map_or(http::StatusCode::OK, |err| map_error_code(err.code())); - (status, Json(resp)) + let resp = match server.cancel_task(request).await { + Ok(resp) => resp, + Err(err) => CancelTaskResponse { error: Some(err) }, + }; + + let status = resp + .error + .as_ref() + .map_or(http::StatusCode::OK, |err| map_error_code(err.code())); + (status, Json(resp)) } fn map_error_code(code: ErrorCode) -> http::StatusCode { - match code { - ErrorCode::InvalidInput => http::StatusCode::BAD_REQUEST, - ErrorCode::InternalError => http::StatusCode::INTERNAL_SERVER_ERROR, - ErrorCode::Unknown => http::StatusCode::INTERNAL_SERVER_ERROR, - } -} \ No newline at end of file + match code { + ErrorCode::InvalidInput => http::StatusCode::BAD_REQUEST, + ErrorCode::InternalError => http::StatusCode::INTERNAL_SERVER_ERROR, + } +} diff --git a/image-processor/src/management/mod.rs b/image-processor/src/management/mod.rs index 897c0f70..702f6659 100644 --- a/image-processor/src/management/mod.rs +++ b/image-processor/src/management/mod.rs @@ -1,10 +1,13 @@ use std::sync::Arc; use anyhow::Context; -use scuffle_image_processor_proto::{CancelTaskRequest, CancelTaskResponse, Error, ErrorCode, ProcessImageRequest, ProcessImageResponse, input_path::InputPath}; +use scuffle_image_processor_proto::{ + CancelTaskRequest, CancelTaskResponse, Error, ErrorCode, ProcessImageRequest, ProcessImageResponse, +}; use url::Url; use crate::global::Global; +use crate::management::validation::{validate_task, Fragment, FragmentBuf}; pub mod grpc; pub mod http; @@ -13,96 +16,45 @@ mod validation; #[derive(Clone)] struct ManagementServer { - global: Arc, + global: Arc, } impl ManagementServer { - async fn process_image( - &self, - request: ProcessImageRequest, - ) -> Result { - let Some(task) = request.task.as_ref() else { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: "task is required".to_string(), - }); - }; + async fn process_image(&self, request: ProcessImageRequest) -> Result { + let mut fragment = FragmentBuf::new(); - let Some(input) = &task.input else { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: "input is required".to_string(), - }); - }; + validate_task(&self.global, fragment.push("task"), request.task.as_ref())?; - let Some(input_path) = input.path.as_ref().and_then(|path| path.input_path.as_ref()) else { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: "task.input.path is required".to_string(), - }); - }; + // We need to do validation here. + if let Some(input_upload) = request.input_upload.as_ref() {} - + todo!() + } - if let Some(events) = &task.events { - let queues = [ - (&events.on_success, "task.events.on_success"), - (&events.on_fail, "task.events.on_fail"), - (&events.on_start, "task.events.on_start"), - (&events.on_cancel, "task.events.on_cancel"), - ]; - - for (queue, field) in queues { - if let Some(queue) = queue { - if self.global.event_queue(&queue.name).is_none() { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{field}.name: event queue not found"), - }); - } - } - } - } - - // We need to do validation here. - if let Some(image) = request.input_upload.as_ref() { - - } - - todo!() - } - - async fn cancel_task( - &self, - request: CancelTaskRequest, - ) -> Result { - todo!() - } + async fn cancel_task(&self, request: CancelTaskRequest) -> Result { + todo!() + } } - pub async fn start(global: Arc) -> anyhow::Result<()> { - let server = ManagementServer { - global, - }; - - let http = async { - if server.global.config().management.http.enabled { - server.run_http().await.context("http") - } else { - Ok(()) - } - }; - let grpc = async { - if server.global.config().management.grpc.enabled { - server.run_grpc().await.context("grpc") - } else { - Ok(()) - } - }; - - futures::future::try_join(http, grpc).await.context("management")?; - - Ok(()) + let server = ManagementServer { global }; + + let http = async { + if server.global.config().management.http.enabled { + server.run_http().await.context("http") + } else { + Ok(()) + } + }; + let grpc = async { + if server.global.config().management.grpc.enabled { + server.run_grpc().await.context("grpc") + } else { + Ok(()) + } + }; + + futures::future::try_join(http, grpc).await.context("management")?; + + Ok(()) } - diff --git a/image-processor/src/management/validation.rs b/image-processor/src/management/validation.rs index e8bf8150..dc85ea38 100644 --- a/image-processor/src/management/validation.rs +++ b/image-processor/src/management/validation.rs @@ -1,307 +1,427 @@ use std::sync::Arc; -use scuffle_image_processor_proto::{input_path::InputPath, DrivePath, Error, ErrorCode, Input, InputMetadata, Output, Task}; +use scuffle_image_processor_proto::{ + input, DrivePath, Error, ErrorCode, EventQueue, Events, Input, InputMetadata, Limits, Output, OutputVariants, Task, +}; use url::Url; use crate::global::Global; +#[derive(Debug, Clone, Copy)] +pub enum FragmentItem { + Map(&'static str), + Index(usize), +} -#[derive(Debug, Default, Clone)] -pub struct Fragment { - path: Vec<&'static str>, +#[derive(Debug)] +pub struct FragmentBuf { + path: Vec, } -impl std::fmt::Display for Fragment { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.path.join(".")) - } +impl FragmentBuf { + pub fn new() -> Self { + Self { path: Vec::new() } + } + + pub fn push<'a>(&'a mut self, path: impl Into) -> Fragment<'a> { + self.path.push(path.into()); + Fragment::new(&mut self.path) + } + + pub fn as_fagment(&mut self) -> Fragment { + Fragment::new(&mut self.path) + } } -impl Fragment { - pub fn new() -> Self { - Self::default() - } - - pub fn push(&mut self, path: &'static str) { - self.path.push(path); - } - - pub fn pop(&mut self) { - self.path.pop(); - } - - pub fn push_clone(&self, path: &'static str) -> Self { - let mut fragment = self.clone(); - fragment.push(path); - fragment - } - - pub fn push_str(&self, path: &str) -> String { - let mut builder = self.path.join("."); - if !builder.is_empty() { - builder.push('.'); - } - builder.push_str(path); - builder - } +#[derive(Debug)] +pub struct Fragment<'a> { + path: &'a mut Vec, } -pub fn validate_task( - global: &Arc, - mut fragment: Fragment, - task: Option<&Task>, -) -> Result<(), Error> { - let task = task.ok_or_else(|| Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{fragment} is required"), - })?; +impl<'a> Fragment<'a> { + pub fn new(path: &'a mut Vec) -> Self { + Self { path } + } +} - validate_input(global, fragment.push_clone("input"), task.input.as_ref())?; +impl From<&'static str> for FragmentItem { + fn from(value: &'static str) -> Self { + Self::Map(value) + } +} - validate_output(global, fragment.push_clone("output"), task.output.as_ref())?; +impl From for FragmentItem { + fn from(value: usize) -> Self { + Self::Index(value) + } +} - Ok(()) +// This is a bit of a hack to allow us to convert from a reference to a copy. +// &&'static str -> &'static str -> FragmentItem +// &usize -> usize -> FragmentItem +impl From<&T> for FragmentItem + where + T: Copy, + FragmentItem: From, +{ + fn from(value: &T) -> Self { + Self::from(*value) + } } -pub fn validate_output( - global: &Arc, - mut fragment: Fragment, - output: Option<&Output>, -) -> Result<(), Error> { - let output = output.ok_or_else(|| Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{fragment} is required"), - })?; +impl std::fmt::Display for Fragment<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut first = true; + for item in self.path.iter() { + match item { + FragmentItem::Map(value) => { + if first { + write!(f, ".")?; + } + write!(f, "{value}")?; + } + FragmentItem::Index(value) => { + write!(f, "[{value}]")?; + } + } + + first = false; + } + + Ok(()) + } +} - validate_drive_path(global, fragment.push_clone("path"), output.path.as_ref(), false)?; - +impl Fragment<'_> { + pub fn push<'a>(&'a mut self, path: impl Into) -> Fragment<'a> { + self.path.push(path.into()); + Fragment::new(self.path) + } +} - Ok(()) +impl Drop for Fragment<'_> { + fn drop(&mut self) { + self.path.pop(); + } } -pub fn validate_input( - global: &Arc, - mut fragment: Fragment, - input: Option<&Input> -) -> Result<(), Error> { - let input = input.ok_or_else(|| Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{fragment} is required"), - })?; +pub fn validate_task(global: &Arc, mut fragment: Fragment, task: Option<&Task>) -> Result<(), Error> { + let task = task.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + validate_input(global, fragment.push("input"), task.input.as_ref())?; - let path = input.path.as_ref().ok_or_else(|| Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{} is required", fragment.push_str("path")), - })?; + validate_output(global, fragment.push("output"), task.output.as_ref())?; - validate_input_path(global, fragment.push_clone("path"), path.input_path.as_ref())?; + validate_events(global, fragment.push("events"), task.events.as_ref())?; - // Metadata is optional - if let Some(metadata) = &input.metadata { - validate_input_metadata(global, fragment.push_clone("metadata"), Some(metadata))?; - } + if let Some(limits) = &task.limits { + validate_limits(fragment.push("limits"), Some(limits))?; + } - Ok(()) + Ok(()) } -pub fn validate_input_metadata( - global: &Arc, - mut fragment: Fragment, - metadata: Option<&InputMetadata>, -) -> Result<(), Error> { - let metadata = metadata.ok_or_else(|| Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{} is required", fragment), - })?; - - match (metadata.static_frame_index, metadata.frame_count) { - (None, Some(frame_count)) if frame_count == 0 => { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: frame_count must be non 0", fragment), - }); - } - (Some(static_frame_index), Some(frame_count)) if static_frame_index >= frame_count => { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: static_frame_index must be less than frame_count, {static_frame_index} >= {frame_count}", fragment), - }); - }, - (Some(_), None) => { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: is required when static_frame_index is provided", fragment.push_str("frame_count")), - }); - }, - _ => {}, - } - - match (metadata.width, metadata.height) { - (Some(width), Some(height)) => { - let checks = [ - (width, "width"), - (height, "height"), - ]; - - for (value, field) in checks { - if value == 0 { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: must be non 0", fragment.push_str(field)), - }); - } - - if value > u16::MAX as u32 { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: must be less than {}", fragment.push_str(field), u16::MAX), - }); - } - } - }, - (None, None) => {}, - (Some(_), None) => { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: is required when width is provided", fragment.push_str("height")), - }); - }, - (None, Some(_)) => { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: height is required when width is provided", fragment.push_str("width")), - }); - } - } - - Ok(()) +fn validate_limits(mut fragment: Fragment, limits: Option<&Limits>) -> Result<(), Error> { + let limits = limits.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + let fields = [ + (limits.max_processing_time_ms, "max_processing_time_ms"), + (limits.max_input_frame_count, "max_input_frame_count"), + (limits.max_input_width, "max_input_width"), + (limits.max_input_height, "max_input_height"), + (limits.max_input_duration_ms, "max_input_duration_ms"), + ]; + + for (value, name) in &fields { + if let Some(0) = value { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: must be non 0", fragment.push(name)), + }); + } + } + + Ok(()) +} + +fn validate_events(global: &Arc, mut fragment: Fragment, events: Option<&Events>) -> Result<(), Error> { + let events = events.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + let events = [ + (events.on_success.as_ref(), "on_success"), + (events.on_failure.as_ref(), "on_failure"), + (events.on_cancel.as_ref(), "on_cancel"), + (events.on_start.as_ref(), "on_start"), + ]; + + for (event, name) in &events { + if let Some(event) = event { + validate_event_queue(global, fragment.push(name), Some(event))?; + } + } + + Ok(()) +} + +fn validate_event_queue(global: &Arc, mut fragment: Fragment, event: Option<&EventQueue>) -> Result<(), Error> { + let event_queue = event.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + if event_queue.name.is_empty() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: is required", fragment.push("name")), + }); + } + + if event_queue.topic.is_empty() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: is required", fragment.push("topic")), + }); + } + + if global.event_queue(&event_queue.name).is_none() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: event queue not found"), + }); + } + + Ok(()) +} + +pub fn validate_output(global: &Arc, mut fragment: Fragment, output: Option<&Output>) -> Result<(), Error> { + let output = output.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + validate_drive_path(global, fragment.push("path"), output.drive_path.as_ref())?; + + validate_output_variants(fragment.push("variants"), output.variants.as_ref())?; + + Ok(()) +} + +pub fn validate_output_variants(mut fragment: Fragment, variants: Option<&OutputVariants>) -> Result<(), Error> { + let variants = variants.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + validate_template_string( + fragment.push("suffix"), + &[ + "id", + "format", + "scale", + "width", + "height", + "format_idx", + "resize_idx", + "static", + "ext", + ], + &variants.suffix, + )?; + + if variants.formats.is_empty() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: is required", fragment.push("formats")), + }); + } + + for (idx, format) in variants.formats.iter().enumerate() {} + + Ok(()) +} + +pub fn validate_input(global: &Arc, mut fragment: Fragment, input: Option<&Input>) -> Result<(), Error> { + let input = input.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + validate_input_path(global, fragment.push("path"), input.path.as_ref())?; + + // Metadata is optional + if let Some(metadata) = &input.metadata { + validate_input_metadata(fragment.push("metadata"), Some(metadata))?; + } + + Ok(()) +} + +pub fn validate_input_metadata(mut fragment: Fragment, metadata: Option<&InputMetadata>) -> Result<(), Error> { + let metadata = metadata.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{} is required", fragment), + })?; + + match (metadata.static_frame_index, metadata.frame_count) { + (None, Some(frame_count)) if frame_count == 0 => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: frame_count must be non 0", fragment), + }); + } + (Some(static_frame_index), Some(frame_count)) if static_frame_index >= frame_count => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!( + "{}: static_frame_index must be less than frame_count, {static_frame_index} >= {frame_count}", + fragment + ), + }); + } + (Some(_), None) => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!( + "{}: is required when static_frame_index is provided", + fragment.push("frame_count") + ), + }); + } + _ => {} + } + + if metadata.width == 0 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: width must be non 0", fragment.push("width")), + }); + } + + if metadata.height == 0 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: height must be non 0", fragment.push("height")), + }); + } + + Ok(()) } pub fn validate_input_path( - global: &Arc, - mut fragment: Fragment, - input_path: Option<&InputPath>, + global: &Arc, + mut fragment: Fragment, + input_path: Option<&input::Path>, ) -> Result<(), Error> { - let input_path = input_path.ok_or_else(|| Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{} is required", fragment.push_str("input_path")), - })?; - - match input_path { - InputPath::DrivePath(drive_path) => { - validate_drive_path(global, fragment.push_clone("input_path.drive_path"), Some(drive_path), true)?; - }, - InputPath::PublicUrl(url) => { - validate_public_url(global, fragment.push_clone("input_path.public_url"), url)?; - }, - } - - Ok(()) + let input_path = input_path.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{} is required", fragment), + })?; + + match input_path { + input::Path::DrivePath(drive_path) => { + validate_drive_path(global, fragment.push("drive_path"), Some(drive_path))?; + } + input::Path::PublicUrl(url) => { + validate_public_url(global, fragment.push("public_url"), url)?; + } + } + + Ok(()) } pub fn validate_drive_path( - global: &Arc, - mut fragment: Fragment, - drive_path: Option<&DrivePath>, - is_input: bool, + global: &Arc, + mut fragment: Fragment, + drive_path: Option<&DrivePath>, ) -> Result<(), Error> { - let drive_path = drive_path.ok_or_else(|| Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{} is required", fragment), - })?; - - if global.drive(&drive_path.drive).is_none() { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: drive not found", fragment.push_str("drive")), - }); - } - - const INPUT_PATH_ALLOWED_VARS: &[&str] = &[ - "id", - ]; - - const OUTPUT_PATH_ALLOWED_VARS: &[&str] = &[ - "id", - "scale", - "ext", - "width", - "format", - "height", - ]; - - let allowed_vars = if is_input { - INPUT_PATH_ALLOWED_VARS - } else { - OUTPUT_PATH_ALLOWED_VARS - }; - - validate_template_string(allowed_vars, &drive_path.path).map_err(|err| { - match err { - strfmt::FmtError::KeyError(key) => Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: invalid variable '{}' allowed variables {:?}", fragment.push_str("path"), key, allowed_vars), - }, - strfmt::FmtError::TypeError(_) | strfmt::FmtError::Invalid(_) => Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: invalid template syntax", fragment.push_str("path")), - }, - } - })?; - - Ok(()) + let drive_path = drive_path.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{} is required", fragment), + })?; + + if global.drive(&drive_path.drive).is_none() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: drive not found", fragment.push("drive")), + }); + } + + validate_template_string(fragment.push("path"), &["id"], &drive_path.path)?; + + Ok(()) } -pub fn validate_public_url( - global: &Arc, - mut fragment: Fragment, - url: &str, -) -> Result<(), Error> { - if url.is_empty() { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{} is required", fragment), - }); - } else if global.public_http_drive().is_none() { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: public http drive not found", fragment), - }); - } - - let url = Url::parse(url).map_err(|e| Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: {}", fragment, e), - })?; - - if url.scheme() != "http" && url.scheme() != "https" { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: scheme must be http or https", fragment), - }); - } - - if url.host().is_none() { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: host is required", fragment), - }); - } - - Ok(()) +pub fn validate_public_url(global: &Arc, fragment: Fragment, url: &str) -> Result<(), Error> { + if url.is_empty() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + }); + } else if global.public_http_drive().is_none() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: public http drive not found"), + }); + } + + let url = Url::parse(url).map_err(|e| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: {e}"), + })?; + + if url.scheme() != "http" && url.scheme() != "https" { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: scheme must be http or https"), + }); + } + + if url.host().is_none() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: url host is required"), + }); + } + + Ok(()) } -fn validate_template_string( - allowed_vars: &[&str], - template: &str, -) -> Result { - let formatter = |fmt: strfmt::Formatter| { - let k: &str = fmt.key; - if !allowed_vars.contains(&k) { - return Err(strfmt::FmtError::KeyError(k.to_owned())); - } - Ok(()) - }; - - strfmt::strfmt_map(template, formatter) +fn validate_template_string(fragment: Fragment, allowed_vars: &[&str], template: &str) -> Result { + if template.is_empty() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + }); + } + + let formatter = |fmt: strfmt::Formatter| { + let k: &str = fmt.key; + if !allowed_vars.contains(&k) { + return Err(strfmt::FmtError::KeyError(k.to_owned())); + } + Ok(()) + }; + + strfmt::strfmt_map(template, formatter).map_err(|err| match err { + strfmt::FmtError::KeyError(key) => Error { + code: ErrorCode::InvalidInput as i32, + message: format!( + "{fragment}: invalid variable '{key}', the allowed variables are {:?}", + allowed_vars + ), + }, + strfmt::FmtError::TypeError(_) | strfmt::FmtError::Invalid(_) => Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: invalid template syntax"), + }, + }) } diff --git a/image-processor/src/worker.rs b/image-processor/src/worker.rs index d87f5fd8..dde504b3 100644 --- a/image-processor/src/worker.rs +++ b/image-processor/src/worker.rs @@ -3,6 +3,6 @@ use std::sync::Arc; use crate::global::Global; pub async fn start(global: Arc) -> anyhow::Result<()> { - std::future::pending::<()>().await; + std::future::pending::<()>().await; Ok(()) } From cc323a64b3b3b00cd173e5d6b5e27fd27b2cbee3 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Mon, 6 May 2024 04:18:38 +0000 Subject: [PATCH 12/21] wd --- .../proto/scuffle/image_processor/types.proto | 4 ++++ image-processor/src/management/validation.rs | 10 +++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/image-processor/proto/scuffle/image_processor/types.proto b/image-processor/proto/scuffle/image_processor/types.proto index 499a20ee..58b930f3 100644 --- a/image-processor/proto/scuffle/image_processor/types.proto +++ b/image-processor/proto/scuffle/image_processor/types.proto @@ -23,6 +23,8 @@ message DrivePath { // The drive to locate the image. string drive = 1; // The path in the drive. + // Possible template argument values are: + // - {id} - The id of the task. string path = 2; } @@ -331,6 +333,8 @@ message EventQueue { // The name of the event queue. string name = 1; // The topic of the event queue. + // Possible template argument values are: + // - {id} - The id of the task. string topic = 2; } diff --git a/image-processor/src/management/validation.rs b/image-processor/src/management/validation.rs index dc85ea38..bef25a83 100644 --- a/image-processor/src/management/validation.rs +++ b/image-processor/src/management/validation.rs @@ -185,13 +185,6 @@ fn validate_event_queue(global: &Arc, mut fragment: Fragment, event: Opt }); } - if event_queue.topic.is_empty() { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: is required", fragment.push("topic")), - }); - } - if global.event_queue(&event_queue.name).is_none() { return Err(Error { code: ErrorCode::InvalidInput as i32, @@ -199,6 +192,9 @@ fn validate_event_queue(global: &Arc, mut fragment: Fragment, event: Opt }); } + // Validate the topic template string + validate_template_string(fragment.push("topic"), &["id"], &event_queue.topic)?; + Ok(()) } From d7b3f64151b3aaecf482ee991e950c12225fb403 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Sat, 11 May 2024 15:35:05 +0000 Subject: [PATCH 13/21] feat: finish image processor --- Cargo.lock | 2254 +---------------- Cargo.toml | 30 +- config/src/sources/cli.rs | 2 +- config/src/sources/env.rs | 2 +- config/src/sources/file/mod.rs | 2 +- config/src/sources/manual.rs | 2 +- foundations/examples/src/http.rs | 4 +- foundations/macros/src/metrics/mod.rs | 25 +- foundations/macros/src/settings/types/mod.rs | 6 +- .../macros/src/settings/types/serde.rs | 4 +- foundations/src/http/server/mod.rs | 2 +- foundations/src/http/server/stream/quic.rs | 2 +- foundations/src/telemetry/server.rs | 2 +- image-processor/Cargo.toml | 2 + image-processor/assets/AINTNOWAY.webp | Bin 0 -> 1273268 bytes image-processor/assets/FRqIinBJ_400x400.jpg | Bin 0 -> 22899 bytes image-processor/assets/xd.jpg | Bin 0 -> 72892 bytes .../scuffle/image_processor/events.proto | 17 +- .../proto/scuffle/image_processor/types.proto | 128 +- image-processor/src/config.rs | 37 +- image-processor/src/database.rs | 37 +- image-processor/src/{disk => drive}/http.rs | 0 image-processor/src/{disk => drive}/local.rs | 0 image-processor/src/{disk => drive}/memory.rs | 26 +- image-processor/src/{disk => drive}/mod.rs | 8 - .../src/{disk => drive}/public_http.rs | 2 +- image-processor/src/{disk => drive}/s3.rs | 0 image-processor/src/event_queue/http.rs | 2 +- image-processor/src/events.rs | 70 + image-processor/src/global.rs | 6 +- image-processor/src/main.rs | 4 +- image-processor/src/management/http.rs | 8 +- image-processor/src/management/mod.rs | 73 +- image-processor/src/management/validation.rs | 259 +- image-processor/src/processor/error.rs | 124 - .../src/processor/job/decoder/ffmpeg.rs | 226 -- .../src/processor/job/decoder/libavif.rs | 140 - .../src/processor/job/decoder/libwebp.rs | 140 - .../src/processor/job/encoder/gifski.rs | 86 - image-processor/src/processor/job/frame.rs | 7 - image-processor/src/processor/job/mod.rs | 235 -- image-processor/src/processor/job/process.rs | 304 --- image-processor/src/processor/job/resize.rs | 200 -- image-processor/src/processor/job/scaling.rs | 720 ------ image-processor/src/processor/mod.rs | 78 - image-processor/src/processor/utils.rs | 57 - image-processor/src/worker.rs | 8 - image-processor/src/worker/error.rs | 1 + image-processor/src/worker/mod.rs | 60 + .../src/worker/process/blocking.rs | 373 +++ .../src/worker/process/decoder/ffmpeg.rs | 212 ++ .../src/worker/process/decoder/libavif.rs | 135 + .../src/worker/process/decoder/libwebp.rs | 132 + .../job => worker/process}/decoder/mod.rs | 60 +- .../src/worker/process/encoder/gifski.rs | 87 + .../job => worker/process}/encoder/libavif.rs | 89 +- .../job => worker/process}/encoder/libwebp.rs | 94 +- .../job => worker/process}/encoder/mod.rs | 55 +- .../job => worker/process}/encoder/png.rs | 38 +- image-processor/src/worker/process/frame.rs | 47 + .../src/worker/process/input_download.rs | 54 + .../job => worker/process}/libavif.rs | 13 +- .../job => worker/process}/libwebp.rs | 2 + image-processor/src/worker/process/mod.rs | 272 ++ image-processor/src/worker/process/resize.rs | 419 +++ .../job => worker/process}/smart_object.rs | 21 +- 66 files changed, 2642 insertions(+), 4863 deletions(-) create mode 100644 image-processor/assets/AINTNOWAY.webp create mode 100644 image-processor/assets/FRqIinBJ_400x400.jpg create mode 100644 image-processor/assets/xd.jpg rename image-processor/src/{disk => drive}/http.rs (100%) rename image-processor/src/{disk => drive}/local.rs (100%) rename image-processor/src/{disk => drive}/memory.rs (88%) rename image-processor/src/{disk => drive}/mod.rs (97%) rename image-processor/src/{disk => drive}/public_http.rs (97%) rename image-processor/src/{disk => drive}/s3.rs (100%) create mode 100644 image-processor/src/events.rs delete mode 100644 image-processor/src/processor/error.rs delete mode 100644 image-processor/src/processor/job/decoder/ffmpeg.rs delete mode 100644 image-processor/src/processor/job/decoder/libavif.rs delete mode 100644 image-processor/src/processor/job/decoder/libwebp.rs delete mode 100644 image-processor/src/processor/job/encoder/gifski.rs delete mode 100644 image-processor/src/processor/job/frame.rs delete mode 100644 image-processor/src/processor/job/mod.rs delete mode 100644 image-processor/src/processor/job/process.rs delete mode 100644 image-processor/src/processor/job/resize.rs delete mode 100644 image-processor/src/processor/job/scaling.rs delete mode 100644 image-processor/src/processor/mod.rs delete mode 100644 image-processor/src/processor/utils.rs delete mode 100644 image-processor/src/worker.rs create mode 100644 image-processor/src/worker/error.rs create mode 100644 image-processor/src/worker/mod.rs create mode 100644 image-processor/src/worker/process/blocking.rs create mode 100644 image-processor/src/worker/process/decoder/ffmpeg.rs create mode 100644 image-processor/src/worker/process/decoder/libavif.rs create mode 100644 image-processor/src/worker/process/decoder/libwebp.rs rename image-processor/src/{processor/job => worker/process}/decoder/mod.rs (63%) create mode 100644 image-processor/src/worker/process/encoder/gifski.rs rename image-processor/src/{processor/job => worker/process}/encoder/libavif.rs (60%) rename image-processor/src/{processor/job => worker/process}/encoder/libwebp.rs (73%) rename image-processor/src/{processor/job => worker/process}/encoder/mod.rs (58%) rename image-processor/src/{processor/job => worker/process}/encoder/png.rs (50%) create mode 100644 image-processor/src/worker/process/frame.rs create mode 100644 image-processor/src/worker/process/input_download.rs rename image-processor/src/{processor/job => worker/process}/libavif.rs (92%) rename image-processor/src/{processor/job => worker/process}/libwebp.rs (86%) create mode 100644 image-processor/src/worker/process/mod.rs create mode 100644 image-processor/src/worker/process/resize.rs rename image-processor/src/{processor/job => worker/process}/smart_object.rs (68%) diff --git a/Cargo.lock b/Cargo.lock index 248509ea..6aaba3bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,27 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "aac" -version = "0.0.1" -dependencies = [ - "byteorder", - "bytes", - "bytesio", - "num-derive", - "num-traits", -] - [[package]] name = "addr2line" version = "0.21.0" @@ -38,17 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.11" @@ -83,17 +51,6 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" -[[package]] -name = "amf0" -version = "0.0.1" -dependencies = [ - "byteorder", - "bytes", - "bytesio", - "num-derive", - "num-traits", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -160,12 +117,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" -dependencies = [ - "backtrace", -] +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" [[package]] name = "arbitrary" @@ -190,109 +144,12 @@ dependencies = [ "syn 2.0.60", ] -[[package]] -name = "argon2" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" -dependencies = [ - "base64ct", - "blake2", - "cpufeatures", - "password-hash", -] - [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" -[[package]] -name = "ascii_utils" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" - -[[package]] -name = "async-graphql" -version = "7.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261fa27d5bff5afdf7beff291b3bc73f99d1529804c70e51b0fbc51e70b1c6a9" -dependencies = [ - "async-graphql-derive", - "async-graphql-parser", - "async-graphql-value", - "async-stream", - "async-trait", - "base64 0.21.7", - "bytes", - "chrono", - "fast_chemail", - "fnv", - "futures-util", - "handlebars", - "http 1.1.0", - "indexmap 2.2.6", - "lru 0.7.8", - "mime", - "multer", - "num-traits", - "once_cell", - "pin-project-lite", - "regex", - "serde", - "serde_json", - "serde_urlencoded", - "sha2", - "static_assertions_next", - "tempfile", - "thiserror", - "tracing", - "tracing-futures", -] - -[[package]] -name = "async-graphql-derive" -version = "7.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3188809947798ea6db736715a60cf645ba3b87ea031c710130e1476b48e45967" -dependencies = [ - "Inflector", - "async-graphql-parser", - "darling 0.20.8", - "proc-macro-crate", - "proc-macro2", - "quote", - "strum", - "syn 2.0.60", - "thiserror", -] - -[[package]] -name = "async-graphql-parser" -version = "7.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e65a0b83027f35b2a5d9728a098bc66ac394caa8191d2c65ed9eb2985cf3d8" -dependencies = [ - "async-graphql-value", - "pest", - "serde", - "serde_json", -] - -[[package]] -name = "async-graphql-value" -version = "7.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e40849c29a39012d38bff87bfed431f1ed6c53fbec493294c1045d61a7ae75" -dependencies = [ - "bytes", - "indexmap 2.2.6", - "serde", - "serde_json", -] - [[package]] name = "async-nats" version = "0.34.0" @@ -361,18 +218,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" - -[[package]] -name = "av1" -version = "0.0.1" -dependencies = [ - "byteorder", - "bytes", - "bytesio", -] +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "av1-grain" @@ -388,15 +236,6 @@ dependencies = [ "v_frame", ] -[[package]] -name = "avif-serialize" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" -dependencies = [ - "arrayvec", -] - [[package]] name = "aws-config" version = "1.3.0" @@ -440,33 +279,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "aws-lc-rs" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8487b59d62764df8231cb371c459314df895b41756df457a1fb1243d65c89195" -dependencies = [ - "aws-lc-sys", - "mirai-annotations", - "paste", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15eb61145320320eb919d9bab524617a7aa4216c78d342fae3a758bc33073e4" -dependencies = [ - "bindgen", - "cc", - "cmake", - "dunce", - "fs_extra", - "libc", - "paste", -] - [[package]] name = "aws-runtime" version = "1.2.0" @@ -497,7 +309,7 @@ version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cedc97499da49c3e36cde578340f9925284685073cb3e512aaf9ab16cd9a2541" dependencies = [ - "ahash 0.8.11", + "ahash", "aws-credential-types", "aws-runtime", "aws-sigv4", @@ -517,7 +329,7 @@ dependencies = [ "hmac", "http 0.2.12", "http-body 0.4.6", - "lru 0.12.3", + "lru", "once_cell", "percent-encoding", "regex-lite", @@ -612,7 +424,7 @@ dependencies = [ "http 0.2.12", "http 1.1.0", "once_cell", - "p256 0.11.1", + "p256", "percent-encoding", "ring 0.17.8", "sha2", @@ -898,12 +710,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "az" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" - [[package]] name = "backtrace" version = "0.3.71" @@ -925,18 +731,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base32" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" - [[package]] name = "base64" version = "0.13.1" @@ -971,57 +765,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "bcder" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627747a6774aab38beb35990d88309481378558875a41da1a4b2e373c906ef0" -dependencies = [ - "bytes", - "smallvec", -] - -[[package]] -name = "binary-helper" -version = "0.0.1" -dependencies = [ - "anyhow", - "async-nats", - "async-stream", - "async-trait", - "aws-config", - "aws-credential-types", - "aws-sdk-s3", - "aws-smithy-types", - "bytes", - "deadpool-postgres", - "fred 8.0.6", - "futures-util", - "http-body 1.0.0", - "hyper 1.3.1", - "once_cell", - "pb", - "pin-project", - "postgres-from-row", - "postgres-types", - "prost 0.12.4", - "rustls 0.23.5", - "rustls-pemfile 2.1.2", - "scuffle-config", - "scuffle-utils", - "serde", - "thiserror", - "tokio", - "tokio-postgres", - "tokio-postgres-rustls", - "tokio-rustls 0.25.0", - "tonic", - "tower-layer", - "tracing", - "tracing-subscriber", - "ulid", -] - [[package]] name = "bindgen" version = "0.69.4" @@ -1034,23 +777,14 @@ dependencies = [ "itertools 0.12.1", "lazy_static", "lazycell", - "log", - "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn 2.0.60", - "which", ] -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - [[package]] name = "bitflags" version = "1.3.2" @@ -1063,16 +797,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" -[[package]] -name = "bitmask-enum" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9990737a6d5740ff51cdbbc0f0503015cb30c390f6623968281eb214a520cfc0" -dependencies = [ - "quote", - "syn 2.0.60", -] - [[package]] name = "bitstream-io" version = "2.2.0" @@ -1091,15 +815,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -1115,7 +830,7 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d43b38e074cc0de2957f10947e376a1d88b9c4dbab340b590800cc1b2e066b2" dependencies = [ - "ahash 0.8.11", + "ahash", "base64 0.13.1", "bitvec", "chrono", @@ -1155,12 +870,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - [[package]] name = "bytes" version = "1.6.0" @@ -1180,24 +889,11 @@ dependencies = [ "either", ] -[[package]] -name = "bytesio" -version = "0.0.1" -dependencies = [ - "byteorder", - "bytes", - "futures", - "scuffle-utils", - "tokio", - "tokio-stream", - "tokio-util", -] - [[package]] name = "cc" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" dependencies = [ "jobserver", "libc", @@ -1262,7 +958,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", - "clap_derive", ] [[package]] @@ -1277,18 +972,6 @@ dependencies = [ "strsim 0.11.1", ] -[[package]] -name = "clap_derive" -version = "4.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.60", -] - [[package]] name = "clap_lex" version = "0.7.0" @@ -1304,28 +987,12 @@ dependencies = [ "cc", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "colorchoice" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -1339,44 +1006,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3618cccc083bb987a415d85c02ca6c9994ea5b44731ec28b9ecf09658655fba9" [[package]] -name = "const_format" -version = "0.2.32" +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" -dependencies = [ - "const_format_proc_macros", -] +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] -name = "const_format_proc_macros" -version = "0.2.32" +name = "convert_case" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "constant_time_eq" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", + "unicode-segmentation", ] [[package]] @@ -1486,12 +1127,6 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -1510,10 +1145,8 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array", "rand_core", "subtle", - "zeroize", ] [[package]] @@ -1629,38 +1262,6 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" -[[package]] -name = "deadpool" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff0fc28638c21092aba483136debc6e177fff3dace8c835d715866923b03323e" -dependencies = [ - "deadpool-runtime", - "num_cpus", - "tokio", -] - -[[package]] -name = "deadpool-postgres" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa08f5c838496cbabb672e3614534444145fc6632995f102e13d30a29a25a13" -dependencies = [ - "deadpool", - "tokio", - "tokio-postgres", - "tracing", -] - -[[package]] -name = "deadpool-runtime" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63dfa964fe2a66f3fde91fc70b267fe193d822c7e603e2a675a49a7f46ad3f49" -dependencies = [ - "tokio", -] - [[package]] name = "debugid" version = "0.8.0" @@ -1670,23 +1271,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "default-net" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c5a6569a908354d49b10db3c516d69aca1eccd97562fd31c98b13f00b73ca66" -dependencies = [ - "dlopen2", - "libc", - "memalloc", - "netlink-packet-core", - "netlink-packet-route", - "netlink-sys", - "once_cell", - "system-configuration", - "windows", -] - [[package]] name = "der" version = "0.6.1" @@ -1749,28 +1333,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", "subtle", ] -[[package]] -name = "dlopen2" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" -dependencies = [ - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "dtoa" version = "1.0.9" @@ -1790,25 +1356,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", + "elliptic-curve", + "rfc6979", "signature 1.6.4", ] -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der 0.7.9", - "digest", - "elliptic-curve 0.13.8", - "rfc6979 0.4.0", - "signature 2.2.0", - "spki 0.7.3", -] - [[package]] name = "ed25519" version = "2.2.3" @@ -1843,50 +1395,20 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct 0.1.1", + "base16ct", "crypto-bigint 0.4.9", "der 0.6.1", "digest", - "ff 0.12.1", + "ff", "generic-array", - "group 0.12.1", + "group", "pkcs8 0.9.0", "rand_core", - "sec1 0.3.0", + "sec1", "subtle", "zeroize", ] -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.5", - "digest", - "ff 0.13.0", - "generic-array", - "group 0.13.0", - "hkdf", - "pem-rfc7468", - "pkcs8 0.10.2", - "rand_core", - "sec1 0.7.3", - "subtle", - "zeroize", -] - -[[package]] -name = "encoding_rs" -version = "0.8.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" -dependencies = [ - "cfg-if", -] - [[package]] name = "enum-as-inner" version = "0.4.0" @@ -1899,18 +1421,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "enum-as-inner" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 2.0.60", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -1950,30 +1460,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "exp_golomb" -version = "0.0.1" -dependencies = [ - "bytes", - "bytesio", -] - -[[package]] -name = "exr" -version = "1.72.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" -dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -1989,15 +1475,6 @@ dependencies = [ "hashbrown 0.13.2", ] -[[package]] -name = "fast_chemail" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" -dependencies = [ - "ascii_utils", -] - [[package]] name = "fast_image_resize" version = "3.0.4" @@ -2034,16 +1511,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core", - "subtle", -] - [[package]] name = "ffmpeg-sys-next" version = "7.0.0" @@ -2088,18 +1555,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" -[[package]] -name = "fixed" -version = "1.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc715d38bea7b5bf487fcd79bcf8c209f0b58014f3018a7a19c2b855f472048" -dependencies = [ - "az", - "bytemuck", - "half", - "typenum", -] - [[package]] name = "fixedbitset" version = "0.4.2" @@ -2125,31 +1580,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "flume" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" -dependencies = [ - "spin 0.9.8", -] - -[[package]] -name = "flv" -version = "0.0.1" -dependencies = [ - "aac", - "amf0", - "av1", - "byteorder", - "bytes", - "bytesio", - "h264", - "h265", - "num-derive", - "num-traits", -] - [[package]] name = "fnv" version = "1.0.7" @@ -2165,37 +1595,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fred" -version = "8.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8e3a1339ed45ad8fde94530c4bdcbd5f371a3c6bd3bf57682923792830aa37" -dependencies = [ - "arc-swap", - "async-trait", - "bytes", - "bytes-utils", - "crossbeam-queue", - "float-cmp", - "futures", - "log", - "parking_lot", - "rand", - "redis-protocol 4.1.0", - "rustls 0.22.4", - "rustls-native-certs 0.7.0", - "rustls-webpki 0.102.3", - "semver 1.0.22", - "socket2 0.5.7", - "tokio", - "tokio-rustls 0.25.0", - "tokio-stream", - "tokio-util", - "trust-dns-resolver 0.23.2", - "url", - "urlencoding", -] - [[package]] name = "fred" version = "9.0.3" @@ -2212,7 +1611,7 @@ dependencies = [ "log", "parking_lot", "rand", - "redis-protocol 5.0.1", + "redis-protocol", "semver 1.0.22", "socket2 0.5.7", "tokio", @@ -2222,12 +1621,6 @@ dependencies = [ "urlencoding", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "funty" version = "2.0.0" @@ -2331,14 +1724,13 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -2353,7 +1745,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" dependencies = [ - "color_quant", "weezl", ] @@ -2408,49 +1799,13 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "gloo-utils" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "group" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff 0.12.1", - "rand_core", - "subtle", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff 0.13.0", + "ff", "rand_core", "subtle", ] @@ -2493,26 +1848,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "h264" -version = "0.0.1" -dependencies = [ - "byteorder", - "bytes", - "bytesio", - "exp_golomb", -] - -[[package]] -name = "h265" -version = "0.0.1" -dependencies = [ - "byteorder", - "bytes", - "bytesio", - "exp_golomb", -] - [[package]] name = "h3" version = "0.0.3" @@ -2574,54 +1909,27 @@ dependencies = [ ] [[package]] -name = "half" -version = "2.4.1" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" -dependencies = [ - "cfg-if", - "crunchy", -] +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "handlebars" -version = "4.5.0" +name = "hashbrown" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "log", - "pest", - "pest_derive", - "serde", - "serde_json", - "thiserror", + "ahash", ] [[package]] name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.11", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.11", + "ahash", "allocator-api2", ] @@ -2649,15 +1957,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - [[package]] name = "hmac" version = "0.12.1" @@ -2667,15 +1966,6 @@ dependencies = [ "digest", ] -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "hostname" version = "0.3.1" @@ -2861,21 +2151,6 @@ dependencies = [ "tokio-io-timeout", ] -[[package]] -name = "hyper-tungstenite" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a343d17fe7885302ed7252767dc7bb83609a874b6ff581142241ec4b73957ad" -dependencies = [ - "http-body-util", - "hyper 1.3.1", - "hyper-util", - "pin-project-lite", - "tokio", - "tokio-tungstenite", - "tungstenite", -] - [[package]] name = "hyper-util" version = "0.1.3" @@ -2936,16 +2211,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "idna" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.5.0" @@ -2956,52 +2221,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "image" -version = "0.24.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "num-traits", - "png", -] - -[[package]] -name = "image" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "exr", - "gif", - "image-webp", - "num-traits", - "png", - "qoi", - "ravif", - "rayon", - "rgb", - "tiff", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d730b085583c4d789dfd07fdcf185be59501666a90c97c40162b37e4fdad272d" -dependencies = [ - "byteorder-lite", - "thiserror", -] - [[package]] name = "imagequant" version = "4.3.1" @@ -3039,7 +2258,6 @@ checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.5", - "serde", ] [[package]] @@ -3138,12 +2356,6 @@ dependencies = [ "libc", ] -[[package]] -name = "jpeg-decoder" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" - [[package]] name = "js-sys" version = "0.3.69" @@ -3153,35 +2365,11 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jwt-next" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89327f6d992ab1b3f6c908ee32cc0bb66068e2696da2cfe21a8764e400fe9c3b" -dependencies = [ - "base64 0.21.7", - "crypto-common", - "digest", - "ecdsa 0.16.9", - "hmac", - "p256 0.13.2", - "p384", - "pem", - "rsa", - "serde", - "serde_json", - "sha2", - "signature 2.2.0", -] - [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -dependencies = [ - "spin 0.5.2", -] [[package]] name = "lazycell" @@ -3189,12 +2377,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - [[package]] name = "libavif-sys" version = "0.16.0+libavif.1.0.4" @@ -3243,12 +2425,6 @@ dependencies = [ "windows-targets 0.52.5", ] -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - [[package]] name = "libwebp-sys2" version = "0.1.9" @@ -3312,15 +2488,6 @@ dependencies = [ "imgref", ] -[[package]] -name = "lru" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" -dependencies = [ - "hashbrown 0.12.3", -] - [[package]] name = "lru" version = "0.12.3" @@ -3373,7 +2540,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", - "rayon", ] [[package]] @@ -3386,12 +2552,6 @@ dependencies = [ "digest", ] -[[package]] -name = "memalloc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1" - [[package]] name = "memchr" version = "2.7.2" @@ -3440,12 +2600,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "mirai-annotations" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" - [[package]] name = "mongodb" version = "2.8.2" @@ -3486,48 +2640,13 @@ dependencies = [ "tokio", "tokio-rustls 0.24.1", "tokio-util", - "trust-dns-proto 0.21.2", - "trust-dns-resolver 0.21.2", + "trust-dns-proto", + "trust-dns-resolver", "typed-builder", "uuid", "webpki-roots 0.25.4", ] -[[package]] -name = "mp4" -version = "0.0.1" -dependencies = [ - "aac", - "av1", - "byteorder", - "bytes", - "bytesio", - "fixed", - "h264", - "h265", - "paste", - "serde", - "serde_json", -] - -[[package]] -name = "multer" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http 1.1.0", - "httparse", - "log", - "memchr", - "mime", - "spin 0.9.8", - "version_check", -] - [[package]] name = "multimap" version = "0.10.0" @@ -3549,54 +2668,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c" -[[package]] -name = "netlink-packet-core" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" -dependencies = [ - "anyhow", - "byteorder", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-route" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" -dependencies = [ - "anyhow", - "bitflags 1.3.2", - "byteorder", - "libc", - "netlink-packet-core", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-utils" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" -dependencies = [ - "anyhow", - "byteorder", - "paste", - "thiserror", -] - -[[package]] -name = "netlink-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" -dependencies = [ - "bytes", - "libc", - "log", -] - [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -3689,23 +2760,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "smallvec", - "zeroize", -] - [[package]] name = "num-complex" version = "0.4.5" @@ -3743,30 +2797,15 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] -[[package]] -name = "num-modular" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" - -[[package]] -name = "num-order" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" -dependencies = [ - "num-modular", -] - [[package]] name = "num-rational" version = "0.4.1" @@ -3781,12 +2820,11 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -3899,7 +2937,7 @@ dependencies = [ "glob", "once_cell", "opentelemetry", - "ordered-float 4.2.0", + "ordered-float", "percent-encoding", "rand", "thiserror", @@ -3914,15 +2952,6 @@ dependencies = [ "crossbeam-channel", ] -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - [[package]] name = "ordered-float" version = "4.2.0" @@ -3950,32 +2979,8 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" dependencies = [ - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2", -] - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", - "primeorder", - "sha2", -] - -[[package]] -name = "p384" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" -dependencies = [ - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", - "primeorder", + "ecdsa", + "elliptic-curve", "sha2", ] @@ -4002,17 +3007,6 @@ dependencies = [ "windows-targets 0.52.5", ] -[[package]] -name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core", - "subtle", -] - [[package]] name = "paste" version = "1.0.14" @@ -4020,49 +3014,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] -name = "path-tree" -version = "0.7.6" +name = "pbjson" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a3a8689ce29b9b1e4d2363bd5b2ee0f6765d6fb6973df48f24145c9325d4f6e" +checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" dependencies = [ - "smallvec", + "base64 0.21.7", + "serde", ] [[package]] -name = "pb" -version = "0.0.1" +name = "pbjson-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" dependencies = [ - "prettyplease", - "proc-macro2", - "prost 0.12.4", - "prost-build", - "quote", - "syn 2.0.60", - "tonic", - "tonic-build", - "ulid", - "uuid", - "walkdir", -] - -[[package]] -name = "pbjson" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" -dependencies = [ - "base64 0.21.7", - "serde", -] - -[[package]] -name = "pbjson-build" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" -dependencies = [ - "heck 0.4.1", - "itertools 0.11.0", + "heck 0.4.1", + "itertools 0.11.0", "prost 0.12.4", "prost-types", ] @@ -4102,16 +3070,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "pem" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" -dependencies = [ - "base64 0.22.1", - "serde", -] - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -4127,51 +3085,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pest" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "pest_meta" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - [[package]] name = "petgraph" version = "0.6.4" @@ -4232,17 +3145,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der 0.7.9", - "pkcs8 0.10.2", - "spki 0.7.3", -] - [[package]] name = "pkcs8" version = "0.9.0" @@ -4269,63 +3171,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "platform-api" -version = "0.0.1" -dependencies = [ - "anyhow", - "arc-swap", - "argon2", - "async-graphql", - "async-nats", - "async-stream", - "async-trait", - "aws-config", - "aws-sdk-s3", - "base64 0.22.1", - "binary-helper", - "bitmask-enum", - "bytes", - "chrono", - "fred 8.0.6", - "futures", - "futures-util", - "hmac", - "http 1.1.0", - "http-body 1.0.0", - "http-body-util", - "hyper 1.3.1", - "hyper-tungstenite", - "hyper-util", - "jwt-next", - "multer", - "path-tree", - "pb", - "pin-project", - "postgres-from-row", - "postgres-types", - "prost 0.12.4", - "rand", - "reqwest", - "rustls 0.23.5", - "rustls-pemfile 2.1.2", - "scuffle-config", - "scuffle-utils", - "serde", - "serde_json", - "sha2", - "tempfile", - "thiserror", - "tokio", - "tokio-rustls 0.26.0", - "tokio-stream", - "tonic", - "totp-rs", - "tracing", - "ulid", - "uuid", -] - [[package]] name = "platforms" version = "3.4.0" @@ -4351,27 +3196,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" -[[package]] -name = "portpicker" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" -dependencies = [ - "rand", -] - -[[package]] -name = "postgres-derive" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83145eba741b050ef981a9a1838c843fa7665e154383325aa8b440ae703180a2" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 2.0.60", -] - [[package]] name = "postgres-from-row" version = "0.5.2" @@ -4417,12 +3241,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d2234cdee9408b523530a9b6d2d6b373d1db34f6a8e51dc03ded1828d7fb67c" dependencies = [ "bytes", - "chrono", "fallible-iterator", - "postgres-derive", "postgres-protocol", - "serde", - "serde_json", ] [[package]] @@ -4471,25 +3291,6 @@ dependencies = [ "syn 2.0.60", ] -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve 0.13.8", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - [[package]] name = "proc-macro2" version = "1.0.81" @@ -4617,32 +3418,6 @@ dependencies = [ "prost 0.12.4", ] -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "qrcodegen" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" - -[[package]] -name = "qrcodegen-image" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59a7c7ddb94c99fa3942dd761dbb305bca462b71d7bd9bcb3f9ff4d454d5736" -dependencies = [ - "base64 0.22.1", - "image 0.24.9", - "qrcodegen", -] - [[package]] name = "quick-error" version = "1.2.3" @@ -4784,22 +3559,6 @@ dependencies = [ "system-deps", "thiserror", "v_frame", - "wasm-bindgen", -] - -[[package]] -name = "ravif" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc13288f5ab39e6d7c9d501759712e6969fcc9734220846fc9ed26cae2cc4234" -dependencies = [ - "avif-serialize", - "imgref", - "loop9", - "quick-error 2.0.1", - "rav1e", - "rayon", - "rgb", ] [[package]] @@ -4822,20 +3581,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redis-protocol" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c31deddf734dc0a39d3112e73490e88b61a05e83e074d211f348404cee4d2c6" -dependencies = [ - "bytes", - "bytes-utils", - "cookie-factory", - "crc16", - "log", - "nom", -] - [[package]] name = "redis-protocol" version = "5.0.1" @@ -4990,16 +3735,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - [[package]] name = "rgb" version = "0.8.37" @@ -5039,50 +3774,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rsa" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8 0.10.2", - "rand_core", - "sha2", - "signature 2.2.0", - "spki 0.7.3", - "subtle", - "zeroize", -] - -[[package]] -name = "rtmp" -version = "0.0.1" -dependencies = [ - "amf0", - "async-trait", - "byteorder", - "bytes", - "bytesio", - "chrono", - "futures", - "hmac", - "num-derive", - "num-traits", - "rand", - "scuffle-utils", - "serde_json", - "sha2", - "tokio", - "tracing", - "uuid", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -5162,21 +3853,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls" -version = "0.23.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afabcee0551bd1aa3e18e5adbf2c0544722014b899adb31bd186ec638d3da97e" -dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "rustls-pki-types", - "rustls-webpki 0.102.3", - "subtle", - "zeroize", -] - [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -5243,7 +3919,6 @@ version = "0.102.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" dependencies = [ - "aws-lc-rs", "ring 0.17.8", "rustls-pki-types", "untrusted 0.9.0", @@ -5261,15 +3936,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "scan_fmt" version = "0.2.6" @@ -5311,30 +3977,8 @@ dependencies = [ ] [[package]] -name = "scuffle-config" -version = "0.0.1" -dependencies = [ - "clap", - "convert_case 0.6.0", - "humantime", - "num-order", - "scuffle_config_derive", - "serde", - "serde-value", - "serde_ignored", - "serde_json", - "serde_path_to_error", - "serde_yaml", - "thiserror", - "toml", - "tracing", - "ulid", - "uuid", -] - -[[package]] -name = "scuffle-ffmpeg" -version = "0.1.0" +name = "scuffle-ffmpeg" +version = "0.1.0" dependencies = [ "bytes", "crossbeam-channel", @@ -5421,16 +4065,18 @@ dependencies = [ "aws-sdk-s3", "aws-smithy-runtime-api", "aws-smithy-types", + "bitvec", "bson", "byteorder", "bytes", "chrono", "fast_image_resize", "file-format", - "fred 9.0.3", + "fred", "futures", "gifski", "http 1.1.0", + "humantime-serde", "imgref", "libavif-sys", "libwebp-sys2", @@ -5474,52 +4120,6 @@ dependencies = [ "tonic-build", ] -[[package]] -name = "scuffle-utils" -version = "0.1.0" -dependencies = [ - "async-trait", - "bytes", - "const_format", - "deadpool-postgres", - "dotenvy", - "fnv", - "fred 8.0.6", - "futures", - "futures-channel", - "futures-util", - "http 1.1.0", - "http-body-util", - "hyper 1.3.1", - "path-tree", - "pin-project", - "portpicker", - "postgres-from-row", - "postgres-types", - "prost 0.12.4", - "serde_json", - "tempfile", - "thiserror", - "tokio", - "tokio-postgres", - "tokio-util", - "tonic", - "tonic-build", - "tower", - "tracing", - "trust-dns-resolver 0.23.2", - "ulid", -] - -[[package]] -name = "scuffle_config_derive" -version = "0.0.1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - [[package]] name = "sdd" version = "0.2.0" @@ -5532,7 +4132,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct 0.1.1", + "base16ct", "der 0.6.1", "generic-array", "pkcs8 0.9.0", @@ -5540,27 +4140,13 @@ dependencies = [ "zeroize", ] -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct 0.2.0", - "der 0.7.9", - "generic-array", - "pkcs8 0.10.2", - "subtle", - "zeroize", -] - [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -5569,9 +4155,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -5607,27 +4193,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float 2.10.1", - "serde", -] - -[[package]] -name = "serde-wasm-bindgen" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_bytes" version = "0.11.14" @@ -5648,26 +4213,6 @@ dependencies = [ "syn 2.0.60", ] -[[package]] -name = "serde_derive_internals" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "serde_ignored" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e319a36d1b52126a0d608f24e93b2d81297091818cd70625fcf50a15d84ddf" -dependencies = [ - "serde", -] - [[package]] name = "serde_json" version = "1.0.116" @@ -5952,12 +4497,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "static_assertions_next" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" - [[package]] name = "strfmt" version = "0.2.4" @@ -5987,28 +4526,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.60", -] - [[package]] name = "subtle" version = "2.5.0" @@ -6072,27 +4589,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "system-deps" version = "6.2.2" @@ -6166,17 +4662,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tiff" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - [[package]] name = "tikv-jemalloc-ctl" version = "0.5.4" @@ -6320,20 +4805,6 @@ dependencies = [ "whoami", ] -[[package]] -name = "tokio-postgres-rustls" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04fb792ccd6bbcd4bba408eb8a292f70fc4a3589e5d793626f45190e6454b6ab" -dependencies = [ - "ring 0.17.8", - "rustls 0.23.5", - "tokio", - "tokio-postgres", - "tokio-rustls 0.26.0", - "x509-certificate", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -6355,17 +4826,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls 0.23.5", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-stream" version = "0.1.15" @@ -6375,26 +4835,13 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", ] [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", @@ -6402,7 +4849,6 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -6414,7 +4860,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.12", + "toml_edit", ] [[package]] @@ -6426,17 +4872,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.12" @@ -6447,7 +4882,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.7", + "winnow", ] [[package]] @@ -6469,10 +4904,7 @@ dependencies = [ "percent-encoding", "pin-project", "prost 0.12.4", - "rustls-pemfile 2.1.2", - "rustls-pki-types", "tokio", - "tokio-rustls 0.25.0", "tokio-stream", "tower", "tower-layer", @@ -6493,22 +4925,6 @@ dependencies = [ "syn 2.0.60", ] -[[package]] -name = "totp-rs" -version = "5.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c4ae9724c5888c0417d2396037ed3b60665925624766416e3e342b6ba5dbd3f" -dependencies = [ - "base32", - "constant_time_eq", - "hmac", - "qrcodegen-image", - "sha1", - "sha2", - "url", - "urlencoding", -] - [[package]] name = "tower" version = "0.4.13" @@ -6574,18 +4990,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "futures", - "futures-task", - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -6614,39 +5018,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "chrono", - "matchers", "nu-ansi-term", - "once_cell", - "regex", "serde", "serde_json", "sharded-slab", "smallvec", "thread_local", - "tracing", "tracing-core", "tracing-log", "tracing-serde", ] -[[package]] -name = "transmuxer" -version = "0.0.1" -dependencies = [ - "aac", - "amf0", - "av1", - "byteorder", - "bytes", - "bytesio", - "flv", - "h264", - "h265", - "mp4", - "serde", - "serde_json", -] - [[package]] name = "trust-dns-proto" version = "0.21.2" @@ -6656,7 +5038,7 @@ dependencies = [ "async-trait", "cfg-if", "data-encoding", - "enum-as-inner 0.4.0", + "enum-as-inner", "futures-channel", "futures-io", "futures-util", @@ -6672,31 +5054,6 @@ dependencies = [ "url", ] -[[package]] -name = "trust-dns-proto" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner 0.6.0", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand", - "smallvec", - "thiserror", - "tinyvec", - "tokio", - "tracing", - "url", -] - [[package]] name = "trust-dns-resolver" version = "0.21.2" @@ -6714,28 +5071,7 @@ dependencies = [ "smallvec", "thiserror", "tokio", - "trust-dns-proto 0.21.2", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" -dependencies = [ - "cfg-if", - "futures-util", - "ipconfig", - "lru-cache", - "once_cell", - "parking_lot", - "rand", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "tracing", - "trust-dns-proto 0.23.2", + "trust-dns-proto", ] [[package]] @@ -6755,48 +5091,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tsify" -version = "0.4.5" -source = "git+https://github.com/ScuffleTV/tsify.git?branch=sisou/comments#e36e55bcf3c9ac7c1d8185e5ad994885f4a2eb46" -dependencies = [ - "gloo-utils", - "serde", - "serde_json", - "tsify-macros", - "wasm-bindgen", -] - -[[package]] -name = "tsify-macros" -version = "0.4.5" -source = "git+https://github.com/ScuffleTV/tsify.git?branch=sisou/comments#e36e55bcf3c9ac7c1d8185e5ad994885f4a2eb46" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.60", -] - -[[package]] -name = "tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.1.0", - "httparse", - "log", - "rand", - "sha1", - "thiserror", - "url", - "utf-8", -] - [[package]] name = "typed-builder" version = "0.10.0" @@ -6814,23 +5108,14 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - [[package]] name = "ulid" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34778c17965aa2a08913b57e1f34db9b4a63f5de31768b55bf20d2795f921259" dependencies = [ - "bytes", "getrandom", - "postgres-types", "rand", - "serde", "uuid", "web-time", ] @@ -6862,12 +5147,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -6904,12 +5183,6 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - [[package]] name = "utf8parse" version = "0.2.1" @@ -6961,282 +5234,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "video-api" -version = "0.0.1" -dependencies = [ - "anyhow", - "async-nats", - "async-stream", - "async-trait", - "base64 0.22.1", - "binary-helper", - "bytes", - "chrono", - "dotenvy", - "fred 8.0.6", - "futures", - "futures-util", - "hex", - "hmac", - "http 0.2.12", - "hyper 0.14.28", - "itertools 0.12.1", - "jwt-next", - "pb", - "postgres-from-row", - "prost 0.12.4", - "rand", - "rand_chacha", - "scuffle-config", - "scuffle-utils", - "serde", - "serde_json", - "sha2", - "tokio", - "tokio-stream", - "tonic", - "tower", - "tracing", - "ulid", - "url", - "uuid", - "video-common", -] - -[[package]] -name = "video-cli" -version = "0.0.1" -dependencies = [ - "anyhow", - "async-nats", - "async-trait", - "base64 0.22.1", - "binary-helper", - "chrono", - "clap", - "fred 8.0.6", - "futures", - "futures-util", - "pb", - "scuffle-config", - "scuffle-utils", - "serde", - "serde_json", - "serde_yaml", - "tokio", - "tonic", - "ulid", - "video-api", - "video-common", -] - -[[package]] -name = "video-common" -version = "0.0.1" -dependencies = [ - "async-nats", - "async-trait", - "bytes", - "chrono", - "futures", - "futures-util", - "pb", - "postgres-from-row", - "postgres-types", - "prost 0.12.4", - "scuffle-utils", - "serde", - "tokio", - "tokio-postgres", - "tracing", - "ulid", - "uuid", -] - -[[package]] -name = "video-edge" -version = "0.0.1" -dependencies = [ - "anyhow", - "async-nats", - "async-stream", - "async-trait", - "binary-helper", - "bytes", - "chrono", - "futures", - "futures-util", - "hmac", - "http-body-util", - "hyper 1.3.1", - "hyper-util", - "itertools 0.12.1", - "jwt-next", - "pb", - "postgres-from-row", - "prost 0.12.4", - "rustls 0.23.5", - "rustls-pemfile 2.1.2", - "scuffle-config", - "scuffle-utils", - "serde", - "serde_json", - "sha2", - "thiserror", - "tokio", - "tokio-rustls 0.26.0", - "tokio-stream", - "tokio-util", - "tonic", - "tracing", - "ulid", - "url", - "uuid", - "video-common", - "video-player-types", -] - -[[package]] -name = "video-ingest" -version = "0.0.1" -dependencies = [ - "aac", - "anyhow", - "async-nats", - "async-stream", - "async-trait", - "base64 0.22.1", - "binary-helper", - "bytes", - "bytesio", - "chrono", - "default-net", - "dotenvy", - "flv", - "futures", - "futures-util", - "hyper 1.3.1", - "mp4", - "pb", - "portpicker", - "postgres-from-row", - "prost 0.12.4", - "rtmp", - "rustls 0.23.5", - "rustls-pemfile 2.1.2", - "scuffle-config", - "scuffle-utils", - "serde", - "serde_json", - "tokio", - "tokio-rustls 0.26.0", - "tokio-stream", - "tonic", - "tracing", - "transmuxer", - "ulid", - "uuid", - "video-common", -] - -[[package]] -name = "video-player" -version = "0.0.1" -dependencies = [ - "bytes", - "bytesio", - "console_error_panic_hook", - "gloo-timers", - "h264", - "js-sys", - "mp4", - "serde", - "serde-wasm-bindgen", - "serde_json", - "serde_path_to_error", - "tokio", - "tracing", - "tracing-core", - "tracing-subscriber", - "tsify", - "ulid", - "url", - "video-player-types", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "video-player-types" -version = "0.0.1" -dependencies = [ - "serde", - "ulid", - "url", -] - -[[package]] -name = "video-transcoder" -version = "0.0.1" -dependencies = [ - "aac", - "anyhow", - "async-nats", - "async-stream", - "async-trait", - "aws-config", - "aws-sdk-s3", - "binary-helper", - "bytes", - "bytesio", - "chrono", - "dotenvy", - "flv", - "futures", - "futures-util", - "hyper 1.3.1", - "image 0.25.1", - "mp4", - "pb", - "portpicker", - "prost 0.12.4", - "scuffle-config", - "scuffle-ffmpeg", - "scuffle-utils", - "serde", - "serde_json", - "sha2", - "tempfile", - "thiserror", - "tokio", - "tokio-stream", - "tokio-util", - "tonic", - "tracing", - "transmuxer", - "ulid", - "uuid", - "video-common", -] - [[package]] name = "vsimd" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "want" version = "0.3.1" @@ -7365,18 +5368,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - [[package]] name = "whoami" version = "1.5.1" @@ -7419,30 +5410,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-core" version = "0.52.0" @@ -7593,18 +5566,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] @@ -7638,25 +5602,6 @@ dependencies = [ "tap", ] -[[package]] -name = "x509-certificate" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66534846dec7a11d7c50a74b7cdb208b9a581cad890b7866430d438455847c85" -dependencies = [ - "bcder", - "bytes", - "chrono", - "der 0.7.9", - "hex", - "pem", - "ring 0.17.8", - "signature 2.2.0", - "spki 0.7.3", - "thiserror", - "zeroize", -] - [[package]] name = "xmlparser" version = "0.13.6" @@ -7681,18 +5626,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "087eca3c1eaf8c47b94d02790dd086cd594b912d2043d4de4bfdd466b3befb7c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "6f4b6c273f496d8fd4eaf18853e6b448760225dc030ff2c485a786859aea6393" dependencies = [ "proc-macro2", "quote", @@ -7704,41 +5649,8 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" -dependencies = [ - "zeroize_derive", -] -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "zune-jpeg" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" -dependencies = [ - "zune-core", -] +[[patch.unused]] +name = "tsify" +version = "0.4.5" +source = "git+https://github.com/ScuffleTV/tsify.git?branch=sisou/comments#e36e55bcf3c9ac7c1d8185e5ad994885f4a2eb46" diff --git a/Cargo.toml b/Cargo.toml index fb665d9c..a26de957 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,23 +1,23 @@ [workspace] members = [ - "platform/api", + # "platform/api", "image-processor", "image-processor/proto", - "video/edge", - "video/ingest", - "video/transcoder", - "video/lib/*", - "video/api", - "video/player", - "video/player_types", - "video/common", - "video/cli", - "binary-helper", - "utils", - "proto", - "config", - "config/derive", + # "video/edge", + # "video/ingest", + # "video/transcoder", + # "video/lib/*", + # "video/api", + # "video/player", + # "video/player_types", + # "video/common", + # "video/cli", + # "binary-helper", + # "utils", + # "proto", + # "config", + # "config/derive", "ffmpeg", "foundations", "foundations/macros", diff --git a/config/src/sources/cli.rs b/config/src/sources/cli.rs index a384965b..d0f49e0d 100644 --- a/config/src/sources/cli.rs +++ b/config/src/sources/cli.rs @@ -447,6 +447,6 @@ impl CliSource { impl Source for CliSource { fn get_key(&self, path: &KeyPath) -> Result> { - scuffle_utilsget_key::(&self.value, path).map_err(|e| e.with_source(ErrorSource::Cli)) + utils::get_key::(&self.value, path).map_err(|e| e.with_source(ErrorSource::Cli)) } } diff --git a/config/src/sources/env.rs b/config/src/sources/env.rs index 66390c5f..4039343b 100644 --- a/config/src/sources/env.rs +++ b/config/src/sources/env.rs @@ -174,6 +174,6 @@ fn extract_keys( impl Source for EnvSource { fn get_key(&self, path: &KeyPath) -> Result> { - scuffle_utilsget_key::(&self.value, path).map_err(|e| e.with_source(ErrorSource::Env)) + utils::get_key::(&self.value, path).map_err(|e| e.with_source(ErrorSource::Env)) } } diff --git a/config/src/sources/file/mod.rs b/config/src/sources/file/mod.rs index 5f43a0d9..cc5f598b 100644 --- a/config/src/sources/file/mod.rs +++ b/config/src/sources/file/mod.rs @@ -145,6 +145,6 @@ impl FileSource { impl Source for FileSource { fn get_key(&self, path: &KeyPath) -> Result> { - scuffle_utilsget_key::(&self.content, path).map_err(|e| e.with_source(ErrorSource::File(self.location.clone()))) + utils::get_key::(&self.content, path).map_err(|e| e.with_source(ErrorSource::File(self.location.clone()))) } } diff --git a/config/src/sources/manual.rs b/config/src/sources/manual.rs index e2496fe1..25c457b1 100644 --- a/config/src/sources/manual.rs +++ b/config/src/sources/manual.rs @@ -92,7 +92,7 @@ impl ManualSource { impl Source for ManualSource { fn get_key(&self, path: &crate::KeyPath) -> crate::Result> { match &self.value { - Some(value) => scuffle_utilsget_key::(value, path).map_err(|e| e.with_source(ErrorSource::Manual)), + Some(value) => utils::get_key::(value, path).map_err(|e| e.with_source(ErrorSource::Manual)), None => Ok(None), } } diff --git a/foundations/examples/src/http.rs b/foundations/examples/src/http.rs index 6de7e2ce..caf883b5 100644 --- a/foundations/examples/src/http.rs +++ b/foundations/examples/src/http.rs @@ -147,9 +147,9 @@ fn map_response(result: Result, Infallible>) -> Result, optional: bool, @@ -51,16 +52,6 @@ impl Options { } } -impl Default for Options { - fn default() -> Self { - Options { - crate_path: None, - optional: false, - builder: None, - } - } -} - impl Parse for Options { fn parse(input: syn::parse::ParseStream) -> syn::Result { if input.is_empty() { @@ -361,15 +352,13 @@ fn metric_function( #constructor() } } + } else if has_args { + quote::quote! { + #crate_path::telemetry::metrics::serde::Family::default() + } } else { - if has_args { - quote::quote! { - #crate_path::telemetry::metrics::serde::Family::default() - } - } else { - quote::quote! { - Default::default() - } + quote::quote! { + Default::default() } }; diff --git a/foundations/macros/src/settings/types/mod.rs b/foundations/macros/src/settings/types/mod.rs index 8db03813..63d56c6d 100644 --- a/foundations/macros/src/settings/types/mod.rs +++ b/foundations/macros/src/settings/types/mod.rs @@ -53,11 +53,11 @@ trait Args: Default { Self: Sized, { attrs - .into_iter() + .iter() .filter(|a| a.path().is_ident("settings")) .try_fold(Self::default(), |mut state, attr| { let Meta::List(meta) = &attr.meta else { - return Err(syn::Error::new_spanned(&attr, "expected #[settings(...)]")); + return Err(syn::Error::new_spanned(attr, "expected #[settings(...)]")); }; let parsed = meta.parse_args_with(Punctuated::::parse_terminated)?; @@ -111,7 +111,7 @@ impl Args for GlobalArgs { }) = &meta.value { self.crate_path = - syn::parse_str(&lit.value()).map_err(|_| syn::Error::new_spanned(&lit, "expected valid path"))?; + syn::parse_str(&lit.value()).map_err(|_| syn::Error::new_spanned(lit, "expected valid path"))?; Ok(true) } else { Err(syn::Error::new_spanned(&meta.value, "expected string")) diff --git a/foundations/macros/src/settings/types/serde.rs b/foundations/macros/src/settings/types/serde.rs index 21122733..c58d280e 100644 --- a/foundations/macros/src/settings/types/serde.rs +++ b/foundations/macros/src/settings/types/serde.rs @@ -37,7 +37,7 @@ impl FromStr for RenameAll { impl RenameAll { /// #[serde(rename_all = "name")] or #[serde(rename_all(serialize = "name", deserialize = "name"))] pub fn parse(attr: &[syn::Attribute]) -> syn::Result> { - Ok(parse_serde_attrs(attr, None, |state, meta| match &meta { + parse_serde_attrs(attr, None, |state, meta| match &meta { Meta::NameValue(meta) if meta.path.is_ident("rename_all") => { if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit), .. @@ -52,7 +52,7 @@ impl RenameAll { } _ => {} })? - .transpose()?) + .transpose() } pub fn apply(&self, name: &str) -> String { diff --git a/foundations/src/http/server/mod.rs b/foundations/src/http/server/mod.rs index b1c3b028..d8f2f290 100644 --- a/foundations/src/http/server/mod.rs +++ b/foundations/src/http/server/mod.rs @@ -108,7 +108,7 @@ fn make_tcp_listener(addr: SocketAddr) -> std::io::Result Connection { tokio::spawn( async move { if let Err(err) = serve_request(&service, request, stream).await { - service.on_error(err.into()).await; + service.on_error(err).await; } drop(ctx); diff --git a/foundations/src/telemetry/server.rs b/foundations/src/telemetry/server.rs index 1f857dfd..d32990f4 100644 --- a/foundations/src/telemetry/server.rs +++ b/foundations/src/telemetry/server.rs @@ -213,7 +213,7 @@ mod health_check { } static HEALTH_CHECK: once_cell::sync::Lazy = - once_cell::sync::Lazy::::new(|| HealthChecker::default()); + once_cell::sync::Lazy::::new(HealthChecker::default); /// Register a health check and return an id pub fn register(check: impl HealthCheck) -> usize { diff --git a/image-processor/Cargo.toml b/image-processor/Cargo.toml index ceb4aabe..f2e85d22 100644 --- a/image-processor/Cargo.toml +++ b/image-processor/Cargo.toml @@ -40,6 +40,7 @@ chrono = { version = "0.4", features = ["serde"] } url = { version = "2", features = ["serde"] } http = "1" urlencoding = "2" +humantime-serde = "1" scuffle-foundations = { version = "*", path = "../foundations" } scuffle-ffmpeg = { version = "*", path = "../ffmpeg", features = ["tracing"] } @@ -54,6 +55,7 @@ aws-smithy-runtime-api = "1" fred = "9.0.3" strfmt = "0.2" once_cell = "1.8" +bitvec = "1" [build-dependencies] tonic-build = "0.11" diff --git a/image-processor/assets/AINTNOWAY.webp b/image-processor/assets/AINTNOWAY.webp new file mode 100644 index 0000000000000000000000000000000000000000..6fe429d1bbcdc1d9d779505b89bcc5c9c3445931 GIT binary patch literal 1273268 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∾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

I0~&5Vm7kn% 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

*`;t0Y~$?0vW+YU%My+$G5qg4*lwcYBjY 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

ShQm@Q`JH}$W_yht(tE`9Ic%DmxpOh#04-SDh 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%0D7(qduP{H~ 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

>V(HbqoBQe5KblAwN%`^F_w46nVr7 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

  • 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

    j|7shK0>0GhO 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%0D$x*K!wUBlI!1J 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 {1&-QoxTin7M})j^Km|tV~!36O4Sl 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�ICFpli%aI5Z+&4TPeb@>;7 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

    7CCvj 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�J!!qh3Mo4TzNc?IWZeUxol^3^}bA{2eazEUD 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, - /// Global, shared memory drive for all tasks otherwise each task gets its - /// own memory drive - #[serde(default = "default_true")] - pub global: bool, /// The drive mode #[serde(default)] pub mode: DriveMode, } -fn default_true() -> bool { - true -} - #[auto_settings(impl_default = false)] pub struct HttpDriveConfig { /// The name of the drive @@ -163,6 +179,7 @@ pub struct HttpDriveConfig { pub url: Url, /// The timeout for the HTTP drive #[serde(default = "default_timeout")] + #[serde(with = "humantime_serde")] pub timeout: Option, /// Allow insecure TLS #[serde(default)] @@ -203,6 +220,7 @@ pub enum DriveMode { pub struct PublicHttpDriveConfig { /// The timeout for the HTTP drive #[serde(default = "default_timeout")] + #[serde(with = "humantime_serde")] pub timeout: Option, /// Allow insecure TLS #[serde(default)] @@ -252,6 +270,7 @@ pub struct HttpEventQueueConfig { /// The timeout for the HTTP event queue /// Default is 30 seconds #[serde(default = "default_timeout")] + #[serde(with = "humantime_serde")] pub timeout: Option, /// Allow insecure TLS (if the url is https, do not verify the certificate) #[serde(default)] diff --git a/image-processor/src/database.rs b/image-processor/src/database.rs index e2d96990..285de759 100644 --- a/image-processor/src/database.rs +++ b/image-processor/src/database.rs @@ -9,6 +9,7 @@ use scuffle_image_processor_proto::Task; use serde::{Deserialize, Serializer}; use crate::global::Global; +use crate::worker::JobError; fn serialize_protobuf(value: &T, serializer: S) -> Result { serializer.serialize_bytes(&value.encode_to_vec()) @@ -27,7 +28,7 @@ pub struct Job { /// The id of the job pub id: ObjectId, /// The priority of the job, higher priority jobs are fetched first - pub priority: i32, + pub priority: u32, /// The lease time of the job on a worker. pub hold_until: Option>, #[serde(serialize_with = "serialize_protobuf", deserialize_with = "deserialize_protobuf")] @@ -87,7 +88,7 @@ impl Job { pub async fn new( global: &Arc, task: Task, - priority: i32, + priority: u32, ttl: Option, ) -> Result { let job = Job { @@ -131,7 +132,7 @@ impl Job { bson::doc! { "$set": { "claimed_by_id": global.worker_id(), - "hold_until": chrono::Utc::now() + chrono::Duration::seconds(60), + "hold_until": chrono::Utc::now() + global.config().worker.hold_time, }, }, Some( @@ -155,12 +156,12 @@ impl Job { let success = Self::collection(global.database()) .update_one( bson::doc! { - "_id": self.id.clone(), + "_id": self.id, "claimed_by_id": global.worker_id(), }, bson::doc! { "$set": { - "hold_until": chrono::Utc::now() + chrono::Duration::seconds(60), + "hold_until": chrono::Utc::now() + global.config().worker.hold_time, }, }, None, @@ -177,18 +178,26 @@ impl Job { /// Whether the job was successfully completed or not, if the job was /// reclaimed by a different worker, it will not be completed and this will /// return false - pub async fn complete(&self, global: &Arc) -> Result { + pub async fn complete(&self, global: &Arc, error: Option) -> Result<(), mongodb::error::Error> { let success = Self::collection(global.database()) .delete_one( bson::doc! { - "_id": self.id.clone(), + "_id": self.id, "claimed_by_id": global.worker_id(), }, None, ) .await?; - Ok(success.deleted_count == 1) + if success.deleted_count == 1 { + if let Some(error) = error { + crate::events::on_failure(global, self, error).await; + } else { + crate::events::on_success(global, self).await; + } + } + + Ok(()) } /// Cancels a job @@ -198,15 +207,21 @@ impl Job { /// # Returns /// The job that was cancelled or None if no job was found pub async fn cancel(global: &Arc, id: ObjectId) -> Result, mongodb::error::Error> { - let job = Self::collection(global.database()) + let Some(job) = Self::collection(global.database()) .find_one_and_delete( bson::doc! { "_id": id, }, None, ) - .await?; + .await? + else { + return Ok(None); + }; - Ok(job) + // If the event had a cancel event, publish it + crate::events::on_cancel(global, &job).await; + + Ok(Some(job)) } } diff --git a/image-processor/src/disk/http.rs b/image-processor/src/drive/http.rs similarity index 100% rename from image-processor/src/disk/http.rs rename to image-processor/src/drive/http.rs diff --git a/image-processor/src/disk/local.rs b/image-processor/src/drive/local.rs similarity index 100% rename from image-processor/src/disk/local.rs rename to image-processor/src/drive/local.rs diff --git a/image-processor/src/disk/memory.rs b/image-processor/src/drive/memory.rs similarity index 88% rename from image-processor/src/disk/memory.rs rename to image-processor/src/drive/memory.rs index 30d2754f..0428b69e 100644 --- a/image-processor/src/disk/memory.rs +++ b/image-processor/src/drive/memory.rs @@ -44,7 +44,6 @@ pub struct MemoryDrive { name: String, mode: DriveMode, files: RwLock, - global: bool, } #[derive(Debug, Clone)] @@ -66,7 +65,6 @@ impl MemoryDrive { Ok(Self { name: config.name.clone(), mode: config.mode, - global: config.global, files: RwLock::new(FileHolder { remaining_capacity: config.capacity.unwrap_or(usize::MAX), files: HashMap::new(), @@ -88,13 +86,12 @@ impl Drive for MemoryDrive { return Err(DriveError::ReadOnly); } - Ok(self - .files + self.files .read() .await .get(path) .map(|file| file.data.clone()) - .ok_or(DriveError::NotFound)?) + .ok_or(DriveError::NotFound) } #[tracing::instrument(skip(self, data), name = "MemoryDisk::write", err, fields(size = data.len()))] @@ -129,23 +126,4 @@ impl Drive for MemoryDrive { self.files.write().await.remove(path).ok_or(DriveError::NotFound)?; Ok(()) } - - fn scoped(&self) -> Option - where - Self: Sized, - { - if self.global { - return None; - } - - Some(Self { - name: self.name.clone(), - mode: self.mode, - global: false, - files: RwLock::new(FileHolder { - remaining_capacity: 0, - files: HashMap::new(), - }), - }) - } } diff --git a/image-processor/src/disk/mod.rs b/image-processor/src/drive/mod.rs similarity index 97% rename from image-processor/src/disk/mod.rs rename to image-processor/src/drive/mod.rs index c48c70f4..3e708982 100644 --- a/image-processor/src/disk/mod.rs +++ b/image-processor/src/drive/mod.rs @@ -59,14 +59,6 @@ pub trait Drive { /// Delete data from a drive fn delete(&self, path: &str) -> impl std::future::Future> + Send; - /// Can be scoped to a specific request - fn scoped(&self) -> Option - where - Self: Sized, - { - None - } - fn healthy(&self) -> impl std::future::Future + Send { async { true } } diff --git a/image-processor/src/disk/public_http.rs b/image-processor/src/drive/public_http.rs similarity index 97% rename from image-processor/src/disk/public_http.rs rename to image-processor/src/drive/public_http.rs index ea70174c..54142917 100644 --- a/image-processor/src/disk/public_http.rs +++ b/image-processor/src/drive/public_http.rs @@ -56,7 +56,7 @@ impl PublicHttpDrive { builder = builder.default_headers(headers); - builder.build().map_err(|e| PublicHttpDriveError::Reqwest(e))? + builder.build().map_err(PublicHttpDriveError::Reqwest)? }, semaphore: config.max_connections.map(|max| tokio::sync::Semaphore::new(max)), }) diff --git a/image-processor/src/disk/s3.rs b/image-processor/src/drive/s3.rs similarity index 100% rename from image-processor/src/disk/s3.rs rename to image-processor/src/drive/s3.rs diff --git a/image-processor/src/event_queue/http.rs b/image-processor/src/event_queue/http.rs index dea43fdf..6f1ced2f 100644 --- a/image-processor/src/event_queue/http.rs +++ b/image-processor/src/event_queue/http.rs @@ -55,7 +55,7 @@ impl HttpEventQueue { builder = builder.default_headers(headers); - builder.build().map_err(|e| HttpEventQueueError::Reqwest(e))? + builder.build().map_err(HttpEventQueueError::Reqwest)? }, url: config.url.clone(), message_encoding: config.message_encoding, diff --git a/image-processor/src/events.rs b/image-processor/src/events.rs new file mode 100644 index 00000000..4207042e --- /dev/null +++ b/image-processor/src/events.rs @@ -0,0 +1,70 @@ +use std::sync::Arc; + +use scuffle_image_processor_proto::{event_callback, EventCallback, EventQueue as EventTopic}; + +use crate::database::Job; +use crate::event_queue::EventQueue; +use crate::global::Global; +use crate::worker::JobError; + +#[tracing::instrument(skip(global, job, event_topic), fields(topic = %event_topic.topic, name = %event_topic.name, job_id = %job.id))] +pub async fn on_event(global: &Arc, job: &Job, event_topic: &EventTopic, event: event_callback::Event) { + let Some(queue) = global.event_queue(&event_topic.name) else { + tracing::warn!("event queue not found: {}", event_topic.name); + return; + }; + + if let Err(err) = queue + .publish( + &event_topic.topic, + EventCallback { + id: job.id.to_string(), + timestamp: chrono::Utc::now().timestamp() as u64, + event: Some(event), + }, + ) + .await + { + tracing::error!("failed to publish event: {err}"); + } +} + +fn start_event(job: &Job) -> event_callback::Event { + event_callback::Event::Start(event_callback::Start {}) +} + +fn success_event(job: &Job) -> event_callback::Event { + event_callback::Event::Success(event_callback::Success {}) +} + +fn fail_event(job: &Job, err: JobError) -> event_callback::Event { + event_callback::Event::Fail(event_callback::Fail { error: Some(err.into()) }) +} + +fn cancel_event(job: &Job) -> event_callback::Event { + event_callback::Event::Cancel(event_callback::Cancel {}) +} + +pub async fn on_start(global: &Arc, job: &Job) { + if let Some(on_start) = &job.task.events.as_ref().and_then(|events| events.on_start.as_ref()) { + on_event(global, job, on_start, start_event(job)).await; + } +} + +pub async fn on_success(global: &Arc, job: &Job) { + if let Some(on_success) = &job.task.events.as_ref().and_then(|events| events.on_success.as_ref()) { + on_event(global, job, on_success, success_event(job)).await; + } +} + +pub async fn on_failure(global: &Arc, job: &Job, err: JobError) { + if let Some(on_failure) = &job.task.events.as_ref().and_then(|events| events.on_failure.as_ref()) { + on_event(global, job, on_failure, fail_event(job, err)).await; + } +} + +pub async fn on_cancel(global: &Arc, job: &Job) { + if let Some(on_cancel) = &job.task.events.as_ref().and_then(|events| events.on_cancel.as_ref()) { + on_event(global, job, on_cancel, cancel_event(job)).await; + } +} diff --git a/image-processor/src/global.rs b/image-processor/src/global.rs index b707d728..d474cc89 100644 --- a/image-processor/src/global.rs +++ b/image-processor/src/global.rs @@ -7,14 +7,13 @@ use scuffle_foundations::BootstrapResult; use crate::config::ImageProcessorConfig; use crate::database::Job; -use crate::disk::public_http::PUBLIC_HTTP_DRIVE_NAME; -use crate::disk::{build_drive, AnyDrive, Drive}; +use crate::drive::public_http::PUBLIC_HTTP_DRIVE_NAME; +use crate::drive::{build_drive, AnyDrive, Drive}; use crate::event_queue::{build_event_queue, AnyEventQueue, EventQueue}; pub struct Global { worker_id: ObjectId, config: ImageProcessorConfig, - client: mongodb::Client, database: mongodb::Database, disks: HashMap, event_queues: HashMap, @@ -81,7 +80,6 @@ impl Global { Ok(Self { worker_id: ObjectId::new(), config, - client, database, disks, event_queues, diff --git a/image-processor/src/main.rs b/image-processor/src/main.rs index fbca385e..27570169 100644 --- a/image-processor/src/main.rs +++ b/image-processor/src/main.rs @@ -4,7 +4,6 @@ use anyhow::Context; use scuffle_foundations::bootstrap::{bootstrap, Bootstrap}; use scuffle_foundations::runtime; use scuffle_foundations::settings::cli::Matches; -use scuffle_image_processor_proto::{event_callback, EventCallback}; use tokio::signal::unix::SignalKind; use self::config::ImageProcessorConfig; @@ -23,8 +22,9 @@ impl Bootstrap for ImageProcessorConfig { mod config; mod database; -mod disk; +mod drive; mod event_queue; +pub mod events; mod global; mod management; mod worker; diff --git a/image-processor/src/management/http.rs b/image-processor/src/management/http.rs index 54cc2b7e..0a245baf 100644 --- a/image-processor/src/management/http.rs +++ b/image-processor/src/management/http.rs @@ -61,6 +61,12 @@ async fn cancel_task( fn map_error_code(code: ErrorCode) -> http::StatusCode { match code { ErrorCode::InvalidInput => http::StatusCode::BAD_REQUEST, - ErrorCode::InternalError => http::StatusCode::INTERNAL_SERVER_ERROR, + ErrorCode::Internal => http::StatusCode::INTERNAL_SERVER_ERROR, + ErrorCode::NotImplemented => http::StatusCode::NOT_IMPLEMENTED, + ErrorCode::Decode => http::StatusCode::INTERNAL_SERVER_ERROR, + ErrorCode::Encode => http::StatusCode::INTERNAL_SERVER_ERROR, + ErrorCode::InputDownload => http::StatusCode::INTERNAL_SERVER_ERROR, + ErrorCode::OutputUpload => http::StatusCode::INTERNAL_SERVER_ERROR, + ErrorCode::Resize => http::StatusCode::INTERNAL_SERVER_ERROR, } } diff --git a/image-processor/src/management/mod.rs b/image-processor/src/management/mod.rs index 702f6659..42d6b333 100644 --- a/image-processor/src/management/mod.rs +++ b/image-processor/src/management/mod.rs @@ -1,13 +1,15 @@ use std::sync::Arc; use anyhow::Context; +use bytes::Bytes; use scuffle_image_processor_proto::{ CancelTaskRequest, CancelTaskResponse, Error, ErrorCode, ProcessImageRequest, ProcessImageResponse, }; -use url::Url; +use crate::database::Job; +use crate::drive::{Drive, DriveWriteOptions}; use crate::global::Global; -use crate::management::validation::{validate_task, Fragment, FragmentBuf}; +use crate::management::validation::{validate_input_upload, validate_task, FragmentBuf}; pub mod grpc; pub mod http; @@ -26,13 +28,74 @@ impl ManagementServer { validate_task(&self.global, fragment.push("task"), request.task.as_ref())?; // We need to do validation here. - if let Some(input_upload) = request.input_upload.as_ref() {} + if let Some(input_upload) = request.input_upload.as_ref() { + validate_input_upload(&self.global, fragment.push("input_upload"), Some(input_upload))?; + } + + if let Some(input_upload) = request.input_upload { + let drive_path = input_upload.path.unwrap(); + let drive = self.global.drive(&drive_path.drive).unwrap(); + + drive + .write( + &drive_path.path, + Bytes::from(input_upload.binary), + Some(DriveWriteOptions { + acl: input_upload.acl, + cache_control: input_upload.cache_control, + content_disposition: input_upload.content_disposition, + content_type: input_upload.content_type, + }), + ) + .await + .map_err(|err| { + tracing::error!("failed to write input upload: {:#}", err); + Error { + code: ErrorCode::Internal as i32, + message: format!("failed to write input upload: {err}"), + } + })?; + } + + let job = Job::new(&self.global, request.task.unwrap(), request.priority, request.ttl) + .await + .map_err(|err| { + tracing::error!("failed to create job: {:#}", err); + Error { + code: ErrorCode::Internal as i32, + message: format!("failed to create job: {err}"), + } + })?; - todo!() + Ok(ProcessImageResponse { + id: job.id.to_string(), + error: None, + }) } async fn cancel_task(&self, request: CancelTaskRequest) -> Result { - todo!() + match Job::cancel( + &self.global, + request.id.parse().map_err(|err| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("id: {err}"), + })?, + ) + .await + { + Ok(Some(_)) => Ok(CancelTaskResponse { error: None }), + Ok(None) => Err(Error { + code: ErrorCode::InvalidInput as i32, + message: "not found".to_owned(), + }), + Err(err) => { + tracing::error!("failed to cancel job: {:#}", err); + Err(Error { + code: ErrorCode::Internal as i32, + message: format!("failed to cancel job: {err}"), + }) + } + } } } diff --git a/image-processor/src/management/validation.rs b/image-processor/src/management/validation.rs index bef25a83..d4d8dd3b 100644 --- a/image-processor/src/management/validation.rs +++ b/image-processor/src/management/validation.rs @@ -1,7 +1,9 @@ +use std::collections::HashSet; use std::sync::Arc; use scuffle_image_processor_proto::{ - input, DrivePath, Error, ErrorCode, EventQueue, Events, Input, InputMetadata, Limits, Output, OutputVariants, Task, + animation_config, input, output, AnimationConfig, Crop, DrivePath, Error, ErrorCode, EventQueue, Events, Input, + InputMetadata, InputUpload, Limits, Output, OutputFormat, OutputFormatOptions, Task, }; use url::Url; @@ -23,14 +25,10 @@ impl FragmentBuf { Self { path: Vec::new() } } - pub fn push<'a>(&'a mut self, path: impl Into) -> Fragment<'a> { + pub fn push(&mut self, path: impl Into) -> Fragment<'_> { self.path.push(path.into()); Fragment::new(&mut self.path) } - - pub fn as_fagment(&mut self) -> Fragment { - Fragment::new(&mut self.path) - } } #[derive(Debug)] @@ -60,9 +58,9 @@ impl From for FragmentItem { // &&'static str -> &'static str -> FragmentItem // &usize -> usize -> FragmentItem impl From<&T> for FragmentItem - where - T: Copy, - FragmentItem: From, +where + T: Copy, + FragmentItem: From, { fn from(value: &T) -> Self { Self::from(*value) @@ -93,7 +91,7 @@ impl std::fmt::Display for Fragment<'_> { } impl Fragment<'_> { - pub fn push<'a>(&'a mut self, path: impl Into) -> Fragment<'a> { + pub fn push(&mut self, path: impl Into) -> Fragment<'_> { self.path.push(path.into()); Fragment::new(self.path) } @@ -105,6 +103,28 @@ impl Drop for Fragment<'_> { } } +pub fn validate_input_upload( + global: &Arc, + mut fragment: Fragment, + input_upload: Option<&InputUpload>, +) -> Result<(), Error> { + let input_upload = input_upload.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + validate_drive_path(global, fragment.push("path"), input_upload.path.as_ref(), &["id"])?; + + if input_upload.binary.is_empty() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: binary is required"), + }); + } + + Ok(()) +} + pub fn validate_task(global: &Arc, mut fragment: Fragment, task: Option<&Task>) -> Result<(), Error> { let task = task.ok_or_else(|| Error { code: ErrorCode::InvalidInput as i32, @@ -204,21 +224,10 @@ pub fn validate_output(global: &Arc, mut fragment: Fragment, output: Opt message: format!("{fragment}: is required"), })?; - validate_drive_path(global, fragment.push("path"), output.drive_path.as_ref())?; - - validate_output_variants(fragment.push("variants"), output.variants.as_ref())?; - - Ok(()) -} - -pub fn validate_output_variants(mut fragment: Fragment, variants: Option<&OutputVariants>) -> Result<(), Error> { - let variants = variants.ok_or_else(|| Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{fragment}: is required"), - })?; - - validate_template_string( - fragment.push("suffix"), + validate_drive_path( + global, + fragment.push("path"), + output.drive_path.as_ref(), &[ "id", "format", @@ -230,17 +239,206 @@ pub fn validate_output_variants(mut fragment: Fragment, variants: Option<&Output "static", "ext", ], - &variants.suffix, )?; - if variants.formats.is_empty() { + if output.formats.is_empty() { return Err(Error { code: ErrorCode::InvalidInput as i32, message: format!("{}: is required", fragment.push("formats")), }); } - for (idx, format) in variants.formats.iter().enumerate() {} + let mut formats = HashSet::new(); + for (idx, format) in output.formats.iter().enumerate() { + validate_output_format_options(fragment.push(idx), Some(format), &mut formats)?; + } + + validate_output_variants_resize(fragment.push("resize"), output.resize.as_ref())?; + + if let Some(animation_config) = output.animation_config.as_ref() { + validate_output_animation_config(fragment.push("animation_config"), Some(animation_config))?; + } + + if let Some(crop) = output.crop.as_ref() { + validate_crop(fragment.push("crop"), Some(crop))?; + } + + match (output.min_aspect_ratio, output.max_aspect_ratio) { + (Some(min_ratio), _) if min_ratio <= 0.0 => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: must be greater than or equal to 0", fragment.push("min_ratio")), + }); + } + (_, Some(max_ratio)) if max_ratio <= 0.0 => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: must be greater than or equal to 0", fragment.push("max_ratio")), + }); + } + (Some(min_ratio), Some(max_ratio)) if min_ratio > max_ratio => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: min_ratio must be less than or equal to max_ratio", fragment), + }); + } + _ => {} + } + + Ok(()) +} + +pub fn validate_crop(mut fragment: Fragment, crop: Option<&Crop>) -> Result<(), Error> { + let crop = crop.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + if crop.width == 0 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: width must be non 0", fragment.push("width")), + }); + } + + if crop.height == 0 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: height must be non 0", fragment.push("height")), + }); + } + + Ok(()) +} + +pub fn validate_output_animation_config( + mut fragment: Fragment, + animation_config: Option<&AnimationConfig>, +) -> Result<(), Error> { + let animation_config = animation_config.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + if let Some(loop_count) = animation_config.loop_count { + if loop_count < -1 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!( + "{}: loop_count must be greater than or equal to -1", + fragment.push("loop_count") + ), + }); + } + } + + if let Some(frame_rate) = &animation_config.frame_rate { + let mut fragment = fragment.push("frame_rate"); + + match frame_rate { + animation_config::FrameRate::DurationMs(duration_ms) => { + if *duration_ms == 0 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: duration_ms must be non 0", fragment), + }); + } + } + animation_config::FrameRate::DurationsMs(durations_ms) => { + let mut fragment = fragment.push("durations_ms.values"); + + if durations_ms.values.is_empty() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: durations_ms must not be empty"), + }); + } + + for (idx, duration_ms) in durations_ms.values.iter().enumerate() { + if *duration_ms == 0 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: duration_ms must be non 0", fragment.push(idx)), + }); + } + } + } + animation_config::FrameRate::Factor(factor) => { + if *factor > 0.0 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: factor must be greater than 0", fragment.push("factor")), + }); + } + } + } + } else { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: frame_rate is required", fragment.push("frame_rate")), + }); + } + + Ok(()) +} + +pub fn validate_output_variants_resize(mut fragment: Fragment, resize: Option<&output::Resize>) -> Result<(), Error> { + let resize = resize.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + let validate_items = |mut fragment: Fragment, items: &[u32]| { + if items.is_empty() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + }); + } + + for (idx, item) in items.iter().enumerate() { + if *item == 0 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: must be non 0", fragment.push(idx)), + }); + } + } + + Ok(()) + }; + + match resize { + output::Resize::Height(height) => { + validate_items(fragment.push("height.values"), &height.values)?; + } + output::Resize::Width(width) => { + validate_items(fragment.push("width.values"), &width.values)?; + } + output::Resize::Scaling(scaling) => { + validate_items(fragment.push("scaling.scales"), &scaling.scales)?; + } + } + + Ok(()) +} + +pub fn validate_output_format_options( + mut fragment: Fragment, + format: Option<&OutputFormatOptions>, + formats: &mut HashSet, +) -> Result<(), Error> { + let format = format.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + if !formats.insert(format.format()) { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: format already exists", fragment.push("format")), + }); + } Ok(()) } @@ -324,7 +522,7 @@ pub fn validate_input_path( match input_path { input::Path::DrivePath(drive_path) => { - validate_drive_path(global, fragment.push("drive_path"), Some(drive_path))?; + validate_drive_path(global, fragment.push("drive_path"), Some(drive_path), &["id"])?; } input::Path::PublicUrl(url) => { validate_public_url(global, fragment.push("public_url"), url)?; @@ -338,6 +536,7 @@ pub fn validate_drive_path( global: &Arc, mut fragment: Fragment, drive_path: Option<&DrivePath>, + allowed_vars: &[&str], ) -> Result<(), Error> { let drive_path = drive_path.ok_or_else(|| Error { code: ErrorCode::InvalidInput as i32, @@ -351,7 +550,7 @@ pub fn validate_drive_path( }); } - validate_template_string(fragment.push("path"), &["id"], &drive_path.path)?; + validate_template_string(fragment.push("path"), allowed_vars, &drive_path.path)?; Ok(()) } diff --git a/image-processor/src/processor/error.rs b/image-processor/src/processor/error.rs deleted file mode 100644 index a1684a56..00000000 --- a/image-processor/src/processor/error.rs +++ /dev/null @@ -1,124 +0,0 @@ -use aws_sdk_s3::operation::get_object::GetObjectError; -use aws_sdk_s3::operation::put_object::PutObjectError; -use file_format::FileFormat; - -#[derive(Debug, thiserror::Error)] -pub enum DecoderError { - #[error("input too long: {0}ms")] - TooLong(i64), - #[error("too many frames: {0}frms")] - TooManyFrames(i64), - #[error("input too high: {0}px")] - TooHigh(i32), - #[error("input too wide: {0}px")] - TooWide(i32), - #[error("{0}")] - Other(anyhow::Error), -} - -#[derive(Debug, thiserror::Error)] -pub enum ProcessorError { - #[error("semaphore ticket acquire: {0}")] - SemaphoreAcquire(#[from] tokio::sync::AcquireError), - - #[error("database: {0}")] - Database(#[from] scuffle_utils::database::tokio_postgres::Error), - - #[error("database pool: {0}")] - DatabasePool(#[from] scuffle_utils::database::deadpool_postgres::PoolError), - - #[error("lost job")] - LostJob, - - #[error("invalid job state")] - InvalidJobState, - - #[error("download source from s3: {0}")] - S3Download(aws_sdk_s3::error::SdkError), - - #[error("download source from s3: {0}")] - S3DownloadStream(aws_sdk_s3::primitives::ByteStreamError), - - #[error("upload target to s3: {0:?}")] - S3Upload(aws_sdk_s3::error::SdkError), - - #[error("publish to nats: {0}")] - NatsPublish(#[from] async_nats::PublishError), - - #[error("image: {0}")] - FileFormat(std::io::Error), - - #[error("unsupported input format: {0}")] - UnsupportedInputFormat(FileFormat), - - #[error("ffmpeg decode: {0}")] - FfmpegDecode(DecoderError), - - #[error("timelimit exceeded")] - TimeLimitExceeded, - - #[error("avif decode: {0}")] - AvifDecode(DecoderError), - - #[error("avif encode: {0}")] - AvifEncode(anyhow::Error), - - #[error("webp decode: {0}")] - WebPDecode(DecoderError), - - #[error("webp encode: {0}")] - WebPEncode(anyhow::Error), - - #[error("png encode: {0}")] - PngEncode(anyhow::Error), - - #[error("image resize: {0}")] - ImageResize(anyhow::Error), - - #[error("blocking task spawn")] - BlockingTaskSpawn, - - #[error("gifski encode: {0}")] - GifskiEncode(anyhow::Error), - - #[error("http download disabled")] - HttpDownloadDisabled, - - #[error("download timeout")] - DownloadTimeout, - - #[error("http download: {0}")] - HttpDownload(#[from] reqwest::Error), -} - -impl ProcessorError { - pub fn friendly_message(&self) -> String { - let msg = match self { - ProcessorError::LostJob => Some("The job was lost"), - ProcessorError::InvalidJobState => Some("The job is in an invalid state"), - ProcessorError::S3Download(_) => Some("Failed to download file"), - ProcessorError::S3Upload(_) => Some("Failed to upload file"), - ProcessorError::FileFormat(_) => Some("Failed to read file format"), - ProcessorError::UnsupportedInputFormat(_) => { - Some("Unsupported input format. Please use one of the supported formats.") - } - ProcessorError::TimeLimitExceeded => Some("The job took too long to process the file"), - ProcessorError::AvifEncode(_) => Some("Failed to reencode image to AVIF"), - ProcessorError::WebPEncode(_) => Some("Failed to reencode image to WebP"), - ProcessorError::PngEncode(_) => Some("Failed to reencode image to PNG"), - ProcessorError::ImageResize(_) => Some("Failed to resize image"), - ProcessorError::GifskiEncode(_) => Some("Failed to reencode image to GIF"), - ProcessorError::FfmpegDecode(e) | ProcessorError::AvifDecode(e) | ProcessorError::WebPDecode(e) => match e { - DecoderError::TooLong(_) => Some("The file is too long"), - DecoderError::TooManyFrames(_) => Some("The file has too many frames"), - DecoderError::TooWide(_) => Some("The image is too wide"), - DecoderError::TooHigh(_) => Some("The image is too high"), - DecoderError::Other(_) => None, - }, - _ => None, - }; - msg.map(|m| m.to_string()).unwrap_or_else(|| format!("{}", self)) - } -} - -pub type Result = std::result::Result; diff --git a/image-processor/src/processor/job/decoder/ffmpeg.rs b/image-processor/src/processor/job/decoder/ffmpeg.rs deleted file mode 100644 index e674bc1c..00000000 --- a/image-processor/src/processor/job/decoder/ffmpeg.rs +++ /dev/null @@ -1,226 +0,0 @@ -use std::borrow::Cow; - -use anyhow::{anyhow, Context as _}; -use imgref::Img; -use rgb::RGBA8; - -use super::{Decoder, DecoderBackend, DecoderInfo, LoopCount}; -use crate::database::Job; -use crate::processor::error::{DecoderError, ProcessorError, Result}; -use crate::processor::job::frame::Frame; - -pub struct FfmpegDecoder<'data> { - input: scuffle_ffmpeg::io::Input>>, - decoder: scuffle_ffmpeg::decoder::VideoDecoder, - scaler: scuffle_ffmpeg::scalar::Scalar, - info: DecoderInfo, - input_stream_index: i32, - average_frame_duration_ts: u64, - send_packet: bool, - eof: bool, - done: bool, -} - -const fn cast_bytes_to_rgba(bytes: &[u8]) -> &[rgb::RGBA8] { - unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const _, bytes.len() / 4) } -} - -static FFMPEG_LOGGING_INITIALIZED: std::sync::Once = std::sync::Once::new(); - -impl<'data> FfmpegDecoder<'data> { - pub fn new(job: &Job, data: Cow<'data, [u8]>) -> Result { - FFMPEG_LOGGING_INITIALIZED.call_once(|| { - scuffle_ffmpeg::log::log_callback_tracing(); - }); - - let input = scuffle_ffmpeg::io::Input::seekable(std::io::Cursor::new(data)) - .context("input") - .map_err(DecoderError::Other) - .map_err(ProcessorError::FfmpegDecode)?; - - let input_stream = input - .streams() - .best(scuffle_ffmpeg::ffi::AVMediaType::AVMEDIA_TYPE_VIDEO) - .ok_or_else(|| ProcessorError::FfmpegDecode(DecoderError::Other(anyhow!("no video stream"))))?; - - let input_stream_index = input_stream.index(); - - let input_stream_time_base = input_stream.time_base(); - let input_stream_duration = input_stream.duration().unwrap_or(0); - let input_stream_frames = input_stream - .nb_frames() - .ok_or_else(|| ProcessorError::FfmpegDecode(DecoderError::Other(anyhow!("no frame count"))))? - .max(1); - - if input_stream_time_base.den == 0 || input_stream_time_base.num == 0 { - return Err(ProcessorError::FfmpegDecode(DecoderError::Other(anyhow!( - "stream time base is 0" - )))); - } - - let decoder = match scuffle_ffmpeg::decoder::Decoder::new(&input_stream) - .context("video decoder") - .map_err(DecoderError::Other) - .map_err(ProcessorError::FfmpegDecode)? - { - scuffle_ffmpeg::decoder::Decoder::Video(decoder) => decoder, - _ => { - return Err(ProcessorError::FfmpegDecode(DecoderError::Other(anyhow!( - "not a video decoder" - )))); - } - }; - - let max_input_width = job.task.limits.as_ref().map(|l| l.max_input_width).unwrap_or(0) as i32; - let max_input_height = job.task.limits.as_ref().map(|l| l.max_input_height).unwrap_or(0) as i32; - let max_input_frame_count = job.task.limits.as_ref().map(|l| l.max_input_frame_count).unwrap_or(0) as i32; - let max_input_duration_ms = job.task.limits.as_ref().map(|l| l.max_input_duration_ms).unwrap_or(0) as i32; - - if max_input_width > 0 && decoder.width() > max_input_width { - return Err(ProcessorError::FfmpegDecode(DecoderError::TooWide(decoder.width()))); - } - - if max_input_height > 0 && decoder.height() > max_input_height { - return Err(ProcessorError::FfmpegDecode(DecoderError::TooHigh(decoder.height()))); - } - - if max_input_frame_count > 0 && input_stream_frames > max_input_frame_count as i64 { - return Err(ProcessorError::FfmpegDecode(DecoderError::TooManyFrames(input_stream_frames))); - } - - // actual duration - // = duration * (time_base.num / time_base.den) * 1000 - // = (duration * time_base.num * 1000) / time_base.den - let duration = - (input_stream_duration * input_stream_time_base.num as i64 * 1000) / input_stream_time_base.den as i64; - if max_input_duration_ms > 0 && duration > max_input_duration_ms as i64 { - return Err(ProcessorError::FfmpegDecode(DecoderError::TooLong(duration))); - } - - let scaler = scuffle_ffmpeg::scalar::Scalar::new( - decoder.width(), - decoder.height(), - decoder.pixel_format(), - decoder.width(), - decoder.height(), - scuffle_ffmpeg::ffi::AVPixelFormat::AV_PIX_FMT_RGBA, - ) - .context("scaler") - .map_err(DecoderError::Other) - .map_err(ProcessorError::FfmpegDecode)?; - - Ok(Self { - info: DecoderInfo { - width: decoder.width() as usize, - height: decoder.height() as usize, - frame_count: input_stream_frames as usize, - // TODO: Support loop count from ffmpeg. - loop_count: LoopCount::Infinite, - timescale: input_stream_time_base.den as u64, - }, - average_frame_duration_ts: (input_stream_duration / input_stream_frames) as u64, - input, - scaler, - decoder, - input_stream_index, - done: false, - eof: false, - send_packet: true, - }) - } -} - -impl Decoder for FfmpegDecoder<'_> { - fn backend(&self) -> DecoderBackend { - DecoderBackend::Ffmpeg - } - - fn decode(&mut self) -> Result> { - if self.done { - return Ok(None); - } - - loop { - if self.send_packet && !self.eof { - let packet = self - .input - .packets() - .find_map(|packet| match packet { - Ok(packet) => { - if packet.stream_index() == self.input_stream_index { - Some(Ok(packet)) - } else { - None - } - } - Err(err) => { - self.done = true; - Some(Err(err)) - } - }) - .transpose() - .context("receive packet") - .map_err(DecoderError::Other) - .map_err(ProcessorError::FfmpegDecode)?; - - if let Some(packet) = packet { - self.decoder.send_packet(&packet).context("send packet").map_err(|err| { - self.done = true; - ProcessorError::FfmpegDecode(DecoderError::Other(err)) - })?; - } else { - self.decoder.send_eof().context("send eof").map_err(|err| { - self.done = true; - ProcessorError::FfmpegDecode(DecoderError::Other(err)) - })?; - self.eof = true; - } - - self.send_packet = false; - } - - let frame = self.decoder.receive_frame().context("receive frame").map_err(|err| { - self.done = true; - ProcessorError::FfmpegDecode(DecoderError::Other(err)) - })?; - - if let Some(frame) = frame { - let frame = self.scaler.process(&frame).context("scaler run").map_err(|err| { - self.done = true; - ProcessorError::FfmpegDecode(DecoderError::Other(err)) - })?; - - let mut data = vec![RGBA8::default(); frame.width() * frame.height()]; - - // The frame has padding, so we need to copy the data. - let frame_data = frame.data(0).unwrap(); - let frame_linesize = frame.linesize(0).unwrap(); - - if frame_linesize == frame.width() as i32 * 4 { - // No padding, so we can just copy the data. - data.copy_from_slice(cast_bytes_to_rgba(frame_data)); - } else { - // The frame has padding, so we need to copy the data. - for (i, row) in data.chunks_exact_mut(frame.width()).enumerate() { - let row_data = &frame_data[i * frame_linesize as usize..][..frame.width() * 4]; - row.copy_from_slice(cast_bytes_to_rgba(row_data)); - } - } - - return Ok(Some(Frame { - image: Img::new(data, self.info.width, self.info.height), - duration_ts: self.average_frame_duration_ts, - })); - } else if self.eof { - self.done = true; - return Ok(None); - } else { - self.send_packet = true; - } - } - } - - fn info(&self) -> DecoderInfo { - self.info - } -} diff --git a/image-processor/src/processor/job/decoder/libavif.rs b/image-processor/src/processor/job/decoder/libavif.rs deleted file mode 100644 index bc2a5dd0..00000000 --- a/image-processor/src/processor/job/decoder/libavif.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::borrow::Cow; -use std::ptr::NonNull; - -use anyhow::Context; - -use super::{Decoder, DecoderBackend, DecoderInfo, LoopCount}; -use crate::database::Job; -use crate::processor::error::{DecoderError, ProcessorError, Result}; -use crate::processor::job::frame::Frame; -use crate::processor::job::libavif::{AvifError, AvifRgbImage}; -use crate::processor::job::smart_object::SmartPtr; - -#[derive(Debug)] -pub struct AvifDecoder<'data> { - decoder: SmartPtr, - info: DecoderInfo, - _data: Cow<'data, [u8]>, - img: AvifRgbImage, - total_duration: u64, - max_input_duration: u64, -} - -impl<'data> AvifDecoder<'data> { - pub fn new(job: &Job, data: Cow<'data, [u8]>) -> Result { - let mut decoder = SmartPtr::new( - NonNull::new(unsafe { libavif_sys::avifDecoderCreate() }) - .ok_or(AvifError::OutOfMemory) - .context("failed to create avif decoder") - .map_err(DecoderError::Other) - .map_err(ProcessorError::AvifDecode)?, - |ptr| { - // Safety: The decoder is valid. - unsafe { - libavif_sys::avifDecoderDestroy(ptr.as_ptr()); - } - }, - ); - - let max_input_width = job.task.limits.as_ref().map(|l| l.max_input_width).unwrap_or(0); - let max_input_height = job.task.limits.as_ref().map(|l| l.max_input_height).unwrap_or(0); - if max_input_height != 0 && max_input_width != 0 { - decoder.as_mut().imageDimensionLimit = max_input_width * max_input_height; - } - - let max_input_frame_count = job.task.limits.as_ref().map(|l| l.max_input_frame_count).unwrap_or(0); - if max_input_frame_count != 0 { - decoder.as_mut().imageCountLimit = max_input_frame_count; - } - - // Safety: The decoder is valid. - let io = NonNull::new(unsafe { libavif_sys::avifIOCreateMemoryReader(data.as_ptr(), data.len()) }) - .ok_or(AvifError::OutOfMemory) - .context("failed to create avif io") - .map_err(DecoderError::Other) - .map_err(ProcessorError::AvifDecode)?; - - // Set the io pointer. - decoder.as_mut().io = io.as_ptr(); - - // Parse the data. - AvifError::from_code(unsafe { libavif_sys::avifDecoderParse(decoder.as_ptr()) }) - .context("failed to parse avif") - .map_err(DecoderError::Other) - .map_err(ProcessorError::AvifDecode)?; - - let image = AvifRgbImage::new(decoder.as_ref()); - - let info = DecoderInfo { - width: image.width as usize, - height: image.height as usize, - loop_count: if decoder.as_ref().repetitionCount <= 0 { - LoopCount::Infinite - } else { - LoopCount::Finite(decoder.as_ref().repetitionCount as usize) - }, - frame_count: decoder.as_ref().imageCount.max(0) as _, - timescale: decoder.as_ref().timescale, - }; - - let max_input_duration_ms = job.task.limits.as_ref().map(|l| l.max_input_duration_ms).unwrap_or(0); - - if max_input_width != 0 && info.width > max_input_width as usize { - return Err(ProcessorError::AvifDecode(DecoderError::TooWide(info.width as i32))); - } - - if max_input_height != 0 && info.height > max_input_height as usize { - return Err(ProcessorError::AvifDecode(DecoderError::TooHigh(info.height as i32))); - } - - if max_input_frame_count != 0 && info.frame_count > max_input_frame_count as usize { - return Err(ProcessorError::AvifDecode(DecoderError::TooManyFrames( - info.frame_count as i64, - ))); - } - - Ok(Self { - _data: data, - img: AvifRgbImage::new(decoder.as_ref()), - decoder, - max_input_duration: max_input_duration_ms as u64 * info.timescale / 1000, - total_duration: 0, - info, - }) - } -} - -impl Decoder for AvifDecoder<'_> { - fn backend(&self) -> DecoderBackend { - DecoderBackend::LibAvif - } - - fn info(&self) -> DecoderInfo { - self.info - } - - fn decode(&mut self) -> Result> { - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - - if AvifError::from_code(unsafe { libavif_sys::avifDecoderNextImage(self.decoder.as_ptr()) }).is_err() { - return Ok(None); - } - - AvifError::from_code(unsafe { libavif_sys::avifImageYUVToRGB(self.decoder.as_ref().image, &mut *self.img) }) - .context("failed to convert YUV to RGB") - .map_err(DecoderError::Other) - .map_err(ProcessorError::AvifDecode)?; - - let duration_ts = self.decoder.as_ref().imageTiming.durationInTimescales; - self.total_duration += duration_ts; - - if self.max_input_duration != 0 && self.total_duration > self.max_input_duration { - return Err(ProcessorError::AvifDecode(DecoderError::TooLong(self.total_duration as i64))); - } - - Ok(Some(Frame { - image: self.img.data().clone(), - duration_ts, - })) - } -} diff --git a/image-processor/src/processor/job/decoder/libwebp.rs b/image-processor/src/processor/job/decoder/libwebp.rs deleted file mode 100644 index 201999e0..00000000 --- a/image-processor/src/processor/job/decoder/libwebp.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::borrow::Cow; -use std::ptr::NonNull; - -use anyhow::{anyhow, Context}; -use imgref::Img; - -use super::{Decoder, DecoderBackend, DecoderInfo, LoopCount}; -use crate::database::Job; -use crate::processor::error::{DecoderError, ProcessorError, Result}; -use crate::processor::job::frame::Frame; -use crate::processor::job::libwebp::{zero_memory_default, WebPError}; -use crate::processor::job::smart_object::SmartPtr; - -pub struct WebpDecoder<'data> { - info: DecoderInfo, - decoder: SmartPtr, - _data: Cow<'data, [u8]>, - timestamp: i32, - total_duration: u64, - max_input_duration: u64, -} - -impl<'data> WebpDecoder<'data> { - pub fn new(job: &Job, data: Cow<'data, [u8]>) -> Result { - let max_input_width = job.task.limits.as_ref().map(|l| l.max_input_width).unwrap_or(0); - let max_input_height = job.task.limits.as_ref().map(|l| l.max_input_height).unwrap_or(0); - let max_input_frame_count = job.task.limits.as_ref().map(|l| l.max_input_frame_count).unwrap_or(0); - let max_input_duration_ms = job.task.limits.as_ref().map(|l| l.max_input_duration_ms).unwrap_or(0); - - let decoder = SmartPtr::new( - NonNull::new(unsafe { - libwebp_sys::WebPAnimDecoderNew( - &libwebp_sys::WebPData { - bytes: data.as_ptr(), - size: data.len(), - }, - std::ptr::null(), - ) - }) - .ok_or(WebPError::OutOfMemory) - .context("failed to create webp decoder") - .map_err(DecoderError::Other) - .map_err(ProcessorError::WebPDecode)?, - |decoder| { - // Safety: The decoder is valid. - unsafe { - libwebp_sys::WebPAnimDecoderDelete(decoder.as_ptr()); - } - }, - ); - - let mut info = zero_memory_default::(); - - // Safety: both pointers are valid and the decoder is valid. - if unsafe { libwebp_sys::WebPAnimDecoderGetInfo(decoder.as_ptr(), &mut info) } == 0 { - return Err(ProcessorError::WebPDecode(DecoderError::Other(anyhow!( - "failed to get webp info" - )))); - } - - if max_input_width != 0 && info.canvas_width > max_input_width { - return Err(ProcessorError::WebPDecode(DecoderError::TooWide(info.canvas_width as i32))); - } - - if max_input_height != 0 && info.canvas_height > max_input_height { - return Err(ProcessorError::WebPDecode(DecoderError::TooHigh(info.canvas_height as i32))); - } - - if max_input_frame_count != 0 && info.frame_count > max_input_frame_count { - return Err(ProcessorError::WebPDecode(DecoderError::TooManyFrames( - info.frame_count as i64, - ))); - } - - Ok(Self { - info: DecoderInfo { - width: info.canvas_width as _, - height: info.canvas_height as _, - loop_count: match info.loop_count { - 0 => LoopCount::Infinite, - _ => LoopCount::Finite(info.loop_count as _), - }, - frame_count: info.frame_count as _, - timescale: 1000, - }, - max_input_duration: max_input_duration_ms as u64, - decoder, - _data: data, - total_duration: 0, - timestamp: 0, - }) - } -} - -impl Decoder for WebpDecoder<'_> { - fn backend(&self) -> DecoderBackend { - DecoderBackend::LibWebp - } - - fn info(&self) -> DecoderInfo { - self.info - } - - fn decode(&mut self) -> Result> { - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - - let mut buf = std::ptr::null_mut(); - let previous_timestamp = self.timestamp; - - // Safety: The buffer is a valid pointer to a null ptr, timestamp is a valid - // pointer to i32, and the decoder is valid. - let result = unsafe { libwebp_sys::WebPAnimDecoderGetNext(self.decoder.as_ptr(), &mut buf, &mut self.timestamp) }; - - // If 0 is returned, the animation is over. - if result == 0 { - return Ok(None); - } - - let buf = NonNull::new(buf) - .ok_or(WebPError::OutOfMemory) - .context("failed to get webp frame") - .map_err(DecoderError::Other) - .map_err(ProcessorError::WebPDecode)?; - - let image = - unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const rgb::RGBA8, self.info.width * self.info.height) }; - - let duration_ts = (self.timestamp - previous_timestamp).max(0) as u64; - self.total_duration += duration_ts; - - if self.max_input_duration != 0 && self.total_duration > self.max_input_duration { - return Err(ProcessorError::WebPDecode(DecoderError::TooLong(self.total_duration as i64))); - } - - Ok(Some(Frame { - image: Img::new(image.to_vec(), self.info.width, self.info.height), - duration_ts: (self.timestamp - previous_timestamp).max(0) as _, - })) - } -} diff --git a/image-processor/src/processor/job/encoder/gifski.rs b/image-processor/src/processor/job/encoder/gifski.rs deleted file mode 100644 index 642e768a..00000000 --- a/image-processor/src/processor/job/encoder/gifski.rs +++ /dev/null @@ -1,86 +0,0 @@ -use anyhow::Context; -use scuffle_utils::task::Task; - -use super::{Encoder, EncoderFrontend, EncoderInfo, EncoderSettings}; -use crate::processor::error::{ProcessorError, Result}; -use crate::processor::job::decoder::LoopCount; -use crate::processor::job::frame::Frame; - -pub struct GifskiEncoder { - collector: gifski::Collector, - writer: Task>>, - info: EncoderInfo, -} - -impl GifskiEncoder { - pub fn new(settings: EncoderSettings) -> Result { - let (collector, writer) = gifski::new(gifski::Settings { - repeat: match settings.loop_count { - LoopCount::Infinite => gifski::Repeat::Infinite, - LoopCount::Finite(count) => gifski::Repeat::Finite(count as u16), - }, - fast: settings.fast, - ..Default::default() - }) - .context("failed to create gifski encoder") - .map_err(ProcessorError::GifskiEncode)?; - - Ok(Self { - collector, - writer: Task::spawn("gifski writer", move || { - let mut buffer = Vec::new(); - writer - .write(&mut buffer, &mut gifski::progress::NoProgress {}) - .context("failed to write gifski output") - .map_err(ProcessorError::GifskiEncode)?; - Ok(buffer) - }), - info: EncoderInfo { - duration: 0, - frame_count: 0, - frontend: EncoderFrontend::Gifski, - height: 0, - loop_count: settings.loop_count, - timescale: settings.timescale, - width: 0, - }, - }) - } - - fn duration(&mut self, duration: u64) -> f64 { - self.info.duration += duration; - self.info.duration as f64 / self.info.timescale as f64 - } -} - -impl Encoder for GifskiEncoder { - fn info(&self) -> EncoderInfo { - self.info - } - - fn add_frame(&mut self, frame: &Frame) -> Result<()> { - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - - let frame = frame.to_owned(); - self.info.height = frame.image.height(); - self.info.width = frame.image.width(); - let duration = self.duration(frame.duration_ts); - self.collector - .add_frame_rgba(self.info.frame_count, frame.image, duration) - .context("failed to add frame to gifski") - .map_err(ProcessorError::GifskiEncode)?; - self.info.frame_count += 1; - Ok(()) - } - - fn finish(self) -> Result> { - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - - drop(self.collector); - - self.writer - .join() - .map_err(|err| anyhow::anyhow!("failed to join gifski thread: {:?}", err)) - .map_err(ProcessorError::GifskiEncode)? - } -} diff --git a/image-processor/src/processor/job/frame.rs b/image-processor/src/processor/job/frame.rs deleted file mode 100644 index 9dd4409d..00000000 --- a/image-processor/src/processor/job/frame.rs +++ /dev/null @@ -1,7 +0,0 @@ -use imgref::ImgVec; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Frame { - pub image: ImgVec, - pub duration_ts: u64, -} diff --git a/image-processor/src/processor/job/mod.rs b/image-processor/src/processor/job/mod.rs deleted file mode 100644 index bd7e4a42..00000000 --- a/image-processor/src/processor/job/mod.rs +++ /dev/null @@ -1,235 +0,0 @@ -use std::borrow::Cow; -use std::sync::Arc; -use std::time::Duration; - -use scuffle_utils::prelude::FutureTimeout; -use scuffle_utils::task::AsyncTask; -use aws_sdk_s3::types::ObjectCannedAcl; -use bytes::Bytes; -use file_format::FileFormat; -use futures::FutureExt; -use prost::Message; -use tokio::select; -use tracing::Instrument; - -use self::decoder::DecoderBackend; -use super::error::{ProcessorError, Result}; -use super::utils; -use crate::{database, pb}; -use crate::global::ImageProcessorGlobal; -use crate::processor::utils::refresh_job; - -pub(crate) mod decoder; -pub(crate) mod encoder; -pub(crate) mod frame; -pub(crate) mod libavif; -pub(crate) mod libwebp; -pub(crate) mod process; -pub(crate) mod resize; -pub(crate) mod scaling; -pub(crate) mod smart_object; - -pub(crate) struct Job<'a, G: ImageProcessorGlobal> { - pub(crate) global: &'a Arc, - pub(crate) job: database::Job, -} - -#[tracing::instrument(skip(global, job), fields(job_id = %job.id), level = "info")] -pub async fn handle_job(global: &Arc, job: database::Job) { - let job = Job { global, job }; - - tracing::info!("processing job"); - - if let Err(err) = job.process().in_current_span().await { - tracing::error!(err = %err, "job failed"); - } -} - -impl<'a, G: ImageProcessorGlobal> Job<'a, G> { - async fn download_source(&self) -> Result { - if self.job.task.input_path.starts_with("http://") || self.job.task.input_path.starts_with("https://") { - if !self.global.config().allow_http { - return Err(ProcessorError::HttpDownloadDisabled); - } - - tracing::debug!("downloading {}", self.job.task.input_path); - - Ok(self - .global - .http_client() - .get(&self.job.task.input_path) - .send() - .await - .map_err(ProcessorError::HttpDownload)? - .error_for_status() - .map_err(ProcessorError::HttpDownload)? - .bytes() - .await - .map_err(ProcessorError::HttpDownload)?) - } else { - tracing::debug!( - "downloading {}/{}", - self.global.config().source_bucket.name, - self.job.task.input_path - ); - - let response = self - .global - .s3_source_bucket() - .get_object(&self.job.task.input_path) - .await - .map_err(ProcessorError::S3Download)?; - - let body = response.body.collect().await.map_err(ProcessorError::S3DownloadStream)?; - Ok(body.into_bytes()) - } - } - - pub(crate) async fn process(self) -> Result<()> { - if let Err(e) = self.process_with_timeout().in_current_span().await { - tracing::warn!(err = %e, "job failed"); - tracing::debug!("publishing job failure event to {}", self.job.task.callback_subject); - self.global - .nats() - .publish( - self.job.task.callback_subject.clone(), - pb::EventPayload { - id: todo!(), - } - .encode_to_vec() - .into(), - ) - .in_current_span() - .await - .map_err(|e| { - tracing::error!(err = %e, "failed to publish event"); - e - })?; - } - - // delete job - utils::delete_job(self.global, self.job.id).await?; - - Ok(()) - } - - async fn process_with_timeout(&self) -> Result<()> { - let mut interval = tokio::time::interval(std::time::Duration::from_secs(15)); - - let job_id = self.job.id; - let max_processing_time_ms = self.job.task.limits.as_ref().map(|l| l.max_processing_time_ms); - - let time_limit = async { - if let Some(max_processing_time_ms) = max_processing_time_ms { - tokio::time::sleep(std::time::Duration::from_millis(max_processing_time_ms as u64)).await; - Err(ProcessorError::TimeLimitExceeded) - } else { - Ok(()) - } - }; - - let global = self.global.clone(); - let mut process = std::pin::pin!(self.inner_process().in_current_span()); - let time_limit = std::pin::pin!(time_limit); - let mut time_limit = time_limit.fuse(); - - loop { - select! { - _ = interval.tick() => { - refresh_job(&global, job_id).in_current_span().await?; - }, - Err(e) = &mut time_limit => { - return Err(e); - }, - r = &mut process => { - return r; - }, - } - } - } - - async fn inner_process(&self) -> Result<()> { - let input_data = { - let mut tries = 0; - loop { - match self.download_source().timeout(Duration::from_secs(5)).await { - Ok(Ok(data)) => break data, - Ok(Err(e)) => { - if tries >= 60 { - return Err(e); - } - - tries += 1; - tracing::debug!(err = %e, "failed to download source, retrying"); - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - } - Err(_) => { - if tries >= 60 { - return Err(ProcessorError::DownloadTimeout); - } - - tries += 1; - tracing::debug!("download timed out, retrying"); - } - } - } - }; - - let backend = DecoderBackend::from_format(FileFormat::from_bytes(&input_data))?; - - let job_c = self.job.clone(); - - tracing::debug!("processing job"); - - let images = AsyncTask::spawn_blocking("process", move || { - process::process_job(backend, &job_c, Cow::Borrowed(&input_data)) - }) - .join() - .await - .map_err(|e| { - tracing::error!(err = %e, "failed to process job"); - ProcessorError::BlockingTaskSpawn - })??; - - for image in images.images.iter() { - let url = image.url(&self.job.task.output_prefix); - // image upload - tracing::debug!("uploading result to {}/{}", self.global.config().target_bucket.name, url); - self.global - .s3_target_bucket() - .put_object( - url, - image.data.clone(), - Some(PutObjectOptions { - acl: Some(ObjectCannedAcl::PublicRead), - content_type: Some(image.content_type().into()), - }), - ) - .in_current_span() - .await - .map_err(ProcessorError::S3Upload)?; - } - // job completion - tracing::debug!("publishing job completion event to {}", self.job.task.callback_subject); - self.global - .nats() - .publish( - self.job.task.callback_subject.clone(), - pb::EventPayload { - id: todo!(), - } - .encode_to_vec() - .into(), - ) - .in_current_span() - .await - .map_err(|e| { - tracing::error!(err = %e, "failed to publish event"); - e - })?; - - tracing::info!("job completed"); - - Ok(()) - } -} diff --git a/image-processor/src/processor/job/process.rs b/image-processor/src/processor/job/process.rs deleted file mode 100644 index d34230d4..00000000 --- a/image-processor/src/processor/job/process.rs +++ /dev/null @@ -1,304 +0,0 @@ -use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; - -use bytes::Bytes; -use rgb::ComponentBytes; -use sha2::Digest; - -use super::decoder::{Decoder, DecoderBackend, LoopCount}; -use super::encoder::{AnyEncoder, Encoder, EncoderFrontend, EncoderSettings}; -use super::resize::{ImageResizer, ImageResizerTarget}; -use crate::database::Job; -use crate::pb::{ImageFormat, ResizeMethod}; -use crate::processor::error::{ProcessorError, Result}; -use crate::processor::job::scaling::{Ratio, ScalingOptions}; - -#[derive(Debug)] -#[allow(dead_code)] -pub struct Image { - pub width: usize, - pub height: usize, - pub frame_count: usize, - pub duration: f64, - pub encoder: EncoderFrontend, - pub data: Bytes, - pub loop_count: LoopCount, - pub request: ImageFormat, -} - -impl Image { - pub fn file_extension(&self) -> &'static str { - match self.request { - ImageFormat::Avif | ImageFormat::AvifStatic => "avif", - ImageFormat::Webp | ImageFormat::WebpStatic => "webp", - ImageFormat::Gif => "gif", - ImageFormat::PngStatic => "png", - } - } - - pub fn content_type(&self) -> &'static str { - match self.request { - ImageFormat::Avif | ImageFormat::AvifStatic => "image/avif", - ImageFormat::Webp | ImageFormat::WebpStatic => "image/webp", - ImageFormat::Gif => "image/gif", - ImageFormat::PngStatic => "image/png", - } - } - - pub fn is_static(&self) -> bool { - matches!( - self.request, - ImageFormat::AvifStatic | ImageFormat::WebpStatic | ImageFormat::PngStatic - ) - } - - pub fn url(&self, prefix: &str) -> String { - format!( - "{prefix}/{static_prefix}{width}x{height}.{ext}", - prefix = prefix.trim_end_matches('/'), - static_prefix = self.is_static().then_some("static_").unwrap_or_default(), - width = self.width, - height = self.height, - ext = self.file_extension() - ) - } -} - -#[derive(Debug)] -pub struct Images { - pub images: Vec, -} - -pub fn process_job(backend: DecoderBackend, job: &Job, data: Cow<'_, [u8]>) -> Result { - let mut decoder = backend.build(job, data)?; - - let info = decoder.info(); - - let formats = job.task.formats().collect::>(); - let mut scales = job.task.scales.iter().cloned().map(|s| s as usize).collect::>(); - - // Sorts the scales from smallest to largest. - scales.sort(); - - if formats.is_empty() || scales.is_empty() { - tracing::debug!("no formats or scales specified"); - return Err(ProcessorError::InvalidJobState); - } - - let static_formats = formats - .iter() - .filter_map(|f| match f { - ImageFormat::AvifStatic => Some(EncoderFrontend::LibAvif), - ImageFormat::WebpStatic => Some(EncoderFrontend::LibWebp), - ImageFormat::PngStatic => Some(EncoderFrontend::Png), - _ => None, - }) - .collect::>(); - - let animation_formats = formats - .iter() - .filter_map(|f| match f { - ImageFormat::Avif => Some(EncoderFrontend::LibAvif), - ImageFormat::Webp => Some(EncoderFrontend::LibWebp), - ImageFormat::Gif => Some(EncoderFrontend::Gifski), - _ => None, - }) - .collect::>(); - - if static_formats.is_empty() && animation_formats.is_empty() { - tracing::debug!("no static or animation formats specified"); - return Err(ProcessorError::InvalidJobState); - } - - let anim_settings = EncoderSettings { - fast: true, - loop_count: info.loop_count, - timescale: info.timescale, - static_image: false, - }; - - let static_settings = EncoderSettings { - fast: true, - loop_count: info.loop_count, - timescale: info.timescale, - static_image: true, - }; - - let (preserve_aspect_height, preserve_aspect_width) = match job.task.resize_method() { - ResizeMethod::Fit => (true, true), - ResizeMethod::Stretch => (false, false), - ResizeMethod::PadBottomLeft => (false, false), - ResizeMethod::PadBottomRight => (false, false), - ResizeMethod::PadTopLeft => (false, false), - ResizeMethod::PadTopRight => (false, false), - ResizeMethod::PadCenter => (false, false), - ResizeMethod::PadCenterLeft => (false, false), - ResizeMethod::PadCenterRight => (false, false), - ResizeMethod::PadTopCenter => (false, false), - ResizeMethod::PadBottomCenter => (false, false), - ResizeMethod::PadTop => (false, true), - ResizeMethod::PadBottom => (false, true), - ResizeMethod::PadLeft => (true, false), - ResizeMethod::PadRight => (true, false), - }; - - let upscale = job.task.upscale().into(); - - let scales = ScalingOptions { - input_height: info.height, - input_width: info.width, - input_image_scaling: job.task.input_image_scaling, - clamp_aspect_ratio: job.task.clamp_aspect_ratio, - scales, - aspect_ratio: job - .task - .aspect_ratio - .as_ref() - .map(|r| Ratio::new(r.numerator as usize, r.denominator as usize)) - .unwrap_or(Ratio::ONE), - upscale, - preserve_aspect_height, - preserve_aspect_width, - } - .compute(); - - // let base_width = input_width as f64 / job.task.aspect_width as f64; - let mut resizers = scales - .into_iter() - .map(|scale| { - ( - scale, - ImageResizer::new(ImageResizerTarget { - height: scale.height, - width: scale.width, - algorithm: job.task.output(), - method: job.task.resize_method(), - upscale: upscale.is_yes(), - }), - Vec::with_capacity(info.frame_count), - ) - }) - .collect::>(); - - let mut frame_hashes = HashMap::new(); - let mut frame_order = Vec::with_capacity(info.frame_count); - let mut count = 0; - - tracing::debug!("decoding frames"); - - while let Some(frame) = decoder.decode()? { - let hash = sha2::Sha256::digest(frame.image.buf().as_bytes()); - if let Some(idx) = frame_hashes.get(&hash) { - if let Some((last_idx, last_duration)) = frame_order.last_mut() { - if last_idx == idx { - *last_duration += frame.duration_ts; - } else { - frame_order.push((*idx, frame.duration_ts)); - } - } else { - frame_order.push((*idx, frame.duration_ts)); - } - } else { - frame_hashes.insert(hash, count); - frame_order.push((count, frame.duration_ts)); - - count += 1; - for (_, resizer, frames) in resizers.iter_mut() { - frames.push(resizer.resize(&frame)?); - } - } - } - - tracing::debug!("decoded frames: {count}"); - - // We no longer need the decoder so we can free it. - drop(decoder); - - struct Stack { - static_encoders: Vec, - animation_encoders: Vec, - } - - let mut stacks = resizers - .iter() - .map(|(_, _, frames)| { - Ok(Stack { - static_encoders: static_formats - .iter() - .map(|&frontend| frontend.build(static_settings)) - .collect::>>()?, - animation_encoders: if frames.len() > 1 { - animation_formats - .iter() - .map(|&frontend| frontend.build(anim_settings)) - .collect::>>()? - } else { - Vec::new() - }, - }) - }) - .collect::>>()?; - - for (stack, frames) in stacks.iter_mut().zip(resizers.iter_mut().map(|(_, _, frames)| frames)) { - for encoder in stack.animation_encoders.iter_mut() { - for (idx, timing) in frame_order.iter() { - let frame = &mut frames[*idx]; - frame.duration_ts = *timing; - encoder.add_frame(frame)?; - } - - tracing::debug!("added frames to animation encoder: {count} => {:?}", encoder.info().frontend); - } - - for encoder in stack.static_encoders.iter_mut() { - encoder.add_frame(&frames[0])?; - tracing::debug!("added frame to static encoder: 1 => {:?}", encoder.info().frontend); - } - } - - let mut images = Vec::new(); - - for stack in stacks.into_iter() { - for encoder in stack.animation_encoders.into_iter() { - let info = encoder.info(); - let output = encoder.finish()?; - images.push(Image { - width: info.width, - height: info.height, - frame_count: info.frame_count, - duration: info.duration as f64 / info.timescale as f64, - encoder: info.frontend, - data: output.into(), - loop_count: info.loop_count, - request: match info.frontend { - EncoderFrontend::Gifski => ImageFormat::Gif, - EncoderFrontend::LibAvif => ImageFormat::Avif, - EncoderFrontend::LibWebp => ImageFormat::Webp, - EncoderFrontend::Png => unreachable!(), - }, - }); - } - - for encoder in stack.static_encoders.into_iter() { - let info = encoder.info(); - let output = encoder.finish()?; - images.push(Image { - width: info.width, - height: info.height, - frame_count: info.frame_count, - duration: info.duration as f64 / info.timescale as f64, - encoder: info.frontend, - data: output.into(), - loop_count: info.loop_count, - request: match info.frontend { - EncoderFrontend::LibAvif => ImageFormat::AvifStatic, - EncoderFrontend::LibWebp => ImageFormat::WebpStatic, - EncoderFrontend::Png => ImageFormat::PngStatic, - EncoderFrontend::Gifski => unreachable!(), - }, - }); - } - } - - Ok(Images { images }) -} diff --git a/image-processor/src/processor/job/resize.rs b/image-processor/src/processor/job/resize.rs deleted file mode 100644 index 2c1bbbb1..00000000 --- a/image-processor/src/processor/job/resize.rs +++ /dev/null @@ -1,200 +0,0 @@ -use anyhow::Context; -use fast_image_resize as fr; -use imgref::Img; -use rgb::{ComponentBytes, RGBA}; - -use super::frame::Frame; -use crate::{pb::{ResizeAlgorithm, ResizeMethod}, processor::error::{ProcessorError, Result}}; - -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -pub struct ImageResizerTarget { - pub width: usize, - pub height: usize, - pub algorithm: ResizeAlgorithm, - pub method: ResizeMethod, - pub upscale: bool, -} - -/// Resizes images to the given target size. -pub struct ImageResizer { - resizer: fr::Resizer, - target: ImageResizerTarget, -} - -impl std::fmt::Debug for ImageResizer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ImageResizer").field("target", &self.target).finish() - } -} - -impl ImageResizer { - pub fn new(target: ImageResizerTarget) -> Self { - Self { - resizer: fr::Resizer::new(match target.algorithm { - ResizeAlgorithm::Nearest => fr::ResizeAlg::Nearest, - ResizeAlgorithm::Box => fr::ResizeAlg::Convolution(fr::FilterType::Box), - ResizeAlgorithm::Bilinear => fr::ResizeAlg::Convolution(fr::FilterType::Bilinear), - ResizeAlgorithm::Hamming => fr::ResizeAlg::Convolution(fr::FilterType::Hamming), - ResizeAlgorithm::CatmullRom => fr::ResizeAlg::Convolution(fr::FilterType::CatmullRom), - ResizeAlgorithm::Mitchell => fr::ResizeAlg::Convolution(fr::FilterType::Mitchell), - ResizeAlgorithm::Lanczos3 => fr::ResizeAlg::Convolution(fr::FilterType::Lanczos3), - }), - target, - } - } - - /// Resize the given frame to the target size, returning a reference to the - /// resized frame. After this function returns original frame can be - /// dropped, the returned frame is valid for the lifetime of the Resizer. - pub fn resize(&mut self, frame: &Frame) -> Result { - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - - let (width, height) = if self.target.method == ResizeMethod::Stretch { - (self.target.width, self.target.height) - } else { - let (mut width, mut height) = if frame.image.width() > frame.image.height() { - let width = self.target.width as f64; - let height = frame.image.height() as f64 / frame.image.width() as f64 * width; - (width, height) - } else { - let height = self.target.height as f64; - let width = frame.image.width() as f64 / frame.image.height() as f64 * height; - (width, height) - }; - - if width > self.target.width as f64 { - height = height / width * self.target.width as f64; - width = self.target.width as f64; - } else if height > self.target.height as f64 { - width = width / height * self.target.height as f64; - height = self.target.height as f64; - } - - let (width, height) = (width.round() as usize, height.round() as usize); - - (width, height) - }; - - let (mut dst_image, crop_box) = - if self.target.method != ResizeMethod::Fit && (width != self.target.width || height != self.target.height) { - let height_delta = self.target.height - height; - let width_delta = self.target.width - width; - - let (top, bottom, left, right) = match self.target.method { - ResizeMethod::PadBottomLeft => (0, height_delta, width_delta, 0), - ResizeMethod::PadBottomRight => (0, height_delta, 0, width_delta), - ResizeMethod::PadTopLeft => (height_delta, 0, width_delta, 0), - ResizeMethod::PadTopRight => (height_delta, 0, 0, width_delta), - ResizeMethod::PadCenter => { - let top = height_delta / 2; - let bottom = height_delta - top; - let left = width_delta / 2; - let right = width_delta - left; - (top, bottom, left, right) - } - ResizeMethod::PadCenterLeft => { - let top = height_delta / 2; - let bottom = height_delta - top; - (top, bottom, width_delta, 0) - } - ResizeMethod::PadCenterRight => { - let top = height_delta / 2; - let bottom = height_delta - top; - (top, bottom, 0, width_delta) - } - ResizeMethod::PadTopCenter => { - let left = width_delta / 2; - let right = width_delta - left; - (height_delta, 0, left, right) - } - ResizeMethod::PadBottomCenter => { - let left = width_delta / 2; - let right = width_delta - left; - (0, height_delta, left, right) - } - ResizeMethod::PadTop => (height_delta, 0, 0, 0), - ResizeMethod::PadBottom => (0, height_delta, 0, 0), - ResizeMethod::PadLeft => (0, 0, width_delta, 0), - ResizeMethod::PadRight => (0, 0, 0, width_delta), - ResizeMethod::Stretch => unreachable!(), - ResizeMethod::Fit => unreachable!(), - }; - - let total_width = width + left + right; - let total_height = height + top + bottom; - - let dst_image = fr::Image::new( - (total_width as u32).try_into().unwrap(), - (total_height as u32).try_into().unwrap(), - fr::pixels::PixelType::U8x4, - ); - ( - dst_image, - ( - left as u32, - top as u32, - (height as u32).try_into().unwrap(), - (width as u32).try_into().unwrap(), - ), - ) - } else { - let dst_image = fr::Image::new( - (width as u32).try_into().unwrap(), - (height as u32).try_into().unwrap(), - fr::pixels::PixelType::U8x4, - ); - ( - dst_image, - ( - 0_u32, - 0_u32, - (height as u32).try_into().unwrap(), - (width as u32).try_into().unwrap(), - ), - ) - }; - - let mut cropped_dst_view = dst_image - .view_mut() - .crop(crop_box.0, crop_box.1, crop_box.2, crop_box.3) - .context("failed to crop image") - .map_err(ProcessorError::ImageResize)?; - - let size = frame.image.buf().len(); - - let src = fr::Image::from_slice_u8( - (frame.image.width() as u32).try_into().unwrap(), - (frame.image.height() as u32).try_into().unwrap(), - unsafe { std::slice::from_raw_parts_mut(frame.image.buf().as_ptr() as *mut u8, size * 4) }, - fr::pixels::PixelType::U8x4, - ) - .unwrap(); - self.resizer - .resize(&src.view(), &mut cropped_dst_view) - .context("failed to resize image") - .map_err(ProcessorError::ImageResize)?; - drop(src); - - let width = dst_image.width().get() as usize; - let height = dst_image.height().get() as usize; - let buffer = dst_image.into_vec(); - - let buffer = unsafe { - let buf = buffer.into_boxed_slice(); - let size = buf.len(); - let ptr = Box::into_raw(buf) as *mut u8; - - let new_size = size / 4; - assert!(new_size * 4 == size, "image buffer size mismatch"); - - Vec::from_raw_parts(ptr as *mut RGBA, new_size, new_size) - }; - - assert_eq!(buffer.as_bytes().len(), width * height * 4, "image buffer size mismatch"); - - Ok(Frame { - image: Img::new(buffer, width, height), - duration_ts: frame.duration_ts, - }) - } -} diff --git a/image-processor/src/processor/job/scaling.rs b/image-processor/src/processor/job/scaling.rs deleted file mode 100644 index 13b190f6..00000000 --- a/image-processor/src/processor/job/scaling.rs +++ /dev/null @@ -1,720 +0,0 @@ -use std::ops::{Mul, MulAssign}; - -use crate::pb::Upscale; - -#[derive(Debug, Clone)] -pub struct ScalingOptions { - pub input_width: usize, - pub input_height: usize, - pub aspect_ratio: Ratio, - pub clamp_aspect_ratio: bool, - pub preserve_aspect_width: bool, - pub preserve_aspect_height: bool, - pub upscale: Upscale, - pub input_image_scaling: bool, - pub scales: Vec, -} - -impl Upscale { - pub fn is_yes(&self) -> bool { - matches!(self, Upscale::Yes) - } - - pub fn is_no(&self) -> bool { - matches!(self, Upscale::No | Upscale::NoPreserveSource) - } - - pub fn preserve_source(&self) -> bool { - matches!(self, Upscale::NoPreserveSource) - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Size { - pub width: T, - pub height: T, -} - -#[derive(Debug, Clone, Copy)] -pub struct Ratio { - n: usize, - d: usize, -} - -impl Ratio { - pub const ONE: Self = Self::new(1, 1); - - pub const fn new(n: usize, d: usize) -> Self { - Self { n, d }.simplify() - } - - const fn gcd(&self) -> usize { - let mut a = self.n; - let mut b = self.d; - - while b != 0 { - let t = b; - b = a % b; - a = t; - } - - a - } - - const fn simplify(mut self) -> Self { - let gcd = self.gcd(); - - self.n /= gcd; - self.d /= gcd; - - self - } - - fn as_f64(&self) -> f64 { - self.n as f64 / self.d as f64 - } -} - -impl std::ops::Div for Ratio { - type Output = Ratio; - - fn div(self, rhs: usize) -> Self::Output { - Self { - n: self.n, - d: self.d.mul(rhs), - } - .simplify() - } -} - -impl std::ops::Mul for Ratio { - type Output = Ratio; - - fn mul(self, rhs: usize) -> Self::Output { - Self { - n: self.n.mul(rhs), - d: self.d, - } - .simplify() - } -} - -impl std::ops::Div for Ratio { - type Output = Ratio; - - fn div(self, rhs: Ratio) -> Self::Output { - Self { - n: self.n * rhs.d, - d: self.d * rhs.n, - } - .simplify() - } -} - -impl std::ops::Mul for Ratio { - type Output = Ratio; - - fn mul(self, rhs: Ratio) -> Self::Output { - Self { - n: self.n * rhs.n, - d: self.d * rhs.d, - } - .simplify() - } -} - -impl PartialEq for Ratio { - fn eq(&self, other: &Self) -> bool { - let this = self.simplify(); - let other = other.simplify(); - - this.n == other.n && this.d == other.d - } -} - -impl Eq for Ratio {} - -impl PartialOrd for Ratio { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Ratio { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - let this = self.simplify(); - let other = other.simplify(); - - (this.n * other.d).cmp(&(this.d * other.n)) - } -} - -impl MulAssign for Ratio { - fn mul_assign(&mut self, rhs: Self) { - *self = *self * rhs; - } -} - -impl std::ops::DivAssign for Ratio { - fn div_assign(&mut self, rhs: Self) { - *self = *self / rhs; - } -} - -impl std::ops::Div for Size -where - T: std::ops::Div + Copy, -{ - type Output = Self; - - fn div(self, rhs: T) -> Self::Output { - Self { - width: self.width / rhs, - height: self.height / rhs, - } - } -} - -impl std::ops::Mul for Size -where - T: std::ops::Mul + Copy, -{ - type Output = Self; - - fn mul(self, rhs: T) -> Self::Output { - Self { - width: self.width * rhs, - height: self.height * rhs, - } - } -} - -impl ScalingOptions { - pub fn compute(&mut self) -> Vec> { - // Sorts the scales from smallest to largest. - self.scales.sort_by(|a, b| a.partial_cmp(b).unwrap()); - - let mut scales = self.compute_scales(); - let padded_size = self.padded_size(); - - let (best_idx, input_scale_factor) = scales - .iter() - .position(|(size, _)| size.width >= padded_size.width || size.height >= padded_size.height) - .map(|idx| (idx, Ratio::ONE)) - .unwrap_or_else(|| { - let size = scales.last().unwrap().0; - - // Since its the padded size, the aspect ratio is the same as the target aspect - // ratio. - let input_scale_factor = padded_size.width / size.width; - - (scales.len() - 1, input_scale_factor) - }); - - if self.input_image_scaling { - let scaled_width = padded_size.width / scales[best_idx].1 / input_scale_factor; - let scaled_height = padded_size.height / scales[best_idx].1 / input_scale_factor; - scales.iter_mut().for_each(|(size, scale)| { - size.width = *scale * scaled_width; - size.height = *scale * scaled_height; - }); - }; - - if self.upscale.preserve_source() { - let padded_size = padded_size / input_scale_factor; - - let size = scales[best_idx].0; - - if size.width > padded_size.width || size.height > padded_size.height { - scales[best_idx].0 = padded_size; - } - } - - if self.clamp_aspect_ratio { - scales.iter_mut().for_each(|(size, scale)| { - let scale = *scale; - - if self.aspect_ratio < Ratio::ONE && size.height > scale / self.aspect_ratio { - let height = scale / self.aspect_ratio; - size.width *= height / size.height; - size.height = height; - } else if self.aspect_ratio > Ratio::ONE && size.width > scale * self.aspect_ratio { - let width = scale * self.aspect_ratio; - size.height *= width / size.width; - size.width = width; - } else if self.aspect_ratio == Ratio::ONE && size.width > scale { - size.height *= scale / size.width; - size.width = scale; - } else if self.aspect_ratio == Ratio::ONE && size.height > scale { - size.width *= scale / size.height; - size.height = scale; - } - - size.width = size.width.max(Ratio::ONE); - size.height = size.height.max(Ratio::ONE); - }); - } - - if self.upscale.is_no() { - scales.retain(|(size, _)| size.width <= padded_size.width && size.height <= padded_size.height); - } - - scales - .into_iter() - .map(|(mut size, scale)| { - let input_aspect_ratio = self.input_aspect_ratio(); - - if self.preserve_aspect_height && self.aspect_ratio <= Ratio::ONE { - let height = size.height * self.aspect_ratio / input_aspect_ratio; - // size.width *= size.height / height; - size.height = height; - } else if self.preserve_aspect_width && self.aspect_ratio >= Ratio::ONE { - let width = size.width * input_aspect_ratio / self.aspect_ratio; - // size.height *= size.width / width; - size.width = width; - } - - if self.preserve_aspect_height || self.preserve_aspect_width { - // need to make sure the new sizes are not larger than the max for this scale - let (width, height) = if self.aspect_ratio > Ratio::ONE { - (scale * self.aspect_ratio, scale) - } else { - (scale, scale / self.aspect_ratio) - }; - - if size.width > width { - size.height *= width / size.width; - size.width = width; - } else if size.height > height { - size.width *= height / size.height; - size.height = height; - } - } - - Size { - width: size.width.as_f64().round() as usize, - height: size.height.as_f64().round() as usize, - } - }) - .collect() - } - - fn compute_scales(&self) -> Vec<(Size, Ratio)> { - self.scales - .iter() - .copied() - .map(|scale| { - let scale = Ratio::new(scale, 1); - - let (width, height) = if self.aspect_ratio > Ratio::ONE { - (scale * self.aspect_ratio, scale) - } else { - (scale, scale / self.aspect_ratio) - }; - - (Size { width, height }, scale) - }) - .collect() - } - - fn input_aspect_ratio(&self) -> Ratio { - Ratio { - n: self.input_width, - d: self.input_height, - } - .simplify() - } - - fn padded_size(&self) -> Size { - let width = Ratio::new(self.input_width, 1); - let height = Ratio::new(self.input_height, 1); - - let (width, height) = if self.aspect_ratio < Ratio::ONE { - (width, width / self.aspect_ratio) - } else if self.aspect_ratio > Ratio::ONE { - (height * self.aspect_ratio, height) - } else { - let max = width.max(height); - (max, max) - }; - - Size { width, height } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_compute_scales_same_aspect() { - let mut options = ScalingOptions { - input_width: 100, - input_height: 100, - aspect_ratio: Ratio::new(1, 1), - preserve_aspect_width: false, - preserve_aspect_height: false, - upscale: Upscale::Yes, - input_image_scaling: false, - clamp_aspect_ratio: true, - scales: vec![32, 64, 96, 128], - }; - - assert_eq!( - options.compute(), - vec![ - Size { width: 32, height: 32 }, - Size { width: 64, height: 64 }, - Size { width: 96, height: 96 }, - Size { width: 128, height: 128 }, - ] - ); - - options.upscale = Upscale::No; - - assert_eq!( - options.compute(), - vec![ - Size { width: 32, height: 32 }, - Size { width: 64, height: 64 }, - Size { width: 96, height: 96 }, - ] - ); - - options.upscale = Upscale::NoPreserveSource; - - assert_eq!( - options.compute(), - vec![ - Size { width: 32, height: 32 }, - Size { width: 64, height: 64 }, - Size { width: 96, height: 96 }, - Size { width: 100, height: 100 }, - ] - ); - - options.input_height = 112; - options.input_width = 112; - options.input_image_scaling = true; - options.upscale = Upscale::No; - - assert_eq!( - options.compute(), - vec![ - Size { width: 28, height: 28 }, - Size { width: 56, height: 56 }, - Size { width: 84, height: 84 }, - Size { width: 112, height: 112 }, - ] - ); - } - - #[test] - fn test_compute_scales_different_aspect() { - let mut options = ScalingOptions { - input_width: 100, - input_height: 100, - aspect_ratio: Ratio::new(16, 9), - preserve_aspect_width: false, - preserve_aspect_height: false, - upscale: Upscale::Yes, - input_image_scaling: false, - clamp_aspect_ratio: true, - scales: vec![360, 720, 1080], - }; - - assert_eq!( - options.compute(), - vec![ - Size { width: 640, height: 360 }, - Size { - width: 1280, - height: 720 - }, - Size { - width: 1920, - height: 1080 - }, - ] - ); - - options.upscale = Upscale::No; - assert_eq!(options.compute(), vec![]); - - options.upscale = Upscale::NoPreserveSource; - assert_eq!(options.compute(), vec![Size { width: 178, height: 100 },]); - - options.aspect_ratio = Ratio::new(9, 16); - options.upscale = Upscale::Yes; - - assert_eq!( - options.compute(), - vec![ - Size { width: 360, height: 640 }, - Size { - width: 720, - height: 1280 - }, - Size { - width: 1080, - height: 1920 - }, - ] - ); - - options.upscale = Upscale::No; - assert_eq!(options.compute(), vec![]); - - options.upscale = Upscale::NoPreserveSource; - assert_eq!(options.compute(), vec![Size { width: 100, height: 178 },]); - - options.input_width = 1920; - options.input_height = 1080; - options.upscale = Upscale::Yes; - - assert_eq!( - options.compute(), - vec![ - Size { width: 360, height: 640 }, - Size { - width: 720, - height: 1280 - }, - Size { - width: 1080, - height: 1920 - }, - ] - ); - - options.upscale = Upscale::No; - assert_eq!( - options.compute(), - vec![ - Size { width: 360, height: 640 }, - Size { - width: 720, - height: 1280 - }, - Size { - width: 1080, - height: 1920 - }, - ] - ); - - options.upscale = Upscale::NoPreserveSource; - assert_eq!( - options.compute(), - vec![ - Size { width: 360, height: 640 }, - Size { - width: 720, - height: 1280 - }, - Size { - width: 1080, - height: 1920 - }, - ] - ); - } - - #[test] - fn test_compute_scales_image_scaling() { - let mut options = ScalingOptions { - input_width: 112, - input_height: 112, - aspect_ratio: Ratio::new(3, 1), - preserve_aspect_width: true, - preserve_aspect_height: true, - upscale: Upscale::NoPreserveSource, - input_image_scaling: true, - clamp_aspect_ratio: true, - scales: vec![32, 64, 96, 128], - }; - - assert_eq!( - options.compute(), - vec![ - Size { width: 28, height: 28 }, - Size { width: 56, height: 56 }, - Size { width: 84, height: 84 }, - Size { width: 112, height: 112 }, - ] - ); - - options.input_width = 112 * 2; - assert_eq!( - options.compute(), - vec![ - Size { - width: 28 * 2, - height: 28 - }, - Size { - width: 56 * 2, - height: 56 - }, - Size { - width: 84 * 2, - height: 84 - }, - Size { - width: 112 * 2, - height: 112 - }, - ] - ); - - options.input_width = 112 * 3; - assert_eq!( - options.compute(), - vec![ - Size { - width: 28 * 3, - height: 28 - }, - Size { - width: 56 * 3, - height: 56 - }, - Size { - width: 84 * 3, - height: 84 - }, - Size { - width: 112 * 3, - height: 112 - }, - ] - ); - - options.input_width = 112 * 4; - assert_eq!( - options.compute(), - vec![ - Size { - width: 32 * 3, - height: 24 - }, - Size { - width: 64 * 3, - height: 48 - }, - Size { - width: 96 * 3, - height: 72 - }, - Size { - width: 128 * 3, - height: 96 - }, - ] - ); - - options.input_width = 112 / 2; - assert_eq!( - options.compute(), - vec![ - Size { - width: 28 / 2, - height: 28 - }, - Size { - width: 56 / 2, - height: 56 - }, - Size { - width: 84 / 2, - height: 84 - }, - Size { - width: 112 / 2, - height: 112 - }, - ] - ); - - options.input_width = 112 / 3; - assert_eq!( - options.compute(), - vec![ - Size { width: 9, height: 28 }, - Size { width: 19, height: 56 }, - Size { width: 28, height: 84 }, - Size { width: 37, height: 112 }, - ] - ); - } - - #[test] - fn test_compute_scales_any_scale() { - let mut options = ScalingOptions { - input_width: 245, - input_height: 1239, - aspect_ratio: Ratio::new(1, 1), - preserve_aspect_width: true, - preserve_aspect_height: true, - upscale: Upscale::NoPreserveSource, - input_image_scaling: true, - clamp_aspect_ratio: true, - scales: vec![720, 1080], - }; - - assert_eq!( - options.compute(), - vec![ - Size { width: 142, height: 720 }, - Size { - width: 214, - height: 1080 - }, - ] - ); - - options.input_width = 1239; - options.input_height = 245; - - assert_eq!( - options.compute(), - vec![ - Size { width: 720, height: 142 }, - Size { - width: 1080, - height: 214 - }, - ] - ); - - options.clamp_aspect_ratio = false; - options.input_image_scaling = false; - options.input_height = 1239; - options.input_width = 245; - - assert_eq!( - options.compute(), - vec![ - Size { width: 142, height: 720 }, - Size { - width: 214, - height: 1080 - }, - ] - ); - - options.input_height = 245; - options.input_width = 1239; - - assert_eq!( - options.compute(), - vec![ - Size { width: 720, height: 142 }, - Size { - width: 1080, - height: 214 - }, - ] - ); - } -} diff --git a/image-processor/src/processor/mod.rs b/image-processor/src/processor/mod.rs deleted file mode 100644 index 24d8bf23..00000000 --- a/image-processor/src/processor/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::sync::atomic::AtomicUsize; -use std::sync::Arc; - -use futures::StreamExt; -use tokio::select; - -use self::error::Result; -use crate::config::ImageProcessorConfig; -use crate::global::ImageProcessorGlobal; -use crate::processor::job::handle_job; - -pub(crate) mod error; -pub(crate) mod job; -pub(crate) mod utils; - -pub async fn run(global: Arc) -> Result<()> { - let config = global.config::(); - - let concurrency = AtomicUsize::new(config.concurrency); - - let mut done = global.ctx().done(); - - let mut futures = futures::stream::FuturesUnordered::new(); - - let make_job_query = { - let global = &global; - let concurrency = &concurrency; - move |wait: bool| async move { - if wait { - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - } - - let concurrency = concurrency.load(std::sync::atomic::Ordering::Relaxed); - - if concurrency == 0 { - tracing::debug!("concurrency limit reached, waiting for a slot"); - None - } else { - tracing::debug!("querying for jobs: {concurrency}"); - Some(utils::query_job(global, concurrency).await) - } - } - }; - - let mut job_query = Some(Box::pin(make_job_query(false))); - - loop { - select! { - Some(jobs) = async { - if let Some(job_query_fut) = &mut job_query { - let r = job_query_fut.await; - job_query = None; - r - } else { - None - } - } => { - let jobs = jobs?; - tracing::debug!("got {} jobs", jobs.len()); - job_query = Some(Box::pin(make_job_query(jobs.is_empty()))); - - for job in jobs { - concurrency.fetch_sub(1, std::sync::atomic::Ordering::Relaxed); - futures.push(handle_job(&global, job)); - } - }, - Some(_) = futures.next() => { - concurrency.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - if job_query.is_none() { - job_query = Some(Box::pin(make_job_query(true))); - } - }, - _ = &mut done => break, - } - } - - Ok(()) -} diff --git a/image-processor/src/processor/utils.rs b/image-processor/src/processor/utils.rs deleted file mode 100644 index c4e4edce..00000000 --- a/image-processor/src/processor/utils.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::sync::Arc; - -use ulid::Ulid; - -use super::error::ProcessorError; -use crate::database::Job; -use crate::global::ImageProcessorGlobal; -use crate::processor::error::Result; - -pub async fn query_job(global: &Arc, limit: usize) -> Result> { - Ok(scuffle_utils::database::query( - "UPDATE image_jobs - SET claimed_by = $1, - hold_until = NOW() + INTERVAL '30 seconds' - FROM ( - SELECT id - FROM image_jobs - WHERE hold_until IS NULL OR hold_until < NOW() - ORDER BY priority DESC, - id DESC - LIMIT $2 - ) AS job - WHERE image_jobs.id = job.id - RETURNING image_jobs.id, image_jobs.task", - ) - .bind(global.instance_id()) - .bind(limit as i64) - .build_query_as() - .fetch_all(global.db()) - .await?) -} - -pub async fn refresh_job(global: &Arc, job_id: Ulid) -> Result<()> { - let result = scuffle_utils::database::query( - "UPDATE image_jobs - SET hold_until = NOW() + INTERVAL '30 seconds' - WHERE image_jobs.id = $1 AND image_jobs.claimed_by = $2", - ) - .bind(job_id) - .bind(global.instance_id()) - .build() - .execute(global.db()) - .await?; - - if result == 0 { Err(ProcessorError::LostJob) } else { Ok(()) } -} - -pub async fn delete_job(global: &Arc, job_id: Ulid) -> Result<()> { - scuffle_utils::database::query("DELETE FROM image_jobs WHERE id = $1 AND claimed_by = $2") - .bind(job_id) - .bind(global.instance_id()) - .build() - .execute(global.db()) - .await?; - - Ok(()) -} diff --git a/image-processor/src/worker.rs b/image-processor/src/worker.rs deleted file mode 100644 index dde504b3..00000000 --- a/image-processor/src/worker.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::sync::Arc; - -use crate::global::Global; - -pub async fn start(global: Arc) -> anyhow::Result<()> { - std::future::pending::<()>().await; - Ok(()) -} diff --git a/image-processor/src/worker/error.rs b/image-processor/src/worker/error.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/image-processor/src/worker/error.rs @@ -0,0 +1 @@ + diff --git a/image-processor/src/worker/mod.rs b/image-processor/src/worker/mod.rs new file mode 100644 index 00000000..cb9b72f8 --- /dev/null +++ b/image-processor/src/worker/mod.rs @@ -0,0 +1,60 @@ +use std::sync::Arc; + +use anyhow::Context; +use scuffle_foundations::context::{self, ContextFutExt}; + +use crate::database::Job; +use crate::global::Global; + +mod process; + +pub use self::process::JobError; + +pub async fn start(global: Arc) -> anyhow::Result<()> { + let config = global.config(); + + let semaphore = Arc::new(tokio::sync::Semaphore::new(config.worker.concurrency)); + + let mut error_count = 0; + let (_, handle) = context::Context::new(); + + loop { + let ctx = handle.context(); + let Some(permit) = semaphore + .clone() + .acquire_owned() + .with_context(&ctx) + .await + .transpose() + .expect("semaphore permit") + else { + break; + }; + + let job = match Job::fetch(&global).await { + Ok(Some(job)) => job, + Ok(None) => { + tokio::time::sleep(config.worker.polling_interval).await; + continue; + } + Err(err) => { + tracing::error!("failed to fetch job: {err}"); + error_count += 1; + if error_count >= config.worker.error_threshold { + Err(err).context("reached error threshold")?; + } + + tokio::time::sleep(config.worker.error_delay).await; + + continue; + } + }; + + error_count = 0; + tokio::spawn(self::process::spawn(job, global.clone(), ctx, permit)); + } + + handle.shutdown().await; + + Ok(()) +} diff --git a/image-processor/src/worker/process/blocking.rs b/image-processor/src/worker/process/blocking.rs new file mode 100644 index 00000000..ef0a0abd --- /dev/null +++ b/image-processor/src/worker/process/blocking.rs @@ -0,0 +1,373 @@ +use std::borrow::Cow; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use bytes::Bytes; +use file_format::FileFormat; +use scuffle_image_processor_proto::{animation_config, Output, OutputFormat, OutputFormatOptions, Task}; +use tokio::sync::OwnedSemaphorePermit; + +use super::decoder::{AnyDecoder, Decoder, DecoderFrontend, DecoderInfo, LoopCount}; +use super::encoder::{AnyEncoder, Encoder, EncoderBackend, EncoderSettings}; +use super::resize::{ImageResizer, ResizeOutputTarget}; +use super::JobError; + +pub struct JobOutput { + pub format: OutputFormat, + pub format_name: Option, + pub format_idx: usize, + pub resize_idx: usize, + pub scale: Option, + pub width: usize, + pub height: usize, + pub data: Vec, +} + +#[derive(Clone, Copy)] +enum FrameConfig { + Skip, + DurationMs(u32), +} + +#[derive(Clone)] +struct CancelToken { + cancelled: Arc, +} + +impl CancelToken { + fn new() -> Self { + Self { + cancelled: Arc::new(AtomicBool::new(false)), + } + } + + fn is_cancelled(&self) -> bool { + self.cancelled.load(std::sync::atomic::Ordering::Relaxed) + } +} + +impl Drop for CancelToken { + fn drop(&mut self) { + self.cancelled.store(true, std::sync::atomic::Ordering::Relaxed); + } +} + +pub async fn spawn(task: Task, input: Bytes, permit: Arc) -> Result, JobError> { + let cancel_token = CancelToken::new(); + let _cancel_guard = cancel_token.clone(); + + let span = tracing::Span::current(); + + tokio::task::spawn_blocking(move || { + // This prevents the permit from being dropped before the task is finished. + // This is because there is no way to cancel a blocking task. + // So, if we cancel the parent future we need to make sure the permit is still + // held, as we are still technically running. If we dont do this we might use + // too many system resources. + let _span = span.enter(); + let _permit = permit; + let mut task = BlockingTask::new(&task, &input)?; + + while task.drive()? { + // Check if the task has been cancelled. + if cancel_token.is_cancelled() { + return Err(JobError::Internal("cancelled")); + } + } + + task.finish() + }) + .await? +} + +struct BlockingTask<'a> { + decoder: AnyDecoder<'a>, + decoder_info: DecoderInfo, + frame_configs: Vec>, + resizer: ImageResizer, + static_encoders: Vec<(usize, Vec<(ResizeOutputTarget, AnyEncoder)>)>, + anim_encoders: Vec<(usize, Vec<(ResizeOutputTarget, AnyEncoder)>)>, + static_frame_idx: usize, + frame_idx: usize, + duration_carried_ms: f64, + frame_rate_factor: Option, +} + +fn split_formats(output: &Output) -> (Vec<(usize, &OutputFormatOptions)>, Vec<(usize, &OutputFormatOptions)>) { + output + .formats + .iter() + .enumerate() + .fold((Vec::new(), Vec::new()), |mut acc, (idx, format_options)| { + match format_options.format() { + OutputFormat::AvifStatic | OutputFormat::WebpStatic | OutputFormat::PngStatic => { + acc.0.push((idx, format_options)) + } + OutputFormat::AvifAnim | OutputFormat::GifAnim | OutputFormat::WebpAnim => acc.1.push((idx, format_options)), + } + acc + }) +} + +fn build_encoder_set( + format_options: &OutputFormatOptions, + resize_outputs: &[ResizeOutputTarget], + loop_count: LoopCount, +) -> Result, JobError> { + let encoder_frontend = match format_options.format() { + OutputFormat::AvifStatic | OutputFormat::AvifAnim => EncoderBackend::LibAvif, + OutputFormat::PngStatic => EncoderBackend::Png, + OutputFormat::WebpStatic | OutputFormat::WebpAnim => EncoderBackend::LibWebp, + OutputFormat::GifAnim => EncoderBackend::Gifski, + }; + + resize_outputs + .iter() + .map(|target| { + Ok(( + *target, + encoder_frontend.build(EncoderSettings { + loop_count, + format: format_options.format(), + name: format_options.name.clone(), + quality: format_options.quality(), + static_image: matches!( + format_options.format(), + OutputFormat::AvifStatic | OutputFormat::WebpStatic | OutputFormat::PngStatic + ), + timescale: 1000, // millisecond timescale + })?, + )) + }) + .collect::, JobError>>() +} + +impl<'a> BlockingTask<'a> { + fn new(task: &'a Task, input: &'a [u8]) -> Result { + let output = task.output.as_ref().ok_or(JobError::InvalidJob)?; + let anim_config = output.animation_config.as_ref(); + + let (static_formats, anim_formats) = split_formats(output); + + if static_formats.is_empty() && anim_formats.is_empty() { + return Err(JobError::InvalidJob); + } + + let file_format = DecoderFrontend::from_format(FileFormat::from_bytes(input))?; + let decoder = file_format.build(task, Cow::Borrowed(input))?; + + let decoder_info = decoder.info(); + + if let Some(metadata) = task.input.as_ref().and_then(|input| input.metadata.as_ref()) { + if decoder_info.width != metadata.width as usize || decoder_info.height != metadata.height as usize { + return Err(JobError::MismatchedDimensions { + width: decoder_info.width, + height: decoder_info.height, + expected_width: metadata.width as usize, + expected_height: metadata.height as usize, + }); + } + + if let Some(frame_count) = metadata.frame_count { + if decoder_info.frame_count != frame_count as usize { + return Err(JobError::MismatchedFrameCount { + frame_count: decoder_info.frame_count, + expected_frame_count: frame_count as usize, + }); + } + } + + if let Some(static_frame_index) = metadata.static_frame_index { + if static_frame_index as usize >= decoder_info.frame_count { + return Err(JobError::StaticFrameIndexOutOfBounds { + idx: static_frame_index as usize, + frame_count: decoder_info.frame_count, + }); + } + } + } + + let mut frame_configs = vec![None; decoder_info.frame_count]; + + if let Some(anim_config) = anim_config { + match anim_config.frame_rate.as_ref() { + Some(animation_config::FrameRate::DurationsMs(durations)) => { + if durations.values.len() != decoder_info.frame_count { + return Err(JobError::MismatchedFrameCount { + frame_count: decoder_info.frame_count, + expected_frame_count: durations.values.len(), + }); + } + + for (idx, duration) in durations.values.iter().enumerate() { + frame_configs[idx] = Some(FrameConfig::DurationMs(*duration)) + } + } + Some(animation_config::FrameRate::DurationMs(duration)) => { + for config in frame_configs.iter_mut() { + *config = Some(FrameConfig::DurationMs(*duration)) + } + } + _ => {} + } + + for idx in anim_config.remove_frame_idxs.iter() { + let idx = *idx as usize; + if idx > decoder_info.frame_count { + return Err(JobError::MismatchedFrameCount { + frame_count: decoder_info.frame_count, + expected_frame_count: idx + 1, + }); + } + + frame_configs[idx] = Some(FrameConfig::Skip); + } + } + + let resizer = ImageResizer::new(&decoder_info, output)?; + + let loop_count = anim_config + .and_then(|anim_config| anim_config.loop_count) + .map(|loop_count| { + if loop_count < 0 { + LoopCount::Infinite + } else { + LoopCount::Finite(loop_count as usize) + } + }) + .unwrap_or(decoder_info.loop_count); + + let static_encoders = static_formats + .into_iter() + .map(|(f_idx, format_options)| { + build_encoder_set(format_options, resizer.outputs(), loop_count).map(|encoders| (f_idx, encoders)) + }) + .collect::, JobError>>()?; + + let anim_encoders = if decoder_info.frame_count > 1 { + anim_formats + .into_iter() + .map(|(f_idx, format_options)| { + build_encoder_set(format_options, resizer.outputs(), loop_count).map(|encoders| (f_idx, encoders)) + }) + .collect::, JobError>>()? + } else if !anim_formats.is_empty() && !output.skip_impossible_formats { + return Err(JobError::ImpossibleOutput(anim_formats[0].1.format())); + } else { + Vec::new() + }; + + if static_encoders.is_empty() && anim_encoders.is_empty() { + return Err(JobError::NoPossibleOutputs); + } + + let static_frame_idx = task + .input + .as_ref() + .and_then(|input| input.metadata.as_ref()) + .and_then(|metadata| metadata.static_frame_index) + .unwrap_or_default() as usize; + + Ok(Self { + decoder, + decoder_info, + frame_configs, + resizer, + static_encoders, + anim_encoders, + static_frame_idx, + frame_idx: 0, + duration_carried_ms: 0.0, + frame_rate_factor: anim_config.and_then(|config| match config.frame_rate.as_ref()? { + animation_config::FrameRate::Factor(factor) => Some(*factor), + _ => None, + }), + }) + } + + pub fn drive(&mut self) -> Result { + let Some(mut frame) = self.decoder.decode()? else { + return Ok(false); + }; + + let idx = self.frame_idx; + self.frame_idx += 1; + + let variants = if idx == self.static_frame_idx { + let variants = self.resizer.resize(frame)?; + + self.static_encoders.iter_mut().try_for_each(|(_, encoders)| { + encoders + .iter_mut() + .zip(variants.iter()) + .try_for_each(|((_, encoder), frame)| encoder.add_frame(frame.as_ref())) + })?; + + Some(variants) + } else { + None + }; + + // Convert from the decode timescale into ms. + frame.duration_ts = (frame.duration_ts as f64 * 1000.0 / self.decoder_info.timescale as f64).round() as u64; + + if let Some(config) = self.frame_configs.get(idx).ok_or(JobError::Internal(""))? { + match config { + FrameConfig::Skip => { + return Ok(true); + } + FrameConfig::DurationMs(duration) => { + frame.duration_ts = *duration as u64; + } + } + } + + if let Some(factor) = self.frame_rate_factor { + let new_duration = (frame.duration_ts as f64 + self.duration_carried_ms) / factor; + let rounded_duration = new_duration.round(); + self.duration_carried_ms = new_duration - rounded_duration; + + if rounded_duration == 0.0 { + return Ok(true); + } + + frame.duration_ts = rounded_duration as u64; + } + + let variants = match variants { + Some(variants) => variants, + None => self.resizer.resize(frame)?, + }; + + self.anim_encoders.iter_mut().try_for_each(|(_, encoders)| { + encoders + .iter_mut() + .zip(variants.iter()) + .try_for_each(|((_, encoder), frame)| encoder.add_frame(frame.as_ref())) + })?; + + Ok(true) + } + + pub fn finish(self) -> Result, JobError> { + self.static_encoders + .into_iter() + .chain(self.anim_encoders) + .flat_map(|(f_idx, encoders)| { + encoders.into_iter().map(move |(output, encoder)| { + let info = encoder.info(); + Ok(JobOutput { + format: info.format, + format_name: info.name.clone(), + format_idx: f_idx, + resize_idx: output.index, + scale: output.scale.map(|s| s as usize), + width: info.width, + height: info.height, + data: encoder.finish()?, + }) + }) + }) + .collect() + } +} diff --git a/image-processor/src/worker/process/decoder/ffmpeg.rs b/image-processor/src/worker/process/decoder/ffmpeg.rs new file mode 100644 index 00000000..24c9e212 --- /dev/null +++ b/image-processor/src/worker/process/decoder/ffmpeg.rs @@ -0,0 +1,212 @@ +use std::borrow::Cow; + +use imgref::Img; +use rgb::RGBA8; +use scuffle_image_processor_proto::Task; + +use super::{Decoder, DecoderError, DecoderFrontend, DecoderInfo, LoopCount}; +use crate::worker::process::frame::{Frame, FrameRef}; + +pub struct FfmpegDecoder<'data> { + input: scuffle_ffmpeg::io::Input>>, + decoder: scuffle_ffmpeg::decoder::VideoDecoder, + scaler: scuffle_ffmpeg::scalar::Scalar, + info: DecoderInfo, + input_stream_index: i32, + send_packet: bool, + eof: bool, + done: bool, + frame: Frame, +} + +const fn cast_bytes_to_rgba(bytes: &[u8]) -> &[rgb::RGBA8] { + unsafe { std::slice::from_raw_parts(bytes.as_ptr() as *const _, bytes.len() / 4) } +} + +static FFMPEG_LOGGING_INITIALIZED: std::sync::Once = std::sync::Once::new(); + +impl<'data> FfmpegDecoder<'data> { + #[tracing::instrument(skip(task, data), fields(name = "FfmpegDecoder::new"))] + pub fn new(task: &Task, data: Cow<'data, [u8]>) -> Result { + FFMPEG_LOGGING_INITIALIZED.call_once(|| { + scuffle_ffmpeg::log::log_callback_tracing(); + }); + + let input = scuffle_ffmpeg::io::Input::seekable(std::io::Cursor::new(data))?; + + let input_stream = input + .streams() + .best(scuffle_ffmpeg::ffi::AVMediaType::AVMEDIA_TYPE_VIDEO) + .ok_or(DecoderError::NoVideoStream)?; + + let input_stream_index = input_stream.index(); + + let input_stream_time_base = input_stream.time_base(); + let input_stream_duration = input_stream.duration().unwrap_or(0); + let input_stream_frames = input_stream.nb_frames().ok_or(DecoderError::NoFrameCount)?.max(1); + + if input_stream_time_base.den == 0 || input_stream_time_base.num == 0 { + return Err(DecoderError::InvalidTimeBase); + } + + let decoder = match scuffle_ffmpeg::decoder::Decoder::new(&input_stream)? { + scuffle_ffmpeg::decoder::Decoder::Video(decoder) => decoder, + _ => { + return Err(DecoderError::InvalidVideoDecoder); + } + }; + + if let Some(max_input_width) = task.limits.as_ref().and_then(|l| l.max_input_width) { + if decoder.width() > max_input_width as i32 { + return Err(DecoderError::TooWide(decoder.width())); + } + } + + if let Some(max_input_height) = task.limits.as_ref().and_then(|l| l.max_input_height) { + if decoder.height() > max_input_height as i32 { + return Err(DecoderError::TooHigh(decoder.height())); + } + } + + if let Some(max_input_frame_count) = task.limits.as_ref().and_then(|l| l.max_input_frame_count) { + if input_stream_frames > max_input_frame_count as i64 { + return Err(DecoderError::TooManyFrames(input_stream_frames)); + } + } + + if let Some(max_input_duration_ms) = task.limits.as_ref().and_then(|l| l.max_input_duration_ms) { + // actual duration + // = duration * (time_base.num / time_base.den) * 1000 + // = (duration * time_base.num * 1000) / time_base.den + let duration = + (input_stream_duration * input_stream_time_base.num as i64 * 1000) / input_stream_time_base.den as i64; + + if duration > max_input_duration_ms as i64 { + return Err(DecoderError::TooLong(duration)); + } + } + + let scaler = scuffle_ffmpeg::scalar::Scalar::new( + decoder.width(), + decoder.height(), + decoder.pixel_format(), + decoder.width(), + decoder.height(), + scuffle_ffmpeg::ffi::AVPixelFormat::AV_PIX_FMT_RGBA, + )?; + + let info = DecoderInfo { + width: decoder.width() as usize, + height: decoder.height() as usize, + frame_count: input_stream_frames as usize, + // TODO: Support loop count from ffmpeg. + loop_count: LoopCount::Infinite, + timescale: input_stream_time_base.den as u64, + }; + + let frame = Frame { + image: Img::new(vec![RGBA8::default(); info.width * info.height], info.width, info.height), + duration_ts: (input_stream_duration / input_stream_frames) as u64, + }; + + Ok(Self { + info, + input, + scaler, + decoder, + input_stream_index, + done: false, + eof: false, + send_packet: true, + frame, + }) + } +} + +impl Decoder for FfmpegDecoder<'_> { + fn backend(&self) -> DecoderFrontend { + DecoderFrontend::Ffmpeg + } + + #[tracing::instrument(skip(self), fields(name = "FfmpegDecoder::decode"))] + fn decode(&mut self) -> Result, DecoderError> { + if self.done { + return Ok(None); + } + + loop { + if self.send_packet && !self.eof { + let packet = self + .input + .packets() + .find_map(|packet| match packet { + Ok(packet) => { + if packet.stream_index() == self.input_stream_index { + Some(Ok(packet)) + } else { + None + } + } + Err(err) => { + self.done = true; + Some(Err(err)) + } + }) + .transpose()?; + + if let Some(packet) = packet { + self.decoder.send_packet(&packet).map_err(|err| { + self.done = true; + err + })?; + } else { + self.decoder.send_eof().map_err(|err| { + self.done = true; + err + })?; + self.eof = true; + } + + self.send_packet = false; + } + + let frame = self.decoder.receive_frame().map_err(|err| { + self.done = true; + err + })?; + + if let Some(frame) = frame { + let frame = self.scaler.process(&frame).map_err(|err| { + self.done = true; + err + })?; + + // The frame has padding, so we need to copy the data. + let frame_data = frame.data(0).unwrap(); + let frame_linesize = frame.linesize(0).unwrap(); + + if frame_linesize == frame.width() as i32 * 4 { + // No padding, so we can just copy the data. + self.frame.image.buf_mut().copy_from_slice(cast_bytes_to_rgba(frame_data)); + } else { + // The frame has padding, so we need to copy the data. + for (i, row) in self.frame.image.buf_mut().chunks_exact_mut(frame.width()).enumerate() { + let row_data = &frame_data[i * frame_linesize as usize..][..frame.width() * 4]; + row.copy_from_slice(cast_bytes_to_rgba(row_data)); + } + } + + return Ok(Some(self.frame.as_ref())); + } else if self.eof { + self.done = true; + return Ok(None); + } else { + self.send_packet = true; + } + } + } + + fn info(&self) -> DecoderInfo { + self.info + } +} diff --git a/image-processor/src/worker/process/decoder/libavif.rs b/image-processor/src/worker/process/decoder/libavif.rs new file mode 100644 index 00000000..d7d4614b --- /dev/null +++ b/image-processor/src/worker/process/decoder/libavif.rs @@ -0,0 +1,135 @@ +use std::borrow::Cow; +use std::ptr::NonNull; + +use scuffle_image_processor_proto::Task; + +use super::{Decoder, DecoderError, DecoderFrontend, DecoderInfo, LoopCount}; +use crate::worker::process::frame::FrameRef; +use crate::worker::process::libavif::{AvifError, AvifRgbImage}; +use crate::worker::process::smart_object::SmartPtr; + +#[derive(Debug)] +pub struct AvifDecoder<'data> { + decoder: SmartPtr, + info: DecoderInfo, + _data: Cow<'data, [u8]>, + img: AvifRgbImage, + total_duration: u64, + max_input_duration: Option, +} + +impl<'data> AvifDecoder<'data> { + #[tracing::instrument(skip(task, data), fields(name = "AvifDecoder::new"))] + pub fn new(task: &Task, data: Cow<'data, [u8]>) -> Result { + let mut decoder = SmartPtr::new( + NonNull::new(unsafe { libavif_sys::avifDecoderCreate() }).ok_or(AvifError::OutOfMemory)?, + |ptr| { + // Safety: The decoder is valid. + unsafe { + libavif_sys::avifDecoderDestroy(ptr.as_ptr()); + } + }, + ); + + if let (Some(max_input_width), Some(max_input_height)) = ( + task.limits.as_ref().and_then(|l| l.max_input_width), + task.limits.as_ref().and_then(|l| l.max_input_height), + ) { + decoder.as_mut().imageDimensionLimit = max_input_width * max_input_height; + } + + if let Some(max_input_frame_count) = task.limits.as_ref().and_then(|l| l.max_input_frame_count) { + decoder.as_mut().imageCountLimit = max_input_frame_count; + } + + // Safety: The decoder is valid. + let io = NonNull::new(unsafe { libavif_sys::avifIOCreateMemoryReader(data.as_ptr(), data.len()) }) + .ok_or(AvifError::OutOfMemory)?; + + // Set the io pointer. + decoder.as_mut().io = io.as_ptr(); + + // Parse the data. + AvifError::from_code(unsafe { libavif_sys::avifDecoderParse(decoder.as_ptr()) })?; + + let image = AvifRgbImage::new(decoder.as_ref()); + + let info = DecoderInfo { + width: image.width as usize, + height: image.height as usize, + loop_count: if decoder.as_ref().repetitionCount <= 0 { + LoopCount::Infinite + } else { + LoopCount::Finite(decoder.as_ref().repetitionCount as usize) + }, + frame_count: decoder.as_ref().imageCount.max(1) as _, + timescale: decoder.as_ref().timescale, + }; + + if let Some(max_input_width) = task.limits.as_ref().and_then(|l| l.max_input_width) { + if info.width > max_input_width as usize { + return Err(DecoderError::TooWide(info.width as i32)); + } + } + + if let Some(max_input_height) = task.limits.as_ref().and_then(|l| l.max_input_height) { + if info.height > max_input_height as usize { + return Err(DecoderError::TooHigh(info.height as i32)); + } + } + + if let Some(max_input_frame_count) = task.limits.as_ref().and_then(|l| l.max_input_frame_count) { + if info.frame_count > max_input_frame_count as usize { + return Err(DecoderError::TooManyFrames(info.frame_count as i64)); + } + } + + Ok(Self { + _data: data, + img: AvifRgbImage::new(decoder.as_ref()), + decoder, + max_input_duration: task + .limits + .as_ref() + .and_then(|l| l.max_input_duration_ms) + .map(|max_input_duration_ms| max_input_duration_ms as u64 * info.timescale / 1000), + total_duration: 0, + info, + }) + } +} + +impl Decoder for AvifDecoder<'_> { + fn backend(&self) -> DecoderFrontend { + DecoderFrontend::LibAvif + } + + fn info(&self) -> DecoderInfo { + self.info + } + + #[tracing::instrument(skip(self), fields(name = "AvifDecoder::decode"))] + fn decode(&mut self) -> Result, DecoderError> { + if AvifError::from_code(unsafe { libavif_sys::avifDecoderNextImage(self.decoder.as_ptr()) }).is_err() { + return Ok(None); + } + + AvifError::from_code(unsafe { libavif_sys::avifImageYUVToRGB(self.decoder.as_ref().image, &mut *self.img) })?; + + let duration_ts = self.decoder.as_ref().imageTiming.durationInTimescales; + self.total_duration += duration_ts; + + if let Some(max_input_duration) = self.max_input_duration { + if self.total_duration > max_input_duration { + return Err(DecoderError::TooLong(self.total_duration as i64)); + } + } + + Ok(Some(FrameRef::new( + self.img.data(), + self.img.width as usize, + self.img.height as usize, + duration_ts, + ))) + } +} diff --git a/image-processor/src/worker/process/decoder/libwebp.rs b/image-processor/src/worker/process/decoder/libwebp.rs new file mode 100644 index 00000000..25298382 --- /dev/null +++ b/image-processor/src/worker/process/decoder/libwebp.rs @@ -0,0 +1,132 @@ +use std::borrow::Cow; +use std::ptr::NonNull; + +use scuffle_image_processor_proto::Task; + +use super::{Decoder, DecoderError, DecoderFrontend, DecoderInfo, LoopCount}; +use crate::worker::process::frame::FrameRef; +use crate::worker::process::libwebp::{zero_memory_default, WebPError}; +use crate::worker::process::smart_object::SmartPtr; + +pub struct WebpDecoder<'data> { + info: DecoderInfo, + decoder: SmartPtr, + _data: Cow<'data, [u8]>, + timestamp: i32, + total_duration: u64, + max_input_duration: Option, +} + +impl<'data> WebpDecoder<'data> { + #[tracing::instrument(skip(task, data), fields(name = "WebpDecoder::new"))] + pub fn new(task: &Task, data: Cow<'data, [u8]>) -> Result { + let decoder = SmartPtr::new( + NonNull::new(unsafe { + libwebp_sys::WebPAnimDecoderNew( + &libwebp_sys::WebPData { + bytes: data.as_ptr(), + size: data.len(), + }, + std::ptr::null(), + ) + }) + .ok_or(WebPError::OutOfMemory)?, + |decoder| { + // Safety: The decoder is valid. + unsafe { + libwebp_sys::WebPAnimDecoderDelete(decoder.as_ptr()); + } + }, + ); + + let mut info = zero_memory_default::(); + + // Safety: both pointers are valid and the decoder is valid. + if unsafe { libwebp_sys::WebPAnimDecoderGetInfo(decoder.as_ptr(), &mut info) } == 0 { + return Err(DecoderError::LibWebp(WebPError::InvalidData)); + } + + if let Some(max_input_width) = task.limits.as_ref().and_then(|l| l.max_input_width) { + if info.canvas_width > max_input_width { + return Err(DecoderError::TooWide(info.canvas_width as i32)); + } + } + + if let Some(max_input_height) = task.limits.as_ref().and_then(|l| l.max_input_height) { + if info.canvas_height > max_input_height { + return Err(DecoderError::TooHigh(info.canvas_height as i32)); + } + } + + if let Some(max_input_frame_count) = task.limits.as_ref().and_then(|l| l.max_input_frame_count) { + if info.frame_count > max_input_frame_count { + return Err(DecoderError::TooManyFrames(info.frame_count as i64)); + } + } + + Ok(Self { + info: DecoderInfo { + width: info.canvas_width as _, + height: info.canvas_height as _, + loop_count: match info.loop_count { + 0 => LoopCount::Infinite, + _ => LoopCount::Finite(info.loop_count as _), + }, + frame_count: info.frame_count as _, + timescale: 1000, + }, + max_input_duration: task + .limits + .as_ref() + .and_then(|l| l.max_input_duration_ms) + .map(|dur| dur as u64), + decoder, + _data: data, + total_duration: 0, + timestamp: 0, + }) + } +} + +impl Decoder for WebpDecoder<'_> { + fn backend(&self) -> DecoderFrontend { + DecoderFrontend::LibWebp + } + + fn info(&self) -> DecoderInfo { + self.info + } + + #[tracing::instrument(skip(self), fields(name = "WebpDecoder::decode"))] + fn decode(&mut self) -> Result, DecoderError> { + let mut buf = std::ptr::null_mut(); + let previous_timestamp = self.timestamp; + + // Safety: The buffer is a valid pointer to a null ptr, timestamp is a valid + // pointer to i32, and the decoder is valid. + let result = unsafe { libwebp_sys::WebPAnimDecoderGetNext(self.decoder.as_ptr(), &mut buf, &mut self.timestamp) }; + + // If 0 is returned, the animation is over. + if result == 0 { + return Ok(None); + } + + let buf = NonNull::new(buf).ok_or(WebPError::OutOfMemory)?; + + let buf = + unsafe { std::slice::from_raw_parts(buf.as_ptr() as *const rgb::RGBA8, self.info.width * self.info.height) }; + + let duration_ts = (self.timestamp - previous_timestamp).max(0) as u64; + self.total_duration += duration_ts; + + if let Some(max_input_duration) = self.max_input_duration { + if self.total_duration > max_input_duration { + return Err(DecoderError::TooLong(self.total_duration as i64)); + } + } + + let duration_ts = (self.timestamp - previous_timestamp).max(0) as u64; + + Ok(Some(FrameRef::new(buf, self.info.width, self.info.height, duration_ts))) + } +} diff --git a/image-processor/src/processor/job/decoder/mod.rs b/image-processor/src/worker/process/decoder/mod.rs similarity index 63% rename from image-processor/src/processor/job/decoder/mod.rs rename to image-processor/src/worker/process/decoder/mod.rs index e08d8edd..ee7f22af 100644 --- a/image-processor/src/processor/job/decoder/mod.rs +++ b/image-processor/src/worker/process/decoder/mod.rs @@ -1,24 +1,54 @@ use std::borrow::Cow; use file_format::FileFormat; +use scuffle_ffmpeg::error::FfmpegError; +use scuffle_image_processor_proto::Task; -use super::frame::Frame; -use crate::database::Job; -use crate::processor::error::{ProcessorError, Result}; +use super::frame::FrameRef; +use super::libavif::AvifError; +use super::libwebp::WebPError; mod ffmpeg; mod libavif; mod libwebp; +#[derive(Debug, thiserror::Error)] +pub enum DecoderError { + #[error("ffmpeg: {0}")] + Ffmpeg(#[from] FfmpegError), + #[error("libavif: {0}")] + LibAvif(#[from] AvifError), + #[error("libwebp: {0}")] + LibWebp(#[from] WebPError), + #[error("unsupported input format: {0}")] + UnsupportedInputFormat(FileFormat), + #[error("no video stream")] + NoVideoStream, + #[error("no frame count")] + NoFrameCount, + #[error("invalid time base")] + InvalidTimeBase, + #[error("invalid video decoder")] + InvalidVideoDecoder, + #[error("exceeded maximum input width: {0}")] + TooWide(i32), + #[error("exceeded maximum input height: {0}")] + TooHigh(i32), + #[error("exceeded maximum input frame count: {0}")] + TooManyFrames(i64), + #[error("exceeded maximum input duration: {0}")] + TooLong(i64), +} + #[derive(Debug, Clone, Copy)] -pub enum DecoderBackend { +pub enum DecoderFrontend { Ffmpeg, LibWebp, LibAvif, } -impl DecoderBackend { - pub const fn from_format(format: FileFormat) -> Result { +impl DecoderFrontend { + pub const fn from_format(format: FileFormat) -> Result { match format { FileFormat::Webp => Ok(Self::LibWebp), // .webp FileFormat::Av1ImageFileFormat // .avif @@ -44,15 +74,15 @@ impl DecoderBackend { | FileFormat::Webm // .webm | FileFormat::BdavMpeg2TransportStream // .m2ts | FileFormat::Mpeg2TransportStream => Ok(Self::Ffmpeg), // .ts - _ => Err(ProcessorError::UnsupportedInputFormat(format)), + _ => Err(DecoderError::UnsupportedInputFormat(format)), } } - pub fn build<'a>(&self, job: &Job, data: Cow<'a, [u8]>) -> Result> { + pub fn build<'a>(&self, task: &Task, data: Cow<'a, [u8]>) -> Result, DecoderError> { match self { - Self::Ffmpeg => Ok(AnyDecoder::Ffmpeg(ffmpeg::FfmpegDecoder::new(job, data)?)), - Self::LibAvif => Ok(AnyDecoder::LibAvif(libavif::AvifDecoder::new(job, data)?)), - Self::LibWebp => Ok(AnyDecoder::LibWebp(libwebp::WebpDecoder::new(job, data)?)), + Self::Ffmpeg => Ok(AnyDecoder::Ffmpeg(ffmpeg::FfmpegDecoder::new(task, data)?)), + Self::LibAvif => Ok(AnyDecoder::LibAvif(libavif::AvifDecoder::new(task, data)?)), + Self::LibWebp => Ok(AnyDecoder::LibWebp(libwebp::WebpDecoder::new(task, data)?)), } } } @@ -64,9 +94,9 @@ pub enum AnyDecoder<'a> { } pub trait Decoder { - fn backend(&self) -> DecoderBackend; + fn backend(&self) -> DecoderFrontend; fn info(&self) -> DecoderInfo; - fn decode(&mut self) -> Result>; + fn decode(&mut self) -> Result, DecoderError>; } #[derive(Debug, Clone, Copy)] @@ -85,7 +115,7 @@ pub enum LoopCount { } impl Decoder for AnyDecoder<'_> { - fn backend(&self) -> DecoderBackend { + fn backend(&self) -> DecoderFrontend { match self { Self::Ffmpeg(decoder) => decoder.backend(), Self::LibAvif(decoder) => decoder.backend(), @@ -101,7 +131,7 @@ impl Decoder for AnyDecoder<'_> { } } - fn decode(&mut self) -> Result> { + fn decode(&mut self) -> Result, DecoderError> { match self { Self::Ffmpeg(decoder) => decoder.decode(), Self::LibAvif(decoder) => decoder.decode(), diff --git a/image-processor/src/worker/process/encoder/gifski.rs b/image-processor/src/worker/process/encoder/gifski.rs new file mode 100644 index 00000000..48de33f1 --- /dev/null +++ b/image-processor/src/worker/process/encoder/gifski.rs @@ -0,0 +1,87 @@ +use scuffle_image_processor_proto::OutputQuality; + +use super::{Encoder, EncoderBackend, EncoderError, EncoderInfo, EncoderSettings}; +use crate::worker::process::decoder::LoopCount; +use crate::worker::process::frame::FrameRef; + +pub struct GifskiEncoder { + collector: gifski::Collector, + writer: std::thread::JoinHandle, EncoderError>>, + info: EncoderInfo, +} + +impl GifskiEncoder { + #[tracing::instrument(skip(settings), fields(name = "GifskiEncoder::new"))] + pub fn new(settings: EncoderSettings) -> Result { + let (collector, writer) = gifski::new(gifski::Settings { + repeat: match settings.loop_count { + LoopCount::Infinite => gifski::Repeat::Infinite, + LoopCount::Finite(count) => gifski::Repeat::Finite(count as u16), + }, + quality: match settings.quality { + OutputQuality::Auto => 100, + OutputQuality::High => 100, + OutputQuality::Lossless => 100, + OutputQuality::Medium => 75, + OutputQuality::Low => 50, + }, + fast: match settings.quality { + OutputQuality::Auto => true, + OutputQuality::High => false, + OutputQuality::Lossless => false, + OutputQuality::Medium => true, + OutputQuality::Low => true, + }, + ..Default::default() + })?; + + Ok(Self { + collector, + writer: std::thread::spawn(move || { + let mut buffer = Vec::new(); + writer.write(&mut buffer, &mut gifski::progress::NoProgress {})?; + Ok(buffer) + }), + info: EncoderInfo { + name: settings.name, + duration: 0, + frame_count: 0, + format: settings.format, + frontend: EncoderBackend::Gifski, + height: 0, + loop_count: settings.loop_count, + timescale: settings.timescale, + width: 0, + }, + }) + } + + fn duration(&mut self, duration: u64) -> f64 { + self.info.duration += duration; + self.info.duration as f64 / self.info.timescale as f64 + } +} + +impl Encoder for GifskiEncoder { + fn info(&self) -> &EncoderInfo { + &self.info + } + + #[tracing::instrument(skip(self), fields(name = "GifskiEncoder::add_frame"))] + fn add_frame(&mut self, frame: FrameRef) -> Result<(), EncoderError> { + let frame = frame.to_owned(); + self.info.height = frame.image.height(); + self.info.width = frame.image.width(); + let duration = self.duration(frame.duration_ts); + self.collector.add_frame_rgba(self.info.frame_count, frame.image, duration)?; + self.info.frame_count += 1; + Ok(()) + } + + #[tracing::instrument(skip(self), fields(name = "GifskiEncoder::finish"))] + fn finish(self) -> Result, EncoderError> { + drop(self.collector); + + self.writer.join().map_err(|_| EncoderError::Thread)? + } +} diff --git a/image-processor/src/processor/job/encoder/libavif.rs b/image-processor/src/worker/process/encoder/libavif.rs similarity index 60% rename from image-processor/src/processor/job/encoder/libavif.rs rename to image-processor/src/worker/process/encoder/libavif.rs index 009adad7..7086ff34 100644 --- a/image-processor/src/processor/job/encoder/libavif.rs +++ b/image-processor/src/worker/process/encoder/libavif.rs @@ -1,12 +1,12 @@ use std::ptr::NonNull; -use anyhow::Context; +use libavif_sys::{AVIF_QUALITY_LOSSLESS, AVIF_QUANTIZER_BEST_QUALITY, AVIF_SPEED_FASTEST, AVIF_SPEED_SLOWEST}; +use scuffle_image_processor_proto::OutputQuality; -use super::{Encoder, EncoderFrontend, EncoderInfo, EncoderSettings}; -use crate::processor::error::{ProcessorError, Result}; -use crate::processor::job::frame::Frame; -use crate::processor::job::libavif::AvifError; -use crate::processor::job::smart_object::{SmartObject, SmartPtr}; +use super::{Encoder, EncoderBackend, EncoderError, EncoderInfo, EncoderSettings}; +use crate::worker::process::frame::FrameRef; +use crate::worker::process::libavif::AvifError; +use crate::worker::process::smart_object::{SmartObject, SmartPtr}; pub struct AvifEncoder { encoder: SmartPtr, @@ -18,25 +18,41 @@ pub struct AvifEncoder { } impl AvifEncoder { - pub fn new(settings: EncoderSettings) -> Result { + #[tracing::instrument(skip(settings), fields(name = "AvifEncoder::new"))] + pub fn new(settings: EncoderSettings) -> Result { let mut encoder = SmartPtr::new( - NonNull::new(unsafe { libavif_sys::avifEncoderCreate() }) - .ok_or(AvifError::OutOfMemory) - .context("failed to create avif encoder") - .map_err(ProcessorError::AvifEncode)?, + NonNull::new(unsafe { libavif_sys::avifEncoderCreate() }).ok_or(AvifError::OutOfMemory)?, |ptr| unsafe { libavif_sys::avifEncoderDestroy(ptr.as_ptr()) }, ); encoder.as_mut().maxThreads = 1; encoder.as_mut().timescale = settings.timescale; encoder.as_mut().autoTiling = 1; - encoder.as_mut().speed = if settings.fast { 8 } else { 2 }; + encoder.as_mut().quality = match settings.quality { + OutputQuality::Auto => encoder.as_mut().quality, + OutputQuality::Lossless => AVIF_QUALITY_LOSSLESS as i32, + OutputQuality::High => 75, + OutputQuality::Medium => 50, + OutputQuality::Low => 25_i32, + }; + encoder.as_mut().qualityAlpha = encoder.as_mut().quality; + encoder.as_mut().minQuantizer = match settings.quality { + OutputQuality::Auto => encoder.as_mut().minQuantizer, + OutputQuality::Lossless => AVIF_QUANTIZER_BEST_QUALITY as i32, + OutputQuality::High => 5, + OutputQuality::Medium => 15, + OutputQuality::Low => 30, + }; + encoder.as_mut().speed = match settings.quality { + OutputQuality::Auto => 8, + OutputQuality::Lossless => AVIF_SPEED_SLOWEST as i32, + OutputQuality::High => 5, + OutputQuality::Medium => 8, + OutputQuality::Low => AVIF_SPEED_FASTEST as i32, + }; let mut image = SmartPtr::new( - NonNull::new(unsafe { libavif_sys::avifImageCreateEmpty() }) - .ok_or(AvifError::OutOfMemory) - .context("failed to create avif image") - .map_err(ProcessorError::AvifEncode)?, + NonNull::new(unsafe { libavif_sys::avifImageCreateEmpty() }).ok_or(AvifError::OutOfMemory)?, |ptr| unsafe { libavif_sys::avifImageDestroy(ptr.as_ptr()) }, ); @@ -56,9 +72,11 @@ impl AvifEncoder { first_duration: None, static_image: settings.static_image, info: EncoderInfo { + name: settings.name, duration: 0, frame_count: 0, - frontend: EncoderFrontend::LibAvif, + format: settings.format, + frontend: EncoderBackend::LibAvif, height: 0, loop_count: settings.loop_count, timescale: settings.timescale, @@ -67,26 +85,23 @@ impl AvifEncoder { }) } - fn flush_frame(&mut self, duration: u64, flags: u32) -> Result<()> { + fn flush_frame(&mut self, duration: u64, flags: u32) -> Result<(), EncoderError> { // Safety: The image is valid. AvifError::from_code(unsafe { libavif_sys::avifEncoderAddImage(self.encoder.as_mut(), self.image.as_mut(), duration, flags) - }) - .context("failed to add image to encoder") - .map_err(ProcessorError::AvifEncode)?; + })?; Ok(()) } } impl Encoder for AvifEncoder { - fn info(&self) -> EncoderInfo { - self.info + fn info(&self) -> &EncoderInfo { + &self.info } - fn add_frame(&mut self, frame: &Frame) -> Result<()> { - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - + #[tracing::instrument(skip(self), fields(name = "AvifEncoder::add_frame"))] + fn add_frame(&mut self, frame: FrameRef) -> Result<(), EncoderError> { if self.rgb.is_none() { self.image.as_mut().width = frame.image.width() as u32; self.image.as_mut().height = frame.image.height() as u32; @@ -104,7 +119,7 @@ impl Encoder for AvifEncoder { self.first_duration = Some(frame.duration_ts); } else if let Some(first_duration) = self.first_duration.take() { if self.static_image { - return Err(ProcessorError::AvifEncode(anyhow::anyhow!("static image already added"))); + return Err(EncoderError::MultipleFrames); } // Flush the first frame to the encoder. @@ -117,9 +132,7 @@ impl Encoder for AvifEncoder { rgb.pixels = frame.image.buf().as_ptr() as _; // Safety: The image and rgb are valid. - AvifError::from_code(unsafe { libavif_sys::avifImageRGBToYUV(self.image.as_mut(), rgb) }) - .context("failed to convert rgb to yuv") - .map_err(ProcessorError::AvifEncode)?; + AvifError::from_code(unsafe { libavif_sys::avifImageRGBToYUV(self.image.as_mut(), rgb) })?; // On the first frame we dont want to flush the image to the encoder yet, this // is because we don't know if there will be more frames. @@ -135,11 +148,10 @@ impl Encoder for AvifEncoder { Ok(()) } - fn finish(mut self) -> Result> { - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - + #[tracing::instrument(skip(self), fields(name = "AvifEncoder::finish"))] + fn finish(mut self) -> Result, EncoderError> { if self.rgb.is_none() { - return Err(ProcessorError::AvifEncode(anyhow::anyhow!("no frames added"))); + return Err(EncoderError::NoFrames); } if let Some(first_duration) = self.first_duration.take() { @@ -150,16 +162,11 @@ impl Encoder for AvifEncoder { libavif_sys::avifRWDataFree(ptr) }); - AvifError::from_code(unsafe { libavif_sys::avifEncoderFinish(self.encoder.as_mut(), &mut *output) }) - .context("failed to finish encoding") - .map_err(ProcessorError::AvifEncode)?; + AvifError::from_code(unsafe { libavif_sys::avifEncoderFinish(self.encoder.as_mut(), &mut *output) })?; let output = output.free(); - let mut data = NonNull::new(output.data) - .ok_or(AvifError::OutOfMemory) - .context("failed to get output data") - .map_err(ProcessorError::AvifEncode)?; + let mut data = NonNull::new(output.data).ok_or(AvifError::OutOfMemory)?; // Safety: The output is valid, and we own the data. let vec = unsafe { std::vec::Vec::from_raw_parts(data.as_mut(), output.size, output.size) }; diff --git a/image-processor/src/processor/job/encoder/libwebp.rs b/image-processor/src/worker/process/encoder/libwebp.rs similarity index 73% rename from image-processor/src/processor/job/encoder/libwebp.rs rename to image-processor/src/worker/process/encoder/libwebp.rs index b63281a6..d09c68b6 100644 --- a/image-processor/src/processor/job/encoder/libwebp.rs +++ b/image-processor/src/worker/process/encoder/libwebp.rs @@ -1,14 +1,13 @@ use std::ptr::NonNull; -use anyhow::Context; use libwebp_sys::WebPMuxAnimParams; +use scuffle_image_processor_proto::OutputQuality; -use super::{Encoder, EncoderFrontend, EncoderInfo, EncoderSettings}; -use crate::processor::error::{ProcessorError, Result}; -use crate::processor::job::decoder::LoopCount; -use crate::processor::job::frame::Frame; -use crate::processor::job::libwebp::{zero_memory_default, WebPError}; -use crate::processor::job::smart_object::{SmartObject, SmartPtr}; +use super::{Encoder, EncoderBackend, EncoderError, EncoderInfo, EncoderSettings}; +use crate::worker::process::decoder::LoopCount; +use crate::worker::process::frame::FrameRef; +use crate::worker::process::libwebp::{zero_memory_default, WebPError}; +use crate::worker::process::smart_object::{SmartObject, SmartPtr}; pub struct WebpEncoder { config: libwebp_sys::WebPConfig, @@ -20,26 +19,39 @@ pub struct WebpEncoder { static_image: bool, } -fn wrap_error(status: i32, err: &'static str, message: &'static str) -> Result<()> { +fn wrap_error(status: i32, err: &'static str) -> Result<(), WebPError> { if status == 0 { Err(WebPError::UnknownError(err)) - .context(message) - .map_err(ProcessorError::WebPEncode) } else { Ok(()) } } impl WebpEncoder { - pub fn new(settings: EncoderSettings) -> Result { + #[tracing::instrument(skip(settings), fields(name = "WebpEncoder::new"))] + pub fn new(settings: EncoderSettings) -> Result { let mut config = zero_memory_default::(); + config.lossless = if settings.quality == OutputQuality::Lossless { 1 } else { 0 }; + config.quality = match settings.quality { + OutputQuality::Auto => 90.0, + OutputQuality::High => 100.0, + OutputQuality::Lossless => 60.0, // 0-6 + OutputQuality::Medium => 75.0, + OutputQuality::Low => 50.0, + }; + config.method = match settings.quality { + OutputQuality::Auto => 4, + OutputQuality::High => 4, + OutputQuality::Lossless => 6, + OutputQuality::Medium => 3, + OutputQuality::Low => 2, + }; config.thread_level = 1; wrap_error( unsafe { libwebp_sys::WebPConfigInit(&mut config) }, "failed to initialize webp config", - "libwebp_sys::WebPConfigInit", )?; let mut picture = SmartObject::new(zero_memory_default::(), |ptr| unsafe { @@ -49,27 +61,28 @@ impl WebpEncoder { wrap_error( unsafe { libwebp_sys::WebPPictureInit(&mut *picture) }, "failed to initialize webp picture", - "libwebp_sys::WebPPictureInit", )?; picture.use_argb = 1; Ok(Self { config, - settings, - picture, - encoder: None, - first_duration: None, - static_image: settings.static_image, info: EncoderInfo { + name: settings.name.clone(), duration: 0, frame_count: 0, - frontend: EncoderFrontend::LibWebp, + format: settings.format, + frontend: EncoderBackend::LibWebp, height: 0, loop_count: settings.loop_count, timescale: settings.timescale, width: 0, }, + static_image: settings.static_image, + settings, + picture, + encoder: None, + first_duration: None, }) } @@ -77,9 +90,7 @@ impl WebpEncoder { self.info.duration * 1000 / self.settings.timescale } - fn flush_frame(&mut self, duration: u64) -> Result<()> { - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - + fn flush_frame(&mut self, duration: u64) -> Result<(), EncoderError> { // Safety: The picture is valid. wrap_error( unsafe { @@ -91,7 +102,6 @@ impl WebpEncoder { ) }, "failed to add webp frame", - "libwebp_sys::WebPAnimEncoderAdd", )?; self.info.duration += duration; @@ -101,20 +111,19 @@ impl WebpEncoder { } impl Encoder for WebpEncoder { - fn info(&self) -> EncoderInfo { - self.info + fn info(&self) -> &EncoderInfo { + &self.info } - fn add_frame(&mut self, frame: &Frame) -> Result<()> { - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - + #[tracing::instrument(skip(self), fields(name = "WebpEncoder::add_frame"))] + fn add_frame(&mut self, frame: FrameRef) -> Result<(), EncoderError> { if self.first_duration.is_none() && self.encoder.is_none() { self.picture.width = frame.image.width() as _; self.picture.height = frame.image.height() as _; self.first_duration = Some(frame.duration_ts); } else if let Some(first_duration) = self.first_duration.take() { if self.static_image { - return Err(ProcessorError::WebPEncode(anyhow::anyhow!("static image already added"))); + return Err(EncoderError::MultipleFrames); } let encoder = SmartPtr::new( @@ -127,8 +136,8 @@ impl Encoder for WebpEncoder { anim_params: WebPMuxAnimParams { bgcolor: 0, loop_count: match self.settings.loop_count { - LoopCount::Infinite => 0, LoopCount::Finite(count) => count as _, + LoopCount::Infinite => 0, }, }, kmax: 0, @@ -139,9 +148,7 @@ impl Encoder for WebpEncoder { }, ) }) - .ok_or(WebPError::OutOfMemory) - .context("failed to create webp encoder") - .map_err(ProcessorError::WebPEncode)?, + .ok_or(WebPError::OutOfMemory)?, |encoder| { // Safety: The encoder is valid. unsafe { @@ -163,7 +170,6 @@ impl Encoder for WebpEncoder { ) }, "failed to import webp frame", - "libwebp_sys::WebPPictureImportRGBA", )?; if self.encoder.is_some() { @@ -177,20 +183,18 @@ impl Encoder for WebpEncoder { Ok(()) } - fn finish(mut self) -> Result> { - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - + #[tracing::instrument(skip(self), fields(name = "WebpEncoder::finish"))] + fn finish(mut self) -> Result, EncoderError> { let timestamp = self.timestamp(); if self.encoder.is_none() && self.first_duration.is_none() { - Err(ProcessorError::WebPEncode(anyhow::anyhow!("no frames added"))) + Err(EncoderError::NoFrames) } else if let Some(mut encoder) = self.encoder { wrap_error( unsafe { libwebp_sys::WebPAnimEncoderAdd(encoder.as_mut(), std::ptr::null_mut(), timestamp as _, std::ptr::null()) }, "failed to add null webp frame", - "libwebp_sys::WebPAnimEncoderAdd", )?; let mut webp_data = SmartObject::new(zero_memory_default::(), |ptr| unsafe { @@ -203,15 +207,11 @@ impl Encoder for WebpEncoder { wrap_error( unsafe { libwebp_sys::WebPAnimEncoderAssemble(encoder.as_mut(), &mut *webp_data) }, "failed to assemble webp", - "libwebp_sys::WebPAnimEncoderAssemble", )?; let webp_data = webp_data.free(); - let mut data = NonNull::new(webp_data.bytes as _) - .ok_or(WebPError::OutOfMemory) - .context("failed to get output data") - .map_err(ProcessorError::WebPEncode)?; + let mut data = NonNull::new(webp_data.bytes as _).ok_or(WebPError::OutOfMemory)?; // Safety: The data is valid and we are taking ownership of it. let vec = unsafe { std::vec::Vec::from_raw_parts(data.as_mut(), webp_data.size, webp_data.size) }; @@ -236,15 +236,11 @@ impl Encoder for WebpEncoder { wrap_error( unsafe { libwebp_sys::WebPEncode(&self.config, &mut *self.picture) }, "failed to encode webp", - "libwebp_sys::WebPEncode", )?; let memory_writer = memory_writer.free(); - let mut data = NonNull::new(memory_writer.mem) - .ok_or(WebPError::OutOfMemory) - .context("failed to get output data") - .map_err(ProcessorError::WebPEncode)?; + let mut data = NonNull::new(memory_writer.mem).ok_or(WebPError::OutOfMemory)?; // Safety: The data is valid and we are taking ownership of it. let vec = unsafe { std::vec::Vec::from_raw_parts(data.as_mut(), memory_writer.size, memory_writer.max_size) }; diff --git a/image-processor/src/processor/job/encoder/mod.rs b/image-processor/src/worker/process/encoder/mod.rs similarity index 58% rename from image-processor/src/processor/job/encoder/mod.rs rename to image-processor/src/worker/process/encoder/mod.rs index 23a43987..e4fff30e 100644 --- a/image-processor/src/processor/job/encoder/mod.rs +++ b/image-processor/src/worker/process/encoder/mod.rs @@ -1,6 +1,9 @@ +use scuffle_image_processor_proto::{OutputFormat, OutputQuality}; + use super::decoder::LoopCount; -use super::frame::Frame; -use crate::processor::error::Result; +use super::frame::FrameRef; +use super::libavif::AvifError; +use super::libwebp::WebPError; mod gifski; mod libavif; @@ -8,24 +11,28 @@ mod libwebp; mod png; #[derive(Debug, Clone, Copy)] -pub enum EncoderFrontend { +pub enum EncoderBackend { Gifski, Png, LibWebp, LibAvif, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct EncoderSettings { - pub fast: bool, + pub name: Option, + pub format: OutputFormat, + pub quality: OutputQuality, pub loop_count: LoopCount, pub timescale: u64, pub static_image: bool, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct EncoderInfo { - pub frontend: EncoderFrontend, + pub name: Option, + pub format: OutputFormat, + pub frontend: EncoderBackend, pub width: usize, pub height: usize, pub loop_count: LoopCount, @@ -34,8 +41,26 @@ pub struct EncoderInfo { pub frame_count: usize, } -impl EncoderFrontend { - pub fn build(&self, settings: EncoderSettings) -> Result { +#[derive(Debug, thiserror::Error)] +pub enum EncoderError { + #[error("gifski: {0}")] + Gifski(#[from] ::gifski::Error), + #[error("thread panicked")] + Thread, + #[error("avif: {0}")] + Avif(#[from] AvifError), + #[error("no frames added")] + NoFrames, + #[error("static image has multiple frames")] + MultipleFrames, + #[error("webp: {0}")] + Webp(#[from] WebPError), + #[error("png: {0}")] + Png(#[from] ::png::EncodingError), +} + +impl EncoderBackend { + pub fn build(&self, settings: EncoderSettings) -> Result { match self { Self::Png => Ok(AnyEncoder::Png(png::PngEncoder::new(settings)?)), Self::Gifski => Ok(AnyEncoder::Gifski(gifski::GifskiEncoder::new(settings)?)), @@ -53,13 +78,13 @@ pub enum AnyEncoder { } pub trait Encoder { - fn info(&self) -> EncoderInfo; - fn add_frame(&mut self, frame: &Frame) -> Result<()>; - fn finish(self) -> Result>; + fn info(&self) -> &EncoderInfo; + fn add_frame(&mut self, frame: FrameRef) -> Result<(), EncoderError>; + fn finish(self) -> Result, EncoderError>; } impl Encoder for AnyEncoder { - fn info(&self) -> EncoderInfo { + fn info(&self) -> &EncoderInfo { match self { Self::Gifski(encoder) => encoder.info(), Self::Png(encoder) => encoder.info(), @@ -68,7 +93,7 @@ impl Encoder for AnyEncoder { } } - fn add_frame(&mut self, frame: &Frame) -> Result<()> { + fn add_frame(&mut self, frame: FrameRef) -> Result<(), EncoderError> { match self { Self::Gifski(encoder) => encoder.add_frame(frame), Self::Png(encoder) => encoder.add_frame(frame), @@ -77,7 +102,7 @@ impl Encoder for AnyEncoder { } } - fn finish(self) -> Result> { + fn finish(self) -> Result, EncoderError> { match self { Self::Gifski(encoder) => encoder.finish(), Self::Png(encoder) => encoder.finish(), diff --git a/image-processor/src/processor/job/encoder/png.rs b/image-processor/src/worker/process/encoder/png.rs similarity index 50% rename from image-processor/src/processor/job/encoder/png.rs rename to image-processor/src/worker/process/encoder/png.rs index 4d4e15dc..c4d5b1f6 100644 --- a/image-processor/src/processor/job/encoder/png.rs +++ b/image-processor/src/worker/process/encoder/png.rs @@ -1,9 +1,7 @@ -use anyhow::Context; use rgb::ComponentBytes; -use super::{Encoder, EncoderFrontend, EncoderInfo, EncoderSettings}; -use crate::processor::error::{ProcessorError, Result}; -use crate::processor::job::frame::Frame; +use super::{Encoder, EncoderBackend, EncoderError, EncoderInfo, EncoderSettings}; +use crate::worker::process::frame::FrameRef; pub struct PngEncoder { result: Option>, @@ -11,13 +9,16 @@ pub struct PngEncoder { } impl PngEncoder { - pub fn new(settings: EncoderSettings) -> Result { + #[tracing::instrument(skip(settings), fields(name = "PngEncoder::new"))] + pub fn new(settings: EncoderSettings) -> Result { Ok(Self { result: None, info: EncoderInfo { + name: settings.name, duration: 0, frame_count: 0, - frontend: EncoderFrontend::Png, + format: settings.format, + frontend: EncoderBackend::Png, height: 0, loop_count: settings.loop_count, timescale: settings.timescale, @@ -28,15 +29,14 @@ impl PngEncoder { } impl Encoder for PngEncoder { - fn info(&self) -> EncoderInfo { - self.info + fn info(&self) -> &EncoderInfo { + &self.info } - fn add_frame(&mut self, frame: &Frame) -> Result<()> { - let _abort_guard = scuffle_utils::task::AbortGuard::new(); - + #[tracing::instrument(skip(self), fields(name = "PngEncoder::add_frame"))] + fn add_frame(&mut self, frame: FrameRef) -> Result<(), EncoderError> { if self.result.is_some() { - return Err(ProcessorError::PngEncode(anyhow::anyhow!("encoder already finished"))); + return Err(EncoderError::MultipleFrames); } self.info.height = frame.image.height(); @@ -54,21 +54,15 @@ impl Encoder for PngEncoder { encoder.set_color(png::ColorType::Rgba); encoder.set_depth(png::BitDepth::Eight); - encoder - .write_header() - .context("failed to write png header") - .map_err(ProcessorError::PngEncode)? - .write_image_data(frame.image.buf().as_bytes()) - .context("failed to write png data") - .map_err(ProcessorError::PngEncode)?; + encoder.write_header()?.write_image_data(frame.image.buf().as_bytes())?; self.result = Some(result); Ok(()) } - fn finish(self) -> Result> { - self.result - .ok_or_else(|| ProcessorError::PngEncode(anyhow::anyhow!("encoder not finished"))) + #[tracing::instrument(skip(self), fields(name = "PngEncoder::finish"))] + fn finish(self) -> Result, EncoderError> { + self.result.ok_or(EncoderError::NoFrames) } } diff --git a/image-processor/src/worker/process/frame.rs b/image-processor/src/worker/process/frame.rs new file mode 100644 index 00000000..b6527517 --- /dev/null +++ b/image-processor/src/worker/process/frame.rs @@ -0,0 +1,47 @@ +use imgref::{Img, ImgVec}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Frame { + pub image: ImgVec, + pub duration_ts: u64, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FrameRef<'a> { + pub image: Img<&'a [rgb::RGBA8]>, + pub duration_ts: u64, +} + +impl FrameRef<'_> { + pub fn to_owned(&self) -> Frame { + Frame { + image: Img::new(self.image.buf().to_vec(), self.image.width(), self.image.height()), + duration_ts: self.duration_ts, + } + } +} + +impl Frame { + pub fn new(width: usize, height: usize) -> Self { + Self { + image: ImgVec::new(vec![rgb::RGBA8::default(); width * height], width, height), + duration_ts: 0, + } + } + + pub fn as_ref(&self) -> FrameRef<'_> { + FrameRef { + image: self.image.as_ref(), + duration_ts: self.duration_ts, + } + } +} + +impl<'a> FrameRef<'a> { + pub fn new(buf: &'a [rgb::RGBA8], width: usize, height: usize, duration_ts: u64) -> Self { + Self { + duration_ts, + image: Img::new(buf, width, height), + } + } +} diff --git a/image-processor/src/worker/process/input_download.rs b/image-processor/src/worker/process/input_download.rs new file mode 100644 index 00000000..b9e1f87a --- /dev/null +++ b/image-processor/src/worker/process/input_download.rs @@ -0,0 +1,54 @@ +use std::sync::Arc; + +use bytes::Bytes; +use scuffle_image_processor_proto::{input, Input}; + +use crate::drive::Drive; +use crate::global::Global; + +#[derive(Debug, thiserror::Error)] +pub enum InputDownloadError { + #[error("missing public http drive")] + MissingPublicHttpDrive, + #[error("missing drive")] + MissingDrive, + #[error("missing input")] + MissingInput, + #[error("drive error: {0}")] + DriveError(#[from] crate::drive::DriveError), +} + +fn get_path(input: Option<&Input>) -> Option<&str> { + match input?.path.as_ref()? { + input::Path::DrivePath(drive) => Some(&drive.path), + input::Path::PublicUrl(url) => Some(url), + } +} + +fn get_drive(input: Option<&Input>) -> Option<&str> { + match input?.path.as_ref()? { + input::Path::DrivePath(drive) => Some(&drive.drive), + input::Path::PublicUrl(_) => None, + } +} + +#[tracing::instrument(skip(global, input), fields(input_path = get_path(input), input_drive = get_drive(input)))] +pub async fn download_input(global: &Arc, input: Option<&Input>) -> Result { + match input + .ok_or(InputDownloadError::MissingInput)? + .path + .as_ref() + .ok_or(InputDownloadError::MissingInput)? + { + input::Path::DrivePath(drive) => Ok(global + .drive(&drive.drive) + .ok_or(InputDownloadError::MissingDrive)? + .read(&drive.path) + .await?), + input::Path::PublicUrl(url) => Ok(global + .public_http_drive() + .ok_or(InputDownloadError::MissingPublicHttpDrive)? + .read(url) + .await?), + } +} diff --git a/image-processor/src/processor/job/libavif.rs b/image-processor/src/worker/process/libavif.rs similarity index 92% rename from image-processor/src/processor/job/libavif.rs rename to image-processor/src/worker/process/libavif.rs index 63d67fcd..5c42d81b 100644 --- a/image-processor/src/processor/job/libavif.rs +++ b/image-processor/src/worker/process/libavif.rs @@ -1,8 +1,7 @@ -use imgref::ImgVec; use rgb::ComponentBytes; #[derive(Debug)] -pub struct AvifRgbImage(libavif_sys::avifRGBImage, imgref::ImgVec); +pub struct AvifRgbImage(libavif_sys::avifRGBImage, Vec); impl AvifRgbImage { pub fn new(dec: &libavif_sys::avifDecoder) -> Self { @@ -17,19 +16,15 @@ impl AvifRgbImage { assert_eq!(channels, 4, "unexpected channel count"); - let mut data = imgref::ImgVec::new( - vec![rgb::RGBA::default(); img.width as usize * img.height as usize], - img.width as usize, - img.height as usize, - ); + let mut data = vec![rgb::RGBA::default(); img.width as usize * img.height as usize]; - img.pixels = data.as_mut().buf_mut().as_bytes_mut().as_mut_ptr() as *mut _; + img.pixels = data.as_bytes_mut().as_mut_ptr(); img.rowBytes = img.width * 4; Self(img, data) } - pub fn data(&self) -> &ImgVec { + pub fn data(&self) -> &Vec { &self.1 } } diff --git a/image-processor/src/processor/job/libwebp.rs b/image-processor/src/worker/process/libwebp.rs similarity index 86% rename from image-processor/src/processor/job/libwebp.rs rename to image-processor/src/worker/process/libwebp.rs index f573db47..769cf8db 100644 --- a/image-processor/src/processor/job/libwebp.rs +++ b/image-processor/src/worker/process/libwebp.rs @@ -4,6 +4,8 @@ pub enum WebPError { UnknownError(&'static str), #[error("out of memory")] OutOfMemory, + #[error("invalid data")] + InvalidData, } pub fn zero_memory_default() -> T { diff --git a/image-processor/src/worker/process/mod.rs b/image-processor/src/worker/process/mod.rs new file mode 100644 index 00000000..b9bddddf --- /dev/null +++ b/image-processor/src/worker/process/mod.rs @@ -0,0 +1,272 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use bson::oid::ObjectId; +use scuffle_foundations::context::Context; +use scuffle_image_processor_proto::{ErrorCode, OutputFormat}; + +use self::resize::ResizeError; +use crate::database::Job; +use crate::drive::{Drive, DriveWriteOptions}; +use crate::global::Global; + +mod blocking; +mod decoder; +mod encoder; +mod frame; +mod input_download; +mod libavif; +mod libwebp; +mod resize; +mod smart_object; + +#[derive(Debug, thiserror::Error)] +pub enum JobError { + #[error("resize: {0}")] + Resize(#[from] ResizeError), + #[error("encoder: {0}")] + Encoder(#[from] encoder::EncoderError), + #[error("decoder: {0}")] + Decoder(#[from] decoder::DecoderError), + #[error("input download: {0}")] + InputDownload(#[from] input_download::InputDownloadError), + #[error("output upload: {0}")] + OutputUpload(#[from] crate::drive::DriveError), + #[error("mongodb: {0}")] + Mongo(#[from] mongodb::error::Error), + #[error("join error: {0}")] + Join(#[from] tokio::task::JoinError), + #[error("mismatched dimensions: {width}x{height} != {expected_width}x{expected_height}")] + MismatchedDimensions { + width: usize, + height: usize, + expected_width: usize, + expected_height: usize, + }, + #[error("mismatched frame count: {frame_count} != {expected_frame_count}")] + MismatchedFrameCount { + frame_count: usize, + expected_frame_count: usize, + }, + #[error("static frame index out of bounds: {idx} >= {frame_count}")] + StaticFrameIndexOutOfBounds { idx: usize, frame_count: usize }, + #[error("invalid job")] + InvalidJob, + #[error("impossible output format, {0:?}, image is not animated")] + ImpossibleOutput(OutputFormat), + #[error("no possible outputs")] + NoPossibleOutputs, + #[error("{0}")] + Internal(&'static str), +} + +impl From for scuffle_image_processor_proto::Error { + fn from(value: JobError) -> Self { + let message = format!("{:#}", value); + + Self { + code: match value { + JobError::Resize(_) => ErrorCode::Resize as i32, + JobError::Encoder(_) => ErrorCode::Encode as i32, + JobError::Decoder(_) => ErrorCode::Decode as i32, + JobError::InputDownload(_) => ErrorCode::InputDownload as i32, + JobError::Mongo(_) => ErrorCode::Internal as i32, + JobError::Join(_) => ErrorCode::Internal as i32, + JobError::MismatchedDimensions { .. } => ErrorCode::InvalidInput as i32, + JobError::MismatchedFrameCount { .. } => ErrorCode::InvalidInput as i32, + JobError::StaticFrameIndexOutOfBounds { .. } => ErrorCode::InvalidInput as i32, + JobError::InvalidJob => ErrorCode::InvalidInput as i32, + JobError::ImpossibleOutput(_) => ErrorCode::InvalidInput as i32, + JobError::Internal(_) => ErrorCode::Internal as i32, + JobError::NoPossibleOutputs => ErrorCode::InvalidInput as i32, + JobError::OutputUpload(_) => ErrorCode::OutputUpload as i32, + }, + message, + } + } +} + +#[derive(Debug)] +pub struct ProcessJob { + job: Job, + _ctx: Context, + permit: Arc, +} + +pub async fn spawn(job: Job, global: Arc, ctx: Context, permit: tokio::sync::OwnedSemaphorePermit) { + let job = ProcessJob::new(job, ctx, permit); + job.process(global).await; +} + +impl ProcessJob { + pub fn new(job: Job, ctx: Context, permit: tokio::sync::OwnedSemaphorePermit) -> Self { + Self { + job, + _ctx: ctx, + permit: Arc::new(permit), + } + } + + #[tracing::instrument(skip(global), fields(job_id = %self.job.id), name = "ProcessJob::process")] + pub async fn process(&self, global: Arc) { + crate::events::on_start(&global, &self.job).await; + + let mut future = self.process_inner(&global); + let mut future = std::pin::pin!(future); + + let mut timeout_fut = self + .job + .task + .limits + .as_ref() + .and_then(|l| l.max_input_duration_ms) + .map(|timeout| Box::pin(tokio::time::sleep(std::time::Duration::from_millis(timeout as u64)))); + + let result = loop { + tokio::select! { + _ = tokio::time::sleep(global.config().worker.refresh_interval) => { + match self.job.refresh(&global).await { + Ok(true) => {}, + Ok(false) => { + tracing::warn!("lost job"); + return; + } + Err(err) => { + tracing::error!("failed to refresh job: {err}"); + return; + } + } + } + Some(_) = async { + if let Some(fut) = timeout_fut.as_mut() { + fut.await; + Some(()) + } else { + None + } + } => { + tracing::warn!("timeout"); + break Err(JobError::Internal("timeout")); + } + result = &mut future => break result, + } + }; + + let err = result + .inspect_err(|err| { + tracing::error!("failed to process job: {err}"); + }) + .err(); + + if let Err(err) = self.job.complete(&global, err).await { + tracing::error!("failed to complete job: {err}"); + } + } + + async fn process_inner(&self, global: &Arc) -> Result<(), JobError> { + let input = input_download::download_input(global, self.job.task.input.as_ref()).await?; + let output_drive_path = self + .job + .task + .output + .as_ref() + .ok_or(JobError::InvalidJob)? + .drive_path + .as_ref() + .ok_or(JobError::InvalidJob)?; + + let output_drive = global.drive(&output_drive_path.drive).ok_or(JobError::InvalidJob)?; + + let job = self.job.clone(); + + let outputs = blocking::spawn(job.task.clone(), input, self.permit.clone()).await?; + + for output in outputs { + let vars = setup_vars( + self.job.id, + output.format_name.clone(), + output.format, + output.scale, + output.width, + output.height, + output.format_idx, + output.resize_idx, + ); + + let file_path = strfmt::strfmt(&output_drive_path.path, &vars).map_err(|err| { + tracing::error!("failed to format path: {err}"); + JobError::Internal("failed to format path") + })?; + + output_drive + .write( + &file_path, + output.data.into(), + Some(DriveWriteOptions { + content_type: Some(content_type(output.format).to_owned()), + ..Default::default() + }), + ) + .await?; + } + + Ok(()) + } +} + +fn setup_vars( + id: ObjectId, + format_name: Option, + format: OutputFormat, + scale: Option, + width: usize, + height: usize, + format_idx: usize, + resize_idx: usize, +) -> HashMap { + let format_name = format_name.unwrap_or_else(|| match format { + OutputFormat::AvifAnim => "avif_anim".to_owned(), + OutputFormat::AvifStatic => "avif_static".to_owned(), + OutputFormat::WebpAnim => "webp_anim".to_owned(), + OutputFormat::WebpStatic => "webp_static".to_owned(), + OutputFormat::PngStatic => "png_static".to_owned(), + OutputFormat::GifAnim => "gif_anim".to_owned(), + }); + + let scale = scale.map(|scale| scale.to_string()).unwrap_or_else(|| "".to_owned()); + + let static_ = match format { + OutputFormat::AvifStatic | OutputFormat::PngStatic | OutputFormat::WebpStatic => "_static", + _ => "", + }; + + let ext = match format { + OutputFormat::AvifAnim | OutputFormat::AvifStatic => "avif", + OutputFormat::PngStatic => "png", + OutputFormat::WebpAnim | OutputFormat::WebpStatic => "webp", + OutputFormat::GifAnim => "gif", + }; + + [ + ("id".to_owned(), id.to_string()), + ("format".to_owned(), format_name), + ("scale".to_owned(), scale), + ("width".to_owned(), width.to_string()), + ("height".to_owned(), height.to_string()), + ("format_idx".to_owned(), format_idx.to_string()), + ("resize_idx".to_owned(), resize_idx.to_string()), + ("static".to_owned(), static_.to_owned()), + ("ext".to_owned(), ext.to_owned()), + ] + .into_iter() + .collect::>() +} + +fn content_type(format: OutputFormat) -> &'static str { + match format { + OutputFormat::AvifAnim | OutputFormat::AvifStatic => "image/avif", + OutputFormat::WebpAnim | OutputFormat::WebpStatic => "image/webp", + OutputFormat::PngStatic => "image/png", + OutputFormat::GifAnim => "image/gif", + } +} diff --git a/image-processor/src/worker/process/resize.rs b/image-processor/src/worker/process/resize.rs new file mode 100644 index 00000000..e8daac92 --- /dev/null +++ b/image-processor/src/worker/process/resize.rs @@ -0,0 +1,419 @@ +use std::num::NonZeroU32; + +use fast_image_resize as fr; +use fr::CropBox; +use rgb::ComponentBytes; +use scuffle_image_processor_proto::{output, scaling, Output, ResizeAlgorithm, ResizeMethod}; + +use super::decoder::DecoderInfo; +use super::frame::{Frame, FrameRef}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Dimensions { + pub width: usize, + pub height: usize, +} + +impl Dimensions { + fn new(width: usize, height: usize) -> Self { + Self { width, height } + } + + fn aspect_ratio(&self) -> f64 { + self.width as f64 / self.height as f64 + } + + fn convert_aspect_ratio(&self, aspect_ratio: f64) -> Self { + if aspect_ratio > self.aspect_ratio() { + Self::new(self.width, (self.width as f64 / aspect_ratio) as usize) + } else { + Self::new((self.height as f64 * aspect_ratio) as usize, self.height) + } + } +} + +enum ImageRef<'a> { + Ref((&'a fr::Image<'a>, Option)), + Owned((fr::Image<'a>, Option)), +} + +impl ImageRef<'_> { + fn crop(&self) -> Option { + match self { + ImageRef::Owned((_, c)) => *c, + ImageRef::Ref((_, c)) => *c, + } + } +} + +impl<'a> std::ops::Deref for ImageRef<'a> { + type Target = fr::Image<'a>; + + fn deref(&self) -> &Self::Target { + match self { + ImageRef::Owned(o) => &o.0, + ImageRef::Ref(r) => r.0, + } + } +} + +/// Resizes images to the given target size. +pub struct ImageResizer { + resizer: fr::Resizer, + input_dims: Dimensions, + cropped_dims: Dimensions, + crop: Option, + resize_dims: Vec, + outputs: Vec, + resize_method: ResizeMethod, + output_frames: Vec, + disable_resize_chaining: bool, +} + +#[derive(Debug, Clone, Copy)] +pub struct ResizeOutputTarget { + pub dimensions: Dimensions, + pub index: usize, + pub scale: Option, +} + +#[derive(thiserror::Error, Debug)] +pub enum ResizeError { + #[error("crop: {0}")] + Crop(#[from] fr::CropBoxError), + #[error("different pixels: {0}")] + DifferentPixels(#[from] fr::DifferentTypesOfPixelsError), + #[error("buffer: {0}")] + Buffer(#[from] fr::ImageBufferError), + #[error("crop dimensions are larger than the input dimensions")] + CropDimensions, + #[error("aspect ratio is too small")] + AspectTooSmall, + #[error("aspect ratio is too large")] + AspectTooLarge, + #[error("invalid crop")] + InvalidCrop, + #[error("missing resize")] + MissingResize, + #[error("no valid resize targets")] + NoValidResizeTargets, + #[error("impossible resize[{0}] {1}x{2} is larger than the input size ({3}x{4})")] + ImpossibleResize(usize, usize, usize, usize, usize), + #[error("input frame has mismatched dimensions")] + MismatchedDimensions, + #[error("{0}")] + Internal(&'static str), +} + +impl ImageResizer { + pub fn new(info: &DecoderInfo, output: &Output) -> Result { + let input_dims = Dimensions::new(info.width, info.height); + + // If there is a crop, we should use that aspect ratio instead. + let cropped_dims = if let Some(crop) = output.crop.as_ref() { + if crop.width == 0 || crop.height == 0 { + return Err(ResizeError::InvalidCrop); + } + + if crop.width + crop.x > info.width as u32 || crop.height + crop.y > info.height as u32 { + return Err(ResizeError::CropDimensions); + } + + Dimensions::new(crop.width as usize, crop.height as usize) + } else { + input_dims + }; + + let resize_method = output.resize_method(); + let mut target_aspect_ratio = cropped_dims.aspect_ratio(); + + if output + .min_aspect_ratio + .is_some_and(|min_aspect_ratio| target_aspect_ratio < min_aspect_ratio) + { + // If the resize method is one of these, we can't make the aspect ratio larger. + // Because we are not allowed to pad the left or right. + if matches!( + resize_method, + ResizeMethod::Fit | ResizeMethod::PadTop | ResizeMethod::PadBottom + ) { + return Err(ResizeError::AspectTooSmall); + } + + target_aspect_ratio = output.min_aspect_ratio(); + } else if output + .max_aspect_ratio + .is_some_and(|max_aspect_ratio| target_aspect_ratio > max_aspect_ratio) + { + // If the resize method is one of these, we can't make the aspect ratio smaller. + // Because we are not allowed to pad the top or bottom. + if matches!( + resize_method, + ResizeMethod::Fit | ResizeMethod::PadLeft | ResizeMethod::PadRight + ) { + return Err(ResizeError::AspectTooLarge); + } + + target_aspect_ratio = output.max_aspect_ratio(); + } + + let mut output_targets: Vec<_> = match output.resize.as_ref().ok_or(ResizeError::MissingResize)? { + output::Resize::Width(widths) => widths + .values + .iter() + .copied() + .enumerate() + .map(|(index, width)| ResizeOutputTarget { + dimensions: Dimensions::new(width as usize, (width as f64 / target_aspect_ratio) as usize), + index, + scale: None, + }) + .collect(), + output::Resize::Height(heights) => heights + .values + .iter() + .copied() + .enumerate() + .map(|(index, height)| ResizeOutputTarget { + dimensions: Dimensions::new((height as f64 * target_aspect_ratio) as usize, height as usize), + index, + scale: None, + }) + .collect(), + output::Resize::Scaling(scaling) => { + let (base_width, base_height) = match scaling.base.clone().ok_or(ResizeError::MissingResize)? { + scaling::Base::Fixed(scale) => { + let input = cropped_dims.convert_aspect_ratio(target_aspect_ratio); + + (input.width / scale as usize, input.height / scale as usize) + } + scaling::Base::BaseWidth(width) => (width as usize, (width as f64 / target_aspect_ratio) as usize), + scaling::Base::BaseHeight(height) => ((height as f64 * target_aspect_ratio) as usize, height as usize), + }; + + scaling + .scales + .iter() + .copied() + .enumerate() + .map(|(index, scale)| ResizeOutputTarget { + dimensions: Dimensions::new(base_width * scale as usize, base_height * scale as usize), + index, + scale: Some(scale), + }) + .collect() + } + }; + + if !output.upscale { + let input_after_transforms = cropped_dims.convert_aspect_ratio(target_aspect_ratio); + + if output.skip_impossible_resizes { + output_targets.retain(|target| target.dimensions <= input_after_transforms); + } else if let Some(target) = output_targets + .iter() + .find(|target| target.dimensions > input_after_transforms) + { + return Err(ResizeError::ImpossibleResize( + target.index, + target.dimensions.width, + target.dimensions.height, + input_after_transforms.width, + input_after_transforms.height, + )); + } + } + + // Build the output frames. + // This is going to be the in the target aspect ratio. + // therefore needs to be done before we convert the aspect ratio back. + let output_frames = output_targets + .iter() + .map(|target| Frame::new(target.dimensions.width, target.dimensions.height)) + .collect(); + + // Convert the apect ratios back to the original aspect ratio. + // This is because padding is added AFTER we resize the image. + // Thus we need to resize the image to the target aspect ratio. + // However if we are stretching the image, we don't need to do this, + // because we want to warp the image. + let resize_targets: Vec<_> = + if target_aspect_ratio != cropped_dims.aspect_ratio() && output.resize_method() != ResizeMethod::Stretch { + output_targets + .iter() + .map(|target| target.dimensions.convert_aspect_ratio(cropped_dims.aspect_ratio())) + .collect() + } else { + output_targets.iter().map(|target| target.dimensions).collect() + }; + + if resize_targets.is_empty() { + return Err(ResizeError::NoValidResizeTargets); + } + + Ok(Self { + resizer: fr::Resizer::new(match output.resize_algorithm() { + ResizeAlgorithm::Nearest => fr::ResizeAlg::Nearest, + ResizeAlgorithm::Box => fr::ResizeAlg::Convolution(fr::FilterType::Box), + ResizeAlgorithm::Bilinear => fr::ResizeAlg::Convolution(fr::FilterType::Bilinear), + ResizeAlgorithm::Hamming => fr::ResizeAlg::Convolution(fr::FilterType::Hamming), + ResizeAlgorithm::CatmullRom => fr::ResizeAlg::Convolution(fr::FilterType::CatmullRom), + ResizeAlgorithm::Mitchell => fr::ResizeAlg::Convolution(fr::FilterType::Mitchell), + ResizeAlgorithm::Lanczos3 => fr::ResizeAlg::Convolution(fr::FilterType::Lanczos3), + }), + input_dims, + cropped_dims, + crop: output.crop.as_ref().map(|crop| CropBox { + left: crop.x as f64, + top: crop.y as f64, + width: crop.width as f64, + height: crop.height as f64, + }), + resize_method: output.resize_method(), + resize_dims: resize_targets, + outputs: output_targets, + output_frames, + disable_resize_chaining: output.disable_resize_chaining, + }) + } + + pub fn outputs(&self) -> &[ResizeOutputTarget] { + &self.outputs + } + + /// Resize the given frame to the target size, returning a reference to the + /// resized frame. After this function returns original frame can be + /// dropped, the returned frame is valid for the lifetime of the Resizer. + pub fn resize(&mut self, frame: FrameRef) -> Result<&[Frame], ResizeError> { + if frame.image.width() != self.input_dims.width || frame.image.height() != self.input_dims.height { + return Err(ResizeError::MismatchedDimensions); + } + + let input_image = fr::Image::from_slice_u8( + NonZeroU32::new(frame.image.width() as u32).unwrap(), + NonZeroU32::new(frame.image.height() as u32).unwrap(), + // Safety: The input_image type is non_mut which disallows mutable actions on the underlying buffer. + unsafe { + let buf = frame.image.buf().as_bytes(); + std::slice::from_raw_parts_mut(buf.as_ptr() as *mut u8, buf.len()) + }, + fr::PixelType::U8x4, + )?; + + let resize_dims = self.resize_dims.iter().rev().copied(); + let output_dims = self.outputs.iter().rev().map(|output| output.dimensions); + let output_frames = self.output_frames.iter_mut().rev(); + + let mut previous_image = ImageRef::Ref((&input_image, self.crop)); + + for ((resize_dims, output_dims), output_frame) in resize_dims.zip(output_dims).zip(output_frames) { + output_frame.duration_ts = frame.duration_ts; + + let mut target_image = fr::Image::from_slice_u8( + NonZeroU32::new(output_dims.width as u32).unwrap(), + NonZeroU32::new(output_dims.height as u32).unwrap(), + output_frame.image.buf_mut().as_mut_slice().as_bytes_mut(), + fr::PixelType::U8x4, + )?; + + let mut view = previous_image.view(); + if let Some(crop) = previous_image.crop() { + view.set_crop_box(crop)?; + } + + let (mut target_view, target_crop) = if resize_dims != output_dims { + let (left, top, width, height) = resize_method_to_crop_dims(self.resize_method, output_dims, resize_dims)?; + ( + target_image.view_mut().crop(left, top, width, height)?, + Some(CropBox { + left: left as f64, + top: top as f64, + width: width.get() as f64, + height: height.get() as f64, + }), + ) + } else { + (target_image.view_mut(), None) + }; + + self.resizer.resize(&view, &mut target_view)?; + + // If we are upscaling then we dont want to downscale from an upscaled image. + // Or if the user has explicitly disabled the resize chain. + if self.disable_resize_chaining || self.cropped_dims < resize_dims { + previous_image = ImageRef::Ref((&input_image, self.crop)); + } else { + previous_image = ImageRef::Owned((target_image, target_crop)); + } + } + + Ok(&self.output_frames) + } +} + +fn resize_method_to_crop_dims( + resize_method: ResizeMethod, + padded_dims: Dimensions, + target_dims: Dimensions, +) -> Result<(u32, u32, NonZeroU32, NonZeroU32), ResizeError> { + let check = |cmp: bool, msg: &'static str| if cmp { Ok(()) } else { Err(ResizeError::Internal(msg)) }; + + check(padded_dims.width >= target_dims.width, "padded width less then target width")?; + check( + padded_dims.height >= target_dims.height, + "padded height less then target height", + )?; + + let center_x = (padded_dims.width - target_dims.width) as u32 / 2; + let center_y = (padded_dims.height - target_dims.height) as u32 / 2; + let left = (padded_dims.width - target_dims.width) as u32; + let top = (padded_dims.height - target_dims.height) as u32; + let bottom = 0; + let right = 0; + let zero = 0; + + let width = NonZeroU32::new(target_dims.width as u32).ok_or(ResizeError::Internal("target width 0"))?; + let height = NonZeroU32::new(target_dims.height as u32).ok_or(ResizeError::Internal("target height 0"))?; + + match resize_method { + ResizeMethod::Fit => Err(ResizeError::Internal("fit should never be called here")), + ResizeMethod::Stretch => Err(ResizeError::Internal("stretch should never be called here")), + ResizeMethod::PadLeft => { + check( + target_dims.width != padded_dims.width, + "pad left should only be called for width padding", + )?; + Ok((left, zero, width, height)) + } + ResizeMethod::PadRight => { + check( + target_dims.height != padded_dims.height, + "pad right should only be called for height padding", + )?; + Ok((right, zero, width, height)) + } + ResizeMethod::PadBottom => { + check( + target_dims.width != padded_dims.width, + "pad bottom should only be called for height padding", + )?; + Ok((zero, bottom, width, height)) + } + ResizeMethod::PadTop => { + check( + target_dims.width != padded_dims.width, + "pad top should only be called for height padding", + )?; + Ok((zero, top, width, height)) + } + ResizeMethod::PadBottomCenter => Ok((center_x, bottom, width, height)), + ResizeMethod::PadTopCenter => Ok((center_x, top, width, height)), + ResizeMethod::PadBottomLeft => Ok((left, bottom, width, height)), + ResizeMethod::PadBottomRight => Ok((right, bottom, width, height)), + ResizeMethod::PadTopLeft => Ok((left, top, width, height)), + ResizeMethod::PadTopRight => Ok((right, top, width, height)), + ResizeMethod::PadCenter => Ok((center_x, center_y, width, height)), + ResizeMethod::PadCenterLeft => Ok((left, center_y, width, height)), + ResizeMethod::PadCenterRight => Ok((right, center_y, width, height)), + } +} diff --git a/image-processor/src/processor/job/smart_object.rs b/image-processor/src/worker/process/smart_object.rs similarity index 68% rename from image-processor/src/processor/job/smart_object.rs rename to image-processor/src/worker/process/smart_object.rs index aa59c1b5..50892194 100644 --- a/image-processor/src/processor/job/smart_object.rs +++ b/image-processor/src/worker/process/smart_object.rs @@ -4,24 +4,29 @@ pub type SmartPtr = SmartObject>; #[derive(Debug)] pub struct SmartObject { - value: T, + value: Option, destructor: fn(&mut T), } impl SmartObject { pub fn new(value: T, destructor: fn(&mut T)) -> Self { - Self { value, destructor } + Self { + value: Some(value), + destructor, + } } pub fn free(mut self) -> T { self.destructor = |_| {}; - std::mem::replace(&mut self.value, unsafe { std::mem::zeroed() }) + self.value.take().unwrap() } } impl Drop for SmartObject { fn drop(&mut self) { - (self.destructor)(&mut self.value); + if let Some(mut value) = self.value.take() { + (self.destructor)(&mut value); + } } } @@ -29,24 +34,24 @@ impl std::ops::Deref for SmartObject { type Target = T; fn deref(&self) -> &Self::Target { - &self.value + self.value.as_ref().unwrap() } } impl std::ops::DerefMut for SmartObject { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.value + self.value.as_mut().unwrap() } } impl AsRef for SmartPtr { fn as_ref(&self) -> &T { - unsafe { self.value.as_ref() } + unsafe { self.value.unwrap().as_ref() } } } impl AsMut for SmartPtr { fn as_mut(&mut self) -> &mut T { - unsafe { self.value.as_mut() } + unsafe { self.value.unwrap().as_mut() } } } From ca2546572e51f3b1b380ceaf2777103e75197e8a Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Sat, 11 May 2024 23:25:00 +0000 Subject: [PATCH 14/21] finish image processor --- Cargo.lock | 235 ++++-------------- Cargo.toml | 6 +- .../src/telemetry/env_filter/env/builder.rs | 24 -- image-processor/Cargo.toml | 12 +- image-processor/proto/Cargo.toml | 2 - image-processor/proto/build.rs | 1 + .../scuffle/image_processor/events.proto | 5 +- .../scuffle/image_processor/service.proto | 6 +- .../proto/scuffle/image_processor/types.proto | 65 +++-- image-processor/src/config.rs | 10 + image-processor/src/database.rs | 60 +++-- image-processor/src/drive/http.rs | 8 +- image-processor/src/drive/memory.rs | 18 +- image-processor/src/drive/mod.rs | 4 + image-processor/src/drive/s3.rs | 48 ++-- image-processor/src/events.rs | 16 +- image-processor/src/main.rs | 2 + image-processor/src/management/grpc.rs | 1 + image-processor/src/management/http.rs | 6 + image-processor/src/management/mod.rs | 64 ++++- image-processor/src/management/validation.rs | 151 ++++++++--- image-processor/src/tests/global.rs | 104 -------- image-processor/src/tests/mod.rs | 3 - .../src/tests/processor/decoder.rs | 118 --------- .../src/tests/processor/encoder.rs | 95 ------- image-processor/src/tests/processor/mod.rs | 3 - image-processor/src/tests/processor/resize.rs | 45 ---- image-processor/src/tests/utils.rs | 23 -- image-processor/src/worker/mod.rs | 10 +- .../src/worker/process/blocking.rs | 10 +- .../src/worker/process/decoder/ffmpeg.rs | 16 +- .../src/worker/process/input_download.rs | 19 +- image-processor/src/worker/process/mod.rs | 143 ++++++----- image-processor/src/worker/process/resize.rs | 6 +- 34 files changed, 528 insertions(+), 811 deletions(-) delete mode 100644 image-processor/src/tests/global.rs delete mode 100644 image-processor/src/tests/mod.rs delete mode 100644 image-processor/src/tests/processor/decoder.rs delete mode 100644 image-processor/src/tests/processor/encoder.rs delete mode 100644 image-processor/src/tests/processor/mod.rs delete mode 100644 image-processor/src/tests/processor/resize.rs delete mode 100644 image-processor/src/tests/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 6aaba3bf..8e6599d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,9 +152,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "async-nats" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eea7b126ebfa4db78e9e788b2a792b6329f35b4f2fdd56dbc646dedc2beec7a5" +checksum = "d5e47d2f7305524258908449aff6c86db36697a9b4219bfb1777e0ca1945358d" dependencies = [ "base64 0.22.1", "bytes", @@ -177,7 +177,7 @@ dependencies = [ "thiserror", "time", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.0", "tracing", "tryhard", "url", @@ -864,12 +864,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.6.0" @@ -1460,12 +1454,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - [[package]] name = "fallible_collections" version = "0.4.9" @@ -1533,9 +1521,9 @@ checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" [[package]] name = "file-format" -version = "0.24.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ba1b81b3c213cf1c071f8bf3b83531f310df99642e58c48247272eef006cae5" +checksum = "9ffe3a660c3a1b10e96f304a9413d673b2118d62e4520f7ddf4a4faccfe8b9b9" [[package]] name = "findshlibs" @@ -1733,10 +1721,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -3002,7 +2988,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall", "smallvec", "windows-targets 0.52.5", ] @@ -3035,21 +3021,6 @@ dependencies = [ "prost-types", ] -[[package]] -name = "pbjson-types" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" -dependencies = [ - "bytes", - "chrono", - "pbjson", - "pbjson-build", - "prost 0.12.4", - "prost-build", - "serde", -] - [[package]] name = "pbkdf2" version = "0.11.0" @@ -3095,24 +3066,6 @@ dependencies = [ "indexmap 2.2.6", ] -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project" version = "1.1.5" @@ -3196,55 +3149,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" -[[package]] -name = "postgres-from-row" -version = "0.5.2" -source = "git+https://github.com/ScuffleTV/postgres-from-row.git?branch=troy/from_fn#3a775f225aae7c0f54e404f3f07aa13fcec2cc9b" -dependencies = [ - "postgres-from-row-derive", - "tokio-postgres", -] - -[[package]] -name = "postgres-from-row-derive" -version = "0.5.2" -source = "git+https://github.com/ScuffleTV/postgres-from-row.git?branch=troy/from_fn#3a775f225aae7c0f54e404f3f07aa13fcec2cc9b" -dependencies = [ - "darling 0.20.8", - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "postgres-protocol" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b6c5ef183cd3ab4ba005f1ca64c21e8bd97ce4699cfea9e8d9a2c4958ca520" -dependencies = [ - "base64 0.21.7", - "byteorder", - "bytes", - "fallible-iterator", - "hmac", - "md-5", - "memchr", - "rand", - "sha2", - "stringprep", -] - -[[package]] -name = "postgres-types" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d2234cdee9408b523530a9b6d2d6b373d1db34f6a8e51dc03ded1828d7fb67c" -dependencies = [ - "bytes", - "fallible-iterator", - "postgres-protocol", -] - [[package]] name = "powerfmt" version = "0.2.0" @@ -3595,15 +3499,6 @@ dependencies = [ "nom", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.1" @@ -3853,6 +3748,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls" +version = "0.23.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afabcee0551bd1aa3e18e5adbf2c0544722014b899adb31bd186ec638d3da97e" +dependencies = [ + "once_cell", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.3", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -4065,9 +3974,7 @@ dependencies = [ "aws-sdk-s3", "aws-smithy-runtime-api", "aws-smithy-types", - "bitvec", "bson", - "byteorder", "bytes", "chrono", "fast_image_resize", @@ -4084,7 +3991,6 @@ dependencies = [ "num_cpus", "once_cell", "png", - "postgres-from-row", "prost 0.12.4", "reqwest", "rgb", @@ -4093,6 +3999,7 @@ dependencies = [ "scuffle-foundations", "scuffle-image-processor-proto", "serde", + "serde-aux", "serde_json", "sha2", "strfmt", @@ -4101,7 +4008,6 @@ dependencies = [ "tonic", "tonic-build", "tracing", - "ulid", "url", "urlencoding", ] @@ -4112,7 +4018,6 @@ version = "0.0.0" dependencies = [ "pbjson", "pbjson-build", - "pbjson-types", "prost 0.12.4", "prost-build", "serde", @@ -4193,6 +4098,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-aux" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d2e8bfba469d06512e11e3311d4d051a4a387a5b42d010404fecf3200321c95" +dependencies = [ + "chrono", + "serde", + "serde_json", +] + [[package]] name = "serde_bytes" version = "0.11.14" @@ -4415,12 +4331,6 @@ dependencies = [ "quote", ] -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "slab" version = "0.4.9" @@ -4779,32 +4689,6 @@ dependencies = [ "syn 2.0.60", ] -[[package]] -name = "tokio-postgres" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d340244b32d920260ae7448cb72b6e238bddc3d4f7603394e7dd46ed8e48f5b8" -dependencies = [ - "async-trait", - "byteorder", - "bytes", - "fallible-iterator", - "futures-channel", - "futures-util", - "log", - "parking_lot", - "percent-encoding", - "phf", - "pin-project-lite", - "postgres-protocol", - "postgres-types", - "rand", - "socket2 0.5.7", - "tokio", - "tokio-util", - "whoami", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -4826,6 +4710,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.5", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -5108,18 +5003,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ulid" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34778c17965aa2a08913b57e1f34db9b4a63f5de31768b55bf20d2795f921259" -dependencies = [ - "getrandom", - "rand", - "uuid", - "web-time", -] - [[package]] name = "unicode-bidi" version = "0.3.15" @@ -5255,12 +5138,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.92" @@ -5337,16 +5214,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "webpki-roots" version = "0.25.4" @@ -5368,17 +5235,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" -[[package]] -name = "whoami" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" -dependencies = [ - "redox_syscall 0.4.1", - "wasite", - "web-sys", -] - [[package]] name = "widestring" version = "1.1.0" @@ -5649,8 +5505,3 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" - -[[patch.unused]] -name = "tsify" -version = "0.4.5" -source = "git+https://github.com/ScuffleTV/tsify.git?branch=sisou/comments#e36e55bcf3c9ac7c1d8185e5ad994885f4a2eb46" diff --git a/Cargo.toml b/Cargo.toml index a26de957..8b40b076 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,6 @@ scuffle-ffmpeg = { path = "ffmpeg" } # TODO: Remove these once the PRs are merged [patch.crates-io] # https://github.com/remkop22/postgres-from-row/pull/9 -postgres-from-row = { git = "https://github.com/ScuffleTV/postgres-from-row.git", branch = "troy/from_fn" } -# https://github.com/madonoharu/tsify/pull/32 -tsify = { git = "https://github.com/ScuffleTV/tsify.git", branch = "sisou/comments" } +# postgres-from-row = { git = "https://github.com/ScuffleTV/postgres-from-row.git", branch = "troy/from_fn" } +# # https://github.com/madonoharu/tsify/pull/32 +# tsify = { git = "https://github.com/ScuffleTV/tsify.git", branch = "sisou/comments" } diff --git a/foundations/src/telemetry/env_filter/env/builder.rs b/foundations/src/telemetry/env_filter/env/builder.rs index 46b433c8..d7db58fd 100644 --- a/foundations/src/telemetry/env_filter/env/builder.rs +++ b/foundations/src/telemetry/env_filter/env/builder.rs @@ -207,45 +207,21 @@ impl Builder { } if !disabled.is_empty() { - #[cfg(feature = "nu_ansi_term")] - use nu_ansi_term::{Color, Style}; // NOTE: We can't use a configured `MakeWriter` because the EnvFilter // has no knowledge of any underlying subscriber or subscriber, which // may or may not use a `MakeWriter`. let warn = |msg: &str| { - #[cfg(not(feature = "nu_ansi_term"))] let msg = format!("warning: {}", msg); - #[cfg(feature = "nu_ansi_term")] - let msg = { - let bold = Style::new().bold(); - let mut warning = Color::Yellow.paint("warning"); - warning.style_ref_mut().is_bold = true; - format!("{}{} {}", warning, bold.paint(":"), bold.paint(msg)) - }; eprintln!("{}", msg); }; let ctx_prefixed = |prefix: &str, msg: &str| { - #[cfg(not(feature = "nu_ansi_term"))] let msg = format!("{} {}", prefix, msg); - #[cfg(feature = "nu_ansi_term")] - let msg = { - let mut equal = Color::Fixed(21).paint("="); // dark blue - equal.style_ref_mut().is_bold = true; - format!(" {} {} {}", equal, Style::new().bold().paint(prefix), msg) - }; eprintln!("{}", msg); }; let ctx_help = |msg| ctx_prefixed("help:", msg); let ctx_note = |msg| ctx_prefixed("note:", msg); let ctx = |msg: &str| { - #[cfg(not(feature = "nu_ansi_term"))] let msg = format!("note: {}", msg); - #[cfg(feature = "nu_ansi_term")] - let msg = { - let mut pipe = Color::Fixed(21).paint("|"); - pipe.style_ref_mut().is_bold = true; - format!(" {} {}", pipe, msg) - }; eprintln!("{}", msg); }; warn("some trace filter directives would enable traces that are disabled statically"); diff --git a/image-processor/Cargo.toml b/image-processor/Cargo.toml index f2e85d22..4479c68e 100644 --- a/image-processor/Cargo.toml +++ b/image-processor/Cargo.toml @@ -11,25 +11,22 @@ tracing = "0.1" tokio = { version = "1.34", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -ulid = { version = "1.1", features = ["uuid"] } -postgres-from-row = "0.5" prost = "0.12" aws-config = "1.1" aws-sdk-s3 = { version = "1.12", features = ["behavior-version-latest"] } async-trait = "0.1" anyhow = "1.0" -async-nats = "0.34" +async-nats = "0.35" tonic = "0.11" futures = "0.3" thiserror = "1.0" -file-format = "0.24" +file-format = "0.25" scopeguard = "1.2" rgb = "0.8" imgref = "1.10" libavif-sys = { version = "0.16" } libwebp-sys2 = { version = "0.1", features = ["1_2", "demux", "mux", "static"] } sha2 = "0.10" -byteorder = "1.5" gifski = "1.13" png = "0.17" num_cpus = "1.16" @@ -42,7 +39,7 @@ http = "1" urlencoding = "2" humantime-serde = "1" -scuffle-foundations = { version = "*", path = "../foundations" } +scuffle-foundations = { version = "*", path = "../foundations", features = [] } scuffle-ffmpeg = { version = "*", path = "../ffmpeg", features = ["tracing"] } scuffle-image-processor-proto = { version = "*", path = "./proto", features = ["server", "serde"]} @@ -55,7 +52,8 @@ aws-smithy-runtime-api = "1" fred = "9.0.3" strfmt = "0.2" once_cell = "1.8" -bitvec = "1" + +serde-aux = "4" [build-dependencies] tonic-build = "0.11" diff --git a/image-processor/proto/Cargo.toml b/image-processor/proto/Cargo.toml index 8e2cb445..72c77320 100644 --- a/image-processor/proto/Cargo.toml +++ b/image-processor/proto/Cargo.toml @@ -10,7 +10,6 @@ license = "MIT OR Apache-2.0" prost = "0.12" tonic = "0.11.0" pbjson = { version = "0.6.0", optional = true } -pbjson-types = { version = "0.6.0", optional = true } serde = { version = "1.0", optional = true } [build-dependencies] @@ -23,7 +22,6 @@ server = [] client = [] serde = [ "dep:serde", - "pbjson-types", "pbjson-build", "pbjson", ] diff --git a/image-processor/proto/build.rs b/image-processor/proto/build.rs index 8143c334..c5ba555b 100644 --- a/image-processor/proto/build.rs +++ b/image-processor/proto/build.rs @@ -6,6 +6,7 @@ fn main() -> Result<(), Box> { let descriptor_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("proto_descriptor.bin"); let config = tonic_build::configure() + .compile_well_known_types(true) .build_server(cfg!(feature = "server")) .build_client(cfg!(feature = "client")); diff --git a/image-processor/proto/scuffle/image_processor/events.proto b/image-processor/proto/scuffle/image_processor/events.proto index 2721d4c2..bd717a5e 100644 --- a/image-processor/proto/scuffle/image_processor/events.proto +++ b/image-processor/proto/scuffle/image_processor/events.proto @@ -5,7 +5,10 @@ package scuffle.image_processor; import "scuffle/image_processor/types.proto"; message EventCallback { - message Success {} + message Success { + string drive = 1; + repeated OutputFile files = 2; + } message Fail { Error error = 1; diff --git a/image-processor/proto/scuffle/image_processor/service.proto b/image-processor/proto/scuffle/image_processor/service.proto index 03a532ae..5948c336 100644 --- a/image-processor/proto/scuffle/image_processor/service.proto +++ b/image-processor/proto/scuffle/image_processor/service.proto @@ -35,8 +35,12 @@ message ProcessImageRequest { message ProcessImageResponse { // A unique identifier for the task string id = 1; + + // If the task had an input upload, this will be the path to the uploaded image. + optional DrivePath upload_path = 2; + // Errors that occurred when creating the task. - optional Error error = 2; + optional Error error = 3; } // The Payload for a ImageProcessor.CancelTask request diff --git a/image-processor/proto/scuffle/image_processor/types.proto b/image-processor/proto/scuffle/image_processor/types.proto index a3592abb..c29f12b2 100644 --- a/image-processor/proto/scuffle/image_processor/types.proto +++ b/image-processor/proto/scuffle/image_processor/types.proto @@ -118,7 +118,7 @@ message InputUpload { // The input image as a binary blob. bytes binary = 1; // A prefix to use for the folder the image will be stored in. - DrivePath path = 2; + DrivePath drive_path = 2; // Content Type of the image. optional string content_type = 3; // Cache control header for the image. @@ -151,7 +151,7 @@ message Input { message Scaling { oneof base { // This is the scale for the input image (after cropping or aspect ratio adjustments are made). - uint32 fixed = 1; + uint32 fixed_base = 1; // This is used to automatically determine the scale of the input image based on the width. // We know what aspect ratio to use based on the aspect ratio adjustments made to the input image. // We can then use that to determine the (input_width / base_width) scale. @@ -184,18 +184,18 @@ message AnimationConfig { // Specify the frame duration for every frame in the output image. // This can be used to specify a constant frame rate for the output image. // frame_rate = 1000 / frame_duration_ms - uint32 duration_ms = 2; + uint32 frame_duration_ms = 2; // Frame durations for each frame in the output image. // Specify the frame duration for each frame in the output image. // If this number does not match the number of frames in the output image the processor will generate a fatal error. - IntegerList durations_ms = 3; + IntegerList frame_durations_ms = 3; // Factor to multiply the frame duration by. // This can be used to speed up or slow down the animation. // The frame duration will be multiplied by this value. // Each frame has a minimum duration of 1ms, if the factor creates some frames that are less than 1ms the processor will, // drop frames and adjust timings of others to ensure that the total duration of the animation is correctly multiplied. // This rule only applies for when the factor is greater than 1. - double factor = 4; + double frame_rate_factor = 4; } // Remove frames idx's from the input image. @@ -227,6 +227,27 @@ message OutputFormatOptions { optional string name = 3; } +message OutputFile { + // The path to the output file. + string path = 1; + // The content type of the output file. + string content_type = 2; + // The acl of the output file. + optional string acl = 3; + // Width of the output image. + uint32 width = 4; + // Height of the output image. + uint32 height = 5; + // The frame count of the output image. + uint32 frame_count = 6; + // The duration of the output image. + uint32 duration_ms = 7; + // The size of the output image in bytes. + uint32 size = 8; + // The format of the output image. + OutputFormat format = 9; +} + message Output { // The drive path to store the output image. // This is a prefix and the processor will append the suffix to this path to determine the final path. @@ -243,43 +264,47 @@ message Output { // - {ext} - The extension of the output image. (e.g. 'webp', 'avif', etc.) DrivePath drive_path = 1; + // Override the acl of the output images. + // By default this will use the ACL specified by the output drive config. + optional string acl_override = 2; + // The desired format to encode the output image. - repeated OutputFormatOptions formats = 2; + repeated OutputFormatOptions formats = 3; // Allow upscaling if the determined dimensions are larger than the input image. - bool upscale = 3; + bool upscale = 4; // Sometimes we might specify that we want 'WebpAnim' but the input image is a static image. // In this case we would typically fatally error because we can't generate an animated image from a static image. // However if this is set to true the processor will ignore these errors and skip the format. - bool skip_impossible_formats = 4; + bool skip_impossible_formats = 5; // Skips resizing if the resize operation is impossible. // For example if the resize results in a width or height of less than 1. // If this is set to true the processor will ignore these errors and skip the resize operation. - bool skip_impossible_resizes = 5; + bool skip_impossible_resizes = 6; // Disables resize chaining. // Resize chaining is when the processor will resize from the source down to the largest size requested. // Then it will form every other size by taking reducing the previous resized image. // Disabling this will resize each image from the source image. Which can be slower but more accurate. - bool disable_resize_chaining = 6; + bool disable_resize_chaining = 7; // Disables 2 pass decoding. // 2 pass decoding allows for the processor to further optimize the image by deduplicating frames. - bool disable_two_pass_decoding = 7; + bool disable_two_pass_decoding = 8; // The resize method used to resize the image. - ResizeMethod resize_method = 8; + ResizeMethod resize_method = 9; // The resize algorithm used to resize the image. - ResizeAlgorithm resize_algorithm = 9; + ResizeAlgorithm resize_algorithm = 10; // The animation configuration for the output image. - optional AnimationConfig animation_config = 10; + optional AnimationConfig animation_config = 11; // A crop is applied to the image before resizing and before an aspect ratio change. - optional Crop crop = 11; + optional Crop crop = 12; // Confine the aspect ratio of the image to a specific range. // For example if you want to allow all images that are 3:1 to 1:3 you would set min_ratio to 1/3 and max_ratio to 3. @@ -290,20 +315,20 @@ message Output { // The minimum ratio of the image. // An aspect ratio is the ratio of the width to the height of the image. - optional double min_aspect_ratio = 12; + optional double min_aspect_ratio = 13; // The maximum ratio of the image. // An aspect ratio is the ratio of the width to the height of the image. - optional double max_aspect_ratio = 13; + optional double max_aspect_ratio = 14; // There must be at least one element in the list. oneof resize { // Resize to a specific width, the height will be determined by the aspect ratio. - IntegerList width = 14; + IntegerList widths = 15; // Resize to a specific height, the width will be determined by the aspect ratio. - IntegerList height = 15; + IntegerList heights = 16; // A scaling config to determine how each dimension should be scaled. - Scaling scaling = 16; + Scaling scaling = 17; } } diff --git a/image-processor/src/config.rs b/image-processor/src/config.rs index a17173dd..d177d1c8 100644 --- a/image-processor/src/config.rs +++ b/image-processor/src/config.rs @@ -60,6 +60,7 @@ pub struct HttpConfig { #[serde(default)] pub struct WorkerConfig { /// Enable the worker server + #[settings(default = true)] pub enabled: bool, /// The number of workers to start /// Default is 0, which means the number of workers is equal to the number @@ -153,6 +154,9 @@ pub struct S3DriveConfig { /// The maximum number of concurrent connections #[serde(default)] pub max_connections: Option, + /// Default ACL for files + #[serde(default)] + pub acl: Option, } fn default_region() -> String { @@ -169,6 +173,9 @@ pub struct MemoryDriveConfig { /// The drive mode #[serde(default)] pub mode: DriveMode, + /// Default ACL for files + #[serde(default)] + pub acl: Option, } #[auto_settings(impl_default = false)] @@ -193,6 +200,9 @@ pub struct HttpDriveConfig { /// Additional headers for the HTTP drive #[serde(default)] pub headers: HashMap, + /// Default ACL for files + #[serde(default)] + pub acl: Option, } fn default_timeout() -> Option { diff --git a/image-processor/src/database.rs b/image-processor/src/database.rs index 285de759..154cb801 100644 --- a/image-processor/src/database.rs +++ b/image-processor/src/database.rs @@ -6,20 +6,43 @@ use mongodb::bson::oid::ObjectId; use mongodb::options::IndexOptions; use mongodb::{Database, IndexModel}; use scuffle_image_processor_proto::Task; -use serde::{Deserialize, Serializer}; use crate::global::Global; -use crate::worker::JobError; -fn serialize_protobuf(value: &T, serializer: S) -> Result { - serializer.serialize_bytes(&value.encode_to_vec()) +mod protobuf { + use serde::{Deserialize, Serializer}; + + pub fn serialize(value: &T, serializer: S) -> Result { + serializer.serialize_bytes(&value.encode_to_vec()) + } + + pub fn deserialize<'de, T: prost::Message + Default, D: serde::Deserializer<'de>>( + deserializer: D, + ) -> Result { + let binary = bson::Binary::deserialize(deserializer)?; + T::decode(binary.bytes.as_slice()).map_err(serde::de::Error::custom) + } } -fn deserialize_protobuf<'de, T: prost::Message + Default, D: serde::Deserializer<'de>>( - deserializer: D, -) -> Result { - let bytes = Vec::::deserialize(deserializer)?; - T::decode(bytes.as_slice()).map_err(serde::de::Error::custom) +mod datetime { + use serde::{Deserialize, Serialize, Serializer}; + + pub fn deserialize<'de, D: serde::Deserializer<'de>>( + deserializer: D, + ) -> Result>, D::Error> { + let bson_datetime = Option::::deserialize(deserializer)?; + Ok(bson_datetime.map(|dt| dt.into())) + } + + pub fn serialize( + value: &Option>, + serializer: S, + ) -> Result { + match value { + Some(value) => bson::DateTime::from(value.clone()).serialize(serializer), + None => None::.serialize(serializer), + } + } } #[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)] @@ -30,11 +53,13 @@ pub struct Job { /// The priority of the job, higher priority jobs are fetched first pub priority: u32, /// The lease time of the job on a worker. + #[serde(with = "datetime")] pub hold_until: Option>, - #[serde(serialize_with = "serialize_protobuf", deserialize_with = "deserialize_protobuf")] + #[serde(with = "protobuf")] /// The task to be performed pub task: Task, /// The ttl of the job, after which it will be deleted + #[serde(with = "datetime")] pub expires_at: Option>, /// The id of the worker that claimed the job pub claimed_by_id: Option, @@ -87,12 +112,13 @@ impl Job { /// The job that was created pub async fn new( global: &Arc, + id: ObjectId, task: Task, priority: u32, ttl: Option, ) -> Result { let job = Job { - id: ObjectId::new(), + id, priority, hold_until: None, task, @@ -178,7 +204,7 @@ impl Job { /// Whether the job was successfully completed or not, if the job was /// reclaimed by a different worker, it will not be completed and this will /// return false - pub async fn complete(&self, global: &Arc, error: Option) -> Result<(), mongodb::error::Error> { + pub async fn complete(&self, global: &Arc) -> Result { let success = Self::collection(global.database()) .delete_one( bson::doc! { @@ -189,15 +215,7 @@ impl Job { ) .await?; - if success.deleted_count == 1 { - if let Some(error) = error { - crate::events::on_failure(global, self, error).await; - } else { - crate::events::on_success(global, self).await; - } - } - - Ok(()) + Ok(success.deleted_count == 1) } /// Cancels a job diff --git a/image-processor/src/drive/http.rs b/image-processor/src/drive/http.rs index 464f0ecd..44761a13 100644 --- a/image-processor/src/drive/http.rs +++ b/image-processor/src/drive/http.rs @@ -13,6 +13,7 @@ pub struct HttpDrive { mode: DriveMode, semaphore: Option, client: reqwest::Client, + acl: Option, } #[derive(Debug, thiserror::Error)] @@ -60,6 +61,7 @@ impl HttpDrive { builder.build().map_err(HttpDriveError::Reqwest)? }, + acl: config.acl.clone(), }) } } @@ -130,7 +132,7 @@ impl Drive for HttpDrive { ); } - if let Some(acl) = &options.acl { + if let Some(acl) = options.acl.as_ref().or(self.acl.as_ref()) { request.headers_mut().insert( reqwest::header::HeaderName::from_static("x-amz-acl"), acl.parse().map_err(HttpDriveError::InvalidHeaderValue)?, @@ -167,4 +169,8 @@ impl Drive for HttpDrive { Ok(()) } + + fn default_acl(&self) -> Option<&str> { + self.acl.as_deref() + } } diff --git a/image-processor/src/drive/memory.rs b/image-processor/src/drive/memory.rs index 0428b69e..8484a703 100644 --- a/image-processor/src/drive/memory.rs +++ b/image-processor/src/drive/memory.rs @@ -44,6 +44,7 @@ pub struct MemoryDrive { name: String, mode: DriveMode, files: RwLock, + acl: Option, } #[derive(Debug, Clone)] @@ -65,6 +66,7 @@ impl MemoryDrive { Ok(Self { name: config.name.clone(), mode: config.mode, + acl: config.acl.clone(), files: RwLock::new(FileHolder { remaining_capacity: config.capacity.unwrap_or(usize::MAX), files: HashMap::new(), @@ -104,13 +106,11 @@ impl Drive for MemoryDrive { let mut files = self.files.write().await; - files.insert( - path.to_owned(), - MemoryFile { - data, - _options: options.unwrap_or_default(), - }, - )?; + let mut options = options.unwrap_or_default(); + + options.acl = options.acl.or_else(|| self.acl.clone()); + + files.insert(path.to_owned(), MemoryFile { data, _options: options })?; Ok(()) } @@ -126,4 +126,8 @@ impl Drive for MemoryDrive { self.files.write().await.remove(path).ok_or(DriveError::NotFound)?; Ok(()) } + + fn default_acl(&self) -> Option<&str> { + self.acl.as_deref() + } } diff --git a/image-processor/src/drive/mod.rs b/image-processor/src/drive/mod.rs index 3e708982..d71a45b6 100644 --- a/image-processor/src/drive/mod.rs +++ b/image-processor/src/drive/mod.rs @@ -62,6 +62,10 @@ pub trait Drive { fn healthy(&self) -> impl std::future::Future + Send { async { true } } + + fn default_acl(&self) -> Option<&str> { + None + } } #[derive(Debug)] diff --git a/image-processor/src/drive/s3.rs b/image-processor/src/drive/s3.rs index 810fb6eb..254b3ee8 100644 --- a/image-processor/src/drive/s3.rs +++ b/image-processor/src/drive/s3.rs @@ -19,20 +19,21 @@ pub struct S3Drive { bucket: String, path_prefix: Option, semaphore: Option, + acl: Option, } #[derive(Debug, thiserror::Error)] pub enum S3DriveError { #[error("s3: {0}")] - S3Error(#[from] aws_sdk_s3::Error), + S3(#[from] aws_sdk_s3::Error), #[error("byte stream: {0}")] - ByteStreamError(#[from] aws_smithy_types::byte_stream::error::Error), + ByteStream(#[from] aws_smithy_types::byte_stream::error::Error), #[error("read: {0}")] - ReadError(#[from] SdkError), + Read(#[from] SdkError), #[error("write: {0}")] - WriteError(#[from] SdkError), + Write(#[from] SdkError), #[error("delete: {0}")] - DeleteError(#[from] SdkError), + Delete(#[from] SdkError), } impl S3Drive { @@ -40,7 +41,7 @@ impl S3Drive { pub async fn new(config: &S3DriveConfig) -> Result { tracing::debug!("setting up s3 disk"); Ok(Self { - name: config.bucket.clone(), + name: config.name.clone(), mode: config.mode, client: aws_sdk_s3::Client::from_conf({ let mut builder = aws_sdk_s3::Config::builder(); @@ -66,6 +67,7 @@ impl S3Drive { path_prefix: config.prefix_path.clone(), bucket: config.bucket.clone(), semaphore: config.max_connections.map(tokio::sync::Semaphore::new), + acl: config.acl.clone(), }) } } @@ -130,22 +132,24 @@ impl Drive for S3Drive { .key(path.trim_start_matches('/')) .body(data.into()); - if let Some(options) = options { - if let Some(cache_control) = &options.cache_control { - req = req.cache_control(cache_control); - } - if let Some(content_type) = &options.content_type { - req = req.content_type(content_type); - } - if let Some(content_disposition) = &options.content_disposition { - req = req.content_disposition(content_disposition); - } - if let Some(acl) = &options.acl { - req = req.acl(acl.as_str().into()); - } + let options = options.unwrap_or_default(); + + if let Some(cache_control) = &options.cache_control { + req = req.cache_control(cache_control); + } + if let Some(content_type) = &options.content_type { + req = req.content_type(content_type); + } + if let Some(content_disposition) = &options.content_disposition { + req = req.content_disposition(content_disposition); + } + if let Some(acl) = options.acl.as_ref().or(self.acl.as_ref()) { + req = req.acl(acl.as_str().into()); } - req.send().await.map_err(S3DriveError::from)?; + req.send().await.map_err(S3DriveError::from).inspect_err(|err| { + tracing::error!("failed to write to s3: {:?}", err); + })?; Ok(()) } @@ -177,4 +181,8 @@ impl Drive for S3Drive { Ok(()) } + + fn default_acl(&self) -> Option<&str> { + self.acl.as_deref() + } } diff --git a/image-processor/src/events.rs b/image-processor/src/events.rs index 4207042e..15afcb09 100644 --- a/image-processor/src/events.rs +++ b/image-processor/src/events.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use scuffle_image_processor_proto::{event_callback, EventCallback, EventQueue as EventTopic}; +use scuffle_image_processor_proto::{event_callback, EventCallback, EventQueue as EventTopic, OutputFile}; use crate::database::Job; use crate::event_queue::EventQueue; @@ -29,19 +29,19 @@ pub async fn on_event(global: &Arc, job: &Job, event_topic: &EventTopic, } } -fn start_event(job: &Job) -> event_callback::Event { +fn start_event(_: &Job) -> event_callback::Event { event_callback::Event::Start(event_callback::Start {}) } -fn success_event(job: &Job) -> event_callback::Event { - event_callback::Event::Success(event_callback::Success {}) +fn success_event(_: &Job, drive: String, files: Vec) -> event_callback::Event { + event_callback::Event::Success(event_callback::Success { drive, files }) } -fn fail_event(job: &Job, err: JobError) -> event_callback::Event { +fn fail_event(_: &Job, err: JobError) -> event_callback::Event { event_callback::Event::Fail(event_callback::Fail { error: Some(err.into()) }) } -fn cancel_event(job: &Job) -> event_callback::Event { +fn cancel_event(_: &Job) -> event_callback::Event { event_callback::Event::Cancel(event_callback::Cancel {}) } @@ -51,9 +51,9 @@ pub async fn on_start(global: &Arc, job: &Job) { } } -pub async fn on_success(global: &Arc, job: &Job) { +pub async fn on_success(global: &Arc, job: &Job, drive: String, files: Vec) { if let Some(on_success) = &job.task.events.as_ref().and_then(|events| events.on_success.as_ref()) { - on_event(global, job, on_success, success_event(job)).await; + on_event(global, job, on_success, success_event(job, drive, files)).await; } } diff --git a/image-processor/src/main.rs b/image-processor/src/main.rs index 27570169..298752a0 100644 --- a/image-processor/src/main.rs +++ b/image-processor/src/main.rs @@ -51,10 +51,12 @@ async fn main(cfg: Matches) { let mut handles = Vec::new(); if global.config().management.grpc.enabled || global.config().management.http.enabled { + tracing::info!("starting management"); handles.push(runtime::spawn(management::start(global.clone()))); } if global.config().worker.enabled { + tracing::info!("starting worker"); handles.push(runtime::spawn(worker::start(global.clone()))); } diff --git a/image-processor/src/management/grpc.rs b/image-processor/src/management/grpc.rs index 7a83ed83..6e599994 100644 --- a/image-processor/src/management/grpc.rs +++ b/image-processor/src/management/grpc.rs @@ -22,6 +22,7 @@ impl scuffle_image_processor_proto::image_processor_server::ImageProcessor for M Ok(resp) => resp, Err(err) => ProcessImageResponse { id: "".to_owned(), + upload_path: None, error: Some(err), }, }; diff --git a/image-processor/src/management/http.rs b/image-processor/src/management/http.rs index 0a245baf..18b5bf25 100644 --- a/image-processor/src/management/http.rs +++ b/image-processor/src/management/http.rs @@ -12,6 +12,7 @@ impl ManagementServer { let router = Router::new() .route("/process_image", post(process_image)) .route("/cancel_task", post(cancel_task)) + .fallback(not_found) .with_state(self.clone()); let addr = self.global.config().management.http.bind; @@ -23,6 +24,10 @@ impl ManagementServer { } } +async fn not_found() -> (http::StatusCode, &'static str) { + (http::StatusCode::NOT_FOUND, "Not Found") +} + async fn process_image( State(server): State, Json(request): Json, @@ -31,6 +36,7 @@ async fn process_image( Ok(resp) => resp, Err(err) => ProcessImageResponse { id: "".to_owned(), + upload_path: None, error: Some(err), }, }; diff --git a/image-processor/src/management/mod.rs b/image-processor/src/management/mod.rs index 42d6b333..b4fdf6fb 100644 --- a/image-processor/src/management/mod.rs +++ b/image-processor/src/management/mod.rs @@ -1,15 +1,18 @@ use std::sync::Arc; use anyhow::Context; +use bson::oid::ObjectId; use bytes::Bytes; use scuffle_image_processor_proto::{ - CancelTaskRequest, CancelTaskResponse, Error, ErrorCode, ProcessImageRequest, ProcessImageResponse, + input, CancelTaskRequest, CancelTaskResponse, DrivePath, Error, ErrorCode, Input, ProcessImageRequest, + ProcessImageResponse, }; use crate::database::Job; use crate::drive::{Drive, DriveWriteOptions}; use crate::global::Global; use crate::management::validation::{validate_input_upload, validate_task, FragmentBuf}; +use crate::worker::process::DecoderFrontend; pub mod grpc; pub mod http; @@ -22,23 +25,63 @@ struct ManagementServer { } impl ManagementServer { - async fn process_image(&self, request: ProcessImageRequest) -> Result { + async fn process_image(&self, mut request: ProcessImageRequest) -> Result { let mut fragment = FragmentBuf::new(); - validate_task(&self.global, fragment.push("task"), request.task.as_ref())?; + validate_task( + &self.global, + fragment.push("task"), + request.task.as_ref(), + request.input_upload.as_ref().and_then(|upload| upload.drive_path.as_ref()), + )?; // We need to do validation here. if let Some(input_upload) = request.input_upload.as_ref() { validate_input_upload(&self.global, fragment.push("input_upload"), Some(input_upload))?; } - if let Some(input_upload) = request.input_upload { - let drive_path = input_upload.path.unwrap(); + let id = ObjectId::new(); + + let upload_path = if let Some(input_upload) = request.input_upload { + let drive_path = input_upload.drive_path.unwrap(); let drive = self.global.drive(&drive_path.drive).unwrap(); + let file_format = file_format::FileFormat::from_bytes(&input_upload.binary); + + DecoderFrontend::from_format(file_format).map_err(|err| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("input_upload.binary: {err}"), + })?; + + let vars = [ + ("id".to_owned(), id.to_string()), + ("ext".to_owned(), file_format.extension().to_owned()), + ] + .into_iter() + .collect(); + + let path = strfmt::strfmt(&drive_path.path, &vars).map_err(|err| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("input_upload.drive_path.path: {err}"), + })?; + + let drive_path = DrivePath { + drive: drive_path.drive, + path: path.clone(), + }; + + if let Some(input) = request.task.as_mut().unwrap().input.as_mut() { + input.path = Some(input::Path::DrivePath(drive_path.clone())); + } else { + request.task.as_mut().unwrap().input = Some(Input { + path: Some(input::Path::DrivePath(drive_path.clone())), + ..Default::default() + }); + } + drive .write( - &drive_path.path, + &path, Bytes::from(input_upload.binary), Some(DriveWriteOptions { acl: input_upload.acl, @@ -55,9 +98,13 @@ impl ManagementServer { message: format!("failed to write input upload: {err}"), } })?; - } - let job = Job::new(&self.global, request.task.unwrap(), request.priority, request.ttl) + Some(drive_path) + } else { + None + }; + + let job = Job::new(&self.global, id, request.task.unwrap(), request.priority, request.ttl) .await .map_err(|err| { tracing::error!("failed to create job: {:#}", err); @@ -69,6 +116,7 @@ impl ManagementServer { Ok(ProcessImageResponse { id: job.id.to_string(), + upload_path, error: None, }) } diff --git a/image-processor/src/management/validation.rs b/image-processor/src/management/validation.rs index d4d8dd3b..5381d235 100644 --- a/image-processor/src/management/validation.rs +++ b/image-processor/src/management/validation.rs @@ -2,8 +2,8 @@ use std::collections::HashSet; use std::sync::Arc; use scuffle_image_processor_proto::{ - animation_config, input, output, AnimationConfig, Crop, DrivePath, Error, ErrorCode, EventQueue, Events, Input, - InputMetadata, InputUpload, Limits, Output, OutputFormat, OutputFormatOptions, Task, + animation_config, input, output, scaling, AnimationConfig, Crop, DrivePath, Error, ErrorCode, EventQueue, Events, Input, + InputMetadata, InputUpload, Limits, Output, OutputFormat, OutputFormatOptions, Scaling, Task, }; use url::Url; @@ -73,7 +73,7 @@ impl std::fmt::Display for Fragment<'_> { for item in self.path.iter() { match item { FragmentItem::Map(value) => { - if first { + if !first { write!(f, ".")?; } write!(f, "{value}")?; @@ -95,6 +95,15 @@ impl Fragment<'_> { self.path.push(path.into()); Fragment::new(self.path) } + + pub fn replace(self, path: impl Into) -> Self { + if self.path.is_empty() { + return self; + } + + *self.path.last_mut().unwrap() = path.into(); + self + } } impl Drop for Fragment<'_> { @@ -113,7 +122,12 @@ pub fn validate_input_upload( message: format!("{fragment}: is required"), })?; - validate_drive_path(global, fragment.push("path"), input_upload.path.as_ref(), &["id"])?; + validate_drive_path( + global, + fragment.push("drive_path"), + input_upload.drive_path.as_ref(), + &["id", "ext"], + )?; if input_upload.binary.is_empty() { return Err(Error { @@ -125,17 +139,24 @@ pub fn validate_input_upload( Ok(()) } -pub fn validate_task(global: &Arc, mut fragment: Fragment, task: Option<&Task>) -> Result<(), Error> { +pub fn validate_task( + global: &Arc, + mut fragment: Fragment, + task: Option<&Task>, + has_image_upload: Option<&DrivePath>, +) -> Result<(), Error> { let task = task.ok_or_else(|| Error { code: ErrorCode::InvalidInput as i32, message: format!("{fragment}: is required"), })?; - validate_input(global, fragment.push("input"), task.input.as_ref())?; + validate_input(global, fragment.push("input"), task.input.as_ref(), has_image_upload)?; validate_output(global, fragment.push("output"), task.output.as_ref())?; - validate_events(global, fragment.push("events"), task.events.as_ref())?; + if let Some(events) = &task.events { + validate_events(global, fragment.push("events"), Some(events))?; + } if let Some(limits) = &task.limits { validate_limits(fragment.push("limits"), Some(limits))?; @@ -333,19 +354,17 @@ pub fn validate_output_animation_config( } if let Some(frame_rate) = &animation_config.frame_rate { - let mut fragment = fragment.push("frame_rate"); - match frame_rate { - animation_config::FrameRate::DurationMs(duration_ms) => { + animation_config::FrameRate::FrameDurationMs(duration_ms) => { if *duration_ms == 0 { return Err(Error { code: ErrorCode::InvalidInput as i32, - message: format!("{}: duration_ms must be non 0", fragment), + message: format!("{}: duration_ms must be non 0", fragment.push("frame_duration_ms")), }); } } - animation_config::FrameRate::DurationsMs(durations_ms) => { - let mut fragment = fragment.push("durations_ms.values"); + animation_config::FrameRate::FrameDurationsMs(durations_ms) => { + let mut fragment = fragment.push("frame_durations_ms.values"); if durations_ms.values.is_empty() { return Err(Error { @@ -363,26 +382,21 @@ pub fn validate_output_animation_config( } } } - animation_config::FrameRate::Factor(factor) => { - if *factor > 0.0 { + animation_config::FrameRate::FrameRateFactor(factor) => { + if *factor <= 0.0 { return Err(Error { code: ErrorCode::InvalidInput as i32, - message: format!("{}: factor must be greater than 0", fragment.push("factor")), + message: format!("{}: factor must be greater than 0", fragment.push("frame_rate_factor")), }); } } } - } else { - return Err(Error { - code: ErrorCode::InvalidInput as i32, - message: format!("{}: frame_rate is required", fragment.push("frame_rate")), - }); } Ok(()) } -pub fn validate_output_variants_resize(mut fragment: Fragment, resize: Option<&output::Resize>) -> Result<(), Error> { +pub fn validate_output_variants_resize(fragment: Fragment, resize: Option<&output::Resize>) -> Result<(), Error> { let resize = resize.ok_or_else(|| Error { code: ErrorCode::InvalidInput as i32, message: format!("{fragment}: is required"), @@ -409,20 +423,74 @@ pub fn validate_output_variants_resize(mut fragment: Fragment, resize: Option<&o }; match resize { - output::Resize::Height(height) => { - validate_items(fragment.push("height.values"), &height.values)?; + output::Resize::Heights(height) => { + validate_items(fragment.replace("height.values"), &height.values)?; } - output::Resize::Width(width) => { - validate_items(fragment.push("width.values"), &width.values)?; + output::Resize::Widths(width) => { + validate_items(fragment.replace("width.values"), &width.values)?; } output::Resize::Scaling(scaling) => { - validate_items(fragment.push("scaling.scales"), &scaling.scales)?; + validate_scaling(fragment.replace("scaling"), Some(scaling))?; } } Ok(()) } +pub fn validate_scaling(mut fragment: Fragment, scaling: Option<&Scaling>) -> Result<(), Error> { + let scaling = scaling.ok_or_else(|| Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: is required"), + })?; + + if scaling.scales.is_empty() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: is required", fragment.push("scales")), + }); + } + + for (idx, scale) in scaling.scales.iter().enumerate() { + if *scale == 0 { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: must be non 0", fragment.push(idx)), + }); + } + } + + let Some(base) = scaling.base.as_ref() else { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: is required", fragment.push("base")), + }); + }; + + match base { + scaling::Base::BaseWidth(width) if *width == 0 => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: must be non 0", fragment.push("base_width")), + }); + } + scaling::Base::BaseHeight(height) if *height == 0 => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: must be non 0", fragment.push("base_height")), + }); + } + scaling::Base::FixedBase(base) if *base == 0 => { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{}: base must be non 0", fragment.push("fixed")), + }); + } + _ => {} + } + + Ok(()) +} + pub fn validate_output_format_options( mut fragment: Fragment, format: Option<&OutputFormatOptions>, @@ -443,13 +511,22 @@ pub fn validate_output_format_options( Ok(()) } -pub fn validate_input(global: &Arc, mut fragment: Fragment, input: Option<&Input>) -> Result<(), Error> { +pub fn validate_input( + global: &Arc, + mut fragment: Fragment, + input: Option<&Input>, + has_image_upload: Option<&DrivePath>, +) -> Result<(), Error> { + if input.is_none() && has_image_upload.is_some() { + return Ok(()); + } + let input = input.ok_or_else(|| Error { code: ErrorCode::InvalidInput as i32, message: format!("{fragment}: is required"), })?; - validate_input_path(global, fragment.push("path"), input.path.as_ref())?; + validate_input_path(global, fragment.push("path"), input.path.as_ref(), has_image_upload)?; // Metadata is optional if let Some(metadata) = &input.metadata { @@ -512,20 +589,28 @@ pub fn validate_input_metadata(mut fragment: Fragment, metadata: Option<&InputMe pub fn validate_input_path( global: &Arc, - mut fragment: Fragment, + fragment: Fragment, input_path: Option<&input::Path>, + has_image_upload: Option<&DrivePath>, ) -> Result<(), Error> { + if input_path.is_some() && has_image_upload.is_some() { + return Err(Error { + code: ErrorCode::InvalidInput as i32, + message: format!("{fragment}: cannot have both path and image_upload"), + }); + } + let input_path = input_path.ok_or_else(|| Error { code: ErrorCode::InvalidInput as i32, - message: format!("{} is required", fragment), + message: format!("{fragment} is required"), })?; match input_path { input::Path::DrivePath(drive_path) => { - validate_drive_path(global, fragment.push("drive_path"), Some(drive_path), &["id"])?; + validate_drive_path(global, fragment.replace("drive_path"), Some(drive_path), &["id"])?; } input::Path::PublicUrl(url) => { - validate_public_url(global, fragment.push("public_url"), url)?; + validate_public_url(global, fragment.replace("public_url"), url)?; } } diff --git a/image-processor/src/tests/global.rs b/image-processor/src/tests/global.rs deleted file mode 100644 index 4f5920c4..00000000 --- a/image-processor/src/tests/global.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::sync::Arc; - -use scuffle_utils::context::Context; - -use crate::config::ImageProcessorConfig; - -pub struct GlobalState { - ctx: Context, - config: ImageProcessorConfig, - nats: async_nats::Client, - jetstream: async_nats::jetstream::Context, - db: Arc, - s3_source_bucket: binary_helper::s3::Bucket, - s3_target_bucket: binary_helper::s3::Bucket, - http_client: reqwest::Client, - instance_id: ulid::Ulid, -} - -impl binary_helper::global::GlobalCtx for GlobalState { - fn ctx(&self) -> &Context { - &self.ctx - } -} - -impl binary_helper::global::GlobalConfigProvider for GlobalState { - fn provide_config(&self) -> &ImageProcessorConfig { - &self.config - } -} - -impl binary_helper::global::GlobalNats for GlobalState { - fn nats(&self) -> &async_nats::Client { - &self.nats - } - - fn jetstream(&self) -> &async_nats::jetstream::Context { - &self.jetstream - } -} - -impl binary_helper::global::GlobalDb for GlobalState { - fn db(&self) -> &Arc { - &self.db - } -} - -impl binary_helper::global::GlobalConfig for GlobalState {} - -impl crate::global::ImageProcessorState for GlobalState { - fn s3_source_bucket(&self) -> &binary_helper::s3::Bucket { - &self.s3_source_bucket - } - - fn s3_target_bucket(&self) -> &binary_helper::s3::Bucket { - &self.s3_target_bucket - } - - fn http_client(&self) -> &reqwest::Client { - &self.http_client - } - - fn instance_id(&self) -> ulid::Ulid { - self.instance_id - } -} - -// pub async fn mock_global_state(config: ImageProcessorConfig) -> -// (Arc, Handler) { let (ctx, handler) = Context::new(); - -// dotenvy::dotenv().ok(); - -// let logging_level = std::env::var("LOGGING_LEVEL").unwrap_or_else(|_| -// "info".to_string()); - -// logging::init(&logging_level, Default::default()).expect("failed to -// initialize logging"); - -// let database_uri = -// std::env::var("PLATFORM_DATABASE_URL_TEST").expect(" -// PLATFORM_DATABASE_URL_TEST must be set"); let nats_addr = -// std::env::var("NATS_ADDR").expect("NATS_URL must be set"); - -// let nats = async_nats::connect(&nats_addr).await.expect("failed to connect to -// nats"); let jetstream = async_nats::jetstream::new(nats.clone()); - -// let db = Arc::new( -// scuffle_utils::database::Pool::connect(&database_uri) -// .await -// .expect("failed to connect to database"), -// ); - -// let global = Arc::new(GlobalState { -// s3_source_bucket: config.source_bucket.setup().await.expect("failed to setup -// source bucket"), s3_target_bucket: -// config.target_bucket.setup().await.expect("failed to setup target bucket"), -// config, -// ctx, -// nats, -// jetstream, -// db, -// }); - -// (global, handler) -// } diff --git a/image-processor/src/tests/mod.rs b/image-processor/src/tests/mod.rs deleted file mode 100644 index 05ba721a..00000000 --- a/image-processor/src/tests/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod global; -mod processor; -mod utils; diff --git a/image-processor/src/tests/processor/decoder.rs b/image-processor/src/tests/processor/decoder.rs deleted file mode 100644 index b0f13ba6..00000000 --- a/image-processor/src/tests/processor/decoder.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::borrow::Cow; - -use imgref::ImgVec; - -use crate::processor::job::decoder::{Decoder, DecoderBackend, DecoderInfo, LoopCount}; -use crate::processor::job::frame::Frame; -use crate::tests::utils::asset_bytes; - -fn decode(asset_name: &str, backend: DecoderBackend, expected_info: DecoderInfo, expected_frames: Vec) { - let asset_bytes = asset_bytes(asset_name); - - let start = std::time::Instant::now(); - - let mut decoder = backend - .build(&Default::default(), Cow::Owned(asset_bytes)) - .expect("decoder build error"); - - let info = decoder.info(); - - assert_eq!(info.frame_count, expected_info.frame_count, "frame count mismatch"); - assert_eq!(info.width, expected_info.width, "width mismatch"); - assert_eq!(info.height, expected_info.height, "height mismatch"); - assert_eq!(info.loop_count, expected_info.loop_count, "loop count mismatch"); - assert_eq!(info.timescale, expected_info.timescale, "timescale mismatch"); - - let mut idx = 0; - while let Some(frame) = decoder.decode().expect("frame decode error") { - let expected = expected_frames.get(idx).expect("frame count mismatch"); - assert_eq!(frame.duration_ts, expected.duration_ts, "frame duration_ts mismatch: {idx}",); - assert_eq!(frame.image.height(), expected.image.height(), "frame height mismatch: {idx}",); - assert_eq!(frame.image.width(), expected.image.width(), "frame width mismatch: {idx}",); - idx += 1; - } - - assert_eq!(idx, expected_frames.len(), "frame count mismatch"); - - println!("decode time ({asset_name}): {:?}", start.elapsed()); -} - -#[test] -fn decode_ffmpeg_gif_test() { - let expected_info = DecoderInfo { - timescale: 100, - frame_count: 93, - loop_count: LoopCount::Infinite, - height: 128, - width: 128, - }; - - let expected_frames = (0..93) - .map(|_| Frame { - duration_ts: 4, - image: ImgVec::new(vec![], 128, 128), - }) - .collect(); - - decode("meow.gif", DecoderBackend::Ffmpeg, expected_info, expected_frames); -} - -#[test] -fn decode_ffmpeg_png_test() { - let expected_info = DecoderInfo { - timescale: 25, - frame_count: 1, - loop_count: LoopCount::Infinite, - height: 400, - width: 400, - }; - - let expected_frames = (0..1) - .map(|_| Frame { - duration_ts: 0, - image: ImgVec::new(vec![], 400, 400), - }) - .collect(); - - decode("frog.png", DecoderBackend::Ffmpeg, expected_info, expected_frames); -} - -#[test] -fn decode_libwebp_webp_test() { - let expected_info = DecoderInfo { - timescale: 1000, - height: 128, - width: 128, - frame_count: 93, - loop_count: LoopCount::Infinite, - }; - - let expected_frames = (0..93) - .map(|_| Frame { - duration_ts: 40, - image: ImgVec::new(vec![], 128, 128), - }) - .collect(); - - decode("meow.webp", DecoderBackend::LibWebp, expected_info, expected_frames); -} - -#[test] -fn decode_libavif_avif_test() { - let expected_info = DecoderInfo { - height: 128, - width: 128, - frame_count: 93, - loop_count: LoopCount::Infinite, - timescale: 100, - }; - - let expected_frames = (0..93) - .map(|_| Frame { - image: ImgVec::new(vec![], 128, 128), - duration_ts: 4, - }) - .collect(); - - decode("meow.avif", DecoderBackend::LibAvif, expected_info, expected_frames); -} diff --git a/image-processor/src/tests/processor/encoder.rs b/image-processor/src/tests/processor/encoder.rs deleted file mode 100644 index 989d88a0..00000000 --- a/image-processor/src/tests/processor/encoder.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::borrow::Cow; -use std::collections::HashMap; - -use rgb::ComponentBytes; -use sha2::Digest; - -use crate::processor::job::decoder::{Decoder, DecoderBackend}; -use crate::processor::job::encoder::{Encoder, EncoderFrontend, EncoderSettings}; -use crate::processor::job::resize::{ImageResizer, ImageResizerTarget}; -use crate::tests::utils::asset_bytes; - -fn encode(asset_name: &str, backend: DecoderBackend, frontend: EncoderFrontend) { - let input_bytes = asset_bytes(asset_name); - - let start = std::time::Instant::now(); - - let mut decoder = backend - .build(&Default::default(), Cow::Owned(input_bytes)) - .expect("failed to build decoder"); - - let info = decoder.info(); - - let mut resizer = ImageResizer::new(ImageResizerTarget { - height: 30, - width: 30, - ..Default::default() - }); - - let mut frames = Vec::with_capacity(info.frame_count); - let mut frame_hashes = HashMap::new(); - let mut frame_order = Vec::with_capacity(info.frame_count); - let mut count = 0; - - while let Some(frame) = decoder.decode().expect("failed to decode") { - let hash = sha2::Sha256::digest(frame.image.buf().as_bytes()); - if let Some(idx) = frame_hashes.get(&hash) { - if let Some((last_idx, last_duration)) = frame_order.last_mut() { - if last_idx == idx { - *last_duration += frame.duration_ts; - } else { - frame_order.push((*idx, frame.duration_ts)); - } - } else { - frame_order.push((*idx, frame.duration_ts)); - } - } else { - frame_hashes.insert(hash, count); - frame_order.push((count, frame.duration_ts)); - - count += 1; - frames.push(resizer.resize(&frame).expect("failed to resize")); - } - } - - let mut encoder = frontend - .build(EncoderSettings { - fast: true, - loop_count: info.loop_count, - timescale: info.timescale, - static_image: false, - }) - .expect("failed to build encoder"); - - for (idx, timing) in frame_order.into_iter() { - let resized = &mut frames[idx]; - resized.duration_ts = timing; - encoder.add_frame(resized).expect("failed to add frame"); - } - - let info = encoder.info(); - dbg!(&info); - let output = encoder.finish().expect("failed to finish"); - let output_path = format!( - "/tmp/{}x{}.{}", - info.width, - info.height, - match info.frontend { - EncoderFrontend::Gifski => "gif", - EncoderFrontend::LibAvif => "avif", - EncoderFrontend::LibWebp => "webp", - EncoderFrontend::Png => "png", - } - ); - std::fs::write(&output_path, output).expect("failed to write output"); - println!("wrote output to {}", output_path); - - println!("encode time ({asset_name}): {:?}", start.elapsed()); -} - -#[test] -fn encode_test() { - encode("cat.gif", DecoderBackend::Ffmpeg, EncoderFrontend::LibWebp); - encode("meow.webp", DecoderBackend::LibWebp, EncoderFrontend::LibAvif); - encode("meow.avif", DecoderBackend::LibAvif, EncoderFrontend::Gifski); -} diff --git a/image-processor/src/tests/processor/mod.rs b/image-processor/src/tests/processor/mod.rs deleted file mode 100644 index 114e509e..00000000 --- a/image-processor/src/tests/processor/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod decoder; -mod encoder; -mod resize; diff --git a/image-processor/src/tests/processor/resize.rs b/image-processor/src/tests/processor/resize.rs deleted file mode 100644 index 0f692b10..00000000 --- a/image-processor/src/tests/processor/resize.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::borrow::Cow; - -use crate::processor::job::decoder::{Decoder, DecoderBackend}; -use crate::processor::job::resize::{ImageResizer, ImageResizerTarget}; -use crate::tests::utils::asset_bytes; - -fn resize(asset_name: &str, backend: DecoderBackend) { - let input_bytes = asset_bytes(asset_name); - - let start = std::time::Instant::now(); - - let mut decoder = backend - .build(&Default::default(), Cow::Owned(input_bytes)) - .expect("decoder build error"); - - let mut resizer = ImageResizer::new(ImageResizerTarget { - height: 30, - width: 30, - ..Default::default() - }); - - while let Some(frame) = decoder.decode().expect("frame decode error") { - let resized = resizer.resize(&frame).expect("resize error"); - - assert_eq!(resized.image.width(), 30, "width mismatch"); - assert_eq!(resized.image.height(), 30, "height mismatch"); - } - - println!("decode time ({asset_name}): {:?}", start.elapsed()); -} - -#[test] -fn resize_gif_test() { - resize("meow.gif", DecoderBackend::Ffmpeg); -} - -#[test] -fn resize_webp_test() { - resize("meow.webp", DecoderBackend::LibWebp); -} - -#[test] -fn resize_avif_test() { - resize("meow.avif", DecoderBackend::LibAvif); -} diff --git a/image-processor/src/tests/utils.rs b/image-processor/src/tests/utils.rs deleted file mode 100644 index b65ab2b9..00000000 --- a/image-processor/src/tests/utils.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::path::PathBuf; -// use std::sync::Arc; - -// use scuffle_utils::context::Handler; - -// use super::global::GlobalState; - -// pub async fn teardown(global: Arc, handler: Handler) { -// drop(global); -// handler.cancel().await; -// } - -pub fn asset_path(name: &str) -> PathBuf { - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .parent() - .unwrap() - .join("assets") - .join(name) -} - -pub fn asset_bytes(name: &str) -> Vec { - std::fs::read(asset_path(name)).unwrap() -} diff --git a/image-processor/src/worker/mod.rs b/image-processor/src/worker/mod.rs index cb9b72f8..f1110b2f 100644 --- a/image-processor/src/worker/mod.rs +++ b/image-processor/src/worker/mod.rs @@ -6,14 +6,20 @@ use scuffle_foundations::context::{self, ContextFutExt}; use crate::database::Job; use crate::global::Global; -mod process; +pub mod process; pub use self::process::JobError; pub async fn start(global: Arc) -> anyhow::Result<()> { let config = global.config(); - let semaphore = Arc::new(tokio::sync::Semaphore::new(config.worker.concurrency)); + let mut concurrency = config.worker.concurrency; + + if concurrency == 0 { + concurrency = num_cpus::get(); + } + + let semaphore = Arc::new(tokio::sync::Semaphore::new(concurrency)); let mut error_count = 0; let (_, handle) = context::Context::new(); diff --git a/image-processor/src/worker/process/blocking.rs b/image-processor/src/worker/process/blocking.rs index ef0a0abd..ea139071 100644 --- a/image-processor/src/worker/process/blocking.rs +++ b/image-processor/src/worker/process/blocking.rs @@ -21,6 +21,8 @@ pub struct JobOutput { pub width: usize, pub height: usize, pub data: Vec, + pub frame_count: usize, + pub duration_ms: u64, } #[derive(Clone, Copy)] @@ -191,7 +193,7 @@ impl<'a> BlockingTask<'a> { if let Some(anim_config) = anim_config { match anim_config.frame_rate.as_ref() { - Some(animation_config::FrameRate::DurationsMs(durations)) => { + Some(animation_config::FrameRate::FrameDurationsMs(durations)) => { if durations.values.len() != decoder_info.frame_count { return Err(JobError::MismatchedFrameCount { frame_count: decoder_info.frame_count, @@ -203,7 +205,7 @@ impl<'a> BlockingTask<'a> { frame_configs[idx] = Some(FrameConfig::DurationMs(*duration)) } } - Some(animation_config::FrameRate::DurationMs(duration)) => { + Some(animation_config::FrameRate::FrameDurationMs(duration)) => { for config in frame_configs.iter_mut() { *config = Some(FrameConfig::DurationMs(*duration)) } @@ -279,7 +281,7 @@ impl<'a> BlockingTask<'a> { frame_idx: 0, duration_carried_ms: 0.0, frame_rate_factor: anim_config.and_then(|config| match config.frame_rate.as_ref()? { - animation_config::FrameRate::Factor(factor) => Some(*factor), + animation_config::FrameRate::FrameRateFactor(factor) => Some(*factor), _ => None, }), }) @@ -364,6 +366,8 @@ impl<'a> BlockingTask<'a> { scale: output.scale.map(|s| s as usize), width: info.width, height: info.height, + frame_count: info.frame_count, + duration_ms: info.duration, data: encoder.finish()?, }) }) diff --git a/image-processor/src/worker/process/decoder/ffmpeg.rs b/image-processor/src/worker/process/decoder/ffmpeg.rs index 24c9e212..458aace8 100644 --- a/image-processor/src/worker/process/decoder/ffmpeg.rs +++ b/image-processor/src/worker/process/decoder/ffmpeg.rs @@ -13,6 +13,8 @@ pub struct FfmpegDecoder<'data> { scaler: scuffle_ffmpeg::scalar::Scalar, info: DecoderInfo, input_stream_index: i32, + average_frame_duration: u64, + previous_timestamp: Option, send_packet: bool, eof: bool, done: bool, @@ -104,9 +106,11 @@ impl<'data> FfmpegDecoder<'data> { timescale: input_stream_time_base.den as u64, }; + let average_frame_duration = (input_stream_duration / input_stream_frames) as u64; + let frame = Frame { image: Img::new(vec![RGBA8::default(); info.width * info.height], info.width, info.height), - duration_ts: (input_stream_duration / input_stream_frames) as u64, + duration_ts: average_frame_duration, }; Ok(Self { @@ -119,6 +123,8 @@ impl<'data> FfmpegDecoder<'data> { eof: false, send_packet: true, frame, + average_frame_duration, + previous_timestamp: Some(0), }) } } @@ -196,6 +202,14 @@ impl Decoder for FfmpegDecoder<'_> { } } + let timestamp = frame + .best_effort_timestamp() + .and_then(|ts| if ts > 0 { Some(ts as u64) } else { None }); + self.frame.duration_ts = timestamp + .map(|ts| ts - self.previous_timestamp.unwrap_or_default()) + .unwrap_or(self.average_frame_duration); + self.previous_timestamp = timestamp; + return Ok(Some(self.frame.as_ref())); } else if self.eof { self.done = true; diff --git a/image-processor/src/worker/process/input_download.rs b/image-processor/src/worker/process/input_download.rs index b9e1f87a..8ba349a4 100644 --- a/image-processor/src/worker/process/input_download.rs +++ b/image-processor/src/worker/process/input_download.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use bson::oid::ObjectId; use bytes::Bytes; use scuffle_image_processor_proto::{input, Input}; @@ -16,6 +17,8 @@ pub enum InputDownloadError { MissingInput, #[error("drive error: {0}")] DriveError(#[from] crate::drive::DriveError), + #[error("strfmt error: {0}")] + StrFmtError(#[from] strfmt::FmtError), } fn get_path(input: Option<&Input>) -> Option<&str> { @@ -33,18 +36,22 @@ fn get_drive(input: Option<&Input>) -> Option<&str> { } #[tracing::instrument(skip(global, input), fields(input_path = get_path(input), input_drive = get_drive(input)))] -pub async fn download_input(global: &Arc, input: Option<&Input>) -> Result { +pub async fn download_input(global: &Arc, id: ObjectId, input: Option<&Input>) -> Result { match input .ok_or(InputDownloadError::MissingInput)? .path .as_ref() .ok_or(InputDownloadError::MissingInput)? { - input::Path::DrivePath(drive) => Ok(global - .drive(&drive.drive) - .ok_or(InputDownloadError::MissingDrive)? - .read(&drive.path) - .await?), + input::Path::DrivePath(drive) => { + let path = strfmt::strfmt(&drive.path, &([("id".to_owned(), id.to_string())].into_iter().collect()))?; + + Ok(global + .drive(&drive.drive) + .ok_or(InputDownloadError::MissingDrive)? + .read(&path) + .await?) + } input::Path::PublicUrl(url) => Ok(global .public_http_drive() .ok_or(InputDownloadError::MissingPublicHttpDrive)? diff --git a/image-processor/src/worker/process/mod.rs b/image-processor/src/worker/process/mod.rs index b9bddddf..2e20a27c 100644 --- a/image-processor/src/worker/process/mod.rs +++ b/image-processor/src/worker/process/mod.rs @@ -3,8 +3,9 @@ use std::sync::Arc; use bson::oid::ObjectId; use scuffle_foundations::context::Context; -use scuffle_image_processor_proto::{ErrorCode, OutputFormat}; +use scuffle_image_processor_proto::{event_callback, ErrorCode, OutputFile, OutputFormat}; +pub use self::decoder::DecoderFrontend; use self::resize::ResizeError; use crate::database::Job; use crate::drive::{Drive, DriveWriteOptions}; @@ -107,8 +108,10 @@ impl ProcessJob { } } - #[tracing::instrument(skip(global), fields(job_id = %self.job.id), name = "ProcessJob::process")] + #[tracing::instrument(skip(global, self), fields(job_id = %self.job.id), name = "ProcessJob::process")] pub async fn process(&self, global: Arc) { + tracing::info!("starting job"); + crate::events::on_start(&global, &self.job).await; let mut future = self.process_inner(&global); @@ -124,73 +127,76 @@ impl ProcessJob { let result = loop { tokio::select! { - _ = tokio::time::sleep(global.config().worker.refresh_interval) => { - match self.job.refresh(&global).await { - Ok(true) => {}, - Ok(false) => { - tracing::warn!("lost job"); - return; - } - Err(err) => { - tracing::error!("failed to refresh job: {err}"); - return; - } - } - } - Some(_) = async { - if let Some(fut) = timeout_fut.as_mut() { - fut.await; - Some(()) - } else { - None - } - } => { - tracing::warn!("timeout"); - break Err(JobError::Internal("timeout")); - } - result = &mut future => break result, - } + _ = tokio::time::sleep(global.config().worker.refresh_interval) => { + match self.job.refresh(&global).await { + Ok(true) => {}, + Ok(false) => { + tracing::warn!("lost job"); + return; + } + Err(err) => { + tracing::error!("failed to refresh job: {err}"); + return; + } + } + } + Some(_) = async { + if let Some(fut) = timeout_fut.as_mut() { + Some(fut.await) + } else { + None + } + } => { + tracing::warn!("timeout"); + break Err(JobError::Internal("timeout")); + } + result = &mut future => break result, + } }; - let err = result - .inspect_err(|err| { + match result { + Ok(success) => { + tracing::info!("job completed"); + crate::events::on_success(&global, &self.job, success.drive, success.files).await; + } + Err(err) => { tracing::error!("failed to process job: {err}"); - }) - .err(); + crate::events::on_failure(&global, &self.job, err).await; + } + } - if let Err(err) = self.job.complete(&global, err).await { + if let Err(err) = self.job.complete(&global).await { tracing::error!("failed to complete job: {err}"); } } - async fn process_inner(&self, global: &Arc) -> Result<(), JobError> { - let input = input_download::download_input(global, self.job.task.input.as_ref()).await?; - let output_drive_path = self - .job - .task - .output - .as_ref() - .ok_or(JobError::InvalidJob)? - .drive_path - .as_ref() - .ok_or(JobError::InvalidJob)?; + async fn process_inner(&self, global: &Arc) -> Result { + let input = input_download::download_input(global, self.job.id, self.job.task.input.as_ref()).await?; + let output = self.job.task.output.as_ref().ok_or(JobError::InvalidJob)?; + + let output_drive_path = output.drive_path.as_ref().ok_or(JobError::InvalidJob)?; let output_drive = global.drive(&output_drive_path.drive).ok_or(JobError::InvalidJob)?; let job = self.job.clone(); - let outputs = blocking::spawn(job.task.clone(), input, self.permit.clone()).await?; + let output_results = blocking::spawn(job.task.clone(), input, self.permit.clone()).await?; + + let is_animated = output_results.iter().any(|r| r.frame_count > 1); + + let mut files = Vec::new(); - for output in outputs { + for output_result in output_results { let vars = setup_vars( self.job.id, - output.format_name.clone(), - output.format, - output.scale, - output.width, - output.height, - output.format_idx, - output.resize_idx, + output_result.format_name.clone(), + output_result.format, + output_result.scale, + output_result.width, + output_result.height, + output_result.format_idx, + output_result.resize_idx, + is_animated, ); let file_path = strfmt::strfmt(&output_drive_path.path, &vars).map_err(|err| { @@ -198,19 +204,41 @@ impl ProcessJob { JobError::Internal("failed to format path") })?; + let size = output_result.data.len(); + output_drive .write( &file_path, - output.data.into(), + output_result.data.into(), Some(DriveWriteOptions { - content_type: Some(content_type(output.format).to_owned()), + content_type: Some(content_type(output_result.format).to_owned()), + acl: output.acl_override.clone(), ..Default::default() }), ) .await?; + + files.push(OutputFile { + path: file_path, + size: size as u32, + format: output_result.format as i32, + frame_count: output_result.frame_count as u32, + height: output_result.height as u32, + width: output_result.width as u32, + duration_ms: output_result.duration_ms as u32, + content_type: content_type(output_result.format).to_owned(), + acl: output + .acl_override + .as_deref() + .or(output_drive.default_acl()) + .map(|s| s.to_owned()), + }); } - Ok(()) + Ok(event_callback::Success { + drive: output_drive_path.drive.clone(), + files, + }) } } @@ -223,6 +251,7 @@ fn setup_vars( height: usize, format_idx: usize, resize_idx: usize, + is_animated: bool, ) -> HashMap { let format_name = format_name.unwrap_or_else(|| match format { OutputFormat::AvifAnim => "avif_anim".to_owned(), @@ -236,7 +265,7 @@ fn setup_vars( let scale = scale.map(|scale| scale.to_string()).unwrap_or_else(|| "".to_owned()); let static_ = match format { - OutputFormat::AvifStatic | OutputFormat::PngStatic | OutputFormat::WebpStatic => "_static", + OutputFormat::AvifStatic | OutputFormat::PngStatic | OutputFormat::WebpStatic if is_animated => "_static", _ => "", }; diff --git a/image-processor/src/worker/process/resize.rs b/image-processor/src/worker/process/resize.rs index e8daac92..44f2ed44 100644 --- a/image-processor/src/worker/process/resize.rs +++ b/image-processor/src/worker/process/resize.rs @@ -158,7 +158,7 @@ impl ImageResizer { } let mut output_targets: Vec<_> = match output.resize.as_ref().ok_or(ResizeError::MissingResize)? { - output::Resize::Width(widths) => widths + output::Resize::Widths(widths) => widths .values .iter() .copied() @@ -169,7 +169,7 @@ impl ImageResizer { scale: None, }) .collect(), - output::Resize::Height(heights) => heights + output::Resize::Heights(heights) => heights .values .iter() .copied() @@ -182,7 +182,7 @@ impl ImageResizer { .collect(), output::Resize::Scaling(scaling) => { let (base_width, base_height) = match scaling.base.clone().ok_or(ResizeError::MissingResize)? { - scaling::Base::Fixed(scale) => { + scaling::Base::FixedBase(scale) => { let input = cropped_dims.convert_aspect_ratio(target_aspect_ratio); (input.width / scale as usize, input.height / scale as usize) From 834cb74accc2db762558e39949d091144a763c10 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Sat, 11 May 2024 23:37:41 +0000 Subject: [PATCH 15/21] remove unused dep --- Cargo.lock | 12 ------------ image-processor/Cargo.toml | 2 -- 2 files changed, 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8e6599d9..3b82d106 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3999,7 +3999,6 @@ dependencies = [ "scuffle-foundations", "scuffle-image-processor-proto", "serde", - "serde-aux", "serde_json", "sha2", "strfmt", @@ -4098,17 +4097,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-aux" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d2e8bfba469d06512e11e3311d4d051a4a387a5b42d010404fecf3200321c95" -dependencies = [ - "chrono", - "serde", - "serde_json", -] - [[package]] name = "serde_bytes" version = "0.11.14" diff --git a/image-processor/Cargo.toml b/image-processor/Cargo.toml index 4479c68e..78daddd5 100644 --- a/image-processor/Cargo.toml +++ b/image-processor/Cargo.toml @@ -53,7 +53,5 @@ fred = "9.0.3" strfmt = "0.2" once_cell = "1.8" -serde-aux = "4" - [build-dependencies] tonic-build = "0.11" From da414e37f82643647d8c703cc6034d6639e074e2 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Sat, 11 May 2024 23:41:50 +0000 Subject: [PATCH 16/21] remove unused dep --- Cargo.lock | 1 - image-processor/Cargo.toml | 3 --- 2 files changed, 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b82d106..3d5c141f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4005,7 +4005,6 @@ dependencies = [ "thiserror", "tokio", "tonic", - "tonic-build", "tracing", "url", "urlencoding", diff --git a/image-processor/Cargo.toml b/image-processor/Cargo.toml index 78daddd5..bf90295c 100644 --- a/image-processor/Cargo.toml +++ b/image-processor/Cargo.toml @@ -52,6 +52,3 @@ aws-smithy-runtime-api = "1" fred = "9.0.3" strfmt = "0.2" once_cell = "1.8" - -[build-dependencies] -tonic-build = "0.11" From 8a57bf82a2ca098dd4215de9c6eaa8790a146efd Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Wed, 22 May 2024 22:57:35 +0200 Subject: [PATCH 17/21] feat(image-processor): add upload info to response --- .../proto/scuffle/image_processor/service.proto | 15 +++++++++++++-- image-processor/src/management/grpc.rs | 2 +- image-processor/src/management/http.rs | 2 +- image-processor/src/management/mod.rs | 14 ++++++++++---- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/image-processor/proto/scuffle/image_processor/service.proto b/image-processor/proto/scuffle/image_processor/service.proto index 5948c336..3acd37da 100644 --- a/image-processor/proto/scuffle/image_processor/service.proto +++ b/image-processor/proto/scuffle/image_processor/service.proto @@ -36,13 +36,24 @@ message ProcessImageResponse { // A unique identifier for the task string id = 1; - // If the task had an input upload, this will be the path to the uploaded image. - optional DrivePath upload_path = 2; + // If the task had an input upload, this will be the info of the uploaded image. + optional ProcessImageResponseUploadInfo upload_info = 2; // Errors that occurred when creating the task. optional Error error = 3; } +message ProcessImageResponseUploadInfo { + // The path of the uploaded image + DrivePath path = 1; + + // The content type of the uploaded image + string content_type = 2; + + // The size of the uploaded image in bytes + uint64 size = 3; +} + // The Payload for a ImageProcessor.CancelTask request message CancelTaskRequest { // The unique identifier of the task to cancel diff --git a/image-processor/src/management/grpc.rs b/image-processor/src/management/grpc.rs index 6e599994..94a11d26 100644 --- a/image-processor/src/management/grpc.rs +++ b/image-processor/src/management/grpc.rs @@ -22,7 +22,7 @@ impl scuffle_image_processor_proto::image_processor_server::ImageProcessor for M Ok(resp) => resp, Err(err) => ProcessImageResponse { id: "".to_owned(), - upload_path: None, + upload_info: None, error: Some(err), }, }; diff --git a/image-processor/src/management/http.rs b/image-processor/src/management/http.rs index 18b5bf25..3c6bf111 100644 --- a/image-processor/src/management/http.rs +++ b/image-processor/src/management/http.rs @@ -36,7 +36,7 @@ async fn process_image( Ok(resp) => resp, Err(err) => ProcessImageResponse { id: "".to_owned(), - upload_path: None, + upload_info: None, error: Some(err), }, }; diff --git a/image-processor/src/management/mod.rs b/image-processor/src/management/mod.rs index b4fdf6fb..31f25584 100644 --- a/image-processor/src/management/mod.rs +++ b/image-processor/src/management/mod.rs @@ -5,7 +5,7 @@ use bson::oid::ObjectId; use bytes::Bytes; use scuffle_image_processor_proto::{ input, CancelTaskRequest, CancelTaskResponse, DrivePath, Error, ErrorCode, Input, ProcessImageRequest, - ProcessImageResponse, + ProcessImageResponse, ProcessImageResponseUploadInfo, }; use crate::database::Job; @@ -42,7 +42,7 @@ impl ManagementServer { let id = ObjectId::new(); - let upload_path = if let Some(input_upload) = request.input_upload { + let upload_info = if let Some(input_upload) = request.input_upload { let drive_path = input_upload.drive_path.unwrap(); let drive = self.global.drive(&drive_path.drive).unwrap(); @@ -79,6 +79,8 @@ impl ManagementServer { }); } + let upload_size = input_upload.binary.len() as u64; + drive .write( &path, @@ -99,7 +101,11 @@ impl ManagementServer { } })?; - Some(drive_path) + Some(ProcessImageResponseUploadInfo { + path: Some(drive_path), + content_type: file_format.media_type().to_owned(), + size: upload_size, + }) } else { None }; @@ -116,7 +122,7 @@ impl ManagementServer { Ok(ProcessImageResponse { id: job.id.to_string(), - upload_path, + upload_info, error: None, }) } From fac6d5d44f4c610ca413b86dfbd34fb9a0ebd957 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Sat, 25 May 2024 18:30:16 +0200 Subject: [PATCH 18/21] feat(image-processor): input metadata on success event --- .../scuffle/image_processor/events.proto | 1 + .../proto/scuffle/image_processor/types.proto | 10 +++++ image-processor/src/events.rs | 40 +++++++++---------- .../src/worker/process/blocking.rs | 29 ++++++++++---- image-processor/src/worker/process/mod.rs | 9 ++++- 5 files changed, 58 insertions(+), 31 deletions(-) diff --git a/image-processor/proto/scuffle/image_processor/events.proto b/image-processor/proto/scuffle/image_processor/events.proto index bd717a5e..8ad046f7 100644 --- a/image-processor/proto/scuffle/image_processor/events.proto +++ b/image-processor/proto/scuffle/image_processor/events.proto @@ -8,6 +8,7 @@ message EventCallback { message Success { string drive = 1; repeated OutputFile files = 2; + InputFileMetadata input_metadata = 3; } message Fail { diff --git a/image-processor/proto/scuffle/image_processor/types.proto b/image-processor/proto/scuffle/image_processor/types.proto index c29f12b2..b96ca016 100644 --- a/image-processor/proto/scuffle/image_processor/types.proto +++ b/image-processor/proto/scuffle/image_processor/types.proto @@ -248,6 +248,16 @@ message OutputFile { OutputFormat format = 9; } +// Returned after the image is processed. +message InputFileMetadata { + // The width of the input image. + uint32 width = 1; + // The height of the input image. + uint32 height = 2; + // The frame count of the input image. + uint32 frame_count = 3; +} + message Output { // The drive path to store the output image. // This is a prefix and the processor will append the suffix to this path to determine the final path. diff --git a/image-processor/src/events.rs b/image-processor/src/events.rs index 15afcb09..eccb41f7 100644 --- a/image-processor/src/events.rs +++ b/image-processor/src/events.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use scuffle_image_processor_proto::{event_callback, EventCallback, EventQueue as EventTopic, OutputFile}; +use scuffle_image_processor_proto::{event_callback, EventCallback, EventQueue as EventTopic}; use crate::database::Job; use crate::event_queue::EventQueue; @@ -29,42 +29,38 @@ pub async fn on_event(global: &Arc, job: &Job, event_topic: &EventTopic, } } -fn start_event(_: &Job) -> event_callback::Event { - event_callback::Event::Start(event_callback::Start {}) -} - -fn success_event(_: &Job, drive: String, files: Vec) -> event_callback::Event { - event_callback::Event::Success(event_callback::Success { drive, files }) -} - -fn fail_event(_: &Job, err: JobError) -> event_callback::Event { - event_callback::Event::Fail(event_callback::Fail { error: Some(err.into()) }) -} - -fn cancel_event(_: &Job) -> event_callback::Event { - event_callback::Event::Cancel(event_callback::Cancel {}) -} - pub async fn on_start(global: &Arc, job: &Job) { if let Some(on_start) = &job.task.events.as_ref().and_then(|events| events.on_start.as_ref()) { - on_event(global, job, on_start, start_event(job)).await; + on_event(global, job, on_start, event_callback::Event::Start(event_callback::Start {})).await; } } -pub async fn on_success(global: &Arc, job: &Job, drive: String, files: Vec) { +pub async fn on_success(global: &Arc, job: &Job, success: event_callback::Success) { if let Some(on_success) = &job.task.events.as_ref().and_then(|events| events.on_success.as_ref()) { - on_event(global, job, on_success, success_event(job, drive, files)).await; + on_event(global, job, on_success, event_callback::Event::Success(success)).await; } } pub async fn on_failure(global: &Arc, job: &Job, err: JobError) { if let Some(on_failure) = &job.task.events.as_ref().and_then(|events| events.on_failure.as_ref()) { - on_event(global, job, on_failure, fail_event(job, err)).await; + on_event( + global, + job, + on_failure, + event_callback::Event::Fail(event_callback::Fail { error: Some(err.into()) }), + ) + .await; } } pub async fn on_cancel(global: &Arc, job: &Job) { if let Some(on_cancel) = &job.task.events.as_ref().and_then(|events| events.on_cancel.as_ref()) { - on_event(global, job, on_cancel, cancel_event(job)).await; + on_event( + global, + job, + on_cancel, + event_callback::Event::Cancel(event_callback::Cancel {}), + ) + .await; } } diff --git a/image-processor/src/worker/process/blocking.rs b/image-processor/src/worker/process/blocking.rs index ea139071..73b0537b 100644 --- a/image-processor/src/worker/process/blocking.rs +++ b/image-processor/src/worker/process/blocking.rs @@ -4,15 +4,20 @@ use std::sync::Arc; use bytes::Bytes; use file_format::FileFormat; -use scuffle_image_processor_proto::{animation_config, Output, OutputFormat, OutputFormatOptions, Task}; +use scuffle_image_processor_proto::{animation_config, InputFileMetadata, Output, OutputFormat, OutputFormatOptions, Task}; use tokio::sync::OwnedSemaphorePermit; use super::decoder::{AnyDecoder, Decoder, DecoderFrontend, DecoderInfo, LoopCount}; -use super::encoder::{AnyEncoder, Encoder, EncoderBackend, EncoderSettings}; +use super::encoder::{AnyEncoder, Encoder, EncoderBackend, EncoderError, EncoderSettings}; use super::resize::{ImageResizer, ResizeOutputTarget}; use super::JobError; pub struct JobOutput { + pub input: InputFileMetadata, + pub output: Vec, +} + +pub struct OutputImage { pub format: OutputFormat, pub format_name: Option, pub format_idx: usize, @@ -54,7 +59,7 @@ impl Drop for CancelToken { } } -pub async fn spawn(task: Task, input: Bytes, permit: Arc) -> Result, JobError> { +pub async fn spawn(task: Task, input: Bytes, permit: Arc) -> Result { let cancel_token = CancelToken::new(); let _cancel_guard = cancel_token.clone(); @@ -351,14 +356,15 @@ impl<'a> BlockingTask<'a> { Ok(true) } - pub fn finish(self) -> Result, JobError> { - self.static_encoders + pub fn finish(self) -> Result { + let output = self + .static_encoders .into_iter() .chain(self.anim_encoders) .flat_map(|(f_idx, encoders)| { encoders.into_iter().map(move |(output, encoder)| { let info = encoder.info(); - Ok(JobOutput { + Ok(OutputImage { format: info.format, format_name: info.name.clone(), format_idx: f_idx, @@ -372,6 +378,15 @@ impl<'a> BlockingTask<'a> { }) }) }) - .collect() + .collect::>()?; + + Ok(JobOutput { + input: InputFileMetadata { + width: self.decoder_info.width as u32, + height: self.decoder_info.height as u32, + frame_count: self.decoder_info.frame_count as u32, + }, + output, + }) } } diff --git a/image-processor/src/worker/process/mod.rs b/image-processor/src/worker/process/mod.rs index 2e20a27c..ea67d78f 100644 --- a/image-processor/src/worker/process/mod.rs +++ b/image-processor/src/worker/process/mod.rs @@ -5,6 +5,7 @@ use bson::oid::ObjectId; use scuffle_foundations::context::Context; use scuffle_image_processor_proto::{event_callback, ErrorCode, OutputFile, OutputFormat}; +use self::blocking::JobOutput; pub use self::decoder::DecoderFrontend; use self::resize::ResizeError; use crate::database::Job; @@ -157,7 +158,7 @@ impl ProcessJob { match result { Ok(success) => { tracing::info!("job completed"); - crate::events::on_success(&global, &self.job, success.drive, success.files).await; + crate::events::on_success(&global, &self.job, success).await; } Err(err) => { tracing::error!("failed to process job: {err}"); @@ -180,7 +181,10 @@ impl ProcessJob { let job = self.job.clone(); - let output_results = blocking::spawn(job.task.clone(), input, self.permit.clone()).await?; + let JobOutput { + output: output_results, + input: input_metadata, + } = blocking::spawn(job.task.clone(), input, self.permit.clone()).await?; let is_animated = output_results.iter().any(|r| r.frame_count > 1); @@ -237,6 +241,7 @@ impl ProcessJob { Ok(event_callback::Success { drive: output_drive_path.drive.clone(), + input_metadata: Some(input_metadata), files, }) } From 90916654a073a2eff7097c2eed86b710b9c56993 Mon Sep 17 00:00:00 2001 From: Lennart Kloock Date: Sat, 1 Jun 2024 12:59:59 +0200 Subject: [PATCH 19/21] feat(image-processor): return metadata on callback --- .../proto/scuffle/image_processor/events.proto | 9 +++++---- image-processor/src/events.rs | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/image-processor/proto/scuffle/image_processor/events.proto b/image-processor/proto/scuffle/image_processor/events.proto index 8ad046f7..fba35b32 100644 --- a/image-processor/proto/scuffle/image_processor/events.proto +++ b/image-processor/proto/scuffle/image_processor/events.proto @@ -21,11 +21,12 @@ message EventCallback { string id = 1; uint64 timestamp = 2; + map metadata = 3; oneof event { - Success success = 3; - Fail fail = 4; - Cancel cancel = 5; - Start start = 6; + Success success = 4; + Fail fail = 5; + Cancel cancel = 6; + Start start = 7; } } diff --git a/image-processor/src/events.rs b/image-processor/src/events.rs index eccb41f7..074463b3 100644 --- a/image-processor/src/events.rs +++ b/image-processor/src/events.rs @@ -20,6 +20,7 @@ pub async fn on_event(global: &Arc, job: &Job, event_topic: &EventTopic, EventCallback { id: job.id.to_string(), timestamp: chrono::Utc::now().timestamp() as u64, + metadata: job.task.events.as_ref().map(|e| e.metadata.clone()).unwrap_or_default(), event: Some(event), }, ) From 8eb0aa9cd183673293464355adcbebcc9957d1e9 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Fri, 21 Jun 2024 22:48:50 +0000 Subject: [PATCH 20/21] fix: remove jemalloc We have observed strange behavior with jemalloc causing segfaults within the image processor library. We ran the library with libasan and valgrind (both incompatiable with jemalloc) and we could not find any leaks or invalid memory access or double frees. However when we use jemalloc we have issues and sometimes it segfaults always deep inside jemalloc code. Since we cannot debug this with valgrind / libasan its impossible to figure out if its a problem in our code (more likely) or jemalloc (unlikely but possible), given that the bug only comes up when we use jemalloc its likely a difference in strictness of the allocator and something we are doing is wrong but the system allocator doesnt care but jemalloc does. This issue is also only present when we link directly to jemalloc + use jemalloc as the global rust allocator (so it might be an issue in the tikv-jemalloc crate). When we use jemalloc via `LD_PRELOAD` the issue isnt there. --- Cargo.lock | 485 +++++++++--------- dev/docker-compose.yml | 37 +- foundations/Cargo.toml | 46 +- foundations/examples/Cargo.toml | 12 +- foundations/examples/src/generics.rs | 2 +- foundations/examples/src/http-server.rs | 21 +- foundations/src/heap.rs | 2 - foundations/src/http/server/stream/quic.rs | 2 + foundations/src/lib.rs | 3 - foundations/src/settings/cli.rs | 8 +- foundations/src/settings/mod.rs | 267 +--------- foundations/src/settings/traits.rs | 6 +- .../src/telemetry/opentelemetry/node.rs | 2 +- foundations/src/telemetry/pprof/heap.rs | 38 -- foundations/src/telemetry/pprof/mod.rs | 5 - image-processor/Cargo.toml | 4 +- .../proto/scuffle/image_processor/types.proto | 54 +- image-processor/src/drive/mod.rs | 1 + image-processor/src/management/mod.rs | 2 +- image-processor/src/worker/process/mod.rs | 4 +- image-processor/src/worker/process/resize.rs | 163 +++--- 21 files changed, 449 insertions(+), 715 deletions(-) delete mode 100644 foundations/src/heap.rs delete mode 100644 foundations/src/telemetry/pprof/heap.rs diff --git a/Cargo.lock b/Cargo.lock index 3d5c141f..e448b43b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,7 +166,7 @@ dependencies = [ "portable-atomic", "rand", "regex", - "ring 0.17.8", + "ring", "rustls-native-certs 0.7.0", "rustls-pemfile 2.1.2", "rustls-webpki 0.102.3", @@ -259,7 +259,7 @@ dependencies = [ "hex", "http 0.2.12", "hyper 0.14.28", - "ring 0.17.8", + "ring", "time", "tokio", "tracing", @@ -279,6 +279,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "aws-lc-rs" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7d844e282b4b56750b2d4e893b2205581ded8709fddd2b6aa5418c150ca877" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a2c29203f6bf296d01141cc8bb9dbd5ecd4c27843f2ee0767bcd5985a927da" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "aws-runtime" version = "1.2.0" @@ -426,7 +453,7 @@ dependencies = [ "once_cell", "p256", "percent-encoding", - "ring 0.17.8", + "ring", "sha2", "subtle", "time", @@ -777,12 +804,15 @@ dependencies = [ "itertools 0.12.1", "lazy_static", "lazycell", + "log", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn 2.0.60", + "which", ] [[package]] @@ -894,6 +924,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -987,6 +1023,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -1331,6 +1377,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "document-features" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +dependencies = [ + "litrs", +] + [[package]] name = "dtoa" version = "1.0.9" @@ -1437,7 +1492,7 @@ version = "0.1.0" dependencies = [ "axum 0.7.5", "futures", - "h3 0.0.4", + "h3 0.0.5", "h3-quinn", "http-body-util", "hyper 1.3.1", @@ -1445,8 +1500,8 @@ dependencies = [ "opentelemetry", "quinn", "rand", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", + "rustls 0.23.5", + "rustls-pemfile 2.1.2", "scuffle-foundations", "socket2 0.5.7", "tokio", @@ -1465,11 +1520,12 @@ dependencies = [ [[package]] name = "fast_image_resize" -version = "3.0.4" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9d450fac8a334ad72825596173f0f7767ff04dd6e3d59c49c894c4bc2957e8b" +checksum = "02abb58c39fa9b20678cedabab49e6c4f6ecb7480d7cb5711496b9289184a875" dependencies = [ "cfg-if", + "document-features", "num-traits", "thiserror", ] @@ -1609,6 +1665,12 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "funty" version = "2.0.0" @@ -1851,9 +1913,9 @@ dependencies = [ [[package]] name = "h3" -version = "0.0.4" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c8886b9e6e93e7ed93d9433f3779e8d07e3ff96bc67b977d14c7b20c849411" +checksum = "d5069de1c2ac82d9e361b07f2b8a2c582ec071750e063530fc7f3b5197e24805" dependencies = [ "bytes", "fastrand", @@ -1866,15 +1928,14 @@ dependencies = [ [[package]] name = "h3-quinn" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73786bcc0e4c2692ba62c650f7b950ac236e5300c5de3b1d26330555e2322046" +checksum = "b8c01d99d7cf812fd34ddf135e6c940df9e24f2e759dbc7179fb0e54d4bd6551" dependencies = [ "bytes", "futures", - "h3 0.0.4", + "h3 0.0.5", "quinn", - "quinn-proto", "tokio", "tokio-util", ] @@ -1952,6 +2013,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "hostname" version = "0.3.1" @@ -2283,27 +2353,27 @@ checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -2315,24 +2385,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "jemalloc_pprof" -version = "0.1.0" +name = "jni" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45b38a2cc3eb7b0e332c6368a6fd6a1a603a5be9526f0810f8e0682513538541" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" dependencies = [ - "anyhow", - "flate2", - "libc", - "num", - "once_cell", - "paste", - "prost 0.11.9", - "tempfile", - "tikv-jemalloc-ctl", - "tokio", - "tracing", + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", ] +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.31" @@ -2436,6 +2507,12 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.12" @@ -2586,6 +2663,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + [[package]] name = "mongodb" version = "2.8.2" @@ -2721,20 +2804,6 @@ dependencies = [ "rand", ] -[[package]] -name = "num" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.4" @@ -2746,15 +2815,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-complex" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" -dependencies = [ - "num-traits", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -2781,17 +2841,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-rational" version = "0.4.1" @@ -2846,9 +2895,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "opentelemetry" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900d57987be3f2aeb70d385fff9b27fb74c5723cc9a52d904d4f9c807a0667bf" +checksum = "1b69a91d4893e713e06f724597ad630f1fa76057a5e1026c0ca67054a9032a76" dependencies = [ "futures-core", "futures-sink", @@ -2856,14 +2905,13 @@ dependencies = [ "once_cell", "pin-project-lite", "thiserror", - "urlencoding", ] [[package]] name = "opentelemetry-http" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7690dc77bf776713848c4faa6501157469017eaf332baccd4eb1cea928743d94" +checksum = "b0ba633e55c5ea6f431875ba55e71664f2fa5d3a90bd34ec9302eecc41c865dd" dependencies = [ "async-trait", "bytes", @@ -2873,9 +2921,9 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a016b8d9495c639af2145ac22387dcb88e44118e45320d9238fbf4e7889abcb" +checksum = "a94c69209c05319cdf7460c6d4c055ed102be242a0a6245835d7bc42c6ec7f54" dependencies = [ "async-trait", "futures-core", @@ -2883,9 +2931,8 @@ dependencies = [ "opentelemetry", "opentelemetry-http", "opentelemetry-proto", - "opentelemetry-semantic-conventions", "opentelemetry_sdk", - "prost 0.12.4", + "prost", "thiserror", "tokio", "tonic", @@ -2893,34 +2940,28 @@ dependencies = [ [[package]] name = "opentelemetry-proto" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8fddc9b68f5b80dae9d6f510b88e02396f006ad48cac349411fbecc80caae4" +checksum = "984806e6cf27f2b49282e2a05e288f30594f3dbc74eb7a6e99422bc48ed78162" dependencies = [ "opentelemetry", "opentelemetry_sdk", - "prost 0.12.4", + "prost", "tonic", ] -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9ab5bd6c42fb9349dcf28af2ba9a0667f697f9bdcca045d39f2cec5543e2910" - [[package]] name = "opentelemetry_sdk" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e90c7113be649e31e9a0f8b5ee24ed7a16923b322c3c5ab6367469c049d6b7e" +checksum = "ae312d58eaa90a82d2e627fd86e075cf5230b3f11794e2ed74199ebbe572d4fd" dependencies = [ "async-trait", - "crossbeam-channel", "futures-channel", "futures-executor", "futures-util", "glob", + "lazy_static", "once_cell", "opentelemetry", "ordered-float", @@ -3017,7 +3058,7 @@ checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" dependencies = [ "heck 0.4.1", "itertools 0.11.0", - "prost 0.12.4", + "prost", "prost-types", ] @@ -3169,9 +3210,9 @@ dependencies = [ "nix", "once_cell", "parking_lot", - "prost 0.12.4", + "prost", "prost-build", - "prost-derive 0.12.4", + "prost-derive", "sha2", "smallvec", "symbolic-demangle", @@ -3246,16 +3287,6 @@ dependencies = [ "syn 2.0.60", ] -[[package]] -name = "prost" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" -dependencies = [ - "bytes", - "prost-derive 0.11.9", -] - [[package]] name = "prost" version = "0.12.4" @@ -3263,7 +3294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" dependencies = [ "bytes", - "prost-derive 0.12.4", + "prost-derive", ] [[package]] @@ -3280,26 +3311,13 @@ dependencies = [ "once_cell", "petgraph", "prettyplease", - "prost 0.12.4", + "prost", "prost-types", "regex", "syn 2.0.60", "tempfile", ] -[[package]] -name = "prost-derive" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" -dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "prost-derive" version = "0.12.4" @@ -3319,7 +3337,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" dependencies = [ - "prost 0.12.4", + "prost", ] [[package]] @@ -3336,9 +3354,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quinn" -version = "0.10.2" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" +checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" dependencies = [ "bytes", "futures-io", @@ -3346,7 +3364,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.21.12", + "rustls 0.23.5", "thiserror", "tokio", "tracing", @@ -3354,16 +3372,16 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.10.6" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" +checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" dependencies = [ "bytes", "rand", - "ring 0.16.20", + "ring", "rustc-hash", - "rustls 0.21.12", - "rustls-native-certs 0.6.3", + "rustls 0.23.5", + "rustls-platform-verifier", "slab", "thiserror", "tinyvec", @@ -3372,15 +3390,15 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.4.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" +checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" dependencies = [ - "bytes", "libc", + "once_cell", "socket2 0.5.7", "tracing", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3639,21 +3657,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.8" @@ -3664,8 +3667,8 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "spin", + "untrusted", "windows-sys 0.52.0", ] @@ -3729,7 +3732,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring", "rustls-webpki 0.101.7", "sct", ] @@ -3741,7 +3744,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.8", + "ring", "rustls-pki-types", "rustls-webpki 0.102.3", "subtle", @@ -3754,8 +3757,10 @@ version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afabcee0551bd1aa3e18e5adbf2c0544722014b899adb31bd186ec638d3da97e" dependencies = [ + "aws-lc-rs", + "log", "once_cell", - "ring 0.17.8", + "ring", "rustls-pki-types", "rustls-webpki 0.102.3", "subtle", @@ -3812,14 +3817,41 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" +[[package]] +name = "rustls-platform-verifier" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5f0d26fa1ce3c790f9590868f0109289a044acb954525f933e2aa3b871c157d" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.5", + "rustls-native-certs 0.7.0", + "rustls-platform-verifier-android", + "rustls-webpki 0.102.3", + "security-framework", + "security-framework-sys", + "webpki-roots 0.26.1", + "winapi", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" + [[package]] name = "rustls-webpki" version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -3828,9 +3860,10 @@ version = "0.102.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3bce581c0dd41bce533ce695a1437fa16a7ab5ac3ccfa99fe1a620a7885eabf" dependencies = [ - "ring 0.17.8", + "aws-lc-rs", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -3845,6 +3878,15 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scan_fmt" version = "0.2.6" @@ -3881,8 +3923,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -3908,7 +3950,7 @@ dependencies = [ "const-str", "flate2", "futures", - "h3 0.0.4", + "h3 0.0.5", "h3-quinn", "h3-webtransport", "http 1.1.0", @@ -3917,8 +3959,7 @@ dependencies = [ "humantime-serde", "hyper 1.3.1", "hyper-util", - "itertools 0.12.1", - "jemalloc_pprof", + "itertools 0.13.0", "matchers", "num_cpus", "once_cell", @@ -3929,24 +3970,22 @@ dependencies = [ "pin-project", "pprof", "prometheus-client", - "prost 0.12.4", + "prost", "quinn", "rand", "regex", - "rustls 0.21.12", + "rustls 0.23.5", "scc", "scuffle-foundations-macros", "serde", - "serde_yaml", "socket2 0.5.7", - "spin 0.9.8", + "spin", "thiserror", "thread_local", - "tikv-jemalloc-ctl", - "tikv-jemallocator", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls 0.26.0", "tokio-util", + "toml", "tower", "tracing", "tracing-subscriber", @@ -3991,7 +4030,7 @@ dependencies = [ "num_cpus", "once_cell", "png", - "prost 0.12.4", + "prost", "reqwest", "rgb", "scopeguard", @@ -4016,7 +4055,7 @@ version = "0.0.0" dependencies = [ "pbjson", "pbjson-build", - "prost 0.12.4", + "prost", "prost-build", "serde", "tonic", @@ -4053,6 +4092,7 @@ dependencies = [ "core-foundation", "core-foundation-sys", "libc", + "num-bigint", "security-framework-sys", ] @@ -4201,19 +4241,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.2.6", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "sha-1" version = "0.10.1" @@ -4353,12 +4380,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -4559,37 +4580,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tikv-jemalloc-ctl" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "619bfed27d807b54f7f776b9430d4f8060e66ee138a28632ca898584d462c31c" -dependencies = [ - "libc", - "paste", - "tikv-jemalloc-sys", -] - -[[package]] -name = "tikv-jemalloc-sys" -version = "0.5.4+5.3.0-patched" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "tikv-jemallocator" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965fe0c26be5c56c94e38ba547249074803efd52adfb66de62107d95aab3eaca" -dependencies = [ - "libc", - "tikv-jemalloc-sys", -] - [[package]] name = "time" version = "0.3.36" @@ -4785,7 +4775,7 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost 0.12.4", + "prost", "tokio", "tokio-stream", "tower", @@ -5017,18 +5007,6 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -5110,6 +5088,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -5222,6 +5210,18 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "widestring" version = "1.1.0" @@ -5253,6 +5253,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -5492,3 +5501,17 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] diff --git a/dev/docker-compose.yml b/dev/docker-compose.yml index 252f62bc..a5d72dfd 100644 --- a/dev/docker-compose.yml +++ b/dev/docker-compose.yml @@ -3,15 +3,13 @@ version: "3.1" name: "db-scuffle-dev" services: - cockroach: - image: ghcr.io/scuffletv/ci/cockroach:latest + mongo: + image: mongo:latest pull_policy: "always" - command: start-single-node --insecure --advertise-addr=0.0.0.0 - volumes: - - cockroach:/cockroach/cockroach-data ports: - - "127.0.0.1:5432:26257" - - "127.0.0.1:8080:8080" + - "27111:27017" + volumes: + - mongo:/data/db nats: image: ghcr.io/scuffletv/ci/nats:latest @@ -33,8 +31,8 @@ services: - "127.0.0.1:9000:9000" - "127.0.0.1:9001:9001" environment: - - "MINIO_ACCESS_KEY=root" - - "MINIO_SECRET_KEY=scuffle123" + - "MINIO_ACCESS_KEY=minioadmin" + - "MINIO_SECRET_KEY=minioadmin" volumes: - minio:/data command: server /data --console-address ":9001" @@ -47,26 +45,13 @@ services: entrypoint: > /bin/sh -c " set -eux; - /usr/bin/mc config host add myminio http://minio:9000 root scuffle123; - /usr/bin/mc rb --force myminio/scuffle-video || true; - /usr/bin/mc rb --force myminio/scuffle-image-processor || true; - /usr/bin/mc rb --force myminio/scuffle-image-processor-public || true; - /usr/bin/mc mb myminio/scuffle-video; - /usr/bin/mc mb myminio/scuffle-image-processor; - /usr/bin/mc mb myminio/scuffle-image-processor-public; - /usr/bin/mc anonymous set download myminio/scuffle-video; - /usr/bin/mc anonymous set download myminio/scuffle-image-processor-public; + /usr/bin/mc config host add myminio http://minio:9000 minioadmin minioadmin; + /usr/bin/mc mb myminio/image-processor; + /usr/bin/mc anonymous set download myminio/image-processor; exit 0; " - redis: - image: ghcr.io/scuffletv/ci/redis:latest - pull_policy: "always" - ports: - - "127.0.0.1:6379:6379" - volumes: - cockroach: nats: minio: - redis: + mongo: diff --git a/foundations/Cargo.toml b/foundations/Cargo.toml index a1744c3f..9390b5d8 100644 --- a/foundations/Cargo.toml +++ b/foundations/Cargo.toml @@ -15,18 +15,15 @@ rand = { version = "0.8", optional = true } tracing = { version = "0.1", optional = true } tracing-subscriber = { version = "0.3", optional = true } -opentelemetry = { version = "0.22", optional = true } -opentelemetry_sdk = { version = "0.22", optional = true } -opentelemetry-otlp = { version = "0.15", optional = true, features = ["http-proto"]} +opentelemetry = { version = "0.23", optional = true } +opentelemetry_sdk = { version = "0.23", optional = true } +opentelemetry-otlp = { version = "0.16", optional = true, features = ["http-proto"]} -tikv-jemallocator = { version = "0.5", optional = true, features = ["unprefixed_malloc_on_supported_platforms"] } -tikv-jemalloc-ctl = { version = "0.5", optional = true } -jemalloc_pprof = { version = "0.1", optional = true } anyhow = { version = "1" } thread_local = { version = "1", optional = true } spin = { version = "0.9", optional = true } -itertools = { version = "0.12", optional = true } +itertools = { version = "0.13", optional = true } scuffle-foundations-macros = { path = "./macros", optional = true, version = "0.0.0" } @@ -40,7 +37,7 @@ once_cell = { version = "1", optional = true } scc = { version = "2", optional = true } serde = { version = "1", optional = true, features = ["derive", "rc"] } -serde_yaml = { version = "0.9", optional = true } +toml = { version = "0.8", optional = true } clap = { version = "4", optional = true } const-str = { version = "0.5", optional = true } @@ -58,12 +55,12 @@ tower = { version = "0.4", optional = true } hyper = { version = "1", optional = true } hyper-util = { version = "0.1", optional = true } http = { version = "1", optional = true } -h3 = { version = "0.0.4", optional = true } -h3-quinn = { version = "0.0.5", optional = true } +h3 = { version = "0.0.5", optional = true } +h3-quinn = { version = "0.0.6", optional = true } h3-webtransport = { version = "0.1.0", optional = true } -quinn = { version = "0.10", default-features = false, features = ["runtime-tokio", "tls-rustls", "ring" ], optional = true} -rustls = { version = "0.21.12", optional = true } -tokio-rustls = { version = "0.24.1", optional = true } +quinn = { version = "0.11", default-features = false, features = ["runtime-tokio", "rustls", "ring" ], optional = true } +rustls = { version = "0.23", optional = true } +tokio-rustls = { version = "0.26", optional = true } bytes = { version = "1", optional = true } thiserror = { version = "1", optional = true } http-body = { version = "1", optional = true } @@ -109,16 +106,6 @@ runtime = [ "tokio/rt-multi-thread", ] -heap = [ - "_telemetry", - "tikv-jemallocator", -] - -pprof = [ - "pprof-cpu", - "pprof-heap", -] - pprof-cpu = [ "_telemetry", "dep:pprof", @@ -126,14 +113,6 @@ pprof-cpu = [ "flate2", ] -pprof-heap = [ - "_telemetry", - "heap", - "jemalloc_pprof", - "tikv-jemallocator/profiling", - "tikv-jemalloc-ctl", -] - opentelemetry = [ "_telemetry", "itertools", @@ -183,7 +162,7 @@ health-check = [ settings = [ "serde", - "serde_yaml", + "toml", "macros", "humantime-serde", ] @@ -258,10 +237,7 @@ http3-webtransport = [ default = [ "opentelemetry", "runtime", - "heap", "pprof-cpu", - "pprof-heap", - "pprof", "macros", "logging", "env-filter", diff --git a/foundations/examples/Cargo.toml b/foundations/examples/Cargo.toml index 29e8e29e..954eac60 100644 --- a/foundations/examples/Cargo.toml +++ b/foundations/examples/Cargo.toml @@ -29,14 +29,14 @@ hyper = { version = "1", features = ["full"] } hyper-util = { version = "0.1", features = ["tokio", "http2"] } socket2 = "0.5" http-body-util = "0.1" -opentelemetry = { version = "0.22" } +opentelemetry = { version = "0.23" } rand = "0.8" -rustls-pemfile = { version = "1.0.4" } -rustls = "0.21" +rustls-pemfile = { version = "2" } +rustls = "0.23" futures = "0.3.21" tower = "0.4" -quinn = "0.10" +quinn = "0.11" axum = "0.7" -h3 = "0.0.4" +h3 = "0.0.5" # h3-webtransport = "0.1" -h3-quinn = "0.0.5" +h3-quinn = "0.0.6" diff --git a/foundations/examples/src/generics.rs b/foundations/examples/src/generics.rs index fec473ac..ab8d3c7a 100644 --- a/foundations/examples/src/generics.rs +++ b/foundations/examples/src/generics.rs @@ -16,5 +16,5 @@ pub struct ExtraSettings { } fn main() { - println!("{}", BaseSettings::::default().to_yaml_string().unwrap()); + println!("{}", BaseSettings::::default().to_docs_string().unwrap()); } diff --git a/foundations/examples/src/http-server.rs b/foundations/examples/src/http-server.rs index bf209253..5a36cd82 100644 --- a/foundations/examples/src/http-server.rs +++ b/foundations/examples/src/http-server.rs @@ -4,7 +4,7 @@ use axum::body::Body; use axum::extract::Request; use axum::response::{IntoResponse, Response}; use hyper::{StatusCode, Uri}; -use rustls::{Certificate, PrivateKey}; +use quinn::crypto::rustls::QuicServerConfig; use scuffle_foundations::bootstrap::{bootstrap, Bootstrap, RuntimeSettings}; use scuffle_foundations::http::server::stream::{IncomingConnection, MakeService, ServiceHandler, SocketKind}; use scuffle_foundations::http::server::Server; @@ -63,28 +63,23 @@ async fn main(settings: Matches) { // Test TLS let certs = rustls_pemfile::certs(&mut std::io::BufReader::new(cert)) - .unwrap() - .into_iter() - .map(Certificate) - .collect::>(); + .collect::, _>>() + .unwrap(); let key = rustls_pemfile::pkcs8_private_keys(&mut std::io::BufReader::new(key)) + .next() .unwrap() - .remove(0); + .unwrap(); - let mut tls_config = rustls::ServerConfig::builder() - .with_safe_default_cipher_suites() - .with_safe_default_kx_groups() - .with_protocol_versions(&[&rustls::version::TLS13]) - .unwrap() + let mut tls_config = rustls::ServerConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) .with_no_client_auth() - .with_single_cert(certs, PrivateKey(key)) + .with_single_cert(certs, key.into()) .unwrap(); tls_config.max_early_data_size = u32::MAX; tls_config.alpn_protocols = vec![b"h3".to_vec()]; - let server_config = quinn::ServerConfig::with_crypto(Arc::new(tls_config.clone())); + let server_config = quinn::ServerConfig::with_crypto(Arc::new(QuicServerConfig::try_from(tls_config.clone()).unwrap())); #[derive(Debug, Clone)] struct ServiceFactory; diff --git a/foundations/src/heap.rs b/foundations/src/heap.rs deleted file mode 100644 index 931627e7..00000000 --- a/foundations/src/heap.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[global_allocator] -static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; diff --git a/foundations/src/http/server/stream/quic.rs b/foundations/src/http/server/stream/quic.rs index c4c67c4c..0b8da32b 100644 --- a/foundations/src/http/server/stream/quic.rs +++ b/foundations/src/http/server/stream/quic.rs @@ -82,6 +82,8 @@ impl Backend for QuicBackend { break; }; + let connection = connection.accept()?; + let span = tracing::trace_span!("connection", remote_addr = %connection.remote_address()); let _guard = span.enter(); tracing::trace!("connection accepted"); diff --git a/foundations/src/lib.rs b/foundations/src/lib.rs index 6b7a52fe..f06b3904 100644 --- a/foundations/src/lib.rs +++ b/foundations/src/lib.rs @@ -1,9 +1,6 @@ #[cfg(feature = "runtime")] pub mod runtime; -#[cfg(feature = "heap")] -pub mod heap; - #[cfg(feature = "macros")] pub use scuffle_foundations_macros::wrapped; diff --git a/foundations/src/settings/cli.rs b/foundations/src/settings/cli.rs index 92bfafce..5596ef8d 100644 --- a/foundations/src/settings/cli.rs +++ b/foundations/src/settings/cli.rs @@ -71,7 +71,7 @@ impl Cli { self } - fn load_file(file: &str, optional: bool) -> anyhow::Result> { + fn load_file(file: &str, optional: bool) -> anyhow::Result> { let contents = match std::fs::read_to_string(file) { Ok(contents) => contents, Err(err) => { @@ -84,7 +84,7 @@ impl Cli { }; let incoming = - serde_yaml::from_str(&contents).with_context(|| format!("Error parsing configuration file: {file}"))?; + toml::from_str(&contents).with_context(|| format!("Error parsing configuration file: {file}"))?; Ok(Some(incoming)) } @@ -97,7 +97,7 @@ impl Cli { .settings .parse() .context("failed to construct settings")? - .to_yaml_string() + .to_docs_string() .context("failed to serialize settings")?; std::fs::write(file, settings).with_context(|| format!("Error writing configuration file: {file}"))?; println!("Generated configuration file: {file}"); @@ -116,7 +116,7 @@ impl Cli { for (file, optional) in files { if let Some(value) = Self::load_file(&file, optional)? { - self.settings.merge(value).context("failed to merge configuration file")?; + self.settings.merge(value); } } diff --git a/foundations/src/settings/mod.rs b/foundations/src/settings/mod.rs index 12b20dd5..1d24e410 100644 --- a/foundations/src/settings/mod.rs +++ b/foundations/src/settings/mod.rs @@ -1,9 +1,3 @@ -use std::borrow::Cow; -use std::collections::HashMap; - -use serde_yaml::value::Tag; -use serde_yaml::Value; - #[cfg(feature = "cli")] pub mod cli; @@ -12,133 +6,55 @@ pub use scuffle_foundations_macros::{auto_settings, Settings}; #[derive(Debug, Clone)] pub struct SettingsParser { - root: serde_yaml::Value, + root: Option, _marker: std::marker::PhantomData, } -enum MergeDirective { - Unset, - Replace, - Merge, -} - -impl MergeDirective { - fn from_tag(tag: &Tag) -> Self { - if tag == "!replace" { - Self::Replace - } else if tag == "!merge" { - Self::Merge - } else { - Self::Unset - } - } -} - impl SettingsParser { - pub fn new(default: &S) -> serde_yaml::Result + pub fn new(default: &S) -> Result where S: serde::Serialize, { Ok(Self { - root: serde_yaml::to_value(default)?, + root: Some(toml::Value::try_from(default)?), _marker: std::marker::PhantomData, }) } - fn merge(&mut self, mut incoming: serde_yaml::Value) -> serde_yaml::Result<()> { - self.root.apply_merge()?; - incoming.apply_merge()?; - - let root = std::mem::take(&mut self.root); - self.root = self.merge_loop(root, incoming, MergeDirective::Unset); - Ok(()) + fn merge(&mut self, incoming: toml::Value) { + let root = self.root.take().unwrap(); + self.root = Some(self.merge_loop(root, incoming)); } - fn merge_loop(&self, root: serde_yaml::Value, incoming: serde_yaml::Value, merge: MergeDirective) -> serde_yaml::Value { + fn merge_loop(&self, root: toml::Value, incoming: toml::Value) -> toml::Value { match (root, incoming) { - (serde_yaml::Value::Mapping(mut first_map), serde_yaml::Value::Mapping(second_map)) => { + (toml::Value::Table(mut first_map), toml::Value::Table(second_map)) => { for (key, value) in second_map { - // If the key is tagged we should process it - let (key, merge) = match key { - serde_yaml::Value::Tagged(tagged) => (tagged.value, MergeDirective::from_tag(&tagged.tag)), - _ => (key, MergeDirective::Unset), - }; - let combined_value = if let Some(existing_value) = first_map.remove(&key) { - if matches!(merge, MergeDirective::Replace) { - value - } else { - self.merge_loop(existing_value, value, merge) - } + self.merge_loop(existing_value, value) } else { value }; first_map.insert(key, combined_value); } - serde_yaml::Value::Mapping(first_map) - } - (serde_yaml::Value::Sequence(mut first_seq), serde_yaml::Value::Sequence(second_seq)) => { - if matches!(merge, MergeDirective::Merge) { - first_seq.extend(second_seq); - } else { - first_seq = second_seq; - } - serde_yaml::Value::Sequence(first_seq) - } - (first, serde_yaml::Value::Tagged(tagged)) => self.handle_tagged(first, *tagged, merge), - (_, second) => second, - } - } - fn handle_tagged( - &self, - first: serde_yaml::Value, - tagged: serde_yaml::value::TaggedValue, - merge: MergeDirective, - ) -> serde_yaml::Value { - // If the tag is replace it doesn't matter what the first value is - // we just return the tagged value - let merge = match (merge, MergeDirective::from_tag(&tagged.tag)) { - (MergeDirective::Unset, merge) => merge, - (merge, _) => merge, - }; - if matches!(merge, MergeDirective::Replace) { - return tagged.value; - } - // If the first value is tagged then we should compare the tags - // and act accordingly - if let serde_yaml::Value::Tagged(first_tagged) = first { - if first_tagged.tag == tagged.tag { - let value = self.merge_loop(first_tagged.value, tagged.value, merge); - // Retag the value - return serde_yaml::Value::Tagged(Box::new(serde_yaml::value::TaggedValue { - tag: first_tagged.tag, - value, - })); - } else { - return serde_yaml::Value::Tagged(Box::new(tagged)); + toml::Value::Table(first_map) } - } - - // Otherwise we do not merge and retag the value - let value = self.merge_loop(first, tagged.value, merge); - if matches!(MergeDirective::from_tag(&tagged.tag), MergeDirective::Unset) { - serde_yaml::Value::Tagged(Box::new(serde_yaml::value::TaggedValue { tag: tagged.tag, value })) - } else { - value + (_, second) => second, } } - pub fn merge_str(&mut self, s: &str) -> serde_yaml::Result<()> { - let incoming = serde_yaml::from_str(s)?; - self.merge(incoming) + pub fn merge_str(&mut self, s: &str) -> Result<(), toml::de::Error> { + let incoming = toml::from_str(s)?; + self.merge(incoming); + Ok(()) } - pub fn parse(self) -> serde_yaml::Result + pub fn parse(self) -> Result where for<'de> S: serde::Deserialize<'de>, { - serde_yaml::from_value(self.root) + self.root.unwrap().try_into() } } @@ -148,150 +64,9 @@ pub use traits::{Settings, Wrapped}; /// Converts a settings struct to a YAML string including doc comments. /// If you want to provide doc comments for keys use to_yaml_string_with_docs. -pub fn to_yaml_string(settings: &T) -> Result { - to_yaml_string_with_docs(settings, &settings.docs()) -} - -type CowStr = Cow<'static, str>; -type DocMap = HashMap, Cow<'static, [CowStr]>>; - -/// Serializes a struct to YAML with documentation comments. -/// Documentation comments are provided in a DocMap. -pub fn to_yaml_string_with_docs(settings: &T, docs: &DocMap) -> Result { - let data = serde_yaml::to_value(settings)?; - let mut result = String::new(); - convert_recursive(docs, &mut Vec::new(), &data, &mut result, 0); - - if result.ends_with("\n\n") { - result.pop(); - } else if !result.ends_with('\n') { - result.push('\n'); - } - - Ok(result) -} - -macro_rules! push_indent { - ($result:expr, $indent:expr) => {{ - for _ in 0..$indent { - $result.push(' '); - } - }}; -} - -macro_rules! push_docs { - ($result:expr, $docs:expr, $stack:expr, $indent:expr) => {{ - $docs.get($stack).into_iter().flat_map(|s| s.iter()).for_each(|doc| { - push_indent!($result, $indent); - $result.push_str("# "); - $result.push_str(doc); - push_new_line!($result); - }); - }}; -} - -macro_rules! push_key { - ($result:expr, $key:expr, $indent:expr) => {{ - push_indent!($result, $indent); - $result.push_str($key); - $result.push_str(":"); - }}; -} - -macro_rules! push_new_line { - ($result:expr) => {{ - if !$result.ends_with('\n') { - $result.push('\n'); - } - }}; +pub fn to_docs_string(settings: &T) -> Result { + toml::to_string_pretty(settings) } -fn convert_recursive(docs: &DocMap, stack: &mut Vec, value: &Value, result: &mut String, indent: usize) { - // Append doc comments at the current level - if matches!(value, Value::Mapping(_) | Value::Sequence(_)) { - stack.push(">".into()); - push_docs!(result, docs, stack, indent); - stack.pop(); - } - - match value { - Value::Mapping(map) => { - for (key, val) in map { - let key_str = key.as_str().unwrap_or_default(); - stack.push(Cow::from(key_str.to_owned())); - - push_docs!(result, docs, stack, indent); - push_key!(result, key_str, indent); - - // We dont want to push a new line if the item is a Tagged value - if matches!(val, Value::Mapping(_) | Value::Sequence(_)) { - push_new_line!(result); - } - - convert_recursive(docs, stack, val, result, indent + 2); - - push_new_line!(result); - - if (val.is_mapping() || val.is_sequence()) && !result.ends_with("\n\n") { - result.push('\n'); - } - - stack.pop(); - } - - if map.is_empty() { - if result.ends_with('\n') { - result.pop(); - } - result.push_str(" {}"); - } - } - Value::Sequence(seq) => { - for (idx, val) in seq.iter().enumerate() { - stack.push(Cow::from(idx.to_string())); - - push_docs!(result, docs, stack, indent); - - push_indent!(result, indent); - result.push('-'); - - if val.is_sequence() { - push_new_line!(result); - } - - convert_recursive(docs, stack, val, result, indent + 2); - - stack.pop(); - - push_new_line!(result); - } - - if seq.is_empty() { - if result.ends_with('\n') { - result.pop(); - } - result.push_str(" []"); - } - } - Value::Tagged(tagged) => { - result.push(' '); - result.push_str(&tagged.tag.to_string()); - - if tagged.value.is_mapping() || tagged.value.is_sequence() { - push_new_line!(result); - } - - convert_recursive(docs, stack, &tagged.value, result, indent); - } - _ => { - result.push(' '); - result.push_str(serde_yaml::to_string(value).unwrap_or_default().trim_end()); - // TODO(troy): figure out a way to do sub-docs for scalars so that - // the format isnt so janky - - // stack.push(">".into()); - // push_docs!(result, docs, stack, indent); - // stack.pop(); - } - } -} +// type CowStr = Cow<'static, str>; +// type DocMap = HashMap, Cow<'static, [CowStr]>>; diff --git a/foundations/src/settings/traits.rs b/foundations/src/settings/traits.rs index 1c1faacc..889dd9ad 100644 --- a/foundations/src/settings/traits.rs +++ b/foundations/src/settings/traits.rs @@ -6,7 +6,7 @@ use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, LinkedList, VecDeque}; use std::hash::Hash; -use super::to_yaml_string; +use super::to_docs_string; pub trait Settings { #[doc(hidden)] @@ -24,11 +24,11 @@ pub trait Settings { docs } - fn to_yaml_string(&self) -> Result + fn to_docs_string(&self) -> Result where Self: serde::Serialize + Sized, { - to_yaml_string(self) + to_docs_string(self) } } diff --git a/foundations/src/telemetry/opentelemetry/node.rs b/foundations/src/telemetry/opentelemetry/node.rs index f26471e4..2495057a 100644 --- a/foundations/src/telemetry/opentelemetry/node.rs +++ b/foundations/src/telemetry/opentelemetry/node.rs @@ -281,7 +281,7 @@ impl SpanNode { span.parent_span_id = self.mapped_parent_id.unwrap_or(SpanId::INVALID); span.span_context = SpanContext::new(self.trace_id, self.mapped_id, TraceFlags::SAMPLED, false, TraceState::NONE); span.events.events = self.events.into_iter().map(|e| e.into_data()).collect(); - span.links.links = self.links.into_iter().map(|link| Link::new(link, Vec::new())).collect(); + span.links.links = self.links.into_iter().map(|link| Link::new(link, Vec::new(), 0)).collect(); span } diff --git a/foundations/src/telemetry/pprof/heap.rs b/foundations/src/telemetry/pprof/heap.rs deleted file mode 100644 index 37878ef5..00000000 --- a/foundations/src/telemetry/pprof/heap.rs +++ /dev/null @@ -1,38 +0,0 @@ -use anyhow::Context; - -#[allow(non_upper_case_globals)] -#[export_name = "malloc_conf"] -#[cfg(not(feature = "disable-jemalloc-config"))] -pub static malloc_conf: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:19,abort_conf:true\0"; - -pub struct Heap; - -impl Default for Heap { - fn default() -> Self { - Self::new() - } -} - -impl Heap { - pub fn new() -> Self { - Self - } - - /// Capture a heap profile for the given duration. - /// The profile can be analyzed using the `pprof` tool. - /// Warning: This method is blocking and may take a long time to complete. - /// It is recommended to run it in a separate thread. - pub fn capture(&mut self) -> anyhow::Result> { - let mut profiler = jemalloc_pprof::PROF_CTL - .as_ref() - .ok_or_else(|| anyhow::anyhow!("jemalloc profiling is not available"))? - .blocking_lock(); - - if !profiler.activated() { - // profiler.deactivate().context("failed to deactivate jemalloc profiling")?; - profiler.activate().context("failed to activate jemalloc profiling")?; - } - - profiler.dump_pprof().context("failed to dump jemalloc pprof profile") - } -} diff --git a/foundations/src/telemetry/pprof/mod.rs b/foundations/src/telemetry/pprof/mod.rs index 5db456c0..49995d20 100644 --- a/foundations/src/telemetry/pprof/mod.rs +++ b/foundations/src/telemetry/pprof/mod.rs @@ -1,10 +1,5 @@ -#[cfg(feature = "pprof-heap")] -mod heap; - #[cfg(feature = "pprof-cpu")] mod cpu; #[cfg(feature = "pprof-cpu")] pub use cpu::Cpu; -#[cfg(feature = "pprof-heap")] -pub use heap::Heap; diff --git a/image-processor/Cargo.toml b/image-processor/Cargo.toml index bf90295c..ef55bbbd 100644 --- a/image-processor/Cargo.toml +++ b/image-processor/Cargo.toml @@ -32,14 +32,14 @@ png = "0.17" num_cpus = "1.16" bytes = "1.0" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } -fast_image_resize = "3.0.4" +fast_image_resize = "4" chrono = { version = "0.4", features = ["serde"] } url = { version = "2", features = ["serde"] } http = "1" urlencoding = "2" humantime-serde = "1" -scuffle-foundations = { version = "*", path = "../foundations", features = [] } +scuffle-foundations = { version = "*", path = "../foundations", features = ["disable-jemalloc-config"] } scuffle-ffmpeg = { version = "*", path = "../ffmpeg", features = ["tracing"] } scuffle-image-processor-proto = { version = "*", path = "./proto", features = ["server", "serde"]} diff --git a/image-processor/proto/scuffle/image_processor/types.proto b/image-processor/proto/scuffle/image_processor/types.proto index b96ca016..99a70597 100644 --- a/image-processor/proto/scuffle/image_processor/types.proto +++ b/image-processor/proto/scuffle/image_processor/types.proto @@ -31,46 +31,46 @@ message DrivePath { // The resize method determines how the image processor should resize the image. enum ResizeMethod { // Fit will resize the image to fit within the desired dimensions without changing the aspect ratio. - Fit = 0; + ResizeMethodFit = 0; // Stretch will stretch the image to fit the desired dimensions. (This will change the aspect ratio of the image.) - Stretch = 1; + ResizeMethodStretch = 1; // Pad will resize the image to fit the desired dimentions and pad the bottom left of the image with the background color if necessary. - PadBottomLeft = 2; + ResizeMethodPadBottomLeft = 2; // Pad will resize the image to fit the desired dimentions and pad the bottom right of the image with the background color if necessary. - PadBottomRight = 3; + ResizeMethodPadBottomRight = 3; // Pad will resize the image to fit the desired dimentions and pad the top left of the image with the background color if necessary. - PadTopLeft = 4; + ResizeMethodPadTopLeft = 4; // Pad will resize the image to fit the desired dimentions and pad the top right of the image with the background color if necessary. - PadTopRight = 5; + ResizeMethodPadTopRight = 5; // Pad will resize the image to fit the desired dimentions and pad the center of the image with the background color if necessary. - PadCenter = 6; + ResizeMethodPadCenter = 6; // Pad will resize the image to fit the desired dimentions and pad the center of the image with the background color if necessary. - PadCenterRight = 7; + ResizeMethodPadCenterRight = 7; // Pad will resize the image to fit the desired dimentions and pad the center of the image with the background color if necessary. - PadCenterLeft = 8; + ResizeMethodPadCenterLeft = 8; // Pad will resize the image to fit the desired dimentions and pad the top center of the image with the background color if necessary. - PadTopCenter = 9; + ResizeMethodPadTopCenter = 9; // Pad will resize the image to fit the desired dimentions and pad the bottom center of the image with the background color if necessary. - PadBottomCenter = 10; + ResizeMethodPadBottomCenter = 10; // Pad will resize the image to fit the desired dimentions and pad the top of the image with the background color if necessary, the left and right will be unchanged. - PadTop = 11; + ResizeMethodPadTop = 11; // Pad will resize the image to fit the desired dimentions and pad the bottom of the image with the background color if necessary, the left and right will be unchanged. - PadBottom = 12; + ResizeMethodPadBottom = 12; // Pad will resize the image to fit the desired dimentions and pad the left of the image with the background color if necessary, the top and bottom will be unchanged. - PadLeft = 13; + ResizeMethodPadLeft = 13; // Pad will resize the image to fit the desired dimentions and pad the right of the image with the background color if necessary, the top and bottom will be unchanged. - PadRight = 14; + ResizeMethodPadRight = 14; } // The resize algorithm determines the algorithm used to resize the image. enum ResizeAlgorithm { - Nearest = 0; - Box = 1; - Bilinear = 2; - Hamming = 3; - CatmullRom = 4; - Mitchell = 5; - Lanczos3 = 6; + ResizeAlgorithmNearest = 0; + ResizeAlgorithmBox = 1; + ResizeAlgorithmBilinear = 2; + ResizeAlgorithmHamming = 3; + ResizeAlgorithmCatmullRom = 4; + ResizeAlgorithmMitchell = 5; + ResizeAlgorithmLanczos3 = 6; } // Limits are used to determine how much processing time and resources the image processor should use. @@ -207,15 +207,15 @@ message AnimationConfig { enum OutputQuality { // Auto quality output. (default) - Auto = 0; + OutputQualityAuto = 0; // High quality output. (large file size) - High = 1; + OutputQualityHigh = 1; // Medium quality output. (medium file size) - Medium = 2; + OutputQualityMedium = 2; // Low quality output. (smaller file size) - Low = 3; + OutputQualityLow = 3; // Lossless output. (very large file size) - Lossless = 4; + OutputQualityLossless = 4; } message OutputFormatOptions { diff --git a/image-processor/src/drive/mod.rs b/image-processor/src/drive/mod.rs index d71a45b6..847b670f 100644 --- a/image-processor/src/drive/mod.rs +++ b/image-processor/src/drive/mod.rs @@ -99,6 +99,7 @@ impl Drive for AnyDrive { } async fn write(&self, path: &str, data: Bytes, options: Option) -> Result<(), DriveError> { + tracing::info!("writing to drive: {}", path); match self { AnyDrive::Local(drive) => drive.write(path, data, options).await, AnyDrive::S3(drive) => drive.write(path, data, options).await, diff --git a/image-processor/src/management/mod.rs b/image-processor/src/management/mod.rs index 31f25584..f1858122 100644 --- a/image-processor/src/management/mod.rs +++ b/image-processor/src/management/mod.rs @@ -49,7 +49,7 @@ impl ManagementServer { let file_format = file_format::FileFormat::from_bytes(&input_upload.binary); DecoderFrontend::from_format(file_format).map_err(|err| Error { - code: ErrorCode::InvalidInput as i32, + code: ErrorCode::Decode as i32, message: format!("input_upload.binary: {err}"), })?; diff --git a/image-processor/src/worker/process/mod.rs b/image-processor/src/worker/process/mod.rs index ea67d78f..cc686bec 100644 --- a/image-processor/src/worker/process/mod.rs +++ b/image-processor/src/worker/process/mod.rs @@ -113,6 +113,8 @@ impl ProcessJob { pub async fn process(&self, global: Arc) { tracing::info!("starting job"); + let start = tokio::time::Instant::now(); + crate::events::on_start(&global, &self.job).await; let mut future = self.process_inner(&global); @@ -157,7 +159,7 @@ impl ProcessJob { match result { Ok(success) => { - tracing::info!("job completed"); + tracing::info!("job completed in {:?}", start.elapsed()); crate::events::on_success(&global, &self.job, success).await; } Err(err) => { diff --git a/image-processor/src/worker/process/resize.rs b/image-processor/src/worker/process/resize.rs index 44f2ed44..b95e1682 100644 --- a/image-processor/src/worker/process/resize.rs +++ b/image-processor/src/worker/process/resize.rs @@ -1,7 +1,5 @@ -use std::num::NonZeroU32; +use fast_image_resize::{self as fr, images::{CroppedImage, CroppedImageMut}, ResizeOptions}; -use fast_image_resize as fr; -use fr::CropBox; use rgb::ComponentBytes; use scuffle_image_processor_proto::{output, scaling, Output, ResizeAlgorithm, ResizeMethod}; @@ -33,12 +31,12 @@ impl Dimensions { } enum ImageRef<'a> { - Ref((&'a fr::Image<'a>, Option)), - Owned((fr::Image<'a>, Option)), + Ref((&'a fr::images::Image<'a>, CropBox)), + Owned((fr::images::Image<'a>, CropBox)), } impl ImageRef<'_> { - fn crop(&self) -> Option { + fn crop(&self) -> CropBox { match self { ImageRef::Owned((_, c)) => *c, ImageRef::Ref((_, c)) => *c, @@ -47,7 +45,7 @@ impl ImageRef<'_> { } impl<'a> std::ops::Deref for ImageRef<'a> { - type Target = fr::Image<'a>; + type Target = fr::images::Image<'a>; fn deref(&self) -> &Self::Target { match self { @@ -68,6 +66,7 @@ pub struct ImageResizer { resize_method: ResizeMethod, output_frames: Vec, disable_resize_chaining: bool, + method: fr::ResizeAlg, } #[derive(Debug, Clone, Copy)] @@ -77,12 +76,26 @@ pub struct ResizeOutputTarget { pub scale: Option, } +#[derive(Debug, Clone, Copy)] +struct CropBox { + left: u32, + top: u32, + width: u32, + height: u32, +} + +impl CropBox { + pub fn new(left: u32, top: u32, width: u32, height: u32) -> Self { + Self { left, top, width, height } + } +} + #[derive(thiserror::Error, Debug)] pub enum ResizeError { #[error("crop: {0}")] Crop(#[from] fr::CropBoxError), - #[error("different pixels: {0}")] - DifferentPixels(#[from] fr::DifferentTypesOfPixelsError), + #[error("resize: {0}")] + Resize(#[from] fr::ResizeError), #[error("buffer: {0}")] Buffer(#[from] fr::ImageBufferError), #[error("crop dimensions are larger than the input dimensions")] @@ -252,7 +265,10 @@ impl ImageResizer { } Ok(Self { - resizer: fr::Resizer::new(match output.resize_algorithm() { + resizer: fr::Resizer::new(), + input_dims, + cropped_dims, + method: match output.resize_algorithm() { ResizeAlgorithm::Nearest => fr::ResizeAlg::Nearest, ResizeAlgorithm::Box => fr::ResizeAlg::Convolution(fr::FilterType::Box), ResizeAlgorithm::Bilinear => fr::ResizeAlg::Convolution(fr::FilterType::Bilinear), @@ -260,14 +276,12 @@ impl ImageResizer { ResizeAlgorithm::CatmullRom => fr::ResizeAlg::Convolution(fr::FilterType::CatmullRom), ResizeAlgorithm::Mitchell => fr::ResizeAlg::Convolution(fr::FilterType::Mitchell), ResizeAlgorithm::Lanczos3 => fr::ResizeAlg::Convolution(fr::FilterType::Lanczos3), - }), - input_dims, - cropped_dims, + }, crop: output.crop.as_ref().map(|crop| CropBox { - left: crop.x as f64, - top: crop.y as f64, - width: crop.width as f64, - height: crop.height as f64, + left: crop.x, + top: crop.y, + width: crop.width, + height: crop.height, }), resize_method: output.resize_method(), resize_dims: resize_targets, @@ -289,9 +303,9 @@ impl ImageResizer { return Err(ResizeError::MismatchedDimensions); } - let input_image = fr::Image::from_slice_u8( - NonZeroU32::new(frame.image.width() as u32).unwrap(), - NonZeroU32::new(frame.image.height() as u32).unwrap(), + let input_image = fr::images::Image::from_slice_u8( + frame.image.width() as u32, + frame.image.height() as u32, // Safety: The input_image type is non_mut which disallows mutable actions on the underlying buffer. unsafe { let buf = frame.image.buf().as_bytes(); @@ -304,44 +318,48 @@ impl ImageResizer { let output_dims = self.outputs.iter().rev().map(|output| output.dimensions); let output_frames = self.output_frames.iter_mut().rev(); - let mut previous_image = ImageRef::Ref((&input_image, self.crop)); + let source_crop = self.crop.unwrap_or(CropBox { + left: 0, + top: 0, + width: input_image.width(), + height: input_image.height(), + }); + + let resize_options = ResizeOptions::new().resize_alg(self.method); + + let mut previous_image = ImageRef::Ref((&input_image, source_crop)); for ((resize_dims, output_dims), output_frame) in resize_dims.zip(output_dims).zip(output_frames) { output_frame.duration_ts = frame.duration_ts; - let mut target_image = fr::Image::from_slice_u8( - NonZeroU32::new(output_dims.width as u32).unwrap(), - NonZeroU32::new(output_dims.height as u32).unwrap(), + let mut target_image = fr::images::Image::from_slice_u8( + output_dims.width as u32, + output_dims.height as u32, output_frame.image.buf_mut().as_mut_slice().as_bytes_mut(), fr::PixelType::U8x4, )?; - let mut view = previous_image.view(); - if let Some(crop) = previous_image.crop() { - view.set_crop_box(crop)?; - } + let source_crop = previous_image.crop(); + let source_view = CroppedImage::new(&*previous_image, source_crop.left, source_crop.top, source_crop.width, source_crop.height)?; - let (mut target_view, target_crop) = if resize_dims != output_dims { - let (left, top, width, height) = resize_method_to_crop_dims(self.resize_method, output_dims, resize_dims)?; - ( - target_image.view_mut().crop(left, top, width, height)?, - Some(CropBox { - left: left as f64, - top: top as f64, - width: width.get() as f64, - height: height.get() as f64, - }), - ) + let target_crop = if resize_dims != output_dims { + resize_method_to_crop_dims(self.resize_method, output_dims, resize_dims)? } else { - (target_image.view_mut(), None) + CropBox { + left: 0, + top: 0, + width: resize_dims.width as u32, + height: resize_dims.height as u32, + } }; + let mut target_view = CroppedImageMut::new(&mut target_image, target_crop.left, target_crop.top, target_crop.width, target_crop.height)?; - self.resizer.resize(&view, &mut target_view)?; + self.resizer.resize(&source_view, &mut target_view, Some(&resize_options))?; // If we are upscaling then we dont want to downscale from an upscaled image. // Or if the user has explicitly disabled the resize chain. if self.disable_resize_chaining || self.cropped_dims < resize_dims { - previous_image = ImageRef::Ref((&input_image, self.crop)); + previous_image = ImageRef::Ref((&input_image, source_crop)); } else { previous_image = ImageRef::Owned((target_image, target_crop)); } @@ -355,7 +373,7 @@ fn resize_method_to_crop_dims( resize_method: ResizeMethod, padded_dims: Dimensions, target_dims: Dimensions, -) -> Result<(u32, u32, NonZeroU32, NonZeroU32), ResizeError> { +) -> Result { let check = |cmp: bool, msg: &'static str| if cmp { Ok(()) } else { Err(ResizeError::Internal(msg)) }; check(padded_dims.width >= target_dims.width, "padded width less then target width")?; @@ -364,16 +382,31 @@ fn resize_method_to_crop_dims( "padded height less then target height", )?; - let center_x = (padded_dims.width - target_dims.width) as u32 / 2; - let center_y = (padded_dims.height - target_dims.height) as u32 / 2; - let left = (padded_dims.width - target_dims.width) as u32; - let top = (padded_dims.height - target_dims.height) as u32; - let bottom = 0; - let right = 0; - let zero = 0; + let delta_x = (padded_dims.width - target_dims.width) as u32; + let delta_y = (padded_dims.height - target_dims.height) as u32; + let center_x = delta_x / 2; + let center_y = delta_y / 2; + + let width = target_dims.width as u32; + let height = target_dims.height as u32; + + if width == 0 || height == 0 { + return Err(ResizeError::Internal("width or height is zero")); + } + + let left = match resize_method { + ResizeMethod::PadLeft | ResizeMethod::PadTopLeft | ResizeMethod::PadBottomLeft => 0, + ResizeMethod::PadRight | ResizeMethod::PadTopRight | ResizeMethod::PadBottomRight => delta_x, + ResizeMethod::PadCenter | ResizeMethod::PadCenterLeft | ResizeMethod::PadCenterRight => center_x, + _ => 0, + }; - let width = NonZeroU32::new(target_dims.width as u32).ok_or(ResizeError::Internal("target width 0"))?; - let height = NonZeroU32::new(target_dims.height as u32).ok_or(ResizeError::Internal("target height 0"))?; + let top = match resize_method { + ResizeMethod::PadTop | ResizeMethod::PadTopLeft | ResizeMethod::PadTopRight => 0, + ResizeMethod::PadBottom | ResizeMethod::PadBottomLeft | ResizeMethod::PadBottomRight => delta_y, + ResizeMethod::PadCenter | ResizeMethod::PadTopCenter | ResizeMethod::PadBottomCenter => center_y, + _ => 0, + }; match resize_method { ResizeMethod::Fit => Err(ResizeError::Internal("fit should never be called here")), @@ -382,38 +415,28 @@ fn resize_method_to_crop_dims( check( target_dims.width != padded_dims.width, "pad left should only be called for width padding", - )?; - Ok((left, zero, width, height)) + ) } ResizeMethod::PadRight => { check( target_dims.height != padded_dims.height, "pad right should only be called for height padding", - )?; - Ok((right, zero, width, height)) + ) } ResizeMethod::PadBottom => { check( target_dims.width != padded_dims.width, "pad bottom should only be called for height padding", - )?; - Ok((zero, bottom, width, height)) + ) } ResizeMethod::PadTop => { check( target_dims.width != padded_dims.width, "pad top should only be called for height padding", - )?; - Ok((zero, top, width, height)) + ) } - ResizeMethod::PadBottomCenter => Ok((center_x, bottom, width, height)), - ResizeMethod::PadTopCenter => Ok((center_x, top, width, height)), - ResizeMethod::PadBottomLeft => Ok((left, bottom, width, height)), - ResizeMethod::PadBottomRight => Ok((right, bottom, width, height)), - ResizeMethod::PadTopLeft => Ok((left, top, width, height)), - ResizeMethod::PadTopRight => Ok((right, top, width, height)), - ResizeMethod::PadCenter => Ok((center_x, center_y, width, height)), - ResizeMethod::PadCenterLeft => Ok((left, center_y, width, height)), - ResizeMethod::PadCenterRight => Ok((right, center_y, width, height)), - } + _ => Ok(()), + }?; + + Ok(CropBox::new(left, top, width, height)) } From 0e0b73d43576746cfbe3d899d799945509bba3d5 Mon Sep 17 00:00:00 2001 From: Troy Benson Date: Fri, 21 Jun 2024 22:57:45 +0000 Subject: [PATCH 21/21] fix: remove jemalloc config feature --- foundations/Cargo.toml | 2 -- image-processor/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/foundations/Cargo.toml b/foundations/Cargo.toml index 9390b5d8..e5710901 100644 --- a/foundations/Cargo.toml +++ b/foundations/Cargo.toml @@ -68,8 +68,6 @@ socket2 = { version = "0.5", optional = true } [features] -disable-jemalloc-config = [] - context = [ "pin-project", "tokio-util", diff --git a/image-processor/Cargo.toml b/image-processor/Cargo.toml index ef55bbbd..442a0acb 100644 --- a/image-processor/Cargo.toml +++ b/image-processor/Cargo.toml @@ -39,7 +39,7 @@ http = "1" urlencoding = "2" humantime-serde = "1" -scuffle-foundations = { version = "*", path = "../foundations", features = ["disable-jemalloc-config"] } +scuffle-foundations = { version = "*", path = "../foundations", features = [] } scuffle-ffmpeg = { version = "*", path = "../ffmpeg", features = ["tracing"] } scuffle-image-processor-proto = { version = "*", path = "./proto", features = ["server", "serde"]}

    #qAt)w1ZcK5;InRmq zvNl+|HW$5C%dNrds-6fde9DGm@7z@uYb?;~wtybqAobhfkT*3WLN|(Y^5>9?Hl|nY z8S2i?M=f9`2S(V}-rPXA;!F@hjzSiL$hWZt!-)zy({;1e<~m3TC*G((E?Ha#Ef|x>n!3@@$p4T7Z*5yMY!8WQJURi9f&|L zC8IbRi{&P0TCdw*%@uM7$5tzgtsE$y5I_Bb)Cz36193C_klYtOBh6_;3siNlt0PIL z2F-+b4ZRR_*)WVDCfCO22Ci3Zf8{WOyzz*gS1psiFb#qIF>DO@U;(tCwj3>C=>S#* z#*c0-0R}+?x}36Fo7(hVsAa8J*6PMWEVP?z+K{2wm^sujPhHFZudpiI;DSFjasf1r zV|WH|oX*9rVR*>s;tpL4hTL$u<(3tzw!QAJdmaA+3h1=D38GR(SuF_SB}vEnr~ChQ z>BIH_3n2asYV1~h_Cip3AI77=^L2uG!L=wT0Y((dhEc#~zBy`ig$s(!m09~cQ?-Zs zuZ?{B@NToK#kZf{rW0?+65L00=E(-EU^YQcO-7U)0000000000n*acR0015U0034{ zI3Ro+007iN0G$CUoB)6TK2acyL;~kP)BpgWmNIZHf$#^J;%s@-hFSO+Aqyzs4wWsh6HT~y)YrtR8eez>W_1-~$ zZtexxFYZ1dPLs3>{Qu7L3-|tO|JZm2{g3@u?LWW=^N;YJjCq*-kNwB6Kj-(Y2mX(4 zzqFtGJ=#BIzw{>8Q3S(P+0WRksX9hKcT4OZEq9c)T*74knR3IK{ zS^gB6SJf?z1Nz06$B-0aN?wQ}L9HIFeJB|aj4-uC%_@`~K@|p}-8bf^pE--5HmR6D zb-PdPz3>vX6rrt7`!B=8Z~+&Pik*0z2&m98{#Fm5l{mDTq=3f*^?#gx3TZc0QvCm( zGHeg7C#4>UgSUsgCuBAc712~bcVZHur_OdHU}ags?i+w1o^*sOs#DQQhF#-dm-a!(Glook@Zq>C1A<>~!;<5`gNFpPt8roxQ0D*V z6x1mnpva#FQ|{#n*ERb*VwQ)~!W|K0du*BGoZxkARh4d^_lBD}g2$Mb*)#QCW=3IU(KXXP_kUJ-lN!t8n$K51;%#P@^VGrC;$(F=Z>>x`g% z!>ahU^}yQ&eu?@~OYG-TzG$GK6$JFYUBASG9Q{;a|+sNhko5!)cRH zjZLqd3`n|xE#B3QPfMb(QX83tP{5)%V@$>k4n^QNdRFoC1dFWx!!R&=ebPm(_X~n+ zn&0!dPMOdrbf@k8c^sMcF=EHZYjxw_@f5B^HnamSGNBOwBsu?B;VBGu5R}96x5y!{ z2BG57w!+ry53Eg}np0=X_{+R9{Sn3~fuP}h%K;HYWF`1<5R*UNu^m-yO|#~~r)D|- zP4p>cs44wK#HS&2!$MkufjgW^_0o_!NPJ^J2CaoB8S&y9(Ee#;{uTD9mQ4cePicrY z@8ac4SgL;gU7+-1)#2M?@e2B2;1b$wc&pQOZ1M=aB1;?bHmMwEn_BzKqYVC_#QE>J z<~C4|^SLmBt1498Bm{Ee(Je0oM&k59$^`sN#QkGx8C zvYLwu`dV(tM=8m$m`bYq^@4#m&6OL7x3xv~rn_C$3u?k%(S24T`rS!I{;fqfIB-ch z$=YbS?Vs;%C}tg|49|uxpcrL#=9p|?yvx*bH;I$!~v3c|XR0#RkBuU8I z*W@kppOXH)R$NSs5pDH~C}tsaIWIdn)hL_sg#^HYEDv7WGzZFRDE0)j?X}i5mvEi5 z3_|+bJ>(5>uBCrqgTEa|Y=h>2fvBHw$ydbEd+!Sz8pP4{E@XjtmWWeY41 zF{_}ks3Ch}J3^ET<;vw|Odz8p*Keh`i^AJ*mgLlKU<-gYj_J6_rp(*fMr+e2U6i(- z^yrZGovhjYsIApoZSc_;9rFSF<&(g(?#H2#R%<#kx4$A-&ApxB3VMe62~T@*QB-B|BeH21N9&;DhgaoK4@? zdrF?k60c8Z%B>QhB)0lr6H&l89&C>q+|;)U!;+==VxK*YYt;|V6`W2?YD<12t5pdjH(OMu##_Pk6SiJ|rx>q06J6p)(vO8UoU#157JINLFN{0( z#grpB^6(XG>A*@qfkm=bR~#58{*v}kQ$P?=7?&X;5S{-ZElQQ32*OdzDjX_EFjJ-K zilR-4|2zIY#7LPCG|&iIPGKf3j>SdOJT0I~NVyv})gT}~F4~fgAy~C_K z!Y2uTc!qxYq^sRHQ?6!hwy|ehsR32|DZTf2If|n^S{cPgAT@r$WS&TMyjv3^E&YQvzB`C5P=xUY{?Kk`h8$@?_R7=!s&s`am?2y55qhBBCB3A> z7HbFQD;9;m-u3#0Mf>rj@tR((S*8H+kdJG+J9rfPiTlVc$o>?3Is&$(wfYdhgw5PP zjx3`oH*Egi5!%Xf1DAvKJpJSaJ5@11bW#`0QgH(=%)(yF zr}P3xOK04fTUmUv#RtHd&ikMj>Aw~v9Flc*z{O*q%{#Yf)X}*hg0S3TTZ_WJ3+7zD zVTP`F|51cfNoY}co||I37stY?T>^nmqIbGg9B1SE{|OdWyyc$}#ITw%``TwV#YCBY zQNIYV4c77!ne!5y!HOZa=&yz9%V65WfW0iBrpz~5DsY3VsK_D4XG5zmJR7hUXPK7< zE(*yx6{XmPwX48&x6~e8d7o1Ea78wnRnG42ugDGzdKVC1xe$WGl)-|@5>kb*o&)-| zm+)}Ue2yqMXD@5qkv_U^X`LudWSFtDbby&m3>P+f05PEq2S{SQ-rWdfaY__V{ApDk zBB!r1Y;6;{bkcLpBuS5K2OHgE2l~A%rOYhXGEYMp8{8RixTi%1^J=p>RKb}U zNO6PleSVXUv-IAC00riX0(ZA#WX6ZPZ=swMm`y&M^N?OUGHg2t&K*{jjlUGqEbDQV zIEk@}wJqqX_qAPM@B(P{VJ%(SRb+E7pAxJZP-fb~tIrv2$***|!6>+zeXDHA`5lXx zxBnY5=-udpOk5eGK+IB>p&<<3R7THylEgsiCwkQ_oB?$EA*u+$_jy-E-Qmkb#j0de zBO>P!PzUZi@==BHjb%MoA1r|t)kgf<0z%(!NR5UKB^-UVaeeQth1Bs=uTa zn0d(d6rrEyRE-iy2SB3)i5&3STo&su^*U`R;6*uIWc4I*RA7|}=;En-(@GC(Za62o zGhqKr@A|iPX1^%>cV8M`aYJG0g+5i;2V@|jv@$p85aI$Wt+y6%urJeP;nKx>SWvd( z(0YievapwFdfPpHTaGWV7NUhs1ssGS~s4fG|T530O0dp4lnQa%piI<_yc@*-f* zdv|RzsQ&%x<+)rKql*?U3PiF?=hGPGDJMUTzjN;32E zZ-&q-3$_*R>cihto0dBOp3!T77x&d?<}Cf|fw6rUJY;OJd`w=%lB{#jPiIfmqZr~K zWLD4!Z4`#LW9$osEdlW4he~0|D~UkJnxZl;L8$JjgM4FkzZmNSEy1W#U#}LvFM=7j zifb=K%A%KcU&MmCpV2&|<>r3lHF-`J``gobRX^Qa;9}wA-Zfs|jsOMEX_1R4=!+c^ zsh;<}5Z|FSk?x=Kkbbqd+oh+ue}NB@%^72F1}w^{qbh&GyH^G=>`Ce=98UX13j6v7D~kEOa!)lK)2&TW$p z=r+jssoB+A{9ZB|qJU4${!H{P;oc)xnkP^ukD!=1S%sL5=w2NE43w&&;${{xnpKP6 z+93u?tj@j(IkiJvIK88Nr#k`%Z*h2pU~jC&DftVNHy??pP}n6-gQ^1#kmXY5=tqVB zacmGkrxL$w+a{@MZ}<4CrY%QZ1xHzkweD1HJ%zjN%W4=BUD1E^ zSp6pYg8nEw>KK|u6giZBa#w;vM|t%A@{VA}MWX_?v$2y}3%ek%KJVO&LIsAeKd%J& z=(OG1i@K++@qK#_QU|8655*PR?$Ll30)JY(BnX0_=R|b}Z64ZQV}qF7$gzfL%j22< zH800BpLc8LJh+5M(lwZ=Vuy9f+HfjT{Xix+Bf*eyPYcbzTXQdhIbYVlyfF7qEG)2r z9Z_(CZ_~>;ErU+Z2X`nvZ0M)yqwY}Toe0GepmVl28keR(ZP249p_|(4`e_)Sn|7}` z`4#pz3`keS=yV-e@o;s|bI`ROQvq(7;8jjhB>?SMu7K;Rnc z8b=OijUSZSFYB$yB^4qM_B=}}_AOmop26e14-5uV;z}#dJ=T+n&CCnZvQ?eQMP)3| zY46kDN6CpnR4io9&z5YC1Y^`Iw8(mZ3f6WFE?gdSf2Ro=u#b6uGY_Q2RF=b(6*1_W zv8&vcCJtl>=SeGGPGQFn@%*!Br-T1q*Ql=u?hB}2`cv6^sRHYv4*)9ISC{tL-Z4pyOe{^xr})gV6z&vN@ye5;3N= zgv-u$_+mnPDwMnEXNOANiPf9OUEA1?(iNRTCg7OLGz)+IFEp>oXX;V(@3_HRdN;!* zG_Z|&-ucsbTge)_5;!CCMp%W?ERIUE)(V|KD2T_bBS`m5z6`jaTd8`Q>5I&M>fwDBZCGjK9#EZ?uAZYK&098MREo1mw zW4Oa2d&f*$b~=3Cz$w<9Q%`VObbS%aYug&W-7G6qZAY^(%mL#I zaQOmFkO9?$NqU#yeY7@j)Q7*x0(L+SG00zU*Dd5ml;Ih8dRuk~h+M0G!sQ)`>TNER zO9g`wFuw`U!56opB8`3)B7{3F>Dx#pZ=Sz$u7cLB<7G;G*?a2OF`Tb}Eu#q5yTT6t zw589eAI&ULw_25HuwQ8{1{4n(bk}0?2}?76ElX%4HB?4*kh!=8lKvbnH8o^#sqX^0 z)VzU$^~(_f$D(_PuhfD!G;6SGw^60~q6o68%?RpYK@QoNWxxFz0%hLC07^X%|@7E%@@eNpCOOO-zz zsR4#*e6cu!F%f6$76&DHeX)p^B?`C;JmbFVnqB%Tl zc(kl=*6N;mJkwLvMtNLUx$cIY21ESG++Y|QZX7sVxy~dh`12`X zP4xcbRT*R`ETEb=m{z#aE5vru1Yp6=cWVEJqQ#_ zcZGTwM14O%LzmBcA zhGPY25MUDFmMwf^oWIvUjPXcsLho|#z3QKTo=+lwv$h%_nIOFXtVt3`&^wFBy10;D z$+SA{|B}Zww}@>TfL=q+fU6CL#Blgbqo6ukV!fQT4?`##Xd5a-7I1#cF8x2@46loI zE=#v?(d&AmJ`v&9+=_?GmvCkZPP@u`she0NIv9D!FyU-E6+_j2B_KU`7<{j|O2|b4 zL;Ny65=seB-2ijADKyQI&*V&pOSIdY{*ijY!){Z~|LQN{K}!ExNB~pw4Yr5XPPi%B zw$cHE+OHs~w(a4IY(FOR5I$ktxqv&X6Co?>Py{izQ#FH6>`#<-&|BTA*IHAMGtGmA zq!Ub09iPLk#FiNpd=7aNZ156g;vISvO`NI#T|R z&W*T*Ty_Kt;RDsFZ$psjb(sPE#Dm^)tXk&Fly!bIydS9~o2Pp7KjRuL$$qB5%0niC z>?@tvJ>rY`o_FBBX{{#Q{v3~JLTD%XDn^VviGi*{QwMnFD~3&W;rjdjkvpF;L#CUy zO@>^-JL7b#ivFQwF36|&rN*V{3BMM9ANH9ZG5x4#9cIse^&g_B$q^19GP*6;?`hJ- zYlO|Ly3tECa6jtSLjlJ^%2};TuMwhMVuC_4oOzKRA*_rgHpw4k)>K+MaSrh}m?+Tl zXR*cU_-zmS&zwn!(eSfZ=u4dy=McuDWZn8*mkswwB_f^%6^vf!mL>XVa7|isju6AV z5et|2_XSJ62oHj`0|W~e(pWXo>eCj+3dZR}&JFj;tEVKqO05T9w<2R(a~#!{1x!C& za5YsNhCme%rB(`bs<4%S`g>2t684{86M#bi(A+;InU!9<3l#K;itvamoP!nA40q@dUZ0!u0m|0~9i&u26YU_!=;jWhot6h*=`)vR{R7sLlQg2H zBT~?dw_eFSJsTi$1LYWB*>%LQbb2(mkuLLuZU<@}#``CHlA45&lX{HYTxKuX++c6+ zr0q*@C0?QgH{lw$sCe#tneSbIuLzICYHgD6r@++;U-IJg>x}}kKnNKDup+p@j@*t& z!!Myk{!7N;?+Ys0(vqq}_KjaVR|3{l7{BNH8e`%Ar(ZaxhrgQ(h=?sR64NxBAbMVjVxT+%U+A-^U z4l*~SZ~wObZ1S1!cgvfoaBsQnd+c{MAO8FIznU`_w0Dv4zWghe4?2LqPB)-yw6{M5 zGKo!4KVCqGkl&_$yX(GdJ}X`TKABI3Q=3=5H{JeTWxX$-zTZW^+c&+R^1pV)pqpCS zbB?=l9hBj4pCU=`@ECI&wW{CvJEZ0gDbvP;E5E3t_Y%&)sHF8w|5~gw<{=1Qpd{Tt zCy|S*w5Rk3LR6D3(*4#Jx94H;4mpoTJGCyN8Mt79e_$(NH%z2#0C`@*9TlSZGX>@w1)Fn|e^ z(aP+jsi*0y%Oz>M{DJ)ME+l&NXeHGXn(6^*4~+hjYYVFyY0r)Hu=m@21>TW%+Ho2- zam9k*Xn?r@gJ>DMff$_Kz{o?j2eDy6h1~r2&ut}4CHAwwd$)c0J)Ax{{h>o$y77j@ z?O2jh?(LUb3M_SCmJgaCN3H3*>#3rrXGeJcPY`i!lLGo7DzIdF?}xG5huqDm$0z8W zAthcD^Z=A_q#?Lodgb$&{jc{UI8SxlM{z(lt*AV}0@6t~ZJxZUHo&gGgP=ra@ub#l zb$dutD6=03SZ+cd;7#GZ63^I@Eu}b~A7A~D>6H8czh9&(N_W^D)cK-hQa%AMaL=j}{*VbrSV z&U7Uyvliw$GszYW`*0ui%03@BV~3fmNB~iR52JT)RG2jo6!0@N!z5A)-iK|eS>T(S zMq9ZcE{`!bHS1hWVwt1O-_Oc%5c#@y5NIDWPe-B)X=+B04gP_1jhmb&D5)r>s+lQ| z)GH=X`^mjH4VUAO<4Y(db}XA*tbnZ(ON3{7E-3*1Jq=G-K4)5qf*`KmYD^?m?fCc1 zW$K)c#S2NKz2khYb^%37Y>h$fn?4}gxYV1n@bCP*6%}&S2?+YP$j=i-H39t(QmX|_9CGig^UMTj5!-0&Zui!zE7Oj+!A4c`rE zkC;-aZmQV}drTq!~yv>jrNxBe?a0j6(h z&nHV+X(tx{cY)#2xg&fTF&vKX?BB#g!u>SEIEazsMys6Gpw}2j>b2_AlTRMojbQ1W2Uqi zf}$t7Vv!JRN_2`pAK13@hkElWCh zfD3V4nsZfjQRg9z+~*ZP-8CWDHHnO*V*p+kF^CSJaE#xJ?H5b*u6Q^Rwgia4k5X^v z{skF}?3+f&o;!@>$PAiV*X-=M0f4NI{EPeoPh&BeOCWDFL;etEbx8akxmxuXPHLjk z5YNO;yhNzLmd4SgJB4qL-uaxyx?X?U7wWdFXH3@R!lh#uf-75Sb4j=V=tJ8Sa-?zi zar&H=O>#F3D(Ll;nO4Z7dZy$U{WwA%2xj`5ZU9Q$EU^hR}#OSbF6qWW9i@(}q zUxP~#Tf2Pq&vkY}=WwL-DMMIiP{j#0kZzcjj3AWf5HjQT@+rbItB57I;=DN!D7(qT z%nw;4O6_P$lLQQn1>tn814Zo(Ew0**mnY`PtkGz?h@cvy!3k8_5 z9I+19JOFc08^azyq7zapfjN|Hd$6a$72UsT9^X_?+OC_^wZ|RBaKo!}3uc1$JtZ8( zn+yA7QbY*>S0y5`wfd<4Et+tjM@f-4aoi_avBao#iVw^G!yG-_)%ZyVEk8~>F{ zmiM@~NK1p^pD1H=&*GdD^6qoy=&%Mf8WFnYs4I;A*cbi8?_<%d^pcHw2&aV&!B@TW zEgsqT#MjG%88!BO`$};#sU+DWL3W(W^wmM`l8_+iJDk6)7P}5d9s<|UgnQ8 z=(uH*?DL9m0^)KzQP~sC*84fibz1k1F^u;+r2E7zG)CI33z{WI2`*OkV{l9kL z80Y{qZQfCArWMfJgK_MK&ape$L1~R-!z%E1>TvZ{+%0tTm?y{)RMuDUkQPcdUTL%q zByGjVcZ5=>cL9k0_=L3$`**+c+4i@y?H>`s9^})ax(>}b0%adw)=bEmBQv_W_h|Qt zR%-Pq43}$SR`&r|M@0oe5^~B#G)&j~;lgNf>T}t7t)8%eXo00LmwaW{umqCUOw8Oy zeYE4`&?VTmW#xe1vG~C>+4UD%ww@m!3<6f&F-d$A7Ar8a41QKG>QQr>)K{MLqNld= zGfd~#3M#Q*1c$R4BDW34o};D`b&*pVXU773?`gzpl^s@K!jy<>X1)nqKq!NoN<+uL z>b7Rfu=47cY|0Wj3xb%M3~wtYs(uI5l4xZ5GWk<5DtjjioM9@;JBg4<%kbSuKg?%$ zLrzx8Ajs!^VLLplm10|uruoNx7M7np@un-&$nAHw46soSY*WQ68=r~bT~EW)-(jCD z6PLIm``LoAI%Wr`Shszl5muX_~z8@V-8-pKyh z$3_0)uSGzulQ;zJS}KxPATGX55P-Nh2NbG@u0xIQt&gkbg$K@FJp-`if)EJWe<_QO z^b;O%dbC7J4lm{=a#n|?7S<^Du0MGEdmRADnQGaYg)f60G=Fkb>|SudkUv9anHQ<> zcuH>1@;q=OdQ99yt4#N5OWMS8pm((~2t^5>9{xZ5ZVbFScW*xo(%G~RXM&a}6;^`L zqv?L@azHzHq&NZD5#es(dEhTqiD=YpcY@72qrF5uwE}%DmCcoght2+Gr@&}7j0r}> zixXD=vDbo5PnkyXwSn+kg0K;)7U2LD*0Z zYTa9uKE`36QJY2h-u@uV)wcagCbK>E4N60}brX=s8jmY#y7s6En!sT}=!cP|+g{*{ zb#y6jXk0cPb(#BtI9al?Ne1|cTO$P+B;JaeTul9i)osMm<*h4Pd|!%U^Rv;)>zez+ zwoAxx>SO8)*d-xsvkpR`JQZs%s4J(LaA&XTW2pX$+$*-|0^^T5cR!8gyA!Nr6|F(a z5hmiT@w}=m&~G^k#D$;uqq7y9nCVdX+){gL&d(^l&j=x|2oQWte;vGBz)B62^@jLL zy;Z_GirQ%U=YcWsj((JBuVOY^Qjwcc%dVa$eZAs6qBn>ueJ~f;q<#R2=`_7mSd_Q{ z_i1IMLi3S-_4A=u1(kZm)S?fzii_;~Bo5y1l z%!W^}$PsmQB~8Jb4TQJ@d?NYym2J0ged%{oLgaM2BEW^l8}NIPvobHz=)ryfve-o} z@A>HA;a@W-r#e;fa_vrkRF7IcrKH@d%HrL92O6vy5Ai(ybeY-2nY%3=1FdyLnLR5) zZ6)L_C1pvtJ5cDp^Dy|c(#ar|+vAGVa%c-(Hay(G8~>CfR9S!(?y}3jq5l>u&YAu( zL=8H1H}WJE+koQ(xr(;NC$@(1=#0;a5S_=;{QTgV<|y~`;F%iZADMWQ|3~qBYNCsP zBydoxL0Yu{06E9eelK;k#Qe8mEc$Av_?QB^>HIBbm=PK&w!6k@kKiRbo=5)Ev9!m| zRjfBqoZ!z36eR@}>yP474TwwA@hfLSGQ_S!@&e4ngQy_-Dduq!B5IrSaFCy%{qX!U zQH2m;>pvaeJ5O%rT~Vrx*DB_OO2ck~r(j>-d0v09(lTs*$G^^|LVt;iL~|hH+h$QA zQdm!o%6Yy|Hsf_W_Mr2anl4PX^U%?%vU`xq@)rh__m5yF(W6>;Ij^yR&IVVvk6`Qr z?gb}!4bd)q5NIK!{16djemLxU)TDG8dbBn0*>x%HzuKh#J2^74)+I%8?D3wxwJC}z#z~}+n0X&y0i!{ zzK^W1c={)J!kV84Y)i}{dBZyhUD7zgq3mJA&LqETHDJuH2GPGRQ(lC(Lo@^~4nfww z%Vb6$=T!F%j3Rrl99UjT#hYWc@(lt(PHb)TB5m`G6Q4rn=%@6*~qsqeHY#hP1^ z%w+^DMr4;vCcO?iSaaEu>9=-vS#Ofu2$KFaGd>$<;7X|<<_zmSL z_C|>Njv&ui8g^y!WG&ISFfcFjlbkL`ZJYWmobk6RCyJ1cUve%QgXb41U2=5l0B5rw ziNMf!vPn);UJZ<|v575wCu?RmU|N=Yl?m^V%sk#*iVS@| zjB=MK>Ao^40RJ#9SfFPUjaeqwtN|1#%F}XMCzwn6IYC!UwLtK*CVz$EfjUSm)8bds zDv1H9tp%2a;^pAA2FHa9Z-w3 zrslXj(31Y1RIy4Hz?!tQgw5lg!P|%J9Rj78_g9zTrNaIgdwx^g%EPalTEGYr?|vtW0*mLG%w$lCfe%;*$T|N1_S`#T?aCKIAjOA>W{Ci@P3x`T%$JV9B7pq@#2r?3A5{sMIfjcPW zjhb}igpMEG@F8DWh1?MmSHhsTGCErK04y`$k!=e+t~t7g_iKAKQ$73Vl>2^VaOp&olTJC&$G-Mxp6ncwzH zsCd$e)mh*$+<`<{MHtdsH z*@#MiAmqM9rR1(bQ!adve==ScU5zYla&^4X+YYT~&WH1ad9TVjaRhG-0iy>~9K{S_ z&z9X`*Gkw(@afkp$S}7f#jtOW8?OvJ!)6y|wJU!nviFDA zp%ul?AXWgw$Q7JpsLJ8t_B01sK2(a%B7RAjq|cPfuIhGD`3XA6#-`7DG)SXgH3e=; zr9U(r=nABxQt5>(UWm_-o()%zm3+27s&QHz%GF!D2k3IQFD96|Ug=wjVFmkLUBnub<*jOQ@nwYvD>Qxd;8)cJ)fSVGwz5u%G?lfzqphR5~v zNHlcftX_GXwMeKzHw2|dr=Yx15Fw2W&IOBmc;&_ufor~DB7J82mqReM`E&6SPmlRj z7;E19{6hu8r^s+DxojIng7e3!WKz8{D{So-?tB!ozDaeCI=XK^6!F~Dv%P?}3%gbw zo&Nxm7x=MHQh9VCusN30lt2x)bpv}Y9DkNaZAMIwGLoak8Xqi^u-(M32Q;&_))#mq zcU53fAm4(1lJi(EXc*&HK7p^`EI>r7*GAysg=TVpA3W)%`vH1=f8&D;UY7+QSZ$zu zi8$zn$uYhTX=OSUord&}5P^wBJ=g&whGFt9$cqi5N$MY{V!Pn8&`bTM@dE+VkEt@B z4@u?rN>KMfC;0e1VYa94<4xj_7Ef%q1HQ7Eo;y}u9F0n6QecK|!{rdC?mVY zleeOUoefc9Xu>EReXh?%M-wos9ztw)d@_bkpXGAh(n!Ot&NvVF}JSKwTfC_6-*c zdS1;Z?7uo|reJ$Usyh9}A@~-%%sHvb1#V->qeV!7Fy@Q z)B~xE4UUooa?)DvI*ShA!hm%&74W6B$3EB*$lW`#VUGvCA|i-*bF*7XibSp|@ju znF8;DK7T8hTGkOS*QnH$dGP@O0&=!HVb`-mBz<-GsC|y81!3y=af=DL!j>_cJO6>i zo-^NE6|p9Eqj-Ljx}f5Afj$mBec9olJY+F_J}g0QHSr1Ko$)kd-Sr6cQcFIVE^XUi zLu_*It3(29EfQ8GHLOk;0;X^Y z-7?wYR&>rACvCTaWE0J)^5haf*g;fe>+;k@G8rLkRlmKqvJ)OEXtlgee(<`OD^H``(v=i+*(}6fXSMJ8a|~PokHE0=Jp&1SP^6%Q0>xX!_0R%}k9t42uK~ zpq07hikjzUz~)2#soci5#9SvcOwaWRl|yq30I=?DX5nB#nBQWJoa*@Qv=b@)p5cS* z59_r^`iVLJy1Xb(Vmfqs`1qQOs}f4dmS}(yz5yN32%}!~D)zg#@7;~O0IVT)QCk+R z!*>Yw*Fk>$?$+aLt?(fI%NWY&whRJ&8ja@C4dbw5NIhk*AhSf&W~-DG3Ew!d`H`O} zR;*nDKEPOPmV4g$6f1ig>|D5nHCbd3peGm@O8u2JTYhpYtxAygpLUiWcZ8Ytww`u{ zLnA6Z56q94b!4~EIrFNUkcKKxHo<+~*QB8b*84qkjuE+U@LiwOF?Hrl?j?jT)>w(t z277jbWKw=Q6=J}uv(GSLT6)_4QhOtRB*K)DTKBIwlMqx7OxEYYW@cNPyf2Y!7fFy7 z9XCPNsOJF722M>b?D!5|8i~*142tH$V1Ob1xgE_g{}0-nl0~-(w>y9 zmcnu-PGTAv?} zCExQ`z)#ZO$!*OS^k08|0uf;#nBj~d$RTX$@vA&+Te|hwyWrxtdmQ(om5Fg$BJ1TA zj-V+tqET$OVhB0h?JgWMZN zeJP$Ln3Ib)B$M;1sjGEkuT(jin&^Dg$_7{ME6W4l=_|N0g(q zFNv_nfRqoG#@53}Sm*Y)7Jc`LIKrz%t1~=U%zD{ix8?4|rU50ss?HeKDa}JrZXGly zI^>XP4h*Yff}l+&>Ewq<=_4Pn^kR?!S~dssm2%?H6b5aCPUNF?+^)ww#(659B@C>U zHgzLK+K5Cx7_fn^)TeQsL0?FtYwuQd@i#r8IatD*1h$HO@a_rz9*x%2ejJ(=LuT(# zu|zaQTG$Mv%y<&9MIy_dRv_fs9PO+Dw_mZ4+tal2?Mjo>kO>#DHtDpck3;Ev0*EO1 z^nN*gCdshYP=kHi#ks-A?LeiE59Zqg*@%+sr}Z09o_Kr5;CG4dVP)D0wo6kbsf{f* z1`mv^Cq?bs42tn8Aa#ra;@;o%k7^q}!0!#I?bRT5r#X@FIfTjcucXTCOYNv?n;TBT z*f}4~gVaw1nL9b#0pb>o7O$&ix5&Rh5m{_HLx!sMa;r~vMtBDSQ!6#K-}1p;Br7$Yr2Yy5GOww$LPB#C)}%1!6+Sn~o&6HrVfnYvdy)o_ zNQ*h*-z|2(I~WgQmY*a)piV_UOPM@R7_3x?f@j`)$BDLr=oz;MA^6Sia-J4Kx;hK! z7K}|&&S(^};Z+mcRs^bBo~@5WNt=77sb-YLo$YCD!2zQNBO3^j*jD2m!w%U=&K@I3 zl+<|Xz*j=1>j|XlhJtLKh39wAo)yENU%xcB>Pw> z2q(GWvf88xWYtOdB|#N%q5l?LdHMV@=~izrjoi>eko09iiQ~^jd5G(Ho9Ec5Z+Sue zn}~oWlXr+-LO39sne5jzYN~53>u}O{;U$K6C{peLU7%R>dMD5v!3%k#F`j@UwxZKl zXTP$!|>&^6eoc_1q>j5vMjN14o`nw^b;^tOgV1mii>M~yoZ`V>Z z+VCz=oby4alU@+ZKkObO$I^(x@k$xq&<@5GT+dHQk)C19)L^;g{^?Yzf-H9m8t@J& z&tt0`pM&qf$(HBgJZAL~KSx8+9I6`zL#LjwE^d8c6~fp#mnGbo%O)TM67iewj9mPI zASn^r@^8Zlqyg_}B3d(q!ete@Yf^2Vn1qLHYhxw!qxsB{wF*RhPfSbi=p#QVb!1M5KL^=!AcO(`eZ%-j z+yJ+%?eKfk4dvhWX|+Ci1KnQ0WZREr`B>K%0Ja2()78glB_O*AOD| zA{xKqx%X~L@;Y7Hw0D+@*Gz6feG0q*;?6s8j?62cYTz8+Cz}` z#W`6yRdp@T)on`-^Q4A2?nD&2j8VjQkT1u-Xin}|P$K|=zjBB3Gs}4td+0Q88gc}c z@YMH)Dh=zY72w&w0cLUY&8(Vr^8n~N4fHXQ)eZpRIS*Q}Qsv%EcSn2%i}g(A8@7?0 zdY8wCpGBwGF`9Kt{D)p?e_} z`@;s^9uiVK-JB}Iv#hAmDV&=|BSc?%^`gM>wITLhmonePO?6_@es|)bU*ADZR6ZNt z8uBYf=K`fXC3+O*nLjVZU$m`QD2%|?4b4V;7K~0oY$JD7Gzus6Ovi8isyAk=8r0nG z&3ke%Erp?%bnIN$E7@!T7q9BAxZahsGJJKsO5auBxF(O;NkR8qu-AWD4CP53j2xSp zaod{cV``K|06q`~7-CJvbq@zXIHL3vht>^2Gs%`z8u3z8WPMWT2OP+ZS|v}h&b&cf zPE}yQZT27I1dUb!Uu3d_lvk8*|21%0g|uVf!#D=STFDmsKH0cKCBuvhf${&w)?5>| zIgIyc?PVLrR|sZ(*>(Z-@)FcIlPkC5OGoEEy)~rfJc8qb6feB$9 z&(&k*^c0f8&UFO8^`=^Eni(Mc!jVhZ6v*iE%1vs^(M;1~G>*qWVt%A#R+z~Tqh)CG zGvbyX%!R;q3yRL<>fcGu{4=o0z{E;7@CDD&fW+e_NcF*89O;}^uY>dVlj3V7W<$E{ z8^>6f7k_yBRz07a2k?iXRPUd%^gbG)D|$C1jVb~e2uQf}Rq=(#i|U7rAZC-SJ$B7I%1Dv_oUy)4 zQrdv6QNw`gJkDjf)hsxB*okVNRMvVo#lO0)W5oCu8fp3-H3{{YRB;@NF7%*1`fL5y z|8qM|JR0&GyU#*-AlMbj8gMj@BFR(|;%#V>-eEPx!?dmK-ut$~Cb!tS%nVM%cEemK z&nmZ^5_qr1wlh0aCt%a{`M!ylN^Z>I8F>3d3bwxfnIG$?SkGu=RE4qOkrbxvPGdZz z!e`5l;z{_5DCpUO#iT=;;b6=q%fJ}nv+L3M9jHXutuFs|!=~nTNVayS&ffJRh5``v z9Sw=GIr9S;587-)4b3cvuW{niKPriw;~+__>IWfeQ%n{vb$foFUMg)zfDEQk+Q6f) z(*jbPxW-D+EANBR%QgS9ajrPd4B$~3Z-0bxVHM3<^*Mm0w=+!+#cGMXI4dMfi0v;> ztV<0#bj2n{kASPf&fQA*G1uvS9W-knsAK$~4*!h^us%p_sNn{%uel zJ%g)+oYpM|9gjcFvmfk7KVi1z2#PDTVWcf)%nO3zt45M9p+MYx!*jh)o=D@EbLcol z5OQ9noNERivQ-qxxgLpazN)F`5N7?k)M08}ZxE6yt=4Qzr<~~Zvj)y3xxIY+sf7v1XDZkGn>aj7R_kB>UJ(RSi@xXZOa#R| zig_cX#f$0t`2Mg=v+{f)2P#C%sn{HJL?ejf0l3Ui`mDn<4vhO8shLv}?Jw)6XY~hp zM4jgy_^s}|_*}wAB^ol?lZoLyTQFEim!=ozlUn+B^zp1M|^ECqQm4hG_S^B!R3Z*s@{2Hu=A6215y zZ*U>rKcY~%A)Q!Dz>-43&&Na`tC;^r)^+qtg z6{(5H$Mk`nSBYQOKX6dm95b}p%Hmf>0=&+F)gNtLq1L=g&+i)TkBoNHfo+9%Z2Kvb zIwaShE8$S4)`7p0rgK%ilY?`o@{{H&v^nUepb?h;QkJF4J3hs)mgfE$7`4x=-6llh z5rF;7xvWv6Cbc-lXH5`31C=5h77eA)Y7IN5ngpNcQLES!pkBZ~|oMwCasSe(WSvH0r0k+3+9?+5;ODQ_D*Q$7@?i%GJbNTWh>e28Vvr%hYSUgSm*`poKlmfk;xZl*Ka*ED~V{{l0L=(LTr zaqM7c{2Gy6Zf%NUd;%qH42h1A(8+r~xcmiztfXr4^xb5K91)DK{eTBuXRB;RJvppC zuIIT%5lYfvy?+{|BUc4?K9P?cud%AZkhZz;hN1I7nOAL4L_T;(v~@7~Z|L%bW#K2T z9Lp{JU4^-V!=MFnura+iJ8UIYsRy++$-@Y6*Jq}7@kxJv%vCwINFboV15ALO)sGm`{?@J&; z0Qm!%Bpz1m4M{vhjan$TK&#C*1585x0i^nx6^|OcPlZnFES&-7ClnvJihX3Unr`WD57a$XO&0AyQtnlmXT#6f3N7 z7mS$R2J{+XSZ@PfhTx5U0c}oGh*fe;SPd1|^}}#ThOEn=E9>3dmkuaa9CeW`Mq#50 zJUNjq-1AOvT3|fE{~Gp;5s=@h6|qfPDidpX%_fmP8o_2o-M8)e)@fiM231!^`1}o9 zdWcKP*Okr*F(xmRCYA29S8G@x>^r+Ulu3eMj7(Dd{= z1{3nqh9@QfcZZo6-3+y2w2Hp|ewa*M8zgQM5{|V4M4T z@A&@m$SiY_gfQblXw{wx;~kk|+HID6vox=GpF6+G6YD9Tj(D>gHtlflQ`sp@qCNym zD&kzOJ^||h4JCKESf(qaI*zncnvjxZid$K}t=(e=cl)is#6!^`GBs z>mXnJN_7g}a(o0kU3CleL zy=*7%fy-K~}wjW0NNKR%#@d#So23;yAjW!~kX*D7F?x>cxsE_PdN}3BiG# zAY6R`(>d(tvB}BaYA_y=Pru?X?^m`%dK6u@sJva<`GPikx;;i9dODbT) z1DoJn8q{y(o@dTSu)@-5^sUD6)7h=fs<9u5ba)ua-Dh*7A?k-(V3tc!*T(iI10d{r zmLgU{kPlLy zQlf_NcEG(!xT(?`>$_*h!TMi>@#YPf{V_$ZE(IUE*Dd&*3;A+WAqy#>l(t?V6uYgw zGAY&cR|C>UFk1Ngi}5HuLqq7k~~Cw^}Te^Kh}+0r_!gm-;Nrh2}?wNWECwJIZnG=aIPPug?$ zQ*rKu_fv-2OCazQ&y!^f{~5+VHB8V~q&|Bi`&=7*@+#EhVzmin?C{(S@d$a|4JO6D zn4YYeLVx?b`R5U(@>YS2OqL}7q&GFtd#ta3Cj+1QmFQmcHF{l(Pl9Lc|(yG4e9Ak*E5nvUiSzh$BI7)q4 z8c*c^b)@+h$N8Sk@#~*Nt~A>Vs)EjKxNQ`?2tj3=0L1h+Iy?7XA@2-66+}suM`FhhY;iCq zbG2xQ_D%_YenS9n1ylB2S7kC45M^2~r&sj@uZm!pue_eNqaZq^{am4ee4foB;>-Ox zbBt?h(_!GcqCPl&e~SE4Mmp)wBnyLDF%Gm)b$MDB%2R~(BzS()v*}*&rfl@rC3755`yY^;G}G#tuAgGpuPn{Ol)GpvZ{?vD)FvNy?Q00Jb7`m*5jRO% zTkUi9vY?q?=Dalp?coHJJegz`(D1WUjfsc&mUTu9nOnLI3ql8Fsz(VW?pye<2IcAJ z^R#M?;DJr*T2oW3 zchKcdM>}t&&RP<6OA)%Xmm2P4EmPZ{Aql+d?NHpm3NXY##kw+ut~Di8==+sxwd9%@ zA9u&o`0~IxI{kdNB{k4S54o$&M)M^N2gL(-NQUs(Td25$`eMNHT#MIIknxI#GqbylCk{DN+CP3UjP#~f&Jk#_w+R&Oa) zcVxBeq8P#P;(Ayi*@X6+jBR+Ga)iu3?eA|HoXs+v>IYx_(E4E?^J8Hb z(Rf&~Bp?9q2tjkrD?u7m$mmpQ)Msq5f*@R?vk4GJlpoRo=1fE!>J9BsWnwZ0U54|d zntr(aYbgVyXXzJIn>+a|e-aIDdh$r?v+m#Gkwl^zqaNWskEDBEaITHqrek?G*HRFJ z$l4FbQrUkwP5r_%-Wu&$a|w(q6YAtK<$gaNS$%tKe+Az;2X#-rLRlnGE~G|j!IrR7 zN56)*uM)I^1YH;OzqhAXYSXP}GhIhFqvc*{+l~#$T?&*sOsd8~OsVe83EcgkpZ>w<#X?a0xvL+b@-eAJZvN+TN>~>4%pQh$u}urt&I?3ls4M#$HSt;q!e%zZ9Hyb0xTAa@@JHP| zJew_HT>N^soZ*?SDPx8-x1N$re{{AQ4 z%Kke)|KHz_&rjyx`JM5X>|c$?QDutvXlp9jRCtH^qbg&AR?01SQFBHYc-ez>e&Cpt zVELKo_h~QwphXX@t)VARM>{ij6-htK2o~0Nu|YFNQ&)(>hk&`-SdoIbdMrtYB52vO zttl#%OeWit-8mV(B5ygcv|7K0odDoUg-*!Gjinpatj|nXL7%;{MrVnh8)geccYNaO)M0q(IJ~{_t`dpVO%eB}i99R(-%Uu3{$441D<0zugip46|Op z1*qq7A7gd&UbCmTSM^IxNKJQT<}R4{2crGP;n!oJ49w@N#z|V%67XY5{^Fp{nxasM zGRKm&uGRqbnBB8gugK#9flatb?S>>`$!$nXe<;Cnc$)c}5LC@@)d20t>y)?E6r7>m zu8Yj`!fd(dWBiB-qfmqbKGb4oT^*UVZa7dNAuB~Urf(k?Ca}C#lr`=>e)3?Ci@I#7 zMs+YqH%K+kMu5S(&zX#mAt9^9{#qW9b}X$qeZyTp?85+UKsJ_lW|3;skJ1R}h-KE) z(xYZ8`-R_n2XA8x?pcLeY6GtUKfcV(V(B;GWQfdZqRCb{_jSg}fqOX&QH(URux&kc z+a(pBR;$Mql7!9JD0En!)y|Ya!#hJcSW>RH3BR6t5WHs0y>cBxP z3(rdtKn1eZ_zw1vq69)ig<<_dNk}#x8uva^E%1HCA}Dn>@9UQ6 zqN?h;^(quO`PG<*M56cZ10|BZpY$7}D!KDd+nXZCzv?WkhJYGkO_dtFmTUZDotLB` zf8w?=Y*nanZPZ*w$lO32QgB-BTG49J^~SFq_=1KsdCxhx=Jo3fu0!Mey~*X7%TZOwGs^Oo$8&ti4#<)>=Vy% z3IBFI!$UH?TFz~fX=HMETCOCDb*k3c&VV5cQ-TSxcNq{88{EoE{eaB! zvy3C5&--cmE`6z(AC#=sYEDB0ML`rD1ba11^H)aLhrANM^`YK^n^V##W7xdaiEbD? zqnEo7=fB8#V5fPOY*(WdFWFV4Ko6X-T`=8wY`Wq>uvs&P$4+CtuXmJD$s_V{x47Jq zzmR;FB_NY&=yzk_(6lZviza?K=RNOYB>-i?^n7yZ&mct77pSxv)6|Oa*#$*9W!5is zva3>Mf*@wWPF?+KL>#bI^tY2WCO!&e-eo2wHz&mi(2dG31fH@QEnE+tAsSO7Aj6|k z(dO!YuVplg`*4K?S%sld0*dsGVU%<4Zwe3v0i~%qVuoI_8kod_< zHu)mYHJ?FCWla%oDNojRu&jS0t%E)O^DHtiB3x8h+7UHG=>pVYaU<1{cM`)BfVV;;NC+}c!!CMxTszj6Mza`<2AJ9J)}T+W^z#luse)iIsv zQbPy4w+)-4=!(WO+bvsH9MubkCx3mGmt37@l9wGJ4rVB>oow<_?&)0m;z>S5ag7*- zE*F+qwmTU1V>wb0j*;MQyIQQdf!LH;F-HJftn;S(EuV>~8QVM=a%_ zmn`+k9NHxlSGXZ|y8d&dbu^Ky!OLWJ}lVnW=Y5 ztoSr@N|c~XEo+cka?i0b7){-cUI))+$^r%(O`Xr8T-HCHo7U)C`k3 z#l~NEKA^}y2*Xp890q=F^Wj8&Cl_I5^yE&4$bf;iD;8TX zk*9G2V&M%2lax_KpWaYl`GKT`eDlxm`P9_iK$2#htwdEt)csbJZ=I`p`P9HBx+aR2 z`vy9ZoiFV39PJvtT+`*|JOwi?EQ%O#H3(#0(xp;Gj{I=-i#IV$@y`mRXPBOirW$A0 zm}kVRK$Ef%-CZx%ly582mjkBuU?eGxp?gAYHtnFHU1d0=l@B*j;u1th;5mFp76iw0 z4+S9uCWxD$X7Wq9neW!j^f}>Zi^Yu^aH2{W!o5{@_!l0pJZ2jY+V2LOZy;j-_Q?z- zT%U#RKClw19|tjT%M#Qz)D564mGL-a8h4CtzztEO7*71nooUZYs|W~(qu>Rq9M}u1 z!usQCPVIkEsnP5zXEIx)B}chGn8hWeoQGpf97b@UR$J*DBYkS9>)&6~Z2*SlNLg-y zPVH+Z$@p(v4mz0D$e}VH!N^tnQZi3DED%AdHWL>=^r(hbZK}$b zA8^phE0tVicsZVbih^hU&^vPu1+F2n9V zY=wk#$T&$SCoI%IQkYv&)f4ejdL1>+aG0Ibbe04QngpdZUCkp3rw3 z7rCx?L`8w20`sYqg{xbHbRy2^dx4?qd==hKN#N-zzRn&fqXw3i^V8_4G1sRmjEzL~ z?n7Gwv{&V(V))F(Q^UlR^9m*w3^$TZuoSqT=MFAA%CzUWqf8j%fa#8xi4&@KlwGH}Cn~tpxUw z)KFRD5!ps|;)N+PLXiLatgU1?G9xZKZ11!8}7 zD;~MG21YV2!L2_qkGAt3JgJbMG|dejssEgKO12*f^(B$CBL;|fY1j9bW;v9tJxOt! zzSx0Wa7fpQD-}sbgFW#LXzn+8SPTV`KO+g(=g9=(TbY>gCQloJ?nc%I$y_O=M;ofH|G<1uVW{9Z> zuXNQ4@nf|u2`KW_C`=Z~Hckf)QcxJuo;0BBx(;SF1oh7)8K97)eXT4db%4gd7 zm1itGBy}@8A49*mCSUQpSzTpr$;UKm@rSPchN2bwe`J5I~-0_mRL6W%bJaTZs%Me`*6_0ljnrs02a61 z_R6asYr3IXznRB>J$+Z(Q4W-of`%1Ju5O$WfDXk z2aP`j_#i0P+lEDBGJZxYQu$P?H8-MXk8D&jN^wk9x(hFMQj08}c{VB}kM-x%C=;8s;^M>3Rx<&*^sE6lz+KP7=$ zlddaII?p9)wHJFsK;<;N1dzmk_p2H)5u34msAb<%-7I6{yWOzAS-gb5MV$u^ce2fZ zj(;kpjF^=>uQ!cYHc(>TpMAD-oEHuD8N63?uk{Rr(>CpC@^&eie%6WmTR7pywz*{V zXFc!xH4?h9M-+imZ|U5t$JTQnfX3POP%)W9Kd!%kR3y3iq+3K9Ix<(D!~exkhcJ^=r399{dt=O z>%L|{ydmCmRDFeCXT-)M5HF<9cJabt{I-(esL!n%=3{knPEA~}VOgcz&{WP}nqMh_(_uQ9i8fLXljBxZTSzsCi>(qx3hR-Rf0 zBpdM!v_YhIMXyj+dZlv#UH#*1B(PNDbEu(C4^-Gn?n(O5ecf)Ja;~=p4h3IWq3cZ! zg6zA==IBdRalwlqe1EtKp$_3PU90E_iXoxdb+S%h(}C`IJ7ve0tLV^{;;b+0fpbbunb{CljAGFpxml zS;QSv%^A8VYY-yqPJW^J-#TrioL7bq8eNl!%?f^)Gf@i%+s>~G>{QcSI73NtCrrjj zQ~l3F8JX7 z=eSD&1EY09UYhVeXVLw1tO~z`93*WvqemPtfO&G}aH1=!lTd`|+2HEoc@Mdn<|N@4 z*bLL=lH0KUCc)^)D`2Mm+2vPGKA>>bc0Lz!jy+aiH0ue7up|Ar)W3PP^nr{?;tXO= z_9$$5U3}iI<#gul<&dXL*32ZSDGTd{*|;fQPFY`q&5$Y_wlJdi8H!R}Y^shTXhS;}Ki6Wa<5>d5^mOpP zF(*uG1=htUKPbORLtey7EIq7{))cbr@TkM>5c5cT>8~37gEVjmmiOU#NC=Sm55H!9 z*2tkqA~WWPKb2H$u-~=HrzUh>RVOQW0P|uLUrqznF(%?s9_A zPnO=}oC0%Ku9))24%KRguD*stYP?K&ZeVc1zX%3q?EsQVtSgST{jnpLiJN?Y*FKrj zJmh>F8l*2_kxH%Ua}P}SEz48r(eYnYq6MJpH}1Z!Q@^MgD1_=M%^h$saz6$7`m%I~ zLp`ZS46=A2#>J->o{(ZxW~xQU;lUhiH?q0sP3kw6mMsaDQoTihpBw@CB&Xz68Y||) z*)mxMm^Kg#X%K2AE8ozOhQXAeu1o9hO`MxQtrV!J|L&_ow%n>9goVz2T$2hd_u{J0 zE*S$9RUA&iaoDR@sAA=OxZpZJP4O%4G6recA&b_5ExwofieJZ&uj5n<{Q=oT3N%aX zB*bC0pVQ={V~Pi9$CO+52r;GCa|O0T3Rj*BFLV^9Kw}T;C|ylsmf0kkssb{AtRYq#r4!u(qd;_qE%u1uO6lFL5Jzk0L3U#X2GNvDfkcZH5C zOb6ZH9oaXLKa5L}*c5-rXs!ZJ@1uo^jV%23crunWOdW3SYw~+qnkNCK(b<}bvS~6% zP(!{i;u>oAh%4B%9$Z^(Q*0WryMn#??&?k7gWeC`Xh$`scHwBCj53N2IxJML{eD$8 z!f4Bv-1ViuwxmkOEqF)*1AlEEKY!V4^nZ!8lmxqL>gHJrEmDvqnj0fzfHYy`RN81N zi;=LOjdE$nUqg5QU?2|qE_#)xH&Sz-%vc3hum9zpWp6(1%tG9+)@UOAw8tCqUVZtE z-`Qq^n|f;4DCi>(zGPV4 z8)jKOFu*XkxRADsr58}JZuQii@PINc`a=5lgP{(fW~lJT0|&q!EqcLBi)?lj1-IV# zltypf(Osfn>!)O+c6a^_rK8H|@Y4HO=-_zwcMBIBfKN78Ze+*mfk*Ul%HE;{@Bjr- z=vo}X;$|L&0Am)|Id#MVD{x6}C&qn}9ie$Y(B_WAKapk?Jn?1k>l2O~h{E2v?b3|K zy~C%jNQc+9pJoj9pLxKt6X?ZiC2JjK>(*2Bp7vijF6*uNxnOn^+yRc%Uj+o-;9wQr z8Bhryz$F=@l4+zwC;L0w&5WR#t6W(TtGHp*U^%Fml(moAr`LB`E9fM-149+>;R2t+ z(J5ycQ$DH^|8I4L**Yg=T#-=XEh84c8$gO$VloD7?W*r#jrDOt3zyUKz_OhPx{Uil zutDNm;b6h3cqgEQ{9XX$oh~CR@sf4}jk=;G|tgA|187ctFyozyn@KCTbLv ztt$=KC=u$?tRydOQN<;)m<_SEG?(h@^fK|<&;WK*s_B<|yYu01-Jh+dxj$gMu%Uto zP1@>$-(ICU74)SW#QmI1NdNR)!@tc?Y^6Pyp3U2ANoyRbR8z-0Yhpt4i(ZrMqfHK` zPOS#HcxiX;;Rq*M*xt7ivP-m2rl(kAuE>Al;*x~i``^l%i$c6i6iF8i9U8GlB-gw@ zEsWsVKo!^~R?41--czLG9W3hYOITy%fjXj|EU$R2C_HnLnU&B*&aE-djcjGrb)5)Y zF1?5cgO2NGd{RmJJqT`+a$kR(?AeC7y^Rd{0!W;Q=I}BzK#1RGEE|92D^n=q!#M^r z|C;7?IVZ5<9?dXJ^IGr&rh>_Lty~|TJ4^|%{LO@jQEfE(1>uKal0*S#g(1(gfH}b? zy7lx}4ge{p6XFoguuwX%9mft@3N|`qS>EEKPQ4j`3Q9f{udbo^VVHcr8}wSeb)#od zqJmddF6#CtbZ0}M2CzQLIIz%FqBU z0NxGWVH@I7na!tDo{|>(0A|^k{5)LjODPitF|s0v5xWlHVg^?Y5{jTXU-CJnQQ9aK zFxA+0Z;mwd2^yYwteQnRH)+{B0LLqCu?EIppNpNxd5P5d<3zE_vg~L2r<6)z(&h8# z_>?TRnYT7rTx`~p;cjiC7uv6$v$(qGFO-Tt0l=-bsvWJf`YjCO|Ab4kL(=t`y5btgfEIImhiu{-G8!o z|7q#`@8^GJ@1XwE-YNS3C>>F(2+*?b;D5}5%p9nX4~Pa2AiVi6KLWXcv*bzi#UF4F zMK7w~RfzaDI;?4OmU=sric0M*N5AiW$lnmZ%{#L5rDwuFWpC~U;JM|$^hSFR{qMbO zyaN0WUIBi;7d7|li2l--Q?0)fZ?NCZ9sCCUqi=y+atqY2zx*?Fw7**(u;1q2`VHVu z{5QMNn%TbT-+K-GsrtYC_We!zAHM6qHvDhJ`Jut~8E>8Q_pa9C4LD(c45CVB2+sjcW` zUa3zrOPJqgj@zsRC?F%_(>@6^n{knB6{@uchnD~t7OvFk58m#_kHc|R_85v)bopsj zRcxSLMX<(6`14PGUZ)lKPW%bg%*T-F42u=G=-Ds^q%6=!4a8Y}zl;pE-N1tRr)3Wbm3qA1s*^aKE zPB}5t@T1)#Cw0rw&}*E`f`!_Y81H9a@R%EsvH|ofGw9oKg!MTm=ZDV-Q~HNszXAVf z9i0Rg#A!mOb+vYm>WwOqx)D(U}*pMXYp@Bl^=4w}tp=W#(P0 z4HlBDvXFHuSSUlz;3+fw(yczTK5zUXrKXV+Q$9*CDz)O@1JwY3F_uNT(el)3&B?FP z3bLwc^X`O{=Dfxg(exYOL$^r(Yf>Oz|VIVSwN5`0>;|1zpxW zUK19}8|x?NXBXCQpU=O>J#B;*Po6_1j_^=jn#A?f0((tZCLDk;+Q{s@(runu`l9G% zjf;kA$vG&|Kv!-^dIqmn?DAP#>(xSg$Bmfhk$jVgal|{OLbICSTWC;F^K=4ZU?xOCP91LidXAMD5Y7I>1K2We6wf^&J z+H@wwlrv?GKJr^BLj<}N)h_dL`YkW<2Ct4bcYS@n7SLqvchEFShuZUKO+S0O0i|cs z#UNVjBeP(AWyLZ+Fi&^i|3DOC;$vc9 z&!CG*a9ZtyMGJ5ItgQUSjjz#l5PKh08~dZoPZu6YMqXU!dEAm%UL4zFP!Oi@*QM8J zm1H!yWMngO#!(X&Qt1;7yXu6-AYU*bznP8&4>z(1z;0j2DNSxJU^cnoFFsej^(WV*A z_JmEm-CT?lKdawOC=oVI3Y*bdEb!a(KOQRb411Y&M)--qk!Os|@R1A7(G*?^vO_z* zs>a19lxb4^rGOaX?kun)&oi~6lZS(^4e{g031tKI0PO-TT}$itw!~kcvdV)G3c&&7 z;+DmK%^KSA7qE>KYXq57Vy47`FZzE&-h_4tJE2hx@cTf;ptZ|bazv>s!JG0;8eEkr z67m^cFrf3JN>MVl8ai80!Ca0e)5X>SfKX5>%aO&byAFE{zmgwWa(4_ANXZYOd&PFq z&coafQ#yG+!GlF|&XPM(S~p#!*>jMNkW%B~U*RtZ0mtMgg=p=vv3=pM$5}+3Oi>Nz zTp9r@(TvyEu&?c(xfA*e{dbSIQ`r2NRO+DuCpq|T`9m{QPhr2p4~=y9t*wzq7g$hs z<)?XwrtS#oJfw27(m7!JuXD_qc#2|tkT2+uGj^$h3WVD1cLg@o^$AtKW!{kwI1nNP z4f3Jh-tAQl$9jG1 z2ZE)sK!uXa6Aa!7mm~M_0*x5)#5Fr`ksF|Q1{^6%Yw+jMzyx%ze(b9o3-Bat+Uf%ZPaUJR^t|=7Z(OE$+B=ki$5NV0ltSZ63IV-YU+(wQA5P zbt#-TI@o0g6XfBYwfqCPkS~X+?x4L9H_~ztoHxzO+U8*???y>VB)Xe6Z~Z`f!^6** zgn0p~=snx7Q6#hZ$na3Wde<^F-uW)S^jX)tL~EH| zY?f1re*lMSI80gB2cF48!Kq;xy`O(=Lub1MW`{L#g&>_fq8d&3$Dk}|u^Mh7TMt;p zae}e$yWu^-lRk(4eMy+!p)EBWs271yU-V8o&Ve}yZcdOXe61stjj^{PilcHwOKg5d zvTwH1?yPjpxoue(r>E;M6Mad}RkfaclIha14xT7-pa@x|-uvrKP8ByRDffZ`wpp>z z$7W#K&NRS@^ywC#jFTFW7IP0keL%!PJtjD#Zu?ujm4ENcf=uu-{h9pH;@5cZr_0V<@8ilxVOf70qSK`yl=N@hjm{Cd=*u+sz7YSzLiLPAqoew zqcF?VYzCAR+Yp(?RPJn+Kg?eeSwYQM7D9{wv5G@1saab-77B#+9PM_D_pFg@me(Kj zvzrevrJFFIko2gctms)@7`_)A7a$uS=?PrpI7o+Fg-~^+8EO-3bV!y%`~JE3sP3lx z^gV0@`2jQcwZiAL*%(=Ym(ZL0nVoJV_nip%Qf{snjRt`htX}4JL`Dl4q*Y4A8=}uK zO|idhzp|r10LDmkRGm(CG!zh;=lQn|9IRTdVRtLT!>>?6b10fyQ6!>&W5kx~Qr;iHQh@FY~Z3UNg-%1|Fo)~G- zr?k+hc107f?S$D73KP$iW`|_I*2~WQIa21T%wXkfYYU8#yTh__2mSiOX3NMn90Nph z2b>^b6$1Du>HCPsFnL&QvZ9q-pm~k{NZPQk>Id#e7J#F!(QUWH67let;3IsqW>k0! z1ALb<%hfncEv*(B(AexJvoO$hiNh^FEF|s9P>T)%4H~*~R@?bl)+Q`KuV;m}3&wd~ zLvFFGs);?iPp`oC*}IzO(Fg%LU!jwlH!P~=j`j$;@ZmfZ$PXp+OC2&rRzZ%qssQ%I zc(DpeQ8>Azgo}HMF+QT;>~l|sl6QiL_9u((BAqj}Yv=g$Sf`@_2hq=(fGU(0j(vOw{b zSGTS@Ay7%FpZS8h;NZA82n$I^Rns|75KeJX5xY2mVC|1&+NlJ(VAbHX+T}#B>e)A4 zrw~6ChNo= z?z+6kofn0-+=tF78&4Aq!mGB|U_ivW>`QP(ON}><21Jm#Xp#$EY>Da?+zf7^Pzwby z;P*ownxS#FO4G`mAPx;MR_dKpj@>>W6yRWgvoNboOu!1+_8(^dt%Pi2iO~YXQZGLA zV*4VYWd)Yv4Pn*Hvq3=sIv4sVxI-e_Hiwu#D}Z&eK2YaYRJ7%^!~_>$T$L5q7b0EK zqM@!YOZA$dKAcnR6EczK=H39$2`$1ST4vF(wW>E0cPcyIH4j?l(kKUf{fi7tXZX0q zRsCv+Y;Z?5YvQj%MaIdkAC`Yj(&8(H6;~A*_RIzp!y+RMG!~O_`O~TQSBc!tzy~ik z5(u9Oq!JuC|4=+ohZ?gRyMrTM>cP0cIrrT6L01YV^z*PEGs)kEc{zI-hYMF=z#zh| zdjZY>V(GJd6lVq|mjvque~SAbP?V-nbrj`Z?qZO%q{`*A?=ou$2`+c7Yj%YM$|B)%0rNFCu zie}^_xF`&D4m$YgU{Qefp@|fYR9j-bVU4QZ^i>)AE&w4vzFeUH(7OdB(x`OuC*iBC zl8%oUi}jx%=D4DbYIy*9~MnWboIiab~IKg0m&&F3jpLBkQ8H#a^O8lR86g z7`v0XJlfMV2&akzQE3)Ei>Ql_H>-U7h|C1mp1IE^?`iC8l$%l}6wl4R2jNs0$_`I0 z0^0@QYC~jUrruOp^XKe!Hl~f3YSfX{ke+>kwCu&v$d}!wVNqOlEOUr=U zcIB%7y;n#YW(5NOkP8{-Vc~dgJ>}1U$~?>ebuW}rJQf^mI1@VyTHNbP^1v0|ndfc) zroWk4AID)*JyL-1!IeS@t*Nn@2YZ1MwS&N7KO2g`b_-*1_gtyszKf~sCX-N{dMyV#86$emA3)Ty%If}ml^V%u% z$*Ue9PsSc_AxFB4;n~U_aM}Nr*k5Hpqi9kI?9hRCQG5K$nw$NA9DiG;{Mv5-)+bwh z?1J`b^od#4tQX=6+?3`Ie=s(@(h_F#$W!v_$?AUwo5~luJ1KbKAuR6_Pm~T!v+358 z;GOs)5C_9p8u%O2)ev6`nsD+pSs~92%IS_tn32ve&cM{gl1BDzFIgSZ`t48il>v~LTbZyZ7_>qQrxDoC`{byNd+g^+egq!x+vbHr zM*mhr8y~pm6Z>yR{!zs=!&W2XFQz@F=UvXS*kJE+^g(;!yD#kOI)0W zP#ftCZ70lVyf(jx!)m^Ik@4^qgq9NJnX?o{=^}#5fe>04oa8)zENsP7(#07NXL)>{ zrgY4s9p(=`JLXNjm$DEqzB`I6&_!k^HRBM}x%4VKI)I}2bDo1i)rvo}1_>{UXrdSm z_l_7;;k;-*t&k9;h5MR|8h%K!D-;XD7mMZ~iPOHNnU+v>EqlZkeAcC7TT_Yv9)~M1 zITZ9Fd{aS!BGa5L_)dKgXN*dn@z z&rlt}`wrt141r)QlH{^L-??5M@2ZtGZb7V}Xu!`J)IS-iTr5bV(|jhjNG-4(Jm@DE zXXDjC;zrDvl{=EZAEDa#F;eGVmlVAn3Kinm3aOc^Fmiefm~;IWq9l5>9Str~MX}#$ z`#uk$g0IL6V|Hu3z}tM(F=jHu9i_gEw%Z>@G$KA^gTYY_B|J_{f_Y#Rd*T86HZfpt z_<>Sr7`HxbV#nyisBMc$4znlm^qP98o;lBpg`L3*eImg^l8TO4O7+tU*LSc34K)-l0hvW#3v_s|Ru{X}5C?(gBUJDl#P#PmJQ4k4|XfYEWiEi>>_oGSNj z1KJ{2+PJ1;c$-?N16?0d`1l84#bZV@Nq(~N=uupdWa_M=4(U%u4@lhlD2Tz%{5 z{0MO2-Mac(##+!cLLo05hUeA6W|mWB$?*0(<~a`L*qs>$Vd|K!SWmo*6sOks=`)@0 zn;gHMDScz2_e@hhx!ASex&pDMFqRw|$pE>9{M*zqCn%pYO_Z-5X+ikM5_}hH z?g|M)>(veiD1+qYg(%EArxjtphTs>lS*9I}#fmAAo46Uqc@Mi{FLa1siXZF@S-+rW z9rh*`ek*cWXUVW+f1G!4iz6DXfKNI6@@ZLOJ*Nqh8W=!;Yy7r*q z6B2xN*T~q58HLeTUD*l|bk^4JF;Jf{Z|K?i3UsPB3U8tB9(alF*|#>03;lX}A0&ec zJ#whWEi&?~5?F6#yo3@^R8;Wq-_}G8Y~|v4qS-29_s>vf|CGeZTafuaZJ(&PLze2u}X|3`{Yl`?WC z7-5Op8}7}TOE)g%B@2i?M`gnd;Ehx(xkfwRqej<^AvzVhagxWMWiMxk%fFA9Te4_B zxK4#^2bb0k5*c%KCLmq^`y|A!hN1t7@LNaJU|qu2=hlHgux;RhoR^$ttim+BFpZZ% zP4Zz>!_CHfdRAe9UWUCsHGC$oOvkwY(!ELW>OghwyPygJ_ibfDD9?a=MRZXBr*sr}*2v0jpn&4W~|WpuX6$CK*Q0M(r` zc&??|N|W;~4&+@*U7SEv=2H;3ZGt4!&!D6K1h6o!u!Rk2OTlRIev#y?kHI}&Sntuz zmmt=(3M?Oc;%j<_0H9!RMERBv{l_#>{c``QX4(-e|4iq~u?vX10aw4v${X_3lVu!_ zhexOKz^`kS-L`+FWBa;ogO;W3!tui$X_)&t8p2qu^rVz2{yFeqidGNdN{aGAW{@JF z%l%W+4JtnY|D)!M_HwCs8LO;%CoUx)=-i}69(xyl<58WdNtzLfIZ1(^z-syLW-!at{7#Y{}%BKa9qYe<~`mivl@n;OwgBV!2eE(~b$ zki66Zdf}mb9naSXdZC$)Ito!F3yc;HEHB0-&FtBMg@$hm+Ejxf{mg(uAaFhYbM0wS ze4-)?9}?yeSt#q~s6#WOa`Sj-gleid!YFyG*;VX(1YcU9($gw}fGrbfhNxw6vEkE8 z;EP8hi0QcTvPW$iXXn*gi73>dx-D1F7fhxiLqARU11waq^FaIV{qiP2;wzP(YLO`E z)!P)n_$hURK9lt#yFnWPc}sigE&$bNG;o^H6`2k|cr=_h;SJ!K_JZxy+~e@CIztLr zkISMN0p;yxnDZEL?MA7mjKKT@78Cs%^2jfP5{>~^Z!rB+vn zVOyMq*;gB40S8PlU(@(`af+;uU?$pI$ON}TJPrZZUJaXmjVqzq$ZK;66GCz%MHMRW znrjN|^ZOV^G}7o%RTeN|&t`MMv1abH*%Hj+#6ZxQ=&R4tSg@=NtG=m^mw(np#i?Uf zmjw^v<2p76=V@z1&@yL$!D+_L2S+>;biP9-#>|ujfFC232q2cW3Sj@Typ z&hWcrFmxr_|0RWJ{|hPn`IqN&`Ts~E!9P-Xtvvo8Qpogq#-ft|je{R^oQrLg-{~Z+ zg8qO>{#_pOE0llJuKsxPf%I$JhrDQeq56|v0{nAZ0saFkj(ho2KA^w&{pPmXnm_dh z{yqPp7t9U(Lr#+e;7%t4eU7>0UwVh~G5_Je)gJ(VihtCL@yPbc|GD=s)rX(Kx7`2P z+wHgTd;V(hUHN+=c5Ybsem8?P4S+Sgirp=VKT0!b0#5!jwIQSIQY>^OTY=@uyCBc=s6)_@1%R6M=7!|wuPuOz}GTU43M&oA9Wfi$O2!G_B4UjTbRgul~l<(R$Mlw6vSP!2?u3AsRV9B1!`nr;&tH=ZYU z0QkheSgx>v`C+km)rA(|Kd(592^T`%Iepw}NdS*(^LVHP_0*BpMTpV@BOaN!;TbmQ zI`hGzY_^jZ=ot{vhiL6-jLoD2<16G1X`ISsD5C^bHp`Pj*Io`LgUArgTVTA8dT;dS z$svL2Mk^P@y}`2B8Sh%no?u+jE8QEcB3qWi{d^L2^!<(R8NJ$hC%po6S}po4>@$Y{ zrMv2}9dKjob+bsnTw#*fG8*7~`M0c9@H(Y$BegHFdrK9*8WmYGNrs&?vO1l7Hj&#`|1@ zB#yiJy+4YT>FBNd-2c~$&L*mM$r|?KG+rztE(^p!Zu;go+q_HRGFAO_-w}q4m z67hqa*a6e*;RHKywo4$?V#>h2CI>D5}2I9I!wJNYqTJNLo(fiu#+HzNC&8pYZFHRl80h+p?}UvpkA0smD8Q%~gl?(Vm) zw>vlhB9u>gu4~y0cs3wRo?ehW%%5r;e4PY@v2Pl-C~nO@KJjM;11Pk&$=H4L^z(_8 z1WZC2MISGAbEv`>)x$V7(~&eftDK{N%NFf?v`_uYbBF91teuaw@ug38VT=O_$2#T< zJ;9XdP9BB;VPFXb0SUJYG^(N%l5f%6T))Lij@_e)`ry^_BoFn`wrWBtq7)#1bM8FH zR-pKiN#}p?yu(A2l=;(NjyCSBSS8T&bU_KiKVXnk!0`KtY&A5aq8E`iydk zQMoQ@{KBit_oQ(O&K$$d*_jLB(AtocC}rk9g?7bFJG@+cS)D&nJ~KAhBmt}#F5DvJ ziGQOyN7ja5x{;&{Y2W!nCzwlgJUIiRU_|X)lCF%Sg69Afq;tkkvich)U zDaSNynm3Fhw(*eub+_NAWkGQEu#iZV{59TEagizn1}a^v0MSe24tm)4l1rzQ0}buHhdQopz7{6zd-uj5Jgmk7hk@$wQpU+Oxma*A6OZnDv*bG`f2{Q zfbhapi!#4_3!hNyhHVqR(@EiK-i>MZFM|2=w>URp)k26>B~y@T5~w8ao@TyEHOK5h zxkY?Bkzj?uxx~Q7VhIX2+xsP&KAgLXg24KtnXm=lxB0sTbRYdXQ7{YCEfRvi5eg58 zJMlGnbu;He{3-$HQ*&KzJ$=6wY8NR*Ovc;ym*KyXT^I~x7A7#+jXRzrZVKOwGD$AF zLq?xN$5L8ccIdc7=@1J$_WB}WTCY@&Qq~#)iI6z|cq$)_h2zd>W-66;rz_k^Ri~QE z=SF0tIH8lRCQZ1BOdm~i>1WxU7W#iaTm}~S+oOi4^CcvYIsF5sz3i;C`HE(t8T3ouQ+zKeCZ-fW42yk zm3~k0j_iYVbOZ_!&LDOFLJK7PP!}&pOaW-18k*^KdK~L1a!R9 zpBc{VlR}4NQ#HX8YC{U>$ef<_`hBl;=ykb&@XRkxoGue7(TUqYr(B&C;nHGlJo+qJ zRLh~{m%L);7U;hHWY3y0Agx{4*(V!X8N@fnz|SsW3~~1ugiQJ4x&5yT>|-f}uix~k zPZe`OG#qV{OL@*%)drwZs9U(JjU0_zrsF0r6L3VRNa`aK;9DKQU3Cw~4Y2h@Q~@Gw zCBbCOHMc?6sez2!+7tlARlCQLdG5yiN4Mp`53^-#t_5}jjsNCh2xA5IeDIIk*hqF> zsYe9=3}5abD#(gZGaoT*9m~$ALnul^3~b@c z0WIWcbt~>Z5J5;giC!AIeJZCI#*vZ*9zYTdjvez2&~&}5&39X~aQ-n#KW>~=(Ehwrm4{pwJM6>Pp*Fe$+f*m&C|m_|$>n_BNF zLbkR?-nYB6{aj@V1jL%SD2}E;sg~aygUi!gSy#{?-BKu2)hRPM;n&3jR*fdx$5sf@ z;S|hAYV+LWyo;Nd9Ae7S>F-G#ScF&XzwR;{9(mLi`{735eFMetr?X6JXcS=#!s-Qr z;Ce4j(EzuD^dUUW;XyV`W_|!`>M$@6GzO-ac7P~}OX~izas_g{y4IoT6$MP%iN8vZ;;#T`9i(Y1nl1u3V1LFB z5&Pyj1C4L!sT5Fco%V#_z6MNDh>qvX!bJOMD75LpSV^WCW?)h#QS=!YoI!;1S0ek- zB2aYvgQx&oZ-Zt!bRt?k2hGnHptYgtegGy||4fZgshF}_A!JIe=;L!L7?YUkw;-Wf1R zZMhnlC?bt)4qfXhpTFwvjU|~KrT(`{3-6U|TMJN@iBa)qLyM~`bxm-K{7uqY1<4+$ z5Zky#87Sz@5DQtjgy5zRP4JPxbE)OJL;#a};@B9T zVqoI%AI|jXQHB>}rbJCj4kok5E5Ou`E43K_>U~+E;c2OjH?_#jqNfcwn%xnfWfGXR zDPBwp6M5O4V|RO8Q8qo_faOpmGxD@*JM@C$)K9oCvx+&1;|4#&D&##fD-+ z*d7g0>B9l{Ia;#^_uwI;7|`>CV43PfDFkf=!zA(Zouua zY-XrU4Cr#>+mV%O*d;;e?SA&MI_!%159IT&Mt9*jbxr@W+N?jqiAZWZ8IU2|nQjZI z+C7m?`VDR%s`y++Q{e$|0r(@XsQuN+3f6tSC$eZhMbp^_orc@A&RbE;9=8l zP(>}J9``92GDkg~YAYj*UQ`LUQE!f~EklH*)#D;Ue-0E9aX@TbP)R~SYWu|2svYGK z###$^L`kiDv6k;g2T;gy;Yv_OSYvnR-y_Y>k29kfoP_yNxO7eV(1n)HquO z(i*3o(U*mieqfvOZrVhHfS{SPWLZV-FZw39+fh>t5Ftw^PGob#o`v6%ypkHQ{R zc!vQ?XBWt?qMS4YttO81Pn@HX;B#rj*IoRgQp$HL1qfC}>1G!vzdD;EG6cb0O~zoo z=>->=)z+UI;kbfY;4T;2GXIt~GBS*l;Xrs%xbi!Xl%mfM10xGYQ3#lcEoz|x_6w@A z?r=)8xusuqs610$sO`gr(P%u8>-%fslPiio3*yz4LB4NCVwY|XOmiN#Aj)Dyw$9P{ zL;sgkf^!h8R3~U`jSfs95LIL~z;u@mvadkG@_bHFeCQi%8N}c*^;#22A=tF%Dj8QW65~{5nz2SIJh3?OQjqyyXe0qA|ceK&W;VrXCOCg2{ z8dzwROJR?9Xrb1JGKMowpvsR~-yk_6hdxzzjXC}Qh|}Q>AAQ_Ka6&nQcb*e_eRzNgLocek~hW zf4O~dTVl;Rt#JeK>ThdSwzRgcSU|z9gJtyo(g74X2qg3s-C!nscKsAnpT10z|}`yZ+S%fgQ`OvT4X8snp+eA?J%; zY<2=FPYfafO92W7*f%>}4gxqyi!EtnTH{#6Ht*gEn=B^F7YC_m2uRCZO>;SO6kdpvbx4M z>m*mVHPf|!o~a+Ob;D{Z2d@nnmUnOGL_AJ?pLyFpa=~=mhptwXcm38T=_()VAaF(6 z1xbB>NGGh{B+OmmNLqb-3WN0Z>!OXkPuZ(+pQ~ZA1mI>j+muWQe;Zbh7u;10C#q-} z@2QVMe1BK*c6AC%*CLeT{+l3a7O7c32}f>tWhpVzvb+*^e7>MRQ_hH@bc#Yny^4hFy1L+wIq84S3Tq zp8q#TXq$yDSoH^#Y8xDNy#Gqfd0Q6WYrs1eaM)Bcp!?B=ezpICj1IH+_!4E-fr{KR z+)rD2qab!woxoi+X|&X(?Y`R+8nJBZBj(W9dd6&s%o?6=U&d&5g4jR`HyFs9f5PNm zH_ZO$qCm9I&9HxNG3axQaDkx8FQ{_5Rc9Z=uKrM!OfE+CR(s`&+vhjrtl|WlI{tP= zk=~q1jtbOznCoDS;K`<3N-)Pu53#F7+l}H?ghTP`w#5uom|_l|fO{Vcmwb(aSD`}} zG>6k;=TMp_+L(`?tt*C}W9?sLkrP?v!P3}0xeB&2SUB0*ezSU*iQMnrAee>Rnw#pw z^$rauY$HmLuUNwX=BGXG&{n8Ob=+^RwctI3kt||J$c_%5C8?n5-&4ve8m|1|!ino} zdE?bSi=%bXZAO}bguz@G8^Y`^0*F_U}HmUR+~A zP1060i^&fq+?J@mwm2U?Ub#We;o7x9p0?L-IrgV4k{cEj@6koP>cj)sb>wgBV{DdvoPIgrIYq^{{4wnf3f zJhqk0j$%dnepgwTazLtB!>={Um$UV_3-#r>$pdV5C~C?Oix=1g3;t?q{=24837PPP zrr3m?JJimLGm3;yuV=(A+Tr?_Ny%QZB*#Xo3eKVqo{%DN;)i>GI0zE=W}wk-^;(U? z#p6*WV^RYxkBu`$W)&#&{+t@stMdp}mZTW;mUB1upU68{d4~FiQyrNf;XUyvYcvDo zsUrJEpV2X9ndR?5aN-jB2J#o2@!w({LDKrgx_t0A@e>+tW*~qx`4RoCejTaaozrXT zx_NOMClp%Zd)+m@edZuw?DY7S;yS|T8E5r`>XY(<(*-Pio|+ZC2h*Mos;td&J5X@^ zbCn6bkes+5nqHXC>PCa+ZtOh6T^er1`+3HYC35+-%X4oMVY|S?;rG2$5tF}bD{N}1 zXxWhdyg+lH^pMdc+CmMUwn5gwmBnewjR#^duXB-!yaQO5E}f%dfEb`i@YD#2Nk+*` zPpD+2h9gEW7E1h@+#&48(1`0j$16}Z+oz!~MVWw(T=vl{ijHU)M1Tn9O_!a<(e|Y? z=6ox#+BuW@XsK)91b}Gz zIAlhhVnqO!6p?6&V)DCgy;fpjpCz2GD#w8Y?Mc8Ee?o0+!6p>8>(qfVtYAdzBg7-v z+7836`Tr_QbPk>1L}kTh==9uH4K7fZH(9qrwD&*#(S9-1)*8`Uwxo`12dvpt`eZkR zxWE#1g}k4trb5NPKJt=gu@t;p(D_|A1VYK^^cH5!sBAt|o~)dY*8P050Sh2e%yG@q zQr|6~8s^~|nk2i~AjYy`0wJ@DR;BynV0`p|o#kiE zGzuJDI@)_pnp`ld6P_a`#}R*})QWp!wZtw0R^rp?h8oqXu&aJngF?V1b*(;IT0=+& z+}bZs@)Cjo^u`GDQ6(o7*oI=m+WR>IS;OPzF&2BT!r^8%Wq01+>@iK9T?bXp&4p(= z=%rVh%DpNUGS+H+meJ=DuvbAWzSdrk#Ak4!;%rJ7+h!!FjPXJ0q}aLb8nC{<&5F(p zq-$_4CS7dYOu`hs-ff41ah})u~m*X#;^{CxcRDCv_er^mNx|2*Fny- zKY(7F5x*(R{x+Lz)_F*b zwHmfKJb(Z}PEAIZ82|tP000620Gj{+djJ3)00011P&iC|82|t;o4_6r|3H{+RG7h> z|NFoQvyBQfnDc)h%v8;P0EQrrLxd0<;~?j7oI?yjh#(Gff`GSe+w-=Kwr!6G$Pk{1 z426{a^nXe@FPL z|Hu5@ANR*otMK@@|Fy_RHN_#Rw}1cdQy*yY`R`vqFVWlVXrO4*UE|;Scj}G?KK{b- zdH=khpZCx6uk-nP|Dy3KG*RrWH!ElS8~>ILR{#69#j@Sw7bN&NAHR7_E5ZF%vtuVq z{db-|&Q)F~80hn!)Uzabe)zbAd7QuL419-BT_UQL)1t<|_2RjHNfY-+p2@m+EXw#e z?E;_6Nq<8Rdt9um5xqb;2TD!<-t$8Bht@ft&JVY8{psT4v`o7Hi#^a{Hv64V&=2PO zHyqAW?i%^3HxSm0V;-ERw-IIY zo4LX7`g`i>TMvwQE_2v&C@*1ff=qwO%z?qFH0lz(mb)LTu7#s_;?%JQ^o^M>nv~S@ z1MTdQ|B8N03Ct{hArN7rJ0y7gA}8FzuAjEy5tRt!q<AS z#0fDYqxkM`vT8ak48>K>b~`<+-uAa;O8U^#&jX1(pH|LBGFDiu6SJO6kkfKKUH{Oh ztgQ2~ONrC{5)I(ll5dkXN-A*~(9SsDQYG8ojaQ=8Yg#PjHi)3UlQ{HK>*5Zn05W90 zj}snn_Z%s1YyzX3N9zs)+8r#zF&!3$^;2TDt6Pqy7vU1~JiR=bP?uGJpF4U!POAa# z_Nh+@V=@7){*pzq6n4}TAvRTkbX=5#V@A`-g z?{V5rCn#%OV(A|zU}cNa9}OKg`(-(etWQ+jtsi%ivzC2YG`HtygZWyC>VA{}_-e_1 zhtzp%-&_y))M*1O%&r^Hbq^+a+7VJf1@}HbPI#r3(k$!~qTt%_+lJ8#AQvQ)$Vk+! zof^;+C+MZOu@!X-`ClheL>tb)J`o}1Xz8}g7-kLgDg)=~1|G!c6K-NY%gM_Za|#E6 zW3su#d$K4;^kQi&)oP04p2e(gBQ+=6r;Q+a?N$Xm%zpP+$}!dbLLf5gp0@DAO>98= z^_kuAw!MM1;^~>gjfUj1_1WA4%7SR?s2V%w1Bn^2Min0F;8qFIYXZYw46P}Q>+Xg7 z2J~}>CZI{`$3nfetCG?f>ppsb$x5e+#wwzs8eS?L#)Q=$$cR4W=T77mpC0tQr^3^i zW{7n&%kY2#KTmiHvUh4io8sH1VYbmcE{;}K)a(iA*Xsf7RgHCZMCyJnJ!~q?1<%#a z;?Od-lkI9#Vn(vY{sEAcPpT&$kWFGDoJd%j+CbXcQP=Qzbrp+LhqjOW%|3&kSnTKN zuJ?zGV<$a`iYmZFQYBgy%%ns&yLZ zZtbxl(WtV(q{7WQ|@j@vpB}Q(QO4mfgp}AJd~`!Ty{3f*J(`5LvTw%>sr-T zRrLWEn6AQr!^dMBB|TAi;UBP)%=Hi;dq4R1q=B9uF(YV`q53iN$4J$AeD=#^P$M0i z?PIi!I-U2z0ql{&}8+KgysnSn1~DiG!l;GBH=~j^wg* zx6buD3Vf~})yg?aFO&-{G-|@XfxinVq<%~xqYF5@vjVc90Yc{K{I8utih6r%41H{| zx!5#i`z63Jj^R?NYouXt)l6<7IK!b{kGe_#+MwU;J!;X##&*gK$`&Bp%%#@ED&5k# z`cAsGA`*r)0xKV~7OF$(>f>Q+O%9qGi2A|stK12#zKzElz~B$|Bd#ch+fl~zgrK({ zcpjCY@dTX>)8|PfpI2L#ocPWvDoyBjMC~c9>sFna*4FbZ){a0KSGdnLUwWp!X;rV1 zlvTQ=tkKeO6GJ_Ypo2n>8?@;5==h&vRWf7kMa30EvQNg_`Gw7j^xLQ?KdJqQXi&w= zCRU~u=8LQ<$41RsM?teTMC_vrjvkWesv%}0hvb>cst1J})IoZR-8{(x`djTlZ>#7{ zXD&Up-=zPEX&Il^$_H-e8#X?F+k8h@;V*umX_# zhk>i(2F^oW&7rUcysfUja8Jb0fL*ffm|D72k;U07kD;g+n<$(9K-Z>O=#BeC72w>+ znn1PbsDXrt+z<8S2#|T#x1a|$f)tct92V5xdH_I0HEVsdrM4o^X~W%Cg&(?M#&~^O z=R&_O->;G%DT6D+dDnDoM1<}N^?*qPHvv)9kV~?aj=3&*Wud3kD=d0|(XoRJzLIy5 z-x`PE1NxC1H|G#vx!4BtugS3L1&`AZdiL5YTy>D>j^a!tx3^6AsrHiN_S@TSRm8d3 z-kW?F(Y&Zjw#FmCCNJBB&M#EdIBX&7v%(>*9CgSrD#Y;3GLV}Lr&5D))=EkmXg9wH z+HO=uPH}wLAqCmR#(-CQ)kzhFeutwqhDyi{kR_tRmHXF}^d8k*Qb!TjYAk!%(c~*j z0&pl)BEiI(0OWP--E23(E?hI|z#xfC(i|QK#T-mdVc?S<{(0l6Elcyo=y2SUkX`Oo}MJxgng!2@*>@{eOH<_F~@){YD3Y9TruRtv2YJz5l}%QR~YVQN{FWd5egTaz~k+*6<8?Rn^M5E zN{O6gf(+=CbmXn9nH8L#os%j8JDJ>KNX*7K3J^F>P9xrM!whYsXRwQ)+Rid6Wb%*O zkDCOA<{`0QWfB9jv2@W&DN3fiC=(r^pKM(r7*tE`lBRniVXKvBh6I_$O@O$Qx8Bb< zV>R7jn>`aN>}I&Q%#w^YQ$zn)+(L&?;aT{111iQGlj z>EVt^=h++S5(iSayh$p{#U8kJx7*mzSlZxgUx}c}K>&lit}^m)$UdO7zKQ@ z{s&z*k9|gJKEGeQMKdE_R9pi8VwWN1ia86o(ALJd0KO3_Vm}=r<^~^_AlA?27HYj+ zCONy%B<0RM^FexU*+VnQv6BEzcWccRu%jc@&U3e@-F2C;@M9V#oq~w$PYi(mQO-fOn&?^i8IER0eW zmlZ^oeUjp4)l~-l9eER*{++xZOQxUuM=!{L8<(-#ZnoP!+C`xF$`O3E)gE%kyojYM zX{P06%Ufl9je>o&SBdXOyIvfUt`i>U+bU!Ci-)Hj5>hlz<9*>tjHbO(&#Z}Z$^WDj zW#_@$3cqFt8(t({d1<~cWxt30(vz==MaOBIqEyJhGz$-_uudCZwC}9829D+}^d1vB zm?{dBhl$N#>xS1c!;HpB9JTrv?j`_biMC8Vwo-ise)CX^BKn+oKsg$#i+sLW1cPb@ zKRcTtx4@OZY6$WgQnl;jhMV?Xk}fU)l&zgJ)Zj8v09b4v-AcS$n0Li8+&_%88$cCq zG37!kMa7JgY&nQhES74(N9Q%~?vPwJX3DzQ(Ly1uZ}!(-5@{NNMgXjim|>L8sOFL{@O`Skpab>%5JI=$@?V4d^uF%A6NKpo&@X4Jq#5Hy zctnYK6TAvVy@U6$;Bb#NJ?uY(TOK_ZV3FxwY8JzU=jFPH;MxJILrqYEVAk#iA|JP0 z1i8%4WnhPs0J^=sGI^wzTiHN|c=Gb2Y?_Nv9s!$ASBK}5g^KBD$otgT6Od4EQlsfA*yZN#l2gR-XXJ>0l*}Y zg3E=hqTQdOitnn02gmZb8bQLrOj=v)~yoh1v`*nj4%0%w0HU;@F?<**pHNNmdy zg1ia6l0J(K^G=`{`ROQvew6e*0ZvX1381VT*!IX$j4Ts=LgGVGc*SW*)&=~aVa14i zw}MODxLE-*y&`bF2eGm}vIa`h&-7VT=@TnH;BP^sf%~Wc!wSdr*J-pFcMlvW3OyRe; zS}M?w)2Klnu!+r1^Jbv zkF@<{9+qOt~r1{cS2Q<5fY1qIjnMJf7eh2|_O#^+is^NJ<5i zl$aJAus`h|)7P;>=Qp4RprtB7`*v4=z6mKtgDstQ7|Chd94kVj6*3DHq_ntoJ@hwK z&l>|fX#6%MqlO(QCg1p^LhcD`fUC@_vkU`tDp_4KWIKUsO$zT|yaWZah^z&cfyRwZ zZKZ-2+_U+eW2OOX4uT(cdpQK~*w1(Ll<-(SYJ`tto}{p02P%byzG zhE$;ls~OB>DvNfDWOOvjnn<$<%xE9N`zRo1SdgfQ-Ul@RJa4)VNI;AMaM%^_noLqU z0HK#Zq*eM>*Rb{5%NTZnaCaaX0K&cP;{!bc%7)8;O4N;1VT&s*Klzp@Frk+nGKDE% zEa?g%HJT90>nhc66~it8hQ>>^s;3z#l2N8I38c|I8YOBt5ugBS3|CC<0k^z~m`npz z5QPK3C>x1%*aE#Jzlruz*Qt(|QD4T3fchoLJdmqW!-5h>o>}!J15Jr~^MeKKhs>5C zrt=0Oxdd;3VQTXj`aPfBMNlt1*CK48fd5} z64{I7tI@F91A!u8+y0~mukR=&7zTq&^U}#CyOLEEg6hh;C~B+39qBsshw4v!XgE7y zhqS&sF~v#}5E)}TR&6o}0lR6Ggo+LfH>yW1k+B0d0gXoL9()(jq2Gc-?HV=hlH<`? zFXq*XXVnqNmnEb!T-B@rtkuq^5z*4v>TG!X>tY;mc|1U|Za@w2Z5Xq{t!B^MoE$i4 ze;eP2uA;$()gg`ghe>p_p0?@R;4=mKr5&f?u+bA>J7PfHm0uSMZbwxlVSRg7*^+@EsT2#{iVZrhKt?E1DfYC83l+fpa zC(F=i(+->RKCHL$JuD!nsQHEE=4de}k!xh7M>Hw05#p!Vx`)zdgq0tzYOLB- zTDM%N`+wLmff1+(OjnCAi6GB*?W`Lu<7#)DRZ#|_Qz7rVjV$eE@2|<^7jQ>RISRj8 zJ?h_we6AX<=R1+2YJbcio=u0BlgF1!aJjSxGmkGEmCV;@a7cjIjq%F9lVTm%jt2=y z)t|mU)={q4fj9-6`3;l*r(LSqRQ$&7NFQYjAB=I5FT7B}mI9ri#*k9nm=)FwKh}V7 zv74mUi=qvIb_52l-z)B8f_i9FMtR|-2=!*md(8)yD(UiFa_Y2gYcSL-gZECub0Ctz zMvxoe8ek1IRye27phb?l+8*C44)xD<{7&Xn;m*oX0yEx1G4A3X{&6S%$>%zju5?oM zb25xldsZqKOW`(_dUjA5D6%9YI`QI-(due@4C%XY-*=_syZ&@I$Zr&9h3>6fa@1ip z^cF|(UrnwGG(VySTf=nZGcZYV=lt0L&AcD4TaJcI2ALCrk>dwut@w(3xW4{*EYlqr zg9@*{HPX<*#{zt;@pgL)o%|-vmA&*dbhS66f8A4tftbQB@{UA^&G-3~%v?#_MTG+J zJ^P`*~ZT__&jsxxA<`~6D1OJyWMd{g!)ipi}6|tld$;@brsb!eTk&k!btm87wVXTo zs`WNM-j`Umw~wX5{G9jeeg4UQTOC^mEq9ZN+-0XS(ux1mG+Tz)YhnTNfAFtpqRS3T zZswqu=GDu|w12;@%bE=bea;V&QR*yv~nrNZ2%zUOBHA8swvM`wBdhF!@NR(Oo|5&im4G zD~7lZf;DeQh>kjx-pV*nio$K)CFFy-QUmjj3GZ;_?L$}P#T(bjh08<+$N8=hgK?yX zF66E|WXt`;;m6vpE{PJZG5(hoz#5;nEG0vjX2?)>Qv}$%uHCLNyYiZ31jZ^$9GG8I z&f`_k(L=1S8_m0g8LJnBPw?3%PD6h+A1g9&ZF2c#4IpDfZpE;>xWX-+)P$1fo!RGx zE1DE`4~IO~jCkdSq_qg{cAHB{!+=y@14l6=Z&Vd7Ud`$ijh|Ov*m4u@Xdi;l`??D5 zH*i1F5=JHlXDC5Flt>Y_(%7yt4tiO0t4nNgz^eH;ad<9Bi%{3L7k0vNB3VWUPNXQA zhenX#P|68_b7e9z@^qCypnTqBt*PbYO8Yv+0Al?sB$CE9WQM8m%`LFo$1NO80s0P> znaW@{E?;9+5=@okD0bc{cXLyFHKZXx_-@Jp+%58ynCt_vk~rneYk=13A`rI8ps>#9 z;IVT~ICstm?)6~lc%Q1zj883C@ldEzp5YdV2s!a_HJixoA9kBd^tJ}^f?&*E-A{fJ zFnZ-C1xV+rG(JTFM2h|bxXiHl0h2EEmVs{S-4%_uZKApyf|c%(&YL*xaZ@D*2?{bf z#I6LlV83i3O)wXVz@hI379p_i6G9fOKJ1GMsUSU_|A_avu5sC2Q*X$gGSa+;wDV~a zhupyCea;)=(@=V>S<+`>4_{Q1=bg+;n2zM9hy)9ug1jj^KIBI7z%=^veg)Gi>~vkV zrrDH*ByV0HFw*9d>Y)?Ye-V+Of$Lq%xaI2<_1edj8!-h;YjDpUT&KkJ{*ZrH?;g)@ zo5NYp#<}MbQ`rMz1%}%MS|%Q+M4X>wD&j;ASsDRr@+oUK*>;N3zGwCGF7({CMI;`U zklw|yYKV*vmLH)$vxf>2C4fiZg!@$AkalO7x)XWQh1%*G9Hpf)3Sc#{qj#H=c)^}m zgPira!6kR3Q3IzkQGgW0738$XW*XEur^vwjrL+cyjbxTu+JNeIMcK$I7tp#(lQj|Y z?15{a%CNU!wS}^skBGcEd6LgL<~mXumP`YYstY`UgP07JCD>!m1sM}bt()pTa4tA+ zPCRm9m9&NHA7_h_?2Ng|($JYD&ymki)ZQM%%Y^^<5(Jz9Yr$Tke%%X!GL`giyDKM? ze#m4JYv>F;J=))a*&_T=bbtj^=cp(2d;l=gfZ+Mglc}9MQdfs-2O798C((vu_o{G$ zxCiJ`iRlr%BSt~(H09ceM{~POo!Rv1>8$^HGM08@cbj3^Mzxfy|FeTAHZOT*A~G-m z!tM$-*5SG#E6KZP{J0#&?X+QEZC|&lEwzzHUdJuMS_uI(nGr&s330E3I8Da7Qxk}| zOkZm?-`1a%(@yFL1e;vkn)({hBAmBmBQ@Cj*rXShy9lvsjIpbeQ|DUwDS0rgfA zT=;`fN!IHE((LYLK1xQ{Xz1f!azjF;I(-dJs@Id|;4?T4v+0ImN6pR24WqjwwH zMj5%&N-__U{75~e8eX{&RmbH1|KGk^g_yMCA6v(sPdo}bHTSfcQZ8w-SE2Fq&wDC5!o;6>{7lY7WXfnO2!Yn~`W$x5iP zDhs&;@wqE?D*$1kbqJZzI!XiQyDOKs&$$rXU1?;<6_wRIf+A`R^+q0vPF({XWqRAC znN8u;LRF$D^-u@*Q_qP88b|YCbwMAo0~cypaxpH}h%9183&Wt397LC){`8BE!LfFJ zz6>5TM^M%b&jlL+Y^XhkL5t_wKokW>{>_C!)87i!jZQ!V%u^#(@UIRGfzrlq1)jNLyZduK5R3J_ zw%<80ka`)ec^2UK<&*n%M;r)i;UlpKI79j;WSf?@6H^$Wk*q0Cu3;~MHGbzLn3dvu z>5hGDLk8xLVEv{JjRU3uEnGcW4Ui*y*tYReZ1sqO?#b&P$ULEqy-|bzVXK{mB9Vr_ z8l(riLZ$7Dp!~*q+?u)Jmc|eBe~jEM4D203ehLo;aVpQ_RB%0**62%Q{vgA`>dn;L zi%W`?u;JfY_+63#BXJv`@G!9PT+m=6IxyD0L?VWuHX=8kCNFxX;zYClds2cOc6j-E z=U>G5hzuUE5iT-Sa_75&$R$i35wqU0nr}REefhnFlljIXP!zT>Hbk~}f?Kdd@FO%1 zgh5S%%jG-J2c2yJ2H~kRsc1Yd;rN>usIi$+?7GroSZ%VN!@PL&7VfFG1lEy?b#fL2(LlR`6FZ$$7QW`tKMu) z#b2lAKx?r?-lqEySG5J>aDa6Ivl7PjP#h@ki|Q`}9G(pVTMe}T+U;0VtISlaqSI+7 zi39LtaCiWO|Um}H}id%*<675>b&3=mg;*?u8xi);j58T*R@traW8P#iy8XI>%ThxDk26!M^o zRaa;Vg1sq3&{EtsgN1feD~Ghbp}Kw8$kcOOaRP z)rb;xta<^t7#Ksq>_(ea#zk$+P{9pf1b^WD4SS5buT86SL_0q;q8U}&eOuzJ!+ZMg z6*qG`s=Yd2(T?)P<-lTJ8ffdxkn#e3`T&%jcp;V&4o)s0&BX>D;_A)pEi5rNuhoRGJmdk97a)haY65ArgJFLu~N6`qd8R7Ie#Yx3Iulc1XC3gypuZ_f7(BA zJ%Y)>7}X&kUOEb(WAKG|E!eXATe#67d<9s*J|>YzLo(tJ;JaVwsHPe7+0M)>L|B))NgMD;2cBbeSm3Th#8gJ{gE z<2gN#ntCW1hgX*98$&?b;5e=Wy@8p%kYoO~Lo%wkUnp_y_s|)fQ+impdWY+_y$2>R zECac!WjG?tQpYz?ejy6<)8YwxjOcJJPzX+8F%P^sRh%IoA)!~CW6SkG&Q+d08XV9K@~KtKv!EhZhTw zsWbsn6yjQTs{Urq#ip1GS9p=Lds=@3l}zf(8AF@Rp{#>ZL9BsJnIYgoM6$7Z5gT1z z%~Cp;EKMeQ3&{{gr80WJOfQEq9Wh4T0<;Y&Gy;~v_$x_xwWhRPRtHxR{M5MwGuqCH z;FloYUN4T~Y$PWj#GFRJyWMSeh_%Gsks!zGYW*`ZM{+!a7jLzr}!`B_(1mu!yQ zy?dAm`8Pn35JEVI5*lVMPOU~Ay>_eQBAVx|=on;GP=`DyX$se6y^x+EJvP2%r(fKP z;Ee}QkSo>P_&Pv7sIx&McM^&V10BKL-DdEyuYWDJ!emyIHA~3?n z)x24Fsx#LBrEnlz&;gkN*wv&A?gH*LSMeW8iM&>Y>c_)`a6K4tz=fMhh3b|@>=u&` zp3?JEQoB@G%bydVfT#od022aQ%Qn(OAW<}YTYwH+Gjk-U2HkKhyk+0rz-uGa5DvN% z%vc)ojVbniy{z#BMoiB?pMQotfo)-oRDq*$lCj)ofa8D=179MhdbW_EP0*}?ssJnK zkB{4E9PvpCq~~if24?!={|BBdJ-?)p5NXU)A;1{t5CcyFS8!bj2|1@L#9Jk5!}jHD z_+dEFgPp*|6wWGKl?O*l1L68mM%uPR{^JYh5EqO_t}C`v0@K3LF_q>9V`Rje*YLao z?KA)cz}JC8ai`FYPb@^KuPK(WIlK-B&prkJA%z3DJO&2hF1l^*NLKaUN+3J+3g-3F zdW0ph*+hOpjpSC_mfVU_2_DT8IEsv0)LBV4mW`ht(E~cIXe|7{To=TP9nntKWaHQ*0u(_}FlGR(73jxYnV$F?poSOt){>c6%)*;kE3Ni2BEbyoBRqjf z)gHdD*2Wk2MsY)HJ);LM%Ww$T06+_?aamWSk~Zd46wc}`hdD+QhL=*U#&+`+CD4ASoht0SH30n|ZUCzZ-0>xe z;xwh9UMk@;zR>l8S0H(8_8C6K^P{}7Vd|$Vk)W#CQlzSaQ zwU)GfzLnuf&;eS2>mZ>c8LCl2)5cQfulQb4dd5U;6%i-Ug_nl}PcsW&${PO=vX=Ws zK{HVGMjNrKGIt6qGoq-3b--#cRqNKA5jQG+Xp4^wEa%tpXM*5h>)?~o%_3ZK_^rPt zW{Y2^q+SWXMhYI^|0u#dAtCly#%4lm6eAk~{t~Wp$8byCYC~C@e%eb&n!_YU6WW@a zJiL-E#_ygZ*Nm6(;Zuf%FOD&}eXRv-NjLHN&qmphf`u=2;Vl=-8GF&PY;r(K)?O@) z7aoz}vFDJn0DF4RMoAtPwgN@yo4vGcn+z8RD4NALrh)RZ#hkg-iiL%j2iiX^#yYx};6(M&5485h> zO9xt@Z)!IFvY%`6SZ!MEz%TohUc&bs&g5PN2EZiaN2F?p#-r~lez9#Z(dJi|*33<0 z1}g$_g*hCdI#38wsOgx+sye=b(R&_}{ukm-<9{VH&5|uf!7^H$nF07V!2jZx z;~$t8ObxXQ{cMU}Q;HZR4QwR5<4rJtFocYSY~sVT61Z0m(6b`O24%jqm+_ab@^(CC z9(w|Z4krBYZ{q*)dY20Cw|Bdl$&W~40DmHtRm(N<2kxbN(nn$*v271gVA2Q~KLJ5b zO-9li0000000000n*acR0015U0034{I3T(k008tx0G$CUoB)6TK2ai!M1sv&%>V$P zmN66rslmctMxB2wRNv(KE%Vv?-aTK5;aAZg{O!|x`o4+(+wY0`0D0W{nSY$=0RQpo z0snhaZ-DrS`ggvT*p{pLZvel#cNy##_`eV*OWFm0U*`FP{2w=;>O2Pi$NrD@```ol z=l1VL49&jF{_ofy?*rOb|1anN|NpQ*uz&kK)4yi_?Y2{?n@ii+G>7AL#-+F)r?-{E zBixmLk>QKOBaTZ-9HEhza*})puP>*mFydIga*0puBKX|#m(;Ecsh~nvmQ0el4vCmP zu;yfOd1>5`gaI6E%Htp-CoH16QO>{Ty?xad$cr2P#jZ(zWHLs`ajK>kW=+ z=zy80y$kauPVHUgW!O~_v1|*j^@K)CQ);+U#R33u?S%Qw?A8Q{mZMKsclO@8#|gJF zLIy1V(cF)83&9o+a%|acn2c*ezGYvn#R>n062yszbIS8Dk&_gUpj(VzJ!jkVbf2a` zg8YZaa8ad{wX1}KX*;gyqka?_qezThJdX3_hdQVUp*)yn90ETJ3JF*oafC{2r`IZs zbGt71@%(}~6*}DBz8EM)n~~51!{_Z#ZJ8j}C@`zh3jhHA_u&fPHvzrn68FpFfsHcs z=OqDNQ;Xebl?p>tGhv<1 z>qN!ZL-d@v#1xl69E&F>+pPV6*-Tr*0dLbT%W7b0N0Nc2m&&i@`_4UVh%=OxRwVf<3zVhvPLj_2GNSg#f(>#h8D%Ys#NC}*!*9@Z$) zo*8jy63Vbc=XYik!IXA?^1ksW#fN2!E#N0&eRoktA!38x?4730#~kj9@KqWPJslWm z9fH`HIv=*`m~UNvh3hBA>o6WYwB%0HdB@$}Z@nIrv$Q15uQzKqzt5{y0Eo@wVD+~J zji$DP=%G|}24*J`#-O7ij{9_Dd30g@#)u#K389`Z1!2O1f0s39yXqdvKh|}HK)GAIpw2)Z6Dcxu@Zptn z=-Z2wU{MhvN18IHN)`gkpV7_rz3`3N>V-K)O-DMubQ7E)Geoc0-WryrysbgG(H&=j z*{;*bz-@04;pxcJm1@&P9EJoh$b^ZjIn;k3s^B+GdY5$LMSxy4Cmo98H2bI$H^%O7 zlqB*bQP_W-K6gAy?Kt!_fXSEB>tjMg44`?Z0>4pngj2WAqFMLG>V5hs)ApngXoEpv zu?AI{Aqs*Ov;?a^tuGmJYbEfX9nHMzV=-c;6V0S#Iu`IO!-lF;vdZUj)r4k@3nA{3 z=vZMZJK3dZeM)7jL-PPffP+^ng0~S+1K~rN`{ZvAYdc#nfqcTeE&*yzf)*Sck zvD((ucM#U*p&Z`&7W)l4(v9`}-Mk#>XZ*`<2cqK|KNUbqRauy}3_?oc2dzdAE)H*X z{gkJ~qf(;n9k}3g6eAZjfzLS8eaK#%d#9>dETsB1GZG?6G$0y0n!3%y56$gg+#%jw zD7u1o8?+I)UhamLWe(R!a=VDs!*DkapWt)f`0IbZ_hJ6h)oYx>=6@1(gA*C+stC3z zBJdF*wY*oD4{bvS|&}3e9~rGBK0dL-cC&;G>QK<1eVxA zJl0W1n!LrX8m&KIQJkfkK6Xv%toTu)U%w{_aZfZN-{OIl8ld#gOi@1c)(94<=iF!7 zTEC>c+%lEs{2mm~(vJ3C1MiS6Ut@4G6zzlOBl~_A0TsrG0~U8fC{NJly$N0n4+eA2 zv5TF5_Wl->@-V)+=v%wq7j`IRTr@!D94GG^UIXwj&8?TCz~fNyKH+(($1C<2s@9RG zg3$IMfa2reYe!J+-u48@rP8iq5BRyjOV*<#@1OpUa%CGOhaxo#{v6$&%u!oEW`?YBM=F z{|DTIR9{eZh9Kb4=J))@oQVc?|D*^YQE1BSW8QF3^#<1DGUfrd4mn}Rx8t-b0*NBr z+Q@T7bL?|Aevl_ITq4nVyO4WXIf2mI>)8oQz`}rDTtXDG4ui#gJ31y$F6Wo=ao3{$ z?!G}8hPq#MZqZAEopyRD?}(bXo3)v>DTlh|C|K3Iz_qf#=4OM^n2>DU<>aJ*kUIdr z?zESLb!P2N=v--O`!c;^5e zPhK_$w~c9_{_AGFG~>rj5#fEE&*kL|yuf;Q)PVpXt+DX-eCJ3}MXqLo*IVYyk$x>O z-#$2>SAZRLpEevLd^WKPVNEQ)%hd=H?FIYQ87tS?+&m{TLlI&H1i7I~t5SD}8tpll zP@KvOL^nA~2PqkXvIJkr>>Q(K``DjZCxf<(s~XnnqQ>mg*1q08I+xM(7XA?RQGi8& zNBqJUFwHJ+s}NT|UzBRID8}RHR$Obu*kBw9hi*KHHgfMiK)He(JwdWCsF4IpaltVK zkVW(WwVH#|a)m8-{%0Gk5&WNetoH2R%{gA#{e#m7ReYH%pHGvca%LCS;jRc6$loK( z8KICixx(WbUq!RBJUZH09bYUMuXylnlvt%xtf~5dB#lr&_{u+?ABHFs1%Iy?Tm`Z& zm&jj^pq#25dp!Y-zAgh1KWe&ODh#JVLuy1-cwru2tK;F>qAKKgv-`q3Issr}-4(aMj}d0s=7sAigm!36Y8(vZpLact;=S{TL?837H2Q^Z z5nXS8C`;<~54SPK|~7Nnqnl1pT3xo=fd+xr6Jwh zQn~|h{C}Xik!*t&V(v`({Kud84f#(-UY-s2=5mwls^&%(y@>Y_Ag5H&ato^;9iWd} z_Rn7mXDcjB2<)2^VJb=oMLAMzV(|FKEmy%zO^y5{L_wW1B5mbLV@aE#Z^*mj7VmcH@lHeTn?m6n&Pk zG0%Uo>B+D6Yr|NiqR#!rPe0yCF>tx89g?u9#f!Q>P!v*vhu-c|FLYpCb;x;7&Gd!* z+D^az+C(RSu|avtfqJ#BTRl0;pcF+K62;Eb5fVld>*r$|f@d1Qq)H$0Mt4c09PS}c zI|TEiLX#k;ZsaOIF=pJe{6lHb94AoogDU0TD-3F~Yjb-2ulSCvx7QpNbDi-SL!iAB zgS+|Epm#zIfY-^UCpU zzTn9rJ9XRE81@6xSba#Z52T9uzKRt1>t#O$s8W_OHc%QW&3aC@v8qkLQ zSXA@7ogdY6k|l4(rp99bkPjw}NmhRx^A)DHnzAd;x`u#0s@zcjfPfYgJs%U}Kk#$U zQ*h>~|ASVD|EvkjTy==RX}g}arwcmfi??cdE%4Yhas{$hCFZBy~S)=ZzRvVyX)3g)AN<+u5M!zp55zmv)95M&78wyi;cBj(lEEFTHw6~ zO%8fgk|O;yUM~1TlT3gl#)safbZJx(XAb!1X~eEb87yv?Yb3lKn|PE}amM>ser&lvi}2CMvTL{lM}{SX8+9;T zE!zHER^)Q5>a{XPa7X6U)LFHsiAjTDW+pcevO(+aDZZN%TIO^ z&LlvG@qLmtPZyrYUM*h80Z3RnEpLtBH)OxLCR9Z_U>k4h;IiuNhF99=A@?`FR(57R zAINoGg)UZ#fzH49Mq~YIm;@_AA;ZKzr_Ap*UIZw#Pl(ke?FjP|5RQ8=mp01r1<>;R zc?XvpPgd!Gaphou4Q#tq=7GPA_`A;3iBIP%A)T?+Zwrkj+sf<@=j5GPRmm7E{x$j@_Jh@p*jU^9xPL-Hqtie_tAJqFBVfm2s zYxB>bC0H*O7B54zF8A_ay%IwW+0s*uQ>$qwBi!aEzJj~$4N3Z;eW<{@6nfh@kFP_; zOl9zQw;9l10Ol+Dd3@GJVLEZJA>IiLp^?4+p90nUaRVTUazh2vdE=3W%+v?E_Ylp)B(q&>*rddlx2>> z--=B2cGaZu3!u`Vugu!FF1}F9tHEJt1qfjhA6ehyj;}>QpDv_H@Q=xhH^2C*KkHS} zhjwbgF?leJ8S>r>hdS+Xtt^tP+;H6O1) ziHzmAwf5+s{wplQ&FMuo{-2KRt>91NUW@?cB<~V#A0U~pMyGmm^idBfC|3SKg4``HlaF_w#>@lS&caIH}kc7kb-UMAJ~tyvFQSb-t((?Q?xJg!(1I? zwWwc^s8NgYI{Pd$}OuG>%Dr>;$sXMgw*gP=XhBU)p}SN6kYBGnyC{5 zbVEs)QU&YRfF5KS>(sRuzwJwzz`*#BLycwhpOKB9672M^?_v8SGIJ(H^dj+tbcYv4 z&J0ta`No%RrHdp5IS3x6`)aoULPNxpQQI^$n{#$A!THac8|&&K8@ExH+p51aN@de9 z;RB?{{fM(`RjX9POwT`$%ros+AwaFFI!0JY48^^YR186j}i8! zo$y5?{9~M0o@CP=M#k71 zqRzg9R0=yL>f1G{%3y=5Gk+&d9QeBL4vFoy*9`g1xC&e<%(j0V8h#8sp_9fqGyJJ2 zisny#p%0b{Hg|z%v&u5N0fsM91MC}46&=#eP>}4YHUzY42$s_VRaNgjqT9rD=*XsXlG;PFwg{GazqqM-jEG;Y5 zv(rr=EsitQ52ovE5dM#fJ)DPP20}=o)5bs`)yxd)VrVUc9B4-L?z&^E;GTy8SaCnrHVMS6k>M1IQ_j3=GxT#sA}`()e}_!cCKPah|_lytl zfj&*A@>4nzOFXYL#r~A9+e3CnA7DJXMboF{qXHr2Ng-C8TQ>X*BQ<%sa55373q7ej zN8igmjVp<0l8pnVq*f-a&h&vm?`#hC32^Lih9}{D*$X4hmQcbmpI5xQ^RDt@YJH0l z3}Q#H`Rs2A?9#*MHsqr;&??m#i$5ybwOxy=_|oQiHaAoK%l4aZNtl46ZFaY*+)8I> zLE}Az0sx~Ar{~#bYV>8L&)x7$Y#}079F9CVbLJ9jU@8uy`AT%NUn9xKDx~D}_@yhx zMv{FAdmY&Gg_54F%A6Fc+4H)=Qu}YsW7-PVE2*4cZB2G$gIz?`nbSmx)BN@OHr#Nb~Rhf!@ebyOmVOi1gsp0DaY=m|v2oF!1Y@c#{% znCIyki08HphJ6QBD1EoAz%%6T;&8Za%ipCw??VT|2cHbHljmCmu=&V^u51h#>*4H{ zSWbsY!eTD^8o)$NKP}@&88FvWm^4;VJK6VW7>HnAScTWxn@&iCi#?|J?j3i`#r|AhVxcC=nIs_Y? z3X>a;j~#3s2JeWPXP{zehmTg4iP_F9~z z7}zwbnk9mI>}(kqv`{AHjDu$lpBSSe?Y{N3N5tb$~7?NT@> z6q%BNR zA=9!0>{bXy?Wcq4q&KB+ATj0d4*{#WRz~KG^^O+`4D>Vc52PlrVBxZcO%@NKuXm!e z_uCo4NW?k2;eJY~uE{Q-Sc*yF`Ch!b-rZ~dcR|n4Dp=l-Rb$_n*;$`gd&NXh)Sr3q zTY-T7N%~cwYA7`P3HY%7lO87`>olLbJy%{xCC!lFL0&oBrwG-XGk=?DaoF}9l411* z_vh?r6a9(=&B&fp!?=V0apfRN&j-I} zrd3K%f}rPNGu;3_ZT{n{^cOU4%^j008$2L>#`_#5v5_aSG~MCsvdAK~Y(N5z(N(}) zy2F7zu8pJ2VfbvhQWWWKotON_2&RcG!Gr88O3sc-lg+)pS=nnAKGH3ARYbCh8>HUv z-T(jqK~7CZDjfg-00000005f+0Dk}g9smFUR!}%h5gh;kFPp$05cgn^ZKT2+%p*U{ z+5AHY1d?F(CmLp|=Klu{fPp9mPh<*X3NbJVA%bfJV#nd_+TOg4q;2Dr$Cc_%xqG_K zD;d}H*?cN&uza=`{<+eJiC5q|3z7P_D4lcRQtd0RxYX4Uokge>Dr{kKo>?CY{fvjDCl*@})3ZJ< z1`4X-up0`{9VNIKRXSPEZkp9!^_$&PEf&EW&E}YjP>U0iK*4^tdax#tE{I~C_^4b* zX;iHcIDvgZe(3)!XbwU%Rf4q&h&WCh*IkWTw_PX>>m#XC9Y1+*vu{UX{j}+{1Tt85 z=<2&<>tCW}qw*xh@%=x-`ZY9nOr=R%bBj8GX;4#<3bv#E*cA3Yj*gxy3kGWIhu&Mp zyoN+Fp|ARgYk^qr$aD?$tdBCs`7j|tT%)5Scx4u32{youcO>VKDB2Yxj;TTMGgQ$! z)@)@vrcbhOuI8d_$Ne}a~d`SsNve3NKDL6ySj;EX99N1oO804cmS82I^a zHf!cnS$Yn!LeI`+T~l$ONX!Dsf1pGn;T5K5 ztA4cs3*DT3|CCfxjGl{68x`9S&tqO{bmmRVR=r>ig%;;uupJtA!9`%*WU}3~;1*nF zg^34u;j#(ZMyT%Ak9Rc&X~l%D`y!^ERk))M?RyFS9B?66MateUC#9&wy1>KIaUz9x;^LiM0K z1~J`WqGSC`gmS`(Rhy^-$aOZ&PRr;qfirv1`bHAJvd!?-2l5!D5p$4)VJT#<+`~c` zY4oKS;^}~3nahR5<)_?&mq&gGs0ej#99jX2WP%{8o_@QU@pR++Zp>@74q5T{ll>4W zc@0sZ)wWvncILXg6qadYi2-4Qqn@oGZ_n^)krPspfzW88o89V<)ax7KWcOHOhehcD zkPTv({AE3;`c;V($Stgeq1@6#o8tKH>$qtTS735unxP{&kQTyu)b;T8y-9-FQ8%7` z!m6xEQ-R#k+~t5@ITE<~`f&OiDnT3Zl%5#4D53)u`Kl9^2Q%o?usVDuQvJ3hqrQz= z81D68czgACBYIjekKQFdCpLB(D}n0Hmfg@u1~x%a#uc)U5{;)ej_GU%sLgu``m30J zWt!0#u3Ub5#rHSjMKCFf#-z}m8V?m}uCukWoQZz8Zc8V;2`yYlNY*wTAj{H1X|-ox z6Tn;z4^xdc{Ilz!>YJE;>w49UP-c{j@^XYnu${9IbSwm<4=c7nm_J~3xdv?vCm;x{ zA8^KA9lbgjqM`sw*Bb;%{Js3Bc$6W<{ZS-s+c^+7?8VPScsfGN5b{`sF(g}PEj=B? z1K5F|-t?H-E|3miGXOBiPPMh4C$t~xL0)Dhu70`ts%u*F<3z!}@b~r#%zd{Ah4nk) zYdwd}%O_hD!4hVp<3UKy<{7zk-4MAOTC8*-C(GrdWl{Vqd-KxcN6r0@ikDWOxJud> z@y!K#3p&UwT#9<-ZWw}#Tp?JGz=nySKbUf{r(@denq3hAuU(TI1LHEIw< zYqV}{!sW9d9kJdZ>e&!Sp=tC~s?257_@J6vxb?#aEn`u0AYKhlcJCUzWS1vlWw~dP?1|ZI=1bMs0Y*^eD`L7GM*#qvN*-%W4X! zwB>?p+q`a=+H7ztmcGBTRzKP-Mv9kBS?SCBMP9inA2Pdl&S^Toq|2?svyr}_8JWj% zFp$9dghyJ%+<<_wL<|iBJY=ZI1H0YbIt)kH{3nU;z=jtNod9|@qdH4q`u*)y-6GNo zVe1Gxkfp)~k=PE69HS*Cgfkb{kT)U1KUZz~*z>&&S8cVLo&89?R{(AZ>-Q~jEhnAu&Fd)9AjfG8wy%}x$)Q?h^HEVh{j%Oxx z;^>c)0>RkREa#Jbdi7|axi0Gc^{w9j1`bgVwX9~=1q&({9;IMlLc)0h`8QA)a4)%q zRJR49hi-m!>xzb~oCJ~PKDj2x=E<2n_msg2Yp0JtYU=NN3Zu8lh=f>P!1tJc#h8b#|TTW261fKI?H6(2@Z@YDztHLqY`W zv2i)}m-*++KddE;6$t5aPir^`vzr(K|AEwB>6+@+v;X5npd$uve^Al>+_S|AaW@?5 za?OO)t9GekCgy03Yta|YKTn>>`JAKqQ%cwt;A46kq*rw?L2teMcQdVp?&=;pz#TgZ z+tkX&eRg&C>jvyEowN+brR0@LJ%m+Gj$H9Y|I$B?6gsHaX#Sy-bO3th!?J)16U4n` zRz-phw*7vkc&KofYkQv_gbV!~ax!}Je3nSi%yMu|k7V~e%F*EV*Ua_y#8LbiM`v`D zcr_`FFq|RA3vhR}$-2={+60CT`tG)B?j+65^QvV9)N7z{zqmIe2-of0V(t%gE2X&N z`PW>1BhOqlNijNq*&P-?1EZodd`&S^!E(l?fyv9kE{ux}@63`#AH`m%tDT00g!+fT z_CjIRM&P+mVx2b0T>Y;i^eLMK1D=rnlJTJvV~vw|^<$JSNxA+_HIk{GCSE$r+Q2Uw z8p(D;QspkmI0bdOq~an%`&=(^nNi!aYtS}x{?g}Fl98OFJX7=MNSaB8{sh7@KeiQ` z1K4oGxKO@?;_8^1y886_9ejLcVitugyI5!}%`u{hb#lhWzwY`pqRH#_7Yu&>B?T;> zcf}Fd^fAll#|6Z-q1CNk^wVbgzEhxwk=ONB-;7Su#)Gvf(warj$LmkjCO*xy=fr$Y ze%e&=r~YBe{EM|uedfWi5Rc30m?k$&YZzntH0>-lrVj?*YPBrS>ptkveVw9R zQ(kwcZ|mxG)OzE+KOBt>%6OzQo2(pLe(DYnKUv_Sj3VZ7J4)VA5_OHpiaa4hx|l=s zm?O<+92$dF2QLOmjXbPP3);NEpLaEm1&%JLGvCF)d7l-aecUjyKNwsAfu!v$yiR&BW1Jdstw}P#H4lq)JeOG z9u9SnFiCCBQ*g#X3+Wx-g-2 zfl6cDl-7SzpA2z+wk1#LoDum430%I2Y_1V1Rc+&pD{2#U_rj^PyCbkrh4B8k<0>zp&T_jhA)xm%Sbdk#-j}=(NV-uuO-y3HiE2sdBp{42pgzF-M7m80sJFP>dDCg{^NW%;&xB)T@s>s7 zkd1$4vYaTKb~FN7I7quAj=7qjMUvwp*z*KlBu2RYT@3}g3ogVYE_Y))0nxbNJYd8I zsF8O=`9}~K(mF6R^1@K&Jqiu(&>21ak#4I z#>EqZQ?p$g<-md}XEU#BjibyXv%sCFc*Gu*81F3qSBV$hSAn~J!#TL7?|*P0?FYrU zT+_|*j%4Uzjy1-^C=bRx6N9IVF~DvdxBfgvxuAX6!j3cC!>V=wK^o7L5_s5Cbx zwDRLkY+jTsmeYg0q$Jq3&QN_!Iu>YR?hB`@VWDhsfPo+&Dz`dsU>i4x6Se~+-dn)b zDj11_yW7Gvu>D^^Yw!BvFauVf6SFLm(&3~j^_)CT0>$K$0$Q&LiH` z?&pEWeM8&0-Hx)&PBw&2zPoIQwslN|CW#R&*$l_I1T=Hxasmdn6~i7oL-3)~CB4%1 z9{>G@$94^BUHlr~&Y@?g@?Y>kC#VGkSVart0TVh?l#KkcCY)EKUE0)s5%_HBgY_)+ z_4ZvDZ1d?>ay%Lb+bKe#AdeTRH(;IRO^jxpdT@j>#@$*@F5?CzUFX-9!3OJcqHFPp z)x+2?{$P9P3KJazB(HNxY{zn7%kJrh(|iwhR--8n_$Fq!-Wz*$rH{{x$JXK&`8&|R zLi$CxBqT!G8~a&|cV_aMU+{^lGswHOIN$>U!>OIatQe%G5QFI$j{rs=+VKsCXO`VK z`8e8qVLaO6m6y=r+u7jG#_X5m5&rlm(5?qmUe4B(wa_3NTz0{4eo^AUyBgXWgEeV3_b<2_uOLnT;eWue~u`9y;N zXQ)!rt8(ujhj{uwMPW2W{U>4)tblNFccY zv0Juwo&9j5Jzro4#%eQ`H!5gQE1k$35>F3x3$zC6`enK$@z)alzOAdR+shZPB)0$` zIxrD%!A12Pw|4eMw01D%44>012cW4T5N<}^nKvl?aMf?W^}BEBWn+9tVkw8@N&#v}*BmwnrUmlXUGUoiS zNJq5aSfzZb!5g?5r)3!T!-Xi<&-wC_9E6^PmQ#G|i4Tn8+C66n#EAd5CH}2|xYuZI5t6cGD2THThi3a3?rF&f) z5HjeLS zyre&;p@A-qAI+6Fm!XUlZh&-(`~^fi;S?rtgq+LnKgBsge$x+)|8a`N-wK`Y_l*vX zm785+;^9c0rDbjKza7YUJ9Al_&oBiXrpg^9b!54u-)y}b)>UB$%BQm3AE!{eu;ka>Wvs|}wxmqIY2D&=Kk~Z25C)EvL9IrbbC-7tBzxh8%_}t76 zoPjFUp^CPmm>ovhdbNY24OW~u{-+FdB}smQyBlw_^xf4vT-*?wnY8VN-)xF$tNjNr z(jrn6e=|_%B*|uK*wzDXP|zJ*l$a=Q@Eaf*c&}uRCJGAS_ZPsIN*8ta&|S!WUNM9b z7SoLWs<5jS81}x5xH>k+URS9HbO+;VTiMzevKPDf52jB+`6F0qcG@Pzw%_5mmwxln zoIkrJ^gnLryVW2Fh{Bz1095WB*UgxLbLxf@3Zd%;+ZeGxs&OJZe85bOADSIBsc?A{ z1b6SaUexOw2qo+E6GeXSa`z}+{DfI5DM|*lf}xdhWsPg1k*V3+UBti=jgw737egG< zDa{s#WV*+gPr**Qc?1K**G~z}55+NsxcUmA*8&HUVD%xBYbQX>cxV7++vv6{fswh@ zZ#{0q0g{C_!NrM|c|aT;W7Tc!-s+XeFI^8}G)Fevv};^jXo6|EPxr1C}OEYZ{4w21NO4lzn}L;O+ykp5A5ht2QEKQg&bIP%jvw` zeoGEHpuVuBMI%70BeNDYjHZ%;tFXWV#>(D_F4KYE5CmXPwim!7%Ae@>J!N80b9I}# zv@O)?`wnoSTUtG&XKv3!0l-@>mlg=Iwy{f9D((g*{2X8=J6@cM;N3tFt~7{%f^MDE$dVTYd2AV17zZMM6TeuAb2Y9T0(m>- zD0ruIkgek`I%bQKC|5IiS8|o3HeiE^g@a^Mh@)j5<3?_4E8z;qVR6IW;z>;R|NolA zCr9AdE!S$}1~{S$pjPKd4NW)aaN2O0Q^`W`c|-f37$Vr-V-Tl48j>Nh!Q63m+raYf@p|tl^d~Fh(QSrIwB!tSYj0QMU*DTt{DO5D_-bB+Ng*^@?3ZeP}vd?R0^cq)+io#=r`++u{nAB<;SLnpOd;ncJR<`{+utv3pth^v2k?>=LR)5JArR;kW!rS7oeD_E*%uF1(skUrUY>3}}&tv?WozVPz_5`Ec(7eKXisSAX9XO2g^^Njp}t6ojM_@@+K?$CONe^FUDS z1-S?0l>qBd!qozcfg@WVC#DcNhA!S*RQmn25nK;Ue?#maO|yGpK$BTKq~iv0+p{sz z4`4FN_2zQND_Rt4#oa_9Q=RiEV%egcIw4aZG5A4VQ|ASi0M{XKs)x7?)(>`m{HYg` zLhyZugbp}fP_s?vvwoH|x7Z|n6Anc!wm3LRNYJOvp;ompmfoeljj23czT{#W#0h3s zjJg=BD%zNC1Qot&*UJ=2bIIl!i|R)wa|0*%4zDKi>s?)vJ6B-ss|}tmL*{t}SU{c- zBOJ7tKoNAB!H2tbL3=$-JuQ`^YN{vNxqF=GaB|z6cshQgl`wwzOcZ^Ek1CzI-*la0 za3)XK?w{DUxv_0KyTK+K+qP}nwr%5yH@0nSW1qbL^Wpt+&P>&O=&7l$MpaMW_tn44 z4i-ER#&yu>RRB1}#TT`PkA5GHc@bdCyFpry6g>NcK=1L4%m9#_A#%m*5eWM>SnfNn zL{NJXjeA3Yw^+@HAG@LYHCo$`w%^n6Hb7k3eQT@`_@k)}(h|PQjA*G|a~nICY>0;N zQ3&roR9uS0dHgyg&N|+FqER;r!F4z>@NDqk@d$tSOS<}AL5eN3B-!u#f8h}r)_@AM z;E>>MHZShtknmMLqx{skHI3z3&MG2i$2}0`0s|ZFvGlY?i?t8=#AE~A5v@1I7LOv8 zUBl%-Ja<2H%K#JHfINepoMPcm-|aHwEcKTq?0Z*qUDgmB;qs z90@ooAX8C`_jS>H+;oG0q}4@};3csw&LS#l6`|e5wzd!`Z&yhrAWWtddE6Y=XH_|P zTxb{5X5WGwArrnglATzkhUeH}{|H95^*6qWgj}e$V@c{*JK4G*)U}%A>-LeJEHOCj z|AMk>aBB}4f}GedzJXgm3&-@1zQsZRx`AA}fxbeYJx4uKP4?@{leN*t+dbyw3tY%*T`HmI z&0^SvXz8Vtm%kBDMC^;w?<>aki#DAVur}SX#F}30ij6b(lQ+h8~L*H z%NN_jNFxZve*6e^{CUJ)gN7gKykYPj3yJD4ZJG}xt`qqzjQ$Ib%|wB08a37BEV0j{ z9{M&csdI99cxVsT`ekze1qRZA+8;{`gsz#gJ;2f1s<|*Ub~qfl?eE?GLfYXu&T^c> zG1NLfA+Fh%ufcC8xQWK<`9Rz1z}gq|-?baYHjIiVg6NO~gxcGFED||wMhZP4Jr~j@ zK66spzjHxynMN@WQ#%jKiH_)c@Wz6uY)&a~G!X)iPT;3M}#Pr1F^ zPVRSLE#oZ13`%P)k8ZbLQD)mSeKq9D0lAO0mYubixtHr>oVA%lFa`wp00cYC*tKsa zQRnP6tUA6dcYht<&9dGfJ^@v%F4`l0Ac1qj`q_?6?Z-M(b%YGP#m8xdzO0`s4B+9w(=#dM)czTa9D3ea(3m z^Ds{OyV()XUFec_TZvZ-Z86tzmZE_cf*^ltu8Ab5K0&;o1$dPGaI$kYt3-c9FYc%U zEreOz?lBrMsY0I9Am_ewm)+IZ4X^14*={ff>EJu;lJolmF#id}1;?Wmoa>;K)v?`E}BWgI&e(0@OG%$d~Bc1`?!xgYR-9CI^a7o2r&(&0GY?2(w5+@@+@ZFh*xrdqLJ+xJq2%ZV1 zR#ydVY$tkf;&jKgMsl-(AQC}gyOPHlLiB!+@V%Kp2$9;v`qrADy^fA6XZ+{qTUQ&M zr6B^-EN0s&Y{RT`9#fyka|}{QHUG3az;c9$_FqVBAapUUeN_Ead(2e34N+WXw)-U9 z`6YB+4H$Bm{S@{wk6{#Z4^Ch)ncR|Js3zFEjgFRfwxO2)@3F|;OnYs065E3z?BgDs z(AMvMb=DqXJmy*E-k;DcmpaZmAA^0vJTuXl%E#dE5wKB-Pn8ANDr3<1jb1 zp3QgDJ6g9yjyn=4>np+b6jBB7q9iw3ukc3J&mXXLY;0>K1}vbr!?ymBI;1fUyFsU3 zi01kE*Jzq%vD&(A;@Okdr5i;WM zc6PhF0k&dHiM)7^4&zhT*PXjrZFLNy-i~-oqlQ5$S-*oCJLLP{6T3Hea|6`0`_?5@ zlJNNP=;Gk;yLvvG(f!BAJ)Cw3mq(N*&^IP{gShsFEu2`-UGbg=+}d*>Z5?Vrf#Ouv z=EKq9)%va5chzoJ?l(K@uG8cEIo@xNmu`GH-n=>9=a-Ws^vqj2k4%h&$M&0<%wvp! zAQteBq`P4SRIlDQ4vwDfT7IEWQ-42P+79e>wr<@XFV|c5Z@<6K&i3xyymgCY9&Se* zhGo`jzsf{moY4-cC)@*4I@~!p%G&N;)Y)!-*j%}K3hL?VY@J@-9vmKG{vAKRyu1wV zx}mq-=91ugcjGlFqCLXl4Xg(kb})@9Dt+<$WoD*+-%t>8Hdm?RoP3Pe-yQ|umqVxR z$o?ubBQK$2bnAJPC-c<0^U;vAZT7hz=YEv7>K<|__nCvQqoZJ34#9K`e(Z7fhV6Dw zq}SU&9|up*aaaHE-p2Fj1oL_u*55wa&|Kz@b=;2fv}crPLqAHUj?~X$`P}}fxjs7F z+UV-qV0^mB%G`J7e!9nuOZ}fK^ZWbtb?>*O@24{qQ_O(CS=I>7e$;u?EZoG4q6~~E zG3ozh6F=kszg1K_-{t?QsE+?rQNbVC6Fvjs8=}8WB>8ss38I9Er5(n8918*hu@Qyg zMNz_-;U(-!^Nc1Qc zLFk*=LsaejCpGSj@r%OwdgRSL7w(@5_VX#)9QKDpIXGpPX-6-bA=>(_`^X2ln7WrarY&81Pl;N0Fp%w zG->>smOuad{_*zVwXOThBM;NjvtwjE$*b}9mK;Rz)#WNbPN`-gMO}$qnj&@$zf}uB z&IAD!To8B9^I4LpX<~B3)>+NY&Nsv&#(o)b>xI7PU+lz4aseJYP_%>rTcf*%M$csb>4`HRab%qc%;F&Oj zkhtI@uVkFS>8+CQ^XIw8=+i(&^@~t1k&={$`Qt#|j-LV5LK+it5IKr%6Qvy=uowzk zLSpitm7`?u@bTl&?+e-}&kf zsIQllZ?%{&<}1s+D>ri+`}wo7`P|YX$`{-`+5Jx8;JiHp8RaW~I7uLV9N7KzvoXi4 zc27?$2(HYKjtozcseXGCST~WYeMFcKfk>hwyC9M{jqdsjQzznINaO(p5%9e` zI^w#AsyVnt4k;rE_gxQlb)16tn3BfBoy4c5$n!ay_EP62n)U;Ml{|Nu`wQLND+&W~*I~%Eh$R)?A{uEdYwFo}#-owy) z?$E3tjx$zS9s-mlPyTMT{K>dKpR{$?*{7Xh)r6;2e2i4$?pgdM8`SXU+~W+Qfj{dF zZmF9^C*2t4dTVz0C*v9Ki65CC0PpPjSFA}%L8Ym@3fG)>5S56u!z|LkvQrnP$;BnT%goy!oS_>F*(RD!PtFv&o(mL6v1+-QX`Ri3VKEx(7h`Xy0SVIIP4EGHJFZ5=&ti{#<)On-v+<$D)3qncPv^A?+Ofuwnx&cYlH8LlFI3IxAl~9$dxPqPc zu01+imZCZ*rw)wke1OsC(N-t$#W)WBp7Mv`62XEBO>QBnYmJ*8u?J6k*z7cHCq-## z-zyt;jY2bx5mtX^Z)rkHuIb6Zhv{k$6CaK&>Mc?2kiG0^CvS|v)!lbL>8iUHXka^j zFM=cleT2MKpcsV*G%}ocWq_yz(frh?&p7{;`vQ+ZsA-}2U~f`8kIk2qkFDwl&Eu+O zGMwLkp?}Hs)y%Q72bP?mBUMUjaUCpUac>LaJjJY*NAThdPYnb!mE7W{p*r#i zsJcis%Hq#b@GjNG#qkU3E%WVe|K1_~@zdbvQD4W|jQ*3WG)^XFSzJ8Y}rL^|#L{ODAjLD%`+1)9qHhH&l00I~u)A zBdMp2>MIlrl)i7OYWtQOS3|cB?F#fPkw}C3PveR7DV9O47xuAW(VkD*!}EX6!he{x zGjJrkZ=f&+rnS4LU!`GCs%ec|!*WpkL=HVKzkcFu(KswHU>AC_l2$IAuEJb*7v%Nq=EXMyN9)(o2G5Jyc7Q8|dzExKlMJZ?R!S1xy4&mY#P^O6RaXIA*K= zY4;U00Z9~==V;5jwJc+DO$XCT5*kYTaSF$cVV2b*x=9@n-}ofT;2uYij0FJepH^_v zQz}Y(n$AcFjP2_IVA^Sx!g(9H5C$V9fxeWGND$yIi9{${^t|@gIc$LMwU4rtZXUrRuEXFc1j}5Bqg>{ z`1|V+n5HzpIN(lm<)?=xVRh<&4vuQZnE#LFZ?Yg(`_=d=T>2lMRczDSO0`Y~e>3qK zpkh^SGSsl6Bzb}M0~b;kL7bXHsap76;jB9@@AK@pE42@$3Df(Gw9b*8{={;Y&>g?e zC>!=X-#n2}0C!Vy3~_1oxFB#Wa&a};BmvHzJl{dGG>G)au@kO$%h#uvn^bFQ%aE&Ip zVh?MOl_^5J)5OZwpVf_WfNyhBr+nMs?@4R-^N?I^fy4}kxUjC!2V~wX>Wk6w+a}n^ z`3+;0G}peXeikJhNUyBv_bcp`od#TKcG>sqehN?x`Vh(S0SiN2(7~_uFDwXoLExPL)K5SY?eY1d>RGG3UlLs$(ju<09c_Vn?A# z6S3+zAdxK|^O$#CDWr)|CuE}=TBzg;c6I{qgEjrnjwGW3fO{&FW_4))7x;Iu|E=n; z0!(`t&jm-OZIRG+`+RdpIWHHl5F|LIS7()i>Ff;Bu&)0 zV+9yot6DD>mEt3Y7d%Sapwe4_I3s;_1md8>{b^AxqHtv2Za?v`3J^ST26En#9!K<) z)tMj=LIf;z;c48o0kq`v2dE@hBOj}o&R-aPk0x69BBfnkdL{HpkMl+C+Z(x*mlbN- z@G85>+QoGoiM7f2C(m3kgyOw7erk&8;m`Zl(A%3(4JFPJU&04z$g71+Hh?=Z zq-6~yp*d0279#Jp;Q6BOw3I6^^Qij`m`d?FdR=G2lw7y~GT)}=dZd(hh&J_)evTC@ z^akI36-fRqIrubCA0*myUisrd4sMjq`p~T@5t=yPE;Wnl3grmUUb5la)c(B-XDhux z^BE7Sb5b=w6oZUjk7t7yh|P2B+i6*Re$n85-;xLJ{-P6sjlGFR?rNo7eH zBrzZ^C!inDZBBDUnL7heZ0CwO2Mn#0(#UxZepRkryR3tcErJcQ8JM;U4nnqV^TU$r zoXEw02=`qgH2bWRtcp0~F9-)W*=4#{j#A{b=s~-M_G(4(1__!OKTg_nF$C^+r$spA zO@nj`e*%o|e@iZ}{Q4X&A@KK-@&;Mm(D3mLIKd~O?sBnSZkr-?<$*wt2s_`<^NMxu zw|-4Js?f3fNpFXE$dk%cU-Z zPo<>(1&wrIqknC&;T=mUhXv%S(J|Nu7($E2n29S$BLUuup-OIZMf{iGTv_YYwQ&$G19D-gTjezfHHVX!_bW%d@z;QY)Z^}F59e$U%+ z%4VpiZorE%uEU_jYk!I!(vHBd>ify{rb~aCXWuY1$Noi{cUa0;$edb>srjvGTmEZ{ zDi)Fozt$hJ(wr306B3?LW@0oZBorZ2gPI}QVv&52+=wzCF%V*9eNMB@U?SO_=k2c~ zrlM=(QqyQf1yFV_`GBOMeq>ZBt&sR^KV?S?tD7B#fZ!nQ#y#*ZF{?7yZJp+kb-{7> z?UzQAEa)tL268kN$p<{P6MV6CSi^hK@RJ~Us$GA?SsA;8kXdL_9fe=}!5s|G#b?Ja zn>k3kEo==8Q&DhSp$3-cV+sTsNft6O9d*JKaON^(e+1iFjd#FQVixiVXP^Qoxd}K+ zKHXs%GJmkOnk0FvhWf5r$lZg61{oUqg${97WIv9v_#3Z-sLA(Bz19oo=7)=;-V)QX zC@?!0lRtmg3pt6+lY|2E$ZTwwZ>5E~ys=r?rI zs6k2O>tx9(^dW2e{4M@x0S?crNSzCDXD0MeOc)j|SAO&vnGQFlYuOvagX62Dq1OGDEQfGD5V*F*T5``U?&# zp+&z)5sn_VzU6icmyZjvJ-f8qf9yR@e(*!^F_!6sHCQPhdRKR03P*GRMZupL#1NF? zS5`zH&^aaD%@;CjK4{uCJwcUnZM<3-hP>NftJbNy>V=ga`XZx(K`vlTMLST>!-x7Y zz~S-g5_Oj@rR$X~3M1?!V=ZI0jC|Q5pHi>h1$8<`1!=uxpFZ~(4ECiZ`|ry2^`V8< zn)Ma659IQV^vAR0W;sVLpR37x@@UAcrQPI{Wj{2>IfWa+4Z`G?;CnLC4`YB#~{MoY=!XLn1-L@;@7)$Rz`Bn!b)XH11D zn1V(Gn?EQz=FHpeJ^=x0fZ@CdhXFbf1)HlFsE^OrI8z~SwBbF~s_HX;*SNc-u=W$eJam@5*&54|8tWJ}&bwFZO z;lLu*U|k>YlK2w@1QFX27};OL>2k0HpzzSh18pTlo>>fg1rN_lQp9Iz=>^`Lh4k8) zP}~A%?B!t)`LrA@EI}YAob8SNI|IQnhpaa;Bac8%e}&Zt2Gn#9mB8W+n%(h?H0t?u z23ydQ>~KRDDs!wm0z5(FNNMq+CIz*8|4Z_Z-d<(@>z?SckBw@|t8KgE()V0)t}?AZ zBpDSlgmqvy&ADxTBi@z9Sap~M1bQz!iFSPg4Qp!WqN;umVR1n$4T%@)103%(ZpAOW^bx|W z(U1}!Yfhb+-q{ErB+=iLs|2s8{4bE9J966FvM2n+iadb&(5g|W%kM*u{6)C9kRt#7 zfc6Ggx62is@Z?r<+go-)jo4RswjPrrbi%PTJ<|X!vI`kB9@`PQmhx+p=KB?r1hq}N z^(2^Xe&yGz_lk?&1*y<(04n(3kd+}Og^wv_NCY}baZFc{_v5A)@pCXU!b=DLlPlJ^J&&LRABK@?Fq;1{=ClhXYoRJgPBZO}BB)$2%7+#}2HLhVBz0R}IX7HPy93iTu9F6k=_Et7z z-hPzh;xn(QiCu~;4ieYkaS(L;@|x{m5DBVl!3-PgBh&{GCs5qaf>_Ur&U6*?B3#z! z_!!Tcm;8KDi~!UuD6Dw?x9}(jMO|iv(2-*d>&~dX1X-I%@E|Gq`Yl{$@h;A|q(Z$S zK}6Q_5bSgri?V02^SC+P)qPK(2)}ak4i5$>J2acg*Nn9n5qq6Vc&Fb$SRAGq&NWI2 zV{F$B9>%}YrxS|{i+c>xQqmkY3FiWuWMcJVVkiGi`(-a{gD!thK|7&t$PI$bdP8pj^;NzGdl{`Y=AUmGMn5*}OqcM^=c0;At%4Zm%-fUJ9#G=++?~d%Y*xl|v`y znzsB6Cs`D#WHRwUNvZv6$b8Lww;7mz#gWktXtZG;h%(f@-IM}16%Fdg)$)5VT?kDU zc^R+XK?-S`Mg_VcRLUn%o znGuS}Y=E8Z5#ZW&$Z&7u=nz_Lz=CfDW(2(BusL#AM=W*rluEWIP;g2#u#@KKDq1($ zc3IRdw1_h|RSZe1PI?SQFk-=dAyM$H;71vxQ96Y=KRZVj{u?qarF8|4l8WnPq70N# zcl&1uwJdhflVn`#L(1;}bXK5ZSw#z2m1LGwHoySVhHwt2rB#xwJRX(a$$+v>pN(wE zPSlyfuXnp8za9Js&^u?*uHl&h1Eq@cl7i8J2c4^VJ%S|54T3bW?*qQ6)P)lJ=(iM( zs#`xpmuI2}96Mh=QXyGQOfFtr3Ls-ZLj`E%w~LbI7Gxtef)j=uAM}DbisI4=t^NCN z2q3Nm4Xm=cbMxn#VB_@E9pwFx6=66_NEO1oH05Gq_*_gL@{X1uj%m=A?2Z<+Q;AxP7V7m=2*EXZ9Hjn(1 z-G4wbzmLC~kpn}fm&?m5Y&8ZLQHwxf1>j>vKY$ai8IdP*va~LRSGqtK{u|#%Y5f#O z9CCTjNli67?s<;Gbl5n>2#D)IIk7Wu;qktld3Vew*wv>dhj7MNGfvUA{n-?PriIT! zA1Pn3a$6Sgd}A17+!3urN^MH8MW=XWR5-P(bs`O-F;tAm7w7~uh-Fn?JHO#92kn3y zsIGT_+PcTmxAC2;2j4eEgGf9cH`nnKStrKh$?O-R8FC77JqMvA=m<96kzoi$q>Oj#Hg_zO4SqvQ1VZA<=;kGb|aMcs(Cr5}E~6fw(#1Fb2HBaM>4 zNy5LnRANw&9W1)CKvofh*z)$!20eQ7vQ)Jn;lwVM@TswKUBl0kn_74n3{tf`^8)zB zA9q8{DC4%PWQxSt0#Vgp+_`A`A0OV}|HFru`7U1&|6e{lVU!pcl`7T_X?)2Ad;wj)E{L9Wp(h)WK{f`MsENL+Z_wYMUUl`5rhQ`Cvp;e_q3Q%5 z|TLz1=D)<4c#k>JBCn3}!`e5|H(}>8+fKOi-?Q z1rt9~*TBmWTM{h^{?Y^`LeC&}kvy2IUVr9 z6_Wb`XlTMCENWc<&tJij-nd9v!Zt96hpwrzz9qa;V%{@BuH835PGmNv@Ov?*cdo|4 zRqQ~e-dqyv$fUWCt5-4OK(iEO$M@v=bH}5WpdhnS?%%O;tSLJXmT8gUf(cgHI&r8~!G-Sqh0+XoT8FFKz%Zr%B!#rK5 z#CHZOH|GQa;#(My}S>4->6 z86Ulp`n~pPz^n9llBCtFAKdZQik6J)8zN9ev&&tCcvGOx`7-dYQeRtoFM1PYjwUG` zcpa5W9WRfW$5|U)u^UDqRo8dY5jW*$)C=MzK;UP(y^$gZ>#PFWE|!6pyD zX7nN)Ox6dO@fhp&u&DGIoBf=HW-aGv3uBrZWv*PIx=|1bVWM7f*SQ!ph*+|OymTvj zaJig23`^k)YHWASyj@&#DS#4(L&2PD&DMpKd-#$)9jWjTyuWq+Ebcl)b&k5L5>Sga zq^S#`;i{UsRa((LM?OeB@P(s8*mn&{r4+-1pHlY5nc^&onfJJVNMnSCGfk0W#6^U6 zqb$o(+$2DOycwoKJTFAvaBDWLe23ZDP-yx){$nUhn&?ea8c`X2sl`?=LEx;uXX6KJ+nyZIjzd`GGW}yxiYH^{zM16~e<%6LF@YNzO~PvZAbks3(JculbYoQHgN0u?fcm>!HVCaXs2SwTrZE z65%=;42K)x3gK(qqy&R4DO%K%orKqAo*N&<^zbWUZ~Z)h@7V{-u)Z_0IpNJO>8Gy$ zP*~ke%*Ws8==D6B6uA??!_w=HH}O-Z_avE$B82N#54+bZvvQCi@leRpRfeAJ)q{RS z|FZ)dUj62vy&hD&((M$mfYN!ixwUgVIJtV#e7U)2-|ua7E6*tU+l#VZ#&gJxB&)If zh_rfG&Vp3PK*>^B4}F4mU#hyRYi<+H0`vDDV^ew^#;-iJ=Igf%lX+LBVtJ+J?_w~q zjwzff^Pa?8;!-=PJXIp?AED`wX~GaF16DqGdTS!9swwnm{QbD!Ozho%3_dIGrgM9G zl44enIwS=S4i97=1AdtBn%V=S!GeAmoL;0A3H&L;8Kr6ex0RTr80=iGO#5VYsFSo# z1(IF7FqVKNW6h1Eg3J4UhZy@Ilyb>~&|5}FT_%~!<1g}irWq3nj7sdWtqZsEH{o9$ z((;wR=G;g7zJ!gAv`1mjEBC3~!hmu)vG>@qy=V{C+6VTto{%{~O|BZF4d-806wTk# zf~q$s2&upB(t1}KM7}1_4zQWK3-h3dn|gBKC4S4q_5riMx%FgQkfdJVoI~N`w1=A^{$+(D-a3WA^a87l~SWb{N(hf6$A^AU>OwS zNWYh01qP1-LpK{Zouu$}KIGKxcSoUO&9F6}D98njM09esp(DmQrM=Rw8Cq98bZA42 z?npD1XyCxKsj*-ELj@wIc>Eymk{C1Wy45W?-$SCp;h)r{XI&VMH<=z!tCP03g=A!xO?%xRf;oyT#8`!`!J0!!%5rfU;JH~vx zJD3%FV^BCE#93cZ3r8Ac8|=o3r5M@SHDCW3zLfp~lpFfN96x|$Qun-;r%CPKl@bP$ zY@qNPW#ZcP()I_^+vu-okVG;-J;)eppqCwJrJRCk7lyvZWBBIjEwF=*b<^(Wo#M%bmjY<>MRLXF zQ2r3>M2TJcJKr}#T&#XRuVym6Qwp*O2# zgN>I(IfH=@T)z|~19KqU-Gd2nOuQ03)!(~v_pgT}O!lYi``b8!s);9US_&2a?X1qf znCo!;7eL_l6K62#Fi1_j5qt-+<*RW*ma5Qo7M&{UKbH}AaTsFyS&Ew*sE z^#kg^#Mqpx{{$bxsuP`(<(bV1%ObJI_DiVI$HvBs_O)asMOsgd1t8DI3mE7Mcfks; zj2dbo2VT-H{1o0Fw`5GeiZR1rc0!V;^6TE0;`9-JODCLzi8Aq!EFYEA;!&U2egqu8 zNnQP<>w{zoB*Lk{Tp1u3{THN4A!d+2up75nF-#Mm3R=OUwkPm7tyJRRZJOn)e1JcCx&aBc++jy6B^|!|AekwVlWFIAJ*n(BN%ob}tOZ zU*-RY5mKeKkoc^PF$a#>YI`PB(?E>x+Nh65N9*Ojfjop(0A{!2YIACyRQSg z3(>*eTOrfhd(Z?zBh*!v$sRn5qe2M0WIaXEF%uzy{Cc%yXyaCAqmLd^emDT7H5P(KHgKHr zJWp9wuPBX-$I#y?J-}gqQzPnwtcfj{!rt-8OyV3A>uTnt-SE8i4!u>I-f;Qed}qgK zB~m=P-qnCQO~Gxu=~1OB!oW`{uW#QSf0eF0z+#v7=i``Ki9j1X{%Bp1Zv zuwPEv&j2m*30Q`V3}Ba$>h(KJf36V#5n!bZ|x70qD}m>Xrbj?Am|0okYmUX zR~eUkghZ2S95z!JVj}i}#axBcpyX;!#EiF0nFf-Xo>D3A{SoAp;I^9VqjIM$cdg*D zk>m3359-hHWeTAhW@Hnz67@r0SHyAXdMslJulpY$S-$frL5J&M`P@>t#(wov13ai- zVK^+e3sW8M=FoVV@C93#I1Rp&>&!AZ)LO!|4!bozDHmQ?Z=MSsD_%!~Ni^Mijv@QA z%e<^I0!nNyhCMu_%2%L6@K2~{j*rt8A%hG*#Du{%VId9tM;Wf)`6Pq*F8p zU8k%bJSdk`kv1fW@jBbxLQrB`^YYToo0s?2*LR9n)_+1_@_$Jo+@t<&hk{ z{LhGwww6|ZrU$}WA0%{tU|_j;tuQld=tCaNWiI8}%+^wK?*P3U6$Vu%(;g$Z65Ggr z9jVkJSUAim#Jl{7SZ7!w1roBTXFc>4T1R54Pc5T_PhUfzOfKOGrpt2?Dc3HNnZDhX zEclork4hQsg=N-=Dt&YAQIInVUTIN6hHjUmxF#6J-nZ6VCW>z81!nCxj_Vq;n>Pr! zZy7>=t6fo8bv;B3Y%qw?hPURJdSxD68f^4{ZN0Qv49`qKBB8|do!c7iviDZ_-L|VE z$*M@Hu=q>08dgH6zGFL7kXFS=m{Up?SCB)!HeM<@HI*RCP3vysqbSHoEG}^sTt9xC zxVl}=RrzYKeME36`z$}m819u^Nyi2UIaH8%%0@SqS^V@kc@h96e4Ia`8$7h|TY$Z3 zJSYK|>l`j`o%n^nzS|RX=x8hiXMK<%3}3M-di16d&Z%8R#BhLTQoPKSZ=_j6IR;Na zi@MUt>Pd$*7TW1b`oeo^V^b0LR@TPkG9y*nK`c>^1wLnA^yPbc(@&D=AF4bO91#cx z(X5gBFE>D|d%wJAS>LaUUxHdC2Fge_@Dv}LjS>%LU`Pq>BcUb|k zEuCD+jPYW&2(88R;ijTskIL9*5_a+#*qU5^x^dX=jNA&zRdhq0A86&&cA%q+5Mt%y zcs@hQ%R$QgAv1ph*XIl5(VBpn(DTW>7uF*=0ieguiwlgate**UewO7tx+Ko_3$&dn z;#abcW@`Y9{u!T6I+g0GvOPtyW{#!r`Ekj_`sX{W znE*A>bY~;9KEjL^P7L6gkbJJx zK*3o=RRSN=eqeb|fYxu^2~F@#Q&twLH=^SWR*Cko(Bw7<@xA{Y>7$D_o-Ip9y3L4h z^!COYcg;0=Zjh3w!r4xt)=U#c7;8;?ilD#SGM_NP{p_G!Toa(T1lCgKggLu3*KQgAMP z5==Ac6zbpK@VUnyPmqAnQ^%Y;8Iv1!^StJE!V`6Ak!pB#HiNbLBrU=(N7gK`yjkJe zE@H#deQUyix$c~hj|CDLmdlk!??wWn)w-oO;Ib->(x9qYiS{R@B`JSNVR7TiR|YAV zf%$u>DRJ0*sQFl$CzDFv*IjElQ=GqN= zAXrHk&%%-YUZe=ac0uC!mKB;^DsxiaqW;pGiN-~TkNrVqrT5y9Ewaov`f9w5S>&y&m?75uV>|)#i;34X533%o2#cS8 z_jy@>O@ZzjxnKDtuBx`vgOu4Ua$t;i7{^n3%y2uCXK{Z$R z{RH7BoAYch_*G#^xPej8L-^|b0lDP+4rV1HJ{3om(D(a`op7$Jn46=BC4&mKc80KK)z1QY z?5rUh%LL?KO}sY~ohgDh=F@TM)Ea9Llht6Q=3ahy!MhCwpp(!qxBOf(E;!a8dw9Xg zYXfocr?&kLF%IBvZ1i$Qjgx7L`S1h@9*umc=yOK`o$VU2451D6vn%kL`CO&p$~J3o zc~KWZvc)?X>6e8>>j3+zZPpOiJ3Kcc*fF=Up=UST7uJi`M8a;cKyPPqDy4dZ7G1&{ z`+E3f6`5ZsdsfaWy|!Kgyb(jUgj#tFh~~^Ih5~0jhSHw($b|3*Aiv;5?LIAkK9%w+ zpp}RGP#RtgRRN8uNakMF!ANOv2MCR!eZzhm{lvrJ4 z?7qWJJLSEiH(=;c-e7_y55NuM<^^(z-$Jln4kFZ4?<~P|CwyO+#r)71Bzy?&?>R=H zUpA|s7zt@_W$Nc8Qo3bynCAYb>Ax>gJ^r>>Y*FAzcw#{@-W)JI_VYzdrZ|~*kZC%? zG%4P+YtW?;kBe@hZiz`YM3d7AJT8jNPpscHjt@DN5tQa$w913ZoVnW z)OURvItQddYw@wFsF=D>0QOO{`O9)bZJ3?Dys5Y}F%C=~n?J5k7@pn`H1up56TorA zrW5}?*}df8{fDZ-Goy`}(VV&q=}y`;%SVOD%IXkeHwar$C5Ih{Uo3l-H_liLIQLm? z4TkA%9~v7VEM&+5o;G$D{RhG-cY`L>y=r%k@t`{cac%+*xUSQ$>GRPj=tgj&_RwrY zy4?*e^a*50GVwSn?@M*Xs;g>Jf~L;72U}B)f&6V(BN0{75MO9+$w?b&cJE`ylVGpO zPjmr-;47dE3xYoL^q-e~N0YeOXC0{_6-8exjoL@!rZ^0#lp#q5;1j@9qBwE#uBd!y zw-CKmD#z5{r(&SDp(o|Wah=7<^i4L2teOXiu14`QyVu%g^mXmNp>JoV+qMpIb;{FM!9E zPE;Xq@=k3#R46h0_q9@}{$M3J=^d63pnk0~7IYJs(2zM&<+@+oU2=Bq6uC-4?=D+RJHF;w0P zawKYnM_0;4L53vuf9N`=AW@=iLAGt%Hcs044eyLP6W4T@=Buuf&e|5Ez%v#+DS1p&RA@rn?{@_>E z+-p3^1aCO^F49QDrVUS2F$vJsG9EBf<24})>##|}Rm?502G=8=SueaY2bh@6C6InS zfHm%Kdc~Od+;ethAPLVmt}DtV#Wy>fTjC&j{$K{UfyY?v`2wZ=hW2kA?&)+NM6J7_ zXQ`ndYs)`TDyT|5S8jBbZKfTOn_A4Tdx;}Zra@{Cn669-yFFC4P) zMC;Lx6XhZqldk4_OMNA4+WptR1Ez);(X#|82QJ;`N^AZ@Bi`VUh0N43fq`)Lb9hIt z%}KtLQAcTt&p)}q>gQhC^f=n*9-^g|1wwe_`ht1wLvr6Ey_}UZxb1)Y+W+6AApz3A zd6Llo*t7vX>Wu%V#LUbA`iumP@F$DCf^g%IJ)+KQZo4zVB(K+PV)pur!UO6zu%kBD zbPoSR`(7uK@5g6&_xShG8Nm1E8Q{0O?6VI?%$s;u(rQzA1O3%!_uKF1_Wv#I|O2qqa zDwy$`ga`SGx7GOsU0k%6)w7O@M#F#^oJ>~&DNwoKRGY8~NF!QJ)F}9^Z1~}sK(z5M zi!lN{F7+TL+2v@Gm$)wrIRpJ@nT97VZ9x_;e?FznEP!PT12{Df$2{%Z%lTvPODnmTtg3jfI{awmR}ok z4}4kMnJ!Z&)MpSz3U33Z^j_z_V7|%?zfs8Eu-;Jas*V{kpe{b}N5yhXH{ldLHf~WsU z?a&H|eLl0TNDNA$wp%dhc6Iar?bU8eU9K+bCa0VBwO=#zqKD+InD_BCD3BVC(@SQ; z_jFi)g!m*a-{8FX&S(#roAoXApiFs5ufAE+mRz2kAw__?yqu;lPI*Q}sL$CasvjQs z>f12TS}MzT?mm9{l@prdb@hQ7?CgXg6O89xm1JfsK`eS>7i(-|c$ITz-N zyoA;KB#e~HOa=FCFO$BQkE4*`(wRv2{5iwu!BY9H3tmEse;D*TE7ygp=tk_Q;FFT? zYYarm6p{{&S4fUK6IxrB#m@J(T(+>F`6%@5xB%=IjY7O;x9I3XFqQIN_vNmAAp$%T z1Jlee{ww(=AGq;)+nY!*Msos42x>4b%&i-VhS>$sfw0xve#zEHuJG zVSP4*7mNq~8fNtY8%Sn&oG23)$gWpg8-=WgQwf6DNyvTSR;$ARmEv$Nxj!9@<0?<= zp_+kR8O1011q#sEJ~QFWj58VL$G*B||BE0&3_tl2KPNj;Aaz?ej*kWFLOPblv{5Ne zG)FQ3b&kE|=@%+1BwsqaaxQgmmY1XzV7Hli`)pIZLN-~T4^Oy&LIERqER zrs4v^UCwF>IizhS8aZ3${3wtFsvVI6s6c3n6x5`~^ zgH#?fYVd$GVNFeZqYZJj;+DpEWPpfCSgI#`^&g$J>6WxDVpzt4R1BC&`{*h^kMs?i zy}S3b33MTE3E4d>Enh`J zh~I)FVk0gdDTikhztVD6ole=BRax1F*-zB4;4SagTyP@nxNjf(B&QThqgx5T7F89s zt^y@*i%h}bh3JSP82x}AbP4$w1YlWGPZV+b&zS{Bf9K`82Ic9y7mc>~x~(2t0R)^^ z7pCTczz(II+!?0*?Q*`W1Qu|g7i7IPF3$xywQ~3!>>W{wA#Sa3h3-m;MEmAvdAl^i z%CnEX2}ns9x?<0EQi^b}&+v*qq_Dvf@A}}m$$%d+OUX$RhJ%9;-^3BK`f*cE& z3Zmyhdq0I+4(@_@0(sdw1XRc2@AOwXzhB@^~aQk*f~J-%RQucVq?Ck1!k@a8|g7 zG*4BAzoI+}xDAX6eS1rFe^Z0;AJwu}-Y zcS^g^&n;uF-1t9`;C)V(_ex+Ltqqd>P@NjCHgv3+dr`U5CU2WcMST6aG7e;5uvI7W zFi?hdBA8PkIEV_kXDv5r<3gyL5yq{wFI|ylo{^_1-BKUDDMbc~gD>*&EaMhy#rYBu z*3e18Ja-crtl(p8;*&_|xTy{$92f+|_nZ&8(s$XS=$yD-xP?N4+v4wO;sj}Zsb$I7K~P9;iBuJ>;{5|CG)qW zk0z`}Z&4hA(Mec^)p03UP za-_nuD0{?a4W$WYZW<79RIeXA`a_M>mz@mOxMb3vWs?n*{+xcFCEJkQodB=&;wxA$ zQ=}cva^B0zaNt_Y?kLy+cQ+7Qe!l&kLwjh#yFJkzA7@(pkd~4)->sJ<;`_!Eqi27V zAVG`nRJtT3MWCF2Q);`*0ZdstZ1Z63>%Ddg@r+~q1XlLxfMclXG$g){QUoUb9ti=l z<^?swpag_Tr}ruTbKA=lDs5LNaTfDg+CfFrV(@Y)!t_PTw)|GfzKni$_gR8TaSvP0 zeu*>H)?zl&tP?z{nRPrT?EfeOK>7Qpi+0GX<9bK6*P63Ybq`WA5L_h2JYYt23(mU}01YE(!GbX25YNbLCI%RwYL zAkYtf{iT7>+<8Saf^d5Z zDKjw;?|tjWf;JmF*1g2kie!qR$;vmTj)H9;$%?j<#=(&@@Esb$V1^Adm{iIp!06p9 z9j{kt%x0-3CqJaK3vxpwSwC@wZ(xo{kj^KAh9KOE|688_4tU}%=XE0(zjTrm_CJS8f3GSTJnbwv9m^)?EClyxe@?Z>`wH_Zv_) zAtfP*S3dzS^`GcRY?Ytcdr2jh52)sSr(laW>5&@}0vLxEOg4`1=`AijBfKhkp#>E@ zi-3=d5GxE*vP|Ga2FPv-#WODfKtoAPW0N~zn3CPfVJy#1+NB$C&lGpMYcUljw&@bI z^f3)h`oHcSEnkDyZ90*OEjR)z0P{XPW$0sS>+T=HrUGcA-ufyvNbXYLI7`l!F@lvp z4@^k=PP3KJS$wFqYg44;NI73hTvFwdZSzgtn+8{4^5v`6#H0r4)Ut*Gt~L2GgXUyu zwdAPh>`oVLNoy4f_n3{y^#ip^qXA9O@+x^dcJfvghCve6nk6FfFx{%abbIL(;8aiW z_1mz}vUc3d?^Z!R?xHe9?1kWG+|t+Y1O4g7P{P$t&Vh@8{4*iuk6ppZF%H%3w^UUM8QSDheYSL<^s{wF zf56j`un^^|$D9yE728HDRjdipGFm|*!YSItMWXQ6J+_ovN;a@GE`{Srxhuk=pI|?4 zj?k;2Ukj}1d~Dg@Qap8^E^Lwbkjm`Yxa(a~wL^sqm}+M0GCHcvpfJE3LANN6?{>dK zF)MB4UVU)Mf7P$Mzwo6=%TssJgJ~p9m-05`CDr{LGqyfnyo=;x3^vymq z4{i3Z{L95>v!Pv=H-fW%fv)~6`%T&PBbh5OY|a3J5#?9vaZ{92;fH#4uQt3la=XF{ zf|6zM%~Hl1Q@q+)2V2x|D{*2n@5Fq^K zD8Sbqv|HPebLPQL(nZ~y+ssD@pf+A^V0?=gYRCL>Nyqp;U>g?6ymoJ&s#|Voz_+&_ zYFObbEehc_IoIWzNq?KX%e+$ zkMgR_N}66>CnlXi(>=&#;kiKCj!Yz~UufyF0H|B4D(ls U&361Py6po&{eshV2n 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

  • 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 z%0DCRO<1--2n)wuY|82ay^mQV9Q@ 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