Compare commits

...
Author SHA1 Message Date
Brandon Thomas a87714dc73 call cache from frontend 2025-08-08 06:01:39 -04:00
Brandon Thomas fab0fb4b13 add caching 2025-08-08 04:44:21 -04:00
Brandon Thomas 0556506b03 command to load media files 2025-08-08 04:17:52 -04:00
Brandon Thomas feeae20735 media files proxy 2025-08-08 03:52:53 -04:00
31 changed files with 977 additions and 343 deletions
Generated
+1
View File
@@ -701,6 +701,7 @@ version = "0.0.1"
dependencies = [
"chrono",
"enums",
"enums_public",
"serde",
"serde_derive",
"serde_json",
@@ -7,6 +7,7 @@ use crate::shared_response_types::user_details_light::UserDetailsLight;
use crate::utils::api_host::ApiHost;
use crate::utils::filter_bad_response::filter_bad_response;
use crate::utils::http_get_anonymous::http_get_anonymous;
use artcraft_api_defs::media_files::get_media_file::GetMediaFileSuccessResponse;
use chrono::{DateTime, Utc};
use enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
use enums::by_table::media_files::media_file_class::MediaFileClass;
@@ -24,7 +25,11 @@ use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::prompts::PromptToken;
/// Get details about a media file from our backend
pub async fn get_media_file(api_host: &ApiHost, media_file_token: &MediaFileToken) -> Result<GetMediaFileSuccessResponse, StorytellerError> {
pub async fn get_media_file(
api_host: &ApiHost,
media_file_token: &MediaFileToken
) -> Result<GetMediaFileSuccessResponse, StorytellerError> {
let url = get_media_file_token_route(api_host, media_file_token);
debug!("Requesting {:?}", &url);
@@ -46,165 +51,6 @@ fn get_media_file_token_route(api_host: &ApiHost, media_file_token: &MediaFileTo
format!("{}/v1/media_files/file/{}", api_hostname_and_scheme, media_file_token)
}
// TODO(bt,2025-04-22): Share API definitions between client and server in common crate.
#[derive(Deserialize, Debug)]
pub struct GetMediaFileSuccessResponse {
pub success: bool,
pub media_file: MediaFileInfo,
}
#[derive(Deserialize, Debug)]
pub struct MediaFileInfo {
pub token: MediaFileToken,
/// The coarse-grained class of media file: image, video, etc.
pub media_class: MediaFileClass,
/// Type of media will dictate which fields are populated and what
/// the frontend should display (eg. video player vs audio player).
/// This is closer in meaning to a "mime type".
pub media_type: MediaFileType,
/// If this is an engine/3D asset, this is the broad category (scene,
/// animation, etc.) of that object.
/// This can also be used for filtering in list/batch endpoints.
pub maybe_engine_category: Option<MediaFileEngineCategory>,
/// If this is an engine/3D asset for an animation or a rig that can
/// be animated with either (or both) skeletal or blend shape animations,
/// this describes the animation regime used or supported.
pub maybe_animation_type: Option<MediaFileAnimationType>,
// /// If the media file has a subtype, we'll report it.
// /// This is mostly used for Bevy engine files.
// #[deprecated(note="This was for the Bevy engine. Do not use.")]
// pub maybe_media_subtype: Option<MediaFileSubtype>,
// /// Extension for the engine to load over remote:// URLs.
// #[deprecated(note="This was for the Bevy engine. Do not use.")]
// pub maybe_engine_extension: Option<String>,
/// If the file was generated as part of a batch, this is the token for the batch.
pub maybe_batch_token: Option<BatchGenerationToken>,
/// If this media file was generated by a scene from the engine, this is a
/// link to that file.
pub maybe_scene_source_media_file_token: Option<MediaFileToken>,
// /// (DEPRECATED) URL path to the media file
// #[deprecated(note="This field doesn't point to the full URL. Use media_links instead to leverage the CDN.")]
// pub public_bucket_path: String,
// /// (DEPRECATED) Full URL to the media file
// #[deprecated(note="This points to the bucket. Use media_links instead to leverage the CDN.")]
// pub public_bucket_url: Url,
/// Rich CDN links to the media, including thumbnails, previews, and more.
pub media_links: MediaLinks,
/// Information about the cover image. Many media files do not require a cover image,
/// e.g. image files, video files with thumbnails, audio files, etc.
/// 3D files require them.
pub cover_image: MediaFileCoverImageDetails,
//// Provenance Data
//pub origin_product: MediaFileOriginProductCategory,
//pub origin_category: MediaFileOriginCategory,
//pub maybe_origin_model_type: Option<MediaFileOriginModelType>,
//pub maybe_origin_model_token: Option<String>,
//pub maybe_origin_filename: Option<String>,
/// Possible model info (if generated by inference)
pub maybe_model_weight_info: Option<GetMediaFileModelInfo>,
/// User info
pub maybe_creator_user: Option<UserDetailsLight>,
pub creator_set_visibility: Visibility,
/// The file was uploaded by the user.
/// This does not include files generated on the client side, like studio renders.
pub is_user_upload: bool,
/// The file was created by the system.
/// This includes files generated on the client side, like studio renders.
pub is_intermediate_system_file: bool,
/// The name or title of the media file (optional)
pub maybe_title: Option<String>,
/// Text transcripts for TTS, etc.
pub maybe_text_transcript: Option<String>,
// /// OPTIONAL. Details on live portrait generated media files,
// /// if this is a live portrait generated file.
// pub maybe_live_portrait_details: Option<MediaFileLivePortraitDetails>,
/// For Comfy / Video Style Transfer jobs, this might include
/// the name of the selected style.
pub maybe_style_name: Option<StyleTransferName>,
/// For Comfy / Video Style Transfer jobs, this indicates use of face detailer.
pub used_face_detailer: bool,
/// For Comfy / Video Style Transfer jobs, this indicates use of upscaling.
pub used_upscaler: bool,
/// The foreign key to the prompt used to generate the media, if applicable.
pub maybe_prompt_token: Option<PromptToken>,
/// The original filename for uploaded files, if they were provided.
/// In the future we'll provide our own internal optional filenames.
pub maybe_original_filename: Option<String>,
/// Duration for audio and video files, if available.
/// Measured in milliseconds.
pub maybe_duration_millis: Option<u64>,
/// We can simulate media files for "tts_results" records.
/// If this is set as true, this informs the frontend and API callers not to treat
/// the token as a media file and improperly assume it can be used with the rest of
/// the media file APIs.
pub is_emulated_media_file: bool,
/// Whether the media file is featured by staff (the featured_items API) or not.
pub is_featured: bool,
/// Statistics about the media file
pub stats: SimpleEntityStats,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub maybe_moderator_fields: Option<GetMediaFileModeratorFields>,
}
#[derive(Deserialize, Debug)]
pub struct GetMediaFileModeratorFields {
/// This may become public in the future.
pub maybe_style_transfer_source_media_file_token: Option<MediaFileToken>,
//pub creator_ip_address: String,
//pub creator_is_banned_if_user: bool,
//pub maybe_creator_deleted_at: Option<DateTime<Utc>>,
//pub maybe_mod_deleted_at: Option<DateTime<Utc>>,
}
#[derive(Deserialize, Debug)]
pub struct GetMediaFileModelInfo {
pub weight_token: ModelWeightToken,
pub weight_type: PublicWeightsType,
pub weight_category: WeightsCategory,
pub title: String,
/// Cover images are small descriptive images that can be set for any model.
/// If a cover image is set, this is the path to the asset.
pub maybe_cover_image_public_bucket_path: Option<String>,
/// NB: Technically this shouldn't be a nullable field, but since there are so many
/// joins we'll relax that this must exist (for now).
pub maybe_weight_creator: Option<UserDetailsLight>,
}
#[cfg(test)]
mod tests {
@@ -0,0 +1,146 @@
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueImageInpaintSuccessResponse;
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
use crate::core::commands::response::shorthand::{InfallibleResponse, ResponseOrErrorType};
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
use crate::core::model::image_models::ImageModel;
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use anyhow::anyhow;
use artcraft_api_defs::media_files::get_media_file::{GetMediaFileSuccessResponse, MediaFileInfo};
use chrono::{DateTime, Utc};
use errors::AnyhowResult;
use log::{error, info};
use serde_derive::{Deserialize, Serialize};
use std::path::Path;
use storyteller_client::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
use storyteller_client::media_files::get_media_file::get_media_file;
use tauri::{AppHandle, State};
use tokens::tokens::media_files::MediaFileToken;
#[derive(Deserialize)]
pub struct GetMediaFileRequest {
pub token: MediaFileToken,
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum GetMediaFileErrorType {
NotFound,
NotAuthorized,
ServerError,
}
#[derive(Debug, Serialize)]
pub struct GetMediaFileResponse {
pub success: bool,
pub media_file: MediaFileInfo,
}
impl SerializeMarker for GetMediaFileResponse {}
#[tauri::command]
pub async fn get_media_file_command(
request: GetMediaFileRequest,
app_data_root: State<'_, AppDataRoot>,
app_env_configs: State<'_, AppEnvConfigs>,
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
) -> ResponseOrErrorType<GetMediaFileResponse, GetMediaFileErrorType > {
info!("get_media_file_command called... token: {}", &request.token);
let downloads = app_data_root.downloads_dir();
let media_file_cache_path = downloads.media_file_json_path(&request.token);
let maybe_cache = load_cache_media_file(&media_file_cache_path).await;
if let Ok(Some(media_file_info)) = maybe_cache {
info!("Loaded media file from cache: {:?}", media_file_info.token);
return Ok(GetMediaFileResponse {
success: true,
media_file: media_file_info,
}.into());
}
let result = query_media_file(
request,
&app_env_configs,
).await?;
info!("Received media file from server: {:?}", result.media_file.token);
// Save the media file to cache
let write_result = serde_json::to_string(&result.media_file);
match write_result {
Err(err) => {
error!("Failed to serialize media file to JSON: {:?}", err);
}
Ok(json_string) => {
if let Err(err) = std::fs::write(&media_file_cache_path, json_string) {
error!("Failed to write media file cache: {:?}", err);
} else {
info!("Media file cache written successfully to {:?}", media_file_cache_path);
}
},
}
Ok(GetMediaFileResponse {
success: true,
media_file: result.media_file
}.into())
}
async fn query_media_file(
request: GetMediaFileRequest,
app_env_configs: &AppEnvConfigs,
) -> Result<GetMediaFileSuccessResponse, CommandErrorResponseWrapper<GetMediaFileErrorType, ()>> {
// TODO(bt,2025-08-08): Include credentials
let result = get_media_file(
&app_env_configs.storyteller_host,
//Some(&creds),
&request.token,
).await;
// TODO(bt,2025-08-08): Handle errors properly
let result = match result {
Ok(result) => result,
Err(err) => {
error!("Failed to get media file: {:?}", err);
return Err(CommandErrorResponseWrapper {
status: CommandErrorStatus::ServerError,
error_message: None,
error_type: Some(GetMediaFileErrorType::ServerError),
error_details: None,
});
}
};
Ok(result)
}
async fn load_cache_media_file(
path: &Path,
) -> AnyhowResult<Option<MediaFileInfo>> {
if !path.exists() || !path.is_file() {
return Ok(None);
}
let file_content = std::fs::read_to_string(path)
.map_err(|err| {
error!("Failed to read media file cache: {:?}", err);
err
})?;
let media_file_info : MediaFileInfo = serde_json::from_str(&file_content)
.map_err(|err| {
error!("Failed to deserialize media file cache: {:?}", err);
err
})?;
Ok(Some(media_file_info))
}
@@ -0,0 +1 @@
pub mod get_media_file_command;
@@ -3,6 +3,7 @@ pub mod enqueue;
pub mod flip_image;
pub mod get_app_info_command;
pub mod load_without_cors_command;
pub mod media_files;
pub mod platform_info_command;
pub mod providers;
pub mod response;
@@ -1,5 +1,6 @@
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
use std::path::{Path, PathBuf};
use tokens::tokens::media_files::MediaFileToken;
#[derive(Clone)]
pub struct AppDownloadsDir {
@@ -19,3 +20,10 @@ impl DataSubdir for AppDownloadsDir {
&self.path
}
}
impl AppDownloadsDir {
pub fn media_file_json_path(&self, media_token: &MediaFileToken) -> PathBuf {
let filename = format!("{}.json", media_token.as_str());
self.path().join(&filename)
}
}
+4 -2
View File
@@ -1,19 +1,21 @@
pub mod core;
pub mod services;
use log::error;
use tauri::Manager;
use crate::core::commands::app_preferences::get_app_preferences_command::get_app_preferences_command;
use crate::core::commands::app_preferences::update_app_preference_command::update_app_preferences_command;
use crate::core::commands::enqueue::image::enqueue_text_to_image_command::enqueue_text_to_image_command;
use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_command::enqueue_image_bg_removal_command;
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::enqueue_image_inpaint_command;
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::enqueue_contextual_edit_image_command;
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::enqueue_image_inpaint_command;
use crate::core::commands::enqueue::object::enqueue_image_to_3d_object_command::enqueue_image_to_3d_object_command;
use crate::core::commands::enqueue::video::enqueue_image_to_video_command::enqueue_image_to_video_command;
use crate::core::commands::flip_image::flip_image;
use crate::core::commands::get_app_info_command::get_app_info_command;
use crate::core::commands::load_without_cors_command::load_without_cors_command;
use crate::core::commands::media_files::get_media_file_command::get_media_file_command;
use crate::core::commands::platform_info_command::platform_info_command;
use crate::core::commands::providers::get_provider_order_command::get_provider_order_command;
use crate::core::commands::providers::set_provider_order_command::set_provider_order_command;
@@ -41,7 +43,6 @@ use crate::services::sora::state::sora_credential_manager::SoraCredentialManager
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
use crate::services::sora::threads::sora_task_polling_thread::sora_task_polling_thread;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use log::error;
use tauri_plugin_dialog;
use tauri_plugin_http;
@@ -156,6 +157,7 @@ pub fn run() {
get_app_info_command,
get_app_preferences_command,
get_fal_api_key_command,
get_media_file_command,
get_provider_order_command,
load_without_cors_command,
open_sora_login_command,
@@ -17,6 +17,7 @@ use crate::core::utils::simple_http_download_to_tempfile::simple_http_download_t
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
use anyhow::anyhow;
use artcraft_api_defs::media_files::get_media_file::GetMediaFileSuccessResponse;
use base64::prelude::BASE64_STANDARD;
use base64::{DecodeError, Engine};
use errors::AnyhowError;
@@ -28,7 +29,7 @@ use log::{error, info, warn};
use reqwest::Url;
use serde_derive::{Deserialize, Serialize};
use storyteller_client::error::storyteller_error::StorytellerError;
use storyteller_client::media_files::get_media_file::{get_media_file, GetMediaFileSuccessResponse};
use storyteller_client::media_files::get_media_file::get_media_file;
use storyteller_client::media_files::upload_image_media_file_from_file::{upload_image_media_file_from_file, UploadImageFromFileArgs};
use storyteller_client::utils::api_host::ApiHost;
use tauri::{AppHandle, Manager, State};
+3
View File
@@ -0,0 +1,3 @@
// media token: m_gff67btr810vg3ng9szj85zskztcgy
pub const STORY_GIRL_GLB_URL :&str = "https://cdn-2.fakeyou.com/media/0/n/7/8/2/0n7826f6vme8vkzfjhv644spgfg1bb54/asset_0n7826f6vme8vkzfjhv644spgfg1bb54.glb";
+2 -1
View File
@@ -1 +1,2 @@
pub mod image_urls;
pub mod image_urls;
mod glb_urls;
@@ -21,6 +21,7 @@ workspace-build-acceleration.workspace = true
# Internal
enums.workspace = true
enums_public.workspace = true
tokens.workspace = true
# External
@@ -0,0 +1,16 @@
use serde_derive::{Deserialize, Serialize};
use url::Url;
use utoipa::ToSchema;
/// Cover image links can only be jpg, png, etc. No videos.
#[derive(Serialize, Deserialize, Debug, ToSchema)]
pub struct CoverImageLinks {
/// Primary link to the cover image via the CDN.
pub cdn_url: Url,
/// Template to construct thumbnail URLs.
/// Replace the string `{WIDTH}` with the desired width.
/// Only relevant for image media files. (Video media files instead have
/// video previews, which, in turn, have their own thumbnail templates.)
pub thumbnail_template: String,
}
@@ -0,0 +1,21 @@
use serde_derive::Deserialize;
use serde_derive::Serialize;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
/// Details about submitted live portrait jobs (request arguments only)
#[derive(Deserialize, Serialize, Debug, ToSchema)]
pub struct MediaFileLivePortraitDetails {
/// Source media token
/// This can be an image or a video media file token
/// This is what constitutes the "portrait" or the overall final video.
/// This video or image must contain a face.
pub source_media_file_token: MediaFileToken,
/// Driving media token
/// This must be a video media file token.
/// This drives the animation of the face, but the actor will disappear
/// and their facial expressions will be transferred to the source.
/// This video must contain a face.
pub face_driver_media_file_token: MediaFileToken,
}
@@ -0,0 +1,42 @@
use crate::common::responses::cover_image_links::CoverImageLinks;
use serde::Deserialize;
use serde::Serialize;
use url::Url;
use utoipa::ToSchema;
/// Everything we need to create a cover image.
/// Cover images are small descriptive images that can be set for any media file.
/// If a cover image is set, this is the path to the asset.
#[derive(Serialize, Deserialize, Debug, ToSchema)]
pub struct MediaFileCoverImageDetails {
/// (DEPRECATED) URL path to the media file
#[deprecated(note="This field doesn't point to the full URL. Use media_links instead to leverage the CDN.")]
pub maybe_cover_image_public_bucket_path: Option<String>,
/// (DEPRECATED) Full URL to the media file
#[deprecated(note="This points to the bucket. Use media_links instead to leverage the CDN.")]
pub maybe_cover_image_public_bucket_url: Option<Url>,
// NB(bt,2024-09-19): I accidentally rolled this field out to production.
// I don't think this field is in use, but maybe ...
// /// (DEPRECATED) Use maybe_links instead.
// #[deprecated(note="Use `maybe_links` instead.")]
// pub maybe_media_links: Option<MediaLinks>,
/// Links to the cover image (CDN direct link, thumbnail template)
/// If a cover image is set, this is the path to the asset.
/// If a cover image is not set, use the information in `default_cover` instead.
/// Rich CDN links to the media, including thumbnails, previews, and more.
pub maybe_links: Option<CoverImageLinks>,
/// For items without a cover image, we can use one of our own.
pub default_cover: MediaFileDefaultCover,
}
/// The default cover is composed of an image and color pair that are
/// predefined by the frontend.
#[derive(Serialize, Deserialize, Debug, ToSchema)]
pub struct MediaFileDefaultCover {
pub image_index: u8,
pub color_index: u8,
}
@@ -1,2 +1,7 @@
pub mod cover_image_links;
pub mod job_details;
pub mod media_links;
pub mod live_portrait;
pub mod media_file_cover_image_details;
pub mod media_links;
pub mod simple_entity_stats;
pub mod user_details_light;
@@ -0,0 +1,11 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
/// Simple stats that can be attached to any entity
#[derive(Serialize, Deserialize, Clone, Debug, ToSchema)]
pub struct SimpleEntityStats {
/// Number of positive ratings (or "likes") for this item
pub positive_rating_count: u32,
/// Number of bookmarks for this item
pub bookmark_count: u32,
}
@@ -0,0 +1,34 @@
use serde::{Deserialize, Serialize};
use tokens::tokens::users::UserToken;
use utoipa::ToSchema;
/// Everything we need to refer to a user on the public web interface.
#[derive(Serialize, Deserialize, Debug, ToSchema)]
pub struct UserDetailsLight {
/// The token for the user
pub user_token: UserToken,
/// The unique username someone logs in with
/// As of 2023-08-23, this is always lowercase
pub username: String,
/// As of 2023-08-23, this is the username with capitalization
/// (In the future, a display name can be customized by the user.)
pub display_name: String,
/// Email hash for Gravatar
/// Always set for now since login is email/username+password.
/// In the future this will need to become an optional *OR* be filled with a bogus hash.
pub gravatar_hash: String,
/// For users without a gravatar, we show one of our own avatars.
pub default_avatar: UserDefaultAvatarInfo,
// In the future, we'll also support user-uploaded avatars that we store on our servers.
}
#[derive(Serialize, Deserialize, Debug, ToSchema)]
pub struct UserDefaultAvatarInfo {
pub image_index: u8,
pub color_index: u8,
}
@@ -1,3 +1,4 @@
pub mod common;
pub mod generate;
pub mod jobs;
pub mod jobs;
pub mod media_files;
@@ -0,0 +1,180 @@
use crate::common::responses::live_portrait::MediaFileLivePortraitDetails;
use crate::common::responses::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::common::responses::media_links::MediaLinks;
use crate::common::responses::simple_entity_stats::SimpleEntityStats;
use crate::common::responses::user_details_light::UserDetailsLight;
use chrono::{DateTime, Utc};
use enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
use enums::by_table::media_files::media_file_subtype::MediaFileSubtype;
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::by_table::model_weights::weights_category::WeightsCategory;
use enums::common::visibility::Visibility;
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
use enums_public::by_table::model_weights::public_weights_types::PublicWeightsType;
use serde_derive::{Deserialize, Serialize};
use tokens::tokens::batch_generations::BatchGenerationToken;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::prompts::PromptToken;
use url::Url;
use utoipa::ToSchema;
#[derive(Serialize, Deserialize, Debug, ToSchema)]
pub struct GetMediaFileSuccessResponse {
pub success: bool,
pub media_file: MediaFileInfo,
}
#[derive(Serialize, Deserialize, Debug, ToSchema)]
pub struct MediaFileInfo {
pub token: MediaFileToken,
/// The coarse-grained class of media file: image, video, etc.
pub media_class: MediaFileClass,
/// Type of media will dictate which fields are populated and what
/// the frontend should display (eg. video player vs audio player).
/// This is closer in meaning to a "mime type".
pub media_type: MediaFileType,
/// If this is an engine/3D asset, this is the broad category (scene,
/// animation, etc.) of that object.
/// This can also be used for filtering in list/batch endpoints.
pub maybe_engine_category: Option<MediaFileEngineCategory>,
/// If this is an engine/3D asset for an animation or a rig that can
/// be animated with either (or both) skeletal or blend shape animations,
/// this describes the animation regime used or supported.
pub maybe_animation_type: Option<MediaFileAnimationType>,
/// If the media file has a subtype, we'll report it.
/// This is mostly used for Bevy engine files.
#[deprecated(note="This was for the Bevy engine. Do not use.")]
pub maybe_media_subtype: Option<MediaFileSubtype>,
/// Extension for the engine to load over remote:// URLs.
#[deprecated(note="This was for the Bevy engine. Do not use.")]
pub maybe_engine_extension: Option<String>,
/// If the file was generated as part of a batch, this is the token for the batch.
pub maybe_batch_token: Option<BatchGenerationToken>,
/// If this media file was generated by a scene from the engine, this is a
/// link to that file.
pub maybe_scene_source_media_file_token: Option<MediaFileToken>,
/// (DEPRECATED) URL path to the media file
#[deprecated(note="This field doesn't point to the full URL. Use media_links instead to leverage the CDN.")]
pub public_bucket_path: String,
/// (DEPRECATED) Full URL to the media file
#[deprecated(note="This points to the bucket. Use media_links instead to leverage the CDN.")]
pub public_bucket_url: Url,
/// Rich CDN links to the media, including thumbnails, previews, and more.
pub media_links: MediaLinks,
/// Information about the cover image. Many media files do not require a cover image,
/// e.g. image files, video files with thumbnails, audio files, etc.
/// 3D files require them.
pub cover_image: MediaFileCoverImageDetails,
//// Provenance Data
//pub origin_product: MediaFileOriginProductCategory,
//pub origin_category: MediaFileOriginCategory,
//pub maybe_origin_model_type: Option<MediaFileOriginModelType>,
//pub maybe_origin_model_token: Option<String>,
//pub maybe_origin_filename: Option<String>,
/// Possible model info (if generated by inference)
pub maybe_model_weight_info: Option<GetMediaFileModelInfo>,
/// User info
pub maybe_creator_user: Option<UserDetailsLight>,
pub creator_set_visibility: Visibility,
/// The file was uploaded by the user.
/// This does not include files generated on the client side, like studio renders.
pub is_user_upload: bool,
/// The file was created by the system.
/// This includes files generated on the client side, like studio renders.
pub is_intermediate_system_file: bool,
/// The name or title of the media file (optional)
pub maybe_title: Option<String>,
/// Text transcripts for TTS, etc.
pub maybe_text_transcript: Option<String>,
/// OPTIONAL. Details on live portrait generated media files,
/// if this is a live portrait generated file.
pub maybe_live_portrait_details: Option<MediaFileLivePortraitDetails>,
/// For Comfy / Video Style Transfer jobs, this might include
/// the name of the selected style.
pub maybe_style_name: Option<StyleTransferName>,
/// For Comfy / Video Style Transfer jobs, this indicates use of face detailer.
pub used_face_detailer: bool,
/// For Comfy / Video Style Transfer jobs, this indicates use of upscaling.
pub used_upscaler: bool,
/// The foreign key to the prompt used to generate the media, if applicable.
pub maybe_prompt_token: Option<PromptToken>,
/// The original filename for uploaded files, if they were provided.
/// In the future we'll provide our own internal optional filenames.
pub maybe_original_filename: Option<String>,
/// Duration for audio and video files, if available.
/// Measured in milliseconds.
pub maybe_duration_millis: Option<u64>,
/// We can simulate media files for "tts_results" records.
/// If this is set as true, this informs the frontend and API callers not to treat
/// the token as a media file and improperly assume it can be used with the rest of
/// the media file APIs.
pub is_emulated_media_file: bool,
/// Whether the media file is featured by staff (the featured_items API) or not.
pub is_featured: bool,
/// Statistics about the media file
pub stats: SimpleEntityStats,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub maybe_moderator_fields: Option<GetMediaFileModeratorFields>,
}
#[derive(Serialize, Deserialize, Debug, ToSchema)]
pub struct GetMediaFileModeratorFields {
/// This may become public in the future.
pub maybe_style_transfer_source_media_file_token: Option<MediaFileToken>,
//pub creator_ip_address: String,
//pub creator_is_banned_if_user: bool,
//pub maybe_creator_deleted_at: Option<DateTime<Utc>>,
//pub maybe_mod_deleted_at: Option<DateTime<Utc>>,
}
#[derive(Serialize, Deserialize, Debug, ToSchema)]
pub struct GetMediaFileModelInfo {
pub weight_token: ModelWeightToken,
pub weight_type: PublicWeightsType,
pub weight_category: WeightsCategory,
pub title: String,
/// Cover images are small descriptive images that can be set for any model.
/// If a cover image is set, this is the path to the asset.
pub maybe_cover_image_public_bucket_path: Option<String>,
/// NB: Technically this shouldn't be a nullable field, but since there are so many
/// joins we'll relax that this must exist (for now).
pub maybe_weight_creator: Option<UserDetailsLight>,
}
@@ -0,0 +1 @@
pub mod get_media_file;
@@ -0,0 +1,73 @@
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use artcraft_api_defs::common::responses::cover_image_links::CoverImageLinks;
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
// TODO(bt,2024-09-18): Consolidate thumbnail builder code and vars
// TODO(bt,2024-09-05): Worth reducing the quality at all?
const QUALITY : u8 = 95;
pub struct CoverImageLinksBuilder {
}
impl CoverImageLinksBuilder {
pub fn from_media_path(
domain: MediaDomain,
bucket_path: &MediaFileBucketPath,
) -> Option<CoverImageLinks> {
let rooted_path = bucket_path.get_full_object_path_str();
CoverImageLinksBuilder::from_rooted_path(domain, rooted_path)
}
pub fn from_maybe_media_path(
domain: MediaDomain,
bucket_path: Option<&MediaFileBucketPath>,
) -> Option<CoverImageLinks> {
match bucket_path {
None => None,
Some(bucket_path) => CoverImageLinksBuilder::from_media_path(domain, bucket_path),
}
}
pub fn from_parts(
domain: MediaDomain,
maybe_cover_image_public_bucket_path: Option<&str>,
maybe_cover_image_public_bucket_prefix: Option<&str>,
maybe_cover_image_public_bucket_extension: Option<&str>,
) -> Option<CoverImageLinks> {
let bucket_path= match maybe_cover_image_public_bucket_path {
None => return None,
Some(path) => path,
};
let bucket_path = MediaFileBucketPath::from_object_hash(
bucket_path,
maybe_cover_image_public_bucket_prefix,
maybe_cover_image_public_bucket_extension
);
CoverImageLinksBuilder::from_media_path(domain, &bucket_path)
}
pub fn from_rooted_path(
domain: MediaDomain,
rooted_path: &str,
) -> Option<CoverImageLinks> {
if !rooted_path.ends_with(".jpg")
&& !rooted_path.ends_with(".png")
&& !rooted_path.ends_with(".gif") {
return None;
}
let mut cdn_url = domain.new_cdn_url();
cdn_url.set_path(rooted_path);
Some(CoverImageLinks {
cdn_url,
thumbnail_template: thumbnail_template(domain, rooted_path),
})
}
}
fn thumbnail_template(media_domain: MediaDomain, rooted_path: &str) -> String {
let host = media_domain.cdn_url_str();
format!("{host}/cdn-cgi/image/width={{WIDTH}},quality={QUALITY}{rooted_path}")
}
@@ -0,0 +1,152 @@
use crate::http_server::common_responses::media::cover_image_links_builder::CoverImageLinksBuilder;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::web_utils::bucket_urls::bucket_url_from_media_path::bucket_url_from_media_path;
use artcraft_api_defs::common::responses::media_file_cover_image_details::{MediaFileCoverImageDetails, MediaFileDefaultCover};
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
/// There are currently 25 cover images numbered 0 to 24 (0-indexed).
/// The original dataset was numbered 1 - 25, but I renamed 25 to 0.
const NUMBER_OF_IMAGES : u64 = 25;
const NUMBER_OF_IMAGES_SALT_OFFSET : u8 = 5;
const NUMBER_OF_COLORS : u64 = 8;
const NUMBER_OF_COLORS_SALT_OFFSET : u8 = 1;
pub struct MediaFileCoverImageDetailsBuilder {
}
pub struct MediaFileDefaultCoverBuilder {
}
impl MediaFileCoverImageDetailsBuilder {
/// Typical constructor
pub fn from_token(token: &MediaFileToken) -> MediaFileCoverImageDetails {
Self::from_token_str(token.as_str())
}
/// For non-media file tokens (eg. emulated TTS results)
pub fn from_token_str(token: &str) -> MediaFileCoverImageDetails {
MediaFileCoverImageDetails {
// TODO(bt,2024-04-07): Add column to schema to support + CRUD to add.
maybe_cover_image_public_bucket_path: None,
maybe_cover_image_public_bucket_url: None,
//maybe_media_links: None,
maybe_links: None,
default_cover: MediaFileDefaultCoverBuilder::from_token_str(token),
}
}
pub fn from_optional_db_fields(
token: &MediaFileToken,
domain: MediaDomain,
maybe_cover_image_public_bucket_path: Option<&str>,
maybe_cover_image_public_bucket_prefix: Option<&str>,
maybe_cover_image_public_bucket_extension: Option<&str>,
) -> MediaFileCoverImageDetails {
Self::from_optional_db_str_fields(
token.as_str(),
domain,
maybe_cover_image_public_bucket_path,
maybe_cover_image_public_bucket_prefix,
maybe_cover_image_public_bucket_extension
)
}
pub fn from_optional_db_str_fields(
token: &str,
domain: MediaDomain,
maybe_cover_image_public_bucket_path: Option<&str>,
maybe_cover_image_public_bucket_prefix: Option<&str>,
maybe_cover_image_public_bucket_extension: Option<&str>,
) -> MediaFileCoverImageDetails {
let maybe_bucket_path = maybe_cover_image_public_bucket_path
.map(|hash| MediaFileBucketPath::from_object_hash(
hash,
maybe_cover_image_public_bucket_prefix,
maybe_cover_image_public_bucket_extension
));
let maybe_cover_image_public_bucket_path = maybe_bucket_path
.as_ref()
.map(|bucket_path| bucket_path
.get_full_object_path_str()
.to_string());
// NB: Fail construction open.
let maybe_cover_image_public_bucket_url = maybe_bucket_path
.as_ref()
.map(|bucket_path| bucket_url_from_media_path(bucket_path).ok())
.flatten();
let maybe_links = CoverImageLinksBuilder::from_maybe_media_path(
domain, maybe_bucket_path.as_ref());
// let maybe_media_links = maybe_bucket_path
// .map(|path| MediaLinks::from_media_path(domain, &path));
MediaFileCoverImageDetails {
maybe_cover_image_public_bucket_path,
maybe_cover_image_public_bucket_url,
//maybe_media_links,
maybe_links,
default_cover: MediaFileDefaultCoverBuilder::from_token_str(token),
}
}
}
impl MediaFileDefaultCoverBuilder {
/// Typical constructor
pub fn from_token(token: &MediaFileToken) -> MediaFileDefaultCover {
Self::from_token_str(token.as_str())
}
/// For non-media file tokens (eg. emulated TTS results)
pub fn from_token_str(token: &str) -> MediaFileDefaultCover {
MediaFileDefaultCover {
image_index: hash(token, NUMBER_OF_IMAGES, NUMBER_OF_IMAGES_SALT_OFFSET),
color_index: hash(token, NUMBER_OF_COLORS, NUMBER_OF_COLORS_SALT_OFFSET),
}
}
}
fn hash(token: &str, max_number: u64, salt: u8) -> u8 {
let mut hasher = DefaultHasher::new();
token.hash(&mut hasher);
salt.hash(&mut hasher);
let hash = hasher.finish();
let index= hash % max_number;
index as u8
}
#[cfg(test)]
mod tests {
use tokens::tokens::media_files::MediaFileToken;
use crate::http_server::common_responses::media::media_file_cover_image_details_builder::MediaFileDefaultCoverBuilder;
#[test]
fn test() {
let token = MediaFileToken::new_from_str("foo");
let cover = MediaFileDefaultCoverBuilder::from_token(&token);
assert_eq!(cover.color_index, 5);
assert_eq!(cover.image_index, 2);
let token = MediaFileToken::new_from_str("bar");
let cover = MediaFileDefaultCoverBuilder::from_token(&token);
assert_eq!(cover.color_index, 5);
assert_eq!(cover.image_index, 3);
let token = MediaFileToken::new_from_str("asdf");
let cover = MediaFileDefaultCoverBuilder::from_token(&token);
assert_eq!(cover.color_index, 0);
assert_eq!(cover.image_index, 23);
}
}
@@ -1,6 +1,8 @@
pub mod cdn_link;
pub mod cover_image_links;
pub mod cover_image_links_builder;
pub mod media_domain;
pub mod media_file_cover_image_details;
pub mod media_file_cover_image_details_builder;
pub mod media_links_builder;
pub mod weights_cover_image_details;
@@ -9,3 +9,4 @@ pub mod simple_response;
pub mod tag_info;
pub mod user_avatars;
pub mod user_details_lite;
pub mod user_details_lite_builder;
@@ -0,0 +1,110 @@
use artcraft_api_defs::common::responses::user_details_light::{UserDefaultAvatarInfo, UserDetailsLight};
use tokens::tokens::users::UserToken;
use utoipa::ToSchema;
use crate::http_server::common_responses::user_avatars::default_avatar_color_from_username::default_avatar_color_from_username;
use crate::http_server::common_responses::user_avatars::default_avatar_from_username::default_avatar_from_username;
pub struct UserDetailsLightBuilder {
}
impl UserDetailsLightBuilder {
pub fn from_db_fields(
user_token: &UserToken,
username: &str,
display_name: &str,
gravatar_hash: &str,
) -> UserDetailsLight {
UserDetailsLight{
default_avatar: UserDefaultAvatarInfoBuilder::from_username(&username),
user_token: user_token.clone(),
username: username.to_string(),
display_name: display_name.to_string(),
gravatar_hash: gravatar_hash.to_string(),
}
}
pub fn from_optional_db_fields(
maybe_user_token: Option<&UserToken>,
maybe_username: Option<&str>,
maybe_display_name: Option<&str>,
maybe_gravatar_hash: Option<&str>,
) -> Option<UserDetailsLight> {
UserDetailsLightBuilder::from_optional_db_fields_owned(
maybe_user_token.map(|u| u.clone()),
maybe_username.map(|s|s.to_string()),
maybe_display_name.map(|s|s.to_string()),
maybe_gravatar_hash.map(|s|s.to_string()),
)
}
pub fn from_optional_db_fields_owned(
maybe_user_token: Option<UserToken>,
maybe_username: Option<String>,
maybe_display_name: Option<String>,
maybe_gravatar_hash: Option<String>,
) -> Option<UserDetailsLight> {
match (maybe_user_token, maybe_username, maybe_display_name, maybe_gravatar_hash) {
(Some(user_token), Some(username), Some(display_name), Some(gravatar_hash)) => {
Some(UserDetailsLight{
default_avatar: UserDefaultAvatarInfoBuilder::from_username(&username),
user_token,
username,
display_name,
gravatar_hash,
})
}
_ => {
None
}
}
}
}
pub struct UserDefaultAvatarInfoBuilder {
}
impl UserDefaultAvatarInfoBuilder {
/// Default avatars are based on username, not user token.
/// NB(bt,2023-08-23): I think the thinking here was that we'd always have the
/// username on hand and that it was more entropic.
pub fn from_username(username: &str) -> UserDefaultAvatarInfo {
UserDefaultAvatarInfo {
image_index: default_avatar_from_username(username),
color_index: default_avatar_color_from_username(username),
}
}
}
#[cfg(test)]
mod tests {
use tokens::tokens::users::UserToken;
use crate::http_server::common_responses::user_details_lite::UserDetailsLight;
#[test]
fn test_from_optional_db_fields() {
let user_token = UserToken::new_from_str("token");
let username = "username";
let display_name = "display_name";
let gravatar_hash= "adsf";
let user_details = UserDetailsLight::from_optional_db_fields(
Some(&user_token),
Some(username),
Some(display_name),
Some(gravatar_hash)
);
let user_details = user_details.expect("Should not be optional.");
assert_eq!(user_details.user_token, user_token);
assert_eq!(user_details.username, username);
assert_eq!(user_details.display_name, display_name);
assert_eq!(user_details.gravatar_hash, gravatar_hash);
assert_eq!(user_details.default_avatar.color_index, 5);
assert_eq!(user_details.default_avatar.image_index, 16);
}
}
@@ -0,0 +1,30 @@
use artcraft_api_defs::common::responses::live_portrait::MediaFileLivePortraitDetails;
use mysql_queries::payloads::media_file_extra_info::media_file_extra_info::MediaFileExtraInfo;
use utoipa::ToSchema;
/// Details about submitted live portrait jobs (request arguments only)
#[derive(Serialize, ToSchema)]
pub struct MediaFileLivePortraitDetailsBuilder {
}
impl MediaFileLivePortraitDetailsBuilder {
/// Extract from database JSON payload
pub fn maybe_from_extra_info(info: &MediaFileExtraInfo) -> Option<MediaFileLivePortraitDetails> {
let lp =
if let MediaFileExtraInfo::L(live_portrait_args) = info {
live_portrait_args
} else {
return None;
};
lp.maybe_portrait_media_token
.as_ref()
.zip(lp.maybe_driver_video_media_token.as_ref())
.map(|(portrait, driver) | {
MediaFileLivePortraitDetails {
source_media_file_token: portrait.clone(),
face_driver_media_file_token: driver.clone(),
}
})
}
}
@@ -1 +1,2 @@
pub mod live_portrait;
pub mod live_portrait;
pub mod live_portrait_builder;
@@ -2,11 +2,10 @@ use std::fmt;
use std::sync::Arc;
use crate::http_server::common_responses::media::media_domain::MediaDomain;
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
use crate::http_server::common_responses::media::media_file_cover_image_details_builder::MediaFileCoverImageDetailsBuilder;
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
use crate::http_server::common_responses::user_details_lite::UserDetailsLight;
use crate::http_server::endpoints::media_files::common_responses::live_portrait::MediaFileLivePortraitDetails;
use crate::http_server::common_responses::user_details_lite_builder::UserDetailsLightBuilder;
use crate::http_server::endpoints::media_files::common_responses::live_portrait_builder::MediaFileLivePortraitDetailsBuilder;
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
use crate::http_server::web_utils::bucket_urls::bucket_url_from_media_path::bucket_url_from_media_path;
use crate::http_server::web_utils::bucket_urls::bucket_url_from_str_path::bucket_url_from_str_path;
@@ -17,6 +16,8 @@ use actix_web::http::StatusCode;
use actix_web::web::{Json, Path};
use actix_web::{web, HttpRequest, HttpResponse};
use artcraft_api_defs::common::responses::media_links::MediaLinks;
use artcraft_api_defs::common::responses::simple_entity_stats::SimpleEntityStats;
use artcraft_api_defs::media_files::get_media_file::{GetMediaFileModelInfo, GetMediaFileModeratorFields, GetMediaFileSuccessResponse, MediaFileInfo};
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use chrono::{DateTime, Utc};
use enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
@@ -45,164 +46,6 @@ pub struct GetMediaFilePathInfo {
token: MediaFileToken,
}
#[derive(Serialize, ToSchema)]
pub struct GetMediaFileSuccessResponse {
pub success: bool,
pub media_file: MediaFileInfo,
}
#[derive(Serialize, ToSchema)]
pub struct MediaFileInfo {
pub token: MediaFileToken,
/// The coarse-grained class of media file: image, video, etc.
pub media_class: MediaFileClass,
/// Type of media will dictate which fields are populated and what
/// the frontend should display (eg. video player vs audio player).
/// This is closer in meaning to a "mime type".
pub media_type: MediaFileType,
/// If this is an engine/3D asset, this is the broad category (scene,
/// animation, etc.) of that object.
/// This can also be used for filtering in list/batch endpoints.
pub maybe_engine_category: Option<MediaFileEngineCategory>,
/// If this is an engine/3D asset for an animation or a rig that can
/// be animated with either (or both) skeletal or blend shape animations,
/// this describes the animation regime used or supported.
pub maybe_animation_type: Option<MediaFileAnimationType>,
/// If the media file has a subtype, we'll report it.
/// This is mostly used for Bevy engine files.
#[deprecated(note="This was for the Bevy engine. Do not use.")]
pub maybe_media_subtype: Option<MediaFileSubtype>,
/// Extension for the engine to load over remote:// URLs.
#[deprecated(note="This was for the Bevy engine. Do not use.")]
pub maybe_engine_extension: Option<String>,
/// If the file was generated as part of a batch, this is the token for the batch.
pub maybe_batch_token: Option<BatchGenerationToken>,
/// If this media file was generated by a scene from the engine, this is a
/// link to that file.
pub maybe_scene_source_media_file_token: Option<MediaFileToken>,
/// (DEPRECATED) URL path to the media file
#[deprecated(note="This field doesn't point to the full URL. Use media_links instead to leverage the CDN.")]
pub public_bucket_path: String,
/// (DEPRECATED) Full URL to the media file
#[deprecated(note="This points to the bucket. Use media_links instead to leverage the CDN.")]
pub public_bucket_url: Url,
/// Rich CDN links to the media, including thumbnails, previews, and more.
pub media_links: MediaLinks,
/// Information about the cover image. Many media files do not require a cover image,
/// e.g. image files, video files with thumbnails, audio files, etc.
/// 3D files require them.
pub cover_image: MediaFileCoverImageDetails,
//// Provenance Data
//pub origin_product: MediaFileOriginProductCategory,
//pub origin_category: MediaFileOriginCategory,
//pub maybe_origin_model_type: Option<MediaFileOriginModelType>,
//pub maybe_origin_model_token: Option<String>,
//pub maybe_origin_filename: Option<String>,
/// Possible model info (if generated by inference)
pub maybe_model_weight_info: Option<GetMediaFileModelInfo>,
/// User info
pub maybe_creator_user: Option<UserDetailsLight>,
pub creator_set_visibility: Visibility,
/// The file was uploaded by the user.
/// This does not include files generated on the client side, like studio renders.
pub is_user_upload: bool,
/// The file was created by the system.
/// This includes files generated on the client side, like studio renders.
pub is_intermediate_system_file: bool,
/// The name or title of the media file (optional)
pub maybe_title: Option<String>,
/// Text transcripts for TTS, etc.
pub maybe_text_transcript: Option<String>,
/// OPTIONAL. Details on live portrait generated media files,
/// if this is a live portrait generated file.
pub maybe_live_portrait_details: Option<MediaFileLivePortraitDetails>,
/// For Comfy / Video Style Transfer jobs, this might include
/// the name of the selected style.
pub maybe_style_name: Option<StyleTransferName>,
/// For Comfy / Video Style Transfer jobs, this indicates use of face detailer.
pub used_face_detailer: bool,
/// For Comfy / Video Style Transfer jobs, this indicates use of upscaling.
pub used_upscaler: bool,
/// The foreign key to the prompt used to generate the media, if applicable.
pub maybe_prompt_token: Option<PromptToken>,
/// The original filename for uploaded files, if they were provided.
/// In the future we'll provide our own internal optional filenames.
pub maybe_original_filename: Option<String>,
/// Duration for audio and video files, if available.
/// Measured in milliseconds.
pub maybe_duration_millis: Option<u64>,
/// We can simulate media files for "tts_results" records.
/// If this is set as true, this informs the frontend and API callers not to treat
/// the token as a media file and improperly assume it can be used with the rest of
/// the media file APIs.
pub is_emulated_media_file: bool,
/// Whether the media file is featured by staff (the featured_items API) or not.
pub is_featured: bool,
/// Statistics about the media file
pub stats: SimpleEntityStats,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub maybe_moderator_fields: Option<GetMediaFileModeratorFields>,
}
#[derive(Serialize, ToSchema)]
pub struct GetMediaFileModeratorFields {
/// This may become public in the future.
pub maybe_style_transfer_source_media_file_token: Option<MediaFileToken>,
//pub creator_ip_address: String,
//pub creator_is_banned_if_user: bool,
//pub maybe_creator_deleted_at: Option<DateTime<Utc>>,
//pub maybe_mod_deleted_at: Option<DateTime<Utc>>,
}
#[derive(Serialize, ToSchema)]
pub struct GetMediaFileModelInfo {
pub weight_token: ModelWeightToken,
pub weight_type: PublicWeightsType,
pub weight_category: WeightsCategory,
pub title: String,
/// Cover images are small descriptive images that can be set for any model.
/// If a cover image is set, this is the path to the asset.
pub maybe_cover_image_public_bucket_path: Option<String>,
/// NB: Technically this shouldn't be a nullable field, but since there are so many
/// joins we'll relax that this must exist (for now).
pub maybe_weight_creator: Option<UserDetailsLight>,
}
#[derive(Debug, ToSchema)]
pub enum GetMediaFileError {
ServerError,
@@ -374,7 +217,7 @@ async fn modern_media_file_lookup(
public_bucket_path: public_bucket_path
.get_full_object_path_str()
.to_string(),
cover_image: MediaFileCoverImageDetails::from_optional_db_fields(
cover_image: MediaFileCoverImageDetailsBuilder::from_optional_db_fields(
&result.token,
media_domain,
result.maybe_file_cover_image_public_bucket_hash.as_deref(),
@@ -385,7 +228,7 @@ async fn modern_media_file_lookup(
maybe_text_transcript: result.maybe_text_transcript,
maybe_live_portrait_details: result.extra_media_file_info
.as_ref()
.map(|info| MediaFileLivePortraitDetails::maybe_from_extra_info(&info))
.map(|info| MediaFileLivePortraitDetailsBuilder::maybe_from_extra_info(&info))
.flatten(),
maybe_style_name: result.maybe_prompt_args
.as_ref()
@@ -409,7 +252,7 @@ async fn modern_media_file_lookup(
weight_category: result.maybe_model_weights_category.unwrap_or(WeightsCategory::TextToSpeech),
title: result.maybe_model_weights_title.unwrap_or_else(|| "model".to_string()),
maybe_cover_image_public_bucket_path,
maybe_weight_creator: UserDetailsLight::from_optional_db_fields_owned(
maybe_weight_creator: UserDetailsLightBuilder::from_optional_db_fields_owned(
result.maybe_model_weight_creator_user_token,
result.maybe_model_weight_creator_username,
result.maybe_model_weight_creator_display_name,
@@ -417,7 +260,7 @@ async fn modern_media_file_lookup(
),
})
},
maybe_creator_user: UserDetailsLight::from_optional_db_fields_owned(
maybe_creator_user: UserDetailsLightBuilder::from_optional_db_fields_owned(
result.maybe_creator_user_token,
result.maybe_creator_username,
result.maybe_creator_display_name,
@@ -509,7 +352,7 @@ async fn emulate_media_file_with_legacy_tts_result_lookup(
GetMediaFileError::ServerError
})?,
public_bucket_path,
cover_image: MediaFileCoverImageDetails::from_token_str(&result.tts_result_token),
cover_image: MediaFileCoverImageDetailsBuilder::from_token_str(&result.tts_result_token),
maybe_model_weight_info: Some(GetMediaFileModelInfo {
// NB: These should be reasonable synthetic defaults for emulated TT2 results, even the "ModelWeightToken".
weight_token: ModelWeightToken::new_from_str(&result.tts_model_token),
@@ -519,7 +362,7 @@ async fn emulate_media_file_with_legacy_tts_result_lookup(
maybe_cover_image_public_bucket_path: None,
maybe_weight_creator: None, // NB: Not worth the trouble of synthesizing this
}),
maybe_creator_user: UserDetailsLight::from_optional_db_fields_owned(
maybe_creator_user: UserDetailsLightBuilder::from_optional_db_fields_owned(
result.maybe_creator_user_token,
result.maybe_creator_username,
result.maybe_creator_display_name,
@@ -11,7 +11,7 @@ import { ChromaKeyMaterial } from "./chromakey";
import { TimeLine } from "./timeline";
import { ClipGroup, ClipType } from "~/enums";
import { ClipUI } from "../clips/clip_ui";
import { LoadWithoutCors } from "@storyteller/tauri-api";
import { GetMediaFile, LoadWithoutCors } from "@storyteller/tauri-api";
import { gridVisibility } from "../signals/engine";
import { InfiniteGridHelper } from "./InfiniteGridHelper";
import { cameras, selectedCameraId } from "../signals/camera";
@@ -426,13 +426,20 @@ class Scene {
}
// TODO: REPLACE
async getMediaURL(media_id: string) {
console.log("getMediaID!!!!!!!!!!!!!", media_id);
const response = await this.mediaFilesApi.GetMediaFileByToken({
mediaFileToken: media_id,
});
console.log("response!!!!!!!!!!!!!!", response);
return response.data!.media_links.cdn_url;
async getMediaURL(media_token: string) {
//console.log("getMediaID!!!!!!!!!!!!!", media_id);
//const response = await this.mediaFilesApi.GetMediaFileByToken({
// mediaFileToken: media_id,
//});
//console.log("response!!!!!!!!!!!!!!", response);
const response = await GetMediaFile(media_token);
//return response.data!.media_links.cdn_url;
console.log('possibly cached media file:', response);
return response.payload.media_file.media_links.cdn_url;
}
private floatToPercent(value: number) {
+1
View File
@@ -12,6 +12,7 @@ export * from "./lib/fal/FalHunyuanImageTo3d.js";
export * from "./lib/fal/FalKlingImageToVideo.js";
export * from "./lib/fal/GetFalApiKey.js";
export * from "./lib/fal/SetFalApiKey.js";
export * from "./lib/media_files/GetMediaFile.js";
export * from "./lib/openai/GetOpenAIApiKey.js";
export * from "./lib/openai/SetOpenAIApiKey.js";
export * from "./lib/providers/GetProviderOrder.js";
@@ -0,0 +1,92 @@
import { invoke } from "@tauri-apps/api/core";
import { CommandResult } from "../common/CommandStatus";
export interface GetMediaFileRequest {
token?: string;
}
interface RawGetMediaFileRequest {
token?: string;
}
export interface GetMediaFileError extends CommandResult {
}
export interface MediaFileInfo {
token: string;
// TODO: Import enums
//media_type: MediaFileType;
//media_class: MediaFileClass | null;
maybe_animation_type: string | null;
//maybe_media_subtype: MediaFileSubtype | null;
maybe_engine_extension: string | null;
maybe_batch_token: string;
maybe_original_filename: string | null;
//maybe_creator_user: UserInfo | null;
maybe_prompt_token: string | null;
origin: {
origin_category: string;
product_category: string;
maybe_model: { title: string } | null;
};
origin_category: string;
origin_product_category: string;
maybe_origin_model_type: null | string;
maybe_origin_model_token: null | string;
maybe_duration_millis: number | null;
maybe_style_name: null | string;
public_bucket_path: string;
public_bucket_url: string;
media_links: {
cdn_url: string;
thumbnail_template: string;
};
cover_image: {
maybe_cover_image_public_bucket_path: null | string;
default_cover: {
image_index: number;
color_index: number;
};
};
creator_set_visibility: string;
maybe_title: null | string;
maybe_text_transcript: null | string;
stats: {
positive_rating_count: number;
bookmark_count: number;
};
maybe_model_weight_info: {
title: string;
weight_token: string;
// TODO: Import enums
//weight_category: WeightCategory;
//weight_type: WeightType;
//maybe_weight_creator: UserInfo;
maybe_cover_image_public_bucket_path: string;
};
created_at: string;
updated_at: string;
}
export interface GetMediaFilePayload {
media_file: MediaFileInfo;
}
export interface GetMediaFileSuccess extends CommandResult {
payload: GetMediaFilePayload;
}
export type GetMediaFileResult = GetMediaFileSuccess | GetMediaFileError;
export const GetMediaFile = async (token: string) : Promise<GetMediaFileResult> => {
let request : RawGetMediaFileRequest = {
token: token,
};
const result = await invoke("get_media_file_command", {
request: request,
});
return (result as GetMediaFileResult);
}