Compare commits

...
Author SHA1 Message Date
ArEnSc cecd3dd6df Update .env 2024-01-17 18:03:20 -05:00
ArEnSc 7b90b11ce6 Fixed build issues ready to merge to master after review. 2024-01-17 17:33:55 -05:00
ArEnSc fc154c4a31 Merge branch 'master' into feature/image_generation 2024-01-17 15:22:53 -05:00
ArEnSc 24818f8ecf Ok this builds inference job. Fixes still needed 2024-01-17 03:06:09 -05:00
Brandon Thomas 2d91504eb5 test to show usage 2024-01-17 02:21:23 -05:00
Brandon Thomas 67a8bb739b Merge pull request #279 from storytold/brandon-insert-batch-generations
insert batch generation records
2024-01-16 23:01:10 -05:00
Brandon Thomas 33613cb5d8 insert batch generation records 2024-01-16 23:00:17 -05:00
Brandon Thomas 2487a8d646 refactor query modules 2024-01-16 21:03:56 -05:00
Brandon Thomas 1771e1dffe fix lint/test failures 2024-01-16 20:52:11 -05:00
Brandon Thomas 2d4d2c667f reference schemas 2024-01-16 19:49:08 -05:00
Brandon Thomas 0c99852dfd Merge pull request #278 from storytold/brandon-new-tables
new tables: idempotency_tokens, batch_generations; new column
2024-01-16 19:11:36 -05:00
Brandon Thomas 7cc310764e new tables: idempotency_tokens, batch_generations; new column 2024-01-16 19:10:23 -05:00
Kasisnu b66b04d86c Merge pull request #277 from storytold/jan-16-mithoo
bugfix: ignore more than one samples for zs
2024-01-16 13:06:48 +05:30
Kasisnu 18d46a0f57 bugfix: ignore more than one samples for zs 2024-01-16 13:06:29 +05:30
Kasisnu 97795fac22 Merge pull request #276 from storytold/jan-16-mithoo
codegen & styletts2 downloader cleanup
2024-01-16 11:40:30 +05:30
Kasisnu cec0cfc907 remove misleading downloaders from styletts2 2024-01-16 11:39:46 +05:30
Kasisnu d516058802 codegen 2024-01-16 11:36:28 +05:30
Kasisnu b77810cd29 Merge pull request #268 from storytold/jan-04-mithoo
adds styletts2 as voice designer model
2024-01-16 10:52:06 +05:30
Brandon Thomas 2507cf0332 analysis of AI streamer bug reports 2024-01-15 21:48:45 -05:00
Brandon Thomas cf4c335d41 migration tool for tts models 2024-01-15 21:27:44 -05:00
Kasisnu 5e98049c50 switch from vallex -> styletts2 for zero shot 2024-01-15 17:44:29 +05:30
ArEnSc eebedbce07 Runs inference but the checkpoint is corrupt? 2024-01-14 18:39:33 -05:00
ArEnSc f4497a4aee Fixed build errors inference job should be good to run now. 2024-01-13 02:27:12 -05:00
ArEnSc 447a457dff image gen should build. 2024-01-13 00:26:51 -05:00
Kasisnu ea3a6d3c0b pass appropriate arguments to styletts inference command 2024-01-12 17:58:12 +05:30
Kasisnu c6a6676f08 adds styletts2 as voice designer model 2024-01-12 14:53:55 +05:30
ArEnSc 7f65014d6d Almost done inference. 2024-01-12 02:33:36 -05:00
ArEnSc c4681937b3 Image generation on it's way... 2024-01-12 00:19:09 -05:00
ArEnSc 2d52ac0fa9 Removed some components,
also added the command env variables, now going to run inference.
2024-01-11 21:24:38 -05:00
ArEnSc 8709fa9c8a enqueue is there. testing inference side. 2024-01-11 16:43:04 -05:00
ArEnSc 4250469fd3 enqueue_image_generation
fixes added checks and changes to the interface.
Will document the api as well.
2024-01-10 20:35:53 -05:00
ArEnSc e5ed4d986f downloading works. 2024-01-05 17:20:21 -05:00
ArEnSc ea84b009a8 Fix compile issues 2024-01-05 16:01:42 -05:00
ArEnSc aa202172a2 uploading models and then reseed. 2024-01-04 21:46:25 -05:00
ArEnSc 1b4e3cccb5 Fixed over sight in the cloud file remote_file_manager,
where the metadata didn't include the bucket path for the file.
Then we didn't pass the correct upload path with the file name
2024-01-04 17:28:00 -05:00
ArEnSc 796e8b5aee Compile fix switching to rust rover since less mapping is required now. 2024-01-04 09:57:45 -05:00
ArEnSc 1614cba2f8 properties added 2024-01-03 23:16:00 -05:00
ArEnSc dbe989c5a1 rebuilds on the routes in storyteller web. 2024-01-03 22:32:25 -05:00
ArEnSc a2cad0b289 getting the args for inference working. 2024-01-03 14:57:49 -05:00
ArEnSc 74db1683b1 unwraped the args 2024-01-03 11:01:37 -05:00
ArEnSc 6e5e326d0e Ok it builds time to fillout the inference command. 2024-01-02 23:06:12 -05:00
ArEnSc bcd806e7f0 Some boilerplate done. 2024-01-02 17:50:22 -05:00
ArEnSc 20b95c090e Now testing the end point. 2024-01-02 16:16:51 -05:00
ArEnSc 87b2f4a113 Args are tested 2024-01-02 12:55:34 -05:00
ArEnSc a7799be70e Merge branch 'master' into feature/image_generation 2024-01-02 12:36:18 -05:00
ArEnSc 7d858d2c19 Payload almost ready need to migrate the db and write unit tests. 2024-01-02 12:24:13 -05:00
ArEnSc 733c24aebb Working on enqueuing image generation 2024-01-02 10:50:24 -05:00
122 changed files with 4802 additions and 128 deletions
@@ -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"
}
@@ -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
+22
View File
@@ -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",
@@ -3059,6 +3078,7 @@ dependencies = [
"enums",
"errors",
"filesys",
"google_drive_common",
"hashing",
"hostname",
"hyper",
@@ -3857,6 +3877,7 @@ dependencies = [
"anyhow",
"buckets",
"chrono",
"composite_identifiers",
"config",
"container_common",
"dotenv",
@@ -6006,6 +6027,7 @@ dependencies = [
"buckets",
"chrono",
"cloud_storage",
"composite_identifiers",
"config",
"container_common",
"cookies",
+1
View File
@@ -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",
+14
View File
@@ -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;
+5
View File
@@ -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"
@@ -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;
@@ -29,6 +29,8 @@ pub enum InferenceCategory {
#[serde(rename = "video_filter")]
VideoFilter,
#[serde(rename = "image_generation")]
ImageGeneration,
#[serde(rename = "mocap")]
Mocap,
}
@@ -45,6 +47,7 @@ impl InferenceCategory {
Self::TextToSpeech => "text_to_speech",
Self::VoiceConversion => "voice_conversion",
Self::VideoFilter => "video_filter",
Self::ImageGeneration => "image_generation",
Self::Mocap => "mocap",
}
}
@@ -55,6 +58,7 @@ impl InferenceCategory {
"text_to_speech" => Ok(Self::TextToSpeech),
"voice_conversion" => Ok(Self::VoiceConversion),
"video_filter" => Ok(Self::VideoFilter),
"image_generation" => Ok(Self::ImageGeneration),
"mocap" => Ok(Self::Mocap),
_ => Err(format!("invalid value: {:?}", value)),
}
@@ -68,6 +72,7 @@ impl InferenceCategory {
Self::TextToSpeech,
Self::VoiceConversion,
Self::VideoFilter,
Self::ImageGeneration,
Self::Mocap,
])
}
@@ -84,6 +89,7 @@ mod tests {
assert_serialization(InferenceCategory::TextToSpeech, "text_to_speech");
assert_serialization(InferenceCategory::VoiceConversion, "voice_conversion");
assert_serialization(InferenceCategory::VideoFilter, "video_filter");
assert_serialization(InferenceCategory::ImageGeneration, "image_generation");
assert_serialization(InferenceCategory::Mocap, "mocap")
}
@@ -93,6 +99,7 @@ mod tests {
assert_eq!(InferenceCategory::TextToSpeech.to_str(), "text_to_speech");
assert_eq!(InferenceCategory::VoiceConversion.to_str(), "voice_conversion");
assert_eq!(InferenceCategory::VideoFilter.to_str(), "video_filter");
assert_eq!(InferenceCategory::ImageGeneration.to_str(), "image_generation");
assert_eq!(InferenceCategory::Mocap.to_str(), "mocap");
}
@@ -102,6 +109,7 @@ mod tests {
assert_eq!(InferenceCategory::from_str("text_to_speech").unwrap(), InferenceCategory::TextToSpeech);
assert_eq!(InferenceCategory::from_str("voice_conversion").unwrap(), InferenceCategory::VoiceConversion);
assert_eq!(InferenceCategory::from_str("video_filter").unwrap(), InferenceCategory::VideoFilter);
assert_eq!(InferenceCategory::from_str("image_generation").unwrap(), InferenceCategory::ImageGeneration);
assert_eq!(InferenceCategory::from_str("mocap").unwrap(), InferenceCategory::Mocap);
}
@@ -114,6 +122,7 @@ mod tests {
assert_eq!(variants.pop_first(), Some(InferenceCategory::TextToSpeech));
assert_eq!(variants.pop_first(), Some(InferenceCategory::VoiceConversion));
assert_eq!(variants.pop_first(), Some(InferenceCategory::VideoFilter));
assert_eq!(variants.pop_first(), Some(InferenceCategory::ImageGeneration));
assert_eq!(variants.pop_first(), Some(InferenceCategory::Mocap));
assert_eq!(variants.pop_first(), None);
@@ -33,8 +33,12 @@ pub enum InferenceModelType {
VallEX,
#[serde(rename = "rerender_a_video")]
RerenderAVideo,
#[serde(rename = "stable_diffusion")]
StableDiffusion,
#[serde(rename = "mocap_net")]
MocapNet,
#[serde(rename = "styletts2")]
StyleTTS2,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -52,7 +56,9 @@ impl InferenceModelType {
Self::Vits => "vits",
Self::VallEX => "vall_e_x",
Self::RerenderAVideo => "rerender_a_video",
Self::StableDiffusion => "stable_diffusion",
Self::MocapNet => "mocap_net",
Self::StyleTTS2 => "styletts2",
}
}
@@ -65,7 +71,9 @@ impl InferenceModelType {
"vits" => Ok(Self::Vits),
"vall_e_x" => Ok(Self::VallEX),
"rerender_a_video" => Ok(Self::RerenderAVideo),
"stable_diffusion" => Ok(Self::StableDiffusion),
"mocap_net" => Ok(Self::MocapNet),
"styletts2" => Ok(Self::StyleTTS2),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -81,7 +89,9 @@ impl InferenceModelType {
InferenceModelType::Vits,
InferenceModelType::VallEX,
InferenceModelType::RerenderAVideo,
InferenceModelType::StableDiffusion,
InferenceModelType::MocapNet,
InferenceModelType::StyleTTS2,
])
}
}
@@ -100,7 +110,9 @@ mod tests {
assert_serialization(InferenceModelType::Vits, "vits");
assert_serialization(InferenceModelType::VallEX, "vall_e_x");
assert_serialization(InferenceModelType::RerenderAVideo, "rerender_a_video");
assert_serialization(InferenceModelType::StableDiffusion, "stable_diffusion");
assert_serialization(InferenceModelType::MocapNet, "mocap_net");
assert_serialization(InferenceModelType::StyleTTS2, "styletts2");
}
#[test]
@@ -112,7 +124,9 @@ mod tests {
assert_eq!(InferenceModelType::Vits.to_str(), "vits");
assert_eq!(InferenceModelType::VallEX.to_str(), "vall_e_x");
assert_eq!(InferenceModelType::RerenderAVideo.to_str(), "rerender_a_video");
assert_eq!(InferenceModelType::StableDiffusion.to_str(), "stable_diffusion");
assert_eq!(InferenceModelType::MocapNet.to_str(), "mocap_net");
assert_eq!(InferenceModelType::StyleTTS2.to_str(), "styletts2");
}
#[test]
@@ -124,7 +138,9 @@ mod tests {
assert_eq!(InferenceModelType::from_str("vits").unwrap(), InferenceModelType::Vits);
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("stable_diffusion").unwrap(), InferenceModelType::StableDiffusion);
assert_eq!(InferenceModelType::from_str("mocap_net").unwrap(), InferenceModelType::MocapNet);
assert_eq!(InferenceModelType::from_str("styletts2").unwrap(), InferenceModelType::StyleTTS2);
}
#[test]
@@ -132,6 +148,7 @@ mod tests {
// 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));
@@ -139,7 +156,9 @@ mod tests {
assert_eq!(variants.pop_first(), Some(InferenceModelType::Vits));
assert_eq!(variants.pop_first(), Some(InferenceModelType::VallEX));
assert_eq!(variants.pop_first(), Some(InferenceModelType::RerenderAVideo));
assert_eq!(variants.pop_first(), Some(InferenceModelType::StableDiffusion));
assert_eq!(variants.pop_first(), Some(InferenceModelType::MocapNet));
assert_eq!(variants.pop_first(), Some(InferenceModelType::StyleTTS2));
assert_eq!(variants.pop_first(), None);
// Generated check
@@ -29,6 +29,9 @@ pub enum InferenceResultType {
#[serde(rename = "zs_voice_embedding")]
ZeroShotVoiceEmbedding,
#[serde(rename = "upload_model")]
UploadModel
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -43,6 +46,7 @@ impl InferenceResultType {
Self::TextToSpeech => "text_to_speech",
Self::VoiceConversion => "voice_conversion",
Self::ZeroShotVoiceEmbedding => "zs_voice_embedding",
Self::UploadModel => "upload_model",
}
}
@@ -52,6 +56,7 @@ impl InferenceResultType {
"text_to_speech" => Ok(Self::TextToSpeech),
"voice_conversion" => Ok(Self::VoiceConversion),
"zs_voice_embedding" => Ok(Self::ZeroShotVoiceEmbedding),
"upload_model" => Ok(Self::UploadModel),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -64,6 +69,7 @@ impl InferenceResultType {
InferenceResultType::TextToSpeech,
InferenceResultType::VoiceConversion,
InferenceResultType::ZeroShotVoiceEmbedding,
InferenceResultType::UploadModel,
])
}
}
@@ -79,6 +85,7 @@ mod tests {
assert_serialization(InferenceResultType::TextToSpeech, "text_to_speech");
assert_serialization(InferenceResultType::VoiceConversion, "voice_conversion");
assert_serialization(InferenceResultType::ZeroShotVoiceEmbedding, "zs_voice_embedding");
assert_serialization(InferenceResultType::UploadModel, "upload_model");
}
#[test]
@@ -87,6 +94,7 @@ mod tests {
assert_eq!(InferenceResultType::TextToSpeech.to_str(), "text_to_speech");
assert_eq!(InferenceResultType::VoiceConversion.to_str(), "voice_conversion");
assert_eq!(InferenceResultType::ZeroShotVoiceEmbedding.to_str(), "zs_voice_embedding");
assert_eq!(InferenceResultType::UploadModel.to_str(), "upload_model");
}
#[test]
@@ -95,6 +103,7 @@ mod tests {
assert_eq!(InferenceResultType::from_str("text_to_speech").unwrap(), InferenceResultType::TextToSpeech);
assert_eq!(InferenceResultType::from_str("voice_conversion").unwrap(), InferenceResultType::VoiceConversion);
assert_eq!(InferenceResultType::from_str("zs_voice_embedding").unwrap(), InferenceResultType::ZeroShotVoiceEmbedding);
assert_eq!(InferenceResultType::from_str("upload_model").unwrap(), InferenceResultType::UploadModel);
}
#[test]
@@ -106,6 +115,7 @@ mod tests {
assert_eq!(variants.pop_first(), Some(InferenceResultType::TextToSpeech));
assert_eq!(variants.pop_first(), Some(InferenceResultType::VoiceConversion));
assert_eq!(variants.pop_first(), Some(InferenceResultType::ZeroShotVoiceEmbedding));
assert_eq!(variants.pop_first(), Some(InferenceResultType::UploadModel));
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);
}
}
+2
View File
@@ -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);
}
}
+9 -1
View File
@@ -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 -1
View File
@@ -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;
+6 -5
View File
@@ -8,12 +8,12 @@ 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::model_weights::test_seed_weights_files;
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;
@@ -61,9 +61,10 @@ pub async fn main() -> AnyhowResult<()> {
seed_tts_tacotron2(&pool, maybe_bucket_clients.as_ref()).await?;
// should seed the weights with a few files for hanashi
seed_weights_files(&pool, maybe_bucket_clients.as_ref()).await?;
info!("Done!");
//seed_weights_files(&pool, maybe_bucket_clients.as_ref()).await?;
//println!("TESTING DOWLOAD");
//test_seed_weights_files().await?;
//info!("Done!");
Ok(())
}
@@ -1,7 +1,14 @@
use std::path::PathBuf;
use std::vec;
use log::info;
use sqlx::{MySql, Pool};
use cloud_storage::remote_file_manager::remote_cloud_file_manager::RemoteCloudFileClient;
use cloud_storage::remote_file_manager::remote_cloud_bucket_details::RemoteCloudBucketDetails;
use cloud_storage::remote_file_manager::weights_descriptor;
use cloud_storage::remote_file_manager::weights_descriptor::{WeightsLoRADescriptor, WeightsSD15Descriptor};
use easyenv::from_filename;
use enums::by_table::model_weights::{
weights_category::WeightsCategory,
@@ -10,12 +17,50 @@ use enums::by_table::model_weights::{
use enums::common::visibility::Visibility;
use errors::{anyhow, AnyhowResult};
use mysql_queries::queries::model_weights::create::create_weight::{create_weight, CreateModelWeightsArgs};
use mysql_queries::queries::users::user::create_account::create_account;
use mysql_queries::queries::users::user::get_user_token_by_username::get_user_token_by_username;
use storyteller_root::get_seed_tool_data_root;
use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::users::UserToken;
use crate::seeding::users::HANASHI_USERNAME;
pub async fn test_seed_weights_files() -> AnyhowResult<()> {
let seed_path = PathBuf::from("/storyteller/root/custom-seed-tool-data");
let remote_cloud_file_client = RemoteCloudFileClient::get_remote_cloud_file_client().await;
let remote_cloud_file_client = match remote_cloud_file_client {
Ok(res) => {
res
}
Err(_) => {
return Err(anyhow!("failed to get remote cloud file client"));
}
};
let mut path_dl1 = seed_path.clone();
path_dl1.push("downloads/loRA");
let mut path_dl2 = seed_path.clone();
path_dl2.push("downloads/checkpoint");
let bucket_details1 = RemoteCloudBucketDetails {
object_hash: String::from("apa0ej6es8d3ss2gwtf1cghge35qn9tn"),
prefix: String::from("sd15"),
suffix: String::from("safetensors"),
};
let bucket_details2 = RemoteCloudBucketDetails {
object_hash: String::from("27kz11et18fargyyxbj66ntfn621k9d3"),
prefix: String::from("loRA"),
suffix: String::from("safetensors"),
};
remote_cloud_file_client.download_file(bucket_details1, String::from("./checkpoint")).await?;
remote_cloud_file_client.download_file(bucket_details2, String::from("./loRA")).await?;
Ok(())
}
pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserToken) -> AnyhowResult<()> {
let sd1_5_markdown_description = r#"
# Dragonfruit AI Models Update and Workflow
@@ -330,6 +375,328 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
}
Ok(())
}
pub async fn seed_weights_for_testing_inference(mysql_pool: &Pool<MySql>, user_token: UserToken) -> AnyhowResult<()>{
let miyhoyo_description = r#"一个面向米哈游角色的模型合集~A collection for MIHOYO Characters~
对应人物tag记录在版本信息里,可在右侧“About this version”选项中查看。The corresponding character prompts are recorded in the version information and can be viewed in the About this version option on the right.
打开目录寻找最爱~(附带链接,点击直达)~Open the catalog, find your favorites~ (with links, click to go directly):
崩坏三 Honkai Impact 3rd:
梅比乌斯- Mobius
琪亚娜-Kiana:琪亚娜 合集-Collections for Kiana (Honkai Impact 3rd)
布洛妮娅-bronya:布洛妮娅 合集-Collections for Bronya (Honkai Impact 3rd | Honkai Star Rail)
芽衣:雷电芽衣 合集-Collections for Raiden Mei (Honkai Impact 3rd)
西琳-Sirin
魔法少女-Miracle ☆ Magical Girl
赤鸢-Jingwei
丹朱苍玄-Nuwa/Fuxi
李素裳-Li Sushang
玉骑士·月痕/锦岁玉团-Jade Knight/Auspicious Dazzle
卡莲-kallen
第六夜想曲-Sixth Serenade
符华-Fu hua
崩落
云墨丹心-Azure Empyrea
八重樱-Yae Sakura
逆神巫女-Gyakushinn Miko
御神装·勿忘-Goushinnso Memento
信花舞伎-Blooming Maiko
希尔-Seele
生死律者-Herrscher of Rebirth
栖于永夜-Eternal Night's Embrace
德莉莎-Theresa:德莉莎 合集-Collections for Theresa (Honkai Impact 3rd)
月下誓约-Lunar Vow: Crimson Love
月下初拥-Luna Kindred
花裳月纱-Rosy Bridesmaid
格蕾修-Griseo
繁星·绘世之卷-Starry Impression
天行·绘星之卷-Cosmic Expression
画中的童话-Maroon Riding Hood
原神 Genshin:
凝光-Ningguang
申鹤-Shenhe
妮露
诺艾尔
神里绫华(包含礼装)
甘雨(包含喜茶联动礼装)-Ganyu
胡桃(包含森罗联动礼装)-Hutao
琳妮特-Lynette
绮良良-Kirara
坎蒂丝-Candace
芙宁娜-Furina
芙宁娜/芙卡洛斯-Furina/Focalors
克洛琳德-Clorinde
娜维娅-Navia
丝柯克-Skirk
夏洛蒂-Charlotte
归终-Guizhong
闲云-Xianyun
千织-Chiori
夏沃蕾-Chevreuse
崩坏:星穹铁道 Honkai Star Rand:
驭空-Yukong
银狼-Silver Wolf
符玄-Fu Xuan
李素裳-Sushang
三月七(礼装)-March 7th (New Outfit)
停云-Tingyun
青雀-Qingque
布洛妮娅·兰德-Bronya Rand
镜流-Jingliu
卡芙卡-Kafka
托帕-Topaz
桂乃芬-Guinaifen
玲可-Lynx
佩拉-Peia
寒鸦-Hanya
藿藿-Huohuo
阮·梅-Ruan Mei
雪衣-Xueyi
希露瓦-Serval
绝区零 Zenless-zone-zero:
星见雅-Miyabi
猫宫又奈-Nekomata
艾莲·乔-Ellen
妮可-Nicole
苍角-Soukaku
格莉丝-Grace
珂蕾妲-koleda
可琳-Corin
其他 Others:
鹿鸣-Luming
"""#;
let sd1_5_markdown_description = r#"
00025-2161235528-1girl,(orange_hair_1.1),(zentangle, mandala, tangle, entangle_0.6),(fractal art),the most beautiful form of chaos,brutalist desi.png
00010-1626070972-elf portrait,enchanting beauty,fantasy,ethereal glow,pointed ears,delicate facial features,long elegant hair,nature-themed attir.png
00001-301370162-girl in a snowy landscape,winter coat,fluffy hood,white fur trim,breath visible in cold air,soft falling snowflakes,glistening s.png
00002-3996269517-venomous sorcerer,ominous dark robes,glowing green eyes,holding a staff with a serpent design,mystical green flames,poisonous mi.png
00003-3817469732-Thunder Deity,immense power,hovering above the sea,stormy ocean waves below,crackling lightning in fingertips,dramatic stormy cl.png
if you want to remake my front image, check this out: Lightflow | SD webUI Extension - workflow restore & save in one click - majicMIXfantasy example | Stable Diffusion Other | Civitai
v3 is here. Shouldn't be difficult to use. Simple and complex prompts can be used, unleash your imagination.
v3来了,简单复杂的提示词都可以用。
I developed the v3 version towards 2D in order to make more room for 2.5D models like majicmix lux.
v3版本我往2D发展是为了给majicmix lux等2.5D模型让出更多空间。
merged from融了:
Jack Of All by GeneratedJudge
AWPainting by DynamicWang
majicMIX fantasy v2 =
Noosphere by skumerz + dalcefoPainting + 饭特稀V08 by zhazhahui345 + GhostMix
"#;
let sd_vae_description = r"This is an earlier version of a stable VAE. Compared to other VAEs, it has a higher level of stability. I am not the creator, but I could not find this VAE on the website, so I am sharing it here.
这是较早时候的稳定VAE,与其他VAE相比具有较高的稳定性,不容易坏图。我不是作者,但是站里没找着所以搬运。#";
info!("Seeding weights for inference...");
ModelWeightToken::reset_rng_for_testing_and_dev_seeding_never_use_in_production_seriously(54321);
let model_weight_token1 = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let model_weight_token2 = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let model_weight_token3 = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let remote_cloud_file_client = RemoteCloudFileClient::get_remote_cloud_file_client().await?;
let sd_15_weights_descriptor = Box::new(WeightsSD15Descriptor {});
let lora_descriptor = Box::new(WeightsLoRADescriptor{});
let sd_vae_15_weights_descriptor = Box::new(WeightsSD15Descriptor {});
let mut path_object_SD = get_seed_tool_data_root();
path_object_SD.push("models/imagegen/sd15/majicmixFantasy_v30Vae.safetensors");
let mut path_object_loRA = get_seed_tool_data_root();
path_object_loRA.push("models/imagegen/loRA/xiawolei-v100-000019.safetensors");
let mut path_to_VAE = get_seed_tool_data_root();
path_to_VAE.push("models/imagegen/vae/zVae_v20.safetensors");
let metadata1 = remote_cloud_file_client.upload_file(sd_15_weights_descriptor,path_object_SD.as_path().to_str().unwrap()).await?;
let metadata2 = remote_cloud_file_client.upload_file(lora_descriptor,path_object_loRA.as_path().to_str().unwrap()).await?;
let metadata3 = remote_cloud_file_client.upload_file(sd_vae_15_weights_descriptor,path_to_VAE.as_path().to_str().unwrap()).await?;
let weights1 = CreateModelWeightsArgs {
token: &model_weight_token1, // replace with actual ModelWeightToken
weights_type: WeightsType::StableDiffusion15, // replace with actual WeightsType
weights_category: WeightsCategory::ImageGeneration, // replace with actual WeightsCategory
title: "Dragonfruit".to_string(),
maybe_description_markdown: Some(sd1_5_markdown_description.to_string()),
maybe_description_rendered_html: Some("<p>Description</p>".to_string()),
creator_user_token: Some(&user_token), // replace with actual UserToken
creator_ip_address: "192.168.1.1",
creator_set_visibility: Visibility::Public,
maybe_last_update_user_token: Some("Last Update User Token".to_string()),
original_download_url: Some("https://civitai.com/models/41865/majicmix-fantasy".to_string()),
original_filename: Some("majicmixFantasy_v30Vae.safetensors".to_string()),
file_size_bytes: metadata1.file_size_bytes as i32,
file_checksum_sha2: metadata1.sha256_checksum.to_string(),
public_bucket_hash: metadata1.bucket_details.clone().unwrap().object_hash,
maybe_public_bucket_prefix: Some(metadata1.bucket_details.clone().unwrap().prefix),
maybe_public_bucket_extension: Some(metadata1.bucket_details.clone().unwrap().suffix),
cached_user_ratings_total_count: 10,
cached_user_ratings_positive_count: 9,
cached_user_ratings_negative_count: 1,
maybe_cached_user_ratings_ratio: Some(0.9),
version: 1,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
};
let weights2 = CreateModelWeightsArgs {
token: &model_weight_token2, // replace with actual ModelWeightToken
weights_type: WeightsType::LoRA, // replace with actual WeightsType
weights_category: WeightsCategory::ImageGeneration, // replace with actual WeightsCategory
title: "MIHOYO Collection 米家全家桶 (Honkai Impact 3rd | Honkai Star Rail | Genshin Impact | Zenless Zone Zero)".to_string(),
maybe_description_markdown: Some(miyhoyo_description.to_string()),
maybe_description_rendered_html: Some(miyhoyo_description.to_string()),
creator_user_token: Some(&user_token), // replace with actual UserToken
creator_ip_address: "292.268.2.2",
creator_set_visibility: Visibility::Public,
maybe_last_update_user_token: Some("<p> Honkai <p>".to_string()),
original_download_url: Some("https://civitai.com/models/95243/mihoyo-collection-honkai-impact-3rd-or-honkai-star-rail-or-genshin-impact-or-zenless-zone-zero".to_string()),
original_filename: Some("xiawolei-v100-000019.safetensors".to_string()),
file_size_bytes: metadata2.file_size_bytes as i32,
file_checksum_sha2: metadata2.sha256_checksum.to_string(),
public_bucket_hash: metadata2.bucket_details.clone().unwrap().object_hash.clone(),
maybe_public_bucket_prefix: Some(metadata2.bucket_details.clone().unwrap().prefix),
maybe_public_bucket_extension: Some(metadata2.bucket_details.clone().unwrap().suffix),
cached_user_ratings_total_count: 20,
cached_user_ratings_positive_count: 9,
cached_user_ratings_negative_count: 2,
maybe_cached_user_ratings_ratio: Some(0.9),
version: 2,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
};
let weights3 = CreateModelWeightsArgs {
token: &model_weight_token3, // replace with actual ModelWeightToken
weights_type: WeightsType::StableDiffusion15, // replace with actual WeightsType
weights_category: WeightsCategory::ImageGeneration, // replace with actual WeightsCategory
title: "z-vae".to_string(),
maybe_description_markdown: Some(sd_vae_description.to_string()),
maybe_description_rendered_html: Some("<p>Description</p>".to_string()),
creator_user_token: Some(&user_token), // replace with actual UserToken
creator_ip_address: "192.168.1.1",
creator_set_visibility: Visibility::Public,
maybe_last_update_user_token: Some("Last Update User Token".to_string()),
original_download_url: Some("https://civitai.com/models/97653/z-vae".to_string()),
original_filename: Some("zVae_v20.safetensors".to_string()),
file_size_bytes: metadata3.file_size_bytes as i32,
file_checksum_sha2: metadata3.sha256_checksum.to_string(),
public_bucket_hash: metadata3.bucket_details.clone().unwrap().object_hash,
maybe_public_bucket_prefix: Some(metadata3.bucket_details.clone().unwrap().prefix),
maybe_public_bucket_extension: Some(metadata3.bucket_details.clone().unwrap().suffix),
cached_user_ratings_total_count: 10,
cached_user_ratings_positive_count: 9,
cached_user_ratings_negative_count: 1,
maybe_cached_user_ratings_ratio: Some(0.9),
version: 1,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
};
create_weight(weights1).await?;
create_weight(weights2).await?;
create_weight(weights3).await?;
Ok(())
}
pub async fn seed_weights_for_user_token(
mysql_pool: &Pool<MySql>,
user_token: UserToken
@@ -630,6 +997,7 @@ pub async fn original_seed_weights(mysql_pool: &Pool<MySql>, _user_token: UserTo
Ok(())
}
pub async fn seed_weights(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
info!("Seeding weights...");
@@ -642,7 +1010,9 @@ pub async fn seed_weights(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
//original_seed_weights(mysql_pool,user_token).await?;
//seed_weights_for_user_token(mysql_pool, user_token).await?;
seed_weights_for_paging(mysql_pool,user_token).await?;
//seed_weights_for_paging(mysql_pool,user_token).await?;
seed_weights_for_testing_inference(mysql_pool,user_token).await?;
//println!("TESTING DOWLOAD");
//test_seed_weights_files().await?;
Ok(())
}
@@ -226,3 +226,5 @@ async fn seed_file_to_bucket(
maybe_bucket_extension: maybe_bucket_extension.map(|s| s.to_string()),
})
}
+47
View File
@@ -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)
}
+16 -2
View File
@@ -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;
@@ -19,7 +19,6 @@ pub trait BucketOrchestrationCore {
bytes: &[u8],
content_type: &str,
is_public: bool) -> AnyhowResult<()>;
}
@@ -1,6 +1,9 @@
use crate::remote_file_manager::remote_cloud_bucket_details::RemoteCloudBucketDetails;
#[derive(Debug, Clone)]
pub struct FileMetaData {
pub file_size_bytes: u64,
pub sha256_checksum: String,
pub mimetype: String
pub mimetype: String,
pub bucket_details: Option<RemoteCloudBucketDetails>
}
@@ -0,0 +1,21 @@
use crate::remote_file_manager::file_descriptor::FileDescriptor;
// TODO ASK BRANDON WHAT path should this be?
const MEDIA_FILE_DIRECTORY: &str = "/media";
pub struct MediaImagePngDescriptor {}
impl FileDescriptor for MediaImagePngDescriptor {
fn remote_directory_path(&self) -> &str {
MEDIA_FILE_DIRECTORY
}
fn get_suffix(&self)->String {
"png".to_string()
}
fn get_prefix(&self)->String {
"image".to_string()
}
fn is_public(&self) -> bool {
true
}
}
@@ -1,7 +1,7 @@
use crate::remote_file_manager::weights_descriptor::{self};
use super::file_descriptor::FileDescriptor;
#[derive(Debug, Clone)]
pub struct RemoteCloudBucketDetails {
pub object_hash: String,
pub prefix: String,
@@ -12,17 +12,13 @@ use crate::remote_file_manager::remote_cloud_bucket_details::RemoteCloudBucketDe
use super::file_descriptor::FileDescriptor;
use super::file_meta_data::FileMetaData;
pub struct RemoteCloudFileClient {
bucket_orchestration_client: Box<dyn BucketOrchestrationCore>
}
impl RemoteCloudFileClient {
pub fn new(bucket_orchestration_client: Box<dyn BucketOrchestrationCore>) -> Self {
Self {
bucket_orchestration_client
}
}
pub async fn get_remote_cloud_file_client() -> AnyhowResult<Self> {
let bucket_orchestration = match BucketOrchestration::new_bucket_client_from_existing_env() {
Ok(client) => client,
@@ -36,6 +32,13 @@ impl RemoteCloudFileClient {
})
}
pub fn new(bucket_orchestration_client: Box<dyn BucketOrchestrationCore>) -> Self {
Self {
bucket_orchestration_client
}
}
pub async fn download_file(&self, remote_cloud_bucket_details:RemoteCloudBucketDetails, to_system_file_path:String) -> AnyhowResult<()> {
let file_descriptor = remote_cloud_bucket_details.file_descriptor_from_bucket_details();
let file_bucket_directory = FileBucketDirectory::from_existing_bucket_details(remote_cloud_bucket_details);
@@ -53,17 +56,26 @@ impl RemoteCloudFileClient {
println!("Reading media file: {:?}", from_system_file_path);
// get meta data
let bytes = file_read_bytes(from_system_file_path)?;
let result = Self::get_file_meta_data(from_system_file_path)?;
let mut result = Self::get_file_meta_data(from_system_file_path)?;
let is_public = file_descriptor.is_public();
let suffix = file_descriptor.get_suffix().clone();
let prefix = file_descriptor.get_prefix().clone();
let directory = FileBucketDirectory::generate_new(
file_descriptor
);
result.bucket_details = Some(RemoteCloudBucketDetails {
object_hash: directory.get_file_object_hash().to_string(),
suffix:suffix,
prefix:prefix,
});
println!("Uploading media file to bucket path: {:?}",directory.get_full_remote_cloud_file_path());
self.bucket_orchestration_client.upload_file_with_content_type_process(
&directory.get_remote_cloud_base_directory(),
&directory.get_full_remote_cloud_file_path(),
bytes.as_ref(),
result.mimetype.as_ref(),
is_public
@@ -83,7 +95,8 @@ impl RemoteCloudFileClient {
Ok(FileMetaData {
file_size_bytes,
sha256_checksum,
mimetype: mimetype.to_string()
mimetype: mimetype.to_string(),
bucket_details: None
})
}
}
@@ -125,7 +138,8 @@ mod tests {
is_public: bool) -> AnyhowResult<()> {
println!("Upload File to Disk");
println!("{}",object_name);
assert_eq!(object_name,String::from("/weights/2/y/q/m/2/2yqm2f1bamh88seyd690h9v24apgezhr/loRA_2yqm2f1bamh88seyd690h9v24apgezhr.safetensors"));
// this is random you have to just check the outputs
//assert_eq!(object_name,String::from("/weights/2/y/q/m/2/2yqm2f1bamh88seyd690h9v24apgezhr/loRA_2yqm2f1bamh88seyd690h9v24apgezhr.safetensors"));
println!("ContentType:{}",content_type);
println!("{}",is_public);
assert_eq!(is_public,true);
@@ -3,7 +3,6 @@ use super::file_descriptor::FileDescriptor;
pub struct WeightsLoRADescriptor;
const REMOTE_FILE_DIRECTORY: &str = "/weights";
impl FileDescriptor for WeightsLoRADescriptor {
fn remote_directory_path(&self) -> &str {
REMOTE_FILE_DIRECTORY
@@ -26,6 +25,7 @@ impl FileDescriptor for WeightsLoRADescriptor {
}
}
pub struct WeightsSD15Descriptor;
impl FileDescriptor for WeightsSD15Descriptor {
@@ -57,6 +57,60 @@ impl GoogleDriveDownloadCommand {
}
}
pub async fn download_file_with_file_name(&self,
download_url: &str,
temp_dir: &TempDir,
file_name:&str) -> AnyhowResult<String>
{
let temp_dir_path = temp_dir.path()
.to_str()
.unwrap_or("/tmp")
.to_string();
let temp_filename = random_crockford_token(10);
let temp_filename = format!("{}/{}", temp_dir_path, file_name);
info!("Downloading {} to: {}", download_url, temp_filename);
let mut command = format!("{} --url \"{}\" --output_filename {}",
&self.download_script,
download_url,
&temp_filename);
if let Some(venv_activation_script) = self.maybe_venv_activation_script.as_deref() {
// NB: "." is source for non-bash shells
command = format!(". {} && {}",
venv_activation_script,
&command);
}
if let Some(change_to_directory) = self.maybe_change_to_directory.as_deref() {
command = format!("cd {} && {}",
change_to_directory,
&command);
}
if let Some(docker_options) = self.maybe_docker_options.as_ref() {
command = docker_options.to_command_string(&command);
}
info!("Running command: {}", command);
let result = Command::new("sh")
.arg("-c")
.arg(command)
.output()?;
info!("Downloader Result: {:?}", result);
if !result.status.success() {
let reason = String::from_utf8(result.stderr).unwrap_or("UNKNOWN".to_string());
return Err(anyhow!("Failure to execute command: {:?}", reason))
}
Ok(temp_filename)
}
/// Download file from Google Drive into the `TempDir`.
/// Return the local filesystem filename.
pub async fn download_file(&self,
+1
View File
@@ -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" }
@@ -4,8 +4,8 @@ use errors::AnyhowResult;
use crate::payloads::generic_inference_args::lipsync_payload::LipsyncArgs;
use crate::payloads::generic_inference_args::mocap_payload::MocapArgs;
use crate::payloads::generic_inference_args::tts_payload::TTSArgs;
use crate::payloads::generic_inference_args::videofilter_payload::{RerenderArgs};
use crate::payloads::generic_inference_args::videofilter_payload::RerenderArgs;
use crate::payloads::generic_inference_args::image_generation_payload::StableDiffusionArgs;
/// Used to encode extra state for the `generic_inference_jobs` table.
/// This should act somewhat like a serialized protobuf stored inside a record.
@@ -42,6 +42,10 @@ pub enum InferenceCategoryAbbreviated {
#[serde(alias = "video_filter")]
VideoFilter,
#[serde(rename = "ig")] // NB: DO NOT CHANGE. It could break live jobs. Renamed to be fewer bytes.
#[serde(alias = "image_generation")]
ImageGeneration,
#[serde(rename = "mc")] // NB: DO NOT CHANGE. It could break live jobs. Renamed to be fewer bytes.
#[serde(alias = "mocap")]
Mocap,
@@ -98,11 +102,13 @@ pub enum PolymorphicInferenceArgs {
/// Rerender a video. (Short name to save space when serializing.)
Rr(RerenderArgs),
/// Image generation. (Short name to save space when serializing.)
Ig(StableDiffusionArgs),
// Mocap (Short name to save space when serializing.)
Mc(MocapArgs)
}
impl GenericInferenceArgs {
pub fn from_json(json: &str) -> AnyhowResult<Self> {
@@ -121,6 +127,7 @@ impl InferenceCategoryAbbreviated {
InferenceCategory::TextToSpeech => Self::TextToSpeech,
InferenceCategory::VoiceConversion => Self::VoiceConversion,
InferenceCategory::VideoFilter => Self::VideoFilter,
InferenceCategory::ImageGeneration => Self::ImageGeneration,
InferenceCategory::Mocap => Self::Mocap,
}
}
@@ -131,6 +138,7 @@ impl InferenceCategoryAbbreviated {
Self::TextToSpeech => InferenceCategory::TextToSpeech,
Self::VoiceConversion => InferenceCategory::VoiceConversion,
Self::VideoFilter => InferenceCategory::VideoFilter,
Self::ImageGeneration =>InferenceCategory::ImageGeneration,
Self::Mocap => InferenceCategory::Mocap,
}
}
@@ -141,6 +149,8 @@ mod tests {
use crate::payloads::generic_inference_args::generic_inference_args::{FundamentalFrequencyMethodForJob, GenericInferenceArgs, InferenceCategoryAbbreviated, PolymorphicInferenceArgs};
use crate::payloads::generic_inference_args::lipsync_payload::{LipsyncAnimationAudioSource, LipsyncAnimationImageSource, LipsyncArgs};
use crate::payloads::generic_inference_args::tts_payload::TTSArgs;
use crate::payloads::generic_inference_args::image_generation_payload::StableDiffusionArgs;
use tokens::tokens::{model_weights::ModelWeightToken, media_files::MediaFileToken};
#[test]
fn typical_lipsync_animation_args_serialize() {
@@ -187,6 +197,48 @@ mod tests {
assert!(json.len() < 1000);
}
#[test]
fn typical_image_gen_args_serialize() {
let video_media_token = MediaFileToken("video_media_token".to_string());
let image_media_token = MediaFileToken("image_media_token".to_string());
let sd_model_token = ModelWeightToken("sd_model_token".to_string());
let lora_model_token = ModelWeightToken("lora_model_token".to_string());
let prompt = "prompt".to_string();
let a_prompt = "a_prompt".to_string();
let n_prompt = "n_prompt".to_string();
let seed = 1;
let upload_path = "upload_path".to_string();
let lora_upload_path = "lora_upload_path".to_string();
let checkpoint = "checkpoint".to_string();
let type_of_inference = "type_of_inference".to_string();
let args = GenericInferenceArgs {
inference_category: Some(InferenceCategoryAbbreviated::ImageGeneration),
args: Some(PolymorphicInferenceArgs::Ig(StableDiffusionArgs {
maybe_sd_model_token: Some(sd_model_token),
maybe_lora_model_token: Some(lora_model_token),
maybe_sampler: Some("sampler".as_ref()),
maybe_height: Some(512),
maybe_width: Some(512),
maybe_cfg_scale: Some(7),
maybe_prompt: Some(prompt),
maybe_n_prompt: Some(n_prompt),
maybe_batch_count: Some(1),
maybe_number_of_samples: Some(25),
maybe_seed: Some(seed),
maybe_upload_path: Some(upload_path),
maybe_lora_upload_path: Some(lora_upload_path),
type_of_inference: type_of_inference,
description: Some("Option".as_ref()),
title: Some("Model Name".as_ref())
})),
};
let json = serde_json::ser::to_string(&args).unwrap();
assert_eq!(json, r#"{"cat":"ig","args":{"Ig":{"vs":"video_media_token","is":"image_media_token","sd":"sd_model_token","lm":"lora_model_token","p":"prompt","ap":"a_prompt","np":"n_prompt","se":1,"mu":"upload_path","lu":"lora_upload_path"}}}"#.to_string());
}
#[test]
fn typical_voice_conversion_args_serialize() {
let args = GenericInferenceArgs {
@@ -0,0 +1,88 @@
use tokens::tokens::{model_weights::ModelWeightToken, media_files::MediaFileToken};
/// Video sources can be one of several:
/// - F: media_files (todo)
/// - U: media_uploads (legacy)
/// For image to image we probably want images
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum StableDiffusionSource {
// NB: DO NOT CHANGE. It could break live jobs. Renamed to be fewer bytes.
/// Media File Token (media_files table)
/// Serde cannot yet rename enum variants.
F(String),
// NB: DO NOT CHANGE. It could break live jobs. Renamed to be fewer bytes.
/// Media Upload Token (media_uploads table)
/// Serde cannot yet rename enum variants.
U(String),
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct StableDiffusionArgs {
#[serde(rename = "sd")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_sd_model_token: Option<ModelWeightToken>,
#[serde(rename = "lm")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_lora_model_token: Option<ModelWeightToken>,
#[serde(rename = "w")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_width: Option<i32>,
#[serde(rename = "h")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_height: Option<i32>,
#[serde(rename = "s")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_sampler: Option<String>,
#[serde(rename = "p")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_prompt: Option<String>,
#[serde(rename = "np")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_n_prompt: Option<String>,
#[serde(rename = "se")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_seed: Option<i64>,
#[serde(rename = "mu")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_upload_path: Option<String>,
#[serde(rename = "cf")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_cfg_scale: Option<i32>,
#[serde(rename = "lu")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_lora_upload_path: Option<String>,
#[serde(rename = "sa")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_number_of_samples: Option<i32>,
#[serde(rename = "bc")]
pub maybe_batch_count: Option<i32>,
#[serde(rename = "t")]
pub type_of_inference: String,
// loRA / checkpoint description
#[serde(rename = "de")]
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
// loRA / checkpoint name and description
#[serde(rename = "na")]
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>
}
@@ -2,4 +2,5 @@ pub mod generic_inference_args;
pub mod lipsync_payload;
pub mod tts_payload;
pub mod videofilter_payload;
pub mod mocap_payload;
pub mod image_generation_payload;
pub mod mocap_payload;
@@ -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;
@@ -179,6 +179,7 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
InferenceCategory::TextToSpeech => record.maybe_tts_model_title.as_deref(),
InferenceCategory::VoiceConversion => record.maybe_voice_conversion_model_title.as_deref(),
InferenceCategory::VideoFilter => Some("Video Filter"),
InferenceCategory::ImageGeneration => Some("Image Generation"),
InferenceCategory::Mocap => Some("Mocap"),
};
@@ -189,6 +190,7 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
InferenceCategory::TextToSpeech => (false, record.maybe_tts_public_bucket_path.as_deref()),
InferenceCategory::VoiceConversion => (true, record.maybe_voice_conversion_public_bucket_hash.as_deref()),
InferenceCategory::VideoFilter => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
InferenceCategory::ImageGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
InferenceCategory::Mocap => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
};
@@ -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,
@@ -65,11 +66,10 @@ pub async fn insert_media_file_from_zero_shot(
maybe_creator_category_synthetic_id = Some(next_lipsync_id);
}
// TODO: Ask if it make sense to use ONE function to do the insert
// TODO: Ask if it make sense to use ONE function to do the insert * I agree it does .... use insert media file generic and eventually remove all this.
// 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,
@@ -0,0 +1,172 @@
use anyhow::anyhow;
use sqlx;
use sqlx::MySqlPool;
use enums::by_table::generic_synthetic_ids::id_category::IdCategory;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType;
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use errors::AnyhowResult;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::weight_model_weight;
use tokens::tokens::users::UserToken;
use crate::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
use crate::queries::generic_synthetic_ids::transactional_increment_generic_synthetic_id::transactional_increment_generic_synthetic_id;
// thought about this it seems like this can be a bit more geneneric instead of having this we can ...
pub struct InsertArgs<'a> {
pub pool: &'a MySqlPool,
pub job: &'a AvailableInferenceJob,
pub maybe_mime_type: Option<&'a str>,
pub file_size_bytes: u64,
pub sha256_checksum: &'a str,
pub public_bucket_directory_hash: &'a str,
pub maybe_public_bucket_prefix: Option<&'a str>,
pub maybe_public_bucket_extension: Option<&'a str>,
pub is_on_prem: bool,
pub worker_hostname: &'a str,
pub worker_cluster: &'a str,
pub maybe_creator_user_token: Option<&'a UserToken>,
pub maybe_creator_anonymous_visitor_token: Option<&'a AnonymousVisitorTrackingToken>,
pub creator_ip_address: &'a str,
pub creator_set_visibility: Visibility,
pub upload_type: UploadType,
pub media_file_type: MediaFileType,
pub duration_millis: u64,
pub media_file_origin: MediaFileOriginProductCategory,
pub media_file_origin_product_category: MediaFileOriginProductCategory,
pub media_file_origin_model_type: Mediafile,
pub maybe_origin_filename: String,
pub maybe_origin_model_token:WeightsModelToken
}
pub async fn insert_media_file_generic(
args: InsertArgs<'_>
) -> AnyhowResult<(MediaFileToken, u64)>
{
let result_token = MediaFileToken::generate();
let mut maybe_creator_file_synthetic_id : Option<u64> = None;
let mut maybe_creator_category_synthetic_id : Option<u64> = None;
let mut transaction = args.pool.begin().await?;
// create a user token
if let Some(creator_user_token) = args.job.maybe_creator_user_token.as_deref() {
let user_token = UserToken::new_from_str(creator_user_token);
let next_media_file_id = transactional_increment_generic_synthetic_id(
&user_token,
IdCategory::MediaFile,
&mut transaction
).await?;
let next_lipsync_id = transactional_increment_generic_synthetic_id(
&user_token,
IdCategory::ZeroShotTtsResult,
&mut transaction
).await?;
maybe_creator_file_synthetic_id = Some(next_media_file_id);
maybe_creator_category_synthetic_id = Some(next_lipsync_id);
}
// TODO: Ask if it make sense to use ONE function to do the insert * I agree it does .... use insert media file generic and eventually remove all this.
// 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 = {
let query_result = sqlx::query!(
r#"
INSERT INTO media_files
SET
token = ?,
origin_category = ?,
origin_product_category = ?,
maybe_origin_model_type = ?,
media_type = ?,
maybe_mime_type = ?,
file_size_bytes = ?,
checksum_sha2 = ?,
public_bucket_directory_hash = ?,
maybe_public_bucket_prefix = ?,
maybe_public_bucket_extension = ?,
maybe_creator_user_token = ?,
maybe_creator_anonymous_visitor_token = ?,
creator_ip_address = ?,
creator_set_visibility = ?,
maybe_creator_file_synthetic_id = ?,
maybe_creator_category_synthetic_id = ?,
is_generated_on_prem = ?,
generated_by_worker = ?,
generated_by_cluster = ?
"#,
result_token.as_str(),
ORIGIN_CATEGORY.to_str(),
ORIGIN_PRODUCT_CATEGORY.to_str(),
ORIGIN_MODEL_TYPE.to_str(),
MEDIA_TYPE.to_str(),
args.maybe_mime_type,
args.file_size_bytes,
args.sha256_checksum,
args.public_bucket_directory_hash,
args.maybe_public_bucket_prefix,
args.maybe_public_bucket_extension,
args.job.maybe_creator_user_token,
args.job.maybe_creator_anonymous_visitor_token,
args.job.creator_ip_address,
args.job.creator_set_visibility.to_str(),
maybe_creator_file_synthetic_id,
maybe_creator_category_synthetic_id,
args.is_on_prem,
args.worker_hostname,
args.worker_cluster
)
.execute(&mut *transaction)
.await;
let record_id = match query_result {
Ok(res) => {
res.last_insert_id()
},
Err(err) => {
// TODO: handle better
//transaction.rollback().await?;
return Err(anyhow!("Mysql error: {:?}", err));
}
};
record_id
};
transaction.commit().await?;
Ok((result_token, record_id))
}
+1 -3
View File
@@ -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;
@@ -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(())
}
@@ -293,6 +293,8 @@ mod tests {
maybe_scoped_weight_type: None,
maybe_scoped_weight_category: None,
mysql_pool: &pool,
maybe_scoped_weight_type: None,
maybe_scoped_weight_category: None,
}).await?;
for weight in weights_by_username.records.iter() {
// print weight
@@ -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;
@@ -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,
@@ -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,
@@ -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>,
@@ -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,
@@ -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 {
@@ -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>
@@ -76,6 +76,8 @@ pub async fn do_process_single_job(job_state: &JobState, job: &AvailableDownload
return Err(anyhow!("Bad download URL: `{}`", &job.download_url));
}
// TODO USE THIS DOWNLOADER
let download_filename = match job_state.sidecar_configs.google_drive_downloader.download_file(&job.download_url, &temp_dir).await {
Ok(filename) => filename,
Err(e) => {
@@ -70,7 +70,7 @@ pub struct PretrainedModels {
/// Configurations and interfaces to code deployed as sidecars or container mounts.
pub struct SidecarConfigs {
pub google_drive_downloader: GoogleDriveDownloadCommand,
pub(crate) google_drive_downloader: GoogleDriveDownloadCommand,
pub rvc_v2_model_check_command: RvcV2ModelCheckCommand,
pub softvc_model_check_command: SoftVcModelCheckCommand,
pub so_vits_svc_model_check_command: SoVitsSvcModelCheckCommand,
@@ -19,7 +19,9 @@ actix_helpers = { path = "../../../lib/actix_helpers" }
bootstrap = { path = "../../../lib/bootstrap" }
buckets = { path = "../../../_types/buckets" }
cloud_storage = { path = "../../../lib/api_clients/cloud_storage" }
google_drive_common = { path = "../../../lib/api_clients/google_drive_common" }
collections = { path = "../../../lib/collections" }
composite_identifiers = { path = "../../../_types/composite_identifiers" }
concurrency = { path = "../../../lib/concurrency" }
config = { path = "../../../lib/deprecated/config" }
easyenv = { path = "../../../lib/easyenv" }
@@ -104,6 +104,14 @@ VITS_INFERENCE_MAYBE_DOCKER_IMAGE_SHA="03017896367b"
RERENDER_INFERENCE_ROOT_DIRECTORY="/home/salt/clone/storyteller-ml/animation/rerender-lora/"
RERENDER_INFERENCE_EXECUTABLE_OR_COMMAND="venv/bin/python rerender.py"
# ================================= #
# #
# STABLE DIFFUSION #
# #
# ================================= #
STABLE_DIFFUSION_INFERENCE_ROOT_DIRECTORY="/home/tensor/code/storyteller/storyteller-ml/image_generation/Storyteller-Studio-Filter/"
STABLE_DIFFUSION_INFERENCE_COMMAND=".venv/bin/python main.py"
# ================================= #
# #
# MocapNET #
@@ -188,7 +188,7 @@ async fn get_model_record_from_cacheable_query(job_dependencies: &JobDependencie
}
}
_ => {
warn!("Job does not have a category or a model token: {:?} - category: {:?}, token: {:?}",
warn!("Only applies to older job workflows. Job does not have a category or a model token: {:?} - category: {:?}, token: {:?}",
job.id, job.inference_category, job.maybe_model_token);
MaybeInferenceModel::None
@@ -17,6 +17,7 @@ use crate::job::job_types::lipsync::process_single_lipsync_job::process_single_l
use crate::job::job_types::tts::process_single_tts_job::process_single_tts_job;
use crate::job::job_types::vc::process_single_vc_job::process_single_vc_job;
use crate::job::job_types::videofilter::process_single_vf_job::process_single_vf_job;
use crate::job::job_types::image_generation::process_single_ig_job::process_single_ig_job;
use crate::job::job_types::mocap::process_single_mc_job::process_single_mc_job;
use crate::job_dependencies::JobDependencies;
@@ -187,6 +188,9 @@ async fn do_process_single_job(
InferenceCategory::VideoFilter => {
process_single_vf_job(job_dependencies, job).await?
}
InferenceCategory::ImageGeneration => {
process_single_ig_job(job_dependencies, job).await?
}
InferenceCategory::Mocap => {
process_single_mc_job(job_dependencies, job).await?
}
@@ -0,0 +1,2 @@
pub mod process_single_ig_job;
pub mod sd;
@@ -0,0 +1,34 @@
use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType;
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::image_generation::sd::process_job::process_job_selection;
use crate::job_dependencies::JobDependencies;
use crate::job::job_types::image_generation::sd::process_job::StableDiffusionProcessArgs;
use errors::anyhow;
pub async fn process_single_ig_job(job_dependencies: &JobDependencies, job: &AvailableInferenceJob) -> Result<JobSuccessResult, ProcessSingleJobError> {
let job = job;
match job.maybe_model_type {
Some(InferenceModelType::StableDiffusion) => {
dispatch_sd_job(
job_dependencies,
job
).await
},
Some(other_model_type) => {
Err(ProcessSingleJobError::Other(anyhow!("Wrong model type for SD: {:?}", other_model_type)))
}
None => {
Err(ProcessSingleJobError::Other(anyhow!("SD model type not set")))
}
}
}
pub async fn dispatch_sd_job(job_dependencies: &JobDependencies, job: &AvailableInferenceJob) -> Result<JobSuccessResult, ProcessSingleJobError> {
let args = StableDiffusionProcessArgs {
job_dependencies,
job,
};
process_job_selection(args).await
}
@@ -0,0 +1,6 @@
pub mod process_job;
pub mod model_downloaders;
pub mod sd_inference_command;
pub mod stable_diffusion_dependencies;
pub mod validate_inputs;
@@ -0,0 +1,20 @@
crate::impl_model_downloader!(
SDVAEEncoder,
"SD Variation Auto Encoder",
"SD_VARIATIONAL_AUTO_ENCODER_BUCKET_PATH",
"/animation_sadtalker/enhancer/parsing_parsenet.pth",
"SD_VARIATIONAL_AUTO_ENCODER_FILESYSTEM_PATH",
"/tmp/downloads/sadtalker/parsing_parsenet.pth"
);
pub struct StableDiffusionDownloaders {
sd_vae_encoder: SDVAEEncoder
}
impl StableDiffusionDownloaders {
pub fn build_all_from_env() -> Self {
Self {
sd_vae_encoder: SDVAEEncoder::from_env(),
}
}
}
@@ -0,0 +1,495 @@
use std::env::temp_dir;
use std::ops::Deref;
use std::path::PathBuf;
use std::thread;
use std::thread::Thread;
use std::time::{Duration, Instant};
use cloud_storage::remote_file_manager::remote_cloud_bucket_details::RemoteCloudBucketDetails;
use mysql_queries::payloads::generic_inference_args::image_generation_payload::StableDiffusionArgs;
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
use mysql_queries::queries::model_weights::get_weight::get_weight_by_token;
use crate::job::job_loop::job_success_result::{ JobSuccessResult, ResultEntity };
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::job_dependencies::JobDependencies;
use anyhow::anyhow;
use log::info;
use sqlx::MySqlPool;
use cloud_storage::remote_file_manager::media_descriptor::MediaImagePngDescriptor;
use mysql_queries::payloads::generic_inference_args::generic_inference_args::PolymorphicInferenceArgs;
use cloud_storage::remote_file_manager::remote_cloud_file_manager::RemoteCloudFileClient;
use cloud_storage::remote_file_manager::weights_descriptor::WeightsLoRADescriptor;
use enums::by_table::generic_inference_jobs::inference_result_type::InferenceResultType::UploadModel;
use enums::by_table::generic_synthetic_ids::id_category::IdCategory::MediaFile;
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::by_table::media_uploads::media_upload_source::MediaUploadSource;
use enums::by_table::media_uploads::media_upload_type::MediaUploadType;
use enums::by_table::model_weights::weights_category::WeightsCategory;
use enums::by_table::model_weights::weights_types::WeightsType;
use enums::common::visibility::Visibility;
use filesys::path_to_string::path_to_string;
use filesys::safe_delete_temp_directory::safe_delete_temp_directory;
use mysql_queries::payloads::media_upload_modification_details::MediaUploadModificationDetails;
use mysql_queries::queries::media_files::create::insert_media_file_from_file_upload::{insert_media_file_from_file_upload, InsertMediaFileFromUploadArgs, UploadType};
use mysql_queries::queries::media_uploads::insert_media_upload::{Args, insert_media_upload};
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::users::UserToken;
use crate::job::job_types::image_generation::sd::sd_inference_command::InferenceArgs;
use crate::job::job_types::image_generation::sd::stable_diffusion_dependencies::StableDiffusionDependencies;
use crate::job::job_types::image_generation::sd::validate_inputs::validate_inputs;
use google_drive_common::google_drive_download_command::GoogleDriveDownloadCommand;
use mysql_queries::queries::model_weights::create::create_weight::{create_weight, CreateModelWeightsArgs};
pub struct StableDiffusionProcessArgs<'a> {
pub job_dependencies: &'a JobDependencies,
pub job: &'a AvailableInferenceJob,
}
// run inference
// insert record into the db with the inference job token complete.
pub async fn sd_args_from_job(
args: &StableDiffusionProcessArgs<'_>
) -> Result<StableDiffusionArgs, ProcessSingleJobError> {
let inference_args = args.job.maybe_inference_args
.as_ref()
.map(|args| args.args.as_ref())
.flatten();
let polymorphic_args = match inference_args {
Some(args) => args,
None => {
return Err(
ProcessSingleJobError::from_anyhow_error(anyhow!("no inference args for job!"))
);
}
};
let sd_args = match polymorphic_args {
PolymorphicInferenceArgs::Ig(args) => args,
_ => {
return Err(
ProcessSingleJobError::from_anyhow_error(anyhow!("wrong inner args for job!"))
);
}
};
let stable_diffusion_args: StableDiffusionArgs = StableDiffusionArgs::from(sd_args.clone());
Ok(stable_diffusion_args)
}
// store the prompt and cluster them today.
pub async fn process_job_selection (args: StableDiffusionProcessArgs<'_>
) -> Result<JobSuccessResult, ProcessSingleJobError> {
let job = args.job;
let sd_args = sd_args_from_job(&args).await?;
if sd_args.type_of_inference == "inference" {
process_job_inference(&args).await
}
else if sd_args.type_of_inference == "lora" {
process_job_lora(&args).await
}
else if sd_args.type_of_inference == "checkpoint"{
process_job_inference(&args).await
}
else {
Err(ProcessSingleJobError::Other(anyhow!("inference type doesn't exist!")))
}
}
pub async fn process_job_checkpoint(args: &StableDiffusionProcessArgs<'_>) -> Result<JobSuccessResult, ProcessSingleJobError> {
// insert model record in create model record
// let maybe_result_entity = ResultEntity {
// entity_type: InferenceResultType::UploadModel,
// entity_token:
// };
Ok(JobSuccessResult {
maybe_result_entity: None,
inference_duration: Duration::from_secs(0),
})
}
pub async fn process_job_lora(args: &StableDiffusionProcessArgs<'_>) -> Result<JobSuccessResult, ProcessSingleJobError> {
Ok(JobSuccessResult {
maybe_result_entity: None,
inference_duration: Duration::from_secs(0),
})
}
pub async fn process_job_inference(
args: &StableDiffusionProcessArgs<'_>
) -> Result<JobSuccessResult, ProcessSingleJobError> {
let job = args.job;
let deps = args.job_dependencies;
let mysql_pool = &deps.db.mysql_pool;
let sd_args = sd_args_from_job(&args).await?;
let sd_deps = match &args.job_dependencies.job.job_specific_dependencies.maybe_stable_diffusion_dependencies {
None => {
return Err(ProcessSingleJobError::Other(anyhow!("Missing Job Specific Dependencies")));
}
Some(val) => {
val
}
};
let creator_user_token:UserToken;
match &job.maybe_creator_user_token {
Some(token) => {
creator_user_token = UserToken::new_from_str(token);
},
None => {
return Err(ProcessSingleJobError::InvalidJob(anyhow!("Missing Creator User Token")));
}
}
let mut job_progress_reporter = args.job_dependencies.clients.job_progress_reporter
.new_generic_inference(job.inference_job_token.as_str())
.map_err(|e| ProcessSingleJobError::Other(anyhow!(e)))?;
//==================== TEMP DIR ==================== //
let work_temp_dir = format!("temp_stable_diffusion_inference_{}", job.id.0);
// thread::sleep(seconds) to check the directory
//NB: TempDir exists until it goes out of scope, at which point it should delete from filesystem.
let mut 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 sd_checkpoint_path = work_temp_dir.path().join("sd_checkpoint.safetensors");
let lora_path = work_temp_dir.path().join("lora.safetensors");
let vae_path = work_temp_dir.path().join("vae.safetensors");
let output_path = work_temp_dir.path().join("output");
let g_drive_path = work_temp_dir.path().join("gdrive");
info!("Paths to download to:");
info!("sd_checkpoint_path:{}",sd_checkpoint_path.display());
info!("lora_path:{}",lora_path.display());
info!("vae_path:{}",vae_path.display());
info!("output_path:{}",output_path.display());
info!("tmp_google_drive_path:{}",g_drive_path.display());
// If it is not inference then it must be loRA or checkpoint downloading
if sd_args.type_of_inference != "inference" {
let mut download_url= String::from("");
if sd_args.type_of_inference == "lora" {
download_url = match sd_args.maybe_lora_upload_path {
Some(val) => { val },
None => { "".to_string() }
};
}
else if sd_args.type_of_inference == "checkpoint" {
download_url = match sd_args.maybe_upload_path {
Some(val) => { val },
None => { "".to_string() }
}
}
if download_url.len() <= 0 {
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("Failed to Download URL Missing")));
}
let file_name = "download.safetensors";
let download_script = easyenv::get_env_string_or_default(
"DOWNLOAD_SCRIPT",
"./scripts/download_internet_file.py");
let google_drive_downloader =
GoogleDriveDownloadCommand::new_production(&download_script);
info!("Downloading {}",download_url);
let download_filename = match google_drive_downloader.download_file_with_file_name(&download_url, &work_temp_dir,file_name).await {
Ok(filename) => filename,
Err(e) => {
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("Failed to Download")));
}
};
let download_file_path = work_temp_dir.path().join(file_name);
info!("File Retrieved at {}",download_file_path.display());
}
// // Unpack loRA and Checkpoint
// // run inference by downloading from google drive.
let lora_token = sd_args.maybe_lora_model_token;
let weight_token = sd_args.maybe_sd_model_token;
let retrieved_sd_record = match weight_token {
Some(token) => {
let retrieved_sd_record = get_weight_by_token(
&token,
false,
&deps.db.mysql_pool
).await?;
match retrieved_sd_record {
Some(record) => record,
None => {
return Err(
ProcessSingleJobError::from_anyhow_error(anyhow!("no record of model!"))
);
}
}
}
None => {
return Err(
ProcessSingleJobError::from_anyhow_error(anyhow!("no sd model token for job!"))
);
}
};
// ignore if no lora token
let retrieved_loRA_record = match lora_token {
Some(token) => {
let retrieved_loRA_record = get_weight_by_token(
&token,
false,
&deps.db.mysql_pool
).await?;
Some(retrieved_loRA_record)
}
None => None,
};
let remote_cloud_file_client = RemoteCloudFileClient::get_remote_cloud_file_client().await?;
// Details for SD checkpoint
let details = RemoteCloudBucketDetails::new(
retrieved_sd_record.public_bucket_hash.clone(),
retrieved_sd_record.maybe_public_bucket_prefix.clone().unwrap_or_else(|| "".to_string()),
retrieved_sd_record.maybe_public_bucket_extension.clone().unwrap_or_else(|| "".to_string())
);
remote_cloud_file_client.download_file(details, path_to_string(sd_checkpoint_path.clone())).await?;
match retrieved_loRA_record {
Some(record) => {
match record {
Some(model_weight_record) => {
let lora_details = RemoteCloudBucketDetails::new(
model_weight_record.public_bucket_hash.clone(),
model_weight_record.maybe_public_bucket_prefix
.clone()
.unwrap_or_else(|| "".to_string()),
model_weight_record.maybe_public_bucket_extension
.clone()
.unwrap_or_else(|| "".to_string())
);
remote_cloud_file_client.download_file(
lora_details,
path_to_string(lora_path.clone())
).await?;
}
None => {}
}
}
None => {}
}
// VAE token for now
let vae_token = String::from("weight_rb0959wfzjhk3d1k93hr3s0qw");
let model_weight_vae = ModelWeightToken(vae_token);
let vae_weight_record = get_weight_by_token(
&model_weight_vae,
false,
&deps.db.mysql_pool
).await?;
let vae_weight_record = match vae_weight_record {
Some(val) => {
val
},
None => {
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("no VAE? thats a problem.")))
}
};
let vae_details = RemoteCloudBucketDetails::new(
vae_weight_record.public_bucket_hash.clone(),
vae_weight_record.maybe_public_bucket_prefix
.clone()
.unwrap_or_else(|| "".to_string()),
vae_weight_record.maybe_public_bucket_extension
.clone()
.unwrap_or_else(|| "".to_string())
);
remote_cloud_file_client.download_file(
vae_details,
path_to_string(vae_path.clone())
).await?;
let prompt = match sd_args.maybe_prompt {
Some(val) => {
val
}
None => return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("No Prompt provided!")))
};
let stderr_output_file = work_temp_dir.path().join("sd_err.txt");
let number_of_samples = match sd_args.maybe_number_of_samples {
Some(val) => {
val
},
None => {
20
}
};
let inference_start_time = Instant::now();
sd_deps.inference_command.execute_inference(InferenceArgs {
work_dir: work_temp_dir.path().to_path_buf(),
output_file: output_path.clone(),
stderr_output_file: stderr_output_file,
prompt: prompt,
negative_prompt:sd_args.maybe_n_prompt.unwrap_or_default(),
number_of_samples: number_of_samples,
samplers: sd_args.maybe_sampler.unwrap_or(String::from("Euler a")),
width: sd_args.maybe_width.unwrap_or(512),
height: sd_args.maybe_height.unwrap_or(512),
cfg_scale: sd_args.maybe_cfg_scale.unwrap_or(7),
seed: sd_args.maybe_seed.unwrap_or(1),
lora_path: lora_path.clone(),
checkpoint_path: sd_checkpoint_path.clone(),
vae: vae_path.clone(),
batch_count: sd_args.maybe_batch_count.unwrap_or(1),
});
let inference_duration = Instant::now().duration_since(inference_start_time);
let creator_ip_address = &job.creator_ip_address;
// run a for loop for output images output_0 in the folder then use upload media.
// pngs
if sd_args.type_of_inference == "inference" {
// gather all the images into a single batch
for i in 0..sd_args.maybe_batch_count.unwrap_or(1) {
let path = output_path.clone();
let file_path = format!("{}_{}.png", path_to_string(path), i);
println!("Upload File Path:{}", file_path);
let metadata = remote_cloud_file_client.upload_file(Box::new(MediaImagePngDescriptor {}), file_path.as_ref()).await?;
let bucket_details = match metadata.bucket_details {
Some(val) => { val }
None => return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("no VAE? thats a problem.")))
};
// TODO: Generic Insert with batch id.
// insert media file generic should run here.
insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
pool: mysql_pool,
maybe_creator_user_token: Some(&creator_user_token),
maybe_creator_anonymous_visitor_token: None,
creator_ip_address: creator_ip_address,
creator_set_visibility: Default::default(),
upload_type: UploadType::Filesystem,
media_file_type: MediaFileType::Image,
maybe_mime_type: Some(metadata.mimetype.as_ref()),
file_size_bytes: metadata.file_size_bytes,
duration_millis: inference_duration.as_secs() * 1000,
sha256_checksum: metadata.sha256_checksum.as_str(),
public_bucket_directory_hash: bucket_details.object_hash.as_str(),
maybe_public_bucket_prefix: Some(bucket_details.prefix.as_str()),
maybe_public_bucket_extension: Some(bucket_details.suffix.as_str()),
}).await?;
}
// hack to check the directory before clean up.
let thirtyMinutes = 1800;
thread::sleep(Duration::from_secs(thirtyMinutes));
// upload media and create a record.
Ok(JobSuccessResult {// TODO: batch token needs to go here
maybe_result_entity: None,
inference_duration: Duration::from_secs(0),
})
}
else { // must be an upload and need upload token
// The parameters will be updated on another screen perhaps?
// so right now it will fill with the availible values.
create_weight(CreateModelWeightsArgs {
token: &ModelWeightToken::generate(),
weights_type: WeightsType::StableDiffusion15,
weights_category: WeightsCategory::ImageGeneration,
title: "".to_string(),
maybe_description_markdown: None,
maybe_description_rendered_html: None,
creator_user_token: Some(&creator_user_token),
creator_ip_address: creator_ip_address,
creator_set_visibility: Default::default(),
maybe_last_update_user_token: None,
original_download_url: None,
original_filename: None,
file_size_bytes: 0,
file_checksum_sha2: "".to_string(),
public_bucket_hash: "".to_string(),
maybe_public_bucket_prefix: None,
maybe_public_bucket_extension: None,
version: 0,
mysql_pool,
}).await?;
Ok(JobSuccessResult {// TODO: batch token needs to go here
maybe_result_entity: None,
inference_duration: Duration::from_secs(0),
})
}
}
#[cfg(test)]
mod tests {
use anyhow::anyhow;
use std::path::PathBuf;
use cloud_storage::remote_file_manager::{
remote_cloud_file_manager::{RemoteCloudFileClient, self},
remote_cloud_bucket_details::RemoteCloudBucketDetails,
};
use errors::{AnyhowError, AnyhowResult};
#[tokio::test]
async fn test_seed_weights_files() -> AnyhowResult<()> {
let seed_path = PathBuf::from("/storyteller/root/custom-seed-tool-data");
let remote_cloud_file_client = RemoteCloudFileClient::get_remote_cloud_file_client().await;
let remote_cloud_file_client = match remote_cloud_file_client {
Ok(res) => {
res
}
Err(_) => {
return Err(anyhow!("failed to get remote cloud file client"));
}
};
let mut path_dl1 = seed_path.clone();
path_dl1.push("downloads/loRA");
let mut path_dl2 = seed_path.clone();
path_dl2.push("downloads/checkpoint");
let bucket_details1 = RemoteCloudBucketDetails {
object_hash: String::from("apa0ej6es8d3ss2gwtf1cghge35qn9tn"),
prefix: String::from("sd15"),
suffix: String::from("safetensors"),
};
let bucket_details2 = RemoteCloudBucketDetails {
object_hash: String::from("27kz11et18fargyyxbj66ntfn621k9d3"),
prefix: String::from("loRA"),
suffix: String::from("safetensors"),
};
remote_cloud_file_client.download_file(bucket_details1, String::from("./checkpoint")).await?;
remote_cloud_file_client.download_file(bucket_details2, String::from("./loRA")).await?;
Ok(())
}
}
@@ -0,0 +1,309 @@
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;
// 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>>()
});
#[derive(Clone)]
pub struct StableDiffusionInferenceCommand {
/// Where the code lives
stable_diffusion_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>,
}
#[derive(Clone)]
pub enum ExecutableOrCommand {
/// Eg. `inference.py`
Executable(PathBuf),
/// Eg. `python3 inference.py`
Command(String),
}
pub struct InferenceArgs {
/// --source_image: path to the input image (or video)
/// --result_dir: path to directory work is performed
pub work_dir: PathBuf,
/// --result_file: path to final file output
pub output_file: PathBuf,
pub stderr_output_file: PathBuf,
pub prompt: String,
pub negative_prompt:String,
pub number_of_samples:i32,
pub samplers:String,
pub width:i32,
pub height:i32,
pub cfg_scale:i32,
pub seed:i64,
pub lora_path:PathBuf,
pub checkpoint_path:PathBuf,
pub vae:PathBuf,
pub batch_count:i32,
}
impl StableDiffusionInferenceCommand {
pub fn new<P: AsRef<Path>>(
stable_diffusion_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 {
stable_diffusion_root_code_directory: stable_diffusion_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 stable_diffusion_root_code_directory = easyenv::get_env_pathbuf_required(
"STABLE_DIFFUSION_INFERENCE_ROOT_DIRECTORY")?;
let maybe_inference_command = easyenv::get_env_string_optional(
"STABLE_DIFFUSION_INFERENCE_COMMAND");
let executable_or_command = match maybe_inference_command {
Some(command) => ExecutableOrCommand::Command(command),
None => {
return Err(anyhow!("neither command nor executable passed"));
}
};
let maybe_virtual_env_activation_command = easyenv::get_env_string_optional(
"STABLE_DIFFUSION_INFERENCE_MAYBE_VENV_COMMAND");
let maybe_default_config_path = easyenv::get_env_pathbuf_optional(
"STABLE_DIFFUSION_INFERENCE_MAYBE_DEFAULT_CONFIG_PATH");
let maybe_execution_timeout: Option<Duration> =
easyenv::get_env_duration_seconds_optional("STABLE_DIFFUSION_TIMEOUT_SECONDS");
let maybe_docker_options = easyenv::get_env_string_optional(
"STABLE_DIFFUSION_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(
"STABLE_DIFFUSION_ALTERNATE_CHECKPOINT_PATH");
Ok(Self {
stable_diffusion_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(
&self,
args: InferenceArgs,
) -> CommandExitStatus {
match self.do_execute_inference(args) {
Ok(exit_status) => exit_status,
Err(error) => CommandExitStatus::FailureWithReason { reason: format!("error: {:?}", error) },
}
}
fn do_execute_inference(
&self,
args: InferenceArgs,
) -> AnyhowResult<CommandExitStatus> {
let mut command = String::new();
command.push_str(&format!("cd {}", path_to_string(&self.stable_diffusion_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(format!("--prompt \"{}\"",&path_to_string(args.prompt)).as_ref());
command.push_str(" --output-dir ");
command.push_str(&path_to_string(args.work_dir));
command.push_str(" --output-name ");
command.push_str(&path_to_string(args.output_file));
// command.push_str(" --stderr-output-file ");
// command.push_str(&path_to_string(args.stderr_output_file.clone()));
command.push_str(format!(" --negative-prompt \"{}\"",&path_to_string(args.negative_prompt)).as_ref());
//command.push_str(&path_to_string(args.negative_prompt));
command.push_str(" --number-of-samples ");
command.push_str(args.number_of_samples.to_string().as_str());
command.push_str(format!(" --samplers \"{}\"",&path_to_string(args.samplers)).as_ref());
//command.push_str(&path_to_string(args.samplers));
command.push_str(" --width ");
command.push_str(args.width.to_string().as_str());
command.push_str(" --height ");
command.push_str(args.height.to_string().as_str());
command.push_str(" --cfg-scale ");
command.push_str(args.cfg_scale.to_string().as_str());
command.push_str(" --seed ");
command.push_str(args.seed.to_string().as_str());
command.push_str(" --loRA-path ");
command.push_str(&path_to_string(args.lora_path));
command.push_str(" --check-point ");
command.push_str(&path_to_string(args.checkpoint_path));
command.push_str(" --vae ");
command.push_str(&path_to_string(args.vae));
command.push_str(" --batch-count ");
command.push_str(args.batch_count.to_string().as_str());
command.push_str(" --batch-size ");
command.push_str(1.to_string().as_str());
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: {}", path_to_string(args.stderr_output_file.clone()));
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,19 @@
use anyhow::{Ok};
use errors::AnyhowResult;
use crate::job::job_types::image_generation::sd::model_downloaders::StableDiffusionDownloaders;
use crate::job::job_types::image_generation::sd::sd_inference_command::StableDiffusionInferenceCommand;
pub struct StableDiffusionDependencies {
pub downloaders: StableDiffusionDownloaders,
pub inference_command: StableDiffusionInferenceCommand,
}
impl StableDiffusionDependencies {
pub fn setup() -> AnyhowResult<Self> {
Ok(Self {
downloaders: StableDiffusionDownloaders::build_all_from_env(),
inference_command: StableDiffusionInferenceCommand::from_env()?,
})
}
}
@@ -0,0 +1,42 @@
use mysql_queries::payloads::generic_inference_args::image_generation_payload::StableDiffusionArgs;
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use anyhow::anyhow;
use mysql_queries::payloads::generic_inference_args::generic_inference_args::PolymorphicInferenceArgs;
use crate::job::job_types::image_generation::sd::process_job::StableDiffusionProcessArgs;
pub async fn validate_inputs(args: StableDiffusionProcessArgs<'_>) -> Result<(), ProcessSingleJobError> {
let inference_args = args.job.maybe_inference_args
.as_ref()
.map(|args| args.args.as_ref())
.flatten();
let polymorphic_args = match inference_args {
Some(args) => args,
None => {
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("no inference args for job!")));
}
};
let sd_args = match polymorphic_args {
PolymorphicInferenceArgs::Ig(args) => args,
_ => {
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("wrong inner args for job!")));
}
};
let stable_diffusion_args: StableDiffusionArgs = StableDiffusionArgs::from(sd_args.clone());
if stable_diffusion_args.type_of_inference == "checkpoint" {
} else if stable_diffusion_args.type_of_inference == "lora" {
} else if stable_diffusion_args.type_of_inference == "inference" {
} else {
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("wrong inference type for job!")));
}
Ok(())
}
@@ -2,4 +2,6 @@ pub mod lipsync;
pub mod tts;
pub mod vc;
pub(crate) mod videofilter;
pub(crate) mod mocap;
pub mod image_generation;
pub(crate) mod mocap;
@@ -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?")))
}
}
}
}
@@ -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);
}
}
@@ -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)
}
@@ -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()?,
})
}
}
@@ -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!")))
}
@@ -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,
@@ -72,6 +72,7 @@ pub struct VallEXInferenceCommand {
pub alternate_checkpoint_dir: Option<PathBuf>,
}
impl VallEXInferenceCommand {
pub fn new<P: AsRef<Path>>(
root_code_directory: P,

Some files were not shown because too many files have changed in this diff Show More