mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 17:28:24 -04:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a87714dc73 | ||
|
|
fab0fb4b13 | ||
|
|
0556506b03 | ||
|
|
feeae20735 |
Generated
+1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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";
|
||||
@@ -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,
|
||||
}
|
||||
+42
@@ -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;
|
||||
+11
@@ -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;
|
||||
+73
@@ -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}")
|
||||
}
|
||||
+152
@@ -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;
|
||||
|
||||
+110
@@ -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);
|
||||
}
|
||||
}
|
||||
+30
@@ -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(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1 +1,2 @@
|
||||
pub mod live_portrait;
|
||||
pub mod live_portrait;
|
||||
pub mod live_portrait_builder;
|
||||
+11
-168
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user