enqueue voice conversion request handler, media synthetic ids, ...
This commit is contained in:
parent
5b20710f96
commit
e1793d71b5
|
|
@ -0,0 +1,5 @@
|
|||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE media_upload_synthetic_ids;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- It gets unwieldy to show results without a numeric sequence.
|
||||
-- We create a synthetic sequence for each user to make revisiting results easier.
|
||||
CREATE TABLE media_upload_synthetic_ids (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key" (PUBLIC)
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The next result id.
|
||||
-- After we use an id, we increment this counter.
|
||||
next_id BIGINT(20) NOT NULL DEFAULT 0,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (user_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
|
@ -6,7 +6,7 @@ use std::fmt::Debug;
|
|||
/// The primary key for user media uploads (images, video, etc.)
|
||||
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct MediaUploadToken(String);
|
||||
pub struct MediaUploadToken(pub String);
|
||||
|
||||
impl_string_token!(MediaUploadToken);
|
||||
impl_crockford_generator!(MediaUploadToken, 32usize, EntityType::MediaUpload, CrockfordLower);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::fmt::Debug;
|
|||
/// The primary key for "generic" inference jobs.
|
||||
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct VoiceConversionModelToken(String);
|
||||
pub struct VoiceConversionModelToken(pub String);
|
||||
|
||||
impl_string_token!(VoiceConversionModelToken);
|
||||
impl_crockford_generator!(VoiceConversionModelToken, 16usize, EntityType::VoiceConversionModel, CrockfordLower);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
/// Test MediaUploadToken in isolation, just in case our macro-derived tests break.
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use tokens::files::media_upload::MediaUploadToken;
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||
struct CompositeType {
|
||||
media_upload_token: MediaUploadToken,
|
||||
string: String,
|
||||
}
|
||||
|
||||
mod interface {
|
||||
use tokens::files::media_upload::MediaUploadToken;
|
||||
|
||||
#[test]
|
||||
fn generate() {
|
||||
let token = MediaUploadToken::generate();
|
||||
assert!(!token.to_string().is_empty());
|
||||
assert!(token.to_string().starts_with("mu_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new() {
|
||||
let token = MediaUploadToken::new("mu_foo".to_string());
|
||||
assert_eq!(token, MediaUploadToken("mu_foo".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_from_str() {
|
||||
let token = MediaUploadToken::new_from_str("mu_foo");
|
||||
assert_eq!(token, MediaUploadToken("mu_foo".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_str() {
|
||||
let token = MediaUploadToken("mu_foo".to_string());
|
||||
assert_eq!(token.as_str(), "mu_foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
let token = MediaUploadToken("mu_foo".to_string());
|
||||
assert_eq!(token.to_string(), "mu_foo".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
mod traits {
|
||||
use tokens::files::media_upload::MediaUploadToken;
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
let token = MediaUploadToken("mu_foo".to_string());
|
||||
assert_eq!(format!("{}", token), "mu_foo".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug() {
|
||||
let token = MediaUploadToken("mu_foo".to_string());
|
||||
assert_eq!(format!("{:?}", token), "MediaUploadToken(\"mu_foo\")".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
mod serialization {
|
||||
use crate::CompositeType;
|
||||
use tokens::files::media_upload::MediaUploadToken;
|
||||
|
||||
#[test]
|
||||
fn serialize() {
|
||||
let expected = "\"mu_foo\"".to_string(); // NB: Quoted
|
||||
|
||||
let token = MediaUploadToken("mu_foo".to_string());
|
||||
assert_eq!(expected, toml::to_string(&token).unwrap());
|
||||
|
||||
// Just to show this serializes the same as a string
|
||||
assert_eq!(expected, toml::to_string("mu_foo").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_serialize() {
|
||||
let value = CompositeType { media_upload_token: MediaUploadToken("mu_foo".to_string()), string: "bar".to_string() };
|
||||
let expected = r#"{"media_upload_token":"mu_foo","string":"bar"}"#.to_string();
|
||||
assert_eq!(expected, serde_json::to_string(&value).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
mod deserialization {
|
||||
use crate::CompositeType;
|
||||
use tokens::files::media_upload::MediaUploadToken;
|
||||
|
||||
#[test]
|
||||
fn deserialize() {
|
||||
let payload = "\"mu_foo\""; // NB: Quoted
|
||||
let expected = "mu_foo".to_string();
|
||||
|
||||
let value: MediaUploadToken = serde_json::from_str(payload).unwrap();
|
||||
assert_eq!(value, MediaUploadToken(expected.clone()));
|
||||
|
||||
// Just to show this deserializes the same way as a string
|
||||
let value: String = serde_json::from_str(payload).unwrap();
|
||||
assert_eq!(value, expected.clone());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_deserialize() {
|
||||
let payload = r#"{"media_upload_token":"mu_foo","string":"bar"}"#.to_string();
|
||||
let expected = CompositeType {
|
||||
media_upload_token: MediaUploadToken("mu_foo".to_string()),
|
||||
string: "bar".to_string(),
|
||||
};
|
||||
|
||||
assert_eq!(expected, serde_json::from_str::<CompositeType>(&payload).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
// These traits should be tested by the macro, but we duplicate them in case that breaks
|
||||
mod crockford_traits {
|
||||
use tokens::files::media_upload::MediaUploadToken;
|
||||
|
||||
const ENTROPIC_CHARACTERS_MINIMUM : usize = 8;
|
||||
|
||||
#[test]
|
||||
fn entropy_is_sufficient() {
|
||||
assert!(MediaUploadToken::entropic_character_len() > ENTROPIC_CHARACTERS_MINIMUM);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_length() {
|
||||
assert_eq!(MediaUploadToken::generate().as_str().len(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokens_are_random() {
|
||||
let mut tokens = std::collections::HashSet::new();
|
||||
for _ in 0..100 {
|
||||
tokens.insert(MediaUploadToken::generate().to_string());
|
||||
}
|
||||
assert_eq!(tokens.len(), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_set() {
|
||||
let token_string = MediaUploadToken::generate().to_string();
|
||||
let prefix = MediaUploadToken::token_prefix();
|
||||
let random_part = token_string.replace(prefix, "");
|
||||
|
||||
assert!(random_part.len() > ENTROPIC_CHARACTERS_MINIMUM);
|
||||
assert!(random_part.chars().all(|c| c.is_numeric() || c.is_lowercase()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefix_ends_with_separator() {
|
||||
let prefix = MediaUploadToken::token_prefix();
|
||||
assert!(prefix.ends_with(":") || prefix.ends_with("_"));
|
||||
|
||||
let token_string = MediaUploadToken::generate().to_string();
|
||||
assert!(token_string.contains(":") || token_string.contains("_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn token_begins_with_prefix() {
|
||||
let prefix = MediaUploadToken::token_prefix();
|
||||
let token_string = MediaUploadToken::generate().to_string();
|
||||
assert!(token_string.starts_with(prefix));
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ mod interface {
|
|||
fn generate() {
|
||||
let token = UserToken::generate();
|
||||
assert!(!token.to_string().is_empty());
|
||||
assert!(token.to_string().starts_with("U:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use sqlx::mysql::MySqlQueryResult;
|
|||
use sqlx::{MySqlPool};
|
||||
use std::sync::Arc;
|
||||
use tokens::files::media_upload::MediaUploadToken;
|
||||
use tokens::jobs::inference::InferenceJobToken;
|
||||
use tokens::users::user::UserToken;
|
||||
|
||||
// TODO(bt, 2022-12-19): Convert this to a database 'enum'. Also, create an 'enums' package similar to 'tokens'.
|
||||
|
|
@ -27,6 +28,9 @@ enum FirehoseEvent {
|
|||
W2lInferenceStarted,
|
||||
W2lInferenceCompleted,
|
||||
|
||||
VcInferenceStarted,
|
||||
VcInferenceCompleted,
|
||||
|
||||
GenericDownloadStarted,
|
||||
GenericDownloadCompleted,
|
||||
|
||||
|
|
@ -54,6 +58,8 @@ impl FirehoseEvent {
|
|||
FirehoseEvent::W2lTemplateUploadCompleted => "w2l_template_upload_completed",
|
||||
FirehoseEvent::W2lInferenceStarted => "w2l_inference_started",
|
||||
FirehoseEvent::W2lInferenceCompleted => "w2l_inference_completed",
|
||||
FirehoseEvent::VcInferenceStarted => "vc_inference_started",
|
||||
FirehoseEvent::VcInferenceCompleted => "vc_inference_completed",
|
||||
FirehoseEvent::GenericDownloadStarted => "generic_download_started",
|
||||
FirehoseEvent::GenericDownloadCompleted => "generic_download_completed",
|
||||
FirehoseEvent::MediaUploaded => "media_uploaded",
|
||||
|
|
@ -186,6 +192,27 @@ impl FirehosePublisher {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn enqueue_vc_inference(&self, maybe_user_token: Option<&UserToken>, inference_job_token: &InferenceJobToken) -> AnyhowResult<()> {
|
||||
let _record_id = self.insert(
|
||||
FirehoseEvent::VcInferenceStarted,
|
||||
maybe_user_token.map(|u| u.as_str()),
|
||||
Some(inference_job_token.as_str()),
|
||||
Some(inference_job_token.as_str()),
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: Change result token type.
|
||||
pub async fn vc_inference_finished(&self, maybe_user_token: Option<&UserToken>, inference_job_token: &InferenceJobToken, result_token: &str) -> AnyhowResult<()> {
|
||||
let _record_id = self.insert(
|
||||
FirehoseEvent::VcInferenceCompleted,
|
||||
maybe_user_token.map(|u| u.as_str()),
|
||||
Some(inference_job_token.as_str()), // TODO: This could be vc model token
|
||||
Some(result_token)
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn enqueue_generic_download(&self, user_token: &str, job_token: &str) -> AnyhowResult<()> {
|
||||
let _record_id = self.insert(
|
||||
FirehoseEvent::GenericDownloadStarted,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use enums::core::visibility::Visibility;
|
|||
use reusable_types::db::enums::generic_inference_type::GenericInferenceType;
|
||||
use sqlx::MySqlPool;
|
||||
use tokens::jobs::inference::InferenceJobToken;
|
||||
use tokens::users::user::UserToken;
|
||||
|
||||
pub struct Args <'a> {
|
||||
pub job_token: &'a InferenceJobToken,
|
||||
|
|
@ -14,10 +15,13 @@ pub struct Args <'a> {
|
|||
pub maybe_raw_inference_text: Option<String>,
|
||||
pub maybe_model_token: Option<String>,
|
||||
|
||||
pub maybe_creator_user_token: Option<&'a str>,
|
||||
pub maybe_creator_user_token: Option<&'a UserToken>,
|
||||
pub creator_ip_address: &'a str,
|
||||
pub creator_set_visibility: Visibility,
|
||||
|
||||
pub priority_level: u8,
|
||||
pub is_debug_request: bool,
|
||||
|
||||
pub mysql_pool: &'a MySqlPool,
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +43,9 @@ SET
|
|||
creator_ip_address = ?,
|
||||
creator_set_visibility = ?,
|
||||
|
||||
priority_level = ?,
|
||||
is_debug_request = ?,
|
||||
|
||||
status = "pending"
|
||||
"#,
|
||||
args.job_token,
|
||||
|
|
@ -52,6 +59,8 @@ SET
|
|||
args.maybe_creator_user_token,
|
||||
args.creator_ip_address,
|
||||
args.creator_set_visibility.to_str(),
|
||||
args.priority_level,
|
||||
args.is_debug_request,
|
||||
);
|
||||
|
||||
let query_result = query.execute(args.mysql_pool)
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
pub mod voice_conversion_models;
|
||||
pub mod inference;
|
||||
pub mod models;
|
||||
|
|
@ -12,6 +12,13 @@ impl RedisKeys {
|
|||
format!("ttsUseCount:{}", model_token)
|
||||
}
|
||||
|
||||
/// This is a counter incremented with web VC inference.
|
||||
/// We can use a job to read MySql and fix counts if they get out of sync.
|
||||
/// These should be long-lived keys.
|
||||
pub fn web_vc_model_usage_count(model_token: &str) -> String {
|
||||
format!("vcUseCount:{}", model_token)
|
||||
}
|
||||
|
||||
/// This is a counter incremented with TTS inference.
|
||||
/// We can use a job to read MySql and fix counts if they get out of sync.
|
||||
/// These should be long-lived keys.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use container_common::hashing::hash_file_sha2::hash_file_sha2;
|
|||
use crate::JobState;
|
||||
use crate::job_steps::job_results::JobResults;
|
||||
use database_queries::queries::generic_download::job::list_available_generic_download_jobs::AvailableDownloadJob;
|
||||
use database_queries::queries::voice_conversion::voice_conversion_models::insert_voice_conversion_model_from_download_job::{Args, insert_voice_conversion_model_from_download_job};
|
||||
use database_queries::queries::voice_conversion::models::insert_voice_conversion_model_from_download_job::{Args, insert_voice_conversion_model_from_download_job};
|
||||
use enums::core::visibility::Visibility;
|
||||
use jobs_common::redis_job_status_logger::RedisJobStatusLogger;
|
||||
use log::{info, warn};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ use crate::configs::plans::plan_category::PlanCategory;
|
|||
const TTS_DEFAULT_PRIORITY_LEVEL : u8 = 0;
|
||||
const TTS_DEFAULT_DURATION_SECONDS : i64 = 12;
|
||||
|
||||
const VC_DEFAULT_PRIORITY_LEVEL : u8 = 0;
|
||||
|
||||
const VC_DEFAULT_MAX_CONCURRENT_MODELS : u32 = 5;
|
||||
|
||||
const VC_DEFAULT_PRE_RECORDED_TIME_LIMIT_SECONDS : i64 = 20;
|
||||
|
|
@ -48,7 +50,11 @@ pub struct Plan {
|
|||
tts_can_upload_private_models: bool,
|
||||
tts_can_share_private_models: bool,
|
||||
|
||||
// ========== Features for Voice Conversion ==========
|
||||
// ========== Features for Web Voice Conversion ==========
|
||||
|
||||
web_vc_base_priority_level: u8,
|
||||
|
||||
// ========== Features for Real Time Voice Conversion ==========
|
||||
|
||||
vc_max_concurrent_models: u32,
|
||||
|
||||
|
|
@ -79,6 +85,7 @@ impl Plan {
|
|||
tts_can_generate_mp3: builder.tts_can_generate_mp3,
|
||||
tts_can_upload_private_models: builder.tts_can_upload_private_models,
|
||||
tts_can_share_private_models: builder.tts_can_share_private_models,
|
||||
web_vc_base_priority_level: builder.web_vc_base_priority_level,
|
||||
vc_max_concurrent_models: builder.vc_max_concurrent_models,
|
||||
vc_pre_recorded_time_limit: builder.vc_pre_recorded_time_limit,
|
||||
vc_pre_recorded_time_is_unlimited: builder.vc_pre_recorded_time_is_unlimited,
|
||||
|
|
@ -132,6 +139,10 @@ impl Plan {
|
|||
pub fn tts_max_duration_seconds(&self) -> i32 {
|
||||
self.tts_max_duration.num_seconds() as i32
|
||||
}
|
||||
|
||||
pub fn web_vc_base_priority_level(&self) -> u8 {
|
||||
self.web_vc_base_priority_level
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder pattern for Plans.
|
||||
|
|
@ -153,7 +164,11 @@ pub struct PlanBuilder {
|
|||
tts_can_upload_private_models: bool,
|
||||
tts_can_share_private_models: bool,
|
||||
|
||||
// ========== Features for Voice Conversion ==========
|
||||
// ========== Features for Web Voice Conversion ==========
|
||||
|
||||
web_vc_base_priority_level: u8,
|
||||
|
||||
// ========== Features for Real Time Voice Conversion ==========
|
||||
|
||||
vc_max_concurrent_models: u32,
|
||||
|
||||
|
|
@ -188,7 +203,10 @@ impl PlanBuilder {
|
|||
tts_can_upload_private_models: false,
|
||||
tts_can_share_private_models : false,
|
||||
|
||||
// VC
|
||||
// VC (WEB)
|
||||
web_vc_base_priority_level: VC_DEFAULT_PRIORITY_LEVEL,
|
||||
|
||||
// VC (REALTIME)
|
||||
vc_max_concurrent_models: VC_DEFAULT_MAX_CONCURRENT_MODELS,
|
||||
vc_pre_recorded_time_limit: Duration::seconds(VC_DEFAULT_PRE_RECORDED_TIME_LIMIT_SECONDS),
|
||||
vc_pre_recorded_time_is_unlimited: false,
|
||||
|
|
@ -260,6 +278,11 @@ impl PlanBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn web_vc_base_priority_level(mut self, value: u8) -> Self {
|
||||
self.web_vc_base_priority_level = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn vc_max_concurrent_models(mut self, value: u32) -> Self {
|
||||
self.vc_max_concurrent_models = value;
|
||||
self
|
||||
|
|
|
|||
|
|
@ -260,7 +260,7 @@ pub async fn upload_media_handler(
|
|||
maybe_creator_anonymous_visitor_token: None,
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
maybe_creator_synthetic_id: None,
|
||||
maybe_creator_synthetic_id: None, // TODO: Don't forget about this.
|
||||
mysql_pool: &server_state.mysql_pool,
|
||||
})
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -14,4 +14,5 @@ pub mod tts;
|
|||
pub mod twitch;
|
||||
pub mod vocoders;
|
||||
pub mod voice_clone_requests;
|
||||
pub mod voice_conversion;
|
||||
pub mod w2l;
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
// NB: Incrementally getting rid of build warnings...
|
||||
#![forbid(unused_imports)]
|
||||
#![forbid(unused_mut)]
|
||||
#![forbid(unused_variables)]
|
||||
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{web, HttpResponse, HttpRequest};
|
||||
use crate::configs::plans::get_correct_plan_for_session::get_correct_plan_for_session;
|
||||
use crate::http_server::endpoints::investor_demo::demo_cookie::request_has_demo_cookie;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::server_state::ServerState;
|
||||
use database_queries::queries::generic_inference::web::insert_generic_inference_job::{Args, insert_generic_inference_job};
|
||||
use enums::core::visibility::Visibility;
|
||||
use http_server_common::request::get_request_header_optional::get_request_header_optional;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use log::{info, warn};
|
||||
use r2d2_redis::redis::Commands;
|
||||
use redis_common::redis_keys::RedisKeys;
|
||||
use reusable_types::db::enums::generic_inference_type::GenericInferenceType;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use tokens::jobs::inference::InferenceJobToken;
|
||||
use tokens::users::user::UserToken;
|
||||
use tts_common::priority::FAKEYOU_INVESTOR_PRIORITY_LEVEL;
|
||||
|
||||
/// Debug requests can get routed to special "debug-only" workers, which can
|
||||
/// be used to trial new code, run debugging, etc.
|
||||
const DEBUG_HEADER_NAME : &'static str = "enable_debug_mode";
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct EnqueueVoiceConversionInferenceRequest {
|
||||
uuid_idempotency_token: String,
|
||||
voice_conversion_model_token: String,
|
||||
media_token: String,
|
||||
creator_set_visibility: Option<Visibility>,
|
||||
is_storyteller_demo: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueVoiceConversionInferenceSuccessResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EnqueueVoiceConversionInferenceError {
|
||||
BadInput(String),
|
||||
NotAuthorized,
|
||||
ServerError,
|
||||
RateLimited,
|
||||
}
|
||||
|
||||
impl ResponseError for EnqueueVoiceConversionInferenceError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
EnqueueVoiceConversionInferenceError::BadInput(_) => StatusCode::BAD_REQUEST,
|
||||
EnqueueVoiceConversionInferenceError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
EnqueueVoiceConversionInferenceError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
EnqueueVoiceConversionInferenceError::RateLimited => StatusCode::TOO_MANY_REQUESTS,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let error_reason = match self {
|
||||
EnqueueVoiceConversionInferenceError::BadInput(reason) => reason.to_string(),
|
||||
EnqueueVoiceConversionInferenceError::NotAuthorized => "unauthorized".to_string(),
|
||||
EnqueueVoiceConversionInferenceError::ServerError => "server error".to_string(),
|
||||
EnqueueVoiceConversionInferenceError::RateLimited => "rate limited".to_string(),
|
||||
};
|
||||
|
||||
to_simple_json_error(&error_reason, self.status_code())
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Not using derive_more::Display since Clion doesn't understand it.
|
||||
impl fmt::Display for EnqueueVoiceConversionInferenceError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn enqueue_voice_conversion_inference_handler(
|
||||
http_request: HttpRequest,
|
||||
request: web::Json<EnqueueVoiceConversionInferenceRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, EnqueueVoiceConversionInferenceError>
|
||||
{
|
||||
let mut maybe_user_token : Option<UserToken> = None;
|
||||
let mut priority_level ;
|
||||
let disable_rate_limiter = false; // NB: Careful!
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool
|
||||
.acquire()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("MySql pool error: {:?}", err);
|
||||
EnqueueVoiceConversionInferenceError::ServerError
|
||||
})?;
|
||||
|
||||
// ==================== USER SESSION ==================== //
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_extended_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
EnqueueVoiceConversionInferenceError::ServerError
|
||||
})?;
|
||||
|
||||
if let Some(user_session) = maybe_user_session.as_ref() {
|
||||
maybe_user_token = Some(UserToken::new_from_str(&user_session.user_token));
|
||||
}
|
||||
|
||||
// TODO: Plan should handle "first anonymous use" and "investor" cases.
|
||||
let plan = get_correct_plan_for_session(
|
||||
server_state.server_environment,
|
||||
maybe_user_session.as_ref());
|
||||
|
||||
priority_level = plan.web_vc_base_priority_level();
|
||||
|
||||
// ==================== INVESTOR PRIORITY ==================== //
|
||||
|
||||
// TODO/TEMP: Give investors even more priority
|
||||
let mut is_investor = false;
|
||||
|
||||
{
|
||||
// TODO/TEMP: The storyteller.io website's AJAX calls will set this.
|
||||
// This is just for the YCombinator demo.
|
||||
match request.is_storyteller_demo {
|
||||
Some(true) => {
|
||||
is_investor = true;
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
|
||||
// TODO/TEMP: The storyteller.io website will redirect and establish this cookie.
|
||||
// This is just for the YCombinator demo.
|
||||
if request_has_demo_cookie(&http_request) {
|
||||
is_investor = true;
|
||||
}
|
||||
|
||||
if is_investor {
|
||||
priority_level = FAKEYOU_INVESTOR_PRIORITY_LEVEL;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== DEBUG MODE ==================== //
|
||||
|
||||
let is_debug_request = get_request_header_optional(&http_request, DEBUG_HEADER_NAME)
|
||||
.is_some();
|
||||
|
||||
// ==================== RATE LIMIT ==================== //
|
||||
|
||||
if !disable_rate_limiter {
|
||||
let mut rate_limiter = match maybe_user_session {
|
||||
None => &server_state.redis_rate_limiters.logged_out,
|
||||
Some(ref user) => {
|
||||
if user.role.is_banned {
|
||||
return Err(EnqueueVoiceConversionInferenceError::NotAuthorized);
|
||||
}
|
||||
&server_state.redis_rate_limiters.logged_in
|
||||
},
|
||||
};
|
||||
|
||||
// TODO/TEMP
|
||||
if is_investor {
|
||||
rate_limiter = &server_state.redis_rate_limiters.logged_in;
|
||||
}
|
||||
|
||||
if let Err(_err) = rate_limiter.rate_limit_request(&http_request) {
|
||||
return Err(EnqueueVoiceConversionInferenceError::RateLimited);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== CHECK AND ENQUEUE VOICE CONVERSION ==================== //
|
||||
|
||||
// TODO(bt): CHECK DATABASE!
|
||||
let model_token = request.voice_conversion_model_token.to_string();
|
||||
let _media_token = request.media_token.to_string();
|
||||
|
||||
let mut redis = server_state.redis_pool
|
||||
.get()
|
||||
.map_err(|e| {
|
||||
warn!("redis error: {:?}", e);
|
||||
EnqueueVoiceConversionInferenceError::ServerError
|
||||
})?;
|
||||
|
||||
let redis_count_key = RedisKeys::web_vc_model_usage_count(&model_token);
|
||||
|
||||
redis.incr(&redis_count_key, 1)
|
||||
.map_err(|e| {
|
||||
warn!("redis error: {:?}", e);
|
||||
EnqueueVoiceConversionInferenceError::ServerError
|
||||
})?;
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let maybe_user_preferred_visibility : Option<Visibility> = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|user_session| user_session.preferences.preferred_tts_result_visibility); // TODO: New setting for web-vc
|
||||
|
||||
let set_visibility = request.creator_set_visibility
|
||||
.or(maybe_user_preferred_visibility)
|
||||
.unwrap_or(Visibility::Public);
|
||||
|
||||
let job_token = InferenceJobToken::generate();
|
||||
|
||||
info!("Creating tts inference job record...");
|
||||
|
||||
let query_result = insert_generic_inference_job(Args {
|
||||
job_token: &job_token,
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
inference_type: GenericInferenceType::VoiceConversion, // TODO: Move from `reusable_types`
|
||||
maybe_inference_args: None, // TODO: Include both tokens as well as allowed duration.
|
||||
maybe_raw_inference_text: None,
|
||||
maybe_model_token: Some(model_token),
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: set_visibility,
|
||||
priority_level,
|
||||
is_debug_request,
|
||||
mysql_pool: &server_state.mysql_pool,
|
||||
}).await;
|
||||
|
||||
match query_result {
|
||||
Ok(_) => {},
|
||||
Err(err) => {
|
||||
warn!("New generic inference job creation DB error: {:?}", err);
|
||||
return Err(EnqueueVoiceConversionInferenceError::ServerError);
|
||||
}
|
||||
}
|
||||
|
||||
server_state.firehose_publisher.enqueue_vc_inference(
|
||||
maybe_user_token.as_ref(),
|
||||
&job_token)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("error publishing event: {:?}", e);
|
||||
EnqueueVoiceConversionInferenceError::ServerError
|
||||
})?;
|
||||
|
||||
let response = EnqueueVoiceConversionInferenceSuccessResponse {
|
||||
success: true,
|
||||
inference_job_token: job_token,
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|_e| EnqueueVoiceConversionInferenceError::ServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
pub mod enqueue_voice_conversion_inference;
|
||||
pub mod get_voice_conversion_result;
|
||||
pub mod list_user_voice_conversion_results;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
pub mod inference;
|
||||
pub mod models;
|
||||
|
|
@ -99,6 +99,7 @@ use crate::http_server::endpoints::w2l::set_w2l_template_mod_approval::set_w2l_t
|
|||
use users_component::default_routes::add_suggested_api_v1_account_creation_and_session_routes;
|
||||
use users_component::endpoints::edit_profile_handler::edit_profile_handler;
|
||||
use users_component::endpoints::get_profile_handler::get_profile_handler;
|
||||
use crate::http_server::endpoints::voice_conversion::inference::enqueue_voice_conversion_inference::enqueue_voice_conversion_inference_handler;
|
||||
|
||||
pub fn add_routes<T, B> (app: App<T, B>) -> App<T, B>
|
||||
where
|
||||
|
|
@ -114,6 +115,7 @@ pub fn add_routes<T, B> (app: App<T, B>) -> App<T, B>
|
|||
let mut app = add_moderator_routes(app); /* /moderation */
|
||||
app = add_tts_routes(app); /* /tts */
|
||||
app = add_w2l_routes(app); /* /w2l */
|
||||
app = add_web_vc_routes(app); /* /v1/voice_conversion */
|
||||
app = add_vocoder_routes(app); /* /vocoder */
|
||||
app = add_retrieval_routes(app); /* /retrieval (aka. "generic_download_jobs") */
|
||||
app = add_category_routes(app); /* /category */
|
||||
|
|
@ -469,6 +471,90 @@ fn add_w2l_routes<T, B> (app: App<T, B>) -> App<T, B>
|
|||
)
|
||||
}
|
||||
|
||||
// ==================== WEB VOICE CONVERSION ROUTES ====================
|
||||
|
||||
fn add_web_vc_routes<T, B> (app: App<T, B>) -> App<T, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(
|
||||
web::scope("/v1/voice_conversion")
|
||||
.service(
|
||||
web::resource("/inference")
|
||||
.route(web::post().to(enqueue_voice_conversion_inference_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
//.service(
|
||||
// web::resource("/upload")
|
||||
// .route(web::post().to(upload_w2l_template_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
//.service(
|
||||
// web::resource("/list")
|
||||
// .route(web::get().to(list_w2l_templates_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
//.service(
|
||||
// web::resource("/template/{token}")
|
||||
// .route(web::get().to(get_w2l_template_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
//.service(
|
||||
// web::resource("/template/{template_token}/count")
|
||||
// .route(web::get().to(get_w2l_template_use_count_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
//.service(
|
||||
// web::resource("/template/{template_token}/edit")
|
||||
// .route(web::post().to(edit_w2l_template_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
//.service(
|
||||
// web::resource("/template/{token}/moderate")
|
||||
// .route(web::post().to(set_w2l_template_mod_approval_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
//.service(
|
||||
// web::resource("/template/{token}/delete")
|
||||
// .route(web::post().to(delete_w2l_template_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
//.service(
|
||||
// web::resource("/result/{token}")
|
||||
// .route(web::get().to(get_w2l_inference_result_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
//.service(
|
||||
// web::resource("/result/{token}/edit")
|
||||
// .route(web::post().to(edit_w2l_inference_result_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
//.service(
|
||||
// web::resource("/result/{token}/delete")
|
||||
// .route(web::post().to(delete_w2l_inference_result_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
//.service(
|
||||
// web::resource("/job/{token}")
|
||||
// .route(web::get().to(get_w2l_inference_job_status_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
//.service(
|
||||
// web::resource("/upload_template_job/{token}")
|
||||
// .route(web::get().to(get_w2l_upload_template_job_status_handler))
|
||||
// .route(web::head().to(|| HttpResponse::Ok()))
|
||||
//)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// ==================== VOCODER ROUTES ====================
|
||||
|
||||
fn add_vocoder_routes<T, B> (app: App<T, B>) -> App<T, B>
|
||||
|
|
|
|||
Loading…
Reference in New Issue