mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 08:57:54 +00:00
Merge branch 'master' into feature/comfyui-sidecar
This commit is contained in:
28 files changed
+660
-201
No files matched your search
+31
-20
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n users.token as user_token,\n users.username,\n users.display_name,\n\n users.email_address,\n users.email_confirmed,\n users.email_gravatar_hash,\n\n users.maybe_stripe_customer_id,\n users.maybe_loyalty_program_key,\n\n users.disable_gravatar,\n users.auto_play_audio_preference,\n users.auto_play_video_preference,\n users.preferred_tts_result_visibility as `preferred_tts_result_visibility: enums::common::visibility::Visibility`,\n users.preferred_w2l_result_visibility as `preferred_w2l_result_visibility: enums::common::visibility::Visibility`,\n\n users.user_role_slug,\n users.is_banned,\n\n users.can_access_studio,\n\n user_roles.can_use_tts,\n user_roles.can_use_w2l,\n user_roles.can_delete_own_tts_results,\n user_roles.can_delete_own_w2l_results,\n user_roles.can_delete_own_account,\n\n user_roles.can_upload_tts_models,\n user_roles.can_upload_w2l_templates,\n user_roles.can_delete_own_tts_models,\n user_roles.can_delete_own_w2l_templates,\n\n user_roles.can_approve_w2l_templates,\n user_roles.can_edit_other_users_profiles,\n user_roles.can_edit_other_users_tts_models,\n user_roles.can_edit_other_users_w2l_templates,\n user_roles.can_delete_other_users_tts_models,\n user_roles.can_delete_other_users_tts_results,\n user_roles.can_delete_other_users_w2l_templates,\n user_roles.can_delete_other_users_w2l_results,\n user_roles.can_ban_users,\n user_roles.can_delete_users\n\nFROM users\nLEFT OUTER JOIN user_sessions\n ON users.token = user_sessions.user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE user_sessions.token = ?\n AND user_sessions.deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\n ",
|
||||
"query": "\nSELECT\n users.token as user_token,\n users.username,\n users.display_name,\n\n users.email_address,\n users.email_confirmed,\n users.email_gravatar_hash,\n\n users.maybe_stripe_customer_id,\n users.maybe_loyalty_program_key,\n\n users.disable_gravatar,\n users.auto_play_audio_preference,\n users.auto_play_video_preference,\n users.preferred_tts_result_visibility as `preferred_tts_result_visibility: enums::common::visibility::Visibility`,\n users.preferred_w2l_result_visibility as `preferred_w2l_result_visibility: enums::common::visibility::Visibility`,\n\n users.user_role_slug,\n users.is_banned,\n\n users.can_access_studio,\n users.maybe_feature_flags,\n\n user_roles.can_use_tts,\n user_roles.can_use_w2l,\n user_roles.can_delete_own_tts_results,\n user_roles.can_delete_own_w2l_results,\n user_roles.can_delete_own_account,\n\n user_roles.can_upload_tts_models,\n user_roles.can_upload_w2l_templates,\n user_roles.can_delete_own_tts_models,\n user_roles.can_delete_own_w2l_templates,\n\n user_roles.can_approve_w2l_templates,\n user_roles.can_edit_other_users_profiles,\n user_roles.can_edit_other_users_tts_models,\n user_roles.can_edit_other_users_w2l_templates,\n user_roles.can_delete_other_users_tts_models,\n user_roles.can_delete_other_users_tts_results,\n user_roles.can_delete_other_users_w2l_templates,\n user_roles.can_delete_other_users_w2l_results,\n user_roles.can_ban_users,\n user_roles.can_delete_users\n\nFROM users\nLEFT OUTER JOIN user_sessions\n ON users.token = user_sessions.user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE user_sessions.token = ?\n AND user_sessions.deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -165,6 +165,16 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "maybe_feature_flags",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "can_use_tts",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -174,7 +184,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"ordinal": 18,
|
||||
"name": "can_use_w2l",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -184,7 +194,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"ordinal": 19,
|
||||
"name": "can_delete_own_tts_results",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -194,7 +204,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"ordinal": 20,
|
||||
"name": "can_delete_own_w2l_results",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -204,7 +214,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"ordinal": 21,
|
||||
"name": "can_delete_own_account",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -214,7 +224,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"ordinal": 22,
|
||||
"name": "can_upload_tts_models",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -224,7 +234,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"ordinal": 23,
|
||||
"name": "can_upload_w2l_templates",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -234,7 +244,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"ordinal": 24,
|
||||
"name": "can_delete_own_tts_models",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -244,7 +254,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"ordinal": 25,
|
||||
"name": "can_delete_own_w2l_templates",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -254,7 +264,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"ordinal": 26,
|
||||
"name": "can_approve_w2l_templates",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -264,7 +274,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"ordinal": 27,
|
||||
"name": "can_edit_other_users_profiles",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -274,7 +284,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"ordinal": 28,
|
||||
"name": "can_edit_other_users_tts_models",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -284,7 +294,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"ordinal": 29,
|
||||
"name": "can_edit_other_users_w2l_templates",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -294,7 +304,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"ordinal": 30,
|
||||
"name": "can_delete_other_users_tts_models",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -304,7 +314,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"ordinal": 31,
|
||||
"name": "can_delete_other_users_tts_results",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -314,7 +324,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"ordinal": 32,
|
||||
"name": "can_delete_other_users_w2l_templates",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -324,7 +334,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"ordinal": 33,
|
||||
"name": "can_delete_other_users_w2l_results",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -334,7 +344,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 33,
|
||||
"ordinal": 34,
|
||||
"name": "can_ban_users",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -344,7 +354,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 34,
|
||||
"ordinal": 35,
|
||||
"name": "can_delete_users",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -392,8 +402,9 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "423109f2dcee23a558c43ecaab0eb2836714d5316620daaa1127b447cad59212"
|
||||
"hash": "1c32bdb79ed2bc1b8988bbb6ea3f2a1db4ed4552d53cb4f31ff4b08de01691f7"
|
||||
}
|
||||
Generated
+1
@@ -3241,6 +3241,7 @@ dependencies = [
|
||||
"tokens",
|
||||
"tokio",
|
||||
"tts_common",
|
||||
"videos",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Staff
|
||||
-- teddanson is victor's alt
|
||||
-- endtimes is @sugarbro (testing)
|
||||
-- el_cid_93 is for testing
|
||||
-- SELECT token, username FROM users WHERE username IN (
|
||||
UPDATE users SET can_access_studio = true WHERE username IN (
|
||||
'bflat',
|
||||
'brandon',
|
||||
'crossproduct',
|
||||
'crossproduct1',
|
||||
'echelon',
|
||||
'el_cid_93',
|
||||
'endtimes',
|
||||
'fyscott',
|
||||
'gateway',
|
||||
'jags111',
|
||||
'mrvintage',
|
||||
'olivicmic',
|
||||
'rewin123',
|
||||
'saltacc',
|
||||
'teddanson',
|
||||
'vegito1089',
|
||||
'wilwong',
|
||||
'yae_ph',
|
||||
'zzz_last_item'
|
||||
);
|
||||
|
||||
UPDATE users SET can_access_studio = true WHERE username IN (
|
||||
'endtimes',
|
||||
'tropicalfun',
|
||||
'zzz_last_item'
|
||||
);
|
||||
@@ -0,0 +1,62 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Staff (1)
|
||||
-- * teddanson is victor's alt
|
||||
UPDATE users
|
||||
SET
|
||||
can_access_studio = false,
|
||||
maybe_feature_flags = 'explore_media,studio,video_style_transfer'
|
||||
WHERE username IN (
|
||||
'bflat',
|
||||
'brandon',
|
||||
'crossproduct',
|
||||
'crossproduct1',
|
||||
'echelon',
|
||||
'olivicmic',
|
||||
'saltacc',
|
||||
'teddanson',
|
||||
'vegito1089',
|
||||
'wilwong',
|
||||
'zzz_last_item'
|
||||
);
|
||||
|
||||
-- Staff (2)
|
||||
-- * el_cid_93 is for testing (who is this??)
|
||||
-- * endtimes is @sugarbro (testing)
|
||||
-- * tammieteller is Tammie (Pebblebed)
|
||||
UPDATE users
|
||||
SET
|
||||
can_access_studio = false,
|
||||
maybe_feature_flags = 'studio,video_style_transfer'
|
||||
WHERE username IN (
|
||||
'el_cid_93',
|
||||
'endtimes',
|
||||
'fyscott',
|
||||
'gateway',
|
||||
'jags111',
|
||||
'justinjohn0306',
|
||||
'mrvintage',
|
||||
'rewin123',
|
||||
'tammieteller',
|
||||
'tropicalfun',
|
||||
'yae_ph',
|
||||
'zzz_last_item'
|
||||
);
|
||||
|
||||
-- Early access to VST only
|
||||
UPDATE users
|
||||
SET
|
||||
can_access_studio = false,
|
||||
maybe_feature_flags = 'video_style_transfer'
|
||||
WHERE username IN (
|
||||
'kenjoplays',
|
||||
'ofccccccc',
|
||||
'sonicgt2',
|
||||
'stewiegroffin',
|
||||
'tanooki426',
|
||||
'wawoul',
|
||||
'zzz_last_item'
|
||||
);
|
||||
|
||||
@@ -6,30 +6,47 @@ use mp4::TrackType;
|
||||
|
||||
use errors::AnyhowResult;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Mp4Info {
|
||||
/// Framerate of the longest video track.
|
||||
pub framerate: f64,
|
||||
|
||||
/// Duration of the entire mp4.
|
||||
pub duration_millis: u128,
|
||||
|
||||
/// Width of the longest video track.
|
||||
pub width: u16,
|
||||
|
||||
/// Height of the longest video track.
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
pub fn get_mp4_info_for_bytes(file_bytes: &[u8], file_size: u64) -> AnyhowResult<Mp4Info> {
|
||||
pub fn get_mp4_info_for_bytes(file_bytes: &[u8]) -> AnyhowResult<Mp4Info> {
|
||||
get_mp4_info_for_bytes_and_len(file_bytes, file_bytes.len())
|
||||
}
|
||||
|
||||
pub fn get_mp4_info_for_bytes_and_len(file_bytes: &[u8], file_size: usize) -> AnyhowResult<Mp4Info> {
|
||||
let reader = BufReader::new(Cursor::new(file_bytes));
|
||||
get_mp4_info(reader, file_size)
|
||||
get_mp4_info(reader, file_size as u64)
|
||||
}
|
||||
|
||||
pub fn get_mp4_info<T: Seek + Read>(reader: T, file_size: u64) -> AnyhowResult<Mp4Info> {
|
||||
let mp4 = mp4::Mp4Reader::read_header(reader, file_size)?;
|
||||
|
||||
let mut framerate = 0.0;
|
||||
let mut longest_duration = Duration::from_secs(0);
|
||||
|
||||
let mut framerate = 0.0;
|
||||
let mut width = 0;
|
||||
let mut height = 0;
|
||||
|
||||
for track in mp4.tracks().values() {
|
||||
match track.track_type() {
|
||||
Ok(TrackType::Video) => {
|
||||
if track.duration() > longest_duration {
|
||||
longest_duration = track.duration();
|
||||
framerate = track.frame_rate();
|
||||
width = track.width();
|
||||
height = track.height();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -43,6 +60,8 @@ pub fn get_mp4_info<T: Seek + Read>(reader: T, file_size: u64) -> AnyhowResult<M
|
||||
Ok(Mp4Info {
|
||||
framerate,
|
||||
duration_millis: mp4.duration().as_millis(),
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -76,5 +95,7 @@ pub mod tests {
|
||||
|
||||
assert_eq!(info.framerate, 30.0);
|
||||
assert_eq!(info.duration_millis, 15168);
|
||||
assert_eq!(info.width, 640);
|
||||
assert_eq!(info.height, 480);
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,9 @@ pub struct SessionUserRecord {
|
||||
|
||||
// ===== FEATURE FLAGS ===== //
|
||||
|
||||
// Optional comma-separated list of parseable `UserFeatureFlag` enum features
|
||||
pub maybe_feature_flags: Option<String>,
|
||||
|
||||
pub can_access_studio: bool,
|
||||
|
||||
// ===== ROLE ===== //
|
||||
@@ -121,6 +124,7 @@ SELECT
|
||||
users.is_banned,
|
||||
|
||||
users.can_access_studio,
|
||||
users.maybe_feature_flags,
|
||||
|
||||
user_roles.can_use_tts,
|
||||
user_roles.can_use_w2l,
|
||||
@@ -183,6 +187,7 @@ WHERE user_sessions.token = ?
|
||||
is_banned: i8_to_bool(raw_user_record.is_banned),
|
||||
|
||||
can_access_studio: i8_to_bool(raw_user_record.can_access_studio),
|
||||
maybe_feature_flags: raw_user_record.maybe_feature_flags,
|
||||
|
||||
// Usage
|
||||
can_use_tts: nullable_i8_to_bool_default_false(raw_user_record.can_use_tts),
|
||||
@@ -250,6 +255,8 @@ struct SessionUserRawDbRecord {
|
||||
// Feature / Rollout Flags
|
||||
can_access_studio: i8,
|
||||
|
||||
maybe_feature_flags: Option<String>,
|
||||
|
||||
// NB: These are `Option` due to the JOIN not being compile-time assured.
|
||||
// Usage
|
||||
can_use_tts: Option<i8>,
|
||||
|
||||
@@ -19,8 +19,6 @@ actix_helpers = { path = "../../../lib/actix_helpers" }
|
||||
bootstrap = { path = "../../../lib/bootstrap" }
|
||||
buckets = { path = "../../../_types/buckets" }
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
google_drive_common = { path = "../../../lib/api_clients/google_drive_common" }
|
||||
thumbnail_generator = { path = "../../../lib/api_clients/thumbnail_generator"}
|
||||
collections = { path = "../../../lib/collections" }
|
||||
composite_identifiers = { path = "../../../_types/composite_identifiers" }
|
||||
concurrency = { path = "../../../lib/concurrency" }
|
||||
@@ -29,6 +27,7 @@ easyenv = { path = "../../../lib/easyenv" }
|
||||
enums = { path = "../../../_types/enums" }
|
||||
errors = { path = "../../../lib/errors" }
|
||||
filesys = { path = "../../../lib/files/filesys" }
|
||||
google_drive_common = { path = "../../../lib/api_clients/google_drive_common" }
|
||||
hashing = { path = "../../../lib/files/hashing" }
|
||||
images = { path = "../../../lib/files/images" }
|
||||
jobs_common = { path = "../../../lib/jobs_common" }
|
||||
@@ -39,8 +38,10 @@ mimetypes = { path = "../../../lib/files/mimetypes" }
|
||||
mysql_queries = { path = "../../../lib/mysql_queries" }
|
||||
redis_common = { path = "../../../lib/redis_common" }
|
||||
subprocess_common = { path = "../../../lib/subprocess_common" }
|
||||
thumbnail_generator = { path = "../../../lib/api_clients/thumbnail_generator"}
|
||||
tokens = { path = "../../../_types/tokens" }
|
||||
tts_common = { path = "../../../lib/tts_common" }
|
||||
videos = { path = "../../../lib/files/videos" }
|
||||
|
||||
# External
|
||||
anyhow = "1.0.75"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use std::fs::read_to_string;
|
||||
use std::fs::{File, read_to_string};
|
||||
use std::io::{BufReader, Read};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::anyhow;
|
||||
@@ -14,6 +16,7 @@ use hashing::sha256::sha256_hash_file::sha256_hash_file;
|
||||
use mysql_queries::payloads::generic_inference_args::generic_inference_args::PolymorphicInferenceArgs::Mc;
|
||||
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
|
||||
use mysql_queries::queries::media_files::create::insert_media_file_from_mocapnet::{insert_media_file_from_mocapnet, InsertArgs};
|
||||
use videos::get_mp4_info::{get_mp4_info, get_mp4_info_for_bytes, Mp4Info};
|
||||
|
||||
use crate::job::job_loop::job_success_result::{JobSuccessResult, ResultEntity};
|
||||
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
|
||||
@@ -53,6 +56,23 @@ pub async fn process_job(args: MocapNetProcessJobArgs<'_>) -> Result<JobSuccessR
|
||||
|
||||
let job_args = validate_job(job)?;
|
||||
|
||||
// ==================== JOB ARGS ==================== //
|
||||
|
||||
let maybe_args = job.maybe_inference_args
|
||||
.as_ref()
|
||||
.map(|args| args.args.as_ref())
|
||||
.flatten();
|
||||
|
||||
let poly_args = match maybe_args {
|
||||
None => return Err(ProcessSingleJobError::Other(anyhow!("Mocap args not found"))),
|
||||
Some(args) => args,
|
||||
};
|
||||
|
||||
let mc_args = match poly_args {
|
||||
Mc(args) => args,
|
||||
_ => return Err(ProcessSingleJobError::Other(anyhow!("Mocap args not found"))),
|
||||
};
|
||||
|
||||
// ==================== TEMP DIR ==================== //
|
||||
|
||||
let work_temp_dir = format!("temp_mocapnet_inference_{}", job.id.0);
|
||||
@@ -86,6 +106,22 @@ pub async fn process_job(args: MocapNetProcessJobArgs<'_>) -> Result<JobSuccessR
|
||||
|
||||
info!("Used video file: {:?}", usable_video_path);
|
||||
|
||||
// ==================== EXTRACT FILE METADATA (IF POSSIBLE) ==================== //
|
||||
|
||||
// TODO(bt): better check for video file type
|
||||
let video_read_result = try_get_video_info(&video_path.filesystem_path);
|
||||
|
||||
let mut maybe_width = mc_args.maybe_size1.clone();
|
||||
let mut maybe_height = mc_args.maybe_size2.clone();
|
||||
|
||||
// NB: Fail open. In the event this fails, we want to continue processing.
|
||||
if let Some(video_info) = video_read_result {
|
||||
info!("Read video info from file: {:?}", video_info);
|
||||
|
||||
maybe_width = Some(video_info.width as i32);
|
||||
maybe_height = Some(video_info.height as i32);
|
||||
}
|
||||
|
||||
// ==================== SETUP FOR INFERENCE ==================== //
|
||||
|
||||
info!("Ready for MocapNET inference...");
|
||||
@@ -102,21 +138,6 @@ pub async fn process_job(args: MocapNetProcessJobArgs<'_>) -> Result<JobSuccessR
|
||||
|
||||
// TODO: Limit output length for non-premium (???)
|
||||
|
||||
let maybe_args = job.maybe_inference_args
|
||||
.as_ref()
|
||||
.map(|args| args.args.as_ref())
|
||||
.flatten();
|
||||
|
||||
let poly_args = match maybe_args {
|
||||
None => return Err(ProcessSingleJobError::Other(anyhow!("Mocap args not found"))),
|
||||
Some(args) => args,
|
||||
};
|
||||
|
||||
let mc_args = match poly_args {
|
||||
Mc(args) => args,
|
||||
_ => return Err(ProcessSingleJobError::Other(anyhow!("Mocap args not found"))),
|
||||
};
|
||||
|
||||
// ==================== RUN INFERENCE SCRIPT ==================== //
|
||||
let stderr_output_file = work_temp_dir.path().join("stderr.txt");
|
||||
let inference_start_time = Instant::now();
|
||||
@@ -130,8 +151,8 @@ pub async fn process_job(args: MocapNetProcessJobArgs<'_>) -> Result<JobSuccessR
|
||||
maybe_ik3: &mc_args.maybe_ik3,
|
||||
maybe_smoothing1: &mc_args.maybe_smoothing1,
|
||||
maybe_smoothing2: &mc_args.maybe_smoothing2,
|
||||
maybe_size1: &mc_args.maybe_size1,
|
||||
maybe_size2: &mc_args.maybe_size2,
|
||||
maybe_size1: &maybe_width,
|
||||
maybe_size2: &maybe_height,
|
||||
stderr_output_file: &stderr_output_file,
|
||||
});
|
||||
|
||||
@@ -255,3 +276,12 @@ pub async fn process_job(args: MocapNetProcessJobArgs<'_>) -> Result<JobSuccessR
|
||||
inference_duration,
|
||||
})
|
||||
}
|
||||
|
||||
// Fail open if this doesn't work
|
||||
fn try_get_video_info(video_filename: &PathBuf) -> Option<Mp4Info> {
|
||||
let video_file = File::open(video_filename).ok()?;
|
||||
let file_length = video_file.metadata().ok()?.len();
|
||||
let reader = BufReader::new(video_file);
|
||||
let info = get_mp4_info(reader, file_length).ok()?;
|
||||
Some(info)
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
#![forbid(unused_mut)]
|
||||
#![forbid(unused_variables)]
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
@@ -12,11 +13,13 @@ use log::warn;
|
||||
use sqlx::MySqlPool;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
|
||||
use http_server_common::response::response_error_helpers::to_simple_json_error;
|
||||
|
||||
use crate::common_responses::user_details_lite::UserDetailsLight;
|
||||
use crate::cookies::anonymous_visitor_tracking::avt_cookie_manager::AvtCookieManager;
|
||||
use crate::utils::session_checker::SessionChecker;
|
||||
use crate::utils::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
|
||||
#[derive(Serialize, Copy, Clone, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@@ -47,6 +50,7 @@ pub struct SessionUserInfo {
|
||||
|
||||
// Feature / rollout flags:
|
||||
pub can_access_studio: bool,
|
||||
pub maybe_feature_flags: HashSet<UserFeatureFlag>,
|
||||
|
||||
// Premium plans:
|
||||
pub fakeyou_plan: FakeYouPlan,
|
||||
@@ -149,6 +153,9 @@ pub async fn session_info_handler(
|
||||
match maybe_user_session {
|
||||
None => {}
|
||||
Some(session_data) => {
|
||||
let feature_flags =
|
||||
UserSessionFeatureFlags::new(session_data.maybe_feature_flags.as_deref());
|
||||
|
||||
if !session_data.is_banned {
|
||||
// NB: Banned users can't be logged in
|
||||
logged_in = true;
|
||||
@@ -166,6 +173,7 @@ pub async fn session_info_handler(
|
||||
|
||||
// Rollout / feature flags:
|
||||
can_access_studio: session_data.can_access_studio,
|
||||
maybe_feature_flags: feature_flags.get_flags(),
|
||||
|
||||
// Premium plans:
|
||||
fakeyou_plan: FakeYouPlan::Free,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod email_to_gravatar;
|
||||
pub mod session_checker;
|
||||
pub mod user_session_extended;
|
||||
pub mod user_session_extended;
|
||||
pub mod user_session_feature_flags;
|
||||
@@ -17,6 +17,7 @@ use redis_common::redis_cache_keys::RedisCacheKeys;
|
||||
|
||||
use crate::cookies::session::session_cookie_manager::SessionCookieManager;
|
||||
use crate::utils::user_session_extended::{UserSessionExtended, UserSessionPreferences, UserSessionPremiumPlanInfo, UserSessionRoleAndPermissions, UserSessionSubscriptionPlan, UserSessionUserDetails};
|
||||
use crate::utils::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SessionChecker {
|
||||
@@ -168,7 +169,6 @@ impl SessionChecker {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================== UserSessionExtended ====================
|
||||
|
||||
//#[deprecated = "Use the PoolConnection<MySql> method instead of the MySqlPool one."]
|
||||
@@ -258,6 +258,9 @@ impl SessionChecker {
|
||||
can_ban_users: user_session.can_ban_users,
|
||||
can_delete_users: user_session.can_delete_users,
|
||||
},
|
||||
feature_flags: UserSessionFeatureFlags {
|
||||
maybe_feature_flags: user_session.maybe_feature_flags,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ use chrono::{DateTime, Utc};
|
||||
|
||||
use enums::common::visibility::Visibility;
|
||||
|
||||
use crate::utils::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct UserSessionExtended {
|
||||
pub user_token: String,
|
||||
@@ -9,6 +11,7 @@ pub struct UserSessionExtended {
|
||||
pub premium: UserSessionPremiumPlanInfo,
|
||||
pub preferences: UserSessionPreferences,
|
||||
pub role: UserSessionRoleAndPermissions,
|
||||
pub feature_flags: UserSessionFeatureFlags,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@@ -96,3 +99,4 @@ pub struct UserSessionRoleAndPermissions {
|
||||
pub can_ban_users: bool,
|
||||
pub can_delete_users: bool,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct UserSessionFeatureFlags {
|
||||
// Optional comma-separated list of parseable `UserFeatureFlag` enum features
|
||||
pub maybe_feature_flags: Option<String>,
|
||||
}
|
||||
|
||||
impl UserSessionFeatureFlags {
|
||||
|
||||
pub fn new(maybe_feature_flags: Option<&str>) -> Self {
|
||||
Self {
|
||||
maybe_feature_flags: maybe_feature_flags.map(|s| s.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_flags(&self) -> HashSet<UserFeatureFlag> {
|
||||
match self.maybe_feature_flags.as_deref() {
|
||||
None => HashSet::new(),
|
||||
Some(feature_flags) => {
|
||||
feature_flags
|
||||
.split(",")
|
||||
.map(|flag| flag.trim())
|
||||
.filter_map(|flag| UserFeatureFlag::from_str(flag).ok())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(bt,2024-03-05): Caching
|
||||
pub fn has_permission_unoptimized(&self, permission: UserFeatureFlag) -> bool {
|
||||
self.get_flags().contains(&permission)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
|
||||
use crate::utils::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
|
||||
#[test]
|
||||
fn test_no_flags() {
|
||||
let flags = UserSessionFeatureFlags::default();
|
||||
assert_eq!(flags.get_flags().len(), 0);
|
||||
|
||||
for flag in UserFeatureFlag::all_variants() {
|
||||
assert_eq!(flags.has_permission_unoptimized(flag), false);
|
||||
}
|
||||
|
||||
// TODO(bt,2024-03-05): Expose strum to callers in test packages.
|
||||
//for flag in UserFeatureFlag::iter() {
|
||||
// assert_eq!(flags.has_permission_unoptimized(flag), false);
|
||||
//}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_feature() {
|
||||
let flags = UserSessionFeatureFlags {
|
||||
maybe_feature_flags: Some("studio".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(flags.get_flags().len(), 1);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::Studio), true);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer), false);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_features() {
|
||||
let flags = UserSessionFeatureFlags {
|
||||
maybe_feature_flags: Some("studio,video_style_transfer,explore_media".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(flags.get_flags().len(), 3);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::Studio), true);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer), true);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplication() {
|
||||
let flags = UserSessionFeatureFlags {
|
||||
maybe_feature_flags: Some("studio,studio,studio,studio".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(flags.get_flags().len(), 1);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::Studio), true);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer), false);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spacing() {
|
||||
let flags = UserSessionFeatureFlags {
|
||||
maybe_feature_flags: Some(" studio, video_style_transfer , , , ".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(flags.get_flags().len(), 2);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::Studio), true);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer), true);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_features_and_typos() {
|
||||
let flags = UserSessionFeatureFlags {
|
||||
maybe_feature_flags: Some("invalid,,typo,stdo,STUDIO".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(flags.get_flags().len(), 0);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::Studio), false);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer), false);
|
||||
assert_eq!(flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia), false);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ use crate::http_server::endpoints::media_files::upload::upload_media_file_handle
|
||||
use crate::http_server::endpoints::media_files::upload_video::upload_error::VideoMediaFileUploadError;
|
||||
use crate::http_server::endpoints::media_files::upload_video::upload_video_media_file_handler::*;
|
||||
use crate::http_server::endpoints::prompts::get_prompt::*;
|
||||
use crate::http_server::endpoints::service::status_alert_handler::*;
|
||||
use crate::http_server::endpoints::user_bookmarks::batch_get_user_bookmarks_handler::*;
|
||||
use crate::http_server::endpoints::user_bookmarks::create_user_bookmark_handler::*;
|
||||
use crate::http_server::endpoints::user_bookmarks::delete_user_bookmark_handler::*;
|
||||
@@ -79,6 +80,7 @@ use crate::http_server::web_utils::response_success_helpers::*;
|
||||
crate::http_server::endpoints::media_files::upload::upload_media_file_handler::upload_media_file_handler,
|
||||
crate::http_server::endpoints::media_files::upload_video::upload_video_media_file_handler::upload_video_media_file_handler,
|
||||
crate::http_server::endpoints::prompts::get_prompt::get_prompt_handler,
|
||||
crate::http_server::endpoints::service::status_alert_handler::status_alert_handler,
|
||||
crate::http_server::endpoints::user_bookmarks::batch_get_user_bookmarks_handler::batch_get_user_bookmarks_handler,
|
||||
crate::http_server::endpoints::user_bookmarks::create_user_bookmark_handler::create_user_bookmark_handler,
|
||||
crate::http_server::endpoints::user_bookmarks::delete_user_bookmark_handler::delete_user_bookmark_handler,
|
||||
@@ -233,10 +235,12 @@ use crate::http_server::web_utils::response_success_helpers::*;
|
||||
SetUserRatingError,
|
||||
SetUserRatingRequest,
|
||||
SetUserRatingResponse,
|
||||
StatusAlertCategory,
|
||||
StatusAlertError,
|
||||
StatusAlertInfo,
|
||||
StatusAlertResponse,
|
||||
StatusDetailsResponse,
|
||||
StorytellerStreamPlan,
|
||||
VideoMediaFileUploadError,
|
||||
UploadVideoMediaSuccessResponse,
|
||||
TerminateInferenceJobError,
|
||||
TerminateInferenceJobPathInfo,
|
||||
TerminateInferenceJobSuccessResponse,
|
||||
@@ -244,6 +248,7 @@ use crate::http_server::web_utils::response_success_helpers::*;
|
||||
UpdateWeightPathInfo,
|
||||
UpdateWeightRequest,
|
||||
UploadMediaSuccessResponse,
|
||||
UploadVideoMediaSuccessResponse,
|
||||
UserBookmarkDetailsForUserList,
|
||||
UserBookmarkEntityType,
|
||||
UserBookmarkForEntityListItem,
|
||||
@@ -253,6 +258,7 @@ use crate::http_server::web_utils::response_success_helpers::*;
|
||||
UserProfileUserBadge,
|
||||
UserRatingEntityType,
|
||||
UserRatingValue,
|
||||
VideoMediaFileUploadError,
|
||||
Weight,
|
||||
WeightsData,
|
||||
ZsDatasetRecord,
|
||||
|
||||
+3
-2
@@ -24,6 +24,7 @@ use crate::http_server::common_responses::pagination_cursors::PaginationCursors;
|
||||
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::server_state::ServerState;
|
||||
use crate::util::allowed_explore_media_access::allowed_explore_media_access;
|
||||
use crate::util::allowed_studio_access::allowed_studio_access;
|
||||
|
||||
#[derive(Deserialize, ToSchema, IntoParams)]
|
||||
@@ -136,8 +137,8 @@ pub async fn list_media_files_handler(
|
||||
|
||||
// ==================== FEATURE FLAG CHECK ==================== //
|
||||
|
||||
if !allowed_studio_access(maybe_user_session.as_ref(), &server_state.flags) {
|
||||
warn!("Storyteller Studio access is not permitted for user");
|
||||
if !allowed_explore_media_access(maybe_user_session.as_ref()) {
|
||||
warn!("Explore media access is not permitted for user");
|
||||
return Err(ListMediaFilesError::NotAuthorized);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ use mimetypes::mimetype_to_extension::mimetype_to_extension;
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use mysql_queries::queries::media_files::create::insert_media_file_from_file_upload::{insert_media_file_from_file_upload, InsertMediaFileFromUploadArgs, UploadType};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use videos::get_mp4_info::{get_mp4_info, get_mp4_info_for_bytes};
|
||||
use videos::get_mp4_info::{get_mp4_info, get_mp4_info_for_bytes, get_mp4_info_for_bytes_and_len};
|
||||
|
||||
use crate::http_server::endpoints::engine::create_scene_handler::{CreateSceneError, CreateSceneSuccessResponse};
|
||||
use crate::http_server::endpoints::media_files::upload_video::drain_multipart_request::drain_multipart_request;
|
||||
@@ -176,7 +176,7 @@ pub async fn upload_video_media_file_handler(
|
||||
|
||||
// ==================== FRAME RATE ==================== //
|
||||
|
||||
let mp4_info = get_mp4_info_for_bytes(file_bytes.as_ref(), file_size_bytes as u64)
|
||||
let mp4_info = get_mp4_info_for_bytes(file_bytes.as_ref())
|
||||
.map_err(|err| {
|
||||
warn!("Error reading mp4 info: {:?}", err);
|
||||
VideoMediaFileUploadError::ServerError
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod health_check_handler;
|
||||
pub mod public_info_handler;
|
||||
pub mod public_info_handler;
|
||||
pub mod status_alert_handler;
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::{HttpRequest, HttpResponse, ResponseError, web};
|
||||
use actix_web::http::StatusCode;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
|
||||
|
||||
use crate::server_state::ServerState;
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct StatusAlertResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// If there's an alert, this will be set.
|
||||
/// The sub keys are optional, but at least one of them will be set.
|
||||
/// i.e. we can have an alert with no message or no predefined category.
|
||||
pub maybe_alert: Option<StatusAlertInfo>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct StatusAlertInfo {
|
||||
/// If an alert is set, this might be a key for a common i18n message to show.
|
||||
pub maybe_category: Option<StatusAlertCategory>,
|
||||
|
||||
/// We can set an optional message to show to the frontend.
|
||||
///
|
||||
/// This field may or may not be set. If it's set, this is custom text that should
|
||||
/// be displayed to the user. For example, we might tell the user when we'll be
|
||||
/// back online, etc. Both this field and `maybe_category` are optional.
|
||||
///
|
||||
/// If `maybe_category` is present, it should be a key for a predefined and
|
||||
/// internationalized (i18n) message stored on the frontend.
|
||||
///
|
||||
/// Either or both of these fields may be set.
|
||||
pub maybe_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum StatusAlertCategory {
|
||||
/// System is down for maintenance
|
||||
DownForMaintenance,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub enum StatusAlertError {
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl ResponseError for StatusAlertError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
StatusAlertError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
serialize_as_json_error(self)
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Not using derive_more::Display since Clion doesn't understand it.
|
||||
impl std::fmt::Display for StatusAlertError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/status_alert_check",
|
||||
responses(
|
||||
(
|
||||
status = 200,
|
||||
description = "Check service status for frontend alert messages",
|
||||
body = StatusAlertResponse,
|
||||
),
|
||||
(status = 500, description = "Server error", body = StatusAlertError)
|
||||
),
|
||||
)]
|
||||
pub async fn status_alert_handler(
|
||||
_http_request: HttpRequest,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, StatusAlertError> {
|
||||
|
||||
let maybe_category = server_state
|
||||
.flags
|
||||
.maybe_status_alert_category
|
||||
.as_deref()
|
||||
.map(|category| category_to_enum(&category))
|
||||
.flatten();
|
||||
|
||||
let maybe_message = server_state
|
||||
.flags
|
||||
.maybe_status_alert_custom_message
|
||||
.as_deref()
|
||||
.map(|message| message.trim().to_string());
|
||||
|
||||
let maybe_alert = match (maybe_category, maybe_message) {
|
||||
(None, None) => None,
|
||||
(category, message) => Some(StatusAlertInfo {
|
||||
maybe_category: category,
|
||||
maybe_message: message,
|
||||
}),
|
||||
};
|
||||
|
||||
let response = StatusAlertResponse {
|
||||
success: true,
|
||||
maybe_alert,
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|e| StatusAlertError::ServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}
|
||||
|
||||
fn category_to_enum(category: &str) -> Option<StatusAlertCategory> {
|
||||
let key = category.trim();
|
||||
match key {
|
||||
"down_for_maintenance" => Some(StatusAlertCategory::DownForMaintenance),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
+41
-90
@@ -24,9 +24,7 @@ use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use migration::text_to_speech::get_tts_model_for_enqueue_inference_migration::TtsModelForEnqueueInferenceMigrationWrapper;
|
||||
use mysql_queries::payloads::generic_inference_args::generic_inference_args::{GenericInferenceArgs, InferenceCategoryAbbreviated};
|
||||
use mysql_queries::queries::generic_inference::web::insert_generic_inference_job::{insert_generic_inference_job, InsertGenericInferenceArgs};
|
||||
use mysql_queries::queries::tts::tts_inference_jobs::insert_tts_inference_job::TtsInferenceJobInsertBuilder;
|
||||
use redis_common::redis_keys::RedisKeys;
|
||||
use tokens::tokens::tts_inference_jobs::TtsInferenceJobToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
use tts_common::priority::{FAKEYOU_DEFAULT_VALID_API_TOKEN_PRIORITY_LEVEL, FAKEYOU_INVESTOR_PRIORITY_LEVEL};
|
||||
use user_input_common::check_for_slurs::contains_slurs;
|
||||
@@ -55,10 +53,6 @@ const STORYTELLER_DEMO_COOKIE_NAME : &str = "storyteller_demo";
|
||||
///
|
||||
const DEBUG_HEADER_NAME : &str = "enable-debug-mode";
|
||||
|
||||
/// Requests with this header can be forced onto the new jobs system.
|
||||
/// It'll be used to help test TT2 on the new system before it is rolled out for everyone.
|
||||
const NEW_JOB_SYSTEM_HEADER_NAME : &str = "new-job-system";
|
||||
|
||||
/// The routing tag header can send workloads to particular k8s hosts.
|
||||
/// This is useful for catching the live logs or intercepting the job.
|
||||
/// NB: This is only for the new job system.
|
||||
@@ -234,9 +228,6 @@ pub async fn enqueue_infer_tts_handler(
|
||||
let is_debug_request = get_request_header_optional(&http_request, DEBUG_HEADER_NAME)
|
||||
.is_some();
|
||||
|
||||
let use_new_job_system_for_request = get_request_header_optional(&http_request, NEW_JOB_SYSTEM_HEADER_NAME)
|
||||
.is_some();
|
||||
|
||||
let maybe_routing_tag=
|
||||
get_request_header_optional(&http_request, ROUTING_TAG_HEADER_NAME)
|
||||
.map(|routing_tag| routing_tag.trim().to_string());
|
||||
@@ -377,95 +368,55 @@ pub async fn enqueue_infer_tts_handler(
|
||||
|
||||
// ==================== ENQUEUE APPROPRIATE TTS TYPE ==================== //
|
||||
|
||||
let use_new_job_system = use_new_job_system_for_request || server_state.flags.enable_enqueue_generic_tts_job;
|
||||
|
||||
let job_token;
|
||||
let job_token_type;
|
||||
|
||||
let model_type = InferenceModelType::Tacotron2;
|
||||
|
||||
let max_duration_seconds = plan.tts_max_duration_seconds();
|
||||
|
||||
if use_new_job_system {
|
||||
// This branch uses the `generic-inference-job` service and tables.
|
||||
info!("Creating tts inference job record (new generic job system)...");
|
||||
// This branch uses the `generic-inference-job` service and tables.
|
||||
info!("Creating tts inference job record (new generic job system)...");
|
||||
|
||||
let maybe_creator_user_token_typed = maybe_user_token
|
||||
.as_deref()
|
||||
.map(|token| UserToken::new_from_str(token));
|
||||
let maybe_creator_user_token_typed = maybe_user_token
|
||||
.as_deref()
|
||||
.map(|token| UserToken::new_from_str(token));
|
||||
|
||||
let maybe_avt_token = server_state.avt_cookie_manager.get_avt_token_from_request(&http_request);
|
||||
let maybe_avt_token = server_state.avt_cookie_manager.get_avt_token_from_request(&http_request);
|
||||
|
||||
let query_result = insert_generic_inference_job(InsertGenericInferenceArgs {
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
job_type: InferenceJobType::Tacotron2,
|
||||
inference_category: InferenceCategory::TextToSpeech,
|
||||
maybe_model_type: Some(model_type),
|
||||
maybe_model_token: Some(request.tts_model_token.as_str()),
|
||||
maybe_input_source_token: None, // NB: TTS doesn't have input media
|
||||
maybe_input_source_token_type: None, // NB: TTS doesn't have input media
|
||||
maybe_download_url: None,
|
||||
maybe_cover_image_media_file_token: None,
|
||||
maybe_raw_inference_text: Some(inference_text.as_str()),
|
||||
maybe_max_duration_seconds: Some(max_duration_seconds),
|
||||
maybe_inference_args: Some(GenericInferenceArgs {
|
||||
inference_category: Some(InferenceCategoryAbbreviated::TextToSpeech),
|
||||
args: None, // NB: We don't need to encode any args yet.
|
||||
}),
|
||||
maybe_creator_user_token: maybe_creator_user_token_typed.as_ref(),
|
||||
maybe_avt_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: set_visibility,
|
||||
priority_level,
|
||||
requires_keepalive: false, // TODO: We may want this to be the case in the future.
|
||||
is_debug_request,
|
||||
maybe_routing_tag: maybe_routing_tag.as_deref(),
|
||||
mysql_pool: &server_state.mysql_pool,
|
||||
}).await;
|
||||
let query_result = insert_generic_inference_job(InsertGenericInferenceArgs {
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
job_type: InferenceJobType::Tacotron2,
|
||||
inference_category: InferenceCategory::TextToSpeech,
|
||||
maybe_model_type: Some(model_type),
|
||||
maybe_model_token: Some(request.tts_model_token.as_str()),
|
||||
maybe_input_source_token: None, // NB: TTS doesn't have input media
|
||||
maybe_input_source_token_type: None, // NB: TTS doesn't have input media
|
||||
maybe_download_url: None,
|
||||
maybe_cover_image_media_file_token: None,
|
||||
maybe_raw_inference_text: Some(inference_text.as_str()),
|
||||
maybe_max_duration_seconds: Some(max_duration_seconds),
|
||||
maybe_inference_args: Some(GenericInferenceArgs {
|
||||
inference_category: Some(InferenceCategoryAbbreviated::TextToSpeech),
|
||||
args: None, // NB: We don't need to encode any args yet.
|
||||
}),
|
||||
maybe_creator_user_token: maybe_creator_user_token_typed.as_ref(),
|
||||
maybe_avt_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: set_visibility,
|
||||
priority_level,
|
||||
requires_keepalive: false, // TODO: We may want this to be the case in the future.
|
||||
is_debug_request,
|
||||
maybe_routing_tag: maybe_routing_tag.as_deref(),
|
||||
mysql_pool: &server_state.mysql_pool,
|
||||
}).await;
|
||||
|
||||
let inference_job_token = match query_result {
|
||||
Ok((inference_job_token, _id)) => inference_job_token,
|
||||
Err(err) => {
|
||||
warn!("New (generic) tts inference job creation DB error: {:?}", err);
|
||||
return Err(InferTtsError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
job_token = inference_job_token.to_string();
|
||||
job_token_type = InferenceJobTokenType::Generic;
|
||||
|
||||
} else {
|
||||
// This branch uses the `tts-inference-job` service and tables.
|
||||
info!("Creating tts inference job record (legacy job system)...");
|
||||
|
||||
// This token is returned to the client.
|
||||
job_token = TtsInferenceJobToken::generate().to_string();
|
||||
|
||||
let query_result = TtsInferenceJobInsertBuilder::new_for_fakeyou_request()
|
||||
.set_job_token(&job_token)
|
||||
.set_uuid_idempotency_token(&request.uuid_idempotency_token)
|
||||
.set_model_token(&request.tts_model_token)
|
||||
.set_raw_inference_text(&inference_text)
|
||||
.set_maybe_creator_user_token(maybe_user_token.as_deref())
|
||||
.set_creator_ip_address(&ip_address)
|
||||
.set_creator_set_visibility(set_visibility.to_str())
|
||||
.set_priority_level(priority_level)
|
||||
.set_max_duration_seconds(max_duration_seconds)
|
||||
.set_is_from_api(is_from_api)
|
||||
.set_is_debug_request(is_debug_request)
|
||||
.insert(&server_state.mysql_pool)
|
||||
.await;
|
||||
|
||||
match query_result {
|
||||
Ok(_) => {},
|
||||
Err(err) => {
|
||||
warn!("New tts inference job creation DB error: {:?}", err);
|
||||
return Err(InferTtsError::ServerError);
|
||||
}
|
||||
let inference_job_token = match query_result {
|
||||
Ok((inference_job_token, _id)) => inference_job_token,
|
||||
Err(err) => {
|
||||
warn!("New (generic) tts inference job creation DB error: {:?}", err);
|
||||
return Err(InferTtsError::ServerError);
|
||||
}
|
||||
};
|
||||
|
||||
job_token_type = InferenceJobTokenType::LegacyTts;
|
||||
}
|
||||
let job_token = inference_job_token.to_string();
|
||||
|
||||
server_state.firehose_publisher.enqueue_tts_inference(maybe_user_token.as_deref(), &job_token, &request.tts_model_token)
|
||||
.await
|
||||
@@ -476,8 +427,8 @@ pub async fn enqueue_infer_tts_handler(
|
||||
|
||||
let response = InferTtsSuccessResponse {
|
||||
success: true,
|
||||
inference_job_token: job_token.to_string(),
|
||||
inference_job_token_type: job_token_type,
|
||||
inference_job_token: job_token,
|
||||
inference_job_token_type: InferenceJobTokenType::Generic,
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ pub async fn list_voice_conversion_models_handler(
|
||||
})?;
|
||||
|
||||
// TODO: Fail open in case the DB is down. Pull from expired cache if query fails.
|
||||
let models = get_all_models(&mut mysql_connection, server_state.flags.switch_voice_conversion_to_model_weights)
|
||||
let models = get_all_models(&mut mysql_connection, true)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Error querying database: {:?}", e);
|
||||
|
||||
+4
-4
@@ -27,10 +27,10 @@ use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session;
|
||||
use crate::configs::plans::plan_category::{PlanCategory};
|
||||
use crate::configs::plans::plan_category::PlanCategory;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::server_state::ServerState;
|
||||
use crate::util::allowed_studio_access::allowed_studio_access;
|
||||
use crate::util::allowed_video_style_transfer_access::allowed_video_style_transfer_access;
|
||||
use crate::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
|
||||
/// Debug requests can get routed to special "debug-only" workers, which can
|
||||
@@ -144,8 +144,8 @@ pub async fn enqueue_comfy_ui_handler(
|
||||
|
||||
// ==================== FEATURE FLAG CHECK ==================== //
|
||||
|
||||
if !allowed_studio_access(maybe_user_session.as_ref(), &server_state.flags) {
|
||||
warn!("Storyteller Studio access is not permitted for user");
|
||||
if !allowed_video_style_transfer_access(maybe_user_session.as_ref(), &server_state.flags) {
|
||||
warn!("Video style transfer access is not permitted for user");
|
||||
return Err(EnqueueComfyError::NotAuthorized);
|
||||
}
|
||||
|
||||
|
||||
@@ -340,7 +340,11 @@ async fn main() -> AnyhowResult<()> {
|
||||
.ok_or(anyhow!("invalid server environment"))?;
|
||||
|
||||
let service_feature_flags = StaticFeatureFlags {
|
||||
// Permanent (control plane / safety) flags
|
||||
// Permanent (control plane / safety) flags : messaging
|
||||
maybe_status_alert_category: easyenv::get_env_string_optional("FF_STATUS_ALERT_CATEGORY"),
|
||||
maybe_status_alert_custom_message: easyenv::get_env_string_optional("FF_STATUS_ALERT_CUSTOM_MESSAGE"),
|
||||
|
||||
// Permanent (control plane / safety) flags : disabling features
|
||||
global_429_pushback_filter_enabled: easyenv::get_env_bool_or_default("FF_GLOBAL_429_PUSHBACK_FILTER_ENABLED", false),
|
||||
disable_unified_queue_stats_endpoint: easyenv::get_env_bool_or_default("FF_DISABLE_QUEUE_STATS_ENDPOINT", false),
|
||||
disable_inference_queue_length_endpoint: easyenv::get_env_bool_or_default("FF_DISABLE_INFERENCE_QUEUE_LENGTH_ENDPOINT", false),
|
||||
@@ -357,10 +361,9 @@ async fn main() -> AnyhowResult<()> {
|
||||
troll_ban_user_percent: easyenv::get_env_num("FF_TROLL_BANNED_USER_PERCENT", 0)?,
|
||||
|
||||
// Temporary flags
|
||||
enable_enqueue_generic_tts_job: easyenv::get_env_bool_or_default("FF_ENABLE_ENQUEUE_GENERIC_TTS_JOB", false),
|
||||
switch_voice_conversion_to_model_weights: easyenv::get_env_bool_or_default("FF_SWITCH_VOICE_CONVERSION_TO_MODEL_WEIGHTS", false),
|
||||
switch_tts_to_model_weights: easyenv::get_env_bool_or_default("FF_SWITCH_TTS_TO_MODEL_WEIGHTS", false),
|
||||
force_session_studio_flags: easyenv::get_env_bool_or_default("FF_FORCE_SESSION_STUDIO_FLAG", false),
|
||||
force_session_video_style_transfer_flags: easyenv::get_env_bool_or_default("FF_FORCE_SESSION_VST_FLAG", false),
|
||||
};
|
||||
|
||||
let third_party_url_redirector = ThirdPartyUrlRedirector::new(server_environment);
|
||||
|
||||
@@ -85,6 +85,7 @@ use crate::http_server::endpoints::moderation::users::list_users::list_users_han
|
||||
use crate::http_server::endpoints::prompts::get_prompt::get_prompt_handler;
|
||||
use crate::http_server::endpoints::service::health_check_handler::get_health_check_handler;
|
||||
use crate::http_server::endpoints::service::public_info_handler::get_public_info_handler;
|
||||
use crate::http_server::endpoints::service::status_alert_handler::status_alert_handler;
|
||||
use crate::http_server::endpoints::stats::get_unified_queue_stats::get_unified_queue_stats_handler;
|
||||
use crate::http_server::endpoints::stubs::app_model_downloads::get_app_model_downloads_handler;
|
||||
use crate::http_server::endpoints::stubs::app_news::get_app_news_handler;
|
||||
@@ -270,6 +271,7 @@ pub fn add_routes<T, B> (app: App<T>, server_environment: ServerEnvironment) ->
|
||||
.into_app();
|
||||
|
||||
// =================== BVH from Workflow ====================
|
||||
|
||||
let mut app = RouteBuilder::from_app(app)
|
||||
.add_post("/v1/conversion/enqueue_bvh_to_workflow", enqueue_bvh_to_workflow_handler)
|
||||
.into_app();
|
||||
@@ -280,6 +282,12 @@ pub fn add_routes<T, B> (app: App<T>, server_environment: ServerEnvironment) ->
|
||||
.add_get("/v1/stats/queues", get_unified_queue_stats_handler)
|
||||
.into_app();
|
||||
|
||||
// ==================== Service Status ====================
|
||||
|
||||
let mut app = RouteBuilder::from_app(app)
|
||||
.add_get("/v1/status_alert_check", status_alert_handler)
|
||||
.into_app();
|
||||
|
||||
// ==================== COMPONENTS ====================
|
||||
|
||||
app = add_suggested_api_v1_account_creation_and_session_routes(app); // /create_account, /session, /login, /logout
|
||||
|
||||
@@ -223,6 +223,12 @@ pub struct StaticFeatureFlags {
|
||||
/// Used to bring the service back online slowly.
|
||||
pub global_429_pushback_filter_enabled: bool,
|
||||
|
||||
/// If we're suffering an outage, set the alert category to display a predefined message to users.
|
||||
pub maybe_status_alert_category: Option<String>,
|
||||
|
||||
/// If we're suffering an outage, set custom text for the alert message.
|
||||
pub maybe_status_alert_custom_message: Option<String>,
|
||||
|
||||
/// Disable the live `/v1/stats/queues` endpoint for all users and serve a static value instead.
|
||||
pub disable_unified_queue_stats_endpoint: bool,
|
||||
|
||||
@@ -254,16 +260,6 @@ pub struct StaticFeatureFlags {
|
||||
/// This should be a number over 100.
|
||||
pub troll_ban_user_percent: u8,
|
||||
|
||||
// TODO(2023-03-20): Remove temporary flag when done.
|
||||
// NB(bt,2023-12-18): This is rolled out (=="TRUE")
|
||||
/// TEMPORARY: Control enqueuing TTS jobs to the generic job worker.
|
||||
pub enable_enqueue_generic_tts_job: bool,
|
||||
|
||||
// TODO(2023-12-18): Remove temporary flag when done.
|
||||
/// TEMPORARY: Move voice control model listing over to `model_weights` from `voice_conversion_models`
|
||||
/// This will control all downstream enqueuing, jobs, etc.
|
||||
pub switch_voice_conversion_to_model_weights: bool,
|
||||
|
||||
// TODO(2024-01-13): Remove temporary flag when done.
|
||||
/// TEMPORARY: Move voice control model listing over to `model_weights` from `tts_models`
|
||||
/// This will control all downstream enqueuing, jobs, etc.
|
||||
@@ -273,6 +269,11 @@ pub struct StaticFeatureFlags {
|
||||
/// TEMPORARY: Require users to have a studio flag on their `users` table record to use studio features.
|
||||
/// This will also prevent anonymous users from using the service APIs marked as studio-only.
|
||||
pub force_session_studio_flags: bool,
|
||||
|
||||
// TODO(2024-03-05): Remove when done.
|
||||
/// TEMPORARY: Require users to have a flag on their `users` table record to use video style transfer.
|
||||
/// This will also prevent anonymous users from using the service APIs marked as VST-only.
|
||||
pub force_session_video_style_transfer_flags: bool,
|
||||
}
|
||||
|
||||
/// Instead of top level service denial, these are bans against entities that instead return
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
|
||||
use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord;
|
||||
use users_component::utils::user_session_extended::UserSessionExtended;
|
||||
use users_component::utils::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
|
||||
use crate::server_state::StaticFeatureFlags;
|
||||
|
||||
/// Check whether we should allow the request access to explore media.
|
||||
pub fn allowed_explore_media_access(maybe_session: Option<impl UserSessionExploreMediaFlag>) -> bool {
|
||||
// NB: We're more careful here since this is sensitive until we have NLP checks.
|
||||
maybe_session
|
||||
.map(|ref session| session.can_access_explore_media())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub trait UserSessionExploreMediaFlag {
|
||||
fn can_access_explore_media(&self) -> bool;
|
||||
}
|
||||
|
||||
impl UserSessionExploreMediaFlag for UserSessionExtended {
|
||||
fn can_access_explore_media(&self) -> bool {
|
||||
self.feature_flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserSessionExploreMediaFlag for &UserSessionExtended {
|
||||
fn can_access_explore_media(&self) -> bool {
|
||||
self.feature_flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserSessionExploreMediaFlag for SessionUserRecord {
|
||||
fn can_access_explore_media(&self) -> bool {
|
||||
user_session_has_feature(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserSessionExploreMediaFlag for &SessionUserRecord {
|
||||
fn can_access_explore_media(&self) -> bool {
|
||||
user_session_has_feature(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn user_session_has_feature(user_session: &SessionUserRecord) -> bool {
|
||||
// TODO(bt, 2024-03-05): this is horrible.
|
||||
// There should be a wrapper class between the query and the caller.
|
||||
let flags = UserSessionFeatureFlags {
|
||||
maybe_feature_flags: user_session.maybe_feature_flags.clone(),
|
||||
};
|
||||
|
||||
flags.has_permission_unoptimized(UserFeatureFlag::ExploreMedia)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
|
||||
use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord;
|
||||
use users_component::utils::user_session_extended::UserSessionExtended;
|
||||
use users_component::utils::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
|
||||
use crate::server_state::StaticFeatureFlags;
|
||||
|
||||
@@ -20,24 +22,44 @@ pub trait UserSessionStudioFlag {
|
||||
|
||||
impl UserSessionStudioFlag for UserSessionExtended {
|
||||
fn can_access_studio(&self) -> bool {
|
||||
self.role.can_access_studio
|
||||
self.role.can_access_studio || self.feature_flags.has_permission_unoptimized(UserFeatureFlag::Studio)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserSessionStudioFlag for &UserSessionExtended {
|
||||
fn can_access_studio(&self) -> bool {
|
||||
self.role.can_access_studio
|
||||
self.role.can_access_studio || self.feature_flags.has_permission_unoptimized(UserFeatureFlag::Studio)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserSessionStudioFlag for SessionUserRecord {
|
||||
fn can_access_studio(&self) -> bool {
|
||||
self.can_access_studio
|
||||
if self.can_access_studio {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO(bt, 2024-03-05): this is horrible.
|
||||
// There should be a wrapper class between the query and the caller.
|
||||
let flags = UserSessionFeatureFlags {
|
||||
maybe_feature_flags: self.maybe_feature_flags.clone(),
|
||||
};
|
||||
|
||||
flags.has_permission_unoptimized(UserFeatureFlag::Studio)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserSessionStudioFlag for &SessionUserRecord {
|
||||
fn can_access_studio(&self) -> bool {
|
||||
self.can_access_studio
|
||||
if self.can_access_studio {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO(bt, 2024-03-05): this is horrible.
|
||||
// There should be a wrapper class between the query and the caller.
|
||||
let flags = UserSessionFeatureFlags {
|
||||
maybe_feature_flags: self.maybe_feature_flags.clone(),
|
||||
};
|
||||
|
||||
flags.has_permission_unoptimized(UserFeatureFlag::Studio)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
|
||||
use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord;
|
||||
use users_component::utils::user_session_extended::UserSessionExtended;
|
||||
use users_component::utils::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
|
||||
use crate::server_state::StaticFeatureFlags;
|
||||
|
||||
/// Check whether we should allow the request access to video style transfer
|
||||
pub fn allowed_video_style_transfer_access(maybe_session: Option<impl UserSessionVideoStyleTransferFlag>, flags: &StaticFeatureFlags) -> bool {
|
||||
if !flags.force_session_video_style_transfer_flags {
|
||||
return true;
|
||||
}
|
||||
|
||||
maybe_session
|
||||
.map(|ref session| session.can_access_video_style_transfer())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub trait UserSessionVideoStyleTransferFlag {
|
||||
fn can_access_video_style_transfer(&self) -> bool;
|
||||
}
|
||||
|
||||
impl UserSessionVideoStyleTransferFlag for UserSessionExtended {
|
||||
fn can_access_video_style_transfer(&self) -> bool {
|
||||
self.feature_flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserSessionVideoStyleTransferFlag for &UserSessionExtended {
|
||||
fn can_access_video_style_transfer(&self) -> bool {
|
||||
self.feature_flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserSessionVideoStyleTransferFlag for SessionUserRecord {
|
||||
fn can_access_video_style_transfer(&self) -> bool {
|
||||
user_session_has_feature(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl UserSessionVideoStyleTransferFlag for &SessionUserRecord {
|
||||
fn can_access_video_style_transfer(&self) -> bool {
|
||||
user_session_has_feature(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn user_session_has_feature(user_session: &SessionUserRecord) -> bool {
|
||||
// TODO(bt, 2024-03-05): this is horrible.
|
||||
// There should be a wrapper class between the query and the caller.
|
||||
let flags = UserSessionFeatureFlags {
|
||||
maybe_feature_flags: user_session.maybe_feature_flags.clone(),
|
||||
};
|
||||
|
||||
flags.has_permission_unoptimized(UserFeatureFlag::VideoStyleTransfer)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod allowed_explore_media_access;
|
||||
pub mod allowed_studio_access;
|
||||
pub mod allowed_video_style_transfer_access;
|
||||
pub mod delete_role_disambiguation;
|
||||
pub mod encrypted_sort_id;
|
||||
pub mod placeholder_images;
|
||||
pub mod troll_user_bans;
|
||||
pub mod troll_user_bans;
|
||||
Reference in new issue
Block a user