mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-13 18:24:28 -05:00
Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe807084d7 | ||
|
|
2d91504eb5 | ||
|
|
67a8bb739b | ||
|
|
33613cb5d8 | ||
|
|
2487a8d646 | ||
|
|
1771e1dffe | ||
|
|
2d4d2c667f | ||
|
|
0c99852dfd | ||
|
|
7cc310764e | ||
|
|
b66b04d86c | ||
|
|
18d46a0f57 | ||
|
|
97795fac22 | ||
|
|
cec0cfc907 | ||
|
|
d516058802 | ||
|
|
b77810cd29 | ||
|
|
2507cf0332 | ||
|
|
cf4c335d41 | ||
|
|
5e98049c50 | ||
|
|
ea3a6d3c0b | ||
|
|
c6a6676f08 |
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO model_weights_extension_tts_details\nSET\n model_weights_token = ?,\n ietf_language_tag = ?,\n ietf_primary_language_subtag = ?,\n text_pipeline_type = ?,\n use_default_mel_multiply_factor = ?,\n maybe_custom_mel_multiply_factor = ?,\n maybe_default_pretrained_vocoder = ?,\n maybe_custom_vocoder_token = ?\nON DUPLICATE KEY UPDATE\n ietf_language_tag = ?,\n ietf_primary_language_subtag = ?,\n text_pipeline_type = ?,\n use_default_mel_multiply_factor = ?,\n maybe_custom_mel_multiply_factor = ?,\n maybe_default_pretrained_vocoder = ?,\n maybe_custom_vocoder_token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 15
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1ce9219b8f05fe54adaa6841acb708e165e1c7855b441b9623a2151197578929"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE tts_models\nSET\n maybe_migration_new_model_weights_token = ?\nWHERE token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "aa7ec1c1e32f773212b264f34173f99db1e515e370c47065f932aaec56f89112"
|
||||
}
|
||||
Generated
+21
@@ -1259,6 +1259,24 @@ dependencies = [
|
||||
"my-workspace-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "composite_identifiers"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"enums",
|
||||
"my-workspace-hack",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"sqlx-core",
|
||||
"sqlx-mysql",
|
||||
"strum",
|
||||
"tokens",
|
||||
"toml 0.5.11",
|
||||
"utoipa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrency"
|
||||
version = "0.0.1"
|
||||
@@ -3052,6 +3070,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"cloud_storage",
|
||||
"collections",
|
||||
"composite_identifiers",
|
||||
"concurrency",
|
||||
"config",
|
||||
"dotenv",
|
||||
@@ -3858,6 +3877,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"buckets",
|
||||
"chrono",
|
||||
"composite_identifiers",
|
||||
"config",
|
||||
"container_common",
|
||||
"dotenv",
|
||||
@@ -6023,6 +6043,7 @@ dependencies = [
|
||||
"buckets",
|
||||
"chrono",
|
||||
"cloud_storage",
|
||||
"composite_identifiers",
|
||||
"config",
|
||||
"container_common",
|
||||
"cookies",
|
||||
|
||||
@@ -5,6 +5,7 @@ members = [
|
||||
# Types: common strongly-typed tokens, ids, payloads, etc.
|
||||
# Also common cloud buckets and other "strongly typed" configs.
|
||||
"crates/_types/buckets",
|
||||
"crates/_types/composite_identifiers",
|
||||
"crates/_types/enums",
|
||||
"crates/_types/reusable_types", # TODO(bt, 2022-12-21): Deprecate
|
||||
"crates/_types/tokens",
|
||||
|
||||
@@ -2,6 +2,20 @@
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
|
||||
-- Find jobs that are broken for a given user
|
||||
select id, last_assigned_worker, internal_debugging_failure_reason,created_at
|
||||
from tts_inference_jobs
|
||||
where maybe_creator_user_token IN (
|
||||
select token
|
||||
from users
|
||||
where username IN ('knifecat ', 'rewritten_code', 'johnloberger')
|
||||
)
|
||||
and attempt_count > 1
|
||||
and status IN ('complete_failure', 'dead', 'attempt_failed')
|
||||
and created_at > ( CURDATE() - INTERVAL 5 DAY );
|
||||
|
||||
|
||||
-- Total pending jobs
|
||||
select count(*)
|
||||
from tts_inference_jobs
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
drop table idempotency_tokens;
|
||||
@@ -0,0 +1,19 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- A form of optimistic locking
|
||||
CREATE TABLE idempotency_tokens (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- This can be a UUID or any entropic value.
|
||||
idempotency_value VARCHAR(36) NOT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (idempotency_value)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
drop table batch_generations;
|
||||
@@ -0,0 +1,42 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Batch generations
|
||||
CREATE TABLE batch_generations (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- The batch token
|
||||
-- This is **NOT** a unique token.
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== ENTITY LINKAGE ==========
|
||||
|
||||
-- The type of record this is attached to
|
||||
-- Currently supported values:
|
||||
-- * 'model_weight'
|
||||
-- * 'media_file'
|
||||
-- * 'comment'
|
||||
entity_type VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The token for the record.
|
||||
entity_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
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 (token, entity_type, entity_token),
|
||||
KEY index_token (token),
|
||||
KEY index_entity_token (entity_token)
|
||||
|
||||
-- NB: Realistically, we probably don't need this index until we have a lot of entity types, and the entity_token
|
||||
-- entropy is probably sufficient enough as-is.
|
||||
-- KEY index_entity_type_and_token (entity_type, entity_token),
|
||||
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE media_files DROP COLUMN is_batch_generated;
|
||||
@@ -0,0 +1,7 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE media_files
|
||||
ADD COLUMN is_batch_generated BOOLEAN NOT NULL DEFAULT FALSE
|
||||
AFTER maybe_origin_filename;
|
||||
@@ -0,0 +1,42 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Batch generations
|
||||
CREATE TABLE batch_generations (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- The batch token
|
||||
-- This is **NOT** a unique token.
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== ENTITY LINKAGE ==========
|
||||
|
||||
-- The type of record this is attached to
|
||||
-- Currently supported values:
|
||||
-- * 'model_weight'
|
||||
-- * 'media_file'
|
||||
-- * 'comment'
|
||||
entity_type VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The token for the record.
|
||||
entity_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
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 (token, entity_type, entity_token),
|
||||
KEY index_token (token),
|
||||
KEY index_entity_token (entity_token)
|
||||
|
||||
-- NB: Realistically, we probably don't need this index until we have a lot of entity types, and the entity_token
|
||||
-- entropy is probably sufficient enough as-is.
|
||||
-- KEY index_entity_type_and_token (entity_type, entity_token),
|
||||
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,19 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- A form of optimistic locking
|
||||
CREATE TABLE idempotency_tokens (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- This can be a UUID or any entropic value.
|
||||
idempotency_value VARCHAR(36) NOT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (idempotency_value)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -58,6 +58,11 @@ CREATE TABLE media_files (
|
||||
-- The original filename of the media (if uploaded by a user)
|
||||
maybe_origin_filename VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Whether this media file was generated as part of a batch
|
||||
-- If so, we can look up the batch in a separate query/call to the `batch_generations` table.
|
||||
-- We won't hold the batch token in this table since it'll be very sparsely populated.
|
||||
is_batch_generated BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- ========== METADATA ==========
|
||||
|
||||
-- We can store text transcripts, etc. in other tables and refer back via foreign key.
|
||||
|
||||
@@ -26,12 +26,14 @@ impl ModelCategory {
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum ModelType {
|
||||
VallEx,
|
||||
StyleTTS2,
|
||||
}
|
||||
|
||||
impl ModelType {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
ModelType::VallEx => "vallex",
|
||||
ModelType::StyleTTS2 => "styletts2",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ impl PublicPath for WeightFileBucketPath {}
|
||||
|
||||
impl WeightFileBucketPath {
|
||||
|
||||
// TODO(bt,2024-01-14): This is temporary standardization. Clean this up.
|
||||
pub fn generate_for_tt2_model() -> Self {
|
||||
Self::generate_new(Some("tt2_"), Some(".pt"))
|
||||
}
|
||||
|
||||
// TODO(bt,2023-12-19): This is temporary standardization. Clean this up.
|
||||
pub fn generate_for_svc_model() -> Self {
|
||||
Self::generate_new(Some("svc_"), Some(".pt"))
|
||||
@@ -35,6 +40,11 @@ impl WeightFileBucketPath {
|
||||
Self::generate_new(Some("rvc_"), Some(".pt"))
|
||||
}
|
||||
|
||||
// TODO(bt,2024-01-15): This is temporary standardization. Clean this up.
|
||||
pub fn tt2_model_file_from_object_hash(hash: &str) -> Self {
|
||||
Self::from_object_hash(hash, Some("tt2_"), Some(".pt"))
|
||||
}
|
||||
|
||||
// TODO(bt,2023-12-19): This is temporary standardization. Clean this up.
|
||||
pub fn svc_model_file_from_object_hash(hash: &str) -> Self {
|
||||
Self::from_object_hash(hash, Some("svc_"), Some(".pt"))
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
[package]
|
||||
name = "composite_identifiers"
|
||||
edition = "2018"
|
||||
version = "0.0.1"
|
||||
authors = [
|
||||
"Brandon Thomas <bt@brand.io>",
|
||||
"Brandon Thomas <echelon@gmail.com>",
|
||||
]
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "composite_identifiers"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
# Internal
|
||||
enums = { path = "../enums" }
|
||||
tokens = { path = "../tokens" }
|
||||
|
||||
# Workspace hack (faster compile times)
|
||||
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
|
||||
|
||||
# Serialization
|
||||
serde = "1.0.125"
|
||||
serde_derive = "1.0.125"
|
||||
|
||||
sqlx = { version = "0.7.2", features = [ "mysql", "sqlite", "runtime-tokio-native-tls", "chrono" ] }
|
||||
sqlx-core = { version = "0.7.2" }
|
||||
sqlx-mysql= { version = "0.7.2" }
|
||||
|
||||
utoipa = { version = "4", features = ["chrono","actix_extras"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
# Enum utilities
|
||||
strum = { version = "0.25", features = ["derive"] }
|
||||
|
||||
# Test serialization
|
||||
serde_json = "1.0.108"
|
||||
toml = "0.5.8"
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
use enums::by_table::batch_generations::batch_generation_entity_type::BatchGenerationEntityType;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
// TODO(bt, 2024-01-16): A ref type with <'a> lifetime of inner data instead of ownership?
|
||||
pub enum BatchGenerationEntity {
|
||||
MediaFile(MediaFileToken),
|
||||
}
|
||||
|
||||
// TODO(bt, 2024-01-16): Codegen? Make traits for these? Maybe overkill.
|
||||
impl BatchGenerationEntity {
|
||||
pub fn get_entity_type(&self) -> BatchGenerationEntityType {
|
||||
match self {
|
||||
Self::MediaFile(_) => BatchGenerationEntityType::MediaFile,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_entity_token_str(&self) -> &str {
|
||||
match self {
|
||||
Self::MediaFile(token) => token.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_composite_keys(&self) -> (BatchGenerationEntityType, &str) {
|
||||
match self {
|
||||
Self::MediaFile(token) => (BatchGenerationEntityType::MediaFile, token.as_str()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod batch_generation_entity;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod batch_generations;
|
||||
@@ -0,0 +1,22 @@
|
||||
//! composite_identifiers
|
||||
//!
|
||||
//! The purpose of this library is to provide easy to construct, **typesafe**, composite identifiers.
|
||||
//! This is mostly a combination of `tokens` and `enums`.
|
||||
//!
|
||||
|
||||
// Never allow these
|
||||
#![forbid(private_bounds)]
|
||||
#![forbid(private_interfaces)]
|
||||
#![forbid(unused_must_use)] // NB: It's unsafe to not close/check some things
|
||||
|
||||
// Okay to toggle
|
||||
#![forbid(unreachable_patterns)]
|
||||
#![forbid(unused_imports)]
|
||||
#![forbid(unused_mut)]
|
||||
#![forbid(unused_variables)]
|
||||
|
||||
// Always allow
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
pub mod by_table;
|
||||
@@ -0,0 +1,109 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
#[cfg(test)]
|
||||
use strum::EnumCount;
|
||||
#[cfg(test)]
|
||||
use strum::EnumIter;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Used in the `batch_generations` table in a `VARCHAR(32)` field named `entity_type`.
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Deserialize, Serialize, Ord, PartialOrd, ToSchema)]
|
||||
pub enum BatchGenerationEntityType {
|
||||
/// Media files
|
||||
/// This will probably be the only type supported, but we'll leave the possibility of future types.
|
||||
#[serde(rename = "media_file")]
|
||||
MediaFile,
|
||||
}
|
||||
|
||||
// TODO(bt, 2023-01-17): This desperately needs MySQL integration tests!
|
||||
impl_enum_display_and_debug_using_to_str!(BatchGenerationEntityType);
|
||||
impl_mysql_enum_coders!(BatchGenerationEntityType);
|
||||
impl_mysql_from_row!(BatchGenerationEntityType);
|
||||
|
||||
/// NB: Legacy API for older code.
|
||||
impl BatchGenerationEntityType {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::MediaFile => "media_file",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(value: &str) -> Result<Self, String> {
|
||||
match value {
|
||||
"media_file" => Ok(Self::MediaFile),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_variants() -> BTreeSet<Self> {
|
||||
// NB: BTreeSet is sorted
|
||||
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
|
||||
BTreeSet::from([
|
||||
Self::MediaFile,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::by_table::batch_generations::batch_generation_entity_type::BatchGenerationEntityType;
|
||||
use crate::test_helpers::assert_serialization;
|
||||
|
||||
mod serde {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_serialization() {
|
||||
assert_serialization(BatchGenerationEntityType::MediaFile, "media_file");
|
||||
}
|
||||
}
|
||||
|
||||
mod impl_methods {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn to_str() {
|
||||
assert_eq!(BatchGenerationEntityType::MediaFile.to_str(), "media_file");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
assert_eq!(BatchGenerationEntityType::from_str("media_file").unwrap(), BatchGenerationEntityType::MediaFile);
|
||||
assert!(BatchGenerationEntityType::from_str("foo").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
mod manual_variant_checks {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = BatchGenerationEntityType::all_variants();
|
||||
assert_eq!(variants.len(), 1);
|
||||
assert_eq!(variants.pop_first(), Some(BatchGenerationEntityType::MediaFile));
|
||||
assert_eq!(variants.pop_first(), None);
|
||||
}
|
||||
}
|
||||
|
||||
mod mechanical_checks {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn variant_length() {
|
||||
use strum::IntoEnumIterator;
|
||||
assert_eq!(BatchGenerationEntityType::all_variants().len(), BatchGenerationEntityType::iter().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
for variant in BatchGenerationEntityType::all_variants() {
|
||||
assert_eq!(variant, BatchGenerationEntityType::from_str(variant.to_str()).unwrap());
|
||||
assert_eq!(variant, BatchGenerationEntityType::from_str(&format!("{}", variant)).unwrap());
|
||||
assert_eq!(variant, BatchGenerationEntityType::from_str(&format!("{:?}", variant)).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod batch_generation_entity_type;
|
||||
@@ -35,6 +35,8 @@ pub enum InferenceModelType {
|
||||
RerenderAVideo,
|
||||
#[serde(rename = "mocap_net")]
|
||||
MocapNet,
|
||||
#[serde(rename = "styletts2")]
|
||||
StyleTTS2,
|
||||
}
|
||||
|
||||
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
|
||||
@@ -53,6 +55,7 @@ impl InferenceModelType {
|
||||
Self::VallEX => "vall_e_x",
|
||||
Self::RerenderAVideo => "rerender_a_video",
|
||||
Self::MocapNet => "mocap_net",
|
||||
Self::StyleTTS2 => "styletts2",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +69,7 @@ impl InferenceModelType {
|
||||
"vall_e_x" => Ok(Self::VallEX),
|
||||
"rerender_a_video" => Ok(Self::RerenderAVideo),
|
||||
"mocap_net" => Ok(Self::MocapNet),
|
||||
"styletts2" => Ok(Self::StyleTTS2),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
@@ -82,6 +86,7 @@ impl InferenceModelType {
|
||||
InferenceModelType::VallEX,
|
||||
InferenceModelType::RerenderAVideo,
|
||||
InferenceModelType::MocapNet,
|
||||
InferenceModelType::StyleTTS2,
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -101,6 +106,7 @@ mod tests {
|
||||
assert_serialization(InferenceModelType::VallEX, "vall_e_x");
|
||||
assert_serialization(InferenceModelType::RerenderAVideo, "rerender_a_video");
|
||||
assert_serialization(InferenceModelType::MocapNet, "mocap_net");
|
||||
assert_serialization(InferenceModelType::StyleTTS2, "styletts2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -113,6 +119,7 @@ mod tests {
|
||||
assert_eq!(InferenceModelType::VallEX.to_str(), "vall_e_x");
|
||||
assert_eq!(InferenceModelType::RerenderAVideo.to_str(), "rerender_a_video");
|
||||
assert_eq!(InferenceModelType::MocapNet.to_str(), "mocap_net");
|
||||
assert_eq!(InferenceModelType::StyleTTS2.to_str(), "styletts2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -125,13 +132,14 @@ mod tests {
|
||||
assert_eq!(InferenceModelType::from_str("vall_e_x").unwrap(), InferenceModelType::VallEX);
|
||||
assert_eq!(InferenceModelType::from_str("rerender_a_video").unwrap(), InferenceModelType::RerenderAVideo);
|
||||
assert_eq!(InferenceModelType::from_str("mocap_net").unwrap(), InferenceModelType::MocapNet);
|
||||
assert_eq!(InferenceModelType::from_str("styletts2").unwrap(), InferenceModelType::StyleTTS2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
// Static check
|
||||
let mut variants = InferenceModelType::all_variants();
|
||||
assert_eq!(variants.len(), 8);
|
||||
assert_eq!(variants.len(), 9);
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::RvcV2));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::SadTalker));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::SoVitsSvc));
|
||||
@@ -140,6 +148,7 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::VallEX));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::RerenderAVideo));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::MocapNet));
|
||||
assert_eq!(variants.pop_first(), Some(InferenceModelType::StyleTTS2));
|
||||
assert_eq!(variants.pop_first(), None);
|
||||
|
||||
// Generated check
|
||||
|
||||
@@ -38,6 +38,9 @@ pub enum MediaFileOriginModelType {
|
||||
|
||||
#[serde(rename = "mocap_net")]
|
||||
MocapNet,
|
||||
|
||||
#[serde(rename = "styletts2")]
|
||||
StyleTTS2,
|
||||
}
|
||||
|
||||
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
|
||||
@@ -56,6 +59,7 @@ impl MediaFileOriginModelType {
|
||||
Self::VallEX => "vall_e_x",
|
||||
Self::Rerender => "rerender",
|
||||
Self::MocapNet => "mocap_net",
|
||||
Self::StyleTTS2 => "styletts2",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +72,7 @@ impl MediaFileOriginModelType {
|
||||
"vall_e_x" => Ok(Self::VallEX),
|
||||
"rerender" => Ok(Self::Rerender),
|
||||
"mocap_net" => Ok(Self::MocapNet),
|
||||
"styletts2" => Ok(Self::StyleTTS2),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
@@ -83,6 +88,7 @@ impl MediaFileOriginModelType {
|
||||
Self::VallEX,
|
||||
Self::Rerender,
|
||||
Self::MocapNet,
|
||||
Self::StyleTTS2,
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -109,6 +115,7 @@ mod tests {
|
||||
assert_eq!(MediaFileOriginModelType::VallEX.to_str(), "vall_e_x");
|
||||
assert_eq!(MediaFileOriginModelType::Rerender.to_str(), "rerender");
|
||||
assert_eq!(MediaFileOriginModelType::MocapNet.to_str(), "mocap_net");
|
||||
assert_eq!(MediaFileOriginModelType::StyleTTS2.to_str(), "styletts2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -120,13 +127,14 @@ mod tests {
|
||||
assert_eq!(MediaFileOriginModelType::from_str("vall_e_x").unwrap(), MediaFileOriginModelType::VallEX);
|
||||
assert_eq!(MediaFileOriginModelType::from_str("rerender").unwrap(), MediaFileOriginModelType::Rerender);
|
||||
assert_eq!(MediaFileOriginModelType::from_str("mocap_net").unwrap(), MediaFileOriginModelType::MocapNet);
|
||||
assert_eq!(MediaFileOriginModelType::from_str("styletts2").unwrap(), MediaFileOriginModelType::StyleTTS2);
|
||||
assert!(MediaFileOriginModelType::from_str("foo").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = MediaFileOriginModelType::all_variants();
|
||||
assert_eq!(variants.len(), 7);
|
||||
assert_eq!(variants.len(), 8);
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::RvcV2));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::SadTalker));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::SoVitsSvc));
|
||||
@@ -134,6 +142,7 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::VallEX));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::Rerender));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::MocapNet));
|
||||
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::StyleTTS2));
|
||||
assert_eq!(variants.pop_first(), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// ===== MySql =====
|
||||
pub mod audit_logs;
|
||||
pub mod batch_generations;
|
||||
pub mod comments;
|
||||
pub mod email_sender_jobs;
|
||||
pub mod entity_stats;
|
||||
@@ -17,6 +18,7 @@ pub mod user_ratings;
|
||||
pub mod voice_conversion_models;
|
||||
pub mod voice_conversion_results;
|
||||
pub mod zs_voices;
|
||||
|
||||
// ===== Sqlite =====
|
||||
pub mod tts_render_tasks;
|
||||
pub mod web_rendition_targets;
|
||||
|
||||
@@ -16,6 +16,8 @@ pub enum ZsVoiceModelType {
|
||||
/// TTS-type zero shot models
|
||||
#[serde(rename = "vall-e-x")]
|
||||
VallEX,
|
||||
#[serde(rename = "styletts2")]
|
||||
StyleTTS2,
|
||||
}
|
||||
|
||||
// TODO(bt, 2023-01-17): This desperately needs MySQL integration tests!
|
||||
@@ -27,12 +29,14 @@ impl ZsVoiceModelType {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::VallEX=> "vall-e-x",
|
||||
Self::StyleTTS2 => "styletts2",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(value: &str) -> Result<Self, String> {
|
||||
match value {
|
||||
"vall-e-x" => Ok(Self::VallEX),
|
||||
"styletts2" => Ok(Self::StyleTTS2),
|
||||
_ => Err(format!("invalid value: {:?}", value)),
|
||||
}
|
||||
}
|
||||
@@ -42,6 +46,7 @@ impl ZsVoiceModelType {
|
||||
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
|
||||
BTreeSet::from([
|
||||
Self::VallEX,
|
||||
Self::StyleTTS2,
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -66,11 +71,13 @@ mod tests {
|
||||
#[test]
|
||||
fn to_str() {
|
||||
assert_eq!(ZsVoiceModelType::VallEX.to_str(), "vall-e-x");
|
||||
assert_eq!(ZsVoiceModelType::StyleTTS2.to_str(), "styletts2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_str() {
|
||||
assert_eq!(ZsVoiceModelType::from_str("vall-e-x").unwrap(), ZsVoiceModelType::VallEX);
|
||||
assert_eq!(ZsVoiceModelType::from_str("styletts2").unwrap(), ZsVoiceModelType::StyleTTS2);
|
||||
assert!(ZsVoiceModelType::from_str("foo").is_err());
|
||||
}
|
||||
}
|
||||
@@ -81,8 +88,9 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = ZsVoiceModelType::all_variants();
|
||||
assert_eq!(variants.len(), 1);
|
||||
assert_eq!(variants.len(), 2);
|
||||
assert_eq!(variants.pop_first(), Some(ZsVoiceModelType::VallEX));
|
||||
assert_eq!(variants.pop_first(), Some(ZsVoiceModelType::StyleTTS2));
|
||||
assert_eq!(variants.pop_first(), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ use strum::EnumIter;
|
||||
pub(crate) enum TokenPrefix {
|
||||
AnonymousVisitorTracking, // AVTs are not stored as primary keys in any table, but an index in many tables.
|
||||
AuditLog,
|
||||
BatchGeneration,
|
||||
Comment,
|
||||
DownloadJob,
|
||||
EmailSenderJob,
|
||||
@@ -90,6 +91,7 @@ impl PrefixGenerator for TokenPrefix {
|
||||
match self {
|
||||
Self::AnonymousVisitorTracking => "avt_",
|
||||
Self::AuditLog => "audit_",
|
||||
Self::BatchGeneration => "batch_g_",
|
||||
Self::Comment => "comment_",
|
||||
Self::DownloadJob => "jdown_", // NB: Previously "JGUP:"
|
||||
Self::EmailSenderJob => "email_job_",
|
||||
@@ -297,7 +299,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_all_prefixes_end_with_separator_length_one() {
|
||||
for prefix in get_all_prefixes().iter().map(|s| *s) {
|
||||
if prefix == "news_story_" || prefix == "tts_task_" || prefix == "INT_API:" || prefix == "pw_reset_" || prefix == "email_job_" {
|
||||
if prefix == "news_story_"
|
||||
|| prefix == "tts_task_"
|
||||
|| prefix == "INT_API:"
|
||||
|| prefix == "pw_reset_"
|
||||
|| prefix == "email_job_"
|
||||
|| prefix == "batch_g_"
|
||||
{
|
||||
// TODO/FIXME: I'm too tired at 5AM to replacen from the left. Make this test valid.
|
||||
// These tokens are from the AIChatBot sidecar, so asserting their validity is less important.
|
||||
continue;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::prefixes::TokenPrefix;
|
||||
|
||||
/// The primary key for Audit Logs.
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct BatchGenerationToken(pub String);
|
||||
|
||||
impl_string_token!(BatchGenerationToken);
|
||||
impl_crockford_generator!(BatchGenerationToken, 32usize, TokenPrefix::BatchGeneration, CrockfordLower);
|
||||
@@ -2,6 +2,7 @@ pub mod anonymous_visitor_tracking;
|
||||
pub mod api_tokens_external;
|
||||
pub mod api_tokens_internal;
|
||||
pub mod audit_logs;
|
||||
pub mod batch_generations;
|
||||
pub mod comments;
|
||||
pub mod email_sender_job_token;
|
||||
pub mod firehose_entries;
|
||||
@@ -20,6 +21,7 @@ pub mod tts_results;
|
||||
pub mod twitch_event_rule;
|
||||
pub mod twitch_oauth_tokens_grouping;
|
||||
pub mod twitch_oauth_tokens_internal;
|
||||
pub mod user_bookmarks;
|
||||
pub mod user_sessions;
|
||||
pub mod user_subscriptions;
|
||||
pub mod users;
|
||||
@@ -34,4 +36,3 @@ pub mod w2l_templates;
|
||||
pub mod zs_voice_dataset_samples;
|
||||
pub mod zs_voice_datasets;
|
||||
pub mod zs_voices;
|
||||
pub mod user_bookmarks;
|
||||
|
||||
@@ -8,12 +8,11 @@ use errors::AnyhowResult;
|
||||
use crate::bucket_clients::get_bucket_clients;
|
||||
use crate::cli_args::parse_cli_args;
|
||||
use crate::seeding::model_weights::seed_weights; // V1
|
||||
use crate::seeding::seed_model_weights::seed_weights_files; // V2 v3 will come later on when everything is more conslidated and clear.
|
||||
use crate::seeding::seed_media_files::seed_media_files;
|
||||
use crate::seeding::seed_model_weights::seed_weights_files; // V2 v3 will come later on when everything is more conslidated and clear.
|
||||
use crate::seeding::tts_tacotron2::seed_tts_tacotron2;
|
||||
use crate::seeding::users::seed_user_accounts;
|
||||
use crate::seeding::voice_conversion::seed_voice_conversion;
|
||||
use crate::seeding::zero_shot_tts::seed_zero_shot_tts;
|
||||
|
||||
pub mod bucket_clients;
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::Parser;
|
||||
use strum::{EnumCount, EnumString, IntoEnumIterator};
|
||||
use strum::EnumIter;
|
||||
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
|
||||
pub struct ParsedArgs {
|
||||
pub action: Action,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, EnumIter, EnumCount, EnumString, strum::Display)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Action {
|
||||
/// Migrate VC
|
||||
MigrateVoiceConversion,
|
||||
/// Migrate TTS
|
||||
MigrateTts,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name="migration-tool")]
|
||||
pub struct Args {
|
||||
#[arg(name="action", long="action", help="action to take", required=true)]
|
||||
action: String,
|
||||
}
|
||||
|
||||
pub fn parse_cli_args() -> AnyhowResult<ParsedArgs> {
|
||||
let args = Args::parse();
|
||||
|
||||
Ok(ParsedArgs {
|
||||
action: action_from_str(&args.action)?,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
fn action_from_str(value: &str) -> AnyhowResult<Action> {
|
||||
let action = Action::from_str(value)
|
||||
.map_err(|err| {
|
||||
let choices = Action::iter()
|
||||
.map(|e| e.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
anyhow!("parse error: {:?}, provided: \"{}\" choices: {:?}", err, value, choices)
|
||||
})?;
|
||||
Ok(action)
|
||||
}
|
||||
@@ -5,17 +5,21 @@
|
||||
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use log::info;
|
||||
use sqlx::{MySql, Pool};
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
use cloud_storage::bucket_client::BucketClient;
|
||||
|
||||
use cloud_storage::bucket_client::BucketClient;
|
||||
use config::shared_constants::DEFAULT_RUST_LOG;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
use crate::cli_args::{Action, parse_cli_args};
|
||||
use crate::deps::Deps;
|
||||
use crate::migrations::tts_models_to_weights::migrate::migrate_tts_to_weights;
|
||||
use crate::migrations::voice_conversion_to_weights::migrate::migrate_voice_conversion_to_weights;
|
||||
|
||||
pub mod cli_args;
|
||||
pub mod deps;
|
||||
pub mod migrations;
|
||||
|
||||
@@ -40,10 +44,20 @@ pub async fn main() -> AnyhowResult<()> {
|
||||
bucket_production_private: get_bucket_client("PRODUCTION_PRIVATE")?,
|
||||
};
|
||||
|
||||
migrate_voice_conversion_to_weights(&deps).await?;
|
||||
let args = parse_cli_args()?;
|
||||
|
||||
match args.action {
|
||||
Action::MigrateVoiceConversion => {
|
||||
migrate_voice_conversion_to_weights(&deps).await?;
|
||||
}
|
||||
Action::MigrateTts => {
|
||||
migrate_tts_to_weights(&deps).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_mysql(env_var_name: &str) -> AnyhowResult<Pool<MySql>> {
|
||||
info!("Connecting to MySQL {env_var_name}...");
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod tts_models_to_weights;
|
||||
pub mod voice_conversion_to_weights;
|
||||
@@ -0,0 +1,47 @@
|
||||
use tempdir::TempDir;
|
||||
|
||||
use buckets::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use cloud_storage::bucket_path_unifier::BucketPathUnifier;
|
||||
use errors::AnyhowResult;
|
||||
use filesys::safe_delete_temp_directory::safe_delete_temp_directory;
|
||||
use filesys::safe_delete_temp_file::safe_delete_temp_file;
|
||||
use hashing::sha256::sha256_hash_file::sha256_hash_file;
|
||||
use mysql_queries::queries::model_weights::migration::upsert_model_weight_from_tts_model::CopiedTtsFileData;
|
||||
use mysql_queries::queries::tts::tts_models::migration::list_whole_tts_models_using_cursor::WholeTtsModelRecord;
|
||||
|
||||
use crate::deps::Deps;
|
||||
|
||||
pub async fn copy_cloud_files(model: &WholeTtsModelRecord, deps: &Deps) -> AnyhowResult<CopiedTtsFileData> {
|
||||
let copied_file_data = copy_model(model, deps).await?;
|
||||
Ok(copied_file_data)
|
||||
}
|
||||
|
||||
async fn copy_model(model: &WholeTtsModelRecord, deps: &Deps) -> AnyhowResult<CopiedTtsFileData> {
|
||||
let bucket_path_unifier = BucketPathUnifier::default_paths();
|
||||
|
||||
let old_model_bucket_path = bucket_path_unifier.tts_synthesizer_path(&model.private_bucket_hash);
|
||||
|
||||
// TODO(bt,2023-12-19): Probably faster to stream between buckets, but whatever.
|
||||
let temp_dir = TempDir::new("model_transfer")?;
|
||||
let model_temp_fs_path = temp_dir.path().join("model.bin");
|
||||
|
||||
deps.bucket_production_private.download_file_to_disk(&old_model_bucket_path, &model_temp_fs_path).await?;
|
||||
|
||||
let file_checksum = sha256_hash_file(&model_temp_fs_path)?;
|
||||
|
||||
let new_model_bucket_path = WeightFileBucketPath::generate_for_tt2_model();
|
||||
|
||||
deps.bucket_production_public.upload_filename_with_content_type(
|
||||
&new_model_bucket_path.get_full_object_path_str(),
|
||||
&model_temp_fs_path,
|
||||
"application/octet-stream").await?;
|
||||
|
||||
safe_delete_temp_file(&model_temp_fs_path);
|
||||
safe_delete_temp_directory(&temp_dir);
|
||||
|
||||
Ok(CopiedTtsFileData {
|
||||
bucket_path: new_model_bucket_path,
|
||||
file_sha_hash: file_checksum,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use log::{info, warn};
|
||||
|
||||
use buckets::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use enums::by_table::model_weights::weights_types::WeightsType;
|
||||
use errors::AnyhowResult;
|
||||
use mysql_queries::queries::model_weights::get_weight::get_weight_by_token;
|
||||
use mysql_queries::queries::model_weights::migration::upsert_model_weight_from_tts_model::{CopiedTtsFileData, upsert_model_weight_from_tts_model};
|
||||
use mysql_queries::queries::tts::tts_models::migration::list_whole_tts_models_using_cursor::{list_whole_tts_models_using_cursor, WholeTtsModelRecord};
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
|
||||
use crate::deps::Deps;
|
||||
use crate::migrations::tts_models_to_weights::copy_cloud_files::copy_cloud_files;
|
||||
|
||||
const PAGE_SIZE: u64 = 10;
|
||||
|
||||
// TODO(bt,2024-01-15): This hasn't been fully vetted / tested. Do not run until it's combed over.
|
||||
|
||||
pub async fn migrate_tts_to_weights(deps: &Deps) -> AnyhowResult<()> {
|
||||
|
||||
let mut cursor = 0;
|
||||
|
||||
loop {
|
||||
info!("Querying {PAGE_SIZE} models at cursor = {cursor}");
|
||||
|
||||
let results
|
||||
= list_whole_tts_models_using_cursor(&deps.mysql_production, PAGE_SIZE, cursor).await?;
|
||||
|
||||
if results.is_empty() {
|
||||
warn!("No more results found; exiting.");
|
||||
break;
|
||||
}
|
||||
|
||||
for result in results.iter() {
|
||||
println!("\n\nmigrating over result {:?} : {:?} ...", result.token, result);
|
||||
copy_model_record_and_bucket_files(result, &deps).await?;
|
||||
}
|
||||
|
||||
if let Some(last_id) = results.last().map(|result| result.id) {
|
||||
cursor = last_id as u64;
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn copy_model_record_and_bucket_files(record: &WholeTtsModelRecord, deps: &Deps) -> AnyhowResult<()> {
|
||||
let mut maybe_copied_data = None;
|
||||
|
||||
match &record.maybe_migration_new_model_weights_token {
|
||||
None => {
|
||||
info!("Copying bucket files for record {:?} ...", record.token);
|
||||
let result = copy_cloud_files(record, &deps).await?;
|
||||
maybe_copied_data = Some(result);
|
||||
}
|
||||
Some(token) => {
|
||||
info!("Existing migrated record; skipping cloud bucket copy...");
|
||||
maybe_copied_data = lookup_bucket_details_from_record(token, &deps).await?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(copied_data) = &maybe_copied_data {
|
||||
info!("Upserting records...");
|
||||
upsert_model_weight_from_tts_model(record, &deps.mysql_production, copied_data).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Look up the bucket storage details from a previously-migrated record
|
||||
async fn lookup_bucket_details_from_record(model_weight_token: &ModelWeightToken, deps: &Deps) -> AnyhowResult<Option<CopiedTtsFileData>> {
|
||||
let maybe_model_weight_record =
|
||||
get_weight_by_token(model_weight_token, true, &deps.mysql_production).await?;
|
||||
|
||||
let model_weight_record = match maybe_model_weight_record {
|
||||
None => return Ok(None),
|
||||
Some(record) => record,
|
||||
};
|
||||
|
||||
let bucket_path = match model_weight_record.weights_type {
|
||||
WeightsType::Tacotron2 => WeightFileBucketPath::tt2_model_file_from_object_hash(&model_weight_record.public_bucket_hash),
|
||||
_ => return Ok(None), // NB: SVC, RVC, Stable Diffusion, etc. aren't valid for TTS migration
|
||||
};
|
||||
|
||||
Ok(Some(CopiedTtsFileData {
|
||||
bucket_path,
|
||||
file_sha_hash: model_weight_record.file_checksum_sha2,
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod copy_cloud_files;
|
||||
pub mod migrate;
|
||||
@@ -16,6 +16,7 @@ path = "src/lib.rs"
|
||||
|
||||
# Internal
|
||||
buckets = { path = "../../_types/buckets" }
|
||||
composite_identifiers = { path = "../../_types/composite_identifiers" }
|
||||
config = { path = "../deprecated/config" }
|
||||
container_common = { path = "../deprecated/container_common" }
|
||||
enums = { path = "../../_types/enums" }
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
use anyhow::anyhow;
|
||||
use log::error;
|
||||
use sqlx::{Executor, MySql, QueryBuilder, Transaction};
|
||||
|
||||
use composite_identifiers::by_table::batch_generations::batch_generation_entity::BatchGenerationEntity;
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
|
||||
pub struct BatchEntry {
|
||||
pub batch_token: BatchGenerationToken,
|
||||
pub entity: BatchGenerationEntity,
|
||||
}
|
||||
|
||||
pub struct InsertBatchArgs<'a, 'b> {
|
||||
pub entries: Vec<BatchGenerationEntity>,
|
||||
pub transaction: &'a mut Transaction<'b, MySql>,
|
||||
}
|
||||
|
||||
/// Insert a list of entities into a "batch" together for grouping; returns the new batch token identifier to return
|
||||
/// to the HTTP caller.
|
||||
///
|
||||
/// NB: Calling code is responsible for rolling back the transaction if this fails.
|
||||
pub async fn insert_batch_generation_records<'a, 'b>(args: InsertBatchArgs<'a, 'b>) -> AnyhowResult<BatchGenerationToken> {
|
||||
let batch_token = BatchGenerationToken::generate();
|
||||
|
||||
let mut query_builder = QueryBuilder::new(r#"
|
||||
INSERT INTO batch_generations (token, entity_type, entity_token) VALUES
|
||||
"#);
|
||||
|
||||
for (i, entry) in args.entries.iter().enumerate() {
|
||||
let (entity_type, entity_token) = entry.get_composite_keys();
|
||||
|
||||
query_builder.push("(");
|
||||
query_builder.push_bind(&batch_token);
|
||||
query_builder.push(",");
|
||||
|
||||
query_builder.push_bind(entity_type);
|
||||
query_builder.push(",");
|
||||
|
||||
query_builder.push_bind(entity_token);
|
||||
query_builder.push(")");
|
||||
|
||||
if i < args.entries.len() - 1 {
|
||||
query_builder.push(",");
|
||||
}
|
||||
}
|
||||
|
||||
let query = query_builder.build();
|
||||
|
||||
let query_result = query.execute(&mut **args.transaction).await;
|
||||
|
||||
match query_result {
|
||||
Ok(_) => Ok(batch_token),
|
||||
Err(err) => {
|
||||
error!("Error with batch generation query: {:?}", &err);
|
||||
Err(anyhow!("model category creation error: {:?}", &err))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
use composite_identifiers::by_table::batch_generations::batch_generation_entity::BatchGenerationEntity;
|
||||
use config::shared_constants::DEFAULT_MYSQL_CONNECTION_STRING;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::queries::batch_generations::insert_batch_generation_records::{insert_batch_generation_records, InsertBatchArgs};
|
||||
|
||||
async fn setup() -> sqlx::Pool<sqlx::MySql> {
|
||||
MySqlPoolOptions::new()
|
||||
.max_connections(3)
|
||||
.connect(&DEFAULT_MYSQL_CONNECTION_STRING).await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn test() {
|
||||
let pool = setup().await;
|
||||
|
||||
let mut transaction = pool.begin().await.unwrap();
|
||||
|
||||
let entries = vec![
|
||||
BatchGenerationEntity::MediaFile(MediaFileToken::new_from_str("media_foo")),
|
||||
BatchGenerationEntity::MediaFile(MediaFileToken::new_from_str("media_bar")),
|
||||
BatchGenerationEntity::MediaFile(MediaFileToken::new_from_str("media_baz")),
|
||||
];
|
||||
|
||||
let r = insert_batch_generation_records(InsertBatchArgs {
|
||||
entries,
|
||||
transaction: &mut transaction,
|
||||
}).await;
|
||||
|
||||
transaction.commit().await.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod insert_batch_generation_records;
|
||||
+3
-3
@@ -18,6 +18,7 @@ pub struct InsertArgs<'a> {
|
||||
pub pool: &'a MySqlPool,
|
||||
pub job: &'a AvailableInferenceJob,
|
||||
|
||||
pub origin_model_type: MediaFileOriginModelType,
|
||||
pub maybe_mime_type: Option<&'a str>,
|
||||
pub file_size_bytes: u64,
|
||||
pub sha256_checksum: &'a str,
|
||||
@@ -69,7 +70,6 @@ pub async fn insert_media_file_from_zero_shot(
|
||||
// Since fill this out apart of the funtion this seems like the only thing to change.
|
||||
const ORIGIN_CATEGORY : MediaFileOriginCategory = MediaFileOriginCategory::Inference;
|
||||
const ORIGIN_PRODUCT_CATEGORY : MediaFileOriginProductCategory = MediaFileOriginProductCategory::ZeroShotVoice;
|
||||
const ORIGIN_MODEL_TYPE : MediaFileOriginModelType = MediaFileOriginModelType::VallEX;
|
||||
const MEDIA_TYPE : MediaFileType = MediaFileType::Audio;
|
||||
|
||||
let record_id = {
|
||||
@@ -111,8 +111,8 @@ pub async fn insert_media_file_from_zero_shot(
|
||||
|
||||
ORIGIN_CATEGORY.to_str(),
|
||||
ORIGIN_PRODUCT_CATEGORY.to_str(),
|
||||
ORIGIN_MODEL_TYPE.to_str(),
|
||||
|
||||
args.origin_model_type.to_str(),
|
||||
|
||||
MEDIA_TYPE.to_str(),
|
||||
args.maybe_mime_type,
|
||||
args.file_size_bytes,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod api_tokens;
|
||||
pub mod audit_logs;
|
||||
pub mod batch_generations;
|
||||
pub mod billing;
|
||||
pub mod comments;
|
||||
pub mod email_sender_jobs;
|
||||
@@ -18,12 +19,9 @@ pub mod stats;
|
||||
pub mod trending_model_analytics;
|
||||
pub mod tts;
|
||||
pub mod twitch;
|
||||
pub mod user_bookmarks;
|
||||
pub mod users;
|
||||
pub mod vocoder;
|
||||
pub mod voice_clone_requests;
|
||||
pub mod voice_conversion;
|
||||
pub mod voice_designer;
|
||||
pub mod w2l;
|
||||
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod upsert_model_weight_from_tts_model;
|
||||
pub mod upsert_model_weight_from_voice_conversion_model;
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
use sqlx::{MySql, MySqlPool, Transaction};
|
||||
|
||||
use buckets::public::weight_files::bucket_file_path::WeightFileBucketPath;
|
||||
use enums::by_table::model_weights::weights_category::WeightsCategory;
|
||||
use enums::by_table::model_weights::weights_types::WeightsType;
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
|
||||
use crate::queries::tts::tts_models::migration::list_whole_tts_models_using_cursor::WholeTtsModelRecord;
|
||||
|
||||
pub struct CopiedTtsFileData {
|
||||
pub bucket_path: WeightFileBucketPath,
|
||||
pub file_sha_hash: String,
|
||||
}
|
||||
|
||||
/// Migrate `tts_models` records to `model_weights` + `model_weights_extension_tts_details` records.
|
||||
/// This is designed to be idempotent and re-runnable. Any time we re-run the query, we should get the same result.
|
||||
/// This will enable us to perfect the query and get the write flows online and switched over.
|
||||
///
|
||||
pub async fn upsert_model_weight_from_tts_model(
|
||||
record: &WholeTtsModelRecord,
|
||||
mysql_pool: &MySqlPool,
|
||||
copied_data: &CopiedTtsFileData,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
let mut transaction = mysql_pool.begin().await?;
|
||||
|
||||
let model_weight_token = create_or_generate_token(record);
|
||||
|
||||
upsert_model_weights_record(record, &model_weight_token, copied_data, &mut transaction).await?;
|
||||
upsert_model_weights_extension_record(record, &model_weight_token, &mut transaction).await?;
|
||||
|
||||
update_original_record(record, &model_weight_token, &mut transaction).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_or_generate_token(record: &WholeTtsModelRecord) -> ModelWeightToken {
|
||||
match record.maybe_migration_new_model_weights_token {
|
||||
Some(ref token) => token.clone(),
|
||||
None => ModelWeightToken::generate(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn upsert_model_weights_record(
|
||||
record: &WholeTtsModelRecord,
|
||||
model_weight_token: &ModelWeightToken,
|
||||
copied_data: &CopiedTtsFileData,
|
||||
mut transaction: &mut Transaction<'_, MySql>,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
// NB: We never supported other TTS models in the tts_models table.
|
||||
const WEIGHTS_TYPE : WeightsType = WeightsType::Tacotron2;
|
||||
const WEIGHTS_CATEGORY : WeightsCategory = WeightsCategory::TextToSpeech;
|
||||
|
||||
// NB: Not setting a few fields (for now)
|
||||
// maybe_last_update_user_token - seems like bad design
|
||||
// TODO(bt): file checksum
|
||||
// TODO(bt): rename maybe_public_bucket_extension to maybe_public_bucket_suffix (!!!)
|
||||
// TODO(bt): do we need model_weights.ip_address_last_update without audit logs?
|
||||
// TODO(bt): rename creator_ip_address to ip_address_creation (and add ip_address_last_update)
|
||||
// TODO(bt): Check model_weights column integer types - signed vs unsigned
|
||||
let query = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO model_weights
|
||||
SET
|
||||
token = ?,
|
||||
weights_type = ?,
|
||||
weights_category = ?,
|
||||
title = ?,
|
||||
maybe_cover_image_media_file_token = NULL,
|
||||
maybe_description_markdown = ?,
|
||||
maybe_description_rendered_html = ?,
|
||||
creator_user_token = ?,
|
||||
creator_ip_address = ?,
|
||||
|
||||
creator_set_visibility = ?,
|
||||
maybe_last_update_user_token = NULL,
|
||||
original_download_url = ?,
|
||||
original_filename = ?,
|
||||
file_size_bytes = ?,
|
||||
file_checksum_sha2 = ?,
|
||||
|
||||
public_bucket_hash = ?,
|
||||
maybe_public_bucket_prefix = ?,
|
||||
maybe_public_bucket_extension = ?,
|
||||
|
||||
maybe_migration_old_model_token = ?,
|
||||
version = ?,
|
||||
created_at = ?,
|
||||
updated_at = ?,
|
||||
user_deleted_at = ?,
|
||||
mod_deleted_at = ?
|
||||
|
||||
ON DUPLICATE KEY UPDATE
|
||||
weights_type = ?,
|
||||
weights_category = ?,
|
||||
title = ?,
|
||||
maybe_cover_image_media_file_token = NULL,
|
||||
maybe_description_markdown = ?,
|
||||
maybe_description_rendered_html = ?,
|
||||
creator_user_token = ?,
|
||||
creator_ip_address = ?,
|
||||
creator_set_visibility = ?,
|
||||
maybe_last_update_user_token = NULL,
|
||||
original_download_url = ?,
|
||||
original_filename = ?,
|
||||
file_size_bytes = ?,
|
||||
file_checksum_sha2 = ?,
|
||||
public_bucket_hash = ?,
|
||||
maybe_public_bucket_prefix = ?,
|
||||
maybe_public_bucket_extension = ?,
|
||||
maybe_migration_old_model_token = ?,
|
||||
version = ?,
|
||||
created_at = ?,
|
||||
updated_at = ?,
|
||||
user_deleted_at = ?,
|
||||
mod_deleted_at = ?
|
||||
"#,
|
||||
// Insert
|
||||
model_weight_token,
|
||||
WEIGHTS_TYPE,
|
||||
WEIGHTS_CATEGORY,
|
||||
record.title,
|
||||
record.description_markdown,
|
||||
record.description_rendered_html,
|
||||
record.creator_user_token,
|
||||
record.creator_ip_address_creation,
|
||||
record.creator_set_visibility.to_str(),
|
||||
record.original_download_url,
|
||||
record.original_filename,
|
||||
record.file_size_bytes,
|
||||
|
||||
copied_data.file_sha_hash,
|
||||
copied_data.bucket_path.get_object_hash(),
|
||||
copied_data.bucket_path.get_optional_prefix(),
|
||||
copied_data.bucket_path.get_optional_extension(),
|
||||
|
||||
record.token.as_str(),
|
||||
record.version,
|
||||
record.created_at,
|
||||
record.updated_at,
|
||||
record.user_deleted_at,
|
||||
record.mod_deleted_at,
|
||||
|
||||
// Update
|
||||
WEIGHTS_TYPE,
|
||||
WEIGHTS_CATEGORY,
|
||||
record.title,
|
||||
record.description_markdown,
|
||||
record.description_rendered_html,
|
||||
record.creator_user_token,
|
||||
record.creator_ip_address_creation,
|
||||
record.creator_set_visibility.to_str(),
|
||||
record.original_download_url,
|
||||
record.original_filename,
|
||||
record.file_size_bytes,
|
||||
|
||||
copied_data.file_sha_hash,
|
||||
copied_data.bucket_path.get_object_hash(),
|
||||
copied_data.bucket_path.get_optional_prefix(),
|
||||
copied_data.bucket_path.get_optional_extension(),
|
||||
|
||||
record.token.as_str(),
|
||||
record.version,
|
||||
record.created_at,
|
||||
record.updated_at,
|
||||
record.user_deleted_at,
|
||||
record.mod_deleted_at,
|
||||
);
|
||||
|
||||
let _r = query.execute(&mut **transaction).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn upsert_model_weights_extension_record(
|
||||
record: &WholeTtsModelRecord,
|
||||
model_weight_token: &ModelWeightToken,
|
||||
transaction: &mut Transaction<'_, MySql>
|
||||
) -> AnyhowResult<()> {
|
||||
let query = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO model_weights_extension_tts_details
|
||||
SET
|
||||
model_weights_token = ?,
|
||||
ietf_language_tag = ?,
|
||||
ietf_primary_language_subtag = ?,
|
||||
text_pipeline_type = ?,
|
||||
use_default_mel_multiply_factor = ?,
|
||||
maybe_custom_mel_multiply_factor = ?,
|
||||
maybe_default_pretrained_vocoder = ?,
|
||||
maybe_custom_vocoder_token = ?
|
||||
ON DUPLICATE KEY UPDATE
|
||||
ietf_language_tag = ?,
|
||||
ietf_primary_language_subtag = ?,
|
||||
text_pipeline_type = ?,
|
||||
use_default_mel_multiply_factor = ?,
|
||||
maybe_custom_mel_multiply_factor = ?,
|
||||
maybe_default_pretrained_vocoder = ?,
|
||||
maybe_custom_vocoder_token = ?
|
||||
"#,
|
||||
// Insert
|
||||
&model_weight_token,
|
||||
record.ietf_language_tag,
|
||||
record.ietf_primary_language_subtag,
|
||||
record.text_pipeline_type,
|
||||
record.use_default_mel_multiply_factor,
|
||||
record.maybe_custom_mel_multiply_factor,
|
||||
record.maybe_default_pretrained_vocoder,
|
||||
record.maybe_custom_vocoder_token,
|
||||
|
||||
// Update
|
||||
record.ietf_language_tag,
|
||||
record.ietf_primary_language_subtag,
|
||||
record.text_pipeline_type,
|
||||
record.use_default_mel_multiply_factor,
|
||||
record.maybe_custom_mel_multiply_factor,
|
||||
record.maybe_default_pretrained_vocoder,
|
||||
record.maybe_custom_vocoder_token
|
||||
);
|
||||
|
||||
let _r = query.execute(&mut **transaction).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_original_record(
|
||||
record: &WholeTtsModelRecord,
|
||||
model_weight_token: &ModelWeightToken,
|
||||
transaction: &mut Transaction<'_, MySql>
|
||||
) -> AnyhowResult<()> {
|
||||
let query = sqlx::query!(
|
||||
r#"
|
||||
UPDATE tts_models
|
||||
SET
|
||||
maybe_migration_new_model_weights_token = ?
|
||||
WHERE token = ?
|
||||
"#,
|
||||
model_weight_token,
|
||||
record.token,
|
||||
);
|
||||
|
||||
let _r = query.execute(&mut **transaction).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod user;
|
||||
pub mod user_badges;
|
||||
pub mod user_bookmarks;
|
||||
pub mod user_password_resets;
|
||||
pub mod user_profiles;
|
||||
pub mod user_ratings;
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
use anyhow::anyhow;
|
||||
use log::warn;
|
||||
use sqlx::{MySql};
|
||||
use sqlx::MySql;
|
||||
use sqlx::pool::PoolConnection;
|
||||
|
||||
use errors::AnyhowResult;
|
||||
|
||||
use crate::queries::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
use crate::queries::users::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
|
||||
pub struct BookmarkCount {
|
||||
pub total_count: usize,
|
||||
+1
-1
@@ -7,7 +7,7 @@ use errors::AnyhowResult;
|
||||
use tokens::tokens::user_bookmarks::UserBookmarkToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::queries::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
use crate::queries::users::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
|
||||
pub struct UserBookmark {
|
||||
pub token: UserBookmarkToken,
|
||||
+2
-2
@@ -6,8 +6,8 @@ use enums::by_table::model_weights::weights_types::WeightsType;
|
||||
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
use crate::queries::user_bookmarks::list_user_bookmarks_result::RawUserBookmarkRecord;
|
||||
use crate::queries::user_bookmarks::list_user_bookmarks_result::UserBookmark;
|
||||
use crate::queries::users::user_bookmarks::list_user_bookmarks_result::RawUserBookmarkRecord;
|
||||
use crate::queries::users::user_bookmarks::list_user_bookmarks_result::UserBookmark;
|
||||
|
||||
pub struct UserBookmarkListPage {
|
||||
pub results: Vec<UserBookmark>,
|
||||
+1
-1
@@ -7,7 +7,7 @@ use errors::AnyhowResult;
|
||||
use tokens::tokens::user_bookmarks::UserBookmarkToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::queries::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
use crate::queries::users::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
|
||||
pub struct UserBookmark {
|
||||
pub token: UserBookmarkToken,
|
||||
+37
-37
@@ -70,56 +70,56 @@ pub struct UserBookmark {
|
||||
}
|
||||
|
||||
pub struct RawUserBookmarkRecord {
|
||||
pub (crate) token: UserBookmarkToken,
|
||||
pub(crate) token: UserBookmarkToken,
|
||||
|
||||
pub (crate) entity_type: UserBookmarkEntityType,
|
||||
pub (crate) entity_token: String,
|
||||
pub(crate) entity_type: UserBookmarkEntityType,
|
||||
pub(crate) entity_token: String,
|
||||
|
||||
// TODO(bt,2023-12-30): I don't think these user fields are necessary.
|
||||
// We already know the user we're querying on behalf of, so presumably we
|
||||
// already have their info (ie. profile page use).
|
||||
pub (crate) user_token: UserToken,
|
||||
pub (crate) username: String,
|
||||
pub (crate) user_display_name: String,
|
||||
pub (crate) user_gravatar_hash: String,
|
||||
pub(crate) user_token: UserToken,
|
||||
pub(crate) username: String,
|
||||
pub(crate) user_display_name: String,
|
||||
pub(crate) user_gravatar_hash: String,
|
||||
|
||||
pub (crate) created_at: DateTime<Utc>,
|
||||
pub (crate) updated_at: DateTime<Utc>,
|
||||
pub (crate) deleted_at: Option<DateTime<Utc>>,
|
||||
pub(crate) created_at: DateTime<Utc>,
|
||||
pub(crate) updated_at: DateTime<Utc>,
|
||||
pub(crate) deleted_at: Option<DateTime<Utc>>,
|
||||
|
||||
pub (crate) maybe_ratings_positive_count: Option<u32>,
|
||||
pub (crate) maybe_ratings_negative_count: Option<u32>,
|
||||
pub (crate) maybe_bookmark_count: Option<u32>,
|
||||
pub(crate) maybe_ratings_positive_count: Option<u32>,
|
||||
pub(crate) maybe_ratings_negative_count: Option<u32>,
|
||||
pub(crate) maybe_bookmark_count: Option<u32>,
|
||||
|
||||
pub (crate) maybe_media_file_type: Option<MediaFileType>,
|
||||
pub (crate) maybe_media_file_origin_category: Option<MediaFileOriginCategory>,
|
||||
pub (crate) maybe_media_file_public_bucket_hash: Option<String>,
|
||||
pub (crate) maybe_media_file_public_bucket_prefix: Option<String>,
|
||||
pub (crate) maybe_media_file_public_bucket_extension: Option<String>,
|
||||
pub(crate) maybe_media_file_type: Option<MediaFileType>,
|
||||
pub(crate) maybe_media_file_origin_category: Option<MediaFileOriginCategory>,
|
||||
pub(crate) maybe_media_file_public_bucket_hash: Option<String>,
|
||||
pub(crate) maybe_media_file_public_bucket_prefix: Option<String>,
|
||||
pub(crate) maybe_media_file_public_bucket_extension: Option<String>,
|
||||
|
||||
pub (crate) maybe_media_file_creator_user_token: Option<UserToken>,
|
||||
pub (crate) maybe_media_file_creator_username: Option<String>,
|
||||
pub (crate) maybe_media_file_creator_display_name: Option<String>,
|
||||
pub (crate) maybe_media_file_creator_gravatar_hash: Option<String>,
|
||||
pub(crate) maybe_media_file_creator_user_token: Option<UserToken>,
|
||||
pub(crate) maybe_media_file_creator_username: Option<String>,
|
||||
pub(crate) maybe_media_file_creator_display_name: Option<String>,
|
||||
pub(crate) maybe_media_file_creator_gravatar_hash: Option<String>,
|
||||
|
||||
pub (crate) maybe_model_weight_type: Option<WeightsType>,
|
||||
pub (crate) maybe_model_weight_category: Option<WeightsCategory>,
|
||||
pub(crate) maybe_model_weight_type: Option<WeightsType>,
|
||||
pub(crate) maybe_model_weight_category: Option<WeightsCategory>,
|
||||
|
||||
pub (crate) maybe_model_weight_cover_image_public_bucket_hash: Option<String>,
|
||||
pub (crate) maybe_model_weight_cover_image_public_bucket_prefix: Option<String>,
|
||||
pub (crate) maybe_model_weight_cover_image_public_bucket_extension: Option<String>,
|
||||
pub(crate) maybe_model_weight_cover_image_public_bucket_hash: Option<String>,
|
||||
pub(crate) maybe_model_weight_cover_image_public_bucket_prefix: Option<String>,
|
||||
pub(crate) maybe_model_weight_cover_image_public_bucket_extension: Option<String>,
|
||||
|
||||
pub (crate) maybe_model_weight_creator_user_token: Option<UserToken>,
|
||||
pub (crate) maybe_model_weight_creator_username: Option<String>,
|
||||
pub (crate) maybe_model_weight_creator_display_name: Option<String>,
|
||||
pub (crate) maybe_model_weight_creator_gravatar_hash: Option<String>,
|
||||
pub(crate) maybe_model_weight_creator_user_token: Option<UserToken>,
|
||||
pub(crate) maybe_model_weight_creator_username: Option<String>,
|
||||
pub(crate) maybe_model_weight_creator_display_name: Option<String>,
|
||||
pub(crate) maybe_model_weight_creator_gravatar_hash: Option<String>,
|
||||
|
||||
pub (crate) maybe_descriptive_text_model_weight_title: Option<String>,
|
||||
pub (crate) maybe_descriptive_text_tts_model_title: Option<String>,
|
||||
pub (crate) maybe_descriptive_text_tts_result_inference_text: Option<String>,
|
||||
pub (crate) maybe_descriptive_text_user_display_name: Option<String>,
|
||||
pub (crate) maybe_descriptive_text_voice_conversion_model_title: Option<String>,
|
||||
pub (crate) maybe_descriptive_text_zs_voice_title: Option<String>,
|
||||
pub(crate) maybe_descriptive_text_model_weight_title: Option<String>,
|
||||
pub(crate) maybe_descriptive_text_tts_model_title: Option<String>,
|
||||
pub(crate) maybe_descriptive_text_tts_result_inference_text: Option<String>,
|
||||
pub(crate) maybe_descriptive_text_user_display_name: Option<String>,
|
||||
pub(crate) maybe_descriptive_text_voice_conversion_model_title: Option<String>,
|
||||
pub(crate) maybe_descriptive_text_zs_voice_title: Option<String>,
|
||||
}
|
||||
|
||||
impl RawUserBookmarkRecord {
|
||||
+1
-1
@@ -7,7 +7,7 @@ use errors::AnyhowResult;
|
||||
use tokens::tokens::user_bookmarks::UserBookmarkToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::queries::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
use crate::queries::users::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
|
||||
pub struct CreateUserBookmarkArgs<'e, 'c, E>
|
||||
where E: 'e + Executor<'c, Database = MySql>
|
||||
@@ -20,6 +20,7 @@ bootstrap = { path = "../../../lib/bootstrap" }
|
||||
buckets = { path = "../../../_types/buckets" }
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
collections = { path = "../../../lib/collections" }
|
||||
composite_identifiers = { path = "../../../_types/composite_identifiers" }
|
||||
concurrency = { path = "../../../lib/concurrency" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../../lib/easyenv" }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod process_single_tts_job;
|
||||
pub mod styletts2;
|
||||
pub mod tacotron2_v2_early_fakeyou;
|
||||
pub mod vits;
|
||||
pub mod vall_e_x;
|
||||
|
||||
@@ -8,7 +8,8 @@ use mysql_queries::queries::tts::tts_models::get_tts_model_for_inference_improve
|
||||
|
||||
use crate::job::job_loop::job_success_result::JobSuccessResult;
|
||||
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
|
||||
use crate::job::job_types::tts::{tacotron2_v2_early_fakeyou, vits};
|
||||
use crate::job::job_types::tts::{styletts2, tacotron2_v2_early_fakeyou, vits};
|
||||
use crate::job::job_types::tts::styletts2::process_job::StyleTTS2ProcessJobArgs;
|
||||
use crate::job::job_types::tts::tacotron2_v2_early_fakeyou::process_job::ProcessJobArgs;
|
||||
use crate::job::job_types::tts::vall_e_x::process_job::VALLEXProcessJobArgs;
|
||||
use crate::job::job_types::tts::vits::process_job::VitsProcessJobArgs;
|
||||
@@ -24,7 +25,7 @@ pub async fn process_single_tts_job(
|
||||
// TODO: Move common checks for slurs, etc. here.
|
||||
|
||||
match job.maybe_model_type {
|
||||
Some(InferenceModelType::VallEX) => {
|
||||
Some(InferenceModelType::VallEX) | Some(InferenceModelType::StyleTTS2) => {
|
||||
// Zero-shot TTS does not need a fine-tuned model token.
|
||||
dispatch_zero_shot_model(
|
||||
job_dependencies,
|
||||
@@ -64,6 +65,12 @@ async fn dispatch_zero_shot_model(
|
||||
job,
|
||||
}).await
|
||||
}
|
||||
Some(InferenceModelType::StyleTTS2) => {
|
||||
styletts2::process_job::process_job(StyleTTS2ProcessJobArgs {
|
||||
job_dependencies,
|
||||
job,
|
||||
}).await
|
||||
}
|
||||
_ => {
|
||||
Err(ProcessSingleJobError::Other(anyhow!("not a zero-shot model")))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod model_downloaders;
|
||||
pub mod process_job;
|
||||
pub mod process_job_create_voice;
|
||||
pub mod process_job_inference_voice;
|
||||
pub mod validate_job;
|
||||
pub mod styletts2_dependencies;
|
||||
pub mod styletts2_inference_command;
|
||||
@@ -0,0 +1,19 @@
|
||||
use crate::util::model_downloader::ModelDownloader;
|
||||
|
||||
pub struct StyleTTS2Downloaders;
|
||||
//{
|
||||
// pub styletts2: StyleTTS2ModelMapping,
|
||||
//}
|
||||
|
||||
impl StyleTTS2Downloaders {
|
||||
// TODO(KS): All checkpoints are in the container, so we don't need to download them here.
|
||||
// Maybe they should be here instead
|
||||
pub fn build_all_from_env() -> Self {
|
||||
Self {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_downloaders(&self) -> Vec<&dyn ModelDownloader> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use anyhow::anyhow;
|
||||
|
||||
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
|
||||
|
||||
use crate::job::job_loop::job_success_result::JobSuccessResult;
|
||||
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
|
||||
use crate::job::job_types::tts::styletts2::process_job_create_voice::process_create_voice;
|
||||
use crate::job::job_types::tts::styletts2::process_job_inference_voice::process_inference_voice;
|
||||
use crate::job::job_types::tts::styletts2::validate_job::validate_job;
|
||||
use crate::job_dependencies::JobDependencies;
|
||||
|
||||
use super::validate_job::JobType;
|
||||
|
||||
// This will download everything get into the root host OS then ... will invoke inference using the pathes from the files invoked
|
||||
pub struct StyleTTS2ProcessJobArgs<'a> {
|
||||
pub job_dependencies: &'a JobDependencies,
|
||||
pub job: &'a AvailableInferenceJob,
|
||||
}
|
||||
|
||||
// query using the token then grab the bucket hash
|
||||
pub async fn process_job(
|
||||
args: StyleTTS2ProcessJobArgs<'_>
|
||||
) -> Result<JobSuccessResult, ProcessSingleJobError> {
|
||||
let job = args.job;
|
||||
|
||||
// get args token
|
||||
let jobArgs = validate_job(&job)?; // bubbles error up
|
||||
|
||||
match jobArgs.job_type {
|
||||
JobType::Create => {
|
||||
if let Some(voice_dataset_token) = jobArgs.voice_dataset_token {
|
||||
process_create_voice(args, voice_dataset_token).await
|
||||
} else {
|
||||
Err(ProcessSingleJobError::Other(anyhow!("Missing Dataset Token?")))
|
||||
}
|
||||
}
|
||||
JobType::Inference => {
|
||||
if let Some(voice_token) = jobArgs.voice_token {
|
||||
process_inference_voice(args, voice_token).await
|
||||
} else {
|
||||
Err(ProcessSingleJobError::Other(anyhow!("Missing Voice Token?")))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
use std::fs::read_to_string;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use log::{error, info, warn};
|
||||
|
||||
use buckets::private::zs_voices::bucket_directory::{ModelCategory, ModelType};
|
||||
use buckets::private::zs_voices::bucket_file_path::ZeroShotVoiceEmbeddingBucketPath;
|
||||
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use cloud_storage::bucket_client::BucketClient;
|
||||
use cloud_storage::bucket_path_unifier::BucketPathUnifier;
|
||||
use enums::by_table::generic_inference_jobs::inference_result_type::InferenceResultType;
|
||||
use enums::by_table::zs_voices::encoding_type::ZsVoiceEncodingType;
|
||||
use enums::by_table::zs_voices::model_category::ZsVoiceModelCategory;
|
||||
use enums::by_table::zs_voices::model_type::ZsVoiceModelType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use mysql_queries::queries::voice_designer::datasets::get_dataset::get_dataset_by_token;
|
||||
use mysql_queries::queries::voice_designer::datasets::get_dataset::ZsDataset;
|
||||
use mysql_queries::queries::voice_designer::voice_samples::list_dataset_samples_for_dataset_token::DatasetSampleRecordForList;
|
||||
use mysql_queries::queries::voice_designer::voice_samples::list_dataset_samples_for_dataset_token::list_dataset_samples_for_dataset_token;
|
||||
use mysql_queries::queries::voice_designer::voices::create_voice::create_voice;
|
||||
use mysql_queries::queries::voice_designer::voices::create_voice::CreateVoiceArgs;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::job;
|
||||
use crate::job::job_loop::job_success_result::JobSuccessResult;
|
||||
use crate::job::job_loop::job_success_result::ResultEntity;
|
||||
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
|
||||
use crate::job::job_types::tts::styletts2::process_job::StyleTTS2ProcessJobArgs;
|
||||
|
||||
const BUCKET_FILE_PREFIX_CREATE: &str = "fakeyou_";
|
||||
const BUCKET_FILE_EXTENSION_CREATE: &str = ".bin";
|
||||
const MIME_TYPE_CREATE: &str = "application/x-binary";
|
||||
|
||||
/// We'll increment this if we change the underlying StyleTTS2 code in an incompatible way.
|
||||
const CURRENT_STYLE_TTS2_MODEL_VERSION: u64 = 0;
|
||||
|
||||
pub async fn process_create_voice(
|
||||
args: StyleTTS2ProcessJobArgs<'_>,
|
||||
dataset_token: String
|
||||
) -> Result<JobSuccessResult, ProcessSingleJobError> {
|
||||
let deps = args.job_dependencies;
|
||||
let job = args.job;
|
||||
let mysql_pool = &deps.db.mysql_pool;
|
||||
|
||||
let model_dependencies = deps
|
||||
.job
|
||||
.job_specific_dependencies
|
||||
.maybe_styletts2_dependencies
|
||||
.as_ref()
|
||||
.ok_or_else(|| ProcessSingleJobError::JobSystemMisconfiguration(Some("missing StyleTTS2 dependencies".to_string())))?;
|
||||
|
||||
// get some globals
|
||||
let mut job_progress_reporter = deps.clients.job_progress_reporter
|
||||
.new_generic_inference(job.inference_job_token.as_str())
|
||||
.map_err(|e| ProcessSingleJobError::Other(anyhow!(e)))?;
|
||||
|
||||
info!("token! {}", dataset_token);
|
||||
let voice_dataset_token = tokens::tokens::zs_voice_datasets::ZsVoiceDatasetToken(dataset_token);
|
||||
|
||||
let creator_ip_address = &job.creator_ip_address;
|
||||
|
||||
let creator_user_token = match &job.maybe_creator_user_token {
|
||||
Some(token) => {
|
||||
UserToken::new_from_str(token)
|
||||
},
|
||||
None => {
|
||||
return Err(ProcessSingleJobError::InvalidJob(anyhow!("Missing Creator User Token")));
|
||||
}
|
||||
};
|
||||
|
||||
// STEP 1. SETUP A TEMP DIRECTORY
|
||||
let work_temp_dir = format!("/tmp/temp_zeroshot_create_voice_{}", job.id.0);
|
||||
// NB: TempDir exists until it goes out of scope, at which point it should delete from filesystem.
|
||||
let work_temp_dir = args.job_dependencies.fs.scoped_temp_dir_creator_for_work
|
||||
.new_tempdir(&work_temp_dir)
|
||||
.map_err(|e| ProcessSingleJobError::from_io_error(e))?;
|
||||
|
||||
let workdir = work_temp_dir.path().to_path_buf();
|
||||
|
||||
// STEP 2. Get dataset for the title for the voice
|
||||
let voice_dataset = get_dataset_by_token(&voice_dataset_token, false, &mysql_pool).await;
|
||||
let single_dataset: ZsDataset;
|
||||
match voice_dataset {
|
||||
Ok(val) => {
|
||||
match val {
|
||||
Some(val) => {
|
||||
single_dataset = val;
|
||||
}
|
||||
None => {
|
||||
return Err(ProcessSingleJobError::InvalidJob(anyhow!("Missing Dataset")));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(ProcessSingleJobError::from_anyhow_error(e));
|
||||
}
|
||||
}
|
||||
info!("Title:{} Token:{}", &single_dataset.title, &single_dataset.token);
|
||||
|
||||
// STEP 3. Download dataset each audio file
|
||||
let result = list_dataset_samples_for_dataset_token(
|
||||
&voice_dataset_token,
|
||||
false,
|
||||
&mysql_pool
|
||||
).await;
|
||||
|
||||
let dataset: Vec<DatasetSampleRecordForList> = match result {
|
||||
Ok(val) => val,
|
||||
Err(e) => return Err(ProcessSingleJobError::from_anyhow_error(e)),
|
||||
};
|
||||
|
||||
info!("Dataset length info: {}", dataset.len());
|
||||
|
||||
let mut downloaded_dataset: Vec<PathBuf> = Vec::new();
|
||||
|
||||
for (index, record) in dataset.iter().enumerate() {
|
||||
//https://storage.googleapis.com/dev-vocodes-public/media/5/3/3/w/8/533w8zs0fy11nv7gkcna7p7vt03h8nda/dev_zs_533w8zs0fy11nv7gkcna7p7vt03h8nda.bin <-- where the file actually is
|
||||
let prefix: Option<&str> = record.maybe_public_bucket_prefix.as_ref().map(|s| s.as_str());
|
||||
let extension: Option<&str> = record.maybe_public_bucket_extension.as_ref().map(|s| s.as_str());
|
||||
|
||||
info!("Record=> hash:{} prefix:{:?} extension:{:?}",
|
||||
record.public_bucket_directory_hash,
|
||||
prefix,
|
||||
extension
|
||||
);
|
||||
|
||||
let audio_media_file = MediaFileBucketPath::from_object_hash(
|
||||
&record.public_bucket_directory_hash,
|
||||
prefix,
|
||||
extension
|
||||
);
|
||||
|
||||
info!("Download using audio_media_file_path: {:?}", audio_media_file.to_full_object_pathbuf());
|
||||
|
||||
let file_name_wav = format!("{}.wav", index);
|
||||
|
||||
let mut file_path = PathBuf::new();
|
||||
file_path.push(workdir.clone());
|
||||
file_path.push(file_path.clone());
|
||||
file_path.push(file_name_wav);
|
||||
|
||||
info!("Downloading to path: {:?}", file_path);
|
||||
|
||||
// TODO: we might want to catch the error and not include the pathes into download dataset?
|
||||
let result = deps.buckets.public_bucket_client.download_file_to_disk(
|
||||
audio_media_file.to_full_object_pathbuf(),
|
||||
&file_path
|
||||
).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("could not download sample: {:?}", err);
|
||||
return Err(ProcessSingleJobError::from_anyhow_error(err));
|
||||
}
|
||||
|
||||
info!("FilePath to clone voice: {}", file_path.to_string_lossy());
|
||||
downloaded_dataset.push(file_path.clone());
|
||||
|
||||
|
||||
// TODO(KS): This is a hack to get only the first file to download
|
||||
// Ideally we could have improved logic to pick the "best" media file from a dataset
|
||||
if downloaded_dataset.len() >= 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
info!("Dataset Length downloaded: {}", downloaded_dataset.len());
|
||||
|
||||
// STEP 4 Download the models
|
||||
info!("Download models (if not present)...");
|
||||
for downloader in model_dependencies.downloaders.all_downloaders() {
|
||||
let result = downloader.download_if_not_on_filesystem(
|
||||
&args.job_dependencies.buckets.private_bucket_client,
|
||||
&args.job_dependencies.fs.scoped_temp_dir_creator_for_short_lived_downloads
|
||||
).await;
|
||||
if let Err(err) = result {
|
||||
error!("could not download: {:?}", err);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
job_progress_reporter
|
||||
.log_status("running inference")
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
// Command line arg for a list of paths to insert the container
|
||||
let audio_files = join_paths(downloaded_dataset);
|
||||
|
||||
info!("Files to process: {:?}", audio_files);
|
||||
|
||||
// Name of the output file
|
||||
let output_embedding_path = work_temp_dir.path().join("style.npz");
|
||||
let stderr_output_file = work_temp_dir.path().join("zero_shot_create_voice_err.txt");
|
||||
|
||||
let inference_start_time = Instant::now();
|
||||
|
||||
// Run Inference
|
||||
let command_exit_status =
|
||||
model_dependencies.create_embedding_command.execute_inference(
|
||||
job::job_types::tts::styletts2::styletts2_inference_command::CreateVoiceInferenceArgs {
|
||||
output_embedding_path: &output_embedding_path,
|
||||
audio_file: audio_files,
|
||||
stderr_output_file: &stderr_output_file,
|
||||
}
|
||||
);
|
||||
|
||||
let inference_duration = Instant::now().duration_since(inference_start_time);
|
||||
|
||||
if !command_exit_status.is_success() {
|
||||
error!("Create Embedding Failed: {:?}", command_exit_status);
|
||||
|
||||
let error = ProcessSingleJobError::Other(anyhow!("CommandExitStatus: {:?}", command_exit_status));
|
||||
|
||||
if let Ok(contents) = read_to_string(&stderr_output_file) {
|
||||
warn!("Captured stderr output: {}", contents);
|
||||
}
|
||||
|
||||
//thread::sleep(Duration::from_secs(300));
|
||||
|
||||
// Clean up temp files
|
||||
//safe_delete_temp_file(&audio_path.filesystem_path);
|
||||
//safe_delete_temp_file(&image_path.filesystem_path);
|
||||
//safe_delete_temp_file(&usable_image_path);
|
||||
//safe_delete_temp_file(&output_video_fs_path);
|
||||
//safe_delete_temp_file(&stderr_output_file);
|
||||
//safe_delete_temp_directory(&work_temp_dir);
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
info!("Inference success!");
|
||||
|
||||
info!("Uploading Media ...");
|
||||
let embedding_bucket_location = ZeroShotVoiceEmbeddingBucketPath::generate_new(
|
||||
ModelCategory::Tts,
|
||||
ModelType::StyleTTS2,
|
||||
CURRENT_STYLE_TTS2_MODEL_VERSION
|
||||
);
|
||||
|
||||
let embedding_bucket_object_pathbuf = embedding_bucket_location.to_full_object_pathbuf();
|
||||
|
||||
// Get Finished File
|
||||
let mut finished_file = work_temp_dir.path().to_path_buf();
|
||||
|
||||
let output_bucket_file_name = String::from("style.npz"); // use extension for bucket upload.
|
||||
finished_file.push(&output_bucket_file_name);
|
||||
|
||||
info!("Upload File Path: {:?}", finished_file);
|
||||
info!("Upload Bucket Path: {:?}", embedding_bucket_object_pathbuf);
|
||||
|
||||
args.job_dependencies.buckets.private_bucket_client
|
||||
.upload_filename_with_content_type(
|
||||
&embedding_bucket_object_pathbuf,
|
||||
&finished_file,
|
||||
&MIME_TYPE_CREATE
|
||||
)
|
||||
.await // TODO: We should check the mimetype to make sure bad payloads can't get uploaded
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
// CLEARIFY! these items
|
||||
// 1.Should this be object hash?
|
||||
// 2.As well as this what should the voice name be?
|
||||
let voice_name = single_dataset.title;
|
||||
|
||||
// insert record
|
||||
let voice_token = create_voice(CreateVoiceArgs {
|
||||
dataset_token: &voice_dataset_token,
|
||||
model_category: ZsVoiceModelCategory::Tts,
|
||||
model_type: ZsVoiceModelType::StyleTTS2,
|
||||
model_version: CURRENT_STYLE_TTS2_MODEL_VERSION,
|
||||
model_encoding_type: ZsVoiceEncodingType::Encodec,
|
||||
voice_title: &voice_name,
|
||||
bucket_hash: embedding_bucket_location.get_object_hash().clone(),
|
||||
maybe_creator_user_token: Some(&creator_user_token),
|
||||
creator_ip_address: &creator_ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
mysql_pool,
|
||||
}).await
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
Ok(JobSuccessResult {
|
||||
maybe_result_entity: Some(ResultEntity {
|
||||
entity_type: InferenceResultType::ZeroShotVoiceEmbedding,
|
||||
entity_token: voice_token.to_string(),
|
||||
}),
|
||||
inference_duration,
|
||||
})
|
||||
}
|
||||
|
||||
pub struct AudioFile {
|
||||
pub filesystem_path: PathBuf,
|
||||
}
|
||||
|
||||
pub async fn download_audio_from_hash(
|
||||
bucket_hash: &str,
|
||||
name: &str,
|
||||
private_bucket_client: &BucketClient,
|
||||
path: &PathBuf
|
||||
) -> Result<AudioFile, ProcessSingleJobError> {
|
||||
let unifer = BucketPathUnifier::default_paths();
|
||||
let object_path = unifer.zero_shot_tts_speaker_encoding(bucket_hash, 0);
|
||||
|
||||
let mut path = path.clone();
|
||||
|
||||
let file_name = format!("{}", name);
|
||||
path.push(&file_name);
|
||||
|
||||
private_bucket_client.download_file_to_disk(object_path, &path)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Could not download audio file: {err}");
|
||||
ProcessSingleJobError::from_anyhow_error(anyhow!("Could not download audio file: {err}"))
|
||||
})?;
|
||||
|
||||
let audio_file = AudioFile {
|
||||
filesystem_path: PathBuf::from(&path.clone()),
|
||||
};
|
||||
|
||||
Ok(audio_file)
|
||||
}
|
||||
|
||||
fn join_paths(paths: Vec<PathBuf>) -> String {
|
||||
paths
|
||||
.into_iter()
|
||||
.map(|p| format!("\"{}\"", p.display()))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::job::job_types::tts::styletts2::process_job_create_voice::join_paths;
|
||||
|
||||
#[test]
|
||||
fn test_path_build() {
|
||||
let paths = vec![
|
||||
PathBuf::from("/home/tensor/code/TTSDockerContainer/Vall-E-mount/input/20.wav"),
|
||||
PathBuf::from("/home/tensor/code/TTSDockerContainer/Vall-E-mount/input/21.wav")
|
||||
];
|
||||
|
||||
let value = join_paths(paths);
|
||||
let expected =
|
||||
"\"/home/tensor/code/TTSDockerContainer/Vall-E-mount/input/20.wav\" \"/home/tensor/code/TTSDockerContainer/Vall-E-mount/input/21.wav\"";
|
||||
assert_eq!(value, expected);
|
||||
}
|
||||
}
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
use std::fs::read_to_string;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use log::{error, info, warn};
|
||||
|
||||
use buckets::private::zs_voices::bucket_directory::{ModelCategory, ModelType};
|
||||
use buckets::private::zs_voices::bucket_file_path::ZeroShotVoiceEmbeddingBucketPath;
|
||||
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use cloud_storage::bucket_client::BucketClient;
|
||||
use cloud_storage::bucket_path_unifier::BucketPathUnifier;
|
||||
use enums::by_table::generic_inference_jobs::inference_result_type::InferenceResultType;
|
||||
use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType;
|
||||
use filesys::file_size::file_size;
|
||||
use hashing::sha256::sha256_hash_file::sha256_hash_file;
|
||||
use mysql_queries::queries::media_files::create::insert_media_file_from_zero_shot_tts::insert_media_file_from_zero_shot;
|
||||
use mysql_queries::queries::media_files::create::insert_media_file_from_zero_shot_tts::InsertArgs;
|
||||
use mysql_queries::queries::voice_designer::voices::get_voice::{get_voice_by_token, ZsVoice};
|
||||
use tts_common::clean_symbols::clean_symbols;
|
||||
|
||||
use crate::job::job_loop::job_success_result::JobSuccessResult;
|
||||
use crate::job::job_loop::job_success_result::ResultEntity;
|
||||
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
|
||||
use crate::job::job_types::tts::styletts2::process_job::StyleTTS2ProcessJobArgs;
|
||||
use crate::job::job_types::tts::styletts2::styletts2_inference_command::InferenceArgs;
|
||||
|
||||
const BUCKET_FILE_PREFIX: &str = "fakeyou_";
|
||||
const BUCKET_FILE_EXTENSION: &str = ".wav";
|
||||
const MIME_TYPE: &str = "audio/wav";
|
||||
|
||||
|
||||
pub async fn process_inference_voice(
|
||||
args: StyleTTS2ProcessJobArgs<'_>,
|
||||
voice_token: String
|
||||
) -> Result<JobSuccessResult, ProcessSingleJobError> {
|
||||
let deps = args.job_dependencies;
|
||||
let job = args.job;
|
||||
let mysql_pool = &deps.db.mysql_pool;
|
||||
|
||||
let model_dependencies = deps
|
||||
.job
|
||||
.job_specific_dependencies
|
||||
.maybe_styletts2_dependencies
|
||||
.as_ref()
|
||||
.ok_or_else(|| ProcessSingleJobError::JobSystemMisconfiguration(Some("missing StyleTTS2 dependencies".to_string())))?;
|
||||
|
||||
// get some globals
|
||||
let mut job_progress_reporter = deps
|
||||
.clients
|
||||
.job_progress_reporter
|
||||
.new_generic_inference(job.inference_job_token.as_str())
|
||||
.map_err(|e| ProcessSingleJobError::Other(anyhow!(e)))?;
|
||||
|
||||
// get job args
|
||||
let text = match job.maybe_raw_inference_text.clone() {
|
||||
Some(value) => {
|
||||
clean_symbols(value.as_str())
|
||||
}
|
||||
None => {
|
||||
return Err(ProcessSingleJobError::InvalidJob(anyhow!("Missing Text for Inference")));
|
||||
}
|
||||
};
|
||||
|
||||
let voice_token = tokens::tokens::zs_voices::ZsVoiceToken(voice_token);
|
||||
|
||||
// Get voice bucket hash - from voice token
|
||||
let voice_lookup_result = get_voice_by_token(&voice_token, false, &mysql_pool).await;
|
||||
|
||||
let voice = match voice_lookup_result {
|
||||
Ok(Some(voice)) => voice,
|
||||
Ok(None) => {
|
||||
warn!("Voice not found: {:?}", voice_token);
|
||||
return Err(ProcessSingleJobError::Other(anyhow!("Voice not found: {:?}", voice_token)));
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("Error looking up voice: {:?}", err);
|
||||
return Err(ProcessSingleJobError::Other(anyhow!("Error looking up voice: {:?}", err)));
|
||||
}
|
||||
};
|
||||
|
||||
// Need to download the models
|
||||
info!("Download models (if not present)...");
|
||||
|
||||
for downloader in model_dependencies.downloaders.all_downloaders() {
|
||||
let result = downloader.download_if_not_on_filesystem(
|
||||
&args.job_dependencies.buckets.private_bucket_client,
|
||||
&args.job_dependencies.fs.scoped_temp_dir_creator_for_short_lived_downloads
|
||||
).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("could not download: {:?}", err);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
// run inference
|
||||
let work_temp_dir = format!("/tmp/temp_zeroshot_inference_{}", job.id.0);
|
||||
|
||||
// NB: TempDir exists until it goes out of scope, at which point it should delete from filesystem.
|
||||
let work_temp_dir = args.job_dependencies.fs.scoped_temp_dir_creator_for_work
|
||||
.new_tempdir(&work_temp_dir)
|
||||
.map_err(|e| ProcessSingleJobError::from_io_error(e))?;
|
||||
|
||||
let _workdir = work_temp_dir.path().to_path_buf();
|
||||
let filename = "style.npz".to_string();
|
||||
|
||||
let mut downloaded_weights_path = work_temp_dir.path().to_path_buf();
|
||||
downloaded_weights_path.push(&filename);
|
||||
|
||||
let voice_file = download_voice_embedding(
|
||||
&voice,
|
||||
&deps.buckets.private_bucket_client,
|
||||
&downloaded_weights_path
|
||||
).await?;
|
||||
|
||||
println!("voice file path! {}", voice_file.filesystem_path.to_string_lossy());
|
||||
|
||||
// Download embeddings file using embedding token
|
||||
// Create a temp dir to download things to
|
||||
job_progress_reporter
|
||||
.log_status("running inference")
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
let text_input_fs_path = work_temp_dir.path().join("inference_input.txt");
|
||||
|
||||
std::fs::write(&text_input_fs_path, &text)
|
||||
.map_err(|e| ProcessSingleJobError::from_io_error(e))?;
|
||||
|
||||
let inference_start_time = Instant::now();
|
||||
|
||||
let output_file_name = work_temp_dir.path().join("output.wav");
|
||||
let stderr_output_file = work_temp_dir.path().join("zero_shot_inference.txt");
|
||||
|
||||
// Run Inference
|
||||
let command_exit_status =
|
||||
model_dependencies.inference_command.execute_inference(
|
||||
InferenceArgs {
|
||||
input_embedding_file_path: &downloaded_weights_path,
|
||||
input_text_file_path: &text_input_fs_path, // text
|
||||
output_file_name: &output_file_name, // output file name in the output folder
|
||||
stderr_output_file: &stderr_output_file,
|
||||
}
|
||||
);
|
||||
|
||||
let inference_duration = Instant::now().duration_since(inference_start_time);
|
||||
|
||||
if !command_exit_status.is_success() {
|
||||
error!("Zero shot TTS failed: {:?}", command_exit_status);
|
||||
|
||||
let error = ProcessSingleJobError::Other(anyhow!("CommandExitStatus: {:?}", command_exit_status));
|
||||
|
||||
if let Ok(contents) = read_to_string(&stderr_output_file) {
|
||||
warn!("Captured stderr output: {}", contents);
|
||||
}
|
||||
|
||||
// Clean up temp files
|
||||
//safe_delete_temp_file(&audio_path.filesystem_path);
|
||||
//safe_delete_temp_file(&image_path.filesystem_path);
|
||||
//safe_delete_temp_file(&usable_image_path);
|
||||
//safe_delete_temp_file(&output_video_fs_path);
|
||||
//safe_delete_temp_file(&stderr_output_file);
|
||||
//safe_delete_temp_directory(&work_temp_dir);
|
||||
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
// upload audio to bucket
|
||||
info!("Uploading media ...");
|
||||
|
||||
let result_bucket_location = MediaFileBucketPath::generate_new(
|
||||
Some(BUCKET_FILE_PREFIX),
|
||||
Some(BUCKET_FILE_EXTENSION)
|
||||
);
|
||||
|
||||
let result_bucket_object_pathbuf = result_bucket_location.to_full_object_pathbuf();
|
||||
|
||||
// Finished file path
|
||||
let mut finished_file = work_temp_dir.path().to_path_buf();
|
||||
finished_file.push(&output_file_name);
|
||||
|
||||
info!("Upload Bucket Path: {:?}", result_bucket_object_pathbuf);
|
||||
info!("Upload File Path: {:?}", finished_file);
|
||||
|
||||
args.job_dependencies.buckets.public_bucket_client
|
||||
.upload_filename_with_content_type(
|
||||
&result_bucket_object_pathbuf,
|
||||
&finished_file,
|
||||
&MIME_TYPE
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
// ==================== UPLOAD AUDIO TO BUCKET ====================
|
||||
|
||||
info!("Calculating sha256...");
|
||||
|
||||
let file_checksum = sha256_hash_file(&finished_file).map_err(|err| {
|
||||
ProcessSingleJobError::Other(anyhow!("Error hashing file: {:?}", err))
|
||||
})?;
|
||||
|
||||
let file_size_bytes = file_size(&finished_file).map_err(|err|
|
||||
ProcessSingleJobError::Other(err)
|
||||
)?;
|
||||
|
||||
job_progress_reporter.log_status("done").map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
// insert into db the record
|
||||
let (media_file_token, id) = insert_media_file_from_zero_shot(InsertArgs {
|
||||
pool: &args.job_dependencies.db.mysql_pool,
|
||||
job: &job,
|
||||
origin_model_type: MediaFileOriginModelType::StyleTTS2,
|
||||
maybe_mime_type: Some(&MIME_TYPE),
|
||||
file_size_bytes,
|
||||
sha256_checksum: &file_checksum,
|
||||
public_bucket_directory_hash: result_bucket_location.get_object_hash(),
|
||||
maybe_public_bucket_prefix: Some(BUCKET_FILE_PREFIX),
|
||||
maybe_public_bucket_extension: Some(BUCKET_FILE_EXTENSION),
|
||||
is_on_prem: args.job_dependencies.job.info.container.is_on_prem,
|
||||
worker_hostname: &args.job_dependencies.job.info.container.hostname,
|
||||
worker_cluster: &args.job_dependencies.job.info.container.cluster_name,
|
||||
}).await.map_err(|e| ProcessSingleJobError::Other(e))?;
|
||||
|
||||
info!(
|
||||
"Job {:?} complete success! Downloaded, ran inference, and uploaded. Saved model record: {}, Result Token: {}",
|
||||
job.id,
|
||||
id,
|
||||
&media_file_token
|
||||
);
|
||||
|
||||
Ok(JobSuccessResult {
|
||||
maybe_result_entity: Some(ResultEntity {
|
||||
entity_type: InferenceResultType::MediaFile,
|
||||
entity_token: media_file_token.to_string(),
|
||||
}),
|
||||
inference_duration,
|
||||
})
|
||||
}
|
||||
|
||||
pub struct VoiceFile {
|
||||
pub filesystem_path: PathBuf,
|
||||
}
|
||||
|
||||
pub async fn download_voice_embedding_from_hash(
|
||||
bucket_hash: &str,
|
||||
name: &str,
|
||||
private_bucket_client: &BucketClient,
|
||||
path: &PathBuf
|
||||
) -> Result<VoiceFile, ProcessSingleJobError> {
|
||||
let unifer = BucketPathUnifier::default_paths();
|
||||
let object_path = unifer.zero_shot_tts_speaker_encoding(bucket_hash, 0);
|
||||
|
||||
let mut path = path.clone();
|
||||
|
||||
let file_name = format!("{}", name);
|
||||
path.push(&file_name);
|
||||
|
||||
private_bucket_client.download_file_to_disk(object_path, &path)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Could not download embedding file: {err}");
|
||||
ProcessSingleJobError::from_anyhow_error(anyhow!("Could not download embedding file: {err}"))
|
||||
})?;
|
||||
|
||||
let voice_file = VoiceFile {
|
||||
filesystem_path: PathBuf::from(&path.clone()),
|
||||
};
|
||||
|
||||
Ok(voice_file)
|
||||
}
|
||||
|
||||
pub async fn download_voice_embedding(
|
||||
voice: &ZsVoice,
|
||||
private_bucket_client: &BucketClient,
|
||||
download_path: &PathBuf,
|
||||
) -> Result<VoiceFile, ProcessSingleJobError> {
|
||||
|
||||
let embedding_bucket_location = ZeroShotVoiceEmbeddingBucketPath::from_object_hash(
|
||||
&voice.bucket_hash,
|
||||
ModelCategory::Tts,
|
||||
ModelType::StyleTTS2,
|
||||
voice.model_version
|
||||
);
|
||||
|
||||
info!("Downloading embedding from: {:?}", &embedding_bucket_location.to_full_object_pathbuf());
|
||||
info!("Downloading to filesystem location: {:?}", download_path);
|
||||
|
||||
let result = private_bucket_client.download_file_to_disk(
|
||||
embedding_bucket_location.to_full_object_pathbuf(),
|
||||
&download_path
|
||||
).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("could not download embedding file: {:?}", err);
|
||||
return Err(ProcessSingleJobError::from_anyhow_error(err));
|
||||
}
|
||||
|
||||
let voice_file = VoiceFile {
|
||||
filesystem_path: PathBuf::from(&download_path.clone()),
|
||||
};
|
||||
|
||||
Ok(voice_file)
|
||||
}
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
use errors::AnyhowResult;
|
||||
|
||||
use crate::job::job_types::tts::styletts2::model_downloaders::StyleTTS2Downloaders;
|
||||
use crate::job::job_types::tts::styletts2::styletts2_inference_command::{StyleTTS2CreateEmbeddingCommand, StyleTTS2InferenceCommand};
|
||||
|
||||
pub struct StyleTTS2Dependencies {
|
||||
pub create_embedding_command: StyleTTS2CreateEmbeddingCommand,
|
||||
pub downloaders: StyleTTS2Downloaders,
|
||||
pub inference_command: StyleTTS2InferenceCommand,
|
||||
}
|
||||
|
||||
impl StyleTTS2Dependencies {
|
||||
pub fn setup() -> AnyhowResult<Self> {
|
||||
Ok(Self {
|
||||
create_embedding_command: StyleTTS2CreateEmbeddingCommand::from_env()?,
|
||||
downloaders: StyleTTS2Downloaders::build_all_from_env(),
|
||||
inference_command: StyleTTS2InferenceCommand::from_env()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
+497
@@ -0,0 +1,497 @@
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use log::info;
|
||||
use once_cell::sync::Lazy;
|
||||
use subprocess::{Popen, PopenConfig, Redirection};
|
||||
|
||||
use errors::AnyhowResult;
|
||||
use filesys::path_to_string::path_to_string;
|
||||
use subprocess_common::docker_options::{DockerFilesystemMount, DockerGpu, DockerOptions};
|
||||
|
||||
use crate::job::job_loop::command_exit_status::CommandExitStatus;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ExecutableOrCommand {
|
||||
/// Eg. `inference.py`
|
||||
Executable(PathBuf),
|
||||
/// Eg. `python3 inference.py`
|
||||
Command(String),
|
||||
}
|
||||
|
||||
// These environment vars are not copied over to the subprocess
|
||||
// TODO/FIXME(bt, 2023-05-28): This is horrific security!
|
||||
static IGNORED_ENVIRONMENT_VARS: Lazy<HashSet<String>> = Lazy::new(|| {
|
||||
let env_var_names = ["MYSQL_URL", "ACCESS_KEY", "SECRET_KEY", "NEWRELIC_API_KEY"];
|
||||
|
||||
env_var_names
|
||||
.iter()
|
||||
.map(|value| value.to_string())
|
||||
.collect::<HashSet<String>>()
|
||||
});
|
||||
|
||||
// The inference command assumes everything has been setup to run inference
|
||||
// so we have all the files downloaded for the inputs.
|
||||
pub struct InferenceArgs<P: AsRef<Path>> {
|
||||
/// --driven_audio: path to the input audio
|
||||
pub input_embedding_file_path: P, // name of the embedding.npz in the work dir
|
||||
pub input_text_file_path: P, // name of the text
|
||||
pub output_file_name: P, // output file name in the output folder
|
||||
pub stderr_output_file: P,
|
||||
}
|
||||
|
||||
pub struct CreateVoiceInferenceArgs<P: AsRef<Path>> {
|
||||
pub output_embedding_path: P,
|
||||
pub audio_file: String,
|
||||
pub stderr_output_file: P,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StyleTTS2InferenceCommand {
|
||||
/// Where the code lives
|
||||
root_code_directory: PathBuf,
|
||||
/// A single executable script or a much larger bash command.
|
||||
executable_or_command: ExecutableOrCommand,
|
||||
/// eg. `source python/bin/activate`
|
||||
maybe_virtual_env_activation_command: Option<String>,
|
||||
/// Optional default config file to use
|
||||
maybe_default_config_path: Option<PathBuf>,
|
||||
/// If this is run under Docker (eg. in development), these are the options.
|
||||
maybe_docker_options: Option<DockerOptions>,
|
||||
/// If the execution should be ended after a certain point.
|
||||
maybe_execution_timeout: Option<Duration>,
|
||||
/// Inference arg.
|
||||
/// --checkpoint_dir: optional location for checkpoints directory
|
||||
pub alternate_checkpoint_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl StyleTTS2InferenceCommand {
|
||||
pub fn new<P: AsRef<Path>>(
|
||||
root_code_directory: P,
|
||||
executable_or_command: ExecutableOrCommand,
|
||||
maybe_virtual_env_activation_command: Option<&str>,
|
||||
maybe_default_config_path: Option<P>,
|
||||
maybe_docker_options: Option<DockerOptions>,
|
||||
maybe_execution_timeout: Option<Duration>,
|
||||
alternate_checkpoint_dir: Option<PathBuf>
|
||||
) -> Self {
|
||||
Self {
|
||||
root_code_directory: root_code_directory.as_ref().to_path_buf(),
|
||||
executable_or_command,
|
||||
maybe_virtual_env_activation_command: maybe_virtual_env_activation_command.map(|s|
|
||||
s.to_string()
|
||||
),
|
||||
maybe_default_config_path: maybe_default_config_path.map(|p| p.as_ref().to_path_buf()),
|
||||
maybe_docker_options,
|
||||
maybe_execution_timeout,
|
||||
alternate_checkpoint_dir,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_env() -> AnyhowResult<Self> {
|
||||
let root_code_directory = easyenv::get_env_pathbuf_required(
|
||||
"STYLE_TTS2_INFERENCE_ROOT_DIRECTORY"
|
||||
)?;
|
||||
|
||||
let maybe_inference_command = easyenv::get_env_string_optional(
|
||||
"STYLE_TTS2_INFERENCE_COMMAND"
|
||||
);
|
||||
|
||||
// EVENTUALLY should remove the check
|
||||
// Optional, eg. `./infer.py`. Typically we'll use the command form instead.
|
||||
let maybe_inference_executable = easyenv::get_env_pathbuf_optional(
|
||||
"STYLE_TTS2_INFERENCE_EXECUTABLE"
|
||||
);
|
||||
|
||||
let executable_or_command = match maybe_inference_command {
|
||||
Some(command) => ExecutableOrCommand::Command(command),
|
||||
None =>
|
||||
match maybe_inference_executable {
|
||||
Some(executable) => ExecutableOrCommand::Executable(executable),
|
||||
None => {
|
||||
return Err(anyhow!("neither command nor executable passed"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let maybe_virtual_env_activation_command = easyenv::get_env_string_optional(
|
||||
"STYLE_TTS2_INFERENCE_MAYBE_VENV_COMMAND"
|
||||
);
|
||||
|
||||
let maybe_default_config_path = easyenv::get_env_pathbuf_optional(
|
||||
"STYLE_TTS2_INFERENCE_MAYBE_DEFAULT_CONFIG_PATH"
|
||||
);
|
||||
|
||||
let maybe_execution_timeout = easyenv::get_env_duration_seconds_optional("TIMEOUT_SECONDS");
|
||||
|
||||
// Probably for local
|
||||
let maybe_docker_options = easyenv
|
||||
::get_env_string_optional("STYLE_TTS2_INFERENCE_MAYBE_DOCKER_IMAGE")
|
||||
.map(|image_name| {
|
||||
DockerOptions {
|
||||
image_name,
|
||||
maybe_bind_mount: Some(DockerFilesystemMount::tmp_to_tmp()),
|
||||
maybe_environment_variables: None,
|
||||
maybe_gpu: Some(DockerGpu::All),
|
||||
}
|
||||
});
|
||||
|
||||
// Override for --checkpoint_dir at inference time
|
||||
let alternate_checkpoint_dir = easyenv::get_env_pathbuf_optional(
|
||||
"STYLE_TTS2_ALTERNATE_CHECKPOINT_PATH"
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
root_code_directory,
|
||||
executable_or_command,
|
||||
maybe_virtual_env_activation_command,
|
||||
maybe_default_config_path,
|
||||
maybe_docker_options,
|
||||
maybe_execution_timeout,
|
||||
alternate_checkpoint_dir,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn execute_inference<P: AsRef<Path>>(&self, args: InferenceArgs<P>) -> CommandExitStatus {
|
||||
match self.do_execute_inference(args) {
|
||||
Ok(exit_status) => exit_status,
|
||||
Err(error) =>
|
||||
CommandExitStatus::FailureWithReason { reason: format!("error: {:?}", error) },
|
||||
}
|
||||
}
|
||||
|
||||
fn do_execute_inference<P: AsRef<Path>>(
|
||||
&self,
|
||||
args: InferenceArgs<P>
|
||||
) -> AnyhowResult<CommandExitStatus> {
|
||||
let mut command = String::new();
|
||||
command.push_str(&format!("cd {}", path_to_string(&self.root_code_directory)));
|
||||
|
||||
if let Some(venv_command) = self.maybe_virtual_env_activation_command.as_deref() {
|
||||
command.push_str(" && ");
|
||||
command.push_str(venv_command);
|
||||
command.push_str(" ");
|
||||
}
|
||||
|
||||
command.push_str(" && ");
|
||||
|
||||
match self.executable_or_command {
|
||||
ExecutableOrCommand::Executable(ref executable) => {
|
||||
command.push_str(&path_to_string(executable));
|
||||
command.push_str(" ");
|
||||
}
|
||||
ExecutableOrCommand::Command(ref cmd) => {
|
||||
command.push_str(cmd);
|
||||
command.push_str(" ");
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Begin Python Args =====
|
||||
|
||||
command.push_str(" --input ");
|
||||
command.push_str(&path_to_string(args.input_text_file_path));
|
||||
|
||||
command.push_str(" --input-style-npz ");
|
||||
command.push_str(&path_to_string(args.input_embedding_file_path));
|
||||
|
||||
|
||||
command.push_str(" --output ");
|
||||
command.push_str(&path_to_string(args.output_file_name));
|
||||
|
||||
//TODO(KS): Make this a parameter
|
||||
command.push_str(" --vcsteps ");
|
||||
command.push_str(format!("{}", 20).as_str());
|
||||
|
||||
// ===== End Python Args =====
|
||||
|
||||
if let Some(docker_options) = self.maybe_docker_options.as_ref() {
|
||||
command = docker_options.to_command_string(&command);
|
||||
}
|
||||
|
||||
info!("Command: {:?}", command);
|
||||
|
||||
let command_parts = ["bash", "-c", &command];
|
||||
|
||||
let mut env_vars = Vec::new();
|
||||
|
||||
// Copy all environment variables from the parent process.
|
||||
// This is necessary to send all the kubernetes settings for Nvidia / CUDA.
|
||||
for (env_key, env_value) in env::vars() {
|
||||
if IGNORED_ENVIRONMENT_VARS.contains(&env_key) {
|
||||
continue;
|
||||
}
|
||||
env_vars.push((OsString::from(env_key), OsString::from(env_value)));
|
||||
}
|
||||
|
||||
let mut config = PopenConfig::default();
|
||||
|
||||
info!("stderr will be written to file: {:?}", args.stderr_output_file.as_ref());
|
||||
|
||||
let stderr_file = File::create(&args.stderr_output_file)?;
|
||||
config.stderr = Redirection::File(stderr_file);
|
||||
|
||||
if !env_vars.is_empty() {
|
||||
config.env = Some(env_vars);
|
||||
}
|
||||
|
||||
let mut p = Popen::create(&command_parts, config)?;
|
||||
|
||||
info!("Subprocess PID: {:?}", p.pid());
|
||||
|
||||
match self.maybe_execution_timeout {
|
||||
None => {
|
||||
let exit_status = p.wait()?;
|
||||
info!("Subprocess exit status: {:?}", exit_status);
|
||||
Ok(CommandExitStatus::from_exit_status(exit_status))
|
||||
}
|
||||
Some(timeout) => {
|
||||
info!("Executing with timeout: {:?}", &timeout);
|
||||
let exit_status = p.wait_timeout(timeout.clone())?;
|
||||
|
||||
match exit_status {
|
||||
None => {
|
||||
// NB: If the program didn't successfully terminate, kill it.
|
||||
info!(
|
||||
"Subprocess didn't end after timeout: {:?}; terminating...",
|
||||
&timeout
|
||||
);
|
||||
let _r = p.terminate()?;
|
||||
Ok(CommandExitStatus::Timeout)
|
||||
}
|
||||
Some(exit_status) => {
|
||||
info!("Subprocess timed wait exit status: {:?}", exit_status);
|
||||
Ok(CommandExitStatus::from_exit_status(exit_status))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StyleTTS2CreateEmbeddingCommand {
|
||||
/// Where the code lives
|
||||
root_code_directory: PathBuf,
|
||||
/// A single executable script or a much larger bash command.
|
||||
executable_or_command: ExecutableOrCommand,
|
||||
/// eg. `source python/bin/activate`
|
||||
maybe_virtual_env_activation_command: Option<String>,
|
||||
/// Optional default config file to use
|
||||
maybe_default_config_path: Option<PathBuf>,
|
||||
/// If this is run under Docker (eg. in development), these are the options.
|
||||
maybe_docker_options: Option<DockerOptions>,
|
||||
/// If the execution should be ended after a certain point.
|
||||
maybe_execution_timeout: Option<Duration>,
|
||||
/// Inference arg.
|
||||
/// --checkpoint_dir: optional location for checkpoints directory
|
||||
pub alternate_checkpoint_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl StyleTTS2CreateEmbeddingCommand {
|
||||
pub fn new<P: AsRef<Path>>(
|
||||
root_code_directory: P,
|
||||
executable_or_command: ExecutableOrCommand,
|
||||
maybe_virtual_env_activation_command: Option<&str>,
|
||||
maybe_default_config_path: Option<P>,
|
||||
maybe_docker_options: Option<DockerOptions>,
|
||||
maybe_execution_timeout: Option<Duration>,
|
||||
alternate_checkpoint_dir: Option<PathBuf>
|
||||
) -> Self {
|
||||
Self {
|
||||
root_code_directory: root_code_directory.as_ref().to_path_buf(),
|
||||
executable_or_command,
|
||||
maybe_virtual_env_activation_command: maybe_virtual_env_activation_command.map(|s|
|
||||
s.to_string()
|
||||
),
|
||||
maybe_default_config_path: maybe_default_config_path.map(|p| p.as_ref().to_path_buf()),
|
||||
maybe_docker_options,
|
||||
maybe_execution_timeout,
|
||||
alternate_checkpoint_dir,
|
||||
}
|
||||
}
|
||||
|
||||
// helper funciton to set up env
|
||||
pub fn from_env() -> AnyhowResult<Self> {
|
||||
// TODO: fix for now just for compilation
|
||||
let root_code_directory = easyenv::get_env_pathbuf_required(
|
||||
"STYLE_TTS2_INFERENCE_ROOT_DIRECTORY"
|
||||
)?;
|
||||
|
||||
let maybe_inference_command = easyenv::get_env_string_optional(
|
||||
"STYLE_TTS2_INFERENCE_COMMAND"
|
||||
);
|
||||
|
||||
// EVENTUALLY should remove the check
|
||||
// Optional, eg. `./infer.py`. Typically we'll use the command form instead.
|
||||
let maybe_inference_executable = easyenv::get_env_pathbuf_optional(
|
||||
"STYLE_TTS2_INFERENCE_EXECUTABLE"
|
||||
);
|
||||
|
||||
let executable_or_command = match maybe_inference_command {
|
||||
Some(command) => ExecutableOrCommand::Command(command),
|
||||
None =>
|
||||
match maybe_inference_executable {
|
||||
Some(executable) => ExecutableOrCommand::Executable(executable),
|
||||
None => {
|
||||
return Err(anyhow!("neither command nor executable passed"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let maybe_virtual_env_activation_command = easyenv::get_env_string_optional(
|
||||
"STYLE_TTS2_INFERENCE_MAYBE_VENV_COMMAND"
|
||||
);
|
||||
|
||||
let maybe_default_config_path = easyenv::get_env_pathbuf_optional(
|
||||
"STYLE_TTS2_INFERENCE_MAYBE_DEFAULT_CONFIG_PATH"
|
||||
);
|
||||
|
||||
let maybe_execution_timeout = easyenv::get_env_duration_seconds_optional("TIMEOUT_SECONDS");
|
||||
|
||||
// Probably for local
|
||||
let maybe_docker_options = easyenv
|
||||
::get_env_string_optional("STYLE_TTS2_INFERENCE_MAYBE_DOCKER_IMAGE")
|
||||
.map(|image_name| {
|
||||
DockerOptions {
|
||||
image_name,
|
||||
maybe_bind_mount: Some(DockerFilesystemMount::tmp_to_tmp()),
|
||||
maybe_environment_variables: None,
|
||||
maybe_gpu: Some(DockerGpu::All),
|
||||
}
|
||||
});
|
||||
|
||||
// Override for --checkpoint_dir at inference time
|
||||
let alternate_checkpoint_dir = easyenv::get_env_pathbuf_optional(
|
||||
"STYLE_TTS2_CHECKPOINT_PATH"
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
root_code_directory,
|
||||
executable_or_command,
|
||||
maybe_virtual_env_activation_command,
|
||||
maybe_default_config_path,
|
||||
maybe_docker_options,
|
||||
maybe_execution_timeout,
|
||||
alternate_checkpoint_dir,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn execute_inference<P: AsRef<Path>>(&self, args: CreateVoiceInferenceArgs<P>) -> CommandExitStatus {
|
||||
match self.do_execute_inference(args) {
|
||||
Ok(exit_status) => exit_status,
|
||||
Err(error) =>
|
||||
CommandExitStatus::FailureWithReason { reason: format!("error: {:?}", error) },
|
||||
}
|
||||
}
|
||||
|
||||
fn do_execute_inference<P: AsRef<Path>>(
|
||||
&self,
|
||||
args: CreateVoiceInferenceArgs<P>
|
||||
) -> AnyhowResult<CommandExitStatus> {
|
||||
let mut command = String::new();
|
||||
command.push_str(&format!("cd {}", path_to_string(&self.root_code_directory)));
|
||||
|
||||
if let Some(venv_command) = self.maybe_virtual_env_activation_command.as_deref() {
|
||||
command.push_str(" && ");
|
||||
command.push_str(venv_command);
|
||||
command.push_str(" ");
|
||||
}
|
||||
|
||||
command.push_str(" && ");
|
||||
|
||||
match self.executable_or_command {
|
||||
ExecutableOrCommand::Executable(ref executable) => {
|
||||
command.push_str(&path_to_string(executable));
|
||||
command.push_str(" ");
|
||||
}
|
||||
ExecutableOrCommand::Command(ref cmd) => {
|
||||
command.push_str(cmd);
|
||||
command.push_str(" ");
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Begin Python Args =====
|
||||
|
||||
command.push_str(" --output-style-npz ");
|
||||
command.push_str(&path_to_string(&args.output_embedding_path));
|
||||
|
||||
command.push_str(" --voice ");
|
||||
command.push_str(&args.audio_file);
|
||||
|
||||
command.push_str(" --vcsteps ");
|
||||
command.push_str(format!("{}", 20).as_str());
|
||||
|
||||
command.push_str(" --input ");
|
||||
command.push_str("/dev/null");
|
||||
|
||||
// ===== End Python Args =====
|
||||
|
||||
if let Some(docker_options) = self.maybe_docker_options.as_ref() {
|
||||
command = docker_options.to_command_string(&command);
|
||||
}
|
||||
|
||||
info!("Command: {:?}", command);
|
||||
|
||||
let command_parts = ["bash", "-c", &command];
|
||||
|
||||
let mut env_vars = Vec::new();
|
||||
|
||||
// Copy all environment variables from the parent process.
|
||||
// This is necessary to send all the kubernetes settings for Nvidia / CUDA.
|
||||
for (env_key, env_value) in env::vars() {
|
||||
if IGNORED_ENVIRONMENT_VARS.contains(&env_key) {
|
||||
continue;
|
||||
}
|
||||
env_vars.push((OsString::from(env_key), OsString::from(env_value)));
|
||||
}
|
||||
|
||||
let mut config = PopenConfig::default();
|
||||
|
||||
info!("stderr will be written to file: {:?}", args.stderr_output_file.as_ref());
|
||||
|
||||
let stderr_file = File::create(&args.stderr_output_file)?;
|
||||
config.stderr = Redirection::File(stderr_file);
|
||||
|
||||
if !env_vars.is_empty() {
|
||||
config.env = Some(env_vars);
|
||||
}
|
||||
|
||||
let mut p = Popen::create(&command_parts, config)?;
|
||||
|
||||
info!("Subprocess PID: {:?}", p.pid());
|
||||
|
||||
match self.maybe_execution_timeout {
|
||||
None => {
|
||||
let exit_status = p.wait()?;
|
||||
info!("Subprocess exit status: {:?}", exit_status);
|
||||
Ok(CommandExitStatus::from_exit_status(exit_status))
|
||||
}
|
||||
Some(timeout) => {
|
||||
info!("Executing with timeout: {:?}", &timeout);
|
||||
let exit_status = p.wait_timeout(timeout.clone())?;
|
||||
|
||||
match exit_status {
|
||||
None => {
|
||||
// NB: If the program didn't successfully terminate, kill it.
|
||||
info!(
|
||||
"Subprocess didn't end after timeout: {:?}; terminating...",
|
||||
&timeout
|
||||
);
|
||||
let _r = p.terminate()?;
|
||||
Ok(CommandExitStatus::Timeout)
|
||||
}
|
||||
Some(exit_status) => {
|
||||
info!("Subprocess timed wait exit status: {:?}", exit_status);
|
||||
Ok(CommandExitStatus::from_exit_status(exit_status))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
use anyhow::anyhow;
|
||||
|
||||
use mysql_queries::payloads::generic_inference_args::generic_inference_args::{
|
||||
InferenceCategoryAbbreviated,
|
||||
PolymorphicInferenceArgs,
|
||||
};
|
||||
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
|
||||
|
||||
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
|
||||
|
||||
pub enum JobType {
|
||||
Create,
|
||||
Inference
|
||||
}
|
||||
// determines the job type voice inference or voice creation
|
||||
pub struct JobArgs {
|
||||
pub voice_token: Option<String>, // infers
|
||||
pub voice_dataset_token: Option<String>, // creates
|
||||
pub job_type: JobType
|
||||
}
|
||||
|
||||
pub fn validate_job(job: &AvailableInferenceJob) -> Result<JobArgs, ProcessSingleJobError> {
|
||||
let inference_category = job.maybe_inference_args
|
||||
.as_ref()
|
||||
.map(|args| args.inference_category)
|
||||
.flatten();
|
||||
|
||||
match inference_category {
|
||||
Some(InferenceCategoryAbbreviated::TextToSpeech) => {}
|
||||
Some(category) => {
|
||||
return Err(
|
||||
ProcessSingleJobError::from_anyhow_error(
|
||||
anyhow!("wrong inference category for job: {:?}", category)
|
||||
)
|
||||
);
|
||||
}
|
||||
None => {
|
||||
return Err(
|
||||
ProcessSingleJobError::from_anyhow_error(anyhow!("no inference category for job!"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let inference_args = job.maybe_inference_args
|
||||
.as_ref()
|
||||
.map(|args| args.args.as_ref())
|
||||
.flatten();
|
||||
|
||||
let inference_args = match inference_args {
|
||||
Some(args) => args,
|
||||
None => {
|
||||
return Err(
|
||||
ProcessSingleJobError::from_anyhow_error(anyhow!("no inference args for job!"))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let ttsArgs = match inference_args {
|
||||
PolymorphicInferenceArgs::Tts(inference_args) => inference_args,
|
||||
_ => {
|
||||
return Err(
|
||||
ProcessSingleJobError::from_anyhow_error(anyhow!("wrong inner args for job!"))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(voice_token) = &ttsArgs.voice_token {
|
||||
return Ok::<JobArgs, ProcessSingleJobError>(JobArgs {
|
||||
voice_token: Some(voice_token.clone()),
|
||||
voice_dataset_token: None,
|
||||
job_type: JobType::Inference
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(dataset_token) = &ttsArgs.dataset_token {
|
||||
return Ok::<JobArgs,ProcessSingleJobError>(JobArgs {
|
||||
voice_token: None,
|
||||
voice_dataset_token: Some(dataset_token.clone()),
|
||||
job_type: JobType::Create
|
||||
});
|
||||
}
|
||||
|
||||
Err(ProcessSingleJobError::from_anyhow_error(anyhow!("Missing Voice Token!")))
|
||||
}
|
||||
+2
@@ -11,6 +11,7 @@ use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use cloud_storage::bucket_client::BucketClient;
|
||||
use cloud_storage::bucket_path_unifier::BucketPathUnifier;
|
||||
use enums::by_table::generic_inference_jobs::inference_result_type::InferenceResultType;
|
||||
use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType;
|
||||
use filesys::file_size::file_size;
|
||||
use hashing::sha256::sha256_hash_file::sha256_hash_file;
|
||||
use mysql_queries::queries::media_files::create::insert_media_file_from_zero_shot_tts::insert_media_file_from_zero_shot;
|
||||
@@ -224,6 +225,7 @@ pub async fn process_inference_voice(
|
||||
let (media_file_token, id) = insert_media_file_from_zero_shot(InsertArgs {
|
||||
pool: &args.job_dependencies.db.mysql_pool,
|
||||
job: &job,
|
||||
origin_model_type: MediaFileOriginModelType::VallEX,
|
||||
maybe_mime_type: Some(&MIME_TYPE),
|
||||
file_size_bytes,
|
||||
sha256_checksum: &file_checksum,
|
||||
|
||||
@@ -2,6 +2,7 @@ use enums::by_table::generic_inference_jobs::inference_model_type::InferenceMode
|
||||
use errors::AnyhowResult;
|
||||
|
||||
use crate::job::job_types::lipsync::sad_talker::sad_talker_dependencies::SadTalkerDependencies;
|
||||
use crate::job::job_types::tts::styletts2::styletts2_dependencies::StyleTTS2Dependencies;
|
||||
use crate::job::job_types::tts::tacotron2_v2_early_fakeyou::tacotron2_dependencies::Tacotron2Dependencies;
|
||||
use crate::job::job_types::tts::vall_e_x::vall_e_x_dependencies::VallExDependencies;
|
||||
use crate::job::job_types::tts::vits::vits_dependencies::VitsDependencies;
|
||||
@@ -20,6 +21,7 @@ pub struct JobSpecificDependencies {
|
||||
pub maybe_vits_dependencies: Option<VitsDependencies>,
|
||||
pub maybe_rerender_dependencies: Option<RerenderDependencies>,
|
||||
pub maybe_mocapnet_dependencies: Option<MocapNetDependencies>,
|
||||
pub maybe_styletts2_dependencies: Option<StyleTTS2Dependencies>,
|
||||
}
|
||||
|
||||
impl JobSpecificDependencies {
|
||||
@@ -33,6 +35,7 @@ impl JobSpecificDependencies {
|
||||
let mut maybe_vits_dependencies = None;
|
||||
let mut maybe_rerender_dependencies = None;
|
||||
let mut maybe_mocapnet_dependencies = None;
|
||||
let mut maybe_styletts2_dependencies = None;
|
||||
|
||||
if scoped_execution.can_run_job(InferenceModelType::RvcV2) {
|
||||
print_with_space("Setting RVCv2 dependencies...");
|
||||
@@ -59,6 +62,11 @@ impl JobSpecificDependencies {
|
||||
maybe_vall_e_x_dependencies = Some(VallExDependencies::setup()?);
|
||||
}
|
||||
|
||||
if scoped_execution.can_run_job(InferenceModelType::StyleTTS2) {
|
||||
print_with_space("Setting StyleTTS2 dependencies...");
|
||||
maybe_styletts2_dependencies = Some(StyleTTS2Dependencies::setup()?);
|
||||
}
|
||||
|
||||
if scoped_execution.can_run_job(InferenceModelType::Vits) {
|
||||
print_with_space("Setting Vits dependencies...");
|
||||
maybe_vits_dependencies = Some(VitsDependencies::setup()?);
|
||||
@@ -82,7 +90,8 @@ impl JobSpecificDependencies {
|
||||
maybe_vall_e_x_dependencies,
|
||||
maybe_vits_dependencies,
|
||||
maybe_rerender_dependencies,
|
||||
maybe_mocapnet_dependencies
|
||||
maybe_mocapnet_dependencies,
|
||||
maybe_styletts2_dependencies,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ actix_helpers = { path = "../../../lib/actix_helpers" }
|
||||
bootstrap = { path = "../../../lib/bootstrap" }
|
||||
buckets = { path = "../../../_types/buckets" }
|
||||
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
|
||||
composite_identifiers = { path = "../../../_types/composite_identifiers" }
|
||||
config = { path = "../../../lib/deprecated/config" }
|
||||
container_common = { path = "../../../lib/deprecated/container_common" }
|
||||
cookies = { path = "../../../lib/cookies" }
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
|
||||
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
|
||||
use mysql_queries::queries::user_bookmarks::batch_get_user_bookmarks::{batch_get_user_bookmarks, BatchUserBookmark};
|
||||
use mysql_queries::queries::users::user_bookmarks::batch_get_user_bookmarks::{batch_get_user_bookmarks, BatchUserBookmark};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
|
||||
+4
-4
@@ -16,10 +16,10 @@ use utoipa::ToSchema;
|
||||
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
|
||||
use mysql_queries::queries::entity_stats::stats_entity_token::StatsEntityToken;
|
||||
use mysql_queries::queries::entity_stats::upsert_entity_stats_on_bookmark_event::{BookmarkAction, upsert_entity_stats_on_bookmark_event, UpsertEntityStatsArgs};
|
||||
use mysql_queries::queries::user_bookmarks::upsert_user_bookmark::{upsert_user_bookmark, CreateUserBookmarkArgs};
|
||||
use mysql_queries::queries::user_bookmarks::get_total_bookmark_count_for_entity::get_total_bookmark_count_for_entity;
|
||||
use mysql_queries::queries::user_bookmarks::get_user_bookmark_transactional_locking::{BookmarkIdentifier, get_user_bookmark_transactional_locking};
|
||||
use mysql_queries::queries::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
use mysql_queries::queries::users::user_bookmarks::upsert_user_bookmark::{upsert_user_bookmark, CreateUserBookmarkArgs};
|
||||
use mysql_queries::queries::users::user_bookmarks::get_total_bookmark_count_for_entity::get_total_bookmark_count_for_entity;
|
||||
use mysql_queries::queries::users::user_bookmarks::get_user_bookmark_transactional_locking::{BookmarkIdentifier, get_user_bookmark_transactional_locking};
|
||||
use mysql_queries::queries::users::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
|
||||
+2
-2
@@ -16,8 +16,8 @@ use utoipa::ToSchema;
|
||||
|
||||
use mysql_queries::queries::entity_stats::stats_entity_token::StatsEntityToken;
|
||||
use mysql_queries::queries::entity_stats::upsert_entity_stats_on_bookmark_event::{BookmarkAction, upsert_entity_stats_on_bookmark_event, UpsertEntityStatsArgs};
|
||||
use mysql_queries::queries::user_bookmarks::delete_user_bookmark::delete_user_bookmark;
|
||||
use mysql_queries::queries::user_bookmarks::get_user_bookmark_transactional_locking::{BookmarkIdentifier, get_user_bookmark_transactional_locking};
|
||||
use mysql_queries::queries::users::user_bookmarks::delete_user_bookmark::delete_user_bookmark;
|
||||
use mysql_queries::queries::users::user_bookmarks::get_user_bookmark_transactional_locking::{BookmarkIdentifier, get_user_bookmark_transactional_locking};
|
||||
use tokens::tokens::user_bookmarks::UserBookmarkToken;
|
||||
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@ use log::warn;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
|
||||
use mysql_queries::queries::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
use mysql_queries::queries::user_bookmarks::list_user_bookmarks_for_entity::list_user_bookmarks_for_entity;
|
||||
use mysql_queries::queries::users::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken;
|
||||
use mysql_queries::queries::users::user_bookmarks::list_user_bookmarks_for_entity::list_user_bookmarks_for_entity;
|
||||
use tokens::tokens::user_bookmarks::UserBookmarkToken;
|
||||
|
||||
use crate::http_server::common_responses::user_details_lite::{DefaultAvatarInfo, UserDetailsLight};
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ use chrono::{DateTime, Utc};
|
||||
use log::warn;
|
||||
|
||||
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
|
||||
use mysql_queries::queries::user_bookmarks::list_user_bookmarks::{ list_user_bookmarks_by_maybe_entity_type};
|
||||
use mysql_queries::queries::users::user_bookmarks::list_user_bookmarks::{ list_user_bookmarks_by_maybe_entity_type};
|
||||
use tokens::tokens::user_bookmarks::UserBookmarkToken;
|
||||
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::by_table::model_weights::weights_category::WeightsCategory;
|
||||
use enums::by_table::model_weights::weights_types::WeightsType;
|
||||
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
|
||||
use mysql_queries::queries::user_bookmarks::list_user_bookmarks::{list_user_bookmarks_by_maybe_entity_type, ListUserBookmarksForUserArgs};
|
||||
use mysql_queries::queries::users::user_bookmarks::list_user_bookmarks::{list_user_bookmarks_by_maybe_entity_type, ListUserBookmarksForUserArgs};
|
||||
use tokens::tokens::user_bookmarks::UserBookmarkToken;
|
||||
|
||||
use crate::http_server::common_responses::pagination_page::PaginationPage;
|
||||
|
||||
+1
-1
@@ -189,7 +189,7 @@ pub async fn enqueue_tts_request(
|
||||
let query_result = insert_generic_inference_job(InsertGenericInferenceArgs {
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
inference_category: InferenceCategory::TextToSpeech,
|
||||
maybe_model_type: Some(InferenceModelType::VallEX), // NB: Model is static during inference
|
||||
maybe_model_type: Some(InferenceModelType::StyleTTS2), // NB: Model is static during inference
|
||||
maybe_model_token: None, // NB: Model is static during inference
|
||||
maybe_input_source_token: None,
|
||||
maybe_input_source_token_type: None,
|
||||
|
||||
+1
-1
@@ -177,7 +177,7 @@ pub async fn create_voice_handler(
|
||||
let query_result = insert_generic_inference_job(InsertGenericInferenceArgs {
|
||||
uuid_idempotency_token: &request.uuid_idempotency_token,
|
||||
inference_category: InferenceCategory::TextToSpeech,
|
||||
maybe_model_type: Some(InferenceModelType::VallEX), // NB: Model is static during inference
|
||||
maybe_model_type: Some(InferenceModelType::StyleTTS2), // NB: Model is static during inference
|
||||
maybe_model_token: None, // NB: Model is static during inference
|
||||
maybe_input_source_token: None,
|
||||
maybe_input_source_token_type: None,
|
||||
|
||||
Reference in New Issue
Block a user