Merge branch 'feature/weights_files' of https://github.com/storytold/storyteller-rust into feature/weights_files

This commit is contained in:
Michael Chung committed 2023-12-18 09:26:42 -05:00
commit 39bae02259
153 files changed
+4434 -581

No files matched your search

+1 -1
View File
@@ -76,4 +76,4 @@ TTS_HIFIGAN_SUPERRES_VOCODER_MODEL_FILENAME=hifigan_superres_twilight_33000.pt
# Development key. Permissible to check in.
NEWRELIC_API_KEY=c402be1c4c4216ec83d0cfc0851520cfFFFFNRAL
STORYTELLER_ROOT="/home/tensor/code/storyteller/" # Put this in .env-secrets so that not everyone gets this setting.
#STORYTELLER_ROOT="/home/tensor/code/storyteller/" # Put this in .env-secrets so that not everyone gets this setting.
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,\n f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`,\n f.entity_token,\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n f.created_at,\n f.updated_at,\n f.deleted_at,\n\n media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,\n\n tts_models.title as maybe_descriptive_text_tts_model_title,\n tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,\n users.display_name as maybe_descriptive_text_user_display_name,\n voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title,\n zs_voices.title as maybe_descriptive_text_zs_voice_title\n\nFROM\n user_bookmarks AS f\nJOIN users AS u\n ON f.user_token = u.token\n\nLEFT OUTER JOIN media_files ON media_files.token = f.entity_token\nLEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token\nLEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token\nLEFT OUTER JOIN users ON users.token = f.entity_token\nLEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token\nLEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token\n\nWHERE\n u.username = ?\n AND f.entity_type = ?\n AND f.deleted_at IS NULL\nORDER BY f.id DESC\nLIMIT 50\n ",
"query": "\nSELECT\n f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,\n f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`,\n f.entity_token,\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n f.created_at,\n f.updated_at,\n f.deleted_at,\n\n media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,\n\n model_weights.title as maybe_descriptive_text_model_weight_title,\n tts_models.title as maybe_descriptive_text_tts_model_title,\n tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,\n users.display_name as maybe_descriptive_text_user_display_name,\n voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title,\n zs_voices.title as maybe_descriptive_text_zs_voice_title\n\nFROM\n user_bookmarks AS f\nJOIN users AS u\n ON f.user_token = u.token\n\nLEFT OUTER JOIN model_weights ON model_weights.token = f.entity_token\nLEFT OUTER JOIN media_files ON media_files.token = f.entity_token\nLEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token\nLEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token\nLEFT OUTER JOIN users ON users.token = f.entity_token\nLEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token\nLEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token\n\nWHERE\n u.username = ?\n AND f.entity_type = ?\n AND f.deleted_at IS NULL\nORDER BY f.id DESC\nLIMIT 50\n ",
"describe": {
"columns": [
{
@@ -125,7 +125,7 @@
},
{
"ordinal": 12,
"name": "maybe_descriptive_text_tts_model_title",
"name": "maybe_descriptive_text_model_weight_title",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
@@ -135,6 +135,16 @@
},
{
"ordinal": 13,
"name": "maybe_descriptive_text_tts_model_title",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 14,
"name": "maybe_descriptive_text_tts_result_inference_text",
"type_info": {
"type": "Blob",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "maybe_descriptive_text_user_display_name",
"type_info": {
"type": "VarString",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "maybe_descriptive_text_voice_conversion_model_title",
"type_info": {
"type": "VarString",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_descriptive_text_zs_voice_title",
"type_info": {
"type": "VarString",
@@ -194,8 +204,9 @@
true,
true,
true,
true,
true
]
},
"hash": "d9c00b3c78ed6ec31b281c7ea5fb1efd0cfa6b20899151f9e7f1a122c1f7a70e"
"hash": "3a7ac7f979ac270dad1070031d0d663d0eb3d969365f6b2fd048ca41c5c455b2"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,\n f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`,\n f.entity_token,\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n f.created_at,\n f.updated_at,\n f.deleted_at,\n\n media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,\n\n tts_models.title as maybe_descriptive_text_tts_model_title,\n tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,\n users.display_name as maybe_descriptive_text_user_display_name,\n voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title,\n zs_voices.title as maybe_descriptive_text_zs_voice_title\n\nFROM\n user_bookmarks AS f\nJOIN users AS u\n ON f.user_token = u.token\n\nLEFT OUTER JOIN media_files ON media_files.token = f.entity_token\nLEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token\nLEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token\nLEFT OUTER JOIN users ON users.token = f.entity_token\nLEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token\nLEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token\n\nWHERE\n u.username = ?\n AND f.deleted_at IS NULL\nORDER BY f.id DESC\nLIMIT 50\n ",
"query": "\nSELECT\n f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`,\n f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`,\n f.entity_token,\n f.user_token as `user_token: tokens::tokens::users::UserToken`,\n u.username,\n u.display_name as user_display_name,\n u.email_gravatar_hash as user_gravatar_hash,\n f.created_at,\n f.updated_at,\n f.deleted_at,\n\n media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,\n\n model_weights.title as maybe_descriptive_text_model_weight_title,\n tts_models.title as maybe_descriptive_text_tts_model_title,\n tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,\n users.display_name as maybe_descriptive_text_user_display_name,\n voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title,\n zs_voices.title as maybe_descriptive_text_zs_voice_title\n\nFROM\n user_bookmarks AS f\nJOIN users AS u\n ON f.user_token = u.token\n\nLEFT OUTER JOIN model_weights ON model_weights.token = f.entity_token\nLEFT OUTER JOIN media_files ON media_files.token = f.entity_token\nLEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token\nLEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token\nLEFT OUTER JOIN users ON users.token = f.entity_token\nLEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token\nLEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token\n\nWHERE\n u.username = ?\n AND f.deleted_at IS NULL\nORDER BY f.id DESC\nLIMIT 50\n ",
"describe": {
"columns": [
{
@@ -125,7 +125,7 @@
},
{
"ordinal": 12,
"name": "maybe_descriptive_text_tts_model_title",
"name": "maybe_descriptive_text_model_weight_title",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
@@ -135,6 +135,16 @@
},
{
"ordinal": 13,
"name": "maybe_descriptive_text_tts_model_title",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 14,
"name": "maybe_descriptive_text_tts_result_inference_text",
"type_info": {
"type": "Blob",
@@ -144,7 +154,7 @@
}
},
{
"ordinal": 14,
"ordinal": 15,
"name": "maybe_descriptive_text_user_display_name",
"type_info": {
"type": "VarString",
@@ -154,7 +164,7 @@
}
},
{
"ordinal": 15,
"ordinal": 16,
"name": "maybe_descriptive_text_voice_conversion_model_title",
"type_info": {
"type": "VarString",
@@ -164,7 +174,7 @@
}
},
{
"ordinal": 16,
"ordinal": 17,
"name": "maybe_descriptive_text_zs_voice_title",
"type_info": {
"type": "VarString",
@@ -194,8 +204,9 @@
true,
true,
true,
true,
true
]
},
"hash": "eea9531d5261833b93baadee8b99449362c074fc2397e23ecd175b6a0425b16b"
"hash": "707af35ee4ab4519f28e6d69982944666e6c7c0f24b5da79dde828ffdb2c8302"
}
Generated
+31 -80
View File
@@ -483,15 +483,6 @@ dependencies = [
"libc",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "anstream"
version = "0.6.4"
@@ -605,9 +596,9 @@ dependencies = [
[[package]]
name = "async-stripe"
version = "0.25.2"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1b39347f478f57a40cbaf82cbaebb4a350acc757cda30fc7ff88707a11d1d81"
checksum = "19a0b12cb3b2df9abfaa381632e4d55b2aede9063034daaa0d0145413719a7e1"
dependencies = [
"chrono",
"futures-util",
@@ -684,17 +675,6 @@ dependencies = [
"url",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi 0.3.9",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -813,6 +793,7 @@ dependencies = [
"tokio",
"toml 0.5.11",
"url_config",
"user_traits_component",
]
[[package]]
@@ -1098,21 +1079,6 @@ dependencies = [
"inout",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags 1.3.2",
"strsim 0.8.0",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap"
version = "4.4.8"
@@ -1132,7 +1098,7 @@ dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim 0.10.0",
"strsim",
]
[[package]]
@@ -1529,7 +1495,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim 0.10.0",
"strsim",
"syn 1.0.109",
]
@@ -1543,7 +1509,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim 0.10.0",
"strsim",
"syn 1.0.109",
]
@@ -1557,7 +1523,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim 0.10.0",
"strsim",
"syn 2.0.39",
]
@@ -1737,7 +1703,7 @@ name = "dev-database-seed"
version = "0.0.1"
dependencies = [
"buckets",
"clap 2.34.0",
"clap",
"cloud_storage",
"config",
"easyenv",
@@ -1763,7 +1729,7 @@ name = "dev-upload-media-file"
version = "0.0.1"
dependencies = [
"buckets",
"clap 2.34.0",
"clap",
"cloud_storage",
"config",
"easyenv",
@@ -2046,7 +2012,7 @@ name = "elasticsearch-cli"
version = "0.0.1"
dependencies = [
"buckets",
"clap 2.34.0",
"clap",
"config",
"easyenv",
"elasticsearch",
@@ -2102,7 +2068,6 @@ dependencies = [
"bootstrap",
"buckets",
"chrono",
"clap 2.34.0",
"cloud_storage",
"concurrency",
"config",
@@ -2666,15 +2631,6 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.3"
@@ -3017,11 +2973,13 @@ dependencies = [
"log",
"media",
"memory_caching",
"mime",
"mimetypes",
"my-workspace-hack",
"mysql_queries",
"once_cell",
"r2d2_redis",
"rand 0.8.5",
"redis_common",
"serde",
"serde_derive",
@@ -3030,6 +2988,7 @@ dependencies = [
"subprocess",
"subprocess_common",
"tempdir",
"time 0.3.30",
"tokens",
"tokio",
"tts_common",
@@ -3074,7 +3033,7 @@ version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi 0.3.3",
"hermit-abi",
"rustix",
"windows-sys 0.48.0",
]
@@ -3377,7 +3336,7 @@ dependencies = [
name = "lru-delete-models"
version = "0.0.1"
dependencies = [
"clap 4.4.8",
"clap",
"errors",
"logging",
"my-workspace-hack",
@@ -3970,7 +3929,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi 0.3.3",
"hermit-abi",
"libc",
]
@@ -5122,7 +5081,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
name = "scrape_test"
version = "0.0.1"
dependencies = [
"clap 2.34.0",
"clap",
"errors",
"my-workspace-hack",
"tokio",
@@ -5903,6 +5862,7 @@ dependencies = [
"hex",
"hmac 0.10.1",
"hostname",
"http",
"http_server_common",
"hyper",
"infer 0.5.0",
@@ -5950,6 +5910,7 @@ dependencies = [
"twitch_oauth2",
"url_config",
"user_input_common",
"user_traits_component",
"users_component",
"utoipa",
"utoipa-swagger-ui",
@@ -6002,12 +5963,6 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.10.0"
@@ -6327,15 +6282,6 @@ dependencies = [
"my-workspace-hack",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.50"
@@ -6438,6 +6384,7 @@ name = "tokens"
version = "0.0.1"
dependencies = [
"my-workspace-hack",
"once_cell",
"rand 0.8.5",
"serde",
"serde_derive",
@@ -6759,7 +6706,6 @@ version = "0.0.1"
dependencies = [
"anyhow",
"chrono",
"clap 2.34.0",
"cloud_storage",
"collections",
"config",
@@ -7061,6 +7007,16 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "user_traits_component"
version = "0.0.1"
dependencies = [
"actix-web",
"errors",
"mockall",
"my-workspace-hack",
]
[[package]]
name = "users_component"
version = "0.0.1"
@@ -7104,6 +7060,7 @@ dependencies = [
"tokens",
"toml 0.5.11",
"user_input_common",
"user_traits_component",
]
[[package]]
@@ -7180,12 +7137,6 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version-sync"
version = "0.9.5"
+1
View File
@@ -83,6 +83,7 @@ members = [
# Service Plugins: complete subsystems that can be plugged into services (users, billing, etc.)
"crates/service/plugins/billing",
"crates/service/plugins/inference_workloads",
"crates/service/plugins/user_traits_component",
"crates/service/plugins/users",
# Vendored code
+40
View File
@@ -344,6 +344,46 @@ where token IN (
'vcm_zmxv2k1q9jd8'
);
update voice_conversion_models
set
mod_deleted_at = NOW(),
maybe_mod_comments = 'takedown request'
where token IN (
'vcm_3mzcjkf78x7m',
'vcm_7k2t1wnyqxsc',
'vcm_w2edpe6q2nya',
'vcm_vz9cg294xa29',
'vcm_0z1ys58589k7',
'vcm_4x8a30h1r26z',
'vcm_0vf9prxsww60',
'vcm_yxbadkvqhg16',
'vcm_p0ezrc6wvzak',
'vcm_6r5pbtexyvtp',
'vcm_pb0ewrf7ych5',
'vcm_7frb2xk0cxk3',
'vcm_x129c9s2af06',
'vcm_e0snndfd59b6',
'vcm_nb8zb0djqwqw',
'vcm_2erz5hbh8tpf',
'vcm_e9wn2ccqe3bk',
'vcm_f8jbxdbjgcjb',
'vcm_x02h7z3jqxb0',
'vcm_xy97xcgnk9y6',
'vcm_a47wtfbsjrfc',
'vcm_kygenhbg5mgj',
'vcm_s8gndv19yt7p',
'vcm_ny9pj57w5eyh'
);
update voice_conversion_models
set
mod_deleted_at = NOW(),
maybe_mod_comments = 'abuse'
where token IN (
'vcm_qajyd528p8kw',
'vcm_f5pcdecmzcc5'
);
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE model_weights DROP COLUMN maybe_migration_old_model_token;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE model_weights
ADD COLUMN maybe_migration_old_model_token VARCHAR(32) DEFAULT NULL
AFTER cached_user_ratings_last_updated_at;
ALTER TABLE model_weights
ADD UNIQUE INDEX unique_index_maybe_migration_old_model_token (maybe_migration_old_model_token);
@@ -0,0 +1,6 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE voice_conversion_models
DROP COLUMN maybe_migration_new_model_weights_token;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE voice_conversion_models
ADD COLUMN maybe_migration_new_model_weights_token VARCHAR(32) DEFAULT NULL
AFTER maybe_mod_comments;
ALTER TABLE voice_conversion_models
ADD UNIQUE INDEX unique_index_maybe_migration_new_model_weights_token (maybe_migration_new_model_weights_token);
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE tts_models DROP COLUMN maybe_migration_new_model_weights_token;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE tts_models
ADD COLUMN maybe_migration_new_model_weights_token VARCHAR(32) DEFAULT NULL
AFTER maybe_mod_user_token;
ALTER TABLE tts_models
ADD UNIQUE INDEX unique_index_maybe_migration_new_model_weights_token (maybe_migration_new_model_weights_token);
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE vocoder_models DROP COLUMN maybe_migration_new_model_weights_token;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE vocoder_models
ADD COLUMN maybe_migration_new_model_weights_token VARCHAR(32) DEFAULT NULL
AFTER maybe_mod_user_token;
ALTER TABLE vocoder_models
ADD UNIQUE INDEX unique_index_maybe_migration_new_model_weights_token (maybe_migration_new_model_weights_token);
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE model_weights DROP COLUMN maybe_avatar_media_file_token;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE model_weights
ADD COLUMN maybe_avatar_media_file_token VARCHAR(32) DEFAULT NULL
AFTER maybe_thumbnail_token;
ALTER TABLE model_weights
ADD INDEX index_maybe_avatar_media_file_token (maybe_avatar_media_file_token);
@@ -0,0 +1,6 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE model_weights DROP COLUMN maybe_cover_media_file_token;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE model_weights
ADD COLUMN maybe_cover_media_file_token VARCHAR(32) DEFAULT NULL
AFTER maybe_avatar_media_file_token;
ALTER TABLE model_weights
ADD INDEX index_maybe_cover_media_file_token (maybe_cover_media_file_token);
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE users DROP COLUMN maybe_avatar_media_file_token;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE users
ADD COLUMN maybe_avatar_media_file_token VARCHAR(32) DEFAULT NULL
AFTER ip_address_last_update;
ALTER TABLE users
ADD INDEX index_maybe_avatar_media_file_token (maybe_avatar_media_file_token);
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE users DROP COLUMN maybe_cover_media_file_token;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE users
ADD COLUMN maybe_cover_media_file_token VARCHAR(32) DEFAULT NULL
AFTER maybe_avatar_media_file_token;
ALTER TABLE users
ADD INDEX index_maybe_cover_media_file_token (maybe_cover_media_file_token);
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE model_weights_preview_samples;
@@ -0,0 +1,47 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE model_weights_preview_samples (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- ========== WEIGHTS CLASSIFICATION ==========
-- Foreign key to model weights.
-- This is in a composite index with media_file_token to make sure a media file only applies
-- to a given model once.
model_weight_token VARCHAR(32) NOT NULL,
-- Foreign key to media files
-- This is in a composite index with model_weight_token to make sure a media file only applies
-- to a given model once.
media_file_token VARCHAR(32) NOT NULL,
-- Information about who set the sample
-- * automation
-- * author
-- * community
set_by VARCHAR(16) NOT NULL,
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod.
-- It completely disappears from the system.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (model_weight_token, media_file_token),
KEY index_model_weight_token (model_weight_token),
KEY index_media_file_token (media_file_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,9 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- I'm doing this to visually align these tables and sort them together, because there will likely be other model_weights_* tables.
RENAME TABLE model_weights_extension_image_generation_details TO model_weights_image_generation_details;
RENAME TABLE model_weights_extension_tts_details TO model_weights_tts_details;
RENAME TABLE model_weights_extension_vocoder_details TO model_weights_vocoder_details;
RENAME TABLE model_weights_extension_voice_conversion_details TO model_weights_voice_conversion_details;
@@ -0,0 +1,9 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- I'm doing this to visually align these tables and sort them together, because there will likely be other model_weights_* tables.
RENAME TABLE model_weights_image_generation_details TO model_weights_extension_image_generation_details;
RENAME TABLE model_weights_tts_details TO model_weights_extension_tts_details;
RENAME TABLE model_weights_vocoder_details TO model_weights_extension_vocoder_details;
RENAME TABLE model_weights_voice_conversion_details TO model_weights_extension_voice_conversion_details;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP INDEX index_weights_type ON model_weights;
DROP INDEX index_weights_category ON model_weights;
DROP INDEX fk_creator_user_token ON model_weights;
DROP INDEX index_creator_set_visibility ON model_weights;
DROP INDEX fk_maybe_avatar_media_file_token ON model_weights;
DROP INDEX fk_maybe_cover_media_file_token ON model_weights;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE INDEX index_weights_type ON model_weights (weights_type);
CREATE INDEX index_weights_category ON model_weights (weights_category);
CREATE INDEX fk_creator_user_token ON model_weights (creator_user_token);
CREATE INDEX index_creator_set_visibility ON model_weights (creator_set_visibility);
CREATE INDEX fk_maybe_avatar_media_file_token ON model_weights (maybe_avatar_media_file_token);
CREATE INDEX fk_maybe_cover_media_file_token ON model_weights (maybe_cover_media_file_token);
@@ -0,0 +1,6 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP INDEX fk_maybe_avatar_media_file_token ON users;
DROP INDEX fk_maybe_cover_media_file_token ON users;
@@ -0,0 +1,6 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE INDEX fk_maybe_avatar_media_file_token ON users (maybe_avatar_media_file_token);
CREATE INDEX fk_maybe_cover_media_file_token ON users (maybe_cover_media_file_token);
@@ -0,0 +1,200 @@
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
-- with comments attached to the fields for centralized documentation.
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Every type of fine tuned model that users can upload.
CREATE TABLE model_weights (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- ========== WEIGHTS CLASSIFICATION ==========
-- The exact type of weights
-- Currently supported values:
-- * 'hifigan_tt2' - hifigan for Tacotron2; TTS vocoder
-- * 'rvc_v2' - RVC (v2); voice conversion
-- * 'sd_1.5' - Stable Diffusion 1.5; image generation
-- * 'sdxl' - Stable Diffusion XL; image generation
-- * 'so_vits_svc' - SVC; voice conversion
-- * 'tt2' - Tacotron2; TTS
weights_type VARCHAR(32) NOT NULL,
-- The broader category of the weights
-- Currently supported values:
-- * 'image_generation' (SD1.5, SDXL)
-- * 'text_to_speech' (TT2)
-- * 'vocoder' (Hifigan)
-- * 'voice_conversion' (RVCv2, SVC)
weights_category VARCHAR(32) NOT NULL,
-- ========== MODEL DESCRIPTION ==========
-- The "name" of the model, which might be complicated.
title VARCHAR(255) NOT NULL,
-- TODO(bt, 2023-12-14): Delete the `maybe_thumbnail_token` column later.
-- We're using other columns instead.
maybe_thumbnail_token VARCHAR(32) DEFAULT NULL,
-- The "avatar" image is a media file of type image that serves as a
-- small avatar or profile picture icon.
maybe_avatar_media_file_token VARCHAR(32) DEFAULT NULL,
-- The "cover" image is a media file of type image that covers the
-- top of the page.
maybe_cover_media_file_token VARCHAR(32) DEFAULT NULL,
-- The description of the model in markdown.
description_markdown TEXT NOT NULL,
-- Generated HTML (not user-editable).
description_rendered_html TEXT NOT NULL,
-- ========== CREATOR DETAILS ==========
-- The person that created the model.
creator_user_token VARCHAR(32) NOT NULL,
-- For abuse tracking.
-- Wide enough for IPv4/6.
-- IP address of creator at time of creation.
creator_ip_address VARCHAR(40) NOT NULL,
-- (THIS MIGHT NOT BE USED)
-- NB: DO NOT SORT!
-- THIS MUST MATCH THE RESPECTIVE JOBS TABLE.
creator_set_visibility ENUM(
'public',
'hidden',
'private'
) NOT NULL DEFAULT 'public',
-- The synthetic id associated with this model.
-- These ids are incremented on a per-user basis to help users
-- sequence their own work. They serve no other purpose.
-- "weights" synthetic ids are incremented over all media files
-- creator_weights_synthetic_id BIGINT(20) DEFAULT NULL,
-- "weights_type" synthetic ids are incremented over a given weight type
-- creator_weights_type_synthetic_id BIGINT(20) DEFAULT NULL,
-- The last user to edit the model. This could be the creator or a moderator.
-- Check the future audit logs table for more info.
maybe_last_update_user_token VARCHAR(32) DEFAULT NULL,
-- ========== IMMUTABLE PROVENANCE AND METADATA ==========
-- Where the file was originally downloaded (if it was downloaded)
original_download_url VARCHAR(512) DEFAULT NULL,
-- The filename that was used at upload time (if available)
original_filename VARCHAR(255) DEFAULT NULL,
-- File characteristics
file_size_bytes INT(10) NOT NULL DEFAULT 0,
-- Checksum of the original weights file
-- SHA2 hash [SHA2 = CHAR(64), SHA1 = CHAR(40), MD5 = CHAR(32)]
-- Note that SHA2 is a hash family (SHA-228, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256, ...)
-- and produces different lengths output depending on the choice of algorithm.
file_checksum_sha2 CHAR(64) NOT NULL,
-- ========== BUCKET STORAGE ==========
-- The hash for the bucket directory that contains the original upload
-- as well as any associated other files.
-- The bucket filename for the primary file (not including the directory path) is given by:
-- `[{maybe_private_bucket_prefix}]{private_bucket_hash}[{maybe_private_bucket_extension}]`
private_bucket_hash VARCHAR(32) NOT NULL,
-- An optional prefix on the bucket filename.
-- If present, this will be prepended to the beginning of the bucket filename to access the file.
-- The bucket filename for the primary file (not including the directory path) is given by:
-- `[{maybe_private_bucket_prefix}]{private_directory_hash}[{maybe_private_bucket_extension}]`
maybe_private_bucket_prefix VARCHAR(16) DEFAULT NULL,
-- An optional appended extension on the bucket filename.
-- If present, this will be appended to the end of the bucket filename to access the file.
-- To allow for flexibility, this extension typically will contain a leading period if
-- the file needs it (eg ".mp4" rather than "mp4")!
-- The bucket filename for the primary file (not including the directory path) is given by:
-- `[{maybe_private_bucket_prefix}]{private_directory_hash}[{maybe_private_bucket_extension}]`
maybe_private_bucket_extension VARCHAR(16) DEFAULT NULL,
-- ========== MODERATION DETAILS ==========
-- Mods may have to approve of vc models for them to show up in a public index.
-- Models can be used by the author (or other parties that know the URL) before
-- they're approved, but unapproved models won't show up in public indices.
-- This is independent of other visibility controls (is_mod_disabled, deleted_at,
-- and creator_set_visibility)
-- is_public_listing_approved BOOLEAN DEFAULT NULL,
-- Extremely popular models may be locked from deletion or modification by users.
-- is_locked_from_user_modification BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== RATING AND RANKING ==========
-- User ratings are cached on the model, but are *not* a source of truth.
-- Total count only includes "positive" and "negative" votes, not neutral ones.
cached_user_ratings_total_count INT(10) UNSIGNED NOT NULL DEFAULT 0,
-- Total positive
cached_user_ratings_positive_count INT(10) UNSIGNED NOT NULL DEFAULT 0,
-- Total negative
cached_user_ratings_negative_count INT(10) UNSIGNED NOT NULL DEFAULT 0,
-- User ratings are cached on the model, but are *not* a source of truth.
-- Total count only includes "positive" and "negative" votes, not neutral ones.
maybe_cached_user_ratings_ratio FLOAT,
-- When the cached ratings were last calculated and updated.
cached_user_ratings_last_updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- ========== MIGRATION DETAILS ==========
-- If migrated from another table, this is the token of the original model.
maybe_migration_old_model_token VARCHAR(32) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod.
-- It completely disappears from the system.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY index_weights_type (weights_type),
KEY index_weights_category (weights_category),
KEY fk_creator_user_token (creator_user_token),
KEY index_creator_set_visibility (creator_set_visibility),
KEY fk_maybe_avatar_media_file_token (maybe_avatar_media_file_token),
KEY fk_maybe_cover_media_file_token (maybe_cover_media_file_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,50 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Every type of fine tuned model that users can upload.
CREATE TABLE model_weights_extension_image_generation_details (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Simultaneous primary and foreign key.
-- This should match a single record in the `model_weights` table.
-- This turns this table into a polymorphic extension table.
model_weights_token VARCHAR(32) NOT NULL,
-- ========== IMAGE GENERATION MODEL METADATA ==========
-- RVC (v2) specific - whether the model has an index file associated with it
-- These files improve the quality of the results.
vc_has_index_file BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod.
-- It completely disappears from the system.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (model_weights_token)
-- TODO:
-- KEY index_model_type (model_type),
-- KEY fk_maybe_vocoder_token (maybe_vocoder_token),
-- KEY fk_creator_user_token (creator_user_token),
-- KEY index_creator_set_visibility (creator_set_visibility),
-- KEY index_private_bucket_hash (private_bucket_hash),
-- KEY index_is_public_listing_approved (is_public_listing_approved),
-- KEY index_is_locked_from_user_modification (is_locked_from_user_modification)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,73 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Extra metadata on tts models
CREATE TABLE model_weights_extension_tts_details (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Simultaneous primary and foreign key.
-- This should match a single record in the `model_weights` table.
-- This turns this table into a polymorphic extension table.
model_weights_token VARCHAR(32) NOT NULL,
-- ========== TTS MODEL METADATA ==========
-- The full IETF BCP47 language tag (eg. en, en-US, es-419, ja-JP, pt, etc.)
-- (Not that it matters apart from categorization, since voice conversion is universal.)
ietf_language_tag VARCHAR(64) NOT NULL DEFAULT 'en',
-- The IETF BCP47 language tag's primary language subtag (eg. "es-419" becomes "es")
-- (Not that it matters apart from categorization, since voice conversion is universal.)
ietf_primary_language_subtag VARCHAR(12) NOT NULL DEFAULT 'en',
-- This is for Tacotron2, specifically.
-- The name of the type of pipeline to use, eg "legacy_fakeyou", "legacy_vocodes", "english_v1", etc.
-- New pipelines may be added at any point in the future and the source of truth for them will mostly
-- live in the `storyteller-ml` repository.
-- Older models and brand new models will have this field set to null, and we'll infer which value to
-- lazily backfill based on the creation date and language setting.
-- tts_text_pipeline_type VARCHAR(64) DEFAULT NULL,
-- If true, multiply the mel outputs before being vocoded by a globally default constant.
use_default_mel_multiply_factor BOOLEAN NOT NULL DEFAULT FALSE,
-- If not null, multiply the mel outputs before being vocoded by this value.
-- This is used instead of the default if `tts_use_default_mel_multiply_factor` is
-- set (ie. `tts_use_default_mel_multiply_factor` is ignored and this custom value is used instead)
maybe_custom_mel_multiply_factor DOUBLE DEFAULT NULL,
-- If set, use a different pretrained vocoder.
-- If not set, use the website default. (Currently HifiGan-SS)
maybe_default_pretrained_vocoder VARCHAR(64) DEFAULT NULL,
-- If set, we'll use a custom vocoder (from table vocoder_models) instead of a default vocoder.
-- If not set, we'll use `maybe_default_pretrained_vocoder` (or the website default).
maybe_custom_vocoder_token VARCHAR(32) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== 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 (model_weights_token)
-- TODO:
-- KEY index_model_type (model_type),
-- KEY fk_maybe_vocoder_token (maybe_vocoder_token),
-- KEY fk_creator_user_token (creator_user_token),
-- KEY index_creator_set_visibility (creator_set_visibility),
-- KEY index_private_bucket_hash (private_bucket_hash),
-- KEY index_is_public_listing_approved (is_public_listing_approved),
-- KEY index_is_locked_from_user_modification (is_locked_from_user_modification)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,50 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Every type of fine tuned model that users can upload.
CREATE TABLE model_weights_extension_vocoder_details (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Simultaneous primary and foreign key.
-- This should match a single record in the `model_weights` table.
-- This turns this table into a polymorphic extension table.
model_weights_token VARCHAR(32) NOT NULL,
-- ========== VOCODER MODEL METADATA ==========
-- Whether the vocoder is recommended for use
-- vocoder_is_staff_recommended BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod.
-- It completely disappears from the system.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (model_weights_token)
-- TODO:
-- KEY index_model_type (model_type),
-- KEY fk_maybe_vocoder_token (maybe_vocoder_token),
-- KEY fk_creator_user_token (creator_user_token),
-- KEY index_creator_set_visibility (creator_set_visibility),
-- KEY index_private_bucket_hash (private_bucket_hash),
-- KEY index_is_public_listing_approved (is_public_listing_approved),
-- KEY index_is_locked_from_user_modification (is_locked_from_user_modification)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,62 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Every type of fine tuned model that users can upload.
CREATE TABLE model_weights_extension_voice_conversion_details (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Simultaneous primary and foreign key.
-- This should match a single record in the `model_weights` table.
-- This turns this table into a polymorphic extension table.
model_weights_token VARCHAR(32) NOT NULL,
-- ========== VOICE CONVERSION MODEL METADATA ==========
-- The full IETF BCP47 language tag (eg. en, en-US, es-419, ja-JP, pt, etc.)
-- (Not that it matters apart from categorization, since voice conversion is universal.)
ietf_language_tag VARCHAR(64) NOT NULL DEFAULT 'en',
-- The IETF BCP47 language tag's primary language subtag (eg. "es-419" becomes "es")
-- (Not that it matters apart from categorization, since voice conversion is universal.)
ietf_primary_language_subtag VARCHAR(12) NOT NULL DEFAULT 'en',
-- RVC (v2) specific - whether the model has an index file associated with it
-- These files improve the quality of the results.
has_index_file BOOLEAN NOT NULL DEFAULT FALSE,
-- Pitch algorithm to use by default
default_pitch_algorithm VARCHAR(64) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod.
-- It completely disappears from the system.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (model_weights_token)
-- TODO:
-- KEY index_model_type (model_type),
-- KEY fk_maybe_vocoder_token (maybe_vocoder_token),
-- KEY fk_creator_user_token (creator_user_token),
-- KEY index_creator_set_visibility (creator_set_visibility),
-- KEY index_private_bucket_hash (private_bucket_hash),
-- KEY index_is_public_listing_approved (is_public_listing_approved),
-- KEY index_is_locked_from_user_modification (is_locked_from_user_modification)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,47 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE model_weights_preview_samples (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- ========== WEIGHTS CLASSIFICATION ==========
-- Foreign key to model weights.
-- This is in a composite index with media_file_token to make sure a media file only applies
-- to a given model once.
model_weight_token VARCHAR(32) NOT NULL,
-- Foreign key to media files
-- This is in a composite index with model_weight_token to make sure a media file only applies
-- to a given model once.
media_file_token VARCHAR(32) NOT NULL,
-- Information about who set the sample
-- * automation
-- * author
-- * community
set_by VARCHAR(16) NOT NULL,
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- If this is removed by a mod.
-- It completely disappears from the system.
user_deleted_at TIMESTAMP NULL,
mod_deleted_at TIMESTAMP NULL,
-- INDICES --
PRIMARY KEY (id),
UNIQUE KEY (model_weight_token, media_file_token),
KEY index_model_weight_token (model_weight_token),
KEY index_media_file_token (media_file_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -200,6 +200,11 @@ CREATE TABLE tts_models (
-- The last moderator that made changes.
maybe_mod_user_token VARCHAR(32) DEFAULT NULL,
-- ========== MIGRATION DETAILS ==========
-- If migrated to model_weights, this is the new token
maybe_migration_new_model_weights_token VARCHAR(32) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
@@ -99,6 +99,11 @@ CREATE TABLE vocoder_models (
-- The last moderator that made changes.
maybe_mod_user_token VARCHAR(32) DEFAULT NULL,
-- ========== MIGRATION DETAILS ==========
-- If migrated to model_weights, this is the new token
maybe_migration_new_model_weights_token VARCHAR(32) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
+11 -1
View File
@@ -53,6 +53,14 @@ CREATE TABLE users (
-- ========== DISPLAY / PROFILE ==========
-- The "avatar" image is a media file of type image that serves as a
-- small avatar or profile picture icon.
maybe_avatar_media_file_token VARCHAR(32) DEFAULT NULL,
-- The "cover" image is a media file of type image that covers the
-- top of the page.
maybe_cover_media_file_token VARCHAR(32) DEFAULT NULL,
-- Gravatar image hashes are precomputed.
email_gravatar_hash CHAR(32) NOT NULL,
@@ -162,7 +170,9 @@ CREATE TABLE users (
UNIQUE KEY (token),
UNIQUE KEY (username),
UNIQUE KEY (email_address),
KEY fk_user_role_slug (user_role_slug)
KEY fk_user_role_slug (user_role_slug),
KEY fk_maybe_avatar_media_file_token (maybe_avatar_media_file_token),
KEY fk_maybe_cover_media_file_token (maybe_cover_media_file_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -121,6 +121,11 @@ CREATE TABLE voice_conversion_models (
-- If a moderator has comments.
maybe_mod_comments VARCHAR(255) DEFAULT NULL,
-- ========== MIGRATION DETAILS ==========
-- If migrated to model_weights, this is the new token
maybe_migration_new_model_weights_token VARCHAR(32) DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
@@ -13,6 +13,14 @@ pub enum CommentEntityType {
#[serde(rename = "user")]
User,
/// Media files
#[serde(rename = "media_file")]
MediaFile,
/// Model weights
#[serde(rename = "model_weight")]
ModelWeight,
/// TTS model (architecture does not matter)
#[serde(rename = "tts_model")]
TtsModel,
@@ -39,6 +47,8 @@ impl CommentEntityType {
pub fn to_str(&self) -> &'static str {
match self {
Self::User => "user",
Self::MediaFile => "media_file",
Self::ModelWeight => "model_weight",
Self::TtsModel => "tts_model",
Self::TtsResult => "tts_result",
Self::W2lTemplate => "w2l_template",
@@ -49,6 +59,8 @@ impl CommentEntityType {
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"user" => Ok(Self::User),
"media_file" => Ok(Self::MediaFile),
"model_weight" => Ok(Self::ModelWeight),
"tts_model" => Ok(Self::TtsModel),
"tts_result" => Ok(Self::TtsResult),
"w2l_template" => Ok(Self::W2lTemplate),
@@ -69,6 +81,8 @@ mod tests {
#[test]
fn test_serialization() {
assert_serialization(CommentEntityType::User, "user");
assert_serialization(CommentEntityType::MediaFile, "media_file");
assert_serialization(CommentEntityType::ModelWeight, "model_weight");
assert_serialization(CommentEntityType::TtsModel, "tts_model");
assert_serialization(CommentEntityType::TtsResult, "tts_result");
assert_serialization(CommentEntityType::W2lTemplate, "w2l_template");
@@ -82,6 +96,8 @@ mod tests {
#[test]
fn test_to_str() {
assert_eq!(CommentEntityType::User.to_str(), "user");
assert_eq!(CommentEntityType::MediaFile.to_str(), "media_file");
assert_eq!(CommentEntityType::ModelWeight.to_str(), "model_weight");
assert_eq!(CommentEntityType::TtsModel.to_str(), "tts_model");
assert_eq!(CommentEntityType::TtsResult.to_str(), "tts_result");
assert_eq!(CommentEntityType::W2lTemplate.to_str(), "w2l_template");
@@ -91,6 +107,8 @@ mod tests {
#[test]
fn test_from_str() {
assert_eq!(CommentEntityType::from_str("user").unwrap(), CommentEntityType::User);
assert_eq!(CommentEntityType::from_str("media_file").unwrap(), CommentEntityType::MediaFile);
assert_eq!(CommentEntityType::from_str("model_weight").unwrap(), CommentEntityType::ModelWeight);
assert_eq!(CommentEntityType::from_str("tts_model").unwrap(), CommentEntityType::TtsModel);
assert_eq!(CommentEntityType::from_str("tts_result").unwrap(), CommentEntityType::TtsResult);
assert_eq!(CommentEntityType::from_str("w2l_template").unwrap(), CommentEntityType::W2lTemplate);
@@ -25,6 +25,9 @@ pub enum InferenceCategory {
#[serde(rename = "voice_conversion")]
VoiceConversion,
#[serde(rename = "rerender_a_video")]
RerenderAVideo,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -38,6 +41,7 @@ impl InferenceCategory {
Self::LipsyncAnimation => "lipsync_animation",
Self::TextToSpeech => "text_to_speech",
Self::VoiceConversion => "voice_conversion",
Self::RerenderAVideo => "rerender_a_video",
}
}
@@ -46,6 +50,7 @@ impl InferenceCategory {
"lipsync_animation" => Ok(Self::LipsyncAnimation),
"text_to_speech" => Ok(Self::TextToSpeech),
"voice_conversion" => Ok(Self::VoiceConversion),
"rerender_a_video" => Ok(Self::RerenderAVideo),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -57,6 +62,7 @@ impl InferenceCategory {
Self::LipsyncAnimation,
Self::TextToSpeech,
Self::VoiceConversion,
Self::RerenderAVideo,
])
}
}
@@ -71,6 +77,7 @@ mod tests {
assert_serialization(InferenceCategory::LipsyncAnimation, "lipsync_animation");
assert_serialization(InferenceCategory::TextToSpeech, "text_to_speech");
assert_serialization(InferenceCategory::VoiceConversion, "voice_conversion");
assert_serialization(InferenceCategory::RerenderAVideo, "rerender_a_video");
}
#[test]
@@ -78,6 +85,7 @@ mod tests {
assert_eq!(InferenceCategory::LipsyncAnimation.to_str(), "lipsync_animation");
assert_eq!(InferenceCategory::TextToSpeech.to_str(), "text_to_speech");
assert_eq!(InferenceCategory::VoiceConversion.to_str(), "voice_conversion");
assert_eq!(InferenceCategory::RerenderAVideo.to_str(), "rerender_a_video");
}
#[test]
@@ -85,16 +93,18 @@ mod tests {
assert_eq!(InferenceCategory::from_str("lipsync_animation").unwrap(), InferenceCategory::LipsyncAnimation);
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("rerender_a_video").unwrap(), InferenceCategory::RerenderAVideo);
}
#[test]
fn all_variants() {
// Static check
let mut variants = InferenceCategory::all_variants();
assert_eq!(variants.len(), 3);
assert_eq!(variants.len(), 4);
assert_eq!(variants.pop_first(), Some(InferenceCategory::LipsyncAnimation));
assert_eq!(variants.pop_first(), Some(InferenceCategory::TextToSpeech));
assert_eq!(variants.pop_first(), Some(InferenceCategory::VoiceConversion));
assert_eq!(variants.pop_first(), Some(InferenceCategory::RerenderAVideo));
assert_eq!(variants.pop_first(), None);
// Generated check
@@ -31,6 +31,8 @@ pub enum InferenceModelType {
Vits,
#[serde(rename = "vall_e_x")]
VallEX,
#[serde(rename = "rerender_a_video")]
RerenderAVideo,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -46,7 +48,8 @@ impl InferenceModelType {
Self::SoVitsSvc => "so_vits_svc",
Self::Tacotron2 => "tacotron2",
Self::Vits => "vits",
Self::VallEX => "vall_e_x"
Self::VallEX => "vall_e_x",
Self::RerenderAVideo => "rerender_a_video",
}
}
@@ -58,6 +61,7 @@ impl InferenceModelType {
"tacotron2" => Ok(Self::Tacotron2),
"vits" => Ok(Self::Vits),
"vall_e_x" => Ok(Self::VallEX),
"rerender_a_video" => Ok(Self::RerenderAVideo),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -71,7 +75,8 @@ impl InferenceModelType {
InferenceModelType::SoVitsSvc,
InferenceModelType::Tacotron2,
InferenceModelType::Vits,
InferenceModelType::VallEX
InferenceModelType::VallEX,
InferenceModelType::RerenderAVideo
])
}
}
@@ -89,6 +94,7 @@ mod tests {
assert_serialization(InferenceModelType::Tacotron2, "tacotron2");
assert_serialization(InferenceModelType::Vits, "vits");
assert_serialization(InferenceModelType::VallEX, "vall_e_x");
assert_serialization(InferenceModelType::RerenderAVideo, "rerender_a_video");
}
#[test]
@@ -99,6 +105,7 @@ mod tests {
assert_eq!(InferenceModelType::Tacotron2.to_str(), "tacotron2");
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");
}
#[test]
@@ -109,19 +116,21 @@ mod tests {
assert_eq!(InferenceModelType::from_str("tacotron2").unwrap(), InferenceModelType::Tacotron2);
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);
}
#[test]
fn all_variants() {
// Static check
let mut variants = InferenceModelType::all_variants();
assert_eq!(variants.len(), 6);
assert_eq!(variants.len(), 7);
assert_eq!(variants.pop_first(), Some(InferenceModelType::RvcV2));
assert_eq!(variants.pop_first(), Some(InferenceModelType::SadTalker));
assert_eq!(variants.pop_first(), Some(InferenceModelType::SoVitsSvc));
assert_eq!(variants.pop_first(), Some(InferenceModelType::Tacotron2));
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(), None);
// Generated check
@@ -21,6 +21,10 @@ pub enum IdCategory {
#[serde(rename = "lipsync_animation")]
LipsyncAnimationResult,
// Results from rerender a video
#[serde(rename = "rerender")]
RerenderResult,
/// Results from tacotron2
/// Applies for RVC and SVC
#[serde(rename = "tts_result")]
@@ -62,6 +66,7 @@ impl IdCategory {
Self::ZeroShotVoiceDataset => "zs_dataset",
Self::ZeroShotVoiceEmbedding => "zs_voice",
Self::ZeroShotTtsResult => "zs_tts_result",
Self::RerenderResult => "rerender",
Self::ModelWeights => "model_weights",
}
}
@@ -75,6 +80,7 @@ impl IdCategory {
"zs_dataset" => Ok(Self::ZeroShotVoiceDataset),
"zs_voice" => Ok(Self::ZeroShotVoiceEmbedding),
"zs_tts_result" => Ok(Self::ZeroShotTtsResult),
"rerender" => Ok(Self::RerenderResult),
"model_weights" => Ok(Self::ModelWeights),
_ => Err(format!("invalid value: {:?}", value)),
}
@@ -86,6 +92,7 @@ impl IdCategory {
BTreeSet::from([
Self::MediaFile,
Self::LipsyncAnimationResult,
Self::RerenderResult,
Self::TtsResult,
Self::VoiceConversionResult,
Self::ZeroShotTtsResult,
@@ -108,6 +115,7 @@ mod tests {
fn test_serialization() {
assert_serialization(IdCategory::MediaFile, "media_file");
assert_serialization(IdCategory::LipsyncAnimationResult, "lipsync_animation");
assert_serialization(IdCategory::RerenderResult, "rerender");
assert_serialization(IdCategory::TtsResult, "tts_result");
assert_serialization(IdCategory::VoiceConversionResult, "voice_conversion");
assert_serialization(IdCategory::ZeroShotVoiceDataset, "zs_dataset");
@@ -120,6 +128,7 @@ mod tests {
fn to_str() {
assert_eq!(IdCategory::MediaFile.to_str(), "media_file");
assert_eq!(IdCategory::LipsyncAnimationResult.to_str(), "lipsync_animation");
assert_eq!(IdCategory::RerenderResult.to_str(), "rerender");
assert_eq!(IdCategory::TtsResult.to_str(), "tts_result");
assert_eq!(IdCategory::VoiceConversionResult.to_str(), "voice_conversion");
assert_eq!(IdCategory::ZeroShotVoiceDataset.to_str(), "zs_dataset");
@@ -132,6 +141,7 @@ mod tests {
fn from_str() {
assert_eq!(IdCategory::from_str("media_file").unwrap(), IdCategory::MediaFile);
assert_eq!(IdCategory::from_str("lipsync_animation").unwrap(), IdCategory::LipsyncAnimationResult);
assert_eq!(IdCategory::from_str("rerender").unwrap(), IdCategory::RerenderResult);
assert_eq!(IdCategory::from_str("tts_result").unwrap(), IdCategory::TtsResult);
assert_eq!(IdCategory::from_str("voice_conversion").unwrap(), IdCategory::VoiceConversionResult);
assert_eq!(IdCategory::from_str("zs_dataset").unwrap(), IdCategory::ZeroShotVoiceDataset);
@@ -144,9 +154,10 @@ mod tests {
fn all_variants() {
// Static check
let mut variants = IdCategory::all_variants();
assert_eq!(variants.len(), 8);
assert_eq!(variants.len(), 9);
assert_eq!(variants.pop_first(), Some(IdCategory::MediaFile));
assert_eq!(variants.pop_first(), Some(IdCategory::LipsyncAnimationResult));
assert_eq!(variants.pop_first(), Some(IdCategory::RerenderResult));
assert_eq!(variants.pop_first(), Some(IdCategory::TtsResult));
assert_eq!(variants.pop_first(), Some(IdCategory::VoiceConversionResult));
assert_eq!(variants.pop_first(), Some(IdCategory::ZeroShotTtsResult));
@@ -30,6 +30,9 @@ pub enum MediaFileOriginModelType {
#[serde(rename = "vall_e_x")]
VallEX,
#[serde(rename = "rerender")]
Rerender,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -45,7 +48,8 @@ impl MediaFileOriginModelType {
Self::SadTalker => "sad_talker",
Self::SoVitsSvc => "so_vits_svc",
Self::Tacotron2 => "tacotron2",
Self::VallEX => "vall_e_x"
Self::VallEX => "vall_e_x",
Self::Rerender => "rerender",
}
}
@@ -56,6 +60,7 @@ impl MediaFileOriginModelType {
"so_vits_svc" => Ok(Self::SoVitsSvc),
"tacotron2" => Ok(Self::Tacotron2),
"vall_e_x" => Ok(Self::VallEX),
"rerender" => Ok(Self::Rerender),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -68,7 +73,8 @@ impl MediaFileOriginModelType {
Self::SadTalker,
Self::SoVitsSvc,
Self::Tacotron2,
Self::VallEX
Self::VallEX,
Self::Rerender,
])
}
}
@@ -93,6 +99,7 @@ mod tests {
assert_eq!(MediaFileOriginModelType::SoVitsSvc.to_str(), "so_vits_svc");
assert_eq!(MediaFileOriginModelType::Tacotron2.to_str(), "tacotron2");
assert_eq!(MediaFileOriginModelType::VallEX.to_str(), "vall_e_x");
assert_eq!(MediaFileOriginModelType::Rerender.to_str(), "rerender");
}
#[test]
@@ -102,18 +109,20 @@ mod tests {
assert_eq!(MediaFileOriginModelType::from_str("so_vits_svc").unwrap(), MediaFileOriginModelType::SoVitsSvc);
assert_eq!(MediaFileOriginModelType::from_str("tacotron2").unwrap(), MediaFileOriginModelType::Tacotron2);
assert_eq!(MediaFileOriginModelType::from_str("vall_e_x").unwrap(), MediaFileOriginModelType::VallEX);
assert_eq!(MediaFileOriginModelType::from_str("rerender").unwrap(), MediaFileOriginModelType::Rerender);
assert!(MediaFileOriginModelType::from_str("foo").is_err());
}
#[test]
fn all_variants() {
let mut variants = MediaFileOriginModelType::all_variants();
assert_eq!(variants.len(), 5);
assert_eq!(variants.len(), 6);
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::RvcV2));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::SadTalker));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::SoVitsSvc));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::Tacotron2));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::VallEX));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::Rerender));
assert_eq!(variants.pop_first(), None);
}
}
@@ -38,6 +38,10 @@ pub enum MediaFileOriginProductCategory {
/// Media files created by (or uploaded for) the Zero Shot voice product.
#[serde(rename = "zs_voice")]
ZeroShotVoice,
// Media files for Rerender
#[serde(rename = "rerender")]
Rerender,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -54,6 +58,7 @@ impl MediaFileOriginProductCategory {
Self::Unknown => "unknown",
Self::VoiceConversion => "voice_conversion",
Self::ZeroShotVoice => "zs_voice",
Self::Rerender => "rerender",
}
}
@@ -64,6 +69,7 @@ impl MediaFileOriginProductCategory {
"unknown" => Ok(Self::Unknown),
"voice_conversion" => Ok(Self::VoiceConversion),
"zs_voice" => Ok(Self::ZeroShotVoice),
"rerender" => Ok(Self::Rerender),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -74,6 +80,7 @@ impl MediaFileOriginProductCategory {
BTreeSet::from([
Self::FaceAnimator,
Self::TextToSpeech,
Self::Rerender,
Self::Unknown,
Self::VoiceConversion,
Self::ZeroShotVoice,
@@ -96,6 +103,7 @@ mod tests {
assert_serialization(MediaFileOriginProductCategory::Unknown, "unknown");
assert_serialization(MediaFileOriginProductCategory::VoiceConversion, "voice_conversion");
assert_serialization(MediaFileOriginProductCategory::ZeroShotVoice, "zs_voice");
assert_serialization(MediaFileOriginProductCategory::Rerender, "rerender")
}
#[test]
@@ -105,6 +113,7 @@ mod tests {
assert_eq!(MediaFileOriginProductCategory::Unknown.to_str(), "unknown");
assert_eq!(MediaFileOriginProductCategory::VoiceConversion.to_str(), "voice_conversion");
assert_eq!(MediaFileOriginProductCategory::ZeroShotVoice.to_str(), "zs_voice");
assert_eq!(MediaFileOriginProductCategory::Rerender.to_str(), "rerender");
}
#[test]
@@ -114,17 +123,19 @@ mod tests {
assert_eq!(MediaFileOriginProductCategory::from_str("unknown").unwrap(), MediaFileOriginProductCategory::Unknown);
assert_eq!(MediaFileOriginProductCategory::from_str("voice_conversion").unwrap(), MediaFileOriginProductCategory::VoiceConversion);
assert_eq!(MediaFileOriginProductCategory::from_str("zs_voice").unwrap(), MediaFileOriginProductCategory::ZeroShotVoice);
assert_eq!(MediaFileOriginProductCategory::from_str("rerender").unwrap(), MediaFileOriginProductCategory::Rerender);
}
#[test]
fn all_variants() {
let mut variants = MediaFileOriginProductCategory::all_variants();
assert_eq!(variants.len(), 5);
assert_eq!(variants.len(), 6);
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::FaceAnimator));
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::TextToSpeech));
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::Unknown));
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::VoiceConversion));
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::ZeroShotVoice));
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::Rerender));
assert_eq!(variants.pop_first(), None);
}
}
@@ -4,9 +4,8 @@ use std::collections::BTreeSet;
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
use utoipa::ToSchema;
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, ToSchema)]
pub enum WeightsCategory {
@@ -51,7 +50,7 @@ impl WeightsCategory {
}
impl_enum_display_and_debug_using_to_str!(WeightsCategory);
impl_mysql_enum_coders!(WeightsCategory);
impl_mysql_from_row!(WeightsCategory);
#[cfg(test)]
mod tests {
@@ -72,7 +72,7 @@ impl WeightsType {
impl_enum_display_and_debug_using_to_str!(WeightsType);
impl_mysql_enum_coders!(WeightsType);
impl_mysql_from_row!(WeightsType);
#[cfg(test)]
mod tests {
@@ -33,6 +33,10 @@ pub enum UserBookmarkEntityType {
#[serde(rename = "media_file")]
MediaFile,
/// ModelWeight (the new way to store models)
#[serde(rename = "model_weight")]
ModelWeight,
/// VoiceConversionModel
#[serde(rename = "voice_conversion_model")]
VoiceConversionModel,
@@ -56,6 +60,7 @@ impl UserBookmarkEntityType {
Self::W2lTemplate => "w2l_template",
Self::W2lResult => "w2l_result",
Self::MediaFile => "media_file",
Self::ModelWeight => "model_weight",
Self::VoiceConversionModel => "voice_conversion_model",
Self::ZsVoice => "zs_voice",
}
@@ -69,6 +74,7 @@ impl UserBookmarkEntityType {
"w2l_template" => Ok(Self::W2lTemplate),
"w2l_result" => Ok(Self::W2lResult),
"media_file" => Ok(Self::MediaFile),
"model_weight" => Ok(Self::ModelWeight),
"voice_conversion_model" => Ok(Self::VoiceConversionModel),
"zs_voice" => Ok(Self::ZsVoice),
_ => Err(format!("invalid value: {:?}", value)),
@@ -92,6 +98,7 @@ mod tests {
assert_serialization(UserBookmarkEntityType::W2lTemplate, "w2l_template");
assert_serialization(UserBookmarkEntityType::W2lResult, "w2l_result");
assert_serialization(UserBookmarkEntityType::MediaFile, "media_file");
assert_serialization(UserBookmarkEntityType::ModelWeight, "model_weight");
assert_serialization(UserBookmarkEntityType::VoiceConversionModel, "voice_conversion_model");
assert_serialization(UserBookmarkEntityType::ZsVoice, "zs_voice");
}
@@ -108,6 +115,7 @@ mod tests {
assert_eq!(UserBookmarkEntityType::W2lTemplate.to_str(), "w2l_template");
assert_eq!(UserBookmarkEntityType::W2lResult.to_str(), "w2l_result");
assert_eq!(UserBookmarkEntityType::MediaFile.to_str(), "media_file");
assert_eq!(UserBookmarkEntityType::ModelWeight.to_str(), "model_weight");
assert_eq!(UserBookmarkEntityType::VoiceConversionModel.to_str(), "voice_conversion_model");
assert_eq!(UserBookmarkEntityType::ZsVoice.to_str(), "zs_voice");
}
@@ -120,6 +128,7 @@ mod tests {
assert_eq!(UserBookmarkEntityType::from_str("w2l_template").unwrap(), UserBookmarkEntityType::W2lTemplate);
assert_eq!(UserBookmarkEntityType::from_str("w2l_result").unwrap(), UserBookmarkEntityType::W2lResult);
assert_eq!(UserBookmarkEntityType::from_str("media_file").unwrap(), UserBookmarkEntityType::MediaFile);
assert_eq!(UserBookmarkEntityType::from_str("model_weight").unwrap(), UserBookmarkEntityType::ModelWeight);
assert_eq!(UserBookmarkEntityType::from_str("voice_conversion_model").unwrap(), UserBookmarkEntityType::VoiceConversionModel);
assert_eq!(UserBookmarkEntityType::from_str("zs_voice").unwrap(), UserBookmarkEntityType::ZsVoice);
assert!(UserBookmarkEntityType::from_str("foo").is_err());
@@ -9,6 +9,14 @@ use strum::EnumIter;
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Deserialize, Serialize)]
pub enum UserRatingEntityType {
/// Media files (inference results, uploads, etc.)
#[serde(rename = "media_file")]
MediaFile,
/// Model weights (modern, polymorphic, type agnostic)
#[serde(rename = "model_weight")]
ModelWeight,
/// TTS model (architecture does not matter)
#[serde(rename = "tts_model")]
TtsModel,
@@ -34,6 +42,8 @@ impl_mysql_enum_coders!(UserRatingEntityType);
impl UserRatingEntityType {
pub fn to_str(&self) -> &'static str {
match self {
Self::MediaFile => "media_file",
Self::ModelWeight => "model_weight",
Self::TtsModel => "tts_model",
Self::TtsResult => "tts_result",
Self::W2lTemplate => "w2l_template",
@@ -43,6 +53,8 @@ impl UserRatingEntityType {
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"media_file" => Ok(Self::MediaFile),
"model_weight" => Ok(Self::ModelWeight),
"tts_model" => Ok(Self::TtsModel),
"tts_result" => Ok(Self::TtsResult),
"w2l_template" => Ok(Self::W2lTemplate),
@@ -62,6 +74,8 @@ mod tests {
#[test]
fn test_serialization() {
assert_serialization(UserRatingEntityType::MediaFile, "media_file");
assert_serialization(UserRatingEntityType::ModelWeight, "model_weight");
assert_serialization(UserRatingEntityType::TtsModel, "tts_model");
assert_serialization(UserRatingEntityType::TtsResult, "tts_result");
assert_serialization(UserRatingEntityType::W2lTemplate, "w2l_template");
@@ -74,6 +88,8 @@ mod tests {
#[test]
fn test_to_str() {
assert_eq!(UserRatingEntityType::MediaFile.to_str(), "media_file");
assert_eq!(UserRatingEntityType::ModelWeight.to_str(), "model_weight");
assert_eq!(UserRatingEntityType::TtsModel.to_str(), "tts_model");
assert_eq!(UserRatingEntityType::TtsResult.to_str(), "tts_result");
assert_eq!(UserRatingEntityType::W2lTemplate.to_str(), "w2l_template");
@@ -82,6 +98,8 @@ mod tests {
#[test]
fn test_from_str() {
assert_eq!(UserRatingEntityType::from_str("media_file").unwrap(), UserRatingEntityType::MediaFile);
assert_eq!(UserRatingEntityType::from_str("model_weight").unwrap(), UserRatingEntityType::ModelWeight);
assert_eq!(UserRatingEntityType::from_str("tts_model").unwrap(), UserRatingEntityType::TtsModel);
assert_eq!(UserRatingEntityType::from_str("tts_result").unwrap(), UserRatingEntityType::TtsResult);
assert_eq!(UserRatingEntityType::from_str("w2l_template").unwrap(), UserRatingEntityType::W2lTemplate);
+3 -1
View File
@@ -25,7 +25,9 @@ serde = "1.0.125"
serde_derive = "1.0.125"
# Random
rand = "0.8.5"
rand = { version = "0.8.5", features = ["std_rng"] }
once_cell = { version = "1.18.0", features = [] }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
sqlx-core = { version = "0.7.2" }
@@ -0,0 +1,43 @@
use std::sync::{Arc, LockResult, RwLock, RwLockWriteGuard};
use once_cell::sync::Lazy;
use rand::rngs::StdRng;
use rand::SeedableRng;
// FIXME(bt,2023-12-14): Is this threadsafe? Looks like it should be fine.
/// Only use this in tests (and dev seed tools), never in production!
#[derive(Clone)]
pub struct DeterministicRng {
rng : Arc<RwLock<StdRng>>
}
impl DeterministicRng {
pub fn get_instance() -> LockResult<RwLockWriteGuard<'static, DeterministicRng>> {
static DETERMINISTIC_RNG : Lazy<Arc<RwLock<DeterministicRng>>> = Lazy::new(|| {
Arc::new(RwLock::new(DeterministicRng::new()))
});
DETERMINISTIC_RNG.write()
}
fn new() -> Self {
Self {
rng: Arc::new(RwLock::new(StdRng::seed_from_u64(0)))
}
}
/// Reset the deterministic RNG's seed.
pub fn reset_rng(&self, state: u64) {
match self.rng.write() {
Err(err) => panic!(format!("test panic due to rng failure {err}")),
Ok(mut lock) => {
*lock = StdRng::seed_from_u64(state);
}
}
}
/// Grab the deterministic RNG
pub fn get_rng(&self) -> LockResult<RwLockWriteGuard<StdRng>>{
self.rng.write()
}
}
+1
View File
@@ -49,6 +49,7 @@ pub(crate) static MINIMUM_CHARACTER_ENTROPY : usize = 8;
#[macro_use]
mod macros;
pub (crate) mod deterministic_rng;
pub (crate) mod prefixes;
pub mod tokens;
+35
View File
@@ -60,6 +60,41 @@ macro_rules! impl_crockford_generator {
$t(token)
}
/// Constructor for a new token to be used **DETERMINISTICALLY** in tests and seeding.
/// This will not use a real RNG, so do not use in production code!
/// Once called, it will output the same random numbers in sequence.
pub fn reset_rng_for_testing_and_dev_seeding_never_use_in_production_seriously(state: u64) {
let deterministic = crate::deterministic_rng::DeterministicRng::get_instance().expect("tests should not fail due to mutex");
deterministic.reset_rng(state);
}
/// Constructor for a new token to be used **DETERMINISTICALLY** in tests and seeding.
/// This will not use a real RNG, so do not use in production code!
/// Once called, it will output the same random numbers in sequence.
pub fn generate_for_testing_and_dev_seeding_never_use_in_production_seriously() -> Self {
use rand::Rng;
// FIXME(bt,2023-12-14): This is bad error handling. (Maybe panic if lock is poisoned?)
let deterministic = crate::deterministic_rng::DeterministicRng::get_instance().expect("tests should not fail due to mutex");
let mut rng = deterministic.get_rng().expect("tests should not fail due to mutex");
let charset = Self::token_character_set();
let entropy_length = Self::entropic_character_len();
let entropy_part: String = (0..entropy_length)
.map(|_| {
let idx = rng.gen_range(0..charset.len());
charset[idx] as char
})
.collect();
let token_prefix = Self::token_prefix();
let token = format!("{}{}", token_prefix, entropy_part);
$t(token)
}
#[inline]
pub fn entropy_suffix(&self) -> &str {
use crate::prefixes::PrefixGenerator;
@@ -21,6 +21,25 @@ mod interface {
assert!(token.to_string().starts_with("m_"));
}
#[test]
fn generate_for_testing_and_dev_seeding_never_use_in_production_seriously_1() {
// NB: Using the same reset seed will produce the same results each time
MediaFileToken::reset_rng_for_testing_and_dev_seeding_never_use_in_production_seriously(0);
assert_eq!(MediaFileToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously().as_str(), "m_q8sz47gmfw2zx02snrbz88ns9m16ab");
assert_eq!(MediaFileToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously().as_str(), "m_ma1xetxrwbh39vg639a9zrq8b9wk6h");
assert_eq!(MediaFileToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously().as_str(), "m_4tswec8z27wnm01njypx4vmfhgj41e");
// NB: Same seed -> same tokens generated
MediaFileToken::reset_rng_for_testing_and_dev_seeding_never_use_in_production_seriously(0);
assert_eq!(MediaFileToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously().as_str(), "m_q8sz47gmfw2zx02snrbz88ns9m16ab");
assert_eq!(MediaFileToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously().as_str(), "m_ma1xetxrwbh39vg639a9zrq8b9wk6h");
assert_eq!(MediaFileToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously().as_str(), "m_4tswec8z27wnm01njypx4vmfhgj41e");
// Once more...
MediaFileToken::reset_rng_for_testing_and_dev_seeding_never_use_in_production_seriously(0);
assert_eq!(MediaFileToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously().as_str(), "m_q8sz47gmfw2zx02snrbz88ns9m16ab");
assert_eq!(MediaFileToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously().as_str(), "m_ma1xetxrwbh39vg639a9zrq8b9wk6h");
assert_eq!(MediaFileToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously().as_str(), "m_4tswec8z27wnm01njypx4vmfhgj41e");
}
#[test]
fn new() {
let token = MediaFileToken::new("m_foo".to_string());
+1 -1
View File
@@ -33,7 +33,7 @@ my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
log = "0.4.14"
clap = "2.34.0" # TODO: This is really out of date, but has conflicts when updated
clap = "4.4.8"
rand = "0.8.5"
# Runtime
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
+7 -18
View File
@@ -1,28 +1,17 @@
use clap::{App, Arg};
use clap::Parser;
use errors::AnyhowResult;
#[derive(Parser, Debug)]
#[command(name="dev-database-seed")]
pub struct CliArgs {
#[arg(name="seed_cloud_bucket", long="bucket", help="Seed the cloud bucket with files", required=false, default_value_t=false)]
pub seed_cloud_bucket: bool,
#[arg(name="seed_elasticsearch", long="elasticsearch", help="Seed the local elasticsearch", required=false, default_value_t=false)]
pub seed_elasticsearch: bool,
}
pub fn parse_cli_args() -> AnyhowResult<CliArgs> {
let matches = App::new("dev-database-seed")
.arg(Arg::with_name("seed_cloud_bucket")
.long("bucket")
.help("Seed the cloud bucket with files")
.takes_value(false)
.required(false))
.arg(Arg::with_name("seed_elasticsearch")
.long("elasticsearch")
.help("Seed the local elasticsearch")
.takes_value(false)
.required(false))
.get_matches();
Ok(CliArgs {
seed_cloud_bucket: matches.is_present("seed_cloud_bucket"),
seed_elasticsearch: matches.is_present("seed_elasticsearch"),
})
Ok(CliArgs::parse())
}
@@ -132,7 +132,7 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
- Gawr Gura MOS 4
"#;
model_weight_token = ModelWeightToken(format!("{}", i));
model_weight_token = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
title = format!("Gawr Gura: {}", i);
description = format!("Description Number {} The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.", i);
description_rendered_html = rvc_markdown_description;
@@ -155,7 +155,7 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
},
21..=40 =>{
// TTS
model_weight_token = ModelWeightToken(format!("{}", i));
model_weight_token = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
title = format!("dragonfruitGTv1: {}", i);
description = format!("Description Number {}:", i);
@@ -187,7 +187,7 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
},
41..=60 => {
// SD 1.5
model_weight_token = ModelWeightToken(format!("{}", i));
model_weight_token = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
title = format!("dragonfruitGTv1: {}", i);
description = format!("Description Number {}:", i);
@@ -232,7 +232,7 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
"#;
let sdxl_image_token = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/be706282-2978-42a0-aaa2-73881aad94e9/width=1024/00049-2287632957-1girl,face,curly%20hair,red%20hair,white%20background,.jpeg";
model_weight_token = ModelWeightToken(format!("{}", i));
model_weight_token = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
title = format!("SDXL_Niji_Special Edition: {}", i);
description = format!("Description Number {}:", i);
@@ -263,7 +263,7 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
"#;
let lora_image_token = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/123645df-dee2-4239-863a-76a150b09c32/width=1024/00000-2171948503.jpeg";
model_weight_token = ModelWeightToken(format!("{}", i));
model_weight_token = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
title = format!("Gawr Gura LoRA: {}", i);
description = format!("Description Number {}:", i);
@@ -295,7 +295,7 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
- Gawr Gura MOS 4
"#;
model_weight_token = ModelWeightToken(format!("{}", i));
model_weight_token = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
title = format!("Gawr Gura: {}", i);
description = format!("Description Number {} The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.", i);
description_rendered_html = rvc_markdown_description;
@@ -356,11 +356,13 @@ pub async fn seed_weights_for_user_token(
) -> AnyhowResult<()> {
info!("Seeding weights...");
let model_weight_token1 = ModelWeightToken("11".to_string());
let model_weight_token2 = ModelWeightToken("22".to_string());
let model_weight_token3 = ModelWeightToken("33".to_string());
let model_weight_token4 = ModelWeightToken("44".to_string());
let model_weight_token5 = ModelWeightToken("55".to_string());
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 model_weight_token4 = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let model_weight_token5 = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let model_weights_args = vec![
CreateModelWeightsArgs {
@@ -503,20 +505,20 @@ pub async fn seed_weights_for_user_token(
pub async fn original_seed_weights(mysql_pool: &Pool<MySql>,user_token: UserToken) -> AnyhowResult<()> {
let model_weight_token1 = ModelWeightToken("1".to_string());
let creator_token1 = UserToken("creatorToken1".to_string());
let model_weight_token1 = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let creator_token1 = UserToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let model_weight_token2 = ModelWeightToken("2".to_string());
let creator_token2 = UserToken("creatorToken2".to_string());
let model_weight_token2 = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let creator_token2 = UserToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let model_weight_token3 = ModelWeightToken("3".to_string());
let creator_token3 = UserToken("creatorToken3".to_string());
let model_weight_token3 = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let creator_token3 = UserToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let model_weight_token4 = ModelWeightToken("4".to_string());
let creator_token4 = UserToken("creatorToken4".to_string());
let model_weight_token4 = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let creator_token4 = UserToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let model_weight_token5 = ModelWeightToken("5".to_string());
let creator_token5 = UserToken("creatorToken5".to_string());
let model_weight_token5 = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let creator_token5 = UserToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let model_weights_args = vec![
CreateModelWeightsArgs {
@@ -5,31 +5,29 @@ use sqlx::{ MySql, Pool };
use buckets::{ public::{ media_files::bucket_file_path::MediaFileBucketPath, self, weight_files::bucket_file_path::WeightFileBucketPath }, private };
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::{
by_table::model_weights::{weights_category::WeightsCategory, weights_types::WeightsType},
common::visibility::Visibility,
};
use errors::AnyhowResult;
use filesys::file_read_bytes::file_read_bytes;
use filesys::file_size::file_size;
use filesys::path_to_string::path_to_string;
use hashing::sha256::sha256_hash_file::sha256_hash_file;
use mimetypes::mimetype_for_bytes::get_mimetype_for_bytes;
use mimetypes::mimetype_for_file::get_mimetype_for_file;
use storyteller_root::get_seed_tool_data_root;
use tokens::tokens::users::UserToken;
use crate::bucket_clients::BucketClients;
use crate::seeding::users::HANASHI_USER_TOKEN;
use enums::{
by_table::model_weights::{ weights_category::WeightsCategory, weights_types::WeightsType },
common::visibility::Visibility,
};
use rand::Rng;
use errors::{ anyhow, AnyhowResult };
use mysql_queries::queries::model_weights::create_weight::{
create_weight,
CreateModelWeightsArgs,
self,
CreateModelWeightsArgs
,
};
use storyteller_root::get_seed_tool_data_root;
use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::users::UserToken;
use crate::bucket_clients::BucketClients;
use crate::seeding::users::HANASHI_USER_TOKEN;
pub async fn seed_weights_files(
mysql_pool: &Pool<MySql>,
@@ -37,6 +35,9 @@ pub async fn seed_weights_files(
) -> AnyhowResult<()> {
info!("Seeding weights files...");
// NB: Stable token generation.
ModelWeightToken::reset_rng_for_testing_and_dev_seeding_never_use_in_production_seriously(123);
let seeded_weights = [
(
HANASHI_USER_TOKEN,
@@ -87,9 +88,8 @@ pub async fn seed_weights_files(
let mut rng = rand::thread_rng();
let n: u32 = rng.gen();
let model_weight_token = ModelWeightToken(format!("{}", n)); // random token
let model_weight_token = model_weight_token;
let model_weight_token = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let title = model_name;
let description = "This is a description";
let description_rendered_html = "This is a description rendered html";
@@ -125,8 +125,8 @@ pub async fn seed_weights_files(
let create_model_weights_args = CreateModelWeightsArgs {
token: &model_weight_token,
weights_type: weights_type, // Assuming weights_type is defined elsewhere
weights_category: weights_category, // Assuming weights_category is defined elsewhere
weights_type, // Assuming weights_type is defined elsewhere
weights_category, // Assuming weights_category is defined elsewhere
title: title.to_string(),
maybe_thumbnail_token: Some(thumbnail_token),
description_markdown: description.to_string(),
@@ -142,11 +142,11 @@ pub async fn seed_weights_files(
private_bucket_hash: public_bucket_hash,
maybe_private_bucket_prefix: Some(public_bucket_prefix.to_string()),
maybe_private_bucket_extension: Some(public_bucket_extension.to_string()),
cached_user_ratings_total_count: cached_user_ratings_total_count,
cached_user_ratings_positive_count: cached_user_ratings_positive_count,
cached_user_ratings_negative_count: cached_user_ratings_negative_count,
cached_user_ratings_total_count,
cached_user_ratings_positive_count,
cached_user_ratings_negative_count,
maybe_cached_user_ratings_ratio: Some(cached_user_ratings_ratio),
version: version,
version,
mysql_pool: &mysql_pool, // Assuming mysql_pool is defined elsewhere
};
+1 -1
View File
@@ -29,7 +29,7 @@ tokens = { path = "../../_types/tokens" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
clap = "2.34.0" # TODO: This is really out of date, but has conflicts when updated
clap = "4.4.8"
log = "0.4.14"
# Runtime
@@ -1,27 +1,24 @@
use std::path::PathBuf;
use clap::{App, Arg};
use errors::{anyhow, AnyhowResult};
use clap::Parser;
use errors::AnyhowResult;
#[derive(Parser, Debug)]
#[command(name="dev-upload-media-file")]
pub struct RawArgs {
#[arg(name="file", short='f', long="file", help="File to upload", required=true)]
pub file_path: String,
}
pub struct CliArgs {
pub file_path: PathBuf,
}
pub fn parse_cli_args() -> AnyhowResult<CliArgs> {
let matches = App::new("dev_upload_media")
.arg(Arg::with_name("file")
.short("f")
.long("file")
.value_name("FILE")
.help("File to upload")
.takes_value(true)
.required(true))
.get_matches();
let file_path = matches.value_of("file")
.map(|val| val.trim().to_string())
.ok_or(anyhow!("no file path supplied"))?;
let args = RawArgs::parse();
Ok(CliArgs {
file_path: PathBuf::from(file_path),
file_path: PathBuf::from(args.file_path),
})
}
+1 -1
View File
@@ -29,7 +29,7 @@ my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
log = "0.4.14"
clap = "2.34.0" # TODO: This is really out of date, but has conflicts when updated
clap = "4.4.8"
strum = { version = "0.25", features = ["derive"] }
# Runtime
+24 -39
View File
@@ -1,12 +1,12 @@
use std::str::FromStr;
use clap::{App, Arg, ArgMatches};
use clap::Parser;
use strum::{EnumCount, EnumString, IntoEnumIterator};
use strum::EnumIter;
use errors::{anyhow, AnyhowResult, bail};
pub struct CliArgs {
pub struct ParsedArgs {
pub mysql_environment: Environment,
pub elasticsearch_environment: Environment,
pub action: Action,
@@ -25,53 +25,38 @@ pub enum Action {
SearchTts,
}
pub fn parse_cli_args() -> AnyhowResult<CliArgs> {
let matches = App::new("dev-database-seed")
.arg(Arg::with_name("action")
.long("action")
.help("action to take")
.takes_value(true)
.required(true))
.arg(Arg::with_name("mysql")
.long("mysql")
.help("Production or development")
.takes_value(true)
.required(false))
.arg(Arg::with_name("elasticsearch")
.long("elasticsearch")
.help("Production or development")
.takes_value(true)
.required(false))
.get_matches();
#[derive(Parser, Debug)]
#[command(name="elasticsearch-cli")]
pub struct Args {
#[arg(name="action", long="action", help="action to take", required=true)]
action: String,
Ok(CliArgs {
mysql_environment: to_environment(&matches, "mysql")?,
elasticsearch_environment: to_environment(&matches, "elasticsearch")?,
action: get_action(&matches)?,
#[arg(name="mysql", long="mysql", help="production or development")]
mysql: Option<String>,
#[arg(name="elasticsearch", long="elasticsearch", help="production or development")]
elasticsearch: Option<String>,
}
pub fn parse_cli_args() -> AnyhowResult<ParsedArgs> {
let args = Args::parse();
Ok(ParsedArgs {
mysql_environment: to_environment(args.mysql.as_deref())?,
elasticsearch_environment: to_environment(args.elasticsearch.as_deref())?,
action: action_from_str(&args.action)?,
})
}
fn to_environment(matches: &ArgMatches, key: &str) -> AnyhowResult<Environment> {
let value = matches.value_of(key)
.map(|s| s.to_lowercase());
Ok(match value.as_deref() {
fn to_environment(environment: Option<&str>) -> AnyhowResult<Environment> {
Ok(match environment {
None => Environment::Development,
Some("dev") | Some("development") => Environment::Development,
Some("prod") | Some("production") => Environment::Production,
_ => bail!("invalid environment: {:?}", value),
_ => bail!("invalid environment: {:?}", environment),
})
}
fn get_action(matches: &ArgMatches) -> AnyhowResult<Action> {
let action = matches.value_of("action")
.map(|s| action_from_str(s))
.transpose()?
.ok_or(anyhow!("no action provided"))?;
Ok(action)
}
fn action_from_str(value: &str) -> AnyhowResult<Action> {
let action = Action::from_str(value)
.map_err(|err| {
+1 -1
View File
@@ -21,7 +21,7 @@ logging = { path = "../../lib/logging" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
clap = { version = "4.4.7", features = ["derive"] }
clap = { version = "4.4.8", features = ["derive"] }
walkdir = "2.4.0"
[dev-dependencies]
+1 -1
View File
@@ -22,5 +22,5 @@ web_scrapers = { path = "../../lib/web_scrapers" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
clap = "2.34.0" # TODO: This is really out of date, but has conflicts when updated
clap = "4.4.8"
tokio = { version = "1.34.0", features = ["macros"] }
+15 -21
View File
@@ -1,30 +1,24 @@
use clap::{App, Arg};
use clap::Parser;
use errors::{anyhow, AnyhowResult};
use errors::AnyhowResult;
use web_scrapers::scrape_supported_webpage::scrape_supported_webpage;
#[derive(Parser, Debug)]
#[command(name = "scrape_test")]
pub struct Args {
#[arg(name="url", short='u', long = "url", help = "URL of the page to scrape", required = true)]
url: String,
#[arg(name="print_html", long = "html", help = "Print the HTML scraped", required = true)]
print_html: bool,
}
#[tokio::main]
pub async fn main() -> AnyhowResult<()> {
let matches = App::new("scrape_test")
.arg(Arg::with_name("url")
.short("u")
.long("url")
.value_name("URL")
.help("URL of the page to scrape")
.takes_value(true)
.required(true))
.arg(Arg::with_name("print_html")
.long("html")
.help("Print the HTML scraped")
.takes_value(false)
.required(false))
.get_matches();
let args = Args::parse();
let url = matches.value_of("url")
.map(|val| val.trim().to_string())
.ok_or(anyhow!("no url supplied"))?;
let print_html = matches.is_present("print_html");
let url = args.url.trim().to_string();
let print_html = args.print_html;
let scrape_result = scrape_supported_webpage(&url).await?;
@@ -1,4 +1,6 @@
use enums::by_table::user_ratings::entity_type::UserRatingEntityType;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::tts_models::TtsModelToken;
use tokens::tokens::w2l_templates::W2lTemplateToken;
@@ -12,6 +14,8 @@ use tokens::tokens::w2l_templates::W2lTemplateToken;
/// NB: We probably don't want to expose this type of interface to web clients because it's
/// monotonous as a JSON interface.
pub enum UserRatingEntity {
MediaFile(MediaFileToken),
ModelWeight(ModelWeightToken),
TtsModel(TtsModelToken),
W2lTemplate(W2lTemplateToken),
}
@@ -20,6 +24,8 @@ pub enum UserRatingEntity {
impl UserRatingEntity {
pub fn get_entity_type(&self) -> UserRatingEntityType {
match self {
UserRatingEntity::MediaFile(_) => UserRatingEntityType::MediaFile,
UserRatingEntity::ModelWeight(_) => UserRatingEntityType::ModelWeight,
UserRatingEntity::TtsModel(_) => UserRatingEntityType::TtsModel,
UserRatingEntity::W2lTemplate(_) => UserRatingEntityType::W2lTemplate,
}
@@ -27,6 +33,8 @@ impl UserRatingEntity {
pub fn get_entity_token_str(&self) -> &str {
match self {
UserRatingEntity::MediaFile(token) => token.as_str(),
UserRatingEntity::ModelWeight(token) => token.as_str(),
UserRatingEntity::TtsModel(token) => token.as_str(),
UserRatingEntity::W2lTemplate(token) => token.as_str(),
}
@@ -3,6 +3,7 @@ use errors::AnyhowResult;
use crate::payloads::generic_inference_args::lipsync_payload::LipsyncArgs;
use crate::payloads::generic_inference_args::tts_payload::TTSArgs;
use crate::payloads::generic_inference_args::videofilter_payload::{RerenderArgs};
/// Used to encode extra state for the `generic_inference_jobs` table.
@@ -35,6 +36,10 @@ pub enum InferenceCategoryAbbreviated {
#[serde(rename = "vc")] // NB: DO NOT CHANGE. It could break live jobs. Renamed to be fewer bytes.
#[serde(alias = "voice_conversion")]
VoiceConversion,
#[serde(rename = "rr")] // NB: DO NOT CHANGE. It could break live jobs. Renamed to be fewer bytes.
#[serde(alias = "rerender_a_video")]
RerenderAVideo,
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
@@ -84,6 +89,9 @@ pub enum PolymorphicInferenceArgs {
#[serde(skip_serializing_if = "Option::is_none")]
transpose: Option<i32>,
},
/// Rerender a video. (Short name to save space when serializing.)
Rr(RerenderArgs)
}
@@ -104,6 +112,7 @@ impl InferenceCategoryAbbreviated {
InferenceCategory::LipsyncAnimation => Self::LipsyncAnimation,
InferenceCategory::TextToSpeech => Self::TextToSpeech,
InferenceCategory::VoiceConversion => Self::VoiceConversion,
InferenceCategory::RerenderAVideo => Self::RerenderAVideo,
}
}
@@ -112,6 +121,7 @@ impl InferenceCategoryAbbreviated {
Self::LipsyncAnimation => InferenceCategory::LipsyncAnimation,
Self::TextToSpeech => InferenceCategory::TextToSpeech,
Self::VoiceConversion => InferenceCategory::VoiceConversion,
Self::RerenderAVideo => InferenceCategory::RerenderAVideo,
}
}
}
@@ -1,3 +1,4 @@
pub mod generic_inference_args;
pub mod lipsync_payload;
pub mod tts_payload;
pub mod tts_payload;
pub mod videofilter_payload;
@@ -0,0 +1,36 @@
/// Video sources can be one of several:
/// - F: media_files (todo)
/// - U: media_uploads (legacy)
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum VideofilterVideoSource {
// 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),
}
impl VideofilterVideoSource {
pub fn media_file_token(token: &str) -> Self {
VideofilterVideoSource::F(token.to_string())
}
pub fn media_upload_token(token: &str) -> Self {
VideofilterVideoSource::U(token.to_string())
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct RerenderArgs {
#[serde(rename = "vs")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_video_source: Option<VideofilterVideoSource>,
#[serde(rename = "sd")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_sd_model: Option<String>,
}
@@ -1,10 +1,14 @@
use enums::by_table::comments::comment_entity_type::CommentEntityType;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::tts_models::TtsModelToken;
use tokens::tokens::w2l_templates::W2lTemplateToken;
use tokens::tokens::users::UserToken;
pub enum CommentEntityToken {
User(UserToken),
MediaFile(MediaFileToken),
ModelWeight(ModelWeightToken),
TtsModel(TtsModelToken),
TtsResult(String), // TODO: Strong type
W2lTemplate(W2lTemplateToken),
@@ -20,6 +24,8 @@ impl CommentEntityToken {
CommentEntityType::TtsResult => Self::TtsResult(token.to_string()),
CommentEntityType::W2lTemplate => Self::W2lTemplate(W2lTemplateToken::new_from_str(token)),
CommentEntityType::W2lResult => Self::W2lResult(token.to_string()),
CommentEntityType::MediaFile => Self::MediaFile(MediaFileToken::new_from_str(token)),
CommentEntityType::ModelWeight => Self::ModelWeight(ModelWeightToken::new_from_str(token)),
}
}
@@ -30,6 +36,8 @@ impl CommentEntityToken {
CommentEntityToken::TtsResult(tts_result_token) => (CommentEntityType::TtsResult, tts_result_token.as_str()),
CommentEntityToken::W2lTemplate(w2l_template_token) => (CommentEntityType::W2lTemplate, w2l_template_token.as_str()),
CommentEntityToken::W2lResult(w2l_result_token) => (CommentEntityType::W2lResult, w2l_result_token.as_str()),
CommentEntityToken::MediaFile(media_file_token) => (CommentEntityType::MediaFile, media_file_token.as_str()),
CommentEntityToken::ModelWeight(model_weight_token) => (CommentEntityType::ModelWeight, model_weight_token.as_str()),
};
(entity_type, entity_token)
}
@@ -178,6 +178,7 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
InferenceCategory::LipsyncAnimation => Some("lipsync animation"),
InferenceCategory::TextToSpeech => record.maybe_tts_model_title.as_deref(),
InferenceCategory::VoiceConversion => record.maybe_voice_conversion_model_title.as_deref(),
InferenceCategory::RerenderAVideo => Some("rerender animation"),
};
// NB: A bit of a hack. We store TTS results with a full path.
@@ -186,6 +187,7 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
InferenceCategory::LipsyncAnimation => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
InferenceCategory::TextToSpeech => (false, record.maybe_tts_public_bucket_path.as_deref()),
InferenceCategory::VoiceConversion => (true, record.maybe_voice_conversion_public_bucket_hash.as_deref()),
InferenceCategory::RerenderAVideo => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
};
// NB: We've moved voice conversion out of their own table and into media_files
@@ -0,0 +1,158 @@
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::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;
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,
// TODO: Media duration.
//pub duration_millis: u64,
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 async fn insert_media_file_from_rerender(
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?;
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_rerender_id = transactional_increment_generic_synthetic_id(
&user_token,
IdCategory::RerenderResult,
&mut transaction
).await?;
maybe_creator_file_synthetic_id = Some(next_media_file_id);
maybe_creator_category_synthetic_id = Some(next_rerender_id);
}
let creator_ip_address = args.job.creator_ip_address.as_str();
let creator_set_visibility = args.job.creator_set_visibility.clone();
const ORIGIN_CATEGORY : MediaFileOriginCategory = MediaFileOriginCategory::Inference;
const ORIGIN_PRODUCT_CATEGORY : MediaFileOriginProductCategory = MediaFileOriginProductCategory::Rerender;
const ORIGIN_MODEL_TYPE : MediaFileOriginModelType = MediaFileOriginModelType::Rerender;
const MEDIA_TYPE : MediaFileType = MediaFileType::Video;
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))
}
@@ -0,0 +1,234 @@
use chrono::{DateTime, Utc};
use sqlx::{Acquire, FromRow, MySql, MySqlConnection, MySqlPool, QueryBuilder, Row};
use sqlx::mysql::MySqlRow;
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 enums::traits::mysql_from_row::MySqlFromRow;
use errors::AnyhowResult;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::users::UserToken;
#[derive(Serialize)]
pub struct MediaFilesByTokensRecord {
pub token: MediaFileToken,
pub media_type: MediaFileType,
pub origin_category: MediaFileOriginCategory,
pub origin_product_category: MediaFileOriginProductCategory,
pub maybe_origin_model_type: Option<MediaFileOriginModelType>,
pub maybe_origin_model_token: Option<String>,
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_creator_user_token: Option<UserToken>,
pub maybe_creator_username: Option<String>,
pub maybe_creator_display_name: Option<String>,
pub maybe_creator_email_gravatar_hash: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
pub async fn list_media_files_by_tokens(
mysql_pool: &MySqlPool,
media_file_tokens: &[MediaFileToken],
can_see_deleted: bool
) -> AnyhowResult<Vec<MediaFilesByTokensRecord>> {
let mut connection = mysql_pool.acquire().await?;
let raw_media_files: Vec<RawMediaFileJoinUser> = get_raw_media_files_by_tokens(&mut connection, media_file_tokens, can_see_deleted).await?;
Ok(map_to_media_files(raw_media_files))
}
async fn get_raw_media_files_by_tokens(
connection: &mut MySqlConnection,
media_file_tokens: &[MediaFileToken],
can_see_deleted: bool
) -> AnyhowResult<Vec<RawMediaFileJoinUser>> {
let connection = connection.acquire().await?;
let mut query_builder : QueryBuilder<MySql> = if can_see_deleted {
QueryBuilder::new(r#"
SELECT
m.token,
m.media_type,
m.origin_category,
m.origin_product_category,
m.maybe_origin_model_type,
m.maybe_origin_model_token,
users.token as maybe_creator_user_token,
users.username as maybe_creator_username,
users.display_name as maybe_creator_display_name,
users.email_gravatar_hash as maybe_creator_email_gravatar_hash,
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.created_at,
m.updated_at
FROM media_files as m
LEFT OUTER JOIN users
ON users.token = m.maybe_creator_user_token
WHERE
m.creator_set_visibility = "public"
AND m.token IN (
"#,
)
} else {
QueryBuilder::new(r#"
SELECT
m.token,
m.media_type,
m.origin_category,
m.origin_product_category,
m.maybe_origin_model_type,
m.maybe_origin_model_token,
users.token as maybe_creator_user_token,
users.username as maybe_creator_username,
users.display_name as maybe_creator_display_name,
users.email_gravatar_hash as maybe_creator_email_gravatar_hash,
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.created_at,
m.updated_at
FROM media_files as m
LEFT OUTER JOIN users
ON users.token = m.maybe_creator_user_token
WHERE
m.creator_set_visibility = "public"
AND m.user_deleted_at IS NULL
AND m.mod_deleted_at IS NULL
AND m.token IN (
"#
)
};
query_builder.push(token_predicate(media_file_tokens));
query_builder.push(")");
let query = query_builder.build_query_as::<RawMediaFileJoinUser>();
let results = query.fetch_all(connection).await?;
Ok(results)
}
/// Return a comma-separated predicate, since SQLx does not yet support WHERE IN(?) for Vec<T>, etc.
/// Issue: https://github.com/launchbadge/sqlx/issues/875
fn token_predicate(tokens: &[MediaFileToken]) -> String {
tokens.iter()
.map(|ty| ty.as_str())
.map(|ty| format!("\"{}\"", ty))
.collect::<Vec<String>>()
.join(", ")
}
fn map_to_media_files(dataset:Vec<RawMediaFileJoinUser>) -> Vec<MediaFilesByTokensRecord> {
let media_files: Vec<MediaFilesByTokensRecord> = dataset
.into_iter()
.map(|media_file: RawMediaFileJoinUser| {
MediaFilesByTokensRecord {
token: media_file.token,
origin_category: media_file.origin_category,
origin_product_category: media_file.origin_product_category,
maybe_origin_model_type: media_file.maybe_origin_model_type,
maybe_origin_model_token: media_file.maybe_origin_model_token,
maybe_creator_user_token: media_file.maybe_creator_user_token,
maybe_creator_username: media_file.maybe_creator_username,
maybe_creator_display_name: media_file.maybe_creator_display_name,
maybe_creator_email_gravatar_hash: media_file.maybe_creator_email_gravatar_hash,
media_type: media_file.media_type,
public_bucket_directory_hash: media_file.public_bucket_directory_hash,
maybe_public_bucket_prefix: media_file.maybe_public_bucket_prefix,
maybe_public_bucket_extension: media_file.maybe_public_bucket_extension,
created_at: media_file.created_at,
updated_at: media_file.updated_at,
}
}).collect();
media_files
}
#[derive(Serialize)]
pub struct RawMediaFileJoinUser {
pub token: MediaFileToken,
pub media_type: MediaFileType,
pub origin_category: MediaFileOriginCategory,
pub origin_product_category: MediaFileOriginProductCategory,
pub maybe_origin_model_type: Option<MediaFileOriginModelType>,
pub maybe_origin_model_token: Option<String>,
pub maybe_creator_user_token: Option<UserToken>,
pub maybe_creator_username: Option<String>,
pub maybe_creator_display_name: Option<String>,
pub maybe_creator_email_gravatar_hash: Option<String>,
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
// NB(bt,2023-12-05): There's an issue with type hinting in the `as` clauses with QueryBuilder (or
// raw query strings) and sqlx::FromRow, regardless of whether it is derived of manually
// implemented. Perhaps this will improve in the future, but for now manually constructed queries
// cannot have type hints, eg. the following:
//
// m.token as `token: tokens::tokens::media_files::MediaFileToken`,
// m.origin_category as `origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,
// m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,
//
// This results in the automatic mapping not being able to be found by name (for macro derive), and
// in the manual case `row.try_get()` etc. won't have the correct column name (since the name is the
// full "as" clause).
impl FromRow<'_, MySqlRow> for RawMediaFileJoinUser {
fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
Ok(Self {
token: MediaFileToken::new(row.try_get("token")?),
media_type: MediaFileType::try_from_mysql_row(row, "media_type")?,
origin_category: MediaFileOriginCategory::try_from_mysql_row(row, "origin_category")?,
origin_product_category: MediaFileOriginProductCategory::try_from_mysql_row(row, "origin_product_category")?,
maybe_origin_model_type: MediaFileOriginModelType::try_from_mysql_row_nullable(row, "maybe_origin_model_type")?,
maybe_origin_model_token: row.try_get("maybe_origin_model_token")?,
maybe_creator_user_token: row.try_get::<Option<String>, _>("maybe_creator_user_token")?
.and_then(|token| Some(UserToken::new_from_str(&token))),
maybe_creator_username: row.try_get("maybe_creator_username")?,
maybe_creator_display_name: row.try_get("maybe_creator_display_name")?,
maybe_creator_email_gravatar_hash: row.try_get("maybe_creator_email_gravatar_hash")?,
public_bucket_directory_hash: row.try_get("public_bucket_directory_hash")?,
maybe_public_bucket_prefix: row.try_get("maybe_public_bucket_prefix")?,
maybe_public_bucket_extension: row.try_get("maybe_public_bucket_extension")?,
created_at: row.try_get("created_at")?,
updated_at: row.try_get("updated_at")?,
})
}
}
@@ -16,11 +16,8 @@ pub struct MediaFileListPage {
pub sort_ascending: bool,
/// ID of the first record.
pub first_id: Option<i64>,
/// ID of the last record.
pub last_id: Option<i64>,
pub current_page: usize,
pub total_page_count: usize,
}
pub struct MediaFileListItem {
@@ -51,9 +48,9 @@ pub enum ViewAs {
pub struct ListMediaFileForUserArgs<'a> {
pub username: &'a str,
pub limit: usize,
pub maybe_filter_media_type: Option<MediaFileType>,
pub maybe_offset: Option<usize>,
pub page_size: usize,
pub page_index: usize,
pub cursor_is_reversed: bool,
pub view_as: ViewAs,
pub mysql_pool: &'a MySqlPool,
@@ -61,25 +58,42 @@ pub struct ListMediaFileForUserArgs<'a> {
pub async fn list_media_files_for_user(args: ListMediaFileForUserArgs<'_>) -> AnyhowResult<MediaFileListPage> {
/// Let's figure out how many results we could have returned total
let count_fields = select_total_count_field();
let mut count_query_builder = query_builder(
args.maybe_filter_media_type,
args.username,
false,
0,
0,
args.cursor_is_reversed,
args.view_as,
count_fields.as_str(),
);
let row_count_query = count_query_builder.build_query_scalar::<i64>();
let row_count_result = row_count_query.fetch_one(args.mysql_pool).await?;
/// Figure out limit start and end based on page size and indexes as requested
let limit_start = args.page_size * args.page_index;
let limit_end = limit_start + args.page_size;
/// Now fetch the actual results with all the fields
let result_fields = select_result_fields();
let mut query = query_builder(
args.maybe_filter_media_type,
args.username,
args.limit,
args.maybe_offset,
true,
limit_start,
limit_end,
args.cursor_is_reversed,
args.view_as,
result_fields.as_str(),
);
let query = query.build_query_as::<MediaFileListItemInternal>();
let results = query.fetch_all(args.mysql_pool).await?;
let first_id = results.first()
.map(|raw_result| raw_result.id);
let last_id = results.last()
.map(|raw_result| raw_result.id);
let number_of_pages = (row_count_result / args.page_size as i64) as usize;
let results = results.into_iter()
.map(|record| {
MediaFileListItem {
@@ -102,43 +116,59 @@ pub async fn list_media_files_for_user(args: ListMediaFileForUserArgs<'_>) -> An
Ok(MediaFileListPage {
records: results,
sort_ascending: !args.cursor_is_reversed,
first_id,
last_id,
current_page: args.page_index,
total_page_count: number_of_pages,
})
}
fn select_result_fields() -> String {
r#"
m.id,
m.token,
m.origin_category,
m.origin_product_category,
m.maybe_origin_model_type,
m.maybe_origin_model_token,
m.media_type,
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.creator_set_visibility,
m.created_at,
m.updated_at
"#
.to_string()
}
fn select_total_count_field() -> String {
r#"
COUNT(m.id) AS total_count
"#
.to_string()
}
fn query_builder<'a>(
maybe_filter_media_type: Option<MediaFileType>,
username: &'a str,
limit: usize,
maybe_offset: Option<usize>,
enforce_limits: bool,
limit_start: usize,
limit_end: usize,
cursor_is_reversed: bool,
view_as: ViewAs,
select_fields: &'a str,
) -> QueryBuilder<'a, MySql> {
// NB: Query cannot be statically checked by sqlx
let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new(
r#"
format!(r#"
SELECT
m.id,
m.token,
m.origin_category,
m.origin_product_category,
m.maybe_origin_model_type,
m.maybe_origin_model_token,
m.media_type,
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.creator_set_visibility,
m.created_at,
m.updated_at
{select_fields}
FROM media_files AS m
LEFT OUTER JOIN users AS u
ON m.maybe_creator_user_token = u.token
@@ -146,7 +176,7 @@ LEFT OUTER JOIN users AS u
WHERE m.user_deleted_at IS NULL
AND m.mod_deleted_at IS NULL
"#
);
));
query_builder.push(" AND u.username = ");
query_builder.push_bind(username);
@@ -171,21 +201,19 @@ WHERE m.user_deleted_at IS NULL
}
}
if let Some(offset) = maybe_offset {
if cursor_is_reversed {
query_builder.push(format!(" AND m.id < {offset} "));
} else {
query_builder.push(format!(" AND m.id > {offset} "));
}
}
if cursor_is_reversed {
query_builder.push(" ORDER BY m.id ASC ");
} else {
query_builder.push(" ORDER BY m.id DESC ");
}
query_builder.push(format!(" LIMIT {limit} "));
if enforce_limits {
if cursor_is_reversed {
query_builder.push(format!(" LIMIT {limit_start}, {limit_end} "));
} else {
query_builder.push(format!(" LIMIT {limit_end}, {limit_start} "));
}
}
query_builder
}
@@ -2,9 +2,11 @@ pub mod delete_media_file;
pub mod get_media_file;
pub mod insert_media_file_from_cli_tool;
pub mod insert_media_file_from_face_animation;
pub mod insert_media_file_from_rerender;
pub mod insert_media_file_from_tacotron2;
pub mod insert_media_file_from_voice_conversion;
pub mod insert_media_file_from_zero_shot_tts;
pub mod list_media_files;
pub mod list_media_files_by_tokens;
pub mod list_media_files_for_user;
pub mod update_media_file;
@@ -0,0 +1,264 @@
use chrono::{DateTime, Utc};
use sqlx::{Acquire, FromRow, MySql, MySqlConnection, MySqlPool, QueryBuilder, Row};
use sqlx::mysql::MySqlRow;
use enums::by_table::model_weights::{
weights_category::WeightsCategory,
weights_types::WeightsType,
};
use enums::traits::mysql_from_row::MySqlFromRow;
use errors::AnyhowResult;
use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::users::UserToken;
#[derive(Serialize)]
pub struct WeightsByTokensRecord {
pub token: ModelWeightToken,
pub weights_type: WeightsType,
pub weights_category: WeightsCategory,
pub title: String,
pub maybe_thumbnail_token: Option<String>,
pub creator_user_token: UserToken,
pub creator_username: String,
pub creator_display_name: String,
pub creator_email_gravatar_hash: String,
pub private_bucket_hash: String,
pub maybe_private_bucket_prefix: Option<String>,
pub maybe_private_bucket_extension: Option<String>,
pub cached_user_ratings_total_count: u32,
pub cached_user_ratings_positive_count: u32,
pub cached_user_ratings_negative_count: u32,
pub maybe_cached_user_ratings_ratio: Option<f32>,
pub cached_user_ratings_last_updated_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub user_deleted_at: Option<DateTime<Utc>>,
pub mod_deleted_at: Option<DateTime<Utc>>,
}
pub async fn list_weights_by_tokens(
mysql_pool: &MySqlPool,
weight_tokens: &[ModelWeightToken],
can_see_deleted: bool
) -> AnyhowResult<Vec<WeightsByTokensRecord>> {
let mut connection = mysql_pool.acquire().await?;
let raw_weights: Vec<RawWeightJoinUser> = get_raw_weights_by_tokens(&mut connection, weight_tokens, can_see_deleted).await?;
Ok(map_to_weights(raw_weights))
}
async fn get_raw_weights_by_tokens(
connection: &mut MySqlConnection,
weight_tokens: &[ModelWeightToken],
can_see_deleted: bool
) -> AnyhowResult<Vec<RawWeightJoinUser>> {
let connection = connection.acquire().await?;
let mut query_builder : QueryBuilder<MySql> = if can_see_deleted {
QueryBuilder::new(r#"
SELECT
mw.token,
mw.title,
mw.weights_type,
mw.weights_category,
mw.maybe_thumbnail_token,
users.token as creator_user_token,
users.username as creator_username,
users.display_name as creator_display_name,
users.email_gravatar_hash as creator_email_gravatar_hash,
mw.private_bucket_hash,
mw.maybe_private_bucket_prefix,
mw.maybe_private_bucket_extension,
mw.cached_user_ratings_negative_count,
mw.cached_user_ratings_positive_count,
mw.cached_user_ratings_total_count,
mw.maybe_cached_user_ratings_ratio,
mw.cached_user_ratings_last_updated_at,
mw.created_at,
mw.updated_at,
mw.user_deleted_at,
mw.mod_deleted_at
FROM model_weights as mw
JOIN users
ON users.token = mw.creator_user_token
WHERE
mw.creator_set_visibility = "public"
AND mw.token IN (
"#,
)
} else {
QueryBuilder::new(r#"
SELECT
mw.token,
mw.title,
mw.weights_type,
mw.weights_category,
mw.maybe_thumbnail_token,
users.token as creator_user_token,
users.username as creator_username,
users.display_name as creator_display_name,
users.email_gravatar_hash as creator_email_gravatar_hash,
mw.private_bucket_hash,
mw.maybe_private_bucket_prefix,
mw.maybe_private_bucket_extension,
mw.cached_user_ratings_negative_count,
mw.cached_user_ratings_positive_count,
mw.cached_user_ratings_total_count,
mw.maybe_cached_user_ratings_ratio,
mw.cached_user_ratings_last_updated_at,
mw.created_at,
mw.updated_at,
mw.user_deleted_at,
mw.mod_deleted_at
FROM model_weights as mw
JOIN users
ON users.token = mw.creator_user_token
WHERE
mw.creator_set_visibility = "public"
AND mw.user_deleted_at IS NULL
AND mw.mod_deleted_at IS NULL
AND mw.token IN (
"#
)
};
query_builder.push(token_predicate(weight_tokens));
query_builder.push(")");
let query = query_builder.build_query_as::<RawWeightJoinUser>();
let results = query.fetch_all(connection).await?;
Ok(results)
}
/// Return a comma-separated predicate, since SQLx does not yet support WHERE IN(?) for Vec<T>, etc.
/// Issue: https://github.com/launchbadge/sqlx/issues/875
fn token_predicate(tokens: &[ModelWeightToken]) -> String {
tokens.iter()
.map(|ty| ty.as_str())
.map(|ty| format!("\"{}\"", ty))
.collect::<Vec<String>>()
.join(", ")
}
fn map_to_weights(dataset:Vec<RawWeightJoinUser>) -> Vec<WeightsByTokensRecord> {
let weights: Vec<WeightsByTokensRecord> = dataset
.into_iter()
.map(|dataset: RawWeightJoinUser| {
WeightsByTokensRecord {
token: dataset.token,
title: dataset.title,
weights_type: dataset.weights_type,
weights_category: dataset.weights_category,
maybe_thumbnail_token: dataset.maybe_thumbnail_token,
creator_user_token: dataset.creator_user_token,
creator_username:dataset.creator_username,
creator_display_name:dataset.creator_display_name,
creator_email_gravatar_hash:dataset.creator_email_gravatar_hash,
private_bucket_hash: dataset.private_bucket_hash,
maybe_private_bucket_prefix: dataset.maybe_private_bucket_prefix,
maybe_private_bucket_extension: dataset.maybe_private_bucket_extension,
cached_user_ratings_negative_count: dataset.cached_user_ratings_negative_count,
cached_user_ratings_positive_count: dataset.cached_user_ratings_positive_count,
cached_user_ratings_total_count: dataset.cached_user_ratings_total_count,
maybe_cached_user_ratings_ratio: dataset.maybe_cached_user_ratings_ratio,
cached_user_ratings_last_updated_at: dataset.cached_user_ratings_last_updated_at,
created_at: dataset.created_at,
updated_at: dataset.updated_at,
user_deleted_at: dataset.user_deleted_at,
mod_deleted_at: dataset.mod_deleted_at,
}
}).collect();
weights
}
#[derive(Serialize)]
pub struct RawWeightJoinUser {
pub token: ModelWeightToken,
pub weights_type: WeightsType,
pub weights_category: WeightsCategory,
pub title: String,
pub maybe_thumbnail_token: Option<String>,
pub creator_user_token: UserToken,
pub creator_username: String,
pub creator_display_name: String,
pub creator_email_gravatar_hash: String,
pub private_bucket_hash: String,
pub maybe_private_bucket_prefix: Option<String>,
pub maybe_private_bucket_extension: Option<String>,
pub cached_user_ratings_total_count: u32,
pub cached_user_ratings_positive_count: u32,
pub cached_user_ratings_negative_count: u32,
pub maybe_cached_user_ratings_ratio: Option<f32>,
pub cached_user_ratings_last_updated_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub user_deleted_at: Option<DateTime<Utc>>,
pub mod_deleted_at: Option<DateTime<Utc>>,
}
// NB(bt,2023-12-05): There's an issue with type hinting in the `as` clauses with QueryBuilder (or
// raw query strings) and sqlx::FromRow, regardless of whether it is derived of manually
// implemented. Perhaps this will improve in the future, but for now manually constructed queries
// cannot have type hints, eg. the following:
//
// m.token as `token: tokens::tokens::media_files::MediaFileToken`,
// m.origin_category as `origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,
// m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,
//
// This results in the automatic mapping not being able to be found by name (for macro derive), and
// in the manual case `row.try_get()` etc. won't have the correct column name (since the name is the
// full "as" clause).
impl FromRow<'_, MySqlRow> for RawWeightJoinUser {
fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
Ok(Self {
token: ModelWeightToken::new(row.try_get("token")?),
weights_type: WeightsType::try_from_mysql_row(row, "weights_type")?,
weights_category: WeightsCategory::try_from_mysql_row(row, "weights_category")?,
title: row.try_get("title")?,
maybe_thumbnail_token: row.try_get("maybe_thumbnail_token")?,
creator_user_token: UserToken::new_from_str(row.try_get("creator_user_token")?),
creator_username: row.try_get("creator_username")?,
creator_display_name: row.try_get("creator_display_name")?,
creator_email_gravatar_hash: row.try_get("creator_email_gravatar_hash")?,
private_bucket_hash: row.try_get("private_bucket_hash")?,
maybe_private_bucket_prefix: row.try_get("maybe_private_bucket_prefix")?,
maybe_private_bucket_extension: row.try_get("maybe_private_bucket_extension")?,
cached_user_ratings_total_count: row.try_get("cached_user_ratings_total_count")?,
cached_user_ratings_positive_count: row.try_get("cached_user_ratings_positive_count")?,
cached_user_ratings_negative_count: row.try_get("cached_user_ratings_negative_count")?,
maybe_cached_user_ratings_ratio: row.try_get("maybe_cached_user_ratings_ratio")?,
cached_user_ratings_last_updated_at: row.try_get("cached_user_ratings_last_updated_at")?,
created_at: row.try_get("created_at")?,
updated_at: row.try_get("updated_at")?,
user_deleted_at: row.try_get("user_deleted_at")?,
mod_deleted_at: row.try_get("mod_deleted_at")?,
})
}
}
@@ -1,7 +1,8 @@
pub mod create_weight;
pub mod delete_weights;
pub mod get_weight;
pub mod update_weight;
pub mod list_weights_by_tokens;
pub mod list_weights_by_user;
pub mod list_weights_query_builder;
pub mod tests;
pub mod update_weight;
@@ -33,6 +33,7 @@ SELECT
media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,
media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,
model_weights.title as maybe_descriptive_text_model_weight_title,
tts_models.title as maybe_descriptive_text_tts_model_title,
tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,
users.display_name as maybe_descriptive_text_user_display_name,
@@ -44,6 +45,7 @@ FROM
JOIN users AS u
ON f.user_token = u.token
LEFT OUTER JOIN model_weights ON model_weights.token = f.entity_token
LEFT OUTER JOIN media_files ON media_files.token = f.entity_token
LEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token
LEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token
@@ -100,6 +102,7 @@ SELECT
media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`,
media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,
model_weights.title as maybe_descriptive_text_model_weight_title,
tts_models.title as maybe_descriptive_text_tts_model_title,
tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text,
users.display_name as maybe_descriptive_text_user_display_name,
@@ -111,6 +114,7 @@ FROM
JOIN users AS u
ON f.user_token = u.token
LEFT OUTER JOIN model_weights ON model_weights.token = f.entity_token
LEFT OUTER JOIN media_files ON media_files.token = f.entity_token
LEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token
LEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token
@@ -44,6 +44,7 @@ pub struct RawUserBookmarkRecord {
pub (crate) maybe_media_file_type: Option<MediaFileType>,
pub (crate) maybe_media_file_origin_category: Option<MediaFileOriginCategory>,
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>,
@@ -59,6 +60,7 @@ impl RawUserBookmarkRecord {
entity_token: self.entity_token,
maybe_entity_descriptive_text: match self.entity_type {
UserBookmarkEntityType::User => self.maybe_descriptive_text_user_display_name,
UserBookmarkEntityType::ModelWeight => self.maybe_descriptive_text_model_weight_title,
UserBookmarkEntityType::TtsModel => self.maybe_descriptive_text_tts_model_title,
UserBookmarkEntityType::TtsResult => self.maybe_descriptive_text_tts_result_inference_text,
UserBookmarkEntityType::W2lTemplate => None,
@@ -1,5 +1,6 @@
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::tts_models::TtsModelToken;
use tokens::tokens::tts_results::TtsResultToken;
use tokens::tokens::users::UserToken;
@@ -10,6 +11,7 @@ use tokens::tokens::zs_voices::ZsVoiceToken;
pub enum UserBookmarkEntityToken {
User(UserToken),
ModelWeight(ModelWeightToken),
TtsModel(TtsModelToken),
TtsResult(TtsResultToken),
W2lTemplate(W2lTemplateToken),
@@ -23,6 +25,7 @@ impl UserBookmarkEntityToken {
pub fn from_entity_type_and_token(entity_type: UserBookmarkEntityType, token: &str) -> Self {
match entity_type {
UserBookmarkEntityType::User => Self::User(UserToken::new_from_str(token)),
UserBookmarkEntityType::ModelWeight => Self::ModelWeight(ModelWeightToken::new_from_str(token)),
UserBookmarkEntityType::TtsModel => Self::TtsModel(TtsModelToken::new_from_str(token)),
UserBookmarkEntityType::TtsResult => Self::TtsResult(TtsResultToken::new_from_str(token)),
UserBookmarkEntityType::W2lTemplate => Self::W2lTemplate(W2lTemplateToken::new_from_str(token)),
@@ -36,6 +39,7 @@ impl UserBookmarkEntityToken {
pub fn get_composite_keys(&self) -> (UserBookmarkEntityType, &str) {
match self {
UserBookmarkEntityToken::User(user_token) => (UserBookmarkEntityType::User, user_token.as_str()),
UserBookmarkEntityToken::ModelWeight(model_weight_token) => (UserBookmarkEntityType::ModelWeight, model_weight_token.as_str()),
UserBookmarkEntityToken::TtsModel(tts_model_token) => (UserBookmarkEntityType::TtsModel, tts_model_token.as_str()),
UserBookmarkEntityToken::TtsResult(tts_result_token) => (UserBookmarkEntityType::TtsResult, tts_result_token.as_str()),
UserBookmarkEntityToken::W2lTemplate(w2l_template_token) => (UserBookmarkEntityType::W2lTemplate, w2l_template_token.as_str()),
@@ -47,7 +47,6 @@ my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
# External
anyhow = "1.0.75"
chrono = { version = "0.4.22", features = ["serde"] }
clap = "2.34.0" # TODO: This is really out of date, but has conflicts when updated
dotenv = "0.15.0"
hostname = "0.3.1"
lfu = "0.2.5"
+4 -3
View File
@@ -38,9 +38,6 @@ subprocess_common = { path = "../../../lib/subprocess_common" }
tokens = { path = "../../../_types/tokens" }
tts_common = { path = "../../../lib/tts_common" }
# Workspace hack (faster compile times)
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
# External
anyhow = "1.0.75"
chrono = { version = "0.4.22", features = ["serde"] }
@@ -78,3 +75,7 @@ serde_json = "1.0.108"
# The initial round of stabilizations for the async/await language feature in Rust 1.39 did not
# include support for async fn in traits.
async-trait = "0.1.58"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
time = "0.3.30"
mime = "0.3.17"
rand = { version = "0.8.5", features = [] }
@@ -95,6 +95,15 @@ VITS_INFERENCE_SCRIPT="infer_ts_job.py"
VITS_INFERENCE_MAYBE_PYTHON_INTERPRETER="python3"
VITS_INFERENCE_MAYBE_DOCKER_IMAGE_SHA="03017896367b"
# ================================= #
# #
# Rerender #
# #
# ================================= #
RERENDER_INFERENCE_ROOT_DIRECTORY="/home/salt/clone/storyteller-ml/animation/rerender-lora/"
RERENDER_INFERENCE_EXECUTABLE_OR_COMMAND="venv/bin/python rerender.py"
# ================================= #
# #
# CACHE SETTINGS #
@@ -16,6 +16,7 @@ use crate::job::job_loop::process_single_job_success_case::ProcessSingleJobSucce
use crate::job::job_types::lipsync::process_single_lipsync_job::process_single_lipsync_job;
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_rr_job;
use crate::job_dependencies::JobDependencies;
pub async fn process_single_job(
@@ -182,6 +183,9 @@ async fn do_process_single_job(
InferenceCategory::VoiceConversion => {
process_single_vc_job(job_dependencies, job).await?
}
InferenceCategory::RerenderAVideo => {
process_single_rr_job(job_dependencies, job).await?
}
};
let maybe_entity_type = job_success_result.maybe_result_entity
@@ -1,3 +1,4 @@
pub mod lipsync;
pub mod tts;
pub mod vc;
pub mod vc;
pub(crate) mod videofilter;
@@ -239,6 +239,7 @@ pub async fn process_job(args: RvcV2ProcessJobArgs<'_>) -> Result<JobSuccessResu
PolymorphicInferenceArgs::Tts { .. } => None,
PolymorphicInferenceArgs::La(_) => None,
PolymorphicInferenceArgs::Vc { override_f0_method, .. } => *override_f0_method,
PolymorphicInferenceArgs::Rr(_) => None,
})
.flatten();
@@ -247,6 +248,7 @@ pub async fn process_job(args: RvcV2ProcessJobArgs<'_>) -> Result<JobSuccessResu
PolymorphicInferenceArgs::Tts { .. } => None,
PolymorphicInferenceArgs::La(_) => None,
PolymorphicInferenceArgs::Vc { transpose, .. } => *transpose,
PolymorphicInferenceArgs::Rr(_) => None,
})
.flatten();
@@ -168,6 +168,7 @@ pub async fn process_job(args: SoVitsSvcProcessJobArgs<'_>) -> Result<JobSuccess
PolymorphicInferenceArgs::Tts { .. } => None,
PolymorphicInferenceArgs::La(_) => None,
PolymorphicInferenceArgs::Vc { auto_predict_f0, .. } => *auto_predict_f0,
PolymorphicInferenceArgs::Rr(_) => None,
})
.flatten()
.unwrap_or(false);
@@ -177,6 +178,7 @@ pub async fn process_job(args: SoVitsSvcProcessJobArgs<'_>) -> Result<JobSuccess
PolymorphicInferenceArgs::Tts { .. } => None,
PolymorphicInferenceArgs::La(_) => None,
PolymorphicInferenceArgs::Vc { transpose, .. } => *transpose,
PolymorphicInferenceArgs::Rr(_) => None,
})
.flatten();
@@ -0,0 +1,2 @@
pub(crate) mod rerender_a_video;
pub(crate) mod process_single_vf_job;
@@ -0,0 +1,71 @@
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::videofilter::rerender_a_video;
use crate::job::job_types::videofilter::rerender_a_video::process_job::RerenderProcessJobArgs;
use crate::job_dependencies::JobDependencies;
pub async fn process_single_rr_job(job_dependencies: &JobDependencies, job: &AvailableInferenceJob) -> Result<JobSuccessResult, ProcessSingleJobError> {
// let maybe_inference_args = job.maybe_inference_args.as_ref().ok_or(ProcessSingleJobError::Other(anyhow!("Inference args not found")))?;
//
// let maybe_rerender_args = maybe_inference_args.args.as_ref().map(|args| match args {
// Rr(args) => Some(args),
// _ => None,
// }).flatten();
//
// let rerender_args = match maybe_rerender_args {
// None => return Err(ProcessSingleJobError::Other(anyhow!("Rerender args not found"))),
// Some(args) => args,
// };
//
// let videofilter_source = rerender_args.maybe_video_source.as_ref().ok_or(ProcessSingleJobError::Other(anyhow!("Video source not found")))?;
//
// let media_file_token = match videofilter_source {
// VideofilterVideoSource::F(token) => token,
// _ => return Err(ProcessSingleJobError::Other(anyhow!("Video source not found"))),
// };
//
// let media_file_token = MediaFileToken::new_from_str(media_file_token);
//
// info!("media_file_token: {:?}", media_file_token);
//
//
// let media_file = match media_file_result {
// Ok(Some(result)) => result,
// Ok(None) => {
// return Err(ProcessSingleJobError::Other(anyhow!("Media file not found")));
// }
// Err(e) => {
// return Err(ProcessSingleJobError::Other(anyhow!("Media file not found")));
// }
// };
// info!("Source media upload file size (bytes): {}", &media_upload.original_file_size_bytes);
// info!("Source media upload duration (millis): {}", &media_upload.original_duration_millis);
// info!("Source media upload duration (seconds): {}", (media_upload.original_duration_millis as f32 / 1000.0));
// let job_success_result = match vc_model.model_type {
// VoiceConversionModelType::RvcV2 => {
// rvc_v2::process_job::process_job(RvcV2ProcessJobArgs {
// job_dependencies,
// job,
// vc_model: &vc_model,
// media_upload_token: &media_upload_token,
// media_upload: &media_upload,
// }).await?
// }
// };
let job_success_result = rerender_a_video::process_job::process_job(
RerenderProcessJobArgs {
job_dependencies,
job,
// media_file
}
).await?;
Ok(job_success_result)
}
@@ -0,0 +1,5 @@
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
pub fn categorize_error(stderr_contents: &str) -> Option<ProcessSingleJobError> {
None
}
@@ -0,0 +1,108 @@
use std::path::PathBuf;
use anyhow::anyhow;
use log::{error, warn};
use sqlx::MySqlPool;
use tempdir::TempDir;
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
use buckets::public::media_uploads::bucket_file_path::MediaUploadOriginalFilePath;
use cloud_storage::bucket_client::BucketClient;
use jobs_common::job_progress_reporter::job_progress_reporter::JobProgressReporter;
use mysql_queries::payloads::generic_inference_args::videofilter_payload::VideofilterVideoSource;
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
use mysql_queries::queries::media_files::get_media_file::get_media_file;
use mysql_queries::queries::media_uploads::get_media_upload_for_inference::get_media_upload_for_inference;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::media_uploads::MediaUploadToken;
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::util::maybe_download_file_from_bucket::{maybe_download_file_from_bucket, MaybeDownloadArgs};
use crate::util::scoped_temp_dir_creator::ScopedTempDirCreator;
pub struct VideoFile {
pub filesystem_path: PathBuf,
}
pub async fn download_video_file(
video_source: &VideofilterVideoSource,
public_bucket_client: &BucketClient,
job_progress_reporter: &mut Box<dyn JobProgressReporter>,
job: &AvailableInferenceJob,
temp_dir_creator: &ScopedTempDirCreator,
work_temp_dir: &TempDir,
mysql_pool: &MySqlPool,
) -> Result<VideoFile, ProcessSingleJobError> {
let bucket_object_path;
match video_source {
VideofilterVideoSource::F(media_file_token) => {
let media_file_token = MediaFileToken::new_from_str(media_file_token);
let media_file_result = get_media_file(
&media_file_token,
false,
mysql_pool
).await;
match media_file_result {
Ok(Some(result)) => {
let media_file_bucket_path =
MediaFileBucketPath::from_object_hash(
&result.public_bucket_directory_hash,
result.maybe_public_bucket_prefix.as_deref(),
result.maybe_public_bucket_extension.as_deref(),
);
bucket_object_path = media_file_bucket_path.to_full_object_pathbuf();
}
Ok(None) => {
return Err(ProcessSingleJobError::from_anyhow_error(
anyhow!("could not find media file: {:?}", media_file_token)))
}
Err(e) => {
error!("could not query media file: {:?}", e);
return Err(ProcessSingleJobError::from_anyhow_error(e))
}
}
}
VideofilterVideoSource::U(media_upload_token) => {
let media_upload_token = MediaUploadToken::new_from_str(media_upload_token);
let media_upload_result = get_media_upload_for_inference(
&media_upload_token,
mysql_pool
).await;
let media_upload_result = match media_upload_result {
Ok(Some(result)) => result,
Ok(None) => {
warn!("could not find media upload: {:?}", media_upload_token);
return Err(ProcessSingleJobError::from_anyhow_error(
anyhow!("could not find media upload: {:?}", media_upload_token)))
}
Err(e) => {
error!("could not query media upload: {:?}", e);
return Err(ProcessSingleJobError::from_anyhow_error(e))
}
};
let media_upload_bucket_path = MediaUploadOriginalFilePath::from_object_hash(&media_upload_result.public_bucket_directory_hash);
bucket_object_path = media_upload_bucket_path.to_full_object_pathbuf();
}
}
let downloaded_filesystem_path = work_temp_dir.path().join("video.mp4");
maybe_download_file_from_bucket(MaybeDownloadArgs {
name_or_description_of_file: "video file",
final_filesystem_file_path: &downloaded_filesystem_path,
bucket_object_path: &bucket_object_path,
bucket_client: public_bucket_client,
job_progress_reporter,
job_progress_update_description: "downloading",
job_id: job.id.0,
scoped_tempdir_creator: &temp_dir_creator,
maybe_existing_file_minimum_size_required: None,
}).await?;
Ok(VideoFile {
filesystem_path: downloaded_filesystem_path,
})
}
@@ -0,0 +1,6 @@
pub(crate) mod process_job;
mod validate_job;
mod download_video_file;
mod rerender_inference_command;
pub(crate) mod rerender_dependencies;
mod categorize_error;
@@ -0,0 +1,409 @@
use std::fs::read_to_string;
use std::time::Instant;
use anyhow::{anyhow, Error};
use log::{error, info, warn};
use serde::{Deserialize, Serialize};
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::by_table::generic_inference_jobs::inference_result_type::InferenceResultType;
use filesys::check_file_exists::check_file_exists;
use filesys::file_size::file_size;
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 mimetypes::mimetype_for_file::get_mimetype_for_file;
use mysql_queries::payloads::generic_inference_args::generic_inference_args::PolymorphicInferenceArgs::Rr;
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
use mysql_queries::queries::media_files::insert_media_file_from_rerender::{insert_media_file_from_rerender, InsertArgs};
use crate::job::job_loop::job_success_result::{JobSuccessResult, ResultEntity};
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::job::job_types::videofilter::rerender_a_video::download_video_file::download_video_file;
use crate::job::job_types::videofilter::rerender_a_video::rerender_inference_command::InferenceArgs;
use crate::job::job_types::videofilter::rerender_a_video::validate_job::validate_job;
use crate::job_dependencies::JobDependencies;
/// The maximum that either width or height can be
const MAX_DIMENSION : u32 = 1500;
const BUCKET_FILE_PREFIX: &str = "fakeyou_";
const BUCKET_FILE_EXTENSION: &str = ".mp4";
pub struct RerenderProcessJobArgs<'a> {
pub job_dependencies: &'a JobDependencies,
pub job: &'a AvailableInferenceJob,
// pub media_file: MediaFile,
}
pub async fn process_job(args: RerenderProcessJobArgs<'_>) -> Result<JobSuccessResult, ProcessSingleJobError> {
let job = args.job;
let deps = args.job_dependencies;
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)))?;
let model_dependencies = args
.job_dependencies
.job
.job_specific_dependencies
.maybe_rerender_dependencies
.as_ref()
.ok_or_else(|| ProcessSingleJobError::JobSystemMisconfiguration(Some("missing Rerender dependencies".to_string())))?;
// ==================== UNPACK + VALIDATE INFERENCE ARGS ==================== //
let job_args = validate_job(job)?;
// ==================== TEMP DIR ==================== //
let work_temp_dir = format!("temp_rerender_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))?;
// ==================== QUERY AND DOWNLOAD FILES ==================== //
let video_path = download_video_file(
&job_args.video_source,
&args.job_dependencies.buckets.public_bucket_client,
&mut job_progress_reporter,
job,
&args.job_dependencies.fs.scoped_temp_dir_creator_for_work,
&work_temp_dir,
&deps.db.mysql_pool
).await?;
info!("Downloaded video file: {:?}", video_path.filesystem_path);
// ==================== TRANSCODE MEDIA (IF NECESSARY) ==================== //
let mut usable_video_path = video_path.filesystem_path.clone();
//TODO: re encode with ffmpeg
info!("Used video file: {:?}", usable_video_path);
// ==================== SETUP FOR INFERENCE ==================== //
info!("Ready for Rerender A Video inference...");
job_progress_reporter.log_status("running inference")
.map_err(|e| ProcessSingleJobError::Other(e))?;
let output_video_fs_path = work_temp_dir.path().join("blend.mp4");
info!("Running Rerender A Video inference...");
info!("Expected output video filename: {:?}", &output_video_fs_path);
// TODO: Limit output length for non-premium (???)
let maybe_args = job.maybe_inference_args
.as_ref()
.map(|args| args.args.as_ref())
.flatten();
let poly_args = match maybe_args {
None => return Err(ProcessSingleJobError::Other(anyhow!("Rerender args not found"))),
Some(args) => args,
};
let rr_args = match poly_args {
Rr(args) => args,
_ => return Err(ProcessSingleJobError::Other(anyhow!("Rerender args not found"))),
};
let sd_model = rr_args.maybe_sd_model.as_ref().ok_or(ProcessSingleJobError::Other(anyhow!("SD model not found")))?;
// ============== CREATE AND WRITE CONFIG TO DISK ============== //
// create seed if not exists
// let seed = rr_args.seed.unwrap_or_else(|| rand::random::<i64>());
let seed = rand::random::<i32>();
let config = RerenderConfig {
input: Some(usable_video_path.to_str().unwrap().to_string()),
output: Some(output_video_fs_path.to_str().unwrap().to_string()),
work_dir: Some(work_temp_dir.path().to_str().unwrap().to_string()),
sd_model: Some(sd_model.to_string()),
prompt: Some("1girl, anime style, red hair".to_string()),
a_prompt: Some("1girl, 3d, realistic, read hair".to_string()),
n_prompt: Some("Bad quality, low quality, photo, artifacts, watermark, signature, faceless".to_string()),
seed: Some(seed as i64),
..RerenderConfig::new()
};
config.validate().map_err(|e| ProcessSingleJobError::Other(anyhow!(e)))?;
let config_path = work_temp_dir.path().join("config.json");
let config_json = serde_json::to_string(&config).unwrap();
std::fs::write(&config_path, config_json).unwrap();
// ==================== RUN INFERENCE SCRIPT ==================== //
let stderr_output_file = work_temp_dir.path().join("stderr.txt");
let inference_start_time = Instant::now();
let command_exit_status = model_dependencies
.inference_command
.execute_inference(InferenceArgs {
config_file: &config_path,
stderr_output_file: &stderr_output_file,
});
let inference_duration = Instant::now().duration_since(inference_start_time);
info!("Inference took duration to complete: {:?}", &inference_duration);
if !command_exit_status.is_success() {
error!("Inference 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);
// match categorize_error(&contents) {
// Some(ProcessSingleJobError::FaceDetectionFailure) => {
// warn!("Face not detected in source image");
// error = ProcessSingleJobError::FaceDetectionFailure;
// }
// _ => {}
// }
}
safe_delete_temp_file(&video_path.filesystem_path);
safe_delete_temp_file(&config_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);
}
// ==================== CHECK NON-WATERMARKED RESULT ==================== //
info!("Checking that output file exists: {:?} ...", output_video_fs_path);
check_file_exists(&output_video_fs_path).map_err(|e| ProcessSingleJobError::Other(e))?;
// ==================== OPTIONAL WATERMARK ==================== //
let finished_file = output_video_fs_path.clone();
// ==================== CHECK ALL FILES EXIST AND GET METADATA ==================== //
info!("Checking that output watermark file exists: {:?} ...", finished_file);
check_file_exists(&finished_file).map_err(|e| ProcessSingleJobError::Other(e))?;
info!("Interrogating result file size ...");
let file_size_bytes = file_size(&finished_file)
.map_err(|err| ProcessSingleJobError::Other(err))?;
info!("Interrogating result mimetype ...");
let mimetype = get_mimetype_for_file(&finished_file)
.map_err(|err| ProcessSingleJobError::from_io_error(err))?
.map(|mime| mime.to_string())
.ok_or(ProcessSingleJobError::Other(anyhow!("Mimetype could not be determined")))?;
info!("Calculating sha256...");
let file_checksum = sha256_hash_file(&finished_file)
.map_err(|err| {
ProcessSingleJobError::Other(anyhow!("Error hashing file: {:?}", err))
})?;
// ==================== UPLOAD VIDEO TO BUCKET ==================== //
job_progress_reporter.log_status("uploading result")
.map_err(|e| ProcessSingleJobError::Other(e))?;
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();
info!("Audio destination bucket path: {:?}", &result_bucket_object_pathbuf);
info!("Uploading media ...");
args.job_dependencies.buckets.public_bucket_client.upload_filename_with_content_type(
&result_bucket_object_pathbuf,
&finished_file,
&mimetype) // TODO: We should check the mimetype to make sure bad payloads can't get uploaded
.await
.map_err(|e| ProcessSingleJobError::Other(e))?;
// ==================== DELETE TEMP FILES ==================== //
safe_delete_temp_file(&video_path.filesystem_path);
safe_delete_temp_file(&config_path);
safe_delete_temp_file(&output_video_fs_path);
safe_delete_temp_file(&stderr_output_file);
safe_delete_temp_directory(&work_temp_dir);
// NB: We should be using a tempdir, but to make absolutely certain we don't overflow the disk...
safe_delete_temp_directory(&work_temp_dir);
// ==================== SAVE RECORDS ==================== //
info!("Saving Rerender result (media_files table record) ...");
let (media_file_token, id) = insert_media_file_from_rerender(InsertArgs {
pool: &args.job_dependencies.db.mysql_pool,
job: &job,
maybe_mime_type: Some(&mimetype),
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!("Rerender Done.");
job_progress_reporter.log_status("done")
.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,
})
}
#[derive(Serialize, Deserialize, Debug)]
pub struct RerenderConfig {
#[serde(rename = "input")]
pub input: Option<String>,
#[serde(rename = "output")]
pub output: Option<String>,
#[serde(rename = "work_dir")]
pub work_dir: Option<String>,
#[serde(rename = "key_subdir")]
pub key_subdir: Option<String>,
#[serde(rename = "sd_model")]
pub sd_model: Option<String>,
#[serde(rename = "lora_path")]
pub lora_path: Option<String>,
#[serde(rename = "interval")]
pub interval: Option<i32>,
#[serde(rename = "prompt")]
pub prompt: Option<String>,
#[serde(rename = "a_prompt")]
pub a_prompt: Option<String>,
#[serde(rename = "n_prompt")]
pub n_prompt: Option<String>,
#[serde(rename = "x0_strength")]
pub x0_strength: Option<f32>,
#[serde(rename = "control_type")]
pub control_type: Option<String>,
#[serde(rename = "canny_low")]
pub canny_low: Option<i32>,
#[serde(rename = "canny_high")]
pub canny_high: Option<i32>,
#[serde(rename = "control_strength")]
pub control_strength: Option<f32>,
#[serde(rename = "seed")]
pub seed: Option<i64>,
#[serde(rename = "warp_period")]
pub warp_period: Option<Vec<f32>>,
#[serde(rename = "ada_period")]
pub ada_period: Option<Vec<f32>>,
}
impl RerenderConfig {
pub fn new() -> Self {
Self {
input: None,
output: None,
work_dir: None,
key_subdir: Some("keys".to_string()),
sd_model: None,
lora_path: None,
interval: Some(8),
prompt: None,
a_prompt: None,
n_prompt: None,
x0_strength: Some(0.95),
control_type: Some("canny".to_string()),
canny_low: Some(50),
canny_high: Some(100),
control_strength: Some(0.7),
seed: None,
warp_period: Some(vec![0., 0.1]),
ada_period: Some(vec![0.8, 1.]),
}
}
pub fn validate(&self) -> Result<(), Error> {
let fields = &[
(self.input.is_some(), "input"),
(self.output.is_some(), "output"),
(self.work_dir.is_some(), "work_dir"),
(self.key_subdir.is_some(), "key_subdir"),
(self.sd_model.is_some(), "sd_model"),
(self.interval.is_some(), "interval"),
(self.prompt.is_some(), "prompt"),
(self.a_prompt.is_some(), "a_prompt"),
(self.n_prompt.is_some(), "n_prompt"),
(self.x0_strength.is_some(), "x0_strength"),
(self.control_type.is_some(), "control_type"),
(self.canny_low.is_some(), "canny_low"),
(self.canny_high.is_some(), "canny_high"),
(self.control_strength.is_some(), "control_strength"),
(self.seed.is_some(), "seed"),
(self.warp_period.is_some(), "warp_period"),
(self.ada_period.is_some(), "ada_period"),
];
let missing_fields: Vec<&str> = fields
.iter()
.filter_map(|(is_some, field_name)| if *is_some { None } else { Some(*field_name) })
.collect();
if missing_fields.is_empty() {
Ok(())
} else {
Err(anyhow!("Missing fields: {}", missing_fields.join(", ")))
}
}
}
@@ -0,0 +1,15 @@
use errors::AnyhowResult;
use crate::job::job_types::videofilter::rerender_a_video::rerender_inference_command::RerenderInferenceCommand;
pub struct RerenderDependencies {
pub inference_command: RerenderInferenceCommand,
}
impl RerenderDependencies {
pub fn setup() -> AnyhowResult<Self> {
Ok(Self {
inference_command: RerenderInferenceCommand::from_env()?,
})
}
}
@@ -0,0 +1,229 @@
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 RerenderInferenceCommand {
/// Where the code lives
rerender_root_code_directory: PathBuf,
/// A single executable script or a much larger bash command.
executable_or_command: ExecutableOrCommand,
/// Config file to use
config_path: Option<PathBuf>,
/// eg. `source python/bin/activate`
maybe_virtual_env_activation_command: Option<String>,
/// 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>,
}
#[derive(Clone)]
pub enum ExecutableOrCommand {
/// Eg. `inference.py`
Executable(PathBuf),
/// Eg. `python3 inference.py`
Command(String),
}
pub struct InferenceArgs<'s, P: AsRef<Path>> {
pub config_file: P,
pub stderr_output_file: &'s Path,
}
impl RerenderInferenceCommand {
pub fn from_env() -> AnyhowResult<Self> {
let rerender_root_code_directory = easyenv::get_env_pathbuf_required(
"RERENDER_INFERENCE_ROOT_DIRECTORY")?;
let config_path = easyenv::get_env_pathbuf_optional(
"RERENDER_INFERENCE_CONFIG_PATH");
let executable_or_command = match easyenv::get_env_string_optional(
"RERENDER_INFERENCE_EXECUTABLE_OR_COMMAND") {
None => {
return Err(anyhow!("RERENDER_INFERENCE_EXECUTABLE_OR_COMMAND is required"));
}
Some(executable_or_command) => {
if executable_or_command.contains(" ") {
ExecutableOrCommand::Command(executable_or_command)
} else {
ExecutableOrCommand::Executable(PathBuf::from(executable_or_command))
}
}
};
let maybe_virtual_env_activation_command = easyenv::get_env_string_optional(
"RERENDER_INFERENCE_MAYBE_VENV_COMMAND");
let maybe_execution_timeout =
easyenv::get_env_duration_seconds_optional("RERENDER_TIMEOUT_SECONDS");
let maybe_docker_options = easyenv::get_env_string_optional(
"RERENDER_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),
}
});
Ok(Self {
rerender_root_code_directory,
executable_or_command,
config_path,
maybe_virtual_env_activation_command,
maybe_docker_options,
maybe_execution_timeout,
})
}
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.rerender_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(" --cfg ");
command.push_str(&path_to_string(args.config_file.as_ref()));
// ===== 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_os_str());
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,85 @@
use anyhow::anyhow;
use mysql_queries::payloads::generic_inference_args::generic_inference_args::{InferenceCategoryAbbreviated, PolymorphicInferenceArgs};
use mysql_queries::payloads::generic_inference_args::videofilter_payload::{VideofilterVideoSource};
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
pub struct JobArgs<'a> {
pub video_source: &'a VideofilterVideoSource,
pub sd_model: &'a str,
}
pub fn validate_job(job: &AvailableInferenceJob) -> Result<JobArgs, ProcessSingleJobError> {
let inference_args = job.maybe_inference_args
.as_ref()
.map(|args| args.args.as_ref())
.flatten();
let inference_category = job.maybe_inference_args
.as_ref()
.map(|args| args.inference_category)
.flatten();
match inference_category {
Some(InferenceCategoryAbbreviated::RerenderAVideo) => {}, // Valid
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 = match inference_args {
Some(args) => args,
None => {
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("no inference args for job!")));
}
};
let inference_args = match inference_args {
PolymorphicInferenceArgs::Rr(inference_args) => inference_args,
_ => {
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("wrong inner args for job!")));
}
};
let video_source = match &inference_args.maybe_video_source {
Some(args) => args,
None => {
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("no video source!")));
}
};
let sd_model = match &inference_args.maybe_sd_model {
Some(args) => args,
None => {
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("no sd model!")));
}
};
// let preprocess = match inference_args.maybe_preprocess {
// None => Some("full".to_string()),
// Some(Preprocess::F) => Some("full".to_string()),
// Some(Preprocess::EF) => Some("extfull".to_string()),
// Some(Preprocess::C) => Some("crop".to_string()),
// Some(Preprocess::EC) => Some("extcrop".to_string()),
// };
//
// let enhancer = match inference_args.maybe_face_enhancer {
// None => None,
// Some(FaceEnhancer::G) => Some("gfpgan".to_string()),
// Some(FaceEnhancer::R) => Some("RestoreFormer".to_string()),
// };
//
// let mut width = inference_args.maybe_resize_width;
// let mut height = inference_args.maybe_resize_height;
Ok(JobArgs {
video_source,
sd_model,
})
}
@@ -7,6 +7,7 @@ use crate::job::job_types::tts::vall_e_x::vall_e_x_dependencies::VallExDependenc
use crate::job::job_types::tts::vits::vits_dependencies::VitsDependencies;
use crate::job::job_types::vc::rvc_v2::rvc_v2_dependencies::RvcV2Dependencies;
use crate::job::job_types::vc::so_vits_svc::svc_dependencies::SvcDependencies;
use crate::job::job_types::videofilter::rerender_a_video::rerender_dependencies::RerenderDependencies;
use crate::util::scoped_execution::ScopedExecution;
pub struct JobSpecificDependencies {
@@ -16,6 +17,7 @@ pub struct JobSpecificDependencies {
pub maybe_tacotron2_dependencies: Option<Tacotron2Dependencies>,
pub maybe_vall_e_x_dependencies: Option<VallExDependencies>,
pub maybe_vits_dependencies: Option<VitsDependencies>,
pub maybe_rerender_dependencies: Option<RerenderDependencies>,
}
impl JobSpecificDependencies {
@@ -27,6 +29,7 @@ impl JobSpecificDependencies {
let mut maybe_tacotron2_dependencies = None;
let mut maybe_vall_e_x_dependencies = None;
let mut maybe_vits_dependencies = None;
let mut maybe_rerender_dependencies = None;
if scoped_execution.can_run_job(InferenceModelType::RvcV2) {
print_with_space("Setting RVCv2 dependencies...");
@@ -58,6 +61,11 @@ impl JobSpecificDependencies {
maybe_vits_dependencies = Some(VitsDependencies::setup()?);
}
if scoped_execution.can_run_job(InferenceModelType::RerenderAVideo) {
print_with_space("Setting Rerender dependencies...");
maybe_rerender_dependencies = Some(RerenderDependencies::setup()?);
}
Ok(JobSpecificDependencies {
maybe_rvc_v2_dependencies,
maybe_sad_talker_dependencies,
@@ -65,6 +73,7 @@ impl JobSpecificDependencies {
maybe_tacotron2_dependencies,
maybe_vall_e_x_dependencies,
maybe_vits_dependencies,
maybe_rerender_dependencies,
})
}
}
Loaded 100 of 153 files, more files were not shown because too many files have changed in this diff. Show more